open-standards/wai/wai-det-jpegai/README.md
Transaction Science e517beace6
Some checks failed
wai-det-jpegai WAISM float-level module of record / does this change touch it (push) Successful in 19s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
wai-det-jpegai WAISM float-level module of record / linux · both checkers, F1, F2 on wasmtime (Cranelift, Winch, Pulley) and Wasmi, the planted faults (push) Successful in 40m47s
wai-det-jpegai WAISM float-level module of record / linux-arm64 · both checkers, F1, F2 on wasmtime (Cranelift, Winch, Pulley) and Wasmi, the planted faults (push) Successful in 55m2s
wai-det-jpegai: the interpreters' runs of the whole corpus, on both instruction sets
The module of record (e13ebb96...) over the corpus's 63 cases on Wasmi and on Pulley, Linux
x86_64 and macOS arm64, 2026-11-12: every case the corpus's, the one run digest the compilers
gave (85141976...); 1,082 s and 3,135 s on Linux x86_64, 992 s and 4,023 s on macOS arm64.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 06:35:47 -05:00

173 lines
11 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-det-jpegai — the JPEG AI exact decoder at the float level
The module of record of WA²ISM™'s float level ([`extensions/waism.md`](../extensions/waism.md),
`waism/1-det`): one WebAssembly module, with the interface of waism §5 and nothing else, that
decodes `wai.image.jpegai` through `wai-rs`'s exact decoder (`jpegai_decode`). That is the
decode a native sink runs when it holds the capability's integer weight bundle: the integer
entropy decode and scale path, the integer networks through the integer operator set
(`jpegai-int/1`), then dequantisation, latent refinement, the post-filters and colour
conversion in IEEE-754 float, in the fixed order of the source. The bundle is not in the
module: it arrives as the capability's parameter set.
| capability | payload | set | output |
|---|---|---|---|
| `wai.image.jpegai` | the codestream | the file sequence of `norm.json` and `weights/*.json` | RGB8, rows top to bottom |
A refusal is the capability's own code, with `wai_run` returning 1: `set_incomplete`,
`model_mismatch`, `stream_exhausted`, `field_range`, or `configuration_undetermined` for a
configuration whose result the JPEG AI reference software leaves to the machine it runs on
(residual variance scaling without GRFS, a signalled colour matrix; SPEC §5, the
`wai.image.jpegai` row; [decisions](../decisions.md) D74). A codestream outside the
configurations the decoder implements ([determinism-tiers](../extensions/determinism-tiers.md)
§5) has no code in that row: the module traps, and the sink refuses the decode through it
(`module_trap`).
## What holds it
- **Held at the float level, refused at the integer level.** Floating point is in the module,
so both of the profile's checkers ([`../wai-module-check`](../wai-module-check)), the Rust
checker and `check-module.mjs`, give it `held` at the float level and `refused R3` at the
integer level, the same verdict from each. No import, one memory with a declared maximum
(8,192 pages; a decode of the reference codestream reaches 3,964), the six exports of §5.
Every allocation traps where memory cannot grow, so a failed `memory.grow` inside `wai_run`
ends in a trap and never in an output (R13); `wai_alloc` alone returns 0.
- **The probe first.** At the float level a sink states a tier for the output only on an
engine set to the deterministic profile's NaN rule, shown by the probe of waism §9.3 run in
the same process before the module's first run there (§6, check 10). The probe is
[`../wai-module-check/planted/probe.wasm`](../wai-module-check/planted/probe.wasm), and the
bytes it must give are the `bytes` line of
[`probe.txt`](../wai-module-check/planted/probe.txt) beside it: the positive canonical NaN in
every lane of the twelve cases. `planted/build.py` writes both, and both checkers hold the
probe at the float level.
- **The module corpus.** [`corpus.json`](corpus.json), written by [`corpus.py`](corpus.py): the
reference codestream (`wai-rs/tests/vectors/jpegai/test256.bin`) over the pinned `jpegai/v1`
bundle, to the SHA-256 of the RGB8 the native exact decoder gives
(`wai-rs/tests/jpegai_cross_platform.rs`, `PINNED`'s `rgb`), and three refusal fixtures: the
bundle without `weights/icci.json` (`set_incomplete`), and with `norm.json` or
`weights/icci.json` not JSON (`model_mismatch`); and the configuration vectors
(`wai-rs/tests/vectors/jpegai/configs/`, `jpegai/configs/<name>`): each one in scope to the
RGB8 digest its manifest pins (the native exact decoder's), the two whose configuration the
reference leaves to the machine to `configuration_undetermined`, and each the decoder does
not implement to the sink's `module_trap`. The bundle's files are named under
`external`, with their SHA-256 from `tools/jpegai-e2e-v1.sha256`;
`tools/wai_jpegai_fetch_e2e_inputs.sh` fetches them and checks each. The module contains
`memory.grow`, so §9.2 also gives it grow-refusal cases; `corpus.json` does not hold them,
and no run here makes a `memory.grow` fail.
- **The engines.** [`host/`](host) runs the probe on each engine, then every case on an
instance of its own, as waism §5 and §6 checks 10 to 13 state, holds every output to the
corpus, and prints a **run digest**: the SHA-256 of one line a case, its name and its
outcome. Each engine is set as waism §7 sets one at the float level: `threads-off`,
`relaxed-off`, fuel, the memory limited to its declared maximum, a fixed stack, and
`nan-canonical`.
| engine | how its NaN rule is set |
|---|---|
| `[email protected]/cranelift` | `Config::cranelift_nan_canonicalization(true)` |
| `[email protected]/winch` | the same setting, which Winch honours |
| `[email protected]/pulley` | Cranelift compiling to Pulley, wasmtime's portable bytecode, run by its interpreter, with the same setting |
| `[email protected]` | the crate feature `deterministic`, which canonicalises every NaN a floating-point instruction gives |
## F1 and F2
- **F1: the probe tells the engines apart.** On wasmtime with Cranelift, with Winch and with
Pulley, the probe gives its bytes with the NaN rule set and other bytes with it unset; on
Wasmi built with its rule, its bytes. `host --f1` runs it on all seven and holds them to
that.
- **F2: the decode is the native exact decoder's on every engine the probe holds.** The
module of record decodes the corpus's vector and each configuration vector in scope to the
native exact decoder's pinned RGB8 (`dbc494a5…` for `test256`), gives each refusal fixture
its code and each configuration the reference leaves to the machine
`configuration_undetermined`, and traps (the sink's `module_trap`) on each configuration the
decoder does not implement, on the four engines above, on two instruction sets.
F1 was measured with the earlier module of record, on Linux x86_64 and Linux arm64, by
`host --f1`; the probe and the engines are unchanged since, and on 2026-11-12 F1 held again on
Linux x86_64 and on macOS arm64:
| host | F1: the probe's bytes, the NaN rule set | F1: other bytes, the rule unset |
|---|---|---|
| Linux x86_64 | Cranelift, Winch, Pulley, Wasmi | Cranelift and Winch in all twelve cases, Pulley in eleven |
| Linux arm64 | Cranelift, Winch, Pulley, Wasmi | Cranelift and Winch in nine cases, Pulley in eight |
With the rule unset, a case on a NaN operand (1, 2 and 6 to 12) keeps that NaN's sign,
quieted, on both instruction sets, except `f32.nearest` (case 7) on Pulley, which gives the
canonical NaN; a NaN made from numbers (cases 3 to 5, and lane 3 of case 11) is negative on
x86_64 and the canonical one on arm64, whose hardware makes that NaN.
F2, 2026-11-12, the module of record over the corpus's 63 cases on all four engines, on both
instruction sets (`host --engines E --external J
corpus.json record/wai_det_jpegai.wasm`, the probe first on each engine in the same process):
| host | engine | cases the corpus's | time |
|---|---|---|---|
| Linux x86_64, 32 threads | Cranelift | 63 of 63 | 173 s |
| Linux x86_64 | Winch | 63 of 63 | 355 s |
| macOS arm64, 28 cores | Cranelift | 63 of 63 | 166 s |
| macOS arm64 | Winch | 63 of 63 | 429 s |
| Linux x86_64 | Wasmi | 63 of 63 | 1,082 s |
| Linux x86_64 | Pulley | 63 of 63 | 3,135 s |
| macOS arm64 | Wasmi | 63 of 63 | 992 s |
| macOS arm64 | Pulley | 63 of 63 | 4,023 s |
Every run gave one run digest, `85141976194249c9a3c81c252cb7100fad8ce5df69bfa2c5c6373bd2f027b141`,
and an instance reached at most 3,964 pages of the 8,192 the module declares. A configuration
vector's decode takes the interpreters minutes where the compilers take seconds (a 192 × 192
picture with eICCI off: 4 s on Cranelift, 8 s on Winch, 27 s on Wasmi and 68 s on Pulley,
compilation included, Linux x86_64), so CI runs every case on Cranelift and Winch and the
`jpegai/v1` cases on Pulley and Wasmi (`.github/workflows/wai-det-jpegai.yml`).
The earlier module of record (666,425 bytes, `53d54e58…`), run past the probe (`--no-probe`)
on the three engines with the rule unset, gave every case of the corpus as it then was the
corpus's on both hosts: no NaN its decode of that corpus made reached its output. A sink does
not rest on that. At the float level the output holds a tier only on an engine the probe holds
(§6, check 10), and then the tier §2 gives that level.
## The module of record
[`record/`](record) holds one build of this crate for `wasm32-unknown-unknown`, named by its
SHA-256 in [`record/manifest.json`](record/manifest.json): `wai_det_jpegai.wasm`, 673,165
bytes, `e13ebb9640dec9d576df61cb834c6e4c5fdd8c271c60a7b4dc343fa6987fa2bb`, built on Linux
x86_64 from main at `f218af5ac`. The module is the same source on every host, and its bytes
are not ([`../wai-int-module`](../wai-int-module) says why), and they move with any change to
the decoder's crates: a build of the same decoder one main commit earlier was 673,157 bytes
(`acc6c819…`) and gave the same run digest. So `record/` is named by its SHA-256, and a build
is held to the corpus, not to `record/`'s bytes.
## The planted faults
Each check is shown failing on the fault it exists for.
- **A probe with a NaN-dependent branch fails F1.** `probe-nan-branch.wasm` is the probe with
a branch after each operation on whether its result is a NaN, writing the canonical NaN
itself. It meets R1 to R12, and both checkers hold it at the float level, but it gives the
probe's bytes on every engine, the ones with the NaN rule unset among them: `host --f1
--probe planted/probe-nan-branch.wasm` reports that it accepted an engine whose NaN rule is
not set, and fails.
- **A decoder with a NaN-dependent branch departs from the pinned output where the probe
refuses the engine.** The feature `planted-nan-branch` (`build.sh --planted`) adds to the
decode a branch on the sign of a NaN made at run time from a negative signalling NaN, which
flips one bit of the output when the sign is set. The deterministic profile makes that NaN
the positive canonical NaN, so on the four engines above the planted module still decodes to
the pinned RGB8. On the same wasmtime engines with the NaN rule unset, run past check 10
(`--no-probe`), its output is another; with check 10 in place, the probe refuses those
engines (`module_probe_mismatch`) and the module never runs there.
## Building and running
```
bash build.sh [--out DIR] [--planted] # the module, held by both checkers
bash ../tools/wai_jpegai_fetch_e2e_inputs.sh J # the jpegai/v1 bundle, each file checked
(cd host && cargo build --locked --release)
host/target/release/wai-det-jpegai-host --f1
host/target/release/wai-det-jpegai-host --f1 --probe ../wai-module-check/planted/probe-nan-branch.wasm
host/target/release/wai-det-jpegai-host --external J corpus.json record/wai_det_jpegai.wasm
python3 -I corpus.py --check # corpus.json is what corpus.py writes
```
`build.sh` names no path of the machine that runs it in the module: the checkout, the Cargo
home and the toolchain are remapped.
CI: `.github/wai-conformance/verifiers/module-check.sh` holds `record/` to its manifest, the
module of record at the float level and refused R3 at the integer level by both checkers, and
the corpus to `corpus.py`; `.github/workflows/wai-det-jpegai.yml` runs the build, F1, the
engines and the planted faults on Linux x86_64 and Linux arm64.