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]>
3188 lines
163 KiB
Rust
3188 lines
163 KiB
Rust
// wai-conformance: int-kernels
|
||
//! Each integer decoder through the integer operator set against its direct decode
|
||
//! (`legacy_int`, test-only), on random parameter sets and payloads. The two must give
|
||
//! the same output, or both refuse with the same code. The only inputs on which they may
|
||
//! differ are the planned breaks the operator set makes (`extensions/integer-operators.md`
|
||
//! §15.3), which are new refusals: each case is classified from the payload and the set
|
||
//! alone, and a case that falls under a planned break must be refused by the decode
|
||
//! through the operator set, whatever the direct decode does there.
|
||
//!
|
||
//! The decoders: `wai.neural.int_synth` (`int_synth/1`), `wai.splat.int_codec`
|
||
//! (`whm1/1`), `wai.audio.int_codec` (`wam1/1`), `wai.neural.int_hyper` (`whm1/1`, its
|
||
//! two networks; the keyframe of `wai.video.int_hyper` is the same decode) and
|
||
//! `wai.neural.int_mlicpp` (`wmb1/1`, its three networks); and `wai.world.model`'s integer
|
||
//! renderer (`world/1`, informative: `T` and `g_s`), against its direct renderer with the
|
||
//! direct guard every other decoder ran; and `wai.image.jpegai`'s scale path and entropy
|
||
//! decodes (`jpegai-int/1`), against its direct computation, and its hyper decoders,
|
||
//! context stages and synthesis (the graphs of §13.6.2), against its direct networks; and
|
||
//! `WIV1` and `WIR1`
|
||
//! (`wai.video.int_motion` and the P-frames of `wai.video.int_hyper`, and the four
|
||
//! `int_refine` capabilities), through the stage operators alone, which make no planned
|
||
//! break (with `video_int` and `staged`).
|
||
//!
|
||
//! The same generators hold load step 8 to its oracle: the bounds pass, whose
|
||
//! convolutions and inverse GDNs are in closed form, against the pass as it stood before
|
||
//! (`wai_ops::bounds_oracle`, every term folded in 512 bits), on every graph they lower.
|
||
//!
|
||
//! The random cases run for a fixed count by default. `WAI_DIFF_SECS` runs each random
|
||
//! test for that many seconds instead (the nightly run, `wai-ops-differential.yml`), and
|
||
//! `WAI_DIFF_SEED` sets the first seed; a failing case names its seed.
|
||
#![cfg(all(feature = "legacy_int", feature = "splat_int", feature = "audio_int", feature = "world_int"))]
|
||
|
||
use std::panic::catch_unwind;
|
||
use std::path::PathBuf;
|
||
use std::time::{Duration, Instant};
|
||
|
||
use std::panic::AssertUnwindSafe;
|
||
|
||
use serde_json::{json, Value};
|
||
use wai::int_hyper_synth::{IntModel, RefusalKind};
|
||
use wai::int_synth::{decode_from_spec, legacy, IntImage, IntSynthError};
|
||
|
||
fn wai() -> PathBuf {
|
||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
||
}
|
||
|
||
/// A splitmix64 sequence.
|
||
struct Rng(u64);
|
||
|
||
impl Rng {
|
||
fn next(&mut self) -> u64 {
|
||
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
||
let mut z = self.0;
|
||
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
||
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
||
z ^ (z >> 31)
|
||
}
|
||
fn below(&mut self, n: u64) -> u64 {
|
||
self.next() % n
|
||
}
|
||
/// A value in `[−r, r]`.
|
||
fn signed(&mut self, r: i64) -> i64 {
|
||
(self.below(2 * r as u64 + 1) as i64) - r
|
||
}
|
||
}
|
||
|
||
/// Run `case` on successive seeds: `n` of them, or for `WAI_DIFF_SECS` seconds. Each
|
||
/// seed is `WAI_DIFF_SEED` (or a fixed default) mixed with the case's index. Returns how
|
||
/// many ran.
|
||
fn cases(name: &str, n: u64, mut case: impl FnMut(&mut Rng, u64)) -> u64 {
|
||
let secs: Option<u64> = std::env::var("WAI_DIFF_SECS").ok().and_then(|s| s.parse().ok());
|
||
let seed0: u64 = std::env::var("WAI_DIFF_SEED").ok().and_then(|s| s.parse().ok()).unwrap_or(0x0D2C);
|
||
let until = secs.map(|s| Instant::now() + Duration::from_secs(s));
|
||
let mut k = 0u64;
|
||
loop {
|
||
match until {
|
||
Some(t) if Instant::now() >= t => break,
|
||
None if k >= n => break,
|
||
_ => {}
|
||
}
|
||
let seed = seed0 ^ k.wrapping_mul(0xD1B5_4A32_D192_ED03);
|
||
case(&mut Rng(seed), seed);
|
||
k += 1;
|
||
}
|
||
eprintln!("{name}: {k} cases from WAI_DIFF_SEED={seed0}");
|
||
k
|
||
}
|
||
|
||
// ------------------------------------------------------------------ int_synth (M1)
|
||
|
||
const CAP: u128 = 1 << 26;
|
||
|
||
/// The planned break of integer-operators §15.3 item 1 that a payload and set fall
|
||
/// under, if any, read from the bytes alone. `"shift 64 to 127"` is the one that is not a
|
||
/// refusal: such a shift now has its exact meaning, where the direct decode's result
|
||
/// depended on the build.
|
||
fn int_synth_planned_break(payload: &[u8], set: &[u8]) -> Option<&'static str> {
|
||
if payload.len() < 12 {
|
||
return None;
|
||
}
|
||
if payload[11] != 0 {
|
||
return Some("a reserved byte that is not 0");
|
||
}
|
||
let c = u16::from_le_bytes([payload[8], payload[9]]) as u128;
|
||
if c == 0 {
|
||
return Some("C of 0");
|
||
}
|
||
let s_log2 = payload[10] as u32;
|
||
if s_log2 >= 64 {
|
||
return None;
|
||
}
|
||
if (1u128 << (2 * s_log2)).checked_mul(c).is_none_or(|n| n > CAP) {
|
||
return Some("latents past 2^26");
|
||
}
|
||
let Ok(root) = serde_json::from_slice::<Value>(set) else { return None };
|
||
let Some(layers) = root.get("layers").and_then(Value::as_array) else { return None };
|
||
if layers.is_empty() {
|
||
return Some("no layers");
|
||
}
|
||
let field = |l: &Value, k: &str, default: Option<u64>| l.get(k).map_or(default, Value::as_u64);
|
||
let mut shift_64 = false;
|
||
let mut side = 1u128 << s_log2;
|
||
for l in layers {
|
||
let (Some(in_ch), Some(out_ch), Some(k), Some(stride), Some(pad), Some(out_pad), Some(shift)) = (
|
||
field(l, "in_ch", None), field(l, "out_ch", None), field(l, "k", None), field(l, "stride", None),
|
||
field(l, "pad", None), field(l, "out_pad", Some(0)), field(l, "shift", Some(0)),
|
||
) else {
|
||
return None;
|
||
};
|
||
if [in_ch, out_ch, k, stride, pad, out_pad].iter().any(|&v| v > u32::MAX as u64) {
|
||
return Some("a field past 32 bits");
|
||
}
|
||
if shift >= 128 {
|
||
return Some("a shift of 128 or more");
|
||
}
|
||
if k == 0 || stride == 0 {
|
||
return Some("k or stride of 0");
|
||
}
|
||
if out_ch == 0 {
|
||
return Some("out_ch of 0");
|
||
}
|
||
shift_64 |= shift >= 64;
|
||
// The run-time plan's caps (integer-operators §3, §5.3): the uncropped sides and
|
||
// output, then the output.
|
||
let full = (side - 1) * stride as u128 + k as u128 + out_pad as u128;
|
||
if full > CAP || out_ch as u128 * full * full > CAP {
|
||
return Some("an uncropped output past 2^26");
|
||
}
|
||
if full <= 2 * pad as u128 {
|
||
return None;
|
||
}
|
||
side = full - 2 * pad as u128;
|
||
if out_ch as u128 * side * side > CAP {
|
||
return Some("a tensor past 2^26");
|
||
}
|
||
}
|
||
let lowered = wai_ops::lower::int_synth(set).ok()?;
|
||
if wai::int_ops::load(lowered.get("synth")?).is_err() {
|
||
return Some("a certificate that does not meet acc64");
|
||
}
|
||
shift_64.then_some("shift 64 to 127")
|
||
}
|
||
|
||
/// A random `int_synth.json` of 1 to 3 layers and a payload for it: kernels 1 to 4,
|
||
/// strides 1 to 3, paddings mostly below the kernel and output paddings mostly below the
|
||
/// stride, shifts 0 to 24, weights of up to 2^4, 2^10 or 2^14 (one set in eight up to
|
||
/// 2^20, where certificates start to fail), a quarter of them 0, and biases of up to
|
||
/// 2^18; latent channels 1 to 3, sides 1 to 4, a quarter of the latents at the ends of
|
||
/// their range. The payload's `H` and `W` are the output's sides, or (one case in eight)
|
||
/// not; one payload in thirty-two has a reserved byte that is not 0.
|
||
fn int_synth_case(r: &mut Rng) -> (Vec<u8>, Vec<u8>) {
|
||
let c0 = 1 + r.below(3) as usize;
|
||
let s_log2 = r.below(3) as u8;
|
||
let (mut c, mut side) = (c0, 1i64 << s_log2);
|
||
let n_layers = 1 + r.below(3) as usize;
|
||
let big = r.below(8) == 0;
|
||
let mut layers = Vec::new();
|
||
for i in 0..n_layers {
|
||
let out = if i + 1 == n_layers && r.below(16) != 0 { 3 } else { 1 + r.below(4) as usize };
|
||
let k = 1 + r.below(4) as usize;
|
||
let stride = 1 + r.below(3) as usize;
|
||
let pad = if r.below(16) == 0 { r.below(k as u64 + 2) } else { r.below(k as u64) } as usize;
|
||
let out_pad = if r.below(16) == 0 { r.below(stride as u64 + 2) } else { r.below(stride as u64) } as usize;
|
||
let shift = r.below(25);
|
||
let wmax = if big { 1 << 20 } else { [1i64 << 4, 1 << 10, 1 << 14][r.below(3) as usize] };
|
||
let weight: Vec<i64> = (0..c * out * k * k).map(|_| if r.below(4) == 0 { 0 } else { r.signed(wmax) }).collect();
|
||
let bias: Vec<i64> = (0..out).map(|_| r.signed(1 << 18)).collect();
|
||
layers.push(json!({
|
||
"in_ch": c, "out_ch": out, "k": k, "stride": stride, "pad": pad, "out_pad": out_pad,
|
||
"weight": weight, "bias": bias, "shift": shift, "relu": r.below(2) == 1,
|
||
}));
|
||
side = ((side - 1) * stride as i64 + k as i64 + out_pad as i64 - 2 * pad as i64).max(1);
|
||
c = out;
|
||
}
|
||
let (mut h, mut w) = (side as u32, side as u32);
|
||
if r.below(8) == 0 {
|
||
h += 1 + r.below(2) as u32;
|
||
w = w.saturating_sub(r.below(2) as u32);
|
||
}
|
||
let mut p = Vec::new();
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(w.to_le_bytes());
|
||
p.extend((c0 as u16).to_le_bytes());
|
||
p.extend([s_log2, if r.below(32) == 0 { 1 + r.below(255) as u8 } else { 0 }]);
|
||
let n = c0 << (2 * s_log2);
|
||
p.extend((0..n).map(|_| match r.below(8) {
|
||
0 => 0x80,
|
||
1 => 0x7F,
|
||
_ => r.signed(127) as i8 as u8,
|
||
}));
|
||
(serde_json::to_vec(&json!({ "layers": layers })).unwrap(), p)
|
||
}
|
||
|
||
/// The direct decode, a panic (an overflow-checked build's arithmetic overflow) caught.
|
||
fn direct(payload: &[u8], set: &[u8]) -> Option<Result<IntImage, IntSynthError>> {
|
||
catch_unwind(|| legacy::decode_from_spec(payload, set)).ok()
|
||
}
|
||
|
||
/// Gate 4 of the migration: the decode through the operator set against the direct
|
||
/// decode on random sets and payloads. Outside the planned breaks both decode to the
|
||
/// same bytes or both refuse (one code, `payload_malformed`), and the direct decode never
|
||
/// panics; under a planned break the decode through the operator set refuses.
|
||
#[test]
|
||
fn int_synth_random_sets_and_payloads_decode_alike() {
|
||
let (mut alike, mut refused, mut breaks) = (0u64, 0u64, std::collections::BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_synth", 3000, |r, seed| {
|
||
let (set, pay) = int_synth_case(r);
|
||
let new = decode_from_spec(&pay, &set);
|
||
let why = || format!("seed {seed}\nset {}\npayload {:02x?}", String::from_utf8_lossy(&set), pay);
|
||
match int_synth_planned_break(&pay, &set) {
|
||
Some("shift 64 to 127") => *breaks.entry("shift 64 to 127").or_default() += 1,
|
||
Some(b) => {
|
||
assert!(new.is_err(), "{b}: the decode through the operator set must refuse it\n{}", why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
}
|
||
None => match (&new, direct(&pay, &set)) {
|
||
(Ok(a), Some(Ok(b))) => {
|
||
assert!(*a == b, "the two decodes differ\n{}", why());
|
||
alike += 1;
|
||
}
|
||
(Err(_), Some(Err(_))) => refused += 1,
|
||
(_, old) => panic!("new {new:?}, direct {old:?}\n{}", why()),
|
||
},
|
||
}
|
||
});
|
||
eprintln!("int_synth: {alike} decoded alike, {refused} refused by both, planned breaks {breaks:?}");
|
||
assert!(alike * 2 > n, "the generator must mostly produce decodable cases: {alike} of {n}");
|
||
assert!(n < 1000 || breaks.get("a certificate that does not meet acc64").is_some_and(|&b| b > 0), "no case reached a failing certificate");
|
||
}
|
||
|
||
/// One layer of `int_synth.json`: every weight 1, the biases 10, 20 and 30 (or, for
|
||
/// fewer channels, their first `out_ch`).
|
||
fn layer(in_ch: u64, out_ch: u64, k: u64, stride: u64, pad: u64, out_pad: u64, shift: u64) -> Value {
|
||
let bias: Vec<i64> = [10, 20, 30].into_iter().chain(std::iter::repeat(0)).take(out_ch as usize).collect();
|
||
json!({
|
||
"in_ch": in_ch, "out_ch": out_ch, "k": k, "stride": stride, "pad": pad, "out_pad": out_pad,
|
||
"weight": vec![1; (in_ch * out_ch * k * k) as usize], "bias": bias, "shift": shift,
|
||
})
|
||
}
|
||
|
||
fn set(layers: Vec<Value>) -> Vec<u8> {
|
||
serde_json::to_vec(&json!({ "layers": layers })).unwrap()
|
||
}
|
||
|
||
/// A payload for an `h`×`h` output: `C` channels of side 1, reserved byte `reserved`, the
|
||
/// latents `lat`.
|
||
fn payload(h: u32, c: u16, reserved: u8, lat: &[i8]) -> Vec<u8> {
|
||
let mut p = Vec::new();
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(c.to_le_bytes());
|
||
p.extend([0u8, reserved]);
|
||
p.extend(lat.iter().map(|&v| v as u8));
|
||
p
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 item 1, each on a payload and set the
|
||
/// direct decode decodes (or, for a shift of 64 or more, decodes by build): the
|
||
/// classifier names it, and the decode through the operator set refuses it, or, for a
|
||
/// shift from 64 to 127, gives the shift its exact meaning.
|
||
#[test]
|
||
fn int_synth_planned_breaks_are_new_refusals() {
|
||
let one = set(vec![layer(1, 3, 1, 1, 0, 0, 0)]);
|
||
let five = payload(1, 1, 0, &[5]);
|
||
let expect = |b: &str, pay: &[u8], s: &[u8], direct_decodes: bool| {
|
||
assert_eq!(int_synth_planned_break(pay, s), Some(b), "{b}: the classifier");
|
||
if direct_decodes {
|
||
assert!(matches!(direct(pay, s), Some(Ok(_))), "{b}: the direct decode decodes it");
|
||
}
|
||
assert!(decode_from_spec(pay, s).is_err(), "{b}: the decode through the operator set refuses it");
|
||
};
|
||
// The base decodes alike: 5·1 + the bias.
|
||
assert_eq!(int_synth_planned_break(&five, &one), None);
|
||
assert_eq!(decode_from_spec(&five, &one).unwrap().rgb, [15, 25, 35]);
|
||
assert_eq!(direct(&five, &one), Some(decode_from_spec(&five, &one)));
|
||
expect("a reserved byte that is not 0", &payload(1, 1, 7, &[5]), &one, true);
|
||
// C of 0: no latents; the set's one layer reads no channel, so the direct decode's
|
||
// output is the bias.
|
||
expect("C of 0", &payload(1, 0, 0, &[]), &set(vec![layer(0, 3, 1, 1, 0, 0, 0)]), true);
|
||
// No layers: the direct decode presents the latents themselves, clamped, as the image.
|
||
expect("no layers", &payload(1, 3, 0, &[5, -3, 100]), &set(vec![]), true);
|
||
// A layer of no channels: the next reads none, so the output is its bias.
|
||
expect("out_ch of 0", &five, &set(vec![layer(1, 0, 1, 1, 0, 0, 0), layer(0, 3, 1, 1, 0, 0, 0)]), true);
|
||
expect("k or stride of 0", &five, &set(vec![layer(1, 3, 0, 1, 0, 1, 0)]), true);
|
||
expect("k or stride of 0", &five, &set(vec![layer(1, 3, 1, 0, 0, 0, 0)]), true);
|
||
expect("a field past 32 bits", &five, &set(vec![layer(1, 3, 1, 1 << 32, 0, 0, 0)]), true);
|
||
// An uncropped output of 3·8193·8193 cropped to 1×1 that no tap reaches: the direct
|
||
// decode forms no uncropped plane, and its output is the bias.
|
||
expect("an uncropped output past 2^26", &five, &set(vec![layer(1, 3, 3, 1, 1 << 12, (1 << 13) - 2, 0)]), true);
|
||
// The certificate: the falsification set that passes i64 by one unit decodes a
|
||
// small latent directly.
|
||
let overflow = std::fs::read(wai().join("ops-conformance/sets/overflow_acc64/int_synth.json")).unwrap();
|
||
expect("a certificate that does not meet acc64", &payload(1, 1, 0, &[1]), &overflow, true);
|
||
// A shift of 128 or more; the direct decode panics or masks, by build.
|
||
expect("a shift of 128 or more", &five, &set(vec![layer(1, 3, 1, 1, 0, 0, 128)]), false);
|
||
// A shift from 64 to 127 has its exact meaning: every sum here is below 2^64.
|
||
let s64 = set(vec![layer(1, 3, 1, 1, 0, 0, 64)]);
|
||
assert_eq!(int_synth_planned_break(&five, &s64), Some("shift 64 to 127"));
|
||
assert_eq!(decode_from_spec(&five, &s64).unwrap().rgb, [0, 0, 0]);
|
||
}
|
||
|
||
// ------------------------------------------------------------------ int_splat (M2)
|
||
|
||
/// One layer of a `WHM1` network, as a set's bytes hold it (integer-payloads §9).
|
||
#[derive(Clone)]
|
||
enum Whm {
|
||
ConvT { inc: u32, out: u32, k: u32, s: u32, p: u32, op: u32, w: Vec<i16>, m: Vec<i64>, b: Vec<i64> },
|
||
Conv { inc: u32, out: u32, k: u32, s: u32, p: u32, w: Vec<i16>, m: Vec<i64>, b: Vec<i64> },
|
||
Igdn { c: u32, g: Vec<i32>, b: Vec<i64> },
|
||
Relu,
|
||
Leaky { q: i64, f: u32 },
|
||
}
|
||
|
||
/// A `WHM1` network's bytes.
|
||
fn whm1_bytes(afrac: u32, gf: u32, shift: u32, layers: &[Whm]) -> Vec<u8> {
|
||
let mut v = b"WHM1".to_vec();
|
||
for x in [afrac, gf, shift, layers.len() as u32] {
|
||
v.extend(x.to_le_bytes());
|
||
}
|
||
let words = |v: &mut Vec<u8>, xs: &[u32]| xs.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
for l in layers {
|
||
match l {
|
||
Whm::ConvT { inc, out, k, s, p, op, w, m, b } => {
|
||
v.push(0);
|
||
words(&mut v, &[*inc, *out, *k, *s, *p, *op]);
|
||
w.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
m.iter().chain(b).for_each(|x| v.extend(x.to_le_bytes()));
|
||
}
|
||
Whm::Conv { inc, out, k, s, p, w, m, b } => {
|
||
v.push(1);
|
||
words(&mut v, &[*inc, *out, *k, *s, *p]);
|
||
w.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
m.iter().chain(b).for_each(|x| v.extend(x.to_le_bytes()));
|
||
}
|
||
Whm::Igdn { c, g, b } => {
|
||
v.push(2);
|
||
words(&mut v, &[*c]);
|
||
g.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
b.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
}
|
||
Whm::Relu => v.push(3),
|
||
Whm::Leaky { q, f } => {
|
||
v.push(4);
|
||
v.extend(q.to_le_bytes());
|
||
v.extend(f.to_le_bytes());
|
||
}
|
||
}
|
||
}
|
||
v
|
||
}
|
||
|
||
/// A `WIS1` cloud's typed fields, owned.
|
||
#[derive(Clone, Debug)]
|
||
struct Cloud {
|
||
afrac: u32,
|
||
c: usize,
|
||
sh: usize,
|
||
sw: usize,
|
||
s: usize,
|
||
n_attr: usize,
|
||
cdfs: Vec<Vec<i32>>,
|
||
offsets: Vec<i32>,
|
||
median_q: Vec<i64>,
|
||
rans: Vec<u8>,
|
||
}
|
||
|
||
impl Cloud {
|
||
fn st(&self) -> wai::int_splat::SplatStream<'_> {
|
||
wai::int_splat::SplatStream {
|
||
afrac: self.afrac, c: self.c, sh: self.sh, sw: self.sw, s: self.s, n_attr: self.n_attr,
|
||
cdfs: &self.cdfs, offsets: &self.offsets, median_q: &self.median_q, rans: &self.rans,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 that a splat set falls under, if any,
|
||
/// read from the set's bytes alone: the set is refused at load by its lowering
|
||
/// (`whm1/1` with the RGB8 step, loaded under `acc64`), for a rule the direct decode
|
||
/// does not make. A set refused at load for a rule integer-payloads §7's plan also makes
|
||
/// (the chain, a zero dimension, a shift a layer uses) is not a break: the plan, which
|
||
/// both decodes run first, refuses it alike. No payload field changes meaning.
|
||
fn int_splat_planned_break(set: &[u8]) -> Option<&'static str> {
|
||
let net = wai_ops::lower::parse_whm1(set).ok()?;
|
||
load_break(&net, wai_ops::lower::whm1(set, true))
|
||
}
|
||
|
||
/// The planned break a parsed `WHM1` or `WAM1` network falls under, given its lowering
|
||
/// (see [`int_splat_planned_break`]).
|
||
fn load_break(net: &wai_ops::lower::Net, lowered: Result<wai_ops::Graph, wai_ops::Refusal>) -> Option<&'static str> {
|
||
use wai_ops::lower::NetLayer as N;
|
||
let loaded = lowered.as_ref().map_err(|r| *r).and_then(wai::int_ops::load);
|
||
if loaded.is_ok() {
|
||
return None;
|
||
}
|
||
let conv = net.layers.iter().any(|l| matches!(l, N::ConvT { .. } | N::Conv { .. }));
|
||
let igdn = net.layers.iter().any(|l| matches!(l, N::Igdn { .. }));
|
||
if !conv && !igdn {
|
||
return Some("no layer of tag 0, 1 or 2");
|
||
}
|
||
if (net.shift > 127 && !conv) || (net.gf > 127 && !igdn) {
|
||
return Some("a shift or gf of 128 or more that no layer uses");
|
||
}
|
||
if (1..=30).contains(&net.afrac) && net.shift <= 127 && net.gf <= 127 {
|
||
let huge = |b: &[i64]| b.iter().any(|v| v.unsigned_abs() >= i64::MAX as u64);
|
||
if net.layers.iter().any(|l| matches!(l, N::ConvT { bias, .. } | N::Conv { bias, .. } if huge(bias))) {
|
||
return Some("a bias of magnitude 2^63 - 1 or more");
|
||
}
|
||
}
|
||
match loaded {
|
||
Err(r) if lowered.is_ok() && r.at == wai_ops::Site::Load(8) => Some("a check range its producer cannot meet"),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// A random layer stack for `int_splat_case`, from `c` input channels: transposed
|
||
/// convolutions, convolutions, inverse GDNs, ReLUs and leaky ReLUs; kernels 1 to 4,
|
||
/// strides 1 to 3, paddings mostly below the kernel. Weights of up to 2^4, 2^10 or 2^12
|
||
/// (one set in eight up to i16's range), a quarter 0; multipliers mostly up to 2^12 (one
|
||
/// in 32 up to 2^40), biases mostly up to 2^20, with one in 64 at an end of i64; IGDN `γ` mostly
|
||
/// non-negative and up to 2^12, `β` up to 2^28. Returns the layers and the output
|
||
/// channels.
|
||
fn splat_layers(r: &mut Rng, mut c: u32, n: usize, only_relu: bool) -> (Vec<Whm>, u32) {
|
||
let big = r.below(8) == 0;
|
||
let mut layers = Vec::new();
|
||
for _ in 0..n {
|
||
let kind = if only_relu { 3 } else { [0, 0, 0, 1, 2, 2, 3, 4][r.below(8) as usize] };
|
||
let l = match kind {
|
||
0 | 1 => {
|
||
let out = 1 + r.below(4) as u32;
|
||
let k = 1 + r.below(4) as u32;
|
||
let s = 1 + r.below(3) as u32;
|
||
let p = if r.below(16) == 0 { r.below(k as u64 + 3) } else { r.below(k as u64) } as u32;
|
||
let wmax = if big { 32767 } else { [16, 1024, 4096][r.below(3) as usize] };
|
||
let w: Vec<i16> = (0..c * out * k * k).map(|_| if r.below(4) == 0 { 0 } else { r.signed(wmax) as i16 }).collect();
|
||
let m: Vec<i64> = (0..out).map(|_| if r.below(32) == 0 { r.signed(1 << 40) } else { r.signed(1 << 12) }).collect();
|
||
let b: Vec<i64> = (0..out)
|
||
.map(|_| match r.below(64) {
|
||
0 => [i64::MAX, i64::MIN, i64::MIN + 1, i64::MAX - 1][r.below(4) as usize],
|
||
1 => r.signed(1 << 40),
|
||
_ => r.signed(1 << 20),
|
||
})
|
||
.collect();
|
||
let inc = c;
|
||
c = out;
|
||
if kind == 0 {
|
||
let op = if r.below(16) == 0 { r.below(s as u64 + 1) } else { r.below(s as u64) } as u32;
|
||
Whm::ConvT { inc, out, k, s, p, op, w, m, b }
|
||
} else {
|
||
Whm::Conv { inc, out, k, s, p, w, m, b }
|
||
}
|
||
}
|
||
2 => {
|
||
let g: Vec<i32> = (0..c * c).map(|_| if r.below(16) == 0 { r.signed(1 << 12) as i32 } else { r.below(1 << 12) as i32 }).collect();
|
||
let b: Vec<i64> = (0..c).map(|_| if r.below(16) == 0 { r.signed(1 << 28) } else { r.below(1 << 28) as i64 }).collect();
|
||
Whm::Igdn { c, g, b }
|
||
}
|
||
3 => Whm::Relu,
|
||
_ => Whm::Leaky { q: if r.below(32) == 0 { r.signed(1 << 40) } else { r.signed(1 << 10) }, f: if r.below(32) == 0 { 64 } else { r.below(16) as u32 } },
|
||
};
|
||
layers.push(l);
|
||
}
|
||
(layers, c)
|
||
}
|
||
|
||
/// The output side of `layers` on a square input of side `n`, or `None` where the
|
||
/// geometry leaves no output.
|
||
fn splat_side(layers: &[Whm], mut n: i64) -> Option<i64> {
|
||
for l in layers {
|
||
n = match l {
|
||
Whm::ConvT { k, s, p, op, .. } => (n - 1) * *s as i64 + *k as i64 + *op as i64 - 2 * *p as i64,
|
||
Whm::Conv { k, s, p, .. } => {
|
||
let np = n + 2 * *p as i64 - *k as i64;
|
||
if np < 0 { return None } else { np / *s as i64 + 1 }
|
||
}
|
||
_ => n,
|
||
};
|
||
if n < 1 {
|
||
return None;
|
||
}
|
||
}
|
||
Some(n)
|
||
}
|
||
|
||
/// A random `wai.splat.int_codec` set and cloud: a network of 1 to 4 layers (one set in
|
||
/// 32 with no layers, one in 32 of ReLUs only) at `afrac` 12 mostly (one set in sixteen
|
||
/// anywhere in 1..=30, one in sixteen at 0 or 31), a shift of 8 to 23, with a header `gf`
|
||
/// or `shift` of 128 or more one set in 32 each; a latent of 1 to 3 channels of side 1 to 4 (one in sixteen
|
||
/// not square), each channel with its own table of 3 to 6 entries, offset and median; the
|
||
/// symbols mostly inside their table, one in eight escaped past it and one in 32 far
|
||
/// past it; medians mostly small, one in eight at or past the 2^47 bound, and one in four
|
||
/// placing a whole channel's latents at the input's `check` range or one past it. The
|
||
/// declared attribute shape is the network's output (one case in eight off by one); the
|
||
/// cloud's `afrac` is the set's (one in 32 not); one stream in sixteen is cut short, one
|
||
/// in 32 has a word appended, one in 64 a byte changed.
|
||
fn int_splat_case(r: &mut Rng) -> (Vec<u8>, Cloud) {
|
||
let afrac = match r.below(16) {
|
||
0 => 1 + r.below(30) as u32,
|
||
1 => [0, 31][r.below(2) as usize],
|
||
_ => 12,
|
||
};
|
||
let gf = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 16 };
|
||
let shift = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 8 + r.below(16) as u32 };
|
||
let c0 = 1 + r.below(3) as u32;
|
||
let n_layers = if r.below(32) == 0 { 0 } else { 1 + r.below(4) as usize };
|
||
let only_relu = r.below(32) == 0;
|
||
let (layers, c_out) = splat_layers(r, c0, n_layers, only_relu);
|
||
let set = whm1_bytes(afrac, gf, shift, &layers);
|
||
|
||
let sh = 1 + r.below(4) as usize;
|
||
let sw = if r.below(16) == 0 { 1 + r.below(4) as usize } else { sh };
|
||
let mut s = splat_side(&layers, sh as i64).unwrap_or(1) as usize;
|
||
let mut n_attr = c_out as usize;
|
||
if r.below(8) == 0 {
|
||
if r.below(2) == 0 { s += 1 } else { n_attr += 1 }
|
||
}
|
||
let c = c0 as usize;
|
||
let a = afrac.min(30);
|
||
let mut cdfs = Vec::new();
|
||
let mut offsets = Vec::new();
|
||
let mut median_q = Vec::new();
|
||
for _ in 0..c {
|
||
let len = 3 + r.below(4) as usize;
|
||
let mut cuts: Vec<i32> = (0..len - 2).map(|_| 1 + r.below(65_535) as i32).collect();
|
||
cuts.sort_unstable();
|
||
cuts.dedup();
|
||
let mut t = vec![0];
|
||
t.extend(cuts);
|
||
t.push(1 << 16);
|
||
cdfs.push(t);
|
||
offsets.push(r.signed(3) as i32);
|
||
median_q.push(match r.below(8) {
|
||
0 => [1i64 << 47, -(1 << 47), (1 << 47) + 1, -(1 << 47) - 1][r.below(4) as usize],
|
||
_ => r.signed(1 << (a + 4)),
|
||
});
|
||
}
|
||
let plane = sh * sw;
|
||
let mut values: Vec<i32> = Vec::with_capacity(c * plane);
|
||
for ch in 0..c {
|
||
let span = cdfs[ch].len() as i64 - 2;
|
||
for _ in 0..plane {
|
||
let v = match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=4 => offsets[ch] as i64 + span + r.below(40) as i64 - 20,
|
||
_ => offsets[ch] as i64 + r.below(span as u64) as i64,
|
||
};
|
||
values.push(v as i32);
|
||
}
|
||
}
|
||
// One case in four: a whole channel's latents at the input's check range, or one
|
||
// past it, where the set lowers.
|
||
if r.below(4) == 0 {
|
||
if let Ok(g) = wai_ops::lower::whm1(&set, true) {
|
||
let x0 = g.tensors[0].hi as i128;
|
||
let ch = r.below(c as u64) as usize;
|
||
let v = offsets[ch];
|
||
let target = [x0, x0 + 1, -x0, -x0 - 1][r.below(4) as usize];
|
||
let med = target - ((v as i128) << a);
|
||
if med.unsigned_abs() <= 1 << 47 {
|
||
median_q[ch] = med as i64;
|
||
values[ch * plane..(ch + 1) * plane].iter_mut().for_each(|x| *x = v);
|
||
}
|
||
}
|
||
}
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for ch in 0..c {
|
||
for &v in &values[ch * plane..(ch + 1) * plane] {
|
||
assert!(enc.encode(v, &cdfs[ch], cdfs[ch].len(), offsets[ch]), "a value the generator writes is codable");
|
||
}
|
||
}
|
||
let mut rans = enc.flush();
|
||
match r.below(64) {
|
||
0..=3 => {
|
||
let cut = (4 * (1 + r.below(3)) as usize).min(rans.len());
|
||
rans.truncate(rans.len() - cut);
|
||
}
|
||
4 | 5 => rans.extend([0, 0, 0, 0]),
|
||
6 => {
|
||
let i = r.below(rans.len() as u64) as usize;
|
||
rans[i] ^= 1 << r.below(8);
|
||
}
|
||
_ => {}
|
||
}
|
||
let cloud_afrac = if r.below(32) == 0 { afrac ^ 1 } else { afrac };
|
||
(set, Cloud { afrac: cloud_afrac, c, sh, sw, s, n_attr, cdfs, offsets, median_q, rans })
|
||
}
|
||
|
||
/// The direct splat decode, a panic (an overflow-checked build's arithmetic overflow)
|
||
/// caught.
|
||
fn splat_direct(gs: &IntModel, cl: &Cloud) -> Option<Result<Vec<u8>, RefusalKind>> {
|
||
catch_unwind(AssertUnwindSafe(|| wai::int_splat::legacy::decode_typed(gs, &cl.st()).map(|d| d.attr).map_err(|r| r.kind))).ok()
|
||
}
|
||
|
||
/// Gate 4 of the migration, for `wai.splat.int_codec`: the decode through the operator
|
||
/// set against the direct decode on random sets and clouds. Outside the planned breaks
|
||
/// both decode to the same attribute bytes or both refuse with the same code, and the
|
||
/// direct decode never panics; under a planned break the decode through the operator set
|
||
/// refuses.
|
||
#[test]
|
||
fn int_splat_random_sets_and_clouds_decode_alike() {
|
||
let (mut alike, mut refused, mut breaks) = (0u64, std::collections::BTreeMap::<String, u64>::new(), std::collections::BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_splat", 3000, |r, seed| {
|
||
let (set, cl) = int_splat_case(r);
|
||
let why = || format!("seed {seed}\ncloud {cl:?}");
|
||
let gs = IntModel::from_bytes(&set).expect("the generator writes WHM1 that parses");
|
||
let new = wai::int_splat::ops::decode_typed(&gs, &cl.st()).map(|d| d.attr).map_err(|r| r.kind);
|
||
match int_splat_planned_break(&set) {
|
||
Some(b) => {
|
||
assert!(new.is_err(), "{b}: the decode through the operator set must refuse it\n{}", why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
}
|
||
None => match (&new, splat_direct(&gs, &cl)) {
|
||
(Ok(a), Some(Ok(b))) => {
|
||
assert!(*a == b, "the two decodes differ\n{}", why());
|
||
alike += 1;
|
||
}
|
||
(Err(a), Some(Err(b))) => {
|
||
assert_eq!(*a, b, "the two decodes refuse with different codes\n{}", why());
|
||
*refused.entry(format!("{a:?}")).or_default() += 1;
|
||
}
|
||
(_, old) => panic!("new {new:?}, direct {old:?}\n{}", why()),
|
||
},
|
||
}
|
||
});
|
||
eprintln!("int_splat: {alike} decoded alike, refused by both {refused:?}, planned breaks {breaks:?}");
|
||
assert!(alike * 5 > n, "the generator must produce decodable cases: {alike} of {n}");
|
||
assert!(n < 1000 || refused.contains_key("FieldRange"), "no case reached a check range");
|
||
assert!(n < 1000 || breaks.len() >= 3, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 item 3 for `wai.splat.int_codec`, each
|
||
/// on a set and cloud the direct decode decodes, or refuses with another code: the
|
||
/// classifier names it, and the decode through the operator set refuses it
|
||
/// (`model_mismatch`, before the stream).
|
||
#[test]
|
||
fn int_splat_planned_breaks_are_new_refusals() {
|
||
// A cloud of 2 channels of side 2, every latent 2·2^12, each symbol the first of the
|
||
// table `t` (offset 2).
|
||
let cloud_with = |t: Vec<i32>| {
|
||
let (c, n, s, n_attr) = (2usize, 2usize, 2usize, 2usize);
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for _ in 0..c * n * n {
|
||
assert!(enc.encode(2, &t, 3, 2));
|
||
}
|
||
Cloud { afrac: 12, c, sh: n, sw: n, s, n_attr, cdfs: vec![t; c], offsets: vec![2; c], median_q: vec![0; c], rans: enc.flush() }
|
||
};
|
||
let conv1 = |inc: u32, out: u32, w: i16, b: i64| Whm::ConvT { inc, out, k: 1, s: 1, p: 0, op: 0, w: vec![w; (inc * out) as usize], m: vec![1; out as usize], b: vec![b; out as usize] };
|
||
let expect = |b: &str, set: &[u8], cl: &Cloud, direct: Option<RefusalKind>| {
|
||
assert_eq!(int_splat_planned_break(set), Some(b), "{b}: the classifier");
|
||
let gs = IntModel::from_bytes(set).unwrap();
|
||
match direct {
|
||
None => assert!(matches!(splat_direct(&gs, cl), Some(Ok(_))), "{b}: the direct decode decodes it"),
|
||
Some(k) => assert_eq!(splat_direct(&gs, cl), Some(Err(k)), "{b}: the direct decode's code"),
|
||
}
|
||
let new = wai::int_splat::ops::decode_typed(&gs, &cl.st()).map(|_| ()).map_err(|r| r.kind);
|
||
assert_eq!(new, Err(RefusalKind::ModelMismatch), "{b}: the decode through the operator set refuses it");
|
||
};
|
||
// The base decodes alike: one 1×1 transposed convolution, weight 1, shift 0.
|
||
let base = whm1_bytes(12, 16, 0, &[conv1(2, 2, 1, 0)]);
|
||
assert_eq!(int_splat_planned_break(&base), None);
|
||
let gs = IntModel::from_bytes(&base).unwrap();
|
||
let cl = cloud_with(vec![0, 1 << 15, 1 << 16]);
|
||
assert_eq!(splat_direct(&gs, &cl), Some(Ok(vec![255; 8])), "2·2^12 clamped to 2^12: 255");
|
||
assert_eq!(wai::int_splat::ops::decode_typed(&gs, &cl.st()).unwrap().attr, vec![255; 8]);
|
||
// No layer of tag 0, 1 or 2: the direct decode presents the latents themselves.
|
||
expect("no layer of tag 0, 1 or 2", &whm1_bytes(12, 16, 0, &[Whm::Relu]), &cl, None);
|
||
expect("no layer of tag 0, 1 or 2", &whm1_bytes(12, 16, 0, &[]), &cl, None);
|
||
// A header shift no layer uses (only an inverse GDN), or a gf no layer uses.
|
||
let igdn = Whm::Igdn { c: 2, g: vec![0; 4], b: vec![1 << 24; 2] };
|
||
expect("a shift or gf of 128 or more that no layer uses", &whm1_bytes(12, 16, 200, &[igdn]), &cl, None);
|
||
expect("a shift or gf of 128 or more that no layer uses", &whm1_bytes(12, 200, 0, &[conv1(2, 2, 1, 0)]), &cl, None);
|
||
// A bias at the end of i64: the direct decode refuses every input at that layer
|
||
// (field_range), after the stream; the lowering refuses the set before it, so a
|
||
// stream that runs out is refused for the set, not the stream.
|
||
expect("a bias of magnitude 2^63 - 1 or more", &whm1_bytes(12, 16, 0, &[conv1(2, 2, 1, i64::MAX)]), &cl, Some(RefusalKind::FieldRange));
|
||
let mut short = cloud_with(vec![0, 1, 1 << 16]);
|
||
short.rans.truncate(8);
|
||
expect("a bias of magnitude 2^63 - 1 or more", &whm1_bytes(12, 16, 0, &[conv1(2, 2, 1, i64::MIN)]), &short, Some(RefusalKind::StreamExhausted));
|
||
// A check range its producer cannot meet: layer 0 gives every value 2^30 (weights 0,
|
||
// bias 2^30); layer 1, two input channels of weight 2^11 under the multiplier 2^40 and
|
||
// shift 0, has X_1 = 2047. The direct decode refuses every input at layer 1
|
||
// (field_range); the load refuses the set (an empty check intersection, §8.1).
|
||
let tight = Whm::ConvT { inc: 2, out: 2, k: 1, s: 1, p: 0, op: 0, w: vec![1 << 11; 4], m: vec![1 << 40; 2], b: vec![0; 2] };
|
||
let set = whm1_bytes(12, 16, 0, &[conv1(2, 2, 0, 1 << 30), tight]);
|
||
expect("a check range its producer cannot meet", &set, &cl, Some(RefusalKind::FieldRange));
|
||
}
|
||
|
||
// ------------------------------------------------------------------ int_audio (M3)
|
||
|
||
/// One layer of a `WAM1` network, as a set's bytes hold it (integer-payloads §9).
|
||
#[derive(Clone)]
|
||
enum Wam {
|
||
ConvT { inc: u32, out: u32, k: u32, s: u32, p: u32, op: u32, w: Vec<i16>, m: Vec<i64>, b: Vec<i64> },
|
||
Igdn { c: u32, g: Vec<i32>, b: Vec<i64> },
|
||
}
|
||
|
||
/// A `WAM1` network's bytes.
|
||
fn wam1_bytes(afrac: u32, gf: u32, shift: u32, layers: &[Wam]) -> Vec<u8> {
|
||
let mut v = b"WAM1".to_vec();
|
||
for x in [afrac, gf, shift, layers.len() as u32] {
|
||
v.extend(x.to_le_bytes());
|
||
}
|
||
for l in layers {
|
||
match l {
|
||
Wam::ConvT { inc, out, k, s, p, op, w, m, b } => {
|
||
v.push(0);
|
||
[*inc, *out, *k, *s, *p, *op].iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
w.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
m.iter().chain(b).for_each(|x| v.extend(x.to_le_bytes()));
|
||
}
|
||
Wam::Igdn { c, g, b } => {
|
||
v.push(2);
|
||
v.extend(c.to_le_bytes());
|
||
g.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
b.iter().for_each(|x| v.extend(x.to_le_bytes()));
|
||
}
|
||
}
|
||
}
|
||
v
|
||
}
|
||
|
||
/// A `WIA1` clip's typed fields, owned.
|
||
#[derive(Clone, Debug)]
|
||
struct Clip {
|
||
afrac: u32,
|
||
n_ch: usize,
|
||
lat_len: usize,
|
||
t_out: usize,
|
||
offset: i32,
|
||
median_q: Vec<i64>,
|
||
cdfs: Vec<Vec<i32>>,
|
||
rans: Vec<u8>,
|
||
}
|
||
|
||
impl Clip {
|
||
fn st(&self) -> wai::int_audio::AudioStream<'_> {
|
||
wai::int_audio::AudioStream {
|
||
afrac: self.afrac, n_ch: self.n_ch, lat_len: self.lat_len, t_out: self.t_out, offset: self.offset,
|
||
median_q: &self.median_q, cdfs: &self.cdfs, rans: &self.rans,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 that an audio set falls under, if any,
|
||
/// read from the set's bytes alone: the set is refused at load by its lowering (`wam1/1`
|
||
/// with the PCM step, loaded under `acc64`), for a rule the direct decode does not make
|
||
/// (as for splat, [`int_splat_planned_break`]; a `WAM1` network has layers of tags 0 and
|
||
/// 2 only, so one with no such layer has no layers). No payload field changes meaning.
|
||
fn int_audio_planned_break(set: &[u8]) -> Option<&'static str> {
|
||
let net = wai_ops::lower::parse_wam1(set).ok()?;
|
||
load_break(&net, wai_ops::lower::wam1(set))
|
||
}
|
||
|
||
/// The length a stack of layers gives on `n` latent samples, or `None` where one leaves
|
||
/// no output.
|
||
fn audio_len(layers: &[Wam], mut n: i64) -> Option<i64> {
|
||
for l in layers {
|
||
if let Wam::ConvT { k, s, p, op, .. } = l {
|
||
n = (n - 1) * *s as i64 + *k as i64 + *op as i64 - 2 * *p as i64;
|
||
if n < 1 {
|
||
return None;
|
||
}
|
||
}
|
||
}
|
||
Some(n)
|
||
}
|
||
|
||
/// A random `wai.audio.int_codec` set and clip: a network of 1 to 4 layers (one set in
|
||
/// 32 with none), one-dimensional transposed convolutions and inverse GDNs, the last a
|
||
/// transposed convolution to one channel (one set in sixteen not), at `afrac` 12 mostly
|
||
/// (one in sixteen anywhere in 1..=30, one in sixteen at 0 or 31), a shift of 8 to 23,
|
||
/// and a header `gf` or `shift` of 128 or more one set in 32 each; kernels 1 to 5,
|
||
/// strides 1 to 3, paddings mostly below the kernel; weights up to 2^4, 2^10 or 2^12 (one
|
||
/// set in eight up to i16's range), multipliers mostly up to 2^12, biases mostly up to
|
||
/// 2^20 with one in 64 at an end of i64; IGDN `γ` up to 2^12, `β` up to 2^28. A latent
|
||
/// of 1 to 3 channels of 1 to 6 samples, one table length and offset for every channel;
|
||
/// symbols mostly inside the table, some escaped and far past it; medians mostly small,
|
||
/// one in eight at or past 2^47, and one case in four with a whole channel at the input's
|
||
/// `check` range or one past it. `t_out` is one the latent is the shortest for (one case
|
||
/// in eight not); the clip's `afrac` is the set's (one in 32 not); one stream in sixteen
|
||
/// is cut short, one in 32 has a word appended, one in 64 a byte changed.
|
||
fn int_audio_case(r: &mut Rng) -> (Vec<u8>, Clip) {
|
||
let afrac = match r.below(16) {
|
||
0 => 1 + r.below(30) as u32,
|
||
1 => [0, 31][r.below(2) as usize],
|
||
_ => 12,
|
||
};
|
||
let gf = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 16 };
|
||
let shift = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 8 + r.below(16) as u32 };
|
||
let c0 = 1 + r.below(3) as u32;
|
||
let n_layers = if r.below(32) == 0 { 0 } else { 1 + r.below(4) as usize };
|
||
let big = r.below(8) == 0;
|
||
let (mut c, mut layers) = (c0, Vec::new());
|
||
for i in 0..n_layers {
|
||
let last = i + 1 == n_layers;
|
||
if !last && r.below(3) == 0 {
|
||
let g: Vec<i32> = (0..c * c).map(|_| if r.below(16) == 0 { r.signed(1 << 12) as i32 } else { r.below(1 << 12) as i32 }).collect();
|
||
let b: Vec<i64> = (0..c).map(|_| if r.below(16) == 0 { r.signed(1 << 28) } else { r.below(1 << 28) as i64 }).collect();
|
||
layers.push(Wam::Igdn { c, g, b });
|
||
continue;
|
||
}
|
||
let out = if last && r.below(16) != 0 { 1 } else { 1 + r.below(3) as u32 };
|
||
let k = 1 + r.below(5) as u32;
|
||
let s = 1 + r.below(3) as u32;
|
||
let p = if r.below(16) == 0 { r.below(k as u64 + 3) } else { r.below(k as u64) } as u32;
|
||
let op = if r.below(16) == 0 { r.below(s as u64 + 1) } else { r.below(s as u64) } as u32;
|
||
let wmax = if big { 32767 } else { [16, 1024, 4096][r.below(3) as usize] };
|
||
let w: Vec<i16> = (0..c * out * k).map(|_| if r.below(4) == 0 { 0 } else { r.signed(wmax) as i16 }).collect();
|
||
let m: Vec<i64> = (0..out).map(|_| if r.below(32) == 0 { r.signed(1 << 40) } else { r.signed(1 << 12) }).collect();
|
||
let b: Vec<i64> = (0..out)
|
||
.map(|_| match r.below(64) {
|
||
0 => [i64::MAX, i64::MIN, i64::MIN + 1, i64::MAX - 1][r.below(4) as usize],
|
||
1 => r.signed(1 << 40),
|
||
_ => r.signed(1 << 20),
|
||
})
|
||
.collect();
|
||
layers.push(Wam::ConvT { inc: c, out, k, s, p, op, w, m, b });
|
||
c = out;
|
||
}
|
||
let set = wam1_bytes(afrac, gf, shift, &layers);
|
||
|
||
let lat_len = 1 + r.below(6) as usize;
|
||
let full = audio_len(&layers, lat_len as i64).unwrap_or(1).max(1) as usize;
|
||
let shorter = if lat_len > 1 { audio_len(&layers, lat_len as i64 - 1).unwrap_or(0).max(0) as usize } else { 0 };
|
||
let mut t_out = if full > shorter { shorter + 1 + r.below((full - shorter) as u64) as usize } else { full };
|
||
if r.below(8) == 0 {
|
||
t_out = if r.below(2) == 0 { full + 1 } else { shorter.max(1) };
|
||
}
|
||
let n_ch = c0 as usize;
|
||
let a = afrac.min(30);
|
||
let len = 3 + r.below(4) as usize;
|
||
let mut cuts: Vec<i32> = (0..len - 2).map(|_| 1 + r.below(65_535) as i32).collect();
|
||
cuts.sort_unstable();
|
||
cuts.dedup();
|
||
let mut t = vec![0];
|
||
t.extend(cuts);
|
||
t.push(1 << 16);
|
||
let offset = r.signed(3) as i32;
|
||
let span = t.len() as i64 - 2;
|
||
let mut median_q: Vec<i64> = (0..n_ch)
|
||
.map(|_| match r.below(8) {
|
||
0 => [1i64 << 47, -(1 << 47), (1 << 47) + 1, -(1 << 47) - 1][r.below(4) as usize],
|
||
_ => r.signed(1 << (a + 4)),
|
||
})
|
||
.collect();
|
||
let mut values: Vec<i32> = (0..n_ch * lat_len)
|
||
.map(|_| match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=4 => offset as i64 + span + r.below(40) as i64 - 20,
|
||
_ => offset as i64 + r.below(span as u64) as i64,
|
||
} as i32)
|
||
.collect();
|
||
if r.below(4) == 0 {
|
||
if let Ok(g) = wai_ops::lower::wam1(&set) {
|
||
let x0 = g.tensors[0].hi as i128;
|
||
let ch = r.below(n_ch as u64) as usize;
|
||
let target = [x0, x0 + 1, -x0, -x0 - 1][r.below(4) as usize];
|
||
let med = target - ((offset as i128) << a);
|
||
if med.unsigned_abs() <= 1 << 47 {
|
||
median_q[ch] = med as i64;
|
||
values[ch * lat_len..(ch + 1) * lat_len].iter_mut().for_each(|x| *x = offset);
|
||
}
|
||
}
|
||
}
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for &v in &values {
|
||
assert!(enc.encode(v, &t, t.len(), offset), "a value the generator writes is codable");
|
||
}
|
||
let mut rans = enc.flush();
|
||
match r.below(64) {
|
||
0..=3 => {
|
||
let cut = (4 * (1 + r.below(3)) as usize).min(rans.len());
|
||
rans.truncate(rans.len() - cut);
|
||
}
|
||
4 | 5 => rans.extend([0, 0, 0, 0]),
|
||
6 => {
|
||
let i = r.below(rans.len() as u64) as usize;
|
||
rans[i] ^= 1 << r.below(8);
|
||
}
|
||
_ => {}
|
||
}
|
||
let clip_afrac = if r.below(32) == 0 { afrac ^ 1 } else { afrac };
|
||
(set, Clip { afrac: clip_afrac, n_ch, lat_len, t_out, offset, median_q, cdfs: vec![t; n_ch], rans })
|
||
}
|
||
|
||
/// The direct audio decode, a panic (an overflow-checked build's arithmetic overflow)
|
||
/// caught.
|
||
fn audio_direct(m: &wai::int_audio::AudioModel, cl: &Clip) -> Option<Result<Vec<i16>, RefusalKind>> {
|
||
catch_unwind(AssertUnwindSafe(|| wai::int_audio::legacy::decode_typed(m, &cl.st()).map_err(|r| r.kind))).ok()
|
||
}
|
||
|
||
/// Gate 4 of the migration, for `wai.audio.int_codec`: the decode through the operator
|
||
/// set against the direct decode on random sets and clips. Outside the planned breaks
|
||
/// both decode to the same samples or both refuse with the same code, and the direct
|
||
/// decode never panics; under a planned break the decode through the operator set
|
||
/// refuses.
|
||
#[test]
|
||
fn int_audio_random_sets_and_clips_decode_alike() {
|
||
let (mut alike, mut refused, mut breaks) = (0u64, std::collections::BTreeMap::<String, u64>::new(), std::collections::BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_audio", 3000, |r, seed| {
|
||
let (set, cl) = int_audio_case(r);
|
||
let why = || format!("seed {seed}\nclip {cl:?}");
|
||
let m = wai::int_audio::AudioModel::from_bytes(&set).expect("the generator writes WAM1 that parses");
|
||
let new = wai::int_audio::ops::decode_typed(&m, &cl.st()).map_err(|r| r.kind);
|
||
match int_audio_planned_break(&set) {
|
||
Some(b) => {
|
||
assert!(new.is_err(), "{b}: the decode through the operator set must refuse it\n{}", why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
}
|
||
None => match (&new, audio_direct(&m, &cl)) {
|
||
(Ok(a), Some(Ok(b))) => {
|
||
assert!(*a == b, "the two decodes differ\n{}", why());
|
||
alike += 1;
|
||
}
|
||
(Err(a), Some(Err(b))) => {
|
||
assert_eq!(*a, b, "the two decodes refuse with different codes\n{}", why());
|
||
*refused.entry(format!("{a:?}")).or_default() += 1;
|
||
}
|
||
(_, old) => panic!("new {new:?}, direct {old:?}\n{}", why()),
|
||
},
|
||
}
|
||
});
|
||
eprintln!("int_audio: {alike} decoded alike, refused by both {refused:?}, planned breaks {breaks:?}");
|
||
assert!(alike * 5 > n, "the generator must produce decodable cases: {alike} of {n}");
|
||
assert!(n < 1000 || refused.contains_key("FieldRange"), "no case reached a check range");
|
||
assert!(n < 1000 || breaks.len() >= 3, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 item 3 for `wai.audio.int_codec`, each
|
||
/// on a set and clip the direct decode decodes, or refuses with another code: the
|
||
/// classifier names it, and the decode through the operator set refuses it
|
||
/// (`model_mismatch`, before the stream).
|
||
#[test]
|
||
fn int_audio_planned_breaks_are_new_refusals() {
|
||
use wai::int_audio::AudioModel;
|
||
// A clip of one channel of 4 latents, each 2·2^12, presenting 4 samples; with
|
||
// `freq1`, each symbol costs 16 bits, so a stream cut to its seed runs out.
|
||
let clip_with = |t: Vec<i32>| {
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for _ in 0..4 {
|
||
assert!(enc.encode(2, &t, 3, 2));
|
||
}
|
||
Clip { afrac: 12, n_ch: 1, lat_len: 4, t_out: 4, offset: 2, median_q: vec![0], cdfs: vec![t], rans: enc.flush() }
|
||
};
|
||
let conv1 = |w: i16, m: i64, b: i64| Wam::ConvT { inc: 1, out: 1, k: 1, s: 1, p: 0, op: 0, w: vec![w], m: vec![m], b: vec![b] };
|
||
let expect = |b: &str, set: &[u8], cl: &Clip, direct: Option<RefusalKind>| {
|
||
assert_eq!(int_audio_planned_break(set), Some(b), "{b}: the classifier");
|
||
let m = AudioModel::from_bytes(set).unwrap();
|
||
match direct {
|
||
None => assert!(matches!(audio_direct(&m, cl), Some(Ok(_))), "{b}: the direct decode decodes it"),
|
||
Some(k) => assert_eq!(audio_direct(&m, cl), Some(Err(k)), "{b}: the direct decode's code"),
|
||
}
|
||
let new = wai::int_audio::ops::decode_typed(&m, &cl.st()).map(|_| ()).map_err(|r| r.kind);
|
||
assert_eq!(new, Err(RefusalKind::ModelMismatch), "{b}: the decode through the operator set refuses it");
|
||
};
|
||
// The base decodes alike: one 1×1 transposed convolution, weight 1, shift 0, so each
|
||
// sample is (2·2^12·32767 + 2^11) >> 12 = 65534, clamped to 32767.
|
||
let cl = clip_with(vec![0, 1 << 15, 1 << 16]);
|
||
let base = wam1_bytes(12, 16, 0, &[conv1(1, 1, 0)]);
|
||
assert_eq!(int_audio_planned_break(&base), None);
|
||
let m = AudioModel::from_bytes(&base).unwrap();
|
||
assert_eq!(audio_direct(&m, &cl), Some(Ok(vec![32767; 4])));
|
||
assert_eq!(wai::int_audio::ops::decode_typed(&m, &cl.st()).unwrap(), vec![32767; 4]);
|
||
// No layers: the direct decode presents the latents themselves.
|
||
expect("no layer of tag 0, 1 or 2", &wam1_bytes(12, 16, 0, &[]), &cl, None);
|
||
// A header shift no layer uses (only an inverse GDN), or a gf no layer uses.
|
||
let igdn = Wam::Igdn { c: 1, g: vec![0], b: vec![1 << 24] };
|
||
expect("a shift or gf of 128 or more that no layer uses", &wam1_bytes(12, 16, 200, &[igdn]), &cl, None);
|
||
expect("a shift or gf of 128 or more that no layer uses", &wam1_bytes(12, 200, 0, &[conv1(1, 1, 0)]), &cl, None);
|
||
// A bias at the end of i64: refused at that layer, after the stream, directly; at
|
||
// load through the lowering, so a stream that runs out is refused for the set.
|
||
expect("a bias of magnitude 2^63 - 1 or more", &wam1_bytes(12, 16, 0, &[conv1(1, 1, i64::MAX)]), &cl, Some(RefusalKind::FieldRange));
|
||
let mut short = clip_with(vec![0, 1, 1 << 16]);
|
||
short.rans.truncate(8);
|
||
expect("a bias of magnitude 2^63 - 1 or more", &wam1_bytes(12, 16, 0, &[conv1(1, 1, i64::MIN)]), &short, Some(RefusalKind::StreamExhausted));
|
||
// A check range its producer cannot meet: every value 2^30 into a layer whose range is
|
||
// ±4095 (weight 2^11, multiplier 2^40, shift 0); and every sample 2^62, past the PCM
|
||
// step's range. The direct decode refuses each at that step (field_range).
|
||
expect("a check range its producer cannot meet", &wam1_bytes(12, 16, 0, &[conv1(0, 1, 1 << 30), conv1(1 << 11, 1 << 40, 0)]), &cl, Some(RefusalKind::FieldRange));
|
||
expect("a check range its producer cannot meet", &wam1_bytes(12, 16, 0, &[conv1(0, 1, 1 << 62)]), &cl, Some(RefusalKind::FieldRange));
|
||
}
|
||
|
||
// ------------------------------------------------------------------ int_hyper (M4)
|
||
|
||
/// A conditional codec's entropy tables (`entropy.json`) and a `WIH1` payload's typed
|
||
/// fields, owned.
|
||
#[derive(Clone, Debug)]
|
||
struct Cond {
|
||
edges: Vec<i64>,
|
||
cdfs: Vec<Vec<i32>>,
|
||
cdf_lengths: Vec<usize>,
|
||
offsets: Vec<i32>,
|
||
z_cdfs: Vec<Vec<i32>>,
|
||
z_cdf_lengths: Vec<usize>,
|
||
z_offsets: Vec<i32>,
|
||
z_median_q: Vec<i64>,
|
||
m: usize,
|
||
sh: usize,
|
||
sw: usize,
|
||
n_z: usize,
|
||
zh: usize,
|
||
zw: usize,
|
||
z_rans: Vec<u8>,
|
||
rans: Vec<u8>,
|
||
}
|
||
|
||
impl Cond {
|
||
fn ent(&self) -> wai::int_hyper_synth::CondEntropy {
|
||
wai::int_hyper_synth::CondEntropy {
|
||
edges: self.edges.clone(), cdfs: self.cdfs.clone(), cdf_lengths: self.cdf_lengths.clone(), offsets: self.offsets.clone(),
|
||
z_cdfs: self.z_cdfs.clone(), z_cdf_lengths: self.z_cdf_lengths.clone(), z_offsets: self.z_offsets.clone(), z_median_q: self.z_median_q.clone(),
|
||
}
|
||
}
|
||
fn st(&self) -> wai::int_hyper_synth::CondStream<'_> {
|
||
wai::int_hyper_synth::CondStream { m: self.m, sh: self.sh, sw: self.sw, n_z: self.n_z, zh: self.zh, zw: self.zw, z_rans: &self.z_rans, rans: &self.rans }
|
||
}
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 that a conditional codec's set falls
|
||
/// under, if any, read from its two networks' bytes alone: either network is refused at
|
||
/// load by its lowering (`h_s` by `whm1/1`, `g_s` by `whm1/1` with the RGB8 step, each
|
||
/// loaded under `acc64`) for a rule the direct decode does not make (see
|
||
/// [`int_splat_planned_break`]). The entropy tables are not lowered, and no payload field
|
||
/// changes meaning.
|
||
fn int_hyper_planned_break(g_s: &[u8], h_s: &[u8]) -> Option<&'static str> {
|
||
let (h, g) = (wai_ops::lower::parse_whm1(h_s).ok()?, wai_ops::lower::parse_whm1(g_s).ok()?);
|
||
load_break(&h, wai_ops::lower::whm1(h_s, false)).or_else(|| load_break(&g, wai_ops::lower::whm1(g_s, true)))
|
||
}
|
||
|
||
/// A random CDF table of 3 to 6 entries.
|
||
fn cdf_table(r: &mut Rng) -> Vec<i32> {
|
||
let len = 3 + r.below(4) as usize;
|
||
let mut cuts: Vec<i32> = (0..len - 2).map(|_| 1 + r.below(65_535) as i32).collect();
|
||
cuts.sort_unstable();
|
||
cuts.dedup();
|
||
let mut t = vec![0];
|
||
t.extend(cuts);
|
||
t.push(1 << 16);
|
||
t
|
||
}
|
||
|
||
/// A last layer of a random network: a convolution (stride 1) or a transposed convolution
|
||
/// from `c` channels to `out`, with `splat_layers`' parameter draws; one in 32 a ReLU.
|
||
fn last_layer(r: &mut Rng, c: u32, out: u32) -> Whm {
|
||
if r.below(32) == 0 {
|
||
return Whm::Relu;
|
||
}
|
||
let k = 1 + r.below(3) as u32;
|
||
let p = if r.below(16) == 0 { r.below(k as u64 + 2) } else { (k / 2) as u64 } as u32;
|
||
let wmax = if r.below(8) == 0 { 32767 } else { [16, 1024, 4096][r.below(3) as usize] };
|
||
let w: Vec<i16> = (0..c * out * k * k).map(|_| if r.below(4) == 0 { 0 } else { r.signed(wmax) as i16 }).collect();
|
||
let m: Vec<i64> = (0..out).map(|_| if r.below(32) == 0 { r.signed(1 << 40) } else { r.signed(1 << 12) }).collect();
|
||
let b: Vec<i64> = (0..out)
|
||
.map(|_| match r.below(64) {
|
||
0 => [i64::MAX, i64::MIN, i64::MIN + 1, i64::MAX - 1][r.below(4) as usize],
|
||
1 => r.signed(1 << 40),
|
||
_ => r.signed(1 << 20),
|
||
})
|
||
.collect();
|
||
if r.below(2) == 0 {
|
||
Whm::Conv { inc: c, out, k, s: 1, p, w, m, b }
|
||
} else {
|
||
let s = 1 + r.below(2) as u32;
|
||
Whm::ConvT { inc: c, out, k, s, p: p.min(k.saturating_sub(1)), op: r.below(s as u64) as u32, w, m, b }
|
||
}
|
||
}
|
||
|
||
/// `n` values coded against one table each, with `RansEncoder`: `tables[i]` and its length
|
||
/// and offset for value `i`. `None` where a value is not codable against its table.
|
||
fn rans_of(values: &[(i32, &[i32], usize, i32)]) -> Option<Vec<u8>> {
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for &(v, t, len, off) in values {
|
||
if !enc.encode(v, t, len, off) {
|
||
return None;
|
||
}
|
||
}
|
||
Some(enc.flush())
|
||
}
|
||
|
||
/// A stream damaged one case in sixteen (cut short), one in 32 (a word appended) or one in
|
||
/// 64 (a byte changed).
|
||
fn damage(r: &mut Rng, s: &mut Vec<u8>) {
|
||
match r.below(64) {
|
||
0..=3 => {
|
||
let cut = (4 * (1 + r.below(3)) as usize).min(s.len());
|
||
s.truncate(s.len() - cut);
|
||
}
|
||
4 | 5 => s.extend([0, 0, 0, 0]),
|
||
6 if !s.is_empty() => {
|
||
let i = r.below(s.len() as u64) as usize;
|
||
s[i] ^= 1 << r.below(8);
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
/// A random `wai.neural.int_hyper` set and `WIH1` payload. The set: `afrac` 12 mostly (one
|
||
/// in sixteen anywhere in 1..=30, one in sixteen at 0 or 31, and `h_s`'s not `g_s`'s one
|
||
/// in 32); each network's header `gf` or `shift` of 128 or more one set in 32 each; `h_s`
|
||
/// from `n_z` (1 to 3) channels, up to one of `splat_layers`' layers then a last layer to
|
||
/// `2·m` channels (one in sixteen to another count); `g_s` from `m` (1 to 3) channels, up
|
||
/// to two layers then a last layer to three channels (one in sixteen not); the tables: 1
|
||
/// to 4 ascending scale edges (one set in 32 none), a latent table, length and offset per
|
||
/// edge, and a hyper-latent table, offset and median per hyper-latent channel (one set in
|
||
/// 32 a channel short), one length in 32 past its row; medians mostly small, one in eight
|
||
/// at or past the 2^47 bound or near i64's ends. The payload: a hyper-latent of side 2 or
|
||
/// 3 (one in sixteen not square), its symbols mostly inside their table and some escaped,
|
||
/// one case in four with a whole channel at `h_s`'s input `check` range or one past it;
|
||
/// the latent's shape `h_s`'s output (one case in eight off by one), each symbol coded
|
||
/// against the table the direct decode's `σ` buckets it to (where `h_s` runs directly);
|
||
/// each stream damaged as [`damage`] says.
|
||
fn int_hyper_case(r: &mut Rng) -> (Vec<u8>, Vec<u8>, Cond) {
|
||
let afrac = match r.below(16) {
|
||
0 => 1 + r.below(30) as u32,
|
||
1 => [0, 31][r.below(2) as usize],
|
||
_ => 12,
|
||
};
|
||
let h_afrac = if r.below(32) == 0 { afrac ^ 1 } else { afrac };
|
||
let header = |r: &mut Rng| {
|
||
let gf = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 16 };
|
||
let shift = if r.below(32) == 0 { 128 + r.below(64) as u32 } else { 8 + r.below(16) as u32 };
|
||
(gf, shift)
|
||
};
|
||
let (n_z, m) = (1 + r.below(3) as u32, 1 + r.below(3) as u32);
|
||
let (n, relu_only) = (r.below(2) as usize, r.below(32) == 0);
|
||
let (mut hl, c) = splat_layers(r, n_z, n, relu_only);
|
||
let out = if r.below(16) == 0 { 1 + r.below(6) as u32 } else { 2 * m };
|
||
hl.push(last_layer(r, c, out));
|
||
let (gf, shift) = header(r);
|
||
let h_s = whm1_bytes(h_afrac, gf, shift, &hl);
|
||
let (n, relu_only) = (r.below(3) as usize, r.below(32) == 0);
|
||
let (mut gl, c) = splat_layers(r, m, n, relu_only);
|
||
let out = if r.below(16) == 0 { 1 + r.below(4) as u32 } else { 3 };
|
||
gl.push(last_layer(r, c, out));
|
||
let (gf, shift) = header(r);
|
||
let g_s = whm1_bytes(afrac, gf, shift, &gl);
|
||
|
||
let a = afrac.min(30);
|
||
let n_edges = if r.below(32) == 0 { 0 } else { 1 + r.below(4) as usize };
|
||
let mut edges = Vec::new();
|
||
let mut e = 1 + r.below(1 << (a + 2)) as i64;
|
||
for _ in 0..n_edges {
|
||
edges.push(e);
|
||
e += 1 + r.below(1 << (a + 2)) as i64;
|
||
}
|
||
let cdfs: Vec<Vec<i32>> = (0..n_edges).map(|_| cdf_table(r)).collect();
|
||
let mut cdf_lengths: Vec<usize> = cdfs.iter().map(Vec::len).collect();
|
||
let offsets: Vec<i32> = (0..n_edges).map(|_| r.signed(3) as i32).collect();
|
||
let k = if r.below(32) == 0 { n_z as usize - 1 } else { n_z as usize };
|
||
let z_cdfs: Vec<Vec<i32>> = (0..k).map(|_| cdf_table(r)).collect();
|
||
let mut z_cdf_lengths: Vec<usize> = z_cdfs.iter().map(Vec::len).collect();
|
||
if r.below(32) == 0 {
|
||
match (n_edges, k) {
|
||
(1.., _) if r.below(2) == 0 => cdf_lengths[0] += 1,
|
||
(_, 1..) => z_cdf_lengths[0] += 1,
|
||
_ => {}
|
||
}
|
||
}
|
||
let z_offsets: Vec<i32> = (0..k).map(|_| r.signed(3) as i32).collect();
|
||
let mut z_median_q: Vec<i64> = (0..k)
|
||
.map(|_| match r.below(8) {
|
||
0 => [1i64 << 47, -(1 << 47), 1 << 60, -(1 << 60), i64::MAX, i64::MIN][r.below(6) as usize],
|
||
_ => r.signed(1 << (a + 4)),
|
||
})
|
||
.collect();
|
||
|
||
let zh = 2 + r.below(2) as usize;
|
||
let zw = if r.below(16) == 0 { 1 + r.below(3) as usize } else { zh };
|
||
let (nz, plane) = (n_z as usize, zh * zw);
|
||
// The hyper-latent's symbol values, per channel against its own table (channel 0's
|
||
// where a channel has none).
|
||
let zt = |ch: usize| ch.min(k.saturating_sub(1));
|
||
let mut zv: Vec<i32> = Vec::with_capacity(nz * plane);
|
||
for ch in 0..nz {
|
||
let (off, span) = if k == 0 { (0, 1) } else { (z_offsets[zt(ch)] as i64, z_cdfs[zt(ch)].len() as i64 - 2) };
|
||
for _ in 0..plane {
|
||
zv.push(match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=4 => off + span + r.below(40) as i64 - 20,
|
||
_ => off + r.below(span.max(1) as u64) as i64,
|
||
} as i32);
|
||
}
|
||
}
|
||
// One case in four: a whole hyper-latent channel at h_s's input check range, or one
|
||
// past it, where h_s lowers.
|
||
if r.below(4) == 0 && k > 0 {
|
||
if let Ok(g) = wai_ops::lower::whm1(&h_s, false) {
|
||
let x0 = g.tensors[0].hi as i128;
|
||
let ch = r.below(k as u64) as usize;
|
||
let v = z_offsets[ch];
|
||
let target = [x0, x0 + 1, -x0, -x0 - 1][r.below(4) as usize];
|
||
let med = target - ((v as i128) << a);
|
||
if let Ok(med) = i64::try_from(med) {
|
||
z_median_q[ch] = med;
|
||
zv[ch * plane..(ch + 1) * plane].iter_mut().for_each(|x| *x = v);
|
||
}
|
||
}
|
||
}
|
||
let z_rans = if k == 0 {
|
||
vec![0, 0, 0, 128, 0, 0, 0, 0]
|
||
} else {
|
||
let vals: Vec<(i32, &[i32], usize, i32)> =
|
||
(0..nz * plane).map(|i| { let t = zt(i / plane); (zv[i], z_cdfs[t].as_slice(), z_cdf_lengths[t], z_offsets[t]) }).collect();
|
||
rans_of(&vals).unwrap_or_else(|| vec![0, 0, 0, 128, 0, 0, 0, 0])
|
||
};
|
||
|
||
// The latent: h_s's output shape, and each symbol's table from the direct decode's σ.
|
||
let hm = IntModel::from_bytes(&h_s).expect("the generator writes WHM1 that parses");
|
||
let side = |n: usize| splat_side(&hl, n as i64).unwrap_or(1) as usize;
|
||
let (mut sh, sw) = (side(zh), side(zw));
|
||
if r.below(8) == 0 {
|
||
sh += 1;
|
||
}
|
||
let mu = m as usize;
|
||
let z_hat: Option<Vec<i64>> = (0..nz * plane)
|
||
.map(|i| {
|
||
let med = *z_median_q.get(zt(i / plane))?;
|
||
(zv[i] as i64).checked_mul(1 << a)?.checked_add(med)
|
||
})
|
||
.collect();
|
||
let sigma: Option<Vec<i64>> = z_hat.and_then(|z| {
|
||
let (o, oc, oh, ow) = wai::int_hyper_synth::legacy::run_net_checked(&hm, &z, nz, zh, zw).ok()?;
|
||
((oc, oh, ow) == (2 * mu, sh, sw)).then(|| o[..mu * sh * sw].to_vec())
|
||
});
|
||
let ny = mu * sh * sw;
|
||
let rans = match (&sigma, n_edges) {
|
||
(Some(s), 1..) => {
|
||
let mut vals = Vec::with_capacity(ny);
|
||
for p in 0..ny {
|
||
let b = wai::int_entropy::det_build_index(s[p], &edges);
|
||
let span = cdfs[b].len() as i64 - 2;
|
||
let v = match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=3 => offsets[b] as i64 + span + r.below(40) as i64 - 20,
|
||
_ => offsets[b] as i64 + r.below(span.max(1) as u64) as i64,
|
||
} as i32;
|
||
vals.push((v, cdfs[b].as_slice(), cdf_lengths[b], offsets[b]));
|
||
}
|
||
rans_of(&vals).unwrap_or_else(|| (0..8 + 4 * r.below(8)).map(|_| r.below(256) as u8).collect())
|
||
}
|
||
_ => (0..8 + 4 * r.below(8)).map(|_| r.below(256) as u8).collect(),
|
||
};
|
||
let mut cond = Cond {
|
||
edges, cdfs, cdf_lengths, offsets, z_cdfs, z_cdf_lengths, z_offsets, z_median_q,
|
||
m: mu, sh, sw, n_z: nz, zh, zw, z_rans, rans,
|
||
};
|
||
damage(r, &mut cond.z_rans);
|
||
damage(r, &mut cond.rans);
|
||
(g_s, h_s, cond)
|
||
}
|
||
|
||
/// The direct conditional decode, a panic (an overflow-checked build's arithmetic
|
||
/// overflow) caught.
|
||
fn hyper_direct(g: &IntModel, h: &IntModel, c: &Cond) -> Option<Result<Vec<u8>, RefusalKind>> {
|
||
let e = c.ent();
|
||
catch_unwind(AssertUnwindSafe(|| wai::int_hyper_synth::legacy::decode_conditional_checked(g, h, &e, &c.st()).map(|d| d.0).map_err(|r| r.kind))).ok()
|
||
}
|
||
|
||
/// Gate 4 of the migration, for `wai.neural.int_hyper` (and so the keyframe of
|
||
/// `wai.video.int_hyper`): the decode through the operator set against the direct decode
|
||
/// on random sets and payloads. Outside the planned breaks both decode to the same image
|
||
/// or both refuse with the same code, and the direct decode never panics; under a planned
|
||
/// break the decode through the operator set refuses.
|
||
#[test]
|
||
fn int_hyper_random_sets_and_payloads_decode_alike() {
|
||
let (mut alike, mut refused, mut breaks) = (0u64, std::collections::BTreeMap::<String, u64>::new(), std::collections::BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_hyper", 3000, |r, seed| {
|
||
let (g_s, h_s, c) = int_hyper_case(r);
|
||
let why = || format!("seed {seed}\npayload {c:?}");
|
||
let (g, h) = (IntModel::from_bytes(&g_s).unwrap(), IntModel::from_bytes(&h_s).unwrap());
|
||
let new = wai::int_hyper_synth::ops::decode(&g, &h, &c.ent(), &c.st()).map(|d| d.0).map_err(|r| r.kind);
|
||
match int_hyper_planned_break(&g_s, &h_s) {
|
||
Some(b) => {
|
||
assert!(new.is_err(), "{b}: the decode through the operator set must refuse it\n{}", why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
}
|
||
None => match (&new, hyper_direct(&g, &h, &c)) {
|
||
(Ok(a), Some(Ok(b))) => {
|
||
assert!(*a == b, "the two decodes differ\n{}", why());
|
||
alike += 1;
|
||
}
|
||
(Err(a), Some(Err(b))) => {
|
||
assert_eq!(*a, b, "the two decodes refuse with different codes\n{}", why());
|
||
*refused.entry(format!("{a:?}")).or_default() += 1;
|
||
}
|
||
(_, old) => panic!("new {new:?}, direct {old:?}\n{}", why()),
|
||
},
|
||
}
|
||
});
|
||
eprintln!("int_hyper: {alike} decoded alike, refused by both {refused:?}, planned breaks {breaks:?}");
|
||
// Two networks and two streams compound the generator's refusal rates, so fewer cases
|
||
// decode than for one network; an eighth is still hundreds per push.
|
||
assert!(alike * 8 > n, "the generator must produce decodable cases: {alike} of {n}");
|
||
assert!(n < 1000 || refused.contains_key("FieldRange"), "no case reached a check range");
|
||
assert!(n < 1000 || ["StreamExhausted", "StreamNoncanonical", "GeometryMismatch", "ModelMismatch"].iter().all(|k| refused.contains_key(*k)), "a refusal kind was not reached: {refused:?}");
|
||
assert!(n < 1000 || breaks.len() >= 3, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 item 3 for `wai.neural.int_hyper`, each
|
||
/// on a set and payload the direct decode decodes, or refuses with another code: the
|
||
/// classifier names it, and the decode through the operator set refuses it
|
||
/// (`model_mismatch`, before either stream).
|
||
#[test]
|
||
fn int_hyper_planned_breaks_are_new_refusals() {
|
||
// A payload of two hyper-latent channels of side 2, every symbol the first of the
|
||
// table `t` (offset 2), so every hyper-latent is 2·2^12; a latent of one channel of
|
||
// side 2, every symbol the first of the one scale edge's table (offset 2).
|
||
let payload = |t: Vec<i32>| {
|
||
let z: Vec<(i32, &[i32], usize, i32)> = (0..8).map(|_| (2, t.as_slice(), t.len(), 2)).collect();
|
||
let y = [0, 1 << 15, 1 << 16];
|
||
let yv: Vec<(i32, &[i32], usize, i32)> = (0..4).map(|_| (2, y.as_slice(), 3, 2)).collect();
|
||
Cond {
|
||
edges: vec![1 << 12], cdfs: vec![y.to_vec()], cdf_lengths: vec![3], offsets: vec![2],
|
||
z_cdfs: vec![t.clone(); 2], z_cdf_lengths: vec![t.len(); 2], z_offsets: vec![2; 2], z_median_q: vec![0; 2],
|
||
m: 1, sh: 2, sw: 2, n_z: 2, zh: 2, zw: 2, z_rans: rans_of(&z).unwrap(), rans: rans_of(&yv).unwrap(),
|
||
}
|
||
};
|
||
let cl = payload(vec![0, 1 << 15, 1 << 16]);
|
||
// h_s: σ and μ are the two hyper-latent channels; g_s: the latent to each of three channels.
|
||
let ident = Whm::Conv { inc: 2, out: 2, k: 1, s: 1, p: 0, w: vec![1, 0, 0, 1], m: vec![1; 2], b: vec![0; 2] };
|
||
let to3 = |w: i16, b: i64| Whm::ConvT { inc: 1, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![w; 3], m: vec![1; 3], b: vec![b; 3] };
|
||
let (h0, g0) = (whm1_bytes(12, 16, 0, &[ident.clone()]), whm1_bytes(12, 16, 0, &[to3(1, 0)]));
|
||
let decode = |g_s: &[u8], h_s: &[u8], c: &Cond| {
|
||
let (g, h) = (IntModel::from_bytes(g_s).unwrap(), IntModel::from_bytes(h_s).unwrap());
|
||
(wai::int_hyper_synth::ops::decode(&g, &h, &c.ent(), &c.st()).map(|d| d.0).map_err(|r| r.kind), hyper_direct(&g, &h, c))
|
||
};
|
||
// The base decodes alike: every latent 2·2^12 + 2·2^12, clamped to 2^12: 255.
|
||
assert_eq!(int_hyper_planned_break(&g0, &h0), None);
|
||
let (new, old) = decode(&g0, &h0, &cl);
|
||
assert_eq!((new, old), (Ok(vec![255; 12]), Some(Ok(vec![255; 12]))));
|
||
let expect = |b: &str, g_s: &[u8], h_s: &[u8], c: &Cond, direct: Option<RefusalKind>| {
|
||
assert_eq!(int_hyper_planned_break(g_s, h_s), Some(b), "{b}: the classifier");
|
||
let (new, old) = decode(g_s, h_s, c);
|
||
match direct {
|
||
None => assert!(matches!(old, Some(Ok(_))), "{b}: the direct decode decodes it: {old:?}"),
|
||
Some(k) => assert_eq!(old, Some(Err(k)), "{b}: the direct decode's code"),
|
||
}
|
||
assert_eq!(new, Err(RefusalKind::ModelMismatch), "{b}: the decode through the operator set refuses it");
|
||
};
|
||
// No layer of tag 0, 1 or 2 in h_s: σ and μ are the hyper-latent's channels directly.
|
||
expect("no layer of tag 0, 1 or 2", &g0, &whm1_bytes(12, 16, 0, &[Whm::Relu]), &cl, None);
|
||
// A header shift no layer of h_s uses (only an inverse GDN that leaves the values as
|
||
// they are), and a gf no layer of g_s uses.
|
||
let igdn = Whm::Igdn { c: 2, g: vec![0; 4], b: vec![1 << 24; 2] };
|
||
expect("a shift or gf of 128 or more that no layer uses", &g0, &whm1_bytes(12, 16, 200, &[igdn]), &cl, None);
|
||
expect("a shift or gf of 128 or more that no layer uses", &whm1_bytes(12, 200, 0, &[to3(1, 0)]), &h0, &cl, None);
|
||
// A bias at the end of i64 in g_s: refused directly as g_s runs (field_range), after
|
||
// both streams; at load through the lowering. In h_s, with a hyper-latent stream that
|
||
// runs out: refused directly for the stream, through the lowering for the set.
|
||
expect("a bias of magnitude 2^63 - 1 or more", &whm1_bytes(12, 16, 0, &[to3(1, i64::MAX)]), &h0, &cl, Some(RefusalKind::FieldRange));
|
||
let mut short = payload(vec![0, 1, 1 << 16]);
|
||
short.z_rans.truncate(8);
|
||
let h_min = Whm::Conv { inc: 2, out: 2, k: 1, s: 1, p: 0, w: vec![1, 0, 0, 1], m: vec![1; 2], b: vec![i64::MIN; 2] };
|
||
expect("a bias of magnitude 2^63 - 1 or more", &g0, &whm1_bytes(12, 16, 0, &[h_min]), &short, Some(RefusalKind::StreamExhausted));
|
||
// A check range its producer cannot meet, in g_s: its first layer gives every value
|
||
// 2^30 (weight 0, bias 2^30), into a layer of weight 2^11 under the multiplier 2^40
|
||
// and shift 0, whose range is ±4095. Refused directly at that layer (field_range).
|
||
let first = Whm::ConvT { inc: 1, out: 1, k: 1, s: 1, p: 0, op: 0, w: vec![0], m: vec![1], b: vec![1 << 30] };
|
||
let tight = Whm::ConvT { inc: 1, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![1 << 11; 3], m: vec![1 << 40; 3], b: vec![0; 3] };
|
||
expect("a check range its producer cannot meet", &whm1_bytes(12, 16, 0, &[first, tight]), &h0, &cl, Some(RefusalKind::FieldRange));
|
||
}
|
||
|
||
// ------------------------------------------------------------------ int_mlicpp (M5)
|
||
|
||
/// A `WMB1` bundle's bytes: the three `WHM1` networks, then the tables (with `afrac`).
|
||
fn wmb1_bytes(h_s: &[u8], entropy: &[u8], g_s: &[u8], tables: &Value) -> Vec<u8> {
|
||
let t = serde_json::to_vec(tables).unwrap();
|
||
let mut v = b"WMB1".to_vec();
|
||
for s in [h_s, entropy, g_s, t.as_slice()] {
|
||
v.extend((s.len() as u32).to_le_bytes());
|
||
v.extend(s);
|
||
}
|
||
v
|
||
}
|
||
|
||
/// A `WMB1` bundle's four sections.
|
||
fn wmb1_sections(b: &[u8]) -> Option<[Vec<u8>; 4]> {
|
||
let mut o = 4;
|
||
let mut s: [Vec<u8>; 4] = Default::default();
|
||
for x in &mut s {
|
||
let n = u32::from_le_bytes(b.get(o..o + 4)?.try_into().ok()?) as usize;
|
||
*x = b.get(o + 4..o + 4 + n)?.to_vec();
|
||
o += 4 + n;
|
||
}
|
||
Some(s)
|
||
}
|
||
|
||
/// A `WIM1` payload's bytes: `H`, `W`; `Cz, Zh, Zw, Cy, Yh, Yw, K`; the two streams.
|
||
fn wim1_bytes(h: u32, w: u32, dims: [u16; 7], z: &[u8], y: &[u8]) -> Vec<u8> {
|
||
let mut p = b"WIM1".to_vec();
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(w.to_le_bytes());
|
||
dims.iter().for_each(|d| p.extend(d.to_le_bytes()));
|
||
p.extend(0u16.to_le_bytes());
|
||
p.extend((z.len() as u32).to_le_bytes());
|
||
p.extend((y.len() as u32).to_le_bytes());
|
||
p.extend(z);
|
||
p.extend(y);
|
||
p
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 that a `wai.neural.int_mlicpp` case falls
|
||
/// under, if any, read from the bundle and the payload's header alone. A bundle that does
|
||
/// not parse, or a header or bundle that the checks both decodes make first refuse
|
||
/// (`int_mlicpp::ops::check`: the header, the `z` tables, each network's geometry on the
|
||
/// header's shapes), is not a break: both refuse it alike. Otherwise the decode through the
|
||
/// operator set loads the three networks (`h_s`, `entropy`, `g_s` with the RGB8 step, by
|
||
/// `whm1/1` under `acc64`) and plans each on the payload's shapes before either stream,
|
||
/// and refuses (`model_mismatch`) a bundle that:
|
||
/// - falls under one of the breaks of the other `WHM1` decoders ([`load_break`]);
|
||
/// - has a layer that does not fit the channels the layer before it gives, a header shift
|
||
/// or `gf` of 128 or more that a layer uses, or a leaky ReLU's shift of 64 or more, which
|
||
/// the direct decode refuses (`model_mismatch` too) only when that network runs, after
|
||
/// the streams and networks before it, so an earlier refusal of the payload comes first
|
||
/// there;
|
||
/// - has a network whose input is not the payload's channel count (`Cz`, `3·Cy`, `Cy`),
|
||
/// which the direct decode likewise refuses when that network runs; or a convolution
|
||
/// whose padded side exceeds `2^26`, which the operator set's run-time plan caps
|
||
/// (integer-operators §5.2) and the direct decode's geometry does not.
|
||
fn int_mlicpp_planned_break(bundle: &[u8], payload: &[u8]) -> Option<&'static str> {
|
||
use wai_ops::lower::NetLayer as N;
|
||
let m = wai::int_mlicpp::IntMlicModel::from_bundle(bundle).ok()?;
|
||
let hd = wai::int_mlicpp::ops::check(&m, payload).ok()?;
|
||
let secs = wmb1_sections(bundle)?;
|
||
for (k, rgb8) in [(0, false), (1, false), (2, true)] {
|
||
let net = wai_ops::lower::parse_whm1(&secs[k]).ok()?;
|
||
if let Some(b) = load_break(&net, wai_ops::lower::whm1(&secs[k], rgb8)) {
|
||
return Some(b);
|
||
}
|
||
let conv = net.layers.iter().any(|l| matches!(l, N::ConvT { .. } | N::Conv { .. }));
|
||
let igdn = net.layers.iter().any(|l| matches!(l, N::Igdn { .. }));
|
||
if (net.shift > 127 && conv) || (net.gf > 127 && igdn) {
|
||
return Some("a shift or gf of 128 or more that a layer uses");
|
||
}
|
||
if net.layers.iter().any(|l| matches!(l, N::Leaky { slope_frac, .. } if *slope_frac > 63)) {
|
||
return Some("a leaky ReLU's shift of 64 or more");
|
||
}
|
||
let mut c: Option<u32> = None;
|
||
for l in &net.layers {
|
||
let (inc, out) = match l {
|
||
N::ConvT { inc, out, .. } | N::Conv { inc, out, .. } => (*inc, *out),
|
||
N::Igdn { c, .. } => (*c, *c),
|
||
_ => continue,
|
||
};
|
||
if c.is_some_and(|c| c != inc) {
|
||
return Some("a layer that does not fit the channels before it");
|
||
}
|
||
c = Some(out);
|
||
}
|
||
}
|
||
let n = wai::int_mlicpp::ops::load(&m).ok()?;
|
||
let (cy, yh, yw) = (hd.cy as u64, hd.yh as u64, hd.yw as u64);
|
||
for (g, input) in [(n.h_s.graph(), (hd.cz as u64, hd.zh as u64, hd.zw as u64)), (n.entropy.graph(), (3 * cy, yh, yw)), (n.g_s.graph(), (cy, yh, yw))] {
|
||
match wai_ops::plan::run_plan(g, &[input], &[]) {
|
||
Err(r) if r.code == wai_ops::Code::ModelMismatch && r.at == wai_ops::Site::Input(0) => {
|
||
return Some("a network whose input is not the payload's channel count");
|
||
}
|
||
Err(r) if r.code == wai_ops::Code::TooLarge => return Some("a convolution's padded side past 2^26"),
|
||
_ => {}
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
/// A refusal of either `int_mlicpp` decode, as both name it: the code (integer-payloads §8),
|
||
/// then which check: for a network as it runs, the network (the direct decode names the
|
||
/// layer's arithmetic, the operator set the `check` range); for any other, the decode's
|
||
/// own message, which the two share.
|
||
fn mlic_label(e: &wai::int_mlicpp::IntMlicError) -> String {
|
||
use wai::int_mlicpp::IntMlicError as E;
|
||
let (code, m) = match e {
|
||
E::Payload(m) => ("payload_malformed", m.as_str()),
|
||
E::Model(m) => ("model_mismatch", m.as_str()),
|
||
};
|
||
let m = match m.find(" refused") {
|
||
Some(i) if ["h_s", "entropy", "g_s"].contains(&&m[..i]) => &m[..i],
|
||
_ => m,
|
||
};
|
||
format!("{code}: {m}")
|
||
}
|
||
|
||
/// One `int_mlicpp` decode's outcome: the `y` symbols it read (buckets and symbols), and
|
||
/// the image or its refusal's label.
|
||
type MlicOutcome = (Vec<usize>, Vec<i32>, Result<Vec<u8>, String>);
|
||
|
||
fn mlic_outcome((t, r): (wai::int_mlicpp::Trace, Result<wai::int_mlicpp::IntMlicImage, wai::int_mlicpp::IntMlicError>)) -> MlicOutcome {
|
||
(t.buckets, t.symbols, r.map(|i| i.rgb).map_err(|e| mlic_label(&e)))
|
||
}
|
||
|
||
/// A layer of an entropy network: a stride-1 convolution from `c` channels to `out` that
|
||
/// keeps the size (kernel 1 or 3, padding `k / 2`; one in 32 another padding); weights of
|
||
/// up to 2^4, 2^8 or 2^10 (one in sixteen up to i16's range), a quarter 0; multipliers up
|
||
/// to 2^12 (one in 128 up to 2^40); biases mostly up to 2^20, one in 256 at an end of i64.
|
||
fn same_size_layer(r: &mut Rng, c: u32, out: u32) -> Whm {
|
||
let k = [1u32, 3][r.below(2) as usize];
|
||
let p = if r.below(32) == 0 { r.below(3) as u32 } else { k / 2 };
|
||
let wmax = if r.below(16) == 0 { 32767 } else { [16, 256, 1024][r.below(3) as usize] };
|
||
let w: Vec<i16> = (0..c * out * k * k).map(|_| if r.below(4) == 0 { 0 } else { r.signed(wmax) as i16 }).collect();
|
||
let m: Vec<i64> = (0..out).map(|_| if r.below(128) == 0 { r.signed(1 << 40) } else { r.signed(1 << 12) }).collect();
|
||
let b: Vec<i64> = (0..out)
|
||
.map(|_| match r.below(256) {
|
||
0 => [i64::MAX, i64::MIN, i64::MIN + 1, i64::MAX - 1][r.below(4) as usize],
|
||
1 => r.signed(1 << 40),
|
||
_ => r.signed(1 << 20),
|
||
})
|
||
.collect();
|
||
Whm::Conv { inc: c, out, k, s: 1, p, w, m, b }
|
||
}
|
||
|
||
/// A random `wai.neural.int_mlicpp` bundle and `WIM1` payload. The bundle: `afrac` 12
|
||
/// mostly (one in sixteen anywhere in 1..=30, one in 32 at 0 or 31, and one network's not
|
||
/// the others' one in 64); each network's header `shift` 12 to 23, and its `gf` or `shift`
|
||
/// of 128 or more one in 64 each; `h_s` from `Cz` (1 to 3) channels, up to one of `splat_layers`' layers then a last
|
||
/// layer to `2·Cy` (one in 32 another count); `entropy` from `3·Cy`, up to one layer that
|
||
/// keeps the size (a leaky ReLU, a ReLU, an inverse GDN, or [`same_size_layer`]) and up to
|
||
/// one convolution and leaky ReLU more, then [`same_size_layer`] to `2·Cy` (one in 32
|
||
/// another count); `g_s` from `Cy` (1, 2 or 4), up to two layers then a last layer to three
|
||
/// channels (one in 32 not); the tables as [`int_hyper_case`]'s, with `afrac` (one set in
|
||
/// 64 with no scale edge, a hyper-latent channel short, or a table's length past its row).
|
||
/// The payload: `K` a divisor of `Cy` (one in 64 not); a hyper-latent of side 2 or 3 (one
|
||
/// in sixteen not square), its symbols as [`int_hyper_case`]'s (medians at an extreme one
|
||
/// in sixteen), one case in eight with a whole channel at `h_s`'s input `check` range or
|
||
/// one past it; the latent's size `h_s`'s
|
||
/// output and the image's `g_s`'s (each one in sixteen off by one); the `y` stream coded in
|
||
/// the decode's order (each slice's anchors, then the rest), each symbol against the table
|
||
/// the direct networks' `σ` buckets it to, mostly inside its table, some escaped and some
|
||
/// far, until a network refuses (the rest of the stream is then random); each stream
|
||
/// damaged as [`damage`] says.
|
||
fn int_mlicpp_case(r: &mut Rng) -> (Vec<u8>, Vec<u8>) {
|
||
use wai::int_hyper_synth::legacy::run_net_checked;
|
||
let afrac = match r.below(32) {
|
||
0 | 1 => 1 + r.below(30) as u32,
|
||
2 => [0, 31][r.below(2) as usize],
|
||
_ => 12,
|
||
};
|
||
let odd = r.below(64) == 0;
|
||
let header = |r: &mut Rng| {
|
||
let gf = if r.below(64) == 0 { 128 + r.below(64) as u32 } else { 16 };
|
||
let shift = if r.below(64) == 0 { 128 + r.below(64) as u32 } else { 12 + r.below(12) as u32 };
|
||
(gf, shift)
|
||
};
|
||
let cz = 1 + r.below(3) as u32;
|
||
let cy = [1u32, 2, 2, 4][r.below(4) as usize];
|
||
let divisors: Vec<u32> = [1u32, 2, 4].into_iter().filter(|d| cy % d == 0).collect();
|
||
let kk = if r.below(64) == 0 { 3 } else { divisors[r.below(divisors.len() as u64) as usize] };
|
||
// h_s: [Cz, Zh, Zw] → [2·Cy, Yh, Yw].
|
||
let (n, relu_only) = (r.below(2) as usize, r.below(32) == 0);
|
||
let (mut hl, c) = splat_layers(r, cz, n, relu_only);
|
||
let out = if r.below(32) == 0 { 1 + r.below(6) as u32 } else { 2 * cy };
|
||
hl.push(last_layer(r, c, out));
|
||
let (gf, shift) = header(r);
|
||
let h_s = whm1_bytes(if odd { afrac ^ 1 } else { afrac }, gf, shift, &hl);
|
||
// entropy: [3·Cy, Yh, Yw] → [2·Cy, Yh, Yw], keeping the size.
|
||
let mut el = Vec::new();
|
||
let mut c = 3 * cy;
|
||
match r.below(8) {
|
||
0 | 1 => el.push(Whm::Leaky { q: if r.below(32) == 0 { r.signed(1 << 40) } else { r.signed(1 << 10) }, f: if r.below(32) == 0 { 64 } else { r.below(16) as u32 } }),
|
||
2 => el.push(Whm::Relu),
|
||
3 => {
|
||
let g: Vec<i32> = (0..c * c).map(|_| r.below(1 << 12) as i32).collect();
|
||
let b: Vec<i64> = (0..c).map(|_| r.below(1 << 28) as i64).collect();
|
||
el.push(Whm::Igdn { c, g, b });
|
||
}
|
||
4 | 5 => {
|
||
let mid = 1 + r.below(6) as u32;
|
||
el.push(same_size_layer(r, c, mid));
|
||
c = mid;
|
||
}
|
||
_ => {}
|
||
}
|
||
if r.below(2) == 0 {
|
||
let mid = 1 + r.below(6) as u32;
|
||
el.push(same_size_layer(r, c, mid));
|
||
el.push(Whm::Leaky { q: r.signed(1 << 10), f: r.below(16) as u32 });
|
||
c = mid;
|
||
}
|
||
let out = if r.below(32) == 0 { 1 + r.below(6) as u32 } else { 2 * cy };
|
||
el.push(same_size_layer(r, c, out));
|
||
let (gf, shift) = header(r);
|
||
let entropy = whm1_bytes(afrac, gf, shift, &el);
|
||
// g_s: [Cy, Yh, Yw] → [3, H, W].
|
||
let (n, relu_only) = (r.below(3) as usize, r.below(32) == 0);
|
||
let (mut gl, c) = splat_layers(r, cy, n, relu_only);
|
||
let out = if r.below(32) == 0 { 1 + r.below(4) as u32 } else { 3 };
|
||
gl.push(last_layer(r, c, out));
|
||
let (gf, shift) = header(r);
|
||
let g_s = whm1_bytes(afrac, gf, shift, &gl);
|
||
|
||
// The tables.
|
||
let a = afrac.min(30);
|
||
let n_edges = if r.below(64) == 0 { 0 } else { 1 + r.below(4) as usize };
|
||
let mut edges = Vec::new();
|
||
let mut e = 1 + r.below(1 << (a + 2)) as i64;
|
||
for _ in 0..n_edges {
|
||
edges.push(e);
|
||
e += 1 + r.below(1 << (a + 2)) as i64;
|
||
}
|
||
let cdfs: Vec<Vec<i32>> = (0..n_edges).map(|_| cdf_table(r)).collect();
|
||
let mut cdf_lengths: Vec<usize> = cdfs.iter().map(Vec::len).collect();
|
||
let offsets: Vec<i32> = (0..n_edges).map(|_| r.signed(3) as i32).collect();
|
||
let k = if r.below(64) == 0 { cz as usize - 1 } else { cz as usize };
|
||
let z_cdfs: Vec<Vec<i32>> = (0..k).map(|_| cdf_table(r)).collect();
|
||
let mut z_cdf_lengths: Vec<usize> = z_cdfs.iter().map(Vec::len).collect();
|
||
if r.below(64) == 0 {
|
||
match (n_edges, k) {
|
||
(1.., _) if r.below(2) == 0 => cdf_lengths[0] += 1,
|
||
(_, 1..) => z_cdf_lengths[0] += 1,
|
||
_ => {}
|
||
}
|
||
}
|
||
let z_offsets: Vec<i32> = (0..k).map(|_| r.signed(3) as i32).collect();
|
||
let mut z_median_q: Vec<i64> = (0..k)
|
||
.map(|_| match r.below(16) {
|
||
0 => [1i64 << 47, -(1 << 47), 1 << 60, -(1 << 60), i64::MAX, i64::MIN][r.below(6) as usize],
|
||
_ => r.signed(1 << (a + 4)),
|
||
})
|
||
.collect();
|
||
|
||
// The hyper-latent.
|
||
let zh = 2 + r.below(2) as usize;
|
||
let zw = if r.below(16) == 0 { 1 + r.below(3) as usize } else { zh };
|
||
let (nz, plane) = (cz as usize, zh * zw);
|
||
let zt = |ch: usize| ch.min(k.saturating_sub(1));
|
||
let mut zv: Vec<i32> = Vec::with_capacity(nz * plane);
|
||
for ch in 0..nz {
|
||
let (off, span) = if k == 0 { (0, 1) } else { (z_offsets[zt(ch)] as i64, z_cdfs[zt(ch)].len() as i64 - 2) };
|
||
for _ in 0..plane {
|
||
zv.push(match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=4 => off + span + r.below(40) as i64 - 20,
|
||
_ => off + r.below(span.max(1) as u64) as i64,
|
||
} as i32);
|
||
}
|
||
}
|
||
if r.below(8) == 0 && k > 0 {
|
||
if let Ok(g) = wai_ops::lower::whm1(&h_s, false) {
|
||
let x0 = g.tensors[0].hi as i128;
|
||
let ch = r.below(k as u64) as usize;
|
||
let v = z_offsets[ch];
|
||
let target = [x0, x0 + 1, -x0, -x0 - 1][r.below(4) as usize];
|
||
if let Ok(med) = i64::try_from(target - ((v as i128) << a)) {
|
||
z_median_q[ch] = med;
|
||
zv[ch * plane..(ch + 1) * plane].iter_mut().for_each(|x| *x = v);
|
||
}
|
||
}
|
||
}
|
||
let random = |r: &mut Rng| (0..8 + 4 * r.below(8)).map(|_| r.below(256) as u8).collect::<Vec<u8>>();
|
||
let mut z_rans = if k == 0 {
|
||
random(r)
|
||
} else {
|
||
let vals: Vec<(i32, &[i32], usize, i32)> = (0..nz * plane).map(|i| { let t = zt(i / plane); (zv[i], z_cdfs[t].as_slice(), z_cdf_lengths[t], z_offsets[t]) }).collect();
|
||
rans_of(&vals).unwrap_or_else(|| random(r))
|
||
};
|
||
|
||
// The sizes, then the latent, coded in the decode's order against the direct networks.
|
||
let side = |l: &[Whm], n: usize| splat_side(l, n as i64).unwrap_or(1) as usize;
|
||
let (mut yh, yw) = (side(&hl, zh), side(&hl, zw));
|
||
if r.below(16) == 0 {
|
||
yh += 1;
|
||
}
|
||
let (mut hh, ww) = (side(&gl, yh), side(&gl, yw));
|
||
if r.below(16) == 0 {
|
||
hh += 1;
|
||
}
|
||
let (hm, em) = (IntModel::from_bytes(&h_s).unwrap(), IntModel::from_bytes(&entropy).unwrap());
|
||
let cyu = cy as usize;
|
||
let ny = cyu * yh * yw;
|
||
let z_hat: Option<Vec<i64>> = (0..nz * plane).map(|i| (zv[i] as i64).checked_mul(1 << a)?.checked_add(*z_median_q.get(zt(i / plane))?)).collect();
|
||
let hyper = z_hat.and_then(|z| {
|
||
let (o, oc, oh, ow) = run_net_checked(&hm, &z, nz, zh, zw).ok()?;
|
||
((oc, oh, ow) == (2 * cyu, yh, yw)).then_some(o)
|
||
});
|
||
let mut vals: Vec<(i32, Vec<i32>, usize, i32)> = Vec::new();
|
||
if let (Some(hyper), 1..) = (hyper, n_edges) {
|
||
let mut ctx = vec![0i64; 3 * ny];
|
||
ctx[ny..].copy_from_slice(&hyper);
|
||
let width = cyu / kk as usize;
|
||
'all: for j in 0..kk as usize {
|
||
for anchor in [true, false] {
|
||
let Ok((params, ..)) = run_net_checked(&em, &ctx, 3 * cyu, yh, yw) else { break 'all };
|
||
if params.len() != 2 * ny {
|
||
break 'all;
|
||
}
|
||
for ch in j * width..(j + 1) * width {
|
||
for (row, col) in (0..yh).flat_map(|y| (0..yw).map(move |x| (y, x))) {
|
||
if ((row + col) % 2 == 0) != anchor {
|
||
continue;
|
||
}
|
||
let e = ch * yh * yw + row * yw + col;
|
||
let b = wai::int_entropy::det_build_index(params[e], &edges);
|
||
let span = cdfs[b].len() as i64 - 2;
|
||
let v = match r.below(32) {
|
||
0 => r.signed(1 << 20),
|
||
1..=3 => offsets[b] as i64 + span + r.below(40) as i64 - 20,
|
||
_ => offsets[b] as i64 + r.below(span.max(1) as u64) as i64,
|
||
} as i32;
|
||
let Some(y) = wai::int_entropy::dequantize(v, a, params[ny + e]) else { break 'all };
|
||
vals.push((v, cdfs[b].clone(), cdf_lengths[b], offsets[b]));
|
||
ctx[e] = y;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
let refs: Vec<(i32, &[i32], usize, i32)> = vals.iter().map(|(v, t, l, o)| (*v, t.as_slice(), *l, *o)).collect();
|
||
let mut y_rans = if refs.is_empty() { random(r) } else { rans_of(&refs).unwrap_or_else(|| random(r)) };
|
||
damage(r, &mut z_rans);
|
||
damage(r, &mut y_rans);
|
||
let tables = json!({
|
||
"afrac": afrac, "scale_edges": edges, "cdfs": cdfs, "cdf_lengths": cdf_lengths, "offsets": offsets,
|
||
"z_cdfs": z_cdfs, "z_cdf_lengths": z_cdf_lengths, "z_offsets": z_offsets, "z_median_q": z_median_q,
|
||
});
|
||
let bundle = wmb1_bytes(&h_s, &entropy, &g_s, &tables);
|
||
let dims = [cz as u16, zh as u16, zw as u16, cy as u16, yh as u16, yw as u16, kk as u16];
|
||
(bundle, wim1_bytes(hh as u32, ww as u32, dims, &z_rans, &y_rans))
|
||
}
|
||
|
||
/// The direct `int_mlicpp` decode, a panic (an overflow-checked build's arithmetic overflow)
|
||
/// caught.
|
||
fn mlic_direct(m: &wai::int_mlicpp::IntMlicModel, p: &[u8]) -> Option<MlicOutcome> {
|
||
catch_unwind(AssertUnwindSafe(|| mlic_outcome(wai::int_mlicpp::legacy::decode_traced(m, p)))).ok()
|
||
}
|
||
|
||
/// Gate 4 of the migration, for `wai.neural.int_mlicpp`: the decode through the operator
|
||
/// set against the direct decode on random bundles and payloads. Outside the planned
|
||
/// breaks both read the same `y` symbols, decode to the same image or both refuse with the
|
||
/// same code at the same check (a network's ranges, or the decode's own), and the direct
|
||
/// decode never panics; under a planned break the decode through the operator set refuses
|
||
/// (`model_mismatch`). A bundle that does not parse is refused by both alike before
|
||
/// either decode starts.
|
||
#[test]
|
||
fn int_mlicpp_random_sets_and_payloads_decode_alike() {
|
||
use std::collections::BTreeMap;
|
||
let (mut alike, mut unparsed, mut refused, mut breaks) = (0u64, 0u64, BTreeMap::<String, u64>::new(), BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_mlicpp", 3000, |r, seed| {
|
||
let (bundle, p) = int_mlicpp_case(r);
|
||
let why = || format!("seed {seed}\nbundle {bundle:?}\npayload {p:?}");
|
||
let Ok(m) = wai::int_mlicpp::IntMlicModel::from_bundle(&bundle) else {
|
||
unparsed += 1;
|
||
return;
|
||
};
|
||
let new = mlic_outcome(wai::int_mlicpp::ops::decode_traced(&m, &p));
|
||
match int_mlicpp_planned_break(&bundle, &p) {
|
||
Some(b) => {
|
||
assert!(matches!(&new.2, Err(e) if e.starts_with("model_mismatch")), "{b}: the decode through the operator set must refuse it: {:?}\n{}", new.2, why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
}
|
||
None => match mlic_direct(&m, &p) {
|
||
Some(old) => {
|
||
assert!(new == old, "the two decodes differ: {:?} / {:?}\n{}", new.2.as_ref().err(), old.2.as_ref().err(), why());
|
||
match &new.2 {
|
||
Ok(_) => alike += 1,
|
||
Err(e) => {
|
||
let (code, msg) = e.split_once(": ").unwrap_or((e, ""));
|
||
let kind = format!("{code}: {}", msg.split_whitespace().take(2).collect::<Vec<_>>().join(" "));
|
||
*refused.entry(kind).or_default() += 1;
|
||
}
|
||
}
|
||
}
|
||
None => panic!("the direct decode panicked\n{}", why()),
|
||
},
|
||
}
|
||
});
|
||
eprintln!("int_mlicpp: {alike} decoded alike, {unparsed} bundles refused by both before decoding, refused by both {refused:?}, planned breaks {breaks:?}");
|
||
// Three networks, two streams and a bundle compound the generator's refusal rates.
|
||
assert!(alike * 10 > n, "the generator must produce decodable cases: {alike} of {n}");
|
||
let reached = |k: &str| refused.keys().any(|x| x.contains(k));
|
||
assert!(n < 1000 || ["h_s", "entropy", "g_s", "stream", "model_mismatch", "payload_malformed"].iter().all(|k| reached(k)), "a refusal was not reached: {refused:?}");
|
||
assert!(n < 1000 || breaks.len() >= 3, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 for `wai.neural.int_mlicpp`, each on a
|
||
/// bundle and payload the direct decode decodes, or refuses later or with another code:
|
||
/// the classifier names it, and the decode through the operator set refuses it
|
||
/// (`model_mismatch`, before either stream).
|
||
#[test]
|
||
fn int_mlicpp_planned_breaks_are_new_refusals() {
|
||
use wai::int_mlicpp::{legacy, ops, IntMlicModel};
|
||
// Cy = 1, K = 1, a latent of side 2; Cz = 2, a hyper-latent of side 2; an image of
|
||
// side 2. h_s: the hyper-latent's two channels as they are; entropy: σ and μ from them;
|
||
// g_s: the latent to each of three channels. Every z symbol is the first of its table
|
||
// (offset 2), so every hyper-latent is 2·2^12; every y symbol the first of the one
|
||
// scale edge's table (offset 2), so every latent is 2·2^12 + 2·2^12, clamped to 2^12:
|
||
// 255.
|
||
let t3 = vec![0, 1 << 15, 1 << 16];
|
||
let tables = |z_short: bool| {
|
||
let nz = if z_short { 1 } else { 2 };
|
||
json!({
|
||
"afrac": 12, "scale_edges": [1 << 12], "cdfs": [t3], "cdf_lengths": [3], "offsets": [2],
|
||
"z_cdfs": vec![t3.clone(); nz], "z_cdf_lengths": vec![3; nz], "z_offsets": vec![2; nz], "z_median_q": vec![0; nz],
|
||
})
|
||
};
|
||
let zs: Vec<(i32, &[i32], usize, i32)> = (0..8).map(|_| (2, t3.as_slice(), 3, 2)).collect();
|
||
let ys: Vec<(i32, &[i32], usize, i32)> = (0..4).map(|_| (2, t3.as_slice(), 3, 2)).collect();
|
||
let (z, y) = (rans_of(&zs).unwrap(), rans_of(&ys).unwrap());
|
||
let payload = |z: &[u8], y: &[u8]| wim1_bytes(2, 2, [2, 2, 2, 1, 2, 2, 1], z, y);
|
||
let ok = payload(&z, &y);
|
||
let cut = |s: &[u8]| s[..s.len() - 4].to_vec();
|
||
let ident = Whm::Conv { inc: 2, out: 2, k: 1, s: 1, p: 0, w: vec![1, 0, 0, 1], m: vec![1; 2], b: vec![0; 2] };
|
||
let pick = Whm::Conv { inc: 3, out: 2, k: 1, s: 1, p: 0, w: vec![0, 1, 0, 0, 0, 1], m: vec![1; 2], b: vec![0; 2] };
|
||
let to3 = |w: i16, b: i64| Whm::ConvT { inc: 1, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![w; 3], m: vec![1; 3], b: vec![b; 3] };
|
||
let net = |shift: u32, gf: u32, l: Vec<Whm>| whm1_bytes(12, gf, shift, &l);
|
||
let (h0, e0, g0) = (net(0, 16, vec![ident.clone()]), net(0, 16, vec![pick.clone()]), net(0, 16, vec![to3(1, 0)]));
|
||
let b = |h: &[u8], e: &[u8], g: &[u8]| wmb1_bytes(h, e, g, &tables(false));
|
||
let decode = |bundle: &[u8], p: &[u8]| {
|
||
let m = IntMlicModel::from_bundle(bundle).unwrap();
|
||
let new = ops::decode(&m, p).map(|i| i.rgb).map_err(|e| mlic_label(&e));
|
||
let old = catch_unwind(AssertUnwindSafe(|| legacy::decode(&m, p).map(|i| i.rgb).map_err(|e| mlic_label(&e)))).ok();
|
||
(new, old)
|
||
};
|
||
// The base decodes alike.
|
||
assert_eq!(int_mlicpp_planned_break(&b(&h0, &e0, &g0), &ok), None);
|
||
let (new, old) = decode(&b(&h0, &e0, &g0), &ok);
|
||
assert_eq!((new, old), (Ok(vec![255; 12]), Some(Ok(vec![255; 12]))));
|
||
let expect = |kind: &str, bundle: Vec<u8>, p: &[u8], direct: Option<&str>| {
|
||
assert_eq!(int_mlicpp_planned_break(&bundle, p), Some(kind), "{kind}: the classifier");
|
||
let (new, old) = decode(&bundle, p);
|
||
match direct {
|
||
None => assert!(matches!(old, Some(Ok(_))), "{kind}: the direct decode decodes it: {old:?}"),
|
||
Some(code) => assert!(matches!(&old, Some(Err(e)) if e.starts_with(code)), "{kind}: the direct decode's code: {old:?}"),
|
||
}
|
||
assert!(matches!(&new, Err(e) if e.starts_with("model_mismatch")), "{kind}: the decode through the operator set refuses it before the streams: {new:?}");
|
||
};
|
||
// The breaks of the other WHM1 decoders, in each of the three networks.
|
||
expect("no layer of tag 0, 1 or 2", b(&net(0, 16, vec![Whm::Relu]), &e0, &g0), &ok, None);
|
||
let igdn = Whm::Igdn { c: 2, g: vec![0; 4], b: vec![1 << 24; 2] };
|
||
expect("a shift or gf of 128 or more that no layer uses", b(&net(200, 16, vec![igdn]), &e0, &g0), &ok, None);
|
||
expect("a shift or gf of 128 or more that no layer uses", b(&h0, &e0, &net(0, 200, vec![to3(1, 0)])), &ok, None);
|
||
expect("a bias of magnitude 2^63 - 1 or more", b(&h0, &e0, &net(0, 16, vec![to3(1, i64::MAX)])), &ok, Some("payload_malformed: g_s"));
|
||
let first = Whm::ConvT { inc: 1, out: 1, k: 1, s: 1, p: 0, op: 0, w: vec![0], m: vec![1], b: vec![1 << 30] };
|
||
let tight = Whm::ConvT { inc: 1, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![1 << 11; 3], m: vec![1 << 40; 3], b: vec![0; 3] };
|
||
expect("a check range its producer cannot meet", b(&h0, &e0, &net(0, 16, vec![first, tight])), &ok, Some("payload_malformed: g_s"));
|
||
// The rules the direct decode makes only when a network runs: the same code where the
|
||
// streams are whole, and the stream's where the streams run out first.
|
||
let chain = |inc: u32| vec![to3(1, 0), Whm::ConvT { inc, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![1; inc as usize * 3], m: vec![1; 3], b: vec![0; 3] }];
|
||
expect("a layer that does not fit the channels before it", b(&h0, &e0, &net(0, 16, chain(2))), &ok, Some("model_mismatch: g_s"));
|
||
expect("a layer that does not fit the channels before it", b(&h0, &e0, &net(0, 16, chain(2))), &payload(&z, &cut(&y)), Some("payload_malformed: y stream"));
|
||
expect("a shift or gf of 128 or more that a layer uses", b(&h0, &e0, &net(200, 16, vec![to3(1, 0)])), &ok, Some("model_mismatch: g_s"));
|
||
expect("a shift or gf of 128 or more that a layer uses", b(&h0, &e0, &net(200, 16, vec![to3(1, 0)])), &payload(&z, &cut(&y)), Some("payload_malformed: y stream"));
|
||
let leaky64 = net(0, 16, vec![pick.clone(), Whm::Leaky { q: 1, f: 64 }]);
|
||
expect("a leaky ReLU's shift of 64 or more", b(&h0, &leaky64, &g0), &ok, Some("model_mismatch: entropy"));
|
||
expect("a leaky ReLU's shift of 64 or more", b(&h0, &leaky64, &g0), &payload(&cut(&z), &y), Some("payload_malformed: z stream"));
|
||
let reads3 = net(0, 16, vec![Whm::Conv { inc: 3, out: 2, k: 1, s: 1, p: 0, w: vec![1; 6], m: vec![1; 2], b: vec![0; 2] }]);
|
||
expect("a network whose input is not the payload's channel count", b(&reads3, &e0, &g0), &ok, Some("model_mismatch: h_s"));
|
||
expect("a network whose input is not the payload's channel count", b(&reads3, &e0, &g0), &payload(&cut(&z), &y), Some("payload_malformed: z stream"));
|
||
// A convolution padded by 2^26 a side and strided by 2^27: the direct decode's geometry
|
||
// gives the output, of side 2, and decodes; the operator set's run-time plan caps the
|
||
// padded side.
|
||
let far = Whm::Conv { inc: 1, out: 1, k: 1, s: 1 << 27, p: 1 << 26, w: vec![1], m: vec![1], b: vec![0] };
|
||
expect("a convolution's padded side past 2^26", b(&h0, &e0, &net(0, 16, vec![far, to3(1, 0)])), &ok, None);
|
||
// Not breaks: a bundle with too few z tables, and a network the shapes refuse, are
|
||
// refused alike before either decode reads a stream.
|
||
let short = wmb1_bytes(&h0, &e0, &g0, &tables(true));
|
||
assert_eq!(int_mlicpp_planned_break(&short, &ok), None);
|
||
let (new, old) = decode(&short, &ok);
|
||
assert!(matches!((&new, &old), (Err(a), Some(Err(b))) if a == b && a.starts_with("model_mismatch")), "{new:?} {old:?}");
|
||
}
|
||
|
||
// ------------------------------------------------------------------ the world renderer (M6)
|
||
|
||
/// A random set of `wai.world.model`'s integer renderer, and a clip for it.
|
||
#[derive(Clone, Debug)]
|
||
struct WorldCase {
|
||
t: Vec<u8>,
|
||
g_s: Vec<u8>,
|
||
c: usize,
|
||
lat: usize,
|
||
z0: Vec<i64>,
|
||
rows: Vec<Vec<i64>>,
|
||
}
|
||
|
||
/// The input channel count of a network's first layer of tag 0, 1 or 2.
|
||
fn in_channels(n: &wai_ops::lower::Net) -> Option<u32> {
|
||
use wai_ops::lower::NetLayer as N;
|
||
n.layers.iter().find_map(|l| match l {
|
||
N::ConvT { inc, .. } | N::Conv { inc, .. } => Some(*inc),
|
||
N::Igdn { c, .. } => Some(*c),
|
||
_ => None,
|
||
})
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 item 2 a world case falls under, if any,
|
||
/// read from the set and the clip alone. The direct renderer checked nothing before it ran,
|
||
/// so each check the renderer makes before its first frame is new: the set refused at load
|
||
/// (`world/1`, loaded under `acc64`) for the `afrac` rule, for `A < 1`, for one of a
|
||
/// `WHM1` network's rules (§13.2, named as [`load_break`] names them) or for layers that do
|
||
/// not chain; a row that does not carry `A` values; and `T` that does not give the latent's
|
||
/// shape back or `g_s` that does not plan on it. A value outside a `check` range is not a
|
||
/// break: the direct guard refuses it alike.
|
||
fn world_planned_break(cs: &WorldCase) -> Option<&'static str> {
|
||
use wai_ops::plan::run_plan;
|
||
let (tn, gn) = (wai_ops::lower::parse_whm1(&cs.t).ok()?, wai_ops::lower::parse_whm1(&cs.g_s).ok()?);
|
||
let loaded = wai_ops::lower::world(&cs.t, &cs.g_s, cs.c as u32).and_then(|l| {
|
||
let (t, g) = (wai::int_ops::load(l.get("T").unwrap())?, wai::int_ops::load(l.get("g_s").unwrap())?);
|
||
Ok((t, g))
|
||
});
|
||
let (t, g) = match loaded {
|
||
Ok(tg) => tg,
|
||
Err(_) => {
|
||
if [tn.afrac, gn.afrac].iter().any(|a| !(1..=30).contains(a)) || tn.afrac != gn.afrac {
|
||
return Some("afrac outside 1..=30, or unequal");
|
||
}
|
||
if let Some(b) = load_break(&tn, wai_ops::lower::whm1(&cs.t, false)).or_else(|| load_break(&gn, wai_ops::lower::whm1(&cs.g_s, true))) {
|
||
return Some(b);
|
||
}
|
||
if in_channels(&tn).is_some_and(|cin| cin <= cs.c as u32) {
|
||
return Some("A < 1");
|
||
}
|
||
return Some("layers that do not chain, or a shift a layer cannot take");
|
||
}
|
||
};
|
||
let a = t.graph().scalars.len();
|
||
if cs.rows.iter().any(|r| r.len() != a) {
|
||
return Some("a row that does not carry A values");
|
||
}
|
||
let shape = [(cs.c as u64, cs.lat as u64, cs.lat as u64)];
|
||
let fits = run_plan(t.graph(), &shape, &vec![0; a]).is_ok_and(|hw| {
|
||
let o = t.graph().outputs[0] as usize;
|
||
(t.graph().tensors[o].channels as u64, hw[o].0, hw[o].1) == shape[0]
|
||
});
|
||
if !fits || run_plan(g.graph(), &shape, &[]).is_err() {
|
||
return Some("T or g_s that does not fit the latent");
|
||
}
|
||
None
|
||
}
|
||
|
||
/// [`same_size_layer`], its multipliers (when `unit` is set) about `2^shift` over the
|
||
/// layer's terms and largest weight, so that its output keeps about its input's
|
||
/// magnitude, of either sign. A random transition of such layers leaves the latent mostly
|
||
/// inside `T`'s clamp, where a difference in `T`'s arithmetic shows.
|
||
fn world_layer_conv(r: &mut Rng, c: u32, out: u32, shift: u32, unit: bool) -> Whm {
|
||
let mut l = same_size_layer(r, c, out);
|
||
if unit && let Whm::Conv { k, w, m, .. } = &mut l {
|
||
let wmax = w.iter().map(|v| v.unsigned_abs() as i64).max().unwrap_or(1).max(1);
|
||
let terms = (c * *k * *k) as i64;
|
||
let g = ((1i64 << shift.min(62)) / (terms * wmax)).max(1);
|
||
for v in m.iter_mut() {
|
||
*v = if r.below(2) == 0 { g } else { -g };
|
||
}
|
||
}
|
||
l
|
||
}
|
||
|
||
/// A random world set and clip. `T`: `afrac` 12 mostly (one in sixteen anywhere in
|
||
/// 1..=30, one in 32 at 0 or 31, and `g_s`'s unequal one in 32), a header `shift` of 8 to
|
||
/// 23 (128 or more one in 64) and `gf` 16 (128 or more one in 64); `C` of 1 to 3 and `A`
|
||
/// of 1 to 4 (0 one in 32); a first layer from `C + A` channels ([`world_layer_conv`],
|
||
/// near unit gain three cases in four), up to one leaky ReLU (its slope below 1 three
|
||
/// cases in four), ReLU or inverse GDN, and a last layer back to `C` (one in sixteen to
|
||
/// `C + 1`, and one in sixteen strided by 2). `g_s`: 1 to 3 of [`splat_layers`]' layers
|
||
/// from `C` channels (none one in 32, ReLUs only one in 32). The clip: a latent of side 1
|
||
/// to 4; `z_0` within `±2^(afrac+1)` mostly, one case in four with a value at `g_s`'s
|
||
/// input range or one past it and one in sixteen at an end of `i64`; 0 to 4 rows of moves
|
||
/// `−1, 0, 1` at `2^afrac`, one row in eight with a value at `T`'s `x_0` range or one past
|
||
/// it, one in 32 with a value up to `2^40`, and one in sixteen without `A` values.
|
||
fn world_case(r: &mut Rng) -> WorldCase {
|
||
let afrac = match r.below(32) {
|
||
0 | 1 => 1 + r.below(30) as u32,
|
||
2 => [0, 31][r.below(2) as usize],
|
||
_ => 12,
|
||
};
|
||
let g_afrac = if r.below(32) == 0 { afrac ^ 1 } else { afrac };
|
||
let shift = if r.below(64) == 0 { 128 + r.below(64) as u32 } else { 8 + r.below(16) as u32 };
|
||
let gf = if r.below(64) == 0 { 128 + r.below(64) as u32 } else { 16 };
|
||
let c = 1 + r.below(3) as u32;
|
||
let a = if r.below(32) == 0 { 0 } else { 1 + r.below(4) as u32 };
|
||
let h1 = 1 + r.below(4) as u32;
|
||
let unit = r.below(4) != 0;
|
||
let mut tl = vec![world_layer_conv(r, c + a, h1, shift, unit)];
|
||
match r.below(4) {
|
||
0 => {
|
||
let f = r.below(16) as u32;
|
||
tl.push(Whm::Leaky { q: if unit { r.below(1 << f) as i64 } else { r.signed(1 << 10) }, f });
|
||
}
|
||
1 => tl.push(Whm::Relu),
|
||
2 => tl.push(Whm::Igdn {
|
||
c: h1,
|
||
g: (0..h1 * h1).map(|_| r.below(1 << 12) as i32).collect(),
|
||
b: (0..h1).map(|_| (1 << 24) + r.below(1 << 20) as i64).collect(),
|
||
}),
|
||
_ => {}
|
||
}
|
||
let out = if r.below(16) == 0 { c + 1 } else { c };
|
||
let mut last = world_layer_conv(r, h1, out, shift, unit);
|
||
if r.below(16) == 0 && let Whm::Conv { s, .. } = &mut last {
|
||
*s = 2;
|
||
}
|
||
tl.push(last);
|
||
let t = whm1_bytes(afrac, gf, shift, &tl);
|
||
let n = if r.below(32) == 0 { 0 } else { 1 + r.below(3) as usize };
|
||
let only_relu = r.below(32) == 0;
|
||
let (gl, _) = splat_layers(r, c, n, only_relu);
|
||
let g_s = whm1_bytes(g_afrac, 16, 8 + r.below(16) as u32, &gl);
|
||
|
||
let (c, lat) = (c as usize, 1 + r.below(4) as usize);
|
||
let one = 1i64 << afrac.min(30);
|
||
let ranges = wai_ops::lower::world(&t, &g_s, c as u32).ok().map(|l| {
|
||
let (tg, gg) = (l.get("T").unwrap(), l.get("g_s").unwrap());
|
||
(tg.tensors[1 + tg.scalars.len()].hi, gg.tensors[0].hi)
|
||
});
|
||
let edge = |r: &mut Rng, x: i64| [x, x.saturating_add(1), -x, -x.saturating_add(1)][r.below(4) as usize];
|
||
let mut z0: Vec<i64> = (0..c * lat * lat).map(|_| r.signed(2 * one)).collect();
|
||
let k = r.below(z0.len() as u64) as usize;
|
||
match (r.below(16), ranges) {
|
||
(0..=3, Some((_, xg))) => z0[k] = edge(r, xg),
|
||
(4, _) => z0[k] = [i64::MIN, i64::MAX][r.below(2) as usize],
|
||
_ => {}
|
||
}
|
||
let mut rows = Vec::new();
|
||
for _ in 0..r.below(5) {
|
||
let n = match r.below(16) {
|
||
0 if a > 0 => a as usize - 1,
|
||
1 => a as usize + 1,
|
||
_ => a as usize,
|
||
};
|
||
let mut row: Vec<i64> = (0..n).map(|_| r.signed(1) * one).collect();
|
||
if !row.is_empty() {
|
||
let p = r.below(row.len() as u64) as usize;
|
||
match (r.below(32), ranges) {
|
||
(0..=3, Some((xt, _))) => row[p] = edge(r, xt),
|
||
(4, _) => row[p] = r.signed(1 << 40),
|
||
_ => {}
|
||
}
|
||
}
|
||
rows.push(row);
|
||
}
|
||
WorldCase { t, g_s, c, lat, z0, rows }
|
||
}
|
||
|
||
/// Where a world graph refused, as the layer whose input check it is: for `T` (`A` fills,
|
||
/// the concatenation, then the layers), the output of operator `A + ℓ`; for `g_s`, its
|
||
/// input for layer 0 and the output of operator `ℓ − 1` for layer `ℓ`. `None` for a
|
||
/// refusal that is no layer's check.
|
||
fn world_layer(net: &str, a: usize, at: wai_ops::Site) -> Option<usize> {
|
||
match (net, at) {
|
||
("T", wai_ops::Site::Op(i)) if i as usize >= a => Some(i as usize - a),
|
||
("g_s", wai_ops::Site::Input(0)) => Some(0),
|
||
("g_s", wai_ops::Site::Op(i)) => Some(i as usize + 1),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// The renderer through the operator set, as `World::decode_planes` runs it after its
|
||
/// checks, one graph at a time, so that a refusal names its network, step and layer.
|
||
fn world_traced(w: &wai::int_world::ops::World, cs: &WorldCase) -> Result<Vec<Vec<u8>>, (&'static str, usize, Option<usize>, wai_ops::Code)> {
|
||
use wai_ops::Tensor;
|
||
let a = w.planes();
|
||
let g = |z: &Tensor, step: usize| {
|
||
let out = wai::int_ops::run1(w.g_s(), z.clone(), &[]).map_err(|r| ("g_s", step, world_layer("g_s", a, r.at), r.code))?;
|
||
let plane = (out.h * out.w) as usize;
|
||
let mut hwc = vec![0u8; out.data.len()];
|
||
for (k, &v) in out.data.iter().enumerate() {
|
||
hwc[(k % plane) * out.c as usize + k / plane] = v as u8;
|
||
}
|
||
Ok(hwc)
|
||
};
|
||
let mut z = Tensor::new(cs.c as u32, cs.lat as u32, cs.lat as u32, cs.z0.clone());
|
||
let mut frames = vec![g(&z, 0)?];
|
||
for (k, row) in cs.rows.iter().enumerate() {
|
||
z = wai::int_ops::run1(w.t(), z, row).map_err(|r| ("T", k + 1, world_layer("T", a, r.at), r.code))?;
|
||
frames.push(g(&z, k + 1)?);
|
||
}
|
||
Ok(frames)
|
||
}
|
||
|
||
/// Gate 4 of the migration, for `wai.world.model`'s integer renderer: the renderer through
|
||
/// the operator set against the direct renderer with the direct guard
|
||
/// (`int_world::legacy::checked`) on random sets and clips. Outside the planned breaks
|
||
/// both render the same frames, which are also the unguarded direct renderer's, through
|
||
/// the same latents after every transition, or both
|
||
/// refuse a value (`field_range`), in the same network, at the same step and layer, and the
|
||
/// guard never panics; under a planned break the renderer through the operator set refuses
|
||
/// before its first frame.
|
||
#[test]
|
||
fn int_world_random_sets_and_clips_render_alike() {
|
||
use std::collections::BTreeMap;
|
||
use wai::int_world::{legacy, ops};
|
||
let (mut alike, mut refused, mut breaks) = (0u64, BTreeMap::<String, u64>::new(), BTreeMap::<&str, u64>::new());
|
||
let n = cases("int_world", 3000, |r, seed| {
|
||
let cs = world_case(r);
|
||
let why = || format!("seed {seed}\ncase {cs:?}");
|
||
let (t, g) = (IntModel::from_bytes(&cs.t).expect("the generator writes WHM1 that parses"), IntModel::from_bytes(&cs.g_s).unwrap());
|
||
let new = ops::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).map(|c| c.frames);
|
||
if let Some(b) = world_planned_break(&cs) {
|
||
assert!(new.is_err(), "{b}: the renderer through the operator set must refuse it\n{}", why());
|
||
*breaks.entry(b).or_default() += 1;
|
||
return;
|
||
}
|
||
let world = ops::load(&t, &g, cs.c).unwrap_or_else(|e| panic!("no planned break, yet the set is refused: {e}\n{}", why()));
|
||
let traced = world_traced(&world, &cs);
|
||
let direct = catch_unwind(AssertUnwindSafe(|| legacy::checked::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).map(|c| c.frames)))
|
||
.unwrap_or_else(|_| panic!("the direct guard panicked\n{}", why()));
|
||
match (&new, &traced, &direct) {
|
||
(Ok(a), Ok(b), Ok(d)) => {
|
||
assert!(a == d && b == d, "the two renderers' frames differ\n{}", why());
|
||
let plain = legacy::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).frames;
|
||
assert!(plain == *d, "the guard changed the direct renderer's frames\n{}", why());
|
||
// The latents too: a random g_s mostly saturates the RGB8 step, which can
|
||
// hide a difference in T.
|
||
let (mut zo, mut zd) = (cs.z0.clone(), cs.z0.clone());
|
||
for (k, row) in cs.rows.iter().enumerate() {
|
||
zo = world.step(&zo, row, cs.lat).unwrap_or_else(|e| panic!("transition {}: {e}\n{}", k + 1, why()));
|
||
zd = legacy::step_planes(&t, &zd, row, cs.c, cs.lat);
|
||
assert!(zo == zd, "the latents after transition {} differ\n{}", k + 1, why());
|
||
}
|
||
alike += 1;
|
||
}
|
||
(Err(a), Err((net, step, layer, code)), Err(d)) => {
|
||
assert_eq!((a.kind, *code), (RefusalKind::FieldRange, wai_ops::Code::FieldRange), "the renderer refused other than a value\n{}", why());
|
||
assert!(d.out_of_range, "the guard refused other than a value: {d:?}\n{}", why());
|
||
assert_eq!((*net, *step, *layer), (d.net, d.step, Some(d.layer)), "the refusals' network, step and layer\n{}", why());
|
||
*refused.entry(format!("{net} layer {}", d.layer)).or_default() += 1;
|
||
}
|
||
_ => panic!("through the operator set {:?}, traced {:?}, directly {:?}\n{}", new.as_ref().err(), traced.as_ref().err(), direct.as_ref().err(), why()),
|
||
}
|
||
});
|
||
eprintln!("int_world: {alike} rendered alike, refused by both {refused:?}, planned breaks {breaks:?}");
|
||
assert!(alike * 8 > n, "the generator must produce renderable cases: {alike} of {n}");
|
||
assert!(n < 1000 || (refused.keys().any(|k| k.starts_with("T")) && refused.keys().any(|k| k.starts_with("g_s"))), "a network's check range was not reached: {refused:?}");
|
||
assert!(n < 1000 || breaks.len() >= 5, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
/// The planned breaks of integer-operators §15.3 item 2 for the world renderer, each on a
|
||
/// set and clip the direct renderer rendered from (or panicked on, or whose guard refused a
|
||
/// value): the classifier names it, and the renderer through the operator set refuses it
|
||
/// before its first frame (`model_mismatch`).
|
||
#[test]
|
||
fn int_world_planned_breaks_are_new_refusals() {
|
||
use wai::int_world::{legacy, ops};
|
||
// C = 1, A = 1, a latent of side 2: T passes z through (weights 1 for z, 0 for the plane),
|
||
// g_s gives three channels of the latent, the RGB8 step then maps 2^12 to 255.
|
||
let pass = |b: i64| Whm::Conv { inc: 2, out: 1, k: 1, s: 1, p: 0, w: vec![1, 0], m: vec![1], b: vec![b] };
|
||
let to3 = |w: i16, b: i64| Whm::ConvT { inc: 1, out: 3, k: 1, s: 1, p: 0, op: 0, w: vec![w; 3], m: vec![1; 3], b: vec![b; 3] };
|
||
let net = |afrac: u32, shift: u32, gf: u32, l: Vec<Whm>| whm1_bytes(afrac, gf, shift, &l);
|
||
let case = |t: Vec<u8>, g_s: Vec<u8>, c: usize, rows: Vec<Vec<i64>>| WorldCase { t, g_s, c, lat: 2, z0: vec![1 << 12; c * 4], rows };
|
||
let base = case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0], vec![1 << 12]]);
|
||
let render = |cs: &WorldCase| {
|
||
let (t, g) = (IntModel::from_bytes(&cs.t).unwrap(), IntModel::from_bytes(&cs.g_s).unwrap());
|
||
let new = ops::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).map(|c| c.frames).map_err(|e| e.kind);
|
||
let old = catch_unwind(AssertUnwindSafe(|| legacy::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).frames)).ok();
|
||
let guard = catch_unwind(AssertUnwindSafe(|| legacy::checked::decode_planes(&t, &g, &cs.z0, &cs.rows, cs.c, cs.lat).map(|c| c.frames.len()))).ok();
|
||
(new, old, guard)
|
||
};
|
||
assert_eq!(world_planned_break(&base), None);
|
||
let (new, old, _) = render(&base);
|
||
assert_eq!(new, Ok(vec![vec![255; 12]; 3]), "the base renders through the operator set");
|
||
assert_eq!(old, Some(vec![vec![255; 12]; 3]), "the base renders directly");
|
||
let expect = |kind: &str, cs: WorldCase| {
|
||
assert_eq!(world_planned_break(&cs), Some(kind), "{kind}: the classifier");
|
||
let (new, old, guard) = render(&cs);
|
||
assert_eq!(new.map(|_| ()), Err(RefusalKind::ModelMismatch), "{kind}: the renderer through the operator set refuses it");
|
||
let d = match (old, guard) {
|
||
(Some(f), Some(Ok(_))) => format!("rendered {} frames, and so did its guard", f.len()),
|
||
(Some(f), Some(Err(g))) => format!("rendered {} frames; its guard refused at {} layer {} (step {})", f.len(), g.net, g.layer, g.step),
|
||
(Some(f), None) => format!("rendered {} frames; its guard panicked", f.len()),
|
||
(None, _) => "panicked".to_string(),
|
||
};
|
||
println!("{kind}: refused model_mismatch; the direct renderer {d}");
|
||
};
|
||
expect("afrac outside 1..=30, or unequal", case(net(13, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
expect("afrac outside 1..=30, or unequal", case(net(31, 0, 16, vec![pass(0)]), net(31, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
expect("A < 1", case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![to3(1, 0)]), 2, vec![]));
|
||
expect("no layer of tag 0, 1 or 2", case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![Whm::Relu]), 1, vec![vec![0]]));
|
||
expect("a shift or gf of 128 or more that no layer uses", case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 200, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
expect("a bias of magnitude 2^63 - 1 or more", case(net(12, 0, 16, vec![pass(i64::MAX)]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
let first = Whm::Conv { inc: 2, out: 1, k: 1, s: 1, p: 0, w: vec![0, 0], m: vec![1], b: vec![1 << 30] };
|
||
let tight = Whm::Conv { inc: 1, out: 1, k: 1, s: 1, p: 0, w: vec![1 << 11], m: vec![1 << 40], b: vec![0] };
|
||
expect("a check range its producer cannot meet", case(net(12, 0, 16, vec![first, tight]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
expect("layers that do not chain, or a shift a layer cannot take", case(net(12, 200, 16, vec![pass(0)]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
expect("a row that does not carry A values", case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0, 0]]));
|
||
let halve = Whm::Conv { inc: 2, out: 1, k: 1, s: 2, p: 0, w: vec![1, 0], m: vec![1], b: vec![0] };
|
||
expect("T or g_s that does not fit the latent", case(net(12, 0, 16, vec![halve]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
let wide = Whm::Conv { inc: 2, out: 2, k: 1, s: 1, p: 0, w: vec![1, 0, 1, 0], m: vec![1; 2], b: vec![0; 2] };
|
||
expect("T or g_s that does not fit the latent", case(net(12, 0, 16, vec![wide]), net(12, 0, 16, vec![to3(1, 0)]), 1, vec![vec![0]]));
|
||
let big = Whm::Conv { inc: 1, out: 1, k: 3, s: 1, p: 0, w: vec![1; 9], m: vec![1], b: vec![0] };
|
||
expect("T or g_s that does not fit the latent", case(net(12, 0, 16, vec![pass(0)]), net(12, 0, 16, vec![big, to3(1, 0)]), 1, vec![vec![0]]));
|
||
}
|
||
|
||
// ------------------------------------------------------------------ load step 8
|
||
|
||
/// What the bounds pass made of the graphs compared: refused, or the profiles met.
|
||
#[derive(Default, Debug)]
|
||
struct Verdicts {
|
||
graphs: u64,
|
||
refused: u64,
|
||
acc32: u64,
|
||
acc64_only: u64,
|
||
neither: u64,
|
||
}
|
||
|
||
/// Load step 8's pass on `g` against its oracle: equal passes, node for node (every
|
||
/// intermediate, result, saturation mark, parameter condition, `K_c`, width and flag,
|
||
/// and every tensor's interval), or the same refusal; and the same first failure under
|
||
/// each profile.
|
||
fn same_pass(g: &wai_ops::Graph, v: &mut Verdicts, why: &dyn Fn() -> String) {
|
||
use wai_ops::bounds::{self, Profile};
|
||
let sizes = wai_ops::plan::load_plan(g).unwrap_or_else(|r| panic!("a lowered graph plans: {r:?}\n{}", why()));
|
||
let (new, old) = (bounds::pass(g, sizes.as_deref()), wai_ops::bounds_oracle::pass(g, sizes.as_deref()));
|
||
assert!(new == old, "the bounds pass and its oracle differ\n{}", why());
|
||
v.graphs += 1;
|
||
match (&new, &old) {
|
||
(Ok(a), Ok(b)) => {
|
||
for p in Profile::ALL {
|
||
assert_eq!(a.first_failure(g, p), b.first_failure(g, p), "{}", why());
|
||
}
|
||
match (a.meets(g, Profile::Acc32), a.meets(g, Profile::Acc64)) {
|
||
(true, _) => v.acc32 += 1,
|
||
(false, true) => v.acc64_only += 1,
|
||
_ => v.neither += 1,
|
||
}
|
||
}
|
||
_ => v.refused += 1,
|
||
}
|
||
}
|
||
|
||
/// The bounds pass against its oracle on every graph the generators above lower:
|
||
/// `int_synth/1`'s graph, the splat set's `g_s`, the audio synthesis, the conditional
|
||
/// codec's `h_s` and `g_s`, the MLIC++ bundle's three networks, and the world
|
||
/// renderer's `T` and `g_s`. Every verdict is reached: a refusal by the pass
|
||
/// itself (a `check` range its producer cannot meet), `acc32`, `acc64` only, and neither.
|
||
#[test]
|
||
fn the_bounds_pass_equals_its_oracle_on_random_sets() {
|
||
let mut v = Verdicts::default();
|
||
let n = cases("bounds pass", 3000, |r, seed| {
|
||
let why = || format!("seed {seed}");
|
||
let (set, _) = int_synth_case(r);
|
||
if let Ok(l) = wai_ops::lower::int_synth(&set) {
|
||
for (_, g) in &l.roles {
|
||
same_pass(g, &mut v, &why);
|
||
}
|
||
}
|
||
let (set, _) = int_splat_case(r);
|
||
if let Ok(g) = wai_ops::lower::whm1(&set, true) {
|
||
same_pass(&g, &mut v, &why);
|
||
}
|
||
let (set, _) = int_audio_case(r);
|
||
if let Ok(g) = wai_ops::lower::wam1(&set) {
|
||
same_pass(&g, &mut v, &why);
|
||
}
|
||
let (g_s, h_s, _) = int_hyper_case(r);
|
||
for (set, rgb8) in [(&h_s, false), (&g_s, true)] {
|
||
if let Ok(g) = wai_ops::lower::whm1(set, rgb8) {
|
||
same_pass(&g, &mut v, &why);
|
||
}
|
||
}
|
||
let (bundle, _) = int_mlicpp_case(r);
|
||
if let Some(secs) = wmb1_sections(&bundle) {
|
||
for (k, rgb8) in [(0, false), (1, false), (2, true)] {
|
||
if let Ok(g) = wai_ops::lower::whm1(&secs[k], rgb8) {
|
||
same_pass(&g, &mut v, &why);
|
||
}
|
||
}
|
||
}
|
||
let cs = world_case(r);
|
||
if let Ok(l) = wai_ops::lower::world(&cs.t, &cs.g_s, cs.c as u32) {
|
||
for (_, g) in &l.roles {
|
||
same_pass(g, &mut v, &why);
|
||
}
|
||
}
|
||
});
|
||
eprintln!("bounds pass: {n} cases, {v:?}");
|
||
assert!(v.graphs > n * 2, "the generators must mostly lower: {} graphs from {n} cases", v.graphs);
|
||
assert!(n < 1000 || (v.refused > 0 && v.acc32 > 0 && v.acc64_only > 0 && v.neither > 0), "a verdict was not reached: {v:?}");
|
||
}
|
||
|
||
// ------------------------------------------------------------- jpegai (M7a)
|
||
//
|
||
// `wai.image.jpegai`'s scale path and entropy decodes (integer-operators §13.6.1, lowered
|
||
// by `jpegai-int/1`) against the direct computation (`jpegai_ops::legacy`): random bundles
|
||
// (the members §13.6.1 reads) and random entropy payloads. The direct computation refuses
|
||
// nothing: where the operator set refuses a stream (`stream_exhausted`) the direct
|
||
// decode panics (overflow checks on) or computes on from a window that wrapped (off).
|
||
|
||
/// A random bundle's members as JSON, and the three entropy payloads with each plane's
|
||
/// GRFS flags.
|
||
struct JCase {
|
||
norm: Value,
|
||
hs: [Value; 2],
|
||
soz: Vec<u8>,
|
||
lat: [Vec<u8>; 2],
|
||
grfs: [Vec<bool>; 2],
|
||
}
|
||
|
||
impl std::fmt::Debug for JCase {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
write!(f, "pmf rows {}, payloads {} / {} / {}", self.norm["pmf"].as_array().map_or(0, Vec::len), self.soz.len(), self.lat[0].len(), self.lat[1].len())
|
||
}
|
||
}
|
||
|
||
/// Random bytes, or one of the committed test codestream's payloads, damaged or not.
|
||
fn jpegai_payload(r: &mut Rng, committed: &[u8]) -> Vec<u8> {
|
||
let mut v: Vec<u8> = match r.below(4) {
|
||
0 => committed.to_vec(),
|
||
1 => (0..r.below(16)).map(|_| r.below(256) as u8).collect(),
|
||
_ => (0..[64, 512, 4096, 9000][r.below(4) as usize]).map(|_| r.below(256) as u8).collect(),
|
||
};
|
||
match r.below(6) {
|
||
0 if !v.is_empty() => { let n = r.below(v.len() as u64) as usize; v.truncate(n) }
|
||
1 => { let mut pre: Vec<u8> = (0..[8u64, 256, 4096][r.below(3) as usize]).map(|_| r.below(256) as u8).collect(); pre.extend_from_slice(&v); v = pre }
|
||
2 if !v.is_empty() => { let i = r.below(v.len() as u64) as usize; v[i] ^= 1 << r.below(8) }
|
||
_ => {}
|
||
}
|
||
v
|
||
}
|
||
|
||
/// One plane's scale network: `c1`, `c2` channels after blocks 1 and 2, `cz` in.
|
||
fn jpegai_scale(r: &mut Rng, cz: u64, c1: u64, c2: u64) -> Value {
|
||
let big = r.below(16) == 0;
|
||
let w = |n: u64, r: &mut Rng| -> Vec<i64> { (0..n).map(|_| if big && r.below(64) == 0 { r.signed(1 << 20) } else { r.signed(3) }).collect() };
|
||
// Shifts up to 8, then, in one network in about eight, one shift of 31 to 40 in each
|
||
// block; rarer, one of 64 to 127 (past acc32's 63) or of 128 (a load check).
|
||
let mode = r.below(64);
|
||
let shifts = |n: u64, r: &mut Rng| -> Vec<i64> {
|
||
let mut v: Vec<i64> = (0..n).map(|_| r.below(9) as i64).collect();
|
||
let i = r.below(n) as usize;
|
||
match mode {
|
||
0 => v[i] = [64, 100, 127][r.below(3) as usize],
|
||
1 => v[i] = 128,
|
||
2..=9 => v[i] = 31 + r.below(10) as i64,
|
||
_ => {}
|
||
}
|
||
v
|
||
};
|
||
let bias = |n: u64, r: &mut Rng| -> Vec<i64> { (0..n).map(|_| r.signed(500)).collect() };
|
||
let c3 = 16 * cz;
|
||
let mut v = json!({
|
||
"conv1.weight": { "dims": [c1, cz, 1, 1], "v": w(c1 * cz, r) },
|
||
"conv1.bias": { "dims": [c1], "v": bias(c1, r) },
|
||
"onnx::Pow_85": { "dims": [1, c1, 1, 1], "v": shifts(c1, r) },
|
||
"depthwise.weight": { "dims": [c2, c1, 3, 3], "v": w(c2 * c1 * 9, r) },
|
||
"depthwise.bias": { "dims": [c2], "v": bias(c2, r) },
|
||
"onnx::Pow_91": { "dims": [1, c2, 1, 1], "v": shifts(c2, r) },
|
||
"pointwise.weight": { "dims": [c3, c2, 1, 1], "v": w(c3 * c2, r) },
|
||
"pointwise.bias": { "dims": [c3], "v": bias(c3, r) },
|
||
"onnx::Pow_97": { "dims": [1, c3, 1, 1], "v": shifts(c3, r) },
|
||
});
|
||
if r.below(64) == 0 {
|
||
v["depthwise.weight"]["dims"] = json!([c2, c1, 1, 1]);
|
||
}
|
||
v
|
||
}
|
||
|
||
/// A random bundle and payloads. Most bundles pass every load check; about one in five
|
||
/// breaks one, or meets acc64 and not acc32, or has a shift from 64 to 127.
|
||
fn jpegai_case(r: &mut Rng, payloads: &[Vec<u8>; 3]) -> JCase {
|
||
let rows = if r.below(16) == 0 { 33 + r.below(8) } else { 32 };
|
||
let w = [1u64, 2, 3, 4, 8, 16, 64][r.below(7) as usize];
|
||
let mut pmf: Vec<Vec<i64>> = (0..rows).map(|_| {
|
||
let mut row = vec![0i64; w as usize];
|
||
for _ in 0..256 { row[r.below(w) as usize] += 1; }
|
||
row
|
||
}).collect();
|
||
let mut bounds: Vec<i64> = (0..32).map(|_| [0i64, 1, 2, 4, 30, 127, 128, 200, 255][r.below(9) as usize]).collect();
|
||
let cdf_row = |r: &mut Rng| -> Vec<i64> {
|
||
let mut c = 0i64;
|
||
(0..63).map(|_| { c = (c + r.below(9) as i64).min(255); c }).collect()
|
||
};
|
||
let mut cdfs_y: Vec<i64> = (0..160).flat_map(|_| cdf_row(r)).collect();
|
||
let mut cdfs_uv: Vec<i64> = (0..96).flat_map(|_| cdf_row(r)).collect();
|
||
let g0 = |r: &mut Rng| [-5000i64, -1000, 0, 300, 380, 400, 1000, 3000][r.below(8) as usize];
|
||
let (gy0, guv0) = (g0(r), g0(r));
|
||
let mut gain_y: Vec<i64> = (0..160).map(|_| gy0 + r.signed(100)).collect();
|
||
let gain_uv: Vec<i64> = (0..96).map(|_| guv0 + r.signed(100)).collect();
|
||
let mut per_channel = 16;
|
||
match r.below(64) {
|
||
0 => { pmf[0][0] += 1 }
|
||
1 => { pmf.truncate(31) }
|
||
2 => { bounds[r.below(32) as usize] = [256, -1][r.below(2) as usize] }
|
||
3 => { let i = r.below(160 * 63 - 1) as usize; cdfs_y[i] = 256 }
|
||
4 => { let row = r.below(96) as usize * 63; cdfs_uv[row] = cdfs_uv[row + 1] + 1 }
|
||
5 => { gain_y.pop(); }
|
||
6 => { per_channel = 15 }
|
||
_ => {}
|
||
}
|
||
let (c1, c2) = (1 + r.below(3), 1 + r.below(3));
|
||
let norm = json!({ "pmf": pmf, "bounds": bounds, "per_channel": per_channel, "cdfs_y": cdfs_y, "cdfs_uv": cdfs_uv, "gain_y": gain_y, "gain_uv": gain_uv });
|
||
let (u1, u2) = (1 + r.below(3), 1 + r.below(3));
|
||
let hs = [jpegai_scale(r, 160, c1, c2), jpegai_scale(r, 96, u1, u2)];
|
||
let soz = jpegai_payload(r, &payloads[0]);
|
||
let lat = [jpegai_payload(r, &payloads[1]), jpegai_payload(r, &payloads[2])];
|
||
let grfs = [(0..160).map(|_| r.below(2) == 0).collect(), (0..96).map(|_| r.below(2) == 0).collect()];
|
||
JCase { norm, hs, soz, lat, grfs }
|
||
}
|
||
|
||
/// The planned break of integer-operators §15.3 item 4 a bundle falls under, read from
|
||
/// its members alone: a load check of §13.6.1 it fails, or scale graphs that do not meet
|
||
/// `acc32`, the capability's profile (a sum past `i32`, or a shift past 63, where the
|
||
/// direct computation's result depended on the build). Each is a refusal.
|
||
fn jpegai_planned_break(c: &JCase) -> Option<&'static str> {
|
||
let n = &c.norm;
|
||
let ints = |v: &Value| -> Vec<i64> { v.as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect() };
|
||
let pmf: Vec<Vec<i64>> = n["pmf"].as_array().unwrap().iter().map(ints).collect();
|
||
if pmf.len() < 32 || pmf.iter().any(|r| r.len() != pmf[0].len() || r.iter().any(|&p| p < 0) || r.iter().sum::<i64>() != 256) {
|
||
return Some("pmf");
|
||
}
|
||
let bounds = ints(&n["bounds"]);
|
||
if bounds.len() < 32 || bounds.iter().any(|&b| !(0..=255).contains(&b)) {
|
||
return Some("bounds");
|
||
}
|
||
if n["per_channel"] != 16 {
|
||
return Some("per_channel");
|
||
}
|
||
for (k, rows) in [("cdfs_y", 160usize), ("cdfs_uv", 96)] {
|
||
let c = ints(&n[k]);
|
||
if c.len() != rows * 63 || c.iter().any(|&v| !(0..=255).contains(&v)) || c.chunks(63).any(|r| r.windows(2).any(|p| p[0] > p[1])) {
|
||
return Some("cdfs");
|
||
}
|
||
}
|
||
if ints(&n["gain_y"]).len() != 160 || ints(&n["gain_uv"]).len() != 96 {
|
||
return Some("gains");
|
||
}
|
||
for (hs, cz) in c.hs.iter().zip([160u64, 96]) {
|
||
let d = |k: &str| -> Vec<u64> { hs[k]["dims"].as_array().unwrap().iter().map(|x| x.as_u64().unwrap()).collect() };
|
||
let (c1, c2) = (d("conv1.weight")[0], d("depthwise.weight")[0]);
|
||
if d("depthwise.weight") != [c2, c1, 3, 3] || d("conv1.weight") != [c1, cz, 1, 1] {
|
||
return Some("dims");
|
||
}
|
||
let shifts: Vec<i64> = ["onnx::Pow_85", "onnx::Pow_91", "onnx::Pow_97"].iter().flat_map(|k| ints(&hs[*k]["v"])).collect();
|
||
if shifts.iter().any(|&s| !(0..=127).contains(&s)) {
|
||
return Some("shift");
|
||
}
|
||
}
|
||
let b = |v: &Value| serde_json::to_vec(v).unwrap();
|
||
let g = wai_ops::lower::jpegai_scale(&b(n), &b(&c.hs[0]), &b(&c.hs[1])).expect("a bundle that passes the load checks lowers");
|
||
if g.roles.iter().any(|(_, g)| wai_ops::graph::load(&g.to_bytes(), wai_ops::Profile::Acc32).is_err()) {
|
||
assert!(g.roles.iter().all(|(_, g)| wai_ops::graph::load(&g.to_bytes(), wai_ops::Profile::Acc64).is_ok()), "the generator's sums fit i64");
|
||
return Some("acc32");
|
||
}
|
||
None
|
||
}
|
||
|
||
/// The tables as the decoders parse them.
|
||
fn jpegai_parsed(c: &JCase) -> (wai::jpegai_decode::NormTables, [wai::jpegai_synthesis::IntNet; 2]) {
|
||
let ints = |k: &str| -> Vec<i64> { c.norm[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect() };
|
||
let n = wai::jpegai_decode::NormTables {
|
||
pmf: c.norm["pmf"].as_array().unwrap().iter().map(|r| r.as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect()).collect(),
|
||
bounds: ints("bounds"),
|
||
per_channel: c.norm["per_channel"].as_u64().unwrap() as usize,
|
||
cdfs_y: ints("cdfs_y"),
|
||
cdfs_uv: ints("cdfs_uv"),
|
||
gain_y: ints("gain_y"),
|
||
gain_uv: ints("gain_uv"),
|
||
lsbs_t0: vec![],
|
||
lsbs_t1: vec![],
|
||
lef_thr: [0; 3],
|
||
lef_mag: [0.0; 3],
|
||
};
|
||
(n, [wai::jpegai_synthesis::IntNet::from_json(&c.hs[0]).unwrap(), wai::jpegai_synthesis::IntNet::from_json(&c.hs[1]).unwrap()])
|
||
}
|
||
|
||
/// The committed test codestream's SOZ, SORP and SORS payloads.
|
||
fn jpegai_committed_payloads() -> [Vec<u8>; 3] {
|
||
use wai::jpegai_bitstream::{find_segment, parse_container, Marker};
|
||
let data = std::fs::read(wai().join("wai-rs/tests/vectors/jpegai/test256.bin")).unwrap();
|
||
let segs = parse_container(&data);
|
||
[Marker::Soz, Marker::Sorp, Marker::Sors].map(|m| {
|
||
let s = find_segment(&segs, m).unwrap();
|
||
data[s.start..s.start + s.len].to_vec()
|
||
})
|
||
}
|
||
|
||
/// Random bundles and payloads: outside the planned breaks, both planes' integers through
|
||
/// the operator set equal the direct computation's, or the operator set refuses
|
||
/// (`stream_exhausted`) where the direct computation panics with overflow checks on (or,
|
||
/// with them off, panics or computes on from a wrapped window, counted). Under a planned
|
||
/// break the bundle is refused (`model_mismatch`), from its bytes and from its parsed
|
||
/// form alike.
|
||
#[test]
|
||
fn jpegai_random_bundles_and_streams_decode_alike() {
|
||
use wai::jpegai_ops::{self, Plane};
|
||
let payloads = jpegai_committed_payloads();
|
||
let (mut alike, mut refused, mut computed_on) = (0u64, 0u64, 0u64);
|
||
let mut breaks = std::collections::BTreeMap::<&str, u64>::new();
|
||
let n = cases("jpegai", 1500, |r, seed| {
|
||
let c = jpegai_case(r, &payloads);
|
||
let why = || format!("seed {seed}\n{c:?}");
|
||
let b = |v: &Value| serde_json::to_vec(v).unwrap();
|
||
let from_bytes = jpegai_ops::load_bytes(&b(&c.norm), &b(&c.hs[0]), &b(&c.hs[1]));
|
||
let (norm, hs) = jpegai_parsed(&c);
|
||
let loaded = jpegai_ops::load(&norm, &hs[0], &hs[1]);
|
||
assert_eq!(from_bytes.as_ref().err().map(|r| r.code), loaded.as_ref().err().map(|r| r.code), "the bytes and the parsed form load alike\n{}", why());
|
||
if let Some(k) = jpegai_planned_break(&c) {
|
||
*breaks.entry(k).or_default() += 1;
|
||
assert_eq!(loaded.err().map(|r| r.code), Some(wai_ops::Code::ModelMismatch), "{k}: the bundle must be refused\n{}", why());
|
||
return;
|
||
}
|
||
let sp = loaded.unwrap_or_else(|r| panic!("a bundle under no planned break is refused: {r:?}\n{}", why()));
|
||
for (k, p) in [Plane::Luma, Plane::Chroma].into_iter().enumerate() {
|
||
let gain = if k == 0 { &norm.gain_y } else { &norm.gain_uv };
|
||
let new = jpegai_ops::plane(&sp, p, &c.soz, &c.lat[k], &c.grfs[k]);
|
||
let old = catch_unwind(AssertUnwindSafe(|| jpegai_ops::legacy::plane(&norm, &hs[k], gain, p, &c.soz, &c.lat[k], &c.grfs[k]))).ok();
|
||
match (new, old) {
|
||
(Ok(a), Some(b)) => { assert!(a == b, "{p:?}: the two decodes differ\n{}", why()); alike += 1 }
|
||
(Err(r), None) => { assert_eq!(r.code, wai_ops::Code::StreamExhausted, "{p:?}\n{}", why()); refused += 1 }
|
||
(Err(r), Some(_)) => {
|
||
assert_eq!(r.code, wai_ops::Code::StreamExhausted, "{p:?}\n{}", why());
|
||
assert!(!cfg!(debug_assertions), "{p:?}: refused, and the checked build's direct decode did not panic\n{}", why());
|
||
computed_on += 1
|
||
}
|
||
(Ok(_), None) => panic!("{p:?}: the direct decode panicked where the operator set decodes\n{}", why()),
|
||
}
|
||
}
|
||
});
|
||
eprintln!("jpegai: {n} cases; planes decoded alike {alike}, refused where the direct decode panicked {refused}, refused where the unchecked build computed on {computed_on}; planned breaks {breaks:?}");
|
||
assert!(alike * 8 > n, "the generator must produce decodable planes: {alike} of {n} cases");
|
||
assert!(refused > 0, "no stream was refused");
|
||
assert!(n < 1000 || breaks.len() >= 5, "too few kinds of planned break reached: {breaks:?}");
|
||
}
|
||
|
||
// -------------------------------------------------------- WIV1 and WIR1 (M8, no graph)
|
||
//
|
||
// `wai.video.int_motion` (and the P-frames of `wai.video.int_hyper`) and the four
|
||
// `int_refine` capabilities decode through the stage operators alone
|
||
// (integer-operators §13.5). They make no planned break: the decode through the operator
|
||
// set and the direct decode give the same frames or units, or the same refusal, on
|
||
// every input, and neither panics.
|
||
|
||
/// A random `WIV1` clip, its fields as `int_video::VideoStream` holds them, and for
|
||
/// keyframe 1 its I-frame.
|
||
#[cfg(feature = "video_int")]
|
||
#[derive(Debug)]
|
||
struct VideoCase {
|
||
n_frames: usize,
|
||
h: usize,
|
||
w: usize,
|
||
block: usize,
|
||
q: i64,
|
||
offset: i32,
|
||
mv_precision: u8,
|
||
cdf: Vec<i32>,
|
||
mvs: Vec<(i16, i16)>,
|
||
rans: Vec<u8>,
|
||
i_frame: Option<Vec<u8>>,
|
||
}
|
||
|
||
/// A table of `len` entries from 0 to 65536, strictly increasing: `len − 1` positive
|
||
/// frequencies of random sizes.
|
||
#[cfg(feature = "video_int")]
|
||
fn table_of(r: &mut Rng, len: usize) -> Vec<i32> {
|
||
let mut cuts: Vec<i32> = Vec::new();
|
||
while cuts.len() < len - 2 {
|
||
let c = 1 + r.below(65_535) as i32;
|
||
if !cuts.contains(&c) {
|
||
cuts.push(c);
|
||
}
|
||
}
|
||
cuts.sort_unstable();
|
||
let mut t = vec![0];
|
||
t.extend(cuts);
|
||
t.push(1 << 16);
|
||
t
|
||
}
|
||
|
||
/// A random clip: 1 to 4 frames of 1 to 12 × 1 to 12 pixels in blocks of 1 to 6 (so the
|
||
/// block grid's last row and column are often partial); whole-pel or half-pel; `q`
|
||
/// mostly small, one clip in sixteen 65535 and one in sixteen anywhere in its range; a
|
||
/// table of 3 to 6 entries (one in eight up to 64) at an offset mostly small, one in
|
||
/// sixteen at either end of `i32`; vectors mostly within two frames of the pixel, one in
|
||
/// sixteen anywhere in `i16`; keyframe 1 one clip in four, with an I-frame of the clip's
|
||
/// size (one in 32 a byte short). The residuals are coded against the table with the
|
||
/// crate's encoder: mostly inside the table, one in eight escaped, one in 64 near the
|
||
/// ends of `i32`. One clip in 64 breaks a field (`q`, the precision, the table, the
|
||
/// vector count). The stream is damaged as [`damage`] says.
|
||
#[cfg(feature = "video_int")]
|
||
fn int_video_case(r: &mut Rng) -> VideoCase {
|
||
let n_frames = 1 + r.below(4) as usize;
|
||
let (h, w, block) = (1 + r.below(12) as usize, 1 + r.below(12) as usize, 1 + r.below(6) as usize);
|
||
let mv_precision = if n_frames == 1 { 1 } else { 1 + r.below(2) as u8 };
|
||
let q = match r.below(16) {
|
||
0 => 65_535,
|
||
1 => 1 + r.below(65_535) as i64,
|
||
_ => 1 + r.below(12) as i64,
|
||
};
|
||
let len = if r.below(8) == 0 { 3 + r.below(62) as usize } else { 3 + r.below(4) as usize };
|
||
let cdf = table_of(r, len);
|
||
let offset = match r.below(16) {
|
||
0 => i32::MIN + r.below(8) as i32,
|
||
1 => i32::MAX - r.below(8) as i32,
|
||
_ => r.signed(40) as i32,
|
||
};
|
||
let nb = h.div_ceil(block) * w.div_ceil(block);
|
||
let far = r.below(16) == 0;
|
||
let mvs: Vec<(i16, i16)> = (0..nb * (n_frames - 1))
|
||
.map(|_| if far { (r.signed(32_767) as i16, r.signed(32_767) as i16) } else { (r.signed(2 * h as i64 + 2) as i16, r.signed(2 * w as i64 + 2) as i16) })
|
||
.collect();
|
||
let i_frame = (r.below(4) == 0).then(|| (0..h * w * 3 - (r.below(32) == 0) as usize).map(|_| r.below(256) as u8).collect::<Vec<u8>>());
|
||
let first = i_frame.is_some() as usize;
|
||
let n2 = cdf.len() as i64 - 2;
|
||
let mut enc = wai::rans::RansEncoder::new();
|
||
for _ in 0..(n_frames - first) * h * w * 3 {
|
||
let v = match r.below(64) {
|
||
0 => [i32::MIN as i64, i32::MAX as i64, i32::MIN as i64 + 1, i32::MAX as i64 - 1][r.below(4) as usize],
|
||
1..=8 => {
|
||
let far = 1u64 << r.below(24);
|
||
offset as i64 + if r.below(2) == 0 { -1 - r.below(far) as i64 } else { n2 + r.below(far) as i64 }
|
||
}
|
||
_ => offset as i64 + r.below(n2 as u64) as i64,
|
||
};
|
||
let v = v.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
|
||
if !enc.encode(v, &cdf, cdf.len(), offset) {
|
||
let inside = (offset as i64 + r.below(n2 as u64) as i64).clamp(i32::MIN as i64, i32::MAX as i64) as i32;
|
||
assert!(enc.encode(inside, &cdf, cdf.len(), offset) || offset as i64 + n2 > i32::MAX as i64, "a value inside the table codes");
|
||
}
|
||
}
|
||
let mut rans = enc.flush();
|
||
damage(r, &mut rans);
|
||
let mut c = VideoCase { n_frames, h, w, block, q, offset, mv_precision, cdf, mvs, rans, i_frame };
|
||
match r.below(64) {
|
||
0 => c.q = [0, 65_536, -1][r.below(3) as usize],
|
||
1 => c.mv_precision = [0, 3][r.below(2) as usize],
|
||
2 => c.cdf[1] = 0,
|
||
3 => {
|
||
c.mvs.pop();
|
||
}
|
||
_ => {}
|
||
}
|
||
c
|
||
}
|
||
|
||
/// The case's clip through the stage operators and directly, each under
|
||
/// `catch_unwind`.
|
||
#[cfg(feature = "video_int")]
|
||
type Frames = Result<Vec<Vec<u8>>, &'static str>;
|
||
|
||
#[cfg(feature = "video_int")]
|
||
fn video_both(c: &VideoCase) -> (std::thread::Result<Frames>, std::thread::Result<Frames>) {
|
||
use wai::int_video::{legacy, ops, VideoStream};
|
||
let s = VideoStream {
|
||
n_frames: c.n_frames, h: c.h, w: c.w, block: c.block, q: c.q, offset: c.offset, mv_precision: c.mv_precision,
|
||
cdf: &c.cdf, mvs: &c.mvs, rans: &c.rans,
|
||
};
|
||
let through = catch_unwind(AssertUnwindSafe(|| match &c.i_frame {
|
||
None => ops::decode_typed(&s).map(|c| c.frames),
|
||
Some(f) => ops::decode_pframes_typed(f, &s).map(|c| c.frames),
|
||
}));
|
||
let direct = catch_unwind(AssertUnwindSafe(|| match &c.i_frame {
|
||
None => legacy::decode_typed(&s).map(|c| c.frames),
|
||
Some(f) => legacy::decode_pframes_typed(f, &s).map(|c| c.frames),
|
||
}));
|
||
(through, direct)
|
||
}
|
||
|
||
/// The case's clip with E9 in its 32-bit form (`int_video::ops::decode_typed_i32`).
|
||
#[cfg(all(feature = "video_int", feature = "i32_forms"))]
|
||
fn video_i32(c: &VideoCase) -> Frames {
|
||
use wai::int_video::{ops, VideoStream};
|
||
let s = VideoStream {
|
||
n_frames: c.n_frames, h: c.h, w: c.w, block: c.block, q: c.q, offset: c.offset, mv_precision: c.mv_precision,
|
||
cdf: &c.cdf, mvs: &c.mvs, rans: &c.rans,
|
||
};
|
||
match &c.i_frame {
|
||
None => ops::decode_typed_i32(&s).map(|c| c.frames),
|
||
Some(f) => ops::decode_pframes_typed_i32(f, &s).map(|c| c.frames),
|
||
}
|
||
}
|
||
|
||
/// Random clips: the decode through the stage operators (`int_video::ops`) and the
|
||
/// direct decode (`int_video::legacy`) give the same frames or the same refusal, and
|
||
/// neither panics; with `i32_forms`, so does the decode with E9 in its 32-bit form.
|
||
#[cfg(feature = "video_int")]
|
||
#[test]
|
||
fn int_video_random_clips_decode_alike() {
|
||
let mut outcomes = std::collections::BTreeMap::<&str, u64>::new();
|
||
let n = cases("int_video", 3000, |r, seed| {
|
||
let c = int_video_case(r);
|
||
let (through, direct) = video_both(&c);
|
||
let (Ok(through), Ok(direct)) = (through, direct) else { panic!("a decode panicked, seed {seed}\n{c:?}") };
|
||
if through != direct {
|
||
panic!("the two decodes differ, seed {seed}: {:?} through the operator set, {:?} directly\n{c:?}", through.as_ref().err(), direct.as_ref().err());
|
||
}
|
||
#[cfg(feature = "i32_forms")]
|
||
assert!(video_i32(&c) == through, "the 32-bit form differs from the exact form, seed {seed}\n{c:?}");
|
||
*outcomes.entry(through.err().unwrap_or("decoded alike")).or_default() += 1;
|
||
});
|
||
eprintln!("int_video: {n} cases; {outcomes:?}");
|
||
let alike = outcomes.get("decoded alike").copied().unwrap_or(0);
|
||
assert!(alike * 4 > n, "the generator must produce decodable clips: {alike} of {n}");
|
||
assert!(outcomes.len() >= 4, "too few kinds of outcome: {outcomes:?}");
|
||
}
|
||
|
||
/// A unit's current output, or a layer's target, as a buffer of its element type.
|
||
#[cfg(feature = "staged")]
|
||
#[derive(Debug, Clone)]
|
||
enum Units {
|
||
U8(Vec<u8>),
|
||
I16(Vec<i16>),
|
||
}
|
||
|
||
#[cfg(feature = "staged")]
|
||
impl Units {
|
||
fn below(&self) -> wai::staged::wir1::Below<'_> {
|
||
match self {
|
||
Units::U8(v) => wai::staged::wir1::Below::U8(v),
|
||
Units::I16(v) => wai::staged::wir1::Below::I16(v),
|
||
}
|
||
}
|
||
fn random(r: &mut Rng, kind: wai::staged::wir1::UnitKind, n: usize) -> Units {
|
||
let ends = r.below(8) == 0;
|
||
match kind {
|
||
wai::staged::wir1::UnitKind::Pcm => Units::I16((0..n).map(|_| if ends && r.below(2) == 0 { [i16::MIN, i16::MAX][r.below(2) as usize] } else { r.next() as i16 }).collect()),
|
||
_ => Units::U8((0..n).map(|_| if ends && r.below(2) == 0 { [0, 255][r.below(2) as usize] } else { r.next() as u8 }).collect()),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A random `WIR1` layer and the current outputs its units are applied to.
|
||
#[cfg(feature = "staged")]
|
||
#[derive(Debug)]
|
||
struct LayerCase {
|
||
kind: wai::staged::wir1::UnitKind,
|
||
payload: Vec<u8>,
|
||
belows: Vec<(Units, (u32, u32, u32))>,
|
||
}
|
||
|
||
/// `p` (a layer of `kind`) with unit `u`'s stream changed by `f`, its length field
|
||
/// rewritten; `p` unchanged where the layer does not parse.
|
||
#[cfg(feature = "staged")]
|
||
fn restream(kind: wai::staged::wir1::UnitKind, p: &[u8], u: usize, f: impl FnOnce(&mut Vec<u8>)) -> Vec<u8> {
|
||
let Ok(w) = wai::staged::wir1::parse(p, kind) else { return p.to_vec() };
|
||
let mut streams: Vec<Vec<u8>> = (0..w.n_units as usize).map(|k| w.stream(p, k).to_vec()).collect();
|
||
f(&mut streams[u]);
|
||
let at = 44 + 4 * w.cdf_len * w.n_tables();
|
||
let mut out = p[..at].to_vec();
|
||
for s in &streams {
|
||
out.extend((s.len() as u32).to_le_bytes());
|
||
}
|
||
for s in &streams {
|
||
out.extend(s);
|
||
}
|
||
out
|
||
}
|
||
|
||
/// A random layer: a unit kind and a predictor it allows; 1 to 3 units; RGB8 units of 1
|
||
/// to 10 × 1 to 10, attribute units of 1 to 3 planes of 1 to 5 × 1 to 5, PCM units of 1
|
||
/// to 3 channels; up2 from the half-size picture; rate at 1 to 6 output samples per 1 to
|
||
/// 6 source samples, one layer in eight at rates up to 2^20, from 1 to 40 source
|
||
/// samples. The current outputs and the targets random, one in eight with many elements
|
||
/// at the range's ends; `q` mostly 1 to 8, one layer in sixteen up to 65535; a table of
|
||
/// at most 2 to 16 symbols (one layer in four), so that most residuals escape, or the
|
||
/// encoder's span; one table or one per unit. Coded by the reference encoder
|
||
/// (`wir1::encode`). One layer in eight has a unit's stream damaged as [`damage`] says,
|
||
/// and one in sixteen applies a unit to a current output of another shape or type.
|
||
#[cfg(feature = "staged")]
|
||
fn wir1_case(r: &mut Rng) -> Option<LayerCase> {
|
||
use wai::staged::wir1::{encode, Predictor, RefineParams, UnitKind};
|
||
let kind = [UnitKind::Rgb8, UnitKind::Attr, UnitKind::Pcm][r.below(3) as usize];
|
||
let predictor = match (kind, r.below(2)) {
|
||
(UnitKind::Rgb8, 1) => Predictor::Up2,
|
||
(UnitKind::Pcm, 1) => Predictor::Rate,
|
||
_ => Predictor::Identity,
|
||
};
|
||
let mut rate = (0u32, 0u32);
|
||
let below_shape: (u32, u32, u32);
|
||
let dims = match (kind, predictor) {
|
||
(UnitKind::Rgb8, _) => {
|
||
let d = (1 + r.below(10) as u32, 1 + r.below(10) as u32, 3);
|
||
below_shape = if predictor == Predictor::Up2 { (d.0.div_ceil(2), d.1.div_ceil(2), 3) } else { d };
|
||
d
|
||
}
|
||
(UnitKind::Attr, _) => {
|
||
let d = (1 + r.below(3) as u32, 1 + r.below(5) as u32, 1 + r.below(5) as u32);
|
||
below_shape = d;
|
||
d
|
||
}
|
||
(UnitKind::Pcm, Predictor::Rate) => {
|
||
let big = r.below(8) == 0;
|
||
let pick = |r: &mut Rng| if big { 1 + r.below(1 << 20) as u32 } else { 1 + r.below(6) as u32 };
|
||
rate = (pick(r), pick(r));
|
||
let (n, c) = (1 + r.below(40) as u32, 1 + r.below(3) as u32);
|
||
let d0 = n as u64 * rate.0 as u64 / rate.1 as u64;
|
||
if d0 == 0 || d0 > 2000 {
|
||
return None;
|
||
}
|
||
below_shape = (n, 1, c);
|
||
(d0 as u32, 1, c)
|
||
}
|
||
_ => {
|
||
let d = (1 + r.below(60) as u32, 1, 1 + r.below(3) as u32);
|
||
below_shape = d;
|
||
d
|
||
}
|
||
};
|
||
let n_units = 1 + r.below(3) as usize;
|
||
let nb = (below_shape.0 * below_shape.1 * below_shape.2) as usize;
|
||
let nt = (dims.0 * dims.1 * dims.2) as usize;
|
||
let belows: Vec<Units> = (0..n_units).map(|_| Units::random(r, kind, nb)).collect();
|
||
let targets: Vec<Units> = (0..n_units).map(|_| Units::random(r, kind, nt)).collect();
|
||
let q = if r.below(16) == 0 { 1 + r.below(65_535) as u32 } else { 1 + r.below(8) as u32 };
|
||
let max_symbols = if r.below(4) == 0 { 2 + r.below(15) as u32 } else { 4095 };
|
||
let p = RefineParams { q, predictor, rate, table_per_unit: r.below(2) == 0, max_symbols };
|
||
let bs: Vec<_> = belows.iter().map(Units::below).collect();
|
||
let ts: Vec<_> = targets.iter().map(Units::below).collect();
|
||
let (mut payload, _) = encode(kind, &bs, below_shape, &ts, dims, &p).ok()?;
|
||
if r.below(8) == 0 {
|
||
let u = r.below(n_units as u64) as usize;
|
||
payload = restream(kind, &payload, u, |s| damage(r, s));
|
||
}
|
||
let mut belows: Vec<(Units, (u32, u32, u32))> = belows.into_iter().map(|b| (b, below_shape)).collect();
|
||
if r.below(16) == 0 {
|
||
let u = r.below(n_units as u64) as usize;
|
||
belows[u] = match r.below(3) {
|
||
0 => (belows[u].0.clone(), (below_shape.0 + 1, below_shape.1, below_shape.2)),
|
||
1 => (match &belows[u].0 { Units::U8(v) => Units::I16(v.iter().map(|&x| x as i16).collect()), Units::I16(v) => Units::U8(v.iter().map(|&x| x as u8).collect()) }, below_shape),
|
||
_ => (match &belows[u].0 { Units::U8(v) => Units::U8(v[1..].to_vec()), Units::I16(v) => Units::I16(v[1..].to_vec()) }, below_shape),
|
||
};
|
||
}
|
||
Some(LayerCase { kind, payload, belows })
|
||
}
|
||
|
||
/// Random layers: every unit through the stage operators (`wir1::ops`) and directly
|
||
/// (`wir1::legacy`) gives the same elements or the same refusal, the streams' checks
|
||
/// alike, and nothing panics; with `i32_forms`, so does every unit with E9 in its 32-bit
|
||
/// form.
|
||
#[cfg(feature = "staged")]
|
||
#[test]
|
||
fn wir1_random_layers_apply_alike() {
|
||
use wai::staged::wir1::{legacy, ops, parse};
|
||
let mut outcomes = std::collections::BTreeMap::<&str, u64>::new();
|
||
let n = cases("wir1", 3000, |r, seed| {
|
||
let Some(c) = wir1_case(r) else {
|
||
*outcomes.entry("no layer").or_default() += 1;
|
||
return;
|
||
};
|
||
// A damaged stream can leave a length the parser refuses (under 8 bytes); then
|
||
// neither decode runs.
|
||
let w = match parse(&c.payload, c.kind) {
|
||
Ok(w) => w,
|
||
Err(e) => {
|
||
assert_eq!(e.code(), "stream_length", "the encoder's layer, seed {seed}");
|
||
*outcomes.entry("refused at parse").or_default() += 1;
|
||
return;
|
||
}
|
||
};
|
||
let checks = catch_unwind(AssertUnwindSafe(|| (ops::check_streams(&w, &c.payload), legacy::check_streams(&w, &c.payload))));
|
||
let Ok((a, b)) = checks else { panic!("a streams' check panicked, seed {seed}\n{c:?}") };
|
||
assert_eq!(a, b, "the streams' checks differ, seed {seed}\n{c:?}");
|
||
for (u, (below, shape)) in c.belows.iter().enumerate() {
|
||
let both = catch_unwind(AssertUnwindSafe(|| {
|
||
(ops::apply_unit(&w, &c.payload, u as u32, below.below(), *shape), legacy::apply_unit(&w, &c.payload, u as u32, below.below(), *shape))
|
||
}));
|
||
let Ok((through, direct)) = both else { panic!("a decode panicked, seed {seed}, unit {u}\n{c:?}") };
|
||
if through != direct {
|
||
panic!("the two decodes differ, seed {seed}, unit {u}: {:?} through the operator set, {:?} directly", through.as_ref().err(), direct.as_ref().err());
|
||
}
|
||
#[cfg(feature = "i32_forms")]
|
||
assert!(ops::apply_unit_i32(&w, &c.payload, u as u32, below.below(), *shape) == through, "the 32-bit form differs from the exact form, seed {seed}, unit {u}");
|
||
*outcomes.entry(through.err().map_or("applied alike", |e| e.code())).or_default() += 1;
|
||
}
|
||
});
|
||
eprintln!("wir1: {n} cases; {outcomes:?}");
|
||
let alike = outcomes.get("applied alike").copied().unwrap_or(0);
|
||
assert!(alike * 2 > n, "the generator must produce decodable units: {alike} of {n}");
|
||
assert!(outcomes.len() >= 4, "too few kinds of outcome: {outcomes:?}");
|
||
}
|
||
|
||
// ------------------------------------------------- jpegai's networks (M7b, §13.6.2)
|
||
//
|
||
// `wai.image.jpegai`'s hyper decoders, context stages and synthesis (integer-operators
|
||
// §13.6.2, lowered by `jpegai-int/1` to `rec_y`, `rec_uv`, `syn_y` and `syn_uv`) against
|
||
// the direct networks (`jpegai_synthesis`, `jpegai_pipeline`), on random networks with
|
||
// the shapes the direct networks read and random inputs. The direct networks refuse
|
||
// nothing. Where the operator set refuses, the refusal is a planned break: a network file
|
||
// that fails a load check (`model_mismatch`, from the files alone), or a value past `A32`
|
||
// (`field_range`) at a checked tensor, which the direct networks computed on.
|
||
|
||
/// A random weight file in the export's form, member by member.
|
||
struct NetFile(serde_json::Map<String, Value>);
|
||
|
||
impl NetFile {
|
||
fn new() -> NetFile {
|
||
NetFile(serde_json::Map::new())
|
||
}
|
||
/// `m.weight` of `dims` with `taps` products per output, its multiplier scaled so that
|
||
/// the convolution keeps its input's magnitude, and `m.bias` of `bias` values when
|
||
/// given. `hot` makes one convolution in about 64 grow its input past `A32`.
|
||
fn conv(&mut self, r: &mut Rng, m: &str, dims: [u64; 4], taps: u64, bias: Option<u64>, hot: bool) {
|
||
let n: u64 = dims.iter().product();
|
||
let a = [3i64, 255, 32767][r.below(3) as usize];
|
||
let w: Vec<i64> = (0..n).map(|_| r.signed(a)).collect();
|
||
// Most shifts 31; some others, from 24 to 40, with the multiplier scaled to match.
|
||
let shift: u64 = if r.below(4) == 0 { 24 + r.below(17) } else { 31 };
|
||
let root = (taps as f64).sqrt().max(1.0);
|
||
let mut mult = ((1u128 << shift) as f64 / (a as f64 * root)) as i64;
|
||
mult = (mult as f64 * [0.5, 1.0, 1.5, 2.0][r.below(4) as usize]) as i64;
|
||
if hot && r.below(64) == 0 {
|
||
mult = mult.saturating_mul(1 << 12);
|
||
}
|
||
self.0.insert(format!("{m}.weight"), json!({ "kind": "conv", "dims": dims, "mult": mult.max(1), "shift": shift, "w": w }));
|
||
if let Some(b) = bias {
|
||
self.q16(&format!("{m}.bias"), (0..b).map(|_| r.signed(1 << 17)).collect());
|
||
}
|
||
}
|
||
fn q16(&mut self, name: &str, v: Vec<i64>) {
|
||
self.0.insert(name.into(), json!({ "kind": "q16", "dims": [v.len()], "v": v }));
|
||
}
|
||
fn res(&mut self, r: &mut Rng, m: &str, c: u64, hot: bool) {
|
||
self.conv(r, &format!("{m}.conv1"), [c, c, 3, 3], 9 * c, Some(c), hot);
|
||
self.conv(r, &format!("{m}.conv2"), [c, c, 3, 3], 9 * c, Some(c), hot);
|
||
}
|
||
fn gate(&mut self, r: &mut Rng, m: &str, c: u64, hot: bool) {
|
||
self.conv(r, &format!("{m}.conv"), [c, c / 4, 3, 3], 9 * c / 4, None, hot);
|
||
self.conv(r, &format!("{m}.conv2"), [c, c, 1, 1], c, None, hot);
|
||
}
|
||
fn cab(&mut self, r: &mut Rng, c: u64, hot: bool) {
|
||
self.res(r, "CAB.residual_trunk.0", c, hot);
|
||
self.res(r, "CAB.residual_trunk.1", c, hot);
|
||
self.conv(r, "CAB.subscale", [c, c, 3, 3], 9 * c, Some(c), hot);
|
||
self.res(r, "CAB.residual_mask2.0", c, hot);
|
||
self.res(r, "CAB.residual_mask3.0", c, hot);
|
||
self.conv(r, "CAB.upscale", [c, c, 3, 3], 4 * c, Some(c), hot);
|
||
}
|
||
fn tab(&mut self, r: &mut Rng, m: &str, c: u64, hid: u64, hot: bool) {
|
||
let g: Vec<i64> = (0..c).map(|_| r.below(1 << 17) as i64).collect();
|
||
self.q16(&format!("{m}.prep_data.norm1.weight"), g);
|
||
self.q16(&format!("{m}.prep_data.norm1.bias"), (0..c).map(|_| r.signed(1 << 15)).collect());
|
||
self.conv(r, &format!("{m}.prep_data.conv1"), [3 * c, c, 1, 1], c, None, hot);
|
||
self.conv(r, &format!("{m}.prep_data.conv2"), [3 * c, 1, 3, 3], 9, None, hot);
|
||
self.q16(&format!("{m}.attn.temperature"), (0..4).map(|_| (1 << 16) + r.below(1 << 19) as i64).collect());
|
||
self.conv(r, &format!("{m}.attn.project_out"), [c, c, 1, 1], c, None, hot);
|
||
let g: Vec<i64> = (0..c).map(|_| r.signed(1 << 17)).collect();
|
||
self.q16(&format!("{m}.ffn.norm1.weight"), g);
|
||
self.q16(&format!("{m}.ffn.norm1.bias"), (0..c).map(|_| r.signed(1 << 15)).collect());
|
||
self.conv(r, &format!("{m}.ffn.project_in"), [2 * hid, c, 1, 1], c, None, hot);
|
||
self.conv(r, &format!("{m}.ffn.dwconv"), [2 * hid, 1, 3, 3], 9, None, hot);
|
||
self.conv(r, &format!("{m}.ffn.project_out"), [c, hid, 1, 1], hid, None, hot);
|
||
}
|
||
fn value(self) -> Value {
|
||
Value::Object(self.0)
|
||
}
|
||
}
|
||
|
||
/// A hyper decoder of internal width `w` over `cz` channels.
|
||
fn ptq_hyper(r: &mut Rng, cz: u64, w: u64, hot: bool) -> Value {
|
||
let mut f = NetFile::new();
|
||
f.conv(r, "conv1", [w, cz, 1, 1], cz, None, hot);
|
||
f.conv(r, "conv2", [w, w, 4, 4], 4 * w, Some(w), hot);
|
||
f.conv(r, "conv3", [w, w, 3, 3], 9 * w, Some(w), hot);
|
||
f.conv(r, "conv4", [4 * cz, w, 3, 3], 9 * w, Some(4 * cz), hot);
|
||
f.value()
|
||
}
|
||
|
||
/// Context stage `i` at the widths the direct stages read (160 throughout).
|
||
fn ptq_mcm(r: &mut Rng, i: u64, hot: bool) -> Value {
|
||
let mut f = NetFile::new();
|
||
if i > 0 {
|
||
f.conv(r, "conv.0", [160, 160 * i, 1, 1], 160 * i, None, hot);
|
||
f.conv(r, "conv.1", [160, 32, 3, 3], 288, Some(160), hot);
|
||
}
|
||
f.conv(r, "fusion_pred_net.conv1", [160, if i == 0 { 160 } else { 320 }, 1, 1], 320, None, hot);
|
||
f.conv(r, "fusion_pred_net.conv2", [160, 160, 1, 1], 160, None, hot);
|
||
f.conv(r, "fusion_pred_net.conv3", [160, 160, 1, 1], 160, None, hot);
|
||
f.value()
|
||
}
|
||
|
||
/// A synthesis network at the widths the direct networks read: luma 128 wide (its
|
||
/// feed-forward hidden width `hid`), chroma 64.
|
||
fn ptq_synth(r: &mut Rng, chroma: bool, hid: u64, hot: bool) -> Value {
|
||
let mut f = NetFile::new();
|
||
if !chroma {
|
||
let w = 128;
|
||
f.res(r, "first_stage.0.0", 160, hot);
|
||
f.conv(r, "first_stage.1", [160, w, 3, 3], 4 * 160, Some(w), hot);
|
||
f.gate(r, "first_stage.2", w, hot);
|
||
f.conv(r, "conv2_t", [w, w, 3, 3], 4 * w, Some(w), hot);
|
||
f.cab(r, w, hot);
|
||
f.gate(r, "iact2", w, hot);
|
||
f.conv(r, "conv3_t", [4 * w, w, 1, 1], w, Some(4 * w), hot);
|
||
f.conv(r, "TAM.ds_conv", [w, w, 3, 3], 9 * w, Some(w), hot);
|
||
f.tab(r, "TAM.TABs.0", w, hid, hot);
|
||
f.tab(r, "TAM.TABs.1", w, hid, hot);
|
||
f.conv(r, "TAM.us_conv", [w, w, 3, 3], 4 * w, Some(w), hot);
|
||
f.gate(r, "iact3", w, hot);
|
||
f.conv(r, "conv4_t", [w, 1, 3, 3], 4 * w, Some(1), hot);
|
||
} else {
|
||
let w = 64;
|
||
f.conv(r, "first_stage.conv1", [48, 256, 3, 3], 9 * 256, Some(48), hot);
|
||
f.conv(r, "conv2_t", [144, w, 3, 3], 4 * 144, Some(w), hot);
|
||
f.cab(r, w, hot);
|
||
f.gate(r, "iact2", w, hot);
|
||
f.conv(r, "conv3_t", [4 * w, w, 1, 1], w, Some(4 * w), hot);
|
||
f.tab(r, "TAM.TABs.0", w, hid, hot);
|
||
f.tab(r, "TAM.TABs.1", w, hid, hot);
|
||
f.gate(r, "iact3", w, hot);
|
||
f.conv(r, "conv4_t", [w, 8, 3, 3], 4 * w, Some(8), hot);
|
||
}
|
||
f.value()
|
||
}
|
||
|
||
/// The direct networks' weights, parsed as the decoder parses them.
|
||
fn ptq_weights(v: &Value) -> wai::jpegai_synthesis::Weights {
|
||
wai::jpegai_synthesis::Weights::from_json(v).unwrap()
|
||
}
|
||
|
||
/// Break one load check of §13.6.2 in a network file; the name of the check, or `None`
|
||
/// (one call in six).
|
||
fn ptq_break(r: &mut Rng, v: &mut Value) -> Option<&'static str> {
|
||
let o = v.as_object_mut().unwrap();
|
||
let convs: Vec<String> = o.iter().filter(|(_, e)| e["kind"] == "conv").map(|(k, _)| k.clone()).collect();
|
||
let k = convs[r.below(convs.len() as u64) as usize].clone();
|
||
Some(match r.below(6) {
|
||
0 => { o.remove(&k); "a member missing" }
|
||
1 => { o[&k]["shift"] = json!(128); "a shift of 128" }
|
||
2 => { o[&k]["w"].as_array_mut().unwrap().pop(); "a weight short" }
|
||
3 => { let d = o[&k]["dims"][0].as_u64().unwrap(); o[&k]["dims"][0] = json!(d + 1); "dims" }
|
||
4 => {
|
||
let b = k.replace(".weight", ".bias");
|
||
match o.get_mut(&b) {
|
||
Some(e) => { e["v"].as_array_mut().unwrap().push(json!(0)); "a bias long" }
|
||
None => return None,
|
||
}
|
||
}
|
||
_ => return None,
|
||
})
|
||
}
|
||
|
||
/// A random Q16 tensor of `n` values, of magnitude about `2^bits`.
|
||
fn q16_values(r: &mut Rng, n: usize, bits: u32) -> Vec<i64> {
|
||
(0..n).map(|_| r.signed(1 << bits)).collect()
|
||
}
|
||
|
||
/// The outcome of the networks through the operator set: equal to the direct networks',
|
||
/// or a refusal that is a planned break (checked here).
|
||
fn ptq_outcome(role: &str, new: Result<Vec<i64>, wai_ops::Refusal>, old: Option<Vec<i64>>, g: &wai_ops::Loaded, why: &dyn Fn() -> String) -> &'static str {
|
||
match (new, old) {
|
||
(Ok(a), Some(b)) => {
|
||
assert!(a == b, "{role}: the two decodes differ\n{}", why());
|
||
"alike"
|
||
}
|
||
(Err(r), _) => {
|
||
// A value past A32, at an input or at a checked tensor's producer.
|
||
assert_eq!(r.code, wai_ops::Code::FieldRange, "{role}: refused with {:?}\n{}", r, why());
|
||
let checked = match r.at {
|
||
wai_ops::Site::Input(k) => g.graph().tensors[g.graph().inputs[k as usize].tensor as usize].policy,
|
||
wai_ops::Site::Op(k) => g.graph().tensors[g.graph().ops[k as usize].outs[0] as usize].policy,
|
||
other => panic!("{role}: refused at {other:?}\n{}", why()),
|
||
};
|
||
assert_eq!(checked, wai_ops::graph::Policy::Check, "{role}: refused at a tensor that is not checked\n{}", why());
|
||
"past A32"
|
||
}
|
||
(Ok(_), None) => panic!("{role}: the direct network panicked where the operator set decodes\n{}", why()),
|
||
}
|
||
}
|
||
|
||
/// Random hyper decoders and context stages, with random hyper-latents and residuals:
|
||
/// `ŷ` through `rec_y` and `rec_uv` is the direct networks', or the operator set refuses
|
||
/// a value past `A32` (where the direct networks computed on, or panicked with overflow
|
||
/// checks on); a network file that fails a load check is refused, from its bytes and its
|
||
/// parsed form alike.
|
||
#[test]
|
||
fn jpegai_random_networks_reconstruct_alike() {
|
||
use wai::jpegai_ops::Plane;
|
||
use wai::jpegai_pipeline::{reconstruct_yhat, reconstruct_yhat_uv};
|
||
use wai::jpegai_synthesis::{decode_hyper, q16_from_symbols};
|
||
let mut outcomes = std::collections::BTreeMap::<&str, u64>::new();
|
||
let n = cases("jpegai_rec", 64, |r, seed| {
|
||
let hot = r.below(2) == 0;
|
||
let chroma = r.below(2) == 0;
|
||
let (plane, cz) = if chroma { (Plane::Chroma, 96u64) } else { (Plane::Luma, 160) };
|
||
let width = 1 + r.below(4);
|
||
let mut hyp = ptq_hyper(r, cz, width, hot);
|
||
let mut mcm: Vec<Value> = (0..4).map(|i| ptq_mcm(r, i, hot)).collect();
|
||
let broken = if r.below(8) == 0 {
|
||
if chroma || r.below(2) == 0 { ptq_break(r, &mut hyp) } else { let i = r.below(4) as usize; ptq_break(r, &mut mcm[i]) }
|
||
} else {
|
||
None
|
||
};
|
||
let why = || format!("seed {seed}: {plane:?}, broken {broken:?}");
|
||
let b = |v: &Value| serde_json::to_vec(v).unwrap();
|
||
// This plane's graph alone: `rec_y` from the hyper decoder and the four stages,
|
||
// or `rec_uv` from the hyper decoder.
|
||
let hyp_net = wai_ops::lower::jpegai_nets::parse_ptq(&b(&hyp));
|
||
let mcm_nets: Vec<_> = mcm.iter().map(|v| wai_ops::lower::jpegai_nets::parse_ptq(&b(v))).collect();
|
||
let graph = (|| -> Result<wai_ops::Graph, wai_ops::Refusal> {
|
||
let mut h = hyp_net.clone()?;
|
||
if chroma {
|
||
wai_ops::lower::jpegai_nets::rec(&mut h, None, true)
|
||
} else {
|
||
let mut m: [wai_ops::lower::jpegai_nets::PtqNet; 4] = [mcm_nets[0].clone()?, mcm_nets[1].clone()?, mcm_nets[2].clone()?, mcm_nets[3].clone()?];
|
||
wai_ops::lower::jpegai_nets::rec(&mut h, Some(&mut m), false)
|
||
}
|
||
})()
|
||
.and_then(|g| wai_ops::graph::load(&g.to_bytes(), wai_ops::Profile::Acc64));
|
||
// The same, from the networks as this crate parses them, where its parser reads them.
|
||
let parsed = catch_unwind(AssertUnwindSafe(|| (ptq_weights(&hyp), mcm.iter().map(ptq_weights).collect::<Vec<_>>()))).ok();
|
||
if let Some((hw, mw)) = &parsed {
|
||
let g2 = (|| -> Result<wai_ops::Graph, wai_ops::Refusal> {
|
||
let mut h = hw.ptq()?;
|
||
if chroma {
|
||
wai_ops::lower::jpegai_nets::rec(&mut h, None, true)
|
||
} else {
|
||
let mut m = [mw[0].ptq()?, mw[1].ptq()?, mw[2].ptq()?, mw[3].ptq()?];
|
||
wai_ops::lower::jpegai_nets::rec(&mut h, Some(&mut m), false)
|
||
}
|
||
})()
|
||
.and_then(|g| wai_ops::graph::load(&g.to_bytes(), wai_ops::Profile::Acc64));
|
||
assert_eq!(g2.as_ref().map(|g| g.graph().clone()).map_err(|e| e.code), graph.as_ref().map(|g| g.graph().clone()).map_err(|e| e.code), "the parsed networks lower as their bytes\n{}", why());
|
||
}
|
||
if let Some(k) = broken {
|
||
assert_eq!(graph.err().map(|e| e.code), Some(wai_ops::Code::ModelMismatch), "{k}: refused\n{}", why());
|
||
*outcomes.entry(k).or_default() += 1;
|
||
return;
|
||
}
|
||
let g = graph.unwrap_or_else(|e| panic!("networks under no planned break are refused: {e:?}\n{}", why()));
|
||
let (hw, mw) = parsed.expect("well-formed networks parse");
|
||
let c = cz as usize;
|
||
let (zh, zw) = ([1usize, 2, 4][r.below(3) as usize], [1usize, 2, 4][r.below(3) as usize]);
|
||
let z: Vec<i64> = (0..c * zh * zw).map(|_| r.below(64) as i64 - 31).collect();
|
||
let bits = [12u32, 20, 26][r.below(3) as usize];
|
||
let resi = q16_values(r, c * 16 * zh * zw, bits);
|
||
let new = wai_ops::run_with(&g, vec![wai_ops::Tensor::new(cz as u32, zh as u32, zw as u32, z.clone()), wai_ops::Tensor::new(cz as u32, 4 * zh as u32, 4 * zw as u32, resi.clone())], &[], &mut wai_ops::Sequence::new(), &wai::int_ops::Host)
|
||
.map(|mut o| o.swap_remove(0).data);
|
||
let old = catch_unwind(AssertUnwindSafe(|| {
|
||
let (psi, ..) = decode_hyper(&q16_from_symbols(&z), c, zh, zw, &hw);
|
||
if chroma {
|
||
reconstruct_yhat_uv(&resi, &psi, c, 4 * zh, 4 * zw)
|
||
} else {
|
||
reconstruct_yhat(&resi, &psi, c, 4 * zh, 4 * zw, [&mw[0], &mw[1], &mw[2], &mw[3]])
|
||
}
|
||
}))
|
||
.ok();
|
||
*outcomes.entry(ptq_outcome(if chroma { "rec_uv" } else { "rec_y" }, new, old, &g, &why)).or_default() += 1;
|
||
});
|
||
eprintln!("jpegai networks, rec: {n} cases; outcomes {outcomes:?}");
|
||
assert!(outcomes.get("alike").copied().unwrap_or(0) * 2 > n, "the generator must produce decodable cases: {outcomes:?}");
|
||
}
|
||
|
||
/// Random synthesis networks at the direct networks' widths, with random latents: the
|
||
/// planes through `syn_y` and `syn_uv` are the direct synthesis's, or the operator set
|
||
/// refuses a value past `A32`. These run at the whole picture's size, so the fixed count
|
||
/// is small; the nightly run gives them their time.
|
||
#[test]
|
||
fn jpegai_random_synthesis_networks_decode_alike() {
|
||
use wai::jpegai_synthesis::{decode_uv_f32, decode_y_f32};
|
||
let mut outcomes = std::collections::BTreeMap::<&str, u64>::new();
|
||
let n = cases("jpegai_syn", if cfg!(debug_assertions) { 2 } else { 8 }, |r, seed| {
|
||
let chroma = r.below(2) == 1;
|
||
let hot = r.below(2) == 0;
|
||
let hid = [32u64, 64, 256][r.below(3) as usize];
|
||
let v = ptq_synth(r, chroma, hid, hot);
|
||
let why = || format!("seed {seed}: chroma {chroma}, hid {hid}, hot {hot}");
|
||
let bytes = serde_json::to_vec(&v).unwrap();
|
||
let mut net = wai_ops::lower::jpegai_nets::parse_ptq(&bytes).unwrap();
|
||
let g = if chroma { wai_ops::lower::jpegai_nets::syn_uv(&mut net) } else { wai_ops::lower::jpegai_nets::syn_y(&mut net) }
|
||
.and_then(|g| wai_ops::graph::load(&g.to_bytes(), wai_ops::Profile::Acc64))
|
||
.unwrap_or_else(|e| panic!("well-formed networks are refused: {e:?}\n{}", why()));
|
||
let w = ptq_weights(&v);
|
||
let bits = [12u32, 18, 22][r.below(3) as usize];
|
||
let ly = q16_values(r, 160 * 256, bits);
|
||
let luv = q16_values(r, 96 * 256, 18);
|
||
let mut inputs = vec![wai_ops::Tensor::new(160, 16, 16, ly.clone())];
|
||
if chroma {
|
||
inputs.push(wai_ops::Tensor::new(96, 16, 16, luv.clone()));
|
||
}
|
||
let new = wai_ops::run_with(&g, inputs, &[], &mut wai_ops::Sequence::new(), &wai::int_ops::Host).map(|mut o| o.swap_remove(0).data);
|
||
let old = catch_unwind(AssertUnwindSafe(|| {
|
||
let f = if chroma {
|
||
let mut cat = ly.clone();
|
||
cat.extend_from_slice(&luv);
|
||
decode_uv_f32(&cat, &w)
|
||
} else {
|
||
decode_y_f32(&ly, &w)
|
||
};
|
||
// Back to the Q16 integers the direct networks' denormalisation gave.
|
||
f.iter().map(|&x| (x as f64 * 65536.0) as i64).collect::<Vec<i64>>()
|
||
}))
|
||
.ok();
|
||
*outcomes.entry(ptq_outcome(if chroma { "syn_uv" } else { "syn_y" }, new, old, &g, &why)).or_default() += 1;
|
||
});
|
||
eprintln!("jpegai networks, synthesis: {n} cases; outcomes {outcomes:?}");
|
||
}
|