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]>
319 lines
16 KiB
Rust
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");
|
|
}
|
|
}
|