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]>
170 lines
6.7 KiB
Rust
170 lines
6.7 KiB
Rust
//! What the kernel tests share: the adapters, the references the kernels are held to, and
|
||
//! the cases.
|
||
//!
|
||
//! The references are transcribed here from the extension text (packaging §5.5, §6.6),
|
||
//! apart from the crate's kernels and from wai-rs, whose encoder wai-pack's differential
|
||
//! test holds the device to; BLAKE3's is the reference crate.
|
||
|
||
#![allow(dead_code)]
|
||
|
||
use wai_pack_gpu::{Discovery, Gpu, Sources};
|
||
|
||
/// The adapters that opened. With none, a test prints SKIPPED and passes, unless
|
||
/// `WAI_PACK_GPU_REQUIRE` is set: then an absent adapter fails, never skips.
|
||
pub fn gpus_with(src: &Sources, label: &str) -> Vec<Gpu> {
|
||
let Discovery { found, refused } = wai_pack_gpu::discover_with(src, label);
|
||
for (a, why) in &refused {
|
||
eprintln!("adapter refused: {}: {why}", a.label());
|
||
}
|
||
if found.is_empty() {
|
||
assert!(std::env::var_os("WAI_PACK_GPU_REQUIRE").is_none(), "WAI_PACK_GPU_REQUIRE is set and no adapter opened");
|
||
eprintln!("SKIPPED: no GPU adapter opened");
|
||
}
|
||
found
|
||
}
|
||
|
||
pub fn gpus() -> Vec<Gpu> {
|
||
gpus_with(&Sources::shipped(), "shipped")
|
||
}
|
||
|
||
/// A seeded generator (xorshift64*), so every run sees the same cases.
|
||
pub struct Rng(pub u64);
|
||
|
||
impl Rng {
|
||
pub fn next(&mut self) -> u64 {
|
||
self.0 ^= self.0 >> 12;
|
||
self.0 ^= self.0 << 25;
|
||
self.0 ^= self.0 >> 27;
|
||
self.0.wrapping_mul(0x2545_F491_4F6C_DD1D)
|
||
}
|
||
pub fn below(&mut self, n: u64) -> u64 {
|
||
self.next() % n
|
||
}
|
||
pub fn bytes(&mut self, n: usize) -> Vec<u8> {
|
||
(0..n).map(|_| self.next() as u8).collect()
|
||
}
|
||
}
|
||
|
||
/// integer-payloads §3's prediction: whole-pel, or half-pel bilinear with integer
|
||
/// rounding, every coordinate clamped to the frame.
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub fn predict(rf: &[u8], h: usize, w: usize, y: i64, x: i64, dy: i64, dx: i64, c: usize, prec: i64) -> i64 {
|
||
let p = |yy: i64, xx: i64| rf[((yy.clamp(0, h as i64 - 1) as usize) * w + xx.clamp(0, w as i64 - 1) as usize) * 3 + c] as i64;
|
||
if prec == 1 {
|
||
return p(y + dy, x + dx);
|
||
}
|
||
let (iy, fy, ix, fx) = (y + dy.div_euclid(2), dy.rem_euclid(2), x + dx.div_euclid(2), dx.rem_euclid(2));
|
||
match (fy, fx) {
|
||
(0, 0) => p(iy, ix),
|
||
(0, _) => (p(iy, ix) + p(iy, ix + 1) + 1) / 2,
|
||
(_, 0) => (p(iy, ix) + p(iy + 1, ix) + 1) / 2,
|
||
_ => (p(iy, ix) + p(iy, ix + 1) + p(iy + 1, ix) + p(iy + 1, ix + 1) + 2) / 4,
|
||
}
|
||
}
|
||
|
||
/// packaging §5.5's search, as the text states it: for each block in raster order, every
|
||
/// candidate's full cost, and the least by cost, then by |dy| + |dx|, then the earliest.
|
||
pub fn search(src: &[u8], rf: &[u8], h: usize, w: usize, block: usize, range: i64, prec: i64) -> Vec<(i16, i16)> {
|
||
let mut out = Vec::new();
|
||
for by in 0..h.div_ceil(block) {
|
||
for bx in 0..w.div_ceil(block) {
|
||
let mut best: Option<(u64, i64, i64, i64)> = None;
|
||
for dy in -range..=range {
|
||
for dx in -range..=range {
|
||
let mut cost = 0u64;
|
||
for y in by * block..((by + 1) * block).min(h) {
|
||
for x in bx * block..((bx + 1) * block).min(w) {
|
||
for c in 0..3 {
|
||
cost += (src[(y * w + x) * 3 + c] as i64 - predict(rf, h, w, y as i64, x as i64, dy, dx, c, prec)).unsigned_abs();
|
||
}
|
||
}
|
||
}
|
||
let key = (cost, dy.abs() + dx.abs());
|
||
if best.is_none_or(|b| key < (b.0, b.1)) {
|
||
best = Some((cost, key.1, dy, dx));
|
||
}
|
||
}
|
||
}
|
||
let b = best.expect("one candidate at least");
|
||
out.push((b.2 as i16, b.3 as i16));
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
/// `Σ (a − b)²`.
|
||
pub fn sse(a: &[u8], b: &[u8]) -> u64 {
|
||
a.iter().zip(b).map(|(&x, &y)| (x as i64 - y as i64).pow(2) as u64).sum()
|
||
}
|
||
|
||
/// A frame pair of each kind the search meets: noise (costs all apart), a smooth field
|
||
/// moved by a fraction of a pel (near ties), a flat field (every candidate ties, so the
|
||
/// tie-break alone decides), and hard edges near the frame's borders (clamping).
|
||
pub fn frame_pairs(rng: &mut Rng, h: usize, w: usize) -> Vec<(&'static str, Vec<u8>, Vec<u8>)> {
|
||
let n = h * w * 3;
|
||
let noise_a = rng.bytes(n);
|
||
let noise_b = rng.bytes(n);
|
||
let field = |t: usize| -> Vec<u8> {
|
||
(0..h * w)
|
||
.flat_map(|i| {
|
||
let (y, x) = (i / w, i % w);
|
||
let v = ((x * 7 + y * 5 + t * 3) % 256) as u8;
|
||
[v, v / 2 + (y % 7) as u8, 255 - v]
|
||
})
|
||
.collect()
|
||
};
|
||
let flat = vec![97u8; n];
|
||
let flat2 = vec![101u8; n];
|
||
let edges = |t: usize| -> Vec<u8> {
|
||
(0..h * w)
|
||
.flat_map(|i| {
|
||
let (y, x) = (i / w, i % w);
|
||
let on = (x + t) % 5 < 2 || y + t < 3 || x + 2 >= w;
|
||
if on { [250, 10, 128] } else { [5, 240, 64] }
|
||
})
|
||
.collect()
|
||
};
|
||
// The noisy frame predicted from a blurred copy of itself, so costs differ by little.
|
||
let mut near = noise_a.clone();
|
||
for v in near.iter_mut() {
|
||
*v = (*v / 8) * 8 + 3;
|
||
}
|
||
// Columns alternating in the reference and moved by one in the source: inside the
|
||
// frame a vector one column left and one right match alike, at the same |dy| + |dx|,
|
||
// so the earlier vector decides.
|
||
let stripes = |t: usize| -> Vec<u8> {
|
||
(0..h * w).flat_map(|i| if (i % w + t).is_multiple_of(2) { [40, 200, 90] } else { [200, 40, 160] }).collect()
|
||
};
|
||
vec![
|
||
("noise", noise_a.clone(), noise_b),
|
||
("near", noise_a, near),
|
||
("field", field(1), field(0)),
|
||
("flat", flat, flat2),
|
||
("edges", edges(1), edges(0)),
|
||
("stripes", stripes(1), stripes(0)),
|
||
]
|
||
}
|
||
|
||
/// The frame sizes and search parameters the motion cases cover: frames that are not a
|
||
/// whole number of blocks, a frame smaller than a block, both precisions, and ranges
|
||
/// from 0 to past the frame.
|
||
pub const MOTION_CASES: &[(usize, usize, usize, i64, i64)] = &[
|
||
// (h, w, block, range, prec)
|
||
(24, 40, 8, 3, 1),
|
||
(24, 40, 8, 3, 2),
|
||
(19, 23, 4, 2, 2),
|
||
(33, 17, 16, 8, 2),
|
||
(9, 13, 16, 5, 1),
|
||
(12, 12, 4, 0, 2),
|
||
(16, 16, 32, 1, 2),
|
||
(36, 64, 16, 14, 2),
|
||
(5, 7, 2, 9, 2),
|
||
];
|
||
|
||
/// The message lengths of the BLAKE3 test vectors, and a few past them.
|
||
pub const BLAKE3_LENGTHS: &[usize] = &[0, 1, 63, 64, 65, 127, 128, 129, 1023, 1024, 1025, 2048, 2049, 3072, 3073, 4096, 4097, 5120, 5121, 6144, 6145, 7168, 7169, 8192, 8193, 16384, 31744, 102_400, 1_000_003];
|
||
|
||
/// The BLAKE3 test vectors' input: byte i is i mod 251.
|
||
pub fn blake3_input(n: usize) -> Vec<u8> {
|
||
(0..n).map(|i| (i % 251) as u8).collect()
|
||
}
|