open-standards/wai/extensions/opus-dred.md
Transaction Science e72b520571 jwp-receipts §2.4.12: one exact path for rules 13 and 14 and the stage hash, checked per object
Rule 14 refused only `float` under a pin of decode-equivalence, while rule 13
and the stage-hash check read any variant but null or exact as not exact. Rules
6 and 13 already refuse such a pin for every class the registry gives, so rule
14 reaches only capabilities with no class, and there `fp16` passed. The three
now share one predicate, DecoderEntry::exact_path: null, `exact`, or, for a
NeuralIntegerExact capability, the lowering its registration names. The last is
how opus-dred §7.2's present entry names wai.audio.opus.dred.int's integer
path, `dred-int/1`, which rule 13 would otherwise have refused; opus-dred says
a session claim of that decode may name it so.

The stage-hash refusal is per object: an entry naming the base at a weaker step
counts whichever units it presented, so the outcome does not depend on whether
the verifier holds the outputs. determinism-tiers §3 rule 3 now says so. The
vector session_stage_hash_not_exact moves its fallback decode inside the set,
at unit 2, so it is refused under either reading, and the earlier form stays as
session_stage_hash_not_exact_outside_the_set, the per-object case.

determinism-tiers §3 calls the closed vocabulary a pin's, says rule 3 governs
decoded media while generation-equivalence carries its own digest, and says
rule 2 is held over the registry class. The README says rule 6 enforces it, with
an inter-frame capability's vector, and rule 14 is its per-path complement.

Five vectors are appended after the earlier ones, which a test pins by
digest: a registered lowering accepted, another capability's lowering
refused, `fp16` under decode-equivalence refused, an inter-frame capability
over its class refused, and the per-object case.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:29:01 -05:00

595 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# WAI Extension: Opus Deep Redundancy, Decoded Exactly
> Status: Draft. Registers two companion-only capabilities for the deep redundancy (DRED) of
> draft-ietf-mlcodec-opus-dred-07: a compact neural latent of the recent speech, carried in an
> Opus packet's padding, from which a receiver rebuilds the acoustic features of packets that
> were lost. `wai.audio.opus.dred` is that decoding as the draft defines it, held to the draft's
> own conformance test (its §4.1), which admits any decoder whose features lie within its
> thresholds of the reference. `wai.audio.opus.dred.int` is the same decoding through an
> **integer profile**, `dred-int/1`: the draft's own decoder network and its own weights file,
> pinned by digest, lowered to `wai-ops/1` graphs ([integer-operators](integer-operators.md))
> whose output is the same integers on every machine and on every fabric whose operator
> implementation conforms. With it, a stream under loss has one reconstruction, named by a
> digest that a verifier recomputes from the packets, the loss and the weights, and a player can
> sign that digest in the log of [synthesis](synthesis.md) §10.3. This extension also says where
> the working group's other documents stand in WAI's registry and in synthesis's lattice (§2.3,
> §7.1), and measures the profile against the floating-point network (§6). No new transport.
> The reference implementation is the `dred_exact` module of `wai-rs` (feature `dred_exact`);
> the corpus is [`live-conformance/dred/`](../live-conformance/dred/), and its second
> implementation is `live-conformance/dred_verify.py`. The integer profile as a proposal to the
> working group is drafted in [`proposals/dred-integer-profile.md`](../proposals/dred-integer-profile.md),
> which is not filed.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision, each pinned to the version named: draft-ietf-mlcodec-opus-dred-07
> (4 August 2026), with the files its §7 references by SHA-256 ([DRED-Weights],
> [DRED-Test-Vectors], [DRED-Compare], [DRED-Ref-Impl]); draft-ietf-mlcodec-opus-extension-06;
> draft-ietf-mlcodec-opus-speech-coding-enhancement-04; draft-ietf-mlcodec-opus-scalable-quality-extension-02;
> draft-ietf-mlcodec-test-battery-00; RFC 6716 §3, §4.1 and §5.1; RFC 7587.
## 1. Scope
The draft's decoder has two parts (its §4): a **feature decoder**, a recurrent network that
turns the latents into 20 acoustic features per 10 ms, which the draft makes normative and
tests within tolerances; and a **vocoder** that turns features into samples, which the draft
leaves to the implementation (its §2.2.4, §4.2). This extension's profile is of the first part.
**Conformance targets.**
- A **sink** that advertises `wai.audio.opus.dred.int` finds DRED in a packet (§3), decodes it
through `dred-int/1` (§4), and gives exactly the features §4 gives.
- A **verifier** of a reconstruction recomputes a stream's reconstruction from its packets, its
loss pattern and the weights, and checks a record of it (§5) and a player's statement of it
(§7.2).
**Out of scope.**
- The vocoder. A sink renders features to samples with any vocoder the draft admits; those
samples are its own, and §7.2 says what a player states of them.
- The decoding of an Opus packet's own frames (RFC 6716 §4), and concealment of a frame no DRED
rebuilds. §7.1 classes both.
- Encoding. The draft's encoder is not normative (its §2.2.1); the corpus's packets are written
by a generator that quantizes as §9 describes.
## 2. Registration
### 2.1 Two strings, one payload
| string | kind | role | replicate class | parameter set |
|---|---|---|---|---|
| `wai.audio.opus.dred` | companion | enhancement layer over `wai.audio.opus.packet` | `StandardTolerance`: features within the draft's §4.1 thresholds of the reference | the draft's, fixed by the draft |
| `wai.audio.opus.dred.int` | companion | enhancement layer over `wai.audio.opus.packet` | `NeuralIntegerExact`: the features of §4, exactly | one file, the draft's weights file (§4.1) |
Both carry the same payload. They differ in what a sink that advertises them promises, which is
what a sender chooses between: a sender that needs any rebuilt speech names the first; a sender
whose reconstruction is to be recomputed, signed or presented at `decode-equivalence` names the
second, with a pin on the weights file (SPEC §3.1) that declares `tier` `decode-equivalence`.
Two strings are needed because a capability's class bounds the tier a pin may claim (SPEC §3.1,
*Tier*): a decode admitted within tolerances cannot back `replicate`, and the draft's decoding is
admitted within tolerances.
### 2.2 Why a companion, not a refinement
A refinement-only capability is applied to the output of the stage below it, bound to that
output's producer by content hash ([staged-delivery](staged-delivery.md)): its payload corrects
what the base decoded. DRED corrects nothing the base decoded: it is a second, coarser encoding of
the same speech, sent ahead so that a receiver can rebuild what a loss took, and it is read only
where the base's own frames are missing. Its payload has no meaning without the encode it was
made with: its offsets index that encode's frames (draft §3), and its first rebuilt samples
continue that encode's last decoded ones (draft §2.2.4). That is the model of an **enhancement
layer** (SPEC §5): a companion-only capability, decoded by its own standard over one base
encode, bound to exactly one selectable rendition of a base it lists, and the base of no second
layer. So both strings are registered as enhancement layers. Their one base is
`wai.audio.opus.packet`, one Opus packet per object, because there the envelope itself aligns the
DRED payload with the packet it belongs to; this revision pins no alignment of a separate DRED
stream with an Ogg Opus stream (`wai.audio.opus`), as SPEC §5 pins none for a separately carried
LCEVC stream.
DRED is carried one of two ways, as an enhancement layer is (SPEC §5, *Enhancement layers*):
- **Embedded.** The DRED extension rides in the base packet's padding (draft §3), under extension
ID 32, or, until the IANA action the draft asks for, under the experimental ID 126 with the
two-byte prefix `'D'`, 12 (draft §3, §5). The payload is the base's bytes, under the base's
capability; a sink without DRED discards the padding (draft-ietf-mlcodec-opus-extension-06 §2).
No DRED component exists.
- **Separate.** A `WAI2` companion of the base rendition (SPEC §7.1), `role` `"companion"`,
`companion_of` `wai.audio.opus.packet`. Its payload is one DRED extension payload as draft §3
defines it (the bytes after the extension's ID byte and any length, with no experimental
prefix), for frame 0 of the base packet in the same envelope (a frame offset of 0, §3.3).
### 2.3 The working group's other documents
- **draft-ietf-mlcodec-opus-extension-06** defines the padding's extension format that §3.2
reads. It carries no payload of its own and registers no capability here.
- **draft-ietf-mlcodec-opus-speech-coding-enhancement-04** sets requirements for a decoder-side
enhancement of the Silk layer's output, and for a blind extension of its bandwidth above
8 kHz. It changes no byte of a packet, so it is no WAI capability: it is a property of the
sink's Opus decoder, and a stream decoded with it is still `wai.audio.opus` or
`wai.audio.opus.packet`, decoded within RFC 6716 §6's tolerances by a decoder that also passes
that draft's objective tests. §7.1 classes what it does to an object.
- **draft-ietf-mlcodec-opus-scalable-quality-extension-02** adds refinement bits for the CELT
layer, and bands above 20 kHz, in the same padding. It is not a neural decoder, and the
refinement rides embedded in the base packets, decoded by the base's decoder; this revision
registers no string for it.
- **draft-ietf-mlcodec-test-battery-00** describes test material for these extensions. WAI reads
it as informative.
### 2.4 Licence class
Both strings are classed `patent-pool`, the conservative class REGISTRY.md gives a capability
whose royalty status is not established. One IPR disclosure on the individual draft that became
draft-ietf-mlcodec-opus-dred, the IETF's disclosure 6063, states an intent to license
royalty-free if the technology is included in a standard the IETF adopts; until that happens the
status is not established. A later revision MAY move both strings to `royalty-free` once it is.
## 3. Finding DRED and its integers
### 3.1 The packet
A sink reads an Opus packet's framing as RFC 6716 §3.2 defines it, and holds it to §3.4's rules
R1–R7. A packet that breaks one is not a valid Opus packet: nothing in it is read, and its DRED
is refused (`packet_malformed`). From a valid packet it takes the number of frames, each frame's
duration in units of 2.5 ms (RFC 6716 §3.1: 4, 8, 16 or 24 for configurations 0–11 by
configuration modulo 4; 4 or 8 for 12–15 by configuration modulo 2; 1, 2, 4 or 8 for 16–31 by
configuration modulo 4), and the padding of a code-3 packet.
### 3.2 The extensions
The padding's extensions are read as draft-ietf-mlcodec-opus-extension-06 §2 defines them,
including the frame separator (ID 1) and the repeat mechanism (ID 2, §2.3), in bitstream order,
each instance with its frame index. Iteration stops at the first extension whose length, or
whose frame index, would read past the padding or the packet's last frame (§2.7); the instances
before it stand.
The packet's **DRED payload** is the first instance, in that order, that is either:
- ID 32 with at least one byte of data: the data; or
- ID 126 whose data is longer than two bytes and starts with `'D'` (0x44), 12: the data after
those two bytes.
Its **frame offset** is its instance's frame index times the frame duration, in units of 2.5 ms.
A packet with no such instance carries no DRED. (The reference implementation the draft names in
its Appendix B finds DRED this way, under ID 126.)
### 3.3 The payload
The payload is decoded with the range decoder of RFC 6716 §4.1, as draft §3 and §3.1 define it.
This section fixes the details the draft's prose leaves open, as the draft's reference decoder
does; where the two could be read apart, the reference decoder's reading is this profile's.
1. The header: `q0 = uint(16)`, `dQ = uint(8)`, `X = uint(2)`; if `X = 1`, an extended offset
`E = uint(256)`, else `E = 0`; an indicator `I = uint(32)`. `uint(n)` is `ec_dec_uint` of RFC
6716 §4.1.5 for `n ≤ 256`, one symbol of `n` equally likely values. `encoded_offset = 32·E + I`.
2. `qmax`: 15, unless `q0 < 14` and `dQ ≠ 0`. Then, with `n = 14 − q0`, a symbol is decoded over a
total of `2n` (`ec_decode`): a value `s ≥ n` gives `qmax = q0 + (s − n) + 1` and is consumed as
`[s, s + 1)`; a value `s < n` gives 15 and is consumed as `[0, n)`.
3. The **offset**, `dred_offset = 16 − encoded_offset + frame_offset`, in units of 2.5 ms: the
newest feature vector the payload rebuilds ends this long after the start of the frame that
carries the extension.
4. The **initial state**: 50 coefficients `k = 0 … 49`, at quantizer `q0`, with the state tables
(draft Tables 6, 8 and 9). Then **latent vectors** `l = 0, 1, …`, newest first, each of 25
coefficients at quantizer `q_l = min(qmax, q0 + ⌊(D[dQ]·l + 8) / 16⌋)` with
`D = [0, 2, 3, 4, 6, 8, 12, 16]`, the slope table of draft §3 in sixteenths, with the latent
tables (Tables 2, 4 and 5). Before each latent
vector, if `8·len − tell ≤ 7`, where `len` is the payload's length in bytes and `tell` is
`ec_tell` (RFC 6716 §4.1.6.1), no more are decoded.
5. **A coefficient** with decay `r` and zero probability `p0` (the tables' entries for its
coefficient and quantizer): if `r = 0` or `p0 = 255`, it is 0 and nothing is decoded.
Otherwise, with `P = 128·p0` and `S = 32768 − P`, a symbol is decoded by `ec_dec_icdf16` (with
`ftb = 15`) against the table `[S, ⌊S/2⌋, 0]`: 0 is a zero coefficient, 1 positive, 2
negative. A non-zero coefficient's magnitude is then `1 + v_1 + v_2 + …`, each `v_j` decoded
by `ec_dec_icdf16` against `icdf[0] = max(7, 128·r)`, `icdf[i] = max(7 − i, ⌊icdf[i − 1]·128·r
/ 2^15⌋)` for `i = 1 … 6`, `icdf[7] = 0`, while the symbol just decoded is 7. When the
magnitude being summed passes `2^16`, the payload is refused (`dred_latent_range`): the draft's
escape rule bounds nothing, and past a payload's last byte the decoder reads zero bytes
(RFC 6716 §4.1.2), so a crafted payload could otherwise keep a decoder reading.
The draft's test bit files carry payloads with no header (each record gives `q0`, with no slope,
and a count of latents); §6 reads them so.
## 4. The integer profile `dred-int/1`
### 4.1 The parameter set
The parameter set of `wai.audio.opus.dred.int` is **one file**: the decoder weights file draft
§2.2.2.1 and §7.1 name ([DRED-Weights]), whose SHA-256 is
`394f0ecf19926cebd678019dc94a54c88ae078340c16325069707f286cdf28a6`. A sink MUST check the file
against that digest before it reads it, and MUST refuse any other file
(`weights_digest_mismatch`): the draft makes another set of weights non-conforming (its §4.1, "an
alternate DNN model is not allowed"), and §4.4 makes these weights' integers a function of this
file's bytes. A pin over the set names that digest.
The file is read as the draft's Appendix A defines it. The profile reads, for each layer of the
table below, the arrays `dec_<layer>_weights_float` and `dec_<layer>_bias`, and for a block-sparse
layer `dec_<layer>_weights_idx`; it reads no other array. A dense matrix is stored input-major
(`weights[in·outputs + out]`, A.3.1). A block-sparse one (A.3.2) is a run of groups of 8 outputs,
each a count of blocks and then each block's first input (a multiple of 4), and its weights are
blocks of 8 outputs by 4 inputs stored input-major inside the block (`w[in·8 + out]`); every
position no block covers is 0.
| layer | inputs | outputs | sparse |
|---|---|---|---|
| `dense1` | 26 | 96 | no |
| `hidden_init` | 50 | 128 | no |
| `gru_init` | 128 | 320 | yes |
| `gru1_input` … `gru5_input` | 96, 192, 288, 384, 480 | 192 | yes |
| `gru1_recurrent` … `gru5_recurrent` | 64 | 192 | no |
| `glu1` … `glu5` | 64 | 64 | no |
| `conv_dense1` … `conv_dense5` | 160, 256, 352, 448, 544 | 32 | yes |
| `conv1` … `conv5` | 64 | 32 | no |
| `output` | 576 | 80 | yes |
The weights are not in this repository. The corpus names the file by digest, and its fetch script
(`live-conformance/dred/fetch.sh`) fetches it from the location the draft gives and checks the
digest.
### 4.2 The tables
The decoder reads draft Tables 2, 4, 5, 6, 8 and 9 (scale, decay and P(0), for the latents and
for the state). Serialized row by row (coefficient `k` from 0, the sixteen quantizers in order,
one byte each), in the order latent scale, latent decay, latent P(0), state scale, state decay,
state P(0), their SHA-256 is
`03f7d6b22e2aca5f5a28525a1311e1113d94b917fbc74a5493b04bbed5c97ded`. An implementation that
transcribes them checks its transcription against this digest.
### 4.3 The inputs
Every graph input is an integer at `2^16` (Q16). A coefficient `x` of the initial state, with
state scale `s` at `q0`, and a coefficient of latent `l`, with latent scale `s` at `q_l`, stand
for `x·256 / max(1, s)` (draft §3.1), which the input holds as
`⌊(2·x·2^24 + s′) / (2·s′)⌋` with `s′ = max(1, s)`: the quotient rounded to the nearest
integer, a tie upward. A latent's 26th input is `q_l/8 − 1` (draft §2.2.2), held as
`(q_l − 8)·2^13`.
### 4.4 Weights to integers
Every weight becomes an integer at `2^24`, and every bias an integer at the scale of the
accumulator it joins: `2^40` for `dense1` and `hidden_init`, whose inputs are at `2^16`, and
`2^39` for every other layer, whose inputs are activations at `2^15`. Each is computed from the
float32 value's bits alone, with no floating-point arithmetic:
- a float32 with sign `σ`, exponent field `e` and fraction field `m` is `(−1)^σ·M·2^E`, with
`M = m`, `E = −149` when `e = 0`, and `M = m + 2^23`, `E = e − 150` otherwise;
- at scale `2^f`, its integer is `(−1)^σ·R_half_even(M, −(E + f))` when `E + f < 0`, and
`(−1)^σ·M·2^(E + f)` otherwise, with `R_half_even` of [integer-operators](integer-operators.md)
§2 (0 for a shift of 63 or more).
A value that is not finite (`e = 255`), a weight whose integer does not fit i32, a missing array,
an array of the wrong length, a block that starts at an input that is not a multiple of 4 or past
the layer's inputs, a group that names one block twice, or a record the file format refuses,
makes the set fail to lower (`model_mismatch`, integer-operators §10.5). The pinned file does
none of these.
### 4.5 Activations
Activations are integers at `2^15` (Q15), in `[−32767, 32767]`. `tanh` and the sigmoid are
`lut16` tables (integer-operators §5.6), read at `2^12` and `2^11`. Their entries are this
integer computation, not the functions' exact values:
- `C_0 = 4609434768280048980` (`⌊e^(−1/2048)·2^62⌋`), and `C_(k+1) = ⌊C_k² / 2^62⌋`;
- for `0 ≤ u < 2^15`, `X(u)` starts at `2^62`, and for each bit `k` of `u` from bit 0 to bit 14
that is set, in that order, `X ← ⌊X·C_k / 2^62⌋`;
- `tanh`: entry `v + 32768`, for `v` in `−32767 … 32767`, is `sgn(v)·min(32767, ⌊(2a + b) /
(2b)⌋)` with `a = 2^15·(2^62 − X(|v|))` and `b = 2^62 + X(|v|)`;
- sigmoid: entry `v + 32768` is `min(32767, ⌊(2a + b) / (2b)⌋)` with `b = 2^62 + X(|v|)`, and
`a = 2^77` for `v ≥ 0`, `a = 2^15·X(|v|)` for `v < 0`;
- entry 0 (`v = −32768`) of each is 0, as `lut16` requires. No certified input reaches it.
Every intermediate fits 128 bits. Serialized as their 65,536 entries in order, each a 16-bit
two's-complement little-endian integer, the tables' SHA-256 are
`aea887ef206a10ea37bd31ef310a75c2ff05ca17df7edbbc15916969bccb4c22` (`tanh`) and
`1fe2d7c6b3255127f612576093f2f50e9e13d88da546ad4c04de264e2bbc8c36` (sigmoid).
### 4.6 The graphs
`dred-int/1` lowers the set to two `WHG1` graphs (integer-operators §10), `init` and `step`, in
which every tensor is `[C, 1, 1]`. Below, `dense(x; L, s)` is a `conv` with `kh = kw = 1` and
`groups = 1` over `x`, with layer `L`'s integer weights (`w_bytes` 4), and an `RQ` with no
multiplier, one shift `s`, `L`'s integer bias placed `pre` (`b_bytes` 8) and `round` `half_up`;
`T_tanh` and `T_sig` are `lut16` with the tables of §4.5; `C(·)` marks an output tensor whose
policy is `clamp [−32767, 32767]` (every table input, and every GRU state); `mul(a, b)` is `mul`
with an `RQ` of shift 15, no bias, `half_up`; `[a, b]` is `concat`; every other tensor's policy is
`derived`.
**`init`.** Input 0 is the initial state `[50]`, policy `clamp [−(2^31 − 1), 2^31 − 1]`.
1. `a = T_tanh(C(dense(IS; hidden_init, 28)))` `[128]`;
2. `s = T_tanh(C(dense(a; gru_init, 27)))` `[320]`;
3. outputs: the five GRU states `h_k = split(s, 64·(k − 1), 64)`, `k = 1 … 5`.
**`step`.** Inputs: the latent `Z` `[26]`, policy `clamp [−(2^31 − 1), 2^31 − 1]`; the GRU states
`h_1 … h_5` `[64]`; the convolution memories `c_1 … c_5` `[32]`; each of the last ten with policy
`clamp [−32767, 32767]`. Then:
1. `t = T_tanh(C(dense(Z; dense1, 28)))` `[96]`.
2. For `k = 1 … 5`, with `x = t`, `h = h_k`, `I = gru<k>_input` and `U = gru<k>_recurrent`:
- the **gates**: `g = T_sig(C(conv([x, h])))`, `[128]`, where the `conv` is `dense` with shift
28 whose output `o < 128` has the weights of row `o` of `I` followed by those of row `o` of
`U`, and the bias `b_I[o] + b_U[o]` (each converted by §4.4 on its own); `z = split(g, 0,
64)`, `r = split(g, 64, 64)`;
- the **candidate**: `p = dense(x; rows 128–191 of I, 19) + mul(r, dense(h; rows 128–191 of
U, 19))` (`add`), at `2^20`; then `hc = T_tanh(C(rescale(p)))`, the `rescale` with one shift
8, no bias, `half_up`;
- the **state**: `h′ = C(hc + mul(z, h + rescale⁻(hc)))`, where `rescale⁻` is `rescale` with
one multiplier −1 (`m_bytes` 4), shift 0, no bias, `floor`, and each `+` is `add`. This is
draft §2.2.2's `z·h + (1 − z)·hc`, written so that each product has one rounding; with
`0 ≤ z < 2^15` its value lies between `hc` and `h`, so the clamp changes nothing;
- the **GLU**: `u = mul(h′, T_sig(C(dense(h′; glu<k>, 28))))`;
- `t ← [t, u]`;
- the **causal convolution**: `c = T_tanh(C(dense(t; conv_dense<k>, 27)))` `[32]` and
`v = T_tanh(C(dense([c_k, c]; conv<k>, 27)))` `[32]`;
- `t ← [t, v]`; the new `h_k` is `h′` and the new `c_k` is `c`.
3. `x = dense(t; output, 23)` `[80]`, at `2^16`, policy `clamp [−2^31, 2^31 − 1]`.
Outputs, in order: `x`, the five new GRU states, the five new memories.
Tensors are numbered as integer-operators §13.0 numbers a lowered graph (inputs first, then each
operator's output in operator order), every input declares `max_h = max_w = 1`, every exponent is
none, and the operators are written in the order above (for the GRU: `concat`, the gates' `conv`,
`lut16`, the two `split`s, the candidate's two `conv`s, `mul`, `add`, `rescale`, `lut16`, then
`rescale⁻`, `add`, `mul`, `add`). For the pinned set, the graphs' identities (the SHA-256 of their
`WHG1` bytes) are `6527304e48bbd6c972fb41cb24ab5f387a3adee6f88347dfc8f10f47060e5c9d` (`init`)
and `4c76f2e5f6b2657b985aa171f7872fc18ae48b9419447e69cb3e52a3225b34d2` (`step`).
### 4.7 The driver
A payload is decoded as a **sequence** of graph runs:
1. `init` on the state input (§4.3); its outputs are the GRU states. The memories are 0.
2. For latent `l = 0, 1, …`, newest first: `step` on the latent's input (§4.3), the GRU states and
the memories; its first output `x` gives four feature vectors, and the others are the states
and memories of the next latent.
The **rows** of the payload are those vectors, newest first: row `4l + j` is
`x[20j], …, x[20j + 19]` of latent `l` (draft §2.2.2: the four vectors of a latent are in reverse
chronological order), and each row is 10 ms older than the row before it. Row 0 ends at the
payload's offset (§3.3 step 3). Each value is the feature at `2^16`, an i32.
Decoding the first `n` latents gives the first `4n` rows, the same integers whatever follows:
the network runs newest first, so a row never depends on an older latent. A sink MAY stop after
the rows it needs.
### 4.8 Profile and fabrics
Every operator of both graphs is in `wai-ops/1-i32` (integer-operators §9.1), and both graphs'
certificates meet `acc64` and not `acc32`: a 16-bit activation times a 32-bit weight, summed over
up to 576 inputs, passes 32 bits. The registry's `ops` member of `wai.audio.opus.dred.int` is
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "dred-int/1"}`; the
payload's range decoder is RFC 6716's and not a stage operator of integer-operators §5E, so the
member has no `stage`. On a GPU, the reference implementation's WGSL kernels (`wai-ops-gpu`) run
both graphs at `acc64`, and `wai-rs/tests/dred_exact_gpu.rs` holds their outputs to the CPU's
graph by graph.
## 5. A stream under loss
### 5.1 The loss pattern
A loss pattern is a seed of 32 bytes and two thresholds, `enter` and `stay`, each an integer in
`0 … 2^16`. Packet `n` (from 0) draws `u_n`, the first two bytes, big-endian, of
SHA-256(`"wai:dred-loss\x01"` ‖ seed ‖ `n` as a big-endian u32). It is lost when `u_n < enter` and
the packet before it was received (or `n = 0`), and when `u_n < stay` and the packet before it was
lost.
### 5.2 Slots
A stream is a run of packets of one frame each, all of duration `d` (in units of 2.5 ms), so
packet `n` starts at `n·d`. Each lost packet's frame is two 10 ms **slots**, half 0 (the earlier)
and half 1, and half `h` of frame `m` ends at `E = m·d + 4·(h + 1)`. A maximal run of lost
packets is **recovered** by the packet after it, when that packet was received and carries DRED
whose payload is not refused (§3); then, with `A = n·d + dred_offset` the end of its row 0 (`n`
the recovering packet), half `h` of frame `m` takes row `r = ⌊(A − E) / 4⌋` of it when `A − E ≥ 0`
and `r` is less than four times the payload's latent count. Otherwise the slot is **not
covered**: no DRED rebuilds it.
### 5.3 The reconstruction buffer
The reconstruction of a stream is its slots in order (frame ascending, then half), as bytes: for
each, the frame (u32), the half (u8) and 1 if it is covered or 0 if not (u8); for a covered one
then the recovering packet (u32), the row (u32) and the row's 20 features (i32 each); every
integer little-endian. Its digest is SHA-256 over those bytes. The buffer of one run is the same
over that run's slots alone.
### 5.4 The record
A record of a reconstruction states:
- `weights_sha256`: the weights file the decode used;
- `lost`: the indices of the packets the stream lost, ascending;
- `slots`: each slot as `[frame, half, packet, row]`, or `[frame, half, null, null]` for one not
covered;
- `output`: the reconstruction's digest, hexadecimal.
### 5.5 Verification
A verifier is given the stream (each received packet as the object a live track carries it in:
a `WAI1` envelope of `wai.audio.opus.packet`, with nothing where a packet was lost), its loss
pattern, the record, and the weights file. It checks, in order, and refuses at the first check
that fails:
1. the record's `weights_sha256` is the digest of §4.1 (`weights_digest_mismatch`);
2. the packets the loss pattern loses are the record's `lost`, and are exactly the packets the
stream lacks (`loss_pattern_mismatch`);
3. the slots §5.2 gives are the record's `slots` (`reconstruction_mismatch`);
4. the digest of the reconstruction §4 gives is the record's `output` (`reconstruction_mismatch`).
A reconstruction that passes is the one every conforming sink gives: the profile admits no other.
## 6. Measurement
What the profile changes is the arithmetic: the same network, the same weights, at fixed point.
`wai-rs/tests/dred_exact_measure.rs` measures how far that moves the features, on the draft's own
test vectors ([DRED-Test-Vectors]): all eight DRED bit files, 20 records each, every record's
latents decoded through the profile, and through the network in floating point (`f64`, from the
same float32 weights, with `tanh` and the sigmoid computed exactly to `f64` precision); and every
packet of the eight packet files that carries DRED, its DRED decoded both ways. Each pair is
compared by the draft's feature comparison (its §4.1 and its tool [DRED-Compare]): per feature,
the mean squared difference over the frames; `total` the square root of their sum, `max` of
their largest, the pitch feature left out of both; `pitch` the mean absolute pitch difference
over the frames the reference voices. The draft's DRED decoding test passes at `total ≤ 0.5`,
`max ≤ 0.15` and `pitch ≤ 0.02`. Two figures are added: the largest absolute difference of any
feature but pitch, and a band log-spectral distance. The 18 cepstral features are the orthonormal
DCT-II of the log10 band energies (draft §2.1.3), so the inverse DCT of their difference is the
difference of the log band energies, and ten times its root mean square over the bands is a
distance in decibels for each frame; its mean over the frames and its largest are given.
The reference features are the test vectors' own. "8-bit emulation" is the floating-point network
computing each layer the weights file also holds 8-bit weights for as the reference
implementation does when it reads them: each input rounded to `⌊0.5 + 127·x⌋`, the 8-bit weights,
each output scaled by its scale.
| compared | frames | total | max | pitch | largest difference | band LSD mean (dB) | band LSD max (dB) |
|---|---|---|---|---|---|---|---|
| integer, reference | 16000 | 0.1314 | 0.0767 | 0.00492 | 0.4139 | 0.2945 | 1.0034 |
| float, reference | 16000 | 0.1314 | 0.0767 | 0.00492 | 0.4151 | 0.2944 | 1.0060 |
| 8-bit emulation, reference | 16000 | 0.0831 | 0.0458 | 0.00255 | 0.2542 | 0.1844 | 0.6929 |
| integer, float | 16000 | 0.0021 | 0.0013 | 0.00011 | 0.0084 | 0.0048 | 0.0210 |
| integer, float (packets) | 80452 | 0.0022 | 0.0013 | 0.00004 | 0.0085 | 0.0049 | 0.0208 |
What the table shows:
- **The profile passes the draft's test** against the draft's reference features, on each of the
eight vectors and over all of them, with `total`, `max` and `pitch` well inside the thresholds.
- **The profile is close to the network.** Against the floating-point network on the same
latents, its features differ by `total` 0.0021, at most 0.0084 in any feature, 0.005 dB of band
log-spectral distance on average: about one sixtieth of the distance between the network and the
draft's reference features.
- **The reference features are not the network's.** The floating-point network and the profile
lie the same distance from them, and the 8-bit emulation lies closer, but not at them. So the
draft's test vectors carry the arithmetic of the build that wrote them.
The last point was also checked once, informatively, on one arm64 host, by building the draft's
reference implementation ([DRED-Ref-Impl]) twice: in its default configuration, which reads the
8-bit weights, and with its float weights, which it computes in float32 with a rational
approximation of `tanh`. Over the eight vectors, the default build's features lie at `total`
0.090–0.096 from the test vectors and the float build's at 0.131–0.136; the profile and the
floating-point network each lie at 0.015–0.018 from the float build, and at 0.129–0.135 from the
default build. The two builds differ from each other by about 0.13 (on the three vectors compared
directly), and neither gives the test vectors' features. Two builds of the draft's own decoder on
one machine, and the build that wrote the test vectors, give three different sets of features
from the same latents; under the integer profile there is one.
## 7. Receipts and the synthesis lattice
### 7.1 Classes
In [synthesis](synthesis.md)'s lattice (§3.2):
- **A reconstruction from DRED is a `convert`.** The features are what the packets carried: a
second encoding of the same speech, which a decoder with a pinned parameter set rebuilds
(synthesis §3.2: "a decoder's reconstruction (neural or not)"). The vocoder that renders them
continues that reconstruction; it generates no content the packets did not describe. So an
object rebuilt from DRED keeps its input's class, `reconstructed` from a capture.
- **Concealment beyond DRED is a `generate`.** Where no DRED covers a slot (§5.2), a sink that
fills it with speech predicted by a model (packet loss concealment, which draft §2 names as what
DRED's synthesis may continue from) produces content the packets never carried, by a trained
model:
that is `generate`, class `synthesized`, with the model in `models`. It is not `assistive`:
synthesis's `assist` edits an input without substantially altering it, and a concealed slot has
no input to edit.
- **Speech coding enhancement is an `assist`.** A decoder-side enhancement of decoded speech
(draft-ietf-mlcodec-opus-speech-coding-enhancement-04) edits one input without substantially
altering it, and that draft's tests bound how far it may move the decoder's own output (its §3
and §4); a blind bandwidth extension adds
content above 8 kHz the packets did not carry, as an upscale adds detail, which synthesis also
classes `assist` (§3.4). Neither is a pinned derivation of synthesis §4, so their evidence
states `tier` `attest`.
### 7.2 The presentation statement
A player that presents a reconstruction under the profile signs it in its log (synthesis §10.3):
for each run of lost packets it presents, a `present` entry whose
- `subject` is the content hash (jwp-receipts §2.1) of the recovering packet's object;
- `tier` is `decode-equivalence`, `decoder` is `wai.audio.opus.dred.int` and `variant` is
`dred-int/1`;
- `output` is the digest of that run's reconstruction buffer (§5.3), `origin` and `class` as
synthesis §10.3 has them (`null` where the player applies no claim).
That `variant` is the lowering `wai.audio.opus.dred.int`'s registration names. A
`reconstruct-session` claim of the same decode may name its decoder entry's path by it, by
`exact` or by `null`: each is an exact path of a `NeuralIntegerExact` capability
(jwp-receipts §2.4.12, `variant`). Any other term is not, and the entry is refused
(`session_variant_not_exact`).
A verifier checks the log as synthesis §10.3 does (`record_malformed`, `bad_key`, `bad_signature`,
`player_chain_broken`), then each `present` entry: its decoder, variant and tier are those above,
its subject is the content hash of the object that recovers a run of the stream, and its `output`
is the digest of that run's buffer that §4 gives. Otherwise the entry states an output the object
does not decode to (`present_output_irreproducible`, synthesis §9.2). The features are the
statement's subject because they are what the profile fixes; a player that also states the digest
of its vocoder's samples states its own output, which no other sink reproduces unless it runs the
same vocoder, exactly.
## 8. Refusals
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `packet_malformed` | §3.1 |
| `dred_latent_range` | §3.3 step 5 |
| `weights_digest_mismatch` | §4.1, §5.5 step 1 |
| `loss_pattern_mismatch` | §5.5 step 2 |
| `reconstruction_mismatch` | §5.5 steps 3 and 4 |
A set that fails to lower or load is refused `model_mismatch` (integer-operators §10.5). A
verifier of a presentation statement also raises synthesis's codes for a log (§7.2).
## 9. Conformance
[`live-conformance/dred/`](../live-conformance/dred/) holds the corpus, written from fixed seeds
by `wai_dred_vectors gen` with the weights file of §4.1, which it names and does not hold:
`vectors.json`, its README, and `fetch.sh`. It has:
- **The profile's identities**: the tables' digest (§4.2), the constant and the two tables'
digests (§4.5), and the two graphs' identities (§4.6).
- **Payload vectors**, each a packet and what a decoder states of it: for a packet with DRED, its
header, frame offset, offset, latent count, the digest of its integers, the digest of every row
and row 0 itself; for one without, that it has none; for one refused, the code. The digest of a
payload's integers is SHA-256 over `q0`, `dQ`, `X`, `E`, `I` and `qmax`, one byte each; the
latent count (u32); the state's 50 indices (i32 each); then, for each latent, its quantizer (one
byte) and its 25 indices (i32 each); little-endian. The digest of the rows is SHA-256 over every
row in order, each feature an i32, little-endian. They cover the
experimental ID with its prefix and ID 32; a slope with a `qmax` below 15 and an extended
offset; a DRED extension on a packet's second frame after a frame separator; an experimental
extension of another version, a truncated extension and plain padding, which carry none; two
malformed packets; and a payload whose magnitude passes the cap.
- **Streams**, each a run of packets as `WAI1` objects with the lost ones absent, and a loss
pattern, and **cases** over them, each a record, a player's log, and the outcome: the
reconstruction as the profile gives it, with a log of its recoveries, for two streams (one with
a slope and an offset that leaves the newest frames of every run uncovered); and one refused
case for each check of §5.5 and §7.2: a record naming another weights file; a record listing a
received packet as lost; a record naming a row one older; a digest with one bit changed; the
digest of a floating-point decode of the same latents, rounded to `2^16`; a log presenting that
decode at `decode-equivalence`; and a log entry whose signature is broken.
Every packet of the corpus is written by its generator: its DRED indices are drawn from the
draft's own distributions, by decoding a seeded byte stream with §3.3's coefficient rule at `q0`,
and requantized to each chunk's quantizer (`x·s_q / s_q0`, rounded half up, where `q` codes the
coefficient). The primary frames are empty (length 0, RFC 6716 §3.2.1). No recorded speech is in
the corpus.
**Feature equivalence.** For each payload vector and each case, a conforming implementation
reaches the outcome the vector states, and for an accepted one, the same digests, rows and
statements, byte for byte.
**On every fabric.** `wai-rs/tests/dred_exact_gpu.rs` decodes the corpus's payloads with every
graph on each GPU adapter found and holds the output to the CPU's.
## 10. Security and privacy considerations
- **Bounded work.** A payload's work is bounded by its length and by the magnitude cap (§3.3):
without the cap, the escape rule could read without end past the payload's last byte. A sink
SHOULD also bound the latents it decodes to the span it needs (§4.7), as the draft lets it
(its §3: "decoders MAY ignore any redundancy data beyond a certain amount").
- **No floating point in the decode.** The profile's decode forms no float, so the denormal
slow-downs draft §6 warns of do not arise in it.
- **Speech before joining.** DRED can carry speech from before a receiver joined (draft §6). A
reconstruction record names the packets and slots it rebuilt; a verifier given a room's
history learns nothing from a record that its packets did not already carry, but a relay that
forwards DRED forwards that speech, and the draft's `ext32-dred-duration` limits apply.
- **What a statement proves.** A presentation statement over a reconstruction's digest proves
which features the player's decode fixed, from packets the verifier holds. It does not prove
that the packets are authentic or that the samples a listener heard are those features
rendered; the object's own receipts (jwp-receipts) and the vocoder's exactness are separate
matters.