open-standards/wai/wai-rs/tests/neural_cost_limit.rs
Transaction Science 15fecef33f neural: a decode reports the capability it decoded through and the path that ran
wai.image.jpegai decodes through its exact decoder or through a float decode,
and both returned the same result, so neither a receipt nor a binding check
could tell which had run. decode_envelope, decode_envelope_strict,
decode_multi, decode_multi_strict and decode_payload now return a Dispatched:
the output, the capability the payload decoded through (its own or the
fallback) and the path, DecodePath::Sole, Exact or Float. DecodePath::variant
gives the term a session claim's decoder entry states.

check_reconstruction_binding reads the path: over a decode-equivalence
capability or the exact decoder it reports whether the hash holds, and over
the float decode it reports the binding as not checked, never as failed, as
reconstruction-binding §2 requires. The notes in determinism-tiers §5 and
reconstruction-binding §2 that the dispatch did not report the path are gone.

tests/jpegai_decode_path.rs runs the float path and requires the result, the
session claim and the binding check to say so, and the exact decoder likewise.
Its JPEG-AI cases need inputs outside the repository and skip without them;
the case with one path runs in the neural gates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:29:01 -05:00

230 lines
11 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The reference neural sink applies the declared cost of an integer payload before it
//! decodes a symbol of it (`extensions/integer-payloads.md` §7, *Memory and work*;
//! `cost_over_limit`, §8), and its C ABI does not decode an integer output it has no
//! kind for. Measured with a global allocator that tracks the bytes live and their
//! peak, so "before decoding" is what the allocator sees: nothing sized from the
//! header is allocated.
//!
//! The payload is a clip a few kilobytes can code: sixteen frames whose stream is one
//! symbol of frequency 65535/65536 over and over. At 4729 × 4729 it decodes to a clip at
//! the 2^30-byte cap (release builds); a debug build uses 1024 × 1024 frames, 48 MiB.
//!
//! The tests share the allocator's counters, so they run one at a time.
#![cfg(all(feature = "neural", feature = "ffi", feature = "video_int"))]
use std::alloc::{GlobalAlloc, Layout, System};
use std::collections::BTreeMap;
use std::ffi::{CStr, CString};
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::sync::Mutex;
use wai::codecs::{CAP_NEURAL_INT_HYPER, CAP_VIDEO_INT_MOTION};
use wai::container::{Conditioning, Manifest, ModelRequirement, Rendition, Wai, WaiMulti};
use wai::ffi::{wai_int_payload_declared, wai_last_error, WaiBuffer, WaiIntDeclared, WAI_OK};
use wai::ffi_neural::{wai_neural_decode_ex, wai_neural_decode_video, WaiDecoded};
use wai::int_payload::{wire, DeclaredCost, IntDecoded};
use wai::neural::{decode_envelope, decode_multi, DecodeError, Decoded, ModelRegistry};
/// The system allocator, tracking the bytes live and the most live at once.
struct Live;
static CUR: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for Live {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
PEAK.fetch_max(CUR.fetch_add(l.size(), Relaxed) + l.size(), Relaxed);
unsafe { System.alloc(l) }
}
unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
PEAK.fetch_max(CUR.fetch_add(l.size(), Relaxed) + l.size(), Relaxed);
unsafe { System.alloc_zeroed(l) }
}
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
if new >= l.size() {
PEAK.fetch_max(CUR.fetch_add(new - l.size(), Relaxed) + (new - l.size()), Relaxed);
} else {
CUR.fetch_sub(l.size() - new, Relaxed);
}
unsafe { System.realloc(p, l, new) }
}
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
CUR.fetch_sub(l.size(), Relaxed);
unsafe { System.dealloc(p, l) }
}
}
#[global_allocator]
static ALLOC: Live = Live;
static SERIAL: Mutex<()> = Mutex::new(());
/// The most bytes `f` holds at once beyond what was live when it started.
fn peak_during<R>(f: impl FnOnce() -> R) -> (R, usize) {
let base = CUR.load(Relaxed);
PEAK.store(base, Relaxed);
let r = f();
(r, PEAK.load(Relaxed) - base)
}
/// Refusing before decoding allocates no more than this: the parsed table, a few
/// motion vectors, the refusal's message.
const SMALL: usize = 64 << 10;
fn vectors() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors")
}
fn read(rel: &str) -> Vec<u8> {
std::fs::read(vectors().join(rel)).unwrap()
}
fn last_error() -> String {
let p = wai_last_error();
if p.is_null() { String::new() } else { unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned() }
}
/// A rANS stream of `n` symbols, each coded with `(start, freq)`, from the state 2^31,
/// so it ends canonically: what an encoder writes.
fn encode(n: u64, (start, freq): (u64, u64)) -> Vec<u8> {
const L: u64 = 1 << 31;
let mut state = L;
let mut renorm: Vec<u32> = Vec::new();
for _ in 0..n {
let x_max = ((L >> 16) << 32) * freq;
while state >= x_max {
renorm.push(state as u32);
state >>= 32;
}
state = ((state / freq) << 16) + (state % freq) + start;
}
renorm.reverse();
let mut words = vec![state as u32, (state >> 32) as u32];
words.extend(renorm);
words.iter().flat_map(|w| w.to_le_bytes()).collect()
}
/// A keyframe-0 clip of sixteen frames whose every residual is 0 at frequency
/// 65535/65536: a valid stream of a few bytes per 2^20 symbols.
fn bomb() -> Vec<u8> {
let side = if cfg!(debug_assertions) { 1024 } else { 4729 };
let n = 16u32;
let rans = encode(n as u64 * side as u64 * side as u64 * 3, (0, 65_535));
wire::write_wiv1(&wire::Wiv1 {
mv_precision: 1, keyframe: 0, n_frames: n, h: side, w: side, block: side, q: 1, offset: 0, cdf: vec![0, 65_535, 65_536],
mvs: vec![(0, 0); n as usize - 1], rans: &rans, wih1: None,
})
}
fn video(cap: &str, payload: Vec<u8>) -> Wai {
Wai::new(Manifest::new("video", "improve", ModelRequirement::new(cap), Conditioning::new("bytes")), payload)
}
/// A registry with a cost limit refuses a payload over it before decoding anything
/// (`cost_over_limit`), and dispatch moves on: a `WAI2` decodes its next entry. The
/// cost it applied is `neural::declared_cost` over the parameters it resolves.
#[test]
fn a_payload_over_the_cost_limit_is_refused_before_any_symbol_is_decoded() {
let _serial = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let bomb = bomb();
assert!(bomb.len() < 4096, "the clip is coded in {} bytes", bomb.len());
let limit = DeclaredCost::new(16 << 20, u64::MAX, u64::MAX, u128::MAX);
let reg = ModelRegistry::new().builtin(CAP_VIDEO_INT_MOTION).with_cost_limit(limit);
let env = video(CAP_VIDEO_INT_MOTION, bomb.clone());
let (r, peak) = peak_during(|| decode_envelope(&env, &reg));
match r {
Err(e @ DecodeError::OverCostLimit(_)) => assert!(e.to_string().starts_with("cost_over_limit: ") && e.to_string().contains("output bytes"), "{e}"),
Err(e) => panic!("expected cost_over_limit, got {e}"),
Ok(_) => panic!("expected cost_over_limit, the clip decoded"),
}
assert!(peak < SMALL, "refusing it held {peak} bytes");
let small = read("int_video/int_video_bitstream.wiv1");
let n_frames = wire::parse_wiv1(&small, Some(0)).unwrap().n_frames as usize;
let m = WaiMulti::new("video", "improve", serde_json::Value::Null,
vec![Rendition::media(CAP_VIDEO_INT_MOTION, "bytes", bomb.clone()), Rendition::media(CAP_VIDEO_INT_MOTION, "bytes", small)]);
let (r, peak) = peak_during(|| decode_multi(&m, &reg).map(|(i, d)| (i, d.output)));
match r {
Ok((1, Decoded::Int(IntDecoded::Video { frames, .. }))) => assert_eq!(frames.len(), n_frames),
Ok((i, _)) => panic!("decoded entry {i}"),
Err(e) => panic!("the entry within the limit must decode: {e}"),
}
assert!(peak < 1 << 20, "skipping the first entry and decoding the second held {peak} bytes");
let params = reg.resolve_verified(CAP_VIDEO_INT_MOTION, None).unwrap();
let cost = wai::neural::declared_cost(CAP_VIDEO_INT_MOTION, &bomb, &params).unwrap();
assert_eq!(cost.exceeds(&limit), Some("output bytes"));
assert_eq!(cost.output_bytes, 16 * 3 * if cfg!(debug_assertions) { 1024 * 1024 } else { 4729 * 4729 });
}
/// The C ABI has no output kind for integer video, so it does not decode it: the
/// clip is refused before anything is allocated for it, by the registry entry point
/// and by the one-model video entry point.
#[test]
fn the_c_abi_refuses_an_integer_video_before_decoding_it() {
let _serial = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let env = video(CAP_VIDEO_INT_MOTION, bomb()).to_emit_bytes().unwrap();
let reg = CString::new(r#"{"wai.video.int_motion": ["/nonexistent"]}"#).unwrap();
let mut out = std::mem::MaybeUninit::<WaiDecoded>::uninit();
let (rc, peak) = peak_during(|| unsafe { wai_neural_decode_ex(env.as_ptr(), env.len(), reg.as_ptr(), 0, out.as_mut_ptr()) });
let why = last_error();
assert_ne!(rc, WAI_OK);
assert!(why.contains("no wai_decoded_t kind"), "{why}");
assert!(peak < SMALL, "decode_ex held {peak} bytes refusing it");
unsafe { wai::ffi::wai_buffer_free(out.assume_init().data) };
let model = CString::new("/nonexistent").unwrap();
let mut frames = WaiBuffer { data: std::ptr::null_mut(), len: 0, cap: 0 };
let null = std::ptr::null_mut();
let (rc, peak) = peak_during(|| unsafe { wai_neural_decode_video(env.as_ptr(), env.len(), model.as_ptr(), &mut frames, null, null, null, null) });
let why = last_error();
assert_ne!(rc, WAI_OK);
assert!(why.contains("no wai_decoded_t kind"), "{why}");
assert!(peak < SMALL, "decode_video held {peak} bytes refusing it");
}
/// `neural::declared_cost` over the set a registry resolves is the library's over the
/// same bytes, and so is the C ABI's given the set's directory: every figure, the
/// multiply-accumulates in two halves. A set without a member is `set_incomplete`.
#[test]
fn the_declared_cost_over_a_registrys_set_and_through_the_c_abi() {
let _serial = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let dir = std::env::temp_dir().join(format!("wai-cost-set-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let set: BTreeMap<String, Vec<u8>> = [("entropy.json", "int_hyper/tiny_entropy.json"), ("g_s.whm", "int_hyper/tiny_g_s.bin"), ("h_s.whm", "int_hyper/tiny_h_s.bin")]
.into_iter().map(|(k, f)| (k.to_string(), read(f))).collect();
for (k, b) in &set {
std::fs::write(dir.join(k), b).unwrap();
}
let payload = read("int_hyper/tiny_bitstream.wih1");
let view: BTreeMap<String, &[u8]> = set.iter().map(|(k, b)| (k.clone(), b.as_slice())).collect();
let held = wai::int_payload::resolve(CAP_NEURAL_INT_HYPER, None, &[wai::pin::HeldBytes::Set { listing: b"", files: &view }], None).unwrap();
let want = wai::int_payload::declared_cost(CAP_NEURAL_INT_HYPER, &payload, &held).unwrap();
assert!(want.macs > 0);
let reg = ModelRegistry::new().register(CAP_NEURAL_INT_HYPER, dir.clone());
let params = reg.resolve_verified(CAP_NEURAL_INT_HYPER, None).unwrap();
assert_eq!(wai::neural::declared_cost(CAP_NEURAL_INT_HYPER, &payload, &params).unwrap(), want);
let cap = CString::new(CAP_NEURAL_INT_HYPER).unwrap();
let path = CString::new(dir.to_str().unwrap()).unwrap();
let declared = |set: *const std::ffi::c_char| {
let mut out = WaiIntDeclared::default();
let rc = unsafe { wai_int_payload_declared(cap.as_ptr(), payload.as_ptr(), payload.len(), set, &mut out) };
(rc, out)
};
let (rc, out) = declared(path.as_ptr());
assert_eq!(rc, WAI_OK, "{}", last_error());
assert_eq!((out.output_known, out.output_bytes, out.symbols, out.cost_known, out.working_bytes),
(1, want.output_bytes, want.symbols, 1, want.working_bytes));
assert_eq!(((out.macs_hi as u128) << 64) | out.macs_lo as u128, want.macs);
let (rc, out) = declared(std::ptr::null());
assert_eq!((rc, out.output_known, out.cost_known), (WAI_OK, 0, 0), "the header alone");
std::fs::remove_file(dir.join("h_s.whm")).unwrap();
let (rc, _) = declared(path.as_ptr());
assert_ne!(rc, WAI_OK);
assert!(last_error().contains("set_incomplete"), "{}", last_error());
std::fs::remove_dir_all(&dir).ok();
}