The metal job runs the arithmetic probe, the corpus at both widths and the mutants on the host's GPU, every capability that runs a wai-ops/1 graph with its graphs there (the jpegai/v1 inputs fetched and required), and the corpus in a headless browser's WebGPU. WAI_OPS_GPU_EXPECT declares the adapter and WAI_OPS_GPU_REQUIRE makes its absence a failure; each step then requires its report lines with their counts. The checks job, on a runner with no GPU, requires that the device tests skip without WAI_OPS_GPU_REQUIRE and fail with it, and builds the browser module, wai-rs's GPU target and the bench tool. The arith test now appends its result line to the report as the others do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
64 lines
2.9 KiB
Rust
64 lines
2.9 KiB
Rust
//! The kernels' arithmetic on the device (`wgsl/arith.wgsl`): every 64-bit and 128-bit
|
||
//! function, from 32-bit words, against the host's 128-bit integers, on the edges of
|
||
//! every width pairwise, every shift under every rounding mode, and a million SplitMix64
|
||
//! cases, on every adapter found. Then an edit of one function (a carry dropped from the
|
||
//! 32 × 32-bit product) is shown to be caught: discovery refuses the adapter.
|
||
|
||
use std::io::Write;
|
||
|
||
use wai_ops_gpu::probe::{self, ProbeIn};
|
||
use wai_ops_gpu::{blocking, kernels::Sources};
|
||
|
||
/// A result line, also appended to `WAI_OPS_GPU_REPORT` when it is set.
|
||
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 the_arithmetic_is_the_hosts_on_every_adapter() {
|
||
let d = blocking::discover();
|
||
if d.found.is_empty() {
|
||
println!("ops-gpu arith: SKIPPED: no adapter: {:?}", d.refused.iter().map(|(a, w)| (a.key(), w.clone())).collect::<Vec<_>>());
|
||
assert!(std::env::var_os("WAI_OPS_GPU_REQUIRE").is_none(), "WAI_OPS_GPU_REQUIRE is set and no adapter was found");
|
||
return;
|
||
}
|
||
let mut cases: Vec<ProbeIn> = probe::cases(0, 0);
|
||
for seed in 0..16u64 {
|
||
let c = probe::cases(65536, 0xA417_0000 + seed);
|
||
cases.extend_from_slice(&c[c.len() - 65536..]);
|
||
}
|
||
for g in &d.found {
|
||
let mut checked = 0usize;
|
||
for chunk in cases.chunks(1 << 16) {
|
||
let got = blocking::probe(g, chunk).unwrap_or_else(|e| panic!("{}: {e}", g.adapter().key()));
|
||
assert_eq!(probe::first_mismatch(chunk, &got), None, "{}", g.adapter().key());
|
||
checked += chunk.len();
|
||
}
|
||
report(&format!("ops-gpu arith: key={} {} cases={checked} words={} equal", g.adapter().key(), g.adapter().describe(), checked * probe::OUT_WORDS));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn an_edited_product_is_caught_by_the_probe() {
|
||
let d = blocking::discover();
|
||
if d.found.is_empty() {
|
||
println!("ops-gpu arith mutant: 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 mut src = Sources::shipped();
|
||
let from = "let hi = p11 + (mid2 >> 16u) + ((c1 + c2) << 16u);";
|
||
assert!(src.probe.contains(from));
|
||
src.probe = src.probe.replace(from, "let hi = p11 + (mid2 >> 16u) + (c2 << 16u);");
|
||
let m = blocking::discover_with(&src, "mutant: a carry dropped");
|
||
assert!(m.found.is_empty(), "an adapter passed the probe with a carry dropped");
|
||
for (a, why) in &m.refused {
|
||
assert!(why.contains("probe"), "{}: refused for another reason: {why}", a.key());
|
||
println!("ops-gpu arith mutant: key={} refused: {why}", a.key());
|
||
}
|
||
assert_eq!(m.refused.len(), d.found.len() + d.refused.len());
|
||
}
|