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]>
320 lines
17 KiB
Rust
320 lines
17 KiB
Rust
// wai-conformance: int-kernels int-kernels-amd
|
||
//! Geometry at the edges of what the fast integer kernels lay out, run through the
|
||
//! public API under the scalar reference kernels AND under each fast-kernel policy
|
||
//! (serial, explicit multi-threaded tilings, automatic):
|
||
//!
|
||
//! * a 1×1 input upsampled by strides from 1 up to `usize::MAX`, through the
|
||
//! transposed convolutions of int_hyper (a `WHM1` layer through its lowering), int_synth
|
||
//! (`decode_from_spec`) and the JPEG-AI synthesis (`conv_transpose`);
|
||
//! * a regular convolution whose stride reaches or exceeds its padded input
|
||
//! (a `WHM1` layer through its lowering, `conv_grouped`);
|
||
//! * padding far beyond the kernel, with output padding restoring a small output;
|
||
//! * an input of two or more rows and columns whose stride and padding make the
|
||
//! reference's own size arithmetic overflow (int_hyper, int_synth, JPEG-AI).
|
||
//!
|
||
//! The reference computes each of the first three in a handful of operations. Every
|
||
//! policy must return the same integers (checked against a hand computation), and
|
||
//! the fast path must not overflow, or allocate or loop in proportion to the stride
|
||
//! or the padding, where the reference does not. In the last, the reference panics,
|
||
//! and every policy must do the same. int_synth decodes through its lowering
|
||
//! (integer-operators §13.1), and the int_hyper networks through theirs (`whm1/1`,
|
||
//! §13.2): their fields are 32 bits and their run-time plan caps every size it forms at
|
||
//! 2^26, so where a case is past either, every policy refuses it, where the scalar kernel
|
||
//! the int_hyper decodes once ran panicked or computed. int_audio decodes through its
|
||
//! lowering `wam1/1` too; its 1-D transposed convolution's edge geometry is tested
|
||
//! through that graph under every policy (`int_audio` unit tests). CI runs this file
|
||
//! beside `int_kernels_vectors` on every `wai-conformance` leg, with overflow checks
|
||
//! on and off.
|
||
#![cfg(all(feature = "neural_int", feature = "legacy_int"))]
|
||
|
||
use serde_json::json;
|
||
use wai::int_kernels::{with_policy, Policy};
|
||
|
||
static POLICIES: std::sync::LazyLock<Vec<Policy>> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies);
|
||
|
||
/// A 3×3 kernel `[[1, 2, 3], [4, 5, 6], [7, 8, 9]]`.
|
||
const KERNEL: [i64; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||
|
||
/// Run under every policy; all results must be identical. Returns the result.
|
||
fn same_under_every_policy<T: PartialEq + std::fmt::Debug>(what: &str, f: impl Fn() -> T) -> T {
|
||
let want = with_policy(POLICIES[0], &f);
|
||
for p in &POLICIES[1..] {
|
||
let got = with_policy(*p, &f);
|
||
assert!(got == want, "{what}: result under {p:?} differs from the reference kernels ({got:?} vs {want:?})");
|
||
}
|
||
want
|
||
}
|
||
|
||
/// Run under every policy, catching a panic. The outcome (the value, or that the
|
||
/// call panicked) must be the same under every policy. Returns the reference
|
||
/// kernels' outcome, `None` for a panic. The panic message is not compared: a fast
|
||
/// policy may fail at another element first, or on a worker thread.
|
||
fn same_outcome_under_every_policy<T: PartialEq + std::fmt::Debug>(what: &str, f: impl Fn() -> T) -> Option<T> {
|
||
let run = |p: Policy| std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| with_policy(p, &f))).ok();
|
||
let want = run(POLICIES[0]);
|
||
for p in &POLICIES[1..] {
|
||
let got = run(*p);
|
||
assert!(got == want, "{what}: outcome under {p:?} differs from the reference kernels' ({got:?} vs {want:?}; None = panicked)");
|
||
}
|
||
want
|
||
}
|
||
|
||
/// Strides from ordinary to the largest a `usize` holds. 2^29 fits the `u32` stride
|
||
/// field of a WHM1 model; from 2^32 (64-bit targets) the product of the two strides
|
||
/// no longer fits in 64 bits.
|
||
///
|
||
/// 2^29 comes first, deliberately. A kernel that allocates one tap table per
|
||
/// (row, column) phase — as the fast transposed kernel did before it built tables
|
||
/// only for phases holding output — fails there at once (the table count exceeds
|
||
/// what any allocation can hold: "capacity overflow"). Placed after 2^16 it would
|
||
/// first commit memory for 2^32 tables (hundreds of GiB) and drive the machine into
|
||
/// swap before failing.
|
||
fn strides() -> Vec<usize> {
|
||
#[allow(unused_mut)] // pushed to on 64-bit targets only
|
||
let mut s = vec![1 << 29, 1, 2, 3, 1 << 13, 1 << 16, usize::MAX];
|
||
#[cfg(target_pointer_width = "64")]
|
||
s.push(1 << 32);
|
||
s
|
||
}
|
||
|
||
/// A one-layer int_hyper model.
|
||
fn hyper_model(layer: serde_json::Value) -> wai::int_hyper_synth::IntModel {
|
||
wai::int_hyper_synth::IntModel::from_json(&json!({ "afrac": 12, "gf": 16, "shift": 0, "layers": [layer] }))
|
||
}
|
||
|
||
/// A one-layer int_hyper model run as the int_hyper decodes run their networks: lowered by
|
||
/// `whm1/1`, loaded under `acc64`, and run by the integer operator set with this crate's
|
||
/// fast kernels behind it (`int_ops`). The output and its shape `(c, h, w)`, or the
|
||
/// refusal's code: a field past 32 bits, or a size the run-time plan caps.
|
||
fn hyper_run(model: &wai::int_hyper_synth::IntModel, x: &[i64], c: usize, h: usize, w: usize) -> Result<(Vec<i64>, usize, usize, usize), wai_ops::Code> {
|
||
let net = wai::int_ops::whm1_net(model).map_err(|r| r.code)?;
|
||
let l = wai_ops::lower::whm1_net_loaded(net, false, wai_ops::Profile::Acc64).map_err(|r| r.code)?;
|
||
let t = wai::int_ops::run1(&l, wai_ops::Tensor::new(c as u32, h as u32, w as u32, x.to_vec()), &[]).map_err(|r| r.code)?;
|
||
Ok((t.data, t.c as usize, t.h as usize, t.w as usize))
|
||
}
|
||
|
||
/// An int_synth payload: a 1×1 output image from one int8 latent (C = 1, S = 1).
|
||
fn synth_payload(latent: i8) -> Vec<u8> {
|
||
let mut p = Vec::new();
|
||
p.extend(1u32.to_le_bytes()); // H
|
||
p.extend(1u32.to_le_bytes()); // W
|
||
p.extend(1u16.to_le_bytes()); // C
|
||
p.extend([0u8, 0]); // S_log2, reserved
|
||
p.push(latent as u8);
|
||
p
|
||
}
|
||
|
||
/// A one-layer int_synth spec: 1 → 3 channels, 3×3 kernel `1..=27` (channel `oc`
|
||
/// holds `9·oc + KERNEL`), shift 0, no ReLU.
|
||
fn synth_spec(stride: usize, pad: usize, out_pad: usize, bias: [i32; 3]) -> Vec<u8> {
|
||
let weight: Vec<i32> = (1..=27).collect();
|
||
json!({ "layers": [{
|
||
"in_ch": 1, "out_ch": 3, "k": 3, "stride": stride, "pad": pad, "out_pad": out_pad,
|
||
"weight": weight, "bias": bias, "shift": 0, "relu": false
|
||
}]})
|
||
.to_string()
|
||
.into_bytes()
|
||
}
|
||
|
||
#[test]
|
||
fn a_1x1_input_upsampled_by_any_stride() {
|
||
// k = 3, pad 1: the output is 1×1 and receives only the centre tap, 5·x.
|
||
for s in strides() {
|
||
let got = same_under_every_policy(&format!("JPEG-AI conv_transpose, stride {s}"), || {
|
||
wai::int_transformer::conv_transpose(&[5], 1, 1, 1, &KERNEL, 1, 3, s, 1, 0, &[1], &[0], 0)
|
||
});
|
||
assert_eq!(got, (vec![25], 1, 1), "JPEG-AI conv_transpose, stride {s}");
|
||
|
||
let model = hyper_model(json!({
|
||
"type": "convT", "out_ch": 1, "k": 3, "stride": s, "pad": 1, "opad": 0,
|
||
"weight": KERNEL, "mult": [1], "bias": [0]
|
||
}));
|
||
let got = same_under_every_policy(&format!("int_hyper convT, stride {s}"), || hyper_run(&model, &[5], 1, 1, 1));
|
||
if s <= u32::MAX as usize {
|
||
assert_eq!(got, Ok((vec![25], 1, 1, 1)), "int_hyper convT, stride {s}");
|
||
} else {
|
||
// A WHM1 field is 32 bits: a model past it is not a WHM1 network.
|
||
assert_eq!(got, Err(wai_ops::Code::ModelMismatch), "int_hyper convT, stride {s}");
|
||
}
|
||
|
||
let (payload, spec) = (synth_payload(5), synth_spec(s, 1, 0, [0, 0, 0]));
|
||
let got = same_under_every_policy(&format!("int_synth, stride {s}"), || {
|
||
wai::int_synth::decode_from_spec(&payload, &spec).map(|img| img.rgb)
|
||
});
|
||
if s <= u32::MAX as usize {
|
||
// centre taps 5, 14, 23 of the three output channels, times 5
|
||
assert_eq!(got, Ok(vec![25, 70, 115]), "int_synth, stride {s}");
|
||
} else {
|
||
// int_synth decodes through its lowering, whose `WHG1` fields are 32 bits: a
|
||
// stride past them is refused, a planned break.
|
||
assert!(got.is_err(), "int_synth, stride {s}: a stride past 32 bits is refused");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn a_regular_conv_whose_stride_exceeds_its_padded_input() {
|
||
// 5×5 input 1..=25, k = 3. Pad 1 (7×7 padded): from stride 5 on, one output,
|
||
// the taps that meet x[0], x[1], x[5], x[6]: 5·1 + 6·2 + 8·6 + 9·7. Pad 2 (9×9
|
||
// padded): from stride 7 on, one output, only tap (2, 2) meets x[0]: 9·1. At
|
||
// stride usize::MAX, pad 2 puts two leading column phases before the first input
|
||
// column, whose position must be found without overflowing.
|
||
let x: Vec<i64> = (1..=25).collect();
|
||
for (pad, want, first) in [(1usize, 5 + 6 * 2 + 8 * 6 + 9 * 7, 5usize), (2, 9, 7)] {
|
||
#[allow(unused_mut)] // pushed to on 64-bit targets only
|
||
let mut ss = vec![first, 7, 8, 9, 1 << 13, 1 << 16, 1 << 29, usize::MAX];
|
||
#[cfg(target_pointer_width = "64")]
|
||
ss.push(1 << 32);
|
||
for s in ss {
|
||
let got = same_under_every_policy(&format!("JPEG-AI conv_grouped, pad {pad}, stride {s}"), || {
|
||
wai::int_transformer::conv_grouped(&x, 1, 5, 5, &KERNEL, 1, 3, s, pad, 1, &[1], &[0], 0)
|
||
});
|
||
assert_eq!(got, (vec![want], 1, 1), "JPEG-AI conv_grouped, pad {pad}, stride {s}");
|
||
|
||
let model = hyper_model(json!({
|
||
"type": "conv", "out_ch": 1, "k": 3, "stride": s, "pad": pad,
|
||
"weight": KERNEL, "mult": [1], "bias": [0]
|
||
}));
|
||
let got = same_under_every_policy(&format!("int_hyper conv, pad {pad}, stride {s}"), || hyper_run(&model, &x, 1, 5, 5));
|
||
if s <= u32::MAX as usize {
|
||
assert_eq!(got, Ok((vec![want], 1, 1, 1)), "int_hyper conv, pad {pad}, stride {s}");
|
||
} else {
|
||
assert_eq!(got, Err(wai_ops::Code::ModelMismatch), "int_hyper conv, pad {pad}, stride {s}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn padding_far_beyond_the_kernel() {
|
||
let p = 1usize << 12;
|
||
// Transposed: pad 2^12 with output padding 2^13 − 2 restores a 1×1 output that
|
||
// no tap reaches (it sits 2^12 rows into the uncropped plane). int_synth decodes
|
||
// through its lowering, whose run-time plan caps the uncropped output a transposed
|
||
// convolution forms at 2^26 elements (integer-operators §5.3); this one is
|
||
// 3·8193·8193, so every policy refuses it before anything is allocated, a planned
|
||
// break. With pad 2^9 and output padding 2^10 − 2 the uncropped output, 3·1025·1025,
|
||
// is within the cap, and the decode gives the bias.
|
||
let (payload, spec) = (synth_payload(5), synth_spec(1, p, 2 * p - 2, [10, 20, 30]));
|
||
let got = same_under_every_policy("int_synth, pad 2^12", || {
|
||
wai::int_synth::decode_from_spec(&payload, &spec).map(|img| img.rgb)
|
||
});
|
||
assert!(got.is_err(), "int_synth, pad 2^12: the uncropped output is past the cap");
|
||
let q = 1usize << 9;
|
||
let (payload, spec) = (synth_payload(5), synth_spec(1, q, 2 * q - 2, [10, 20, 30]));
|
||
let got = same_under_every_policy("int_synth, pad 2^9", || {
|
||
wai::int_synth::decode_from_spec(&payload, &spec).map(|img| img.rgb)
|
||
});
|
||
assert_eq!(got, Ok(vec![10, 20, 30]));
|
||
// Regular: a 1×1 input, 1×1 kernel, pad 2^12, stride 2^13 — a 2×2 output whose
|
||
// samples all fall in the padding, so each is the bias.
|
||
let got = same_under_every_policy("JPEG-AI conv_grouped, pad 2^12", || {
|
||
wai::int_transformer::conv_grouped(&[5], 1, 1, 1, &[3], 1, 1, 2 * p, p, 1, &[1], &[7], 0)
|
||
});
|
||
assert_eq!(got, (vec![7; 4], 2, 2));
|
||
let model = hyper_model(json!({
|
||
"type": "conv", "out_ch": 1, "k": 1, "stride": 2 * p, "pad": p, "weight": [3], "mult": [1], "bias": [7]
|
||
}));
|
||
let got = same_under_every_policy("int_hyper conv, pad 2^12", || hyper_run(&model, &[5], 1, 1, 1));
|
||
assert_eq!(got, Ok((vec![7; 4], 1, 2, 2)));
|
||
}
|
||
|
||
/// An int_synth payload: an `h`×`w` output image from a 2×2 int8 latent (C = 1,
|
||
/// S = 2), latents in row-major order.
|
||
#[cfg(target_pointer_width = "64")]
|
||
fn synth_payload_2x2(h: u32, w: u32, latents: [i8; 4]) -> Vec<u8> {
|
||
let mut p = Vec::new();
|
||
p.extend(h.to_le_bytes());
|
||
p.extend(w.to_le_bytes());
|
||
p.extend(1u16.to_le_bytes()); // C
|
||
p.extend([1u8, 0]); // S_log2 = 1, reserved
|
||
p.extend(latents.map(|v| v as u8));
|
||
p
|
||
}
|
||
|
||
// Transposed convolutions of an input with two or more rows and columns, whose
|
||
// stride and padding make the reference's own size arithmetic overflow. The
|
||
// int_hyper, int_audio and JPEG-AI references size an uncropped plane
|
||
// `cout·(ho + 2·pad)·(wo + 2·pad)` in unchecked `usize` arithmetic. With overflow
|
||
// checks on, the reference panics on that arithmetic. With them off it wraps and then
|
||
// indexes out of the wrapped buffer. (int_synth decodes through its lowering, whose
|
||
// plan forms every size in checked 64-bit arithmetic and caps it: it refuses these,
|
||
// below.) A fast policy must have
|
||
// the same outcome: it may not return a value where the reference panics. A 1×1
|
||
// input cannot show this, since `(h − 1)·stride` is 0. The int_audio 1-D form is
|
||
// tested through its lowering's graph (`int_audio` unit tests).
|
||
|
||
/// A 3×3 input, and a 2×2 one with fields that fit a WHM1 model's `u32`.
|
||
const X: [i64; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||
/// Stride 2^32 − 1 and pad 2^31 − 1: a 2×2 output from a 2×2 input, k = 1 (or a
|
||
/// 4×4 one, k = 3), under an uncropped plane of 2^32 × 2^32. On a 64-bit target
|
||
/// the plane size overflows; on a 32-bit one the stride arithmetic already does.
|
||
const S32: usize = u32::MAX as usize;
|
||
const P32: usize = (u32::MAX >> 1) as usize;
|
||
|
||
#[test]
|
||
fn jpegai_conv_transpose_where_the_reference_plane_size_overflows() {
|
||
// 3×3 input, k = 1, stride = pad = 2^62: a 1×1 output, but an uncropped plane
|
||
// of (2^63 + 1)² elements. The reference panics in both profiles.
|
||
#[cfg(target_pointer_width = "64")]
|
||
{
|
||
let s = 1usize << 62;
|
||
let got = same_outcome_under_every_policy("JPEG-AI conv_transpose, 3×3, stride = pad = 2^62", || {
|
||
wai::int_transformer::conv_transpose(&X, 1, 3, 3, &[7], 1, 1, s, s, 0, &[1], &[0], 0)
|
||
});
|
||
assert_eq!(got, None, "the reference panics");
|
||
}
|
||
let got = same_outcome_under_every_policy("JPEG-AI conv_transpose, 2×2, u32 stride and pad", || {
|
||
wai::int_transformer::conv_transpose(&X[..4], 1, 2, 2, &[7], 1, 1, S32, P32, 0, &[1], &[0], 0)
|
||
});
|
||
assert_eq!(got, None, "u32 fields: the reference panics");
|
||
}
|
||
|
||
#[test]
|
||
fn int_hyper_convt_where_the_reference_plane_size_overflows() {
|
||
// As above, through a WHM1 layer's lowering, as the int_hyper decodes run it. The
|
||
// scalar kernel they once ran panicked on both; through the operator set, every policy
|
||
// refuses both before anything is allocated: 2^62 is past a WHM1 field's 32 bits, and
|
||
// the u32 stride and pad give an uncropped side, (2 − 1)·(2^32 − 1) + 1, past the cap.
|
||
let convt = |s: usize, p: usize| {
|
||
hyper_model(json!({
|
||
"type": "convT", "out_ch": 1, "k": 1, "stride": s, "pad": p, "opad": 0,
|
||
"weight": [7], "mult": [1], "bias": [0]
|
||
}))
|
||
};
|
||
#[cfg(target_pointer_width = "64")]
|
||
{
|
||
let model = convt(1 << 62, 1 << 62);
|
||
let got = same_outcome_under_every_policy("int_hyper convT, 3×3, stride = pad = 2^62", || hyper_run(&model, &X, 1, 3, 3));
|
||
assert_eq!(got, Some(Err(wai_ops::Code::ModelMismatch)), "refused, not panicked");
|
||
}
|
||
let model = convt(S32, P32);
|
||
let got = same_outcome_under_every_policy("int_hyper convT, 2×2, u32 stride and pad", || hyper_run(&model, &X[..4], 1, 2, 2));
|
||
assert!(matches!(got, Some(Err(_))), "u32 fields: refused by the cap, not panicked: {got:?}");
|
||
}
|
||
|
||
#[cfg(target_pointer_width = "64")]
|
||
#[test]
|
||
fn int_synth_where_the_reference_output_size_overflows_i64() {
|
||
// 2×2 latent, k = 3, stride = pad = out_pad = 2^62: a 3×3 output. The direct
|
||
// decode computed (2 − 1)·2^62 + 3 + 2^62 − 2·2^62 in `i64`, which overflows
|
||
// (2^63 + 3) although every `usize` step fits: it panicked with overflow checks on
|
||
// and, with them off, returned the wrapped size's output. int_synth decodes through
|
||
// its lowering (integer-operators §13.1), whose `WHG1` fields are 32 bits: in every
|
||
// build and under every policy the decode is refused, not panicked. A planned break.
|
||
let s = 1usize << 62;
|
||
let (payload, spec) = (synth_payload_2x2(3, 3, [1, 2, 3, 4]), synth_spec(s, s, s, [0, 0, 0]));
|
||
let got = same_outcome_under_every_policy("int_synth, 2×2 latent, stride = pad = out_pad = 2^62", || {
|
||
wai::int_synth::decode_from_spec(&payload, &spec).map(|img| img.rgb)
|
||
});
|
||
assert!(matches!(got, Some(Err(_))), "refused, not panicked: {got:?}");
|
||
|
||
// The u32 fields fit `WHG1`, but the uncropped side (2 − 1)·(2^32 − 1) + 3 is past
|
||
// the run-time plan's cap of 2^26: refused before anything is allocated (the direct
|
||
// decode returned the bias), a planned break.
|
||
let (payload, spec) = (synth_payload_2x2(4, 4, [1, 2, 3, 4]), synth_spec(S32, P32, 0, [10, 20, 30]));
|
||
let got = same_outcome_under_every_policy("int_synth, 2×2 latent, u32 stride and pad", || {
|
||
wai::int_synth::decode_from_spec(&payload, &spec).map(|img| img.rgb)
|
||
});
|
||
assert!(matches!(got, Some(Err(_))), "u32 fields: refused by the cap: {got:?}");
|
||
}
|