All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 7s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 13s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 8s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 8s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 15s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 7s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 7s
wai cross-architecture byte-equality / does this change touch it (pull_request) Successful in 13s
WAI operation catalogue / does this change touch it (pull_request) Successful in 7s
WAI energy receipts / does this change touch it (pull_request) Successful in 8s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 13s
wai formal verification (integer core) / does this change touch it (pull_request) Successful in 5s
wai-fleet service / does this change touch it (pull_request) Successful in 8s
wai-key-proxy / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 12s
wai-ledger service / does this change touch it (pull_request) Successful in 7s
WAI neural sink gates / does this change touch it (pull_request) Successful in 5s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 7s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 7s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 15s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 7s
WAI Studio / does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 13s
wai-web wasm32 / does this change touch it (pull_request) Successful in 7s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 3m44s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 36s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 36s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m43s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m56s
wai formal verification (integer core) / kani (isqrt512) (pull_request) Successful in 27m8s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
wai formal verification (integer core) / every harness in one shard (pull_request) Successful in 12s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 1m10s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 4m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 5m45s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 7m11s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 8m18s
wai formal verification (integer core) / kani (stage) (pull_request) Successful in 3m36s
wai-key-proxy / proxy · linux (pull_request) Successful in 1m10s
wai-ledger service / service · linux (pull_request) Successful in 2m4s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m48s
wai-fleet service / service · linux (pull_request) Successful in 5m19s
wai formal verification (integer core) / kani (integer-core) (pull_request) Successful in 7m0s
wai formal verification (integer core) / kani (rans64-t2) (pull_request) Successful in 10m40s
WAI neural sink gates / the exact JPEG-AI decoder reports its path (pinned inputs, required) (pull_request) Successful in 2m12s
wai-pack / wai-pack · linux (pull_request) Successful in 1m58s
wai formal verification (integer core) / kani (arith) (pull_request) Successful in 10m50s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 5m53s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 59s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 15m28s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 3m58s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 3m23s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 3m1s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 17m13s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 6m21s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m39s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m36s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m35s
wai formal verification (integer core) / kani (plan) (pull_request) Successful in 18m17s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m55s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m37s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m4s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m5s
wai formal verification (integer core) / kani (stage-long) (pull_request) Successful in 20m6s
wai formal verification (integer core) / kani (sound) (pull_request) Successful in 28m7s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 59s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 3m50s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 49s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m16s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 7m47s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 7m26s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 25s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 34s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 13m23s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 22m1s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 13s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 8m13s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m58s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 13m35s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 37s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m10s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m14s
wai-fleet service / service · macos (pull_request) Successful in 46s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m28s
wai-ledger service / service · macos (pull_request) Successful in 1m8s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 22s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
wai-pack / wai-pack · macos (pull_request) Successful in 2m28s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m46s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 12m58s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m29s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 7m30s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 14m5s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m57s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m46s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 10m40s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 11m35s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 10m40s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h30m4s
The change of wai/det-source-fixes (152aab5c7..9c4159958, 15 commits), on the fix branch: its changelog entries as files of wai/changes/, its wai-conformance.yml edits as step files, corpus files and test tags, and every other file as the branch has it. - a5bb23634 wai-rs: the inverse GDN's integer roots take no floating point - b9463b4d0 wai-rs: the splat ranges and JSON form are read in integer arithmetic - b20be5196 wai-rs: int_entropy's scale conversions are formed in integers - 437669081 wai-rs: a test that the integer decode paths hold no floating point - 36aade4f1 wai: the integer decode paths without floating point, in the changelog and integer-payloads §10 - f781bf091 wai-rs: the AVX-512 root's comment states the bound s + bit keeps - 799ab8476 wai-rs: float_free reads the other integer entropy decoders too - b79447e64 wai-rs: splat ranges one last digit apart decode to adjacent values - 43f75cc36 wai: the changelog names the decoders float_free reads and the last-digit pairs - 2e308fd28 wai-rs: the parameter sets' JSON is read by the strict reader - 42f848583 wai-rs: int_payload re-exports Binary32 - d1dbe4936 wai-rs: float_free reads the operator set, every integer module, and serde_json's parser - 931699331 wai: the parameter sets' JSON read strictly, in integer-payloads §9 and §14 and the changelog - 97d133380 wai-rs: float_free names what a decode reaches beyond what it reads - 9c4159958 wai: the changelog names the general JSON reader once Co-Authored-By: Claude Opus 5.5 <[email protected]>
386 lines
18 KiB
Rust
386 lines
18 KiB
Rust
//! The integer decoders with their graphs on a GPU (`int_ops::gpu`, `wai_ops_gpu`): every
|
|
//! committed vector and refusal fixture of every capability whose decode runs a
|
|
//! `wai-ops/1` graph decodes to the same bytes, or is refused with the same code, as the
|
|
//! decode whose graphs run on the CPU, on every adapter this process finds.
|
|
//!
|
|
//! - `int-payload-conformance/vectors.json`: its payload vectors and refusal fixtures
|
|
//! (`wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.audio.int_codec`,
|
|
//! `wai.splat.int_codec`; `wai.video.int_motion` runs no graph, and is listed with no
|
|
//! graph runs);
|
|
//! - `neural-conformance/`: the `wai.neural.int_synth` vectors;
|
|
//! - `wai-rs/tests/vectors/int_mlicpp`: the `wai.neural.int_mlicpp` vector;
|
|
//! - `wai-rs/tests/vectors/int_world{,2}`: the integer renderer of `wai.world.model`
|
|
//! (informative, integer-operators §13.5);
|
|
//! - `wai.image.jpegai`'s exact decoder on the `jpegai/v1` inputs, when
|
|
//! `WAI_JPEGAI_CODESTREAM`, `WAI_JPEGAI_WEIGHTS_DIR` and `WAI_JPEGAI_NORM` name them
|
|
//! (`tools/wai_jpegai_fetch_e2e_inputs.sh`). Its scale graphs and hyper decoders run on
|
|
//! the device; its synthesis graphs use operators outside `wai-ops/1-i32` and run on the
|
|
//! CPU, which the line for it states.
|
|
//!
|
|
//! Each graph the device runs is also run on the interpreter, and its outputs compared
|
|
//! (`gpu::with_gpu_compared`): so the equality is shown graph by graph, and not only
|
|
//! through each decode's final bytes.
|
|
//!
|
|
//! **A skip is not a pass.** With no adapter the test prints `ops-gpu capabilities:
|
|
//! SKIPPED` and fails when `WAI_OPS_GPU_REQUIRE` is set; without the `jpegai/v1` inputs it
|
|
//! prints that `wai.image.jpegai` was not run, and fails when `WAI_REQUIRE_SOTA_VECTORS`
|
|
//! is set. A capability whose decodes ran no graph on the device fails. Each adapter's
|
|
//! result is one line per capability, `ops-gpu capability: key=…`, appended to
|
|
//! `WAI_OPS_GPU_REPORT` when it is set.
|
|
#![cfg(all(feature = "ops_gpu", feature = "video_int", feature = "audio_int", feature = "splat_int", feature = "world_int"))]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use serde_json::Value;
|
|
use wai::int_hyper_synth::IntModel;
|
|
use wai::int_ops::gpu::{self, Stats, wai_ops_gpu};
|
|
use wai::int_payload::{IntDecoded, IntParams, decode_int};
|
|
use wai::pin::HeldBytes;
|
|
|
|
fn wai() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
fn read(rel: &str) -> Vec<u8> {
|
|
std::fs::read(wai().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
|
|
}
|
|
fn json(rel: &str) -> Value {
|
|
serde_json::from_slice(&read(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// A decode's result as bytes: every output byte the decode gives, or its refusal code.
|
|
type Out = Result<Vec<u8>, String>;
|
|
|
|
fn bytes_of(d: &IntDecoded) -> Vec<u8> {
|
|
let mut b = Vec::new();
|
|
match d {
|
|
IntDecoded::Image { rgb, width, height } => {
|
|
b.extend_from_slice(&width.to_le_bytes());
|
|
b.extend_from_slice(&height.to_le_bytes());
|
|
b.extend_from_slice(rgb);
|
|
}
|
|
IntDecoded::Video { frames, width, height } => {
|
|
b.extend_from_slice(&width.to_le_bytes());
|
|
b.extend_from_slice(&height.to_le_bytes());
|
|
for f in frames {
|
|
b.extend_from_slice(f);
|
|
}
|
|
}
|
|
IntDecoded::Pcm { samples, sample_rate, channels } => {
|
|
b.extend_from_slice(&sample_rate.to_le_bytes());
|
|
b.extend_from_slice(&channels.to_le_bytes());
|
|
for s in samples {
|
|
b.extend_from_slice(&s.to_le_bytes());
|
|
}
|
|
}
|
|
IntDecoded::Attr { bytes, n_attr, s, n_gaussians, ranges } => {
|
|
for v in [n_attr, s, n_gaussians] {
|
|
b.extend_from_slice(&v.to_le_bytes());
|
|
}
|
|
for (lo, hi) in ranges {
|
|
b.extend_from_slice(&lo.0.to_le_bytes());
|
|
b.extend_from_slice(&hi.0.to_le_bytes());
|
|
}
|
|
b.extend_from_slice(bytes);
|
|
}
|
|
_ => panic!("an output form this test does not compare"),
|
|
}
|
|
b
|
|
}
|
|
|
|
/// One decode of the corpus: its capability, its name, and the decode.
|
|
struct Case {
|
|
cap: &'static str,
|
|
name: String,
|
|
refusal: bool,
|
|
run: Box<dyn Fn() -> Out>,
|
|
}
|
|
|
|
fn cap_static(c: &str) -> &'static str {
|
|
for k in [
|
|
"wai.video.int_motion", "wai.neural.int_hyper", "wai.video.int_hyper", "wai.audio.int_codec", "wai.splat.int_codec", "wai.neural.int_synth",
|
|
"wai.neural.int_mlicpp", "wai.world.model", "wai.image.jpegai",
|
|
] {
|
|
if k == c {
|
|
return k;
|
|
}
|
|
}
|
|
panic!("an unknown capability {c}")
|
|
}
|
|
|
|
fn payload_cases() -> Vec<Case> {
|
|
let idx = json("int-payload-conformance/vectors.json");
|
|
let mut out = Vec::new();
|
|
for (list, refusal) in [("vectors", false), ("refusals", true)] {
|
|
for e in idx[list].as_array().unwrap() {
|
|
let cap = cap_static(e["capability"].as_str().unwrap());
|
|
let payload = read(e["payload"].as_str().unwrap());
|
|
let set: BTreeMap<String, Vec<u8>> = e["set"].as_object().into_iter().flatten().map(|(k, v)| (k.clone(), read(v.as_str().unwrap()))).collect();
|
|
let run = move || -> Out {
|
|
let view: BTreeMap<String, &[u8]> = set.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect();
|
|
let params = wai::int_payload::resolve(cap, None, &[HeldBytes::Set { listing: b"", files: &view }], None).unwrap_or_else(|_| IntParams::none());
|
|
decode_int(cap, &payload, ¶ms).map(|d| bytes_of(&d)).map_err(|e| e.code().to_string())
|
|
};
|
|
out.push(Case { cap, name: e["name"].as_str().unwrap().to_string(), refusal, run: Box::new(run) });
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
fn synth_cases() -> Vec<Case> {
|
|
let hashes = json("neural-conformance/expected_hashes.json");
|
|
hashes
|
|
.as_object()
|
|
.unwrap()
|
|
.keys()
|
|
.map(|name| {
|
|
let dir = format!("neural-conformance/vectors/{name}");
|
|
let (payload, set) = (read(&format!("{dir}/latent.bin")), read(&format!("{dir}/int_synth.json")));
|
|
let run = move || -> Out { wai::int_synth::decode_from_spec(&payload, &set).map(|i| [i.width.to_le_bytes().to_vec(), i.height.to_le_bytes().to_vec(), i.rgb].concat()).map_err(|e| e.to_string()) };
|
|
Case { cap: "wai.neural.int_synth", name: name.clone(), refusal: false, run: Box::new(run) }
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn mlicpp_cases() -> Vec<Case> {
|
|
const DIR: &str = "wai-rs/tests/vectors/int_mlicpp";
|
|
let m = wai::int_mlicpp::IntMlicModel::from_bundle(&read(&format!("{DIR}/model.wmb"))).unwrap();
|
|
let p = wai::container::Wai::from_bytes(&read(&format!("{DIR}/stream.wai"))).unwrap().payload;
|
|
let run = move || -> Out { wai::int_mlicpp::decode(&m, &p).map(|i| [(i.width as u64).to_le_bytes().to_vec(), (i.height as u64).to_le_bytes().to_vec(), i.rgb].concat()).map_err(|e| format!("{e:?}")) };
|
|
vec![Case { cap: "wai.neural.int_mlicpp", name: "int_mlicpp".into(), refusal: false, run: Box::new(run) }]
|
|
}
|
|
|
|
fn world_cases() -> Vec<Case> {
|
|
let mut out = Vec::new();
|
|
for (name, dir, js, t, g) in [("world", "int_world", "world.json", "world_T.bin", "world_gs.bin"), ("world2", "int_world2", "world2.json", "world2_T.bin", "world2_gs.bin")] {
|
|
let j = json(&format!("wai-rs/tests/vectors/{dir}/{js}"));
|
|
let tm = IntModel::from_bytes(&read(&format!("wai-rs/tests/vectors/{dir}/{t}"))).unwrap();
|
|
let gm = IntModel::from_bytes(&read(&format!("wai-rs/tests/vectors/{dir}/{g}"))).unwrap();
|
|
let c = j["c"].as_u64().unwrap() as usize;
|
|
let lat = j["lat"].as_u64().unwrap() as usize;
|
|
let z0: Vec<i64> = j["z0"].as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect();
|
|
let afrac = j["afrac"].as_u64().unwrap() as u32;
|
|
let q = |d: i64| wai_ops::stage::dequantize(d, afrac, 0).unwrap();
|
|
let mv = |a: &Value| wai::int_world::ACTIONS[a.as_u64().unwrap() as usize];
|
|
let rows: Vec<Vec<i64>> = match j.get("pairs") {
|
|
Some(p) => p
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|p| {
|
|
let ((xa, ya), (xb, yb)) = (mv(&p[0]), mv(&p[1]));
|
|
vec![q(xa), q(ya), q(xb), q(yb)]
|
|
})
|
|
.collect(),
|
|
None => j["actions"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|a| {
|
|
let (dx, dy) = mv(a);
|
|
vec![q(dx), q(dy), 0]
|
|
})
|
|
.collect(),
|
|
};
|
|
let run = move || -> Out { wai::int_world::decode_planes(&tm, &gm, &z0, &rows, c, lat).map(|w| w.frames.concat()).map_err(|e| format!("{e:?}")) };
|
|
out.push(Case { cap: "wai.world.model", name: name.into(), refusal: false, run: Box::new(run) });
|
|
}
|
|
out
|
|
}
|
|
|
|
mod jpegai {
|
|
use wai::jpegai_decode::{DecoderWeights, NormTables, PlaneWeights};
|
|
use wai::jpegai_icci::{Conv, IcciNet, IcciPath, ResBlock};
|
|
use wai::jpegai_synthesis::{IntNet, Weights};
|
|
|
|
fn jload(p: &std::path::Path) -> serde_json::Value {
|
|
serde_json::from_slice(&std::fs::read(p).unwrap()).unwrap()
|
|
}
|
|
fn w_of(dir: &str, name: &str) -> Weights {
|
|
Weights::from_json(&jload(&std::path::Path::new(dir).join(name))).unwrap()
|
|
}
|
|
fn int_of(dir: &str, name: &str) -> IntNet {
|
|
IntNet::from_json(&jload(&std::path::Path::new(dir).join(name))).unwrap()
|
|
}
|
|
fn i64v(v: &serde_json::Value, k: &str) -> Vec<i64> {
|
|
v[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect()
|
|
}
|
|
fn i32v(v: &serde_json::Value, k: &str) -> Vec<i32> {
|
|
v[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap() as i32).collect()
|
|
}
|
|
fn f3(v: &serde_json::Value, k: &str) -> [f32; 3] {
|
|
let a = v[k].as_array().unwrap();
|
|
[a[0].as_f64().unwrap() as f32, a[1].as_f64().unwrap() as f32, a[2].as_f64().unwrap() as f32]
|
|
}
|
|
fn i3(v: &serde_json::Value, k: &str) -> [i64; 3] {
|
|
let a = v[k].as_array().unwrap();
|
|
[a[0].as_i64().unwrap(), a[1].as_i64().unwrap(), a[2].as_i64().unwrap()]
|
|
}
|
|
fn conv_of(c: &serde_json::Value) -> Conv {
|
|
let g = |k: &str| c[k].as_u64().unwrap() as usize;
|
|
Conv {
|
|
w: c["w"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap() as f32).collect(),
|
|
b: c["b"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap() as f32).collect(),
|
|
cin: g("cin"),
|
|
cout: g("cout"),
|
|
kh: g("kh"),
|
|
kw: g("kw"),
|
|
ph: g("ph"),
|
|
pw: g("pw"),
|
|
}
|
|
}
|
|
fn path_of(v: &serde_json::Value) -> IcciPath {
|
|
IcciPath {
|
|
conv_first: conv_of(&v["conv_first"]),
|
|
blocks: v["blocks"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|b| ResBlock { conv1: conv_of(&b["c1"]), conv2: conv_of(&b["c2"]), scale: b["scale"].as_f64().unwrap() as f32 })
|
|
.collect(),
|
|
conv_last: conv_of(&v["conv_last"]),
|
|
scale: v["scale"].as_f64().unwrap() as f32,
|
|
}
|
|
}
|
|
|
|
/// The `jpegai/v1` inputs, as `tests/jpegai_e2e_full.rs` loads them.
|
|
pub fn load(cs: &str, dir: &str, nf: &str) -> (Vec<u8>, DecoderWeights, NormTables) {
|
|
let data = std::fs::read(cs).unwrap_or_else(|e| panic!("read {cs}: {e}"));
|
|
let nv = jload(std::path::Path::new(nf));
|
|
let iw = jload(&std::path::Path::new(dir).join("icci.json"));
|
|
let icci = IcciNet { luma: path_of(&iw["luma"]), chroma: path_of(&iw["chroma"]), conv_last_v: conv_of(&iw["conv_last_v"]), scale_v: iw["scale_v"].as_f64().unwrap() as f32 };
|
|
let weights = DecoderWeights {
|
|
luma: PlaneWeights { hyper_scale: int_of(dir, "hs_y.json"), hyper_decoder: w_of(dir, "hyp_y.json"), synthesis: w_of(dir, "synth_y.json") },
|
|
chroma: PlaneWeights { hyper_scale: int_of(dir, "hs_uv.json"), hyper_decoder: w_of(dir, "hyp_uv.json"), synthesis: w_of(dir, "synth_uv.json") },
|
|
mcm_y: [w_of(dir, "mcm0_y.json"), w_of(dir, "mcm1_y.json"), w_of(dir, "mcm2_y.json"), w_of(dir, "mcm3_y.json")],
|
|
icci,
|
|
};
|
|
let norm = NormTables {
|
|
pmf: nv["pmf"].as_array().unwrap().iter().map(|r| r.as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect()).collect(),
|
|
bounds: i64v(&nv, "bounds"),
|
|
per_channel: nv["per_channel"].as_u64().unwrap() as usize,
|
|
cdfs_y: i64v(&nv, "cdfs_y"),
|
|
cdfs_uv: i64v(&nv, "cdfs_uv"),
|
|
gain_y: i64v(&nv, "gain_y"),
|
|
gain_uv: i64v(&nv, "gain_uv"),
|
|
lsbs_t0: i32v(&nv, "lsbs_t0"),
|
|
lsbs_t1: i32v(&nv, "lsbs_t1"),
|
|
lef_thr: i3(&nv, "lef_thr0"),
|
|
lef_mag: f3(&nv, "lef_mag0"),
|
|
};
|
|
(data, weights, norm)
|
|
}
|
|
}
|
|
|
|
fn jpegai_cases() -> (Vec<Case>, Option<String>) {
|
|
let env = |k: &str| std::env::var(k).ok().filter(|v| !v.is_empty());
|
|
let (Some(cs), Some(dir), Some(nf)) = (env("WAI_JPEGAI_CODESTREAM"), env("WAI_JPEGAI_WEIGHTS_DIR"), env("WAI_JPEGAI_NORM")) else {
|
|
return (vec![], Some("WAI_JPEGAI_CODESTREAM, WAI_JPEGAI_WEIGHTS_DIR and WAI_JPEGAI_NORM are not all set (tools/wai_jpegai_fetch_e2e_inputs.sh)".into()));
|
|
};
|
|
let (data, weights, norm) = jpegai::load(&cs, &dir, &nf);
|
|
let run = move || -> Out { wai::jpegai_decode::decode(&data, &weights, &norm).map(|i| [(i.h as u64).to_le_bytes().to_vec(), (i.w as u64).to_le_bytes().to_vec(), i.data.clone()].concat()).map_err(|r| format!("{:?}", r.code())) };
|
|
(vec![Case { cap: "wai.image.jpegai", name: "jpegai/v1 test256".into(), refusal: false, run: Box::new(run) }], None)
|
|
}
|
|
|
|
/// What one capability's cases did on one adapter.
|
|
#[derive(Default)]
|
|
struct Tally {
|
|
cases: u32,
|
|
refusals: u32,
|
|
stats: Stats,
|
|
differ: Vec<String>,
|
|
}
|
|
|
|
#[test]
|
|
fn every_capability_decodes_alike_with_its_graphs_on_every_adapter() {
|
|
let d = wai_ops_gpu::blocking::discover();
|
|
for (a, why) in &d.refused {
|
|
report(&format!("ops-gpu adapter refused: key={} {} reason={why:?}", a.key(), a.describe()));
|
|
}
|
|
if d.found.is_empty() {
|
|
report("ops-gpu capabilities: 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 cases = payload_cases();
|
|
cases.extend(synth_cases());
|
|
cases.extend(mlicpp_cases());
|
|
cases.extend(world_cases());
|
|
let (jp, missing) = jpegai_cases();
|
|
cases.extend(jp);
|
|
if let Some(why) = &missing {
|
|
report(&format!("ops-gpu capability: wai.image.jpegai NOT RUN: {why}"));
|
|
assert!(std::env::var_os("WAI_REQUIRE_SOTA_VECTORS").is_none(), "WAI_REQUIRE_SOTA_VECTORS is set and the jpegai/v1 inputs are absent");
|
|
}
|
|
// The CPU's result for every case, once.
|
|
let want: Vec<Out> = cases.iter().map(|c| (c.run)()).collect();
|
|
for (c, w) in cases.iter().zip(&want) {
|
|
assert_eq!(c.refusal, w.is_err(), "{} {}: a vector decodes and a refusal fixture is refused, on the CPU: {:?}", c.cap, c.name, w.as_ref().err());
|
|
}
|
|
for g in d.found.into_iter().map(Arc::new) {
|
|
let a = g.adapter().clone();
|
|
let mut by_cap: BTreeMap<&'static str, Tally> = BTreeMap::new();
|
|
for (c, w) in cases.iter().zip(&want) {
|
|
let (got, stats) = gpu::with_gpu_compared(&g, || (c.run)());
|
|
let t = by_cap.entry(c.cap).or_default();
|
|
t.cases += 1;
|
|
t.refusals += c.refusal as u32;
|
|
if &got != w {
|
|
t.differ.push(format!("{}: {}", c.name, match (w, &got) {
|
|
(Ok(x), Ok(y)) => format!("{} bytes vs {} bytes, first difference at {:?}", x.len(), y.len(), x.iter().zip(y).position(|(p, q)| p != q)),
|
|
_ => format!("{:?} vs {:?}", w.as_ref().map(|v| v.len()), got.as_ref().map(|v| v.len())),
|
|
}));
|
|
}
|
|
t.stats.acc32 += stats.acc32;
|
|
t.stats.acc64 += stats.acc64;
|
|
for (k, n) in stats.cpu {
|
|
*t.stats.cpu.entry(k).or_default() += n;
|
|
}
|
|
t.stats.errors.extend(stats.errors);
|
|
t.stats.compared += stats.compared;
|
|
t.stats.differed += stats.differed;
|
|
}
|
|
for (cap, t) in &by_cap {
|
|
let cpu: Vec<String> = t.stats.cpu.iter().map(|(k, n)| format!("{n}x {k}")).collect();
|
|
report(&format!(
|
|
"ops-gpu capability: key={} {} cap={cap} cases={} refusals={} graphs_gpu_acc32={} graphs_gpu_acc64={} graphs_cpu={} [{}] graphs_compared={} graphs_differ={} differ={} errors={}",
|
|
a.key(),
|
|
a.describe(),
|
|
t.cases,
|
|
t.refusals,
|
|
t.stats.acc32,
|
|
t.stats.acc64,
|
|
t.stats.on_cpu(),
|
|
cpu.join("; "),
|
|
t.stats.compared,
|
|
t.stats.differed,
|
|
t.differ.len(),
|
|
t.stats.errors.len()
|
|
));
|
|
}
|
|
for (cap, t) in &by_cap {
|
|
assert!(t.differ.is_empty(), "{} {cap}: {:?}", a.key(), t.differ);
|
|
assert!(t.stats.errors.is_empty(), "{} {cap}: device errors {:?}", a.key(), t.stats.errors);
|
|
assert_eq!(t.stats.differed, 0, "{} {cap}: a graph's outputs on the device differ from the interpreter's", a.key());
|
|
assert_eq!(t.stats.compared, t.stats.on_gpu(), "{} {cap}: every graph the device ran is compared", a.key());
|
|
if *cap != "wai.video.int_motion" {
|
|
assert!(t.stats.on_gpu() > 0, "{} {cap}: no graph ran on the device", a.key());
|
|
} else {
|
|
assert_eq!(t.stats.on_gpu() + t.stats.on_cpu(), 0, "{} {cap}: int_motion runs no graph", a.key());
|
|
}
|
|
if *cap != "wai.image.jpegai" {
|
|
assert_eq!(t.stats.on_cpu(), 0, "{} {cap}: every graph of this capability is in wai-ops/1-i32", a.key());
|
|
}
|
|
}
|
|
}
|
|
}
|