One squashed history of three stacked PRs, onto main atb16816671: - #426 (wai/pack-efficiency): a title's whole AV1 ladder in one package over one integer base; several floor rungs (packaging §7.1), a re-pack from a parent (§11.5), a floor that is the ladder's own CMAF (§7.7), a floor-only profile (§7.8), a streaming pack (§11.4), and the independent verifier at scale (§9.4). - #438 (wai/cross-fabric): each pack and verify step on the fabric with the least measured energy (§11.6); the `fabrics` member of measurement claims (jwp-receipts §2.4.8, energy-measurement §6.3); the wai-pack-gpu crate; `wai-pack fabric`; `wai-pack floor-decode`. - #452 (wai/gpu-int-tier), its GPU-tier part only: claiming decode-equivalence on a GPU (determinism-tiers §5), the WGSL integer semantics suite, the rules check on naga's IR, and the kernels' shift masks and min(max()) clamps. Left out, by the review's split (EN-C takes it over): #452's platform decode in dispatch, its commits6ad92e2e8,8695bc75fandfed8694f4and its CHANGELOG entry: extensions/platform-decode.md, platform-decode-conformance/, wai::platform_decode, wai-web's platform_decode bundle and demo, and its two SPEC.md sentences. #438's research note is not carried as written: it is rewritten as wai/research/cross-fabric.md, with hardware named by architecture, PCI id and OS version, and every table marked as one run. floor_decode.rs and a wai_pack.rs comment name the platform's media engine and a full-length film the same way. wai/CHANGELOG.md is the union of both sides' entries, the stack's first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
95 lines
5.9 KiB
Rust
95 lines
5.9 KiB
Rust
//! The corpus catches an edited kernel. Each mutant below changes one expression of
|
||
//! one shader module, the kind of slip an integer kernel makes: a rounding shift off by
|
||
//! one, a wrong saturation bound, a dropped carry or cross term, a sign rule, an index
|
||
//! rule. A device running the mutant must give at least one result of the corpus
|
||
//! (`tests/corpus.rs`) that differs from the committed one. The arithmetic probe is left
|
||
//! as shipped, so the device opens and the corpus alone has to catch the edit.
|
||
//!
|
||
//! Each adapter's result is one line, `ops-gpu mutants: key=… killed N of N`, appended
|
||
//! to `WAI_OPS_GPU_REPORT` when it is set.
|
||
|
||
#[path = "support/corpus.rs"]
|
||
mod corpus;
|
||
|
||
use std::io::Write;
|
||
|
||
use wai_ops_gpu::{blocking, kernels::Sources};
|
||
|
||
#[derive(Clone, Copy)]
|
||
enum Module {
|
||
Ops32,
|
||
Ops64,
|
||
Move,
|
||
}
|
||
|
||
/// `(name, module, the text, its replacement)`; the text must occur in the module.
|
||
const MUTANTS: &[(&str, Module, &str, &str)] = &[
|
||
("half_up addend off by one bit (64-bit rounding, acc32)", Module::Ops32, "let h = w_pow2(s - 1u);", "let h = w_pow2(s);"),
|
||
("half_up addend off by one bit (128-bit rounding, acc64)", Module::Ops64, "let h = q_pow2(s - 1u);", "let h = q_pow2(s);"),
|
||
("rounding shift one short (acc64)", Module::Ops64, "return q_sar(q_add(p, h), s);", "return q_sar(q_add(p, h), s - 1u);"),
|
||
("half_even ties to odd (acc64)", Module::Ops64, "(q_eq(rem, h) && (q.x & 1u) == 1u)", "(q_eq(rem, h) && (q.x & 1u) == 0u)"),
|
||
("half_even ties to odd (acc32)", Module::Ops32, "(w_eq(rem, h) && (q.x & 1u) == 1u)", "(w_eq(rem, h) && (q.x & 1u) == 0u)"),
|
||
("clamp operator saturates one below its top (acc64)", Module::Ops64, "put64(t, w_clamp(a64(t), lo, hi));", "put64(t, w_clamp(a64(t), lo, w_sub(hi, W(1u, 0u))));"),
|
||
("clamp policy saturates one above its bottom (acc32)", Module::Ops32, "O[t] = bitcast<u32>(min(max(v, lo), hi));", "O[t] = bitcast<u32>(min(max(v, lo + 1), hi));"),
|
||
("check policy misses values above its range (acc64)", Module::Ops64, "} else if (w_lt(v, lo) || w_lt(hi, v)) {", "} else if (w_lt(v, lo)) {"),
|
||
// In the 32-bit kernels the 32 × 32-bit product only ever sees magnitudes of at most
|
||
// 2^31, for which neither carry of its middle sum can be set (a0·b1 + a1·b0 stays
|
||
// below 2^32 − 2^17), so dropping one there is an equivalent mutant: it is dropped
|
||
// in the 64-bit kernels, whose products take full 32-bit words.
|
||
("product's middle carry dropped (acc64)", Module::Ops64, "let hi = p11 + (mid2 >> 16u) + ((c1 + c2) << 16u);", "let hi = p11 + (mid2 >> 16u) + (c2 << 16u);"),
|
||
("64-bit product's cross term dropped (acc64)", Module::Ops64, "return W(p.x, p.y + a.x * b.y + a.y * b.x);", "return W(p.x, p.y + a.x * b.y);"),
|
||
("signed product's sign rule (acc32)", Module::Ops32, "return select(m, w_neg(m), na != nb);", "return select(m, w_neg(m), na);"),
|
||
("post bias dropped (acc32)", Module::Ops32, "y = w_add(y, w_sext(m32(P.b_off + 2u * o)));", "y = y;"),
|
||
("transposed convolution's residue (acc64)", Module::Ops64, "for (var i = yy % P.sh; i < P.kh && i <= yy; i += P.sh) {", "for (var i = (yy + 1u) % P.sh; i < P.kh && i <= yy; i += P.sh) {"),
|
||
("convolution's bottom padding edge (acc32)", Module::Ops32, "if (iy < P.ph || iy - P.ph >= P.h) {", "if (iy < P.ph || iy - P.ph > P.h) {"),
|
||
("igdn term floored one bit further (acc64)", Module::Ops64, "n = q_add(n, q_sar(gx2, P.f));", "n = q_add(n, q_sar(gx2, P.f + 1u));"),
|
||
("leaky shift of 32 or more (acc32)", Module::Ops32, "if (s >= 32u) {\n return bitcast<u32>(bitcast<i32>(a) >> 31u);", "if (s > 32u) {\n return bitcast<u32>(bitcast<i32>(a) >> 31u);"),
|
||
("pixel_shuffle's sub-position order", Module::Move, "(y % r) * r + x % r", "(x % r) * r + y % r"),
|
||
("subpos_merge's column offset", Module::Move, "copy_el((c * P.ho + 2u * y + P.k0) * P.wo + 2u * x + P.k1, t);", "copy_el((c * P.ho + 2u * y + P.k1) * P.wo + 2u * x + P.k0, t);"),
|
||
];
|
||
|
||
fn report(line: &str) {
|
||
println!("{line}");
|
||
if let Ok(p) = std::env::var("WAI_OPS_GPU_REPORT") {
|
||
let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&p).unwrap();
|
||
writeln!(f, "{line}").unwrap();
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn every_mutant_is_caught_on_every_adapter() {
|
||
let d = blocking::discover();
|
||
if d.found.is_empty() {
|
||
report("ops-gpu mutants: SKIPPED: no adapter");
|
||
assert!(std::env::var_os("WAI_OPS_GPU_REQUIRE").is_none(), "WAI_OPS_GPU_REQUIRE is set and no adapter was found");
|
||
return;
|
||
}
|
||
let keys: Vec<String> = d.found.iter().map(|g| g.adapter().key()).collect();
|
||
let vs = corpus::vectors();
|
||
let mut killed = vec![0usize; keys.len()];
|
||
for &(name, module, from, to) in MUTANTS {
|
||
let mut src = Sources::shipped();
|
||
let text = match module {
|
||
Module::Ops32 => &mut src.ops32,
|
||
Module::Ops64 => &mut src.ops64,
|
||
Module::Move => &mut src.mv,
|
||
};
|
||
assert!(text.contains(from), "mutant {name:?}: its text is not in the module");
|
||
*text = text.replace(from, to);
|
||
let m = blocking::discover_with(&src, name);
|
||
assert_eq!(m.found.len(), keys.len(), "mutant {name:?}: the adapters did not all open: {:?}", m.refused.iter().map(|(_, w)| w).collect::<Vec<_>>());
|
||
for g in &m.found {
|
||
let k = keys.iter().position(|x| *x == g.adapter().key()).expect("the same adapters");
|
||
let t = corpus::run_all(g, &vs, 1);
|
||
let caught = !t.mismatches.is_empty() || !t.errors.is_empty();
|
||
println!("ops-gpu mutant: key={} {name:?}: {}", keys[k], t.mismatches.first().map_or_else(|| "SURVIVED".to_string(), |m| format!("caught: {m}")));
|
||
killed[k] += caught as usize;
|
||
}
|
||
}
|
||
for (k, key) in keys.iter().enumerate() {
|
||
report(&format!("ops-gpu mutants: key={key} killed {} of {}", killed[k], MUTANTS.len()));
|
||
}
|
||
for (k, key) in keys.iter().enumerate() {
|
||
assert_eq!(killed[k], MUTANTS.len(), "{key}: a mutant survived");
|
||
}
|
||
}
|