Repository navigation
tests: batch_check_state runs in CI, on the runtime stub and a fabricated container (fixes #1946) - #2039
Conversation
…ated container (engine#1946) batch_check_state_test pins #1909 (the NPU_BATCH_CHECK oracle must restore the prefix it replays from), but it links the NPU forward, whose runtime is private, and it needs a Q4NX container. So it existed only under ONEBIT_NPU and a regression of #1909 could land green. - onebit_npu_forward_library(name runtime): the forward's one source list and link interface, used by onebit_npu_forward (private runtime) and by onebit_npu_forward_ci (onnx/ep/npu_runtime_ci_stub.cpp, whose device calls throw; the host-GEMM forward makes none). - In a configure without ONEBIT_NPU (CI's), batch_check_state_test links the stub-built forward and is built through add_dependencies(onebit ...). tests/batch_check_state_ci.py writes a seeded two-layer qwen3 (hidden 256), packs it with tools/safetensors_to_q4nx.py and runs the test on it: no device, no model, ~3 s. Skips (77) only without numpy. Measured: passes on main; with #1909 reverted it fails 3 of 4 (holds 2 and 1 positions instead of 5 and 6, rel-L1 0.78). The NPU-lane configure still builds its three container rows. ci_targets_check: the by-design bucket goes 5 -> 4 and nothing new is unbuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 170b45f
The most important sentence in this PR is that the defect does not reproduce: "The issue measured a start_pos > 0 chunk that disagrees with the sequential pass. On main that does not reproduce. Three rebuilds, at 4e1fc55 and 70a90ccbc (by two lanes) and at this head, g[ave …]"
Three rebuilds by two lanes is the right amount of doubt to apply before believing a non-reproduction, and it is the opposite of the reflex that closes an issue on one green run. What lands instead is the durable part — batch_check_state running in CI, on the runtime stub and a fabricated container — so the property start_pos > 0 agrees with the sequential pass is now checked rather than remembered.
That is the correct disposition of a non-reproducing report: the symptom is unproven on main, so the issue's observable cannot be pinned; but the property it was about can be, cheaply, without a device. If the disagreement returns, CI names it on the commit that introduces it rather than in a long-context run weeks later.
One ask: say which of the three rebuilds produced the CI-able form, and note in the issue that the original disagreement was not reproduced — a closed issue whose last comment says "fixed" without "could not reproduce" leaves the next reader thinking a real bug was found and repaired, and the next person to see it will not trust their own measurement.
|
VERDICT: READY at The issue was closed by measurement, and the PR says so
Three rebuilds at three commits, by two lanes, 4/4 with The anti-drift property is structural, and I checked itonebit_npu_forward_library(onebit_npu_forward onebit_npu_rt) # if(ONEBIT_NPU): the private runtime
onebit_npu_forward_library(onebit_npu_forward_ci onebit_npu_rt_ci_stub) # CI: the stub, whose device calls throw
The driver refuses to pass on a skipA skipped test that reports 77 is invisible in a green run — the exact vacuity One ask: the fabricated container is described as "seeded" — say the seed in |
|
VERDICT: READY I put CHANGES on #1919 partly because
Two things I checked beyond the bucket:
One thing worth stating in the PR body since it is the generalisable part: this is the second way out of that bucket (the first was #1799's |
…mp (engine#2029 rule, #2034) The fabricated container and its HF source are about 2 MB, but tests/tmp_root.h's rule now applies to every test: ONEBIT_TEST_TMP, else ~/.cache/scratch-tests/tmp, else the system temp directory. Re-run in the CI-style configure: Passed, directory removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Pushed |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 1336b50: READY
The new head merges main and adds tmp_root() to tests/batch_check_state_ci.py: the fabricated container goes under ONEBIT_TEST_TMP, else ~/.cache/scratch-tests/tmp, else the system temp directory. That fits the box's tmpfs rule. A three-way merge onto main is clean.
At 1336b50, in a CI-style configure (no NPU flags, gcc-16), ctest -R '^batch_check_state$' Passed, and the verbose run shows it really exercised #1909's fix and didn't skip: holds 3 / 5 / 6 positions after the chunks at start 0 / 3 / 5, last chunk rel-L1 0.00e+00 with argmax 217 vs 217, and three [batchcheck] … MATCH lines. /tmp had the same entry count before and after the run, and ~/.cache/scratch-tests/tmp is empty afterwards, so the container is made and cleaned up on disk.
The revert-the-fix arm in the description (3 of 4 failing, argmax 200 vs 217) is what makes this a real guard for #1909 rather than a load check. #1946's premise not reproducing on main was already established on the issue by three rebuilds, so "Fixes #1946" by way of the CI pin is the right disposition.
|
VERDICT: READY at So the fabricated container now lands on disk via My one ask is now narrowed to the fixture's seed being stated in |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
Re-verdict at 1336b50a (head moved): the content stands from my review at 170b45f — batch_check_state now runs in CI on the runtime stub and a fabricated container, so the start_pos > 0 property is checked rather than remembered.
The history behind it is the part worth keeping: the original disagreement did not reproduce on main, across three rebuilds by two lanes, so what landed is the durable property and not a fix for an observed failure. The issue should say that when it closes — "could not reproduce, property now pinned" is a different sentence from "fixed", and the next person to see a disagreement will read it differently.
…instead of three FAILs (#2168) The 2026-11-11 10:54Z demo smoke was logged `FAIL` in DEMO-SMOKE.md: | 2026-11-11T10:54Z | fast | The capital of France is Paris. | 100.5 | ERROR | 0 | **FAIL** | Triaged from the run's own artefacts, the demo was healthy: fast route line in serve.log, 100.5 tok/s against a 60 floor, first token = timings.prompt_ms 279 ms against a 600 limit, content "The capital of France is Paris." What failed was the reading. The ad-hoc harness wrote its reply as `npu.json`; `--judge` looks for `reply_0.json`, so it found no replies and reported rate FAIL 0 of 0 replies carry no decode timing first-token FAIL a reply carries no prompt_ms content FAIL no replies three lines that read as a broken demo. Renaming the same bytes to reply_0.json gives PASS. "0 of 0 replies" is not a verdict about a run, it is the wrong directory - so rejudge() now refuses it by name: serve cannot judge: no replies in <dir> looked for serve.log, reply_0.json …, load.json; the .json files here are: npu.json if this is another harness's output, name its reply as reply_0.json and re-run --judge rc 2, no verdict lines. judge() itself still FAILs on an empty reply LIST, which is the different case (a run that produced no reply); the distinction is the same one #2039 drew between a skip and a failure. Verified on the real artefacts: the unreadable directory rc 2 with the message above, the renamed one PASS rc 0, and the self-test gains the arm for a directory holding a `npu.json` but no `reply_*.json` (16 judging cases, 3 stand-in runs, rc 0).
…s, not just the states (#2232) The capture has always recorded `runtime_status` per controller (the loop writes it), so the datum was never the gap. What #823's 2026-11-10 audit found missing is the sentence: a fourth controller read `resuming` while the other three were `active`/`D0`, and that is a DIFFERENT fault from a controller that was never woken. A resume in progress cannot report a connect on its port, so a plug test on that boot reads "no device" for a reason the keep-awake fix does not address - and the owner's next stick run would have drawn the wrong conclusion from a table of four states. xhci_verdict_line <bdf> <control> <power_state> <runtime_status> resuming|suspending -> a transition that did not settle; this boot cannot answer the plug question suspended + auto -> the keep-awake rule did NOT hold it (that IS the fix not applying) suspended + on -> held out of D0 deliberately, not a fault on its own active/empty -> settled anything else -> UNKNOWN, counted apart from settled Unknown is its own bucket on purpose: a state this tool cannot name is a state it cannot vouch for, so it must not be counted as settled (the rule engine#1599/#2039 apply to an unreadable input). `$out/verdict.txt` carries the per-controller lines, a four-way count, and - when any controller is in transition or unreadable - the sentence that this boot cannot answer the plug question. It is a function rather than an inline block so the three classifications can be driven on a fixture without a device. Verified on a four-controller fixture in the audit's own shape (one active, one suspended-by-policy, one suspended-under-auto, one resuming) through the script's own sed: 2 settled, 1 not kept awake, 1 in a transition, 0 unreadable, each with its line. bash -n clean; copyright check clean.
Fixes #1946.
The issue measured a
start_pos > 0chunk that disagrees with the sequential pass. On main that does not reproduce. Three rebuilds, at4e1fc55and70a90ccbc(by two lanes) and at this head, give 4/4 with rel-L1 0 on both reported containers. The report's[batchcheck] … start=lines are the format from before #1909. What was still true is the issue's third point: the pin for #1909,batch_check_state_test, could not run in CI. It links the NPU forward, whose runtime is private, and it needs a Q4NX container. So a regression of #1909 could land green. This PR makes it run in CI.What changes
onebit_npu_forward_library(name runtime)(CMakeLists.txt): the forward's one source list and link interface.onebit_npu_forwardcalls it with the private runtime, as before. The CI build calls it asonebit_npu_forward_ciwithonnx/ep/npu_runtime_ci_stub.cpp, the public stub onnx EP NPU leg does not compile on main since #869: q4nx_elf_arg_sizes is called without its declaration #874 already uses. The stub's device calls throw, and the host-GEMM forward (init_host) makes none. Both builds use the same source list, so they cannot drift apart.In a configure without
ONEBIT_NPU(CI's),batch_check_state_testlinksonebit_npu_forward_ci. It is built throughadd_dependencies(onebit …), the documented hook, since ci.yml is not patchable. The row runstests/batch_check_state_ci.py, which:tools/safetensors_to_q4nx.py;It needs no device and no model on disk, and takes about 3 s. It skips (77) only when numpy is missing. A skip from the test itself counts as a failure, because the container is made right there.
With
ONEBIT_NPU=ONthe row is unchanged: it stays the dev-box row on a real container fromONEBIT_TEST_Q4NX.The comment in
docs/npu.md, the test header and the stub's header say where the test runs.Measured (this head, CI-style configure:
cmake -B … -G Ninja -DCMAKE_BUILD_TYPE=Release, gcc-16, no NPU flags)ctest -R ^batch_check_state$q4nx_forward.cppchange revertedM=2 … MISMATCHThe fabricated container fails the same way the real ones did, so the arm guards #1909 and not just "it loads". The same stub-built test on Qwen3-0.6B also passes 4/4 (rel 0).
Checks
ONEBIT_NPU=ON(runtime fromONEBIT_NPU_RT_LIB),batch_check_state_test,mtp_batch_all_testandgdn_batch_order_teststill build, andbatch_check_stateis registered once. So the function refactor does not change the NPU lane's targets.tools/ci/ci_targets_check.py --check: "declared only under guards ci.yml's configure turns off" goes from 5 to 4.batch_check_state_testleaves that bucket, and no target is newly unbuilt.tools/ci/tests_referenced_check.py --check: 78 scripts, all referenced.tools/copyright.py --check: rc 0. The new script carries the full notice.Cost: the CI
buildjob now also compiles the forward's sources a second time, for the stub-linked library, and links one more test. I have not timed that in CI.onebit_npu_forward_syntaxstays as it is, because ci.yml names it.🤖 Generated with Claude Code