183 lines
6.7 KiB
Rust
183 lines
6.7 KiB
Rust
//! Validation tests for uber-kernel correctness
|
|
//!
|
|
//! These tests verify that the fused CUDA kernels produce output that matches
|
|
//! the reference cuBLAS-based forward pass within floating-point tolerance.
|
|
|
|
use pinn_mre_helmholtz::{Config, Mre1DPinnSolver, ForwardWorkspace};
|
|
use rtx_tensor::{Device, Tensor};
|
|
|
|
#[cfg(feature = "cuda")]
|
|
mod uber_kernel_tests {
|
|
use super::*;
|
|
use pinn_mre_helmholtz::uber_kernel::{
|
|
forward_uber_value_only, forward_uber_value_only_out, forward_fused_lite,
|
|
};
|
|
|
|
/// Maximum allowed difference between fused kernel and cuBLAS output
|
|
/// Using 1e-4 because different accumulation orders can cause small differences
|
|
const MAX_ERROR: f32 = 1e-4;
|
|
|
|
/// Compare two tensors element-wise, return max absolute difference
|
|
fn max_abs_diff(a: &Tensor, b: &Tensor) -> f32 {
|
|
let a_data = a.to_vec().expect("Failed to get a data");
|
|
let b_data = b.to_vec().expect("Failed to get b data");
|
|
|
|
assert_eq!(a_data.len(), b_data.len(), "Tensor sizes don't match");
|
|
|
|
a_data.iter()
|
|
.zip(b_data.iter())
|
|
.map(|(x, y)| (x - y).abs())
|
|
.fold(0.0f32, |acc, x| acc.max(x))
|
|
}
|
|
|
|
#[test]
|
|
fn test_uber_value_only_matches_forward() {
|
|
let cfg = Config {
|
|
n_data: 200,
|
|
n_pde: 200,
|
|
epochs: 1,
|
|
..Config::default()
|
|
};
|
|
|
|
let device = Device::Cuda(0);
|
|
let solver = Mre1DPinnSolver::new(cfg.clone()).expect("Failed to create solver");
|
|
let mut ws = ForwardWorkspace::new(200, &cfg, &device).expect("Failed to create workspace");
|
|
|
|
// Reference: cuBLAS-based forward pass (writes to ws.output)
|
|
solver.u_net
|
|
.forward_with_workspace(&solver.x_data, &mut ws)
|
|
.expect("Reference forward failed");
|
|
|
|
// Test: uber_value_only kernel
|
|
let weights = solver.u_net.to_uber_kernel_weights(&cfg);
|
|
let scale = 2.0 * std::f64::consts::PI as f32;
|
|
|
|
let uber_output = forward_uber_value_only(&solver.x_data, &weights, scale)
|
|
.expect("Uber kernel forward failed");
|
|
|
|
let max_diff = max_abs_diff(&ws.output, &uber_output);
|
|
println!("uber_value_only vs cuBLAS max diff: {}", max_diff);
|
|
|
|
assert!(
|
|
max_diff < MAX_ERROR,
|
|
"uber_value_only output differs from cuBLAS by {} (max allowed: {})",
|
|
max_diff, MAX_ERROR
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_uber_value_only_out_matches_forward() {
|
|
let cfg = Config {
|
|
n_data: 200,
|
|
n_pde: 200,
|
|
epochs: 1,
|
|
..Config::default()
|
|
};
|
|
|
|
let device = Device::Cuda(0);
|
|
let solver = Mre1DPinnSolver::new(cfg.clone()).expect("Failed to create solver");
|
|
let mut ws = ForwardWorkspace::new(200, &cfg, &device).expect("Failed to create workspace");
|
|
|
|
// Reference: cuBLAS-based forward pass (writes to ws.output)
|
|
solver.u_net
|
|
.forward_with_workspace(&solver.x_data, &mut ws)
|
|
.expect("Reference forward failed");
|
|
|
|
// Test: uber_value_only_out kernel with pre-allocated output
|
|
let weights = solver.u_net.to_uber_kernel_weights(&cfg);
|
|
let scale = 2.0 * std::f64::consts::PI as f32;
|
|
|
|
let mut output = Tensor::zeros([200, 2], &device)
|
|
.expect("Failed to allocate output");
|
|
|
|
forward_uber_value_only_out(&solver.x_data, &weights, scale, &mut output)
|
|
.expect("Uber kernel forward_out failed");
|
|
|
|
let max_diff = max_abs_diff(&ws.output, &output);
|
|
println!("uber_value_only_out vs cuBLAS max diff: {}", max_diff);
|
|
|
|
assert!(
|
|
max_diff < MAX_ERROR,
|
|
"uber_value_only_out output differs from cuBLAS by {} (max allowed: {})",
|
|
max_diff, MAX_ERROR
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fused_lite_matches_forward() {
|
|
let cfg = Config {
|
|
n_data: 200,
|
|
n_pde: 200,
|
|
epochs: 1,
|
|
..Config::default()
|
|
};
|
|
|
|
let device = Device::Cuda(0);
|
|
let solver = Mre1DPinnSolver::new(cfg.clone()).expect("Failed to create solver");
|
|
let mut ws = ForwardWorkspace::new(200, &cfg, &device).expect("Failed to create workspace");
|
|
|
|
// Reference: cuBLAS-based forward pass (writes to ws.output)
|
|
solver.u_net
|
|
.forward_with_workspace(&solver.x_data, &mut ws)
|
|
.expect("Reference forward failed");
|
|
|
|
// Test: fused_lite kernel with shared memory
|
|
let weights = solver.u_net.to_uber_kernel_weights(&cfg);
|
|
let scale = 2.0 * std::f64::consts::PI as f32;
|
|
|
|
let mut output = Tensor::zeros([200, 2], &device)
|
|
.expect("Failed to allocate output");
|
|
|
|
forward_fused_lite(&solver.x_data, &weights, scale, &mut output)
|
|
.expect("Fused-lite kernel failed");
|
|
|
|
let max_diff = max_abs_diff(&ws.output, &output);
|
|
println!("fused_lite vs cuBLAS max diff: {}", max_diff);
|
|
|
|
assert!(
|
|
max_diff < MAX_ERROR,
|
|
"fused_lite output differs from cuBLAS by {} (max allowed: {})",
|
|
max_diff, MAX_ERROR
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_all_kernels_consistent_larger_batch() {
|
|
let cfg = Config {
|
|
n_data: 1000,
|
|
n_pde: 1000,
|
|
epochs: 1,
|
|
..Config::default()
|
|
};
|
|
|
|
let device = Device::Cuda(0);
|
|
let solver = Mre1DPinnSolver::new(cfg.clone()).expect("Failed to create solver");
|
|
let mut ws = ForwardWorkspace::new(1000, &cfg, &device).expect("Failed to create workspace");
|
|
|
|
// Reference: cuBLAS-based forward pass (writes to ws.output)
|
|
solver.u_net
|
|
.forward_with_workspace(&solver.x_data, &mut ws)
|
|
.expect("Reference forward failed");
|
|
|
|
let weights = solver.u_net.to_uber_kernel_weights(&cfg);
|
|
let scale = 2.0 * std::f64::consts::PI as f32;
|
|
|
|
// Test uber_value_only
|
|
let uber_output = forward_uber_value_only(&solver.x_data, &weights, scale)
|
|
.expect("Uber kernel forward failed");
|
|
let uber_diff = max_abs_diff(&ws.output, &uber_output);
|
|
println!("uber_value_only (1000 pts) vs cuBLAS max diff: {}", uber_diff);
|
|
|
|
// Test fused_lite
|
|
let mut fused_output = Tensor::zeros([1000, 2], &device)
|
|
.expect("Failed to allocate output");
|
|
forward_fused_lite(&solver.x_data, &weights, scale, &mut fused_output)
|
|
.expect("Fused-lite kernel failed");
|
|
let fused_diff = max_abs_diff(&ws.output, &fused_output);
|
|
println!("fused_lite (1000 pts) vs cuBLAS max diff: {}", fused_diff);
|
|
|
|
assert!(uber_diff < MAX_ERROR, "uber_value_only error too large: {}", uber_diff);
|
|
assert!(fused_diff < MAX_ERROR, "fused_lite error too large: {}", fused_diff);
|
|
}
|
|
}
|