wai: D2c O8 + M7b — JPEG AI's networks written in jpegai-int/1 and run through wai-ops/1, goldens unchanged #315

Merged
dcharlot merged 15 commits from wai/d2c-m7b into main 2026-10-18 12:35:32 -04:00
Owner

D2c O8 and M7b: wai.image.jpegai's hyper decoders, context stages and synthesis are now written out as WHG1 graphs in integer-operators §13.6.2. That section is now normative, and the decoder runs these networks through wai-ops/1. Every committed and pinned golden is unchanged: the Synthesis stage hash and the RGB SHA-256 among them.

The branch was built on #308 (M8, which carried #298, M7a). Both have landed, and origin/main is merged in (938582b3). Main's other changes since are outside wai/, so the diff against main is this work alone.

O8: what the text now says

(a) The graphs. jpegai-int/1 writes six graphs, in §13.0's role order: hs_y, hs_uv, rec_y, rec_uv, syn_y, syn_uv. The two scale graphs (§13.6.1) are unchanged. The four new ones (§13.6.2) are written in §13.0's canonical numbering, from the bundle's eight weight files:

  • rec_y / rec_uv: a plane's hyper decoder ψ (the z·2^16 lift, conv1, the 4×4 transposed conv2, ReLU6, conv3, ReLU6, conv4 to 4C).
    • For luma, ψ is followed by the four context stages: subpos_split of the residual in the order (0,0), (1,1), (0,1), (1,0); per stage, split(ψ), the context (conv.0, conv.1 in groups of 5, concat) and the three fusion convolutions; then add and subpos_merge.
    • For chroma, ψ's four parts are merged and added to the residual.
    • Inputs: z [C,4,4] and the dequantised residual [C,16,16]. Output: ŷ.
  • syn_y / syn_uv: the synthesis, built from blocks the text defines:
    • C (conv), T (transposed conv), R (residual block), G (gate x·(f(x)+1)), CAB and TAB (layer norm, the three attention convolutions, l2norm, gram, the per-head temperature, softmax, apply, then the feed-forward half with elu and its product), pixel_shuffle, and D (denormalisation, clamp [0, 255·2^16]);
    • each TAB half takes its first convolution's shift, as §13.6.2 had already mapped.
  • Member rules:
    • kind conv: dims, a w of prod(dims) integers that fit i64, mult, and shift in 0..=127;
    • kind q16: v;
    • members of any other kind are ignored;
    • a file in which any object has two members of one name is refused.
  • Load checks. Every shape the topology names is a load check (model_mismatch). So is the certificate meeting acc64.

(b) The floating-point→Q16 boundary. Every input except the hyper-latent is checked within i32 (A32 = check [−2^31, 2^31 − 1]). So is the output of every operator that can grow a value: convolution, transposed convolution, add, Q16 mul, layernorm_c, gram, apply, and the scale rescales. With those checks, every sum is bounded by the weights alone. The new refusals (field_range), each named in §13.6.2 and in §15.3 item 4:

  • a dequantised residual whose Q16 value leaves i32, at input 1 of rec_*;
  • a latent that leaves it, at the inputs of syn_*;
  • an activation that leaves it, at any checked tensor.

The direct decoder computed on in each of these cases: in i64, or i128 for the transposed convolutions, and it wrapped or panicked past them. On the test codestream every input stays within 21 bits and every activation within 24, so no committed decode reaches a check.

Why one uniform range rather than per-tensor admissible ranges (§8.4): an admissible range depends on the other inputs of its consumers. In a residual block, add(x, conv(conv(x))), and in a gate, mul(x, f(x) + 1), the other input is a function of the tensor being ranged, so the ranges would have to be found jointly. The gate's product also binds any uniform range at 2^31.5 (mul.p is acc-class). Checking every growable activation within i32 leaves every sum certifiable from the weights, at 42 to 60 bits on jpegai/v1.

(c) The accumulator class, measured. tests/jpegai_nets_ops.rs runs the four graphs on the test codestream through the interpreter, with wai_ops::bounds::Within holding every value to its certified interval (about 19.5 billion values, none outside). The largest magnitudes, in bits (certified bits in brackets):

graph conv.acc convT.acc mul.p gram.acc apply.acc an input an activation
rec_y 36 (53) 33 (48) — — — 19 21
rec_uv 33 (42) 32 (46) — — — 17 21
syn_y 36 (55) 35 (54) 35 (60) 32 (46) 30 (49) 21 24
syn_uv 37 (55) 36 (52) 37 (58) 32 (46) 33 (49) 21 24

The largest real sum is 106,184,428,047 (37 bits, syn_uv). No real or certified sum reaches 2^63, so the class is acc64, and O8's acc128 fallback is not needed. The product intermediates reach 53 bits (rq.p), which acc64 holds in 128 bits.

§1's claim widens: an implementation of the text reproduces the integers of wai.image.jpegai's integer stages, the networks' outputs for their inputs among them.

The verdict and the profiles

The jpegai/v1 certificate (3e243dc1…, committed by digest) now holds six graphs. Its verdict is acc64, and its acc32_first_failure is rec_y node 1 (conv.acc, 42 bits). The scale graphs still meet acc32 on every node. So that the set can meet acc64 while its scale path keeps acc32 (M7a's decision, for D3b), §11 gains role_profiles.

  • The registry's member is { wai-ops/1, acc64, full, jpegai-int/1, role_profiles: { hs_y: acc32, hs_uv: acc32 } }. Its schema is regenerated, and role_profiles requires profile.
  • Both certifiers take --role-profile ROLE=PROFILE, and the external-set check reads external.json's role_profiles.
  • wai-jpegai-e2e.yml certifies with --profile acc64 --role-profile hs_y=acc32 --role-profile hs_uv=acc32.

The independent checker

tools/wai_ops_check.py gained §13.6.2's lowering. A separate session wrote it from §13.6.2's text alone, without reading the Rust. It reproduces every lowered graph and certificate byte for byte:

  • the committed synthetic set, through ops-conformance: 19 sets, 34 graphs, 19 certificates;
  • jpegai/v1, through certify --check and --external jpegai/v1=DIR.

Its readings made two member rules explicit in the text, and both implementations now refuse what the text refuses: a duplicate name at any depth, and a w that does not hold prod(dims) values each fitting i64. Its other choices (the operator order within the steps, operand order, and that add and mul need equal channel counts) matched the committed bytes.

Gates, each red once and restored

gate commits falsification red
1 lowering and certificate 96f5468e (on 08b5ac25, f44a37ab) the TAB's depthwise convolution takes its own shift, not prep_data.conv1's the_committed_bundle_lowers_to_its_committed_networks: "the lowering of the bytes differs from the committed syn_y.whg1"; wai-verify vectors verify, 5 failures. The pinned certificate stays identical (every jpegai/v1 shift is 31) and the Python checker stays green (it is independent)
2 golden equality 6bed5fa4 the attention temperature indexed i mod 4, not by head the_networks_decode_through_the_lowering_as_directly: "Y through syn_y differs from the direct synthesis"; the_whole_decode_through_the_networks_is_the_pinned_decode (the stages). The committed synthetic set, with one channel per head, and the pre-switch file goldens stay green
3 refusal parity, ops member b9663685 A32's upper end one past (2^31) a_value_past_its_range_is_refused_field_range: "rec_y: input 1 holding 2147483648", refused at Op(13), not Input(1)
4 differential f97757a3 the parsed form reads a shift of 32 to 127 modulo 32 (only random networks reach one) every jpegai_nets_ops test green (8 of 8, pinned bundle); jpegai_random_networks_reconstruct_alike: "the parsed networks lower as their bytes", seed 3372 (chroma)
5 fast kernels c26b3ce0, cbc4caf7 the fast kernel finishes a grouped convolution's output with its neighbour channel's requantisation every_kernel_policy_runs_the_networks_alike ("rec_y Policy { path: Fast, threads: 1 }: the outputs differ from the interpreter alone") and the golden equality. The first run left the unit test green: every committed vector of the four graphs refused past A32. cbc4caf7 keeps the synthetic networks' magnitudes, so every rec/syn vector now decodes, and requires a completed decode per graph; run again, the unit test is red with the same message
6 the switch f4de2c78 the networks' convolutions round half_up the file golden, which now runs the networks through the operator set: jpegai_cross_platform "differs from the pinned decode, first at latent_y", every stage from latent_y on (synthesis aaba532d…, rgb 1b351a7b…, against the pinned d5669572… and dbc494a5…)
6 memory the interpreter keeps every tensor to the end jpegai_memory: "the decode through the operator set held 653409531 bytes; the direct decode held 175465380"
7 the direct networks leave production 2d64a57d the test-only direct networks' Q16 product rounds half up production green (jpegai_cross_platform, jpegai_e2e_full, jpegai_reconstruction_binding, and the lowering, refusal and policy tests); the comparisons red: the_networks_decode_through_the_lowering_as_directly, the whole decode against the direct one, jpegai_random_synthesis_networks_decode_alike (seed 15111065706836451375)

Each fault was applied in the worktree only, run on the build host, and restored with git. The gate's tests then ran green on the committed tree.

Goldens, unchanged

  • tests/jpegai_cross_platform.rs: all nine pinned hashes, after the switch. That includes the Synthesis stage d56695726bb6ebed403199f60a79a5698734eef1e5ca2813859d490c0882d2a4 and rgb dbc494a5a670914edf55c07c48afc0dd348cd05d3a82fff293bf384b2bb69ea3, with latent_y and latent_uv (which rec_* produce) and every post-filter stage.
  • The RGB SHA-256 wai-jpegai-e2e.yml pins: jpegai_reconstruction_binding "matches the pinned WAI_JPEGAI_EXPECT_SHA256"; jpegai_e2e_full max|Δ| = 1 on 4.446% of samples, as before.
  • Per network, on the test codestream: ŷ through rec_y (40,960 values) and rec_uv (24,576), and Y (65,536) and U, V (131,072) through syn_y and syn_uv, equal the direct networks' values, bit for bit as the floats the decode reports.
  • M7a's goldens: jpegai_ops 9 of 9, after the switch.

What changes in the code

  • wai-ops:
    • lower::jpegai_nets: parse_ptq, parse_nets, rec, syn_y, syn_uv, nets, nets_loaded;
    • lower::jpegai writes the six roles, lower::jpegai_scale the scale path alone, and JpegaiBytes names the members;
    • Builder::ops (an operator with several outputs);
    • the generator's synthetic set gains eight small-width weight files (digest 1bc0daae…), with graph vectors for the multi-input graphs.
  • wai-rs:
    • jpegai_ops::Networks (load_networks, load_networks_bytes, reconstruct, synthesize_y, synthesize_uv); the store keeps it as Resident;
    • jpegai_decode decodes every integer stage through the operator set; decode_loaded(data, icci, norm, scale path, networks, observe);
    • the loaders return Result<_, BadMember>; IntNet keeps only well-formed tensors;
    • the neural sink lowers the networks from their eight files' bytes through the loaded-set store, keyed by their listing, at acc64. It parses only the eICCI net besides, and reports model_mismatch or field_range without falling back. a_refused_exact_decode_does_not_fall_back gains a non-integer network weight and a mistyped eICCI field;
    • the browser crate's decodeJpegAiFull reports a mistyped member.
  • Memory (jpegai_memory, on the test codestream): the decode through the operator set holds at most 72,752,387 bytes at once, where the direct decode held 175,465,380. Lowering and loading the networks peaks at 89,727,913 bytes; the loaded networks hold 55,530,900.
  • Test-only (legacy_int): int_transformer, jpegai_synthesis' networks, jpegai_pipeline::reconstruct_yhat(_uv), and int_kernels' par_map, par_zip, conv_acc_i64. The tests that run them require legacy_int. wai_decode_bench's JPEG-AI cases now run the graphs the decode runs.
  • Text: integer-operators §1, §5.1, §11, §13.0, §13.6, §13.6.2 and §15.1/§15.3; determinism-tiers §5; SPEC §5, §7 and §8; CHANGELOG. A truncated sentence in the base's WIV1/WIR1 CHANGELOG entry is completed.
  • CI:
    • tests/jpegai_nets_ops.rs runs in both fast-kernel steps of wai-conformance.yml, where its pinned cases skip;
    • it also has a step in wai-jpegai-e2e.yml that requires 8 tests by name with no skip banner. The job's timeout goes to 60 minutes, because the measured-maxima test takes about 5 minutes on the build host;
    • the differential's two new tests run per push and in the nightly run (whose header names them).
    • scripts/check-tests-in-ci.py is clean. Gate 7 made seven direct-network tests require legacy_int in Cargo.toml, and e6453a95 gates their files the same way, as the check requires.

Planned breaks (integer-operators §15.3 item 4, SPEC §8)

On the exact decoder:

  • a weight file that breaks §13.6.2's member rules or shapes, or networks whose certificate does not meet acc64: model_mismatch, where the loaders panicked or the decoder computed with what the files held;
  • a dequantised residual, a latent or an activation past i32 as a Q16 value: field_range, where it computed on.

No committed bundle or codestream reaches either.

The refusal order: the codestream's scope, then the bundle (its scale path, then its networks), then the streams, then the networks' ranges as each runs.

Results (build host, through rb, under memguard)

Every run went through the build host's runner, under its memory guard at 64 GB. Each script writes cargo's output to a log on the host and greps it. Exit statuses:

  • The D2c gate (gate.sh), at 2d64a57d: GREEN. Build, library tests, carriage, the corpus loop (21 of 21), the fast-kernel step, byte-exact-conformance, wai-web on wasm32, and the neural gates, each exit 0.
  • At the merge head 938582b3 (whose wai/, workflows and scripts equal e6453a95's): the D2c gate GREEN again, every step exit 0 and the corpus loop 21 of 21; check-tests-in-ci.py 0; registry export --check 0, up to date; nothing pulled back, so include/wai.h is unchanged.
  • At 2d64a57d:
    • registry export --check: 0, up to date;
    • jpegai_nets_ops on the pinned bundle: 0, 8 of 8 passed (256 s);
    • cargo kani --lib --only-codegen in wai-verify: 0;
    • nothing pulled back, so include/wai.h is unchanged.
  • At e6453a95:
    • check-tests-in-ci.py: 0, clean;
    • jpegai_e2e_synthesis, jpegai_reconstruct_calibration, jpegai_ops (9) and jpegai_nets_ops (8, pinned cases skipped) under neural_int,legacy_int: 0;
    • int_kernels_geometry under the fast-kernel step's features: 0, 6 passed;
    • the four float calibration tests under neural,legacy_int: 0 (each returns without its fixture, as NOT-IN-CI.txt says).
  • Gate 1 (96f5468e):
    • wai-ops tests: 0;
    • wai-verify build and tests: 0;
    • the pinned certificate through bounds --check: 0, identical;
    • --role-profile syn_y=acc32: 1, as it should be;
    • jpegai_ops and jpegai_nets_ops: 0.
    • wai-verify vectors verify exited 1 here: the external set's registry member still said acc32. Gate 3 gives it acc64 with role_profiles, and it has passed since.
  • Gate 3 (b9663685): registry export 0, --check 0, the registry tests 0 (14 passed), the differential 0, wai-verify vectors verify 0, the Python checker with --external jpegai/v1 0, the wai-ops tests 0.
  • Gate 4 (f97757a3): the differential's two new tests 0, in release:
    • reconstruction, 64 random cases: 56 alike, 3 past A32, and 5 broken networks (1 member missing, 2 with shift 128, 2 with a short w), each refused alike by both;
    • synthesis, 8 random cases: 7 alike and 1 past A32.
  • O8 (c) (5d3a0ea9): the_networks_keep_to_their_certificate_on_the_test_codestream: 0 (295 s, about 19.5 billion values held to their intervals).
  • Gate 5 follow-up (cbc4caf7): the Python checker 0, wai-verify vectors verify 0, the wai-ops tests 0, jpegai_ops and jpegai_nets_ops 0, the fast-kernel unit test 0.
  • Gate 6 (f4de2c78):
    • the release build of the tests: 0;
    • jpegai_e2e_full, jpegai_reconstruction_binding and jpegai_cross_platform, with the pinned SHA-256: 0;
    • jpegai_ops, jpegai_nets_ops and jpegai_memory: 0;
    • clippy on the library: 0;
    • jpegai_decode_symbols: 0;
    • the neural sink's exact-dispatch tests: 0 (3 passed);
    • wai-web tests: 0 (32 passed), and its wasm32 check: 0.
  • Gate 7 (2d64a57d):
    • cargo check of the library under neural_int and under neural, and of the tests under legacy_int: 0;
    • clippy: 0;
    • wai_decode_bench's build and its JPEG-AI cases: 0;
    • int_kernels_geometry: 0;
    • the e2e trio: 0;
    • jpegai_ops and jpegai_nets_ops: 0;
    • byte-exact-conformance: 0;
    • the wai-verify build: 0.
  • The independent checker: 19 sets, 34 graphs and 19 certificates byte for byte. certify --check on the pinned bundle: 0.

Kani

No harness changed. wai-verify's library compiles wai-ops' lower module, which this branch extends (Builder::ops, lower::jpegai_nets), and wai-rs' int_kernels.rs, whose direct-network helpers are now gated on legacy_int.

  • cargo kani --lib --only-codegen: 0. Every harness builds under the verifier's toolchain.
  • The plan shard (--harness proofs::plan::, the one that reaches the lowerings' predicates): 0. Its 6 enabled harnesses all verified: K12 largest, p_conv and p_leaky; K15; and K16 pixel_shuffle and pixel_unshuffle. The run had a time limit of 900 s per harness and 2,400 s overall, under the 64 GB guard.
  • The shard's other 7 harnesses are kani_open on main and stay that way here, as do K27's two totality harnesses (k27_mans_sgm_is_total, k27_mans_fac_is_total).

Left

  • The reference-decoder stage fixtures (WAI_JPEGAI_FRONTEND_FIXTURE, WAI_JPEGAI_HYPERSCALE_IO, and the calibration tests' fixtures that NOT-IN-CI.txt lists) are still rebuilt only from the reference software's local dumps.
    • They were made by hook scripts (run_decode_*hook.py) that wrap the reference decoder and record each stage's inputs and outputs on the test codestream. The scripts and their dumps sit with the exported reference assets, outside the repository (jpegai-vm-assets/ is ignored).
    • The reference software and its exported assets are BSD-3-Clause. That licence permits redistributing derived data with its notice, which is the basis on which jpegai/v1 is already published. The notice also says that it grants no patent rights; the fixtures are values of the decode, as jpegai/v1's weights are.
    • So publishing the fixtures beside jpegai/v1 on the models root, pinned in tools/jpegai-e2e-v1.sha256, with the notice, is feasible. wai-jpegai-e2e.yml could then run jpegai_e2e_frontend, jpegai_e2e_chroma and jpegai_hyperscale_calibration.
    • A committed builder would be those hook scripts. It would still need the reference software checked out with its models, so it would rebuild the fixtures by hand, not in CI.
  • D3b: these networks are acc64-only (layer norm, attention and the Q16 activations, §7.3), so a GPU leg for them would emulate 64-bit arithmetic or leave them to the CPU. The scale path stays D3b's acc32 target.
  • Nothing is deployed. The site's /jpegai page picks up the change with its next wasm build: decodeJpegAiFull now decodes the networks through the operator set and reports a mistyped member.

Notes

  • Every cargo build, test, clippy and Kani run went through rb on the build host. Logs are under $RB_ROOT/logs/d2c-m7b/.
  • Session limits stopped the work three times. Work in progress was committed first (08b5ac25), and each falsification's fault was restored with git before the next step.
  • No cargo fmt, no git stash, and no cargo output piped into head or tail as the only copy. No history was rewritten. include/wai.h is unchanged.
  • No write or command was refused by the permission system.

🤖 Generated with Claude Code

D2c O8 and M7b: `wai.image.jpegai`'s hyper decoders, context stages and synthesis are now written out as `WHG1` graphs in [integer-operators](wai/extensions/integer-operators.md) §13.6.2. That section is now normative, and the decoder runs these networks through `wai-ops/1`. Every committed and pinned golden is unchanged: the `Synthesis` stage hash and the RGB SHA-256 among them. The branch was built on #308 (M8, which carried #298, M7a). Both have landed, and `origin/main` is merged in (`938582b3`). Main's other changes since are outside `wai/`, so the diff against main is this work alone. ## O8: what the text now says **(a) The graphs.** `jpegai-int/1` writes six graphs, in §13.0's role order: `hs_y`, `hs_uv`, `rec_y`, `rec_uv`, `syn_y`, `syn_uv`. The two scale graphs (§13.6.1) are unchanged. The four new ones (§13.6.2) are written in §13.0's canonical numbering, from the bundle's eight weight files: - **`rec_y` / `rec_uv`:** a plane's hyper decoder ψ (the `z·2^16` lift, `conv1`, the 4×4 transposed `conv2`, ReLU6, `conv3`, ReLU6, `conv4` to `4C`). - For luma, ψ is followed by the four context stages: `subpos_split` of the residual in the order `(0,0), (1,1), (0,1), (1,0)`; per stage, `split(ψ)`, the context (`conv.0`, `conv.1` in groups of 5, `concat`) and the three fusion convolutions; then `add` and `subpos_merge`. - For chroma, ψ's four parts are merged and added to the residual. - Inputs: `z [C,4,4]` and the dequantised residual `[C,16,16]`. Output: `ŷ`. - **`syn_y` / `syn_uv`:** the synthesis, built from blocks the text defines: - `C` (conv), `T` (transposed conv), `R` (residual block), `G` (gate `x·(f(x)+1)`), `CAB` and `TAB` (layer norm, the three attention convolutions, `l2norm`, `gram`, the per-head temperature, `softmax`, `apply`, then the feed-forward half with `elu` and its product), `pixel_shuffle`, and `D` (denormalisation, `clamp [0, 255·2^16]`); - each TAB half takes its first convolution's shift, as §13.6.2 had already mapped. - **Member rules:** - kind `conv`: `dims`, a `w` of prod(dims) integers that fit i64, `mult`, and `shift` in `0..=127`; - kind `q16`: `v`; - members of any other kind are ignored; - a file in which any object has two members of one name is refused. - **Load checks.** Every shape the topology names is a load check (`model_mismatch`). So is the certificate meeting `acc64`. **(b) The floating-point→Q16 boundary.** Every input except the hyper-latent is checked within i32 (`A32 = check [−2^31, 2^31 − 1]`). So is the output of every operator that can grow a value: convolution, transposed convolution, `add`, Q16 `mul`, `layernorm_c`, `gram`, `apply`, and the scale `rescale`s. With those checks, every sum is bounded by the weights alone. The new refusals (`field_range`), each named in §13.6.2 and in §15.3 item 4: - a dequantised residual whose Q16 value leaves i32, at input 1 of `rec_*`; - a latent that leaves it, at the inputs of `syn_*`; - an activation that leaves it, at any checked tensor. The direct decoder computed on in each of these cases: in i64, or i128 for the transposed convolutions, and it wrapped or panicked past them. On the test codestream every input stays within 21 bits and every activation within 24, so no committed decode reaches a check. Why one uniform range rather than per-tensor admissible ranges (§8.4): an admissible range depends on the other inputs of its consumers. In a residual block, `add(x, conv(conv(x)))`, and in a gate, `mul(x, f(x) + 1)`, the other input is a function of the tensor being ranged, so the ranges would have to be found jointly. The gate's product also binds any uniform range at `2^31.5` (`mul.p` is `acc`-class). Checking every growable activation within i32 leaves every sum certifiable from the weights, at 42 to 60 bits on `jpegai/v1`. **(c) The accumulator class, measured.** `tests/jpegai_nets_ops.rs` runs the four graphs on the test codestream through the interpreter, with `wai_ops::bounds::Within` holding every value to its certified interval (about 19.5 billion values, none outside). The largest magnitudes, in bits (certified bits in brackets): | graph | `conv.acc` | `convT.acc` | `mul.p` | `gram.acc` | `apply.acc` | an input | an activation | |---|---|---|---|---|---|---|---| | `rec_y` | 36 (53) | 33 (48) | — | — | — | 19 | 21 | | `rec_uv` | 33 (42) | 32 (46) | — | — | — | 17 | 21 | | `syn_y` | 36 (55) | 35 (54) | 35 (60) | 32 (46) | 30 (49) | 21 | 24 | | `syn_uv` | 37 (55) | 36 (52) | 37 (58) | 32 (46) | 33 (49) | 21 | 24 | The largest real sum is 106,184,428,047 (37 bits, `syn_uv`). No real or certified sum reaches 2^63, so the class is `acc64`, and O8's `acc128` fallback is not needed. The `product` intermediates reach 53 bits (`rq.p`), which `acc64` holds in 128 bits. **§1's claim widens:** an implementation of the text reproduces the integers of `wai.image.jpegai`'s integer stages, the networks' outputs for their inputs among them. ## The verdict and the profiles The `jpegai/v1` certificate (`3e243dc1…`, committed by digest) now holds six graphs. Its verdict is `acc64`, and its `acc32_first_failure` is `rec_y` node 1 (`conv.acc`, 42 bits). The scale graphs still meet `acc32` on every node. So that the set can meet `acc64` while its scale path keeps `acc32` (M7a's decision, for D3b), §11 gains `role_profiles`. - The registry's member is `{ wai-ops/1, acc64, full, jpegai-int/1, role_profiles: { hs_y: acc32, hs_uv: acc32 } }`. Its schema is regenerated, and `role_profiles` requires `profile`. - Both certifiers take `--role-profile ROLE=PROFILE`, and the external-set check reads `external.json`'s `role_profiles`. - `wai-jpegai-e2e.yml` certifies with `--profile acc64 --role-profile hs_y=acc32 --role-profile hs_uv=acc32`. ## The independent checker `tools/wai_ops_check.py` gained §13.6.2's lowering. A separate session wrote it from §13.6.2's text alone, without reading the Rust. It reproduces every lowered graph and certificate byte for byte: - the committed synthetic set, through `ops-conformance`: 19 sets, 34 graphs, 19 certificates; - `jpegai/v1`, through `certify --check` and `--external jpegai/v1=DIR`. Its readings made two member rules explicit in the text, and both implementations now refuse what the text refuses: a duplicate name at any depth, and a `w` that does not hold prod(dims) values each fitting i64. Its other choices (the operator order within the steps, operand order, and that `add` and `mul` need equal channel counts) matched the committed bytes. ## Gates, each red once and restored | gate | commits | falsification | red | |---|---|---|---| | 1 lowering and certificate | `96f5468e` (on `08b5ac25`, `f44a37ab`) | the TAB's depthwise convolution takes its own shift, not `prep_data.conv1`'s | `the_committed_bundle_lowers_to_its_committed_networks`: "the lowering of the bytes differs from the committed syn_y.whg1"; `wai-verify vectors verify`, 5 failures. The pinned certificate stays identical (every `jpegai/v1` shift is 31) and the Python checker stays green (it is independent) | | 2 golden equality | `6bed5fa4` | the attention temperature indexed `i mod 4`, not by head | `the_networks_decode_through_the_lowering_as_directly`: "Y through syn_y differs from the direct synthesis"; `the_whole_decode_through_the_networks_is_the_pinned_decode` (the stages). The committed synthetic set, with one channel per head, and the pre-switch file goldens stay green | | 3 refusal parity, `ops` member | `b9663685` | `A32`'s upper end one past (`2^31`) | `a_value_past_its_range_is_refused_field_range`: "rec_y: input 1 holding 2147483648", refused at `Op(13)`, not `Input(1)` | | 4 differential | `f97757a3` | the parsed form reads a shift of 32 to 127 modulo 32 (only random networks reach one) | every `jpegai_nets_ops` test green (8 of 8, pinned bundle); `jpegai_random_networks_reconstruct_alike`: "the parsed networks lower as their bytes", seed 3372 (chroma) | | 5 fast kernels | `c26b3ce0`, `cbc4caf7` | the fast kernel finishes a grouped convolution's output with its neighbour channel's requantisation | `every_kernel_policy_runs_the_networks_alike` ("rec_y Policy { path: Fast, threads: 1 }: the outputs differ from the interpreter alone") and the golden equality. The first run left the unit test green: every committed vector of the four graphs refused past `A32`. `cbc4caf7` keeps the synthetic networks' magnitudes, so every rec/syn vector now decodes, and requires a completed decode per graph; run again, the unit test is red with the same message | | 6 the switch | `f4de2c78` | the networks' convolutions round `half_up` | the file golden, which now runs the networks through the operator set: `jpegai_cross_platform` "differs from the pinned decode, first at `latent_y`", every stage from `latent_y` on (`synthesis aaba532d…`, `rgb 1b351a7b…`, against the pinned `d5669572…` and `dbc494a5…`) | | 6 memory | | the interpreter keeps every tensor to the end | `jpegai_memory`: "the decode through the operator set held 653409531 bytes; the direct decode held 175465380" | | 7 the direct networks leave production | `2d64a57d` | the test-only direct networks' Q16 product rounds half up | production green (`jpegai_cross_platform`, `jpegai_e2e_full`, `jpegai_reconstruction_binding`, and the lowering, refusal and policy tests); the comparisons red: `the_networks_decode_through_the_lowering_as_directly`, the whole decode against the direct one, `jpegai_random_synthesis_networks_decode_alike` (seed 15111065706836451375) | Each fault was applied in the worktree only, run on the build host, and restored with git. The gate's tests then ran green on the committed tree. ## Goldens, unchanged - `tests/jpegai_cross_platform.rs`: all nine pinned hashes, after the switch. That includes the `Synthesis` stage `d56695726bb6ebed403199f60a79a5698734eef1e5ca2813859d490c0882d2a4` and `rgb` `dbc494a5a670914edf55c07c48afc0dd348cd05d3a82fff293bf384b2bb69ea3`, with `latent_y` and `latent_uv` (which `rec_*` produce) and every post-filter stage. - The RGB SHA-256 `wai-jpegai-e2e.yml` pins: `jpegai_reconstruction_binding` "matches the pinned WAI_JPEGAI_EXPECT_SHA256"; `jpegai_e2e_full` max|Δ| = 1 on 4.446% of samples, as before. - Per network, on the test codestream: ŷ through `rec_y` (40,960 values) and `rec_uv` (24,576), and Y (65,536) and U, V (131,072) through `syn_y` and `syn_uv`, equal the direct networks' values, bit for bit as the floats the decode reports. - M7a's goldens: `jpegai_ops` 9 of 9, after the switch. ## What changes in the code - **`wai-ops`:** - `lower::jpegai_nets`: `parse_ptq`, `parse_nets`, `rec`, `syn_y`, `syn_uv`, `nets`, `nets_loaded`; - `lower::jpegai` writes the six roles, `lower::jpegai_scale` the scale path alone, and `JpegaiBytes` names the members; - `Builder::ops` (an operator with several outputs); - the generator's synthetic set gains eight small-width weight files (digest `1bc0daae…`), with graph vectors for the multi-input graphs. - **`wai-rs`:** - `jpegai_ops::Networks` (`load_networks`, `load_networks_bytes`, `reconstruct`, `synthesize_y`, `synthesize_uv`); the store keeps it as `Resident`; - `jpegai_decode` decodes every integer stage through the operator set; `decode_loaded(data, icci, norm, scale path, networks, observe)`; - the loaders return `Result<_, BadMember>`; `IntNet` keeps only well-formed tensors; - the neural sink lowers the networks from their eight files' bytes through the loaded-set store, keyed by their listing, at `acc64`. It parses only the eICCI net besides, and reports `model_mismatch` or `field_range` without falling back. `a_refused_exact_decode_does_not_fall_back` gains a non-integer network weight and a mistyped eICCI field; - the browser crate's `decodeJpegAiFull` reports a mistyped member. - **Memory** (`jpegai_memory`, on the test codestream): the decode through the operator set holds at most 72,752,387 bytes at once, where the direct decode held 175,465,380. Lowering and loading the networks peaks at 89,727,913 bytes; the loaded networks hold 55,530,900. - **Test-only (`legacy_int`):** `int_transformer`, `jpegai_synthesis`' networks, `jpegai_pipeline::reconstruct_yhat(_uv)`, and `int_kernels`' `par_map`, `par_zip`, `conv_acc_i64`. The tests that run them require `legacy_int`. `wai_decode_bench`'s JPEG-AI cases now run the graphs the decode runs. - **Text:** integer-operators §1, §5.1, §11, §13.0, §13.6, §13.6.2 and §15.1/§15.3; determinism-tiers §5; SPEC §5, §7 and §8; CHANGELOG. A truncated sentence in the base's WIV1/WIR1 CHANGELOG entry is completed. - **CI:** - `tests/jpegai_nets_ops.rs` runs in both fast-kernel steps of `wai-conformance.yml`, where its pinned cases skip; - it also has a step in `wai-jpegai-e2e.yml` that requires 8 tests by name with no skip banner. The job's timeout goes to 60 minutes, because the measured-maxima test takes about 5 minutes on the build host; - the differential's two new tests run per push and in the nightly run (whose header names them). - `scripts/check-tests-in-ci.py` is clean. Gate 7 made seven direct-network tests require `legacy_int` in `Cargo.toml`, and `e6453a95` gates their files the same way, as the check requires. ## Planned breaks (integer-operators §15.3 item 4, SPEC §8) On the exact decoder: - a weight file that breaks §13.6.2's member rules or shapes, or networks whose certificate does not meet `acc64`: `model_mismatch`, where the loaders panicked or the decoder computed with what the files held; - a dequantised residual, a latent or an activation past i32 as a Q16 value: `field_range`, where it computed on. No committed bundle or codestream reaches either. The refusal order: the codestream's scope, then the bundle (its scale path, then its networks), then the streams, then the networks' ranges as each runs. ## Results (build host, through `rb`, under memguard) Every run went through the build host's runner, under its memory guard at 64 GB. Each script writes cargo's output to a log on the host and greps it. Exit statuses: - **The D2c gate** (`gate.sh`), at `2d64a57d`: GREEN. Build, library tests, `carriage`, the corpus loop (21 of 21), the fast-kernel step, `byte-exact-conformance`, `wai-web` on wasm32, and the neural gates, each exit 0. - **At the merge head `938582b3`** (whose `wai/`, workflows and scripts equal `e6453a95`'s): the D2c gate GREEN again, every step exit 0 and the corpus loop 21 of 21; `check-tests-in-ci.py` 0; `registry export --check` 0, up to date; nothing pulled back, so `include/wai.h` is unchanged. - **At `2d64a57d`:** - `registry export --check`: 0, up to date; - `jpegai_nets_ops` on the pinned bundle: 0, 8 of 8 passed (256 s); - `cargo kani --lib --only-codegen` in `wai-verify`: 0; - nothing pulled back, so `include/wai.h` is unchanged. - **At `e6453a95`:** - `check-tests-in-ci.py`: 0, clean; - `jpegai_e2e_synthesis`, `jpegai_reconstruct_calibration`, `jpegai_ops` (9) and `jpegai_nets_ops` (8, pinned cases skipped) under `neural_int,legacy_int`: 0; - `int_kernels_geometry` under the fast-kernel step's features: 0, 6 passed; - the four float calibration tests under `neural,legacy_int`: 0 (each returns without its fixture, as NOT-IN-CI.txt says). - **Gate 1** (`96f5468e`): - `wai-ops` tests: 0; - `wai-verify` build and tests: 0; - the pinned certificate through `bounds --check`: 0, identical; - `--role-profile syn_y=acc32`: 1, as it should be; - `jpegai_ops` and `jpegai_nets_ops`: 0. - `wai-verify vectors verify` exited 1 here: the external set's registry member still said `acc32`. Gate 3 gives it `acc64` with `role_profiles`, and it has passed since. - **Gate 3** (`b9663685`): `registry export` 0, `--check` 0, the registry tests 0 (14 passed), the differential 0, `wai-verify vectors verify` 0, the Python checker with `--external jpegai/v1` 0, the `wai-ops` tests 0. - **Gate 4** (`f97757a3`): the differential's two new tests 0, in release: - reconstruction, 64 random cases: 56 alike, 3 past `A32`, and 5 broken networks (1 member missing, 2 with shift 128, 2 with a short `w`), each refused alike by both; - synthesis, 8 random cases: 7 alike and 1 past `A32`. - **O8 (c)** (`5d3a0ea9`): `the_networks_keep_to_their_certificate_on_the_test_codestream`: 0 (295 s, about 19.5 billion values held to their intervals). - **Gate 5 follow-up** (`cbc4caf7`): the Python checker 0, `wai-verify vectors verify` 0, the `wai-ops` tests 0, `jpegai_ops` and `jpegai_nets_ops` 0, the fast-kernel unit test 0. - **Gate 6** (`f4de2c78`): - the release build of the tests: 0; - `jpegai_e2e_full`, `jpegai_reconstruction_binding` and `jpegai_cross_platform`, with the pinned SHA-256: 0; - `jpegai_ops`, `jpegai_nets_ops` and `jpegai_memory`: 0; - clippy on the library: 0; - `jpegai_decode_symbols`: 0; - the neural sink's exact-dispatch tests: 0 (3 passed); - `wai-web` tests: 0 (32 passed), and its wasm32 check: 0. - **Gate 7** (`2d64a57d`): - `cargo check` of the library under `neural_int` and under `neural`, and of the tests under `legacy_int`: 0; - clippy: 0; - `wai_decode_bench`'s build and its JPEG-AI cases: 0; - `int_kernels_geometry`: 0; - the e2e trio: 0; - `jpegai_ops` and `jpegai_nets_ops`: 0; - `byte-exact-conformance`: 0; - the `wai-verify` build: 0. - The independent checker: 19 sets, 34 graphs and 19 certificates byte for byte. `certify --check` on the pinned bundle: 0. ## Kani No harness changed. `wai-verify`'s library compiles `wai-ops`' `lower` module, which this branch extends (`Builder::ops`, `lower::jpegai_nets`), and `wai-rs`' `int_kernels.rs`, whose direct-network helpers are now gated on `legacy_int`. - `cargo kani --lib --only-codegen`: 0. Every harness builds under the verifier's toolchain. - The `plan` shard (`--harness proofs::plan::`, the one that reaches the lowerings' predicates): 0. Its 6 enabled harnesses all verified: K12 `largest`, `p_conv` and `p_leaky`; K15; and K16 `pixel_shuffle` and `pixel_unshuffle`. The run had a time limit of 900 s per harness and 2,400 s overall, under the 64 GB guard. - The shard's other 7 harnesses are `kani_open` on main and stay that way here, as do K27's two totality harnesses (`k27_mans_sgm_is_total`, `k27_mans_fac_is_total`). ## Left - **The reference-decoder stage fixtures** (`WAI_JPEGAI_FRONTEND_FIXTURE`, `WAI_JPEGAI_HYPERSCALE_IO`, and the calibration tests' fixtures that NOT-IN-CI.txt lists) are still rebuilt only from the reference software's local dumps. - They were made by hook scripts (`run_decode_*hook.py`) that wrap the reference decoder and record each stage's inputs and outputs on the test codestream. The scripts and their dumps sit with the exported reference assets, outside the repository (`jpegai-vm-assets/` is ignored). - The reference software and its exported assets are BSD-3-Clause. That licence permits redistributing derived data with its notice, which is the basis on which `jpegai/v1` is already published. The notice also says that it grants no patent rights; the fixtures are values of the decode, as `jpegai/v1`'s weights are. - So publishing the fixtures beside `jpegai/v1` on the models root, pinned in `tools/jpegai-e2e-v1.sha256`, with the notice, is feasible. `wai-jpegai-e2e.yml` could then run `jpegai_e2e_frontend`, `jpegai_e2e_chroma` and `jpegai_hyperscale_calibration`. - A committed builder would be those hook scripts. It would still need the reference software checked out with its models, so it would rebuild the fixtures by hand, not in CI. - **D3b:** these networks are `acc64`-only (layer norm, attention and the Q16 activations, §7.3), so a GPU leg for them would emulate 64-bit arithmetic or leave them to the CPU. The scale path stays D3b's `acc32` target. - Nothing is deployed. The site's `/jpegai` page picks up the change with its next wasm build: `decodeJpegAiFull` now decodes the networks through the operator set and reports a mistyped member. ## Notes - Every cargo build, test, clippy and Kani run went through `rb` on the build host. Logs are under `$RB_ROOT/logs/d2c-m7b/`. - Session limits stopped the work three times. Work in progress was committed first (`08b5ac25`), and each falsification's fault was restored with git before the next step. - No `cargo fmt`, no `git stash`, and no cargo output piped into `head` or `tail` as the only copy. No history was rewritten. `include/wai.h` is unchanged. - No write or command was refused by the permission system. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The state of the work when a session limit stopped it, committed so that nothing
is lost. Not a gate commit; the gates follow in their own commits.

- integer-operators §13.6.2 written as a normative lowering (the members, the
  blocks, TAB, the graphs rec_y, rec_uv, syn_y, syn_uv, the A32 check ranges,
  the load checks, the driver, the refusals, the profiles); §1's claim widened;
  §5.1, §11 (role_profiles), §13.0 (roles, widths, driver contract), §13.6's
  intro, §15.1 and §15.3 item 4. The measured accumulator table is still a
  placeholder (MEASURED_TABLE).
- determinism-tiers §5: the networks' codes (model_mismatch, field_range).
- wai-ops: lower::jpegai_nets (the lowering of §13.6.2), lower::jpegai now
  writes the six roles, lower::jpegai_scale the scale path alone; Builder::ops.
- The generator: the synthetic jpegai set gains small-width weight files for the
  eight networks (set digest 1b5d0f77…, replacing 60020d06…); graph vectors for
  the multi-input graphs. The pinned bundle's certificate (3e243dc1…) is
  regenerated with the six graphs (acc64; acc32 for hs_y and hs_uv).
- wai-rs: jpegai_ops::Networks (load, reconstruct, synthesize_y/_uv),
  Weights::ptq, the store's Resident impl; tests/jpegai_nets_ops.rs (lowering
  and certificate, golden equality per network, the measured maxima held to the
  certificate, kernel-policy parity). Ran green once on the build host on the
  pinned bundle. The decode is not switched yet.

The independent Python checker does not yet lower §13.6.2.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
integer-operators §13.6.2, Accumulators, measured: on the jpegai/v1 test codestream
every value the four graphs form lies within its certified interval
(tests/jpegai_nets_ops.rs, held by wai_ops::bounds::Within), and the largest
magnitudes are 37 bits for any acc-class sum (syn_uv's conv.acc and mul.p),
36 for a transposed convolution's, against certified bounds of 42 to 60 bits.
Inputs stay within 21 bits and activations within 24, inside the A32 checks.
No real or certified sum reaches 2^63, so acc64 holds and O8's acc128
fallback is not needed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
So the committed set's lowered bytes show §13.6.2's TAB rule (each half's
convolutions take its first convolution's shift). The set's digest is now
4e4ba5e4…; its graphs and certificate are regenerated by wai-verify vectors gen,
and the tests name the new digest.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
jpegai-int/1 now writes six graphs (hs_y, hs_uv, rec_y, rec_uv, syn_y, syn_uv;
integer-operators §13.6.2). The independent checker (tools/wai_ops_check.py),
written by a separate session from §13.6.2's text without reading the Rust,
lowers the eight weight files and reproduces every lowered graph and certificate
byte for byte: the committed synthetic set (4e4ba5e4…) through `ops-conformance`
(19 sets, 34 graphs, 19 certificates), and jpegai/v1 (3e243dc1…) through
`certify --check`, with `--external jpegai/v1=DIR` recomputing it too.

- The verdict: Appendix C's prediction for these networks (acc64) holds; the set
  meets acc64 and not acc32 (first failure rec_y node 1, conv.acc 42 bits), and
  the scale graphs still meet acc32 on every node.
- §11's role_profiles in both certifiers: `--role-profile ROLE=PROFILE` (exit 1
  when the named graph fails it), and the external-set check reads an entry's
  role_profiles; external.json's jpegai/v1 entry declares acc64 with acc32 for
  hs_y and hs_uv. wai-jpegai-e2e.yml certifies with both.
- The checker's readings of the member rules made two of them explicit: a file
  with two members of one name at any depth is refused, and a conv member's `w`
  holds the product of its dims of integers that fit i64. wai-ops' parse and the
  parsed form's conversion (Weights::ptq) now refuse what the text refuses.
- wai-jpegai-e2e.yml gains a step for tests/jpegai_nets_ops.rs on the pinned
  bundle (and its timeout 60 minutes); both fast-kernel steps of
  wai-conformance.yml run it on the committed set.
- tests/jpegai_ops.rs: the scale-path certificate test reads the set's eleven
  members and holds the scale graphs, not the set, to acc32.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- jpegai_decode::decode_networks runs the whole decode with every integer stage
  through the operator set: the scale path and entropy decodes (§13.6.1), and the
  hyper decoders, context stages and synthesis through rec_y, rec_uv, syn_y and
  syn_uv (§13.6.2). The decode's default is unchanged until the switch (gate 6).
- tests/jpegai_nets_ops.rs, on jpegai/v1 and the test codestream (build host,
  release, WAI_REQUIRE_SOTA_VECTORS=1):
  - the_networks_decode_through_the_lowering_as_directly: ŷ through rec_y
    (40960 values) and rec_uv (24576) equals the direct hyper decoder and context
    stages; Y (65536) and U, V (131072) through syn_y and syn_uv equal the direct
    synthesis, bit for bit as the float values the decode reports;
  - the_whole_decode_through_the_networks_is_the_pinned_decode: every stage hash
    equals the direct decode's and the pinned values of
    tests/jpegai_cross_platform.rs: the Synthesis stage d56695726bb6ebed…, the RGB
    SHA-256 dbc494a5a670914e…, both unchanged.
- wai-jpegai-e2e.yml requires the new test by name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Refusal parity (tests/jpegai_nets_ops.rs):
  - every_load_check_of_the_networks_refuses_model_mismatch: 19 breaks of
    §13.6.2's load checks in the committed networks (a member missing or of the
    other kind, dims, counts, groups other than 4 or 5, a kernel not the
    topology's, a bias short, shifts of 128 and -1, a non-integer weight, a
    multiplier past i64, three temperatures, ψ of other than 4C channels, a luma
    synthesis of two planes, an odd feed-forward width, a mistyped member, a sum
    past i64 under A32) are refused model_mismatch from the files' bytes, 15 of
    them from the parsed networks too (the rest the direct parser cannot read);
    and two members of one name, from the bytes;
  - a_value_past_its_range_is_refused_field_range: a residual or a latent one
    past A32 at either end is refused field_range at its input by every graph
    that reads it; on the test codestream a residual at either end of A32, where
    the mean keeps the sum within it, decodes through rec_y and rec_uv as the
    direct decode does, and one past it is refused where the direct decode
    computed on (integer-operators §15.3 item 4).
- Registry: wai.image.jpegai's ops member is { wai-ops/1, acc64, full,
  jpegai-int/1, role_profiles: { hs_y: acc32, hs_uv: acc32 } } (§11); the schema
  gains role_profiles (it requires profile); registry/capabilities.json and its
  schema regenerated by `wai registry export`.
- SPEC §5 (the row's refusals and its reference implementation), §7
  (NeuralEntropyExact names the profiles; the networks lie outside the claim),
  §8 (the networks' planned breaks).
- CHANGELOG: a truncated sentence of the WIV1/WIR1 entry completed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
tests/int_ops_differential.rs gains a half for jpegai's hyper decoders, context
stages and synthesis (§13.6.2): random weight files in the export's form at the
shapes the direct networks read (multipliers scaled to keep magnitudes, shifts 24
to 40, one convolution in about 64 grown past A32 in a "hot" case), random
hyper-latents, residuals and latents.

- jpegai_random_networks_reconstruct_alike (rec_y, rec_uv): ŷ equals the direct
  hyper decoder's and context stages', or the operator set refuses field_range at
  a checked tensor (the direct networks computed on); a file that breaks a load
  check (a member missing, a shift of 128, a weight short, dims, a bias long) is
  refused model_mismatch, and its bytes and its parsed form lower to the same
  graph or the same refusal.
- jpegai_random_synthesis_networks_decode_alike (syn_y, syn_uv), at the whole
  picture's size: the planes equal the direct synthesis's, or field_range.
- Per push (release, build host): rec 64 cases (alike 56, past A32 3; planned
  breaks: a member missing 1, a shift of 128 2, a weight short 2); synthesis 8
  cases (alike 7, past A32 1). The nightly wai-ops-differential.yml runs both for
  its time, and names them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- jpegai_ops::fast_kernels::the_jpegai_networks_run_their_convolutions_on_the_fast_kernel:
  on the committed networks' four graphs, under every fast policy, every
  convolution and transposed convolution of each graph runs on the fast kernel
  (counted, int_ops::Host) and every value is the interpreter's alone, on inputs
  of four magnitudes; debug and release, build host.
- tests/jpegai_nets_ops.rs, every_kernel_policy_runs_the_networks_alike (since
  the work-in-progress commit): the reference and four fast policies give the
  same outputs or the same refusals on the committed networks (16 runs, 12
  refused alike past A32) and on the test codestream's inputs to the pinned
  bundle's graphs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Gate 5's fault (a grouped convolution finished with its neighbour channel's
requantisation on the fast kernel) left the unit test green: every committed
vector of rec_y, rec_uv, syn_y and syn_uv refused past A32, because the synthetic
networks' multipliers grew each convolution's output by up to 2^6 per product, so
no decode of those graphs ran its operators end to end.

- The generator now sets each synthetic convolution's multiplier so that a weight
  of 32767 times its products keeps the input's magnitude (and halves the biases):
  every rec_y, rec_uv, syn_y and syn_uv vector of graphs/lower_jpegai.jsonl now
  decodes, and only the vectors one past an input's range refuse. The set's digest
  is 1bc0daae…; its graphs, certificate and vectors are regenerated, and both
  certifiers reproduce them (wai-verify vectors verify; wai_ops_check.py
  ops-conformance), with the tests naming the new digest.
- jpegai_ops::fast_kernels::the_jpegai_networks_run_their_convolutions_on_the_fast_kernel
  requires a completed decode of each of the four graphs, not of any one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- jpegai_decode::{decode, decode_with_stages} load the bundle's networks
  (load_networks, under acc64) beside its scale path and run the hyper decoders,
  context stages and synthesis through rec_y, rec_uv, syn_y and syn_uv;
  decode_loaded(data, icci, norm, scale path, networks, observe) decodes with
  both loaded once. The direct networks are reached only from
  jpegai_decode::legacy (Front::Direct, Back::Direct, test-only, legacy_int).
- The loaders no longer panic: Weights::from_json, IntNet::from_json and
  IcciNet::from_json return Result<_, BadMember>; a mistyped member is
  model_mismatch. IntNet keeps only well-formed tensors, so a member §13.6.1 reads
  that is mistyped is missing, as the lowering of the bytes refuses it.
- The neural sink lowers and loads the networks from their eight files' bytes
  through the store of loaded sets (keyed by their listing, at acc64), parses only
  the eICCI net besides, and reports a refusal (model_mismatch, field_range)
  without falling back: a_refused_exact_decode_does_not_fall_back gains a
  non-integer network weight and a mistyped eICCI field.
- The browser crate's decodeJpegAiFull reports a mistyped member.
- Goldens, unchanged (build host, release): jpegai_cross_platform's nine pinned
  hashes, the Synthesis stage d56695726bb6ebed… among them; the decoded RGB
  SHA-256 dbc494a5a670914e… (jpegai_reconstruction_binding with
  WAI_JPEGAI_EXPECT_SHA256, jpegai_e2e_full: max|Δ| = 1 on 4.446%).
- Memory (jpegai_memory, reference kernels): with the scale path and the networks
  loaded beforehand the decode holds 72,752,387 bytes, the direct decode
  175,465,380; the networks take 89,727,913 to load and hold 55,530,900.
- Also green: clippy's disallowed-methods guard and jpegai_decode_symbols, the
  sink's three dispatch tests, wai-web's tests and its wasm32 build; include/wai.h
  unchanged.
- CHANGELOG: the M7b entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The decode runs the hyper decoders, context stages and synthesis through the
operator set (gate 6), so their direct computation is test-only (legacy_int), the
reference the golden-equality tests and the differential hold the graphs to:

- int_transformer (the Q16 attention, normalisation and convolution executors);
- jpegai_synthesis' networks (decode_y/_uv and their _f32 forms, decode_hyper,
  mcm_stage0/_n, q16_from_symbols, and the helpers and Weights' accessors they
  use); Weights, IntNet, BadMember, from_json and ptq stay, for the loads;
- jpegai_pipeline's reconstruct_yhat/_uv and the sub-lattice shuffles;
- int_kernels' par_map, par_zip and conv_acc_i64, which only they called.

The tests that run the direct networks require legacy_int (int_kernels_geometry,
which the fast-kernel steps run with it; the calibration tests NOT-IN-CI lists).
wai_decode_bench's JPEG-AI cases now run the graphs the decode runs: its kernel
cases as one-operator WHG1 graphs under acc64, its synthesis case through syn_y and
syn_uv, through the interpreter with the fast kernels behind it.

Build host: the library with neural_int alone and with neural (no new warning),
with legacy_int and its tests; clippy's guard; the pinned goldens
(jpegai_cross_platform, jpegai_e2e_full, jpegai_reconstruction_binding with the
pinned RGB SHA-256); jpegai_ops and jpegai_nets_ops; byte-exact-conformance (101);
the wai-verify build; the bench's JPEG-AI cases; wai-web wasm32; include/wai.h
unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Gate 7 made these seven tests require `legacy_int` in Cargo.toml, but their files
were still gated on `neural_int` or `neural` alone, which `check-tests-in-ci.py`
reports: a file's `#![cfg]` must name what its `[[test]]` requires. Each file is
now gated on `all(..., feature = "legacy_int")`, as `jpegai_ops.rs` is.

Three test comments named the design's appendix and a plan item. They now name
the text's sections (§11's `role_profiles`, §13.6.2's table).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into wai/d2c-m7b
All checks were successful
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Successful in 1m12s
wai-pack / wai-pack · macos (pull_request) Successful in 1m51s
wai formal verification (integer core) / kani (integer-core) (pull_request) Successful in 6m13s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m20s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 3m22s
wai-ops integer operators / linux-x86_64 (pull_request) Successful in 50s
wai-ops integer operators / wasm32-wasip2 (wasmtime) (pull_request) Successful in 32s
wai-ops integer operators / wasm32 under Node (pull_request) Successful in 22s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 6m17s
wai-ops integer operators / independent checker (Python) (pull_request) Successful in 12s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m59s
wai-pack / wai-pack · linux (pull_request) Successful in 1m23s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m14s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m7s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m9s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m17s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 59m35s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 58m54s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 59m22s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 8m51s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 13s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 4m22s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 2m2s
wai-ops integer operators / linux-aarch64 (pull_request) Successful in 1m29s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 10m16s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 9m34s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 39s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m48s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m45s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m12s
938582b3d5
#308 (M8, which carried #298, M7a) has landed on main as bbc2681d. Main's other
changes since are outside wai/ (mesh, jcp, joule-code, joulehook).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into wai/d2c-m7b; CHANGELOG keeps every entry
Some checks failed
wai formal verification (integer core) / kani (plan) (pull_request) Has been cancelled
wai formal verification (integer core) / kani (sound) (pull_request) Has been cancelled
wai formal verification (integer core) / kani (stage) (pull_request) Has been cancelled
wai formal verification (integer core) / kani (rans64-t2) (pull_request) Has been cancelled
wai formal verification (integer core) / kani (stage-long) (pull_request) Has been cancelled
wai formal verification (integer core) / kani (integer-core) (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
wai cross-architecture byte-equality / does this change touch it (pull_request) Has been cancelled
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Has been cancelled
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Has been cancelled
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
c80e28e9a9
Main gained wai-quantum 0.6.3 and lockfile updates from other merges. The only
conflict was the CHANGELOG's top insertion; O8 + M7b's entry stays first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!315
No description provided.