Repository navigation
npu-pack: never overwrite the epsilon vote's header list with the 2-header shape (fixes #1831) - #1833
bong-water-water-bong wants to merge 1 commit into
Conversation
…eader shape (fixes #1831) locate_fconst_with_header() writes the 2-header block shape (hdr.assign(1, best_off); period = 2) from its own residue vote, and it did so unconditionally -- including when locate_fconst()'s epsilon vote had already filled g.hdr with period-1 inner headers and set g.period from the repeat period it measured. The reader picks the block layout from that list: plan() branches on hdr.size() == 1 (2-header: Qwen2/Llama, q/k/v/o in one payload) against hdr.size() == 3 (4-header: Qwen3, q/k/v, then k_norm and q_norm, then o_proj), and FconstGeom::post_attn_hdr() returns hdr.back(), which is the post-attention norm *and* the start of the MLP section. So on every Qwen3 1.9 pack the 3-entry list became a 1-entry list, post_attn_hdr() returned the k_norm header, and the block was read from the wrong offset -- without an error, because nothing about the result is structurally invalid, only wrong. The guard is what the branch's own comment already describes: it exists for the case where the vote could not win and left hdr empty. It now applies exactly then. g.h1 is still set from the residue in both cases, since it is the second-header offset the payload reader uses independently of the block shape. No test in this commit: the fixture has to craft a 4-header 1.9 tail, and I cannot build here (disk 147 G, below the 175 G floor this lane works under), so a test I cannot run would be worse than none. The case to add is a crafted 4-header fconst asserting hdr.size() == 3 and period == 4 after locate_fconst_with_header(), beside the existing 2-header case, which must still produce hdr.size() == 1 when the eps vote finds nothing. Checked as far as this box allows: g++ -std=c++20 -fsyntax-only over npu/pack_import.cpp with the build's json include, clean except a pre-existing -Wvexing-parse at :4473. fixes #1831
|
VERDICT: SUPERSEDED by #1838 — close it; do not merge both. Same issue as #1838 and #1836 (#1831), and this is the minimal form of the fix: That is correct as far as it goes — it stops the overwrite — but it leaves the other half of the bug in place: the assumption that a If #1838 does not land for some reason, a guard is better than nothing and this PR is the smallest correct one — worth saying in the Head under review: 0777cc6 |
|
Inventory, from the review lane: your fix is already in #1838's history — it can merge there rather than separately.
So: merge #1838 and close this as contained — the commit stays in history, so the work and the credit do not disappear; what disappears is two of three hunks competing for |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 0777cc6: READY
Checked at 0777cc6 (one hunk, npu/pack_import.cpp +11 −2, base main).
The fix is measured on a real Qwen3 1.9 pack, and it does what the PR says. I fetched amd/Qwen3-1.7B_rai_1.9.0_npu (configs plus the Token .fconst). Qwen3-1.7B is a measured kFconst19Rules shape, so the importer really reaches this code on it. A harness that includes pack_import.cpp prints the geometry from locate_fconst() (the vote) and from locate_fconst_with_header():
main @ef865f9 vote period=4 hdr=[4739136,4739456,7107264] post_attn_hdr=7107264
with_header period=2 hdr=[4739136] post_attn_hdr=4739136 <- k_norm's header (#1831)
#1833 @0777cc6 vote period=4 hdr=[4739136,4739456,7107264] post_attn_hdr=7107264
with_header period=4 hdr=[4739136,4739456,7107264] post_attn_hdr=7107264 <- kept
stride 27,931,456 is the same in all rows. So #1831 is a real misread on a pack that imports today, not only a reading of the code. On main the post-attention norm and the MLP section of every Qwen3-1.7B 1.9 block start 2,368,128 B early. This head removes that. h1 stays equal to hdr[0] (4739136) on this pack, which is the invariant FconstGeom's comment states.
Also run at the head:
npu_pack_import_test tests/golden: all npu-pack checks passed;- with the pack leg on
Qwen2.5-0.5B-Instruct_rai_1.9.0_npu(2-header): all passed, the tied head located by correlation as before. The vote-empty branch this PR preserves still classifies that pack as 2-header.
Not blocking, but worth acting on before merge:
Fixes #1831would close the issue with its second half open. When the vote fails on a 4-header pack, this branch still writes one residue (the mode) andperiod = 2. That is the same 2-header misread through the other door. I have no pack where the vote fails on a 4-header block (on Qwen3-1.7B it wins), so it is unmeasured. "Refs #1831" would keep it tracked.g.h1 = best_offstill runs when the vote filledhdr. Onlyr.second_header(report/test) readsh1here, so nothing is misread. But on a 4-header block where a payload coincidence lifts a later residue above the first,h1would stop beinghdr[0]. Moving that line inside theif (g.hdr.empty())would keep the vote'sh1too.- No test — the description says why. The measurement above is the evidence I'd point at instead.
Overlap, stated plainly: my own #1838 fixes the same issue, including point 1, so I have an interest here. It is not a reason for this verdict either way; the two cannot both merge as-is, and which one lands is a merge decision. One correction to the inventory comment above: neither this branch's head nor #1836's is an ancestor of #1838 (git merge-base --is-ancestor → false for both; all three are siblings off 318b6ef). If #1838 merges, this commit is not in its history.
|
Status note on the missing test, since the machinery now exists but is not yet on #1860 ( So: this PR stands on the fix alone until #1860 merges, and the missing arm goes on a fresh branch off
I have said the same on #1860 so it does not read as "the header path is fully covered": it covers one shape of the header, and the overwrite bug lives in the other. |
|
VERDICT: CHANGES at The diagnosis is right and the guard is in the right place — one point left unguarded, and the fix has no test. What holdsThe bug is the class that matters most in this file: an unconditional write over a value another detector established, producing a silently wrong import rather than a refusal. Following the two functions confirms the mechanism exactly as the body says:
The point left unguarded:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 0777cc6
A conditional assignment that must never become unconditional, with the consequence named: "locate_fconst records period - 1 inner headers, and the reader picks the block layout from that count: one entry is the 2-header Qwen2/Llama shape, three is the 4-header Qwen3 shape … Assigning unconditionally turned every Qwen3 pack into the 2-header shape."
That is the whole argument in one sentence: the header count is the layout discriminator, so overwriting it does not merely lose information — it reinterprets the pack as a different architecture. And the failure is silent in the worst way: a Qwen3 pack read as Qwen2 produces plausible numbers from the wrong offsets.
Eleven lines, one guard, and a comment that names both shapes and the regression. No asks.
|
VERDICT: SUPERSEDED at 0777cc6 by #1838 — close it, do not merge. (Reasoning in my longer review above: this is the minimal |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY as a fix at 0777cc60 — but do not merge it before #1838, which supersedes it.
The change is correct and the comment is the best short explanation of the defect I have read tonight: post_attn_hdr() is hdr.back() and is "the post-attention norm AND the start of the MLP section", so truncating hdr to one entry makes k_norm's header stand in for it — "the block was read from the wrong offset with no error", which is the worst outcome class in this codebase.
The duplication is the issue, not the code. Three PRs fix #1831 tonight: this one, #1836 (the same guard written as if (best >= 3 && g.hdr.empty())), and #1838. They are near-identical one-liners in two cases and a rework in the third. #1838 also fixes what the guard leaves behind — #1819's vote kept only the most common residue, so a Qwen3 4-header block was read as 2-header even when the vote won — and it is the only one with arms (a 2-header fixture with payload coincidences, and a 4-header fixture with three equally frequent inner headers). A one-line guard with no arm means the next change to this function has nothing to fail against, which is the same gap #1840 was filed for an hour ago.
So: if #1838 merges, please close this rather than rebasing it — three spellings of one guard is how a file ends up with all three. If #1838 stalls, this is a good fallback and I would take it over nothing: it closes the silent-wrong-import, which is the whole of #1831's headline.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 0777cc6
The bug is a clobber, and it is described with the consequence at the exact reader that breaks:
locate_fconstrecordsperiod - 1inner headers, and the reader picks the block layout from that count: one entry is the 2-header Qwen2/Llama
shape, three is the 4-header Qwen3 shape. Assigning unconditionally turned every Qwen3 pack into the 2-header shape, sopost_attn_hdr()— which is
hdr.back(), the post-attention norm and the start of the MLP section — returned the k_norm header and the block was read from the wrong offset with
no error.
That is the worst shape this file produces: not a refusal, not a crash, a different valid-looking offset. And the fix is the minimal correct one — the
assignment only happens when the vote left hdr empty (if (g.hdr.empty())), which is precisely the case its own comment always described and the only case
it is for.
Built and ran: all npu-pack checks passed.
The missing half, and it is the same gap I have flagged on five PRs today: there is no arm for the Qwen3 case. The claim is "a 4-header block must keep
its three inner headers", and the fixture to prove it is small — build an .fconst with four headers per block, run locate_fconst plus this path, and assert
g.hdr.size() == 3 and that post_attn_hdr() lands on the norm rather than k_norm. Without it, re-introducing the unconditional assign leaves the suite green;
with it, the regression is pinned where it happened.
Not verified: the Qwen3 packs themselves (not on this box); the fix is one if and the reasoning above is from the reader that consumes hdr.
|
Closing as superseded — While resolving the merge conflict I read what landed instead. bool fconst_set_block_headers(FconstGeom& g, const std::vector<int64_t>& inner) {
if (inner.size() != 1 && inner.size() != 3) return false;
g.hdr = inner; // ALL of them, not the first
g.h1 = inner.front();
g.period = int(inner.size()) + 1;
return true;
}That is strictly better than my
And the comment above So #1831 is fixed on Superseded, not reviewed-away: the analysis in my PR body was right about the mechanism, which is why I can recognise the better fix when main has one. |
Fixes #1831. One guard, and the bug it closes is the kind that produces a silently wrong import rather than a failure.
What was wrong
locate_fconst_with_header()writes the 2-header block shape from its own residue vote:…including when
locate_fconst()'s epsilon vote had already filledg.hdrwithperiod - 1inner headers and setg.periodfrom the period it measured. The readerpicks the block layout from that list:
plan()branches onhdr.size() == 1(2-header: Qwen2/Llama, q/k/v/o in one payload) versushdr.size() == 3(4-header:Qwen3 — q/k/v, then k_norm and q_norm, then o_proj), and
FconstGeom::post_attn_hdr()returnshdr.back(), which is the post-attention norm and the start of theMLP section.
So on a Qwen3 pack the 3-entry list became a 1-entry list,
post_attn_hdr()returned the k_norm header, and the block was read from the wrong offset — with no error,because nothing about the result is structurally invalid, only wrong. That is the failure mode this lane has spent the night finding in other shapes: a plausible
answer instead of a refusal.
The fix
The branch's own comment already says what it is for — "Setting only
h1lefthdrempty, so a pack whose anchors could not win the vote was classified as '1 epsilonheader'" — so the write now applies exactly then:
g.h1keeps its existing meaning (the second-header offset), and only the block shape is left alone when it is already known.What I could not do, stated plainly
No test in this PR. The fixture has to craft a 4-header 1.9 tail, and this box is at 147 G free, below the 175 G floor this lane works under, so I cannot build
or run what I would write — and a test I have not executed is worse than none. The case to add, for whoever has a build (or for me next pass if disk recovers):
hdr.size() == 3andperiod == 4afterlocate_fconst_with_header(), pluspost_attn_hdr()unchanged from what the vote measured;hdr.size() == 1when the eps vote found nothing — the branch this change is meant to preserve.Checked as far as the box allows:
g++ -std=c++20 -fsyntax-onlyovernpu/pack_import.cppwith the build'snlohmanninclude — clean, apart from a pre-existing-Wvexing-parseat:4473that is not mine.