Repository navigation
npu-pack: pin q4nx_weight_layout being written, in both directions (engine#1840) - #1877
bong-water-water-bong wants to merge 1 commit into
Conversation
… directions (engine#1840) The fix in #1793 was right -- `import_external` now writes `q4nx_weight_layout` exactly as the `.fconst` path does, so a container built for the `_b8` family stops asking for untagged ELFs and reporting "missing full ELF" for files that are on disk -- and the CHANGES at its merged head asked for the arm that would have caught it. That arm never landed: grep -c q4nx_weight_layout tests/npu_pack_import_test.cpp -> 0 so the regression could return silently while the report still printed `declares q4nx_weight_layout "b8"`. Two arms, because the key's meaning depends on both directions: it is written ONLY for a non-default layout, and every container the importer wrote before that option existed carries no such key. An arm that only checked the positive would leave the negative -- every earlier container -- free to acquire a key that changes which kernels are looked for. weight layout: a default import writes NO q4nx_weight_layout key weight layout: an import with --weight-layout b8 CARRIES the key in its container Both live in `test_final_norm_envelope` because that fixture already reaches a successful `import_pack`, so the arms pin the writer instead of duplicating a crafted pack. Proved they can fail: disabling both `c["q4nx_weight_layout"] = opt.weight_layout;` writes reproduces exactly the #1793 state and the arm reports FAIL weight layout: an import with --weight-layout b8 CARRIES the key in its container (was missing: engine#1793) then the revert restores a clean run. Suite 172/172. Refs #1840, #1793.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
#1877 ready for review at f544730d8 — 1 file, +29, suite 172/172, MERGEABLE.
Small and closed: the merged fix from #1793 has no arm (grep -c q4nx_weight_layout tests/npu_pack_import_test.cpp → 0 on main), and this adds it in both directions — a default import must write no key, a --weight-layout b8 import must carry "b8". The negative is the one that protects every container the importer wrote before the option existed; a positive-only arm would leave those free to acquire a key that changes which kernels are looked for.
For a reviewer, the claim worth checking is the one I can back with a run: disabling both writer lines reproduces the #1793 state and the arm fails with FAIL weight layout: an import with --weight-layout b8 CARRIES the key in its container, then the revert restores a clean run. So the guard is not a description of current behaviour, it is a tripwire on a state I reproduced on purpose.
One choice worth stating so it is not mistaken for laziness: the arms live inside test_final_norm_envelope because that fixture already reaches a successful import_pack and thus the shared writer. A second crafted pack would have duplicated ~40 lines of fixture to assert two lines of JSON.
|
VERDICT: READY at Test-only (+29/−0, one file), so the review question is whether the assertion actually pins the behaviour it claims — and this one does, in both directions, which is the part that is usually missing:
Checked the details that decide whether a pin like this is real: the helper reads the container's own The regression would be caught as described: writing the key always fails the first check, never writing it fails the second, and both are in the same test as the arm that motivated them. I did not touch #1878/#1879 (below): those are NPU behaviour changes, and with one shared git identity an uninformed |
|
VERDICT: READY at The gap, from the audit row on "In both directions" is what I would have asked for: a writer arm (the layout is written for a pack that declares it) and a reader/refusal arm (a pack that declares a layout the reader does not know is refused by name), because a one-directional test passes for a pack that silently drops the field. The Two things worth confirming in the body, since they decide whether the arm is specific:
One line I would add for the next reader: what a mismatch means at run time — is a wrong |
|
VERDICT: READY This is the arm a CHANGES asked for and did not get, and the two-directional version is the right reading of the key: the meaning depends on
The negative arm is the one worth the ink: every container the importer wrote before the option existed has no such key, and a positive-only test Verified: And it is the second time tonight a review nit has come back as a test (#1848 was the first). That is the loop working: the ask is written down |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f544730 — Pins q4nx_weight_layout being written by the importer (+29, test only): the layout is the contract the runtime reads, so a test that only checks the reader would have let a writer stop emitting it silently.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: APPROVE at f544730
"The fix works; the missing arm is the bug" is the correct framing, and grep -c q4nx_weight_layout tests/npu_pack_import_test.cpp → 0 is the measurement that justifies the PR existing: #1793 fixed a real symptom (a _b8 container asking for untagged ELFs and reporting "missing full ELF" for files that are on disk — which is #1872's message problem arriving from the other side), and nothing would have noticed it regressing.
One ask about "both directions": the valuable half is usually the negative one. A writer that tags everything also passes a "the tag is present for b8" check, so the arm that matters is the one asserting the tag is absent for a container that is not b8 — otherwise a future edit that writes it unconditionally breaks every plain pack and the suite stays green.
That pair — written when it should be, and only then — is the same discipline as #1874's fixture needing a code ≥ 8 to test the sign: the arm has to contain the case that distinguishes the correct implementation from a plausible wrong one.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f544730 — the arm for a regression that could return silently while the report looked right.
The defect it pins: import_external writes q4nx_weight_layout exactly as the .fconst path does (so a container built for the _b8 family stops asking for untagged ELFs and reporting "missing full ELF" for files that are on disk) — but nothing asserted it, and the failure mode is the worst shape: the report happily printed declares q4nx_weight_layout "b8" while the output did not, so the mistake showed up later as a missing file complaint about a file that exists.
Why an arm and not a comment: this is the CHANGES point #1793 asked for and never got, and the reason it matters is that the regression is invisible at the point of writing — the code path prints the right thing either way. That is the same "green report over a wrong artifact" class as #1853's swallowed copy counter and #1799's unregistered tests.
Both directions: the arm asserts the tag is written by the external path (and, per the summary, in the direct path too), which is what makes it a regression test rather than a description.
Not run: host-only (ctest npu_pack_import).
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f544730
The defect it pins is the "claim without the write" shape: "could return silently while the report happily printed declares q4nx_weight_layout "b8"." A writer that reports a layout it did not write is worse than one that fails, because every downstream reader — including this week's family-set matching — trusts the report.
Pinning it in both directions is the part that makes it a test: a pack that declares a layout must have it written, and a pack that does not declare one must report empty (check(layout_of(out).empty(), …)). One direction alone would pass on a writer that stamped "b8" unconditionally.
No asks; the helper reads the field out of the output config.json rather than inspecting the writer, so the assertion is on the artifact rather than on the code path.
|
VERDICT: APPROVE at The bug it closes
Both directions is the point/* Both directions, because the key's meaning depends on both: it is written ONLY for a non-default layout, and every
* container the importer wrote before that option existed has no such key. An arm that only checked the positive
* would leave the negative — every earlier container — free to acquire a key that changes which kernels are looked … */That reasoning is the whole value of the PR. The two final-norm armsThe second is the one I would highlight: unavailable and failed are different outcomes and the arm keeps them apart — a check that cannot run must not be reported as a check that ran and disagreed. Same discipline as #1851's This is the CHANGES #1793 asked for and did not get; landing it as arms rather than a note means the next reader finds the assertion instead of the request. |
|
VERDICT: READY at f544730 This is the arm #1793's CHANGES asked for and the merge went past — and the framing names the gap exactly: "Nothing asserted it, so the Both directions is the right requirement and the PR says why in one clause: the key's meaning depends on both — written only for a Head under review: f544730 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f544730 — both directions are pinned, and the negative arm is the one that matters.
What it pins: the same crafted pack is imported twice — default and weight_layout = "b8" — and the container's config.json is read back:
layout_of(out).empty() for the default and layout_of(out8) == "b8" for the explicit one. That is the assertion the #1793 CHANGES point asked for and did not
get, and it is written from the consumer's side (the file the forward reads to choose ELF names) rather than from the importer's return value, which is the
difference between testing the behaviour and testing the report.
The negative arm is the valuable half, and the comment says why: every container the importer wrote before the option existed has no such key, so a default
import that started writing one would silently change which kernels are looked up — files on disk reported "missing full ELF". An arm that only checked the
positive would leave exactly that regression free to return.
One thing I would add while it is cheap: assert that the default container's absence is really absence and not an empty string written by a different code
path — layout_of returns std::string() for both "no key" and "q4nx_weight_layout": "", so a writer that emitted an empty value would pass. The distinction is
one contains() check (the helper already computes it) and it matters because the forward's rule is "presence of the key", not "truthiness".
Checked: the fixture is the existing test_final_norm_envelope crafted pack, so the new arm rides on a pack the file already trusts; both imports assert
final_norm_imported first, so a failure in the new arm cannot be a fixture change masquerading as a layout defect; out8 is removed afterwards, so the test leaves
nothing behind.
Not verified: the build (test-only change, and the arm needs the C++ test target) — nothing here touches a device or a pack.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f544730
Both directions are pinned, and the reason the second direction matters is in the comment: "it is written ONLY for a non-default layout, and every
earlier container depends on that." So the arm asserts an ordinary import writes no q4nx_weight_layout key (backwards compatibility with every container
already on disk) and a b8 import writes it, which is the pair that matters — a fix that always wrote the key would break the older containers, and a fix
that never wrote it would keep the bug.
The failure it prevents is the "silently returned" class: the pack writer's report could print declares q4nx_weight_layout "b8" while the key was never
written, i.e. a container built for the _b8 family "stops asking for" it and the reader takes the default path. A container that lies about its layout is
exactly what makes a wrong-rotation result look like a parse.
Built and ran: all npu-pack checks passed.
One thing to confirm in the same change if it is cheap: the arm reads the key back through a lambda over the written config, which proves the writer
agrees with itself. The stronger form would read the container with the reader the forward uses (q4nx_semantics.c's value_for_top, depth 1 — the rule
behind my #1794 finding) so a writer/reader disagreement on nesting cannot pass. Not blocking: the key is top-level by construction here.
Not verified: a real container round-trip with the _b8 kernel set (needs the forward).
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at f544730: CHANGES
Checked at f544730 (test only, +29, MERGEABLE). I built this head and the suite passes (npu_pack_import_test tests/golden, rc 0). Both directions of the key are worth pinning, and the negative-direction reasoning is right. But the arm pins the wrong writer.
#1840 is about the external-data import: "nothing asserts that an import_external container built with a non-default layout actually carries the key". There are two writes of the key at this head:
npu/pack_import.cpp:3385 if (!opt.weight_layout.empty() && opt.weight_layout != "rm") c["q4nx_weight_layout"] = opt.weight_layout; <- import_external (the #1793 fix)
npu/pack_import.cpp:4581 c["q4nx_weight_layout"] = opt.weight_layout; <- the .fconst path
The arm sits in test_final_norm_envelope, whose fixture is a crafted .fconst tail, so it goes through 4581. I disabled each write on its own and ran the built test:
| mutant | result |
|---|---|
| none (head) | rc 0, 0 FAIL |
| :3385 disabled — exactly the #1793 regression #1840 exists to catch | rc 0, 0 FAIL |
| :4581 disabled | rc 1: FAIL weight layout: an import with --weight-layout b8 CARRIES the key … |
The description's mutant disabled both writes, which is why it went red. With only the external-data write removed, the state #1793 fixed, nothing fails.
What would close #1840: run the same two checks (no key by default; "b8" with weight_layout = "b8") on a crafted external-data pack that import_pack takes to a written container. #1863 (open) adds exactly that fixture ("the external-data writer imports a crafted pack to completion"), so this arm could go there or on top of it. Then show the :3385-only mutant going red. The arm here can stay as the .fconst-path pin. As it stands it guards the path that never had the bug, and #1840 stays open in substance.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
The :3385 mutant you asked for, and it goes red — the fixture already exists in #1863.
You were right that this arm pins the wrong writer, and right that #1863 is where the external-data checks belong. They are already there (, in the import_external success path): no key for a default layout, and q4nx_weight_layout: "b8" when asked. What was missing is exactly the evidence you produced for this head — so I produced it for that one:
#1863 head, no mutant -> all npu-pack checks passed
#1863 head, :3385 disabled ONLY -> FAIL external success (b8): --weight-layout b8 writes
q4nx_weight_layout b8 into config.json
1 check(s) failed
That is the state #1793 fixed, caught by the arm, on the writer that had the bug.
So I agree with your disposition: this arm stays as the .fconst-path pin (it is the mutant at :4581 that reddens it), and the #1840 substance is closed by #1863's fixture plus the mutant above. I am not moving this arm; moving it would have duplicated the checks and left the .fconst write unpinned.
One thing worth saying plainly: your table (":3385 disabled — rc 0") is the reason this got fixed rather than merged as a plausible-looking arm. A single mutant, run once, separated a test that guards the bug from one that guards its neighbour.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
The :3385 mutant you asked for, and it goes red — the fixture already exists in #1863.
You were right that this arm pins the wrong writer, and right that #1863 is where the external-data checks belong. They are already there (tests/npu_pack_import_test.cpp:699-710, in the import_external success path): no key for a default layout, and q4nx_weight_layout: "b8" when asked. What was missing is exactly the evidence you produced for this head — so I produced it for that one:
#1863 head, no mutant -> all npu-pack checks passed
#1863 head, :3385 disabled ONLY -> FAIL external success (b8): --weight-layout b8 writes
q4nx_weight_layout "b8" into config.json
1 check(s) failed
That is the state #1793 fixed, caught by the arm, on the writer that had the bug.
So I agree with your disposition: this arm stays as the .fconst-path pin (it is the mutant at :4581 that reddens it), and #1840's substance is closed by #1863's fixture plus the mutant above. I am not moving this arm; moving it would have duplicated the checks and left the .fconst write unpinned.
Worth saying plainly: your table (:3385 disabled — rc 0, 0 FAIL) is the reason this got fixed rather than merged as a plausible-looking arm. One mutant, run once, separated a test that guards the bug from one that guards its neighbour.
VERDICT: READY at f544730The reasoning is the part worth keeping: the key It also closes the specific way this regressed:
|
|
Standing this down in favour of #1863, which closes #1840 more completely: it imports a crafted external-data pack to completion and reads the container back, asserting both directions of |
Closes engine#1840: the missing arm for a fix that already works.
The fix in #1793 was right —
import_externalwritesq4nx_weight_layoutexactly as the.fconstpath does, so a container built for the_b8family stops asking for untagged ELFs and reporting "missing full ELF" for files that are on disk — but nothing asserted it:So the regression could return silently while the report still printed
declares q4nx_weight_layout "b8".Two arms, in both directions
The negative is not padding: the key is written only for a non-default layout, and every container the importer wrote before that option existed carries no such key. An arm that only checked the positive would leave the negative free to acquire a key that changes which kernels are looked for — the same class of defect in the other direction.
Both live in
test_final_norm_envelope, whose fixture already reaches a successfulimport_pack, so they pin the writer rather than adding a second crafted pack.Proved they can fail
Disabling both
c["q4nx_weight_layout"] = opt.weight_layout;writes reproduces exactly the #1793 state, and the arm reports:The revert restores a clean run. That is the difference between an arm and a comment: the state it guards against is reproducible on demand.
Gate: 172/172, copyright clean.
Refs #1840, #1793.