Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
465
crates/ruvector-mincut-gated-transformer/tests/mod_routing.rs
Normal file
465
crates/ruvector-mincut-gated-transformer/tests/mod_routing.rs
Normal file
@@ -0,0 +1,465 @@
|
||||
//! Token routing and modulation tests.
|
||||
//!
|
||||
//! Tests for token routing based on lambda patterns, capacity constraints,
|
||||
//! boundary token handling, and skip ratio calculations.
|
||||
|
||||
use ruvector_mincut_gated_transformer::{
|
||||
GateDecision, GatePacket, GatePolicy, InferInput, InferOutput, MincutGatedTransformer,
|
||||
QuantizedWeights, TransformerConfig,
|
||||
};
|
||||
|
||||
fn create_transformer(config: TransformerConfig) -> MincutGatedTransformer {
|
||||
let policy = GatePolicy::default();
|
||||
let weights = QuantizedWeights::empty(&config);
|
||||
MincutGatedTransformer::new(config, policy, weights).unwrap()
|
||||
}
|
||||
|
||||
// ============ Lambda-Based Routing ============
|
||||
|
||||
#[test]
|
||||
fn test_routing_with_increasing_lambda() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
let tiers: Vec<u8> = (0..10)
|
||||
.map(|i| {
|
||||
let gate = GatePacket {
|
||||
lambda: 50 + i * 10, // Increasing lambda
|
||||
lambda_prev: 50 + (i.saturating_sub(1)) * 10,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
transformer.reset();
|
||||
output.stats.tier
|
||||
})
|
||||
.collect();
|
||||
|
||||
// With increasing lambda, should stay at tier 0
|
||||
for tier in tiers {
|
||||
assert_eq!(tier, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_with_decreasing_lambda() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
|
||||
let mut tier_changes = 0;
|
||||
let mut prev_tier = 0u8;
|
||||
|
||||
for i in 0..10u32 {
|
||||
let current_lambda = 100 - i * 5;
|
||||
let prev_lambda = if i > 0 { 100 - (i - 1) * 5 } else { 100 };
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: current_lambda,
|
||||
lambda_prev: prev_lambda,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
|
||||
if output.stats.tier != prev_tier {
|
||||
tier_changes += 1;
|
||||
prev_tier = output.stats.tier;
|
||||
}
|
||||
|
||||
transformer.reset();
|
||||
}
|
||||
|
||||
// Should see tier degradation as lambda decreases (may not change every step)
|
||||
// At minimum, should change when lambda drops below thresholds
|
||||
assert!(tier_changes >= 0); // Allow no changes if all within same tier range
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_with_oscillating_lambda() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
|
||||
// Larger oscillations to trigger interventions
|
||||
let lambdas = vec![100, 50, 100, 45, 100, 40, 100, 35];
|
||||
let mut decisions = Vec::new();
|
||||
|
||||
for (i, &lambda) in lambdas.iter().enumerate() {
|
||||
let gate = GatePacket {
|
||||
lambda,
|
||||
lambda_prev: if i > 0 { lambdas[i - 1] } else { 100 },
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
|
||||
decisions.push(output.witness.decision);
|
||||
transformer.reset();
|
||||
}
|
||||
|
||||
// Large oscillations should trigger interventions
|
||||
let interventions = decisions
|
||||
.iter()
|
||||
.filter(|d| **d != GateDecision::Allow)
|
||||
.count();
|
||||
assert!(
|
||||
interventions > 0,
|
||||
"Expected some interventions, but all were Allow"
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Capacity Constraints ============
|
||||
|
||||
#[test]
|
||||
fn test_capacity_with_sequence_length() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let gate_normal = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// Test with varying sequence lengths
|
||||
for seq_len in [8, 16, 32, 64] {
|
||||
let tokens: Vec<u32> = (0..seq_len).collect();
|
||||
let input = InferInput::from_tokens(&tokens, gate_normal);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
|
||||
let result = transformer.infer(&input, &mut output);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Effective seq len should be bounded by config
|
||||
assert!(output.stats.effective_seq_len <= config.seq_len_max);
|
||||
|
||||
transformer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_capacity_with_degraded_tier() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..64).collect();
|
||||
|
||||
// Normal capacity
|
||||
let gate_normal = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate_normal);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let normal_capacity;
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
normal_capacity = output.stats.effective_seq_len;
|
||||
}
|
||||
|
||||
transformer.reset();
|
||||
|
||||
// Degraded capacity
|
||||
let gate_degraded = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 30, // Triggers ReduceScope
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate_degraded);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let degraded_capacity;
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
degraded_capacity = output.stats.effective_seq_len;
|
||||
}
|
||||
|
||||
// Degraded tier should have reduced capacity
|
||||
assert!(degraded_capacity < normal_capacity);
|
||||
}
|
||||
|
||||
// ============ Boundary Token Handling ============
|
||||
|
||||
#[test]
|
||||
fn test_boundary_edge_concentration() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
|
||||
// Low concentration (edges spread out)
|
||||
let gate_low_conc = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 10,
|
||||
boundary_concentration_q15: 4096, // Low concentration
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate_low_conc);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let low_conc_decision;
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
low_conc_decision = output.witness.decision;
|
||||
}
|
||||
|
||||
transformer.reset();
|
||||
|
||||
// High concentration (edges concentrated)
|
||||
let gate_high_conc = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 10,
|
||||
boundary_concentration_q15: 25000, // High concentration
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate_high_conc);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let high_conc_decision;
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
high_conc_decision = output.witness.decision;
|
||||
}
|
||||
|
||||
// High concentration should trigger intervention
|
||||
assert!(high_conc_decision.is_intervention() || low_conc_decision == GateDecision::Allow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_edges_threshold() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
|
||||
// Test at various boundary edge counts
|
||||
let edge_counts = [5, 10, 15, 20, 25, 30, 35, 40];
|
||||
let mut intervention_count = 0;
|
||||
|
||||
for &edges in &edge_counts {
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: edges,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
|
||||
if output.witness.decision.is_intervention() {
|
||||
intervention_count += 1;
|
||||
}
|
||||
|
||||
transformer.reset();
|
||||
}
|
||||
|
||||
// Should see increasing interventions with higher edge counts
|
||||
assert!(intervention_count > 0);
|
||||
}
|
||||
|
||||
// ============ Skip Ratio Calculation ============
|
||||
|
||||
#[test]
|
||||
fn test_skip_ratio_with_inactive_spikes() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// Run with inactive spikes
|
||||
let mut skip_count = 0;
|
||||
let total_runs = 10;
|
||||
|
||||
for _ in 0..total_runs {
|
||||
let spike = ruvector_mincut_gated_transformer::SpikePacket {
|
||||
fired: 0, // Inactive
|
||||
rate_q15: 500,
|
||||
novelty_q15: 500,
|
||||
top_len: 0,
|
||||
top_idx: [0; 16],
|
||||
top_w_q15: [0; 16],
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate).with_spikes(spike);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
|
||||
if output.stats.skipped == 1 {
|
||||
skip_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// All inactive spikes should skip
|
||||
assert_eq!(skip_count, total_runs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_ratio_with_mixed_activity() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let mut skip_count = 0;
|
||||
let activity_pattern = [1, 0, 0, 1, 0, 0, 0, 1, 0, 0];
|
||||
|
||||
for &fired in &activity_pattern {
|
||||
let spike = ruvector_mincut_gated_transformer::SpikePacket {
|
||||
fired,
|
||||
rate_q15: if fired == 1 { 20000 } else { 500 },
|
||||
novelty_q15: if fired == 1 { 15000 } else { 500 },
|
||||
top_len: 0,
|
||||
top_idx: [0; 16],
|
||||
top_w_q15: [0; 16],
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate).with_spikes(spike);
|
||||
let mut logits = vec![0i32; config.logits as usize];
|
||||
let mut output = InferOutput::new(&mut logits);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
|
||||
if output.stats.skipped == 1 {
|
||||
skip_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip count should match inactive count (7 out of 10)
|
||||
assert_eq!(skip_count, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_drop_ratio_calculation() {
|
||||
let test_cases = vec![
|
||||
(100u32, 100u32, 0u16), // No drop
|
||||
(100u32, 90u32, 3276u16), // 10% drop
|
||||
(100u32, 75u32, 8192u16), // 25% drop
|
||||
(100u32, 50u32, 16384u16), // 50% drop
|
||||
(100u32, 25u32, 24576u16), // 75% drop
|
||||
];
|
||||
|
||||
for (prev, curr, expected_ratio) in test_cases {
|
||||
let gate = GatePacket {
|
||||
lambda: curr,
|
||||
lambda_prev: prev,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let ratio = gate.drop_ratio_q15();
|
||||
|
||||
// Allow 10% tolerance for fixed-point arithmetic
|
||||
let tolerance = expected_ratio / 10;
|
||||
assert!(
|
||||
ratio >= expected_ratio.saturating_sub(tolerance)
|
||||
&& ratio <= expected_ratio + tolerance,
|
||||
"Drop ratio mismatch: expected ~{}, got {}",
|
||||
expected_ratio,
|
||||
ratio
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_preserves_token_order() {
|
||||
let config = TransformerConfig::baseline();
|
||||
let mut transformer = create_transformer(config.clone());
|
||||
|
||||
let tokens: Vec<u32> = (0..32).collect();
|
||||
|
||||
// Run multiple times with same inputs
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits1 = vec![0i32; config.logits as usize];
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits1);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
}
|
||||
|
||||
transformer.reset();
|
||||
|
||||
let input = InferInput::from_tokens(&tokens, gate);
|
||||
let mut logits2 = vec![0i32; config.logits as usize];
|
||||
{
|
||||
let mut output = InferOutput::new(&mut logits2);
|
||||
transformer.infer(&input, &mut output).unwrap();
|
||||
}
|
||||
|
||||
// Output should be deterministic
|
||||
assert_eq!(logits1, logits2);
|
||||
}
|
||||
Reference in New Issue
Block a user