Repository navigation
prepare, levers: a checkpoint's BF16 matrices stay BF16 by default; their Q8_0 rewrite behind BLOOMERY_PREPARE_Q8 - #607
Conversation
There was a problem hiding this comment.
Verdict: CLEAN: No behavior or quality defect survived the format-policy and reference-selection checks.
Checked
Duplicates: 7 new items searched; no duplicate owner found. Dryer reported 0 pairs; gpu-gates was checked by Grep.
Complexity: plan_tensors is new at 20: placement/mapping, then declarations and format refusals. Gate run rose 19→20 through format reporting. Both are outside the inference hot path.
Silent failure: 4 filter/default sites followed to their consumers; none discards an error. Unused tensors and tensors loaded as widened values are intentionally excluded from dense-form selection.
Tests: Static mutation checks: forcing Q8_0 or dropping the single-expert/F32 refusals at crates/model-hf/src/bin/prepare.rs:271 breaks the format assertions. Swapping reference pairs or dropping the mixed check at crates/gpu-gates/src/bin/gate_qwen4exp_nvfp4.rs:348 breaks the selection assertions. Copying F32 bytes through the Q8_0 branch at crates/hf/src/modelopt.rs:615 breaks the byte assertions. Inverting prepare_q8 at crates/levers/src/lib.rs:1190 breaks accessor assertions; restoring the old message at crates/levers/src/lib.rs:2082 breaks the display assertion. Removing prepare's size check (line 95) or conflict check (line 89) breaks the corresponding error assertions. Overwriting an existing draft refusal in paged_draft breaks its preservation assertion.
Gates: No threshold or pin moved. Both existing oracle pairs retain their checks; the PR includes FAIL-first output.
Adversarial: NaN reaches the finite-value rejection; one-expert stacks retain rank three and are refused before writing; unused next-token tensors cannot change the selected sets. Additional clean check: Q8_0 indexer projections are rejected by the loader's F32/BF16 shape check.
Escape catalog: C1–C4 have no matching changed pattern.
Notes
Tests were inspected, not executed in this review. The PR supplies GB10 gate output from an earlier revision and reports byte-identical regenerated shards at head.
Card check: gpu-evidence. This pull request changes crates/gpu-gates/src/bin/gate_qwen4exp_nvfp4.rs and carries a gate run on the author's card, which counts as verified.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
|
The review is done and the contribution check passed, so I'm merging this. |
기본
prepare가 체크포인트의 BF16 가중치를 Q8_0으로 바꾸지 않고 저장된 형식 그대로 쓰게 하고, Q8_0 재작성은BLOOMERY_PREPARE_Q8레버로 남깁니다.Part of #565.
What and why
preparenow writes a BF16 matrix of a checkpoint as BF16. NVFP4, MXFP8 and FP8 tensors are kept as stored, as before. Until nowpreparerewrote the matrices that the architecture map marks as Q8_0 unless--keep-bf16was given. The rewrite is now opt-in through the leverBLOOMERY_PREPARE_Q8=1.The oracle holds separate reference sets for the BF16 and the Q8_0 dense form, so the form a split holds decides which set the NVFP4 gate must judge it by. The BF16 dense path on the card already exists. This PR makes it the default output of
prepareand makes the gate judge a split by the dense form it holds.The converter has no support for a BF16 or F32 routed expert stack. Both modes (default and
BLOOMERY_PREPARE_Q8=1) refuse such a stack before any shard is written, and the error names the HF and GGUF tensor names and the stored format. An F32 dense matrix with the Q8_0 role is refused in the default mode with its tensor names andBLOOMERY_PREPARE_Q8=1, because the card program reads a dense projection only as Q8_0 or BF16. With the lever set, the matrix is written as Q8_0.Q8_0 is not the same numerically as the stored BF16. In the CPU oracle forward, rounding the BF16 projections to Q8_0 moves the last position by KL 4.288e-02 on the batch set and by KL 4.181e-02 at position 3000 of the D3K set. The lever's help text names these figures.
crates/model-hf/src/bin/prepare.rs:plan_tensorsbuilds the plan, the map and the declarations in one place. A stored BF16 matrix whose map role is Q8_0 keeps its format unless the lever is set. The refusals above happen here.--keep-bf16is still accepted; alone it equals the default, and next to the lever it is refused by name.crates/levers/src/registry.rs,crates/levers/src/lib.rs: theBLOOMERY_PREPARE_Q8row (Class M, flag, default 0, parsed, load) and theprepare_q8()accessor. TheDraft38Off::Borrowedtext now says "not a form the MTP draft reads", which matches whatmtp_borrowsaccepts (embedding Q8_0 or BF16, head Q8_0, Q6_K or BF16).crates/gpu-gates/src/bin/gate_qwen4exp_nvfp4.rs:dense_ofreads the type ofoutput.weightand picks the judged oracle sets. A BF16 head is judged by the_bf16pair, a Q8_0 head by the Q8_0 pair, whoever wrote the split. Tensors of the next-token layer that the load does not use are left out of that judgement. A split that mixes BF16 and Q8_0 dense tensors, or has a head of another type or none, is refused by tensor name. The indexer q and k projections are exempt from the mixed check, because the oracle holds them BF16 beside either form andpreparekeeps them BF16. This only selects a reference set: the model loader refuses Q8_0 indexer projections, so a split with them does not load. The judged-set line says when the indexer includes Q8_0. The gate takes its model path from the existingBLOOMERY_REF_MODEL, resolved once for the dimensions and the load.justfile:gate-gpu-qwen4exp-nvfp4keeps the same defaultBLOOMERY_REF_MODELas before this PR and runs the bin's unit test first, likegate-gpu-bf16. A split written by the new defaultprepareis selected by settingBLOOMERY_REF_MODEL.crates/model-hf/Cargo.toml,Cargo.lock: a dependency onbloomery-levers. Two doc comments incrates/refset/src/arch/qwen4exp/nvfp4.rsandcrates/model/src/arch/qwen35moe/mtp.rsfollow the new default.Release notes: Added:
preparekeeps a checkpoint's BF16 matrices in their stored format by default; the Q8_0 rewrite is available throughBLOOMERY_PREPARE_Q8=1, and the NVFP4 gate judges a split by its dense form.Code shape
model-hfarch map, so the lever and--keep-bf16act on the same list. The choice of judged sets is one function,dense_of, and the gate reuses the existing model path override and the load's own tensor roles.plan_tensors. The map's roles are unchanged.dimsandrunin the gate moved intodense_of.--keep-bf16next to the lever, an F32 matrix in the default mode, a BF16 or F32 routed stack in either mode, a split without a head, a head of a third type and a mixed split are each refused by name.cargo fmtis applied to the changed files.Duplication found or removed: none.
What I ran
The PR head is
86fb17366bc9a90181aa5ddee8a214a89193aeaeon upstream main0f7b51c6ebd2214b6393c841d2ae108354984de7. The unit tests and the gate ran on an earlier revision of this branch, which sat on main938167b2997b48b60593c27956efd36b352a8fad. Since then the diff changed only in documentation comments (the gate module anddense_ofdocs, one paragraph inprepare.rs, the help sentence ofBLOOMERY_PREPARE_Q8) and a test fixture inprepare.rs, and the branch was rebased onto main with its #603 (serve), #604 (tools/refscripts) and #606 (the fixture budget check). None of these touches anything the gate builds or reads. With thepreparesource of the PR head,prepareon the same checkpoint writes the four shards of the split the gate judged byte for byte (see the check below), so the gate result carries to this head.Unit tests of
prepare. The test covers the BF16 default, the refusal of F32 and BF16 expert stacks in both modes (including a stack of one expert), the F32 matrix in the default mode and the Q8_0 opt-in:The gate's test of the judged sets, including the unused next-token layer tensors and the mixed split:
Each of these checks was first run against the code with its defect put back, and it caught the defect. A stack of one expert was let through in both modes when the stack test counted source tensors instead of the tensor's rank:
With the default forced to Q8_0, the BF16 matrix came out as Q8_0:
With the mixed-format refusal deleted from
dense_of, the gate's test took a mixed split as Q8_0:GPU evidence
Card, from
nvidia-smion the DGX Spark that ran the gates:The gate command, run once per split.
BLOOMERY_REF_MODELwas set to the first shard of each split, so the shown default path applies to the second run only; no source file was edited between the runs.On the split with BF16 dense parts as stored, the gate judged by the BF16 reference sets:
On the Q8_0 split, the gate judged by the Q8_0 sets:
Comparison against the reference sets, taken from the oracle dump of the checkpoint:
just gate-gpu-qwen4exp-nvfp4on the splitpreparenow writes (BF16 dense parts as stored) against the BF16 reference setsThe split prepare writes by default (BF16 dense parts kept BF16) passes the real Qwen3.8 NVFP4 gate against its own BF16 reference sets; a Q8_0 split written by the earlier prepare passes against the Q8_0 sets.
At this head,
prepareon checkpointfc694b54wrote four shards, and each matches theSHA256SUMSof the stored-format split the gate judged:To reproduce, from the repository root with CUDA configured:
This PR changes no kernel and makes no speed claim. BF16 dense reads more bytes per token than Q8_0, so decode is expected to be slower than on a Q8_0 split.
What I ran into
The following does not block this PR. The gate's selection test is a bin test, so it runs only where
cargo oxideis available. The "Q8_0 split from another converter" case, the mixed split and the unused next-token layer case in that test are synthetic lists of names, types and roles, not a real published file. The F32 and BF16 stack refusals are tested with synthetic tensor headers, not with a published checkpoint that stores them. The Q8_0 split in the second gate run was written by the previous defaultprepare, not by the new lever, so the run does not show the lever's output byte for byte. The levers unit tests were not rerun at this head; the registry change since their last run is a help sentence. The comments oftools/ref/qwen4exp-nvfp4-oracle.pystill describe the BF16 projections as the onespreparewrites as Q8_0. The writer is pinned by its md5 together with the sets it wrote, so a comment edit there needs the sets written again; the refset module doc (crates/refset/src/arch/qwen4exp/nvfp4.rs) states the current default.Hardware
A DGX Spark (GB10) for the gates. The
nvidia-smilines of the card are under GPU evidence. A separate CPU host ran theprepareunit tests.AI help
AI 코딩 에이전트가 코드를 작성한 후 설명을 썼고, 테스트를 마쳤습니다. diff 리뷰와 PR 초안 리뷰는 제가 직접 했습니다.