open-standards/wai/wai-pack/tests/fabric.rs
Transaction Science fbabae76e8 wai-pack tests: a fabric that ran steps with no share is refused by the packer (#438 review M2)
The packer's fabric_record refuses a fabric that ran steps of the pack with no share
or a share of 0 (#438 review M2); no test held it. the_fabric_record_rides_in_the_
measurement_claim now packs with an empty split and requires the packer's own reason,
not only the claim's sum rule one layer down.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 04:00:28 -04:00

319 lines
16 KiB
Rust

//! Where a step runs changes no byte of a package and no verdict (packaging §11.6).
//!
//! On every build: the reference places every call on the CPU; a cost table places a call
//! on the fabric it prices least, and only on a measurement; a metered pack's claim carries
//! the fabric record and the verifiers accept it, and a record whose shares do not divide
//! the figure is never written.
//!
//! With the `gpu` feature, on every GPU that opens, the differential: the motion search on
//! the device against the CPU reference on seeded frames of every kind the search meets;
//! every golden package packed with the device, byte for byte the committed package; every
//! golden and every package and staged mutation given the CPU's verdict by a verifier that
//! hashes on the device; a device whose kernel is edited refused by the qualification, and,
//! used anyway, still never changing a verdict. With no adapter these print SKIPPED and
//! pass, unless WAI_PACK_GPU_REQUIRE is set.
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use wai_pack::fabric::{Cost, Costs, Fabric, Fabrics, Mode, Step};
use wai_pack::golden;
fn corpus() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../pack-conformance")
}
#[test]
fn the_reference_places_every_call_on_the_cpu() {
let f = Fabrics::reference();
for s in Step::ALL {
for units in [0, 1, u64::MAX] {
assert_eq!(f.place(s, units), Fabric::Cpu, "{}", s.term());
}
assert!(f.offers(s, Fabric::Cpu) && !f.offers(s, Fabric::Media) && !f.offers(s, Fabric::Npu));
assert_eq!(Step::from_term(s.term()), Some(s));
}
for fb in Fabric::ALL {
assert_eq!(Fabric::from_term(fb.term()), Some(fb));
}
}
/// A cost table reads back as it was written, and a fabric is chosen over the CPU only
/// where it is qualified and both are priced: an unpriced or unqualified fabric is never
/// chosen, whatever the table says of it.
#[test]
fn a_cost_table_places_only_on_a_measurement() {
let t = Costs {
host: "a host".into(),
costs: vec![
Cost { step: Step::Sse, fabric: Fabric::Cpu, backend: "cpu".into(), uj_per_call: 0, nj_per_unit_milli: 400 },
Cost { step: Step::Sse, fabric: Fabric::Gpu, backend: "gpu".into(), uj_per_call: 30, nj_per_unit_milli: 20 },
Cost { step: Step::BaseSearch, fabric: Fabric::Gpu, backend: "gpu".into(), uj_per_call: 1, nj_per_unit_milli: 1 },
],
};
let back = Costs::parse(&t.to_json("2026-10-22T00:00:00Z", "a meter")).unwrap();
assert_eq!(back, t);
// 30 uJ a call against 0.4 nJ a unit on the CPU: the GPU's from 78,948 units.
assert!(t.costs[1].nj(78_948) < t.costs[0].nj(78_948));
assert!(t.costs[1].nj(78_946) >= t.costs[0].nj(78_946));
let f = Fabrics::open(Mode::Auto, t.clone());
// No CPU cost for the search: never off the CPU, however cheap the GPU's.
assert_eq!(f.place(Step::BaseSearch, 1 << 40), Fabric::Cpu);
let big = f.place(Step::Sse, 1 << 30);
assert_eq!(big, if f.offers(Step::Sse, Fabric::Gpu) { Fabric::Gpu } else { Fabric::Cpu });
assert_eq!(f.place(Step::Sse, 1000), Fabric::Cpu, "a small call costs less on the CPU");
for bad in ["{}", r#"{"schema":"wai.pack.fabric-costs/1","costs":[{"step":"lunch","fabric":"cpu","backend":"x","uj_per_call":0,"nj_per_unit_milli":0}]}"#] {
assert!(Costs::parse(bad).is_err(), "{bad}");
}
}
/// A meter whose figure the test reads back, so a fabric split can divide it.
struct Seen {
inner: wai::energy_meter::ModelMeter,
figure: Arc<Mutex<Option<u64>>>,
}
impl wai::energy_meter::Meter for Seen {
fn start(&mut self) {
self.inner.start();
}
fn stop(&mut self) -> Option<wai::energy_meter::EnergyReading> {
let r = self.inner.stop()?;
*self.figure.lock().unwrap() = Some(r.joules_micro);
Some(r)
}
fn kind(&self) -> wai::energy_class::AcquisitionKind {
self.inner.kind()
}
}
fn metered_pack(fabrics: Arc<Fabrics>, split: impl Fn(u64) -> Vec<(Fabric, u64, &'static str)> + Send + Sync + 'static) -> Result<wai_pack::pack::PackOutput, wai_pack::pack::PackError> {
let (policy, sources, mut o) = golden::golden_inputs(&corpus(), "video-base-01").unwrap();
let figure = Arc::new(Mutex::new(None));
let mut t = 0u64;
o.meter = Some(Box::new(Seen { inner: wai::energy_meter::ModelMeter::new(move || { t += 1_000_000; t }, 5_000), figure: Arc::clone(&figure) }));
o.power_reader = "model:5000mW".into();
o.issued_at = "2026-10-22T00:00:00Z".into();
o.fabrics = fabrics;
o.fabric_split = Some(Arc::new(move || figure.lock().unwrap().map(&split)));
wai_pack::pack::pack(&policy, &sources, &mut o)
}
fn claim_body(files: &BTreeMap<String, Vec<u8>>) -> serde_json::Value {
let (_, b) = files.iter().find(|(p, _)| p.starts_with("claims/")).expect("a metered package has its claim");
let v: serde_json::Value = serde_json::from_slice(b).unwrap();
v["body"].clone()
}
/// A metered pack writes the fabric record in its measurement claim: every step the pack
/// ran, each on the CPU here, its share the whole figure; both verifiers of a package take
/// it. A split that does not divide the figure is refused, never written.
#[test]
fn the_fabric_record_rides_in_the_measurement_claim() {
let p = metered_pack(Arc::new(Fabrics::reference()), |fig| vec![(Fabric::Cpu, fig, "task"), (Fabric::Gpu, 0, "rail")]).unwrap();
wai_pack::verify::verify_files(&p.files).unwrap();
let body = claim_body(&p.files);
let steps: Vec<(String, String)> = body["fabrics"]["steps"].as_array().unwrap().iter().map(|s| (s["step"].as_str().unwrap().to_string(), s["fabric"].as_str().unwrap().to_string())).collect();
for want in ["base-code", "base-search", "digest", "floor-read", "index", "object-hash", "source-read", "sse"] {
assert!(steps.contains(&(want.to_string(), "cpu".to_string())), "{want} on the CPU in {steps:?}");
}
let mut sorted = steps.clone();
sorted.sort();
assert_eq!(steps, sorted, "steps in the order of their terms");
let shares = &body["fabrics"]["shares"];
assert_eq!(shares.as_array().unwrap().len(), 1, "no share for a fabric no step ran on: {shares}");
assert_eq!(shares[0]["joules_micro"], body["joules_micro"]);
assert_eq!(shares[0]["attribution"], "task");
// The objects are the unmetered pack's.
let plain = golden::pack_golden(&corpus(), "video-base-01").unwrap();
for (k, v) in &plain.files {
if k.starts_with("objects/") || k.starts_with("index-") || k.starts_with("floor/") {
assert!(p.files.get(k) == Some(v), "{k}");
}
}
let short = metered_pack(Arc::new(Fabrics::reference()), |fig| vec![(Fabric::Cpu, fig - 1, "task")]).unwrap_err();
assert_eq!(short.code, "pack-measurement-claim", "{short}");
let elsewhere = metered_pack(Arc::new(Fabrics::reference()), |fig| vec![(Fabric::Cpu, fig - 1, "task"), (Fabric::Gpu, 1, "rail")]).unwrap_err();
assert_eq!(elsewhere.code, "pack-measurement-claim", "a share on a fabric no step ran on: {elsewhere}");
assert!(elsewhere.detail.contains("no step ran there"), "refused for the share itself, not only by the claim's sum: {elsewhere}");
// The CPU ran every step and the meter gave it no share: refused by the packer, for that
// fabric (#438 review M2), not only by the claim's own rules one layer down.
let unmetered = metered_pack(Arc::new(Fabrics::reference()), |_| Vec::new()).unwrap_err();
assert_eq!(unmetered.code, "pack-measurement-claim", "{unmetered}");
assert!(unmetered.detail.contains("the meter gave it no share"), "a fabric that ran steps with no share: {unmetered}");
}
#[cfg(feature = "gpu")]
mod gpu {
use super::*;
use wai::int_video_enc::{CpuSearch, MotionSearch};
fn held(p: &Path) -> BTreeMap<String, Vec<u8>> {
wai::pin::read_dir_set(p).unwrap()
}
const GOLDENS: &[&str] = &["video-base-01", "video-staged-01", "video-ladder-01", "video-cmaf-01", "video-floor-only-01", "video-floor-only-02", "video-repack-01"];
fn gpu_fabrics() -> Option<Fabrics> {
let f = Fabrics::open(Mode::Gpu, Costs::default());
for (who, why) in f.refused() {
eprintln!("not used: {who}: {why}");
}
if !f.offers(Step::BaseSearch, Fabric::Gpu) {
assert!(std::env::var_os("WAI_PACK_GPU_REQUIRE").is_none(), "WAI_PACK_GPU_REQUIRE is set and no GPU qualified");
eprintln!("SKIPPED: no GPU qualified");
return None;
}
Some(f)
}
/// RGB8 frames of a conformance fixture.
fn fixture_frames(name: &str) -> (Vec<Vec<u8>>, usize, usize) {
let y4m = wai_pack::fixture::generate(name).unwrap();
let (hd, frames) = wai_pack::source::y4m::read_all(&y4m).unwrap();
(frames.into_iter().map(|f| f.rgb).collect(), hd.h as usize, hd.w as usize)
}
/// The device's search is the reference's on the qualifying frames, on seeded noise at
/// every block size and range the policy admits, and on every frame pair of every
/// fixture; and the encoder, its search on the device, writes the reference's payload.
#[test]
fn the_search_on_the_gpu_is_the_reference() {
let Some(f) = gpu_fabrics() else { return };
let mut n = 0;
let mut check = |src: &[u8], rf: &[u8], h: usize, w: usize, block: usize, range: i64, prec: i64| {
let (mut want, mut got) = (Vec::new(), Vec::new());
CpuSearch.frame(src, rf, h, w, block, range, prec, &mut want);
f.search().frame(src, rf, h, w, block, range, prec, &mut got);
assert_eq!(got, want, "{h}x{w} block {block} range {range} prec {prec}");
n += 1;
};
for (h, w, block, range, prec, src, rf) in wai_pack::fabric::qualifying_frames() {
check(&src, &rf, h, w, block, range, prec);
}
let mut x = 0x5eedu64;
let mut byte = || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x as u8
};
for block in [4usize, 8, 16, 32] {
for (search, prec) in [(0i64, 1i64), (1, 2), (4, 2), (7, 2), (32, 1), (63, 2)] {
let (h, w) = (37, 53);
let src: Vec<u8> = (0..h * w * 3).map(|_| byte()).collect();
let rf: Vec<u8> = src.iter().map(|v| v.wrapping_add(byte() % 5)).collect();
check(&src, &rf, h, w, block, search * prec, prec);
}
}
for name in wai_pack::fixture::NAMES {
let (frames, h, w) = fixture_frames(name);
for pair in frames.windows(2).take(12) {
check(&pair[1], &pair[0], h, w, 16, 8, 2);
check(&pair[1], &pair[0], h, w, 8, 3, 1);
}
let p = wai::int_video_enc::EncParams { block: 16, search: 4, mv_precision: 2, q: 12 };
let clip = &frames[..frames.len().min(24)];
let want = wai::int_video_enc::encode(clip, h as u32, w as u32, &p).unwrap();
let got = wai::int_video_enc::encode_with(clip, h as u32, w as u32, &p, &mut f.search()).unwrap();
assert_eq!(got, want, "{name}: the payload with the search on the GPU");
}
assert!(f.record().iter().any(|r| r.step == Step::BaseSearch && r.fabric == Fabric::Gpu));
assert!(f.fallbacks().is_empty(), "{:?}", f.fallbacks());
eprintln!("the search on the GPU: {n} frames, every vector the reference's");
}
/// Every golden package, packed with every step a GPU takes on the GPU, is the
/// committed package byte for byte.
#[test]
fn every_golden_packs_the_same_on_the_gpu() {
let Some(f) = gpu_fabrics() else { return };
let f = Arc::new(f);
for name in GOLDENS {
let (policy, sources, mut o) = golden::golden_inputs(&corpus(), name).unwrap();
o.fabrics = Arc::clone(&f);
let p = wai_pack::pack::pack(&policy, &sources, &mut o).unwrap();
let want = held(&corpus().join("packs").join(name).join("package"));
assert_eq!(p.files.keys().collect::<Vec<_>>(), want.keys().collect::<Vec<_>>(), "{name}");
for (k, v) in &want {
assert!(p.files[k] == *v, "{name}: {k} differs from the committed golden");
}
}
let rec = f.record();
for s in [Step::BaseSearch, Step::Sse, Step::ObjectHash] {
assert!(rec.iter().any(|r| r.step == s && r.fabric == Fabric::Gpu), "{} ran on the GPU: {rec:?}", s.term());
}
}
fn verdict(files: &BTreeMap<String, Vec<u8>>, f: &Fabrics) -> String {
match wai_pack::verify::verify_files_with(files, f) {
Ok(_) => "ok".into(),
Err(v) => v.into_iter().next().map_or_else(|| "refused".into(), |r| r.code),
}
}
fn cases() -> Vec<(String, BTreeMap<String, Vec<u8>>, String)> {
let mut out: Vec<(String, BTreeMap<String, Vec<u8>>, String)> = Vec::new();
for name in ["video-base-01", "video-staged-01", "video-ladder-01", "video-cmaf-01", "video-floor-only-02"] {
out.push((name.to_string(), held(&corpus().join("packs").join(name).join("package")), "ok".into()));
}
let key = |name: &str| {
let t = std::fs::read_to_string(corpus().join("packs").join(name).join("key.hex")).unwrap();
let b = wai_pack::unhex(t.trim()).unwrap();
ed25519_dalek::SigningKey::from_bytes(&b.try_into().unwrap())
};
let base = held(&corpus().join("packs/video-base-01/package"));
for m in golden::package_mutations(&base, &key("video-base-01")) {
if m.empty_dirs.is_empty() && m.unreadable_dirs.is_empty() {
out.push((format!("package-mutations/{}", m.name), m.files, m.reason));
}
}
let staged = held(&corpus().join("packs/video-staged-01/package"));
for m in golden::staged_mutations(&staged, &key("video-staged-01")) {
out.push((format!("staged-mutations/{}", m.name), m.files, m.reason));
}
out
}
/// Every golden verifies, and every package and staged mutation gets its reason, from a
/// verifier that hashes on the GPU: the CPU's verdict, case by case.
#[test]
fn every_verdict_is_the_same_on_the_gpu() {
let Some(f) = gpu_fabrics() else { return };
let cases = cases();
for (name, files, reason) in &cases {
let cpu = verdict(files, &Fabrics::reference());
assert_eq!(&cpu, reason, "{name}: the corpus's reason");
assert_eq!(verdict(files, &f), cpu, "{name}: on the GPU");
}
assert!(f.record().iter().any(|r| r.step == Step::ObjectHash && r.fabric == Fabric::Gpu));
eprintln!("{} verdicts, each the CPU's with the hashes on the GPU", cases.len());
}
/// An edited kernel does not qualify; used anyway, a search on it writes another
/// package, and a hash on it never changes a verdict, because a refusal it causes is
/// decided again on the CPU and a match it gives is the reference's hash.
#[test]
fn an_edited_kernel_is_refused_and_moves_no_verdict() {
if gpu_fabrics().is_none() {
return;
}
let mut src = wai_pack_gpu::Sources::shipped();
src.motion = src.motion.replacen("return i < bi;", "return i > bi;", 1);
src.blake3 = src.blake3.replacen("v[b] = rotr(v[b] ^ v[c], 12u);", "v[b] = rotr(v[b] ^ v[c], 11u);", 1);
let mut found = wai_pack_gpu::discover_with(&src, "an edited tie rule and rotation").found;
assert!(!found.is_empty(), "the edited kernels compile");
let g = found.remove(0);
assert!(wai_pack::fabric::qualify(&g, Step::BaseSearch).is_err(), "the edited search is not qualified");
assert!(wai_pack::fabric::qualify(&g, Step::ObjectHash).is_err(), "the edited hash is not qualified");
let bad = Fabrics::with_unqualified_gpu(g, &[Step::BaseSearch, Step::ObjectHash], Mode::Gpu);
for (name, files, _) in cases() {
assert_eq!(verdict(&files, &bad), verdict(&files, &Fabrics::reference()), "{name}: a broken hash on the GPU");
}
let (policy, sources, mut o) = golden::golden_inputs(&corpus(), "video-base-01").unwrap();
o.fabrics = Arc::new(bad);
let p = wai_pack::pack::pack(&policy, &sources, &mut o);
let want = held(&corpus().join("packs/video-base-01/package"));
assert!(p.map_or(true, |p| p.files["LISTING"] != want["LISTING"]), "an unqualified search writes another package, or none");
}
}