Repository navigation
forward: Qwen3.6-35B-A3B-NPU2 answers -- [in, out] BF16 matrices and its shared-expert gate (refs #1786) - #2318
Conversation
PR Reviewer Guide 🔍(Review updated until commit e682158)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at be816a8: READY
This is stacked on #2314: the base is cl-flm/1786-embeddinggemma, and this PR's own commit is be816a86 only. I checked it out at the head and built in an ONEBIT_NPU host tree (NPU_INFER_HOST_GEMM=1).
| run | result |
|---|---|
bf16_matrix_test |
ok: both orientations, the padded grid and the refusals |
batch_check_state arms: default, qwen2, qwen2_untied, lfm2, lfm3, qwen2vl, qwen3_5, hy_v3, hy_v3_sgate |
ALL PASS each; hy_v3_sgate corr 1.0000000 |
embed_forward (#2314's arm, now through bf16_matrix.h) |
2/2 PASS |
mutant: the shared_expert_gate.weight fallback not read (both load sites) |
FAIL, hy_v3_sgate corr 0.9889216 |
mutant: the transposed branch removed (if (false)) |
FAIL, [in, out] is read and [in, out]: read transposed, each value at its own place |
CI build at this head |
100% tests passed, 0 tests failed out of 285; bf16_matrix (#98) and batch_check_state_hy_v3_sgate (#264) both ran and passed |
The orientation rule cannot misread a non-square [out, in]. The transposed branch needs r0 == cols && c0 == rows. A true [out, in] fits the grid only if r0 <= rows and c0 <= cols. Both can hold at once only when rows == cols. So the one ambiguous case is the square one, and the header names it and reads it as [out, in], which is right for Embedding-Gemma's square projections. A square [in, out] matrix (a router with H == E, say) would be read wrong with no error. Nothing on the box has one, and the header comment is where that would be found.
Not run by me: the pack itself is not on this box, so the host "before/after" text and the NPU Paris row are the author's.
Possibly related, unverified: on #2279 I noted that the raw MiniCPM-V-4.7-35B-A3B container stores a shared_expert_gate.weight while the forward reads mlp.share_router.weight. This fallback may cover that pack too. Worth a host run on #1788's row.
When #2314 merges, retarget this PR to main and re-check git merge-tree there.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY -- the orientation is decided by shape with a stated rule, and the new test is made reachable by CI rather than merely added
The fault is the pair of spellings this stack keeps meeting, and both are measured rather than guessed: ssm_alpha_proj/ssm_beta_proj are BF16 [2048, 32] -- [in, out], like the pack's [H][E] router -- and read transposed they match the GGUF of the same checkpoint at corr 0.99999 (with the GGUF's 32 value heads interleaved, noted as a reference-side reorder). The second is the shared expert's gate appearing as shared_expert_gate.weight beside the routed ones. Before this, the BF16 reader refused the first and the forward stopped at load -- which is the good failure, and the PR says so: read as [out, in] they would have been garbage instead.
q4nx_bf16_matrix_into() is a small, deterministic rule set, and each branch has an arm:
[out, in]copied, padding zeroed;- a declared shape that is exactly the transpose of the grid asked for is read transposed -- decided by shape, not by a heuristic or a name;
- a square matrix is read as
[out, in]; - wider than the grid, or short of bytes: refused (
-1), tested both ways.
The test plumbing is the part I would highlight: bf16_matrix_test is added as a dependency of onebit so it reaches CI's ctest (ci_targets). That is the mechanism that stops a new test from being a file nobody runs -- and it is exactly what engine#2302's ci_targets_check is there to verify, so the two PRs reinforce each other rather than each needing its own discipline.
One sentence I would add, about the square case. "A square matrix is read as [out, in]" is a convention, and a square matrix read the other way is the same shape -- so nothing anywhere would fail; the numbers would simply be the transpose. That is not a defect in this PR (the packs it serves have no square BF16 matrix), but it is the one branch whose correctness rests on absence rather than on evidence. Either say that ("no pack we serve has a square BF16 matrix, so the convention is unreachable today") or, if a square case is reachable, prefer a refusal -- the failure mode of refusing is a loud load error, and the failure mode of guessing is garbage that matches no correlation and no shape.
Scope is stated honestly: stacked on #2314 with the base named and a retarget note; ctest -j 6 279/279 without device tests; every forward arm by hand rc 0 across six families.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at be816a8
The orientation rule is total and its refusals are propagated, which are the two things this kind of header gets wrong.
if (r0 == cols && c0 == rows && rows != cols) { … transposed … return 0; }
if (r0 > rows || c0 > cols) return -1;
… copy with zero padding where (r < r0 && c < c0) …Total: the two ways a declared shape can be wrong are [in, out] (read transposed) and bigger than the grid (refused); everything else is [out, in] with zero padding. And the branches cannot both fire -- r0 == cols && c0 == rows together with r0 <= rows && c0 <= cols implies rows == cols, which the first branch excludes by name. That is why the square case is unambiguous as written rather than by luck: it falls to the copy, and the test asserts exactly that (o[1] == 1.0f && o[4] == 100.0f).
Propagated: dequant_any does return q4nx_bf16_matrix_into(...), and its callers check it -- if (dequant_any(...) != 0) { err_ = "embedding dtype " + dt + " but dequant failed"; return false; } and return raw && dequant_any(...) == 0. So a refused matrix is a load failure with a message, not a zeroed weight that quietly answers.
Five arms, one per branch: [out, in] copied, [in, out] transposed with each value at its own place, the padded grid, the square case, wider than the grid -> -1, short of bytes -> -1. The only branch without an arm is the null/rows <= 0 guard, which is fine.
And the pack-level evidence is the kind that catches a transposed read: "ssm_alpha_proj/ssm_beta_proj are BF16 [2048, 32], i.e. [in, out] … read transposed" is a shape claim checked against unsloth's Q8_0 GGUF of the same checkpoint (routed expert 0's gate 0.9958, shared gate projection 1.0…), not a similarity score. A transposed read would give a low cosine everywhere, not a wrong answer in one layer.
| host, before | ia -a. (-i |
| host, after | Thinking Process:\n1. ** |
| NPU known answer | Paris, finish=stop |
| NPU decode | 0.27 tok/s, ttft 71 s on a 23-token prompt |
The two rows that keep this honest: Paris on one request (not a benchmark asserting a rate), and decode reported at 0.27 tok/s / ttft 71 s rather than omitted -- a number that would embarrass a claim is the one worth printing.
The second fix is a name-table fix and it says so: shared_expert_gate.weight beside mlp.share_router.weight, read at both MoE sites. That is the FLM spelling problem in its usual form -- two names for one tensor -- and the hy_v3_sgate fixture pins the gate to the reference with the sigmoid.
Two notes, neither blocking: the header was extracted from #2314's dequant_any branch, so this PR is stacked on cl-flm/1786-embeddinggemma and both should land in that order; and bf16_matrix_test was added as a dependency of onebit so ci_targets sees it -- worth keeping in mind that onebit is now the target that pulls a test into the build, which is fine here but is the shape ci_targets_check was written about.
Not run: the device row (npu-hold 351 s, yours) and the host arms.
|
VERDICT: READY Two spelling bugs in one pack, and the method that found them is the reason I am comfortable with the fix. The orientation claim is validated against an independent reference, not argued from the pack. The test can fail in the way that matters. The refusal posture is the right one: "Anything else — wider than the grid, short of bytes — is refused (-1), never guessed." For a reader that has to infer orientation from a declared shape, guessing is how you get a tensor that is wrong and plausible; refusing is how you get a load error with a name attached. One thing to keep an eye on, not a blocker: the orientation rule keys off the declared shape being exactly Not verified by me: the NPU-side numbers ( |
|
VERDICT: READY at The method is the one that works: same weights, two encodings
Reading a small range out of the container and comparing it against the same checkpoint's GGUF is what makes a layout claim checkable without decoding a whole model: the GGUF is an independent encoding of identical weights, so a mismatch is a bug in one of the two readers rather than a property of the model. That is the same instrument #2063 used for q/k/v order and #2301 for the Q8 code order — and here it found two spellings, which is the sign of reading rather than guessing.
The proof is a known-answer prompt on the route that matters| NPU known answer | That is the right level of evidence for a family row — the served answer, on the device, with the finish reason stated. And "prefill is ~2 min per request on this route" is the honest caveat that makes one request acceptable: the cost is named rather than hidden, and a reader knows why there is one arm and not six. "Anything wider or short of bytes is refused" is the third piece: the reader refuses shapes it does not recognise rather than reading them at the nearest legal width — the refusal-by-name discipline (#2254, #2261, #2301) applied to a byte count, which is the only defence against a silently truncated read. Stacked correctly"Stacked on #2314: base is Two asks
|
…its shared-expert gate (refs #1786) Two spellings in FastFlowLM's Qwen3.6-35B-A3B pack made the reply noise: - ssm_alpha_proj / ssm_beta_proj are BF16 [2048, 32], [in, out] like the pack's [H][E] router. Against unsloth's Q8_0 GGUF of the checkpoint, transposed, corr 0.99999. The BF16 read moves to bf16_matrix.h: a declared shape that is exactly the transpose of the grid asked for is read that way, a square one as [out, in], anything else wider or short is refused. - the shared expert's sigmoid gate is `shared_expert_gate.weight`; the FLM name table had `mlp.share_router.weight`, so the gate was never read and the shared expert was added ungated in every layer. Either name is read now. Device row (npu-hold, amdxdna clean): "Paris", finish=stop; decode 0.27 tok/s. bf16_matrix_test pins both orientations; batch_check_state_hy_v3_sgate pins the gate (with the name missed it reads corr 0.989). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
be816a8 to
479acbf
Compare
|
#2314 merged, and GitHub retargeted this PR to ready for review at 479acbf |
|
Failed to review PR |
|
VERDICT: READY at The stale stack line
#2314 has merged. So this line is now describing a dependency that no longer exists, and the diff has the shape that goes with it: 18 files by What I measured insteadNo conflict, and no revert of the embedding work that landed with #2314 — so the branch is safe to merge as it stands, and the 18-file appearance is base staleness rather than content. That is the third time this week a two-line-change PR has looked like a dozen files, and the test merge is the only instrument that settles it. Two asks
Everything substantive stands from my previous reviewThe method (a small range read from the container against the same checkpoint's Q8_0 GGUF, so a mismatch is a reader bug), the two spellings ( |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 479acbf: READY
The PR is retargeted to main now that #2314 has merged (3230a4b). It is one commit, 479acbf7, 0 behind main.
- Own diff: the sorted
+/−lines of479acbf7against its merge base are identical to those ofbe816a86, where I posted READY. - Merged tree:
git merge-tree --write-tree origin/main 479acbfagainst main gives the 6 files, +183 −24. The −24 are this PR's own replacements: 14 at the two shared-gate load sites anddequant_any's BF16 branch, and 10 inbatch_check_state_ci.py. No line of main's is lost. - So my runs at
be816a8carry over:bf16_matrix_testok.- 9
batch_check_statearms ALL PASS, includinghy_v3_sgateat corr 1.0000000. embed_forward2/2.- The no-fallback mutant FAILs at corr 0.9889, and the no-transpose mutant FAILs twice.
- CI at this head: the
buildjob passed, 100% tests passed, 0 tests failed out of 287, andbf16_matrix(#100) andbatch_check_state_hy_v3_sgate(#266) ran and passed.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at the merge head e682158 -- the arms survived, and the square case now says the honest thing
Two checks for a merge-only head, and both pass:
- the eight arms are intact in
bf16_matrix_test(both orientations, the padded grid, the square case, and the refusals for wider-than-grid and short-of-bytes), so the conflict resolution did not quietly drop one; - and the square case now carries the sentence I asked for: "a square one cannot be told from its shape and is read as
[out, in]". That is the right framing -- it states that the branch rests on a convention rather than on evidence, which is exactly what was missing when the rule read as if shape decided it. A reader who meets a square BF16 matrix later now knows they are in unverified territory instead of assuming the rule handled it.
The measured anchor is unchanged: read transposed, ssm_alpha_proj/ssm_beta_proj match the unsloth Q8_0 GGUF of the same checkpoint at corr 0.99999, with the 32 value heads interleaved on the reference side.
Nothing further from me on this one; my previous review's only outstanding item is closed by that sentence.
|
Persistent review updated to latest commit e682158 |
Refs #1786 (the
Qwen3.6-35B-A3B-NPU2row). Stacked on #2314: base iscl-flm/1786-embeddinggemma, because this extends #2314's BF16 matrix reader. Retarget tomainonce #2314 merges.What was wrong. Two spellings in FastFlowLM's Qwen3.6-35B-A3B pack, both found against unsloth's Q8_0 GGUF of the same checkpoint. Only small ranges were read, through ggml's Q8_0 layout.
ssm_alpha_proj/ssm_beta_projare BF16[2048, 32], i.e.[in, out], like the pack's[H][E]router. Read transposed they match the GGUF at corr 0.99999 (the GGUF orders the 32 value heads interleaved). Before this PR the BF16 reader refused them and the forward stopped at load; read as[out, in]they would be garbage.shared_expert_gate.weight, beside the router. The FLM name table hadmlp.share_router.weight, so the gate was never read and the shared expert was added ungated in every layer. The reply was noise (ia -a. (-i).The other tensor kinds decode against the GGUF as they should: routed expert 0's gate 0.9958, the shared gate projection 1.0000.
ssm_outand the GDNqkvmatch per value head.Change.
npu/forward/include/bf16_matrix.h(new, header-only):q4nx_bf16_matrix_into()takes over npu: Embedding-Gemma-300M-NPU2 serves /v1/embeddings on the per-op forward (refs #1786) #2314's BF16 branch ofdequant_any().[out, in]is copied, padding zeroed.[out, in].q4nx_forward.cpp: the shared-expert gate is read asmlp.share_router.weightorshared_expert_gate.weight, at both MoE load sites.docs/npu.md: a paragraph in the FLM measured-rows section.Tests.
bf16_matrix_testcovers both orientations, the padded grid, the square case and the refusals. With the transposed branch removed, its two[in, out]checks fail.batch_check_state_hy_v3_sgateis the hy_v3 fixture with ashared_expert_gate.weight, against the hy_v3 reference with the gate applied: corr 1.0000000. With the new name not read, corr 0.989 → FAIL.bf16_matrix_testis added as a dependency ofonebitso it reaches CI's ctest (ci_targets).ctest -j 6without the device tests, 279/279. Every forward arm by hand rc 0 (qwen3, lfm2, lfm3, qwen2, qwen2vl, qwen2_untied, qwen3_5, hy_v3, hy_v3_sgate, gptoss), andembed_forwardrc 0.Measured on the pack (
1bit serve -m Qwen3.6-35B-A3B-NPU2 --device npu, npu-hold 351 s, amdxdna clean before and after, locally built kernel set):ia -a. (-iThinking Process:\n1. **Paris, finish=stop (one request; prefill is ~2 min per request on this route)🤖 Generated with Claude Code