#[test]
#[cfg(feature = "gpu")]
#[ignore = "Flaky performance test - speedup varies with system load"]
fn test_imp_033_generate_with_cache() {
use crate::gpu::{GpuGenerateConfig, GpuModel, GpuModelConfig};
use std::time::Instant;
let config = GpuModelConfig {
vocab_size: 256,
hidden_dim: 64,
num_heads: 4,
num_kv_heads: 4, num_layers: 2,
intermediate_dim: 128,
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::from_gguf_config(config).expect("IMP-033: Should create model");
let prompt = vec![1, 2, 3, 4, 5];
let gen_config = GpuGenerateConfig::deterministic(50);
for _ in 0..3 {
let _ = model.generate(&prompt, &gen_config);
}
let start = Instant::now();
let tokens = model
.generate_with_cache(&prompt, &gen_config)
.expect("IMP-033: generate_with_cache should succeed");
let cached_time = start.elapsed();
assert!(
tokens.len() > prompt.len(),
"IMP-033: Should generate new tokens"
);
let start = Instant::now();
let _ = model
.generate(&prompt, &gen_config)
.expect("IMP-033: Regular generate should succeed");
let naive_time = start.elapsed();
let speedup = naive_time.as_secs_f64() / cached_time.as_secs_f64();
assert!(
speedup > 0.4, "IMP-033: Cached generation speedup ({:.2}x) should be reasonable",
speedup
);
let tokens1 = model
.generate_with_cache(&prompt, &gen_config)
.expect("IMP-033: Should generate");
let tokens2 = model
.generate_with_cache(&prompt, &gen_config)
.expect("IMP-033: Should generate again");
assert_eq!(
tokens1, tokens2,
"IMP-033: Deterministic generation should produce same output"
);
let long_config = GpuGenerateConfig::deterministic(100);
let long_tokens = model
.generate_with_cache(&prompt, &long_config)
.expect("IMP-033: Long generation should complete");
assert!(
long_tokens.len() >= prompt.len() + 50,
"IMP-033: Long generation should produce substantial output"
);
}
#[test]
#[cfg(feature = "gpu")]
fn test_imp_034_preallocated_attention() {
use crate::gpu::{AttentionBuffers, GpuModel, GpuModelConfig};
let config = GpuModelConfig {
vocab_size: 256,
hidden_dim: 64,
num_heads: 4,
num_kv_heads: 4, num_layers: 2,
intermediate_dim: 128,
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 = 512;
let buffers = AttentionBuffers::new(&config, max_seq_len);
assert_eq!(
buffers.q_buffer.len(),
config.hidden_dim,
"IMP-034: Q buffer should be hidden_dim"
);
assert_eq!(
buffers.scores_buffer.len(),
config.num_heads * max_seq_len,
"IMP-034: Scores buffer should be num_heads * max_seq_len"
);
assert_eq!(
buffers.output_buffer.len(),
config.hidden_dim,
"IMP-034: Output buffer should be hidden_dim"
);
let mut model = GpuModel::with_attention_buffers(config.clone(), max_seq_len)
.expect("IMP-034: Should create model with buffers");
assert!(
model.has_attention_buffers(),
"IMP-034: Model should have attention buffers"
);
let prompt = vec![1, 2, 3, 4, 5];
let gen_config = crate::gpu::GpuGenerateConfig::deterministic(10);
let tokens = model
.generate_optimized(&prompt, &gen_config)
.expect("IMP-034: Optimized generation should work");
assert!(
tokens.len() > prompt.len(),
"IMP-034: Should generate tokens with pre-allocated buffers"
);
}
#[test]
#[cfg(feature = "gpu")]
fn test_imp_035_batched_multihead() {
use crate::gpu::{GpuModel, GpuModelConfig};
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-035: Should create model");
let prompt = vec![1, 2, 3, 4, 5, 6, 7, 8];
let gen_config = crate::gpu::GpuGenerateConfig::deterministic(32);
for _ in 0..3 {
let _ = model.generate_optimized(&prompt, &gen_config);
}
let start = Instant::now();
let _ = model.generate_optimized(&prompt, &gen_config);
let optimized_time = start.elapsed();
let start = Instant::now();
let _ = model.generate_with_cache(&prompt, &gen_config);
let original_time = start.elapsed();
let speedup = original_time.as_secs_f64() / optimized_time.as_secs_f64();
eprintln!(
"IMP-035: Batched multihead speedup: {:.2}x (optimized: {:?}, original: {:?})",
speedup, optimized_time, original_time
);
}
#[test]
#[cfg(feature = "gpu")]
#[ignore = "flaky - timing depends on system load and GPU warmup state"]
fn test_imp_036_optimized_kv_access() {
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-036: 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, 9, 10];
let _ = model.forward_gpu_with_cache(&prompt, &mut kv_cache);
for token in [11, 12, 13] {
let _ = model.forward_gpu_incremental(token, &mut kv_cache);
}
let mut optimized_times = Vec::with_capacity(10);
for token in 20..30 {
let start = Instant::now();
let _ = model.forward_gpu_incremental_optimized(token, &mut kv_cache);
optimized_times.push(start.elapsed().as_secs_f64());
}
let mut original_times = Vec::with_capacity(10);
for token in 30..40 {
let start = Instant::now();
let _ = model.forward_gpu_incremental(token, &mut kv_cache);
original_times.push(start.elapsed().as_secs_f64());
}
optimized_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
original_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let optimized_median = optimized_times[optimized_times.len() / 2];
let original_median = original_times[original_times.len() / 2];
let speedup = original_median / optimized_median;
assert!(
speedup >= 0.5, "IMP-036: Optimized KV access speedup ({:.2}x) should be >= 0.5x (no major regression)",
speedup
);
}
#[test]
#[cfg(feature = "gpu")]
fn test_imp_037_fused_qkv() {
use crate::gpu::{GpuModel, GpuModelConfig};
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-037: Should create model");
assert!(
model.has_fused_qkv(),
"IMP-037: Model should have fused QKV projection"
);
let input = vec![0.1f32; config.hidden_dim];
let (q_fused, k_fused, v_fused) = model
.fused_qkv_projection(&input)
.expect("IMP-037: Fused QKV projection should work");
assert_eq!(q_fused.len(), config.hidden_dim, "IMP-037: Q output size");
assert_eq!(k_fused.len(), config.hidden_dim, "IMP-037: K output size");
assert_eq!(v_fused.len(), config.hidden_dim, "IMP-037: V output size");
let prompt = vec![1, 2, 3, 4, 5, 6, 7, 8];
let gen_config = crate::gpu::GpuGenerateConfig::deterministic(16);
for _ in 0..3 {
let _ = model.generate_optimized(&prompt, &gen_config);
}
let start = Instant::now();
let _ = model.generate_with_fused_qkv(&prompt, &gen_config);
let fused_time = start.elapsed();
let start = Instant::now();
let _ = model.generate_optimized(&prompt, &gen_config);
let regular_time = start.elapsed();
let speedup = regular_time.as_secs_f64() / fused_time.as_secs_f64();
eprintln!(
"IMP-037: Fused QKV speedup: {:.2}x (fused: {:?}, regular: {:?})",
speedup, fused_time, regular_time
);
}
#[test]
#[cfg(feature = "gpu")]
fn test_imp_038_simd_softmax() {
use crate::gpu::{scalar_softmax, simd_softmax};
use std::time::Instant;
let input = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let simd_result = simd_softmax(&input);
let scalar_result = scalar_softmax(&input);
assert_eq!(
simd_result.len(),
input.len(),
"IMP-038: Output size matches"
);
let sum: f32 = simd_result.iter().sum();
assert!(
(sum - 1.0).abs() < 1e-5,
"IMP-038: SIMD softmax should sum to 1.0, got {}",
sum
);
for (i, (simd, scalar)) in simd_result.iter().zip(scalar_result.iter()).enumerate() {
assert!(
(simd - scalar).abs() < 1e-5,
"IMP-038: SIMD softmax[{}] ({}) should match scalar ({})",
i,
simd,
scalar
);
}
let large_input: Vec<f32> = (0..1024).map(|i| i as f32 * 0.01).collect();
for _ in 0..10 {
let _ = simd_softmax(&large_input);
let _ = scalar_softmax(&large_input);
}
let start = Instant::now();
for _ in 0..100 {
let _ = simd_softmax(&large_input);
}
let simd_time = start.elapsed();
let start = Instant::now();
for _ in 0..100 {
let _ = scalar_softmax(&large_input);
}
let scalar_time = start.elapsed();
let speedup = scalar_time.as_secs_f64() / simd_time.as_secs_f64();
let _ = speedup;
}