mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-07-10 01:38:44 +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
|
||
|---|---|---|
| .. | ||
| benches | ||
| examples | ||
| fuzz | ||
| src | ||
| tests | ||
| ARCHITECTURE.md | ||
| Cargo.toml | ||
| README.md | ||
Ruvector Graph
A graph database with Cypher queries, hyperedges, and vector search -- all in one crate.
[dependencies]
ruvector-graph = "0.1.1"
Most graph databases make you choose: you can have relationships or vector search, a query language or raw traversals, pairwise edges or nothing. ruvector-graph gives you all of them together. Write familiar Cypher queries like Neo4j, attach vector embeddings to any node for semantic search, and model complex group relationships with hyperedges that connect three or more nodes at once. It runs on servers, in browsers via WASM, and across clusters with built-in RAFT consensus. Part of the RuVector ecosystem.
| ruvector-graph | Neo4j / Typical Graph DB | Vector DB + Custom Glue | |
|---|---|---|---|
| Query language | Full Cypher parser built-in | Cypher (Neo4j) or proprietary | No graph queries |
| Hyperedges | Native -- one edge connects N nodes | Pairwise only -- workarounds needed | Not applicable |
| Vector search | HNSW on every node, semantic similarity | Separate plugin or not available | Vectors only, no graph structure |
| SIMD acceleration | SimSIMD hardware-optimized ops | JVM-based | Varies |
| Browser / WASM | default-features = false, features = ["wasm"] |
Server only | Server only |
| Distributed | Built-in RAFT consensus + federation | Enterprise tier (paid) | Varies |
| Cost | Free, open source (MIT) | Community or paid license | Varies |
Key Features
| Feature | What It Does | Why It Matters |
|---|---|---|
| Cypher Engine | Parse and execute Cypher queries -- MATCH (a)-[:KNOWS]->(b) |
Use a query language you already know instead of raw traversal code |
| Hypergraph Model | Edges connect any number of nodes, not just pairs | Model meetings, co-authorships, reactions -- any group relationship -- natively |
| Vector Embeddings | Attach embeddings to nodes, run HNSW similarity search | Combine "who is connected to whom" with "what is semantically similar" |
| Property Graph | Rich JSON properties on every node and edge | Store real data on your graph elements, not just IDs |
| Label Indexes | Roaring bitmap indexes for fast label lookups | Filter millions of nodes by label in microseconds |
| SIMD Optimized | Hardware-accelerated distance calculations via SimSIMD | Faster vector operations without changing your code |
| Distributed Mode | RAFT consensus for multi-node deployments | Scale out without bolting on a separate coordination layer |
| Federation | Cross-cluster graph queries | Query across data centers as if they were one graph |
| Compression | ZSTD and LZ4 for storage | Smaller on disk without sacrificing read speed |
| WASM Compatible | Run in browsers with WebAssembly | Same graph engine on server and client |
Installation
[dependencies]
ruvector-graph = "0.1.1"
Feature Flags
[dependencies]
# Full feature set
ruvector-graph = { version = "0.1.1", features = ["full"] }
# Minimal WASM-compatible build
ruvector-graph = { version = "0.1.1", default-features = false, features = ["wasm"] }
# Distributed deployment
ruvector-graph = { version = "0.1.1", features = ["distributed"] }
Available features:
full(default): Complete feature set with all optimizationssimd: SIMD-optimized operationsstorage: Persistent storage with redbasync-runtime: Tokio async supportcompression: ZSTD/LZ4 compressiondistributed: RAFT consensus supportfederation: Cross-cluster federationwasm: WebAssembly-compatible minimal buildmetrics: Prometheus monitoring
Quick Start
Create a Graph
use ruvector_graph::{Graph, Node, Edge, GraphConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new graph
let config = GraphConfig::default();
let graph = Graph::new(config)?;
// Create nodes
let alice = graph.create_node(Node {
labels: vec!["Person".to_string()],
properties: serde_json::json!({
"name": "Alice",
"age": 30
}),
..Default::default()
})?;
let bob = graph.create_node(Node {
labels: vec!["Person".to_string()],
properties: serde_json::json!({
"name": "Bob",
"age": 25
}),
..Default::default()
})?;
// Create relationship
graph.create_edge(Edge {
label: "KNOWS".to_string(),
source: alice.id,
target: bob.id,
properties: serde_json::json!({
"since": 2020
}),
..Default::default()
})?;
Ok(())
}
Cypher Queries
use ruvector_graph::{Graph, CypherExecutor};
// Execute Cypher query
let executor = CypherExecutor::new(&graph);
let results = executor.execute("
MATCH (p:Person)-[:KNOWS]->(friend:Person)
WHERE p.name = 'Alice'
RETURN friend.name AS name, friend.age AS age
")?;
for row in results {
println!("Friend: {} (age {})", row["name"], row["age"]);
}
Vector-Enhanced Graph
use ruvector_graph::{Graph, VectorConfig};
// Enable vector embeddings on nodes
let config = GraphConfig {
vector_config: Some(VectorConfig {
dimensions: 384,
distance_metric: DistanceMetric::Cosine,
..Default::default()
}),
..Default::default()
};
let graph = Graph::new(config)?;
// Create node with embedding
let node = graph.create_node(Node {
labels: vec!["Document".to_string()],
properties: serde_json::json!({"title": "Introduction to Graphs"}),
embedding: Some(vec![0.1, 0.2, 0.3, /* ... 384 dims */]),
..Default::default()
})?;
// Semantic similarity search
let similar = graph.search_similar_nodes(
vec![0.1, 0.2, 0.3, /* query vector */],
10, // top-k
Some(vec!["Document".to_string()]), // filter by labels
)?;
Hyperedges
use ruvector_graph::{Graph, Hyperedge};
// Create a hyperedge connecting multiple nodes
let meeting = graph.create_hyperedge(Hyperedge {
label: "PARTICIPATED_IN".to_string(),
nodes: vec![alice.id, bob.id, charlie.id],
properties: serde_json::json!({
"event": "Team Meeting",
"date": "2024-01-15"
}),
..Default::default()
})?;
API Overview
Core Types
// Node in the graph
pub struct Node {
pub id: NodeId,
pub labels: Vec<String>,
pub properties: serde_json::Value,
pub embedding: Option<Vec<f32>>,
}
// Edge connecting two nodes
pub struct Edge {
pub id: EdgeId,
pub label: String,
pub source: NodeId,
pub target: NodeId,
pub properties: serde_json::Value,
}
// Hyperedge connecting multiple nodes
pub struct Hyperedge {
pub id: HyperedgeId,
pub label: String,
pub nodes: Vec<NodeId>,
pub properties: serde_json::Value,
}
Graph Operations
impl Graph {
// Node operations
pub fn create_node(&self, node: Node) -> Result<Node>;
pub fn get_node(&self, id: &NodeId) -> Result<Option<Node>>;
pub fn update_node(&self, node: Node) -> Result<Node>;
pub fn delete_node(&self, id: &NodeId) -> Result<bool>;
// Edge operations
pub fn create_edge(&self, edge: Edge) -> Result<Edge>;
pub fn get_edge(&self, id: &EdgeId) -> Result<Option<Edge>>;
pub fn delete_edge(&self, id: &EdgeId) -> Result<bool>;
// Traversal
pub fn neighbors(&self, id: &NodeId, direction: Direction) -> Result<Vec<Node>>;
pub fn traverse(&self, start: &NodeId, config: TraversalConfig) -> Result<Vec<Path>>;
// Vector search
pub fn search_similar_nodes(&self, query: Vec<f32>, k: usize, labels: Option<Vec<String>>) -> Result<Vec<Node>>;
}
Performance
Benchmarks (1M Nodes, 10M Edges)
Operation Latency (p50) Throughput
-----------------------------------------------------
Node lookup ~0.1ms 100K ops/s
Edge traversal ~0.5ms 50K ops/s
1-hop neighbors ~1ms 20K ops/s
Cypher simple query ~5ms 5K ops/s
Vector similarity ~2ms 10K ops/s
Related Crates
- ruvector-core - Core vector database engine
- ruvector-graph-node - Node.js bindings
- ruvector-graph-wasm - WebAssembly bindings
- ruvector-raft - RAFT consensus for distributed mode
- ruvector-cluster - Clustering and sharding
Documentation
- RuVector README - Complete project overview
- API Documentation - Full API reference
- GitHub Repository - Source code
License
MIT License - see LICENSE for details.