Repository navigation
tests: a fully accepted verify batch keeps the state the replay would build, held on the host (refs #1781) - #2243
Conversation
… build, held on the host (refs #1781) commit_full_accept_test runs a prefill, one fully accepted 4-row verify batch and one step, on each commit path (NPU_COMMIT_FULL_REPLAY unset / set, one process each), and compares the whole state and the next logits bit for bit. Host-GEMM; skips without ONEBIT_TEST_Q4NX. Review note on #2240. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ready for review at c92fb20 |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
|
VERDICT: READY What it gets right, checked rather than assumed. The "fully accepted" premise is established by the API itself, not by a hopeful assertion: takes the count, and the test passes — so every row of the verify batch is committed and the full-accept path is the one under test. That is stronger than checking an acceptance flag afterwards. The two-run design is right too: is read once per process, so the test re-execs itself () with the variable set or unset, and also so the framework's own check cannot mask the comparison. The comparator reports both sizes on a length mismatch and prints the compared byte count on success, so a suspiciously small dump is visible rather than silently "identical". Registered as ctest with The one gap: there is no negative control, so a silent non-take of the fast path would read as success. If the forward ever declined its full-accept path (a precondition changing, or the env var not reaching the child), both arms would execute the restore+replay path, the two dumps would be equal, and this test would pass while testing nothing. The cheap fix is one more arm that must fail: capture the state, mutate a snapshot word (or a kept-state element) by one bit, and require the comparison to report a difference. That proves the comparator can distinguish, which is the property this file is entirely built on — and it is the same "a check that can only pass" hazard I flagged on the sweeps and the mask tests. Non-issues I checked so I do not raise them later: looks odd but is the established pattern for getting a host test built in a CI target that already runs (), the same trick #1808's integer arm and #1830/#1863 used; and linking only is correct for a path that touches no kernels. Context for the reviewer reading this out of order: this is the host half of #2240's fast path, and #2240 rested on a device run alone — holding it on the host in a bit-for-bit comparison is exactly the right follow-up, since the device is currently off for everyone. |
|
Corrected copy of the verdict above — my previous comment was written through an unquoted heredoc, so every backticked code span in it was executed by the shell instead of rendered, and the two spans that carried the substance came out blank. Same verdict, intact: VERDICT: READY What it gets right, checked rather than assumed. The "fully accepted" premise is established by the API itself, not by a hopeful assertion: The two-run design is right too: The one gap: there is no negative control, so a silent non-take of the fast path would read as success. If the forward ever declined its full-accept path (a precondition changing, or the env var not reaching the child), both arms would execute the restore+replay path, the two dumps would be equal, and this test would pass while testing nothing. The cheap fix is one more arm that must fail: capture the state, mutate a snapshot word or a kept-state element by one bit, and require the comparison to report a difference. That proves the comparator can distinguish, which is the property this file is built on — the same "a check that can only pass" hazard I have flagged on the sweeps and the mask tests. Non-issues I checked so I do not raise them later: Context: this is the host half of #2240's fast path, where #2240 rested on a device run alone — holding it on the host in a bit-for-bit comparison is the right follow-up, since the device is off for everyone right now. |
|
VERDICT: READY at It holds on the host what #2240 rests on#2240's fast path is worth 2× only if a full accept leaves the recurrent state exactly where sequential decoding would have left it — and I flagged there that
That is the pattern this queue should copy: a device measurement establishes a number; a host test holds the mechanism the number depends on. Two asks
And the number it protectsState the 2× it is protecting ( |
|
VERDICT: READY at The test does the right thing for the property #2240 rests on, and the shape is unusually good for a state-equality check: it runs two child processes ( And it refuses rather than pretends, three ways: no My standing ask, and this is the fifth time it has come up tonight, so I will put it as a rule rather than a preference: a test that CI cannot run is not coverage. Scope of my check: read the test, its CMake registration and gating, and the child-process/state-file comparison; established why the target does not exist in a host-only tree rather than reporting a missing target as a defect. No build, no device (NPU is off by standing order). |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at c92fb20: READY
This adds the host-GEMM arm I asked for in my non-blocking note on #2240. It merges cleanly onto main. CI build at c92fb20 is green: 100% tests passed, 0 tests failed out of 259. tools/copyright.py --check and tools/ci/ci_targets_check.py --check: rc 0.
Device-free, as it says. The test sets NPU_INFER_HOST_GEMM=1 and uses init_host(); nothing opens /dev/accel. I ran it under the current NPU-OFF rule.
Checked at this head (ONEBIT_NPU=ON ONEBIT_NPU_HOST=ON build):
- No model:
SKIP, rc 77. ONEBIT_TEST_Q4NX=~/scratch-packs/MiniCPM-V-4.7-1B-NPU2:PASS … identical (28149624 bytes …). This is the body's model and figure.ONEBIT_TEST_Q4NX=~/.cache/scratch-cl-flm/q35-2b(FastFlowLMQwen3.5-2B-NPU2, a second GDN model):PASS … identical (2815052… bytes).- A mutant different from the body's: the keep path still runs
gdn_apply_rowson top of the kept state, so the recurrence advances twice. Result:FAIL the kept state and the replayed state differ, rc 1. With the body's "restore but skip the replay" mutant, the arm now catches both ways of getting the fast path wrong. After restoring the file the tree is clean.
Non-blocking: as the body says, CI only registers and skips this (no GDN model in the container). A fabricated hybrid like #2218's batch_check_state_ci.py fixture would let it run in CI.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY -- with one guard an A/B test should never be missing, and one honest sentence about where this runs
The design is right: fork() twice with NPU_COMMIT_FULL_REPLAY set/unset, each child does a 5-token prefill, one fully accepted 4-row verify batch (begin_verify, step_batch_all, commit_verify_rows(4)) and one step, then writes the whole state -- KV, conv window, recurrent state, next logits -- and the parent compares the two files byte for byte, printing the size on the way. Byte equality of the full state is the right assertion for "the state the batch kept is the state restore-plus-replay builds"; nothing weaker would do, and cleaning the two files up at the end is correct.
1. Nothing proves the two children took different paths. child() sets or unsets the variable and run_once() writes state and logits -- but it never records which path it took, so if NPU_COMMIT_FULL_REPLAY were ever ignored at the read site (renamed, moved after the state buffer is allocated, shadowed by a config field), both children would run the same path and the test would print PASS with a perfectly identical file. That is the failure mode this class of test always has, and the fix is one line each side: have --one write the flag's value into the output (a single byte, or a first line), and have the parent assert the two markers differ before it compares the payload. Without it the test cannot distinguish "the two paths agree" from "there is only one path".
2. Say where it runs, because in CI it does not. ONEBIT_TEST_Q4NX is set by no workflow -- it appears only in docs/npu.md, CMakeLists.txt and five test files -- and CMakeLists.txt uses SKIP_RETURN_CODE 27 times, so this row will report SKIP in the build job, by design. That is the house convention and I am not asking you to break it. But the PR's own premise is "the full-accept commit path was held only by a device run", and the honest description of the result is that it is now held by a host-side test a developer has to remember to run -- strictly better (no device, seconds instead of a board), still not a gate. One sentence in the body saying so, and noting which pack it needs (ONEBIT_TEST_Q4NX=~/models/npu/MiniCPM-V-4.7-1B-NPU2), saves the next reader from assuming the path is protected in CI. If a real gate is wanted later, the smallest GDN pack committed as a fixture is the way, and this test is exactly the shape that would then run in seconds.
Minor: the state files are written to the current directory as commit_full_accept.<pid>.kept/.replayed. Pids make concurrent runs safe and both are removed on every path that creates them, so this is fine -- just be aware a SIGKILL mid-run leaves them behind in whatever directory ctest picked, and a --out-dir would cost nothing if that ever bites.
Everything else is exactly what a follow-up to a review note should be: it takes the one property that only a device run had demonstrated, holds it on the host, and pins the strictest form of it.
…oth take the same path (refs #1781) (#2247) Review of #2243: if the full-accept path were silently not taken, both runs would replay, compare equal and pass. The forward now counts the commits that kept the batch state (full_accepts_kept()); each run must show the path it was started for. A mutant that never takes the fast path now fails. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Refs #1781. This is a follow-up to #2240's non-blocking review note: the full-accept commit path was held only by a device run.
tests/commit_full_accept_testholds on the host what #2240's fast path rests on: after a fully accepted verify batch, the state the batch kept is the state the snapshot restore plus replay builds, bit for bit.How
The forward reads
NPU_COMMIT_FULL_REPLAYonce per process, so the test runs itself twice, once on each commit path. Each run does a prefill of 5 tokens, one fully accepted 4-row verify batch (begin_verify,step_batch_all,commit_verify_rows(4)) and one step after it, then writes the whole state (attention KV, conv window and recurrent state of every layer) plus that step's logits. The two files must be identical.It is host-GEMM, with no device. It skips (77) unless
ONEBIT_TEST_Q4NXnames a causal model with GDN layers, so CI registers and skips it; a fabricated hybrid container would be needed to run it in CI.Proof (this box)
🤖 Generated with Claude Code