mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-07-09 17:28:42 +00:00
* feat(timesfm): TimesFM 1.0 200M decoder-only inference port to candle
Native Rust/candle port of google-research/timesfm (pytorch_patched_decoder.py)
for temporal embeddings + zero-shot forecasting inside RuVector. Behind an opt-in
`candle` feature (default = [], cpu-fallback pattern like ruvector-hailo); no
lockfile churn (candle 0.9.2 already pinned by ruvllm).
- config.rs: TimesfmConfig (1280 dim, 20 layers, 16 heads, 80 head_dim, patch 32/128)
- model.rs: ResidualBlock patch embedding, sinusoidal pos-emb (no RoPE), 20x decoder
(fused qkv, learnable per-head-dim softplus scaling, causal+padding mask), RevIN
instance norm, forward [B,N,128,10] + autoregressive decode to arbitrary horizon
- scripts/convert_weights.py: HF safetensors → VarBuilder key remap (--dry-run)
- 12 tests (shape + RevIN numerical regression); clippy -D warnings clean
Adversarial review caught + fixed a real RevIN bug (masked_mean_std did a global
mean/std instead of the reference's first-qualifying-patch selection) + added
regression tests. Honest scope: dimensionally + structurally faithful, but real
numerical weight-parity vs the published safetensors is NOT yet verified (tests
run on dummy weights). Open low-impact faithfulness deviations documented in code.
Co-Authored-By: claude-flow <ruv@ruv.net>
* style(timesfm): rustfmt the crate (format the RevIN-fix edits) — green the Rustfmt gate for this crate
Our crate is now fmt-clean + clippy-clean; the remaining workspace-wide fmt
diffs are pre-existing in other crates, out of scope for this PR.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(timesfm): weight-parity validated against official PyTorch reference
Drives the candle TimesFM 1.0 200M port from "compiles on dummy weights" to
a real numerical PASS against google/timesfm-1.0-200m.
Measured (f32 CPU, deterministic 512-pt series, horizon 128):
max-abs-diff = 8.58e-6 MAE = 3.25e-6 rel-error = 5.83e-7
(target was <1e-2; we hit the f32 accumulation floor ~1e-5.)
Bridge: the real torch_model.ckpt state_dict (253 keys) maps 1:1 through
scripts/convert_weights.py with zero unmapped/missing keys.
Bug found + fixed (src/model.rs build_mask): the attention mask used
f32::NEG_INFINITY for masked positions. With real 0/1 paddings the padding
term `padding * -inf` computes `0 * -inf = NaN`, poisoning the whole mask
so softmax emitted NaN for every row (every forecast value was NaN). The
old `nan_to_zero` guard silently failed (where_cond dtype mismatch -> fallback
`NaN * 1 = NaN`). Replaced with the reference's large *finite* negative
(-0.7 * f32::MAX) and element-wise `minimum` merge, exactly matching
convert_paddings_to_mask + causal_mask + merge_masks. No NaN, exact parity.
Added:
- examples/parity.rs end-to-end parity runner with metrics + verdict
- tests/parity.rs gated integration test (skips cleanly w/o the
814MB artifacts; never fabricates a pass)
- scripts/gen_reference.py reference forecast generator (official decoder)
Co-Authored-By: claude-flow <ruv@ruv.net>
* bench(timesfm): forward-only latency bench — 45ms/forecast (200M, ctx512/h128, warm CPU); parity validated 8.58e-6
* feat(timesfm): predictive-pruning module for Darwin (ADR-191 §2)
Add crates/timesfm/src/prune.rs: forecast an optimization curve's plateau
from its first K points with TimesFM and decide PRUNE vs CONTINUE against a
viability threshold (lower=better, like exploitability). Decoupled — operates
on a generic Vec<f32>, no cross-repo poker-darwin dep.
- decide_prune(): forecast tail to target horizon, plateau = mean of last
horizon/4 steps; PRUNE iff plateau > threshold. Guards: non-finite forecast
=> CONTINUE conf 0 (never kill on a broken forecast); already-viable
(best_so_far <= threshold) => CONTINUE. Scale-invariant confidence.
- examples/predictive_prune.rs + tests/prune.rs: two synthetic curves with
REAL weights — doomed (floor 0.20) => PRUNE (forecast plateau 1.98, conf
0.72); healthy (already below 0.05) => CONTINUE. Both decisions correct.
Skips cleanly when weights absent (no fabricated pass).
- Honest calibration note: TimesFM mean-reverts upward on short synthetic
decays so absolute plateau is biased high; decision rides the robust
relative-ordering + already-viable signals, not absolute calibration.
- Doc-comment shows how poker-darwin calls this on its champion curve.
Tests: 12 shape + parity + prune = 14/14 green (candle); light build green.
Co-Authored-By: claude-flow <ruv@ruv.net>
* test(timesfm): bench24 harness for GCP 24-case deployment test (ADR-191 Phase B)
24 distinct forecast cases (varied period/trend/amp/noise/freq_id; ctx=512,
horizon=128) on real weights. Per-case latency + finiteness assert, aggregate
mean/p50/p95/p99, throughput, peak RSS, machine-readable JSON line. Non-finite
output is a hard FAIL (exit 1), never a silent pass.
Local baseline (ruvultra, 32-thread CPU): 24/24 finite, mean 42.5ms p95 44.2ms,
throughput 23.5 fps, peak RSS 1.55GB.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ci) + feat(timesfm): README, publish=true, research-nightly shard, rustfmt
CI fixes:
- timesfm added to research-nightly shard (-p timesfm)
- timesfm excluded from core-and-rest shard (--exclude timesfm)
- cargo fmt -p timesfm: model.rs + 4 example files formatted
- cargo fmt -p ruvector-graph: typed_graph_bench.rs + 4 src files
(pre-existing rustfmt failure blocking the PR)
crates/timesfm/README.md (new):
- Architecture diagram (ResidualBlock → 20× decoder → RevIN → output)
- Feature flags table (candle/cuda/metal/hub)
- Quick-start: inference + weight loading workflow
- Known limitations section (weight parity, MLP mask, pos-emb shift)
- References (ICML 2024 paper, HuggingFace model card)
crates/timesfm/Cargo.toml:
- publish = true (was false)
- readme = "README.md"
Co-Authored-By: claude-flow <ruv@ruv.net>
* chore: cargo fmt ruvector-proof-gate (pre-existing rustfmt CI blocker)
Co-Authored-By: claude-flow <ruv@ruv.net>
* chore: cargo fmt temporal-coherence + tiny-dancer-core (pre-existing)
Co-Authored-By: claude-flow <ruv@ruv.net>
* chore: cargo fmt tiny-dancer-node + ruvllm openmythos (pre-existing)
Co-Authored-By: claude-flow <ruv@ruv.net>
* chore: cargo fmt rvf-runtime/store.rs (pre-existing)
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ci): timesfm tests run with --features candle in research-nightly
The research-nightly shard was running timesfm without --features candle,
causing a compile error (all model code is behind the feature gate).
Fix: remove timesfm from the shared nextest run; add a dedicated step
that runs only timesfm tests with --features candle.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ruvllm): remove broken private-item doc link (DepthLora)
Code Quality CI was failing: public doc in mod.rs linked to private
recurrent::DepthLora. Replace with plain backtick name.
Pre-existing issue surfaced by rustfmt touching the file.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ruvllm): fix all private-item rustdoc links in openmythos/mod.rs
Three doc comments linked to private items (LtiInjection, RecurrentBlock,
DepthLora) in the recurrent module. rustdoc's -D warnings caught them.
Replaced with plain-text names. Pre-existing, surfaced by rustfmt touching
the file.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ruvllm): fix private attention module doc link
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(timesfm): gate bench/bench24 examples behind candle feature
The bench and bench24 examples import candle_core/candle_nn/timesfm::model
unconditionally, breaking Clippy and stock workspace builds that run without
--features candle. Add [[example]] required-features = ["candle"] so they are
skipped when the feature is off, matching parity/predictive_prune which already
self-gate via #[cfg(feature = "candle")].
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(maxsim): add ruvector-maxsim to workspace + make clippy-clean
The research-nightly CI shard referenced -p ruvector-maxsim (added
|
||
|---|---|---|
| .. | ||
| src | ||
| .npmignore | ||
| build.rs | ||
| Cargo.toml | ||
| package.json | ||
| README.md | ||
Ruvector Tiny Dancer Node
Node.js bindings for Tiny Dancer neural routing via NAPI-RS.
ruvector-tiny-dancer-node provides native Node.js bindings for production-grade AI agent routing. Run FastGRNN neural inference at native speed for intelligent request routing in server-side applications. Part of the Ruvector ecosystem.
Why Tiny Dancer Node?
- Native Performance: Rust speed in Node.js
- Production Ready: Battle-tested in high-throughput systems
- Async/Await: Non-blocking inference operations
- TypeScript: Complete type definitions included
- Multi-Threaded: Leverage all CPU cores
Features
Core Capabilities
- Neural Inference: FastGRNN model execution
- Model Training: Train custom routing models
- Feature Engineering: Request feature extraction
- Persistent Storage: SQLite-backed model storage
- Batch Processing: Efficient batch inference
Advanced Features
- Model Versioning: Manage multiple model versions
- A/B Testing: Route comparison and testing
- Metrics: Performance and accuracy tracking
- Hot Reload: Update models without restart
- Distributed: Coordinate across instances
Installation
npm install @ruvector/tiny-dancer-node
# or
yarn add @ruvector/tiny-dancer-node
# or
pnpm add @ruvector/tiny-dancer-node
Quick Start
Basic Routing
import { TinyDancer, RouteRequest } from '@ruvector/tiny-dancer-node';
// Create router instance
const router = new TinyDancer({
modelPath: './models/router.db',
});
// Initialize
await router.init();
// Route request
const result = await router.route({
query: "What is the weather like today?",
context: {
userId: "user-123",
sessionLength: 5,
},
agents: ["weather", "general", "calendar"],
});
console.log(`Route to: ${result.agent} (confidence: ${result.confidence})`);
Model Training
import { TinyDancer, TrainingData } from '@ruvector/tiny-dancer-node';
const router = new TinyDancer();
await router.init();
// Prepare training data
const trainingData: TrainingData[] = [
{
query: "What's the weather?",
correctAgent: "weather",
context: { category: "weather" },
},
{
query: "Schedule a meeting",
correctAgent: "calendar",
context: { category: "scheduling" },
},
// ... more examples
];
// Train model
const result = await router.train({
data: trainingData,
epochs: 100,
learningRate: 0.001,
validationSplit: 0.2,
});
console.log(`Training accuracy: ${result.accuracy}`);
console.log(`Validation accuracy: ${result.validationAccuracy}`);
// Save model
await router.saveModel('./models/custom-router.bin');
Performance Monitoring
import { TinyDancer } from '@ruvector/tiny-dancer-node';
const router = new TinyDancer({ enableMetrics: true });
await router.init();
// Route with metrics
const result = await router.route(request);
// Get performance metrics
const metrics = router.getMetrics();
console.log(`Average latency: ${metrics.avgLatencyMs}ms`);
console.log(`P99 latency: ${metrics.p99LatencyMs}ms`);
console.log(`Requests/sec: ${metrics.requestsPerSecond}`);
console.log(`Cache hit rate: ${metrics.cacheHitRate}`);
API Reference
TinyDancer Class
class TinyDancer {
constructor(config?: TinyDancerConfig);
// Lifecycle
init(): Promise<void>;
close(): Promise<void>;
// Routing
route(request: RouteRequest): Promise<RouteResult>;
routeBatch(requests: RouteRequest[]): Promise<RouteResult[]>;
// Training
train(options: TrainOptions): Promise<TrainResult>;
loadModel(path: string): Promise<void>;
saveModel(path: string): Promise<void>;
// Scoring
scoreAgents(request: RouteRequest): Promise<AgentScore[]>;
// Metrics
getMetrics(): RouterMetrics;
resetMetrics(): void;
}
Types
interface TinyDancerConfig {
modelPath?: string;
enableMetrics?: boolean;
cacheSize?: number;
numThreads?: number;
}
interface RouteRequest {
query: string;
context?: Record<string, any>;
agents: string[];
constraints?: RouteConstraints;
}
interface RouteResult {
agent: string;
confidence: number;
scores: Record<string, number>;
latencyMs: number;
}
interface TrainOptions {
data: TrainingData[];
epochs: number;
learningRate: number;
validationSplit?: number;
batchSize?: number;
}
interface TrainResult {
accuracy: number;
validationAccuracy: number;
loss: number;
epochs: number;
trainingTimeMs: number;
}
interface RouterMetrics {
totalRequests: number;
avgLatencyMs: number;
p50LatencyMs: number;
p99LatencyMs: number;
requestsPerSecond: number;
cacheHitRate: number;
}
Express Integration
import express from 'express';
import { TinyDancer } from '@ruvector/tiny-dancer-node';
const app = express();
const router = new TinyDancer();
app.use(express.json());
app.post('/route', async (req, res) => {
const result = await router.route({
query: req.body.query,
context: req.body.context,
agents: ['agent-a', 'agent-b', 'agent-c'],
});
res.json(result);
});
app.listen(3000);
Platform Support
| Platform | Architecture | Status |
|---|---|---|
| Linux | x64 | ✅ |
| Linux | arm64 | ✅ |
| macOS | x64 | ✅ |
| macOS | arm64 (M1/M2) | ✅ |
| Windows | x64 | ✅ |
Building from Source
# Clone repository
git clone https://github.com/ruvnet/ruvector.git
cd ruvector/crates/ruvector-tiny-dancer-node
# Install dependencies
npm install
# Build native module
npm run build
# Run tests
npm test
Related Packages
- ruvector-tiny-dancer-core - Core Rust implementation
- ruvector-tiny-dancer-wasm - WebAssembly bindings
- @ruvector/core - Core vector bindings
Documentation
- Main README - Complete project overview
- API Documentation - Full API reference
- GitHub Repository - Source code
License
MIT License - see LICENSE for details.