use super::graph::MetalGraph;
#[cfg(all(feature = "metal", target_os = "macos"))]
fn build_f32_weight(n_rows: usize, k: usize) -> Vec<f32> {
let mut w = vec![0f32; n_rows * k];
let mut lcg: u32 =
0x517C_C1B7 ^ ((n_rows as u32) << 7) ^ (k as u32).wrapping_mul(2_654_435_761);
for v in w.iter_mut() {
lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*v = ((lcg >> 8) as f32 / (1u32 << 24) as f32) - 0.5;
}
w
}
#[test]
#[ignore = "heavy: Metal f32-GEMM parity sweep (N up to 9728, K up to 9728) — ~15 min on debug build; run explicitly with --run-ignored"]
#[cfg(all(feature = "metal", target_os = "macos"))]
fn test_encode_gemm_f32_matches_reference() {
use rayon::prelude::*;
let graph = match MetalGraph::global() {
Ok(g) => g,
Err(_) => {
eprintln!("no Metal device — skipping f32 GEMM parity sweep");
return;
}
};
let ms = [1usize, 7, 32, 33, 128, 512];
let ns = [40usize, 1024, 2560, 4096, 9728];
let ks = [2560usize, 9728];
let mut weight_key: u64 = 7_950_000;
let mut worst_overall = 0f32;
for &k in &ks {
for &n_rows in &ns {
let weight = build_f32_weight(n_rows, k);
let handle = graph
.get_or_upload_f32_weight(weight_key, &weight)
.expect("get_or_upload_f32_weight failed");
weight_key += 1;
for &m in &ms {
let input: Vec<f32> = (0..m * k)
.map(|i| {
let row = i / k;
let col = i % k;
((col as f32) * 0.009 - 0.41).sin() + (row as f32) * 0.0003
})
.collect();
let mut got = vec![0f32; m * n_rows];
graph
.encode_gemm_f32(&handle, &input, &mut got, m, n_rows, k)
.expect("encode_gemm_f32 failed");
let mut expected = vec![0f32; m * n_rows];
expected
.par_chunks_mut(n_rows)
.enumerate()
.for_each(|(mm, out_row)| {
let in_row = &input[mm * k..mm * k + k];
for (n, slot) in out_row.iter_mut().enumerate() {
let w_row = &weight[n * k..n * k + k];
let mut acc = 0f32;
for kk in 0..k {
acc += in_row[kk] * w_row[kk];
}
*slot = acc;
}
});
let mut max_abs_err = 0f32;
for (idx, (a, b)) in expected.iter().zip(got.iter()).enumerate() {
let e = (a - b).abs();
if e > max_abs_err {
max_abs_err = e;
}
assert!(
e < 1e-3,
"f32 N={n_rows} K={k} M={m} idx={idx}: expected {a}, got {b} (|Δ|={e})"
);
}
if m * n_rows > 0 {
let any_nonzero = got.iter().any(|&v| v.abs() > 1e-6);
assert!(
any_nonzero,
"f32 N={n_rows} K={k} M={m}: GEMM output is all-zero (suspicious)"
);
}
if max_abs_err > worst_overall {
worst_overall = max_abs_err;
}
eprintln!(
"encode_gemm_f32: N={n_rows:>4} K={k:>4} M={m:>4} max_abs_err={max_abs_err:e}"
);
}
}
}
eprintln!("encode_gemm_f32 parity sweep WORST max_abs_err={worst_overall:e} (bound 1e-3)");
}