#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_123d_thread_safe_metrics() {
use std::sync::Arc;
use std::thread;
let metrics = Arc::new(DispatchMetrics::new());
let num_threads = 4;
let dispatches_per_thread = 100;
let handles: Vec<_> = (0..num_threads)
.map(|i| {
let m = Arc::clone(&metrics);
thread::spawn(move || {
for _ in 0..dispatches_per_thread {
if i % 2 == 0 {
m.record_cpu_dispatch();
} else {
m.record_gpu_dispatch();
}
}
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should not panic");
}
assert_eq!(
metrics.total_dispatches(),
num_threads * dispatches_per_thread,
"IMP-123d: Should have all dispatches recorded"
);
assert_eq!(
metrics.cpu_dispatches(),
2 * dispatches_per_thread,
"IMP-123d: Should have correct CPU count"
);
assert_eq!(
metrics.gpu_dispatches(),
2 * dispatches_per_thread,
"IMP-123d: Should have correct GPU count"
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_129a_latency_histogram_struct() {
let metrics = DispatchMetrics::new();
assert_eq!(metrics.cpu_latency_count(), 0);
assert_eq!(metrics.gpu_latency_count(), 0);
assert!(metrics.cpu_latency_mean_us() == 0.0 || metrics.cpu_latency_mean_us().is_nan());
assert!(metrics.gpu_latency_mean_us() == 0.0 || metrics.gpu_latency_mean_us().is_nan());
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_129b_record_latency() {
use std::time::Duration;
let metrics = DispatchMetrics::new();
metrics.record_cpu_latency(Duration::from_micros(100));
metrics.record_cpu_latency(Duration::from_micros(200));
metrics.record_gpu_latency(Duration::from_micros(1000));
assert_eq!(metrics.cpu_latency_count(), 2);
assert_eq!(metrics.gpu_latency_count(), 1);
let cpu_mean = metrics.cpu_latency_mean_us();
assert!(
(cpu_mean - 150.0).abs() < 1.0,
"IMP-129b: CPU mean should be ~150us, got {}",
cpu_mean
);
let gpu_mean = metrics.gpu_latency_mean_us();
assert!(
(gpu_mean - 1000.0).abs() < 1.0,
"IMP-129b: GPU mean should be ~1000us, got {}",
gpu_mean
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_129c_histogram_buckets() {
use std::time::Duration;
let metrics = DispatchMetrics::new();
metrics.record_cpu_latency(Duration::from_micros(50)); metrics.record_cpu_latency(Duration::from_micros(200)); metrics.record_cpu_latency(Duration::from_micros(600)); metrics.record_cpu_latency(Duration::from_micros(2000)); metrics.record_cpu_latency(Duration::from_micros(10000));
let buckets = metrics.cpu_latency_buckets();
assert_eq!(buckets.len(), 5, "IMP-129c: Should have 5 buckets");
assert_eq!(buckets[0], 1, "IMP-129c: Bucket 0 (0-100us) should have 1");
assert_eq!(
buckets[1], 1,
"IMP-129c: Bucket 1 (100-500us) should have 1"
);
assert_eq!(
buckets[2], 1,
"IMP-129c: Bucket 2 (500-1000us) should have 1"
);
assert_eq!(
buckets[3], 1,
"IMP-129c: Bucket 3 (1000-5000us) should have 1"
);
assert_eq!(buckets[4], 1, "IMP-129c: Bucket 4 (5000+us) should have 1");
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_129d_thread_safe_latency() {
use std::sync::Arc;
use std::thread;
use std::time::Duration;
let metrics = Arc::new(DispatchMetrics::new());
let num_threads = 4;
let recordings_per_thread = 100;
let handles: Vec<_> = (0..num_threads)
.map(|i| {
let m = Arc::clone(&metrics);
thread::spawn(move || {
for j in 0..recordings_per_thread {
let latency = Duration::from_micros((i * 100 + j) as u64);
if i % 2 == 0 {
m.record_cpu_latency(latency);
} else {
m.record_gpu_latency(latency);
}
}
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should not panic");
}
assert_eq!(
metrics.cpu_latency_count(),
2 * recordings_per_thread,
"IMP-129d: Should have all CPU latencies recorded"
);
assert_eq!(
metrics.gpu_latency_count(),
2 * recordings_per_thread,
"IMP-129d: Should have all GPU latencies recorded"
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_124a_forward_single_with_cache_adaptive() {
let config = GGUFConfig {
architecture: "test".to_string(),
constraints: crate::gguf::ArchConstraints::from_architecture("test"),
hidden_dim: 64,
intermediate_dim: 128,
num_layers: 2,
num_heads: 4,
num_kv_heads: 4,
vocab_size: 100,
context_length: 256,
rope_theta: 10000.0,
eps: 1e-5,
rope_type: 0,
explicit_head_dim: None,
query_pre_attn_scalar: None,
bos_token_id: None,
eos_token_id: None,
};
let model = create_test_model_with_config(&config);
let mut cache = OwnedQuantizedKVCache::new(
config.num_layers,
config.hidden_dim,
128, );
let metrics = std::sync::Arc::new(DispatchMetrics::new());
let result = model.forward_single_with_cache_adaptive(0, &mut cache, 0, &metrics);
assert!(result.is_ok(), "IMP-124a: Should produce valid output");
let logits = result.expect("Should have logits");
assert_eq!(
logits.len(),
config.vocab_size,
"IMP-124a: Should output vocab_size logits"
);
let result2 = model.forward_single_with_cache_adaptive(1, &mut cache, 1, &metrics);
assert!(result2.is_ok(), "IMP-124a: Second token should work");
assert!(
metrics.total_dispatches() > 0,
"IMP-124a: Should record dispatch decisions after second token"
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_124b_adaptive_matches_standard() {
let config = GGUFConfig {
architecture: "test".to_string(),
constraints: crate::gguf::ArchConstraints::from_architecture("test"),
hidden_dim: 64,
intermediate_dim: 128,
num_layers: 2,
num_heads: 4,
num_kv_heads: 4,
vocab_size: 100,
context_length: 256,
rope_theta: 10000.0,
eps: 1e-5,
rope_type: 0,
explicit_head_dim: None,
query_pre_attn_scalar: None,
bos_token_id: None,
eos_token_id: None,
};
let model = create_test_model_with_config(&config);
let mut cache1 = OwnedQuantizedKVCache::new(config.num_layers, config.hidden_dim, 128);
let mut cache2 = OwnedQuantizedKVCache::new(config.num_layers, config.hidden_dim, 128);
let metrics = std::sync::Arc::new(DispatchMetrics::new());
for i in 0..10 {
let token = (i % 10) as u32;
let standard = model
.forward_single_with_cache(token, &mut cache1, i)
.expect("Standard forward should work");
let adaptive = model
.forward_single_with_cache_adaptive(token, &mut cache2, i, &metrics)
.expect("Adaptive forward should work");
for (j, (&s, &a)) in standard.iter().zip(adaptive.iter()).enumerate() {
assert!(
(s - a).abs() < 1e-4,
"IMP-124b: Output mismatch at position {} token {}: {} vs {}",
j,
i,
s,
a
);
}
}
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_124c_tracks_metrics_per_layer() {
let config = GGUFConfig {
architecture: "test".to_string(),
constraints: crate::gguf::ArchConstraints::from_architecture("test"),
hidden_dim: 64,
intermediate_dim: 128,
num_layers: 2,
num_heads: 4,
num_kv_heads: 4,
vocab_size: 100,
context_length: 256,
rope_theta: 10000.0,
eps: 1e-5,
rope_type: 0,
explicit_head_dim: None,
query_pre_attn_scalar: None,
bos_token_id: None,
eos_token_id: None,
};
let model = create_test_model_with_config(&config);
let mut cache = OwnedQuantizedKVCache::new(config.num_layers, config.hidden_dim, 128);
let metrics = std::sync::Arc::new(DispatchMetrics::new());
for i in 0..5 {
let _ = model.forward_single_with_cache_adaptive(i as u32, &mut cache, i, &metrics);
}
let expected_min_dispatches = 4; assert!(
metrics.total_dispatches() >= expected_min_dispatches,
"IMP-124c: Should record at least {} dispatches, got {}",
expected_min_dispatches,
metrics.total_dispatches()
);
assert_eq!(
metrics.cpu_dispatches(),
metrics.total_dispatches(),
"IMP-124c: All dispatches should be CPU for short sequences"
);
assert_eq!(
metrics.gpu_dispatches(),
0,
"IMP-124c: No GPU dispatches for short sequences"
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_124d_long_cache_uses_gpu() {
let config = GGUFConfig {
architecture: "test".to_string(),
constraints: crate::gguf::ArchConstraints::from_architecture("test"),
hidden_dim: 64,
intermediate_dim: 128,
num_layers: 2,
num_heads: 4,
num_kv_heads: 4,
vocab_size: 100,
context_length: 512,
rope_theta: 10000.0,
eps: 1e-5,
rope_type: 0,
explicit_head_dim: None,
query_pre_attn_scalar: None,
bos_token_id: None,
eos_token_id: None,
};
let model = create_test_model_with_config(&config);
let mut cache = OwnedQuantizedKVCache::new(
config.num_layers,
config.hidden_dim,
256, );
let metrics = std::sync::Arc::new(DispatchMetrics::new());
for i in 0..70 {
let _ = model.forward_single_with_cache_adaptive(i as u32, &mut cache, i, &metrics);
}
assert!(
metrics.gpu_dispatches() > 0,
"IMP-124d: Should have GPU dispatches for long sequences, got cpu={} gpu={}",
metrics.cpu_dispatches(),
metrics.gpu_dispatches()
);
assert!(
metrics.gpu_ratio() > 0.0,
"IMP-124d: GPU ratio should be > 0 for long sequences"
);
}
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_imp_125a_generate_with_cache_adaptive() {
let config = GGUFConfig {
architecture: "test".to_string(),
constraints: crate::gguf::ArchConstraints::from_architecture("test"),
hidden_dim: 64,
intermediate_dim: 128,
num_layers: 2,
num_heads: 4,
num_kv_heads: 4,
vocab_size: 100,
context_length: 256,
rope_theta: 10000.0,
eps: 1e-5,
rope_type: 0,
explicit_head_dim: None,
query_pre_attn_scalar: None,
bos_token_id: None,
eos_token_id: None,
};
let model = create_test_model_with_config(&config);
let metrics = std::sync::Arc::new(DispatchMetrics::new());
let gen_config = QuantizedGenerateConfig {
max_tokens: 5,
temperature: 0.0, top_k: 1,
stop_tokens: vec![],
trace: false,
..Default::default()
};
let prompt = vec![1u32, 2, 3]; let result = model.generate_with_cache_adaptive(&prompt, &gen_config, &metrics);
assert!(result.is_ok(), "IMP-125a: Should produce valid output");
let tokens = result.expect("Should have tokens");
assert!(
tokens.len() >= prompt.len(),
"IMP-125a: Output should include at least prompt tokens"
);
assert!(
tokens.len() <= prompt.len() + gen_config.max_tokens,
"IMP-125a: Output should not exceed max length"
);
}