use crate::gpu::*;
use crate::tensor::Tensor;
use serial_test::serial;
#[test]
fn test_gpu_compute_auto_creation() {
let compute = GpuCompute::auto();
assert!(compute.is_ok(), "Auto creation should succeed");
let compute = compute.expect("test");
assert!(compute.backend() == ComputeBackend::Gpu || compute.backend() == ComputeBackend::Cpu);
}
#[test]
fn test_gpu_compute_cpu_backend() {
let compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
assert!(!compute.is_gpu());
assert_eq!(compute.backend(), ComputeBackend::Cpu);
}
#[test]
fn test_gpu_compute_matmul_cpu_fallback() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = vec![1.0, 2.0, 3.0, 4.0]; let b = vec![5.0, 6.0, 7.0, 8.0];
let c = compute.matmul(&a, &b, 2, 2, 2).expect("test");
assert_eq!(c.len(), 4);
assert!((c[0] - 19.0).abs() < 1e-5);
assert!((c[1] - 22.0).abs() < 1e-5);
assert!((c[2] - 43.0).abs() < 1e-5);
assert!((c[3] - 50.0).abs() < 1e-5);
}
#[test]
fn test_gpu_compute_matmul_non_square() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let b = vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0];
let c = compute.matmul(&a, &b, 2, 3, 2).expect("test");
assert_eq!(c.len(), 4);
assert!((c[0] - 58.0).abs() < 1e-5);
assert!((c[1] - 64.0).abs() < 1e-5);
assert!((c[2] - 139.0).abs() < 1e-5);
assert!((c[3] - 154.0).abs() < 1e-5);
}
#[test]
fn test_gpu_compute_matmul_dimension_error() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = vec![1.0, 2.0, 3.0]; let b = vec![1.0, 2.0, 3.0, 4.0];
let result = compute.matmul(&a, &b, 2, 2, 2);
assert!(result.is_err());
}
#[test]
fn test_gpu_compute_matmul_tensor() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = Tensor::from_vec(vec![2, 3], vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).expect("test");
let b = Tensor::from_vec(vec![3, 2], vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0]).expect("test");
let c = compute.matmul_tensor(&a, &b).expect("test");
assert_eq!(c.shape(), &[2, 2]);
assert!((c.data()[0] - 58.0).abs() < 1e-5);
assert!((c.data()[3] - 154.0).abs() < 1e-5);
}
#[test]
fn test_gpu_compute_matmul_tensor_dimension_mismatch() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = Tensor::from_vec(vec![2, 3], vec![1.0; 6]).expect("test");
let b = Tensor::from_vec(vec![2, 2], vec![1.0; 4]).expect("test");
let result = compute.matmul_tensor(&a, &b);
assert!(result.is_err());
}
#[test]
fn test_gpu_compute_dot_cpu_fallback() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let result = compute.dot(&a, &b).expect("test");
assert!((result - 32.0).abs() < 1e-5); }
#[test]
fn test_gpu_compute_dot_length_mismatch() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0];
let result = compute.dot(&a, &b);
assert!(result.is_err());
}
#[test]
fn test_gpu_compute_relu_cpu_fallback() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let input = vec![-1.0, 0.0, 1.0, -0.5, 2.0];
let output = compute.relu(&input).expect("test");
assert_eq!(output, vec![0.0, 0.0, 1.0, 0.0, 2.0]);
}
#[test]
fn test_gpu_compute_sigmoid_cpu_fallback() {
let mut compute = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let input = vec![0.0];
let output = compute.sigmoid(&input).expect("test");
assert!((output[0] - 0.5).abs() < 1e-5); }
#[test]
fn test_hybrid_scheduler_creation() {
let scheduler = HybridScheduler::new();
assert!(scheduler.is_ok());
}
#[test]
fn test_hybrid_scheduler_threshold() {
let scheduler = HybridScheduler::with_threshold(1000).expect("test");
assert_eq!(scheduler.gpu_threshold(), 1000);
}
#[test]
fn test_hybrid_scheduler_should_use_gpu() {
let scheduler = HybridScheduler::with_threshold(1000).expect("test");
assert!(!scheduler.should_use_gpu(9, 9, 9) || !scheduler.has_gpu());
if scheduler.has_gpu() {
assert!(scheduler.should_use_gpu(10, 10, 10));
assert!(scheduler.should_use_gpu(100, 100, 100));
}
}
#[test]
fn test_hybrid_scheduler_matmul() {
let mut scheduler = HybridScheduler::with_threshold(1000).expect("test");
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
let c = scheduler.matmul(&a, &b, 2, 2, 2).expect("test");
assert_eq!(c.len(), 4);
assert!((c[0] - 19.0).abs() < 1e-5);
}
#[test]
#[serial]
fn test_gpu_backend_matmul() {
let compute = GpuCompute::new(ComputeBackend::Gpu);
if compute.is_err() {
eprintln!("GPU not available, skipping test");
return;
}
let mut compute = compute.expect("test");
assert!(compute.is_gpu());
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
let c = compute.matmul(&a, &b, 2, 2, 2).expect("test");
assert!((c[0] - 19.0).abs() < 1e-4);
assert!((c[1] - 22.0).abs() < 1e-4);
assert!((c[2] - 43.0).abs() < 1e-4);
assert!((c[3] - 50.0).abs() < 1e-4);
}
#[test]
#[serial]
fn test_gpu_backend_large_matmul_speedup() {
use std::time::Instant;
let compute = GpuCompute::new(ComputeBackend::Gpu);
if compute.is_err() {
eprintln!("GPU not available, skipping test");
return;
}
let mut gpu = compute.expect("test");
let mut cpu = GpuCompute::new(ComputeBackend::Cpu).expect("test");
let (rows, inner_dim, cols) = (256usize, 256usize, 256usize);
let matrix_a: Vec<f32> = (0..rows * inner_dim)
.map(|i| (i % 17) as f32 * 0.1)
.collect();
let matrix_b: Vec<f32> = (0..inner_dim * cols)
.map(|i| (i % 19) as f32 * 0.1)
.collect();
let _ = gpu.matmul(&matrix_a, &matrix_b, rows, inner_dim, cols);
let _ = cpu.matmul(&matrix_a, &matrix_b, rows, inner_dim, cols);
let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let _ = gpu.matmul(&matrix_a, &matrix_b, rows, inner_dim, cols);
}
let gpu_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _ = cpu.matmul(&matrix_a, &matrix_b, rows, inner_dim, cols);
}
let cpu_time = start.elapsed();
let speedup = cpu_time.as_secs_f64() / gpu_time.as_secs_f64();
eprintln!(
"GPU matmul speedup: {:.1}x (GPU: {:.2}ms, CPU: {:.2}ms)",
speedup,
gpu_time.as_millis() as f64 / iterations as f64,
cpu_time.as_millis() as f64 / iterations as f64
);
if speedup < 1.0 {
println!(
"Warning: GPU slower than CPU (likely coverage overhead): {:.2}x",
speedup
);
}
}
#[test]
#[serial]
#[ignore] fn test_phase4_acceptance_gpu_throughput() {
use std::time::Instant;
let mut compute = GpuCompute::auto().expect("test");
let has_gpu = compute.is_gpu();
let hidden = 256;
let intermediate = 512;
let num_layers = 4;
let tokens = 100;
let w1: Vec<f32> = (0..hidden * intermediate)
.map(|i| (i % 13) as f32 * 0.01)
.collect();
let w2: Vec<f32> = (0..intermediate * hidden)
.map(|i| (i % 17) as f32 * 0.01)
.collect();
let input: Vec<f32> = vec![0.5; hidden];
let _ = compute.matmul(&input, &w1, 1, hidden, intermediate);
let start = Instant::now();
for _token in 0..tokens {
for _layer in 0..num_layers {
let h1 = compute
.matmul(&input, &w1, 1, hidden, intermediate)
.expect("test");
let _ = compute
.matmul(&h1, &w2, 1, intermediate, hidden)
.expect("test");
}
}
let elapsed = start.elapsed();
let tok_per_sec = tokens as f64 / elapsed.as_secs_f64();
let (target, backend_name) = if has_gpu {
(25.0, "GPU (wgpu)")
} else {
(25.0, "CPU")
};
eprintln!(
"Phase 4 throughput [{backend_name}]: {tok_per_sec:.1} tok/s (target: ≥{target} tok/s)",
);
assert!(
tok_per_sec >= target,
"Phase 4 acceptance FAILED [{backend_name}]: {:.1} tok/s < {target} tok/s",
tok_per_sec
);
}
#[test]
fn test_buffer_pool_creation() {
let pool = GpuBufferPool::new();
let stats = pool.stats();
assert_eq!(stats.cached_buffers, 0);
assert_eq!(stats.cached_bytes, 0);
}
#[test]
fn test_buffer_pool_acquire_release() {
let mut pool = GpuBufferPool::new();
let buf = pool.acquire(1000);
assert_eq!(buf.len(), 1000);
pool.release(buf);
let stats = pool.stats();
assert_eq!(stats.cached_buffers, 1);
}
#[test]
fn test_buffer_pool_reuse() {
let mut pool = GpuBufferPool::new();
let buf1 = pool.acquire(1000);
let _buf1_ptr = buf1.as_ptr(); pool.release(buf1);
let buf2 = pool.acquire(1000);
let stats = pool.stats();
assert!(buf2.len() == 1000);
drop(buf2);
assert!(stats.cached_buffers <= 1);
}
#[test]
fn test_buffer_pool_clear() {
let mut pool = GpuBufferPool::new();
let buf1 = pool.acquire(1000);
let buf2 = pool.acquire(2000);
pool.release(buf1);
pool.release(buf2);
pool.clear();
let stats = pool.stats();
assert_eq!(stats.cached_buffers, 0);
}
#[test]
fn test_buffer_pool_bucket_sizing() {
let mut pool = GpuBufferPool::new();
let buf = pool.acquire(100);
assert!(buf.len() == 100); pool.release(buf);
let stats = pool.stats();
assert!(stats.cached_bytes >= 100 * 4);
}
#[test]
fn test_async_result_ready() {
let result = AsyncGpuResult::ready(vec![1.0, 2.0, 3.0]);
assert!(result.is_ready());
assert!(result.try_get().is_some());
assert_eq!(result.wait(), vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_async_result_pending() {
let mut result = AsyncGpuResult::pending();
assert!(!result.is_ready());
assert!(result.try_get().is_none());
result.set_result(vec![4.0, 5.0, 6.0]);
assert!(result.is_ready());
assert_eq!(result.wait(), vec![4.0, 5.0, 6.0]);
}
include!("hybrid_scheduler.rs");
include!("streaming.rs");
include!("imp_1001d.rs");