open-standards/wai/extensions/integer-operators.md
Transaction Science 9751a06676
Some checks failed
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 15s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 18s
wai-app and Create / wai-app · does this change touch it (push) Successful in 15s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 32s
wai cross-architecture byte-equality / does this change touch it (push) Successful in 11s
wai-fleet service / does this change touch it (push) Successful in 11s
wai-audit collector and player integration / does this change touch it (push) Successful in 15s
WAI operation catalogue / does this change touch it (push) Successful in 15s
wai-key-proxy / does this change touch it (push) Successful in 14s
wai-ops integer operators / does this change touch it (push) Successful in 14s
wai-ledger service / does this change touch it (push) Successful in 18s
wai-quantum studio and desktop / does this change touch it (push) Successful in 14s
wai-quantum for JavaScript / does this change touch it (push) Successful in 16s
wai-sink-android native sink library / does this change touch it (push) Successful in 16s
wai-sink-swift native sink package / does this change touch it (push) Successful in 11s
WAI Studio / does this change touch it (push) Successful in 12s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai cross-architecture byte-equality / windows-x86_64 (push) Successful in 1m7s
wai cross-architecture byte-equality / macos-arm64 (reference) (push) Successful in 24s
wai-ops integer operators / macos-arm64 (push) Successful in 57s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 7s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 7s
wai cross-architecture byte-equality / linux-x86_64 (push) Successful in 1m2s
wai-ops integer operators / wasm32 under Node (push) Successful in 31s
wai-ops integer operators / independent checker (Python) (push) Successful in 19s
wai-ops integer operators / linux-x86_64 (push) Successful in 1m12s
wai-ops integer operators / wasm32-wasip2 (wasmtime) (push) Successful in 1m7s
wai-key-proxy / proxy · linux (push) Successful in 1m34s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Has been cancelled
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · macos (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m0s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m21s
wai cross-architecture byte-equality / linux-aarch64 (push) Successful in 1m18s
wai-ops integer operators / linux-aarch64 (push) Successful in 1m58s
wai: a refinement conditioned on side information both ends hold, wai.image.int_cond_refine
Registers wai.image.int_cond_refine, refinement-only and StandardDefined, with the
staged form conditioned residual and the payload WCP1 (staged-delivery §6.2,
Appendix B). A layer's next stage is the base prediction (the stage below, or its
up2 at the output's size), plus a correction an integer network carried in the
payload computes from it and the side planes refines.side names, clamped, plus a
WIR1 residual decoded over that prediction. The network is one WHG1 graph lowered
by wcp1/1 under acc64, so every sink holding the same planes computes the same
bytes. Over rendered content the side planes are G-buffers carried as exact
objects of their own, or the frame the sink renders itself from a
wai.splat.int_codec object at a pinned pose with wai-splat-render:i1; with no side
plane over a lower-resolution base the layer is a dual stream's detail.

staged-delivery §3.1 reads refines.side (content hashes, or render entries with a
splat-exact §2 pose); §4.1 step 4 refuses a plane the sink does not hold
(side-absent) or that is not exact (side-not-exact), two reasons that join §4.2's
closed list and the stage claim's. A WAI2 refinement component carries no side
(refinement-side); a packer refuses a WAI1 layer that carries one. SPEC §5's
Refinement capabilities and Base admission rows, §7.1, integer-operators §11 and
jwp-receipts' session rule follow.

wai-rs: staged::wcp1 (parse, cost, lowering, graph decode, the loop-by-loop
arithmetic the encoder holds the graph to, encoder), staged::side, StageSink
add_side, add_render and set_macs_limit, the WCP1 framing check,
RoleError::RefinementSide, AppliedForm::Conditioned. staged-conformance/cond/
holds its cases (exact planes, the dual stream, both in one layer, a rendered
plane, planes absent and not exact, geometry, admission, side and WCP1 faults,
cost limits) and cond/wai2/; wai_staged_vectors (now with splat_exact) writes and
replays them, and staged_verify.py replays them with its own WCP1 parser, network
loop, sink, PNG decode and splat renderer. The two Rust API breaks are
acknowledged in the change's entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 17:50:44 -05:00

1740 lines
157 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# WAI Extension: Integer Operators (`wai-ops/1`)
> Status: Draft. This extension defines `wai-ops/1`, the integer operator set through which every integer-exact WAI capability decodes. It defines:
> - the operators, with their exact integer arithmetic;
> - the tensor range rules;
> - two accumulator profiles, `acc64` and `acc32`;
> - the static bounds rules that decide which profile a parameter set meets, and the **bounds certificate** that records the result;
> - the accelerator subset `wai-ops/1-npu`;
> - the `WHG1` graph form;
> - how a capability declares the profile it needs;
> - how the set relates to the determinism tiers;
> - the canonical lowerings of the existing integer network forms to `WHG1`;
> - the conformance that an operator implementation, a parameter set, a sink and a certifier must each pass.
>
> **What a reader can build from this text.** With the payload extensions it cites, an implementation of this text decodes `wai.video.int_motion` and its frame form `wai.video.int_motion.frame`, `wai.audio.int_codec`, `wai.splat.int_codec`, `wai.neural.int_synth`, `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.neural.int_mlicpp`, `wai.{image,video,audio,splat}.int_refine` and `wai.image.int_cond_refine`, and reproduces the integers of `wai.image.jpegai`'s integer stages: the symbols and scale indices it entropy-decodes, and what its integer networks give for their inputs (§13.6). 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` is informative (§13.5).
>
> Reference implementation: the `wai-ops` crate (`wai/wai-ops`): parse, lowering, interpreter, stage operators and bounds. Tool: `wai-verify bounds`. Conformance: `wai/ops-conformance/`. An operator implementation of `wai-ops/1-i32` for GPUs, at both profiles, is `wai/wai-ops-gpu` (WGSL); [determinism-tiers](determinism-tiers.md) §5 lists the adapters on which it has reproduced the vectors. An operator implementation of `wai-ops/1-npu` for neural processors is `wai/wai-ops-npu`; §9.5 lists the devices on which it has reproduced the vectors. 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`, `wai.audio.int_codec`, `wai.neural.int_hyper`, `wai.video.int_hyper` and `wai.neural.int_mlicpp`, and the integer stages of `wai.image.jpegai` (§13.6): its scale path and entropy decodes on both of its reference decode paths, and its integer networks on its exact decoder; and, through their stage operators alone with no graph (§13.5), `wai.video.int_motion`, the P-frames of `wai.video.int_hyper`, and `wai.{image,video,audio,splat}.int_refine`; `wai.image.int_cond_refine`, through one graph its payload carries, lowered by `wcp1/1` ([staged-delivery](staged-delivery.md) Appendix B.6), and the stage operators of `WIR1` for its residual; and their frame forms ([frame-stream](frame-stream.md)), `wai.video.int_motion.frame` through the stage operators, and `wai.video.int_hyper.frame` through `whm1/1` for its keyframes and the stage operators for its other frames. The integer renderer of `wai.world.model` renders through it too, by `world/1` (§13.5), and stays informative. 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.
## 1. Scope
**Conformance targets.** This extension states requirements for four things.
- **An operator implementation**: a library, a set of kernels, a shader set or a toolchain back end. It conforms to a profile (§7) on a fabric when it reproduces every per-operator vector (§14.1) and every graph vector (§14.2) tagged with that profile, refusals included.
- **A parameter set**: the networks a capability's decode runs. It conforms to its capability when its bounds certificate (§8) meets the capability's declared profile (§11).
- **A sink decoding a capability that §13 lowers.** It runs that capability's graphs through an operator implementation of a profile that the set's certificate meets, and it reproduces the capability's conformance corpora.
- **A certifier**: a program that computes bounds certificates (§8). It conforms when it reproduces every certificate of §14.3 byte for byte, and the load verdict of every graph vector.
**Out of scope**
- Floating-point stages.
- How a sink schedules work across threads or devices. §6 says what an implementation may reorder.
- How a sink reports or attests the fabric that ran. That belongs to [determinism-tiers](determinism-tiers.md).
- Fixed generative algorithms that are not networks.
**Relation to the payload extensions.** [integer-payloads](integer-payloads.md) and [staged-delivery](staged-delivery.md) keep their byte layouts, check orders and refusal codes. This extension:
- defines the arithmetic of the networks those payloads decode against, which integer-payloads §9 used to leave to the reference decoders;
- names the entropy and sample-domain arithmetic they already define, adds the modes other capabilities need, and states the width each operation needs.
## 2. Notation and integer functions
All values are mathematical integers. No formula in this extension has an implicit width, and none wraps or saturates unless it says so.
| Notation | Meaning |
|---|---|
| `⌊a / b⌋` | Floor division, for `b > 0`: the largest integer `q` with `q·b ≤ a`. |
| `⌈a / b⌉` | `−⌊−a / b⌋`, for `b > 0`. |
| `trunc(a / b)` | Division rounded toward zero, for `b ≠ 0`. |
| `a mod b` | `a − b·⌊a / b⌋`, for `b > 0`. It always lies in `[0, b)`. |
| `a OR b`, `a XOR b` | Bitwise, on non-negative integers. |
| `sgn(a)` | −1, 0 or 1. |
| `clamp(v, lo, hi)` | `min(max(v, lo), hi)`, for `lo ≤ hi`. |
| `isqrt(n)` | For `n ≥ 0`, the largest `r ≥ 0` with `r² ≤ n`. |
| `isqrt_ceil(n)` | For `n ≥ 0`, the smallest `r ≥ 0` with `r² ≥ n`. |
| `wrap16(a)` | `((a + 2^15) mod 2^16) − 2^15`. |
| *fits iN* | `−2^(N−1) ≤ v ≤ 2^(N−1) − 1`. |
| `bits([lo, hi])` | The smallest `N ≥ 1` such that both `lo` and `hi` fit iN. |
**Rounding a scaled product.** For an integer `p` and a shift `s ≥ 0`:
- `R_floor(p, s) = ⌊p / 2^s⌋`.
- `R_half_up(p, s) = ⌊(p + 2^(s−1)) / 2^s⌋` for `s ≥ 1`, and `p` for `s = 0`. A tie rounds toward +∞, whatever the sign of `p`.
- `R_half_even(p, s)` is `p` for `s = 0`. Otherwise, let `q = ⌊p / 2^s⌋`, `r = p − q·2^s` and `h = 2^(s−1)`. The result is `q + 1` if `r > h`, or if `r = h` and `q` is odd. Otherwise it is `q`.
All three are non-decreasing in `p`. §8 relies on this.
**Exponential of a non-positive Q16 value.** `exp_neg_q16(x)` is defined for `x ≤ 0`. It forms these values, in this order, and the names in brackets are the intermediates §5.7 classifies:
- `u = −x` (`exp.negx`);
- `n = ⌊u / 45426⌋`, and `g = 45426·n` (`exp.nl`);
- `r = x + g`, so `r ∈ (−45426, 0]`;
- `t = r + 88670`, so `t ∈ [43245, 88670]`;
- `t2 = t²` (`exp.t2`), and `q = ⌊t2 / 2^16⌋`;
- `c = 23495·q` (`exp.c`), and `y = ⌊c / 2^16⌋ + 22544`.
The result is 0 if `n ≥ 63`, and `⌊y / 2^n⌋` otherwise. It lies in `[0, 65553]`, and `exp_neg_q16(0) = 65553`.
**Q16 reciprocal square root.** `rsqrt_q16(v)` is defined for `v ≥ 0`. Let `r = isqrt(v)`. The result is 0 if `r = 0`. Otherwise it is `⌊D / r⌋`, where the dividend `D = 2^32` is the intermediate `rsq.num`. Every operator that calls it has `v ≥ 1`, so `r ≥ 1` and `D` is always formed.
## 3. Tensors and ranges
**Shape and layout.** A tensor has a shape `[C, H, W]` with every dimension at least 1. Its elements are indexed `(c, y, x)` at the row-major position `(c·H + y)·W + x` (CHW). A one-dimensional signal of `T` samples per channel is `[C, 1, T]`. An operator's output shape is a function of its input shapes, its parameters and, for `crop`, the graph's scalars (§5). Shapes are planned before any element is computed (§4).
**Caps.** No tensor exceeds `2^26` elements, and no size an operator forms (a padded side; a transposed convolution's uncropped side, and its uncropped output of `cout` planes; an upsampled side) exceeds `2^26`. Sizes are formed in 64-bit arithmetic with overflow checked, and a size past 64 bits is past the cap. A size past the cap is refused with `too_large` before anything is allocated. These are the caps of integer-payloads §7.
**Range and policy.** Every tensor of a graph has a **policy**.
| Policy | Meaning |
|---|---|
| `derived` | The tensor's range is whatever §8 derives from its producer. Nothing is enforced at run time. |
| `clamp` | The tensor declares a range `[lo, hi]`. Its producer's results are replaced by `clamp(v, lo, hi)`, and that is part of the operator's output. |
| `check` | The tensor declares a range `[lo, hi]`. When the tensor has been produced, the decode is **refused** (`field_range`, §4) if any element lies outside `[lo, hi]`. Nothing that reads the tensor runs. |
A graph input has the policy `check` or `clamp`. `lo ≤ hi`, and both fit i64.
**Exponent.** A tensor MAY carry an exponent `e`, an integer in −127..=127. It means that the integer `q` represents `q·2^−e`. The arithmetic never reads an exponent. Three load rules use one, and a graph that breaks one is refused (`model_mismatch`):
- for `add` and `concat`, take the operator's input tensors and its output tensor: either none of them declares an exponent, or every one of them declares one and all are equal;
- for `chunk_add`, the same rule, over its input tensor and its output tensor;
- for `lut16`, the rule of §5.6.
No other operator's exponents are constrained. A tensor record encodes "no exponent" as −128 (§10.2).
**Storage.** An implementation stores a tensor in any integer type that holds the tensor's certified range (§8), including an unsigned type or an offset encoding. Before an element enters a product that a sum of §5 accumulates, it is decoded to its value: every term of a certified sum is the product of its operands' values. A multiply-accumulate over offset-encoded operands, such as `Σ w·(x + z)` followed by subtracting `z·Σ w`, forms partial sums that the certificate does not bound, and an implementation MUST NOT use one.
## 4. Exactness, evaluation order and refusal
**Exactness.** Each operator's output is an exact integer function of its input tensors, its parameters and the graph's scalars (§10). An implementation MUST produce exactly those integers. Evaluation order is not part of any operator's meaning. Where a definition writes a sum, the sum is the exact integer, whatever the order of its terms. §6 lists what an implementation may do with that freedom.
**Refusal points.** A decode through a graph stops at the first of these that applies, and computes nothing after it.
1. **Load**, once per graph (§10.5). The graph is malformed. Or its channel counts do not plan, or, when it declares its input sizes, its shapes do not plan on those sizes (§5). Or a size passes the caps. Or its certificate does not meet the profile its capability declares (§11). The code is `model_mismatch`, or `too_large` for a size.
2. **Run**, at each decode, in this order:
1. each input, one at a time in input-table order, state inputs included: its channel count against its tensor record, and `H ≥ 1` and `W ≥ 1` (`model_mismatch`); then its size, `H ≤ max_h` and `W ≤ max_w` when the input declares them, and `C·H·W ≤ max_elems` (§10.1) (`too_large`). An input's values are not read in this step;
2. each scalar against its declared range (`field_range`);
3. the **run-time plan**: every operator in graph order, on the sizes and scalar values of this decode. For each operator, in this order:
- every size it forms before its shape rule is read is checked against `2^26` (`too_large`). Those sizes are `conv`'s padded sides `H + 2ph` and `W + 2pw`; `conv_transpose`'s uncropped sides and its uncropped output of `cout` planes (§5.3); and the output sides of `pixel_shuffle`, `pad_rep` and `subpos_merge`;
- then its shape rule (§5) (`geometry_mismatch`);
- then each output tensor: `C·H·W ≤ max_elems` (`too_large`).
This step catches what load-time planning cannot: a divisibility that holds at the declared sizes but not at a smaller one; a kernel larger than a small padded input; inputs of `add`, `concat`, `crop_to` or `subpos_merge` whose sizes disagree; a `crop` whose scalars do not fit; a state whose shape no longer matches (§10.3);
4. evaluation. It starts with the inputs, in input-table order, state inputs included: each input's policy applies to it, so a `clamp` input is clamped, and an element of a `check` input outside its range refuses the decode (`field_range`). Then the operators run one by one in graph order. When a `check` tensor has been produced, an element outside its range refuses the decode (`field_range`).
A capability whose payload extension uses fewer codes reports its own in place of these. `wai.neural.int_synth` reports `payload_malformed` for every refusal (integer-payloads §8); `wai.neural.int_mlicpp` reports `payload_malformed` for a refusal of its payload or of a value its decode forms, and `model_mismatch` for a bundle that does not fit (integer-payloads §14).
A capability's driver MAY plan its graphs before it decodes any stream, at the point where its payload extension plans its networks. It then reports its extension's codes, in its extension's order, and step 3 never refuses in a decode that reaches it.
An implementation with overflow checks and one without compute the same integers and refuse the same inputs. Overflow is excluded by §8 before any decode starts.
## 5. Graph operators
A graph operator reads input tensors and writes output tensors. Its parameters are pinned with the parameter set (§10). Each subsection gives the operator's:
- **shape**: the shape rule, which §4 applies at load and at run time;
- **definition**: the exact output;
- **intermediates**: the named values that §7 and §8 classify.
| code | operator | in → out | family | in `wai-ops/1-i32` (§9.1) | in `wai-ops/1-npu` (§9.2) |
|---|---|---|---|---|---|
| `0x01` | `conv` | 1 → 1 | linear | yes | yes; groups 1 or depthwise |
| `0x02` | `rescale` | 1 → 1 | requantisation | yes | yes |
| `0x03` | `chscale` | 1 → 1 | requantisation | yes | flagged |
| `0x04` | `lut16` | 1 → 1 | table | yes | flagged |
| `0x05` | `add` | 2 → 1 | elementwise | yes | yes |
| `0x06` | `concat` | n ≥ 2 → 1 | movement | yes | yes |
| `0x07` | `chunk_add` | 1 → 1 | elementwise | yes | yes |
| `0x08` | `split` | 1 → 1 | movement | yes | yes |
| `0x09` | `pixel_shuffle` | 1 → 1 | movement | yes | yes |
| `0x0A` | `pixel_unshuffle` | 1 → 1 | movement | yes | yes |
| `0x0B` | `clamp` | 1 → 1 | elementwise | yes | yes |
| `0x0C` | `relu` | 1 → 1 | elementwise | yes | yes |
| `0x0D` | `leaky` | 1 → 1 | elementwise | yes | yes |
| `0x0E` | `pad_rep` | 1 → 1 | movement | yes | yes |
| `0x0F` | `crop_to` | 2 → 1 | movement | yes | yes |
| `0x10` | `mul` | 2 → 1 | elementwise | yes | yes |
| `0x11` | `conv_transpose` | 1 → 1 | linear | yes | yes (§9.3) |
| `0x12` | `igdn` | 1 → 1 | normalisation | yes | no |
| `0x13` | `abs` | 1 → 1 | elementwise | yes | yes |
| `0x14` | `crop` | 1 → 1 | movement | yes | yes, with constant operands only |
| `0x15` | `subpos_split` | 1 → 4 | movement | yes | yes |
| `0x16` | `subpos_merge` | 4 → 1 | movement | yes | yes |
| `0x17` | `fill` | 1 → 1 | movement | yes | yes |
| `0x18` | `layernorm_c` | 1 → 1 | normalisation | no | no |
| `0x19` | `l2norm` | 1 → 1 | normalisation | no | no |
| `0x1A` | `softmax` | 1 → 1 | normalisation | no | no |
| `0x1B` | `sigmoid` | 1 → 1 | scalar function | no | no |
| `0x1C` | `elu` | 1 → 1 | scalar function | no | no |
| `0x1D` | `gram` | 2 → 1 | contraction | yes | no |
| `0x1E` | `apply` | 2 → 1 | contraction | yes | no |
### 5.1 Requantisation (`rq`)
`rq` is the epilogue of `conv`, `conv_transpose`, `rescale`, `mul`, `gram` and `apply`, and `chscale` (§5.4) follows the same steps. It is not an operator in its own right. For output channel `o`, its parameters are:
- `mult[o]`: an integer that fits i64, or **absent**, which means 1;
- `shift[o]`: an integer in `0..=127`;
- `bias[o]`: an integer that fits i64, or **absent**, which means 0. A bias has a **placement**, either `pre` or `post`;
- `round`: one of `floor`, `half_up` or `half_even`.
A `mult` or `shift` given as one value is that value for every channel.
```
rq_o(v): t = v + (bias placed pre ? bias[o] : 0) rq.t
p = t · mult[o] rq.p
q = R_round(p, shift[o]) (§2) rq.q
y = q + (bias placed post ? bias[o] : 0) rq.y
```
`y` is the operator's result. The output tensor's policy (§3) then applies to it.
**Intermediates**
- `rq.t`, class `acc`.
- `rq.p`, class `product`, whose operands are `t` and `mult[o]`. The values formed are `p` and, when `round` is not `floor` and `s ≥ 1`, `p + 2^(s−1)`. Its interval covers both (§8.3). With `mult` absent, `p = t`.
- `rq.q`, class `acc`: the rounded value before a `post` bias.
- `rq.y`, class `acc`.
An implementation forms each of these in a width that holds its certified interval (§6). In particular, it MUST NOT narrow `q` to a width that does not hold `rq.q` before it adds a `post` bias, even where `y` would fit.
*Informative.* The registered capabilities use these settings (§13):
- no multiplier, one shift, `pre` bias, `floor`: each layer of `int_synth`;
- no multiplier, one shift per channel, `pre` bias, `floor`: the three convolutions of the `wai.image.jpegai` scale path;
- no multiplier, shift 0, `pre` bias: the per-channel gain of that scale path;
- one multiplier per channel, one shift, `post` bias, `floor`: the convolutions of `WHM1` and `WAM1`;
- one multiplier, one shift, `post` bias, `floor`: the convolutions of the `wai.image.jpegai` integer networks;
- no multiplier, shift 16, no bias, `floor`: their Q16 products;
- no multiplier, shift 0, a `post` bias of `2^16`: their gates' `f + 1`;
- one multiplier `2^16`, shift 0, no bias: their hyper decoders' input;
- one multiplier, one shift, no bias, `half_up`: the RGB8 output step (multiplier 255) and the PCM output step (multiplier 32767);
- no multiplier, shift 16, no bias, `half_up`: `gram` and `apply` in channel attention;
- one multiplier per channel, shift 16, no bias, `half_up`: the attention temperature;
- one multiplier, shift 0, `post` bias: the denormalisation of `wai.image.jpegai`.
Changing any one setting changes decoded output.
### 5.2 `conv` (`0x01`)
**Parameters**
- `cout ≥ 1`;
- kernel `kh, kw ≥ 1`;
- stride `sh, sw ≥ 1`;
- zero padding `ph, pw ≥ 0`;
- `groups ≥ 1`;
- weights `Wt[cout][cin/groups][kh][kw]`;
- an `rq` over `cout` channels.
**Shape.** The input is `[cin, H, W]`. Both `cin` and `cout` are multiples of `groups`, with `H + 2ph ≥ kh` and `W + 2pw ≥ kw`.
- `Ho = ⌊(H + 2ph − kh) / sh⌋ + 1`, and `Wo` likewise.
- The output is `[cout, Ho, Wo]`.
**Definition.** Let `cg = cin / groups` and `og = cout / groups`. Let `X̃[c][u][v]` be `X[c][u][v]` for `0 ≤ u < H` and `0 ≤ v < W`, and 0 elsewhere.
```
acc[o][y][x] = Σ_{ci < cg} Σ_{i < kh} Σ_{j < kw} Wt[o][ci][i][j] · X̃[⌊o/og⌋·cg + ci][y·sh + i − ph][x·sw + j − pw]
out[o][y][x] = rq_o(acc[o][y][x])
```
A depthwise convolution is `groups = cin = cout`. There is no dilation.
**Intermediates:** `conv.term` (each product) and `conv.acc` (every partial sum), both class `acc`; then `rq`.
### 5.3 `conv_transpose` (`0x11`)
**Parameters**
- `cout ≥ 1`;
- kernel `kh, kw ≥ 1`;
- stride `sh, sw ≥ 1`;
- padding `ph, pw ≥ 0`;
- output padding `oph, opw ≥ 0`;
- weights `Wt[cin][cout][kh][kw]`;
- an `rq` over `cout` channels.
`groups` is 1.
**Shape.** The input is `[cin, H, W]`.
- `Ho = (H − 1)·sh + kh + oph − 2·ph` must be at least 1, and `Wo` likewise.
- The uncropped sides `uh = (H − 1)·sh + kh + oph` and `uw = (W − 1)·sw + kw + opw`, and the uncropped output of `cout·uh·uw` elements, are each within `2^26` (§3). They are checked before `Ho` and `Wo` (§4).
- The output is `[cout, Ho, Wo]`.
**Definition**
```
acc[o][y][x] = Σ Wt[ci][o][i][j] · X[ci][ty / sh][tx / sw]
over ci < cin, i < kh, j < kw with
ty = y + ph − i ≥ 0, ty mod sh = 0, ty / sh < H,
tx = x + pw − j ≥ 0, tx mod sw = 0, tx / sw < W
out[o][y][x] = rq_o(acc[o][y][x])
```
*Informative.* This is the same set of products as the scatter form: add every `Wt[ci][o][i][j]·X[ci][u][v]` into an uncropped plane at `(u·sh + i, v·sw + j)`, then keep rows `[ph, ph + Ho)` and columns `[pw, pw + Wo)`. Output padding extends only the bottom and right edges, and it need not be smaller than the stride. A position that no tap reaches holds `rq_o(0)`. A one-dimensional transposed convolution is `kh = 1`, `sh = 1`, `ph = 0`, `oph = 0` over `[cin, 1, T]`.
**Intermediates:** `convT.term` (each product) and `convT.acc` (every partial sum), both class `acc`; then `rq`.
### 5.4 `rescale` (`0x02`), `chscale` (`0x03`) and `mul` (`0x10`)
**`rescale`.** The input is `[C, H, W]`, with an `rq` over `C` channels.
- `out[c][y][x] = rq_c(in[c][y][x])`.
- With `mult` and `bias` set, this is also an affine map: an added constant, a scaled offset, or a rounding shift.
- **Intermediates:** those of `rq`.
**`chscale`.** The parameters are:
- a scalar index `k` (§10);
- a table `M[n_rows][C]` of integers, each of which fits i64;
- one shift `s` in `0..=127`;
- a `round`.
There is no bias.
- `out[c][y][x] = R_round(in[c][y][x] · M[r][c], s)`, where `r` is the value of scalar `k`.
- The scalar's declared range lies within `[0, n_rows − 1]`; this is a load check.
- **Intermediates:** those of `rq`, with `t` the input value, `M[r][c]` the multiplier and no bias, so `y = q`.
**`mul`.** The inputs are `a [C, H, W]` and `b`, which is either `[C, H, W]` or `[1, H, W]`. There is an `rq` over `C` channels with no bias.
- `out[c][y][x] = rq_c(a[c][y][x] · b[c′][y][x])`, where `c′ = c` if `b` has `C` channels, and 0 otherwise.
- **Intermediates:** `mul.p` (each product `a·b`), class `acc`; then `rq`, with `t = mul.p`.
### 5.5 Elementwise: `add` (`0x05`), `chunk_add` (`0x07`), `clamp` (`0x0B`), `relu` (`0x0C`), `leaky` (`0x0D`), `abs` (`0x13`)
| operator | shape | definition |
|---|---|---|
| `add` | `a` and `b` have the same shape | `a + b` |
| `chunk_add` | `[C, H, W]` with `C` even → `[C/2, H, W]` | `out[c] = in[c] + in[c + C/2]` |
| `clamp(lo, hi)` | same shape; `lo ≤ hi`, both fit i64 | `clamp(v, lo, hi)` |
| `relu` | same shape | `max(v, 0)` |
| `leaky(a, f)` | same shape; `a` fits i64, `f` in `0..=63` | `v` if `v ≥ 0`, else `⌊v·a / 2^f⌋` |
| `abs` | same shape | `|v|` |
A one-sided `clamp` uses `−2^63` or `2^63 − 1` for its open side. A saturating sum is `add` with a `clamp` policy on its output.
**Intermediates:** `leaky.p` (each product `v·a`, formed for negative `v` only), class `acc`. The sums of `add` and `chunk_add` are their results.
### 5.6 `lut16` (`0x04`)
**Parameters**
- a table `T` of 65,536 integers, each of which fits i16, with `T[0] = 0`;
- exponents `e_in` and `e_out`, each an integer in −127..=127 or **none** (encoded −128, §10.4).
**Shape.** The output has the input's shape. Two load checks apply (`model_mismatch`):
- The input's certified range (§8) lies within `[−32767, 32767]`.
- `e_in` and the input tensor's exponent are both none or are equal; so are `e_out` and the output tensor's exponent.
**Definition.** `out = T[v + 32768]`.
The table is parameter data, pinned with the set. The function a table stands for, and how its entries were computed, are not part of this extension.
### 5.7 Normalisation: `igdn` (`0x12`), `layernorm_c` (`0x18`), `l2norm` (`0x19`), `softmax` (`0x1A`)
**`igdn`.**
- **Parameters:** shifts `gf` and `frac`, each in `0..=127`; `γ[C][C]`, row-major with row `i` the output channel and column `j` the input channel; `β[C]`.
- **Shape:** `[C, H, W]` → `[C, H, W]`.
- **Definition:** at each position `(y, x)`, with `x_j = in[j][y][x]`:
```
N_i = β_i + Σ_j ⌊ γ[i][j] · x_j² / 2^gf ⌋ (each term is floored, then the terms are summed)
r_i = isqrt(max(0, N_i))
out[i][y][x] = ⌊ x_i · r_i / 2^frac ⌋
```
Flooring the sum once, instead of each term, is a different function.
- **Intermediates:**
- `igdn.x2` (each `x_j²`), class `wide`;
- `igdn.gx2` (each `γ[i][j]·x_j²`, before the floor), class `product`, whose operands are `x_j²` and `γ[i][j]`;
- `igdn.term` (each floored term), class `wide`;
- `igdn.N` (every partial sum of `N_i`, with `β_i` or without), class `wide`;
- `igdn.r`, class `acc`;
- `igdn.xr` (each `x_i·r_i`), class `wide`.
**`layernorm_c`.**
- **Parameters:** `ε`, an integer in `1..=2^64 − 1`; `γ[C]`; `β[C]`.
- **Shape:** `[C, H, W]` → `[C, H, W]`.
- **Definition:** at each position, over that position's `C` channel values `x_c`:
```
S = Σ_c x_c ln.S
M = S + ⌊C/2⌋·sgn(S) ln.m
m = trunc(M / C)
d_c = x_c − m ln.d
T = Σ_c d_c² ln.T
V = ⌊T / C⌋
U = V + ε ln.v
k = rsqrt_q16(U) (§2) rsq.num, ln.k
n_c = ⌊d_c · k / 2^16⌋ ln.dk (d_c·k), ln.n
out[c] = ⌊n_c · γ[c] / 2^16⌋ + β[c] ln.ng (n_c·γ[c])
```
- **Intermediates:** `ln.S` and `ln.T` (every partial sum), `ln.m`, `ln.v`, `ln.dk` and `ln.ng`, class `wide`; `ln.d`, `rsq.num`, `ln.k` and `ln.n`, class `acc`.
**`l2norm`.**
- **Parameters:** `ε`, an integer in `1..=2^64 − 1`.
- **Shape:** unchanged.
- **Definition:** for each channel `c`, over all `H·W` positions of that channel:
```
T = Σ_{y,x} v²
U = T + ε
k = rsqrt_q16(U)
out = ⌊v · k / 2^16⌋
```
- **Intermediates:** `l2.T` (every partial sum of `T`, and `U`) and `l2.vk` (each `v·k`), class `wide`; `rsq.num` and `l2.k`, class `acc`.
**`softmax`.**
- **Parameters:** none.
- **Shape:** unchanged. It normalises along `W`, separately for each `(c, y)`.
- **Definition:**
```
m = max_x v_x
e_x = exp_neg_q16(v_x − m) (§2)
Σ = Σ_x e_x (at least 65553)
out_x = ⌊(e_x · 2^16 + ⌊Σ / 2⌋) / Σ⌋
```
Each output lies in `[0, 2^16]`. The outputs of a row need not sum to `2^16`.
- **Intermediates:** `sm.diff` (each `v_x − m`), `exp.negx`, `exp.nl`, `exp.t2`, `exp.c` (§2), `sm.sum` (every partial sum of `Σ`) and `sm.num` (each `e_x·2^16 + ⌊Σ/2⌋`), all class `acc`.
### 5.8 Scalar functions: `sigmoid` (`0x1B`), `elu` (`0x1C`)
**`sigmoid`.** No parameters; the shape is unchanged.
- If `v ≥ 0`: let `e = exp_neg_q16(−v)`; the output is `⌊2^32 / (2^16 + e)⌋`.
- If `v < 0`: let `e = exp_neg_q16(v)`; the output is `⌊e · 2^16 / (2^16 + e)⌋`.
- In both cases the argument of `exp_neg_q16` is `−|v|`, so `exp.negx = |v|`.
- The output lies in `[0, 2^16]`.
- **Intermediates:** `exp.negx`, `exp.nl`, `exp.t2`, `exp.c`, and `sig.num` (the dividend: `2^32`, or `e·2^16`), all class `acc`.
**`elu(α)`.** `α` fits i64; the shape is unchanged.
- If `v > 0`, the output is `v`.
- Otherwise, the output is `⌊α · (exp_neg_q16(v) − 2^16) / 2^16⌋`.
- At `v = 0` this gives `⌊17·α / 2^16⌋`.
- **Intermediates:** `exp.negx`, `exp.nl`, `exp.t2` and `exp.c`, class `acc`; `elu.p` (each `α·(e − 2^16)`), class `wide`.
### 5.9 Contractions: `gram` (`0x1D`), `apply` (`0x1E`)
Both take `groups ≥ 1` and an `rq` with no multiplier, no bias and one shift.
**`gram`.** The inputs are `A` and `B`, both `[C, H, W]`. `C` is a multiple of `groups`, and `d = C / groups`. The output is `[C, 1, d]`:
```
out[g·d + i][0][j] = R_round( Σ_{y,x} A[g·d + i][y][x] · B[g·d + j][y][x], s )
```
**`apply`.** The inputs are `A [C, 1, d]` and `V [C, H, W]`, with `C = groups·d`. The output is `[C, H, W]`:
```
out[g·d + i][y][x] = R_round( Σ_{j<d} A[g·d + i][0][j] · V[g·d + j][y][x], s )
```
**Intermediates:** `gram.term` and `apply.term` (each product), and `gram.acc` and `apply.acc` (every partial sum), all class `acc`; then `rq`, with `t` the sum.
### 5.10 Data movement
| operator | parameters | shape | definition |
|---|---|---|---|
| `concat` (`0x06`) | none | inputs share `H` and `W` → channels summed | the inputs' channels, in input order |
| `split` (`0x08`) | `start`, `count ≥ 1` | `start + count ≤ C` → `[count, H, W]` | `out[c] = in[start + c]` |
| `pixel_shuffle` (`0x09`) | `r ≥ 1` | `C` a multiple of `r²` → `[C/r², H·r, W·r]` | `out[c][h·r + i][w·r + j] = in[c·r² + i·r + j][h][w]` |
| `pixel_unshuffle` (`0x0A`) | `r ≥ 1` | `H` and `W` multiples of `r` → `[C·r², H/r, W/r]` | `out[c·r² + i·r + j][h][w] = in[c][h·r + i][w·r + j]` |
| `pad_rep` (`0x0E`) | `m ≥ 1` | → `[C, ⌈H/m⌉·m, ⌈W/m⌉·m]` | `out[c][y][x] = in[c][min(y, H−1)][min(x, W−1)]` |
| `crop_to` (`0x0F`) | none; the inputs are `in` and `ref` | `ref`'s `H′ ≤ H` and `W′ ≤ W` → `[C, H′, W′]` | `out[c][y][x] = in[c][y][x]`; only `ref`'s shape is read |
| `crop` (`0x14`) | `y0`, `x0`, `h`, `w`, each a constant or a scalar | `y0, x0 ≥ 0`, `h, w ≥ 1`, `y0 + h ≤ H`, `x0 + w ≤ W` → `[C, h, w]` | `out[c][y][x] = in[c][y0 + y][x0 + x]` |
| `subpos_split` (`0x15`) | `order[4]`, a permutation of `(0,0), (0,1), (1,0), (1,1)` | `H` and `W` even → four outputs `[C, H/2, W/2]` | `out_k[c][y][x] = in[c][2y + a_k][2x + b_k]`, with `(a_k, b_k) = order[k]` |
| `subpos_merge` (`0x16`) | `order[4]` | four inputs `[C, H, W]` → `[C, 2H, 2W]` | the inverse of `subpos_split` with the same order |
| `fill` (`0x17`) | a value, constant or scalar; fits i64 | the output's channels come from its tensor record; `H` and `W` come from the input | every element is the value |
`pixel_shuffle` takes channel `c·r² + i·r + j` to row offset `i` and column offset `j` of output channel `c`.
A constant operand is a `u32` (§10.4), and a scalar operand is any value of its scalar's range, negative ones included. A `crop` with a scalar operand is planned once the scalar is known (§4, run step 3), and one that does not fit, a negative start among them, is refused with `geometry_mismatch`.
At load, when the inputs declare their sizes (§10.5 step 7), each side of a `crop` is planned on the smallest start and the largest length within the input. Take one side, of input size `S`:
- the start `s0` is the constant, or 0 for a scalar start, whatever the scalar's range;
- the length is the constant, or `min(hi, S − s0)` for a scalar length, with `hi` the top of its scalar's range;
- the shape rule then applies to `s0` and that length, and a side that fails it is refused (`model_mismatch`).
## 5E. Stage operators
A stage operator runs in a capability's **driver**, outside any graph. It works on streams, symbols and samples, and each has a fixed width (§7.4). The payload extensions keep the text that already defines four of them:
- `rans64_decode` is defined in integer-payloads §2;
- `mc_predict` in integer-payloads §3;
- `up2_predict` and `rate_predict` in staged-delivery Appendix A.4.
This section names those four and adds the modes other capabilities need. It also defines the other nine, which no payload extension defines today.
| # | operator | definition | width |
|---|---|---|---|
| E1 | `rans64_decode` | integer-payloads §2, with the modes below | `u64` state |
| E2 | `dequantize` | `sym·2^f + base` | 128-bit, or 64-bit checked |
| E3 | `bucket` | scale to table index, rule `ceil` or `floor` | comparisons |
| E4 | `coded` | `v > thr` | comparisons |
| E5 | `scale_index` | `clamp(R_half_up(v, s), 0, n − 1)` | `i32` |
| E6 | `block_stat` | a rounded block sum, broadcast over its block | `i32` |
| E7 | `band_lookup` | count the edges, then look up the table | comparisons |
| E8 | `mans_decode` | below | `u64` window, `u8` states |
| E9 | `residual_step` | `clamp(pred + r·q, lo, hi)` | `i64`, or `i32` by identity |
| E10 | `mc_predict` | integer-payloads §3 | `i32` |
| E11 | `up2_predict` | staged-delivery A.4 | `i32` |
| E12 | `rate_predict` | staged-delivery A.4, with `rdiv` | `i64` |
| E13 | `coding_order` | `raster` or `checkerboard` | indices |
**Conditions and their refusals.** Each operator below states the conditions its operands, modes and tables meet. An operand, mode or table that breaks one is refused before any symbol or element is decoded, with these codes:
- `field_range` for E2's `f` and value, as E2 states;
- `geometry_mismatch` when the `prev` that E11 or E12 reads does not have the shape staged-delivery A.4 step 1 requires, or when a size or rate that shape is computed from (`d0`, `d1`, `rate_num`, `rate_den`) is 0;
- `model_mismatch` for every other condition, the checks of E1's and E8's tables included.
The refusals that arise while a stream is decoded are E1's and E8's own, and they come after these.
**E1 `rans64_decode` modes.** A capability fixes one value for each of the three modes (§11). A symbol is decoded against a table `cdf` of length `n` whose **offset** is `o`: integer-payloads §2 defines both, and `o` is added to every value the table decodes.
- **Table rule.**
- `t4097` is integer-payloads §2: `3 ≤ n ≤ 4097`, `cdf[0] = 0`, `cdf[n−1] = 65536`, strictly increasing.
- `t2` is the same except that `2 ≤ n ≤ 65537`. With `n = 2`, every symbol is the escape.
- Before any symbol is decoded, every table is checked against the table rule, and its offset `o` must fit i32. Under `strict`, `lo ≤ hi`. Each symbol's table must be one of the tables given.
- **Escape rule.**
- `lenient` is integer-payloads §2. It reads one 4-bit count and then continuation groups while the count group just read is 15 and the stream has not run out, at most 8 of them. It clamps the count to 8, accepts a non-minimal count, and saturates the value and the symbol to `i32`. The `count` value groups that follow are read whatever the stream's state.
- `strict(lo, hi)`, with `lo ≤ hi` both fitting i32, reads one 4-bit count and **no continuation groups**:
1. `count = bits(4)`;
2. if `count ≤ 8`, `raw = Σ_{j<count} bits(4)·2^(4j)`, reading the groups for `j = 0, 1, …` in turn; if `count > 8`, no group is read;
3. the value is `−⌊raw/2⌋ − 1` if `raw` is odd and `⌊raw/2⌋ + (n − 2)` if it is even, and the symbol is `value + o`. Nothing is saturated.
- **Reads after a stream runs out.** Reads go on: integer-payloads §2 defines renormalisation to leave the state as it stands, and `bits` reads from that state. Whether the decode is refused for it is the end rule's.
- **Checks on one symbol under `strict`.** When a symbol's reads are done, its refusals are checked in this order, and the first that applies refuses the decode:
1. under the `canonical` end rule, the stream ran out at any point during this symbol's decode, in its table step or in any `bits` (`stream_exhausted`);
2. for an escape, `count > 8` (`stream_noncanonical`);
3. for an escape, `count ≥ 1` and the group read last (`j = count − 1`) is 0, so the encoding is not minimal (`stream_noncanonical`);
4. for every symbol, escaped or not, a symbol outside `[lo, hi]` (`field_range`).
- **End rule.**
- `canonical` is integer-payloads §2: a decode stops at the first symbol during whose decode the stream runs out (`stream_exhausted`), and a stream must end canonically after its last symbol (`stream_noncanonical`).
- `final_only` tests whether the stream has run out once, after its last symbol (`stream_exhausted`), and has no canonical-end rule. Symbols decoded after the stream ran out are the integers §2 gives from the state as it stands, and the decode is refused at the end. A refusal that a symbol's own checks give before the end comes first.
**E2 `dequantize(sym, f, base)`.**
- `f` is at most 62; a larger value is refused with `field_range`.
- The value is `sym·2^f + base`. It is refused (`field_range`) if it does not fit i64.
- The value then enters a graph input, whose `check` range applies.
**E3 `bucket(σ, e, rule)`.** Comparisons only.
- **`ceil`.** It takes `N ≥ 1` edges `e_0 … e_{N−1}`.
- Let `σ′ = max(σ, e_0)`. The index is `b = (N − 1) − #{ i < N − 1 : σ′ ≤ e_i }`.
- It lies in `[0, N − 1]`, for any edges.
- `e_{N−1}` is never compared.
- **`floor`.** It takes `N − 1 ≥ 1` edges `e_1 … e_{N−1}`, non-decreasing: both are conditions (`model_mismatch`).
- The index is `b = #{ i : e_i ≤ σ }`.
- It lies in `[0, N − 1]`.
**E4 `coded(v, thr)`** is `v > thr`. A position that is not coded consumes no symbol.
**E5 `scale_index(v, s, n)`** is `clamp(R_half_up(v, s), 0, n − 1)`, for `s` in `0..=127` and `n ≥ 1`.
**E6 `block_stat(x; b, s, lo, hi)`**, for `b ≥ 1`, `s` in `0..=127` and `lo ≤ hi`. For each channel, the plane is tiled by `b × b` blocks from `(0, 0)`. The last row and column of blocks may be partial.
- `S` is the sum over a block's positions that lie in the plane.
- `v = clamp(R_half_up(S, s), lo, hi)`.
- Every position of the block takes `v`.
- `s` is applied whatever the number of positions in the block.
**E7 `band_lookup(v, e[0..m), table[rows][m+1], row)`**, for `row` in `[0, rows)`, with row `row` of the table holding `m + 1` entries.
- `band = #{ k : v ≥ e_k }`.
- The result is `table[row][band]`.
**E8 `mans_decode`.** This is a two-state masked table-ANS decode over a backward 64-bit bit window. Its tables and operands are checked before the window starts, and one that fails is refused with `model_mismatch`. The checks are the conditions of *Building the latent tables* and of *The row table* below; every bound in `[0, 255]`; for each span, a mask and a list of distributions of one length, each distribution `σ` naming both a row of `T_y` and a bound; and one length for each row. **Every refusal after those checks is `stream_exhausted`**: the window would need bits or bytes that the stream does not hold. A capability that reports one code for every refusal reports that code instead (§4).
*Memory and window.*
- `M` is 8 zero bytes followed by the stream's `L` bytes.
- `LE64(i)` is the little-endian 64-bit value at `M[i .. i + 8]`.
- The window holds an index `p`, a 64-bit head `h`, a bit position `b`, and two 8-bit states `s1` and `s2`.
- The stream is decoded as one thread: the window starts at the end of the memory.
*Window operations.*
```
init: p = L; h = LE64(p); if h = 0, refuse (no sentinel bit)
b = the index of h's highest set bit; h = h − 2^b
s1 = read(8); s2 = read(8); flush
read(k): if k > b, refuse
b = b − k; r = ⌊h / 2^b⌋; h = h − r·2^b; return r
flush: d = 7 XOR ⌊b / 8⌋; if d > p, refuse
p = p − d; h = LE64(p) mod 2^(56 + (b mod 8)); b = 56 + (b mod 8)
```
*Latent decode (`sgm`).* For each distribution `σ`, the table `T_y[σ][state]` holds 256 entries of 32 bits, and `B_y[σ]` is an **unsigned** 8-bit bound: `β ∈ [0, 255]`.
- `in(which, i)`:
- `t = T_y[σ_i][s_which]`;
- `s_which = (⌊t/2^16⌋ OR read(⌊t/2^24⌋)) mod 2^8`;
- `sym_i = wrap16(t mod 2^16)`.
- `out(i)`: let `β = B_y[σ_i]`. If `sym_i + β = 0`:
- `f = read(1)`;
- `v = read(f = 1 ? 3 : 16)`;
- `g = v mod 2`;
- `sym_i = wrap16(⌊v/2⌋ + β)` if `g = 0`, and `wrap16(−⌊v/2⌋ − β)` if `g = 1`;
- then `flush`.
- A **span** of `L′` positions, with a mask, is decoded in groups of four positions `i … i+3`:
- `in(s1, i)`, `in(s2, i+1)`, `in(s1, i+2)`, `in(s2, i+3)`, each only where the mask is set;
- then `flush`;
- then `out` for each of the four, only where the mask is set.
- Then the remainder:
- if `L′` mod 4 ≥ 2: position `i` (`s1`), then `i+1` (`s2`), each as `in` then `out` where the mask is set; then `flush`;
- if `L′` is odd: position `i` (`s1`) as `in` then `out` where the mask is set; then `flush`.
- An unmasked position is 0 and reads nothing. It keeps its place in the `s1`/`s2` alternation.
- The window and the states carry across spans.
*Building the latent tables.* The rows `P[σ]`, for `σ = 0 … R − 1`, all have one width `w ≥ 1`. Each holds non-negative integers that sum to exactly 256. These are conditions (`model_mismatch`). For row `P = P[σ]`, let `C[0] = 0` and `C[k+1] = C[k] + P[k]`. Then:
- `F[k] = C[k] − ⌊C[k]/2⌋`;
- `G[k] = ⌊C[k]/2⌋ + 128`;
- `sym(k) = k/2` if `k` is even, and `−(k + 1)/2` if it is odd.
For each column `k` in order, while `C[k] < 256`:
- every slot `u ∈ [F[k], F[k+1])` gets symbol `sym(k)` and `next = P[k] + (u − F[k])`;
- every slot `u ∈ [G[k], G[k+1])` gets symbol `sym(k)` and `next = P[k] + (F[k+1] − F[k]) + (u − G[k])`.
Every slot is written, and `next ∈ [1, 511]`. Each slot `u` then gets the entry `nb·2^24 + base·2^16 + (sym mod 2^16)`, where:
- `nb` is the number of doublings that bring `next` to at least 256, so `nb ≤ 8`;
- `base = (next·2^nb) XOR 256`, so `base < 256`.
*Hyper-latent decode (`fac`).*
- **The preprocessing table.** `Zp[i]` is defined for `i` from 511 down to 1. Keep a counter `nb`, starting at 0. At each `i`, first add 1 to `nb` once if `i·2^nb < 256`. Then `Zp[i] = ((i·2^nb) XOR 256) + 256·nb`.
- **The row table.** A row is 63 cumulative counts `0 ≤ c_0 ≤ … ≤ c_62 ≤ 255`, a condition (`model_mismatch`); let `c_{−1} = 0`.
- For each symbol `a < 63`, let `k = c_a − c_{a−1}`. Each slot `j ∈ [c_{a−1}, c_a)` gets `T_z[j] = Zp[k + j − c_{a−1}]·2^16 + a`.
- Then `T_z[255] = Zp[1]·2^16 + 63`.
- Slots that the row does not write keep the previous row's entries. `T_z` starts at all zeros.
- **`in(which, i)`:** `t = T_z[s_which]`; `s_which = (⌊t/2^16⌋ OR read(⌊t/2^24⌋)) mod 2^8`; `sym_i = t mod 2^8`.
- **`out(i)`:** if `sym_i = 63`, then `sym_i = read(6)`.
- **A row of `L′` symbols** is decoded in groups of four:
- `in(s1, i)`, `in(s2, i+1)`, `in(s1, i+2)`, `in(s2, i+3)`;
- then `out` for each of the four;
- then `flush`.
- **The remainder of a row:**
- if `L′` mod 4 ≥ 2: `in(s1, i)`, `out(i)`, `in(s2, i+1)`, `out(i+1)`;
- if `L′` is odd: `in(s1, i)`, `out(i)`.
- Each row ends with one `flush`.
- Rows are decoded in order, each with its own row table, and the window, the states and `T_z` carry across them.
How a capability divides its streams into rows and spans, and which tables each uses, is the capability's (for `wai.image.jpegai`, §13.6.1).
**E9 `residual_step(pred, r, q, lo, hi)`** is `clamp(pred + r·q, lo, hi)`, where `lo < hi`, `pred ∈ [lo, hi]`, `q ∈ [1, 65535]` and `r` fits i32.
- **The 32-bit form (identity).** With `R = ⌊(hi − lo)/q⌋ + 1`, the step equals `clamp(pred + clamp(r, −R, R)·q, lo, hi)`.
- *Proof.* If `r > R`, then `r·q > R·q > hi − lo`, so both forms give `hi`. The case `r < −R` is symmetric.
- *Width.* Every value this form forms fits i32 when `hi − lo ≤ 65535` and both `lo − 131070` and `hi + 131070` fit i32. Then `R·q ≤ (hi − lo) + q ≤ 131070`, so `pred + clamp(r, −R, R)·q` lies in `[lo − 131070, hi + 131070]`. The ranges §13.5 registers, `[0, 255]` and `[−32768, 32767]`, meet both conditions. Near the i32 limits the second fails: with `lo = 2^31 − 65536`, `hi = 2^31 − 1`, `q = 65535`, `pred = hi` and `r = 2`, the form forms `hi + 2·65535`. A fabric forms such a step in a wider width.
**E10 `mc_predict`** and **E11 `up2_predict`** are integer-payloads §3 (whole-pel and half-pel) and staged-delivery A.4 (`Up2`). E10 is for a pixel `(y, x)` of the frame, a channel `c` of 0, 1 or 2, and `mv_precision` 1 or 2. Every sum they form is at most `4·255 + 2`. Written out, for a channel `ch` of a picture `P` of `h × w` samples, with the half-pel average
`avg(a, b) = (a + b + 1) >> 1` and `avg4(a, b, c, d) = (a + b + c + d + 2) >> 2`:
- **E10.** `p(Y, X) = P[clamp(Y, 0, h − 1), clamp(X, 0, w − 1)]`, and the vector `(dy, dx)` is in units of `1/mv_precision` pel.
- Whole-pel: `p(y + dy, x + dx)`.
- Half-pel: `iy = y + (dy >> 1)`, `fy = dy & 1`, `ix = x + (dx >> 1)`, `fx = dx & 1`, the shifts arithmetic. The prediction is `p(iy, ix)` when `fy = fx = 0`; `avg(p(iy, ix), p(iy, ix + 1))` when only `fx` is 1; `avg(p(iy, ix), p(iy + 1, ix))` when only `fy` is 1; and `avg4(p(iy, ix), p(iy, ix + 1), p(iy + 1, ix), p(iy + 1, ix + 1))` when both are.
- **E11.** `P` is the picture below, `h′ × w′`, and `(y, x)` is an element of the picture at twice its resolution, with `y < 2h′` and `x < 2w′`. `p(Y, X) = P[min(Y, h′ − 1), min(X, w′ − 1)]`, `iy = y >> 1`, `fy = y & 1`, `ix = x >> 1`, `fx = x & 1`, and the prediction is E10's half-pel rule on those four values.
**E12 `rate_predict`** is staged-delivery A.4 (`Rate`), with `rdiv(n, d) = 0` for `d < 1`, and `sgn(n)·⌊(2|n| + d) / (2d)⌋` otherwise. Written out, for `n′ ≥ 1` source samples `S[0..n′)` of one channel and output sample `i`: `s = i·rate_den`, `lo = s div rate_num`, `ρ = s mod rate_num`, `hi = min(lo + 1, n′ − 1)`, and the prediction is `rdiv(S[lo]·rate_num + (S[hi] − S[lo])·ρ, rate_num)`; every `i < ⌊n′·rate_num/rate_den⌋` has `lo < n′`. Its samples are i16, and `rate_num` and `rate_den` are `u32` fields of at least 1, so `ρ < rate_num < 2^32` and `|b − a| ≤ 65535`. The products `a·rate_num` and `(b − a)·ρ` then have magnitudes below `2^48`, the sum `n` below `2^47`, and rdiv's `2|n| + d` below `2^49`. Every value it forms fits i50, so it needs 64-bit arithmetic.
**E13 `coding_order`.**
- `raster`: channel, then row, then column.
- `checkerboard(anchor)`: the elements with `(row + col) mod 2 = 0` when `anchor` is set, and the rest otherwise, each in raster order.
A capability MAY define other orders in its own extension.
## 6. What an implementation may change
Because every operator is an exact integer function (§4), an implementation MAY do any of the following.
- **Partition outputs** across threads, lanes, tiles or devices. A tensor MAY be split across several storage bindings.
- **Reorder and regroup the terms of any sum.** This includes splitting a sum across workers and adding the partial sums in any order. A term that is zero MAY be skipped, and a zero term MAY be added.
- **Compute a value in any width that holds every value it forms.** The certificate (§8) gives the interval of every intermediate. In particular:
- **Chunked 32-bit partial sums.** Let `P` be the largest magnitude in the operator's term interval (§8.3). Then `K_c = ⌊(2^31 − 1) / P⌋`, or unbounded when `P = 0`, and any partial sum of at most `K_c` terms fits i32. The certificate records `K_c` as `k_c` (§8.5). So an implementation MAY form i32 partial sums of at most `K_c` terms, widen them, and add.
- **Dot-product instructions.** An instruction that adds `n` products into one i32 lane leaves that lane holding `n·j` products after `j` instructions. The lane widens before `n·j` would exceed `K_c`. Each operand must also fit the instruction's operand type. For an instruction over 8-bit operands, that means the weights fit i8 and the input tensor's certified interval fits i8; the certificate records both widths per node (§8.5).
- **Bias.** A `pre` bias joins the wide sum, unless the certificate's partial-sum interval (§8.3), which includes the bias, fits the narrower width.
- **Narrowing a product.** `rq.p` MAY be formed in 64 bits wherever its certified interval fits i64.
- **Run later operators speculatively.** A refused decode outputs nothing.
An implementation MUST NOT:
- use floating-point arithmetic for any value of a graph or stage operator;
- use an instruction whose result the instruction set leaves to the implementation. On Wasm, that means no relaxed SIMD;
- depend on a value wrapping. A certified graph forms no value outside the width it is formed in, so none wraps;
- form a sum over offset-encoded operands (§3, *Storage*).
On a fabric whose shift instruction takes its amount modulo the register width, an implementation handles every shift of §5 that reaches or passes that width explicitly. A shift of a 32-bit value by 32 or more is an example.
On WebAssembly, a sink checks the first two of these "MUST NOT"s on the module it runs: a module held at the integer level of the WAISM™ profile ([waism](waism.md) §6) contains no floating-point instruction and no relaxed vector instruction, and a run of it that traps or meets a limit outputs nothing.
## 7. Profiles
### 7.1 Width classes
Every intermediate that §5 names has a class. Every tensor element, every producer result (§8.1), every graph input and every scalar is class `tensor`.
| class | `acc64` | `acc32` |
|---|---|---|
| `tensor` | fits i64 | fits i32 |
| `acc` | fits i64 | fits i32 |
| `wide` | fits i128 | fits i32 |
| `product` | fits i128 | fits i64, formed from two operands that each fit i32 |
There are two `product` intermediates. `rq.p` has the operands `rq.t` and the multiplier (for `chscale`, the input and `M[r][c]`). `igdn.gx2` has the operands `igdn.x2` and `γ[i][j]`. Under `acc32` both operands are certified to fit i32: `rq.t` and `igdn.x2` by their own classes, and the multiplier and `γ` by §7.2's parameter conditions.
### 7.2 The profiles
- **`acc64`.** Every intermediate and producer result of every operator fits its class's `acc64` width.
- This is the reference profile for CPU and Wasm.
- It needs a 128-bit value only where a `wide` or `product` interval passes 64 bits.
- **`acc32`.** Three conditions hold:
1. every intermediate and producer result fits its class's `acc32` width, and every graph input's and scalar's declared range fits i32;
2. only graph inputs have the policy `check`;
3. the parameters meet these conditions:
| operator | parameter condition under `acc32` | key (§8.5) |
|---|---|---|
| every `rq` (`conv`, `conv_transpose`, `rescale`, `mul`, `gram`, `apply`) | each multiplier fits i32 | `rq.mult` |
| | each shift is at most 63 | `rq.shift` |
| | each `post` bias fits i32 | `rq.post_bias` |
| `conv`, `conv_transpose` | each weight fits i32 | `weights` |
| `chscale` | each entry of `M` fits i32 | `chscale.m` |
| | `s` is at most 63 | `chscale.s` |
| `igdn` | each `γ` fits i32 | `igdn.gamma` |
| | `gf` and `frac` are each at most 63 | `igdn.shift` |
| `leaky` | `a` fits i32 | `leaky.a` |
A `pre` bias needs no condition: it lies in `rq.t`'s interval, which already fits i32.
- Every accumulator then fits a 32-bit register.
- The only 64-bit values are the two `product` intermediates, each the exact product of two 32-bit operands.
- An `acc32` graph needs no refusal inside the graph, so a fabric that cannot stop a computation partway through can run it.
- A set that meets `acc32` also meets `acc64`.
Which profile a parameter set meets is decided by its certificate (§8). Two sets for one capability can meet different profiles.
### 7.3 Operators that never meet `acc32`, and fabrics (informative)
Five operators form values whose intervals §8.3 fixes above i32 whatever their input or parameters:
- `softmax`, `sigmoid` and `elu`, through `exp.t2` (up to `88670² ≈ 2^32.9`) and `exp.c` (up to `23495·119970 ≈ 2^31.4`);
- `sigmoid`, through `sig.num` (up to `65553·2^16`);
- `layernorm_c` and `l2norm`, through `rsq.num` (`2^32`), and `l2norm` through `l2.vk` (`±2^32`).
So a graph that uses any of them meets `acc64` at best. The other 25 graph operators form `wai-ops/1-i32` (§9.1): the operators a 32-bit fabric must implement to run every graph that can meet `acc32`.
- **CPU and Wasm** run `acc64` natively: Wasm has 64-bit integers, and a 128-bit value is formed from 64-bit halves. Relaxed SIMD is excluded (§6).
- **A 32-bit integer shading language** runs `acc32`. It forms each `product` intermediate from 16-bit halves in unsigned 32-bit arithmetic, which wraps by definition in every such language and so survives translation between them unchanged. It then rounds, shifts and adds the `post` bias on that two-word value before it narrows the result to i32. Every signed value of an `acc32` graph provably fits i32, so no signed overflow reaches a translated shader.
- **A fabric that emulates 64-bit and 128-bit arithmetic exactly** is an `acc64` implementation on that fabric.
### 7.4 Stage operators
Stage operators (§5E) run in the driver at the widths listed in §5E.
- `rans64_decode` and `mans_decode` need a 64-bit state or window.
- `dequantize` needs a 128-bit intermediate, or checked 64-bit arithmetic.
- `rate_predict` forms values of magnitude up to `2^49` (E12), so it needs 64-bit arithmetic.
- `scale_index`, `block_stat`, `band_lookup`, `coded`, `bucket`, `mc_predict`, `up2_predict` and `coding_order` fit i32, and so does `residual_step` in its 32-bit form, under E9's conditions.
A fabric that runs a stage operator MUST reproduce its widths exactly, for example by emulating 64-bit values as pairs of 32-bit values.
## 8. Static bounds and the bounds certificate
### 8.1 Intervals
The bounds pass gives every tensor, scalar and named intermediate an integer interval `[lo, hi]`.
- It visits operators in graph order (§10.3).
- Its only inputs are the graph's declared input and scalar ranges, the input sizes the graph declares (§10.2), the parameters, and the policies.
- Where a rule of §8.3 is per output channel, the pass applies it to each channel and records the **hull** over channels: the smallest interval that contains every channel's interval. Every recorded intermediate interval, and every tensor interval, is such a hull.
It never reads a payload. Its result is a function of the graph's bytes alone.
**Arithmetic, and saturation.** Let `sat(v) = clamp(v, −2^255, 2^255)`.
- The pass applies `sat` to the result of every primitive operation it performs: `+`, `−`, `×`, `⌊a / b⌋` (including a shift), `isqrt`, `isqrt_ceil`, `min`, `max`, `|·|` and negation.
- A sum that a rule writes with `Σ` is a left fold of binary additions in the order its index runs, starting from the addend the rule writes first, with `sat` after each addition. A partial-sum interval (§8.2) adds terms of one sign only, so for it the order makes no difference.
- A product or sum of more than two operands that a rule writes without `Σ` is a left fold in the order written, with `sat` after each operation, and `R_round` is evaluated as §2 writes it. Otherwise, a product of three factors, one of which can be 0 while the other two pass `2^255`, would be saturated in one order and not in another. No rule of §8.3 forms one, and this order is fixed for any rule that does.
- Every declared range, parameter, constant and size lies in `[−2^255, 2^255]` before any operation.
*Width.* Every operand of a primitive operation therefore lies in `[−2^255, 2^255]`. A product then lies in `[−2^510, 2^510]`, a sum or difference in `[−2^256, 2^256]`, and every other result within its operands' range. A signed 512-bit integer holds `[−2^511, 2^511 − 1]`, so an implementation computes each result exactly in 512 bits and then saturates it. The width is needed: `igdn.x2` on an input whose interval reaches `±2^255` forms `2^510`, which no narrower signed integer holds.
*Soundness.* A node during whose computation any primitive result was changed by `sat` is **saturated**. It fits no profile, whatever intervals it records (§8.5). An interval of a node that is not saturated is exact for its rule. A saturated node fails the verdict, so the intervals downstream of it never decide a verdict.
**Producer results and tensor intervals.** A graph input's interval is its declared range, and a scalar's is its declared range. For any other tensor, let `[a, b]` be the interval its producer's rule gives: the tensor's **producer result**. It is classed `tensor` (§7.1), because it exists before the policy applies. The tensor's own interval is then:
- `derived`: `[a, b]`;
- `clamp [lo, hi]`: `[clamp(a, lo, hi), clamp(b, lo, hi)]`;
- `check [lo, hi]`: `[max(a, lo), min(b, hi)]`. If that interval is empty, every input would be refused, and the graph is refused at load (`model_mismatch`).
**Sizes.** A reduction whose length depends on the picture uses the largest length the graph's declared input sizes allow, with shapes planned on those sizes. Every shape rule of §5 is non-decreasing in its input sizes. This covers `gram` (`L`, the largest `H·W` of its inputs), `l2norm` (`L`, the largest `H·W` of its input) and `softmax` (`W_max`, the largest `W` of its input). A graph that contains one of these three operators MUST declare its input sizes (§10.2).
### 8.2 Term and partial-sum intervals
For a weight `w` and an input interval `[l, h]`, the **term interval** is `[min(w·l, w·h), max(w·l, w·h)]`. For a product of two run-time values with intervals `[l1, h1]` and `[l2, h2]`, it is the **corner hull** `[min(C), max(C)]` of the four products `C = {l1·l2, l1·h2, h1·l2, h1·h2}`.
For a set `K` of terms, with a bias `b` (0 when there is none), the **partial-sum interval** is:
```
A(K, b) = [ min(0, b) + Σ_{k∈K} min(0, lo_k), max(0, b) + Σ_{k∈K} max(0, hi_k) ]
```
It contains every sum of every subset of `K`, with or without `b`. So it bounds every partial sum in every order and grouping (§6), the final value, and the final value at a position where padding removes taps.
*Informative: the same integers from a few sums.* Let every term of `K` be a weight's term interval over one input interval `[l, h]` with `l ≤ h`, as for `conv` and for each residue of `conv_transpose` (§8.3). Let `S⁺` and `S⁻` be the sums of the positive and of the negative weights, and `w_min` and `w_max` the least and the greatest weight.
- A weight `w > 0` has the term `[w·l, w·h]`, and adds `w·min(0, l)` to the lower end and `w·max(0, h)` to the upper end. A weight `w < 0` has the term `[w·h, w·l]`, and adds `w·max(0, h)` and `w·min(0, l)`.
- So `A(K, b) = [min(0, b) + min(0, l)·S⁺ + max(0, h)·S⁻, max(0, b) + max(0, h)·S⁺ + min(0, l)·S⁻]`.
- For a fixed end, `w·l` is monotone in `w`, so the hull of the term intervals is the corner hull of `[w_min, w_max]` and `[l, h]`.
- The left fold of §8.1 adds terms of one sign, so its partial sums lie between 0 and its result. When no term and neither end passes `2^255`, nothing saturates, and these are the pass's integers. Otherwise a certifier folds term by term.
A certifier can therefore read each weight once and keep a few sums for each output channel, or for each residue of `conv_transpose`. The reference implementation does this.
### 8.3 Operator bound rules
The interval of each producer result and named intermediate is as follows. `[l, h]` is the input tensor's interval; `X = max(|l|, |h|)`. A rule that names a channel is applied per channel, and §8.1 hulls the results.
| operator | rule |
|---|---|
| `conv` | For output channel `o`, let `K_o` be its `cg·kh·kw` taps, each with its term interval. `conv.term` is the hull of the term intervals of `K_o`. `conv.acc = A(K_o, b_pre[o])`, where `b_pre[o]` is the `pre` bias or 0. `rq.t` is `conv.acc`. Then the `rq` rules. |
| `conv_transpose` | For output channel `o`, `convT.term` is the hull of the term intervals of all `cin·kh·kw` taps of `o`. For each residue `ρ = (ρy, ρx) ∈ [0, sh) × [0, sw)`, `K_ρ` is the taps with `i mod sh = ρy` and `j mod sw = ρx`. `convT.acc` is the hull over `ρ` of `A(K_ρ, b_pre[o])`; one output position reads the taps of one residue only. `rq.t` is `convT.acc`. Then the `rq` rules. |
| `rq.p` | Let `[t_lo, t_hi]` be `rq.t` and `m` the channel's multiplier (1 when absent). Let `P = [min(t_lo·m, t_hi·m), max(t_lo·m, t_hi·m)]`. Let `a = 2^(s−1)` when `round` is `half_up` or `half_even` and `s ≥ 1`, and `a = 0` otherwise. Then `rq.p = [P_lo, P_hi + a]`. |
| `rq.q` | `rq.q = [R(P_lo, s), R(P_hi, s)]`, with `P` from the `rq.p` rule, **without** the addend `a`, and `R` the channel's rounding (§2). `R` is non-decreasing, so this holds every `q`. |
| `rq.y` | `rq.y = [q_lo + b, q_hi + b]`, where `b` is the channel's `post` bias, or 0. The producer result is `rq.y`. |
| `rescale` | `rq.t = [l + b_pre[c], h + b_pre[c]]`. Then the `rq` rules. |
| `chscale` | For channel `c`: `rq.t = [l, h]`; `rq.p` is the hull, over the rows `r` that the scalar's range allows, of the `rq.p` rule with `m = M[r][c]`; `rq.q` is likewise the hull of the `rq.q` rule. `rq.y = rq.q`. |
| `mul` | `mul.p` is the corner hull of `a`'s and `b`'s intervals. `rq.t = mul.p`. Then the `rq` rules. |
| `gram` | `gram.term` is the corner hull of `A`'s and `B`'s intervals. `gram.acc = [L·min(0, term_lo), L·max(0, term_hi)]`. `rq.t = gram.acc`. Then the `rq` rules, with `m = 1`. |
| `apply` | `apply.term` is the corner hull of `A`'s and `V`'s intervals. `apply.acc = [d·min(0, term_lo), d·max(0, term_hi)]`. `rq.t = apply.acc`. Then the `rq` rules, with `m = 1`. |
| `add` | `[la + lb, ha + hb]` |
| `chunk_add` | `[2l, 2h]` |
| `clamp`, `relu`, `abs` | the image of the interval under the map: `[clamp(l, lo, hi), clamp(h, lo, hi)]`; `[max(l, 0), max(h, 0)]`; `|·|` is `[l, h]` if `l ≥ 0`, `[−h, −l]` if `h ≤ 0`, and `[0, max(−l, h)]` otherwise |
| `leaky` | If `l < 0`: `leaky.p` is the corner hull of `[l, min(h, −1)]` and `[a, a]`, and the negative part is `[⌊p_lo / 2^f⌋, ⌊p_hi / 2^f⌋]`. If `l ≥ 0`: `leaky.p = [0, 0]` and there is no negative part. The non-negative part is `[max(l, 0), h]` when `h ≥ 0`. The producer result is the hull of the parts present. |
| `lut16` | `[min, max]` of `T[u + 32768]` over `u ∈ [l, h]` |
| `igdn` | `igdn.x2` is `[0, max(l², h²)]` if `l ≤ 0 ≤ h`, and `[min(l², h²), max(l², h²)]` otherwise. For output channel `i` and input channel `j`: `gx2_ij` is the corner hull of `[γ[i][j], γ[i][j]]` and `igdn.x2`; `term_ij = [⌊gx2_lo / 2^gf⌋, ⌊gx2_hi / 2^gf⌋]`. `igdn.gx2` and `igdn.term` are the hulls of these. `igdn.N = A({term_ij : j < C}, β_i)`. The **final norm** is `Nf_i = [β_i + Σ_j term_ij,lo, β_i + Σ_j term_ij,hi]`, each sum a left fold over `j` from `β_i`; it is not recorded, because `igdn.N` contains it. `igdn.r = [isqrt(max(0, Nf_lo)), isqrt(max(0, Nf_hi))]`, from the final norm, not from `igdn.N`. `igdn.xr` is the corner hull of `[l, h]` and `igdn.r`. The producer result is `[⌊xr_lo / 2^frac⌋, ⌊xr_hi / 2^frac⌋]`. |
| `layernorm_c` | `ln.S = [C·min(0, l), C·max(0, h)]`. `ln.m = [C·l − ⌊C/2⌋, C·h + ⌊C/2⌋]`. The mean lies in `[l, h]`, so `ln.d = [l − h, h − l]`. `ln.T = [0, C·(h − l)²]`. `ln.v = [ε, (h − l)² + ε]`. `rsq.num = [2^32, 2^32]`. `ln.k = [0, ⌊2^32 / r0⌋]`, with `r0 = isqrt(ε)`. `ln.dk = [−D, D]` with `D = min((h − l)·⌊2^32/r0⌋, ⌈isqrt_ceil(C)·2^32·(r0 + 1) / r0⌉)`. `ln.n = [−N, N]` with `N = min(⌊(h − l)·⌊2^32/r0⌋ / 2^16⌋ + 1, N_max)` and `N_max = ⌊isqrt_ceil(C)·2^16·(r0 + 1) / r0⌋ + 1` (lemma L1). For channel `c`, `ln.ng` is the corner hull of `ln.n` and `[γ[c], γ[c]]`, and the producer result is `[⌊ng_lo / 2^16⌋ + β[c], ⌊ng_hi / 2^16⌋ + β[c]]`. |
| `l2norm` | `l2.T = [0, L·X² + ε]`. `rsq.num = [2^32, 2^32]`. `l2.k = [0, ⌊2^32 / isqrt(ε)⌋]`. `l2.vk = [−2^32, 2^32]`, and the producer result is `[−2^16, 2^16]` (lemma L2). |
| `exp_neg_q16` | Given the interval `[u_lo, u_hi]` of `u = −x` (each operator below gives it): `exp.negx = [u_lo, u_hi]`; `exp.nl = [45426·⌊u_lo/45426⌋, 45426·⌊u_hi/45426⌋]`; `exp.t2 = [43245², 88670²]`; `exp.c = [0, 23495·⌊88670²/2^16⌋]`. The result lies in `[0, 65553]`. These four are recorded for every `softmax`, `sigmoid` and `elu`, whatever their input. |
| `softmax` | `sm.diff = [l − h, 0]`. `u = [0, h − l]`, then the `exp_neg_q16` rule. `sm.sum = [0, W_max·65553]`. `sm.num = [0, 65553·2^16 + ⌊W_max·65553 / 2⌋]`. The producer result is `[0, 2^16]`. |
| `sigmoid` | `u` is the image of `[l, h]` under `|·|`, then the `exp_neg_q16` rule. `sig.num = [0, 65553·2^16]`. The producer result is `[0, 2^16]`. |
| `elu` | `u = [max(0, −h), max(0, −l)]`, then the `exp_neg_q16` rule. `elu.p` is the corner hull of `[α, α]` and `[−2^16, 17]`. The producer result is the hull of `[max(l, 1), h]` (when `h > 0`) and `[⌊elu.p_lo / 2^16⌋, ⌊elu.p_hi / 2^16⌋]` (when `l ≤ 0`). |
| movement | The input's interval, for each output; `crop_to`'s is its first input's. `concat` and `subpos_merge` take the hull of their inputs' intervals. `fill` takes `[v, v]` for a constant `v`, or its scalar's range. |
**Lemma L1** (normalised output). Let `ε ≥ 1` and `r0 = isqrt(ε)`. Let `d_c` be the deviations, `T = Σ d_c²`, `V = ⌊T/C⌋`, `r = isqrt(V + ε)` and `k = ⌊2^32 / r⌋`.
- `d_c² ≤ T < C·(V + 1)`, and `(r + 1)² > V + ε ≥ V + 1`.
- So `|d_c| < √C·(r + 1)`.
- So `|d_c·k| < √C·2^32·(1 + 1/r) ≤ isqrt_ceil(C)·2^32·(r0 + 1)/r0`.
- Hence `|n_c| ≤ N_max`.
**Lemma L2** (L2-normalised output). `v² ≤ T < T + ε < (r + 1)²`, so `|v| ≤ r`. Then `|v·k| ≤ r·⌊2^32/r⌋ ≤ 2^32`.
### 8.4 Admissible ranges
The **admissible range** of a tensor `τ` for a profile `P` is computed as follows.
- **Scope.** The **consumers** of `τ` are the operators that read `τ` directly. Operators further downstream do not count.
- **The condition.** For `X ∈ [0, 2^63]`, `C(X)` holds when, with `τ`'s interval replaced by `[−X, X]` wherever a consumer reads it, and every other tensor's interval as the pass computed it, every value that a consumer classes meets `P`'s width for its class. Those values are the consumer's named intermediates and its producer results (§7.1), and none of them may be saturated (§8.1). Parameter conditions (§7.2) and policies are not part of `C`.
- **Search.** Every rule of §8.3 is monotone under inclusion, so `C(X)` implies `C(X′)` for every `X′ ≤ X`. `X*` is the largest `X ∈ [0, 2^63]` for which `C(X)` holds, found by binary search on that interval. The admissible range is `[−X*, min(X*, 2^63 − 1)]`, so it always fits i64.
- **No admissible range.** If `C(0)` fails, `τ` has no admissible range for `P`: no value of `τ` lets its consumers meet `P`. A lowering or exporter that needs one refuses the set (`model_mismatch`).
A consumer whose rule does not depend on `τ`'s interval, such as `fill` reading only a shape, never limits `X*`. An operator whose fixed intervals fail `P` (§7.3) gives no admissible range for that profile.
A lowering (§13) or an exporter uses admissible ranges to choose `check` or `clamp` ranges. The lowerings of §13.2 and §13.3 use their own predicates instead, which the forms they lower fix. Those ranges lie within the admissible ranges for `acc64`.
### 8.5 The certificate
A **bounds certificate** records the bounds pass for one parameter set's graphs. It is **canonical JSON**:
- UTF-8 with no byte-order mark, and no whitespace outside strings. The file does not end with a newline.
- Every string holds printable ASCII (U+0020–U+007E) only, with `"` written `\"` and `\` written `\\`. No other escape occurs. A role or capability name outside that alphabet cannot be certified.
- Object members are in ascending order of their keys' bytes. Every key is ASCII.
- Every integer is a JSON string of decimal digits: no leading zero, a `-` only before a nonzero value, and zero written `"0"`.
- Booleans are `true` and `false`. An absent value is `null`.
- Digests are 64 lowercase hexadecimal digits.
- Arrays are in the order this section gives.
**The members.**
```
certificate = {
"capability": the capability string, or null;
"certificate": "wai-ops/1 bounds";
"graphs": [ graph … ], in the order of the lowering's roles (§13.0), or file order for "whg1";
"lowering": the §13 identifier, or "whg1";
"set_sha256": the digest a pin over the set carries (SPEC §3.1): for a one-file set, the SHA-256 of the
file; for a set of several files, the SHA-256 of its wai.prior.set listing; null when the
graphs were certified on their own;
"verdict": verdict
}
graph = {
"graph_sha256": the SHA-256 of the graph's WHG1 bytes, as lowered (§13.0) when lowered;
"inputs": [ { "hi", "init" (state only), "kind": "data" | "state", "lo", "max_h", "max_w",
"policy": "check" | "clamp", "src" (state only), "tensor" } … ], in input-table order;
"name": the graph's role;
"nodes": [ node … ], in graph order;
"scalars": [ { "hi", "index", "lo" } … ], in scalar-table order
}
node = {
"acc32_params": [ key … ]: the parameter conditions of §7.2 the node fails, in that table's order;
"fits": [ profile … ]: the profiles the node meets, in ascending order ("acc32", "acc64");
"index": the operator's index;
"intermediates": { key: { "bits", "hi", "lo" } … }: exactly the keys of Table 8.5-A for the operator;
"k_c": K_c (§6) as an integer, or "unbounded" when P = 0; present for conv, conv_transpose,
igdn, gram and apply only, over conv.term, convT.term, igdn.term, gram.term and apply.term;
"mac": { "a_bits": [ bits of each input tensor's interval, in input order ],
"w_bits": bits of [min, max] over the weights (conv, conv_transpose) or M (chscale) };
present for conv, conv_transpose, chscale, mul, gram and apply only; "w_bits" for the
first three only;
"npu_flags": [ flag … ]: the node's flags of §9.2, in ascending order;
"op": the operator's name (§5);
"out": [ { "bits", "hi", "lo", "policy": "derived" | "clamp" | "check",
"result": { "bits", "hi", "lo" }, "tensor" } … ], in output order.
"lo" and "hi" are the tensor's interval after its policy; "result" is the producer result;
"saturated": true or false (§8.1)
}
verdict = {
"acc32": true or false; "acc32_first_failure": failure, present when "acc32" is false;
"acc64": true or false; "acc64_first_failure": failure, present when "acc64" is false;
"npu": true or false; "npu_flags": [ flag … ]; "npu_reasons": [ reason … ]
}
failure = { "bits" (interval keys only), "graph": the role, "index", "key", "where": "input" | "scalar" | "node" }
```
`bits` is `bits([lo, hi])` (§2).
**Table 8.5-A. The intermediates each operator records.**
| operator | keys |
|---|---|
| `conv` | `conv.term`, `conv.acc`, `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `conv_transpose` | `convT.term`, `convT.acc`, `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `rescale`, `chscale` | `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `mul` | `mul.p`, `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `gram` | `gram.term`, `gram.acc`, `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `apply` | `apply.term`, `apply.acc`, `rq.t`, `rq.p`, `rq.q`, `rq.y` |
| `leaky` | `leaky.p` |
| `igdn` | `igdn.x2`, `igdn.gx2`, `igdn.term`, `igdn.N`, `igdn.r`, `igdn.xr` |
| `layernorm_c` | `ln.S`, `ln.m`, `ln.d`, `ln.T`, `ln.v`, `rsq.num`, `ln.k`, `ln.dk`, `ln.n`, `ln.ng` |
| `l2norm` | `l2.T`, `rsq.num`, `l2.k`, `l2.vk` |
| `softmax` | `sm.diff`, `exp.negx`, `exp.nl`, `exp.t2`, `exp.c`, `sm.sum`, `sm.num` |
| `sigmoid` | `exp.negx`, `exp.nl`, `exp.t2`, `exp.c`, `sig.num` |
| `elu` | `exp.negx`, `exp.nl`, `exp.t2`, `exp.c`, `elu.p` |
| every other operator | none: the object is `{}` |
The table lists keys in definition order; the JSON writes them in ascending byte order.
**What a node fits.** A node fits profile `P` when it is not saturated, every recorded intermediate and producer result meets `P`'s width for its class (§7.1), and, for `acc32`, its `acc32_params` is empty and none of its output tensors has the policy `check`.
**The verdict.** `acc64` (or `acc32`) is true when every graph's inputs and scalars have declared ranges that meet the profile's `tensor` width, and every node of every graph fits the profile.
**First failure.** When a profile fails, its first failure is found by visiting the graphs in order and, within each, the inputs in table order, then the scalars in table order, then the nodes in graph order. The first input, scalar or node that does not meet the profile is the failure. `where` says which kind it is, and `index` is its position in the input table or the scalar table, or the node's index. Its `key` is:
- for an input or scalar, `"range"`, with `bits` of its declared range;
- for a saturated node, `"saturated"`, with no `bits`;
- otherwise, among the node's intermediates and producer results that fail the width, the one with the most `bits`, a tie going to the smallest key in byte order. A producer result's key is `"out.<k>"`, for output `k`. The failure carries its `bits`;
- otherwise (`acc32` only), the first entry of `acc32_params`, or, failing that, `"policy.check"`, with no `bits`.
**The accelerator verdict.** `npu_reasons` is the set of reasons the graphs are outside `wai-ops/1-npu` (§9.2), deduplicated, in ascending order, from this closed vocabulary:
- `"acc32"`: the `acc32` verdict is false;
- `"op:<name>"`: an operator marked *no* in §5's table, for example `"op:igdn"`;
- `"conv_groups"`: a `conv` whose `groups` is neither 1 nor its `cin`, or whose `cout` differs from `cin` when `groups` is `cin` and above 1;
- `"crop_scalar"`: a `crop` with a scalar operand.
`npu` is true when `npu_reasons` is empty. `npu_flags` is the union of every node's `npu_flags`, deduplicated, in ascending order, whatever `npu` is.
The certificate's identity is the SHA-256 of its bytes. Two conforming certifiers produce identical bytes for one set.
### 8.6 What a sink checks
Before a sink decodes through a parameter set, it MUST establish the set's verdict by the rules of §8.1–§8.5. It need not write the certificate. It MAY cache the verdict by the set's digest.
- A set whose verdict does not meet its capability's declared profile (§11) is refused (`model_mismatch`).
- So is a set whose pin claims a profile that its verdict does not meet.
*Informative: what a sink may keep between decodes.* A sink may keep more than the verdict: the set's graphs as loaded, so that a later decode through the same set skips the lowering and the load, which read the whole set again.
- **The key** is what makes this sound. It is the SHA-256 of the exact bytes the set was lowered from (for a set of several members, of their listing, which is the digest a pin over exactly those members names, [SPEC](../SPEC.md) §3.1), together with the capability, the lowering and the profile the graphs were loaded under. A name, a path or a label does not fix the bytes: keyed by one, a set could decode with the graphs of another set of the same name.
- **Only what loaded.** An entry is made after every load step of §10.5 has passed. A set that is refused is not kept, so it is lowered and loaded again on its next decode and refused again, with the same code.
- **Nothing else moves.** A decode makes every check before the load as before, in the order its payload extension gives ([integer-payloads](integer-payloads.md) §7), and the graphs run as they do after a fresh load, so the output is the same bytes.
- **Memory.** What such entries hold is the set's, the same for every payload, and outside a decode's declared working bytes ([integer-payloads](integer-payloads.md) §7). The sink bounds it with its own budget. The reference implementation keeps them in `int_set_cache`.
## 9. Subsets
### 9.1 `wai-ops/1` and `wai-ops/1-i32`
`wai-ops/1` is the full set of §5 and §5E.
`wai-ops/1-i32` is `wai-ops/1` without `layernorm_c`, `l2norm`, `softmax`, `sigmoid` and `elu`: the operators marked *yes* in §5's `wai-ops/1-i32` column. A graph that meets `acc32` uses only these (§7.3). So an operator implementation for a 32-bit integer fabric, such as a shading language, a DSP or an integer accelerator programmed directly, implements `wai-ops/1-i32`, and it can run every graph that meets `acc32`. Stage operators stay at their own widths (§7.4).
### 9.2 `wai-ops/1-npu`
A graph is in the accelerator subset when all of the following hold:
1. its verdict meets `acc32`, which already excludes a `check` policy inside the graph;
2. it uses only operators marked *yes* or *flagged* in §5's `wai-ops/1-npu` column;
3. every `conv` has `groups = 1`, or is depthwise (`groups = cin = cout`);
4. every `crop` has constant operands. A crop with a scalar operand has a shape that depends on a value.
The certificate records why a graph is outside the subset (`npu_reasons`, §8.5). Membership is decidable from the certificate alone.
**Flags.** A flag does not change membership. It tells a toolchain which feature a node uses that a fixed-function unit may lack, so that it knows where to look, and which rewrite of §9.4 may apply. Each node records its flags:
| flag | when |
|---|---|
| `lut16` | a `lut16`: a 65,536-entry table |
| `chscale` | a `chscale`: a multiplier row chosen at run time |
| `state_add` | an `add` or `chunk_add` with an operand that is a graph input of kind `state` (§10.2), that is, a value carried across decodes |
| `rq_half_even` | an `rq` or `chscale` whose rounding is `half_even` |
| `rq_floor_mult` | an `rq` or `chscale` whose rounding is `floor` and which has a multiplier |
| `rq_post_bias` | an `rq` with a `post` bias |
| `rq_neg_mult` | an `rq` or `chscale` with a negative multiplier |
| `rq_shift_range` | an `rq` or `chscale` with a shift outside `0..=31` |
A node also records, in `mac`, the exact bit width of every multiply operand: each input tensor's interval (`a_bits`) and the weights' (`w_bits`). An integer multiply-accumulate array that takes 8-bit or 16-bit operands needs those widths, not a flag.
**Excluded operators, and why.**
| excluded | reason |
|---|---|
| `igdn`, `layernorm_c`, `l2norm`, `softmax` | Each normalises by a statistic computed at run time. That needs a square root or a division, and a reduction across channels or positions. |
| `sigmoid`, `elu` | Each needs a division, or a polynomial followed by a data-dependent shift. Where the input fits i16, an exporter replaces it with `lut16`. |
| `gram`, `apply` | Each multiplies two run-time tensors and reduces over a length that depends on the picture. |
| a `crop` with a scalar operand | Its output shape depends on a value. |
| a `check` policy inside a graph | An accelerator cannot refuse partway through a graph. §7.2 already excludes it from `acc32`. |
| every stage operator | They run in the driver (§7.4). |
**Outside `wai-ops/1` altogether.** These are absent from both sets, so a capability that needs one cannot be expressed in `wai-ops/1`:
- a learned slope on negative values per channel or per element;
- sampling at data-dependent positions;
- resampling by a non-integer factor;
- dilation;
- shapes that depend on values, other than a `crop` from scalars.
**Membership is not a claim of exactness.** It says that a graph uses only what integer accelerators are built to compute. Whether an accelerator computes it *exactly* is shown only by the vectors of §14, run on that accelerator (§12). §9.5 records the neural processors on which those vectors have run, and what each one's arithmetic does where it is not exact.
### 9.3 Transposed convolution as phase convolutions (informative)
A target without a transposed convolution computes one as `sh·sw` phase convolutions. For residue `ρ = (ρy, ρx)`:
```
Z_ρ[o][u][v] = Σ_{ci} Σ_{t, t′} Wt[ci][o][ρy + sh·t][ρx + sw·t′] · X̃[ci][u − t][v − t′]
out[o][y][x] = rq_o( Z_ρ[o][⌊(y + ph)/sh⌋][⌊(x + pw)/sw⌋] ) with ρ = ((y + ph) mod sh, (x + pw) mod sw)
```
Here `X̃` is zero outside the input. Each phase is a stride-1 convolution with a flipped sub-kernel of at most `⌈kh/sh⌉ × ⌈kw/sw⌉` taps. Missing taps are zero weights, which add zero terms.
- The phases are interleaved by `pixel_shuffle` (when `sh = sw`) and cropped by `ph` and `pw`.
- The integers are those of §5.3, because the products are the same and sums are exact (§4).
- Each phase's partial sums lie in `A(K_ρ, b)`, the interval §8.3 already certifies.
- The vectors of §14.1 include phase-form cases.
### 9.4 Mapping onto a fixed-function unit (informative)
These rewrites are exact: each gives the same integers as the operator it replaces. A toolchain uses them where a flag of §9.2 marks the feature.
- **`floor` to `half_up`, with no multiplier.** For `s ≥ 1`, `R_floor(t + b, s) = R_half_up(t + b − 2^(s−1), s)`. So a `floor` requantisation with no multiplier and `pre` bias `b` is a `half_up` one with `pre` bias `b − 2^(s−1)`. With a multiplier there is no such single-bias form in general, hence `rq_floor_mult`.
- **A negative multiplier.** An `rq` whose channel `o` has `m[o] < 0` equals one with `−m[o]`, channel `o`'s weights negated and its `pre` bias negated. Its `post` bias is unchanged.
- **A shift outside a unit's range.** `R(t·m, s) = R(t·(m·2^k), s + k)` for every `k ≥ 0` and every rounding of §2. A shift below the range rises into it while `m·2^k` still fits the unit's multiplier. A shift above it needs `m` to be a multiple of `2^k`.
- **A `post` bias.** `R(t·m, s) + b = R(t·m + b·2^s, s)` for `floor` and `half_up`, and for `half_even` when `b` is even (an odd `b` changes the parity that breaks a tie). When the identity holds and `m` divides `b·2^s`, the `post` bias is a `pre` bias of `b·2^s / m`.
- **Rounding ties away from zero.** A unit that rounds ties away from zero agrees with `half_up` on non-negative products only. An `rq` whose `rq.p` interval is non-negative maps exactly, and others do not.
- **Depth-to-space order.** `pixel_shuffle` takes channel `c·r² + i·r + j` to output channel `c` (§5.10). A target whose depth-to-space reads channel `(i·r + j)·C′ + c` instead needs that permutation folded into the output channels of the operator that produces the input.
- **Replicate padding.** Not every target pads by replication. `pad_rep` can be built from `crop` and `concat` of edge rows and columns.
- **Storage limits.** A tensor of `2^26` elements at 4 bytes each is 256 MiB, more than some fabrics allow in one storage binding (128 MiB is a common default). An implementation tiles such a tensor across bindings (§6).
### 9.5 `wai-ops/1-npu` on neural processors (informative)
An operator implementation of `wai-ops/1-npu` for neural processors is `wai/wai-ops-npu`. It is a kernel for one compute core of a tile array, compiled for the core by the array's open toolchain. Its host side is built against the device runtime. The interpreter runs each graph and asks the core for the outputs of every operator that computes values (§6):
- `conv` and `conv_transpose`, as sums over taps. The host gathers each position's taps, with zero where padding, or a transposed convolution's stride, leaves no input. The core accumulates them from the `pre` bias and requantises the sum. The operand width is read from the certificate's `mac` record (§8.5). Operands that fit 8 or 16 bits go into the vector unit's 32-bit accumulator lanes, and 32-bit operands into its 64-bit lanes. A sum of more taps than the core takes at once is split, and each part's accumulators carry into the next.
- `rescale`, `chscale` and `mul`, through the vector unit's multiply and its shift-round step.
- `add`, `chunk_add`, `clamp`, `relu`, `abs` and `leaky`.
- `lut16`. The table goes to the core one block of 4,096 entries at a time, with the inputs whose entries lie in that block.
The host keeps the rest: the plan, every check of §4's run, each tensor's range policy, the movement operators (which compute no value), and the state of a sequence. So a device adds no refusal. Every refusal of a graph in the subset is the interpreter's, made on the host before or after the device's work. A graph in the subset has no `check` tensor inside it (§7.2).
**The unit's arithmetic, measured.** The vector unit forms `t·m` exactly in a 64-bit accumulator lane, from two 32-bit operands. Its shift-round step then narrows the lane to 32 bits under one of its own rounding modes. A probe ran `t·m` through that step under each mode at every shift from 0 to 63. The cases were exact ties of §2, a value one unit either side of a tie, random operands at every width, and the extreme operands. It compared each result with §2 wherever the rounded value fits 32 bits:
| the unit's mode | equals | at shifts |
|---|---|---|
| round toward −∞ | `R_floor` | 0 to 59 |
| round to nearest, ties toward +∞ | `R_half_up` | 0 to 59 |
| round to nearest, ties to even | `R_half_even` | 0 to 59 |
| round to nearest, ties away from zero | no rounding of §2 | it agrees with `R_half_up` on non-negative products only (§9.4) |
| round toward +∞; round to nearest, ties toward −∞ | `R_half_up` and `R_floor` respectively, at shift 1 only | |
| toward zero; away from zero; ties toward zero; ties to odd | no rounding of §2 at any shift but 0 | |
- **Shifts of 60 to 63.** At these shifts every mode gave `t·m` shifted *left* by `64 − s` and saturated, on every case. The unit cannot form `R(t·m, s)` for those shifts in one step, and an `acc32` graph may use them (§7.2 allows shifts up to 63). Of the graph vectors in the subset, 36 have such a shift (`chscale` 5, `conv` 6, `conv_transpose` 3, `leaky` 2, `mul` 4, `rescale` 16). The implementation forms those in the core's scalar unit, in 64-bit arithmetic. A kernel that left them to the shift-round step gave other results on all 36.
- **Saturation.** When the rounded value passes 32 bits, the step's saturating mode clamps it, and its non-saturating mode keeps its low 32 bits. Neither is `R(t·m, s)`. `acc32` keeps every `rq.q` within i32 (§7.1), so no graph in the subset reaches this.
- **Accumulator lanes.** A 32-bit lane (8- and 16-bit operands) keeps the low 32 bits of a sum that passes i32, and a 64-bit lane (32-bit operands) wraps at 64 bits. So the narrow paths are exact only where every partial sum fits i32, which `acc32` certifies (`conv.acc`, `convT.acc`). They also need the operands to fit the narrower width (`mac`).
- **Outside the subset.** The corpus has 458 graphs that use only the subset's operators but whose verdict fails `acc32`. Run on the device anyway, 110 of them gave other results than the interpreter's, and 226 operators held a value past the 32-bit words the device takes. Membership is decided by the certificate (§9.2), and these runs show why.
The vector unit's requantisation, with the scalar route for shifts of 60 to 63, agreed with §2 on 786,432 cases. Those cases covered every rounding of §2, every shift from 0 to 63, each with one multiplier and one `post` bias per channel, and every case whose result fits 32 bits.
**Results by neural processor.** A run reproduced a graph vector when every decode gave the committed outputs, or the committed refusal code, and the interpreter's. The vectors are those `npu.jsonl` lists in the subset (§14.5).
| neural processor | toolchain and runtime | result |
|---|---|---|
| PCI vendor 0x1022, device 0x17f0, revision 0x11: its tile array's second generation, firmware 1.1.2.65; one compute core used | the tile array's open toolchain 1.4.3 with its core compiler 22.0.0, device runtime 2.21.75, Linux 7.0 x86_64 | every graph vector in the subset: 733 vectors and 1,005 decodes, 32 of them refusals, with no difference. 302 of the vectors move data only, so the device ran nothing for them. Each operand width and each kind of work ran, counted in the units the core takes at once: 77 sums at 8 bits, 4,160 at 16 bits and 34 at 32 bits, 687 requantisations, 458 elementwise runs and 23 table blocks. Every one of twelve edits of the kernel was caught by the corpus. Measured 2026-10-22, release builds, rustc 1.98.0 |
| any other neural processor, including the neural engine of an arm64 desktop | | not run; nothing is claimed |
*Time and energy* (`ops-npu-bench --secs 3 --rounds 3`, the same host, 2026-10-22). Every device result was first compared with the interpreter's. Each figure below is the range over three windows of 3 s:
| workload | CPU (interpreter) | the device |
|---|---|---|
| one pass over the subset's vectors | 1.12–1.14 ms | 192.6–193.0 ms (2,172 dispatches) |
| an `int_synth` graph (§13.1), `[8, 32, 32]` to `[3, 64, 64]`, three transposed convolutions | 7.22–7.26 ms | 89.1–89.2 ms (778 dispatches) |
- The device path is bound by its dispatches. It uses one core, four work units per dispatch and a host round trip per operator, and the host gathers every tap. No speed claim is made.
- The package's energy counter (powercap `package-0`) is a vendor counter, a reading of class `OnChipCounter` (energy-measurement §2; legacy `HwShunt` is never emitted, §2.6) while it counts. It covers the whole package and everything running on it, including other users' work on that host. Its readings per window were 26.1–29.0 J idle; 90.7–94.6 J with the CPU looping either workload; 74.7–75.4 J with the device looping the vectors; and 76.4–78.1 J with the device looping the synthesis.
- The device runtime also reports a power figure that it calls an estimate. Integrated over a window, that is a model, class `ModelBased`. It gave 2.0–2.6 J per device window. In idle windows it gave 0.3–1.2 J, higher right after device work, so it lags.
- Per pass, the device path costs more energy than the CPU on these workloads. No energy claim is made for the device.
**The Web Neural Network API.** Whether a browser could run the subset exactly through this API was decided from the W3C Candidate Recommendation Draft of 21 October 2026 (`CRD-webnn-20261021`; its sections are cited below as CR §). No implementation of it was run. The API has no integer multiply-accumulate operator, and its requantisation operator rounds in floating point. So the subset is exact there only when each operator is built from the API's integer element-wise operations and reductions. Then every value stays within i32, which `acc32` certifies. The CR states no result for an integer value that leaves its type, and the certificate excludes one.
| operator | through the API | why |
|---|---|---|
| `conv`, `conv_transpose` | not through `conv2d` (CR §8.9.10), `convTranspose2d` (CR §8.9.11), `matmul` (CR §8.9.35) or `gemm` (CR §8.9.24) | Each accepts only `"float32"`, `"float16"` and `"bfloat16"` operands. A float32 sum of integer products is exact only while every partial sum stays within 2^24, and the CR does not fix the precision of intermediate results. Exact by composition: gather the taps (`slice` CR §8.9.47, `reshape` CR §8.9.42, `transpose` CR §8.9.54), then `mul` (CR §8.9.13) and `reduceSum` (CR §8.9.39), all on `"int32"`, the type each is required to support. 8-bit inputs widen first by `cast` (CR §8.9.7), which keeps an in-range integer's value. |
| `rescale`, `chscale`, `mul` (the requantisation of §5.1) | not through `quantizeLinear` (CR §8.9.17) or `dequantizeLinear` (CR §8.9.16) | `quantizeLinear` divides in floating point and rounds by `roundEven`. That gives `R_half_even` only for a power-of-two scale and a value within 2^24, and no form of `R_floor` or `R_half_up`. Its required outputs are 8-bit. `dequantizeLinear` gives a floating-point result. Exact by composition: the API has no shift. Its integer `div` truncates toward zero (CR §8.9.13), so a floor needs a correction built from `lesser` (CR §8.9.14) and `where` (CR §8.9.56). A multiplier forms a product up to 2^62, which needs `"int64"`: the CR allows that type for `mul` but does not require it, and `opSupportLimits()` (CR §8.3.9) says whether an implementation has it. A shift of 63 has no `"int64"` divisor. |
| `lut16` | exact through `gather` (CR §8.9.20) | The table is an `"int32"` input, and the index `v + 32768` is an `"int32"` `add`. Every index lies in the table (§5.6). |
| `add`, `chunk_add` | exact through `add` (CR §8.9.13), with `slice` for `chunk_add` | `"int32"` is a required type. |
| `abs` | exact (CR §8.9.15) | `"int32"` is a required type. |
| `relu`, `clamp` | exact through `max` and `min` (CR §8.9.13) on `"int32"` | `relu` (CR §8.9.40) and `clamp` (CR §8.9.8) allow `"int32"` but require only floating-point types. |
| `leaky` | not through `leakyRelu` (CR §8.9.31) | It takes floating-point operands and a floating-point slope. Exact by composition: `mul`, then a floor division by `2^f` built as for the requantisation, then `where`. |
| the movement operators | exact through `concat` (CR §8.9.9), `slice`, `reshape`, `transpose`, `pad` in its `"edge"` mode (CR §8.9.36) and `expand` (CR §8.9.19) | They move values and compute none. `pad` allows `"int32"` but requires only floating-point types. |
## 10. `WHG1`: the graph form
A `WHG1` file holds one graph. A parameter set that carries graphs names each one by role, in its capability's registration. Every multi-byte field is little-endian, and every reserved field is zero.
### 10.1 Header (32 bytes)
| off | size | field | rule |
|---|---|---|---|
| 0 | 4 | magic `WHG1` | |
| 4 | 1 | version | `1` |
| 5 | 1 | operator set | `1` (`wai-ops/1`) |
| 6 | 2 | reserved | `0` |
| 8 | 4 | `n_tensors` | ≥ 1 |
| 12 | 4 | `n_ops` | ≥ 1 |
| 16 | 2 | `n_inputs` | ≥ 1 |
| 18 | 2 | `n_outputs` | ≥ 1 |
| 20 | 2 | `n_scalars` | |
| 22 | 2 | reserved | `0` |
| 24 | 4 | `max_elems` | 1..=`2^26`: the cap on every tensor of this graph |
| 28 | 4 | reserved | `0` |
### 10.2 Tables
The tables follow the header in this order.
- **Scalars:** `n_scalars` records of 16 bytes: `i64 lo`, `i64 hi`, with `lo ≤ hi`.
- The driver supplies each scalar's value at run time. A value outside its range is refused (`field_range`).
- **Inputs:** `n_inputs` records of 32 bytes:
- `u32 tensor`;
- `u32 max_h`, `u32 max_w`: the largest size the driver may supply. Either both are at least 1, or both are 0, which means the size is set per decode and bounded only by the caps (§3). Either every input declares its sizes or none does;
- `u8 kind`: 0 for data, 1 for state; then 3 bytes reserved;
- `u32 src`: for a state input, the position in the outputs table of the output that feeds it; 0 for a data input;
- `u32` reserved;
- `i64 init`: for a state input, the value of every element at the first decode of a sequence; 0 for a data input.
- The driver supplies each data input. Its `H` and `W` are at most `max_h` and `max_w` when they are declared, otherwise `too_large`.
- **Outputs:** `n_outputs` records of 4 bytes: `u32 tensor`.
- **Tensors:** `n_tensors` records of 24 bytes:
- `u32 channels` (≥ 1);
- `i8 exponent`, in −127..=127, or −128 for none;
- `u8 policy`: 0 for `derived`, 1 for `clamp`, 2 for `check`;
- `u16` reserved;
- `i64 lo`, `i64 hi`. With `derived`, both are 0. Otherwise `lo ≤ hi`.
- **Operators:** `n_ops` records, in evaluation order. Each record is:
- `u8 opcode`, `u8 n_in`, `u8 n_out`, `u8` reserved;
- `u32 in[n_in]`, `u32 out[n_out]`;
- `u32 plen`, then `plen` bytes of parameters (§10.4).
Nothing follows the last operator.
### 10.3 Graph rules
- Every tensor is a graph input or the output of exactly one operator.
- An operator reads only graph inputs and the outputs of earlier operators.
- No graph input is an operator's output.
- Every graph output is defined.
- A graph input's policy is `check` or `clamp`.
- Input 0 is of kind data.
- `n_in` and `n_out` are the operator's: §5's table gives them, `concat` has `n_in ≥ 2`, and the `subpos_*` operators have 4 on their four-tensor side.
- Each operator's output channel count, as §5 derives it, equals its output tensor's `channels`.
- **State.** A state input's `src` is less than `n_outputs`. That output's tensor has the state input's channel count, and `init` lies within the state input's declared range.
**Carrying state.** A **sequence** is a run of decodes that the capability's driver defines, such as the frames of one clip. A decode **completes** when it is not refused (§4). Until a decode of the sequence completes, each state input is the tensor `[channels, H, W]` whose every element is `init`, where `H` and `W` are input 0's at the decode being made. After that, it is the tensor that output `src` held at the end of the sequence's last completed decode, after that tensor's policy. A refused decode changes no state: the decode after it reads what it would have read had the refused decode not been made. The state input's own policy then applies to it, as to any input (§4, run step 4). The run-time plan (§4) checks its shape against every operator that reads it.
### 10.4 Parameter layouts
**The requantisation block `RQ`**
```
u8 round 0 floor, 1 half_up, 2 half_even
u8 bias 0 none, 1 pre, 2 post
u8 m_kind 0 absent, 1 one value, 2 one per channel
u8 s_kind 1 one value, 2 one per channel
u8 m_bytes 4 or 8 (0 when m_kind = 0)
u8 b_bytes 4 or 8 (0 when bias = 0)
u16 reserved
i<m_bytes> mult[0, 1 or C]
u8 shift[1 or C] each ≤ 127
i<b_bytes> bias[C] when bias ≠ 0
```
**Each operator's parameters**
| operator | parameters |
|---|---|
| `conv` | `u32 cout, kh, kw, sh, sw, ph, pw, groups`; `u8 w_bytes` (1, 2, 4 or 8), 3 reserved; `RQ`; `i<w_bytes> Wt[cout][cin/groups][kh][kw]` |
| `conv_transpose` | `u32 cout, kh, kw, sh, sw, ph, pw, oph, opw`; `u8 w_bytes`, 3 reserved; `RQ`; `i<w_bytes> Wt[cin][cout][kh][kw]` |
| `rescale` | `RQ` |
| `chscale` | `u32 scalar, n_rows`; `u8 round` (as in `RQ`), `u8 m_bytes` (4 or 8), `u8 shift` (at most 127), 1 reserved; `i<m_bytes> M[n_rows][C]` |
| `mul` | `RQ`, with `bias = 0` |
| `lut16` | `i8 e_in, i8 e_out` (each −128 for none), 2 reserved; `i16 T[65536]` |
| `add`, `concat`, `chunk_add`, `relu`, `abs`, `crop_to`, `softmax`, `sigmoid` | none (`plen = 0`) |
| `split` | `u32 start, count` |
| `pixel_shuffle`, `pixel_unshuffle` | `u32 r` |
| `clamp` | `i64 lo, hi` |
| `leaky` | `i64 a`; `u32 f` |
| `pad_rep` | `u32 m` |
| `crop` | four operands `y0, x0, h, w`, each `u8 kind` (0 constant, 1 scalar), 3 reserved, `u32 value-or-index` |
| `subpos_split`, `subpos_merge` | `u8 order[4]`, each `2a + b` |
| `fill` | `u8 kind` (0 constant, 1 scalar), 7 reserved, `i64 value` (the index when `kind = 1`) |
| `igdn` | `u32 gf, frac`; `u8 g_bytes` (4 or 8), 7 reserved; `i<g_bytes> γ[C][C]`; `i64 β[C]` |
| `layernorm_c` | `u64 ε`; `u8 g_bytes` (4 or 8), 7 reserved; `i<g_bytes> γ[C]`; `i64 β[C]` |
| `l2norm` | `u64 ε` |
| `elu` | `i64 α` |
| `gram`, `apply` | `u32 groups`; `RQ`, with `m_kind = 0`, `bias = 0` and `s_kind = 1` |
`cin` and `C` are the input tensor's channel count. Every parameter length is exact.
### 10.5 Load checks
A graph is refused (`model_mismatch`) at the first of these that fails:
1. the magic, version and operator set;
2. the reserved fields;
3. the counts against the file's length;
4. each table's records (§10.2);
5. each operator's opcode, its arity, its parameter length and the ranges of its parameters (§5, §10.4);
6. the graph rules, state rules included (§10.3), and the exponent rules (§3, §5.6);
7. the plan. Every operator's channel counts. When the inputs declare their sizes, every shape, planned on `max_h` and `max_w`, where a size past a cap is `too_large`. A graph with a `gram`, `l2norm` or `softmax` whose inputs do not declare their sizes is refused;
8. the bounds pass (§8), with `lut16`'s range check (§5.6), and the capability's profile against the verdict (§8.6).
The graph's identity is the SHA-256 of its bytes.
## 11. Declaring a profile
**In the registry.** Each capability decoded through `wai-ops/1` carries an `ops` member in `registry/capabilities.json`:
```json
"ops": {
"set": "wai-ops/1",
"profile": "acc64",
"subset": "full",
"lowering": "whm1/1",
"stage": { "rans64": { "table": "t4097", "escape": "lenient", "end": "canonical" },
"bucket": "ceil" }
}
```
- `profile` is the profile that every conforming parameter set of the capability meets. A set whose verdict does not meet it is refused. A set MAY meet a stronger profile; its certificate shows that.
- `subset` is `full` or `npu`. A capability registered `npu` refuses a set that is not in the subset (§9.2).
- `lowering` names the §13 lowering, or `whg1` when the set carries graphs.
- `stage` fixes the modes of the stage operators the driver uses (§5E).
- `role_profiles`, OPTIONAL, names roles of the lowering (§13.0) whose graphs every conforming set holds to a stronger profile than `profile`, each with that profile. A graph meets a profile when its inputs' and scalars' declared ranges meet the profile's `tensor` width and every node of the graph fits the profile (§8.5). A set with a named graph that does not is refused (`model_mismatch`). In this revision `wai.image.jpegai` declares `profile` `acc64` and `role_profiles` `{ "hs_y": "acc32", "hs_uv": "acc32" }`: its scale path runs on a 32-bit fabric as on a 64-bit one, and its other integer networks meet `acc64` (§13.6.2).
- `wai.image.int_cond_refine` carries its network in its payload and reads no parameter set: its member carries `set`, `profile` `acc64`, `subset` `full`, `lowering` `wcp1/1` ([staged-delivery](staged-delivery.md) Appendix B.6) and `stage`, and every payload that parses meets `acc64`.
- A capability whose decode runs no graph, only stage operators (§13.5), has no parameter set, so its member carries `set` and `stage` and no `profile`, `subset` or `lowering`. In this revision, `wai.video.int_motion`, its frame form `wai.video.int_motion.frame`, and `wai.{image,video,audio,splat}.int_refine`; `wai.video.int_hyper.frame` carries `wai.video.int_hyper`'s member:
```json
"ops": {
"set": "wai-ops/1",
"stage": { "rans64": { "table": "t4097", "escape": "lenient", "end": "canonical" } }
}
```
Registering a capability's `ops` member, or changing it, follows SPEC §8. A stronger profile or subset narrows the sets a capability accepts, so it is a breaking change to that capability.
**In the pin.** A pin MAY carry `ops_profile`, the profile the sender claims for the pinned set: `"acc32"` or `"acc64"`.
- It lets a sink choose a fabric before it fetches the set.
- A sink that resolves the set and finds a weaker verdict refuses it (`model_mismatch`).
- Without the member, a sink learns the profile from the set.
**In the sink.**
- A sink runs a set's graphs only through an operator implementation that conforms to a profile the set's verdict meets. An `acc32` set may run on an `acc32` or an `acc64` implementation. An `acc64` set runs only on an `acc64` implementation.
- A sink that moves a decode from one fabric to another reports where it ran, as [determinism-tiers](determinism-tiers.md) requires. A move between conforming implementations does not change the output.
## 12. Relation to the determinism tiers
**`decode-equivalence`.** Take a capability whose decode is made only of `wai-ops/1` graphs and stage operators. Its output is the same integers on every fabric whose operator implementation conforms (§1) to a profile its set meets, because every operator is an exact integer function (§4) and the certificate excludes overflow (§8). A `NeuralIntegerExact` capability registered with an `ops` member backs `intent = replicate` on those fabrics (SPEC §7).
**`entropy-consistency`.** Take a `NeuralEntropyExact` capability. Its stage operators, and every graph whose output selects a distribution or decides which positions are coded (its **scale path**), MUST be `wai-ops/1`, run on a conforming implementation. Its other graphs MAY run on an implementation that does not conform, or in floating point. Their output is then outside the claim.
**Evidence for a fabric.** Conformance belongs to an implementation on a fabric, and it is shown by the vectors of §14 on that fabric.
- Being in a subset (§9) is not evidence of exactness.
- An accelerator whose implementation has not passed the vectors gives no `decode-equivalence` claim, whatever operators it can map.
- A receipt for a decode through `wai-ops/1` names the profile and the fabric in the execution fields of [determinism-tiers](determinism-tiers.md).
## 13. Lowerings of the registered forms
A **lowering** maps a parameter set in an existing form to `WHG1` graphs, deterministically and canonically, without changing a byte of the set. Pins, payloads and outputs therefore stay as they are. A sink MAY decode the existing form directly, provided it computes the integers of the lowered graphs and refuses what they refuse. A lowered graph's certificate is computed on the lowered bytes, and records their digest.
### 13.0 The canonical form, and the driver contract
A lowering writes exactly one byte sequence for a set. These rules fix it, and §13.1–§13.6 fix each lowering's operators.
- **Header.** Version 1, operator set 1, `max_elems = 2^26`, and the counts of what is written.
- **Scalars, inputs and outputs** are those the lowering lists, in its order. Every lowered input is of kind data. Its policy and range are the lowering's, and its `max_h` and `max_w` are the driver contract's (below).
- **Tensor numbering.** Input `i` is tensor `i`. The operators' output tensors follow, in operator order, and each operator's outputs in their own order take the next unused indices.
- **Tensor records.** `channels` as planned. Exponent −128 (none). Policy `derived`, with `lo = hi = 0`, except where the lowering gives `check` and its range.
- **Operators** are written in the lowering's order, with every reserved byte 0. A parameter that the source form gives once per channel is written per channel (kind 2), even where its values are equal. One that the source form gives once is written once (kind 1).
- **Widths.** Weights, multipliers, biases and `γ` take the width the source form fixes. Where the source form is JSON and fixes none, each takes the smallest width its field allows that holds every value of that field in that operator: 1, 2, 4 or 8 bytes for weights, and 4 or 8 for multipliers, biases and `γ`. A constant that the lowering itself writes, such as the multiplier 255, takes the smallest allowed width that holds it.
| source | `w_bytes` | `m_bytes` | `b_bytes` | `g_bytes` |
|---|---|---|---|---|
| `int_synth.json` | 4 | — | 4 | — |
| `WHM1`, `WAM1` | 2 | 8 | 8 | 4 |
| a `wai.image.jpegai` bundle (JSON) | smallest | smallest | smallest | smallest |
**Roles and order.** A lowered set's graphs, by role, in this order:
| capability | lowering | graphs, in order | output step inside the graph |
|---|---|---|---|
| `wai.neural.int_synth` | `int_synth/1` | `synth` | `clamp(0, 255)` |
| `wai.neural.int_hyper`; the keyframe of `wai.video.int_hyper` | `whm1/1` | `h_s`, `g_s` | in `g_s`, the RGB8 step |
| `wai.audio.int_codec` | `wam1/1` | `g_s` | the PCM step (§13.3) |
| `wai.splat.int_codec` | `whm1/1` | `g_s` | the RGB8 step |
| `wai.neural.int_mlicpp` | `wmb1/1` | `h_s`, `entropy`, `g_s` | in `g_s`, the RGB8 step |
| `wai.video.int_cond`, `wai.video.int_cond.frame` | `wcb1/1` | `ctx`, `prior`, `dec`, `recon` | in `recon`, the RGB8 step |
| `wai.world.model` (informative) | `world/1` | `T`, `g_s` | in `g_s`, the RGB8 step |
| `wai.image.jpegai` | `jpegai-int/1` | `hs_y`, `hs_uv`, `rec_y`, `rec_uv`, `syn_y`, `syn_uv` | in `hs_*`, the per-channel gain; in `syn_*`, the denormalisation (§13.6.2) |
The **RGB8 step** is two operators after the network's last: `clamp(0, 2^afrac)`, then `rescale` with one multiplier 255 (`m_bytes` 4), one shift `afrac`, no bias and `half_up`. Everything else a driver does with an output, such as splitting `h_s`'s output into `σ` and `μ`, or laying values out as bytes, is a driver step and not an operator.
**The driver contract.** The declared inputs and scalars of every lowered graph:
| graph | input 0 | `max_h`, `max_w` | scalars |
|---|---|---|---|
| `synth` | the latents `[C, S, S]`, `check [−128, 127]` | 0, 0 | none |
| a `whm1/1` or `wam1/1` graph | `x_0`, the network's input, with the policy and range of §13.2 | 0, 0 | `wam1/1` only: scalar 0, `t_out`, `[1, 2^26]` |
| `T` (`world/1`) | `z`, `check [−2^63, 2^63 − 1]` | 0, 0 | scalars `0 … A − 1`, the plane values, each `[−2^63, 2^63 − 1]` |
| `hs_y`, `hs_uv` | `z [Cz, 4, 4]`, `check [−31, 32]` | 4, 4 | none |
| `rec_y`, `rec_uv` | `z [C, 4, 4]`, `check [−31, 32]`; input 1, the residual `[C, 16, 16]`, `check [−2^31, 2^31 − 1]` | 4, 4; 16, 16 | none |
| `syn_y`, `syn_uv` | the luma latent `[160, 16, 16]`; for `syn_uv`, input 1, the chroma latent `[96, 16, 16]`; each `check [−2^31, 2^31 − 1]` | 16, 16 | none |
Sizes of 0 mean that the input's size is set per decode (§10.2). A driver plans these graphs on the payload's sizes, at the point its payload extension plans its networks (§4).
### 13.1 The `int_synth` model specification (`int_synth/1`)
**The set.** One file, `int_synth.json` ([integer-payloads](integer-payloads.md) §13). It has a `layers` list. Each layer has `in_ch`, `out_ch`, `k`, `stride`, `pad`, `out_pad` (0 when absent), `weight` as `[in_ch][out_ch][k][k]` 32-bit integers, `bias` as `[out_ch]` 32-bit integers, `shift` (0 when absent) and `relu` (false when absent).
**The graph.** Input 0 is the latents (§13.0). For each layer, in order:
- `conv_transpose` with `cout = out_ch`, `kh = kw = k`, `sh = sw = stride`, `ph = pw = pad`, `oph = opw = out_pad`, the weights as stored, and an `RQ` with `round` `floor`, a `pre` bias, no multiplier and one shift (the layer's);
- then `relu`, if `relu` is set.
Then `clamp(0, 255)`. The graph's one output is the clamp's.
**Load checks.** These are in addition to the payload's own checks, and every refusal is `payload_malformed`:
- there is at least one layer;
- the layers chain: the first `in_ch` is `C`, and each later `in_ch` is the previous `out_ch`;
- the parameter lengths are exact;
- `in_ch`, `out_ch`, `k`, `stride`, `pad` and `out_pad` are below `2^32`, the width of their `WHG1` fields (§10.4);
- `out_ch ≥ 1`, `k ≥ 1`, `stride ≥ 1` and `shift ≤ 127`;
- the caps (§3);
- the certificate meets `acc64`.
**Driver.** The output, which MUST be `[3, H, W]` with the payload's `H` and `W`, as row-major HWC bytes.
### 13.2 `WHM1` networks (`whm1/1`)
**The header.** `WHM1` (integer-payloads §9) carries `afrac`, `gf` and `shift`, then the layers. Layer `ℓ` reads `x_ℓ` and writes `x_{ℓ+1}`. The network's input is `x_0`. Input 0 is tensor `x_0`, and layer `ℓ`'s operator writes tensor `ℓ + 1`, so tensor `ℓ` is `x_ℓ`. `x_0`'s channel count is the input channel count that the first layer of tag 0, 1 or 2 declares. A network with no such layer cannot be lowered (`model_mismatch`).
| tag | lowered to |
|---|---|
| 0 | `conv_transpose`: `kh = kw = k`, `sh = sw = stride`, `ph = pw = pad`, `oph = opw = opad`, weights `i16 [c][out][k][k]`; `RQ`: one multiplier per channel (`i64`), one shift (the header's), a `post` bias (`i64`), `floor` |
| 1 | `conv`: `groups = 1`, `kh = kw = k`, `sh = sw = stride`, `ph = pw = pad`, weights `i16 [out][c][k][k]`; the same `RQ` |
| 2 | `igdn`: `gf` = the header's `gf`, `frac` = the header's `afrac`; `γ` `i32 [c][c]`, `β` `i64 [c]` |
| 3 | `relu` |
| 4 | `leaky`: `a = slope_q`, `f = slope_frac` |
**Range checks.** Each `x_ℓ` that is read by a layer of tag 0, 1, 2 or 4 has the policy `check [−X_ℓ, min(X_ℓ, I64)]`. That includes the network's input. A network input that no such layer reads has `check [−2^63, I64]`, since a graph input is never `derived` (§10.3). Every other tensor is `derived`: in particular each `x_ℓ` other than the network's input that a layer of tag 3 reads, and the last layer's output.
Let `c` be layer `ℓ`'s input channels; `Wm`, `Mm` and `Bm` the largest magnitudes of its weights, multipliers and biases; `G` and `Bb` those of its `γ` and `β`; `I64 = 2^63 − 1` and `I128 = 2^127 − 1`. Then `X_ℓ` is the largest `X` in `[0, 2^63]` for which `P_ℓ(X)` holds:
| tag | `P_ℓ(X)` |
|---|---|
| 0, 1 | `c·k·k·Wm·X ≤ I64` and `⌊c·k·k·Wm·X·Mm / 2^shift⌋ + 1 + Bm ≤ I64` |
| 2 | `G·X² ≤ I128`; `N = Bb + c·(⌊G·X²/2^gf⌋ + 1) ≤ I128`; `isqrt(N) ≤ I64`; `⌊X·isqrt(N) / 2^afrac⌋ + 1 ≤ I64` |
| 4 | `X·|slope_q| ≤ I64` |
- `P_ℓ` is evaluated exactly. Every value it forms is below `2^255`, because `c` and `k` are below `2^32`.
- Every `P_ℓ` holds at `X` whenever it holds at a larger `X`, so `X_ℓ` is found exactly by a binary search.
- If `P_ℓ(0)` fails, the set is refused (`model_mismatch`).
- `max|x_ℓ| ≤ X_ℓ` exactly when `P_ℓ(max|x_ℓ|)` holds. So these ranges refuse exactly the inputs that integer-payloads §7 *Exactness* refuses, at the same layer.
- Every intermediate that §8.3 gives these layers lies within the bounds `P_ℓ` checks. So every set this lowering accepts meets `acc64`.
**With the RGB8 step** (§13.0), two operators follow the last layer, and they write tensors `L + 1` and `L + 2` for a network of `L` layers.
**Load checks.** These are integer-payloads §7, *Planning the networks*:
- `shift < 128` and `gf < 128`;
- `afrac` in `1..=30`, the rule of every capability this extension lowers (integer-payloads §7 for `WIH1`, `WIA1` and `WIS1`, integer-payloads §14 for `WMB1` and §17.3 for `WCB1`; `world/1` in §13.5);
- `slope_frac < 64`;
- the lengths against the running channel count;
- every size within the caps;
- the certificate meets `acc64`.
### 13.3 `WAM1` networks (`wam1/1`)
The header is that of `WHM1`. The tensors are `[C, 1, T]`.
- **Tag 0** is `conv_transpose` with `kh = 1`, `sh = 1`, `ph = 0`, `oph = 0`, `kw = k`, `sw = stride`, `pw = pad` and `opw = opad`. Its weights are `i16 [c][out][k]`, and it has the `RQ` of §13.2.
- **Tag 2** is `igdn`, as in §13.2.
The range checks are those of §13.2, except that the term count for tag 0 is `c·k` in place of `c·k·k`.
**The PCM step** (`wai.audio.int_codec`). After the last layer, three operators:
1. `crop` with `y0 = 0`, `x0 = 0` and `h = 1` constant, and `w` scalar 0 (`t_out`). Its output tensor has the policy `check [⌈−2^63 / 32767⌉, ⌊(I64 − 2^(afrac−1)) / 32767⌋]`. This is the range in which `v·32767 + 2^(afrac−1)` fits i64, which is what integer-payloads §5 step 3 refuses outside.
2. `rescale` with one multiplier 32767 (`m_bytes` 4), one shift `afrac`, no bias and `half_up`.
3. `clamp(−32768, 32767)`.
The bytes are 16-bit little-endian samples.
### 13.4 `WMB1` bundles (`wmb1/1`)
A `WMB1` bundle (integer-payloads §14) holds three `WHM1` networks, `h_s`, `entropy` and `g_s`, each lowered by §13.2, `g_s` with the RGB8 step. Its tables section is the stage operators' data and is not lowered.
### 13.4a `WCB1` bundles (`wcb1/1`)
A `WCB1` bundle (integer-payloads §17.3) holds four `WHM1` networks, `ctx`, `prior`, `dec` and `recon`, each lowered by §13.2, `recon` with the RGB8 step. The four share one `afrac`, which is the tables' (`model_mismatch` otherwise). Its tables section is the stage operators' data and is not lowered.
### 13.5 Drivers
Each capability's driver is defined by its payload extension. This table names the stage operators, modes, graphs and driver steps each one uses. A row marked *informative* is not part of what §1 says a reader can build.
| capability | payload | status | stage operators and modes | graphs | driver steps |
|---|---|---|---|---|---|
| `wai.neural.int_synth` | integer-payloads §13 | normative | none | `synth` | the output's shape; HWC bytes |
| `wai.neural.int_hyper`, and the keyframe of `wai.video.int_hyper` | `WIH1` (integer-payloads §4), with `entropy.json` (integer-payloads §9) | normative | `rans64_decode` (`t4097`, `lenient`, `canonical`); `dequantize`; `bucket` (`ceil`); `coding_order` (`raster`) | `h_s`; `g_s` | `h_s`'s output is checked against `[2m, sh, sw]` (`geometry_mismatch`); its channels `[0, m)` are `σ` and `[m, 2m)` are `μ`; `g_s` gives 3 channels (`model_mismatch`); HWC bytes |
| `wai.video.int_motion`, and the P-frames of `wai.video.int_hyper` | `WIV1` (integer-payloads §3) | normative | `rans64_decode` (`t4097`, `lenient`, `canonical`); `mc_predict`; `residual_step` with `[0, 255]` | none | RGB8 frames |
| `wai.video.int_motion.frame`, and the inter frames of `wai.video.int_hyper.frame` (the intra frames of the latter are the `WIH1` row above) | `WIF1` (integer-payloads §16) | normative | as `WIV1`, one stream per frame | none | an RGB8 frame |
| `wai.audio.int_codec` | `WIA1` (integer-payloads §5) | normative | `rans64_decode` (as above); `dequantize` | `g_s` | `t_out` as scalar 0; the samples as bytes |
| `wai.splat.int_codec` | `WIS1` (integer-payloads §6) | normative | as above | `g_s` | CHW bytes |
| `wai.neural.int_mlicpp` | `WIM1` (integer-payloads §14) | normative | `rans64_decode` (`t2`, `lenient`, `final_only`); `dequantize`; `bucket` (`ceil`); `coding_order`: for each of the `K` channel slices, `checkerboard(true)` then `checkerboard(false)` over the slice's channels | `h_s`; `entropy` before every pass; `g_s` | the entropy graph's input is `concat(y, h_s output)`, which the driver forms with **every element of `y` not yet decoded equal to 0**; its output's channels `[0, Cy)` are `σ` and `[Cy, 2Cy)` are `μ`; HWC bytes |
| `wai.video.int_cond`, and each frame of `wai.video.int_cond.frame` | `WIC1` and `WCF1` (integer-payloads §17), with `model.wcb` (§17.3) | normative | `rans64_decode` (`t4097`, `lenient`, `canonical`), one stream a frame; `dequantize`; `bucket` (`ceil`); `coding_order` (`raster`) | `ctx` and `prior` before the frame's stream; `dec`, then `recon`, after it | `ctx`'s input is the reference feature, all zero for an intra frame and the feature `dec` gave the frame before it otherwise; `prior`'s output is checked against `[2m, sh, sw]` (`geometry_mismatch`), its channels `[0, m)` are `σ` and `[m, 2m)` are `μ`; `dec`'s input is `concat(ŷ, ctx output)`; `dec`'s output is kept as the next frame's reference feature; HWC bytes |
| `wai.{image,video,audio,splat}.int_refine` | `WIR1` (staged-delivery Appendix A) | normative | `rans64_decode` (`t4097`, `lenient`, `canonical`), one stream per unit; `residual_step` with `[0, 255]` or `[−32768, 32767]`; `up2_predict`; `rate_predict` | none | the unit's elements |
| `wai.world.model` | none yet | informative | `dequantize` (the plane values) | `T`; `g_s` | below |
**`WIV1` and `WIR1`: stage operators, no graph.** These drivers run no network, so they have no lowering, no parameter set and no certificate; their `ops` member names the set and E1's modes (§11).
- **`WIV1`** (integer-payloads §3). One E1 decode reads the clip's residual stream against its one table at its `offset`. For each frame `f` in order, each pixel `(y, x)` in raster order and each channel `c` of R, G, B: the prediction is 128 for frame 0 under keyframe 0, and otherwise E10 on frame `f − 1` as decoded, at the clip's `mv_precision`, with the vector of the pixel's block (entry `(f − 1)·nb + (y div block)·nbx + (x div block)`); the symbol `r` is E1's next; the sample is E9 `residual_step(pred, r, q, 0, 255)`. Under keyframe 1, frame 0 is the nested `WIH1`'s decode (the row above), and the stream starts at frame 1. The `canonical` end rule applies to the clip's stream as a whole.
- **`WIF1`** (integer-payloads §16). The same steps for one frame, from a stream of its own, which the `canonical` end rule holds after the frame's last symbol: an intra frame of keyframe 0 is frame 0 above; an inter frame is a frame `f ≥ 1`, its prediction E10 on the previous decoded frame of its group, at its own `mv_precision` and `block`, against its group's table, `offset` and `q`.
- **`WIR1`** (staged-delivery Appendix A). For each unit, after A.4 step 1's shape check, one E1 decode reads the unit's stream against the unit's table at the layer's `offset`. For element `i = (a·d1 + b)·d2 + c` in raster order: the prediction is the element below at `i` (identity), E11 at `(a, b)` on channel `c` of the `⌈d0/2⌉ × ⌈d1/2⌉ × d2` picture below (up2), or E12 for output sample `a` of channel `c` of the source samples below (rate); the symbol `r` is E1's next; the element is E9 `residual_step(pred, r, q, lo, hi)` with `[lo, hi]` `[0, 255]` for u8 and `[−32768, 32767]` for i16. The `canonical` end rule applies to each unit's stream.
- **Widths.** Both ranges meet E9's 32-bit conditions, so a fabric that runs these drivers in 32-bit integers runs E9 in its 32-bit form; E1 still needs a 64-bit state, and E12 64-bit arithmetic (§7.4).
**`world/1` (informative).** `wai.world.model` has no payload text yet. Its integer renderer takes two `WHM1` networks, the transition `T` and the synthesis `g_s`, an initial latent `z_0 [C, lat, lat]`, and a sequence of action rows.
- **Load checks** (`model_mismatch`). Both networks' `afrac` lie in `1..=30` and are equal. `A`, the number of action planes, is `T`'s input channel count (§13.2) minus `C`, and `A ≥ 1`. Every row has `A` entries.
- **The graph `T`.** Scalars `0 … A − 1` are the plane values, and input 0 is `z` (§13.0). The operators are:
- `fill` from scalar `a`, for `a = 0 … A − 1` in turn, each reading `z` and writing a one-channel tensor;
- `concat(z, fill_0, …, fill_{A−1})`, writing `x_0`, with `T`'s layer-0 range check (§13.2);
- `T`'s layers (§13.2);
- `clamp(−2^afrac, 2^afrac)`, with `T`'s `afrac`. Its output is the graph's.
- **The graph `g_s`** is `whm1/1` with the RGB8 step, which uses `g_s`'s `afrac`. The load check makes the two equal.
- **The driver.** Frame 0 is `g_s(z_0)`. For each row in order, `z` becomes `T(z)` with the row's entries as the scalars, and the next frame is `g_s(z)`. A single-agent set (`A = 3`) whose rows are moves `(dx, dy)` has the plane values `dequantize(dx, afrac, 0)`, `dequantize(dy, afrac, 0)` and 0. A two-agent set (`A = 4`) has `(dxA, dyA, dxB, dyB)`, each scaled the same way.
- **Before the first frame,** after the load checks: `z_0` is `[C, lat, lat]`, and both graphs are planned on that shape (§4). `T` must give the shape back, so that each transition reads what the one before it wrote, and `g_s` must plan on it; a network that does not is refused (`model_mismatch`), as a set that does not fit a payload's shapes is. The reference renderer reports a latent that is not `C·lat·lat` values as `geometry_mismatch`, a side of 0 or a move whose plane value leaves `i64` as `field_range`, and a latent past `2^26` values as `too_large`. These codes are informative until the renderer has payload text.
### 13.6 The integer stages of `wai.image.jpegai` (`jpegai-int/1`)
The capability is `NeuralEntropyExact` (§12). §13.6.1 is its scale path and its two entropy decodes, and §13.6.2 its other integer networks. Both are normative, and complete from this text, the codestream syntax of ISO/IEC 6048-1, and the bundle members they name.
#### 13.6.1 The scale path and the entropy decodes
**Inputs**
- **From the codestream** (ISO/IEC 6048-1). A segment's **payload** is the bytes that follow its marker and its length field, and its length is the one that field gives. In the configuration the capability decodes ([determinism-tiers](determinism-tiers.md) §5), each payload below is one entropy substream, decoded as one thread (E8).
- the payload of the SOZ segment, which carries the hyper-latents of both planes, luma first, as one stream;
- the payloads of the SORP segment (the luma latent) and the SORS segment (the chroma latent);
- each component header's per-channel GRFS flags, one per latent channel.
- **From the bundle's `norm.json`**, a JSON object. The members this subsection reads, and their load checks:
- `pmf`: `R ≥ 32` rows, each an array of `w ≥ 1` non-negative integers that sum to 256, all of one width;
- `bounds`: at least 32 integers, each in `[0, 255]`;
- `per_channel`: 16, the hyper-latent symbols of one channel (4 × 4);
- `cdfs_y`: `160·63` integers; `cdfs_uv`: `96·63`. Each is in `[0, 255]`, and each run of 63 (a row) is non-decreasing;
- `gain_y`: 160 integers; `gain_uv`: 96. Each fits i64.
Its other members feed floating-point stages and are not read here.
- **From the bundle's `weights/hs_y.json` and `weights/hs_uv.json`**: JSON objects whose members are tensors `{ "dims": [ … ], "v": [ … ] }`, with `v` row-major over `dims`. The scale graph reads the nine members of Table 13.6-A, with the shapes shown. Other members are ignored. The member names are the bundle's, and mean nothing here. The block named `depthwise` is a dense convolution.
**Table 13.6-A. The scale graph's members.**
| block | weights | bias | per-channel shift |
|---|---|---|---|
| 1 | `conv1.weight` `[C1, Cz, 1, 1]` | `conv1.bias` `[C1]` | `onnx::Pow_85` `[1, C1, 1, 1]` |
| 2 | `depthwise.weight` `[C2, C1, 3, 3]` | `depthwise.bias` `[C2]` | `onnx::Pow_91` `[1, C2, 1, 1]` |
| 3 | `pointwise.weight` `[C3, C2, 1, 1]` | `pointwise.bias` `[C3]` | `onnx::Pow_97` `[1, C3, 1, 1]` |
`Cz` is 160 in `hs_y.json` and 96 in `hs_uv.json`, and `C3 = 16·Cz`. Every weight and bias fits i64, and every shift is in `0..=127`. A bundle that fails any load check of this subsection is refused (`model_mismatch`).
**The scale graph** (`hs_y` from `hs_y.json` with `gain_y`; `hs_uv` from `hs_uv.json` with `gain_uv`). Input 0 is `z` (§13.0). The operators, in order:
1. `clamp(−128, 127)`;
2. `conv`, block 1: `cout = C1`, 1×1, stride 1, padding 0, `groups` 1; an `RQ` with `round` `floor`, a `pre` bias (the block's bias), no multiplier and one shift per channel (the block's shifts);
3. `relu`;
4. `clamp(−128, 127)`;
5. `conv`, block 2: `cout = C2`, 3×3, stride 1, padding 1, `groups` 1; the same kind of `RQ`;
6. `relu`;
7. `clamp(−128, 127)`;
8. `conv`, block 3: `cout = C3`, 1×1, stride 1, padding 0, `groups` 1; the same kind of `RQ`;
9. `pixel_shuffle(4)`;
10. `abs`;
11. `clamp(0, 3967)`;
12. `rescale`: no multiplier, shift 0, `floor`, and a `pre` bias per channel, the plane's gains.
The output is `v = I_s + g[c]`, `[Cz, 16, 16]`.
**Each plane.** The luma plane has `C = 160` channels, the rows `cdfs_y`, the gains `gain_y`, the latent segment SORP and `skip = 0`. The chroma plane has `C = 96`, `cdfs_uv`, `gain_uv`, SORS and `skip = 160`.
1. **Hyper-latent.** A fresh `mans_decode` in `fac` mode (§5E, E8) runs over the SOZ segment's payload, with `L` its length.
- It first decodes `skip` rows of 16 symbols, row `i` with row `i` of `cdfs_y`, and discards them. So the chroma plane passes over the luma hyper-latent, with luma's rows.
- It then decodes `C` rows of 16 symbols, row `c` with row `c` of this plane's rows.
- `z[c][y][x] = sym_{c, 4y + x} − 31`, so `z ∈ [−31, 32]`.
2. **Scale.** The scale graph on `z` gives `v`.
3. **Stage operators**, at every channel `c` and position:
- `mask = coded(v, 382)`;
- `likely = block_stat(v; 8, 6, 0, 4351)`;
- `rvs = band_lookup(likely, [1004, 1353, 1938], [[−31, 20, 122, 181], [−80, −31, 71, 130]], row 0 when channel c's GRFS flag is set, else row 1)`. Each edge is the first `likely` of its band, so `likely = 1003` is in band 0;
- `σ_idx = scale_index(v + rvs, 7, 32)`, which lies in `[0, 31]`.
4. **Latent.** The latent tables are built by E8 from `pmf` and `bounds`: `T_y[σ]` from row `σ`, and `B_y[σ] = bounds[σ]` for `σ < 32`. A fresh `mans_decode` in `sgm` mode runs over the payload of this plane's latent segment, with `L` its length. It decodes spans `c = 0 … C − 1` in order. Span `c` is channel `c`'s 256 positions in raster order, with those positions' `σ_idx` and mask. The symbols form the latent `y_q [C, 16, 16]`, with 0 at every unmasked position.
**Refusals.** A bundle that fails a load check: `model_mismatch`. A `mans_decode` refusal: `stream_exhausted` (E8). [determinism-tiers](determinism-tiers.md) says how the capability reports them. A refused exact decode MUST NOT fall back silently.
**What this subsection fixes.** `z`, `v`, the mask, `likely`, `σ_idx` and `y_q` are integers on every fabric. They are what the capability's `entropy-consistency` claim covers (§12).
#### 13.6.2 The hyper decoder, the context stages and the synthesis
The capability's other integer networks are each plane's hyper decoder, the luma plane's four context stages and each plane's synthesis. Their weights are integers and their values are Q16: an integer `q` stands for `q / 2^16`. `jpegai-int/1` lowers them to four graphs:
- `rec_y` and `rec_uv`: a plane's hyper decoder, and for luma its four context stages, from the plane's hyper-latent `z` and its dequantised residual to its reconstructed latent `ŷ`;
- `syn_y` and `syn_uv`: the synthesis, from the planes' latents to the planes.
**The members.** The graphs read the bundle's `weights/hyp_y.json` and `weights/hyp_uv.json` (the hyper decoders), `weights/mcm0_y.json` to `weights/mcm3_y.json` (the context stages 0 to 3), and `weights/synth_y.json` and `weights/synth_uv.json` (the synthesis). Each is a JSON object. A member whose value is an object with a `kind` of `"conv"` or `"q16"` is read, and every other member is ignored.
- Kind `"conv"`: `dims`, an array of non-negative integers; `w`, an array of as many integers as the product of `dims`, each fitting i64, row-major over `dims`; `mult`, an integer that fits i64; and `shift`, an integer in `0..=127`.
- Kind `"q16"`: `v`, an array of integers that fit i64. Its other members, `dims` among them, are not read.
A file in which any object, at any depth, has two members of one name, or in which a member of either kind lacks one of these or holds another value, is refused (`model_mismatch`). A member of neither kind is ignored, a bias among them: a module whose `m.bias` is of neither kind has no bias.
A **module** `m` names its members. Its weights are `m.weight`, of kind `conv`. Its bias is `m.bias`, of kind `q16`, and a module MAY have none. A layer normalisation's `m.weight` and `m.bias` are both of kind `q16`. A member that the graphs below read, and that the file lacks or holds as the other kind, is refused (`model_mismatch`). The member names are the bundle's, and mean nothing here.
**Blocks.** Each block reads a tensor `x` of `c` channels and appends operators, in the order given. `A32` is the policy `check [−2^31, 2^31 − 1]`.
| block | operators |
|---|---|
| `C(m; k, s, p, g)` | `conv` from `m.weight`, whose `dims` MUST be `[cout, c/g, k, k]` with `c` and `cout` multiples of `g`: `kh = kw = k`, `sh = sw = s`, `ph = pw = p`, `groups = g`. `g = dw` means `groups = c`, and then `cout = c`. Its `RQ`: `floor`; one multiplier, `mult`; one shift, `shift` (in a `TAB`, as below); a `post` bias of `cout` values from `m.bias`, or none when the file has no `m.bias`. The output is `A32`. |
| `T(m; k, op)` | `conv_transpose` from `m.weight`, whose `dims` MUST be `[c, cout, k, k]`: `kh = kw = k`, stride 2, padding 1, output padding `op`, with `C`'s `RQ`. The output is `A32`. |
| `relu6` | `clamp(0, 6·2^16)` |
| `R(m)` | `C(m.conv1; 3, 1, 1, 1)`, `relu`, `C(m.conv2; 3, 1, 1, 1)`, then `add(x, ·)`, `A32` |
| `G(m)` | `relu6`, `C(m.conv; 3, 1, 1, 4)`, `C(m.conv2; 1, 1, 0, 1)`, then `rescale` with no multiplier, shift 0, `floor` and a `post` bias of `2^16` on every channel, `A32`, then `mul(x, ·)`, `A32`: the gate `x·(f(x) + 1)` |
| `CAB` | on `x`: `R(CAB.residual_trunk.0)`, `R(CAB.residual_trunk.1)`, giving `t`; on `x` again: `C(CAB.subscale; 3, 2, 1, 1)`, `R(CAB.residual_mask2.0)`, `R(CAB.residual_mask3.0)`, `T(CAB.upscale; 3, 1)`, `sigmoid`, giving `s`; then `mul(t, s)`, `A32`, and `add(x, ·)`, `A32` |
| `D` | the denormalisation: `rescale` with one multiplier 128, shift 0, `floor` and a `post` bias of `127·2^16 + 2^15` on every channel, with the policy `clamp [0, 255·2^16]` |
`mul` here is a Q16 product: no multiplier, shift 16, no bias, `floor`. The two inputs of each `mul` and each `add` have one channel count. A constant this lowering writes takes the smallest allowed width that holds it (§13.0).
**`TAB(m)`** reads `x` of `c` channels, `c` a multiple of 4, and `d = c/4`. Its attention half's convolutions take the shift of `m.prep_data.conv1.weight`, and its feed-forward half's that of `m.ffn.project_in.weight`. Each keeps its own multiplier. In order:
1. `layernorm_c` with `ε = 42949`, `γ` the `c` values of `m.prep_data.norm1.weight` and `β` those of `m.prep_data.norm1.bias`, `A32`;
2. `C(m.prep_data.conv1; 1, 1, 0, 1)`, whose `cout` MUST be `3c`, then `C(m.prep_data.conv2; 3, 1, 1, dw)`;
3. `split` at `0`, `c` and `2c`, each of `c` channels: `q`, `k` and `v`;
4. `l2norm` with `ε = 42949` on `q`, then on `k`;
5. `gram` of the two, with `groups` 4, shift 16 and `half_up`, `A32`;
6. `rescale` with one multiplier per channel, channel `i`'s the value at `⌊i/d⌋` of the 4 values of `m.attn.temperature`, shift 16, `half_up` and no bias, `A32`;
7. `softmax`, then `apply` of it to `v`, with `groups` 4, shift 16 and `half_up`, `A32`;
8. `C(m.attn.project_out; 1, 1, 0, 1)`, then `add(x, ·)`, `A32`, giving `x′`;
9. `layernorm_c` with `ε = 42949` from `m.ffn.norm1` on `x′`, `A32`;
10. `C(m.ffn.project_in; 1, 1, 0, 1)`, whose `cout` `2h` MUST be even, then `C(m.ffn.dwconv; 3, 1, 1, dw)`;
11. `split` at `0` and `h`, each of `h` channels: `a` and `b`;
12. `elu` with `α = 2^16` on `a`, then `mul(·, b)`, `A32`;
13. `C(m.ffn.project_out; 1, 1, 0, 1)`, then `add(x′, ·)`, `A32`.
Every other convolution takes its own shift. Today's bundle happens to give every convolution shift 31; the rule is the decoder's for any bundle.
**The graphs.** Each graph's inputs are listed in input-table order, each with the sizes it declares. The sub-lattice order is `(0,0), (1,1), (0,1), (1,0)`.
- **`rec_y`** (`C = 160`, from `hyp_y.json` and `mcm0_y.json` to `mcm3_y.json`) and **`rec_uv`** (`C = 96`, from `hyp_uv.json`). Input 0 is `z [C, 4, 4]`, `check [−31, 32]`, sizes 4 × 4: the hyper-latent of §13.6.1 step 1. Input 1 is the dequantised residual `r [C, 16, 16]`, `A32`, sizes 16 × 16. The hyper decoder comes first:
1. `rescale` of `z` with one multiplier `2^16`, shift 0, no bias and `floor`, its output `derived`;
2. `C(conv1; 1, 1, 0, 1)`, `T(conv2; 4, 0)`, `relu6`, `C(conv3; 3, 1, 1, 1)`, `relu6`, `C(conv4; 3, 1, 1, 1)`, whose `cout` MUST be `4C`. Its output is `ψ`.
Then, for `rec_uv`: `split(ψ, iC, C)` for `i = 0 … 3`; `subpos_merge` of the four in the sub-lattice order; and `add(r, ·)`, `A32`, which is the graph's output `ŷ`.
For `rec_y`: `subpos_split(r)` in the sub-lattice order, giving `r_0 … r_3`. Then, for each context stage `i = 0 … 3`, from `mcm{i}_y.json`:
1. `split(ψ, iC, C)`, giving `p_i`;
2. for `i = 0`, `f = p_i`. For `i ≥ 1`, with the context `y_0` for `i = 1` and `concat(y_0, …, y_{i−1})` otherwise: `C(conv.0; 1, 1, 0, 1)`, then `C(conv.1; 3, 1, 1, 5)`, then `concat(·, p_i)`, giving `f`;
3. on `f`: `C(fusion_pred_net.conv1; 1, 1, 0, 1)`, `relu`, `C(fusion_pred_net.conv2; 1, 1, 0, 1)`, `relu`, `C(fusion_pred_net.conv3; 1, 1, 0, 1)`, whose `cout` MUST be `C`, giving `μ_i`;
4. `add(r_i, μ_i)`, `A32`, giving `y_i`.
Last, `subpos_merge(y_0, y_1, y_2, y_3)` in the sub-lattice order, which is the graph's output `ŷ`.
- **`syn_y`**, from `synth_y.json`. Input 0 is the luma latent `[160, 16, 16]`, `A32`, sizes 16 × 16. Then: `R(first_stage.0.0)`, `T(first_stage.1; 3, 1)`, `G(first_stage.2)`, `T(conv2_t; 3, 1)`, `CAB`, `G(iact2)`, `C(conv3_t; 1, 1, 0, 1)`, `pixel_shuffle(2)`, `C(TAM.ds_conv; 3, 2, 1, 1)`, `TAB(TAM.TABs.0)`, `TAB(TAM.TABs.1)`, `T(TAM.us_conv; 3, 1)`, `G(iact3)`, `T(conv4_t; 3, 1)`, whose `cout` MUST be 1, and `D`. The output is the luma plane `[1, 256, 256]`.
- **`syn_uv`**, from `synth_uv.json`. Input 0 is the luma latent `[160, 16, 16]` and input 1 the chroma latent `[96, 16, 16]`, each `A32`, sizes 16 × 16. Then: `concat` of inputs 0 and 1; `C(first_stage.conv1; 3, 1, 1, 1)`; `concat` of that and input 1; `T(conv2_t; 3, 1)`, `CAB`, `G(iact2)`, `C(conv3_t; 1, 1, 0, 1)`, `pixel_shuffle(2)`, `TAB(TAM.TABs.0)`, `TAB(TAM.TABs.1)`, `G(iact3)`, `T(conv4_t; 3, 1)`, whose `cout` MUST be 8, `pixel_shuffle(2)`, and `D`. The output is the chroma planes `[2, 256, 256]`, U then V.
The input of each `pixel_shuffle(2)` has a multiple of 4 channels. Every tensor that no block above gives `A32` or `clamp` is `derived`: the outputs of `clamp`, `relu`, `sigmoid`, `softmax`, `l2norm`, `elu` and the movement operators, and the hyper decoder's scaled input. Each of those lies within the hull of its inputs' intervals and `[−2^16, 2^16]`, and the scaled input within `[−31·2^16, 32·2^16]`.
**Range checks.** These graphs check every activation that an operator can grow, at `A32`, and so every sum they form is bounded by the weights alone. Their inputs come from floating-point stages, so these checks refuse what the direct decoder computed on:
- a dequantised residual or a latent whose Q16 value leaves i32 (input 1 of `rec_*`, the inputs of `syn_*`);
- an activation that leaves i32: the output of a convolution, a transposed convolution, an addition, a Q16 product, a layer normalisation, an attention contraction, the attention scale or a gate's `f + 1`.
Each is `field_range` (§4).
**Load checks.** In addition to §13.6.1's, a bundle is refused (`model_mismatch`) when a file breaks the member rules above; when a block's `dims` or value counts are not those it names, or a channel count is not a multiple it names; when a graph does not plan (§10.5 step 7); or when the certificate does not meet `acc64`.
**Driver.** For each plane, after §13.6.1's steps:
1. The floating-point stages dequantise `y_q` into the residual. The driver converts each residual value `x` to the integer nearest `x·2^16`, a tie rounding away from zero, and runs `rec_*` on `z` and that residual. It gives `ŷ`.
2. The floating-point stages read `ŷ` as `ŷ / 2^16`, refine it into the plane's latent, and the driver converts the latent to Q16 as in step 1.
Then `syn_y` runs on the luma latent, and `syn_uv` on both latents. The post-filters read each output value `v` as `v / 2^16`.
**Refusals.** A bundle that fails a load check: `model_mismatch`. A decode whose input or activation leaves its range: `field_range`. [determinism-tiers](determinism-tiers.md) says how the capability reports them. A refused exact decode MUST NOT fall back silently.
**Profiles.** `layernorm_c`, `l2norm`, `softmax`, `sigmoid` and `elu` form values above i32 whatever their inputs (§7.3), so these four graphs meet `acc64` at best. The capability's `ops` member declares `acc64` for the set, and `acc32` for the scale path's graphs `hs_y` and `hs_uv` (§11).
**What this subsection fixes.** Given the same `z` and residual, `ŷ` is the same integers on every fabric whose implementation conforms to `acc64`. Given the same latents, so are the synthesis planes. The inputs come from floating-point stages, so the capability's claim stays `entropy-consistency` (§12): these graphs MAY run on an implementation that does not conform, and their output is then outside the claim.
**Accumulators, measured (informative).** No sum of these graphs needs more than 64 bits. On the reference codestream of `jpegai/v1` (`tests/jpegai_nets_ops.rs`), every value each operator forms lies within its certified interval, and the largest magnitudes reached are:
| graph | `conv.acc` | `convT.acc` | `mul.p` | `gram.acc` | `apply.acc` | an input | an activation |
|---|---|---|---|---|---|---|---|
| `rec_y` | 36 (53) | 33 (48) | — | — | — | 19 | 21 |
| `rec_uv` | 33 (42) | 32 (46) | — | — | — | 17 | 21 |
| `syn_y` | 36 (55) | 35 (54) | 35 (60) | 32 (46) | 30 (49) | 21 | 24 |
| `syn_uv` | 37 (55) | 36 (52) | 37 (58) | 32 (46) | 33 (49) | 21 | 24 |
Each cell is `bits` (§2) of the largest magnitude reached under that key in that graph, with the certificate's `bits` for the key in brackets. The activations include the denormalised planes, which lie in `[0, 255·2^16]`. The certified bounds come from `A32` and the bundle's weights. No measured sum and no certified one reaches `2^63`, so `acc64` holds for every sum of these graphs, and no wider accumulator is needed. The `product` intermediates, which `acc64` forms in 128 bits (§7.1), reach 53 bits (`rq.p`).
### 13.7 `WSR1` priors (`sr2/1`)
[exact-upscale](exact-upscale.md) §3.4 defines the lowering `sr2/1`: a `WSR1` prior of that
extension (§3.1 there) to one graph with two data inputs, a picture and its base, which the driver
computes with E11 `up2_predict`. Its operators are `rescale`, `conv`, `clamp`, `pixel_shuffle` and
`add`, all in `wai-ops/1-i32` (§9.1); every prior that reads meets `acc64`, and a prior whose
certificate meets `acc32` runs on a 32-bit fabric. The derivations are not capabilities and have no
`ops` member (§11); their reference lowering is `wai-rs`'s `int_sr::graph`.
## 14. Conformance
### 14.1 Vector files
**Encoding.** Every vector file is JSON Lines: one canonical JSON value (§8.5's rules) per line, each line ending with one line feed, including the last. Every integer is a decimal string, shape dimensions included. Bytes are lowercase hexadecimal.
**Per-operator vectors.** `wai/ops-conformance/vectors/<operator>.jsonl` holds one vector per line:
```
vector = { "decodes": [ decode … ],
"graph": hex: a WHG1 file with exactly one operator;
"id": a string, unique in its file;
"load": "ok", or the code its load refuses with (§10.5);
"profiles": [ profile … ], in ascending order: the profiles whose implementations MUST reproduce it }
decode = { "expect": { "outputs": [ tensor … ] } or { "refuse": code },
"inputs": [ tensor … ], in input-table order;
"scalars": [ value … ], in scalar-table order }
tensor = { "data": [ value … ], in CHW order; "shape": [ C, H, W ] }
```
- The graph's bytes carry the operator's parameters, the inputs' ranges, policies and sizes, the output tensors' policies, the exponents and the state table. None of these has a second encoding.
- A decode's `inputs` lists the graph's **data** inputs only, in input-table order. A state input is never supplied: the sequence carries it (§10.3).
- An implementation of a profile loads the graph against that profile (§8.6). `load` is the outcome under every profile listed.
- A vector whose `load` is a refusal has no decodes, and its `profiles` is `["acc32", "acc64"]`: an implementation of either profile refuses it. A vector whose `load` is `"ok"` lists exactly the profiles its graph's verdict meets (§8.5).
- A vector with several decodes is one sequence (§10.3): its state carries from each decode that completes to the next, and a refused decode changes no state.
**Stage-operator vectors.** `wai/ops-conformance/stage/<operator>.jsonl` holds one vector per line:
```
{ "expect": { … } or { "at": an index, "refuse": code },
"id": …, "in": { … }, "modes": { … }, "op": the operator's name (§5E) }
```
`modes` is `{}` except for two operators:
- `rans64_decode`: `"table"` (`"t4097"` or `"t2"`), `"escape"` (`"lenient"` or `"strict"`) and `"end"` (`"canonical"` or `"final_only"`). Under `strict(lo, hi)`, `lo` and `hi` are two more members, `"lo"` and `"hi"`.
- `bucket`: `"bucket"` (`"ceil"` or `"floor"`).
Each operator's `in` and `expect` are as follows. A tensor is `{ "data", "shape" }`, as in a graph vector, with its elements in CHW order (§3).
| operator | `in` | `expect` |
|---|---|---|
| `rans64_decode` | `"stream"`: hex of the stream's `L` bytes, `L` a multiple of 4 and at least 8 (integer-payloads §2); `"tables"`: `[ { "cdf": […], "offset" } … ]`; `"reads"`: the table index of each symbol, in order | `"symbols"` |
| `dequantize` | `"sym"`, `"f"`, `"base"` | `"value"` |
| `bucket` | `"sigma"`, `"edges"` | `"index"` |
| `coded` | `"v"`, `"thr"` | `"coded"`: `true` or `false` |
| `scale_index` | `"v"`, `"s"`, `"n"` | `"index"` |
| `block_stat` | `"x"`: a tensor, each channel one plane; `"b"`, `"s"`, `"lo"`, `"hi"` | `"out"`: a tensor of `x`'s shape |
| `band_lookup` | `"v"`, `"edges"`, `"table"`: an array of rows, `"row"` | `"value"` |
| `mans_decode` | `"memory"`: hex of the stream's `L` bytes; `"mode"`: `"sgm"` or `"fac"`. For `sgm`: `"pmf"`: the rows `P[σ]`; `"bounds"`: `B_y`; and `"spans"`: `[ { "mask": [ true … ], "sigma": […] } … ]`. For `fac`: `"rows"`: `[ [63 counts] … ]`, and `"lengths"`: the symbols of each row | `"symbols"`: one array per span or row, with 0 at every unmasked position |
| `residual_step` | `"pred"`, `"r"`, `"q"`, `"lo"`, `"hi"` | `"value"` |
| `mc_predict` | `"frame"`: the previous decoded frame `R` of integer-payloads §3 as a tensor `[3, h, w]`, with element `(c, y, x)` the sample of channel `c` (R, G, B) at row `y` and column `x`; `"y"`, `"x"`: the pixel; `"c"`: the channel; `"dy"`, `"dx"`: the pixel's vector; `"precision"`: `mv_precision` | `"value"`: `pred` |
| `up2_predict` | `"prev"`: the unit's current output as a tensor `[c, h′, w′]`, with element `(ch, y, x)` the element staged-delivery A.4 writes `prev[y, x, ch]`; `"d0"`, `"d1"` | `"out"`: the prediction as a tensor `[c, d0, d1]`, laid out likewise |
| `rate_predict` | `"prev"`: the unit's `n′` samples of `c` channels as a tensor `[c, 1, n′]`, with element `(ch, 0, i)` the sample A.4 writes `prev[i·c + ch]`; `"rate_num"`, `"rate_den"`, `"d0"` | `"out"`: the prediction as a tensor `[c, 1, d0]`, laid out likewise |
| `coding_order` | `"shape"`: `[C, H, W]`; `"order"`: `"raster"` or `"checkerboard"`; `"anchor"`: `true` or `false`, read for `checkerboard` only | `"indices"`: each element's CHW position `(c·H + y)·W + x`, in coding order |
**The refusal index `at`.** A refused stage vector records where the refusal arose:
- `−1`: a refusal before any symbol or element, for an operand, mode or table that breaks its operator's conditions (§5E, *Conditions and their refusals*);
- `0`: every refusal of `dequantize`; and, for `mans_decode`, a refusal in the window's `init`;
- for `rans64_decode`, the index in `reads` of the symbol whose decode refuses, by any of E1's checks on one symbol. A refusal after the last symbol, under `canonical` an end that is not canonical and under `final_only` a stream that has run out, has `at` equal to the number of symbols;
- for `mans_decode`, a position. Positions are numbered from 0 across every position of every span (`sgm`), or every symbol of every row (`fac`), in decode order, unmasked positions included. A refusal in `in(i)` or in `out(i)`, the escape's `flush` included, has `at = i`. A refusal in a `flush` that belongs to no single position has `at` equal to the last position whose `in` step ran: the `sgm` group's flush after its four `in` steps, the `sgm` remainder flushes, the `fac` group's flush and a `fac` row's final flush. In `sgm`, the `in` step of an unmasked position runs and reads nothing.
**Who writes the vectors.** One reference generator writes every vector file, inputs and expectations alike. The inputs are one shared corpus, and nothing else generates them. An independent checker, written from this text alone, reads each file, recomputes `load`, `profiles` (from its own bounds pass, §8) and every `expect` from the graph bytes and the inputs, and writes the file again. Its file MUST be byte-identical to the committed one. The independence lies in that recomputation.
An operator implementation conforms to a profile when it reproduces every vector tagged with that profile, refusals included. **Each operator's vectors cover at least:**
1. an input that attains each end of every accumulator's partial-sum interval;
2. for every rounding mode, ties on positive and on negative products, and shifts 0, 1, 31, 32, 62 and 63; and shifts 64 and 127, tagged `acc64` only, since §7.2 caps every `acc32` shift at 63;
3. a kernel larger than its input; output padding at least the stride; a stride larger than the kernel; a 1×1 plane; odd sizes;
4. for `acc32`, intermediates and products at exactly `±(2^31 − 1)` and `−2^31`; vectors one past those are tagged `acc64` only;
5. each `check` range at its ends and one past them, and each scalar range likewise; and a run-time plan refusal for every shape rule of §5 (§4);
6. the cases that distinguish this set's definitions from near neighbours:
- `igdn` where flooring the sum would differ from flooring each term;
- `leaky` on negative values that floor away from zero;
- `softmax` rows that do not sum to `2^16`;
- `elu(0)`;
- `layernorm_c` with odd `C` and a mean that is a half; and with `ε = 2^64 − 1`, where `V + ε` passes 64 bits;
- `exp_neg_q16` at `n = 62` and `n = 63`;
- `sigmoid` and `elu` on an input range that reaches `−2^63`, which fails `acc64` and is refused at load, and on one that stops at `−(2^63 − 1)`, which decodes;
- an `rq` whose `q` passes 64 bits while `y` fits, refused at load under `acc64`;
- `bucket` exactly at an edge;
7. for stage operators:
- every `rans64_decode` mode, and under `strict` each refusal of E1 alone and in each pair that one symbol can give at once, under both end rules;
- `mans_decode` on a zero tail and on a short stream, both refused; a bound of 128 and one of 255; a row whose escape is taken at each;
- `residual_step` at `r = ±R` and `r = ±(R + 1)`, and at the i32 limits.
### 14.2 Graph vectors
`wai/ops-conformance/graphs/<name>.jsonl` holds vectors of the per-operator form whose graphs have several operators. Among them:
- the phase form of §9.3, set against `conv_transpose`;
- a state input carried across three decodes, starting from its `init`;
- a graph refused at each load check of §10.5;
- a graph refused at each step of §4's run;
- each lowering of §13 on a small set.
### 14.3 Certificates and lowered graphs
`wai/ops-conformance/certificates/<capability>/<set digest>.json` holds the certificate of every parameter set in a capability's conformance corpus. Beside it, `<set digest>/<role>.whg1` holds each lowered graph. The directory also holds:
- a set that fails `acc64` by one unit;
- a set whose pin claims `acc32` and that fails it by one weight.
A conforming certifier reproduces every certificate byte for byte. A conforming lowering reproduces every lowered graph byte for byte.
### 14.4 Capability corpora
Each capability of §13 is decoded through its lowering. It MUST reproduce every committed output digest and every refusal code of its existing corpora, as listed in integer-payloads §12, staged-delivery and SPEC §5.
### 14.5 The accelerator subset's vectors
`wai/ops-conformance/npu.jsonl` gives each graph vector's place in `wai-ops/1-npu` (§9.2). It has one line per vector of `vectors/` and `graphs/` whose graph loads. The files are taken in the byte order of their paths, and each file's vectors in its own order. Each line is a canonical JSON object (§8.5's rules) with these members:
- `file`: the vector file's path below `ops-conformance/`;
- `id`: the vector's id;
- `npu`, `npu_flags` and `npu_reasons`: those of the verdict (§8.5) of the vector's graph, certified on its own (lowering `"whg1"`).
An operator implementation of `wai-ops/1-npu` conforms on a fabric when it reproduces every vector whose line has `npu` true, refusals included (§1). The flags tell its toolchain which features each vector exercises. The generator writes this file, and the independent checker writes it again from its own bounds pass. Both MUST give the committed bytes.
## 15. Changes to other documents
### 15.1 Text changes
These change [integer-payloads](integer-payloads.md) with this extension:
- **integer-payloads §9:** the sentence "The layers' integer arithmetic is the reference decoders'…" becomes "The layers' integer arithmetic is [integer-operators](integer-operators.md) §13.2 (`WHM1`) and §13.3 (`WAM1`)." The text of `entropy.json` (§15.2) follows the `WHM1` and `WAM1` paragraph.
- **integer-payloads:** two new sections, §13 (the `wai.neural.int_synth` payload and set) and §14 (`WIM1` and `WMB1`).
These land with each capability's lowering, capability by capability:
- **integer-payloads §2:** one sentence. The decode it defines is `rans64_decode` with `t4097`, `lenient` and `canonical`.
- **integer-payloads §4 step 4, §5 step 3 and §6 step 3:** cite §13.0's output steps.
- **integer-payloads §7, *Exactness*:** "Each network decodes through its lowering ([integer-operators](integer-operators.md) §13), and a value outside a `check` range is refused (`field_range`)." The refusal set is unchanged.
- **SPEC §3.1:** the OPTIONAL pin member `ops_profile` (§11).
- **SPEC §5:** the rows of `wai.neural.int_synth` and `wai.neural.int_mlicpp` cite integer-payloads §13 and §14, and §13.1 and §13.4.
- **SPEC §7:** a `NeuralIntegerExact` capability names its `ops` member.
- **determinism-tiers:** §5 cites §12; the execution fields carry the profile; `wai.image.jpegai`'s exact decoder reports `model_mismatch` and `stream_exhausted` (§13.6.1) and `field_range` (§13.6.2), and does not fall back silently.
- **staged-delivery A.4:** cites E9, E11 and E12.
- **`registry/capabilities.json`:** the `ops` members.
### 15.2 The payload text this extension depends on
The payload text that §13 relies on for four capabilities is in [integer-payloads](integer-payloads.md). Each part states what the reference decoder does today, except where §15.3 lists a change.
- **`entropy.json`**, the tables of `wai.neural.int_hyper` and `wai.video.int_hyper`: integer-payloads §9.
- **The `wai.neural.int_synth` payload and its set, `int_synth.json`**: integer-payloads §13.
- **The `WIM1` payload and the `WMB1` bundle** of `wai.neural.int_mlicpp`: integer-payloads §14. Its decode gives the entropy network every element of `y` not yet decoded as 0, and its three networks and its tables share one `afrac` in `1..=30`.
### 15.3 Behaviour this revision changes
No committed output changes, and no committed refusal fixture changes its code. These changes apply only to inputs that no corpus contains, and each takes effect when its capability decodes through its lowering (§13):
1. **`wai.neural.int_synth`.**
- A set whose shift is 128 or more, whose `k` or `stride` is 0, or whose certificate fails `acc64`, is refused.
- A set with no layers, or with a layer whose `out_ch` is 0, is refused, and so is one with a field of 2^32 or more. The direct decoder decoded each of these where the shapes allowed: no layers presented the latents themselves as the image, and a layer of no channels left the next with only its bias.
- A payload whose reserved byte is not 0, or whose `C` is 0, is refused. The direct decoder checked neither.
- A shift from 64 to 127 now has its exact meaning.
- 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. Its direct renderer checked nothing: it computed past its exact range, rendered frames from a set or a row that does not fit, or panicked. The renderer through `world/1` also refuses, before the first frame, a set with no action plane (`A ≥ 1`), a row that does not carry `A` values, `T` that does not give the latent's shape back or `g_s` that does not plan on it, and a set that breaks item 3's rules in either network.
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`, `wai.audio.int_codec`, `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.neural.int_mlicpp` and the integer renderer of `wai.world.model` 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. For the conditional codec either network can carry the change, and the set is refused after its plan and its shape checks and before either stream (integer-payloads §7), where the direct decoder refused with the code of whichever stream or network failed first. For `wai.neural.int_mlicpp` any of its three networks can, and the bundle is refused after the header and the networks' geometry are checked and before either stream (integer-payloads §14). Its direct decoder made four more of these checks only when a network ran, after the streams and networks before it: a layer that does not fit the channels the layer before it gives; a network whose input is not the payload's channel count; a header `shift` or `gf` of 128 or more that a layer uses; and a leaky ReLU's shift of 64 or more. It refused each with `model_mismatch` too, so only the order changes: where a stream or a value was refused first, the bundle now is. Its graphs' run-time plan also caps a convolution's padded side at `2^26` (§4, §5.2), which its direct decoder did not; such a bundle is refused (`model_mismatch`).
4. **`wai.image.jpegai`.** These take effect for its exact decoder and for the reference sink's float decode path, which decode the scale path and the entropy through the same lowering.
- Its exact decoder refuses a malformed latent or hyper-latent stream (`stream_exhausted`) where it panicked.
- 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 its loader wrapped; and a bundle whose scale graphs do not meet `acc32`, the profile its `ops` member declares (§11): a sum past `i32`, or a shift past 63, which it computed in `i64` or shifted as its build did.
- The codestream's scope is checked first, then the bundle (its scale path, then its other networks), then the streams, then the networks' ranges as each runs. A refusal of the exact decode is its result: a sink that holds the bundle does not then decode the capability through the float path ([determinism-tiers](determinism-tiers.md) §5).
- Its residual-variance bands start at `likely` 1004, 1353 and 1938 (§13.6.1 step 3), where the reference decoder's own tables change value. An earlier revision of this text and of the exact decoder started them one lower, so a block whose `likely` was exactly 1003, 1352 or 1937 took the next band's addition and dequantisation multiplier, where the reference decoder and the reference sink's float decode path, which reads the reference decoder's tables, did not. No committed codestream has such a block.
- Its exact decoder runs its hyper decoders, context stages and synthesis through `rec_y`, `rec_uv`, `syn_y` and `syn_uv` (§13.6.2). It refuses a bundle whose weight files break §13.6.2's member rules, whose blocks do not have the shapes the topology names, or whose certificate does not meet `acc64` (`model_mismatch`), where its loaders panicked on a mistyped member or it computed with what the files held. It refuses a decode in which a dequantised residual, a latent or an activation of these networks leaves i32 as a Q16 value (`field_range`), where it computed on in 64 or 128 bits, or wrapped or panicked past them. No committed bundle or codestream reaches either: on the reference codestream every input and activation lies within i32 (§13.6.2, *Accumulators, measured*).
5. **`WIV1` and `WIR1`.** None. Their decodes through the stage operators (§13.5) give the output and the refusal their direct decoders gave, on every input: the operators are the arithmetic integer-payloads §3 and staged-delivery Appendix A already defined, and the refusals keep their codes and their order.