Repository navigation
npu: load a DFlash draft package as a model source — config + container to a checked, decodable model (Refs #1780) - #2048
Conversation
…r -> a checked, decodable model (Refs #1780) npu/dflash_draft.{h,cpp}: reads the DFlash config.json, opens the Q4NX container with the engine's reader, checks every tensor against the config's geometry, and hands out views that decode one tensor at a time through the forward's q4_1 tile decoder. Refuses by name: a missing or mis-sized tensor, an unexpected dtype label, a scalar that disagrees with the config, an unexplained tensor, DFlash2, a weight path outside the package. The fusion matrix is stored as [hidden/32][taps*hidden][32], not row-major; measured on the Bolt draft and asserted with a control. tests/dflash_draft_test.cpp: synthetic package (known answers at every element, 16 refusals), plus an optional arm on a real package. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at 25c6400 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 25c6400 — Loading a DFlash draft package as a model source (+275 loader, docs +22): the gate #1957 added is only useful if a legitimate draft can actually be loaded and inspected, so this is the positive path that makes the refusal meaningful.
|
VERDICT: READY at The contradiction I chased: the body says it "refuses a config that is not a What is right, and why it is worth naming: One gap I do not think is closed:
Two things to state in the body, both for the next reader: that the test is registered with |
|
VERDICT: APPROVE at 111 checks pass on the real container, and every number the loader reports agrees with what I measured independently on the same file (#1780): 64 tensors, 257.9 MB, 5 layers, hidden 2560, IM 5120, 32/8 heads × 128, What the loader does right, in the order I care about
On the registration
For the record: the pack stays on disk at |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 25c64005 — and the two measurements in the body are worth more than the loader they support, because both are the kind of claim that usually gets asserted from a shape rather than checked against the weights.
1. "model.fc.weight is not row-major" is settled by a control, not by a belief. The declared shape [80, 12800, 32] is the element order, and the evidence is a neighbour-correlation table with the alternative reading run as the control:
as decoded: next row 0.1073, 32 rows on 0.1059 | next column 0.0217, 32 columns on 0.0207
read plainly: next row 0.0194, 32 rows on 0.1067 | next column 0.1050, 32 columns on 0.0235
Decoded in the declared order the two column figures agree and the two row figures agree; read plainly they contradict each other. That is exactly how a layout claim should be made when there is no reference output yet — and being explicit that "this is evidence from the weights' statistics, not from a forward: the first forward that reproduces a reference output is the stronger check, and none exists yet" is the sentence that keeps the claim honest.
2. "A norm gain can be negative" is the best kind of test failure. A first version asserting "positive" failed on three post-attention norms holding one gain just below zero (−0.0009 to −0.0024 of 2,560), and the assertion became "finite and bounded". That is a test that discovered a property of the data rather than encoding its author's assumption — and the observation that read as half-float the same element would be ≈ −0.8 among 1.3–2.4 values is an independent point for the bf16 reading #1957 settled. Two different routes to the same conclusion is what makes a dtype claim stick.
The loader's shape is right for a first slice: read the config and refuse a non-DFlashDraftModel; check every tensor against the geometry the config gives (tile grid per projection, 2 B per element for norms and the fusion matrix, container scalars equal to the config's, no tensor left unexplained); return views into the mapped file and decode one tensor at a time through the existing tile decoder. "No tensor unexplained" is the check that catches a container that grew a component; decoding lazily is what keeps a 270 MB package from becoming a second copy of itself in RAM — the same principle as #1784's embedding tables.
Two notes:
not wired into forward-serveis stated plainly, which is right; when it is wired, the acceptance arithmetic belongs on #1780 and not here, and the first forward that reproduces a reference output is the gate I would want named on that PR.- The neighbour-correlation control deserves to be a test arm, not only a body table. If the arm compares the two readings' agreement pattern (declared: rows agree, columns agree; plain: they disagree) rather than the specific numbers, it keeps working when the package changes and still fails if the layout reading flips.
Refs #1780 — this closes item 1 ("the draft container imports and loads on our runtime") and I would say so on the issue, since it currently has no fix PR attached and this is one.
|
Preparing review... |
|
VERDICT: READY at 689f6ba The scope statement is the one I would ask for on an issue with an owner and a sibling PR: item 1 only ("the draft container imports and loads on our The loader's checks are the right ones for a container that must be trusted before any of its numbers are: config must be a The two measured findings are why this PR is worth reading rather than just merging. Both were found by the test failing on the real package:
The PR also says clearly what it does not have: "the first forward that reproduces a reference output is the stronger check, and none exists yet" — Head under review: 689f6ba |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 689f6ba: READY
The new head is a merge of main, and the PR's own diff is identical to 25c6400 (verdicted READY). A three-way merge onto current main is clean. I ran it at this head:
dflash_draft_testwith no package: "ok (53 checks)", with the real-package arm skipped by name.- On the real package (
~/scratch-packs/f2/bolt-dflash, which hasconfig.json,configuration.json,dflash_weight.q4nxand aMANIFEST.sha256):
real package: 64 tensors, 270406816 bytes; 5 layers, hidden 2560, intermediate 5120, 32 heads / 8 kv x 128, block 8, taps 5 11 17 23 35
real package: 327680000 projection weights decoded
fusion matrix, as decoded: next row 0.1073, 32 rows on 0.1059 | next column 0.0217, 32 columns on 0.0207
fusion matrix, read plainly: next row 0.0194, 32 rows on 0.1067 | next column 0.1050, 32 columns on 0.0235
dflash_draft: ok (111 checks)
The tensor count and byte total match the description. The two fusion-matrix lines are the evidence for its first measured claim, that model.fc.weight's declared [80, 12800, 32] is the block-column element order and not row-major. Read through the decoder, a weight correlates with the next row (0.107) and not the next column (0.022). Read plainly, the relationship flips (next row 0.019, next column 0.105), which is what a transposed read of a row-structured matrix looks like. So the loader decodes the matrix in its real orientation, and a plain read would have had the axes swapped.
The /tmp-free fixture handling and the "views into the mapped file, decode one tensor at a time" design are right for a 270 MB package that nothing feeds yet.
Refs #1780 (W8-F2, drafts half). A scoped slice: item 1 of the issue's "supported" list, "the draft container imports and loads on our runtime". It follows #1957 (the Python gate, merged) and does not overlap pi-docs' acceptance arithmetic. The issue stays open; what remains is listed at the end.
What it adds
npu/dflash_draft.{h,cpp},onebit::npu::dflash::load_draft(dir):config.json(geometry, block size, mask token, target layers) and refuses a config that is not aDFlashDraftModel;decode_projection(),decode_gain()anddecode_fusion()produce one tensor at a time through the tile decoder the forward already uses.It is not wired into
forward-serve: there is no draft forward for it to feed yet.Two things measured on the real package that the loader depends on
Package:
Nemotron-3.5-Bolt-E4B-DFlash-NPU2on this box (64 tensors, 270,406,816 bytes).1.
model.fc.weightis not row-major. Its declared shape[80, 12800, 32]is the element order (32-row blocks, column by column, the row inside the block last). Correlation of log-magnitude between neighbours, from the test's own output:Decoded in the declared order, the two column-sharing figures agree and the two row-sharing figures agree. Read as plain row-major they contradict each other. The test asserts the first and uses the second as its control. This is evidence from the weights' statistics, not from a forward: the first forward that reproduces a reference output is the stronger check, and none exists yet.
2. A norm gain can be negative. Three post-attention norms each hold one gain just below zero (-0.0009 to -0.0024) of 2,560. My first version of the test asserted "positive" and failed on exactly these; the assertion is now "finite and bounded". Read as half-float the same element would be about -0.8 among values of 1.3 to 2.4, one more point for the bf16 reading #1957 settled on.
Proof
The synthetic half encodes every projection tile by tile from a formula and compares every decoded element, checks the fusion matrix comes back row-major, and matches sixteen refusals on their messages (missing tensor, too few tiles, a tensor of another architecture, a wrong dtype label, a plain-shaped fusion matrix, scalar disagreements, DFlash2, a missing key, target layers out of order, a weight path leaving the package, a non-tile geometry, a truncated file, an empty directory).
Mutants, built from a copy of the source (tree untouched):
tools/copyright.py --checkrc 0;ci_targets_check.py --check: "9 target(s) … (baseline 9), and none new" (the test is a build dependency ofonebit, so CI builds it);tests_referenced_check.pyclean. Docs:docs/amd-drafters.md, a loader subsection under the existing DFlash section.Limits
What remains on #1780
fc, five non-causal layers over a masked block, scoring with the target's embedding and head);Nemotron-3.5-Bolt-E4B-NPU2package on the box;Qwen3.8-27B-DFlash2-NPU2package: refused by name here, by design;🤖 Generated with Claude Code