memra_validate/lib.rs
1//! memra-validate — shared validation-protocol core (Phase D extraction, ARCHITECTURE-H100.md §5).
2//!
3//! Pure host logic, zero engine/CUDA dependency: the pieces every gate bin and bench
4//! duplicated (kernel_check, gdn_bench, fa_sanitize, dtype_gpu_check5, the N=5 bench
5//! protocol). CPU kernel references stay with their kernels; THIS crate owns the
6//! protocol: deterministic test vectors, error measures, tolerance banding, N-rep
7//! medians, and the ALL-GREEN tally contract.
8//!
9//! Extraction law: moved code is verbatim (bit-identical vectors and measures) — the
10//! `pr` generator here is the kernel_check/dtype_gpu_check5 variant; fa_sanitize's
11//! 16-bit variant intentionally stays local to it (different distribution = different
12//! test vectors = a silent gate change).
13
14/// Max absolute elementwise difference (the universal gate measure).
15pub fn maxdiff(a: &[f32], b: &[f32]) -> f32 {
16 a.iter().zip(b).map(|(x, y)| (x - y).abs()).fold(0.0, f32::max)
17}
18
19/// Relative error of `d` against the max-|value| scale of `reference` (floored to avoid
20/// zero-division on all-zero references) — the kernel_check GEMM-band convention.
21pub fn rel_of(d: f32, reference: &[f32]) -> f32 {
22 let scale = reference.iter().map(|v| v.abs()).fold(0.0, f32::max).max(1e-3);
23 d / scale
24}
25
26/// Deterministic unit-interval-ish test vector generator (Knuth multiplicative hash →
27/// [-1, 1)). Verbatim the kernel_check/dtype_gpu_check5 `pr` — gate vectors must never
28/// drift across crates or sessions.
29pub fn pr(i: usize) -> f32 {
30 let x = (i.wrapping_mul(2654435761) ^ 0x9E3779B9) as u32;
31 ((x >> 8) as f32 / (1u32 << 24) as f32) * 2.0 - 1.0
32}
33
34/// Median of N runs (the repo's N=5 protocol; N is the caller's law, this is the math).
35/// Sorts a copy; even N takes the lower-middle (matches the existing bench bins).
36pub fn median(xs: &[f64]) -> f64 {
37 assert!(!xs.is_empty());
38 let mut v = xs.to_vec();
39 v.sort_by(|a, b| a.partial_cmp(b).unwrap());
40 v[(v.len() - 1) / 2]
41}
42
43/// Run `f` N times, return (median, all runs). The N=5-medians protocol runner.
44pub fn run_n_median<E>(n: usize, mut f: impl FnMut(usize) -> Result<f64, E>) -> Result<(f64, Vec<f64>), E> {
45 let mut runs = Vec::with_capacity(n);
46 for i in 0..n {
47 runs.push(f(i)?);
48 }
49 Ok((median(&runs), runs))
50}
51
52/// ALL-GREEN tally: gates print per-case lines and exit nonzero on any failure.
53/// `check` returns the condition so call sites keep their inline `{ ... "FAIL" }` style
54/// or use it directly.
55#[derive(Default)]
56pub struct GateTally {
57 pub fails: usize,
58}
59
60impl GateTally {
61 pub fn check(&mut self, label: &str, ok: bool) -> bool {
62 println!("{label}: {}", if ok { "OK" } else { "FAIL" });
63 if !ok {
64 self.fails += 1;
65 }
66 ok
67 }
68
69 /// Terminal verdict in the kernel-check contract: prints ALL GREEN or returns Err.
70 pub fn finish(&self, what: &str) -> Result<(), String> {
71 if self.fails == 0 {
72 println!("ALL GREEN: {what}");
73 Ok(())
74 } else {
75 Err(format!("{}: {} gate(s) FAILED", what, self.fails))
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn median_odd_even() {
86 assert_eq!(median(&[3.0, 1.0, 2.0]), 2.0);
87 assert_eq!(median(&[4.0, 1.0, 3.0, 2.0]), 2.0); // lower-middle
88 }
89
90 #[test]
91 fn pr_deterministic_snapshot() {
92 // Pin the exact vector law — any drift here silently changes every gate.
93 assert_eq!(pr(71), pr(71));
94 let v: Vec<f32> = (0..4).map(pr).collect();
95 assert!(v.iter().all(|x| (-1.0..1.0).contains(x)));
96 }
97
98 #[test]
99 fn tally_contract() {
100 let mut t = GateTally::default();
101 assert!(t.check("a", true));
102 assert!(!t.check("b", false));
103 assert!(t.finish("demo").is_err());
104 assert_eq!(t.fails, 1);
105 }
106}