open-standards/wai/wai-ops-npu/tests/support/rq.rs
Transaction Science 6265e74262 wai-ops-npu: clippy's notes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 07:20:27 -04:00

97 lines
4.4 KiB
Rust

//! The requantisation probe: `R(t·m, s) + post` (§2, §5.1) through the kernel's
//! epilogue, as every operator that requantises forms it, for each rounding of §2 and
//! every shift from 0 to 63, over cases whose result fits 32 bits (the values an `acc32`
//! graph forms). Each line holds one multiplier and post bias, as one channel does:
//! powers of two (so that a quarter of its cases are exact ties and some are one unit
//! off a tie), random values at every width, and the extremes.
#![allow(dead_code)]
use wai_ops_npu::Npu;
use wai_ops_npu::lines::{Epi, MAX_N, RQ_RESCALE, round_code, rq_line};
use wai_ops_npu::probe::{ROUNDS, Rng, round_name, spec};
/// One line's cases: the inputs, the multiplier, the shift, the rounding and the results
/// §2 gives.
type Want = (Vec<i32>, i32, u32, &'static str, Vec<i64>);
/// What one probe run gave: the cases run, those that differ, and the first that did.
pub fn run(npu: &Npu, reps: usize, seed: u64) -> (u64, String) {
let (_, bad, first) = run_counted(npu, reps, seed);
(bad, first)
}
pub fn run_counted(npu: &Npu, reps: usize, seed: u64) -> (u64, u64, String) {
let mut rng = Rng::new(seed);
let mut lines = Vec::new();
let mut want: Vec<Want> = Vec::new();
for s in 0..=63u32 {
for r in ROUNDS {
for _ in 0..reps {
let class = rng.next_u64() % 6;
let (m, pow_b) = match class {
0 | 1 => {
let b = if s == 0 { 0 } else { (rng.next_u64() % s.min(31) as u64) as u32 };
let v = 1i64 << b;
((if class == 0 { v } else { -v }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, Some(b))
}
2 => (rng.i32(), None),
3 => (rng.bits(16), None),
4 => ([i32::MIN, i32::MAX, -1, 1][(rng.next_u64() % 4) as usize], None),
_ => (rng.bits(8), None),
};
let post = if rng.next_u64().is_multiple_of(3) { 0 } else { rng.bits(20) };
let mut ts = Vec::with_capacity(MAX_N);
let mut ys = Vec::with_capacity(MAX_N);
let mut tries = 0;
while ts.len() < MAX_N && tries < 200 * MAX_N {
tries += 1;
let k = ts.len();
let t = match pow_b {
Some(b) if s >= 1 && k % 4 < 2 && s - 1 - b <= 29 => {
let a = s - 1 - b;
let u = rng.bits(31 - a) as i64 >> 1;
let t = ((2 * u + 1) << a) as i32;
if k % 4 == 1 { t.saturating_add(if rng.next_u64() & 1 == 0 { 1 } else { -1 }) } else { t }
}
_ => match rng.next_u64() % 4 {
0 => rng.i32(),
1 => {
let bt = 1 + (rng.next_u64() % 32) as u32;
rng.bits(bt)
}
2 => [i32::MIN, i32::MAX, -1, 0, 1][(rng.next_u64() % 5) as usize],
_ => rng.bits(12),
},
};
let q = spec(t, m, s, r);
let y = q + post as i128;
if i32::try_from(q).is_ok() && i32::try_from(y).is_ok() {
ts.push(t);
ys.push(y as i64);
}
}
if ts.is_empty() {
continue;
}
let x: Vec<i64> = ts.iter().map(|&t| t as i64).collect();
lines.push(rq_line(RQ_RESCALE, &x, &[], 0, Epi { round: round_code(r), shift: s as i32, mult: m, post }).unwrap());
want.push((ts, m, s, round_name(r), ys));
}
}
}
let outs = npu.dispatch(&lines).expect("the probe's dispatches");
let (mut cases, mut bad, mut first) = (0u64, 0u64, String::new());
for ((ts, m, s, r, ys), o) in want.iter().zip(&outs) {
for (i, (&t, &y)) in ts.iter().zip(ys).enumerate() {
cases += 1;
if o[i] as i64 != y {
if bad == 0 {
first = format!("{r} s={s} t={t} m={m}: want {y}, got {}", o[i]);
}
bad += 1;
}
}
}
(cases, bad, first)
}