wai-neural-e2e: make the job link and run, and fix the decoder mismatch it was hiding #101
Loading…
Reference in a new issue
No description provided.
Delete branch "ci/neural-e2e-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This job has never passed, so its reference comparisons have never executed in
CI. Two independent root causes, both established from evidence — the first from
the job's own log. First green run:
3374f391— all five decoders exact.1. The Rust test could not link on the runner
The job reaches its last step and fails there. From the job log:
ort'sdownload-binarieslinks a prebuilt static onnxruntime (1.28.0 for ort2.0.0-rc.13) built against glibc ≥ 2.38 and GCC 13's libstdc++. The
runner's default job image is
catthehacker/ubuntu:act-22.04. All five undefinedsymbols were checked in both images:
act-22.04(runner default)act-24.04Fix:
container: image: catthehacker/ubuntu:act-24.04on this job. No Rustor Cargo change, so nothing here touches the files the landing chain edits. The
job only looked like it failed early because the runner's Python caches are
warm.
2. Encode the fixtures the way the decoder evaluates them
With the link fixed, the comparisons expose a real mismatch. The Rust decoder
opens every ONNX model via
ort_session::load_deterministic— one intra-opthread, one inter-op thread, full optimisation, deliberately. The fixture
generator used onnxruntime's defaults — every core — so a convolution's
reduction was split across threads and its float output shifted.
Traced symbol by symbol, encoder and decoder agree on the first 416 of 12,288
MLIC++ symbols — bypass-coded ones included — then diverge inside the first
pass, before anything is fed back:
scale_table[16]One scale straddles one threshold and the arithmetic-coded stream desyncs:
11,841 symbols wrong,
max|Δ|=255. On main that garbage overflows the bypassloop and panics at
rans.rs:77; the landing chain bounds that loop, so there itdecodes silently to a wrong image.
zbuild_indexboundscale_table[0]All 12 session sites now go through
_session, which mirrorsload_deterministic.3. A correction to this PR's own guard
The filter
_matches_referenceselects six tests, butsnac_real_model_matches_referenceneeds real pretrained SNAC weights thissynthetic job never builds, so it skips on every run. As first written, the guard
would have kept the job red with every decoder correct — I had checked it fires
with no fixtures, but never ran it with fixtures. It is now excluded by name,
so the guard stays strict for the five in scope.
Result on the real runner (
3374f391)Falsified locally: removing only the threading pin brings the MLIC++ panic back;
deleting one in-scope fixture makes the guard fail the step.
Other red checks on this PR:
macOS energy backend (jcp-energy)is the knownIOReport meter defect, unrelated to this change. The
linux-arm64legs onearlier commits were red because I broke that runner — see below — and have been
re-run on
2579c9c4. #109 was hit too.Interaction with the landing chain
Every link of #103–#109 edits
rans.rsandneural_native_decode.rs; this PRtouches neither, so it merges independently. Once it lands, every PR blocked on
this job needs a re-run to pick up the fixed workflow.
🤖 Generated with Claude Code
Every `*_matches_reference` test in `neural_native_decode.rs` opens with if !wai.exists() || !ref.exists() || !model.exists() { eprintln!("skipping ... /tmp artifacts not present"); return; } so a missing fixture makes the test skip — and cargo reports a skip of this shape as a **pass**. Verified by running the job's exact command on a machine with no fixtures: test result: ok. 6 passed; 0 failed Six green, nothing compared. The job is named "synthetic fixtures → Rust decode → reference (±4)" and in that state it verifies none of those things. Today the job is red, so this is latent rather than active: the Python fixture step must be failing outright, which is the one way this surfaces. But the moment fixture generation succeeds while writing nothing — a harness quietly producing 0 of 5, a path change, a partial install — the job goes fully green having checked nothing, and the neural byte-exactness claim loses its only end-to-end guard with no signal at all. The fix asserts on the skip rather than enumerating fixture paths. A path list here would be a second copy of the one in the test file, free to drift from it, which is the same failure one level up. Grepping the run for "skipping" stays correct whatever fixtures the harnesses grow. Falsified: with no fixtures, `cargo test` exits 0 and the guard exits 1, naming all six skipped comparisons. Two things this does NOT do. It does not diagnose the current red — the forge exposes no job logs, so the failing step cannot be identified from here; the most likely cause is the unpinned `pip install torch torchvision compressai onnx onnxruntime`, whose versions have moved since this workflow last ran. And it does not pin those versions, because that would be fixing something I have not diagnosed. Worth noting how rarely this runs: its trigger paths (`wai/wai-rs/src/neural/**`, the test, the validator) were last touched 2026-07-31, and the workflow itself 2026-08-26. A gate that fires a few times a year can rot between firings, and this one was only exercised now because an unrelated stale-branch rebase produced a push delta wide enough to match its paths. Co-Authored-By: Claude Opus 5 <[email protected]>The companion to the skip guard, and the reason it is not hypothetical. `wai_validate_synthetic.py` ends with `return 0 if ok else 1`, and its harness loop deliberately catches per-harness exceptions to keep going. So a run where one of five harnesses succeeded exited **0**: the CI step went green, the four decoders whose fixtures were never written then SKIPPED in the Rust suite, and a skip of that shape is reported as a pass. 5/5 -> exit 0 1/5 -> exit 0 0/5 -> exit 1 The job therefore had a path to fully green while having checked one decoder out of five, with nothing in the output saying so. That is the scenario the previous commit's guard was written against; it turns out the generator hands it over rather than it needing an unlikely coincidence. Now `return 0 if ok == len(harnesses) else 1`. Partial generation stays useful interactively — the per-harness ✓/✗ lines print either way and a caller wanting one fixture can ignore the exit code — but CI can no longer read a partial run as a pass. Kept alongside the grep guard rather than replacing it. They fail on different things: this one refuses to claim fixtures that were not written, the guard refuses a comparison that did not happen whatever the cause. On the job's current red, now measured rather than assumed: the failing status reports **1m7s on this branch and 1m5s on #54**, whose tree does not touch this workflow at all. Near-identical, and far too early to have compiled the `neural` feature set and reached the Rust step where the guard lives. Both die at the same earlier Python step, so neither change here causes the red and neither can clear it. Diagnosing it needs the job log, which this forge does not expose; the unpinned `pip install torch torchvision compressai onnx onnxruntime` remains the likely cause and is still not being fixed blind. Co-Authored-By: Claude Opus 5 <[email protected]>ci(wai-neural-e2e): a skipped reference comparison is reported as a passto ci(wai-neural-e2e): this job can go green having verified one decoder of fivee49cc554c35bc79b16725bc79b16729eec011c48ci(wai-neural-e2e): this job can go green having verified one decoder of fiveto wai-neural-e2e: make the job run, and fix the decoder mismatch it was hiding9eec011c483374f391de3374f391de2579c9c42dwai-neural-e2e: make the job run, and fix the decoder mismatch it was hidingto wai-neural-e2e: make the job link and run, and fix the decoder mismatch it was hiding