Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 90 additions & 90 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ members = [
resolver = "2"

[workspace.package]
version = "2.0.5"
version = "2.0.6"
edition = "2021"
rust-version = "1.77"
license = "MIT"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,8 @@ Everything RuVector can do — organized by category. Vector search, graph queri
| **RuvLTRA Models** | Pre-trained GGUF for routing & embeddings | <10ms inference → [HuggingFace](https://huggingface.co/ruv/ruvltra) |
| **Streaming Tokens** | Real-time token generation | Responsive chat UX |
| **Quantization** | Q4, Q5, Q8 model support | Run 7B models in 4GB RAM |
| **π-Quantization (ADR-090)** | 2-bit weights via π-transform + Hadamard rotation + QAT-STE | **10 GB/s** dequantization, 16x memory reduction |
| **MoE Memory-Aware Routing (ADR-092)** | Cache-aware expert selection with EMA affinity tracking | **70%+ cache hit rate**, <10µs routing latency |

```bash
npm install @ruvector/ruvllm # Node.js
Expand Down Expand Up @@ -754,6 +756,7 @@ cargo add ruvector-raft ruvector-cluster ruvector-replication
| Feature | What It Does | Why It Matters |
|---------|--------------|----------------|
| **Tensor Compression** | f32→f16→PQ8→PQ4→Binary | 2-32x memory reduction |
| **INT8 CNN Quantization (ADR-091)** | Quantized Conv2D/Linear/Pooling with SIMD kernels | **4x memory reduction**, 2x faster CNN inference |
| **Differentiable Search** | Soft attention k-NN | End-to-end trainable |
| **Semantic Router** | Route queries to optimal endpoints | Multi-model AI orchestration |
| **Hybrid Routing** | Keyword-first + embedding fallback | **90% accuracy** for agent routing |
Expand Down
3 changes: 1 addition & 2 deletions crates/neural-trader-coherence/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ impl CoherenceGate for ThresholdGate {
let cut_ok = ctx.mincut_value >= floor;
let cusum_ok = ctx.cusum_score < self.config.cusum_threshold;
let drift_ok = ctx.drift_score < self.config.max_drift_score;
let boundary_ok =
ctx.boundary_stable_count >= self.config.boundary_stability_windows;
let boundary_ok = ctx.boundary_stable_count >= self.config.boundary_stability_windows;
// Learning requires tighter drift margin (half the max).
let learn_drift_ok = ctx.drift_score < self.config.max_drift_score * 0.5;

Expand Down
7 changes: 2 additions & 5 deletions crates/neural-trader-replay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,8 @@ pub trait MemoryStore {

/// Attempts to write a segment. Returns `true` if the gate allowed
/// admission, `false` if rejected.
fn maybe_write(
&mut self,
seg: ReplaySegment,
gate: &CoherenceDecision,
) -> anyhow::Result<bool>;
fn maybe_write(&mut self, seg: ReplaySegment, gate: &CoherenceDecision)
-> anyhow::Result<bool>;
}

// ---------------------------------------------------------------------------
Expand Down
44 changes: 30 additions & 14 deletions crates/neural-trader-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ fn bytes16_to_hex(b: &[u8; 16]) -> String {
fn hex_to_bytes16_inner(s: &str) -> Result<[u8; 16], String> {
let s = s.trim();
// Strip optional 0x prefix for JS ergonomics.
let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
let s = s
.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.unwrap_or(s);
if !s.is_ascii() || s.len() != 32 {
return Err(
"hex string must be exactly 32 ASCII hex chars (optional 0x prefix)".to_string(),
);
}
let mut out = [0u8; 16];
for (i, byte) in out.iter_mut().enumerate() {
*byte = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
.map_err(|e| e.to_string())?;
*byte = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).map_err(|e| e.to_string())?;
}
Ok(out)
}
Expand All @@ -66,7 +68,8 @@ fn hex_to_bytes16(s: &str) -> Result<[u8; 16], JsValue> {
/// Serialize using BigInt-aware serializer to avoid u64 precision loss.
fn to_js<T: Serialize>(v: &T) -> Result<JsValue, JsValue> {
let ser = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
v.serialize(&ser).map_err(|e| JsValue::from_str(&e.to_string()))
v.serialize(&ser)
.map_err(|e| JsValue::from_str(&e.to_string()))
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -143,8 +146,16 @@ enum_convert!(SegmentKindWasm <=> neural_trader_replay::SegmentKind {
#[wasm_bindgen]
#[derive(Clone, Copy, Debug)]
pub enum NodeKindWasm {
Symbol = 0, Venue = 1, PriceLevel = 2, Order = 3, Trade = 4,
Event = 5, Participant = 6, TimeBucket = 7, Regime = 8, StrategyState = 9,
Symbol = 0,
Venue = 1,
PriceLevel = 2,
Order = 3,
Trade = 4,
Event = 5,
Participant = 6,
TimeBucket = 7,
Regime = 8,
StrategyState = 9,
}
enum_convert!(NodeKindWasm <=> neural_trader_core::NodeKind {
Symbol, Venue, PriceLevel, Order, Trade, Event, Participant,
Expand All @@ -154,9 +165,18 @@ enum_convert!(NodeKindWasm <=> neural_trader_core::NodeKind {
#[wasm_bindgen]
#[derive(Clone, Copy, Debug)]
pub enum EdgeKindWasm {
AtLevel = 0, NextTick = 1, Generated = 2, Matched = 3, ModifiedFrom = 4,
CanceledBy = 5, BelongsToSymbol = 6, OnVenue = 7, InWindow = 8,
CorrelatedWith = 9, InRegime = 10, AffectsState = 11,
AtLevel = 0,
NextTick = 1,
Generated = 2,
Matched = 3,
ModifiedFrom = 4,
CanceledBy = 5,
BelongsToSymbol = 6,
OnVenue = 7,
InWindow = 8,
CorrelatedWith = 9,
InRegime = 10,
AffectsState = 11,
}
enum_convert!(EdgeKindWasm <=> neural_trader_core::EdgeKind {
AtLevel, NextTick, Generated, Matched, ModifiedFrom, CanceledBy,
Expand Down Expand Up @@ -774,11 +794,7 @@ impl ReservoirStoreWasm {

/// Retrieve segments matching a symbol, returned as JSON array.
#[wasm_bindgen(js_name = "retrieveBySymbol")]
pub fn retrieve_by_symbol(
&self,
symbol_id: u32,
limit: usize,
) -> Result<JsValue, JsValue> {
pub fn retrieve_by_symbol(&self, symbol_id: u32, limit: usize) -> Result<JsValue, JsValue> {
let query = neural_trader_replay::MemoryQuery {
symbol_id,
embedding: vec![],
Expand Down
59 changes: 46 additions & 13 deletions crates/ruvector-cnn-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

#![allow(clippy::new_without_default)]

use wasm_bindgen::prelude::*;
use ruvector_cnn::contrastive::{InfoNCELoss as RustInfoNCE, TripletLoss as RustTriplet, TripletDistance};
use ruvector_cnn::contrastive::{
InfoNCELoss as RustInfoNCE, TripletDistance, TripletLoss as RustTriplet,
};
use ruvector_cnn::simd;
use wasm_bindgen::prelude::*;

/// Initialize panic hook for better error messages
#[wasm_bindgen(start)]
Expand Down Expand Up @@ -94,9 +96,8 @@ impl WasmCnnEmbedder {
let mean: f32 = channel_data.iter().sum::<f32>() / pixels_per_channel as f32;

// Variance
let variance: f32 = channel_data.iter()
.map(|x| (x - mean).powi(2))
.sum::<f32>() / pixels_per_channel as f32;
let variance: f32 = channel_data.iter().map(|x| (x - mean).powi(2)).sum::<f32>()
/ pixels_per_channel as f32;

// Store in embedding
if c * 2 < self.embedding_dim {
Expand Down Expand Up @@ -195,7 +196,12 @@ impl WasmInfoNCELoss {
/// Compute loss for a batch of embedding pairs
/// embeddings: [2N, D] flattened where (i, i+N) are positive pairs
#[wasm_bindgen]
pub fn forward(&self, embeddings: &[f32], batch_size: usize, dim: usize) -> Result<f32, JsValue> {
pub fn forward(
&self,
embeddings: &[f32],
batch_size: usize,
dim: usize,
) -> Result<f32, JsValue> {
if embeddings.len() != 2 * batch_size * dim {
return Err(JsValue::from_str(&format!(
"Expected {} elements, got {}",
Expand Down Expand Up @@ -269,17 +275,29 @@ impl WasmTripletLoss {
negatives: &[f32],
dim: usize,
) -> Result<f32, JsValue> {
if anchors.len() % dim != 0 || positives.len() != anchors.len() || negatives.len() != anchors.len() {
if anchors.len() % dim != 0
|| positives.len() != anchors.len()
|| negatives.len() != anchors.len()
{
return Err(JsValue::from_str("Invalid triplet dimensions"));
}

let batch_size = anchors.len() / dim;
let mut total_loss = 0.0f64;

for i in 0..batch_size {
let a: Vec<f64> = anchors[i * dim..(i + 1) * dim].iter().map(|&x| x as f64).collect();
let p: Vec<f64> = positives[i * dim..(i + 1) * dim].iter().map(|&x| x as f64).collect();
let n: Vec<f64> = negatives[i * dim..(i + 1) * dim].iter().map(|&x| x as f64).collect();
let a: Vec<f64> = anchors[i * dim..(i + 1) * dim]
.iter()
.map(|&x| x as f64)
.collect();
let p: Vec<f64> = positives[i * dim..(i + 1) * dim]
.iter()
.map(|&x| x as f64)
.collect();
let n: Vec<f64> = negatives[i * dim..(i + 1) * dim]
.iter()
.map(|&x| x as f64)
.collect();
total_loss += self.inner.forward(&a, &p, &n);
}

Expand Down Expand Up @@ -351,14 +369,28 @@ impl LayerOps {
) -> Vec<f32> {
let channels = gamma.len();
let mut output = vec![0.0f32; input.len()];
simd::batch_norm_simd(input, &mut output, gamma, beta, mean, var, epsilon, channels);
simd::batch_norm_simd(
input,
&mut output,
gamma,
beta,
mean,
var,
epsilon,
channels,
);
output
}

/// Apply global average pooling
/// Returns one value per channel
#[wasm_bindgen]
pub fn global_avg_pool(input: &[f32], height: usize, width: usize, channels: usize) -> Vec<f32> {
pub fn global_avg_pool(
input: &[f32],
height: usize,
width: usize,
channels: usize,
) -> Vec<f32> {
let mut output = vec![0.0f32; channels];
simd::global_avg_pool_simd(input, &mut output, height, width, channels);
output
Expand All @@ -382,7 +414,8 @@ mod tests {
input_size: 8,
embedding_dim: 64,
normalize: true,
})).unwrap();
}))
.unwrap();

let image_data = vec![128u8; 8 * 8 * 3];
let embedding = embedder.extract(&image_data, 8, 8).unwrap();
Expand Down
Loading
Loading