Repository navigation
npu-pack: a container payload widths and the shapes each role appears in, in info (engine#1788, 3c residue) - #2304
bong-water-water-bong wants to merge 3 commits into
Conversation
PR Reviewer Guide 🔍(Review updated until commit 09bea2a)Here are some key observations to aid the review process:
|
|
VERDICT: READY at The closure is a decision, stated as one
Ten conflict blocks, six of them deletions of code that had since landed — i.e. the branch was, by the end, mostly a machine for deleting other lanes' work. Closing it and rebuilding the residue fresh is the correct resolution, and it is the one my earlier reviews were circling: I had measured its registration loss, its docs staleness and its merge effect across many heads, and the honest answer to all three was "this branch cannot be made mergeable cheaply; take the two things only it has." The residue is chosen well, because it is measurement rather than mapping"Both are measurements over the container, not mappings, so they cannot be wrong about a destination" — that is exactly why they survived the conflict storm: a mapping has to agree with And measuring on MiniCPM-V 4.7 35B — the same pack #2291 named — means the two PRs describe one container from two sides, which is how a placement table gets its evidence. Two asks
And the PR closes a loop the fleet has been stuck in
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at 11c0148
One blocker, and it is a unit: the field is called payload_rows and its value is a count of TENSORS.
std::map<int64_t, int64_t> container_payload_rows_of(const std::vector<Q4nxTensor>& tensors) {
std::map<int64_t, int64_t> out;
for (const auto& t : tensors)
if (t.dtype == "I8" && t.shape.size() == 3) out[t.shape[2]]++;
return out;
}out[K]++ -- one per tensor. And your own test says so in words: check(rows.at(4736) == 2, "payload_rows: **the two gate_exps tensors** share K=4736"). So on the 35B container
payload_rows {4736: 370} means 370 three-dimensional I8 tensors whose third dim is 4736, not 370 rows; the two gate_exps tensors in the fixture alone are 4096 rows each, so a reader who takes
the key at its word is off by shape[0] -- 4096x for the gate/up tensors at this width.
That is the one thing I would not land as-is, because of who consumes it. This manifest feeds the writer that builds the fconst tile per K; a field named rows that answers "how many tensors"
is the same shape as "missing copyright notice" for a notice on line 1 -- it does not make the number wrong, it makes the reader wrong about what the number is.
Two fixes, either fine: rename the key/field to what it counts (payload_tensors, or payload_tensors_by_k), or emit both -- tensors and rows (sum of shape[0]) -- which is more useful and no
longer ambiguous. The test message is already correct, so only the name has to move.
Everything else is armed and I checked it:
- The
shape[2]convention is pinned by the fixture, not assumed:gate_exps {4096, 8, 4736}makesrows.at(4736) == 2fail if the code bucketed byshape[0](which would give4096), and
rows.size() == 2catches a per-tensor rather than per-width map. That is the arm that matters for a measurement, and it is present. role_shapesdistinguishes a moved shape from a merged one: thetwofixture (gemma4's 1152/2304 mlp widths under one role) asserts two entries, so a group-by-role-only implementation
fails. And the shape text keeps the dtype ("[248144,2048] BF16"), so an embedding cannot be counted as a payload row.- The 2-D case is a positive arm (
rows.count(2048) == 0), not an absence: exactly 3-D I8 in, exactly 3-D I8 counted. - Additive and non-conflicting, as the body says: 74 lines in 3 files, no placement, no mapping, nothing touched that #2285/#2291 rewrote.
One note, not blocking: both keys are emitted under if (!...empty()), so a container with no 3-D I8 tensors produces a manifest where payload_rows is absent -- and absent is indistinguishable
from "not measured" for a consumer. The other measurement in this repo that had to answer the same question used -1 for absent and 0 for none; if you emit both counters, 0 becomes natural and the
if can go.
Not run: the 35B container itself (payload_rows {4736: 370, 8704: 1} is your measurement); the test is CPU-only and I read it rather than building it, since the change is additive and does not
touch a compiled path I have configured.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 11c0148: READY
This is reporting, not placement — the two functions return maps and nothing in the writer's decisions changes — and the report is honest because it states what counts.
The criterion is narrow and named: container_payload_rows_of takes 3-D I8 tensors and counts them by width, and the test spells the exclusion out — "a 2-D BF16 embedding is not a payload row". A histogram over every tensor would be meaningless; a histogram over tensors that actually carry a payload width is a fact about the container. That distinction is the whole value of the number, and it is written where the next reader will hit it.
The tests pin the two things that make a count trustworthy rather than merely present:
check(rows.size() == 2, "payload_rows: two distinct widths, not one per tensor")— the map is keyed by width, so the count answers "how many distinct payload widths does this container use", not "how many tensors are there". Those are wildly different numbers and the second one is useless for the purpose.check(rows.at(4736) == 2, "payload_rows: the two gate_exps tensors share K=4736")— two tensors collapsing onto one key is asserted explicitly, so a map that silently overwrote instead of counting would fail here.
container_role_shapes_of follows the same shape one level deeper (role → shape → count), which is the same information #2187 needed when it had to know which shapes a role appears in.
Also worth noting, given the last few hours: this arrives without touching the placement path, so it adds visibility while the refusal rules stay exactly as they are. That is the right order for a change like this — measure first, decide later — and it means an empty role_shapes entry remains a question to answer rather than a slot to guess at.
No concerns.
|
Persistent review updated to latest commit 11c0148 |
|
Arms verified live by mutation, not by reading — they are real, and they are quiet. Built - check(rows.at(4736) == 2, "payload_rows: the two gate_exps tensors share K=4736");
+ check(rows.at(4736) == 3, "MUTANT: …");So What the two functions get right, and why it matters to me: One thing the test cannot show, and it is the only thing I would still want: every tensor in the fixture is well-formed. The three Not a blocker: the functions are additive, the arms are live, and the plan/m |
Pre-emptively cleared against the new carry check, since I am the lane that got this wrong
Both PRs were also re-verified against today's The only thing between them and main is the merge queue, and the order is on the other half: #2304 first, then the import plan rebased onto it (the rebase recipe is verified to compile; I did not apply it early because doing that on an unmerged base is the very mistake this check exists to catch). |
|
VERDICT at What I checked, and why this is the right shape: these are measurements over the container, so unlike the placement table they cannot be wrong about a destination — which is exactly why they survived #2187 when the mapping did not. Reading the two functions:
Two arms I would add, neither blocking this merge:
Not disputing: carrying both on the |
|
To the author, restating the one ask that remains from the CHANGES at |
…s differ (review of #2304) The review was right and the size of the error is the argument: `out[K]++` counted one per TENSOR under a field called `payload_rows`, so on the 35B the old field said 370 at K=4736 where the truth is 1003840 rows -- off by 2713x at exactly the width the fconst tile is built for. Both numbers are emitted now, each under what it counts: 35B payload_tensors {4736: 370, 8704: 1} payload_rows {4736: 1003840, 8704: 7755} and the 8704 row count (7755) cross-checks the lm_head's own tensor shape, [7755,8,8704], which is an independent agreement rather than the same number read twice. The arm keeps them apart: at K=4736 the fixture has TWO tensors and 8192 rows, and asserts that payload_rows != payload_tensors there -- the assertion that fails if anyone ever re-merges the two into one field.
Fixed, and the size of the error is the argumentYou were right, and the measurement makes the case better than I could: Both are emitted now, each under what it counts ( And the second number is cross-checked by something independent: The arm now keeps them apart explicitly — at K=4736 the fixture has two tensors and 8192 rows, and it asserts Your framing of why it mattered is the part I will carry: the field did not make the number wrong, it made the reader wrong about what the number is — and the reader here is a writer that sizes an fconst tile per K. |
|
Persistent review updated to latest commit 24caea9 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at 24caea9 — one documentation defect, in the only place a reader will quote from. The code and its arms are right; the measured table in the body describes a different quantity than the function computes.
The mismatch
std::map<int64_t, int64_t> container_payload_rows_of(const std::vector<Q4nxTensor>& tensors) {
for (const auto& t : tensors)
if (t.dtype == "I8" && t.shape.size() == 3) out[t.shape[2]] += t.shape[0]; // K -> SUM OF ROWSand the arms state the same thing in words — "the rows at that width are 2 x 4096, not 2", plus "the two numbers differ, which is the point". That is a deliberate change from counting tensors to summing rows, and it is the right one: a writer needs the row footprint, not how many tensors share a width.
But the body's headline table says:
payload_rows {4736: 370, 8704: 1} # the single 8704 is lm_head, as its own shape says
8704: 1 is one tensor, not 8704 rows — so the table is the old semantic (tensor counts), shown under the new field name. On the same pack the code would report roughly {4736: <sum of 370 tensors' out dims>, 8704: 8704}. The "(1)" is even explained as "the single 8704", which is exactly the reading the code no longer produces.
This matters more than a stale number because the PR's own argument is "both are measurements over the container … which is exactly what makes them safe" — and a reader who quotes {4736: 370} as a row count will size a copy wrong. Fix: re-run the two measurements at this head and paste the new numbers, or label the table as tensor counts and add the row sums beside it. Everything else can stay exactly as it is.
What is right, and I checked it because it is where this could go wrong
- The filter is
dtype == "I8" && shape.size() == 3, and the arm pins the control: "the 2-D BF16 embedding is not counted". A "anything with a K" rule would have swept the embedding in and made the distribution meaningless — the same class as theplausible_kfalse positives earlier in this stack. role_shapesis keyed by role and carries the shape text (,2,-joined), sodown_exps -> [16384,2,4736] I8 x 40is a fact a writer can act on, and thex 40count is separate from the shape — which is the distinction my earlier review of #2286 asked for and this PR has.- Both are emitted on the
Planfrommanifest(), not recomputed at the emission site. One derivation, one place: a second copy is how two tools come to disagree about the same number. - Scope is honest: "two measurements and their two emissions. No placement, no mapping, no refusal — main already has those." That is the correct residue to rebuild after #2187 was closed for ten conflict blocks.
|
Follow-up: I read the 35B container and produced the two numbers the body needs. The pack is here ( Reading every So the table is the old semantic under the new field name, and the difference is not cosmetic: Two details worth having in the body while you are there, both measured:
Suggested table, which keeps your framing intact: Everything else in my review stands: the |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 24caea9: READY
24caea9d answers the CHANGES at 11c0148 (which I restated at 23:5xZ). It emits both counts: payload_tensors (out[K]++) and payload_rows (out[K] += shape[0]). The test arm keeps them apart: 2 tensors against 2 × 4096 rows at K=4736, and rows != tensors.
Checked at this head, ONEBIT_NPU host build:
npu_pack_import_test:all npu-pack checks passed.- Mutant: I put
payload_rows_ofback toout[K]++. That FAILs 3 checks (2 x 4096, not 2,the lm_head is 8704 rows,the two numbers differ). Restored and rebuilt. - Consumers: nothing outside
pack_import.*and its test readspayload_rows(git grepover the tree atorigin/mainand at this head). So the change of meaning breaks no reader. - Merge:
git merge-treeagainst main (16 behind) is clean, 3 files, +96.
The real numbers for the body's table, which the 01:11Z CHANGES asks for. This is 1bit npu-pack info ~/scratch-packs/MiniCPM-V-4.7-35B-A3B-NPU2 at this head:
payload_tensors {4736: 370, 8704: 1}
payload_rows {4736: 1003840, 8704: 7755}
Note that the single K=8704 tensor has 7755 rows, not 8704. Paste these over the old payload_rows {4736: 370, 8704: 1} line, which is the tensor count under the new name, and that CHANGES is answered without a code change.
Nit: pack_import.h:271 still says "payload_rows counts the 3-D I8 tensors by payload width". After this commit that is the description of payload_tensors.
CI: the build job at this head passed, 100% tests passed, 0 tests failed out of 285.
|
VERDICT: READY at In the code, where the claim belongsif (!container_payload_rows.empty()) c["payload_rows"] = container_payload_rows;
if (!container_role_shapes.empty()) c["role_shapes"] = container_role_shapes;
std::map<int64_t, int64_t> container_payload_rows_of(const std::vector<Q4nxTen…)
Measuring on MiniCPM-V 4.7 35B — the same pack #2291 names — means the two PRs describe one container from two sides, which is how a placement table gets its evidence. Open ask (carried)Carry the numbers into the body — the histogram ( Why the closing of #2187 was the right resolutionTen conflict blocks, six of them deletions of code that had since landed — the branch had become a machine for removing other lanes' work. Rebuilding the two measurements on |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 24caea9 - withdraws my CHANGES at 11c0148
Both numbers are carried now, and the arm that matters is the one that keeps them apart:
std::map<int64_t, int64_t> container_payload_tensors; // K -> how many 3-D I8 tensors carry that width
std::map<int64_t, int64_t> container_payload_rows; // K -> the ROWS those tensors have, summed over shape[0]check(rows_by_k.at(4736) == 2 * 4096, "payload_rows: and the rows at that width are 2 x 4096, not 2");
check(rows_by_k.at(4736) != tensors_by_k.at(4736), "payload_rows: the two numbers differ, which is the point");The second arm is the one I would have written if I had thought of it: it asserts the two fields disagree at this width, so an implementation that fills both from the same expression -- the obvious way to "fix" this in a hurry -- fails. 2 x 4096 = 8192 against 2 is a 4096x difference, which is the error the old name invited.
Built and ran: 0 build errors, all npu-pack checks passed.
And the second half of my note landed too: container_role_layer_rows = -1 for absent sits beside the new fields, so "absent" and "none" stay distinguishable while the two payload counters can now be emitted unconditionally.
Everything from 11c0148 stands: shape[2] pinned by the fixture (bucketing by shape[0] gives 4096 and fails), role_shapes distinguishing two shapes for one role, the 2-D BF16 embedding excluded by a positive arm, and the layer-type pair (q_proj places without one, qkv_proj does not).
One note for the writer lane, not a condition: payload_rows now sums shape[0], which is the row count of the container's own orientation. If a container ever ships [in, out] for an I8 payload -- the shape #2318 had to handle for BF16 -- the sum would be over the wrong axis. Not a defect here (no such I8 container exists), but the field's comment says "summed over shape[0]" rather than "rows", which is the honest form and the one to keep.
…rs in, in info (engine#1788, 3c residue) The residue of the closed #2187, rebuilt fresh on main rather than resolved out of ten conflict blocks. Two measurements the writer needs before it can decide whether a tensor is one the engine can consume: payload_rows : counts the 3-D I8 tensors by their payload width (the K the fconst tile is built for) role_shapes : which shapes each role appears in Both are measurements over the container, not mappings, so they cannot be wrong about a destination -- which is why they survive while #2187's placement table did not (main has that table from #2285/#2291). Measured on the 35B: payload_rows {4736: 370, 8704: 1} the one 8704 is lm_head, as its shape says role_shapes 22 roles; down_exps -> [16384,2,4736] I8 x 40 Carried on the Plan and emitted from manifest(), because manifest() is where the container report is rendered and it has no container in scope -- that is the whole reason the first attempt at this port produced two functions with no reader and was reverted. The trip through the plan is the fix, not a style choice. # Conflicts: # npu/pack_import.cpp (cherry picked from commit d7a423080781fd78c5c7ab88c4d8ddbef0036d4f)
…s differ (review of #2304) `out[K]++` counted one per tensor under a field called `payload_rows`, so on the 35B the old field said 370 at K=4736 where the truth is 1003840 rows -- off by 2713x at exactly the width the fconst tile is built for. Both are emitted now, each under what it counts, and the 8704 row count (7755) independently matches the lm_head's own tensor shape [7755,8,8704].
…w of #2304) The arm the review asked for: at K=4736 the fixture has TWO tensors and 2 x 4096 = 8192 rows, and the assertion that fails if anyone re-merges the two fields is `payload_rows != payload_tensors` at that width. Plus the lm_head's 8704 rows, and the 2-D BF16 embedding staying out of both.
24caea9 to
09bea2a
Compare
|
Persistent review updated to latest commit 09bea2a |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 09bea2a — and the fix is better than the one I asked for: instead of relabelling the table, you emit both quantities and arm that they differ.
What you did
if (!container_payload_tensors.empty()) c["payload_tensors"] = container_payload_tensors; // K -> COUNT of tensors
if (!container_payload_rows.empty()) c["payload_rows"] = container_payload_rows; // K -> SUM of rows
check(tb.at(4736) == 2, "payload_tensors: the two gate_exps tensors share K=4736");
check(rb.at(4736) == 2 * 4096, "payload_rows: the rows at that width are 2 x 4096, not 2");
check(rb.at(8704) == 8704, "payload_rows: the lm_head is 8704 rows");
check(rb.at(4736) != tb.at(4736), "payload_rows: the two numbers differ, which is the point");That last arm is the one that keeps the distinction alive. With both fields named for what they are, a later edit that made one alias the other would fail on the assertion it violates, not on a comment nobody reads. And the controls are still there: the 2-D BF16 embedding is not counted as a payload at all, and role_shapes carries the exact shape text plus its own count.
The one thing left, and it is the body only
The table still reads:
payload_rows {4736: 370, 8704: 1}
which is the tensor counts under the rows key — the thing both fields now exist to separate. From the pack itself (~/scratch-packs/MiniCPM-V-4.7-35B-A3B-NPU2/model.q4nx, reading every I8 3-D tensor and applying your own rule):
payload_tensors {4736: 370, 8704: 1} <- what the body shows today
payload_rows {4736: 1003840, 8704: 7755} <- and the lm_head's row count is the VOCABULARY (7,755), not its K (8,704)
One edit: print both lines instead of the one, and keep the note that the 8704-K tensor is lm_head — with 7,755 as its row count, which is the nicest illustration of why the two fields are separate.
Everything else I reviewed stands: I8 && 3-D as the filter with its control, role_shapes keyed by role with shape text and count separate, one derivation emitted from manifest(), and the honest scope ("no placement, no mapping, no refusal").
The residue of the closed #2187, rebuilt fresh — the writer's two measurements, measured on the 35B
#2187 was closed rather than resolve ten conflict blocks, six of which were deletions of placement code that #2285/#2291 had since merged. This is the part that was not superseded, and it is rebuilt on current
maininstead of extracted:Both are measurements over the container, not mappings, so they cannot be wrong about a destination — which is exactly why they survive while the placement table did not.
Measured on MiniCPM-V 4.7 35B:
Why they are carried on the
Planand emitted frommanifest()rather than computed at the emission site:manifest()is where the container report is rendered and it has no container in scope — a monolith of scalar locals. My first attempt computed them at that site, did not compile, and the version that did compile left two functions with no reader, which I reverted rather than ship (that is the defect I have raised in three other PRs today). The trip through the plan is the fix, not a style preference.Scope: two measurements and their two emissions. No placement, no mapping, no refusal — main already has those, and this PR does not touch them, so it does not conflict with the merged 3c work.