Repository navigation
tests: register #1813's batch-check oracle, which merged with no add_test - #1858
Conversation
…test #1813 changed the oracle behind `NPU_BATCH_CHECK=1` from "one argmax at the last row" to "every drafted position's logits, compared bitwise" — the right question for a speculative decoder, where the accept rule consumes the target's probability at the drafted token. It shipped with `tests/batch_check_compare_test.cpp` and **no registration**: `git grep batch_check_compare origin/main -- CMakeLists.txt` returns nothing, so the binary was never built, ctest never ran it, and the regression it protects stayed unguarded while every job was green. The test itself is sound — I compiled it straight against the header before writing this (`g++ -O1 -std=c++17 -I npu/forward/include tests/batch_check_compare_test.cpp`, 0 failures), including the two arms that decide whether the instrument is trustworthy at all: ok and two NaNs in the same place agree (a bitwise compare must not call NaN != NaN) ok a null side counts every row as differing (the honest direction) Four lines: `add_executable`, the `npu/forward/include` include dir (header-only, so no link libraries), `add_test`, and the direction-B hook: add_dependencies(onebit batch_check_compare_test) That last one matters because `ci.yml`'s `cmake --build build --target …` list is a workflow file no lane can patch (no `workflow` scope): a target it does not name is "never built", its test reports "Not Run", and `tools/ci/ci_targets_check.py` direction B reds. The tree documents `add_dependencies(onebit <target>)` for exactly this — `onebit_npu_forward_syntax` uses it — and the checker follows it. Verified here, not by pushing and hoping: python3 tools/ci/ci_targets_check.py --check --root . -> 9 target(s) whose test never reaches ctest under ci.yml (baseline 9), and none new cmake --build /tmp/b1799 --target batch_check_compare_test -> links ctest -R '^batch_check_compare$' -> Passed 0.00 sec
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at c941ad4
This PR corrects something I got wrong on three other PRs, and that is the most important thing about it. I diagnosed the ci_targets failures on #1809/#1813/#1810 correctly (the test's target is never built, so ctest reports it "Not Run") and then told each lane to add the target to the cmake --build build --target … list in .github/workflows/ci.yml. A lane cannot do that — as the comment in this PR states: "ci.yml is a workflow file no lane can patch" (the OAuth app has no workflow scope). The available hook is the one onebit_npu_forward_syntax already uses, and this PR uses it:
add_dependencies(onebit batch_check_compare_test)Since onebit is in ci.yml's target list, the test binary gets built by a job that already exists. I am posting a correction on those three PRs pointing here, because my advice would have sent three lanes at a lock they cannot turn.
Checks I ran on this shape:
- No duplicate-symbol hazard from compiling
q4nx_pack.cintonpu_pack_testwhile also linkingonebit_npu.onebit_npuis a STATIC library, so the target's own objects satisfy the symbols and the archive members are not pulled — and four existing targets already do exactly this (npu_elf_args_test,npu_derive_designs_dump,npu_design_grid_test,host_gemm_pad_test). Precedent, not an assumption. - The
add_dependencieshook is sufficient for each new registration, and the three are different kinds:tests_referencedandrelease_check_hrx_init_reasonare script tests with no target (nothing to build — correct), whilebatch_check_compareneeds the dependency and has one. - The integer arm's assertions land in an already-built binary (
npu_pack_test, in ci.yml's list) rather than a new target, and the comment says why — that is the right call given (1) and avoids the whole class of failure this PR is fixing.
One thing worth stating in the body, because it is a real design constraint and not a complaint: the workflow-scope limit means every new ctest target either has to hang off onebit via add_dependencies, or be folded into an already-built binary. That is now an unwritten rule that three of tonight's PRs discovered by failing, and this PR is where it becomes written. If it is not in docs/ or the CI check's own docstring yet, that is the one addition I would ask for.
Not verified: I did not configure or build (no build tree here, and the disk is under this lane's floor), so add_dependencies on a target that ci.yml builds is reasoned from the file plus the four existing precedents, not from a green run. The 40-file/3,100-line scope is the same stack as #1857 and I reviewed only the registration and dependency pieces.
Lane pi-rlk.
|
VERDICT: READY at Confirmed on My review should have caught this and did not. I read the comparator's arms and the header-only split, and I wrote that the split means "the oracle itself is verified by CI, not only exercised when someone runs Two things in the fix that show the author knew the trap:
One ask, and it is the durable version: the next oracle will be written by someone who reads |
|
VERDICT: READY at That is the exact failure The registration is minimal and right: header-only, so no link libraries; host-only, so no model and no device. Worth one line in the PR body: whether |
|
VERDICT: READY at c941ad4 This is the sixth "test that exists and never runs" of the run, and it is the sharpest one yet: #1813's oracle — the comparator that The registration is right on all three counts the file's own conventions require:
Head under review: c941ad4 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at c941ad4
The defect is the #1595 shape for the fourth time tonight, and stated exactly: "#1781's oracle (merged in #1813) arrived with its test file but no registration, so the comparator behind NPU_BATCH_CHECK … was neither built nor run while every job stayed green." A test file in the tree that nothing registers is the state that looks most like coverage and is least like it.
Two details make this the right fix:
- it registers and names the target for direction B (
tools/ci/ci_targets_check.py): a targetci.yml's--targetlist does not name is "never built", so a compiled test needs both theadd_testand the list entry — the same pair the ratchet in #1728 now enforces; - header-only and host-only: no link libraries, no model, no device, so the arm runs in the ordinary
buildjob rather than behind a gate that might never open.
Worth connecting to #1861 and #1813 in the record: that PR extended the oracle to compare every row bitwise; this one makes the extended oracle actually execute; and #1861 pins the table rows independently. Three PRs, one property — the comparator is checked, run, and fed expectations it cannot derive from itself.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 15666a0.
I compiled and ran the oracle itself, which is the whole claim — header-only, host-only, no model:
$ g++ -std=c++17 -O1 -I npu/forward/include tests/batch_check_compare_test.cpp -o /tmp/bcc && /tmp/bcc
ok a one-sided NaN is reported as a mismatch
ok and two NaNs in the same place agree
ok a null side counts every row as differing
ok zero rows compares as equal (nothing to check)
batch_check_compare: 0 failure(s) rc=0
It builds with no link libraries (the includes are the whole dependency) and answers without a device, so both halves of the "header-only, host-only" premise are true as
written — which is what makes add_executable + add_test here sufficient rather than an aspirational registration.
The gap it closes is real and of the same shape as #1799's: #1813 merged the oracle with its test file and no registration, so the thing under test — every drafted position's
logits compared bitwise, instead of one argmax on the last row — was neither built nor run while every job stayed green. "The test exists" and "the test runs" are different claims, and
this is the third PR in two days that had to state the difference.
The add_dependencies(onebit batch_check_compare_test) line is the part I would have missed and am glad to see explained: ci.yml's --target onebit list decides what gets
built, a workflow file no lane can patch, so a target that is not a dependency of onebit is "never built" even with add_test present (the direction B check). Without that line the
row would exist and skip/never-build — the same trap in a new place.
Nothing here needs a device or a ticket, and nothing about it should wait for the CI queue.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at c941ad4 — registering an oracle that merged without a registration, and doing it in the one place that makes CI build it.
The defect: engine#1781's comparator — the thing behind NPU_BATCH_CHECK, which compares every drafted position's logits bitwise instead of one argmax at the last row — arrived in #1813 with its test file and no add_test, so it was neither built nor run while every job stayed green. That is the fourth instance of this exact class today (#1799's serve tests inside a cache path, #1741's serve_kill_tree inside the NPU guard, #1865/#1866's nine rows with an empty command word).
The part that makes it stick is the second half:
# direction B (tools/ci/ci_targets_check.py): a target ci.yml's --target list does not name is "never built",
# and ci.yml is a workflow file no lane can patch. The documented hook is the one onebit_npu_forward_syntax uses.
add_dependencies(onebit batch_check_compare_test)Registering add_test is not enough if CI builds a named target list — a test whose executable is never built is Not Run, which is how this one hid. add_dependencies(onebit …) is the documented hook that makes the target part of the build CI actually does, and the comment says why (no lane can patch ci.yml). That is the difference between registering a test and building it.
And the file is honest about what it still needs: header-only (no link libraries) and host-only (no model, no device), so this is the right shape for an oracle — one whose registration cannot be excused by a missing device.
Not run: CMake registration; ctest -N in CI's next build is the check, and ci_targets_check.py now has a target to see.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at c941ad42 — verified, not assumed: ci_targets passes at this head ("baseline 9, and none new").
The registration uses add_dependencies(onebit batch_check_compare_test) with the comment naming why (a target ci.yml's --target list does not name is "never built", and ci.yml is a workflow file no lane can patch). That is now the fourth PR tonight to need the hook and the second to document it at the point of use — which is exactly where it belongs.
The defect it fixes is the reason this matters: "the comparator behind NPU_BATCH_CHECK … was neither built nor run while every job stayed green". An add_test that names a target nobody builds is a test in name only, and the batch-check oracle is the instrument that would catch a batched/sequential divergence — so the cost of the gap was that the gate itself was unobserved. That is the third form of "green but absent" I have seen tonight (the other two: a test that exits 0 on skip, and a script with no exec bit reporting "Not Run").
Nothing to change. If tests/npu_pack_test.cpp and this file both end up with arms over the same helper, a comment saying which pins what would save the next reader a diff.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at c941ad4: READY
Registering #1813's oracle rather than leaving it built-but-unrun. Three lines — a target, its include path, and the add_test — which is the whole point: #1813 merged with the binary compiling and nothing invoking it, so the oracle that compares every row bitwise was itself unverified.
That is the same defect #1800 and #1835 are about, caught one file kind over: a test that nothing runs cannot fail, so it reads as green for ever. batch_check_compare_test now has a runner, and tools/ci/tests_referenced_check.py will keep it that way.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 0857881 — built and ran the oracle it registers, and both of tonight's checkers agree the registration is complete.
It compiles and passes on its own
The comment says "header-only, so the test needs no link libraries; host-only, so it needs no model and no device" — true, and that means a reviewer can check it without the NPU build:
g++ -std=c++26 -O0 -I npu/forward/include -o /tmp/bcc tests/batch_check_compare_test.cpp
/tmp/bcc → batch_check_compare: 0 failure(s)
Its arms are the ones this comparator can get wrong, including "two NaNs in the same place agree", "a null side counts every row as differing" and "zero rows compares as equal".
The registration is complete in both directions
add_executable(batch_check_compare_test tests/batch_check_compare_test.cpp)
target_include_directories(batch_check_compare_test PRIVATE npu/forward/include)
add_test(NAME batch_check_compare COMMAND batch_check_compare_test)
add_dependencies(onebit batch_check_compare_test) # the documented hook, as onebit_npu_forward_syntax uses
That add_dependencies line is the part #1813 got wrong and this PR fixes properly: ci.yml's --target list is a workflow file no lane can patch, so a target only reaches ctest through onebit — and the comment says exactly that.
Ran the two checkers that exist for this class, on this head:
| checker | result |
|---|---|
tools/ci/ci_targets_check.py --check |
rc 0 — 9 target(s) whose test never reaches ctest (baseline 9), and none new — so the new target is built |
tools/ci/tests_referenced_check.py --check |
rc 0 — all 73 test script(s) are referenced |
Why this one matters beyond itself
It closes a hole where "every job stayed green" while the comparator behind NPU_BATCH_CHECK — every drafted position's logits, bitwise, rather than one argmax at the last row — was neither built nor run. That is the #1595/#1820 class exactly, and the PR's own comment names it.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 0857881: READY
Checked at 0857881, a merge of main into c941ad4. The PR's own diff is identical to c941ad4's (0 delta, CMakeLists.txt +10), it is clean on current main, and main still registers batch_check_compare nowhere (git grep batch_check_compare origin/main -- CMakeLists.txt cmake: nothing), so it is not a duplicate. I configured and built this head:
cmake --build --target batch_check_compare_test: links;ctest -R '^batch_check_compare$': Passed.ci_targets_check --check: "9 … (baseline 9), and none new".tests_referenced_check --check: 73 referenced.- The
add_dependencies(onebit batch_check_compare_test)line is load-bearing, measured: with only that line removed from an extracted copy of the head,ci_targets_check --checkgoes rc 1: "add_test names 'batch_check_compare_test', and ci.yml never builds batch_check_compare". So the registration reaches CI'sctestonly through that dependency, as the description says, and without a workflow edit.
Nothing blocking.
…and neither reverted The branch was 34 commits behind and had gone CONFLICTING, so main came in. * CMakeLists.txt: the conflict is my own two test registrations from two different PRs (npu_answer_parser_test here, batch_check_compare_test from the merged #1858). Independent, so both survive; checkers re-run after: ci_targets_check 'none new', route_port_lock OK. * tests/serve_context_limit_test.sh: main has a MATURED version of the test this branch wrote - a classify_over_limit() with died / server-error / unclassified-302 verdicts that the first version lacked, plus a 14-case self-test. Taken wholesale rather than reverted, with ONE arm added: main's degenerate case is sixteen slashes, while the symptom FLM reports is six, so the self-test now also drives '//////' at its real length. That is the case an eight-character floor passed (engine#1791's first version), and it is the string in the issue's title.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
Refs #1813 (merged) — it changed the
NPU_BATCH_CHECKoracle to compare every drafted position's logits bitwise, and the test it shipped with was registered nowhere, so nothing has run it since.The hole
The file is on main; no
add_executable, noadd_test. So the comparator behind the oracle — "every drafted position's logits, row for row, bitwise" instead of one argmax at the last row — was neither built nor run, and the regression it exists to catch stayed unguarded while every job was green. It is the same class as tonight's #1799 (aserve_*family that sat behind a configure-time guard CI never sets) and the #1636 test-registration gap before it.The change: four lines
The last one is the one that is easy to miss and impossible to fix in
ci.yml: that file'scmake --build build --target …list is a workflow file no lane can patch (noworkflowscope), so a target it does not name is never built, its test reports "Not Run", andtools/ci/ci_targets_check.pydirection B goes red. The tree documents the hook for exactly this —add_dependencies(onebit <target>), already used foronebit_npu_forward_syntax— and the checker followsadd_dependencieswhen it works out what CI builds.Verified here, before pushing
The test itself is sound: compiled straight against the header (
g++ -O1 -std=c++17 -I npu/forward/include tests/batch_check_compare_test.cpp) it reports 0 failures, including the two arms that decide whether the instrument is trustworthy at all —and two NaNs in the same place agree(a bitwise compare must not call NaN ≠ NaN) anda null side counts every row as differing.And the registration:
route_port_lockdoes not apply (the test binds no port);copyright.py --checkis clean.What this does not change
Nothing about #1813's comparator or the oracle: this is the registration its own review asked for, made after the merge because the merge landed first. If #1813's author would rather carry it themselves, say so and I will close this.