ferrum_testkit/op_diff/
mod.rs1pub mod activation_bridge;
31pub mod argmax_rows;
32pub mod embedding_lookup;
33pub mod flash_attention;
34pub mod fused_add_rms_norm;
35pub mod gemm;
36pub mod kv_cache_append;
37pub mod marlin_matmul; pub mod metal_context;
39pub mod paged_varlen_attn; pub mod qk_norm_rope;
41pub mod required;
42pub mod residual_add;
43pub mod rms_norm;
44pub mod silu_mul;
45pub mod split_qkv;
46pub mod transpose_head_to_token;
47
48pub const NMSE_FP32_TOL: f64 = 1e-7;
50pub const NMSE_FP16_TOL: f64 = 1e-6;
52
53pub use ferrum_bench_core::release_regression::numerics::nmse;
54
55pub type Output = Vec<f32>;
58
59pub trait OpUnderTest {
61 fn name(&self) -> &str;
63
64 fn run_cpu(&self, seed: u64) -> Output;
66
67 #[cfg(all(target_os = "macos", feature = "metal"))]
69 fn run_metal(&self, seed: u64) -> Output;
70
71 #[cfg(feature = "cuda")]
73 fn run_cuda(&self, seed: u64) -> Output;
74}
75
76#[derive(Debug)]
81pub struct NmseReport {
82 pub op: String,
83 pub seed: u64,
84 pub cpu: Vec<f32>,
85 pub metal_nmse: Option<f64>,
86 pub cuda_nmse: Option<f64>,
87}
88
89impl NmseReport {
90 pub fn within_tol(&self, tol: f64) -> bool {
92 self.metal_nmse.map_or(true, |n| n < tol) && self.cuda_nmse.map_or(true, |n| n < tol)
93 }
94}
95
96pub fn compare_backends(op: &dyn OpUnderTest, seed: u64) -> NmseReport {
99 let cpu = op.run_cpu(seed);
100 let metal_nmse = run_metal_nmse(op, &cpu, seed);
101 let cuda_nmse = run_cuda_nmse(op, &cpu, seed);
102 NmseReport {
103 op: op.name().to_string(),
104 seed,
105 cpu,
106 metal_nmse,
107 cuda_nmse,
108 }
109}
110
111#[cfg(all(target_os = "macos", feature = "metal"))]
112fn run_metal_nmse(op: &dyn OpUnderTest, cpu: &[f32], seed: u64) -> Option<f64> {
113 Some(nmse(cpu, &op.run_metal(seed)))
114}
115
116#[cfg(not(all(target_os = "macos", feature = "metal")))]
117fn run_metal_nmse(_op: &dyn OpUnderTest, _cpu: &[f32], _seed: u64) -> Option<f64> {
118 None
119}
120
121#[cfg(feature = "cuda")]
122fn run_cuda_nmse(op: &dyn OpUnderTest, cpu: &[f32], seed: u64) -> Option<f64> {
123 Some(nmse(cpu, &op.run_cuda(seed)))
124}
125
126#[cfg(not(feature = "cuda"))]
127fn run_cuda_nmse(_op: &dyn OpUnderTest, _cpu: &[f32], _seed: u64) -> Option<f64> {
128 None
129}
130
131pub fn random_vec(n: usize, lo: f32, hi: f32, seed: u64) -> Vec<f32> {
133 use rand::{Rng, SeedableRng};
134 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
135 (0..n).map(|_| rng.random_range(lo..hi)).collect()
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn random_vec_determinism() {
144 let a = random_vec(100, -1.0, 1.0, 42);
145 let b = random_vec(100, -1.0, 1.0, 42);
146 assert_eq!(a, b);
147 }
148}