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]>
140 lines
6.8 KiB
Rust
140 lines
6.8 KiB
Rust
// wai-conformance: int-kernels
|
||
//! A `wai.neural.int_mlicpp` payload whose header the model cannot fit is refused
|
||
//! before the decode allocates anything sized from it.
|
||
//!
|
||
//! The header caps each tensor it sizes directly, but what the networks allocate
|
||
//! depends on the model as well: a small, well-formed header can declare a hyper-latent
|
||
//! that `h_s` would expand far past the cap, or an image or latent that does not match
|
||
//! what the networks produce. The decode derives every network's shapes from its layer
|
||
//! geometry first (`int_hyper_synth::net_shape`) and refuses such a payload with
|
||
//! nothing allocated. This file checks that directly: it installs a global allocator
|
||
//! that records the largest single allocation, and asserts that refusing each
|
||
//! malformed payload allocates no more than a few kilobytes. On a 32-bit target, where
|
||
//! the allocation that check prevents would abort the process rather than fail, the
|
||
//! same test shows the decode returns an error instead.
|
||
//!
|
||
//! The model is loaded the same way: a damaged compact network (`WHM1`) is refused
|
||
//! without the parser reserving for what its counts claim.
|
||
//!
|
||
//! Its own test binary with a single test, so that no other test's allocations land in
|
||
//! the window it measures.
|
||
#![cfg(feature = "neural_int")]
|
||
|
||
use std::alloc::{GlobalAlloc, Layout, System};
|
||
use std::path::PathBuf;
|
||
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
||
|
||
use wai::container::Wai;
|
||
use wai::int_hyper_synth::{IntLayer, IntModel};
|
||
use wai::int_mlicpp::{decode, Header, IntMlicError, IntMlicModel};
|
||
|
||
/// The system allocator, recording the largest single request.
|
||
struct Largest;
|
||
static LARGEST: AtomicUsize = AtomicUsize::new(0);
|
||
|
||
unsafe impl GlobalAlloc for Largest {
|
||
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
|
||
LARGEST.fetch_max(l.size(), Relaxed);
|
||
unsafe { System.alloc(l) }
|
||
}
|
||
unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
|
||
LARGEST.fetch_max(l.size(), Relaxed);
|
||
unsafe { System.alloc_zeroed(l) }
|
||
}
|
||
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
|
||
LARGEST.fetch_max(new, Relaxed);
|
||
unsafe { System.realloc(p, l, new) }
|
||
}
|
||
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
|
||
unsafe { System.dealloc(p, l) }
|
||
}
|
||
}
|
||
|
||
#[global_allocator]
|
||
static ALLOC: Largest = Largest;
|
||
|
||
/// The largest single allocation `f` makes, and its result.
|
||
fn largest_in<R>(f: impl FnOnce() -> R) -> (R, usize) {
|
||
LARGEST.store(0, Relaxed);
|
||
let r = f();
|
||
(r, LARGEST.load(Relaxed))
|
||
}
|
||
|
||
fn read(name: &str) -> Vec<u8> {
|
||
let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp").join(name);
|
||
std::fs::read(&p).unwrap_or_else(|e| panic!("{}: {e}", p.display()))
|
||
}
|
||
|
||
/// A header field of the committed payload replaced (offsets as in `parse_header`).
|
||
fn with(p: &[u8], off: usize, bytes: &[u8]) -> Vec<u8> {
|
||
let mut p = p.to_vec();
|
||
p[off..off + bytes.len()].copy_from_slice(bytes);
|
||
p
|
||
}
|
||
|
||
const FEW_KB: usize = 16 << 10;
|
||
|
||
#[test]
|
||
fn a_header_the_model_cannot_fit_is_refused_before_anything_is_allocated() {
|
||
let model = IntMlicModel::from_bundle(&read("model.wmb")).expect("model");
|
||
let pl = Wai::from_bytes(&read("stream.wai")).expect("envelope").payload;
|
||
|
||
// The counter sees the decode's allocations: the committed payload's largest is at
|
||
// least its 64 × 64 RGB image.
|
||
let (img, largest) = largest_in(|| decode(&model, &pl));
|
||
img.expect("the committed payload decodes");
|
||
assert!(largest >= 64 * 64 * 3, "the counter saw a {largest}-byte allocation at most");
|
||
|
||
// A compact network claiming 2^32 − 1 layers in 20 bytes: refused as truncated,
|
||
// with nothing reserved for the layers it claims (about 480 GB of them).
|
||
let whm1 = [b"WHM1".as_slice(), &12u32.to_le_bytes(), &16u32.to_le_bytes(), &24u32.to_le_bytes(),
|
||
&u32::MAX.to_le_bytes()].concat();
|
||
let (r, largest) = largest_in(|| IntModel::from_bytes(&whm1).err());
|
||
assert!(largest <= FEW_KB, "parsing a damaged network reserved {largest} bytes");
|
||
assert_eq!(r, Some("model truncated"));
|
||
|
||
let u16le = |v: u16| v.to_le_bytes();
|
||
let u32le = |v: u32| v.to_le_bytes();
|
||
let cases: [(&str, Vec<u8>, &str); 3] = [
|
||
// z [4, 4000, 4000]: 64 Mi elements, within the header's cap, but h_s would
|
||
// expand it to [16, 16000, 16000] where the latent is [16, 8, 8].
|
||
("an inflated hyper-latent", with(&with(&pl, 14, &u16le(4000)), 16, &u16le(4000)), "h_s gives"),
|
||
// A 1000 × 1000 latent where h_s gives 8 × 8.
|
||
("an inflated latent", with(&with(&pl, 20, &u16le(1000)), 22, &u16le(1000)), "h_s gives"),
|
||
// A 4000 × 4000 image where g_s gives 64 × 64.
|
||
("an inflated image", with(&with(&pl, 4, &u32le(4000)), 8, &u32le(4000)), "g_s gives"),
|
||
];
|
||
for (what, bad, why) in cases {
|
||
let (r, largest) = largest_in(|| decode(&model, &bad));
|
||
// Allocation first: a decoder can also refuse these later, once a stream runs
|
||
// out, but only after allocating and filling what the header asked for.
|
||
assert!(largest <= FEW_KB, "{what}: refusing it allocated {largest} bytes at once ({r:?})");
|
||
match r {
|
||
Err(IntMlicError::Model(e)) => assert!(e.contains(why), "{what}: {e}"),
|
||
other => panic!("{what}: expected a model refusal, got {other:?}"),
|
||
}
|
||
}
|
||
|
||
// A model whose synthesis, on the committed (well-formed) header's own shapes,
|
||
// would form a buffer far past the cap on the way to the right output: a stride-2^20
|
||
// transposed convolution spreads the 8 × 8 latent over a plane of about 7.3 million
|
||
// squared, and a stride-2^20 convolution brings it back to 8 × 8 before the rest of
|
||
// g_s. Its output shape is the image's, so only the size check can refuse it.
|
||
let s = 1usize << 20;
|
||
let mut wide = IntMlicModel::from_bundle(&read("model.wmb")).expect("model");
|
||
let unit = |out_ch| (vec![1i64; 8 * out_ch], vec![1i64; out_ch], vec![0i64; out_ch]);
|
||
let (weight, mult, bias) = unit(8);
|
||
wide.g_s.layers.insert(0, IntLayer::ConvT { out_ch: 8, k: 1, stride: s, pad: 0, opad: 0, weight, mult, bias });
|
||
let (weight, mult, bias) = unit(8);
|
||
wide.g_s.layers.insert(1, IntLayer::Conv { out_ch: 8, k: 1, stride: s, pad: 0, weight, mult, bias });
|
||
let (r, largest) = largest_in(|| decode(&wide, &pl));
|
||
assert!(largest <= FEW_KB, "a synthesis wider than the cap: refusing it allocated {largest} bytes at once ({r:?})");
|
||
match r {
|
||
// On a 64-bit target the plane's size is formed, and is over the cap; on a
|
||
// 32-bit one it does not fit `usize`, and the geometry is refused as such.
|
||
Err(IntMlicError::Model(e)) => assert!(
|
||
e.contains("g_s") && (e.contains(&format!("over the {}-element cap", Header::MAX_TENSOR)) || e.contains("overflows")),
|
||
"{e}"),
|
||
other => panic!("a synthesis wider than the cap: expected a model refusal, got {other:?}"),
|
||
}
|
||
}
|