Files
wifi-densepose/crates/ruvector-mincut-gated-transformer/tests/mod_routing.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

466 lines
14 KiB
Rust

//! 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);
}