open-standards/wai/extensions/integer-operators.md
Transaction Science dcb1505cd8 int_world gate 6 (WIP: committed before running): the switch
int_world::{decode, decode_deltas, decode_planes} render through world/1 and
return a Result; int_world::{load, World} hold the set lowered and loaded once,
with step, step_delta, step_action and render for the live path, which replace
the per-row step functions of a parsed model. int_world::legacy is test-only
(legacy_int). wai-web's WorldModel loads its set once and renders through it;
its methods throw on a refusal.

Callers moved: tests int_world, int_world2, int_world_shared,
int_kernels_vectors; the int_world_decode example; the decode bench's world
row. tests/int_world_memory.rs (its own binary; both fast-kernel steps): a
clip, a step and a frame hold no more than the direct renderer's.

Text: integer-operators' status note, §13.5 (before the first frame: the
plan, T giving the shape back, the informative codes), §15.3 items 2 and 3;
SPEC §8's planned breaks; CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 02:39:32 -04:00

125 KiB
Raw Permalink Blame History

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, wai.audio.int_codec, wai.splat.int_codec, wai.neural.int_synth, wai.neural.int_hyper, wai.video.int_hyper, wai.neural.int_mlicpp and wai.{image,video,audio,splat}.int_refine, and reproduces the symbols and scale indices that wai.image.jpegai entropy-decodes (§13.6.1). The payload text four of these depend on, entropy.json, the int_synth payload and the WIM1 payload with its WMB1 bundle, is in integer-payloads, and §15.2 says where. The integer renderer of wai.world.model and the other integer networks of wai.image.jpegai are informative (§13.5, §13.6.2).

Reference implementation: the wai-ops crate (wai/wai-ops): parse, lowering, interpreter, stage operators and bounds. Tool: wai-verify bounds. Conformance: wai/ops-conformance/. A capability's reference decoder decodes through this operator set from the revision that registers the capability's ops member (§11); in this revision, wai.neural.int_synth, wai.splat.int_codec, wai.audio.int_codec, wai.neural.int_hyper, wai.video.int_hyper and wai.neural.int_mlicpp. 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.
  • Fixed generative algorithms that are not networks.

Relation to the payload extensions. integer-payloads and staged-delivery 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 PTQ networks;
  • no multiplier, shift 16, no bias, floor: their Q16 products;
  • 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 `

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.

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. 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.

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)]; `
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 `
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 §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 §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 §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.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).

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:

"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).

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 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.

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 —

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.world.model (informative) world/1 T, g_s in g_s, the RGB8 step
wai.image.jpegai jpegai-int/1 hs_y, hs_uv the per-channel gain

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

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 §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·
  • 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, and integer-payloads §14 for WMB1; 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.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.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.{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

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: normative, and complete from this text, the codestream syntax of ISO/IEC 6048-1, and the bundle members it names. §13.6.2 maps its other integer networks, and is informative.

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 §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, [1003, 1352, 1937], [[−31, 20, 122, 181], [−80, −31, 71, 130]], row 0 when channel c's GRFS flag is set, else row 1);
    • σ_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 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 other integer networks (informative)

The hyper decoder, the four context stages and the synthesis are integer as well. Their topology is not written out here, so this subsection maps each integer step to wai-ops/1 and does no more.

integer step wai-ops/1
the hyper decoder's input z·2^16: rescale with one multiplier 2^16 and shift 0
a convolution (grouped or depthwise) conv; RQ: one multiplier (the convolution's own mult), one shift (its own shift, except as below), a post bias (its Q16 bias, or 0), floor
a transposed convolution conv_transpose, with the same RQ
the shift inside a TAB the attention half's three convolutions (prep_data.conv1, prep_data.conv2, attn.project_out) all use prep_data.conv1's shift; the feed-forward half's three (ffn.project_in, ffn.dwconv, ffn.project_out) all use ffn.project_in's. Each keeps its own multiplier
ReLU6, clip clamp(0, 6·2^16), clamp
a residual or mean addition add
a Q16 product mul; RQ: no multiplier, shift 16, floor
a gate x·(f + 1) in Q16 rescale (a post bias of 2^16), then mul
sigmoid, ELU, softmax sigmoid, elu(2^16), softmax
channel layer normalisation layernorm_c(ε = 42949)
channel attention l2norm(42949) on q and on k; gram(heads, 16, half_up); rescale with one multiplier per channel (the head's temperature), shift 16 and half_up; softmax; apply(heads, 16, half_up)
channel slices; concatenation; the 2×2 sub-lattices, in the order (0,0), (1,1), (0,1), (1,0); depth-to-space split; concat; subpos_split and subpos_merge; pixel_shuffle
denormalisation rescale with one multiplier 128, shift 0 and a post bias of 127·2^16 + 2^15, with the policy clamp [0, 255·2^16]; for a byte, then rescale with shift 16 and half_up

Every other convolution uses its own shift. Today's bundle happens to give every convolution shift 31; the rule above is the decoder's for any bundle. These graphs' inputs come from floating-point stages, so once their topology is written, they have the policy check, with their admissible ranges for acc64 (§8.4).

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.

15. Changes to other documents

15.1 Text changes

These change integer-payloads with this extension:

  • integer-payloads §9: the sentence "The layers' integer arithmetic is the reference decoders'…" becomes "The layers' integer arithmetic is integer-operators §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 §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 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. 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.
    • Its exact decoder refuses a malformed latent or hyper-latent stream (stream_exhausted) where it panics today.
    • It refuses a bundle that fails §13.6.1's load checks (model_mismatch), including a bound above 255 or a shift outside 0..=127, which today's loader wraps.