#[test]
#[cfg(feature = "gpu")]
fn test_imp_039_fused_attn_proj() {
use crate::gpu::{GpuModel, GpuModelConfig, StreamingKVCache};
use std::time::Instant;
let config = GpuModelConfig {
vocab_size: 256,
hidden_dim: 128,
num_heads: 8,
num_kv_heads: 8, num_layers: 4,
intermediate_dim: 256,
eps: 1e-5,
rope_theta: 10000.0,
explicit_head_dim: None,
layer_types: None,
linear_key_head_dim: None,
linear_value_head_dim: None,
linear_num_key_heads: None,
linear_num_value_heads: None,
linear_conv_kernel_dim: None,
constraints: None,
num_experts: None,
num_experts_per_tok: None,
expert_intermediate_size: None,
};
let mut model = GpuModel::with_attention_buffers(config.clone(), 256)
.expect("IMP-039: Should create model");
let head_dim = config.hidden_dim / config.num_heads;
let mut kv_cache = StreamingKVCache::new(config.num_layers, 256, config.num_heads, head_dim);
let prompt = vec![1, 2, 3, 4, 5, 6, 7, 8];
let _ = model.forward_gpu_with_cache(&prompt, &mut kv_cache);
assert!(
model.has_fused_attn_proj(),
"IMP-039: Model should have fused attention projection"
);
for token in 10..15 {
let _ = model.forward_gpu_incremental_optimized(token, &mut kv_cache);
}
let mut fused_times = Vec::with_capacity(10);
for token in 20..30 {
let start = Instant::now();
let _ = model.forward_with_fused_attn_proj(token, &mut kv_cache);
fused_times.push(start.elapsed().as_secs_f64());
}
let mut regular_times = Vec::with_capacity(10);
for token in 30..40 {
let start = Instant::now();
let _ = model.forward_gpu_incremental_optimized(token, &mut kv_cache);
regular_times.push(start.elapsed().as_secs_f64());
}
fused_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
regular_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let fused_median = fused_times[fused_times.len() / 2];
let regular_median = regular_times[regular_times.len() / 2];
let speedup = regular_median / fused_median;
let _ = speedup;
}
#[test]
fn test_imp_040_contiguous_attention() {
use crate::gpu::{ContiguousAttentionBuffer, GpuModelConfig};
let config = GpuModelConfig {
vocab_size: 256,
hidden_dim: 128,
num_heads: 8,
num_kv_heads: 8, num_layers: 4,
intermediate_dim: 256,
eps: 1e-5,
rope_theta: 10000.0,
explicit_head_dim: None,
layer_types: None,
linear_key_head_dim: None,
linear_value_head_dim: None,
linear_num_key_heads: None,
linear_num_value_heads: None,
linear_conv_kernel_dim: None,
constraints: None,
num_experts: None,
num_experts_per_tok: None,
expert_intermediate_size: None,
};
let max_seq_len = 256;
let head_dim = config.hidden_dim / config.num_heads;
let mut buffer = ContiguousAttentionBuffer::new(max_seq_len, config.num_heads, head_dim);
assert!(
buffer.is_contiguous(),
"IMP-040: Buffer should be contiguous"
);
let (q_view, k_view, v_view, o_view) = buffer.get_views();
assert_eq!(
q_view.len(),
max_seq_len * config.num_heads * head_dim,
"IMP-040: Q view should have correct size"
);
assert_eq!(
k_view.len(),
max_seq_len * config.num_heads * head_dim,
"IMP-040: K view should have correct size"
);
assert_eq!(
v_view.len(),
max_seq_len * config.num_heads * head_dim,
"IMP-040: V view should have correct size"
);
assert_eq!(
o_view.len(),
max_seq_len * config.num_heads * head_dim,
"IMP-040: O view should have correct size"
);
buffer.reset();
assert!(
buffer.is_contiguous(),
"IMP-040: Buffer should remain contiguous after reset"
);
}
#[test]
#[ignore = "flaky under coverage instrumentation due to timing variance"]
fn test_imp_041_vectorized_rope() {
use crate::gpu::{scalar_rope, simd_rope};
use std::time::Instant;
let hidden_dim = 128;
let seq_len = 64;
let head_dim = hidden_dim / 8;
let input: Vec<f32> = (0..seq_len * hidden_dim)
.map(|i| (i as f32) * 0.01)
.collect();
let scalar_result = scalar_rope(&input, seq_len, head_dim, 10000.0);
let simd_result = simd_rope(&input, seq_len, head_dim, 10000.0);
assert_eq!(
scalar_result.len(),
simd_result.len(),
"IMP-041: Results should have same length"
);
for (i, (s, v)) in scalar_result.iter().zip(simd_result.iter()).enumerate() {
assert!(
(s - v).abs() < 1e-5,
"IMP-041: Results should match at index {}: scalar={}, simd={}",
i,
s,
v
);
}
for _ in 0..5 {
let _ = scalar_rope(&input, seq_len, head_dim, 10000.0);
let _ = simd_rope(&input, seq_len, head_dim, 10000.0);
}
let mut scalar_times = Vec::with_capacity(10);
for _ in 0..10 {
let start = Instant::now();
for _ in 0..100 {
let _ = scalar_rope(&input, seq_len, head_dim, 10000.0);
}
scalar_times.push(start.elapsed().as_secs_f64());
}
scalar_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mut simd_times = Vec::with_capacity(10);
for _ in 0..10 {
let start = Instant::now();
for _ in 0..100 {
let _ = simd_rope(&input, seq_len, head_dim, 10000.0);
}
simd_times.push(start.elapsed().as_secs_f64());
}
simd_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let scalar_median = scalar_times[scalar_times.len() / 2];
let simd_median = simd_times[simd_times.len() / 2];
let speedup = scalar_median / simd_median;
assert!(
speedup >= 0.2, "IMP-041: SIMD RoPE speedup ({:.2}x) should be >= 0.2x (severe slowdown indicates bug)",
speedup
);
}
#[test]
fn test_imp_042_fused_output_residual() {
use crate::gpu::{GpuModel, GpuModelConfig, StreamingKVCache};
use std::time::Instant;
let config = GpuModelConfig {
vocab_size: 256,
hidden_dim: 128,
num_heads: 8,
num_kv_heads: 8, num_layers: 4,
intermediate_dim: 256,
eps: 1e-5,
rope_theta: 10000.0,
explicit_head_dim: None,
layer_types: None,
linear_key_head_dim: None,
linear_value_head_dim: None,
linear_num_key_heads: None,
linear_num_value_heads: None,
linear_conv_kernel_dim: None,
constraints: None,
num_experts: None,
num_experts_per_tok: None,
expert_intermediate_size: None,
};
let mut model = GpuModel::with_attention_buffers(config.clone(), 256)
.expect("IMP-042: Should create model");
let head_dim = config.hidden_dim / config.num_heads;
let mut kv_cache = StreamingKVCache::new(config.num_layers, 256, config.num_heads, head_dim);
let prompt = vec![1, 2, 3, 4, 5, 6, 7, 8];
let _ = model.forward_gpu_with_cache(&prompt, &mut kv_cache);
assert!(
model.has_fused_output_residual(),
"IMP-042: Model should have fused output residual capability"
);
for token in 10..15 {
let _ = model.forward_gpu_incremental_optimized(token, &mut kv_cache);
}
let regular_logits = model
.forward_gpu_incremental_optimized(50, &mut kv_cache)
.expect("IMP-042: Regular forward should work");
let fused_logits = model
.forward_with_fused_output_residual(51, &mut kv_cache)
.expect("IMP-042: Fused forward should work");
assert_eq!(
regular_logits.len(),
fused_logits.len(),
"IMP-042: Output sizes should match"
);
let mut fused_times = Vec::with_capacity(10);
for token in 60..70 {
let start = Instant::now();
let _ = model.forward_with_fused_output_residual(token, &mut kv_cache);
fused_times.push(start.elapsed().as_secs_f64());
}
let mut regular_times = Vec::with_capacity(10);
for token in 70..80 {
let start = Instant::now();
let _ = model.forward_gpu_incremental_optimized(token, &mut kv_cache);
regular_times.push(start.elapsed().as_secs_f64());
}
fused_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
regular_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let fused_median = fused_times[fused_times.len() / 2];
let regular_median = regular_times[regular_times.len() / 2];
let speedup = regular_median / fused_median;
let _ = speedup;
}
#[test]
fn test_imp_043_batch_embedding() {
use crate::gpu::{batch_embed, GpuModelConfig};
use std::time::Instant;
let config = GpuModelConfig {
vocab_size: 1024,
hidden_dim: 256,
num_heads: 8,
num_kv_heads: 8, num_layers: 4,
intermediate_dim: 512,
eps: 1e-5,
rope_theta: 10000.0,
explicit_head_dim: None,
layer_types: None,
linear_key_head_dim: None,
linear_value_head_dim: None,
linear_num_key_heads: None,
linear_num_value_heads: None,
linear_conv_kernel_dim: None,
constraints: None,
num_experts: None,
num_experts_per_tok: None,
expert_intermediate_size: None,
};
let embedding_table: Vec<f32> = (0..config.vocab_size * config.hidden_dim)
.map(|i| (i as f32) * 0.001)
.collect();
let tokens: Vec<usize> = vec![1, 5, 10, 20, 50, 100, 200, 500];
let batch_result = batch_embed(&embedding_table, &tokens, config.hidden_dim);
assert_eq!(
batch_result.len(),
tokens.len() * config.hidden_dim,
"IMP-043: Batch embed should return tokens * hidden_dim elements"
);
for (i, &token) in tokens.iter().enumerate() {
let start_idx = token * config.hidden_dim;
let end_idx = start_idx + config.hidden_dim;
let expected = &embedding_table[start_idx..end_idx];
let batch_start = i * config.hidden_dim;
let batch_end = batch_start + config.hidden_dim;
let actual = &batch_result[batch_start..batch_end];
for (j, (&e, &a)) in expected.iter().zip(actual.iter()).enumerate() {
assert!(
(e - a).abs() < 1e-6,
"IMP-043: Mismatch at token {} dim {}: expected {}, got {}",
token,
j,
e,
a
);
}
}
for _ in 0..5 {
let _ = batch_embed(&embedding_table, &tokens, config.hidden_dim);
}
let mut batch_times = Vec::with_capacity(10);
for _ in 0..10 {
let start = Instant::now();
for _ in 0..100 {
let _ = batch_embed(&embedding_table, &tokens, config.hidden_dim);
}
batch_times.push(start.elapsed().as_secs_f64());
}
batch_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mut individual_times = Vec::with_capacity(10);
for _ in 0..10 {
let start = Instant::now();
for _ in 0..100 {
let mut result = Vec::with_capacity(tokens.len() * config.hidden_dim);
for &token in &tokens {
let start_idx = token * config.hidden_dim;
let end_idx = start_idx + config.hidden_dim;
result.extend_from_slice(&embedding_table[start_idx..end_idx]);
}
}
individual_times.push(start.elapsed().as_secs_f64());
}
individual_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let batch_median = batch_times[batch_times.len() / 2];
let individual_median = individual_times[individual_times.len() / 2];
let speedup = individual_median / batch_median;
let _ = speedup;
}