Skip to main content

WAI Extension: Integer Operators (wai-ops/1)

Mirrored from the canonical text at commit 4ab66d24 ().

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/. This Draft precedes all three. Until they land, each 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.

Out of scope

Relation to the payload extensions. integer-payloads and staged-delivery keep their byte layouts, check orders and refusal codes. This extension:

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.

NotationMeaning
⌊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 ba − b·⌊a / b⌋, for b > 0. It always lies in [0, b).
a OR b, a XOR bBitwise, 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:

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:

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.

PolicyMeaning
derivedThe tensor’s range is whatever §8 derives from its producer. Nothing is enforced at run time.
clampThe 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.
checkThe 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):

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 one code for every refusal reports that code in place of these. Examples are wai.neural.int_synth and wai.neural.int_mlicpp, which report payload_malformed (integer-payloads §8).

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:

codeoperatorin → outfamilyin wai-ops/1-i32 (§9.1)in wai-ops/1-npu (§9.2)
0x01conv1 → 1linearyesyes; groups 1 or depthwise
0x02rescale1 → 1requantisationyesyes
0x03chscale1 → 1requantisationyesflagged
0x04lut161 → 1tableyesflagged
0x05add2 → 1elementwiseyesyes
0x06concatn ≥ 2 → 1movementyesyes
0x07chunk_add1 → 1elementwiseyesyes
0x08split1 → 1movementyesyes
0x09pixel_shuffle1 → 1movementyesyes
0x0Apixel_unshuffle1 → 1movementyesyes
0x0Bclamp1 → 1elementwiseyesyes
0x0Crelu1 → 1elementwiseyesyes
0x0Dleaky1 → 1elementwiseyesyes
0x0Epad_rep1 → 1movementyesyes
0x0Fcrop_to2 → 1movementyesyes
0x10mul2 → 1elementwiseyesyes
0x11conv_transpose1 → 1linearyesyes (§9.3)
0x12igdn1 → 1normalisationyesno
0x13abs1 → 1elementwiseyesyes
0x14crop1 → 1movementyesyes, with constant operands only
0x15subpos_split1 → 4movementyesyes
0x16subpos_merge4 → 1movementyesyes
0x17fill1 → 1movementyesyes
0x18layernorm_c1 → 1normalisationnono
0x19l2norm1 → 1normalisationnono
0x1Asoftmax1 → 1normalisationnono
0x1Bsigmoid1 → 1scalar functionnono
0x1Celu1 → 1scalar functionnono
0x1Dgram2 → 1contractionyesno
0x1Eapply2 → 1contractionyesno

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:

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

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):

Changing any one setting changes decoded output.

5.2 conv (0x01)

Parameters

Shape. The input is [cin, H, W]. Both cin and cout are multiples of groups, with H + 2ph ≥ kh and W + 2pw ≥ kw.

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

groups is 1.

Shape. The input is [cin, H, W].

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.

chscale. The parameters are:

There is no bias.

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.

5.5 Elementwise: add (0x05), chunk_add (0x07), clamp (0x0B), relu (0x0C), leaky (0x0D), abs (0x13)

operatorshapedefinition
adda and b have the same shapea + 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 i64clamp(v, lo, hi)
relusame shapemax(v, 0)
leaky(a, f)same shape; a fits i64, f in 0..=63v if v ≥ 0, else ⌊v·a / 2^f⌋
abssame 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

Shape. The output has the input’s shape. Two load checks apply (model_mismatch):

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.

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.

layernorm_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])

l2norm.

T = Σ_{y,x} v²
U = T + ε
k = rsqrt_q16(U)
out = ⌊v · k / 2^16⌋

softmax.

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.

5.8 Scalar functions: sigmoid (0x1B), elu (0x1C)

sigmoid. No parameters; the shape is unchanged.

elu(α). α fits i64; the shape is unchanged.

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

operatorparametersshapedefinition
concat (0x06)noneinputs share H and W → channels summedthe inputs’ channels, in input order
split (0x08)start, count ≥ 1start + count ≤ C → [count, H, W]out[c] = in[start + c]
pixel_shuffle (0x09)r ≥ 1C 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 ≥ 1H 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 refref’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 scalary0, 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 i64the output’s channels come from its tensor record; H and W come from the inputevery 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:

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:

This section names those four and adds the modes other capabilities need. It also defines the other nine, which no payload extension defines today.

#operatordefinitionwidth
E1rans64_decodeinteger-payloads §2, with the modes belowu64 state
E2dequantizesym·2^f + base128-bit, or 64-bit checked
E3bucketscale to table index, rule ceil or floorcomparisons
E4codedv > thrcomparisons
E5scale_indexclamp(R_half_up(v, s), 0, n − 1)i32
E6block_stata rounded block sum, broadcast over its blocki32
E7band_lookupcount the edges, then look up the tablecomparisons
E8mans_decodebelowu64 window, u8 states
E9residual_stepclamp(pred + r·q, lo, hi)i64, or i32 by identity
E10mc_predictinteger-payloads §3i32
E11up2_predictstaged-delivery A.4i32
E12rate_predictstaged-delivery A.4, with rdivi64
E13coding_orderraster or checkerboardindices

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:

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.

E2 dequantize(sym, f, base).

E3 bucket(σ, e, rule). Comparisons only.

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.

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.

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.

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

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:

For each column k in order, while C[k] < 256:

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:

Hyper-latent decode (fac).

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.

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.

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.

An implementation MUST NOT:

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.

classacc64acc32
tensorfits i64fits i32
accfits i64fits i32
widefits i128fits i32
productfits i128fits 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

operatorparameter condition under acc32key (§8.5)
every rq (conv, conv_transpose, rescale, mul, gram, apply)each multiplier fits i32rq.mult
each shift is at most 63rq.shift
each post bias fits i32rq.post_bias
conv, conv_transposeeach weight fits i32weights
chscaleeach entry of M fits i32chscale.m
s is at most 63chscale.s
igdneach γ fits i32igdn.gamma
gf and frac are each at most 63igdn.shift
leakya fits i32leaky.a

A pre bias needs no condition: it lies in rq.t’s interval, which already fits i32.

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:

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.

7.4 Stage operators

Stage operators (§5E) run in the driver at the widths listed in §5E.

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

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:

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.

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.

operatorrule
convFor 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_transposeFor 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.pLet [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.qrq.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.yrq.y = [q_lo + b, q_hi + b], where b is the channel’s post bias, or 0. The producer result is rq.y.
rescalerq.t = [l + b_pre[c], h + b_pre[c]]. Then the rq rules.
chscaleFor 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.
mulmul.p is the corner hull of a’s and b’s intervals. rq.t = mul.p. Then the rq rules.
gramgram.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.
applyapply.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, absthe image of the interval under the map: [clamp(l, lo, hi), clamp(h, lo, hi)]; [max(l, 0), max(h, 0)]; `
leakyIf 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]
igdnigdn.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_cln.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]].
l2norml2.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_q16Given 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.
softmaxsm.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].
sigmoidu is the image of [l, h] under `
eluu = [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).
movementThe 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⌋.

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.

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:

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.

operatorkeys
convconv.term, conv.acc, rq.t, rq.p, rq.q, rq.y
conv_transposeconvT.term, convT.acc, rq.t, rq.p, rq.q, rq.y
rescale, chscalerq.t, rq.p, rq.q, rq.y
mulmul.p, rq.t, rq.p, rq.q, rq.y
gramgram.term, gram.acc, rq.t, rq.p, rq.q, rq.y
applyapply.term, apply.acc, rq.t, rq.p, rq.q, rq.y
leakyleaky.p
igdnigdn.x2, igdn.gx2, igdn.term, igdn.N, igdn.r, igdn.xr
layernorm_cln.S, ln.m, ln.d, ln.T, ln.v, rsq.num, ln.k, ln.dk, ln.n, ln.ng
l2norml2.T, rsq.num, l2.k, l2.vk
softmaxsm.diff, exp.negx, exp.nl, exp.t2, exp.c, sm.sum, sm.num
sigmoidexp.negx, exp.nl, exp.t2, exp.c, sig.num
eluexp.negx, exp.nl, exp.t2, exp.c, elu.p
every other operatornone: 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:

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:

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.

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:

flagwhen
lut16a lut16: a 65,536-entry table
chscalea chscale: a multiplier row chosen at run time
state_addan 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_evenan rq or chscale whose rounding is half_even
rq_floor_multan rq or chscale whose rounding is floor and which has a multiplier
rq_post_biasan rq with a post bias
rq_neg_multan rq or chscale with a negative multiplier
rq_shift_rangean 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.

excludedreason
igdn, layernorm_c, l2norm, softmaxEach normalises by a statistic computed at run time. That needs a square root or a division, and a reduction across channels or positions.
sigmoid, eluEach needs a division, or a polynomial followed by a data-dependent shift. Where the input fits i16, an exporter replaces it with lut16.
gram, applyEach multiplies two run-time tensors and reduces over a length that depends on the picture.
a crop with a scalar operandIts output shape depends on a value.
a check policy inside a graphAn accelerator cannot refuse partway through a graph. §7.2 already excludes it from acc32.
every stage operatorThey 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:

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.

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.

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)

offsizefieldrule
04magic WHG1
41version1
51operator set1 (wai-ops/1)
62reserved0
84n_tensors≥ 1
124n_ops≥ 1
162n_inputs≥ 1
182n_outputs≥ 1
202n_scalars
222reserved0
244max_elems1..=2^26: the cap on every tensor of this graph
284reserved0

10.2 Tables

The tables follow the header in this order.

Nothing follows the last operator.

10.3 Graph rules

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

operatorparameters
convu32 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_transposeu32 cout, kh, kw, sh, sw, ph, pw, oph, opw; u8 w_bytes, 3 reserved; RQ; i<w_bytes> Wt[cin][cout][kh][kw]
rescaleRQ
chscaleu32 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]
mulRQ, with bias = 0
lut16i8 e_in, i8 e_out (each −128 for none), 2 reserved; i16 T[65536]
add, concat, chunk_add, relu, abs, crop_to, softmax, sigmoidnone (plen = 0)
splitu32 start, count
pixel_shuffle, pixel_unshuffleu32 r
clampi64 lo, hi
leakyi64 a; u32 f
pad_repu32 m
cropfour operands y0, x0, h, w, each u8 kind (0 constant, 1 scalar), 3 reserved, u32 value-or-index
subpos_split, subpos_mergeu8 order[4], each 2a + b
fillu8 kind (0 constant, 1 scalar), 7 reserved, i64 value (the index when kind = 1)
igdnu32 gf, frac; u8 g_bytes (4 or 8), 7 reserved; i<g_bytes> γ[C][C]; i64 β[C]
layernorm_cu64 ε; u8 g_bytes (4 or 8), 7 reserved; i<g_bytes> γ[C]; i64 β[C]
l2normu64 ε
elui64 α
gram, applyu32 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" }
}

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

In the sink.

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.

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.

sourcew_bytesm_bytesb_bytesg_bytes
int_synth.json4—4—
WHM1, WAM12884
a wai.image.jpegai bundle (JSON)smallest—smallest—

Roles and order. A lowered set’s graphs, by role, in this order:

capabilityloweringgraphs, in orderoutput step inside the graph
wai.neural.int_synthint_synth/1synthclamp(0, 255)
wai.neural.int_hyper; the keyframe of wai.video.int_hyperwhm1/1h_s, g_sin g_s, the RGB8 step
wai.audio.int_codecwam1/1g_sthe PCM step (§13.3)
wai.splat.int_codecwhm1/1g_sthe RGB8 step
wai.neural.int_mlicppwmb1/1h_s, entropy, g_sin g_s, the RGB8 step
wai.world.model (informative)world/1T, g_sin g_s, the RGB8 step
wai.image.jpegaijpegai-int/1hs_y, hs_uvthe 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:

graphinput 0max_h, max_wscalars
synththe latents [C, S, S], check [−128, 127]0, 0none
a whm1/1 or wam1/1 graphx_0, the network’s input, with the policy and range of §13.20, 0wam1/1 only: scalar 0, t_out, [1, 2^26]
T (world/1)z, check [−2^63, 2^63 − 1]0, 0scalars 0 … A − 1, the plane values, each [−2^63, 2^63 − 1]
hs_y, hs_uvz [Cz, 4, 4], check [−31, 32]4, 4none

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:

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:

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

taglowered to
0conv_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
1conv: groups = 1, kh = kw = k, sh = sw = stride, ph = pw = pad, weights i16 [out][c][k][k]; the same RQ
2igdn: gf = the header’s gf, frac = the header’s afrac; γ i32 [c][c], β i64 [c]
3relu
4leaky: 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:

tagP_ℓ(X)
0, 1c·k·k·Wm·X ≤ I64 and ⌊c·k·k·Wm·X·Mm / 2^shift⌋ + 1 + Bm ≤ I64
2G·X² ≤ I128; N = Bb + c·(⌊G·X²/2^gf⌋ + 1) ≤ I128; isqrt(N) ≤ I64; ⌊X·isqrt(N) / 2^afrac⌋ + 1 ≤ I64
4`X·

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:

13.3 WAM1 networks (wam1/1)

The header is that of WHM1. The tensors are [C, 1, T].

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.

capabilitypayloadstatusstage operators and modesgraphsdriver steps
wai.neural.int_synthinteger-payloads §13normativenonesynththe output’s shape; HWC bytes
wai.neural.int_hyper, and the keyframe of wai.video.int_hyperWIH1 (integer-payloads §4), with entropy.json (integer-payloads §9)normativerans64_decode (t4097, lenient, canonical); dequantize; bucket (ceil); coding_order (raster)h_s; g_sh_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_hyperWIV1 (integer-payloads §3)normativerans64_decode (t4097, lenient, canonical); mc_predict; residual_step with [0, 255]noneRGB8 frames
wai.audio.int_codecWIA1 (integer-payloads §5)normativerans64_decode (as above); dequantizeg_st_out as scalar 0; the samples as bytes
wai.splat.int_codecWIS1 (integer-payloads §6)normativeas aboveg_sCHW bytes
wai.neural.int_mlicppWIM1 (integer-payloads §14)normativerans64_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 channelsh_s; entropy before every pass; g_sthe 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_refineWIR1 (staged-delivery Appendix A)normativerans64_decode (t4097, lenient, canonical), one stream per unit; residual_step with [0, 255] or [−32768, 32767]; up2_predict; rate_predictnonethe unit’s elements
wai.world.modelnone yetinformativedequantize (the plane values)T; g_sbelow

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.

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

Table 13.6-A. The scale graph’s members.

blockweightsbiasper-channel shift
1conv1.weight [C1, Cz, 1, 1]conv1.bias [C1]onnx::Pow_85 [1, C1, 1, 1]
2depthwise.weight [C2, C1, 3, 3]depthwise.bias [C2]onnx::Pow_91 [1, C2, 1, 1]
3pointwise.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 stepwai-ops/1
the hyper decoder’s inputz·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 convolutionconv_transpose, with the same RQ
the shift inside a TABthe 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, clipclamp(0, 6·2^16), clamp
a residual or mean additionadd
a Q16 productmul; RQ: no multiplier, shift 16, floor
a gate x·(f + 1) in Q16rescale (a post bias of 2^16), then mul
sigmoid, ELU, softmaxsigmoid, elu(2^16), softmax
channel layer normalisationlayernorm_c(ε = 42949)
channel attentionl2norm(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-spacesplit; concat; subpos_split and subpos_merge; pixel_shuffle
denormalisationrescale 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 ] }

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:

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

operatorinexpect
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:

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:

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

These land with each capability’s lowering, capability by capability:

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.

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 payload whose reserved byte is not 0, or whose C is 0, is refused. The reference decoder does not check either today.
    • 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.
  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.
  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.