open-standards/wai/wai-rs/src/int_audio.rs
Transaction Science c7daf86787 wai: the declared cost counts the networks' multiply-accumulates
int_payload::DeclaredCost gains macs: the multiply-accumulates the
decode's networks comprise, layer by layer, from the same plan that
sizes their buffers (int_hyper_synth::plan_net, the audio synthesis's
plan): a convolution's every output times c·k·k, a transposed
convolution's every input times out·k·k (out·k in the audio synthesis),
an inverse GDN's every element times c. It is exact, summed in u128,
and 0 for a WIV1 with keyframe 0. A kernel may skip a zero term, so it
performs at most this many. wai-web's intPayloadCost gives it as a
BigInt (macs). integer-payloads §7 states the definition.

Tests: int_hyper_synth's the_planned_macs_are_every_layers_terms (200
random networks, each term counted at the shapes apply_layer forms);
int_audio's the_planned_macs_are_every_layers_terms (the committed
synthesis, at the lengths the reference kernel gives);
int_payload_cost (0 for keyframe 0, more for every network);
wai-web's the_declared_cost_is_read_before_decoding.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 22:22:38 -04:00

666 lines
32 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.

//! Integer-exact synthesis of a LEARNED 1-D audio codec — the decode side of a
//! `NeuralIntegerExact` audio codec, the audio analog of [`crate::int_hyper_synth`].
//! Pure i64, no float, no `ort`: the reconstructed int16 PCM is byte-identical on
//! every machine (*sample-equivalence*, a portable PCM hash).
//!
//! A decode through a FLOAT network lets ~1e-5 round-off make the waveform — and
//! its hash — diverge between machines. Here the synthesis `g_s` is a stack of integer 1-D
//! transposed convolutions (per-output-channel `(mult,shift)` requant) + integer
//! IGDN ([`crate::int_transform::igdn`]); the latent is recovered by the integer
//! rANS ([`crate::rans`]) under a per-channel factorized prior. Every operation is
//! i64 + an arithmetic shift, so two sinks honoring the spec produce the same PCM.
//!
//! It runs the model the export tool emits (`tools/wai_int_audio_export.py`): a
//! compact binary (`WAM1`) with int12 transposed-conv weights as `i16` and IGDN
//! `gamma` as `i32`.
use serde_json::Value;
use crate::int_transform::igdn;
use crate::rans::RansDecoder;
/// One layer of the integer synthesis `g_s`. `ConvT1d` weight is `[cin·cout·k]`
/// (layout `ic, oc, kx`); `Igdn` carries a full `c×c` gamma (`2^gf`) + beta
/// (`2^(2·afrac)`). Widened to i64 for the decode (arithmetic stays inside i64).
pub enum ALayer {
ConvT1d { out_ch: usize, k: usize, stride: usize, pad: usize, opad: usize, weight: Vec<i64>, mult: Vec<i64>, bias: Vec<i64> },
Igdn { c: usize, gamma: Vec<i64>, beta: Vec<i64> },
}
/// A parsed integer 1-D audio synthesis model (`WAM1`).
pub struct AudioModel {
pub afrac: u32,
pub gf: u32,
pub shift: u32,
pub layers: Vec<ALayer>,
}
/// Little-endian cursor over the binary model, bounds-checked: every read is of bytes
/// that are present, with its end formed in checked arithmetic, so a damaged model is
/// refused and never makes the parser allocate more than the bytes it holds.
struct Cur<'a> {
b: &'a [u8],
p: usize,
}
impl<'a> Cur<'a> {
/// The next `n` elements of `size` bytes each.
fn take(&mut self, n: usize, size: usize) -> Result<&'a [u8], &'static str> {
let e = n.checked_mul(size).and_then(|len| self.p.checked_add(len)).ok_or("model truncated")?;
let s = self.b.get(self.p..e).ok_or("model truncated")?;
self.p = e;
Ok(s)
}
fn u32(&mut self) -> Result<usize, &'static str> {
Ok(u32::from_le_bytes(self.take(1, 4)?.try_into().unwrap()) as usize)
}
fn u8(&mut self) -> Result<u8, &'static str> {
Ok(self.take(1, 1)?[0])
}
fn i16v(&mut self, n: usize) -> Result<Vec<i64>, &'static str> {
Ok(self.take(n, 2)?.chunks_exact(2).map(|c| i16::from_le_bytes([c[0], c[1]]) as i64).collect())
}
fn i32v(&mut self, n: usize) -> Result<Vec<i64>, &'static str> {
Ok(self.take(n, 4)?.chunks_exact(4).map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]) as i64).collect())
}
fn i64v(&mut self, n: usize) -> Result<Vec<i64>, &'static str> {
Ok(self.take(n, 8)?.chunks_exact(8).map(|c| i64::from_le_bytes(c.try_into().unwrap())).collect())
}
}
/// The product of a layer's dimensions, refused where it overflows `usize`.
fn count(dims: &[usize]) -> Result<usize, &'static str> {
dims.iter().try_fold(1usize, |a, &d| a.checked_mul(d)).ok_or("layer size overflows")
}
impl AudioModel {
/// Parse the export tool's compact binary model: magic `WAM1`, then
/// `afrac/gf/shift/n_layers` (u32) and per layer a `u8` tag — 0 convT1d, 2 igdn
/// — with int12 weights as `i16`, IGDN `gamma` as `i32`, the rest `i64`, and
/// nothing after the last layer.
pub fn from_bytes(b: &[u8]) -> Result<Self, &'static str> {
if b.get(0..4) != Some(b"WAM1") {
return Err("bad model magic (want WAM1)");
}
let mut c = Cur { b, p: 4 };
let (afrac, gf, shift) = (c.u32()? as u32, c.u32()? as u32, c.u32()? as u32);
let n = c.u32()?;
// Each layer is at least its tag byte, so a count beyond the bytes left is a
// truncated model, and reserving for it would let four damaged bytes ask for a
// large allocation.
let mut layers = Vec::with_capacity(n.min(b.len() - c.p));
for _ in 0..n {
layers.push(match c.u8()? {
0 => {
let inc = c.u32()?;
let out = c.u32()?;
let (k, stride, pad, opad) = (c.u32()?, c.u32()?, c.u32()?, c.u32()?);
ALayer::ConvT1d { out_ch: out, k, stride, pad, opad,
weight: c.i16v(count(&[inc, out, k])?)?, mult: c.i64v(out)?, bias: c.i64v(out)? }
}
2 => {
let cc = c.u32()?;
ALayer::Igdn { c: cc, gamma: c.i32v(count(&[cc, cc])?)?, beta: c.i64v(cc)? }
}
_ => return Err("bad layer tag"),
});
}
// Nothing follows the last layer (`extensions/integer-payloads.md` §9).
if c.p != b.len() {
return Err("bytes follow the last layer");
}
Ok(Self { afrac, gf, shift, layers })
}
}
/// One integer 1-D transposed conv with per-output-channel requant — see
/// [`reference::conv_t_1d`] for the definition. Runs the fast kernel
/// ([`crate::int_kernels`], as a 1-row 2-D transposed conv) unless the current
/// [`crate::int_kernels::Policy`] selects the reference; both give the same integer
/// for every output element.
#[allow(clippy::too_many_arguments)]
fn conv_t_1d(
x: &[i64], cin: usize, t: usize, weight: &[i64], cout: usize, k: usize,
stride: usize, pad: usize, opad: usize, mult: &[i64], bias: &[i64], shift: u32,
) -> (Vec<i64>, usize) {
use crate::int_kernels::{conv_acc_i64, policy, ConvGeom, KernelPath};
if policy().path == KernelPath::Fast && mult.len() >= cout && bias.len() >= cout
&& let Some(g) = ConvGeom::transposed(cin, 1, t, cout, 1, k, 1, stride, 0, pad, 0, opad, x.len(), weight.len())
&& let Some(out) = conv_acc_i64(&g, x, weight, |oc, raw| ((raw as i128 * mult[oc] as i128) >> shift) as i64 + bias[oc])
{
return (out, g.wo);
}
reference::conv_t_1d(x, cin, t, weight, cout, k, stride, pad, opad, mult, bias, shift)
}
/// The scalar reference kernel the fast path is checked against, kept verbatim.
#[doc(hidden)]
pub mod reference {
/// One integer 1-D transposed conv with per-output-channel requant
/// `((raw·mult)>>shift)+bias`. `x` is `[cin, t]`; `weight` is `[cin][cout][k]`.
/// Returns `([cout, to], to)`. Pure i64 (i128 only for the requant multiply) —
/// mirrors the export byte-for-byte.
#[allow(clippy::too_many_arguments)]
pub fn conv_t_1d(
x: &[i64], cin: usize, t: usize, weight: &[i64], cout: usize, k: usize,
stride: usize, pad: usize, opad: usize, mult: &[i64], bias: &[i64], shift: u32,
) -> (Vec<i64>, usize) {
let to = (t - 1) * stride + k + opad - 2 * pad;
let tp = to + 2 * pad;
let mut raw = vec![0i64; cout * tp];
for ic in 0..cin {
let xc = &x[ic * t..ic * t + t];
for oc in 0..cout {
let wk = &weight[(ic * cout + oc) * k..(ic * cout + oc) * k + k];
let plane = oc * tp;
for (it, &v) in xc.iter().enumerate() {
if v == 0 {
continue;
}
let base = plane + it * stride;
for kx in 0..k {
raw[base + kx] += wk[kx] * v;
}
}
}
}
let mut out = vec![0i64; cout * to];
for oc in 0..cout {
let (m, b) = (mult[oc] as i128, bias[oc]);
for ot in 0..to {
let r = raw[oc * tp + ot + pad] as i128;
out[oc * to + ot] = ((r * m) >> shift) as i64 + b;
}
}
(out, to)
}
}
fn i64s(v: &Value) -> Vec<i64> {
v.as_array().map(|a| a.iter().filter_map(|n| n.as_i64()).collect()).unwrap_or_default()
}
use crate::int_hyper_synth::{refused, Refused, RefusalKind, MAX_AFRAC, MAX_TENSOR};
/// A clip's coded form, typed: what a `WIA1` payload carries
/// (`extensions/integer-payloads.md` §5), and what the tool's JSON form carries, read
/// by [`AudioJson`]. `cdfs` holds one table per latent channel, all of one length.
#[derive(Debug, Clone, Copy)]
pub struct AudioStream<'a> {
/// The fixed-point scale the stream was coded at; it must equal the model's.
pub afrac: u32,
pub n_ch: usize,
pub lat_len: usize,
/// Samples presented: the first `t_out` of the synthesis.
pub t_out: usize,
/// The symbol offset every channel's table is coded at (the JSON form's `lo`).
pub offset: i32,
pub median_q: &'a [i64],
pub cdfs: &'a [Vec<i32>],
pub rans: &'a [u8],
}
/// The tool's JSON bitstream (`tools/wai_int_audio_export.py`'s
/// `audio_bitstream.json`), read into the typed form. A converter input and a
/// debugging aid; the payload is `WIA1`. What the old JSON decoder left implicit is
/// explicit here: its `afrac` is read (the old decoder ignored it) and must be the
/// model's; every channel's table must have the first table's length (the old decoder
/// read every table at that length).
#[derive(Debug, Clone)]
pub struct AudioJson {
pub afrac: u32,
pub n_ch: usize,
pub lat_len: usize,
pub t_out: usize,
pub offset: i32,
pub median_q: Vec<i64>,
pub cdfs: Vec<Vec<i32>>,
pub rans: Vec<u8>,
}
impl AudioJson {
/// Read the tool's JSON bitstream.
pub fn from_value(bs: &Value) -> Result<Self, &'static str> {
let uz = |k: &'static str| bs[k].as_u64().and_then(|v| usize::try_from(v).ok()).ok_or(k);
let (n_ch, lat_len, t_out) = (uz("n_ch")?, uz("lat_len")?, uz("t_out")?);
let afrac = bs["afrac"].as_u64().and_then(|v| u32::try_from(v).ok()).ok_or("afrac")?;
let offset = bs["lo"].as_i64().and_then(|v| i32::try_from(v).ok()).ok_or("lo")?;
let median_q = i64s(&bs["median_q"]);
let cdfs = bs["cdfs"].as_array().ok_or("cdfs")?.iter()
.map(|row| row.as_array().and_then(|r| r.iter().map(|n| n.as_i64().and_then(|v| i32::try_from(v).ok())).collect::<Option<Vec<i32>>>()))
.collect::<Option<Vec<_>>>().ok_or("cdfs holds a value outside i32")?;
let first = cdfs.first().ok_or("empty cdfs")?.len();
if cdfs.iter().any(|c| c.len() != first) {
return Err("the channels' tables are not of one length");
}
let rans = b64_decode(bs["rans_b64"].as_str().ok_or("rans_b64")?)?;
Ok(AudioJson { afrac, n_ch, lat_len, t_out, offset, median_q, cdfs, rans })
}
/// The typed form, borrowing this one's tables.
pub fn stream(&self) -> AudioStream<'_> {
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 model's output `[c, t]` on a `[c, t]` latent, planned from its layers' geometry
/// and parameter lengths alone, before anything is allocated
/// (`extensions/integer-payloads.md` §7). Layer by layer in order: the layer fits the
/// running channel count (`ModelMismatch`: output channels, kernel and stride
/// non-zero, `c·out·k` weights, `out` multipliers and biases, a requantisation shift
/// under 128; an inverse GDN of the running channel count, with `c·c` and `c`
/// parameters and shifts under 128); the uncropped length `(t − 1)·stride + k + opad`
/// and the buffer of `out` channels of it are within [`MAX_TENSOR`] (`TooLarge`); and
/// the crop `2·pad` leaves an output (`ModelMismatch`). Every size is formed in `u64`
/// with checked arithmetic, a size past `u64` being over the cap, so every target
/// refuses alike. Returns the output `[c, t]`, the most elements one layer holds, and
/// the multiply-accumulates the layers comprise (`extensions/integer-payloads.md` §7):
/// a transposed convolution's every input times `out·k`, an inverse GDN's every
/// element times `c`, summed in `u128`.
fn plan(model: &AudioModel, c: usize, t: usize) -> Result<(usize, usize, u64, u128), Refused> {
use RefusalKind::*;
const CAP: u64 = MAX_TENSOR as u64;
let (mut c, mut t) = (c as u64, t as u64);
// The most elements the reference kernels hold while one layer runs, its input
// included: a transposed convolution's input, uncropped buffer and output; an
// inverse GDN's input, output and one position's squares (`c` values of 16 bytes).
let mut live = c * t;
let mut macs: u128 = 0;
for layer in &model.layers {
match layer {
ALayer::ConvT1d { out_ch, k, stride, pad, opad, weight, mult, bias } => {
let (cout, k, s, p, op) = (*out_ch as u64, *k as u64, *stride as u64, *pad as u64, *opad as u64);
if cout == 0 || k == 0 || s == 0 {
return Err(refused(ModelMismatch, "a layer has zero output channels, kernel or stride"));
}
if c.checked_mul(cout).and_then(|v| v.checked_mul(k)) != Some(weight.len() as u64)
|| mult.len() as u64 != cout || bias.len() as u64 != cout || model.shift > 127
{
return Err(refused(ModelMismatch, "a layer's parameters do not match the running channel count"));
}
let full = (t - 1).checked_mul(s).and_then(|v| v.checked_add(k)).and_then(|v| v.checked_add(op));
let full = full.filter(|&n| n <= CAP).ok_or(refused(TooLarge, "a layer's uncropped length exceeds 2^26"))?;
if cout.checked_mul(full).is_none_or(|n| n > CAP) {
return Err(refused(TooLarge, "a layer would form a buffer over 2^26 elements"));
}
let crop = p.checked_mul(2).unwrap_or(u64::MAX);
if full <= crop {
return Err(refused(ModelMismatch, "a layer's padding leaves no output"));
}
live = live.max(c * t + cout * full + cout * (full - crop));
macs = macs.saturating_add((c * t) as u128 * (cout * k) as u128);
(c, t) = (cout, full - crop);
}
ALayer::Igdn { c: cc, gamma, beta } => {
if *cc as u64 != c || c.checked_mul(c) != Some(gamma.len() as u64) || beta.len() as u64 != c
|| model.gf > 127 || model.afrac > 127
{
return Err(refused(ModelMismatch, "an IGDN layer does not match the running channel count"));
}
live = live.max(2 * c * t + 2 * c);
macs = macs.saturating_add((c * t) as u128 * c as u128);
}
}
}
// Every running size is at most 2^26 here, so it fits usize on every target.
Ok((c as usize, t as usize, live, macs))
}
/// What [`decode_typed`] checks before it decodes a symbol, and the buffers it then
/// forms. Returns the synthesis's length, the most bytes the decode holds at once
/// on the reference kernels beyond the payload and the parsed model (the latent and
/// each layer's buffers, `i64` each, then the synthesis's output, `i64`, with the
/// PCM, `i16`), and the synthesis's multiply-accumulates on the latent.
///
/// The latent's length is the one `t_out` needs and no longer: the synthesis of
/// `lat_len` latent samples gives at least `t_out` samples (`GeometryMismatch`
/// otherwise), and that of `lat_len − 1` gives fewer (`GeometryMismatch`), so a
/// payload cannot declare a large latent to present a short clip. A length for which
/// the model leaves no output gives none.
pub(crate) fn audio_plan(model: &AudioModel, s: &AudioStream<'_>) -> Result<(usize, u64, u128), Refused> {
use RefusalKind::*;
let afrac = model.afrac;
if !(1..=MAX_AFRAC).contains(&afrac) {
return Err(refused(ModelMismatch, "the model's fixed-point scale is outside 1..=30"));
}
if s.afrac != afrac {
return Err(refused(ModelMismatch, "the stream's afrac is not the model's"));
}
let (c, l) = (s.n_ch, s.lat_len);
if c == 0 || l == 0 || s.t_out == 0 || s.median_q.len() != c || s.cdfs.len() != c {
return Err(refused(FieldRange, "a dimension is zero, or the medians or tables are not one per channel"));
}
if s.median_q.iter().any(|m| m.unsigned_abs() > 1 << 47) {
return Err(refused(FieldRange, "a median's magnitude exceeds 2^47"));
}
let len = s.cdfs[0].len();
let ok = |t: &Vec<i32>| t.len() == len && (3..=4097).contains(&len) && t[0] == 0 && t[len - 1] == 1 << 16
&& t.windows(2).all(|p| p[0] < p[1]);
if !s.cdfs.iter().all(ok) {
return Err(refused(CdfMalformed, "a table is not a CDF from 0 to 65536 of the channels' one length"));
}
if c.checked_mul(l).is_none_or(|n| n > MAX_TENSOR) || s.t_out > MAX_TENSOR {
return Err(refused(TooLarge, "the latent or the clip exceeds 2^26 elements"));
}
let (oc, t, live, macs) = plan(model, c, l)?;
if oc != 1 {
return Err(refused(ModelMismatch, "the model does not give one channel"));
}
if t < s.t_out {
return Err(refused(GeometryMismatch, "the synthesis gives fewer samples than t_out"));
}
let shorter = if l > 1 { plan(model, c, l - 1).map_or(0, |(_, t, ..)| t) } else { 0 };
if shorter >= s.t_out {
return Err(refused(GeometryMismatch, "the latent is longer than t_out needs: one sample fewer gives t_out samples"));
}
let working = (8 * live).max(8 * t as u64 + 2 * s.t_out as u64);
Ok((t, working, macs))
}
/// One layer's arithmetic on the values `x` holds, checked before it runs, as
/// [`crate::int_hyper_synth::run_net_checked`] checks a 2-D layer: every product and
/// partial sum and the requantised value of a convolution fit `i64`; IGDN's terms,
/// norm and root fit their types.
fn layer_in_range(model: &AudioModel, layer: &ALayer, x: &[i64], c: usize) -> Result<(), Refused> {
use crate::int_kernels::max_abs;
const I64: u128 = i64::MAX as u128;
match layer {
ALayer::ConvT1d { k, weight, mult, bias, .. } => {
if model.shift > 127 {
return Err(refused(RefusalKind::ModelMismatch, "requantisation shift of 128 or more"));
}
let terms = (c as u128).saturating_mul(*k as u128);
let acc = terms.saturating_mul(max_abs(weight) as u128).saturating_mul(max_abs(x) as u128);
if acc > I64 {
return Err(refused(RefusalKind::FieldRange, "a latent could take an accumulation outside i64"));
}
let q = ((acc * max_abs(mult) as u128) >> model.shift) + 1;
if q.saturating_add(max_abs(bias) as u128) > I64 {
return Err(refused(RefusalKind::FieldRange, "a latent could take a requantised value outside i64"));
}
Ok(())
}
ALayer::Igdn { gamma, beta, .. } => crate::int_hyper_synth::igdn_check(x, c, gamma, beta, model.gf, model.afrac)
.map_err(|(why, values)| refused(if values { RefusalKind::FieldRange } else { RefusalKind::ModelMismatch }, why)),
}
}
/// Decode a typed clip → int16 PCM, mono, byte-exact on every machine, refusing
/// rather than panicking or overflowing on any input:
///
/// 1. the stream's `afrac` is the model's, in `1..=30`; every table is well formed;
/// the model's layers fit the latent's shape, give one channel, and give at least
/// `t_out` samples from a latent no longer than that needs, with no buffer over
/// [`MAX_TENSOR`] elements — all before anything is allocated ([`audio_plan`]);
/// 2. per channel, rANS-decode the symbol `s`; the latent is `s·2^afrac + median_q`
/// ([`crate::int_entropy::dequantize`]), and a stream that runs out before its last
/// symbol is refused;
/// 3. integer synthesis `g_s`, each layer's arithmetic checked before it runs;
/// 4. the first `t_out` samples, `(x·32767 + 2^(afrac−1)) >> afrac` clamped to `i16`.
pub fn decode_typed(model: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> {
use crate::int_entropy::dequantize;
use RefusalKind::*;
audio_plan(model, s)?;
let (afrac, c, l, len) = (model.afrac, s.n_ch, s.lat_len, s.cdfs[0].len());
// 1. factorized rANS-decode the per-channel latent (channel-major), rebuild y_q.
// The latent grows a symbol at a time, and the first symbol that runs the stream
// out refuses the payload before its value is used.
let mut y = Vec::new();
let mut dec = RansDecoder::new(s.rans).map_err(|_| refused(FieldRange, "the latent stream is not a rANS stream"))?;
for ch in 0..c {
for _ in 0..l {
let sym = dec.decode(&s.cdfs[ch], len, s.offset);
if dec.is_exhausted() {
return Err(refused(StreamExhausted, "the latent stream ended before its last symbol"));
}
crate::int_hyper_synth::push_capped(&mut y, dequantize(sym, afrac, s.median_q[ch]).ok_or(refused(FieldRange, "a latent value leaves i64"))?, c * l);
}
}
if !dec.is_canonical_end() {
return Err(refused(StreamNoncanonical, "the latent stream does not end canonically after its last symbol"));
}
// 2. integer synthesis → waveform, each layer checked on the values it is given.
let (mut x, mut cc, mut tt) = (y, c, l);
for layer in &model.layers {
layer_in_range(model, layer, &x, cc)?;
(x, cc, tt) = run_layer(model, layer, x, cc, tt);
}
// 3. waveform → int16 PCM (round-half-up, clamp): the first t_out samples.
let half = 1i64 << (afrac - 1);
let mut pcm = Vec::with_capacity(s.t_out);
for &v in &x[..s.t_out] {
let num = v.checked_mul(32767).and_then(|n| n.checked_add(half))
.ok_or(refused(FieldRange, "a synthesised sample leaves i64 when scaled"))?;
pcm.push((num >> afrac).clamp(-32768, 32767) as i16);
}
Ok(pcm)
}
/// One layer of the synthesis on `x` (`[c, t]`): the new activations and shape.
fn run_layer(model: &AudioModel, layer: &ALayer, x: Vec<i64>, c: usize, t: usize) -> (Vec<i64>, usize, usize) {
match layer {
ALayer::ConvT1d { out_ch, k, stride, pad, opad, weight, mult, bias } => {
let (nx, to) = conv_t_1d(&x, c, t, weight, *out_ch, *k, *stride, *pad, *opad, mult, bias, model.shift);
(nx, *out_ch, to)
}
ALayer::Igdn { gamma, beta, .. } => (igdn(&x, c, t, model.afrac, gamma, beta, model.gf), c, t),
}
}
/// Decode the tool's JSON bitstream (`audio_bitstream.json`) → int16 PCM:
/// [`AudioJson`] then [`decode_typed`]. A debugging and converter path; the payload
/// is `WIA1`. Byte-identical on every machine.
pub fn decode(model: &AudioModel, bs: &Value) -> Result<Vec<i16>, &'static str> {
decode_typed(model, &AudioJson::from_value(bs)?.stream()).map_err(|r| r.detail)
}
/// FNV-1a 64-bit over the little-endian int16 PCM bytes — the dependency-free
/// checksum the export tool and this decoder share to prove byte-for-byte agreement
/// across languages (sample-equivalence).
pub fn pcm_fnv1a(pcm: &[i16]) -> u64 {
let mut hash: u64 = 14695981039346656037;
for &s in pcm {
for &b in &s.to_le_bytes() {
hash = (hash ^ b as u64).wrapping_mul(1099511628211);
}
}
hash
}
/// Dependency-free base64 decode (standard alphabet, no padding required) — the
/// rANS bytes ride in JSON as base64, same as `int_video`.
fn b64_decode(s: &str) -> Result<Vec<u8>, &'static str> {
fn val(c: u8) -> Result<u8, &'static str> {
Ok(match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
_ => return Err("bad base64 char"),
})
}
let s: Vec<u8> = s.bytes().filter(|&c| c != b'=' && !c.is_ascii_whitespace()).collect();
let mut out = Vec::with_capacity(s.len() * 3 / 4);
for chunk in s.chunks(4) {
let mut acc = 0u32;
let mut bits = 0;
for &c in chunk {
acc = (acc << 6) | val(c)? as u32;
bits += 6;
}
// each full byte sits in the high `bits-8k` positions
for k in 0..bits / 8 {
out.push((acc >> (bits - 8 * (k + 1))) as u8);
}
}
Ok(out)
}
/// The fast 1-D transposed conv against the scalar reference, element by element.
#[cfg(test)]
mod plan_tests {
use super::*;
/// The committed synthesis's planned multiply-accumulates on the committed latent are
/// its layers' terms at the lengths the reference kernel gives (run here on a zero
/// latent): a transposed convolution's every input times `out·k`, an inverse GDN's
/// every element times `c`.
#[test]
fn the_planned_macs_are_every_layers_terms() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_audio");
let model = AudioModel::from_bytes(&std::fs::read(dir.join("audio_model.bin")).unwrap()).unwrap();
let wia1 = std::fs::read(dir.join("audio_bitstream.wia1")).unwrap();
let a = crate::int_wire::parse_wia1(&wia1).unwrap();
let (c0, l) = (a.n_ch as usize, a.lat_len as usize);
let (.., macs) = plan(&model, c0, l).unwrap();
let (mut c, mut t, mut terms) = (c0, l, 0u128);
for layer in &model.layers {
match layer {
ALayer::ConvT1d { out_ch, k, stride, pad, opad, weight, mult, bias } => {
let (_, to) = reference::conv_t_1d(&vec![0; c * t], c, t, weight, *out_ch, *k, *stride, *pad, *opad, mult, bias, model.shift);
terms += (c * t) as u128 * (out_ch * k) as u128;
(c, t) = (*out_ch, to);
}
ALayer::Igdn { .. } => terms += (c * t) as u128 * c as u128,
}
}
assert!(terms > 0);
assert_eq!(macs, terms);
}
}
#[cfg(test)]
mod kernel_equivalence {
use super::*;
use crate::int_kernels::testkit::{Rng, FAST};
use crate::int_kernels::with_policy;
#[test]
fn conv_t_1d_equals_reference() {
let mut g = Rng(0xA0D_0001);
let mut ran = 0;
for case in 0..500 {
let k = 1 + g.below(9);
let stride = 1 + g.below(5);
let pad = g.below(k);
let opad = if stride > 1 { g.below(stride) } else { 0 };
let t = 1 + g.below(40);
let (cin, cout) = (1 + g.below(6), 1 + g.below(6));
if (t - 1) * stride + k + opad <= 2 * pad {
continue;
}
let (xr, wr) = [(1 << 15, 2047), (1 << 40, 1 << 10)][case % 2];
let x = g.vec(cin * t, xr, 30);
let wt = g.vec(cin * cout * k, wr, 5);
let mult: Vec<i64> = (0..cout).map(|_| 1 + g.signed(1 << 16).abs()).collect();
let bias = g.vec(cout, 1 << 16, 0);
let want = reference::conv_t_1d(&x, cin, t, &wt, cout, k, stride, pad, opad, &mult, &bias, 24);
for p in FAST {
let got = with_policy(p, || conv_t_1d(&x, cin, t, &wt, cout, k, stride, pad, opad, &mult, &bias, 24));
assert_eq!(got, want, "conv_t_1d {p:?} k={k} s={stride} p={pad} op={opad} t={t}");
}
ran += 1;
}
assert!(ran > 250, "too few valid cases ({ran})");
}
/// Overflow only at a position the reference accumulates into and then crops:
/// a length-1 input, kernel 3, pad 1 keeps only the middle of the reference's
/// 3-sample raw buffer (`2·2^40·1 = 2^41`); both outer samples are
/// `2·2^40·2^22 = 2^63` and overflow `i64`. The fast path must panic where the
/// reference does and otherwise give the same 2^41.
#[test]
fn overflow_only_at_a_cropped_border_behaves_like_reference() {
use crate::int_kernels::testkit::overflow_checks;
use crate::int_kernels::Policy;
let (cin, cout, k) = (2usize, 1usize, 3usize);
let x = vec![1i64 << 40; cin];
let wt: Vec<i64> = (0..cin * cout).flat_map(|_| [1i64 << 22, 1, 1 << 22]).collect();
let (mult, bias) = (vec![1i64; cout], vec![0i64; cout]);
let run = |p: Policy| {
std::panic::catch_unwind(|| with_policy(p, || conv_t_1d(&x, cin, 1, &wt, cout, k, 1, 1, 0, &mult, &bias, 0))).ok()
};
let want = run(Policy::reference());
if overflow_checks() {
assert!(want.is_none(), "the reference overflows at a cropped sample in a checked build");
} else {
assert_eq!(want, Some((vec![1i64 << 41], 1)));
}
for p in FAST {
assert_eq!(run(p), want, "fast must match the reference, panic included ({p:?})");
}
}
/// A one-sample input upsampled by any stride is one output sample (k = 3,
/// pad 1): a handful of operations for the reference. The fast path must return
/// the same sample without overflowing, or allocating or looping per stride step.
#[test]
fn a_one_sample_input_upsampled_by_any_stride() {
use crate::int_kernels::Policy;
let (x, wt) = ([5i64], [1i64, 2, 3]);
// usize::MAX first: a kernel allocating a table per phase fails there at once
// ("capacity overflow") instead of committing memory for 2^29 tables later.
for s in [usize::MAX, 1, 2, 1 << 13, 1 << 16, 1 << 29] {
let run = |p: Policy| with_policy(p, || conv_t_1d(&x, 1, 1, &wt, 1, 3, s, 1, 0, &[1], &[0], 0));
let want = run(Policy::reference());
assert_eq!(want, (vec![10], 1), "stride {s}: the middle of 5·[1, 2, 3]");
for p in FAST {
assert_eq!(run(p), want, "stride {s} {p:?}");
}
}
}
/// Several input samples upsampled by a stride and cropped by padding so large
/// that the reference's uncropped buffer `cout · (to + 2·pad)` cannot be sized:
/// three samples, kernel 1, stride = pad = 2^62, one output sample. With two
/// output channels the size overflows `usize` (the reference panics with
/// overflow checks on, and with them off indexes out of the wrapped buffer);
/// with one it is 2^63 + 1 samples, more than any allocation can hold ("capacity
/// overflow"). The reference panics in every build, so every policy must, and
/// the fast kernel must not be entered.
#[cfg(target_pointer_width = "64")]
#[test]
fn several_samples_whose_uncropped_buffer_cannot_be_sized() {
use crate::int_kernels::testkit::fast_conv_runs;
use crate::int_kernels::Policy;
let s = 1usize << 62;
for cout in [2usize, 1] {
let (x, wt) = ([1i64, 2, 3], vec![7i64; cout]);
let (mult, bias) = (vec![1i64; cout], vec![0i64; cout]);
let run = |p: Policy| {
fast_conv_runs(|| std::panic::catch_unwind(|| with_policy(p, || conv_t_1d(&x, 1, 3, &wt, cout, 1, s, s, 0, &mult, &bias, 0))).ok())
};
assert_eq!(run(Policy::reference()), (None, 0), "{cout} channels: the reference panics");
for p in FAST {
assert_eq!(run(p), (None, 0), "{cout} channels {p:?}");
}
}
}
/// Geometry whose padded input would be more than `PAD_FACTOR` (16) times what
/// the reference reads and writes is refused by the fast path, and the reference
/// runs: the fast kernel is not entered and the samples are the reference's. At
/// exactly the factor the fast kernel runs. A one-sample input, kernel 1, stride
/// 1, cropped by `pad` with output padding `2·pad` restoring one output sample
/// that no tap reaches: the padded input is `cin · (1 + pad)` samples against
/// `cin + cin + 1` the reference touches (input, weights, output).
#[test]
fn padding_beyond_the_pad_factor_runs_the_reference() {
use crate::int_kernels::testkit::fast_conv_runs;
// (channels, pad, expected fast-kernel runs): 1 · 48 = 16 · 3 and
// 2 · 40 = 16 · 5 at the factor; 1 · 49 and 2 · 41 beyond it.
for (cin, pad, runs) in [(1usize, 47usize, 1u64), (1, 48, 0), (2, 39, 1), (2, 40, 0)] {
let (x, wt) = (vec![5i64; cin], vec![3i64; cin]);
let want = reference::conv_t_1d(&x, cin, 1, &wt, 1, 1, 1, pad, 2 * pad, &[3], &[7], 1);
assert_eq!(want, (vec![7], 1), "reference, {cin} channels, pad {pad}");
for p in FAST {
let got = fast_conv_runs(|| with_policy(p, || conv_t_1d(&x, cin, 1, &wt, 1, 1, 1, pad, 2 * pad, &[3], &[7], 1)));
assert_eq!(got, (want.clone(), runs), "{cin} channels, pad {pad} {p:?}");
}
}
}
}