Files
rustytorch/crates/specialized/rtx-cfd/tests/gpu_kernel_tests.rs
T
Omar Sobh 49ccebc863
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 5s
CI / Clippy Check (push) Failing after 4s
CI / Build (ubuntu-latest) (push) Failing after 5s
Performance Benchmarks / Run Benchmarks (push) Failing after 6s
Documentation / Build User Guide (push) Successful in 4s
CI / Build CPU-Only (Explicit) (push) Failing after 48s
Documentation / Build API Documentation (push) Failing after 49s
rtx-cfd legacy GPU tests: the two point-source Poisson tests expect the sign of ∇²φ = f
2026-09-16 07:36:07 -05:00

450 lines
14 KiB
Rust

//! Comprehensive GPU kernel tests for rtx-cfd
//!
//! This module tests all CUDA kernels for correctness and performance.
//! Tests compare GPU results with CPU reference implementations.
use approx::assert_relative_eq;
use rtx_cfd::kernels::*;
use rtx_cfd::*;
#[cfg(feature = "cuda")]
mod cuda_tests {
use super::*;
const TEST_TOLERANCE: f32 = 1e-5;
fn create_test_config() -> CfdConfig {
CfdConfig {
nx: 64,
ny: 64,
nz: 1,
lx: 1.0,
ly: 1.0,
lz: 1.0,
dt: 0.001,
viscosity: 0.01,
density: 1.0,
device_id: 0,
..CfdConfig::default()
}
}
#[test]
fn test_cuda_kernel_manager_creation() -> CfdResult<()> {
let config = create_test_config();
let _manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
Ok(())
}
#[test]
fn test_memory_operations() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
// Test allocation
let size = 1000;
let device_array = manager.allocate_f32(size)?;
assert_eq!(device_array.len(), size);
// Test host to device copy
let host_data: Vec<f32> = (0..size).map(|i| i as f32).collect();
let device_data = manager.copy_to_device(&host_data)?;
assert_eq!(device_data.len(), size);
// Test device to host copy
let result = manager.copy_from_device(&device_data)?;
assert_eq!(result.len(), size);
for (i, &val) in result.iter().enumerate() {
assert_relative_eq!(val, i as f32, epsilon = TEST_TOLERANCE);
}
Ok(())
}
#[test]
fn test_advection_kernel_1d() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = AdvectionKernel::new(&manager, AdvectionScheme::Upwind)?;
let n = 100;
let dx = 0.01;
let dt = 0.001;
let velocity = 1.0;
// Create initial sine wave
let phi_host: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * i as f32 / n as f32).sin())
.collect();
let phi = manager.copy_to_device(&phi_host)?;
let mut phi_new = manager.allocate_f32(n)?;
// Apply advection
kernel.apply(&phi, &mut phi_new, velocity, dt, dx)?;
// Copy result back
let result = manager.copy_from_device(&phi_new)?;
// Verify result is reasonable (sine wave should be shifted)
assert_eq!(result.len(), n);
// Check that the solution has advected (values should be different but similar magnitude)
let max_val = result.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
assert!(max_val > 0.5); // Should still have significant magnitude
assert!(max_val < 1.5); // But not too large
Ok(())
}
#[test]
fn test_advection_kernel_2d() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = AdvectionKernel::new(&manager, AdvectionScheme::Upwind)?;
let nx = 32;
let ny = 32;
let dx = 0.03125; // 1.0 / 32
let dy = 0.03125;
let dt = 0.001;
// Create 2D Gaussian blob
let mut phi_host = vec![0.0f32; nx * ny];
let center_x = nx / 2;
let center_y = ny / 2;
for j in 0..ny {
for i in 0..nx {
let x = (i as f32 - center_x as f32) * dx;
let y = (j as f32 - center_y as f32) * dy;
let r2 = x * x + y * y;
phi_host[j * nx + i] = (-10.0 * r2).exp();
}
}
// Constant velocity field
let u_host = vec![1.0f32; nx * ny];
let v_host = vec![0.5f32; nx * ny];
let phi = manager.copy_to_device(&phi_host)?;
let u = manager.copy_to_device(&u_host)?;
let v = manager.copy_to_device(&v_host)?;
let mut phi_new = manager.allocate_f32(nx * ny)?;
// Apply 2D advection
kernel.apply_2d(&phi, &mut phi_new, &u, &v, dt, dx, dy, nx, ny)?;
let result = manager.copy_from_device(&phi_new)?;
// Verify result
assert_eq!(result.len(), nx * ny);
let max_val = result.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
assert!(max_val > 0.5); // Gaussian should still have significant magnitude
assert!(max_val < 1.1); // But shouldn't grow
Ok(())
}
#[test]
fn test_diffusion_kernel_explicit() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = DiffusionKernel::new(&manager, DiffusionScheme::Explicit)?;
let n = 100;
let dx = 0.01;
let dt = 0.00001; // Small time step for stability
let alpha = 0.01;
// Create initial step function
let mut temp_host = vec![0.0f32; n];
for i in n / 3..2 * n / 3 {
temp_host[i] = 1.0;
}
let temp = manager.copy_to_device(&temp_host)?;
let mut temp_new = manager.allocate_f32(n)?;
// Apply diffusion
kernel.apply(&temp, &mut temp_new, alpha, dt, dx)?;
let result = manager.copy_from_device(&temp_new)?;
// Verify diffusion smoothed the step function
assert_eq!(result.len(), n);
// Check that edges are smoothed
let edge_smoothing = result[n / 3] > temp_host[n / 3 - 1]; // Should have diffused outward
assert!(edge_smoothing);
Ok(())
}
#[test]
fn test_diffusion_kernel_2d() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = DiffusionKernel::new(&manager, DiffusionScheme::Explicit)?;
let nx = 32;
let ny = 32;
let dx = 0.03125;
let dy = 0.03125;
let dt = 0.0001; // Small time step for 2D stability
let alpha = 0.01;
// Create 2D step function (hot center)
let mut temp_host = vec![0.0f32; nx * ny];
for j in ny / 3..2 * ny / 3 {
for i in nx / 3..2 * nx / 3 {
temp_host[j * nx + i] = 1.0;
}
}
let temp = manager.copy_to_device(&temp_host)?;
let mut temp_new = manager.allocate_f32(nx * ny)?;
// Apply 2D diffusion
kernel.apply_2d(&temp, &mut temp_new, alpha, dt, dx, dy, nx, ny)?;
let result = manager.copy_from_device(&temp_new)?;
// Verify 2D diffusion
assert_eq!(result.len(), nx * ny);
// Check that heat has spread
let center_idx = (ny / 2) * nx + (nx / 2);
assert!(result[center_idx] > 0.8); // Center should still be hot
// Edge should have some heat
let edge_idx = (ny / 3 - 1) * nx + (nx / 2);
assert!(result[edge_idx] > 0.0);
Ok(())
}
#[test]
fn test_poisson_kernel_jacobi() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = PoissonKernel::new(&manager)?;
let nx = 32;
let ny = 32;
let dx = 1.0 / (nx - 1) as f32;
let dy = 1.0 / (ny - 1) as f32;
// Create source term (constant source in center)
let mut source_host = vec![0.0f32; nx * ny];
for j in ny / 4..3 * ny / 4 {
for i in nx / 4..3 * nx / 4 {
source_host[j * nx + i] = 1.0;
}
}
let source = manager.copy_to_device(&source_host)?;
let mut phi = manager.allocate_f32(nx * ny)?; // Start with zeros
// Solve Poisson equation
let iterations = kernel.solve_jacobi_2d(&mut phi, &source, nx, ny, dx, dy, 100, 1e-6)?;
let result = manager.copy_from_device(&phi)?;
// Verify convergence
assert!(iterations <= 100);
assert_eq!(result.len(), nx * ny);
// Solution should be non-zero in the source region
let center_idx = (ny / 2) * nx + (nx / 2);
// ∇²φ = f with zero boundaries: a positive source makes a NEGATIVE bump.
assert!(result[center_idx] < -0.01);
// Boundaries should remain zero (Dirichlet BC)
assert_relative_eq!(result[0], 0.0, epsilon = TEST_TOLERANCE);
assert_relative_eq!(result[nx - 1], 0.0, epsilon = TEST_TOLERANCE);
Ok(())
}
#[test]
fn test_matrix_ops_tridiagonal() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = MatrixOpsKernel::new(&manager)?;
let n = 100;
// Create tridiagonal matrix (2 on diagonal, -1 on off-diagonals)
let diagonal = vec![2.0f32; n];
let off_diagonal = vec![-1.0f32; n - 1];
let x = vec![1.0f32; n];
let d_diagonal = manager.copy_to_device(&diagonal)?;
let d_off_diagonal = manager.copy_to_device(&off_diagonal)?;
let d_x = manager.copy_to_device(&x)?;
let mut d_y = manager.allocate_f32(n)?;
// Perform matrix-vector multiplication
kernel.tridiagonal_matvec(&d_diagonal, &d_off_diagonal, &d_x, &mut d_y)?;
let result = manager.copy_from_device(&d_y)?;
// Verify result (should be [1, 0, 0, ..., 0, 1] for this matrix)
assert_eq!(result.len(), n);
assert_relative_eq!(result[0], 1.0, epsilon = TEST_TOLERANCE); // 2*1 + (-1)*1 = 1
assert_relative_eq!(result[n - 1], 1.0, epsilon = TEST_TOLERANCE); // (-1)*1 + 2*1 = 1
// Interior points should be 0
for i in 1..n - 1 {
assert_relative_eq!(result[i], 0.0, epsilon = TEST_TOLERANCE); // (-1)*1 + 2*1 + (-1)*1 = 0
}
Ok(())
}
#[test]
fn test_matrix_ops_dot_product() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = MatrixOpsKernel::new(&manager)?;
let n = 1000;
let x = vec![2.0f32; n];
let y = vec![3.0f32; n];
let d_x = manager.copy_to_device(&x)?;
let d_y = manager.copy_to_device(&y)?;
let result = kernel.dot_product(&d_x, &d_y)?;
// Expected: 2 * 3 * 1000 = 6000
let expected = 6000.0f32;
assert_relative_eq!(result, expected, epsilon = TEST_TOLERANCE);
Ok(())
}
#[test]
fn test_matrix_ops_vector_norm() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = MatrixOpsKernel::new(&manager)?;
let n = 100;
let x = vec![3.0f32; n]; // Each element is 3
let d_x = manager.copy_to_device(&x)?;
let result = kernel.vector_norm(&d_x)?;
// Expected: sqrt(3^2 * 100) = sqrt(900) = 30
let expected = 30.0f32;
assert_relative_eq!(result, expected, epsilon = TEST_TOLERANCE);
Ok(())
}
#[test]
fn test_matrix_ops_axpy() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let kernel = MatrixOpsKernel::new(&manager)?;
let n = 100;
let alpha = 2.5f32;
let x = vec![2.0f32; n];
let y_initial = vec![1.0f32; n];
let d_x = manager.copy_to_device(&x)?;
let mut d_y = manager.copy_to_device(&y_initial)?;
// y = alpha * x + y = 2.5 * 2 + 1 = 6
kernel.axpy(alpha, &d_x, &mut d_y)?;
let result = manager.copy_from_device(&d_y)?;
let expected = 6.0f32;
for &val in result.iter() {
assert_relative_eq!(val, expected, epsilon = TEST_TOLERANCE);
}
Ok(())
}
#[test]
fn test_performance_comparison() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
let n = 10000;
let host_data: Vec<f32> = (0..n).map(|i| i as f32 * 0.1).collect();
// Measure GPU memory operations
let start = std::time::Instant::now();
let device_data = manager.copy_to_device(&host_data)?;
let _result = manager.copy_from_device(&device_data)?;
let gpu_time = start.elapsed();
println!("GPU memory round-trip for {} elements: {:?}", n, gpu_time);
// GPU should be reasonably fast (< 10ms for this size)
assert!(gpu_time.as_millis() < 100);
Ok(())
}
#[test]
fn test_kernel_error_handling() -> CfdResult<()> {
let config = create_test_config();
let manager = std::sync::Arc::new(CudaKernelManager::new(&config)?);
// Test with mismatched array sizes (should not crash)
let small_array = manager.allocate_f32(10)?;
let large_array = manager.allocate_f32(100)?;
let kernel = AdvectionKernel::new(&manager, AdvectionScheme::Upwind)?;
// This should handle the size mismatch gracefully
let mut phi_new = manager.allocate_f32(10)?;
let result = kernel.apply(&small_array, &mut phi_new, 1.0, 0.001, 0.01);
// Should succeed because kernel uses the smaller array's size
assert!(result.is_ok());
Ok(())
}
}
#[cfg(not(feature = "cuda"))]
mod cpu_fallback_tests {
use super::*;
#[test]
fn test_cuda_not_available() {
let config = CfdConfig {
nx: 32,
ny: 32,
nz: 1,
lx: 1.0,
ly: 1.0,
lz: 1.0,
dt: 0.001,
viscosity: 0.01,
density: 1.0,
device_id: 0,
gpu_memory_pool_size: 1024 * 1024 * 1024,
reference_length: 1.0,
reference_velocity: 1.0,
use_gpu: true,
..CfdConfig::default()
};
let result = CudaKernelManager::new(&config);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("CUDA not available"));
}
}
}