wai-ops: wai.audio.int_codec decodes through the integer operator set, goldens unchanged (D2c, M3) #264

Merged
dcharlot merged 27 commits from wai/d2c-m3 into main 2026-10-15 09:05:49 -04:00
29 changed files with 2657 additions and 298 deletions

View file

@ -14,7 +14,10 @@ name: wai cross-architecture byte-equality
# (`wai.video.int_hyper`) whose I-frame comes out of a learned conditional codec, # (`wai.video.int_hyper`) whose I-frame comes out of a learned conditional codec,
# and the MLIC++-topology codec (`wai.neural.int_mlicpp`), whose entropy network runs # and the MLIC++-topology codec (`wai.neural.int_mlicpp`), whose entropy network runs
# before every checkerboard pass and must give every platform the encoder's scale # before every checkerboard pass and must give every platform the encoder's scale
# bucket for every element. The binary payloads SPEC §5 pins (WIV1, WIH1) are parsed # bucket for every element. The learned integer audio and Gaussian-splat attribute codecs
# (`wai.audio.int_codec`, `wai.splat.int_codec`) decode their committed WIA1 and WIS1
# payloads to their PCM and attribute digests. The binary payloads SPEC §5 pins (WIV1,
# WIH1, WIA1, WIS1) are parsed
# with the wire parser a sink runs and decoded to the same digests, and the staged-delivery # with the wire parser a sink runs and decoded to the same digests, and the staged-delivery
# residual layers (WIR1) of the staged corpus are applied stage by stage to pinned digests. # residual layers (WIR1) of the staged corpus are applied stage by stage to pinned digests.
# Video is the strictest leg — inter-frame decode is a # Video is the strictest leg — inter-frame decode is a
@ -61,6 +64,8 @@ on:
- 'wai/wai-rs/src/int_transform.rs' - 'wai/wai-rs/src/int_transform.rs'
- 'wai/wai-rs/src/int_hyper_synth.rs' - 'wai/wai-rs/src/int_hyper_synth.rs'
- 'wai/wai-rs/src/int_mlicpp.rs' - 'wai/wai-rs/src/int_mlicpp.rs'
- 'wai/wai-rs/src/int_audio.rs'
- 'wai/wai-rs/src/int_splat.rs'
- 'wai/wai-rs/src/int_payload/wire.rs' - 'wai/wai-rs/src/int_payload/wire.rs'
- 'wai/wai-rs/src/staged/predict.rs' - 'wai/wai-rs/src/staged/predict.rs'
- 'wai/wai-rs/src/staged/wir1.rs' - 'wai/wai-rs/src/staged/wir1.rs'
@ -76,6 +81,8 @@ on:
- 'wai/wai-rs/tests/vectors/int_video/**' - 'wai/wai-rs/tests/vectors/int_video/**'
- 'wai/wai-rs/tests/vectors/int_hyper/**' - 'wai/wai-rs/tests/vectors/int_hyper/**'
- 'wai/wai-rs/tests/vectors/int_mlicpp/**' - 'wai/wai-rs/tests/vectors/int_mlicpp/**'
- 'wai/wai-rs/tests/vectors/int_audio/**'
- 'wai/wai-rs/tests/vectors/int_splat/**'
- '.github/workflows/wai-byte-equality.yml' - '.github/workflows/wai-byte-equality.yml'
push: push:
branches: branches:
@ -94,6 +101,8 @@ on:
- 'wai/wai-rs/src/int_transform.rs' - 'wai/wai-rs/src/int_transform.rs'
- 'wai/wai-rs/src/int_hyper_synth.rs' - 'wai/wai-rs/src/int_hyper_synth.rs'
- 'wai/wai-rs/src/int_mlicpp.rs' - 'wai/wai-rs/src/int_mlicpp.rs'
- 'wai/wai-rs/src/int_audio.rs'
- 'wai/wai-rs/src/int_splat.rs'
- 'wai/wai-rs/src/int_payload/wire.rs' - 'wai/wai-rs/src/int_payload/wire.rs'
- 'wai/wai-rs/src/staged/predict.rs' - 'wai/wai-rs/src/staged/predict.rs'
- 'wai/wai-rs/src/staged/wir1.rs' - 'wai/wai-rs/src/staged/wir1.rs'
@ -109,6 +118,8 @@ on:
- 'wai/wai-rs/tests/vectors/int_video/**' - 'wai/wai-rs/tests/vectors/int_video/**'
- 'wai/wai-rs/tests/vectors/int_hyper/**' - 'wai/wai-rs/tests/vectors/int_hyper/**'
- 'wai/wai-rs/tests/vectors/int_mlicpp/**' - 'wai/wai-rs/tests/vectors/int_mlicpp/**'
- 'wai/wai-rs/tests/vectors/int_audio/**'
- 'wai/wai-rs/tests/vectors/int_splat/**'
- '.github/workflows/wai-byte-equality.yml' - '.github/workflows/wai-byte-equality.yml'
jobs: jobs:

View file

@ -249,7 +249,11 @@ jobs:
# test-only) decodes random sets and payloads both ways: the same output or both # test-only) decodes random sets and payloads both ways: the same output or both
# refused, except on the planned breaks of integer-operators §15.3, where the decode # refused, except on the planned breaks of integer-operators §15.3, where the decode
# through the operator set refuses. wai-ops-differential.yml runs it for an hour in # through the operator set refuses. wai-ops-differential.yml runs it for an hour in
# each profile, nightly. # each profile, nightly. tests/int_splat_ops.rs holds wai.splat.int_codec to its
# lowering whm1/1 the same way (integer-operators §13.2): its committed set lowers,
# from its bytes and from the model this crate parses, to its committed graph, and
# loads with its certificate's verdict. tests/int_audio_ops.rs does the same for
# wai.audio.int_codec and its lowering wam1/1 (§13.3).
# tests/int_mlicpp.rs decodes the committed wai.neural.int_mlicpp vector against # tests/int_mlicpp.rs decodes the committed wai.neural.int_mlicpp vector against
# its pinned hashes (image, every scale bucket, every symbol) under every kernel # its pinned hashes (image, every scale bucket, every symbol) under every kernel
# policy and thread count, and checks that damaged payloads are refused rather than # policy and thread count, and checks that damaged payloads are refused rather than
@ -307,10 +311,10 @@ jobs:
# codestream. No step named any of them until scripts/check-tests-in-ci.py asked. # codestream. No step named any of them until scripts/check-tests-in-ci.py asked.
- name: Fast integer kernels ≡ reference kernels (overflow checks on) - name: Fast integer kernels ≡ reference kernels (overflow checks on)
working-directory: wai/wai-rs working-directory: wai/wai-rs
run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration
- name: Fast integer kernels ≡ reference kernels (release, overflow checks off) - name: Fast integer kernels ≡ reference kernels (release, overflow checks off)
working-directory: wai/wai-rs working-directory: wai/wai-rs
run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test manifest_parse_memory --test manifest_reader_equivalence --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test manifest_parse_memory --test manifest_reader_equivalence --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration
# The integer decode path builds under each of its features alone, so a sink can take # The integer decode path builds under each of its features alone, so a sink can take
# one medium without the others, and for wasm32 (a browser sink); `int_payload` arms a # one medium without the others, and for wasm32 (a browser sink); `int_payload` arms a
# medium only under its feature, and none needs `neural`. So does the staged-measurement # medium only under its feature, and none needs `neural`. So does the staged-measurement

View file

@ -6,7 +6,8 @@ name: wai-ops differential (nightly)
# build profile: with overflow checks on and with them off. Outside the planned breaks # build profile: with overflow checks on and with them off. Outside the planned breaks
# of integer-operators §15.3 the two decode to the same bytes or both refuse, and the # of integer-operators §15.3 the two decode to the same bytes or both refuse, and the
# direct decode never panics; under a planned break the decode through the operator set # direct decode never panics; under a planned break the decode through the operator set
# refuses (wai/wai-rs/tests/int_ops_differential.rs). Today: wai.neural.int_synth. # refuses (wai/wai-rs/tests/int_ops_differential.rs). Today: wai.neural.int_synth,
# wai.splat.int_codec and wai.audio.int_codec.
# #
# Each night starts from its own seed, the date, which the log prints; a failing case # Each night starts from its own seed, the date, which the log prints; a failing case
# names its seed, and `WAI_DIFF_SEED=<seed>` re-runs it. The same tests run for a fixed # names its seed, and `WAI_DIFF_SEED=<seed>` re-runs it. The same tests run for a fixed
@ -60,4 +61,4 @@ jobs:
if [ "${{ matrix.profile }}" = release ]; then P="--release"; fi if [ "${{ matrix.profile }}" = release ]; then P="--release"; fi
export WAI_DIFF_SEED="$(date -u +%Y%m%d)" export WAI_DIFF_SEED="$(date -u +%Y%m%d)"
echo "WAI_DIFF_SECS=$WAI_DIFF_SECS WAI_DIFF_SEED=$WAI_DIFF_SEED" echo "WAI_DIFF_SECS=$WAI_DIFF_SECS WAI_DIFF_SEED=$WAI_DIFF_SEED"
cargo test $P --locked --no-default-features --features legacy_int --test int_ops_differential -- --nocapture cargo test $P --locked --no-default-features --features legacy_int,splat_int,audio_int --test int_ops_differential -- --nocapture

View file

@ -6,6 +6,73 @@ wire format may change and such changes are recorded here.
## v0.4.0 — current ## v0.4.0 — current
### `wai.audio.int_codec` through the integer operator set
- **The decode.** `int_audio::decode_typed` (so the `WIA1` dispatch, the staged sink and
the browser crate) plans the synthesis as
[integer-payloads](extensions/integer-payloads.md) §7 orders, lowers `model.wam` by
`wam1/1` ([integer-operators](extensions/integer-operators.md) §13.3, with its PCM
step), loads the graph under `acc64` with its bounds certificate, decodes the stream,
and runs the graph through the operator set's interpreter with `t_out` as its scalar
and the fast transposed-convolution kernel behind it. Its `check` ranges are the
checks the direct decode made before each layer, and the PCM step's range is the one
in which a sample's scaling fits `i64`, so the same clips are refused
(`field_range`). Every committed output is unchanged, byte for byte, and every
committed refusal fixture keeps its code. The direct decode is kept, test-only
(feature `legacy_int`), in the differential tests and their nightly run.
- **Planned breaks** (SPEC §8, integer-operators §15.3 item 3), as for
`wai.splat.int_codec`: new refusals of sets no committed corpus contains, each
`model_mismatch` before any stream is decoded, the PCM step's range among the `check`
ranges whose producer must meet them. A scan of every committed audio set and payload
finds none affected.
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.audio.int_codec` the member
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "wam1/1",
"stage": {"rans64": {"table": "t4097", "escape": "lenient", "end": "canonical"}}}`.
SPEC §5's row cites the payload ([integer-payloads](extensions/integer-payloads.md) §5)
and the lowering (integer-operators §13.3), and SPEC §7 names the capability among
those that decode through the operator set.
### `wai.splat.int_codec` through the integer operator set
- **The decode.** `int_splat::decode_typed` (so the `WIS1` dispatch, the staged sink and
the browser crate) plans the networks as
[integer-payloads](extensions/integer-payloads.md) §7 orders, lowers `g_s.whm` by
`whm1/1` ([integer-operators](extensions/integer-operators.md) §13.2, with the RGB8
step of §13.0), loads the graph under `acc64` with its bounds certificate, decodes the
stream, and runs the graph through the operator set's interpreter with the fast
transposed-convolution kernel behind it. Its `check` ranges are the checks the direct
decode made before each layer, so the same latents are refused (`field_range`).
Every committed output is unchanged, byte for byte, and every committed refusal
fixture keeps its code. The direct decode is kept, test-only (feature
`legacy_int`): `tests/int_ops_differential.rs` decodes random sets and clouds both
ways, every push and for an hour per build profile each night.
- **Planned breaks** (SPEC §8, integer-operators §15.3 item 3): new refusals of sets no
committed corpus contains, each `model_mismatch` before any stream is decoded. A layer
with a bias of magnitude `2^63 − 1` or more, or a `check` range its producer cannot
meet, refused every decode with `field_range` when the network ran; a network with no
layer of tag 0, 1 or 2, or with a header `shift` or `gf` of 128 or more that no layer
uses, decoded where the shapes allowed. A scan of every committed splat set and
payload finds none affected.
- **The interpreter's memory.** A run releases each tensor once its last reader has
run, takes its inputs by value, and holds a transposed convolution's uncropped plane
in `i64` (each cell is within the certified `convT.acc`). With its set loaded, a splat
decode through the operator set holds no more than the payload's declared working
bytes (integer-payloads §7, *Memory and work*); the declared cost of a `WIS1` payload
is unchanged. `int_payload::declared_cost` now refuses, with the decode's code, a set
whose lowering does not load.
- **The byte-equality matrix** (`wai-byte-equality.yml`, `byte-exact-conformance`) now
decodes the committed `WIA1` and `WIS1` payloads of `wai.audio.int_codec` and
`wai.splat.int_codec` on every leg, under the reference and the fast kernels, to their
pinned PCM and attribute digests; it compiled neither decoder before.
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.splat.int_codec` the member
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "whm1/1",
"stage": {"rans64": {"table": "t4097", "escape": "lenient", "end": "canonical"}}}`.
SPEC §5's row cites the payload ([integer-payloads](extensions/integer-payloads.md) §6)
and the lowering (integer-operators §13.2), and SPEC §7 names the capability among
those that decode through the operator set.
### wai-quantum 0.5.0: provenance, sampling members, device programs, and three more members ### wai-quantum 0.5.0: provenance, sampling members, device programs, and three more members
Breaking: `Certificate` gains a public field, `provenance`, and `FabricClass` is now Breaking: `Certificate` gains a public field, `provenance`, and `FabricClass` is now

View file

@ -811,12 +811,12 @@ standard. See the linked extension for each.
| `wai.splat.spz` | SPZ (an open-source Gaussian-splat format) | asset, content-addressed; presentation only | | `wai.splat.spz` | SPZ (an open-source Gaussian-splat format) | asset, content-addressed; presentation only |
| `wai.audio.scene` | WSCN scene (objects + trajectories) | mixdown-equivalence · [spatial-audio](extensions/spatial-audio.md) | | `wai.audio.scene` | WSCN scene (objects + trajectories) | mixdown-equivalence · [spatial-audio](extensions/spatial-audio.md) |
| `wai.audio.score` | WSCR container (note events) | music as instructions; sink synthesizes, mixdown-equivalence · [score](extensions/score.md) | | `wai.audio.score` | WSCR container (note events) | music as instructions; sink synthesizes, mixdown-equivalence · [score](extensions/score.md) |
| `wai.audio.int_codec` | `WIA1` — a LEARNED 1-D transform codec: integer transposed-conv synthesis + integer IGDN over a factorized-rANS latent (`tools/wai_int_audio_export.py` writes the model and a JSON form a converter reads) | **sample-equivalence**: int16 PCM byte-identical on every machine (a portable PCM hash); the audio analog of `wai.neural.int_hyper`, where a float learned-audio decode desyncs on ~1e-5 round-off. Pure i64, no float/ONNX (`int_audio.rs`) | | `wai.audio.int_codec` | `WIA1` — a LEARNED 1-D transform codec: integer transposed-conv synthesis + integer IGDN over a factorized-rANS latent (`tools/wai_int_audio_export.py` writes the model and a JSON form a converter reads) | **sample-equivalence**: int16 PCM byte-identical on every machine (a portable PCM hash); the audio analog of `wai.neural.int_hyper`, where a float learned-audio decode desyncs on ~1e-5 round-off. Pure i64, no float/ONNX (`int_audio.rs`). Payload: [integer-payloads](extensions/integer-payloads.md) §5; decoded through its lowering, [integer-operators](extensions/integer-operators.md) §13.3 (`wam1/1`) |
| `wai.haptic.signal` | WHAP envelope (intensity/sharpness keyframes) | sample-equivalence · [haptics](extensions/haptics.md) | | `wai.haptic.signal` | WHAP envelope (intensity/sharpness keyframes) | sample-equivalence · [haptics](extensions/haptics.md) |
| `wai.feed.state` | WWLD, sim `wai.world.sim.feed0` | replay-equivalence · [state-feeds](extensions/state-feeds.md) | | `wai.feed.state` | WWLD, sim `wai.world.sim.feed0` | replay-equivalence · [state-feeds](extensions/state-feeds.md) |
| `wai.avatar.pose` | WWLD, sim `wai.world.sim.skeletal0` | pose-equivalence · [avatars](extensions/avatars.md) | | `wai.avatar.pose` | WWLD, sim `wai.world.sim.skeletal0` | pose-equivalence · [avatars](extensions/avatars.md) |
| `wai.splat.4d` | W4DS container (per-Gaussian keyframed tracks) | frame-state-equivalence · [volumetric](extensions/volumetric.md) | | `wai.splat.4d` | W4DS container (per-Gaussian keyframed tracks) | frame-state-equivalence · [volumetric](extensions/volumetric.md) |
| `wai.splat.int_codec` | `WIS1` — a LEARNED Gaussian-splat ATTRIBUTE codec: a learned transform over the `[14,S,S]` attribute image (pos/scale/rot/color/opacity), integer synthesis (int_hyper machinery, 14 channels) over a factorized-rANS latent (`int_splat.rs`; `tools/wai_int_splat_export.py` writes the model and a JSON form a converter reads) | **splat-equivalence**: the decoded attribute tensor is byte-identical on every machine (a portable cloud hash) — the int_hyper of 3D, where a float splat-attribute decode desyncs. The float rasterizer stays sink-supplied presentation; the *attributes* are the contract. Pure i64, no float/ONNX | | `wai.splat.int_codec` | `WIS1` — a LEARNED Gaussian-splat ATTRIBUTE codec: a learned transform over the `[14,S,S]` attribute image (pos/scale/rot/color/opacity), integer synthesis (int_hyper machinery, 14 channels) over a factorized-rANS latent (`int_splat.rs`; `tools/wai_int_splat_export.py` writes the model and a JSON form a converter reads) | **splat-equivalence**: the decoded attribute tensor is byte-identical on every machine (a portable cloud hash) — the int_hyper of 3D, where a float splat-attribute decode desyncs. The float rasterizer stays sink-supplied presentation; the *attributes* are the contract. Pure i64, no float/ONNX. Payload: [integer-payloads](extensions/integer-payloads.md) §6; decoded through its lowering, [integer-operators](extensions/integer-operators.md) §13.2 (`whm1/1`) |
| `wai.film.linear` | WFLM container (sealed world + camera track + frame schedule) | film-equivalence · [linear-film](extensions/linear-film.md) | | `wai.film.linear` | WFLM container (sealed world + camera track + frame schedule) | film-equivalence · [linear-film](extensions/linear-film.md) |
| `wai.film.reel` | WREL container (sealed world + a sequence of shots = camera track + schedule each) | reel-equivalence (film-equivalence with cuts) · [linear-film](extensions/linear-film.md) | | `wai.film.reel` | WREL container (sealed world + a sequence of shots = camera track + schedule each) | reel-equivalence (film-equivalence with cuts) · [linear-film](extensions/linear-film.md) |
| `wai.ternary.tt` | WQTT container (balanced-ternary MPS cores — packed trits + integer per-column scales) | a **quantum-inspired** (tensor-network) transform ported from the ternary/MPS fabric with ONE change: the per-column scale is an **integer**, so the whole decode is `i64`/`i128` and the reconstruction is byte-identical on every machine — **reconstruction-equivalence** (a portable recon hash), receiptable. Scope: a **provenance** transform, measured at 13.3 dB SNR for 3.8× with six residual layers (`Lossy` tier; the extension's §5). Encoder-side float SVD only · [ternary-tt](extensions/ternary-tt.md) | | `wai.ternary.tt` | WQTT container (balanced-ternary MPS cores — packed trits + integer per-column scales) | a **quantum-inspired** (tensor-network) transform ported from the ternary/MPS fabric with ONE change: the per-column scale is an **integer**, so the whole decode is `i64`/`i128` and the reconstruction is byte-identical on every machine — **reconstruction-equivalence** (a portable recon hash), receiptable. Scope: a **provenance** transform, measured at 13.3 dB SNR for 3.8× with six residual layers (`Lossy` tier; the extension's §5). Encoder-side float SVD only · [ternary-tt](extensions/ternary-tt.md) |
@ -1512,7 +1512,8 @@ registry as `codecs::capability_replicate_class(cap) -> ReplicateClass`:
([integer-operators](extensions/integer-operators.md) §11): the lowering its sets ([integer-operators](extensions/integer-operators.md) §11): the lowering its sets
are lowered by, the profile every conforming set's certificate meets, and the modes are lowered by, the profile every conforming set's certificate meets, and the modes
of its stage operators. A set whose certificate does not meet that profile is of its stage operators. A set whose certificate does not meet that profile is
refused. In this revision: `wai.neural.int_synth`. refused. In this revision: `wai.neural.int_synth`, `wai.splat.int_codec` and
`wai.audio.int_codec`.
- **`NeuralEntropyExact`** — the entropy decode is integer by construction, so - **`NeuralEntropyExact`** — the entropy decode is integer by construction, so
the decoded symbols are byte-identical on every machine; later stages run float. the decoded symbols are byte-identical on every machine; later stages run float.
Registered at `entropy-consistency`. An encoder MUST NOT emit `intent = Registered at `entropy-consistency`. An encoder MUST NOT emit `intent =
@ -2399,6 +2400,14 @@ envelope that uses any string a later revision registers.
convolution's uncropped output, past `2^26` elements); and a set whose certificate convolution's uncropped output, past `2^26` elements); and a set whose certificate
does not meet `acc64`. A shift from 64 to 127, which a build used to mask or panic does not meet `acc64`. A shift from 64 to 127, which a build used to mask or panic
on, now has its exact meaning. on, now has its exact meaning.
- `wai.splat.int_codec`: a set refused at load by its lowering, before any stream is
decoded (`model_mismatch`): a layer with a bias of magnitude `2^63 − 1` or more, or
a `check` range its producer cannot meet, each of which refused every decode
(`field_range`) when the network ran; a network with no layer of tag 0, 1 or 2; and
a header `shift` or `gf` of 128 or more that no layer uses.
- `wai.audio.int_codec`: the same, for its synthesis, whose `check` ranges include
its PCM step's: a set whose last layer cannot give a sample inside that range,
which refused every decode at that step, is refused at load.
--- ---

View file

@ -32,3 +32,9 @@ wai-ops = { path = "../wai-ops" }
# as a sink does, through the integer operator set; the crate's own tests compile the # as a sink does, through the integer operator set; the crate's own tests compile the
# direct decode's unit tests under `cfg(test)`. # direct decode's unit tests under `cfg(test)`.
legacy_int = [] legacy_int = []
# wai-rs's features for the splat and audio decoders (`int_splat`, `int_audio`), which
# this crate always includes: on by default, so the included sources' tests that need
# them run here too.
splat_int = []
audio_int = []
default = ["splat_int", "audio_int"]

View file

@ -0,0 +1,143 @@
//! Cross-architecture byte-equality for the learned integer audio and Gaussian-splat
//! attribute codecs (`wai.audio.int_codec`, `wai.splat.int_codec`;
//! `extensions/integer-payloads.md` §5 and §6).
//!
//! The committed `WIA1` and `WIS1` payloads are parsed with the wire parser a sink runs
//! (`int_payload/wire.rs`) and decoded through the checked typed decoders, under the
//! scalar reference kernels and under the fast kernels a sink runs by default; the JSON
//! forms they are converted from decode to the same bytes. Each output must be the
//! digest pinned below, which is also the one its vector's `sha256.json` sidecar states
//! (and, for the PCM, the FNV-1a the export's Python integer reference computed), and the
//! payloads themselves are pinned by their SHA-256. One differing sample or attribute on
//! any leg fails the matrix.
#![cfg(test)]
use std::path::PathBuf;
use serde_json::Value;
use sha2::{Digest, Sha256};
use crate::int_audio::{decode_typed as decode_audio, pcm_fnv1a, AudioJson, AudioModel, AudioStream};
use crate::int_hyper_synth::IntModel;
use crate::int_kernels::{with_policy, Policy};
use crate::int_payload_wire::{parse_wia1, parse_wis1};
use crate::int_splat::{decode_typed as decode_splat, SplatJson, SplatStream};
const AUDIO_WIA1_SHA256: &str = "e4343c9468f6c7b652f911e24e273223ef14feaa7014df014f2d99e9f28e1b3b";
const SPLAT_WIS1_SHA256: &str = "473ea0223840758b26ff7d540d861e3b2c1565dbc2bec68b0235b3f50f19d422";
/// The PCM, as signed 16-bit little-endian samples.
const AUDIO_PCM_SHA256: &str = "4b0344c87c67fc30b7d977ae2077fc310822199598e5d2f9f8fc06314b3a2875";
const AUDIO_PCM_FNV1A: u64 = 0xe5ff_66ad_1b86_117c;
/// The attribute tensor's bytes, plane after plane.
const SPLAT_ATTR_SHA256: &str = "f07679de87ff7c3d4fc4890b80919ef857fb85bd4d9e4cfa16105211d580427f";
fn vectors() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors")
}
fn read(rel: &str) -> Vec<u8> {
let p = vectors().join(rel);
std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}
fn jload(rel: &str) -> Value {
serde_json::from_slice(&read(rel)).unwrap_or_else(|e| panic!("parse {rel}: {e}"))
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn sha256(b: &[u8]) -> String {
hex(&Sha256::digest(b))
}
/// `f`, under the scalar reference kernels and under the default (fast) kernels: the two
/// must agree, and the reference's result is returned.
fn both_kernels<T: PartialEq + std::fmt::Debug>(what: &str, f: impl Fn() -> T) -> T {
let reference = with_policy(Policy::reference(), &f);
let fast = f();
assert_eq!(fast, reference, "{what}: the fast kernels and the reference kernels differ");
reference
}
/// `WIA1`: the committed payload, parsed and decoded, gives the pinned PCM; so does the
/// JSON bitstream it is converted from.
#[test]
fn audio_wia1_decodes_to_its_pcm_golden() {
let payload = read("int_audio/audio_bitstream.wia1");
assert_eq!(sha256(&payload), AUDIO_WIA1_SHA256, "the committed WIA1 payload");
let model = AudioModel::from_bytes(&read("int_audio/audio_model.bin")).expect("the WAM1 synthesis parses");
let a = parse_wia1(&payload).expect("the WIA1 payload parses");
let s = AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
};
let pcm = both_kernels("WIA1", || decode_audio(&model, &s).expect("the WIA1 payload decodes"));
let s16le: Vec<u8> = pcm.iter().flat_map(|v| v.to_le_bytes()).collect();
assert_eq!(sha256(&s16le), AUDIO_PCM_SHA256, "the PCM diverged");
assert_eq!(pcm_fnv1a(&pcm), AUDIO_PCM_FNV1A, "the PCM's FNV-1a diverged");
assert_eq!(jload("int_audio/sha256.json")["audio"]["sha256"].as_str(), Some(AUDIO_PCM_SHA256), "the sidecar");
assert_eq!(jload("int_audio/audio.json")["pcm_fnv1a"].as_str(), Some(format!("{AUDIO_PCM_FNV1A:016x}").as_str()));
let j = AudioJson::from_value(&jload("int_audio/audio_bitstream.json")).expect("the JSON form reads");
let from_json = both_kernels("the audio JSON form", || decode_audio(&model, &j.stream()).expect("the JSON form decodes"));
assert_eq!(from_json, pcm, "the JSON form and the payload decode alike");
}
/// `WIS1`: the committed payload, parsed and decoded, gives the pinned attribute tensor;
/// so does the JSON bitstream it is converted from.
#[test]
fn splat_wis1_decodes_to_its_attribute_golden() {
let payload = read("int_splat/splat.wis1");
assert_eq!(sha256(&payload), SPLAT_WIS1_SHA256, "the committed WIS1 payload");
let gs = IntModel::from_bytes(&read("int_splat/splat_gs.bin")).expect("the WHM1 synthesis parses");
let v = parse_wis1(&payload).expect("the WIS1 payload parses");
let s = SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
};
let attr = both_kernels("WIS1", || decode_splat(&gs, &s).expect("the WIS1 payload decodes").attr);
assert_eq!(attr.len(), v.attr as usize * v.s as usize * v.s as usize, "the attribute tensor's size");
assert_eq!(sha256(&attr), SPLAT_ATTR_SHA256, "the attribute tensor diverged");
assert_eq!(jload("int_splat/sha256.json")["splat"]["sha256"].as_str(), Some(SPLAT_ATTR_SHA256), "the sidecar");
let bs = jload("int_splat/splat.json");
let j = SplatJson::from_value(&bs).expect("the JSON form reads");
let from_json = both_kernels("the splat JSON form", || decode_splat(&gs, &j.stream()).expect("the JSON form decodes").attr);
assert_eq!(from_json, attr, "the JSON form and the payload decode alike");
let fnv = crate::int_hyper_synth::fnv1a(&attr);
assert_eq!(Some(format!("{fnv:016x}").as_str()), bs["attr_hash"].as_str(), "the export's attribute hash");
}
/// `WIA1` through the synthesis's lowering `wam1/1` (`extensions/integer-operators.md`
/// §13.3, with the PCM step): loaded under `acc64` and run by the integer operator set's
/// interpreter, with the fast kernels behind it, it gives the same pinned PCM on every
/// leg.
#[test]
fn audio_through_its_lowering_is_golden() {
let payload = read("int_audio/audio_bitstream.wia1");
let model = AudioModel::from_bytes(&read("int_audio/audio_model.bin")).expect("the WAM1 synthesis parses");
let a = parse_wia1(&payload).expect("the WIA1 payload parses");
let s = AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
};
let pcm = both_kernels("WIA1 through the lowering", || crate::int_audio::ops::decode_typed(&model, &s).expect("decodes through the lowering"));
let s16le: Vec<u8> = pcm.iter().flat_map(|v| v.to_le_bytes()).collect();
assert_eq!(sha256(&s16le), AUDIO_PCM_SHA256, "the PCM through the lowering diverged");
assert_eq!(pcm_fnv1a(&pcm), AUDIO_PCM_FNV1A);
}
/// `WIS1` through `g_s`'s lowering `whm1/1` (`extensions/integer-operators.md` §13.2):
/// loaded under `acc64` and run by the integer operator set's interpreter, with the fast
/// kernels behind it, it gives the same pinned attribute tensor on every leg.
#[test]
fn splat_through_its_lowering_is_golden() {
let payload = read("int_splat/splat.wis1");
let gs = IntModel::from_bytes(&read("int_splat/splat_gs.bin")).expect("the WHM1 synthesis parses");
let v = parse_wis1(&payload).expect("the WIS1 payload parses");
let s = SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
};
let attr = both_kernels("WIS1 through the lowering", || crate::int_splat::ops::decode_typed(&gs, &s).expect("decodes through the lowering").attr);
assert_eq!(sha256(&attr), SPLAT_ATTR_SHA256, "the attribute tensor through the lowering diverged");
}

View file

@ -75,11 +75,22 @@ pub mod int_hyper_synth;
#[path = "../../wai-rs/src/int_mlicpp.rs"] #[path = "../../wai-rs/src/int_mlicpp.rs"]
pub mod int_mlicpp; pub mod int_mlicpp;
// The learned integer audio and Gaussian-splat attribute codecs (`wai.audio.int_codec`,
// `wai.splat.int_codec`): one-dimensional and two-dimensional integer transposed
// convolutions and inverse GDN, over the networks, kernels and rANS decoder already
// included here, so the matrix checks their PCM and attribute bytes on every platform.
#[path = "../../wai-rs/src/int_audio.rs"]
pub mod int_audio;
#[path = "../../wai-rs/src/int_splat.rs"]
pub mod int_splat;
// The wire parser of the integer payloads SPEC §5 pins (WIV1, WIH1, WIA1, WIS1). // The wire parser of the integer payloads SPEC §5 pins (WIV1, WIH1, WIA1, WIS1).
// Self-contained — std's Vec only, nothing from the crate — so the matrix parses the // Self-contained — std's Vec only, nothing from the crate — so the matrix parses the
// committed binary payloads with the parser a sink runs. // committed binary payloads with the parser a sink runs.
#[path = "../../wai-rs/src/int_payload/wire.rs"] #[path = "../../wai-rs/src/int_payload/wire.rs"]
pub mod int_payload_wire; pub mod int_payload_wire;
// The name wai-rs gives the same module (`int_wire`), which int_audio's own tests use.
pub use int_payload_wire as int_wire;
// The staged-delivery residual (`WIR1`, extensions/staged-delivery.md Appendix A): its // The staged-delivery residual (`WIR1`, extensions/staged-delivery.md Appendix A): its
// parser, predictors and arithmetic step. Self-contained beside the rANS coder and the // parser, predictors and arithmetic step. Self-contained beside the rANS coder and the
@ -93,4 +104,5 @@ mod int_video_golden;
mod neural_keyframe_golden; mod neural_keyframe_golden;
mod jpegai_golden; mod jpegai_golden;
mod int_mlicpp_golden; mod int_mlicpp_golden;
mod int_codec_golden;
mod staged_golden; mod staged_golden;

View file

@ -14,7 +14,7 @@
> >
> **What a reader can build from this text.** With the payload extensions it cites, an implementation of this text decodes `wai.video.int_motion`, `wai.audio.int_codec`, `wai.splat.int_codec`, `wai.neural.int_synth`, `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.neural.int_mlicpp` and `wai.{image,video,audio,splat}.int_refine`, and reproduces the symbols and scale indices that `wai.image.jpegai` entropy-decodes (§13.6.1). The payload text four of these depend on, `entropy.json`, the `int_synth` payload and the `WIM1` payload with its `WMB1` bundle, is in [integer-payloads](integer-payloads.md), and §15.2 says where. The integer renderer of `wai.world.model` and the other integer networks of `wai.image.jpegai` are informative (§13.5, §13.6.2). > **What a reader can build from this text.** With the payload extensions it cites, an implementation of this text decodes `wai.video.int_motion`, `wai.audio.int_codec`, `wai.splat.int_codec`, `wai.neural.int_synth`, `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.neural.int_mlicpp` and `wai.{image,video,audio,splat}.int_refine`, and reproduces the symbols and scale indices that `wai.image.jpegai` entropy-decodes (§13.6.1). The payload text four of these depend on, `entropy.json`, the `int_synth` payload and the `WIM1` payload with its `WMB1` bundle, is in [integer-payloads](integer-payloads.md), and §15.2 says where. The integer renderer of `wai.world.model` and the other integer networks of `wai.image.jpegai` are informative (§13.5, §13.6.2).
> >
> Reference implementation: the `wai-ops` crate (`wai/wai-ops`): parse, lowering, interpreter, stage operators and bounds. Tool: `wai-verify bounds`. Conformance: `wai/ops-conformance/`. A capability's reference decoder decodes through this operator set from the revision that registers the capability's `ops` member (§11); in this revision, `wai.neural.int_synth`. Until then, a capability's reference implementation is the decoder its payload extension names, and §15.3 lists where this text and that decoder differ. > Reference implementation: the `wai-ops` crate (`wai/wai-ops`): parse, lowering, interpreter, stage operators and bounds. Tool: `wai-verify bounds`. Conformance: `wai/ops-conformance/`. A capability's reference decoder decodes through this operator set from the revision that registers the capability's `ops` member (§11); in this revision, `wai.neural.int_synth`, `wai.splat.int_codec` and `wai.audio.int_codec`. Until then, a capability's reference implementation is the decoder its payload extension names, and §15.3 lists where this text and that decoder differ.
> >
> The keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are as defined in RFC 2119 and RFC 8174. > The keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are as defined in RFC 2119 and RFC 8174.
@ -1528,7 +1528,7 @@ No committed output changes, and no committed refusal fixture changes its code.
- A shift from 64 to 127 now has its exact meaning. - A shift from 64 to 127 now has its exact meaning.
- The caps apply. - The caps apply.
2. **`wai.world.model`'s integer renderer** refuses values outside its lowered `check` ranges, and a set whose `afrac` is outside `1..=30` or differs between its two networks. 2. **`wai.world.model`'s integer renderer** refuses values outside its lowered `check` ranges, and a set whose `afrac` is outside `1..=30` or differs between its two networks.
3. **`WHM1` and `WAM1` layers.** A layer whose predicate fails at 0 (a bias of magnitude `2^63 − 1` or more) is refused at load, not at decode. So is a set in which a `check` range cannot hold any value its producer can give (§8.1), which today refuses every decode with `field_range`. 3. **`WHM1` and `WAM1` layers.** A layer whose predicate fails at 0 (a bias of magnitude `2^63 − 1` or more) is refused at load, not at decode. So is a set in which a `check` range cannot hold any value its producer can give (§8.1), which today refuses every decode with `field_range`. Each is refused with `model_mismatch`, before any stream is decoded, where the direct decoder refused with `field_range`, or with a stream's code when the stream failed first. A network with no layer of tag 0, 1 or 2, and one whose header `shift` or `gf` is 128 or more where no layer uses it, are refused too (§13.2); the direct decoder decoded each where the shapes allowed. These take effect for `wai.splat.int_codec` and `wai.audio.int_codec` in this revision; for `wai.audio.int_codec` the `check` ranges include the PCM step's (§13.3), so a set whose last layer cannot give a sample inside it is refused at load, where every decode was refused at that step.
4. **`wai.image.jpegai`.** 4. **`wai.image.jpegai`.**
- Its exact decoder refuses a malformed latent or hyper-latent stream (`stream_exhausted`) where it panics today. - Its exact decoder refuses a malformed latent or hyper-latent stream (`stream_exhausted`) where it panics today.
- It refuses a bundle that fails §13.6.1's load checks (`model_mismatch`), including a bound above 255 or a shift outside `0..=127`, which today's loader wraps. - It refuses a bundle that fails §13.6.1's load checks (`model_mismatch`), including a bound above 255 or a shift outside `0..=127`, which today's loader wraps.

View file

@ -83,6 +83,9 @@ escape of 4-bit groups):
hashes. A stream that codes no symbol (the residual stream of a one-frame `WIV1` hashes. A stream that codes no symbol (the residual stream of a one-frame `WIV1`
with keyframe 1) is the state `2^31` alone: the words `2^31` and `0`. with keyframe 1) is the state `2^31` alone: the words `2^31` and `0`.
The decode these rules define is [integer-operators](integer-operators.md) E1,
`rans64_decode`, with the modes `t4097`, `lenient` and `canonical`.
**One clip, several payloads.** These rules fix how a payload is read, not the **One clip, several payloads.** These rules fix how a payload is read, not the
only payload of a decoded output: tables that differ in entries the stream never only payload of a decoded output: tables that differ in entries the stream never
selects (a symbol the clip does not use, say), or a different escape layout, code selects (a symbol the clip does not use, say), or a different escape layout, code
@ -239,9 +242,13 @@ The header's field rules are checked in the order `afrac`, then `sr`, `n_ch`,
1. For each channel `k` and each of its `lat_len` elements, decode a symbol `s` 1. For each channel `k` and each of its `lat_len` elements, decode a symbol `s`
against channel `k`'s table at `offset`; the element is `s·2^afrac + median_k`. against channel `k`'s table at `offset`; the element is `s·2^afrac + median_k`.
2. The synthesis (one-dimensional integer transposed convolutions and integer 2. The synthesis (one-dimensional integer transposed convolutions and integer
inverse GDN) on `[n_ch, lat_len]` gives one channel of `t ≥ t_out` samples. inverse GDN) on `[n_ch, lat_len]` gives one channel of `t ≥ t_out` samples. It
decodes through its lowering `wam1/1` ([integer-operators](integer-operators.md)
§13.3), with `t_out` as its scalar.
3. The first `t_out` samples `v` become `(v·32767 + 2^(afrac−1)) >> afrac`, clamped 3. The first `t_out` samples `v` become `(v·32767 + 2^(afrac−1)) >> afrac`, clamped
to `[−32768, 32767]`. to `[−32768, 32767]`: the PCM step that ends the lowered graph (integer-operators
§13.3). A sample for which `v·32767 + 2^(afrac−1)` leaves `i64` is refused
(`field_range`).
The output is `t_out` samples of `i16` PCM, one channel, at `sr` hertz: the buffer of The output is `t_out` samples of `i16` PCM, one channel, at `sr` hertz: the buffer of
the capability's sample-equivalence digest (signed 16-bit little-endian samples). the capability's sample-equivalence digest (signed 16-bit little-endian samples).
@ -275,8 +282,11 @@ The header's field rules are checked in the order `afrac`, the dimensions,
1. For each channel `k` and each of its `sh·sw` elements, decode a symbol `s` 1. For each channel `k` and each of its `sh·sw` elements, decode a symbol `s`
against channel `k`'s table and offset; the element is `s·2^afrac + median_k`. against channel `k`'s table and offset; the element is `s·2^afrac + median_k`.
2. `g_s` on `[c, sh, sw]` gives `[attr, s, s]` (`geometry_mismatch` otherwise). 2. `g_s` on `[c, sh, sw]` gives `[attr, s, s]` (`geometry_mismatch` otherwise).
`g_s` decodes through its lowering `whm1/1`
([integer-operators](integer-operators.md) §13.2).
3. Each value `v` is clamped to `[0, 2^afrac]` and becomes 3. Each value `v` is clamped to `[0, 2^afrac]` and becomes
`(v·255 + 2^(afrac−1)) >> afrac`, a byte. `(v·255 + 2^(afrac−1)) >> afrac`, a byte: the RGB8 step that ends the lowered
graph (integer-operators §13.0).
The output, plane after plane, is the buffer of the capability's splat-equivalence The output, plane after plane, is the buffer of the capability's splat-equivalence
digest. The first `n_gaussians` cells of the planes, in raster order, hold one digest. The first `n_gaussians` cells of the planes, in raster order, hold one
@ -361,7 +371,10 @@ caps; a sink's policy may be stricter.
hyper-latent channels than the tables; hyper-latent channels than the tables;
- each layer's parameters have the lengths the running channel count needs, and its - each layer's parameters have the lengths the running channel count needs, and its
geometry leaves an output (*Planning the networks*); geometry leaves an output (*Planning the networks*);
- the audio synthesis gives one channel; the conditional codec's `g_s` gives three. - the audio synthesis gives one channel; the conditional codec's `g_s` gives three;
- a network that decodes through its lowering lowers, and its graph loads under
`acc64` ([integer-operators](integer-operators.md) §13.2 and §13.3: for `WIS1`,
`g_s`; for `WIA1`, the synthesis).
**The shapes the payload declares must be the ones the decode gives** **The shapes the payload declares must be the ones the decode gives**
(`geometry_mismatch`): `h_s`'s output and `[2·m, sh, sw]`; a `WIV1` keyframe and (`geometry_mismatch`): `h_s`'s output and `[2·m, sh, sw]`; a `WIV1` keyframe and
@ -384,8 +397,9 @@ order and refuses with the first that fails:
with keyframe 1, its keyframe's as for `WIH1`, then the keyframe's size against with keyframe 1, its keyframe's as for `WIH1`, then the keyframe's size against
`h × w` (`geometry_mismatch`); for `WIA1`, `h × w` (`geometry_mismatch`); for `WIA1`,
one channel (`model_mismatch`), then at least `t_out` samples, then a latent no one channel (`model_mismatch`), then at least `t_out` samples, then a latent no
longer than that needs (both `geometry_mismatch`); for `WIS1`, `g_s`'s shape longer than that needs (both `geometry_mismatch`), then the synthesis lowered and
(`geometry_mismatch`); loaded (`model_mismatch`); for `WIS1`, `g_s`'s shape
(`geometry_mismatch`), then `g_s` lowered and loaded (`model_mismatch`);
5. each stream in decode order (`WIH1`: the hyper-latent's, then, after `h_s` runs, 5. each stream in decode order (`WIH1`: the hyper-latent's, then, after `h_s` runs,
the latent's; `WIV1` with keyframe 1: the keyframe's streams, then the residual the latent's; `WIV1` with keyframe 1: the keyframe's streams, then the residual
stream), symbol by symbol: a stream), symbol by symbol: a
@ -403,7 +417,11 @@ network layer runs, the decode checks on the values present that no product, par
sum, requantised value or inverse-GDN term can leave the type its arithmetic is sum, requantised value or inverse-GDN term can leave the type its arithmetic is
formed in. A stream whose values fail either check is refused (`field_range`). So a formed in. A stream whose values fail either check is refused (`field_range`). So a
decode with overflow checks and one without compute the same integers, and neither decode with overflow checks and one without compute the same integers, and neither
panics. panics. A network that decodes through its lowering
([integer-operators](integer-operators.md) §13; for `WIS1`, §13.2; for `WIA1`, §13.3)
makes the second check as the `check` ranges of its graph: a value outside one is
refused (`field_range`), and the refusals are the same. So does `WIA1`'s PCM step
(§5 step 3), whose range is its graph's last `check` range.
## 8. Refusal codes ## 8. Refusal codes
@ -581,8 +599,8 @@ was made.
`wai_int_payload_vectors gen` and checked by `wai_int_payload_vectors verify`; the `wai_int_payload_vectors gen` and checked by `wai_int_payload_vectors verify`; the
same corpus checked by `int_payload_verify.py`, which shares no code with wai-rs and same corpus checked by `int_payload_verify.py`, which shares no code with wai-rs and
decodes the `WIV1` keyframe-0 payloads in full; and the cross-architecture goldens of decodes the `WIV1` keyframe-0 payloads in full; and the cross-architecture goldens of
`byte-exact-conformance`, which parse and decode the `WIV1` and `WIH1` payloads on `byte-exact-conformance`, which parse and decode the `WIV1`, `WIH1`, `WIA1` and `WIS1`
every platform of its matrix. payloads on every platform of its matrix.
## 13. The `wai.neural.int_synth` payload and set ## 13. The `wai.neural.int_synth` payload and set

View file

@ -75,8 +75,7 @@ compared:
**Not decoded independently.** The Python verifier does not decode `WIH1`, `WIV1` **Not decoded independently.** The Python verifier does not decode `WIH1`, `WIV1`
with keyframe 1 (its keyframe is a `WIH1`), `WIA1` or `WIS1`: their decodes run with keyframe 1 (its keyframe is a `WIH1`), `WIA1` or `WIS1`: their decodes run
integer networks it does not implement. Their outputs are checked by wai-rs against integer networks it does not implement. Their outputs are checked by wai-rs against
the sidecars above, and the `WIV1` and `WIH1` payloads also by the the sidecars above, and also by the cross-architecture goldens in
cross-architecture goldens in
[`../byte-exact-conformance/`](../byte-exact-conformance/). For their `decode` [`../byte-exact-conformance/`](../byte-exact-conformance/). For their `decode`
fixtures the Python verifier checks that they parse; their codes are checked by fixtures the Python verifier checks that they parse; their codes are checked by
wai-rs. wai-rs.

View file

@ -99,7 +99,20 @@
"sets": [], "sets": [],
"spec_section": "§5 Interactive, temporal & computed content", "spec_section": "§5 Interactive, temporal & computed content",
"edition": null, "edition": null,
"registered": "2026-06-27" "registered": "2026-06-27",
"ops": {
"set": "wai-ops/1",
"profile": "acc64",
"subset": "full",
"lowering": "wam1/1",
"stage": {
"rans64": {
"table": "t4097",
"escape": "lenient",
"end": "canonical"
}
}
}
}, },
{ {
"capability": "wai.audio.int_refine", "capability": "wai.audio.int_refine",
@ -1011,7 +1024,20 @@
"sets": [], "sets": [],
"spec_section": "§5 Interactive, temporal & computed content", "spec_section": "§5 Interactive, temporal & computed content",
"edition": null, "edition": null,
"registered": "2026-06-28" "registered": "2026-06-28",
"ops": {
"set": "wai-ops/1",
"profile": "acc64",
"subset": "full",
"lowering": "whm1/1",
"stage": {
"rans64": {
"table": "t4097",
"escape": "lenient",
"end": "canonical"
}
}
}
}, },
{ {
"capability": "wai.splat.int_refine", "capability": "wai.splat.int_refine",

View file

@ -297,7 +297,8 @@ def build_codec(C):
# ---- integer reference decode (numpy; mirrors the Rust i64 arithmetic) ------- # ---- integer reference decode (numpy; mirrors the Rust i64 arithmetic) -------
def conv_t_1d_int(xi, wi, mult, bias, stride, pad, opad): def conv_t_1d_int(xi, wi, mult, bias, stride, pad, opad):
"""Integer 1-D transposed conv + per-out-channel (mult>>SHIFT)+bias — byte-for- """Integer 1-D transposed conv + per-out-channel (mult>>SHIFT)+bias — byte-for-
byte what Rust int_audio::conv_t_1d does. xi [cin,T]; wi [cin,cout,k].""" byte what the Rust decoder's transposed convolution does (integer-operators §13.3,
wam1/1). xi [cin,T]; wi [cin,cout,k]."""
import numpy as np import numpy as np
cin, cout, k = wi.shape; Tin = xi.shape[1] cin, cout, k = wi.shape; Tin = xi.shape[1]
To = (Tin - 1) * stride + k + opad - 2 * pad To = (Tin - 1) * stride + k + opad - 2 * pad

View file

@ -1,7 +1,9 @@
//! The reference interpreter of the graph operators (§5). //! The reference interpreter of the graph operators (§5).
//! //!
//! Tensors hold `i64`. Every intermediate is formed in `i128`, which holds every //! Tensors hold `i64`. Every intermediate is formed in `i128`, which holds every
//! value a graph certified for `acc64` forms (§7.1), and every step is still checked: //! value a graph certified for `acc64` forms (§7.1), except `conv_transpose`'s uncropped
//! plane, whose cells are `acc`-class partial sums and so are held in `i64` (see
//! `k_conv_t`). Every step is still checked:
//! a graph whose arithmetic would leave `i128`, or whose producer result would leave //! a graph whose arithmetic would leave `i128`, or whose producer result would leave
//! `i64`, is one that load step 8 refuses, so [`run`], which takes only a loaded graph, //! `i64`, is one that load step 8 refuses, so [`run`], which takes only a loaded graph,
//! never meets one. Were it to, the decode would be refused (`model_mismatch`) at that //! never meets one. Were it to, the decode would be refused (`model_mismatch`) at that
@ -189,40 +191,46 @@ pub fn rq(rq: &Rq, o: usize, v: i128) -> Option<i128> {
/// from `seq` (§10.3). `scalars` holds every scalar's value. Returns the graph's /// from `seq` (§10.3). `scalars` holds every scalar's value. Returns the graph's
/// outputs, in output-table order. A refused decode leaves `seq` as it was. /// outputs, in output-table order. A refused decode leaves `seq` as it was.
pub fn run(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence) -> Res<Vec<Tensor>> { pub fn run(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence) -> Res<Vec<Tensor>> {
run_inner(g, inputs, scalars, seq, &mut Quiet, &Reference) run_inner(g, inputs.to_vec(), scalars, seq, &mut Quiet, &Reference)
} }
/// [`run`], with the host's kernels `k` asked first for each operator ([`Kernels`]). /// [`run`], with the host's kernels `k` asked first for each operator ([`Kernels`]),
/// The outputs and refusals are [`run`]'s, provided `k` keeps its contract. /// taking the data inputs by value, so that a decode holds no second copy of them. The
pub fn run_with<K: Kernels + ?Sized>(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence, k: &K) -> Res<Vec<Tensor>> { /// outputs and refusals are [`run`]'s, provided `k` keeps its contract.
pub fn run_with<K: Kernels + ?Sized>(g: &Loaded, inputs: Vec<Tensor>, scalars: &[i64], seq: &mut Sequence, k: &K) -> Res<Vec<Tensor>> {
run_inner(g, inputs, scalars, seq, &mut Quiet, k) run_inner(g, inputs, scalars, seq, &mut Quiet, k)
} }
/// [`run`], reporting every named intermediate and producer result to `obs` /// [`run`], reporting every named intermediate and producer result to `obs`
/// ([`Observe`]). The outputs and refusals are [`run`]'s. /// ([`Observe`]). The outputs and refusals are [`run`]'s.
pub fn run_observed<O: Observe>(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence, obs: &mut O) -> Res<Vec<Tensor>> { pub fn run_observed<O: Observe>(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence, obs: &mut O) -> Res<Vec<Tensor>> {
run_inner(g, inputs, scalars, seq, obs, &Reference) run_inner(g, inputs.to_vec(), scalars, seq, obs, &Reference)
} }
fn run_inner<O: Observe, K: Kernels + ?Sized>(g: &Loaded, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence, obs: &mut O, k: &K) -> Res<Vec<Tensor>> { /// One decode. A tensor is held from the operator that writes it (or the start, for an
/// input) until the last operator that reads it has run, and an output until the end:
/// the integers are the same whatever is held, and a decode then holds, beside its
/// outputs, only what the operator running needs.
fn run_inner<O: Observe, K: Kernels + ?Sized>(g: &Loaded, inputs: Vec<Tensor>, scalars: &[i64], seq: &mut Sequence, obs: &mut O, k: &K) -> Res<Vec<Tensor>> {
let g: &Graph = g.graph(); let g: &Graph = g.graph();
// The inputs, data and state, in input-table order. // The inputs, data and state, in input-table order.
let n_data = g.inputs.iter().filter(|i| i.kind == 0).count(); let n_data = g.inputs.iter().filter(|i| i.kind == 0).count();
if inputs.len() != n_data { if inputs.len() != n_data {
return Err(Refusal::new(Code::ModelMismatch, Site::Input(inputs.len().min(n_data) as u32))); return Err(Refusal::new(Code::ModelMismatch, Site::Input(inputs.len().min(n_data) as u32)));
} }
let first = inputs.first().map(|t| (t.h, t.w));
let mut data = inputs.into_iter();
let mut all: Vec<Tensor> = Vec::with_capacity(g.inputs.len()); let mut all: Vec<Tensor> = Vec::with_capacity(g.inputs.len());
let (mut d, mut s) = (0usize, 0usize); let mut s = 0usize;
for rec in &g.inputs { for rec in &g.inputs {
if rec.kind == 0 { if rec.kind == 0 {
all.push(inputs[d].clone()); all.push(data.next().expect("one data input per data record"));
d += 1;
} else { } else {
let t = match &seq.state { let t = match &seq.state {
Some(st) => st[s].clone(), Some(st) => st[s].clone(),
None => { None => {
let i0 = &inputs[0]; let (h0, w0) = first.expect("a state input's first decode takes the first data input's sizes");
Tensor::filled(g.tensors[rec.tensor as usize].channels, i0.h, i0.w, rec.init) Tensor::filled(g.tensors[rec.tensor as usize].channels, h0, w0, rec.init)
} }
}; };
all.push(t); all.push(t);
@ -238,6 +246,18 @@ fn run_inner<O: Observe, K: Kernels + ?Sized>(g: &Loaded, inputs: &[Tensor], sca
let hw = run_plan(g, &shapes, scalars)?; let hw = run_plan(g, &shapes, scalars)?;
let mut vals: Vec<Option<Tensor>> = vec![None; g.tensors.len()]; let mut vals: Vec<Option<Tensor>> = vec![None; g.tensors.len()];
// The last operator that reads each tensor, and the graph's outputs, which are held
// to the end.
let mut last: Vec<Option<usize>> = vec![None; g.tensors.len()];
for (i, op) in g.ops.iter().enumerate() {
for &t in &op.ins {
last[t as usize] = Some(i);
}
}
let mut output = vec![false; g.tensors.len()];
for &t in &g.outputs {
output[t as usize] = true;
}
// Step 4 begins with the inputs: each input's policy applies to it. // Step 4 begins with the inputs: each input's policy applies to it.
for (k, (rec, mut t)) in g.inputs.iter().zip(all).enumerate() { for (k, (rec, mut t)) in g.inputs.iter().zip(all).enumerate() {
let tr = &g.tensors[rec.tensor as usize]; let tr = &g.tensors[rec.tensor as usize];
@ -269,8 +289,20 @@ fn run_inner<O: Observe, K: Kernels + ?Sized>(g: &Loaded, inputs: &[Tensor], sca
} }
vals[t as usize] = Some(ten); vals[t as usize] = Some(ten);
} }
// Release what no later operator reads: this operator's inputs whose last reader
// it is, and any output of it that nothing reads.
for &t in op.ins.iter().chain(&op.outs) {
let t = t as usize;
if !output[t] && last[t].is_none_or(|l| l <= i) {
vals[t] = None;
}
}
}
let mut outputs: Vec<Tensor> = Vec::with_capacity(g.outputs.len());
for (k, &t) in g.outputs.iter().enumerate() {
let v = if g.outputs[k + 1..].contains(&t) { vals[t as usize].clone() } else { vals[t as usize].take() };
outputs.push(v.expect("every tensor is defined"));
} }
let outputs: Vec<Tensor> = g.outputs.iter().map(|&t| vals[t as usize].clone().expect("every tensor is defined")).collect();
let next: Vec<Tensor> = g.inputs.iter().filter(|i| i.kind == 1).map(|i| outputs[i.src as usize].clone()).collect(); let next: Vec<Tensor> = g.inputs.iter().filter(|i| i.kind == 1).map(|i| outputs[i.src as usize].clone()).collect();
seq.state = Some(next); seq.state = Some(next);
Ok(outputs) Ok(outputs)
@ -421,6 +453,12 @@ pub(crate) fn k_conv<O: Observe>(
/// `conv_transpose` (§5.3), in the scatter form: the same products as §5.3's gather, /// `conv_transpose` (§5.3), in the scatter form: the same products as §5.3's gather,
/// summed exactly. /// summed exactly.
///
/// The uncropped plane holds `i64`. Every cell, cropped or kept, is at every step a sum
/// of some of the taps of one residue (`i mod sh`, `j mod sw`), so it lies in that
/// residue's partial-sum interval, which §8.3's `convT.acc` covers; and a loaded graph
/// meets `acc64`, under which `convT.acc` and `convT.term` fit `i64` (§7.1). A term or a
/// cell that would leave `i64`, which no loaded graph forms, refuses (`model_mismatch`).
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn k_conv_t<O: Observe>( pub(crate) fn k_conv_t<O: Observe>(
x: &Tensor, cout: usize, kh: usize, kw: usize, sh: usize, sw: usize, ph: usize, pw: usize, oph: usize, opw: usize, rq: &Rq, wt: &[i64], x: &Tensor, cout: usize, kh: usize, kw: usize, sh: usize, sw: usize, ph: usize, pw: usize, oph: usize, opw: usize, rq: &Rq, wt: &[i64],
@ -429,7 +467,7 @@ pub(crate) fn k_conv_t<O: Observe>(
let (c, h, w) = (x.c as usize, x.h as usize, x.w as usize); let (c, h, w) = (x.c as usize, x.h as usize, x.w as usize);
let uh = (h - 1) * sh + kh + oph; let uh = (h - 1) * sh + kh + oph;
let uw = (w - 1) * sw + kw + opw; let uw = (w - 1) * sw + kw + opw;
let mut plane = vec![0i128; cout * uh * uw]; let mut plane = vec![0i64; cout * uh * uw];
for ci in 0..c { for ci in 0..c {
for u in 0..h { for u in 0..h {
for v in 0..w { for v in 0..w {
@ -444,8 +482,8 @@ pub(crate) fn k_conv_t<O: Observe>(
let wv = wt[((ci * cout + o) * kh + i) * kw + j] as i128; let wv = wt[((ci * cout + o) * kh + i) * kw + j] as i128;
obs.see(at, "convT.term", wv * xv); obs.see(at, "convT.term", wv * xv);
let cell = &mut plane[row + j]; let cell = &mut plane[row + j];
*cell = cell.checked_add(wv * xv).ok_or(wide(at))?; *cell = i64::try_from(wv * xv).ok().and_then(|t| cell.checked_add(t)).ok_or(wide(at))?;
obs.see(at, "convT.acc", *cell); obs.see(at, "convT.acc", *cell as i128);
} }
} }
} }
@ -456,7 +494,7 @@ pub(crate) fn k_conv_t<O: Observe>(
for o in 0..cout { for o in 0..cout {
for y in 0..oh { for y in 0..oh {
for xx in 0..ow { for xx in 0..ow {
let acc = plane[(o * uh + y + ph) * uw + xx + pw]; let acc = plane[(o * uh + y + ph) * uw + xx + pw] as i128;
d[(o * oh + y) * ow + xx] = to64(rq_apply(rq, o, acc, at, obs).ok_or(wide(at))?, at)?; d[(o * oh + y) * ow + xx] = to64(rq_apply(rq, o, acc, at, obs).ok_or(wide(at))?, at)?;
} }
} }

View file

@ -16,7 +16,7 @@ mod whm;
pub use jpegai::jpegai; pub use jpegai::jpegai;
pub use synth::int_synth; pub use synth::int_synth;
pub use whm::{Net, NetLayer, int_hyper, parse_wam1, parse_whm1, splat, wam1, whm1, wmb1, world}; pub use whm::{Net, NetLayer, int_hyper, parse_wam1, parse_whm1, splat, wam1, wam1_net, whm1, whm1_net, wmb1, world};
#[cfg(kani)] #[cfg(kani)]
pub(crate) use whm::{p_conv, p_igdn, p_leaky}; pub(crate) use whm::{p_conv, p_igdn, p_leaky};

View file

@ -312,7 +312,17 @@ fn net_graph(net: &Net, output_step: bool) -> Res<Graph> {
/// `whm1/1`: one `WHM1` network, with the RGB8 step when `output_step` is set. /// `whm1/1`: one `WHM1` network, with the RGB8 step when `output_step` is set.
pub fn whm1(bytes: &[u8], output_step: bool) -> Res<Graph> { pub fn whm1(bytes: &[u8], output_step: bool) -> Res<Graph> {
net_graph(&parse_whm1(bytes)?, output_step) whm1_net(&parse_whm1(bytes)?, output_step)
}
/// [`whm1`] of a network already parsed: a decoder that holds its network parsed lowers
/// it from that form, which is [`parse_whm1`] of its bytes. The graph is the one
/// [`whm1`] writes from those bytes.
pub fn whm1_net(net: &Net, output_step: bool) -> Res<Graph> {
if net.audio {
return refuse();
}
net_graph(net, output_step)
} }
/// `whm1/1` for `wai.neural.int_hyper` and the keyframe of `wai.video.int_hyper`: /// `whm1/1` for `wai.neural.int_hyper` and the keyframe of `wai.video.int_hyper`:
@ -328,14 +338,23 @@ pub fn splat(g_s: &[u8]) -> Res<Lowered> {
/// `wam1/1` (§13.3): the `WAM1` synthesis with the PCM step. /// `wam1/1` (§13.3): the `WAM1` synthesis with the PCM step.
pub fn wam1(bytes: &[u8]) -> Res<Graph> { pub fn wam1(bytes: &[u8]) -> Res<Graph> {
let net = parse_wam1(bytes)?; wam1_net(&parse_wam1(bytes)?)
header_ok(&net)?; }
let c0 = in_channels(&net)?;
/// [`wam1`] of a network already parsed: a decoder that holds its network parsed lowers
/// it from that form, which is [`parse_wam1`] of its bytes. The graph is the one [`wam1`]
/// writes from those bytes.
pub fn wam1_net(net: &Net) -> Res<Graph> {
if !net.audio {
return refuse();
}
header_ok(net)?;
let c0 = in_channels(net)?;
let mut b = Builder::new(); let mut b = Builder::new();
let first = net.layers.first().ok_or(()).or_else(|_| refuse())?; let first = net.layers.first().ok_or(()).or_else(|_| refuse())?;
let x0 = b.input(x_record(&net, first, c0, true)?, 0, 0); let x0 = b.input(x_record(net, first, c0, true)?, 0, 0);
let t_out = b.scalar(1, 1 << 26); let t_out = b.scalar(1, 1 << 26);
let x = layers(&mut b, &net, x0)?; let x = layers(&mut b, net, x0)?;
let c = b.channels(x); let c = b.channels(x);
// The range in which v·32767 + 2^(afrac−1) fits i64. // The range in which v·32767 + 2^(afrac−1) fits i64.
let lo = -((1i128 << 63) / 32767) as i64; // ⌈−2^63 / 32767⌉ let lo = -((1i128 << 63) / 32767) as i64; // ⌈−2^63 / 32767⌉

View file

@ -749,13 +749,23 @@ required-features = ["neural_int"]
[[test]] [[test]]
name = "int_ops_differential" name = "int_ops_differential"
path = "tests/int_ops_differential.rs" path = "tests/int_ops_differential.rs"
required-features = ["legacy_int"] required-features = ["legacy_int", "splat_int", "audio_int"]
[[test]] [[test]]
name = "int_synth_ops" name = "int_synth_ops"
path = "tests/int_synth_ops.rs" path = "tests/int_synth_ops.rs"
required-features = ["legacy_int"] required-features = ["legacy_int"]
[[test]]
name = "int_audio_ops"
path = "tests/int_audio_ops.rs"
required-features = ["audio_int", "legacy_int"]
[[test]]
name = "int_splat_ops"
path = "tests/int_splat_ops.rs"
required-features = ["splat_int", "legacy_int"]
[[test]] [[test]]
name = "int_mlicpp" name = "int_mlicpp"
path = "tests/int_mlicpp.rs" path = "tests/int_mlicpp.rs"

View file

@ -1353,8 +1353,12 @@ pub struct OpsMember {
/// networks through their lowering (`extensions/integer-operators.md` §15.1). /// networks through their lowering (`extensions/integer-operators.md` §15.1).
pub fn capability_ops(cap: &str) -> Option<OpsMember> { pub fn capability_ops(cap: &str) -> Option<OpsMember> {
let ops = |lowering, rans64, bucket| Some(OpsMember { profile: "acc64", subset: "full", lowering, rans64, bucket }); let ops = |lowering, rans64, bucket| Some(OpsMember { profile: "acc64", subset: "full", lowering, rans64, bucket });
// integer-payloads §2's stream: `rans64_decode` with `t4097`, `lenient` and `canonical`.
const PAYLOAD_STREAM: Option<(&str, &str, &str)> = Some(("t4097", "lenient", "canonical"));
match cap { match cap {
CAP_NEURAL_INT_SYNTH => ops("int_synth/1", None, None), CAP_NEURAL_INT_SYNTH => ops("int_synth/1", None, None),
CAP_SPLAT_INT_CODEC => ops("whm1/1", PAYLOAD_STREAM, None),
CAP_AUDIO_INT_CODEC => ops("wam1/1", PAYLOAD_STREAM, None),
_ => None, _ => None,
} }
} }

View file

@ -1420,13 +1420,14 @@ mod tests {
/// The `ops` members (`extensions/integer-operators.md` §11). Only a /// The `ops` members (`extensions/integer-operators.md` §11). Only a
/// `NeuralIntegerExact` capability carries one, every one is at `acc64` with the /// `NeuralIntegerExact` capability carries one, every one is at `acc64` with the
/// full subset and names a lowering of §13, and the export writes exactly these. /// full subset and names a lowering of §13, and the export writes exactly these.
/// In this revision `wai.neural.int_synth` is the one capability that decodes /// In this revision `wai.neural.int_synth`, `wai.splat.int_codec` and
/// through the operator set. /// `wai.audio.int_codec` decode through the operator set.
#[test] #[test]
fn ops_members_name_the_operator_set_a_capability_decodes_through() { fn ops_members_name_the_operator_set_a_capability_decodes_through() {
let regs = registrations(); let regs = registrations();
let with: Vec<&str> = regs.iter().filter(|r| r.ops.is_some()).map(|r| r.capability).collect(); let mut with: Vec<&str> = regs.iter().filter(|r| r.ops.is_some()).map(|r| r.capability).collect();
assert_eq!(with, [CAP_NEURAL_INT_SYNTH]); with.sort_unstable();
assert_eq!(with, [CAP_AUDIO_INT_CODEC, CAP_NEURAL_INT_SYNTH, CAP_SPLAT_INT_CODEC]);
for r in regs.iter().filter(|r| r.ops.is_some()) { for r in regs.iter().filter(|r| r.ops.is_some()) {
let o = r.ops.unwrap(); let o = r.ops.unwrap();
assert_eq!(r.replicate, Some(ReplicateClass::NeuralIntegerExact), "{}", r.capability); assert_eq!(r.replicate, Some(ReplicateClass::NeuralIntegerExact), "{}", r.capability);
@ -1437,6 +1438,14 @@ mod tests {
for c in export["capabilities"].as_array().unwrap() { for c in export["capabilities"].as_array().unwrap() {
let want = match c["capability"].as_str().unwrap() { let want = match c["capability"].as_str().unwrap() {
CAP_NEURAL_INT_SYNTH => serde_json::json!({ "set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "int_synth/1" }), CAP_NEURAL_INT_SYNTH => serde_json::json!({ "set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "int_synth/1" }),
CAP_SPLAT_INT_CODEC => serde_json::json!({
"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "whm1/1",
"stage": { "rans64": { "table": "t4097", "escape": "lenient", "end": "canonical" } }
}),
CAP_AUDIO_INT_CODEC => serde_json::json!({
"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "wam1/1",
"stage": { "rans64": { "table": "t4097", "escape": "lenient", "end": "canonical" } }
}),
_ => serde_json::Value::Null, _ => serde_json::Value::Null,
}; };
assert_eq!(c["ops"], want, "{}", c["capability"]); assert_eq!(c["ops"], want, "{}", c["capability"]);

View file

@ -16,7 +16,6 @@
use serde_json::Value; use serde_json::Value;
use crate::int_transform::igdn;
use crate::rans::RansDecoder; use crate::rans::RansDecoder;
/// One layer of the integer synthesis `g_s`. `ConvT1d` weight is `[cin·cout·k]` /// One layer of the integer synthesis `g_s`. `ConvT1d` weight is `[cin·cout·k]`
@ -112,27 +111,11 @@ impl AudioModel {
} }
} }
/// One integer 1-D transposed conv with per-output-channel requant — see /// The direct decode's one-dimensional transposed convolution, kept verbatim: the scalar
/// [`reference::conv_t_1d`] for the definition. Runs the fast kernel /// kernel its fast dispatch was checked against. That dispatch is gone: the decode runs
/// ([`crate::int_kernels`], as a 1-row 2-D transposed conv) unless the current /// through the integer operator set, whose interpreter lends the convolution to the fast
/// [`crate::int_kernels::Policy`] selects the reference; both give the same integer /// kernel behind it (`int_ops`), held to the interpreter there.
/// for every output element. #[cfg(any(test, feature = "legacy_int"))]
#[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)] #[doc(hidden)]
pub mod reference { pub mod reference {
/// One integer 1-D transposed conv with per-output-channel requant /// One integer 1-D transposed conv with per-output-channel requant
@ -308,11 +291,13 @@ fn plan(model: &AudioModel, c: usize, t: usize) -> Result<(usize, usize, u64, u1
Ok((c as usize, t as usize, live, macs)) Ok((c as usize, t as usize, live, macs))
} }
/// What [`decode_typed`] checks before it decodes a symbol, and the buffers it then /// What [`decode_typed`] checks before it decodes a symbol, short of lowering the
/// forms. Returns the synthesis's length, the most bytes the decode holds at once /// synthesis ([`ops::plan`] adds that), and the buffers it then forms. Returns the
/// on the reference kernels beyond the payload and the parsed model (the latent and /// synthesis's length, the most bytes the decode holds at once on the reference kernels
/// each layer's buffers, `i64` each, then the synthesis's output, `i64`, with the /// beyond the payload and the set as it holds it (the parsed model, and its loaded
/// PCM, `i16`), and the synthesis's multiply-accumulates on the latent. /// lowering): the latent and each layer's buffers (its input, its output and a
/// transposed convolution's uncropped plane), `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 /// 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` /// `lat_len` latent samples gives at least `t_out` samples (`GeometryMismatch`
@ -359,34 +344,6 @@ pub(crate) fn audio_plan(model: &AudioModel, s: &AudioStream<'_>) -> Result<(usi
Ok((t, working, macs)) 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 /// Decode a typed clip → int16 PCM, mono, byte-exact on every machine, refusing
/// rather than panicking or overflowing on any input: /// rather than panicking or overflowing on any input:
/// ///
@ -397,17 +354,28 @@ fn layer_in_range(model: &AudioModel, layer: &ALayer, x: &[i64], c: usize) -> Re
/// 2. per channel, rANS-decode the symbol `s`; the latent is `s·2^afrac + median_q` /// 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 /// ([`crate::int_entropy::dequantize`]), and a stream that runs out before its last
/// symbol is refused; /// symbol is refused;
/// 3. integer synthesis `g_s`, each layer's arithmetic checked before it runs; /// 3. integer synthesis `g_s` through the integer operator set ([`crate::int_ops`]),
/// 4. the first `t_out` samples, `(x·32767 + 2^(afrac−1)) >> afrac` clamped to `i16`. /// lowered by `wam1/1` and loaded under `acc64` with its bounds certificate before
/// the stream is decoded (`extensions/integer-operators.md` §13.3); its `check` ranges
/// refuse a latent that could take a layer outside the exact integer range
/// (`field_range`);
/// 4. the graph's PCM step: the first `t_out` samples, `(x·32767 + 2^(afrac−1)) >> afrac`
/// clamped to `i16`, a sample whose scaling would leave `i64` refused (`field_range`).
///
/// This is [`ops::decode_typed`]. The direct decode it replaced is kept, test-only
/// (feature `legacy_int`), as `legacy::decode_typed`.
pub fn decode_typed(model: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> { pub fn decode_typed(model: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> {
ops::decode_typed(model, s)
}
/// Step 2 of the decode, after its plan: the factorized rANS decode of the per-channel
/// latent (channel-major), each symbol rebuilt as `s·2^afrac + median_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; the stream must then end canonically.
fn latent(s: &AudioStream<'_>, afrac: u32) -> Result<Vec<i64>, Refused> {
use crate::int_entropy::dequantize; use crate::int_entropy::dequantize;
use RefusalKind::*; use RefusalKind::*;
audio_plan(model, s)?; let (c, l, len) = (s.n_ch, s.lat_len, s.cdfs[0].len());
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 y = Vec::new();
let mut dec = RansDecoder::new(s.rans).map_err(|_| refused(FieldRange, "the latent stream is not a rANS stream"))?; 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 ch in 0..c {
@ -422,31 +390,168 @@ pub fn decode_typed(model: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>,
if !dec.is_canonical_end() { if !dec.is_canonical_end() {
return Err(refused(StreamNoncanonical, "the latent stream does not end canonically after its last symbol")); 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. Ok(y)
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. /// The direct decode, as the decoder ran before it decoded through the integer operator
fn run_layer(model: &AudioModel, layer: &ALayer, x: Vec<i64>, c: usize, t: usize) -> (Vec<i64>, usize, usize) { /// set: each layer's arithmetic checked on the values it is given before it runs
match layer { /// (`layer_in_range`, the guard the graph's `check` ranges now carry), each transposed
ALayer::ConvT1d { out_ch, k, stride, pad, opad, weight, mult, bias } => { /// convolution on the scalar kernel ([`reference::conv_t_1d`]), then the PCM formed here.
let (nx, to) = conv_t_1d(&x, c, t, weight, *out_ch, *k, *stride, *pad, *opad, mult, bias, model.shift); /// Test-only (`legacy_int`): the differential tests decode both ways, and the two differ
(nx, *out_ch, to) /// only on the planned breaks of `extensions/integer-operators.md` §15.3.
#[cfg(any(test, feature = "legacy_int"))]
pub mod legacy {
use super::*;
use crate::int_transform::igdn;
/// 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)),
} }
ALayer::Igdn { gamma, beta, .. } => (igdn(&x, c, t, model.afrac, gamma, beta, model.gf), c, t), }
/// 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) = reference::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),
}
}
/// The direct decode of [`super::decode_typed`]'s arguments.
pub fn decode_typed(model: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> {
use RefusalKind::*;
audio_plan(model, s)?;
let afrac = model.afrac;
let y = latent(s, afrac)?;
// Integer synthesis → waveform, each layer checked on the values it is given.
let (mut x, mut cc, mut tt) = (y, s.n_ch, s.lat_len);
for layer in &model.layers {
layer_in_range(model, layer, &x, cc)?;
(x, cc, tt) = run_layer(model, layer, x, cc, tt);
}
// 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)
}
}
/// The synthesis through the integer operator set (`extensions/integer-operators.md`):
/// `WAM1`'s lowering `wam1/1` (§13.3), with the PCM step.
pub mod ops {
use super::*;
use wai_ops::lower::{Net, NetLayer};
use wai_ops::{Code, Refusal, Site};
/// The model this crate parses ([`AudioModel`]) in the form `wai-ops` lowers it from
/// ([`Net`]): the one [`wai_ops::lower::parse_wam1`] gives of the bytes it was parsed
/// from, so [`wai_ops::lower::wam1_net`] of it writes the graph
/// [`wai_ops::lower::wam1`] writes from those bytes. The model does not keep a
/// transposed convolution's input channels: they are its weights' count over its
/// output channels and kernel, as the bytes give them (0 where those hold a 0, which
/// the lowering refuses). A model parsed from bytes holds only 32-bit fields; one with
/// a field past 32 bits is refused here, as the lowering refuses one
/// (`model_mismatch`).
pub fn wam1_net(m: &AudioModel) -> Result<Net, Refusal> {
let f = |v: usize| u32::try_from(v).map_err(|_| Refusal::new(Code::ModelMismatch, Site::Lowering));
let layers = m.layers.iter().map(|l| {
Ok(match l {
ALayer::ConvT1d { out_ch, k, stride, pad, opad, weight, mult, bias } => NetLayer::ConvT {
inc: f(out_ch.checked_mul(*k).and_then(|d| weight.len().checked_div(d)).unwrap_or(0))?,
out: f(*out_ch)?, k: f(*k)?, stride: f(*stride)?, pad: f(*pad)?, opad: f(*opad)?,
w: weight.clone(), mult: mult.clone(), bias: bias.clone(),
},
ALayer::Igdn { c, gamma, beta } => NetLayer::Igdn { c: f(*c)?, gamma: gamma.clone(), beta: beta.clone() },
})
});
Ok(Net { afrac: m.afrac, gf: m.gf, shift: m.shift, layers: layers.collect::<Result<_, Refusal>>()?, audio: true })
}
/// A refusal of the operator set as the audio decode reports it
/// (`extensions/integer-payloads.md` §8): a value outside a `check` range is
/// `field_range`; the other codes are the payload extension's of the same name.
fn kind(r: Refusal, what: &'static str) -> Refused {
let k = match r.code {
Code::FieldRange => RefusalKind::FieldRange,
Code::TooLarge => RefusalKind::TooLarge,
Code::GeometryMismatch => RefusalKind::GeometryMismatch,
Code::StreamExhausted => RefusalKind::StreamExhausted,
Code::StreamNoncanonical => RefusalKind::StreamNoncanonical,
Code::ModelMismatch => RefusalKind::ModelMismatch,
};
refused(k, what)
}
/// The synthesis lowered by `wam1/1` with the PCM step and loaded under `acc64`: the
/// set as the decode holds it, beside the parsed model.
pub fn load(m: &AudioModel) -> Result<wai_ops::Loaded, Refused> {
const WHAT: &str = "the synthesis does not lower to an integer-operator graph that loads under acc64";
let net = wam1_net(m).map_err(|r| kind(r, WHAT))?;
let g = wai_ops::lower::wam1_net(&net).map_err(|r| kind(r, WHAT))?;
drop(net);
crate::int_ops::load(&g).map_err(|r| kind(r, WHAT))
}
/// Everything the decode checks before it decodes a symbol: [`audio_plan`], then the
/// synthesis lowered and loaded. Returns the loaded graph, and [`audio_plan`]'s
/// working bytes and multiply-accumulates.
pub(crate) fn plan(m: &AudioModel, s: &AudioStream<'_>) -> Result<(wai_ops::Loaded, u64, u128), Refused> {
let (_, working, macs) = audio_plan(m, s)?;
Ok((load(m)?, working, macs))
}
/// [`super::decode_typed`] through the integer operator set: the same PCM, and the
/// same refusals, except where integer-operators §15.3 lists a change.
pub fn decode_typed(m: &AudioModel, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> {
let (g, ..) = plan(m, s)?;
decode_loaded(&g, m.afrac, s)
}
/// The decode after its plan: the stream at `afrac`, then `g`, the set's loaded
/// lowering ([`load`]), with `t_out` as its scalar 0, then the samples. `s` must be a
/// clip the plan admitted against that set. What it holds beyond the set is what the
/// payload declares (integer-payloads §7, *Memory and work*).
pub fn decode_loaded(g: &wai_ops::Loaded, afrac: u32, s: &AudioStream<'_>) -> Result<Vec<i16>, Refused> {
let y = latent(s, afrac)?;
// The plan admitted every size, so each fits u32.
let x = wai_ops::Tensor::new(s.n_ch as u32, 1, s.lat_len as u32, y);
let out = crate::int_ops::run1(g, x, &[s.t_out as i64]).map_err(|r| kind(r, "a decoded latent or a synthesised sample is outside a check range of the synthesis"))?;
if (out.c, out.h, out.w as usize) != (1, 1, s.t_out) {
return Err(refused(RefusalKind::GeometryMismatch, "the synthesis does not give t_out samples"));
}
// The PCM step ends the graph with clamp(−32768, 32767), so every value is a sample.
Ok(out.data.iter().map(|&v| v as i16).collect())
} }
} }
@ -500,7 +605,7 @@ fn b64_decode(s: &str) -> Result<Vec<u8>, &'static str> {
Ok(out) Ok(out)
} }
/// The fast 1-D transposed conv against the scalar reference, element by element. /// The plan's multiply-accumulates against the layers' own terms.
#[cfg(test)] #[cfg(test)]
mod plan_tests { mod plan_tests {
use super::*; use super::*;
@ -511,7 +616,9 @@ mod plan_tests {
/// every element times `c`. /// every element times `c`.
#[test] #[test]
fn the_planned_macs_are_every_layers_terms() { fn the_planned_macs_are_every_layers_terms() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_audio"); // Through `../wai-rs`, so the path holds in wai-rs and in the byte-equality crate,
// which includes this file.
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors/int_audio");
let model = AudioModel::from_bytes(&std::fs::read(dir.join("audio_model.bin")).unwrap()).unwrap(); 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 wia1 = std::fs::read(dir.join("audio_bitstream.wia1")).unwrap();
let a = crate::int_wire::parse_wia1(&wia1).unwrap(); let a = crate::int_wire::parse_wia1(&wia1).unwrap();
@ -533,134 +640,101 @@ mod plan_tests {
} }
} }
/// Edge geometry through the decode's graph (`wam1/1`, run by the interpreter with the
/// fast kernels behind it), under every kernel policy: the cases the direct decode's
/// fast one-dimensional dispatch was tested on beside its kernel, now that the dispatch
/// is gone. Each graph is the lowering of a one-layer model; its output is the PCM step's.
#[cfg(test)] #[cfg(test)]
mod kernel_equivalence { mod geometry {
use super::*; use super::*;
use crate::int_kernels::testkit::{Rng, FAST}; use crate::int_kernels::testkit::{fast_conv_runs, FAST};
use crate::int_kernels::with_policy; use crate::int_kernels::{with_policy, Policy};
use wai_ops::{Code, Tensor};
#[test] /// A one-layer model: a transposed convolution of `k` taps `w` from `cin` channels to
fn conv_t_1d_equals_reference() { /// one, its multiplier `m`, bias `b` and the header's shift `shift`, at `afrac` 12.
let mut g = Rng(0xA0D_0001); #[allow(clippy::too_many_arguments)]
let mut ran = 0; fn one(cin: usize, k: usize, stride: usize, pad: usize, opad: usize, w: Vec<i64>, m: i64, b: i64, shift: u32) -> wai_ops::Loaded {
for case in 0..500 { assert_eq!(w.len(), cin * k);
let k = 1 + g.below(9); let model = AudioModel { afrac: 12, gf: 16, shift, layers: vec![ALayer::ConvT1d { out_ch: 1, k, stride, pad, opad, weight: w, mult: vec![m], bias: vec![b] }] };
let stride = 1 + g.below(5); ops::load(&model).expect("the one-layer model lowers and loads")
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: /// `x` (`cin` channels of one sample) through `g`, presenting `t_out` samples, under
/// a length-1 input, kernel 3, pad 1 keeps only the middle of the reference's /// the reference and each fast policy: the outcomes, which must agree, and the fast
/// 3-sample raw buffer (`2·2^40·1 = 2^41`); both outer samples are /// kernel's runs under each fast policy.
/// `2·2^40·2^22 = 2^63` and overflow `i64`. The fast path must panic where the fn run(g: &wai_ops::Loaded, x: &[i64], t_out: i64) -> (Result<Vec<i64>, Code>, Vec<u64>) {
/// reference does and otherwise give the same 2^41. let go = || crate::int_ops::run1(g, Tensor::new(x.len() as u32, 1, 1, x.to_vec()), &[t_out]).map(|t| t.data).map_err(|r| r.code);
#[test] let want = with_policy(Policy::reference(), go);
fn overflow_only_at_a_cropped_border_behaves_like_reference() { let mut runs = Vec::new();
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 { for p in FAST {
assert_eq!(run(p), want, "fast must match the reference, panic included ({p:?})"); let (got, n) = fast_conv_runs(|| with_policy(p, go));
assert_eq!(got, want, "{p:?} differs from the interpreter alone");
runs.push(n);
} }
(want, runs)
} }
/// A one-sample input upsampled by any stride is one output sample (k = 3, /// The PCM step's sample for a synthesised value `v` at `afrac` 12.
/// pad 1): a handful of operations for the reference. The fast path must return fn pcm(v: i64) -> i64 {
/// the same sample without overflowing, or allocating or looping per stride step. ((v * 32767 + 2048) >> 12).clamp(-32768, 32767)
}
/// A one-sample input upsampled by any stride a `WAM1` field holds is one output
/// sample (k = 3, pad 1): the middle tap, 5·2 = 10, under every policy, with no
/// allocation or loop in proportion to the stride.
#[test] #[test]
fn a_one_sample_input_upsampled_by_any_stride() { fn a_one_sample_input_upsampled_by_any_stride() {
use crate::int_kernels::Policy; for s in [u32::MAX as usize, 1, 2, 1 << 13, 1 << 16, 1 << 29] {
let (x, wt) = ([5i64], [1i64, 2, 3]); let g = one(1, 3, s, 1, 0, vec![1, 2, 3], 1, 0, 0);
// usize::MAX first: a kernel allocating a table per phase fails there at once assert_eq!(run(&g, &[5], 1).0, Ok(vec![pcm(10)]), "stride {s}");
// ("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 /// Several samples upsampled by a stride and cropped by padding so large that the
/// that the reference's uncropped buffer `cout · (to + 2·pad)` cannot be sized: /// direct decode's uncropped buffer could not be sized: here the stride and the
/// three samples, kernel 1, stride = pad = 2^62, one output sample. With two /// padding are 2^31 (a `WAM1` field is 32 bits). The run-time plan caps the uncropped
/// output channels the size overflows `usize` (the reference panics with /// length at 2^26 before anything is formed, so every policy refuses (`too_large`),
/// overflow checks on, and with them off indexes out of the wrapped buffer); /// and the fast kernel is not entered.
/// 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] #[test]
fn several_samples_whose_uncropped_buffer_cannot_be_sized() { fn several_samples_whose_uncropped_buffer_is_past_the_cap() {
use crate::int_kernels::testkit::fast_conv_runs; let s = 1usize << 31;
use crate::int_kernels::Policy; let model = AudioModel { afrac: 12, gf: 16, shift: 0, layers: vec![ALayer::ConvT1d { out_ch: 1, k: 1, stride: s, pad: s, opad: 0, weight: vec![7], mult: vec![1], bias: vec![0] }] };
let s = 1usize << 62; let g = ops::load(&model).expect("the model lowers and loads");
for cout in [2usize, 1] { let go = |p: Policy| fast_conv_runs(|| with_policy(p, || crate::int_ops::run1(&g, Tensor::new(1, 1, 3, vec![1, 2, 3]), &[1]).map_err(|r| r.code)));
let (x, wt) = ([1i64, 2, 3], vec![7i64; cout]); assert!(matches!(go(Policy::reference()), (Err(Code::TooLarge), 0)));
let (mult, bias) = (vec![1i64; cout], vec![0i64; cout]); for p in FAST {
let run = |p: Policy| { assert!(matches!(go(p), (Err(Code::TooLarge), 0)), "{p:?}");
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 /// Geometry whose zero-padded input would be more than the fast kernels' pad factor
/// the reference reads and writes is refused by the fast path, and the reference /// (16) times what the reference reads and writes is declined by the host, and the
/// runs: the fast kernel is not entered and the samples are the reference's. At /// interpreter computes it; at exactly the factor the fast kernel runs. A one-sample
/// exactly the factor the fast kernel runs. A one-sample input, kernel 1, stride /// input, kernel 1, stride 1, cropped by `pad` with output padding `2·pad` restoring
/// 1, cropped by `pad` with output padding `2·pad` restoring one output sample /// one output sample that no tap reaches: its value is the bias, 7.
/// that no tap reaches: the padded input is `cin · (1 + pad)` samples against
/// `cin + cin + 1` the reference touches (input, weights, output).
#[test] #[test]
fn padding_beyond_the_pad_factor_runs_the_reference() { fn padding_beyond_the_pad_factor_runs_on_the_interpreter() {
use crate::int_kernels::testkit::fast_conv_runs; // (channels, pad, fast-kernel runs): 1 · 48 = 16 · 3 and 2 · 40 = 16 · 5 at the
// (channels, pad, expected fast-kernel runs): 1 · 48 = 16 · 3 and // factor; 1 · 49 and 2 · 41 beyond it.
// 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)] { 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 g = one(cin, 1, 1, pad, 2 * pad, vec![3; cin], 3, 7, 1);
let want = reference::conv_t_1d(&x, cin, 1, &wt, 1, 1, 1, pad, 2 * pad, &[3], &[7], 1); let (out, n) = run(&g, &vec![5; cin], 1);
assert_eq!(want, (vec![7], 1), "reference, {cin} channels, pad {pad}"); assert_eq!(out, Ok(vec![pcm(7)]), "{cin} channels, pad {pad}");
for p in FAST { assert!(n.iter().all(|&r| r == runs), "{cin} channels, pad {pad}: fast runs {n:?}");
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:?}");
}
} }
} }
/// An input on which the scalar kernel's sums would overflow `i64` only at the
/// positions it accumulates into and then crops (two channels of 2^50, taps 2^14, 1,
/// 2^14 — `WAM1`'s weights are 16-bit — pad 1: the kept middle sums to 2^51, each
/// cropped border to 2^65): through the lowering, the layer's `check` range refuses it
/// before any arithmetic, under every policy (`field_range`), as the direct decode's
/// guard did; one within the range runs.
#[test]
fn an_input_that_could_overflow_is_refused_before_the_kernel() {
let g = one(2, 3, 1, 1, 0, [1 << 14, 1, 1 << 14, 1 << 14, 1, 1 << 14].to_vec(), 1, 0, 0);
assert_eq!(run(&g, &[1 << 50, 1 << 50], 1).0, Err(Code::FieldRange));
assert_eq!(run(&g, &[1, 1], 1).0, Ok(vec![pcm(2)]));
}
} }

View file

@ -480,6 +480,13 @@ impl std::fmt::Display for NetRefusal {
/// a network whose worst case over all inputs would. A decoder that feeds values it /// a network whose worst case over all inputs would. A decoder that feeds values it
/// read from an untrusted stream into a network uses this, and refuses the stream on /// read from an untrusted stream into a network uses this, and refuses the stream on
/// `Err`. /// `Err`.
///
/// A network that decodes through its lowering in the integer operator set
/// (`extensions/integer-operators.md` §13.2) makes these checks as the `check` ranges of
/// its graph instead, which refuse exactly what these refuse, at the same layer. The
/// splat and audio decodes do (`int_splat`, `int_audio`; their direct decodes, which
/// call this or [`igdn_check`], are test-only); the conditional codec's checked decode
/// ([`decode_conditional_checked`]) still calls this, and goes with it when it moves.
pub fn run_net_checked( pub fn run_net_checked(
model: &IntModel, input: &[i64], mut c: usize, mut h: usize, mut w: usize, model: &IntModel, input: &[i64], mut c: usize, mut h: usize, mut w: usize,
) -> Result<(Vec<i64>, usize, usize, usize), NetRefusal> { ) -> Result<(Vec<i64>, usize, usize, usize), NetRefusal> {
@ -647,7 +654,7 @@ fn check_layer(model: &IntModel, layer: &IntLayer, x: &[i64], c: usize, h: usize
/// The IGDN checks [`run_net_checked`] documents, for activations `x` of `c` channels /// The IGDN checks [`run_net_checked`] documents, for activations `x` of `c` channels
/// at `2^afrac` against `gamma` (`c × c`, at `2^gf`) and `beta` (`c`). Shared with the /// at `2^afrac` against `gamma` (`c × c`, at `2^gf`) and `beta` (`c`). Shared with the
/// 1-D synthesis of `int_audio`, whose IGDN is the same [`igdn`]. /// test-only direct decode of `int_audio`, whose IGDN is the same [`igdn`].
pub(crate) fn igdn_check(x: &[i64], c: usize, gamma: &[i64], beta: &[i64], gf: u32, afrac: u32) -> Result<(), Failed> { pub(crate) fn igdn_check(x: &[i64], c: usize, gamma: &[i64], beta: &[i64], gf: u32, afrac: u32) -> Result<(), Failed> {
use crate::int_kernels::max_abs; use crate::int_kernels::max_abs;
const I64: u128 = i64::MAX as u128; const I64: u128 = i64::MAX as u128;

View file

@ -25,8 +25,10 @@
use wai_ops::graph::{Graph, Loaded, Op, Params}; use wai_ops::graph::{Graph, Loaded, Op, Params};
use wai_ops::interp::{Kernels, Sequence, Tensor}; use wai_ops::interp::{Kernels, Sequence, Tensor};
use wai_ops::lower::{Net, NetLayer};
use wai_ops::{Profile, Refusal}; use wai_ops::{Profile, Refusal};
use crate::int_hyper_synth::{IntLayer, IntModel};
use crate::int_kernels::{policy, ConvGeom, KernelPath}; use crate::int_kernels::{policy, ConvGeom, KernelPath};
/// The profile of every capability that decodes through the operator set /// The profile of every capability that decodes through the operator set
@ -40,10 +42,42 @@ pub fn load(g: &Graph) -> Result<Loaded, Refusal> {
wai_ops::graph::load(&g.to_bytes(), PROFILE) wai_ops::graph::load(&g.to_bytes(), PROFILE)
} }
/// A `WHM1` network parsed by this crate ([`IntModel`]) in the form `wai-ops` lowers it
/// from ([`Net`]): the one [`wai_ops::lower::parse_whm1`] gives of the bytes the model was
/// parsed from, so [`wai_ops::lower::whm1_net`] of it writes the graph
/// [`wai_ops::lower::whm1`] writes from those bytes. The model does not keep a layer's
/// input channels: a convolution's are its weights' count over its output channels and
/// kernel, as the bytes give them, and an inverse GDN's are its `β`'s count. A layer
/// with no output channels or a kernel of 0 is given 0, and the lowering refuses it. A
/// model parsed from bytes holds only 32-bit fields; one with a field past 32 bits
/// (another source) is refused here, as the lowering refuses one (`model_mismatch`).
pub fn whm1_net(m: &IntModel) -> Result<Net, Refusal> {
let f = |v: usize| u32::try_from(v).map_err(|_| Refusal::new(wai_ops::Code::ModelMismatch, wai_ops::Site::Lowering));
let inc = |w: &[i64], out: usize, k: usize| {
f(out.checked_mul(k).and_then(|v| v.checked_mul(k)).and_then(|d| w.len().checked_div(d)).unwrap_or(0))
};
let layers = m.layers.iter().map(|l| {
Ok(match l {
IntLayer::ConvT { out_ch, k, stride, pad, opad, weight, mult, bias } => NetLayer::ConvT {
inc: inc(weight, *out_ch, *k)?, out: f(*out_ch)?, k: f(*k)?, stride: f(*stride)?, pad: f(*pad)?, opad: f(*opad)?,
w: weight.clone(), mult: mult.clone(), bias: bias.clone(),
},
IntLayer::Conv { out_ch, k, stride, pad, weight, mult, bias } => NetLayer::Conv {
inc: inc(weight, *out_ch, *k)?, out: f(*out_ch)?, k: f(*k)?, stride: f(*stride)?, pad: f(*pad)?,
w: weight.clone(), mult: mult.clone(), bias: bias.clone(),
},
IntLayer::Igdn { gamma, beta } => NetLayer::Igdn { c: f(beta.len())?, gamma: gamma.clone(), beta: beta.clone() },
IntLayer::Relu => NetLayer::Relu,
IntLayer::Leaky { slope_q, slope_frac } => NetLayer::Leaky { slope_q: *slope_q, slope_frac: *slope_frac },
})
});
Ok(Net { afrac: m.afrac, gf: m.gf, shift: m.shift, layers: layers.collect::<Result<_, Refusal>>()?, audio: false })
}
/// One decode of a loaded graph with one data input and no state, through the /// One decode of a loaded graph with one data input and no state, through the
/// interpreter with this crate's kernels behind it ([`Host`]): its first output. /// interpreter with this crate's kernels behind it ([`Host`]): its first output.
pub fn run1(g: &Loaded, x: Tensor, scalars: &[i64]) -> Result<Tensor, Refusal> { pub fn run1(g: &Loaded, x: Tensor, scalars: &[i64]) -> Result<Tensor, Refusal> {
let mut out = wai_ops::run_with(g, &[x], scalars, &mut Sequence::new(), &Host)?; let mut out = wai_ops::run_with(g, vec![x], scalars, &mut Sequence::new(), &Host)?;
Ok(out.swap_remove(0)) Ok(out.swap_remove(0))
} }
@ -178,6 +212,95 @@ mod equivalence {
assert!(fast > 600, "the fast kernel ran only {fast} times"); assert!(fast > 600, "the fast kernel ran only {fast} times");
} }
/// The one-dimensional transposed convolutions of `wam1/1` (§13.3: `kh = 1`, `sh = 1`,
/// `ph = 0`, `oph = 0` over `[cin, 1, T]`), with `WAM1`'s requantisation (a multiplier
/// per channel, one shift, a `post` bias, `floor`): random kernels up to 9, strides up
/// to 5, lengths up to 40, and operands that fit `i32` and that do not. The fast
/// kernel behind the interpreter gives the interpreter's integers. This is
/// `int_audio`'s equivalence of its fast one-dimensional kernel with its scalar one,
/// held to the interpreter instead.
#[test]
fn one_dimensional_transposed_convolutions_equal_the_interpreter() {
let mut r = Rng(0x0D2C_0003);
let (mut loaded, mut fast) = (0, 0);
for case in 0..400 {
let k = 1 + r.below(9) as u32;
let s = 1 + r.below(5) as u32;
let pad = r.below(k as usize) as u32;
let op = if s > 1 { r.below(s as usize) as u32 } else { 0 };
let t = 1 + r.below(40) as u32;
let (cin, cout) = (1 + r.below(6) as u32, 1 + r.below(6) as u32);
if (t - 1) * s + k + op <= 2 * pad {
continue;
}
let (xmax, wmax) = [(1i64 << 15, 2047i64), (1 << 40, 1 << 10)][case % 2];
let n = cout as usize;
let rq = Rq {
round: Round::Floor, bias_kind: BiasKind::Post, m_kind: 2, s_kind: 1, m_bytes: 8, b_bytes: 8,
mult: (0..n).map(|_| 1 + r.signed(1 << 16).abs()).collect(), shift: vec![24], bias: r.vec(n, 1 << 16, 0),
};
let wt = r.vec((cin * cout * k) as usize, wmax, 5);
let p = Params::ConvT { cout, kh: 1, kw: k, sh: 1, sw: s, ph: 0, pw: pad, oph: 0, opw: op, w_bytes: 8, rq, w: wt };
let g = graph(TensorRec::check(cin, -xmax, xmax), opcode::CONV_TRANSPOSE, cout, p);
let x = Tensor::new(cin, 1, t, r.vec((cin * t) as usize, xmax, 30));
let (l, f) = agree(&g, &x);
loaded += l as u32;
fast += f;
}
assert!(loaded > 250, "only {loaded} of the random graphs loaded");
assert!(fast > 500, "the fast kernel ran only {fast} times");
}
/// The committed `wai.splat.int_codec` synthesis through its lowering `whm1/1`: under
/// every fast policy each of its two transposed convolutions runs on the fast kernel,
/// and every attribute byte is the interpreter's. Its inverse GDN runs on the
/// interpreter under every policy: this host lends no IGDN kernel.
#[cfg(feature = "splat_int")]
#[test]
fn the_splat_synthesis_runs_its_convolutions_on_the_fast_kernel() {
use crate::int_splat::{ops, SplatStream};
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors/int_splat");
let gs = IntModel::from_bytes(&std::fs::read(dir.join("splat_gs.bin")).unwrap()).unwrap();
let p = std::fs::read(dir.join("splat.wis1")).unwrap();
let v = crate::int_wire::parse_wis1(&p).unwrap();
let st = SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
};
let (want, none) = fast_conv_runs(|| with_policy(Policy::reference(), || ops::decode_typed(&gs, &st).unwrap().attr));
assert_eq!(none, 0, "the reference policy runs no fast kernel");
for p in FAST {
let (got, runs) = fast_conv_runs(|| with_policy(p, || ops::decode_typed(&gs, &st).unwrap().attr));
assert!(got == want, "{p:?}: the attribute bytes differ from the interpreter alone");
assert_eq!(runs, 2, "{p:?}: both transposed convolutions run on the fast kernel");
}
}
/// The committed `wai.audio.int_codec` synthesis through its lowering `wam1/1`: under
/// every fast policy each of its four one-dimensional transposed convolutions runs on
/// the fast kernel, and every sample is the interpreter's. Its inverse GDNs and its
/// PCM step run on the interpreter under every policy.
#[cfg(feature = "audio_int")]
#[test]
fn the_audio_synthesis_runs_its_convolutions_on_the_fast_kernel() {
use crate::int_audio::{ops, AudioModel, AudioStream};
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors/int_audio");
let m = AudioModel::from_bytes(&std::fs::read(dir.join("audio_model.bin")).unwrap()).unwrap();
let p = std::fs::read(dir.join("audio_bitstream.wia1")).unwrap();
let a = crate::int_wire::parse_wia1(&p).unwrap();
let s = AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
};
let (want, none) = fast_conv_runs(|| with_policy(Policy::reference(), || ops::decode_typed(&m, &s).unwrap()));
assert_eq!(none, 0, "the reference policy runs no fast kernel");
for p in FAST {
let (got, runs) = fast_conv_runs(|| with_policy(p, || ops::decode_typed(&m, &s).unwrap()));
assert!(got == want, "{p:?}: the samples differ from the interpreter alone");
assert_eq!(runs, 4, "{p:?}: all four transposed convolutions run on the fast kernel");
}
}
/// A value outside an input's `check` range is refused by the interpreter before any /// A value outside an input's `check` range is refused by the interpreter before any
/// kernel runs, under every policy alike. /// kernel runs, under every policy alike.
#[test] #[test]

View file

@ -537,14 +537,16 @@ fn cost_wiv1(cap: &str, _: &[u8], _: &IntParams<'_>) -> Result<DeclaredCost, Int
#[cfg(feature = "audio_int")] #[cfg(feature = "audio_int")]
fn cost_wia1(cap: &str, payload: &[u8], params: &IntParams<'_>) -> Result<DeclaredCost, IntPayloadError> { fn cost_wia1(cap: &str, payload: &[u8], params: &IntParams<'_>) -> Result<DeclaredCost, IntPayloadError> {
use crate::int_audio::{audio_plan, AudioModel, AudioStream}; use crate::int_audio::{AudioModel, AudioStream};
let a = wire::parse_wia1(payload)?; let a = wire::parse_wia1(payload)?;
let m = AudioModel::from_bytes(params.member(cap, "model.wam")?).map_err(|e| model(format!("model.wam: {e}")))?; let m = AudioModel::from_bytes(params.member(cap, "model.wam")?).map_err(|e| model(format!("model.wam: {e}")))?;
let s = AudioStream { let s = AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize, afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans, offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
}; };
let (_, working, macs) = audio_plan(&m, &s)?; // Everything the decode checks before its first symbol, its set lowered and loaded
// included, so a payload the decode refuses there is refused here with its code.
let (_, working, macs) = crate::int_audio::ops::plan(&m, &s)?;
// The parsed medians and tables, held for the whole decode. // The parsed medians and tables, held for the whole decode.
let tables = a.n_ch as u64 * (8 + VEC + 4 * a.cdfs[0].len() as u64); let tables = a.n_ch as u64 * (8 + VEC + 4 * a.cdfs[0].len() as u64);
Ok(DeclaredCost { output_bytes: 2 * a.t_out as u64, symbols: a.n_ch as u64 * a.lat_len as u64, working_bytes: tables + working, macs }) Ok(DeclaredCost { output_bytes: 2 * a.t_out as u64, symbols: a.n_ch as u64 * a.lat_len as u64, working_bytes: tables + working, macs })
@ -558,14 +560,16 @@ fn cost_wia1(cap: &str, _: &[u8], _: &IntParams<'_>) -> Result<DeclaredCost, Int
#[cfg(feature = "splat_int")] #[cfg(feature = "splat_int")]
fn cost_wis1(cap: &str, payload: &[u8], params: &IntParams<'_>) -> Result<DeclaredCost, IntPayloadError> { fn cost_wis1(cap: &str, payload: &[u8], params: &IntParams<'_>) -> Result<DeclaredCost, IntPayloadError> {
use crate::int_hyper_synth::IntModel; use crate::int_hyper_synth::IntModel;
use crate::int_splat::{splat_plan, SplatStream}; use crate::int_splat::SplatStream;
let v = wire::parse_wis1(payload)?; let v = wire::parse_wis1(payload)?;
let gs = IntModel::from_bytes(params.member(cap, "g_s.whm")?).map_err(|e| model(format!("g_s.whm: {e}")))?; let gs = IntModel::from_bytes(params.member(cap, "g_s.whm")?).map_err(|e| model(format!("g_s.whm: {e}")))?;
let s = SplatStream { let s = SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize, afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans, n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
}; };
let (working, macs) = splat_plan(&gs, &s)?; // Everything the decode checks before its first symbol, its set lowered and loaded
// included, so a payload the decode refuses there is refused here with its code.
let (_, working, macs) = crate::int_splat::ops::plan(&gs, &s)?;
// The parsed ranges, offsets, medians and tables, held for the whole decode. // The parsed ranges, offsets, medians and tables, held for the whole decode.
let cdfs: u64 = v.cdfs.iter().map(|t| 4 * t.len() as u64).sum(); let cdfs: u64 = v.cdfs.iter().map(|t| 4 * t.len() as u64).sum();
let tables = 8 * v.attr as u64 + v.c as u64 * (4 + 8 + VEC) + cdfs; let tables = 8 * v.attr as u64 + v.c as u64 * (4 + 8 + VEC) + cdfs;

View file

@ -3,15 +3,16 @@
//! color[3], opacity), arranged as a `[14, S, S]` attribute image. A decode that runs //! color[3], opacity), arranged as a `[14, S, S]` attribute image. A decode that runs
//! float arithmetic can round differently on different machines, and then the //! float arithmetic can round differently on different machines, and then the
//! reconstructed cloud — and its hash — differ between them. Here the synthesis is //! reconstructed cloud — and its hash — differ between them. Here the synthesis is
//! integer (the [`crate::int_hyper_synth`] machinery, 14 channels instead of RGB's 3) //! integer (a `WHM1` network, 14 channels instead of RGB's 3) over a per-channel
//! over a per-channel factorized-rANS latent, all i64 — so the decoded attribute //! factorized-rANS latent, all exact integers — so the decoded attribute tensor is
//! tensor is byte-identical on every machine: **splat-equivalence**, a portable cloud //! byte-identical on every machine: **splat-equivalence**, a portable cloud hash. The
//! hash. The float rasterizer is sink-supplied presentation; the *attributes* are the //! synthesis decodes through the integer operator set, lowered by `whm1/1`
//! conformance contract. //! (`extensions/integer-operators.md` §13.2; [`crate::int_ops`]). The float rasterizer is
//! sink-supplied presentation; the *attributes* are the conformance contract.
use serde_json::Value; use serde_json::Value;
use crate::int_hyper_synth::{fnv1a, refused, run_net_checked, IntModel, Refused, RefusalKind, MAX_AFRAC, MAX_TENSOR}; use crate::int_hyper_synth::{fnv1a, refused, IntModel, Refused, RefusalKind, MAX_AFRAC, MAX_TENSOR};
use crate::rans::RansDecoder; use crate::rans::RansDecoder;
/// A decoded splat: the `[attr · s · s]` `u8` attribute image (channel-major), to be /// A decoded splat: the `[attr · s · s]` `u8` attribute image (channel-major), to be
@ -117,23 +118,32 @@ impl SplatJson {
/// 1. the stream's `afrac` is the model's, in `1..=30`; the medians, offsets and /// 1. the stream's `afrac` is the model's, in `1..=30`; the medians, offsets and
/// tables are one per channel and every table is well formed; `g_s` maps the /// tables are one per channel and every table is well formed; `g_s` maps the
/// latent's shape to `[n_attr, s, s]` with no buffer over /// latent's shape to `[n_attr, s, s]` with no buffer over
/// [`crate::int_hyper_synth::MAX_TENSOR`] elements — all before anything is /// [`crate::int_hyper_synth::MAX_TENSOR`] elements; then `g_s` is lowered by
/// allocated; /// `whm1/1` and loaded under `acc64` with its bounds certificate
/// (`extensions/integer-operators.md` §13.2) — all before anything is allocated
/// for the payload;
/// 2. per channel, rANS-decode the symbol and rebuild `s·2^afrac + median_q` /// 2. per channel, rANS-decode the symbol and rebuild `s·2^afrac + median_q`
/// ([`crate::int_entropy::dequantize`]); a stream that runs out before its last /// ([`crate::int_entropy::dequantize`]); a stream that runs out before its last
/// symbol is refused; /// symbol is refused;
/// 3. integer synthesis `g_s` through [`crate::int_hyper_synth::run_net_checked`]; /// 3. integer synthesis `g_s` through the integer operator set ([`crate::int_ops`]),
/// 4. round-half-up → `u8`, channel-major `[attr, s, s]`. /// whose `check` ranges refuse a latent that could take a layer outside the exact
/// integer range (`field_range`), ending with the RGB8 step: each value clamped to
/// `[0, 2^afrac]` and rounded half up to `u8`, channel-major `[attr, s, s]`.
///
/// This is [`ops::decode_typed`]. The direct decode it replaced is kept, test-only
/// (feature `legacy_int`), as `legacy::decode_typed`.
pub fn decode_typed(gs: &IntModel, st: &SplatStream<'_>) -> Result<DecodedSplat, Refused> { pub fn decode_typed(gs: &IntModel, st: &SplatStream<'_>) -> Result<DecodedSplat, Refused> {
ops::decode_typed(gs, st)
}
/// Step 2 of the decode, after its plan: per channel, the factorized rANS decode of the
/// latent, each symbol rebuilt as `s·2^afrac + median_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; the stream must then end canonically.
fn latent(st: &SplatStream<'_>, afrac: u32) -> Result<Vec<i64>, Refused> {
use crate::int_entropy::dequantize; use crate::int_entropy::dequantize;
use RefusalKind::*; use RefusalKind::*;
splat_plan(gs, st)?; let (c, plane) = (st.c, st.sh * st.sw);
let (afrac, c, sh, sw, s, n_attr) = (gs.afrac, st.c, st.sh, st.sw, st.s, st.n_attr);
// 1. per-channel factorized rANS-decode the latent, 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 plane = sh * sw;
let mut y = Vec::new(); let mut y = Vec::new();
let mut dec = RansDecoder::new(st.rans).map_err(|_| refused(FieldRange, "the latent stream is not a rANS stream"))?; let mut dec = RansDecoder::new(st.rans).map_err(|_| refused(FieldRange, "the latent stream is not a rANS stream"))?;
for ch in 0..c { for ch in 0..c {
@ -148,26 +158,118 @@ pub fn decode_typed(gs: &IntModel, st: &SplatStream<'_>) -> Result<DecodedSplat,
if !dec.is_canonical_end() { if !dec.is_canonical_end() {
return Err(refused(StreamNoncanonical, "the latent stream does not end canonically after its last symbol")); return Err(refused(StreamNoncanonical, "the latent stream does not end canonically after its last symbol"));
} }
// 2. integer synthesis → attribute tensor at 2^afrac. Ok(y)
let (x, ..) = run_net_checked(gs, &y, c, sh, sw).map_err(|r| {
if r.out_of_range {
refused(FieldRange, "a decoded latent could take a layer outside the exact integer range")
} else {
refused(ModelMismatch, "g_s's parameters do not fit the running shapes")
}
})?;
drop(y);
// 3. round-half-up → u8 (channel-major [attr, s, s]).
let (one, half) = (1i64 << afrac, 1i64 << (afrac - 1));
let attr = x.iter().map(|&v| ((v.clamp(0, one) * 255 + half) >> afrac).clamp(0, 255) as u8).collect();
Ok(DecodedSplat { attr, n_attr, s })
} }
/// What [`decode_typed`] checks before it decodes a symbol, and the most bytes the /// The direct decode, as the decoder ran before it decoded through the integer operator
/// decode then holds at once on the reference kernels beyond the payload and the /// set: the synthesis `g_s` run layer by layer by
/// parsed model: the latent (`i64`) with `g_s`'s buffers while it runs, then `g_s`'s /// [`crate::int_hyper_synth::run_net_checked`], which checks each layer's arithmetic on
/// output (`i64`) with the attribute tensor (`u8`); and `g_s`'s multiply-accumulates /// the values it is given before it runs, then the attribute bytes formed here.
/// ([`crate::int_hyper_synth::NetPlan`]). /// Test-only (`legacy_int`): the differential tests decode both ways, and the two differ
/// only on the planned breaks of `extensions/integer-operators.md` §15.3.
#[cfg(any(test, feature = "legacy_int"))]
pub mod legacy {
use super::*;
use crate::int_hyper_synth::run_net_checked;
/// The direct decode of [`super::decode_typed`]'s arguments.
pub fn decode_typed(gs: &IntModel, st: &SplatStream<'_>) -> Result<DecodedSplat, Refused> {
use RefusalKind::*;
splat_plan(gs, st)?;
let (afrac, c, sh, sw, s, n_attr) = (gs.afrac, st.c, st.sh, st.sw, st.s, st.n_attr);
let y = latent(st, afrac)?;
// Integer synthesis → attribute tensor at 2^afrac.
let (x, ..) = run_net_checked(gs, &y, c, sh, sw).map_err(|r| {
if r.out_of_range {
refused(FieldRange, "a decoded latent could take a layer outside the exact integer range")
} else {
refused(ModelMismatch, "g_s's parameters do not fit the running shapes")
}
})?;
drop(y);
// Round-half-up → u8 (channel-major [attr, s, s]).
let (one, half) = (1i64 << afrac, 1i64 << (afrac - 1));
let attr = x.iter().map(|&v| ((v.clamp(0, one) * 255 + half) >> afrac).clamp(0, 255) as u8).collect();
Ok(DecodedSplat { attr, n_attr, s })
}
}
/// The decode through the integer operator set (`extensions/integer-operators.md`):
/// `g_s` lowered by `whm1/1` with the RGB8 step (§13.2, §13.0), loaded under `acc64` with
/// its bounds certificate, and run by the operator set's interpreter
/// ([`crate::int_ops`]), whose `check` ranges carry the exactness checks of
/// integer-payloads §7. The driver keeps integer-payloads §7's order: the plan of
/// [`splat_plan`], then the set lowered and loaded, then the stream, then the graph.
pub mod ops {
use super::*;
use wai_ops::{Code, Loaded, Refusal, Tensor};
/// A refusal of the operator set as the splat decode reports it
/// (`extensions/integer-payloads.md` §8): a value outside a `check` range is
/// `field_range`; the other codes are the payload extension's of the same name.
fn kind(r: Refusal, what: &'static str) -> Refused {
let k = match r.code {
Code::FieldRange => RefusalKind::FieldRange,
Code::TooLarge => RefusalKind::TooLarge,
Code::GeometryMismatch => RefusalKind::GeometryMismatch,
Code::StreamExhausted => RefusalKind::StreamExhausted,
Code::StreamNoncanonical => RefusalKind::StreamNoncanonical,
Code::ModelMismatch => RefusalKind::ModelMismatch,
};
refused(k, what)
}
/// `g_s` lowered by `whm1/1` with the RGB8 step and loaded under `acc64`: the set as
/// the decode holds it, beside the parsed model.
pub fn load(gs: &IntModel) -> Result<Loaded, Refused> {
const WHAT: &str = "g_s does not lower to an integer-operator graph that loads under acc64";
let net = crate::int_ops::whm1_net(gs).map_err(|r| kind(r, WHAT))?;
let g = wai_ops::lower::whm1_net(&net, true).map_err(|r| kind(r, WHAT))?;
drop(net);
crate::int_ops::load(&g).map_err(|r| kind(r, WHAT))
}
/// Everything the decode checks before it decodes a symbol: [`splat_plan`], then `g_s`
/// lowered and loaded. Returns the loaded graph, and [`splat_plan`]'s working bytes and
/// multiply-accumulates.
pub(crate) fn plan(gs: &IntModel, st: &SplatStream<'_>) -> Result<(Loaded, u64, u128), Refused> {
let (working, macs) = splat_plan(gs, st)?;
Ok((load(gs)?, working, macs))
}
/// [`super::decode_typed`] through the integer operator set: the same attribute bytes,
/// and the same refusals, except where integer-operators §15.3 lists a change.
pub fn decode_typed(gs: &IntModel, st: &SplatStream<'_>) -> Result<DecodedSplat, Refused> {
let (g, ..) = plan(gs, st)?;
decode_loaded(&g, gs.afrac, st)
}
/// The decode after its plan: the stream at `afrac`, then `g`, the set's loaded
/// lowering ([`load`]), then the bytes. `st` must be a cloud the plan admitted
/// against that set. What it holds beyond the set is what the payload declares
/// (integer-payloads §7, *Memory and work*).
pub fn decode_loaded(g: &Loaded, afrac: u32, st: &SplatStream<'_>) -> Result<DecodedSplat, Refused> {
let (c, sh, sw, s, n_attr) = (st.c, st.sh, st.sw, st.s, st.n_attr);
let y = latent(st, afrac)?;
// The plan admitted every size, so each fits u32.
let x = Tensor::new(c as u32, sh as u32, sw as u32, y);
let out = crate::int_ops::run1(g, x, &[]).map_err(|r| kind(r, "a decoded latent is outside a check range of g_s"))?;
if (out.c as usize, out.h as usize, out.w as usize) != (n_attr, s, s) {
return Err(refused(RefusalKind::GeometryMismatch, "g_s does not give the attribute shape the payload declares"));
}
// The RGB8 step ends the graph, so every value is a byte.
let attr = out.data.iter().map(|&v| v as u8).collect();
Ok(DecodedSplat { attr, n_attr, s })
}
}
/// What [`decode_typed`] checks before it decodes a symbol, short of lowering `g_s`
/// ([`ops::plan`] adds that), and the most bytes the decode then holds at once on the
/// reference kernels beyond the payload and the set as it holds it (the parsed model,
/// and its loaded lowering): the latent (`i64`) with `g_s`'s buffers while it runs (a
/// layer's input, its output and a transposed convolution's uncropped plane), then
/// `g_s`'s output (`i64`) with the attribute tensor (`u8`); and `g_s`'s
/// multiply-accumulates ([`crate::int_hyper_synth::NetPlan`]).
pub(crate) fn splat_plan(gs: &IntModel, st: &SplatStream<'_>) -> Result<(u64, u128), Refused> { pub(crate) fn splat_plan(gs: &IntModel, st: &SplatStream<'_>) -> Result<(u64, u128), Refused> {
use RefusalKind::*; use RefusalKind::*;
let afrac = gs.afrac; let afrac = gs.afrac;

View file

@ -0,0 +1,499 @@
//! `wai.audio.int_codec` through the integer operator set: its lowering `wam1/1`
//! (`extensions/integer-operators.md` §13.3, with the PCM step) and the certificate of
//! every committed set (§8.5), as this crate loads them (`int_ops`).
//!
//! The lowered graphs and certificates are committed under `ops-conformance/certificates/`,
//! where `wai-verify` and the independent checker (`tools/wai_ops_check.py`) each
//! reproduce them byte for byte (`wai-ops.yml`). Here the lowering this crate runs, from
//! the model it parses (`int_audio::ops::wam1_net`), must write those same bytes, and the
//! load a sink makes must give the verdict the certificate records.
//!
//! The decode through the lowering (`int_audio::ops`) must give, on every committed
//! vector, the samples of the direct decode (`int_audio::legacy`) and the vector's
//! committed digests.
use std::path::PathBuf;
use serde_json::Value;
use wai::int_audio::{self, AudioJson, AudioModel, AudioStream};
use wai_ops::Profile;
const CAP: &str = "wai.audio.int_codec";
fn wai() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
}
fn read(rel: &str) -> Vec<u8> {
std::fs::read(wai().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}
fn json(rel: &str) -> Value {
serde_json::from_slice(&read(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}
fn sha256(b: &[u8]) -> String {
wai_ops::sha256::sha256(b).iter().map(|x| format!("{x:02x}")).collect()
}
/// The `wai.audio.int_codec` sets `ops-conformance/certificates/index.json` lists:
/// `(name, synthetic, set file, set digest, lowered graph file)`, each relative to `wai/`.
fn indexed() -> Vec<(String, bool, String, String, String)> {
json("ops-conformance/certificates/index.json")["sets"]
.as_array()
.unwrap()
.iter()
.filter(|s| s["capability"] == CAP)
.map(|s| {
assert_eq!(s["lowering"], "wam1/1");
let roles = s["roles"].as_array().unwrap();
assert_eq!(roles.len(), 1, "wam1/1 lowers an audio set to one graph, g_s");
(
s["name"].as_str().unwrap().to_string(),
s["synthetic"].as_bool().unwrap(),
s["members"]["model.wam"].as_str().unwrap().to_string(),
s["set_sha256"].as_str().unwrap().to_string(),
roles[0].as_str().unwrap().to_string(),
)
})
.collect()
}
/// The graph this crate lowers from the model it parses: `int_audio::ops::wam1_net`,
/// then `wam1/1` with the PCM step.
fn lowered_from_the_parsed_model(set: &[u8]) -> wai_ops::Graph {
let m = AudioModel::from_bytes(set).expect("the WAM1 set parses");
let net = wai::int_audio::ops::wam1_net(&m).expect("a model parsed from bytes converts");
assert!(net == wai_ops::lower::parse_wam1(set).unwrap(), "the converted model is the one wai-ops parses from the bytes");
wai_ops::lower::wam1_net(&net).expect("the set lowers")
}
/// Gate 1 of the migration: every committed `wai.audio.int_codec` set (the conformance
/// vector's and the synthetic one of `ops-conformance/sets/`) lowers to its committed
/// graph, from its bytes (`lower::wam1`) and from the model this crate parses; the
/// certificate beside it is that graph's; and loading the graph under the capability's
/// profile gives the certificate's verdict. Each set carries `check` ranges (§13.2,
/// §13.3), so it meets `acc64` by construction and not `acc32`, first failing at `g_s`'s
/// input range (the vector's at 47 bits, as Appendix C predicts); and the PCM step's
/// `crop`, whose length is a scalar, keeps it out of the accelerator subset.
#[test]
fn every_committed_set_lowers_to_its_committed_graph_and_verdict() {
let sets = indexed();
let mut names: Vec<(&str, bool)> = sets.iter().map(|s| (s.0.as_str(), s.1)).collect();
names.sort();
assert_eq!(names, [("audio", false), ("audio", true)], "the committed audio sets");
for (name, synthetic, set, digest, role) in &sets {
let name = if *synthetic { format!("{name} (synthetic)") } else { name.clone() };
let bytes = read(set);
let g = wai_ops::lower::wam1(&bytes).unwrap_or_else(|r| panic!("{name}: does not lower: {r:?}"));
// The verdict first: the load a sink makes.
let loaded = wai::int_ops::load(&g).unwrap_or_else(|r| panic!("{name}: refused under acc64: {r:?}"));
assert!(!loaded.meets(Profile::Acc32), "{name}: a set with check ranges inside its graph does not meet acc32");
// Then the committed bytes: the set, its lowered graph (both ways) and its certificate.
assert_eq!(&sha256(&bytes), digest, "{name}: the set's digest");
let graph = g.to_bytes();
assert!(graph == read(role), "{name}: the lowering of the bytes differs from the committed {role}");
assert!(lowered_from_the_parsed_model(&bytes).to_bytes() == graph, "{name}: the lowering of the parsed model differs from the committed {role}");
let cert = json(&format!("ops-conformance/certificates/{CAP}/{digest}.json"));
assert_eq!(cert["set_sha256"].as_str(), Some(digest.as_str()), "{name}");
assert_eq!(cert["lowering"], "wam1/1", "{name}");
assert_eq!(cert["graphs"][0]["name"], "g_s", "{name}");
assert_eq!(cert["graphs"][0]["graph_sha256"].as_str(), Some(sha256(&graph).as_str()), "{name}: the certificate is the graph's");
let v = &cert["verdict"];
assert_eq!((v["acc64"].as_bool(), v["acc32"].as_bool()), (Some(true), Some(false)), "{name}: the certificate's verdict");
let first = &v["acc32_first_failure"];
assert_eq!(
(first["graph"].as_str(), first["where"].as_str(), first["index"].as_str(), first["key"].as_str()),
(Some("g_s"), Some("input"), Some("0"), Some("range")),
"{name}: the first acc32 failure is g_s's input range"
);
assert!(v["npu_reasons"].as_array().unwrap().iter().any(|r| r == "crop_scalar"), "{name}: the PCM step's crop");
let x0 = &cert["graphs"][0]["inputs"][0];
assert_eq!(x0["policy"].as_str(), Some("check"), "{name}");
assert_eq!(x0["hi"].as_str(), Some(g.tensors[0].hi.to_string().as_str()), "{name}: the input's range is X_0");
if !synthetic {
assert_eq!(first["bits"].as_str(), Some("47"), "{name}: Appendix C's 47 bits");
assert_eq!(g.tensors[0].hi, 56322496561155, "{name}");
}
// The scalar is t_out, `[1, 2^26]` (§13.0's driver contract).
assert_eq!((g.scalars.len(), g.scalars[0].lo, g.scalars[0].hi), (1, 1, 1 << 26), "{name}: scalar 0 is t_out");
}
}
/// The typed form of a parsed `WIA1` payload.
fn stream<'a>(a: &'a wai::int_wire::Wia1<'a>) -> AudioStream<'a> {
AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
}
}
/// The `wai.audio.int_codec` vectors `int-payload-conformance/vectors.json` indexes:
/// `(name, payload, set member model.wam, JSON form, sidecar)`, each relative to `wai/`.
fn vectors() -> Vec<(String, String, String, String, String)> {
json("int-payload-conformance/vectors.json")["vectors"]
.as_array()
.unwrap()
.iter()
.filter(|v| v["capability"] == CAP)
.map(|v| {
let s = |k: &Value| k.as_str().unwrap().to_string();
(s(&v["name"]), s(&v["payload"]), s(&v["set"]["model.wam"]), s(&v["converted_from"]["json"]), s(&v["sidecar"]))
})
.collect()
}
fn le_bytes(pcm: &[i16]) -> Vec<u8> {
pcm.iter().flat_map(|v| v.to_le_bytes()).collect()
}
/// Gate 2 of the migration: golden equality. Every committed `wai.audio.int_codec`
/// vector decodes through the lowering (`int_audio::ops`) to the samples the direct
/// decode gives (`int_audio::legacy`), and to every digest the corpus commits for it:
/// the SHA-256 sidecar of the PCM and the export's FNV-1a. The payload (`WIA1`) and the
/// JSON form it is converted from both decode, and alike. The staged audio base carries
/// the same payload, so it is covered too.
#[test]
fn every_committed_vector_decodes_through_the_lowering_to_its_golden() {
let vs = vectors();
assert_eq!(vs.len(), 1, "the committed audio vectors");
for (name, payload, set, from, sidecar) in &vs {
let m = AudioModel::from_bytes(&read(set)).unwrap();
let p = read(payload);
let a = wai::int_wire::parse_wia1(&p).unwrap_or_else(|e| panic!("{name}: {e:?}"));
let j = AudioJson::from_value(&json(from)).unwrap();
let want = json(sidecar)[name]["sha256"].as_str().unwrap().to_string();
let fnv = json("wai-rs/tests/vectors/int_audio/audio.json")["pcm_fnv1a"].as_str().unwrap().to_string();
for (form, st) in [("WIA1", stream(&a)), ("JSON", j.stream())] {
let ops = int_audio::ops::decode_typed(&m, &st).unwrap_or_else(|r| panic!("{name} {form}: {r}"));
let direct = int_audio::legacy::decode_typed(&m, &st).unwrap_or_else(|r| panic!("{name} {form}: {r}"));
assert!(ops == direct, "{name} {form}: the decode through the lowering differs from the direct decode");
assert_eq!(ops.len(), st.t_out, "{name} {form}: t_out samples");
assert_eq!(sha256(&le_bytes(&ops)), want, "{name} {form}: the SHA-256 sidecar");
assert_eq!(format!("{:016x}", int_audio::pcm_fnv1a(&ops)), fnv, "{name} {form}: the export's FNV-1a");
}
let base = read("staged-conformance/cases/audio_int_codec_1l/base.wai");
assert!(base.windows(p.len()).any(|w| w == p.as_slice()), "{name}: the staged audio base carries this payload");
}
}
/// The committed vector's set and payload (`int-payload-conformance`'s `audio`).
const SET: &str = "wai-rs/tests/vectors/int_audio/audio_model.bin";
const PAYLOAD: &str = "wai-rs/tests/vectors/int_audio/audio_bitstream.wia1";
/// A typed clip that owns its tables, so a fixture can change any field.
#[derive(Clone)]
struct Owned {
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 Owned {
fn committed() -> Owned {
let p = read(PAYLOAD);
let a = wai::int_wire::parse_wia1(&p).unwrap();
Owned {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: a.median_q.clone(), cdfs: a.cdfs.clone(), rans: a.rans.to_vec(),
}
}
/// `n_ch` channels of `lat_len` latents at `afrac` 12, every latent of channel `k`
/// equal to `values[k]` (one table symbol each, its median placing it), presenting
/// `t_out` samples.
fn constant(values: &[i64], lat_len: usize, t_out: usize) -> Owned {
let (t, offset) = (vec![0, 1 << 15, 1 << 16], 3);
let mut enc = wai::rans::RansEncoder::new();
for _ in 0..values.len() * lat_len {
assert!(enc.encode(offset, &t, 3, offset));
}
Owned {
afrac: 12, n_ch: values.len(), lat_len, t_out, offset,
median_q: values.iter().map(|v| v - ((offset as i64) << 12)).collect(), cdfs: vec![t; values.len()], rans: enc.flush(),
}
}
fn st(&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 committed set, parsed, changed by `f`.
fn model(f: impl FnOnce(&mut AudioModel)) -> AudioModel {
let mut m = AudioModel::from_bytes(&read(SET)).unwrap();
f(&mut m);
m
}
/// The committed clip, changed by `f`.
fn clip(f: impl FnOnce(&mut Owned)) -> Owned {
let mut o = Owned::committed();
f(&mut o);
o
}
/// The code integer-payloads §8 gives a refusal of the audio decode.
fn code_of(r: wai::int_hyper_synth::Refused) -> &'static str {
wai::int_payload::IntPayloadError::from(r).code()
}
/// Both decodes of a clip against a model: through the lowering, then directly.
fn both(m: &AudioModel, o: &Owned) -> (Result<Vec<i16>, &'static str>, Result<Vec<i16>, &'static str>) {
(int_audio::ops::decode_typed(m, &o.st()).map_err(code_of), int_audio::legacy::decode_typed(m, &o.st()).map_err(code_of))
}
/// Gate 3 of the migration: refusal parity, on the committed fixtures. Each
/// `wai.audio.int_codec` fixture of `int-payload-conformance` is refused by a sink with
/// its code; one that parses (`decode`) is refused by the decode through the lowering and
/// by the direct decode alike, with that code.
#[test]
fn every_committed_refusal_fixture_keeps_its_code() {
let mut n = 0;
for f in json("int-payload-conformance/vectors.json")["refusals"].as_array().unwrap().iter().filter(|f| f["capability"] == CAP) {
let (name, want) = (f["name"].as_str().unwrap(), f["code"].as_str().unwrap());
let (p, set) = (read(f["payload"].as_str().unwrap()), read(f["set"]["model.wam"].as_str().unwrap()));
let params = wai::int_payload::resolve(CAP, None, &[wai::pin::HeldBytes::File(&set)], None).expect("an unpinned set resolves");
let sink = wai::int_payload::decode_int(CAP, &p, &params).map(|_| ()).map_err(|e| e.code());
assert_eq!(sink, Err(want), "{name}: the sink's code");
if f["layer"] == "decode" {
let m = AudioModel::from_bytes(&set).unwrap();
let a = wai::int_wire::parse_wia1(&p).unwrap_or_else(|e| panic!("{name}: a decode fixture parses: {e:?}"));
let ops = int_audio::ops::decode_typed(&m, &stream(&a)).err().map(code_of);
let direct = int_audio::legacy::decode_typed(&m, &stream(&a)).err().map(code_of);
assert_eq!((ops, direct), (Some(want), Some(want)), "{name}: through the lowering, then directly");
}
n += 1;
}
assert!(n >= 14, "the committed audio fixtures: {n}");
}
/// Gate 3, beside the committed fixtures: a fixture for each refusal the direct decode
/// makes (integer-payloads §5 and §7, in their order), each made from the committed set
/// and clip by one change. Each is refused by both decodes with the same code; the code
/// it states is integer-payloads §8's for that rule. The committed set and clip decode.
#[test]
fn every_refusal_of_the_direct_decode_is_refused_alike_through_the_lowering() {
use int_audio::ALayer;
let (m, base) = (model(|_| {}), Owned::committed());
assert!(int_audio::ops::decode_typed(&m, &base.st()).is_ok(), "the fixtures' base decodes");
let same = || model(|_| {});
let last = |m: &mut AudioModel| m.layers.len() - 1;
let fixtures: Vec<(&str, &str, AudioModel, Owned)> = vec![
("the set's afrac of 0", "model_mismatch", model(|m| m.afrac = 0), clip(|_| {})),
("the set's afrac of 31", "model_mismatch", model(|m| m.afrac = 31), clip(|_| {})),
("a clip afrac not the set's", "model_mismatch", same(), clip(|o| o.afrac = 11)),
("no latent channels", "field_range", same(), clip(|o| o.n_ch = 0)),
("a latent length of 0", "field_range", same(), clip(|o| o.lat_len = 0)),
("a t_out of 0", "field_range", same(), clip(|o| o.t_out = 0)),
("a median short", "field_range", same(), clip(|o| { o.median_q.pop(); })),
("a table short", "field_range", same(), clip(|o| { o.cdfs.pop(); })),
("a median past 2^47", "field_range", same(), clip(|o| o.median_q[3] = -(1 << 47) - 1)),
("a table that does not end at 2^16", "cdf_malformed", same(), clip(|o| *o.cdfs[2].last_mut().unwrap() = 65535)),
("a table of another length", "cdf_malformed", same(), clip(|o| { o.cdfs[5].pop(); *o.cdfs[5].last_mut().unwrap() = 1 << 16; })),
("a latent past 2^26", "too_large", same(), clip(|o| o.lat_len = 1 << 23)),
("a t_out past 2^26", "too_large", same(), clip(|o| o.t_out = (1 << 26) + 1)),
("an uncropped buffer past 2^26", "too_large", same(), clip(|o| (o.lat_len, o.t_out) = (1 << 22, 1 << 25))),
("a layer's bias short", "model_mismatch", model(|m| {
if let ALayer::ConvT1d { bias, .. } = &mut m.layers[0] { bias.pop(); }
}), clip(|_| {})),
("a stride of 0", "model_mismatch", model(|m| {
if let ALayer::ConvT1d { stride, .. } = &mut m.layers[2] { *stride = 0; }
}), clip(|_| {})),
("an inverse GDN short of a beta", "model_mismatch", model(|m| {
if let ALayer::Igdn { beta, .. } = &mut m.layers[1] { beta.pop(); }
}), clip(|_| {})),
("padding that leaves no output", "model_mismatch", model(|m| {
let l = last(m);
if let ALayer::ConvT1d { pad, .. } = &mut m.layers[l] { *pad = 1 << 20; }
}), clip(|_| {})),
("a synthesis of 16 channels", "model_mismatch", model(|m| { m.layers.pop(); }), clip(|_| {})),
("a t_out past the synthesis", "geometry_mismatch", same(), clip(|o| o.t_out += 1)),
("a latent longer than t_out needs", "geometry_mismatch", same(), clip(|o| o.t_out -= 512)),
("a stream too short to be one", "field_range", same(), clip(|o| o.rans.truncate(4))),
("a stream that runs out", "stream_exhausted", same(), clip(|o| { let n = o.rans.len() / 2; o.rans.truncate(n - n % 4); })),
("a word left unread", "stream_noncanonical", same(), clip(|o| o.rans.extend([0, 0, 0, 0]))),
("every latent past layer 0's range", "field_range", same(), clip(|o| o.median_q.iter_mut().for_each(|m| *m = 1 << 47))),
("every latent below it", "field_range", same(), clip(|o| o.median_q.iter_mut().for_each(|m| *m = -(1 << 47)))),
];
for (what, want, m, o) in &fixtures {
let (ops, direct) = both(m, o);
assert_eq!((ops.err(), direct.err()), (Some(*want), Some(*want)), "{what}: through the lowering, then directly");
}
}
/// Gate 3, at the edge of every `check` range of the committed set: for each of its
/// layers, a network of that layer alone (with the set's header), followed by a
/// transposed convolution to one channel whose weights and bias are 0 (whose own range
/// is all of `i64`), decodes a clip whose latents all lie in `[−X_ℓ, X_ℓ]`, with one
/// channel at `X_ℓ` (or `−X_ℓ`), to the same samples both ways, and refuses one at
/// `X_ℓ + 1` (or `−X_ℓ − 1`) both ways (`field_range`), where `X_ℓ` is tensor `ℓ`'s
/// range in the committed graph. And the PCM step's range: a network adding a bias to
/// its one channel decodes a sample at the top of the range in which
/// `v·32767 + 2^(afrac−1)` fits `i64`, and the bottom, and refuses one past each, both
/// ways.
#[test]
fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() {
use int_audio::ALayer;
let full = wai_ops::lower::wam1(&read(SET)).unwrap();
let n = AudioModel::from_bytes(&read(SET)).unwrap().layers.len();
for l in 0..n {
let one = model(|m| {
let layer = m.layers.swap_remove(l);
let c = match &layer {
ALayer::ConvT1d { out_ch, .. } => *out_ch,
ALayer::Igdn { c, .. } => *c,
};
m.layers = vec![layer, ALayer::ConvT1d { out_ch: 1, k: 1, stride: 1, pad: 0, opad: 0, weight: vec![0; c], mult: vec![1], bias: vec![0] }];
});
let g = wai_ops::lower::wam1_net(&int_audio::ops::wam1_net(&one).unwrap()).unwrap();
let (c, x) = (g.tensors[0].channels as usize, g.tensors[0].hi);
assert_eq!((g.tensors[0].lo, x), (full.tensors[l].lo, full.tensors[l].hi), "layer {l}: the range of tensor {l}");
// The clip's length: the layer's output length on 4 latents.
let t = match &one.layers[0] {
ALayer::ConvT1d { k, stride, pad, opad, .. } => 3 * stride + k + opad - 2 * pad,
ALayer::Igdn { .. } => 4,
};
let at = |v: i64| {
let mut vals = vec![0i64; c];
vals[c / 2] = v;
Owned::constant(&vals, 4, t)
};
for v in [x, -x] {
let (ops, direct) = both(&one, &at(v));
assert!(ops.is_ok() && ops == direct, "layer {l}: at {v}: {ops:?} through the lowering, {direct:?} directly");
}
for v in [x + 1, -x - 1] {
assert_eq!(both(&one, &at(v)), (Err("field_range"), Err("field_range")), "layer {l}: one past the range, {v}");
}
}
// The PCM step: one channel plus a bias.
let lo = -((1i128 << 63) / 32767) as i64;
let hi = ((i64::MAX as i128 - (1 << 11)) / 32767) as i64;
let add = |b: i64| AudioModel {
afrac: 12, gf: 16, shift: 0,
layers: vec![ALayer::ConvT1d { out_ch: 1, k: 1, stride: 1, pad: 0, opad: 0, weight: vec![1], mult: vec![1], bias: vec![b] }],
};
let one = Owned::constant(&[5 << 12], 4, 4);
for (b, ok) in [(hi - (5 << 12), Ok(vec![32767; 4])), (lo - (5 << 12), Ok(vec![-32768; 4])), (hi - (5 << 12) + 1, Err("field_range")), (lo - (5 << 12) - 1, Err("field_range"))] {
let (ops, direct) = both(&add(b), &one);
assert_eq!((&ops, &direct), (&ok, &ok), "a sample at {}", 5 * 4096 + b);
}
}
/// Gate 5 of the migration, the whole-decoder half: the decode through the lowering
/// under the reference kernels (the interpreter computes every operator) and under each
/// fast policy (the transposed convolutions on this crate's fast kernels, serial and
/// threaded) gives the same samples, and the same refusals, on every committed vector,
/// as `WIA1` and as its JSON form, and on clips with a channel moved to the input's
/// check range and one past it, at each end. The per-operator half is `int_ops`'
/// equivalence tests, one of which counts the fast kernel's runs on this set.
#[test]
fn every_kernel_policy_decodes_through_the_lowering_alike() {
use wai::int_kernels::{with_policy, Policy};
const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)];
let m = model(|_| {});
let x0 = wai_ops::lower::wam1(&read(SET)).unwrap().tensors[0].hi;
let at = |t: i64| clip(move |o| o.median_q[1] = t - ((o.offset as i64) << o.afrac));
let j = AudioJson::from_value(&json("wai-rs/tests/vectors/int_audio/audio_bitstream.json")).unwrap();
let mut clips: Vec<(String, Owned)> = vec![
("WIA1".into(), Owned::committed()),
("JSON".into(), Owned {
afrac: j.afrac, n_ch: j.n_ch, lat_len: j.lat_len, t_out: j.t_out, offset: j.offset,
median_q: j.median_q.clone(), cdfs: j.cdfs.clone(), rans: j.rans.clone(),
}),
];
for (what, t) in [("channel 1's median at X_0", x0), ("at X_0 + 1", x0 + 1), ("at -X_0", -x0), ("at -X_0 - 1", -x0 - 1)] {
clips.push((what.into(), at(t)));
}
for (k, (what, o)) in clips.iter().enumerate() {
let want = with_policy(POLICIES[0], || int_audio::ops::decode_typed(&m, &o.st())).map_err(code_of);
assert!(k >= 2 || want.is_ok(), "{what}: the committed form decodes");
for pol in &POLICIES[1..] {
let got = with_policy(*pol, || int_audio::ops::decode_typed(&m, &o.st())).map_err(code_of);
assert!(got == want, "{what}: {pol:?} differs from the interpreter alone");
}
}
}
/// The committed `wai.audio.int_codec` sets and payloads, relative to `wai/`: every set
/// and payload `int-payload-conformance/vectors.json` names for the capability (its
/// vectors and its refusal fixtures), and every base of a staged case decoded through
/// the capability, with the set its case names and the payload its envelope carries.
/// Returns `(set, (where, payload))` pairs.
fn committed_sets_and_payloads() -> Vec<(String, (String, Vec<u8>))> {
let mut v = Vec::new();
let index = json("int-payload-conformance/vectors.json");
for e in index["vectors"].as_array().unwrap().iter().chain(index["refusals"].as_array().unwrap()) {
if e["capability"] == CAP {
let (set, p) = (e["set"]["model.wam"].as_str().unwrap(), e["payload"].as_str().unwrap());
v.push((set.to_string(), (p.to_string(), read(p))));
}
}
let cases = wai().join("staged-conformance/cases");
let mut dirs: Vec<_> = std::fs::read_dir(&cases).unwrap().filter_map(|e| e.ok()).map(|e| e.path()).collect();
dirs.sort();
for d in dirs {
let Ok(b) = std::fs::read(d.join("case.json")) else { continue };
let case: Value = serde_json::from_slice(&b).unwrap();
for base in case["bases"].as_array().into_iter().flatten().filter(|b| b["decoded_via"] == CAP) {
let f = d.join(base["file"].as_str().unwrap());
let env = wai::container::Wai::from_bytes(&std::fs::read(&f).unwrap()).unwrap();
assert_eq!(env.manifest.model_requirement.capability, CAP, "{}", f.display());
v.push((base["decode"]["set"]["model.wam"].as_str().unwrap().to_string(), (f.display().to_string(), env.payload)));
}
}
v
}
/// The scan for the load rules the switch brings (integer-operators §15.3 item 3): no
/// committed set or payload is affected. Every committed audio set (the one the
/// payloads decode against, and the synthetic one of `ops-conformance/sets/`) has
/// layers, header shifts under 128, no bias of magnitude `2^63 − 1` or more, and lowers
/// and loads under `acc64`, its PCM step's range included; every committed payload that
/// the direct decode decodes decodes through the lowering to the same samples, and every
/// one it refuses is refused with the same code. (These are the files that carry a `WIA1`
/// payload or name the capability's set; `WIA1` appears in no other committed payload or
/// envelope.)
#[test]
fn the_new_load_rules_affect_no_committed_set_or_payload() {
use wai_ops::lower::NetLayer as N;
let all = committed_sets_and_payloads();
let mut sets: Vec<String> = all.iter().map(|(s, _)| s.clone()).collect();
sets.extend(indexed().into_iter().map(|s| s.2));
sets.sort_unstable();
sets.dedup();
for set in &sets {
let bytes = read(set);
let net = wai_ops::lower::parse_wam1(&bytes).unwrap_or_else(|r| panic!("{set}: {r:?}"));
assert!(!net.layers.is_empty(), "{set}: no layers");
assert!(net.shift < 128 && net.gf < 128, "{set}: a header shift of 128 or more");
for l in &net.layers {
if let N::ConvT { bias, .. } = l {
assert!(bias.iter().all(|b| b.unsigned_abs() < i64::MAX as u64), "{set}: a bias of magnitude 2^63 - 1 or more");
}
}
let g = wai_ops::lower::wam1(&bytes).unwrap_or_else(|r| panic!("{set}: does not lower: {r:?}"));
assert!(wai::int_ops::load(&g).is_ok(), "{set}: does not load under acc64");
}
let (mut decoded, mut refused) = (0, 0);
for (set, (at, p)) in &all {
let m = AudioModel::from_bytes(&read(set)).unwrap();
let Ok(a) = wai::int_wire::parse_wia1(p) else {
refused += 1;
continue;
};
let ops = int_audio::ops::decode_typed(&m, &stream(&a)).map_err(code_of);
let direct = int_audio::legacy::decode_typed(&m, &stream(&a)).map_err(code_of);
assert!(ops == direct, "{at}: the decode through the lowering differs from the direct decode");
if ops.is_ok() { decoded += 1 } else { refused += 1 }
}
assert_eq!(sets.len(), 2, "the committed audio sets: {sets:?}");
assert!(decoded >= 2 && refused >= 14, "the committed audio payloads, at least those of this revision: {decoded} decoded, {refused} refused");
}

View file

@ -18,9 +18,9 @@
//! and every policy must do the same. int_synth decodes through its lowering //! and every policy must do the same. int_synth decodes through its lowering
//! (integer-operators §13.1), whose fields are 32 bits and whose run-time plan caps //! (integer-operators §13.1), whose fields are 32 bits and whose run-time plan caps
//! every size it forms at 2^26: where a case is past either, every policy refuses it, //! every size it forms at 2^26: where a case is past either, every policy refuses it,
//! one of the planned breaks of integer-operators §15.3. The int_audio 1-D transposed //! one of the planned breaks of integer-operators §15.3. int_audio decodes through its
//! convolution has the //! lowering `wam1/1` too; its 1-D transposed convolution's edge geometry is tested
//! same stride check beside its kernel (`int_audio` unit tests). CI runs this file //! 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 //! beside `int_kernels_vectors` on every `wai-conformance` leg, with overflow checks
//! on and off. //! on and off.
#![cfg(feature = "neural_int")] #![cfg(feature = "neural_int")]
@ -224,7 +224,7 @@ fn synth_payload_2x2(h: u32, w: u32, latents: [i8; 4]) -> Vec<u8> {
// below.) A fast policy must have // below.) A fast policy must have
// the same outcome: it may not return a value where the reference panics. A 1×1 // 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 // input cannot show this, since `(h − 1)·stride` is 0. The int_audio 1-D form is
// tested beside its kernel. // 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`. /// 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]; const X: [i64; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];

View file

@ -6,16 +6,22 @@
//! alone, and a case that falls under a planned break must be refused by the decode //! 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. //! 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`) and `wai.audio.int_codec` (`wam1/1`).
//!
//! The random cases run for a fixed count by default. `WAI_DIFF_SECS` runs each random //! 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 //! 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. //! `WAI_DIFF_SEED` sets the first seed; a failing case names its seed.
#![cfg(feature = "legacy_int")] #![cfg(all(feature = "legacy_int", feature = "splat_int", feature = "audio_int"))]
use std::panic::catch_unwind; use std::panic::catch_unwind;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::panic::AssertUnwindSafe;
use serde_json::{json, Value}; use serde_json::{json, Value};
use wai::int_hyper_synth::{IntModel, RefusalKind};
use wai::int_synth::{decode_from_spec, legacy, IntImage, IntSynthError}; use wai::int_synth::{decode_from_spec, legacy, IntImage, IntSynthError};
fn wai() -> PathBuf { fn wai() -> PathBuf {
@ -296,3 +302,683 @@ fn int_synth_planned_breaks_are_new_refusals() {
assert_eq!(int_synth_planned_break(&five, &s64), Some("shift 64 to 127")); 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]); 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));
}

View file

@ -5,7 +5,9 @@
//! A sink reads the declared cost before it decodes and applies its own size policy, //! A sink reads the declared cost before it decodes and applies its own size policy,
//! so the cost must be one it can rely on: the decode, on the reference kernels, holds //! so the cost must be one it can rely on: the decode, on the reference kernels, holds
//! at least the output and at most the declared working bytes beside the payload and //! at least the output and at most the declared working bytes beside the payload and
//! the parameter set as parsed. For a `WIV1` with keyframe 0, which runs no network, //! the parameter set as parsed: for a capability that decodes through the integer
//! operator set, parsed, lowered and loaded, which is the same for every payload the
//! set decodes. For a `WIV1` with keyframe 0, which runs no network,
//! the declared working bytes are what the decode holds, to within a few bytes. //! the declared working bytes are what the decode holds, to within a few bytes.
//! //!
//! The clip a few kilobytes can code: sixteen frames of 4729 × 4729 whose stream is //! The clip a few kilobytes can code: sixteen frames of 4729 × 4729 whose stream is
@ -126,8 +128,19 @@ fn the_declared_cost_bounds_what_the_decode_holds() {
Box::new((wai::int_hyper_synth::IntModel::from_bytes(&hyp["g_s.whm"]).unwrap(), Box::new((wai::int_hyper_synth::IntModel::from_bytes(&hyp["g_s.whm"]).unwrap(),
wai::int_hyper_synth::IntModel::from_bytes(&hyp["h_s.whm"]).unwrap(), t)) as Box<dyn std::any::Any> wai::int_hyper_synth::IntModel::from_bytes(&hyp["h_s.whm"]).unwrap(), t)) as Box<dyn std::any::Any>
} }
c::CAP_AUDIO_INT_CODEC => Box::new(wai::int_audio::AudioModel::from_bytes(&model).unwrap()) as Box<dyn std::any::Any>, c::CAP_AUDIO_INT_CODEC => {
c::CAP_SPLAT_INT_CODEC => Box::new(wai::int_hyper_synth::IntModel::from_bytes(&gs).unwrap()) as Box<dyn std::any::Any>, let m = wai::int_audio::AudioModel::from_bytes(&model).unwrap();
let g = wai::int_audio::ops::load(&m).unwrap();
Box::new((m, g)) as Box<dyn std::any::Any>
}
// A decode through the integer operator set holds its set parsed and its
// lowered graph loaded (`int_splat::ops::load`), and the lowering forms each
// in turn.
c::CAP_SPLAT_INT_CODEC => {
let m = wai::int_hyper_synth::IntModel::from_bytes(&gs).unwrap();
let g = wai::int_splat::ops::load(&m).unwrap();
Box::new((m, g)) as Box<dyn std::any::Any>
}
_ => Box::new(()) as Box<dyn std::any::Any>, _ => Box::new(()) as Box<dyn std::any::Any>,
}); });
drop(r); drop(r);
@ -174,4 +187,38 @@ fn the_declared_cost_bounds_what_the_decode_holds() {
println!("{what}: declared {} working bytes ({} output, {} symbols, {} multiply-accumulates); held {peak}", println!("{what}: declared {} working bytes ({} output, {} symbols, {} multiply-accumulates); held {peak}",
cost.working_bytes, cost.output_bytes, cost.symbols, cost.macs); cost.working_bytes, cost.output_bytes, cost.symbols, cost.macs);
} }
// The decode through the integer operator set, with its set already parsed and
// loaded: what it then holds for the payload (the latent, each operator's input,
// output and uncropped plane, the attribute bytes) is within the declared working
// bytes alone. The set's own lowering, above, holds more for a moment; that is the
// set's, the same for every payload.
let p = read("int_splat/splat.wis1");
let cost = declared_cost(c::CAP_SPLAT_INT_CODEC, &p, &spl).unwrap();
let v = wire::parse_wis1(&p).unwrap();
let st = wai::int_splat::SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
};
let m = wai::int_hyper_synth::IntModel::from_bytes(&gs).unwrap();
let g = wai::int_splat::ops::load(&m).unwrap();
let (r, peak) = with_policy(Policy::reference(), || peak_during(|| wai::int_splat::ops::decode_loaded(&g, m.afrac, &st)));
r.unwrap();
assert!(peak >= cost.output_bytes && peak <= cost.working_bytes, "splat, set loaded: held {peak}, declared {}", cost.working_bytes);
println!("splat, its set loaded beforehand: declared {} working bytes; held {peak}", cost.working_bytes);
// The same for the audio synthesis.
let p = read("int_audio/audio_bitstream.wia1");
let cost = declared_cost(c::CAP_AUDIO_INT_CODEC, &p, &aud).unwrap();
let a = wire::parse_wia1(&p).unwrap();
let st = wai::int_audio::AudioStream {
afrac: a.afrac as u32, n_ch: a.n_ch as usize, lat_len: a.lat_len as usize, t_out: a.t_out as usize,
offset: a.offset, median_q: &a.median_q, cdfs: &a.cdfs, rans: a.rans,
};
let m = wai::int_audio::AudioModel::from_bytes(&model).unwrap();
let g = wai::int_audio::ops::load(&m).unwrap();
let (r, peak) = with_policy(Policy::reference(), || peak_during(|| wai::int_audio::ops::decode_loaded(&g, m.afrac, &st)));
r.unwrap();
assert!(peak >= cost.output_bytes && peak <= cost.working_bytes, "audio, set loaded: held {peak}, declared {}", cost.working_bytes);
println!("audio, its set loaded beforehand: declared {} working bytes; held {peak}", cost.working_bytes);
} }

View file

@ -0,0 +1,440 @@
//! `wai.splat.int_codec` through the integer operator set: its lowering `whm1/1`
//! (`extensions/integer-operators.md` §13.2, with the RGB8 step of §13.0) and the
//! certificate of every committed set (§8.5), as this crate loads them (`int_ops`).
//!
//! The lowered graphs and certificates are committed under `ops-conformance/certificates/`,
//! where `wai-verify` and the independent checker (`tools/wai_ops_check.py`) each
//! reproduce them byte for byte (`wai-ops.yml`). Here the lowering this crate runs, from
//! the model it parses (`int_ops::whm1_net`), must write those same bytes, and the load a
//! sink makes must give the verdict the certificate records.
//!
//! The decode through the lowering (`int_splat::ops`) must give, on every committed
//! vector, the bytes of the direct decode (`int_splat::legacy`) and the vector's
//! committed digests.
use std::path::PathBuf;
use serde_json::Value;
use wai::int_hyper_synth::IntModel;
use wai::int_splat::{self, SplatJson, SplatStream};
use wai_ops::Profile;
const CAP: &str = "wai.splat.int_codec";
fn wai() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
}
fn read(rel: &str) -> Vec<u8> {
std::fs::read(wai().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}
fn json(rel: &str) -> Value {
serde_json::from_slice(&read(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}
fn sha256(b: &[u8]) -> String {
wai_ops::sha256::sha256(b).iter().map(|x| format!("{x:02x}")).collect()
}
/// The `wai.splat.int_codec` sets `ops-conformance/certificates/index.json` lists:
/// `(name, set file, set digest, lowered graph file)`, each relative to `wai/`.
fn indexed() -> Vec<(String, String, String, String)> {
json("ops-conformance/certificates/index.json")["sets"]
.as_array()
.unwrap()
.iter()
.filter(|s| s["capability"] == CAP)
.map(|s| {
assert_eq!(s["lowering"], "whm1/1");
let roles = s["roles"].as_array().unwrap();
assert_eq!(roles.len(), 1, "whm1/1 lowers a splat set to one graph, g_s");
(
s["name"].as_str().unwrap().to_string(),
s["members"]["g_s.whm"].as_str().unwrap().to_string(),
s["set_sha256"].as_str().unwrap().to_string(),
roles[0].as_str().unwrap().to_string(),
)
})
.collect()
}
/// The graph this crate lowers from the model it parses: `int_ops::whm1_net`, then
/// `whm1/1` with the RGB8 step.
fn lowered_from_the_parsed_model(set: &[u8]) -> wai_ops::Graph {
let m = IntModel::from_bytes(set).expect("the WHM1 set parses");
let net = wai::int_ops::whm1_net(&m).expect("a model parsed from bytes converts");
assert!(net == wai_ops::lower::parse_whm1(set).unwrap(), "the converted model is the one wai-ops parses from the bytes");
wai_ops::lower::whm1_net(&net, true).expect("the set lowers")
}
/// Gate 1 of the migration: every committed `wai.splat.int_codec` set lowers to its
/// committed graph, from its bytes (`lower::splat`) and from the model this crate parses;
/// the certificate beside it is that graph's; and loading the graph under the
/// capability's profile gives the certificate's verdict. The set carries `check` ranges
/// (§13.2), so it meets `acc64` by construction and not `acc32`; its first `acc32` failure
/// is `g_s`'s input range, at 44 bits.
#[test]
fn every_committed_set_lowers_to_its_committed_graph_and_verdict() {
let sets = indexed();
let names: Vec<&str> = sets.iter().map(|s| s.0.as_str()).collect();
assert_eq!(names, ["splat"], "the committed splat sets");
for (name, set, digest, role) in &sets {
let bytes = read(set);
let lowered = wai_ops::lower::splat(&bytes).unwrap_or_else(|r| panic!("{name}: does not lower: {r:?}"));
let roles: Vec<&str> = lowered.roles.iter().map(|(r, _)| *r).collect();
assert_eq!(roles, ["g_s"], "{name}");
let g = lowered.get("g_s").unwrap();
// The verdict first: the load a sink makes.
let loaded = wai::int_ops::load(g).unwrap_or_else(|r| panic!("{name}: refused under acc64: {r:?}"));
assert!(!loaded.meets(Profile::Acc32), "{name}: a set with check ranges inside its graph does not meet acc32");
// Then the committed bytes: the set, its lowered graph (both ways) and its certificate.
assert_eq!(&sha256(&bytes), digest, "{name}: the set's digest");
let graph = g.to_bytes();
assert!(graph == read(role), "{name}: the lowering of the bytes differs from the committed {role}");
assert!(lowered_from_the_parsed_model(&bytes).to_bytes() == graph, "{name}: the lowering of the parsed model differs from the committed {role}");
let cert = json(&format!("ops-conformance/certificates/{CAP}/{digest}.json"));
assert_eq!(cert["set_sha256"].as_str(), Some(digest.as_str()), "{name}");
assert_eq!(cert["lowering"], "whm1/1", "{name}");
assert_eq!(cert["graphs"][0]["name"], "g_s", "{name}");
assert_eq!(cert["graphs"][0]["graph_sha256"].as_str(), Some(sha256(&graph).as_str()), "{name}: the certificate is the graph's");
let v = &cert["verdict"];
assert_eq!((v["acc64"].as_bool(), v["acc32"].as_bool()), (Some(true), Some(false)), "{name}: the certificate's verdict");
let first = &v["acc32_first_failure"];
assert_eq!(
(first["graph"].as_str(), first["where"].as_str(), first["index"].as_str(), first["key"].as_str(), first["bits"].as_str()),
(Some("g_s"), Some("input"), Some("0"), Some("range"), Some("44")),
"{name}: the first acc32 failure Appendix C predicts"
);
// The input's check range is the lowering's X_0 for the first layer (§13.2).
let x0 = &cert["graphs"][0]["inputs"][0];
assert_eq!((x0["policy"].as_str(), x0["lo"].as_str(), x0["hi"].as_str()), (Some("check"), Some("-5632249656115"), Some("5632249656115")), "{name}");
assert_eq!(g.tensors[0].lo, -5632249656115, "{name}");
assert_eq!(g.tensors[0].hi, 5632249656115, "{name}");
}
}
/// The typed form of a parsed `WIS1` payload.
fn stream<'a>(v: &'a wai::int_wire::Wis1<'a>) -> SplatStream<'a> {
SplatStream {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize,
n_attr: v.attr as usize, cdfs: &v.cdfs, offsets: &v.offsets, median_q: &v.median_q, rans: v.rans,
}
}
/// The `wai.splat.int_codec` vectors `int-payload-conformance/vectors.json` indexes:
/// `(name, payload, set member g_s.whm, JSON form, sidecar)`, each relative to `wai/`.
fn vectors() -> Vec<(String, String, String, String, String)> {
json("int-payload-conformance/vectors.json")["vectors"]
.as_array()
.unwrap()
.iter()
.filter(|v| v["capability"] == CAP)
.map(|v| {
let s = |k: &Value| k.as_str().unwrap().to_string();
(s(&v["name"]), s(&v["payload"]), s(&v["set"]["g_s.whm"]), s(&v["converted_from"]["json"]), s(&v["sidecar"]))
})
.collect()
}
/// Gate 2 of the migration: golden equality. Every committed `wai.splat.int_codec`
/// vector decodes through the lowering (`int_splat::ops`) to the bytes the direct decode
/// gives (`int_splat::legacy`), and to every digest the corpus commits for it: the
/// SHA-256 sidecar and the export's FNV-1a. The payload (`WIS1`) and the JSON form it is
/// converted from both decode, and alike. The staged corpus's splat base carries the
/// same payload, so it is covered too.
#[test]
fn every_committed_vector_decodes_through_the_lowering_to_its_golden() {
let vs = vectors();
assert_eq!(vs.len(), 1, "the committed splat vectors");
for (name, payload, set, from, sidecar) in &vs {
let gs = IntModel::from_bytes(&read(set)).unwrap();
let p = read(payload);
let v = wai::int_wire::parse_wis1(&p).unwrap_or_else(|e| panic!("{name}: {e:?}"));
let bs = json(from);
let j = SplatJson::from_value(&bs).unwrap();
let want = json(sidecar)[name]["sha256"].as_str().unwrap().to_string();
for (form, st) in [("WIS1", stream(&v)), ("JSON", j.stream())] {
let ops = int_splat::ops::decode_typed(&gs, &st).unwrap_or_else(|r| panic!("{name} {form}: {r}"));
let direct = int_splat::legacy::decode_typed(&gs, &st).unwrap_or_else(|r| panic!("{name} {form}: {r}"));
assert!(ops.attr == direct.attr, "{name} {form}: the decode through the lowering differs from the direct decode");
assert_eq!((ops.n_attr, ops.s), (direct.n_attr, direct.s), "{name} {form}");
assert_eq!(sha256(&ops.attr), want, "{name} {form}: the SHA-256 sidecar");
assert_eq!(Some(format!("{:016x}", ops.attr_hash()).as_str()), bs["attr_hash"].as_str(), "{name} {form}: the export's FNV-1a");
}
let base = read("staged-conformance/cases/splat_attr_1l/base.wai");
assert!(base.windows(p.len()).any(|w| w == p.as_slice()), "{name}: the staged splat base carries this payload");
}
}
/// The committed vector's set and payload (`int-payload-conformance`'s `splat`).
const SET: &str = "wai-rs/tests/vectors/int_splat/splat_gs.bin";
const PAYLOAD: &str = "wai-rs/tests/vectors/int_splat/splat.wis1";
/// A typed cloud that owns its tables, so a fixture can change any field.
#[derive(Clone)]
struct Owned {
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 Owned {
fn committed() -> Owned {
let p = read(PAYLOAD);
let v = wai::int_wire::parse_wis1(&p).unwrap();
Owned {
afrac: v.afrac as u32, c: v.c as usize, sh: v.sh as usize, sw: v.sw as usize, s: v.s as usize, n_attr: v.attr as usize,
cdfs: v.cdfs.clone(), offsets: v.offsets.clone(), median_q: v.median_q.clone(), rans: v.rans.to_vec(),
}
}
fn st(&self) -> SplatStream<'_> {
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 committed set, parsed, changed by `f`.
fn model(f: impl FnOnce(&mut IntModel)) -> IntModel {
let mut m = IntModel::from_bytes(&read(SET)).unwrap();
f(&mut m);
m
}
/// The committed cloud, changed by `f`.
fn cloud(f: impl FnOnce(&mut Owned)) -> Owned {
let mut o = Owned::committed();
f(&mut o);
o
}
/// The code integer-payloads §8 gives a refusal of the splat decode.
fn code_of(r: wai::int_hyper_synth::Refused) -> &'static str {
wai::int_payload::IntPayloadError::from(r).code()
}
/// Gate 3 of the migration: refusal parity, on the committed fixtures. Each
/// `wai.splat.int_codec` fixture of `int-payload-conformance` is refused by a sink with
/// its code; one that parses (`decode`) is refused by the decode through the lowering and
/// by the direct decode alike, with that code.
#[test]
fn every_committed_refusal_fixture_keeps_its_code() {
let mut n = 0;
for f in json("int-payload-conformance/vectors.json")["refusals"].as_array().unwrap().iter().filter(|f| f["capability"] == CAP) {
let (name, want) = (f["name"].as_str().unwrap(), f["code"].as_str().unwrap());
let (p, set) = (read(f["payload"].as_str().unwrap()), read(f["set"]["g_s.whm"].as_str().unwrap()));
let params = wai::int_payload::resolve(CAP, None, &[wai::pin::HeldBytes::File(&set)], None).expect("an unpinned set resolves");
let sink = wai::int_payload::decode_int(CAP, &p, &params).map(|_| ()).map_err(|e| e.code());
assert_eq!(sink, Err(want), "{name}: the sink's code");
if f["layer"] == "decode" {
let gs = IntModel::from_bytes(&set).unwrap();
let v = wai::int_wire::parse_wis1(&p).unwrap_or_else(|e| panic!("{name}: a decode fixture parses: {e:?}"));
let ops = int_splat::ops::decode_typed(&gs, &stream(&v)).err().map(code_of);
let direct = int_splat::legacy::decode_typed(&gs, &stream(&v)).err().map(code_of);
assert_eq!((ops, direct), (Some(want), Some(want)), "{name}: through the lowering, then directly");
}
n += 1;
}
assert!(n >= 11, "the committed splat fixtures: {n}");
}
/// Gate 3, beside the committed fixtures: a fixture for each refusal the direct decode
/// makes (integer-payloads §7, in its order), each made from the committed set and cloud
/// by one change. Each is refused by both decodes with the same code; the code it states
/// is integer-payloads §8's for that rule. The committed set and cloud decode.
#[test]
fn every_refusal_of_the_direct_decode_is_refused_alike_through_the_lowering() {
let (gs, base) = (model(|_| {}), Owned::committed());
assert!(int_splat::ops::decode_typed(&gs, &base.st()).is_ok(), "the fixtures' base decodes");
let same = || model(|_| {});
let fixtures: Vec<(&str, &str, IntModel, Owned)> = vec![
("the set's afrac of 0", "model_mismatch", model(|m| m.afrac = 0), cloud(|_| {})),
("the set's afrac of 31", "model_mismatch", model(|m| m.afrac = 31), cloud(|_| {})),
("a cloud afrac not the set's", "model_mismatch", same(), cloud(|o| o.afrac = 11)),
("no latent channels", "field_range", same(), cloud(|o| o.c = 0)),
("a median short", "field_range", same(), cloud(|o| { o.median_q.pop(); })),
("an offset short", "field_range", same(), cloud(|o| { o.offsets.pop(); })),
("a table short", "field_range", same(), cloud(|o| { o.cdfs.pop(); })),
("a median past 2^47", "field_range", same(), cloud(|o| o.median_q[3] = (1 << 47) + 1)),
("a table that does not end at 2^16", "cdf_malformed", same(), cloud(|o| *o.cdfs[2].last_mut().unwrap() = 65535)),
("a latent past 2^26", "too_large", same(), cloud(|o| (o.sh, o.sw) = (1 << 13, 1 << 13))),
("an attribute tensor past 2^26", "too_large", same(), cloud(|o| o.s = 1 << 14)),
("an uncropped plane past 2^26", "too_large", same(), cloud(|o| (o.sh, o.sw) = (1100, 1100))),
("a layer's bias short", "model_mismatch", model(|m| {
if let wai::int_hyper_synth::IntLayer::ConvT { bias, .. } = &mut m.layers[0] { bias.pop(); }
}), cloud(|_| {})),
("padding that leaves no output", "model_mismatch", model(|m| {
if let wai::int_hyper_synth::IntLayer::ConvT { pad, .. } = &mut m.layers[2] { *pad = 1000; }
}), cloud(|_| {})),
("an attribute side not g_s's", "geometry_mismatch", same(), cloud(|o| o.s = 31)),
("a stream too short to be one", "field_range", same(), cloud(|o| o.rans.truncate(4))),
("a stream that runs out", "stream_exhausted", same(), cloud(|o| { let n = o.rans.len() / 2; o.rans.truncate(n - n % 4); })),
("a word left unread", "stream_noncanonical", same(), cloud(|o| o.rans.extend([0, 0, 0, 0]))),
("every latent past layer 0's range", "field_range", same(), cloud(|o| o.median_q.iter_mut().for_each(|m| *m = 1 << 47))),
("every latent below it", "field_range", same(), cloud(|o| o.median_q.iter_mut().for_each(|m| *m = -(1 << 47)))),
];
for (what, want, gs, o) in &fixtures {
let ops = int_splat::ops::decode_typed(gs, &o.st()).err().map(code_of);
let direct = int_splat::legacy::decode_typed(gs, &o.st()).err().map(code_of);
assert_eq!((ops, direct), (Some(*want), Some(*want)), "{what}: through the lowering, then directly");
}
}
/// Gate 3, at the edge of every `check` range: for each layer of the committed set, a
/// network of that layer alone (with the set's header) is lowered, and its input's
/// `check` range is `[−X_ℓ, X_ℓ]`, the range the full graph gives tensor `ℓ` (§13.2). An
/// input whose largest magnitude is `X_ℓ` runs, through the lowering and directly, to the
/// same output; one at `X_ℓ + 1` is refused by both: by the graph (`field_range`, at
/// input 0) and by the direct decode's check of that layer (`run_net_checked`, a value
/// out of range at layer 0). So the ranges refuse exactly what integer-payloads §7
/// *Exactness* refuses, at the same layer.
#[test]
fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() {
use wai::int_hyper_synth::run_net_checked;
use wai_ops::{Code, Site, Tensor};
let set = read(SET);
let full = wai_ops::lower::whm1(&set, true).unwrap();
let net = wai_ops::lower::parse_whm1(&set).unwrap();
for l in 0..net.layers.len() {
let one = model(|m| {
let layer = m.layers.swap_remove(l);
m.layers = vec![layer];
});
let g = wai_ops::lower::whm1_net(&wai::int_ops::whm1_net(&one).unwrap(), false).unwrap();
let (c, lo, hi) = (g.tensors[0].channels, g.tensors[0].lo, g.tensors[0].hi);
assert_eq!((c, lo, hi), (full.tensors[l].channels, full.tensors[l].lo, full.tensors[l].hi), "layer {l}: the range of tensor {l}");
assert!(hi > 0 && lo == -hi, "layer {l}: a symmetric check range [{lo}, {hi}]");
let loaded = wai::int_ops::load(&g).unwrap();
let x = |v: i64| {
let mut d = vec![0i64; c as usize * 4];
d[c as usize * 2 + 1] = v;
d
};
for v in [hi, -hi] {
let ops = wai::int_ops::run1(&loaded, Tensor::new(c, 2, 2, x(v)), &[]).unwrap_or_else(|r| panic!("layer {l}: {v} runs: {r:?}"));
let (direct, ..) = run_net_checked(&one, &x(v), c as usize, 2, 2).unwrap_or_else(|r| panic!("layer {l}: {v} runs directly: {r}"));
assert!(ops.data == direct, "layer {l}: at {v} the two differ");
}
for v in [hi + 1, -hi - 1] {
let r = wai::int_ops::run1(&loaded, Tensor::new(c, 2, 2, x(v)), &[]).expect_err("one past the range");
assert_eq!((r.code, r.at), (Code::FieldRange, Site::Input(0)), "layer {l}: {v}");
let d = run_net_checked(&one, &x(v), c as usize, 2, 2).expect_err("one past the range, directly");
assert!(d.out_of_range && d.layer == 0, "layer {l}: {v}: {d}");
}
}
}
/// Gate 5 of the migration, the whole-decoder half: the decode through the lowering
/// under the reference kernels (the interpreter computes every operator) and under each
/// fast policy (the transposed convolutions on this crate's fast kernels, serial and
/// threaded) gives the same attribute bytes, and the same refusals, on every committed
/// vector, as `WIS1` and as its JSON form, and on clouds at the edge of the input's check
/// range. The per-operator half is `int_ops`' equivalence tests, one of which counts the
/// fast kernel's runs on this set.
#[test]
fn every_kernel_policy_decodes_through_the_lowering_alike() {
use wai::int_kernels::{with_policy, Policy};
const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)];
let gs = model(|_| {});
let x0 = wai_ops::lower::whm1(&read(SET), true).unwrap().tensors[0].hi;
let at = |t: i64| cloud(move |o| o.median_q[1] = t - ((o.offsets[1] as i64) << o.afrac));
let mut clouds: Vec<(String, Owned)> = vec![("WIS1".into(), Owned::committed())];
let j = SplatJson::from_value(&json("wai-rs/tests/vectors/int_splat/splat.json")).unwrap();
clouds.push(("JSON".into(), Owned {
afrac: j.afrac, c: j.c, sh: j.sh, sw: j.sw, s: j.s, n_attr: j.n_attr,
cdfs: j.cdfs.clone(), offsets: j.offsets.clone(), median_q: j.median_q.clone(), rans: j.rans.clone(),
}));
for (what, t) in [("channel 1's median at X_0", x0), ("at X_0 + 1", x0 + 1), ("at -X_0", -x0), ("at -X_0 - 1", -x0 - 1)] {
clouds.push((what.into(), at(t)));
}
for (k, (what, o)) in clouds.iter().enumerate() {
let want = with_policy(POLICIES[0], || int_splat::ops::decode_typed(&gs, &o.st())).map(|d| d.attr).map_err(code_of);
assert!(k >= 2 || want.is_ok(), "{what}: the committed form decodes");
for pol in &POLICIES[1..] {
let got = with_policy(*pol, || int_splat::ops::decode_typed(&gs, &o.st())).map(|d| d.attr).map_err(code_of);
assert!(got == want, "{what}: {pol:?} differs from the interpreter alone");
}
}
}
/// The committed `wai.splat.int_codec` sets and payloads, relative to `wai/`: every set
/// and payload `int-payload-conformance/vectors.json` names for the capability (its
/// vectors and its refusal fixtures), and every base of a staged case decoded through
/// the capability, with the set its case names and the payload its envelope carries.
/// Returns `(set, (where, payload))` pairs.
fn committed_sets_and_payloads() -> Vec<(String, (String, Vec<u8>))> {
let mut v = Vec::new();
let index = json("int-payload-conformance/vectors.json");
for e in index["vectors"].as_array().unwrap().iter().chain(index["refusals"].as_array().unwrap()) {
if e["capability"] == CAP {
let (set, p) = (e["set"]["g_s.whm"].as_str().unwrap(), e["payload"].as_str().unwrap());
v.push((set.to_string(), (p.to_string(), read(p))));
}
}
let cases = wai().join("staged-conformance/cases");
let mut dirs: Vec<_> = std::fs::read_dir(&cases).unwrap().filter_map(|e| e.ok()).map(|e| e.path()).collect();
dirs.sort();
for d in dirs {
let Ok(b) = std::fs::read(d.join("case.json")) else { continue };
let case: Value = serde_json::from_slice(&b).unwrap();
for base in case["bases"].as_array().into_iter().flatten().filter(|b| b["decoded_via"] == CAP) {
let f = d.join(base["file"].as_str().unwrap());
let env = wai::container::Wai::from_bytes(&std::fs::read(&f).unwrap()).unwrap();
assert_eq!(env.manifest.model_requirement.capability, CAP, "{}", f.display());
v.push((base["decode"]["set"]["g_s.whm"].as_str().unwrap().to_string(), (f.display().to_string(), env.payload)));
}
}
v
}
/// The scan for the load rules the switch brings (integer-operators §15.3 item 3): no
/// committed set or payload is affected. Every committed set has a layer of tag 0, 1 or
/// 2, header shifts under 128, no bias of magnitude `2^63 − 1` or more, and lowers and
/// loads under `acc64`; every committed payload that the direct decode decodes decodes
/// through the lowering to the same bytes, and every one it refuses is refused with the
/// same code. (These are the files that carry a `WIS1` payload or name the capability's
/// set; `WIS1` appears in no other committed payload or envelope.)
#[test]
fn the_new_load_rules_affect_no_committed_set_or_payload() {
use wai_ops::lower::NetLayer as N;
let all = committed_sets_and_payloads();
let mut sets: Vec<&str> = all.iter().map(|(s, _)| s.as_str()).collect();
sets.sort_unstable();
sets.dedup();
for set in &sets {
let bytes = read(set);
let net = wai_ops::lower::parse_whm1(&bytes).unwrap_or_else(|r| panic!("{set}: {r:?}"));
assert!(net.layers.iter().any(|l| matches!(l, N::ConvT { .. } | N::Conv { .. } | N::Igdn { .. })), "{set}: no layer of tag 0, 1 or 2");
assert!(net.shift < 128 && net.gf < 128, "{set}: a header shift of 128 or more");
for l in &net.layers {
if let N::ConvT { bias, .. } | N::Conv { bias, .. } = l {
assert!(bias.iter().all(|b| b.unsigned_abs() < i64::MAX as u64), "{set}: a bias of magnitude 2^63 - 1 or more");
}
}
let g = wai_ops::lower::whm1(&bytes, true).unwrap_or_else(|r| panic!("{set}: does not lower: {r:?}"));
assert!(wai::int_ops::load(&g).is_ok(), "{set}: does not load under acc64");
}
let (mut decoded, mut refused) = (0, 0);
for (set, (at, p)) in &all {
let gs = IntModel::from_bytes(&read(set)).unwrap();
let Ok(v) = wai::int_wire::parse_wis1(p) else {
refused += 1;
continue;
};
let ops = int_splat::ops::decode_typed(&gs, &stream(&v)).map(|d| d.attr).map_err(code_of);
let direct = int_splat::legacy::decode_typed(&gs, &stream(&v)).map(|d| d.attr).map_err(code_of);
assert!(ops == direct, "{at}: the decode through the lowering differs from the direct decode");
if ops.is_ok() { decoded += 1 } else { refused += 1 }
}
assert_eq!(sets.len(), 1, "the committed splat sets");
assert!(decoded >= 2 && refused >= 11, "the committed splat payloads, at least those of this revision: {decoded} decoded, {refused} refused");
}