All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / 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 7s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m25s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
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 6s
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 6s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m4s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m15s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 6m19s
wai-fleet service / service · macos (pull_request) Successful in 1m2s
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 2m46s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m43s
wai-ledger service / service · macos (pull_request) Successful in 1m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m18s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m19s
wai-pack / wai-pack · macos (pull_request) Successful in 3m8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m49s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m9s
wai-fleet service / service · linux (pull_request) Successful in 2m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m28s
wai-ledger service / service · linux (pull_request) Successful in 1m43s
wai-pack / wai-pack · linux (pull_request) Successful in 1m55s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m0s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m10s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m12s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m2s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m3s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m34s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m22s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m46s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 34s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m28s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m7s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 14m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m34s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m16s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 11m4s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m6s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m58s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 10m55s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 9m51s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m21s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 30m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h4m7s
Every change to WAI added an entry at the head of wai/CHANGELOG.md, and every new module appended to the hand-kept lists of .github/workflows/wai-conformance.yml. So any two branches conflicted on those lines and had to merge one after another. This moves both into files a change adds. - Changelog entries: a change adds wai/changes/<yyyy-mm-dd>-<slug>.md holding the entry text. scripts/changelog-assemble.py renders the entries into CHANGELOG.md's Unreleased section for a release, in a stable order. scripts/check-changelog-fragment.py, run by repo-checks on every pull request, fails a pull request that changes wai/ and adds or edits no entry file directly in wai/changes/ (a release passes, and so does a commit with the trailer line "Changelog: none"). A pull request opened before the check reached main passes as it is; one opened after it that adds its entry to the head of Unreleased fails and is told where the entry goes. wai breaking reads breaking changes from entry files too. - The conformance workflow: each per-module verifier step is a file in .github/wai-conformance/verifiers/, each corpus a file in corpora/, and the integer-kernel tests carry a tag line. scripts/wai-conformance-lists.py derives every list the workflow kept by hand (WAI_FEATURES, the union --bin and --test lists, the features-alone lists, the kernel and corpus steps) from those files when the workflow runs. Its `selftest --exact` holds the derived lists to the hand lists of the commit before this one, and its dry-run prints every command each job runs: the dry-runs before and after this change differ only in the gate's path list, which gains the two paths this change adds. - What stays shared, and how to insert there, is written in wai/CONTRIBUTING.md and wai/changes/README.md: wai-rs Cargo.toml features and [[test]] entries, lib.rs, registry rows, wai/.gitattributes and the live corpus table take an insert beside the related entry, never at the end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
385 lines
20 KiB
Rust
385 lines
20 KiB
Rust
// wai-conformance: int-kernels int-kernels-amd
|
||
//! `wai.neural.int_synth` through the integer operator set: its lowering `int_synth/1`
|
||
//! (`extensions/integer-operators.md` §13.1) and the certificate of every committed
|
||
//! set (§8.5, §14.3), as this crate loads them (`int_ops`).
|
||
//!
|
||
//! The lowered graphs and certificates are committed under `ops-conformance/certificates/`,
|
||
//! where `wai-verify` and the independent checker (`tools/wai_ops_check.py`) each
|
||
//! reproduce them byte for byte (`wai-ops.yml`). Here the lowering this crate runs must
|
||
//! write those same bytes, and the load a sink makes must give the verdict the
|
||
//! certificate records.
|
||
//!
|
||
//! The decode through the lowering (`int_synth::decode_from_spec`, the one a sink runs) must give, on
|
||
//! every committed vector, the bytes of the direct decode it replaced
|
||
//! (`int_synth::legacy`, behind the test-only feature `legacy_int`, which this file
|
||
//! needs) and the vector's committed digests. Every refusal of the direct decode is a
|
||
//! refusal of the decode through the lowering, with the same code: integer-payloads §13
|
||
//! gives one, `payload_malformed`.
|
||
|
||
use std::path::PathBuf;
|
||
|
||
use serde_json::Value;
|
||
use wai_ops::interp::Tensor;
|
||
use wai_ops::{Code, Profile, Site};
|
||
|
||
const CAP: &str = "wai.neural.int_synth";
|
||
|
||
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 sha256(b: &[u8]) -> String {
|
||
wai_ops::sha256::sha256(b).iter().map(|x| format!("{x:02x}")).collect()
|
||
}
|
||
|
||
/// The `int_synth` sets an index of `ops-conformance/certificates/` lists:
|
||
/// `(name, set file, set digest, lowered graph file)`, each relative to `wai/`.
|
||
fn indexed(index: &str) -> Vec<(String, String, String, String)> {
|
||
json(index)["sets"]
|
||
.as_array()
|
||
.unwrap()
|
||
.iter()
|
||
.filter(|s| s["capability"] == CAP)
|
||
.map(|s| {
|
||
assert_eq!(s["lowering"], "int_synth/1");
|
||
let roles = s["roles"].as_array().unwrap();
|
||
assert_eq!(roles.len(), 1, "int_synth/1 lowers to one graph, synth");
|
||
(
|
||
s["name"].as_str().unwrap().to_string(),
|
||
s["members"]["int_synth.json"].as_str().unwrap().to_string(),
|
||
s["set_sha256"].as_str().unwrap().to_string(),
|
||
roles[0].as_str().unwrap().to_string(),
|
||
)
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
/// The certificate of the set with digest `d`.
|
||
fn certificate(d: &str) -> Value {
|
||
json(&format!("ops-conformance/certificates/{CAP}/{d}.json"))
|
||
}
|
||
|
||
/// Gate 1 of the migration: every committed `int_synth` set lowers to its committed
|
||
/// graph, the certificate beside it is that graph's, and loading the graph under the
|
||
/// capability's profile gives the certificate's verdict. The neural-conformance corpus
|
||
/// meets `acc64`; facade, gradient and bloom meet `acc32` too, and photo does not.
|
||
#[test]
|
||
fn every_committed_set_lowers_to_its_committed_graph_and_verdict() {
|
||
let sets = indexed("ops-conformance/certificates/index.json");
|
||
let mut names: Vec<&str> = sets.iter().map(|s| s.0.as_str()).collect();
|
||
names.sort();
|
||
assert_eq!(names, ["bloom", "facade", "gradient", "photo", "synth"], "the committed int_synth sets");
|
||
for (name, set, digest, role) in &sets {
|
||
let bytes = read(set);
|
||
let lowered = wai_ops::lower::int_synth(&bytes).unwrap_or_else(|r| panic!("{name}: does not lower: {r:?}"));
|
||
let roles: Vec<&str> = lowered.roles.iter().map(|(r, _)| *r).collect();
|
||
assert_eq!(roles, ["synth"], "{name}");
|
||
let g = lowered.get("synth").unwrap();
|
||
// The verdict first: the load a sink makes, then the profile each set meets.
|
||
let loaded = wai::int_ops::load(g).unwrap_or_else(|r| panic!("{name}: refused under acc64: {r:?}"));
|
||
let acc32 = matches!(name.as_str(), "facade" | "gradient" | "bloom" | "synth");
|
||
assert_eq!(loaded.meets(Profile::Acc32), acc32, "{name}: whether it meets acc32");
|
||
// Then the committed bytes: the set, its lowered graph and its certificate.
|
||
assert_eq!(&sha256(&bytes), digest, "{name}: the set's digest");
|
||
let graph = g.to_bytes();
|
||
assert!(graph == read(role), "{name}: the lowering differs from the committed {role}");
|
||
let cert = certificate(digest);
|
||
assert_eq!(cert["set_sha256"].as_str(), Some(digest.as_str()), "{name}");
|
||
assert_eq!(cert["graphs"][0]["name"], "synth", "{name}");
|
||
assert_eq!(cert["graphs"][0]["graph_sha256"].as_str(), Some(sha256(&graph).as_str()), "{name}: the certificate is the graph's");
|
||
assert_eq!(cert["verdict"]["acc64"], true, "{name}");
|
||
assert_eq!(Value::Bool(loaded.meets(Profile::Acc32)), cert["verdict"]["acc32"], "{name}: the certificate's verdict on acc32");
|
||
}
|
||
// The browser demo serves byte copies of the corpus's sets.
|
||
for name in ["bloom", "facade", "gradient", "photo"] {
|
||
let copy = sha256(&read(&format!("wai-web/demo/int_synth/{name}/int_synth.json")));
|
||
assert!(sets.iter().any(|s| s.2 == copy), "wai-web/demo/int_synth/{name}: not a committed set");
|
||
}
|
||
}
|
||
|
||
/// The two falsification sets of §14.3. `overflow_acc64`'s accumulator passes i64 by
|
||
/// one unit, so the capability's load refuses it at load step 8; `claim_acc32`'s
|
||
/// passes i32 by one unit, so it loads under `acc64` and does not meet `acc32`.
|
||
#[test]
|
||
fn the_falsification_sets_fail_the_profile_they_claim() {
|
||
let sets = indexed("ops-conformance/certificates/falsification.json");
|
||
let mut names: Vec<&str> = sets.iter().map(|s| s.0.as_str()).collect();
|
||
names.sort();
|
||
assert_eq!(names, ["claim_acc32", "overflow_acc64"]);
|
||
for (name, set, digest, role) in &sets {
|
||
let bytes = read(set);
|
||
assert_eq!(&sha256(&bytes), digest, "{name}");
|
||
let lowered = wai_ops::lower::int_synth(&bytes).unwrap();
|
||
let g = lowered.get("synth").unwrap();
|
||
assert!(g.to_bytes() == read(role), "{name}: the lowering differs from the committed {role}");
|
||
let cert = certificate(digest);
|
||
match name.as_str() {
|
||
"overflow_acc64" => {
|
||
assert_eq!(cert["verdict"]["acc64"], false);
|
||
let r = wai::int_ops::load(g).expect_err("the overflow set is refused");
|
||
assert_eq!((r.code, r.at), (Code::ModelMismatch, Site::Load(8)));
|
||
}
|
||
_ => {
|
||
assert_eq!((cert["verdict"]["acc64"].as_bool(), cert["verdict"]["acc32"].as_bool()), (Some(true), Some(false)));
|
||
assert!(!wai::int_ops::load(g).expect("acc64").meets(Profile::Acc32));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Gate 2 of the migration: golden equality. Every committed `int_synth` vector decodes
|
||
/// through the lowering to the bytes the direct decode gives, and to every digest the
|
||
/// corpus commits for it: the SHA-256 sidecar, the BLAKE3 `rgb_hash` with its sizes,
|
||
/// and photo's reference RGB from the Python integer forward. The browser demo serves
|
||
/// byte copies of the corpus's payloads and digests.
|
||
#[test]
|
||
fn every_committed_vector_decodes_through_the_lowering_to_its_golden() {
|
||
let hashes = json("neural-conformance/expected_hashes.json");
|
||
let sidecar = json("neural-conformance/sha256.json");
|
||
assert!(read("wai-web/demo/int_synth/expected_hashes.json") == read("neural-conformance/expected_hashes.json"));
|
||
let mut n = 0;
|
||
for (name, want) in hashes.as_object().unwrap() {
|
||
let dir = format!("neural-conformance/vectors/{name}");
|
||
let (payload, set) = (read(&format!("{dir}/latent.bin")), read(&format!("{dir}/int_synth.json")));
|
||
assert!(payload == read(&format!("wai-web/demo/int_synth/{name}/latent.bin")), "{name}: the demo's copy");
|
||
let ops = wai::int_synth::decode_from_spec(&payload, &set).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||
let direct = wai::int_synth::legacy::decode_from_spec(&payload, &set).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||
assert!(ops == direct, "{name}: the decode through the lowering differs from the direct decode");
|
||
assert_eq!((ops.width as u64, ops.height as u64), (want["width"].as_u64().unwrap(), want["height"].as_u64().unwrap()), "{name}");
|
||
assert_eq!(sha256(&ops.rgb), sidecar[name]["sha256"].as_str().unwrap(), "{name}: the SHA-256 sidecar");
|
||
let mut h = blake3::Hasher::new();
|
||
h.update(b"wai:int-synth-rgb\x01");
|
||
h.update(&ops.rgb);
|
||
assert_eq!(h.finalize().to_hex().as_str(), want["rgb_hash"].as_str().unwrap(), "{name}: rgb_hash");
|
||
if let Ok(r) = std::fs::read(wai().join(format!("{dir}/ref_rgb.bin"))) {
|
||
assert!(ops.rgb == r, "{name}: the Python integer-forward reference");
|
||
}
|
||
n += 1;
|
||
}
|
||
assert_eq!(n, 4, "the four committed int_synth vectors");
|
||
}
|
||
|
||
/// A payload of `C` channels at side `2^s_log2` for an `H`×`W` output: the latents are
|
||
/// `fill`, and `extra` bytes are added (negative: removed) at the end.
|
||
fn payload(h: u32, w: u32, c: u16, s_log2: u8, fill: u8, extra: i64) -> Vec<u8> {
|
||
let mut p = Vec::new();
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(w.to_le_bytes());
|
||
p.extend(c.to_le_bytes());
|
||
p.extend([s_log2, 0]);
|
||
let n = (c as usize) << (2 * s_log2 as usize);
|
||
p.resize((12 + n as i64 + extra) as usize, fill);
|
||
p
|
||
}
|
||
|
||
/// One layer of `int_synth.json`, every weight `w`.
|
||
fn layer(in_ch: u64, out_ch: u64, k: u64, stride: u64, pad: u64, w: i64) -> Value {
|
||
serde_json::json!({
|
||
"in_ch": in_ch, "out_ch": out_ch, "k": k, "stride": stride, "pad": pad,
|
||
"weight": vec![w; (in_ch * out_ch * k * k) as usize], "bias": vec![1; out_ch as usize], "shift": 1, "relu": true,
|
||
})
|
||
}
|
||
|
||
fn set(layers: Vec<Value>) -> Vec<u8> {
|
||
serde_json::to_vec(&serde_json::json!({ "layers": layers })).unwrap()
|
||
}
|
||
|
||
/// The fixtures: one for each refusal the direct decode makes (integer-payloads §13),
|
||
/// each beside a payload and set it decodes. One layer, 1 → 3 channels, k 2, stride 2,
|
||
/// on a 1×1 latent: a 2×2 output.
|
||
fn refusal_fixtures() -> Vec<(&'static str, Vec<u8>, Vec<u8>)> {
|
||
let good_set = set(vec![layer(1, 3, 2, 2, 0, 3)]);
|
||
let good = payload(2, 2, 1, 0, 9, 0);
|
||
let edit = |f: &dyn Fn(&mut Value)| {
|
||
let mut v: Value = serde_json::from_slice(&good_set).unwrap();
|
||
f(&mut v);
|
||
serde_json::to_vec(&v).unwrap()
|
||
};
|
||
let p = |what, pay: Vec<u8>| (what, pay, good_set.clone());
|
||
let q = |what, s: Vec<u8>| (what, good.clone(), s);
|
||
vec![
|
||
p("no payload", vec![]),
|
||
p("a payload shorter than its header", good[..11].to_vec()),
|
||
p("a latent short", payload(2, 2, 1, 0, 9, -1)),
|
||
p("a byte past the latents", payload(2, 2, 1, 0, 9, 1)),
|
||
p("a side of 2^64", { let mut v = good.clone(); v[10] = 64; v }),
|
||
p("a side of 2^255", { let mut v = good.clone(); v[10] = 255; v }),
|
||
p("C·S·S past the word", { let mut v = payload(2, 2, 1, 0, 9, 0); v[8..10].copy_from_slice(&u16::MAX.to_le_bytes()); v[10] = 31; v }),
|
||
p("an output taller than H", payload(1, 2, 1, 0, 9, 0)),
|
||
p("an output narrower than W", payload(2, 3, 1, 0, 9, 0)),
|
||
p("H of 0", payload(0, 2, 1, 0, 9, 0)),
|
||
p("first in_ch is not C", payload(2, 2, 2, 0, 9, 0)),
|
||
q("a set that is not JSON", b"{".to_vec()),
|
||
q("a set with no layers member", b"{}".to_vec()),
|
||
q("layers that is not a list", br#"{"layers":{}}"#.to_vec()),
|
||
q("a layer that is not an object", br#"{"layers":[1]}"#.to_vec()),
|
||
q("a layer without k", edit(&|v| { v["layers"][0].as_object_mut().unwrap().remove("k"); })),
|
||
q("a negative stride", edit(&|v| v["layers"][0]["stride"] = (-2).into())),
|
||
q("a k with a fraction", edit(&|v| v["layers"][0]["k"] = 2.5.into())),
|
||
q("a negative shift", edit(&|v| v["layers"][0]["shift"] = (-1).into())),
|
||
q("a relu that is not a boolean", edit(&|v| v["layers"][0]["relu"] = 1.into())),
|
||
q("a weight short", edit(&|v| { v["layers"][0]["weight"].as_array_mut().unwrap().pop(); })),
|
||
q("a bias past out_ch", edit(&|v| v["layers"][0]["bias"].as_array_mut().unwrap().push(0.into()))),
|
||
q("a weight past i32", edit(&|v| v["layers"][0]["weight"][0] = (1i64 << 31).into())),
|
||
q("a bias below i32", edit(&|v| v["layers"][0]["bias"][0] = (-(1i64 << 31) - 1).into())),
|
||
q("layers that do not chain", set(vec![layer(1, 2, 1, 1, 0, 1), layer(3, 3, 2, 2, 0, 1)])),
|
||
q("four output channels", set(vec![layer(1, 4, 2, 2, 0, 3)])),
|
||
q("an output side of 0", set(vec![layer(1, 3, 2, 2, 1, 3)])),
|
||
]
|
||
}
|
||
|
||
/// The code a sink reports for a decode of `wai.neural.int_synth` (`int_payload`, the
|
||
/// integer decode path the dispatch takes).
|
||
fn code(payload: &[u8], set: &[u8]) -> Result<(), &'static str> {
|
||
let params = wai::int_payload::resolve(CAP, None, &[wai::pin::HeldBytes::File(set)], None).expect("an unpinned set resolves");
|
||
wai::int_payload::decode_int(CAP, payload, ¶ms).map(|_| ()).map_err(|e| e.code())
|
||
}
|
||
|
||
/// Gate 3 of the migration: refusal parity. Each fixture, refused by the direct decode,
|
||
/// is refused by the decode through the lowering, and a sink reports it as
|
||
/// `payload_malformed`, the one code integer-payloads §13 gives. Beside them, the
|
||
/// payload and set they are edited from decode both ways.
|
||
#[test]
|
||
fn every_refusal_of_the_direct_decode_is_refused_through_the_lowering() {
|
||
let good = (payload(2, 2, 1, 0, 9, 0), set(vec![layer(1, 3, 2, 2, 0, 3)]));
|
||
let a = wai::int_synth::decode_from_spec(&good.0, &good.1).expect("the fixtures' base decodes");
|
||
assert_eq!(Ok(a.clone()), wai::int_synth::legacy::decode_from_spec(&good.0, &good.1));
|
||
assert_eq!(a.rgb, [14u8; 12], "(9·3 + 1) >> 1 at every sample");
|
||
assert_eq!(code(&good.0, &good.1), Ok(()));
|
||
let fixtures = refusal_fixtures();
|
||
for (what, pay, s) in &fixtures {
|
||
let direct = wai::int_synth::legacy::decode_from_spec(pay, s);
|
||
let ops = wai::int_synth::decode_from_spec(pay, s);
|
||
assert!(direct.is_err(), "{what}: the direct decode refuses it: {direct:?}");
|
||
assert!(ops.is_err(), "{what}: the decode through the lowering refuses it: {ops:?}");
|
||
assert_eq!(code(pay, s), Err("payload_malformed"), "{what}");
|
||
}
|
||
}
|
||
|
||
/// The lowered graph's one `check` range, its input's `[−128, 127]` (§13.0's driver
|
||
/// contract), at its ends and one past them: a payload's latents are signed bytes, so
|
||
/// only a graph-level decode reaches past them, and the graph refuses it (`field_range`,
|
||
/// at input 0).
|
||
#[test]
|
||
fn the_latent_range_refuses_one_past_each_end() {
|
||
let lowered = wai_ops::lower::int_synth(&read("neural-conformance/vectors/gradient/int_synth.json")).unwrap();
|
||
let g = wai::int_ops::load(lowered.get("synth").unwrap()).unwrap();
|
||
let (c, s) = (4u32, 8u32);
|
||
let x = |v: i64| {
|
||
let mut d = vec![0i64; (c * s * s) as usize];
|
||
d[37] = v;
|
||
Tensor::new(c, s, s, d)
|
||
};
|
||
for v in [-128, 127] {
|
||
assert!(wai::int_ops::run1(&g, x(v), &[]).is_ok(), "{v} is a latent");
|
||
}
|
||
for v in [-129, 128] {
|
||
let r = wai::int_ops::run1(&g, x(v), &[]).expect_err("one past the latent range");
|
||
assert_eq!((r.code, r.at), (Code::FieldRange, Site::Input(0)), "{v}");
|
||
}
|
||
}
|
||
|
||
/// Gate 5 of the migration, the whole-decoder half: the decode through the lowering
|
||
/// under the reference kernels (the interpreter computes every operator) and under each
|
||
/// fast policy (the transposed convolutions on this crate's fast kernels, serial and
|
||
/// threaded) gives the same bytes on every committed vector, and on the fixtures' base
|
||
/// case. The per-operator half is `int_ops`' equivalence tests.
|
||
#[test]
|
||
fn every_kernel_policy_decodes_through_the_lowering_alike() {
|
||
use wai::int_kernels::{with_policy, Policy};
|
||
static POLICIES: std::sync::LazyLock<Vec<Policy>> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies);
|
||
let mut cases: Vec<(String, Vec<u8>, Vec<u8>)> = json("neural-conformance/expected_hashes.json")
|
||
.as_object()
|
||
.unwrap()
|
||
.keys()
|
||
.map(|name| {
|
||
let dir = format!("neural-conformance/vectors/{name}");
|
||
(name.clone(), read(&format!("{dir}/latent.bin")), read(&format!("{dir}/int_synth.json")))
|
||
})
|
||
.collect();
|
||
cases.push(("the fixtures' base".into(), payload(2, 2, 1, 0, 9, 0), set(vec![layer(1, 3, 2, 2, 0, 3)])));
|
||
for (name, p, s) in &cases {
|
||
let want = with_policy(POLICIES[0], || wai::int_synth::decode_from_spec(p, s)).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||
for pol in &POLICIES[1..] {
|
||
let got = with_policy(*pol, || wai::int_synth::decode_from_spec(p, s));
|
||
assert!(got.as_ref() == Ok(&want), "{name}: {pol:?} differs from the interpreter alone");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The committed `int_synth` sets and payloads: every directory under
|
||
/// `neural-conformance/vectors/`, the browser demo's `wai-web/demo/int_synth/` and
|
||
/// `ops-conformance/sets/` that holds an `int_synth.json`, with its `latent.bin` where
|
||
/// it has one. Relative to `wai/`.
|
||
fn committed_sets_and_payloads() -> Vec<(String, Option<String>)> {
|
||
let mut v = Vec::new();
|
||
for root in ["neural-conformance/vectors", "wai-web/demo/int_synth", "ops-conformance/sets"] {
|
||
let mut dirs: Vec<String> = std::fs::read_dir(wai().join(root))
|
||
.unwrap()
|
||
.filter_map(|e| e.ok())
|
||
.filter(|e| e.path().join("int_synth.json").exists())
|
||
.map(|e| format!("{root}/{}", e.file_name().to_string_lossy()))
|
||
.collect();
|
||
dirs.sort();
|
||
for d in dirs {
|
||
let p = format!("{d}/latent.bin");
|
||
let payload = wai().join(&p).exists().then_some(p);
|
||
v.push((format!("{d}/int_synth.json"), payload));
|
||
}
|
||
}
|
||
v
|
||
}
|
||
|
||
/// O12: the load rules the switch brings (integer-operators §15.3 item 1, and the two
|
||
/// the lowering makes that its text now states) affect no committed set or payload.
|
||
/// Every committed set has layers, each with `out_ch`, `k` and `stride` at least 1,
|
||
/// fields that fit 32 bits and a shift of at most 63 (so none from 64 to 127 changes
|
||
/// meaning either), and lowers and loads under `acc64`; the one exception is
|
||
/// `overflow_acc64`, the falsification set made to fail its certificate. Every
|
||
/// committed payload has a reserved byte of 0, `C ≥ 1` and `C·S·S ≤ 2^26`, is exactly
|
||
/// its length, and decodes through the lowering against its set.
|
||
#[test]
|
||
fn the_new_load_rules_affect_no_committed_set_or_payload() {
|
||
let all = committed_sets_and_payloads();
|
||
let (mut sets, mut payloads) = (0, 0);
|
||
for (set_path, payload_path) in &all {
|
||
let bytes = read(set_path);
|
||
let v: Value = serde_json::from_slice(&bytes).unwrap();
|
||
let layers = v["layers"].as_array().unwrap();
|
||
assert!(!layers.is_empty(), "{set_path}: no layers");
|
||
for (i, l) in layers.iter().enumerate() {
|
||
let f = |k: &str, d: u64| l.get(k).map_or(Some(d), Value::as_u64).unwrap_or_else(|| panic!("{set_path} layer {i}: {k}"));
|
||
for k in ["in_ch", "out_ch", "k", "stride", "pad"] {
|
||
assert!(f(k, u64::MAX) <= u32::MAX as u64, "{set_path} layer {i}: {k} past 32 bits");
|
||
}
|
||
assert!(f("out_pad", 0) <= u32::MAX as u64, "{set_path} layer {i}: out_pad");
|
||
assert!(f("out_ch", 0) >= 1 && f("k", 0) >= 1 && f("stride", 0) >= 1, "{set_path} layer {i}: out_ch, k or stride of 0");
|
||
assert!(f("shift", 0) <= 63, "{set_path} layer {i}: a shift of 64 or more");
|
||
}
|
||
let lowered = wai_ops::lower::int_synth(&bytes).unwrap_or_else(|r| panic!("{set_path}: does not lower: {r:?}"));
|
||
let loaded = wai::int_ops::load(lowered.get("synth").unwrap());
|
||
if set_path.contains("/overflow_acc64/") {
|
||
assert!(loaded.is_err(), "{set_path}: the falsification set fails acc64");
|
||
} else {
|
||
assert!(loaded.is_ok(), "{set_path}: does not load under acc64: {loaded:?}");
|
||
}
|
||
sets += 1;
|
||
let Some(p) = payload_path else { continue };
|
||
let b = read(p);
|
||
let c = u16::from_le_bytes([b[8], b[9]]) as u64;
|
||
let n = c << (2 * b[10] as u64);
|
||
assert_eq!(b[11], 0, "{p}: the reserved byte");
|
||
assert!(c >= 1 && n <= 1 << 26 && b.len() as u64 == 12 + n, "{p}: C, the cap and the length");
|
||
assert!(wai::int_synth::decode_from_spec(&b, &bytes).is_ok(), "{p}: decodes through the lowering");
|
||
payloads += 1;
|
||
}
|
||
assert!(sets >= 11 && payloads >= 8, "the committed int_synth sets and payloads, at least those of this revision: {all:?}");
|
||
}
|