Repository navigation
npu: the 4,608-byte symmetric chunk is read, so Hy-MT2-1.8B-NPU2 serves on the forward route - #2203
bong-water-water-bong wants to merge 3 commits into
Conversation
FastFlowLM's Hy-MT2 pack stores its projections in a tile no reader knew: 256 bf16 scales, then 4,096 code bytes in column order, value = two's complement nibble * scale. The size names the kind. Settled by the tie test against the pack's own bf16 embedding; the pack then answers on the per-op route with kernel shapes that already existed. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at 3d183af |
PR Reviewer Guide 🔍(Review updated until commit 0911320)Here are some key observations to aid the review process:
✅ Resolved findingsnpu/forward/include/q4nx_dequant.h:101Missing Q4S Chunk Definition The PR introduces a new chunk type |
|
VERDICT: READY A new packed tile is exactly where a decoder can be plausible and wrong, so I checked the arithmetic against the layout, both ways round. The tile accounts for itself to the byte. With Decoder and fixture agree on all four independent choices: byte The suggestion (non-blocking, and it is the only substantive one I have): The test is the strong part, and worth naming because three things make it a real oracle rather than a mirror: the expected values are computed from the description ( One small thing: the fixture's literal |
|
Head moved to The PR body says "if there is a remaining difference in how this family is modelled (its So I merged #1976's head ( What this means for this PR:
This PR does not depend on #1976 to be correct as far as it goes; the row depends on #1976 to be a parity row. They touch different files except ready for review at 0911320 |
|
Persistent review updated to latest commit 0911320 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 0911320: READY
It merges cleanly onto main.
The layout, re-derived independently. I wrote a numpy decoder from the header comment alone (not from the C) and ran the tie test on the pack on this box (~/.cache/scratch-cl-flm/Hy-MT2-1.8B-NPU2/model.q4nx): the first 1024 rows of lm_head.weight (I8 [30208, 4608]) against model.embed_tokens.weight (BF16 [120818, 2048]):
| codes | sign | corr | |d|/|s| mean | max |
|---|---|---|---|---|
| column-major | two's complement | +0.99611 | 0.244 | 1.000 |
| column-major | minus 8 | -0.50807 | 7.975 | 9.000 |
| column-major | unsigned | -0.39325 | 7.395 | 16.500 |
| row-major | two's / minus 8 / unsigned | <= 0.014 | 3.9 to 7.9 | 16 to 23 |
This is the body's table, to the digit, from a second implementation.
Tests, from an ONEBIT_NPU=ON ONEBIT_NPU_HOST=ON build at the head:
npu_dequant_bf16_test: all cases ok, including the 8q4sarms and the Q4S row-equals-whole arm.npu_design_grid_test: ok.- Mutants (one edit each to
q4nx_dequant.c, rebuilt, then restored; the tree is clean afterwards and the test passes again):
| mutant | result |
|---|---|
| even row reads the high nibble | rc 1, 2 FAIL |
scale index r*8 + g (row-major scales) |
rc 1, 3 FAIL |
4608 dropped from q4nx_tile_row_kind |
rc 1, 3 FAIL |
Code:
- 4,608 is a safe new size. Every reader that takes a chunk size accepts 4,608 in the same place it accepts 4,736 and 8,704.
dequant_anyinfers the size asnbytes / tiles, and a padded grid can only make that larger, so no 5,120, 4,736 or 8,704 container can come out as 4,608. - The fused int8 packer cannot misread these tiles.
q4nx_pack_q4_1_tiles_to_int8has no caller in the forward, so a 4,608 tensor reaches the device only through this tested dequant.
CI: build at 0911320 failed in Tools for checkout and the build at 15:31Z. That is the runner DNS outage, not this tree.
Non-blocking: the served comparison is 1 of 5 identical to FastFlowLM's wording, and all ten are correct translations. As the body says, that settles the reader, not the modelling (rope_scaling: dynamic is the open item). An independent reference (a GGUF or the HF checkpoint) is what would settle it.
|
VERDICT: READY at The 4,608-byte chunk is the fifth tile size this reader knows, and the arms prove it was added without disturbing the others — that second arm is the one I would have asked for by hand: a tile-sized buffer must still be read as a q4_1 grid, not silently re-interpreted as bf16 because the new branch matched first. Adding a size to a decoder whose branches are distinguished by length is exactly where a wrong-order Two properties of the fix worth naming as right: the pack needed no new kernels (its four shapes already existed for other packs, so the row closes on the reader alone), and the refusal paths are unchanged ( Two asks:
Scope of my check: fresh configure rc 0, both suites built in a scratch dir and run (96 bf16 arms + the importer's full set). No device: the pack serving on the per-op NPU route is the PR's own ticket. |
|
VERDICT: READY at The bug is the reader being narrower than the artifact, in the smallest possible formThree chunk constants exist in the tree, and the two predicates listed two of them: - if (cb == Q4NX_TILE_BYTES || cb == Q4NX_CHUNK_Q4K || cb == Q4NX_CHUNK_Q8)
+ if (cb == Q4NX_TILE_BYTES || cb == Q4NX_CHUNK_Q4K || cb == Q4NX_CHUNK_Q8 || cb == Q4NX_CHUNK_Q4S)The constant had a definition, a docstring line and a value since it was introduced; what it did not have was a mention in the two places that decide "is this byte count a chunk". So the pack was readable by nothing and the failure looked like a format problem, not a classification problem — exactly the class of defect that costs a day going the wrong way. After this, the predicate set is exhaustive — say soWith One helper instead of two listsBoth sites — And the test earns its 63 lines
|
|
Folded into #2218 at |
Refs #1786 (board row 17,
Hy-MT2-1.8B-NPU2, "not started" until today).FastFlowLM/Hy-MT2-1.8B-NPU2could not be read by anything in the engine: its projections are stored 4,608 bytes to a tile, and the readers know 5,120, 4,736 and 8,704. This adds that tile to the forward's decoder. Nothing else was needed: the pack then serves on the per-op NPU route with four kernel shapes that already existed for other packs.The tile
The size names the kind, so unlike the 5,120-byte symmetric tile (#2170) nothing is inferred from the data.
How the layout was settled: the pack ties its embeddings,
embed_tokensis bf16 andlm_headis its quantised copy, so each candidate reading was scored against the embedding over the first 1024 rows, in units of the scale.Mean 0.244 and maximum 1.000 is what a rounding quantiser leaves (0.25, with the +8 clip), and it is the same figure the LFM2 packs gave.
Before and after
On the first four tiles of the pack's layer-0
q_proj, main's decoder and this branch's:Served, on this box, 2026-11-11
Host float GEMMs (no device), a translation request:
Das Wetter ist heute schön. Wir werden zum Sp(12 tokens).One
npu-holdticket, FastFlowLM 1.0.12's runtime then ours, samemodel.q4nx(sha256a992683e04c2110e…both), five greedy translation requests:All ten are correct translations. Ours on the array is the wording the host float run gives (p0), so the device agrees with float arithmetic on these weights, as it did for LFM2. I have no reference for this model that is independent of both runtimes (no GGUF on the box), so I am not claiming whose wording is the model's, and the docs row says that. If there is a remaining difference in how this family is modelled (its
rope_scalingisdynamic), this evidence would not show it; that is the open item.A second ticket (42 s): the known-answer prompt three times,
Paristhree times, identical; decode 2.05 and 2.00 tok/s, first token 0.888 and 0.976 s on a 13-token prompt.Tests
npu_dequant_bf16_test:q4s_chunk(8 checks: every element against the layout indexed from its description, the three layouts it is not each shown to give a different table, the size told apart from 5,120, one byte short refused), and the existing row-equals-whole arm now runs on four kinds.ctest -j 6: 237 of 239 pass; the two that do not arenpu_rt_shimandnpu_dmabuf_import, which do not link in my build directory (they did not in npu: LFM2.5-1.2B answers on the forward route — conv layers run, the symmetric tile kind is read, q/k norm on #2170 either, and passed in its CI).tools/copyright.py --checkrc 0.Not in this PR
q4nx_packed_grid) does not try 4,608: adding a fourth size there changes which grids tie for existing padded containers, and this pack's grids are exact. A padded 4,608 container would be refused by name as today.npu/q4nx.cpp) and the importer do not know this tile.🤖 Generated with Claude Code