goal 4: Qwen3.8-27B on ONE Blackhole card, from the QUASAR NVFP4 checkpoint #15
No reviewers
Labels
No labels
human-approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
bitpartner/tt-stack!15
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/claude-auth/goal-4-nvfp4-single-card"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Docs and scaffolding for the new operator goal (2026-09-05): get a smaller quant running natively on a single card, using QUASAR NVFP4.
Stacked on #14 (goals 2-3). No behaviour change on any existing profile.
What is in here
docs/GOAL-NVFP4-SINGLE-CARD.md— the goal: what "natively" means on both axes, four gates (G4.1-G4.4), the measured budget, what fits, expected decode speed, the work in dependency order, and the ways it fails.goal4profile inscripts/vllm-tt-serve.sh, committed non-functional on purpose — the unpacker and the coverage gate do not exist yet, but the mesh syntax needs recording.The budget, measured
Live device weights sum to 25.853 GiB against 30.87 GiB usable per card — 5.02 GiB left, which will not serve long context. QUASAR's coverage takes the four bf8 groups it quantised down to bf4, saving 3.374 GiB → 22.479 GiB, freeing 8.39 GiB.
Against 34,816 B/token of KV (16 full-attention layers; the 48 GDN layers hold a fixed-size state), one 262,144 session needs 8.50 GiB — 1.1 GiB short until
tok_embeddingsalso drops to bf8. That lever is held in reserve, not assumed. Goal 2 missed its first attempt at this margin by 0.17 GiB.Corrections
Linear. Its configignoreslm_head. All three sources — unsloth, QUASAR, and tt-metal's own policy — independently keeplm_headat 8 bits. Settled; should not cost an experiment.FP4-PRIORITY.md's ~29 tok/s ceiling used 27.49 GiB, which counts prefill-onlygate_upand the embedding table. The decode read is 20.17 GiB. More to the point, goal 3 showed the ceiling is not the binding constraint: ~80% of per-token time is fixed cost, anddown_proj→ bf4 bought +3.1% where that section predicted +15%. The prize here is footprint — which is exactly what makes one card possible — not speed.Two things worth flagging
This goal incidentally runs the TP=1 diagnostic goal 3 asked for. If single-stream decode comes in much faster than the predicted 10-14 tok/s, the fixed cost was the TP=2 all-reduce, not the GDN chain.
The bf8 accuracy baseline is still uncaptured and is a prerequisite of G4.4. It has been outstanding since goal 3 and costs one two-card run.
Tier
T0 — documentation and a non-functional serve profile.
@hermes review please.
Steps A, B, B2 and C are done — and the plan was missing a step
Progress since the PR opened. All evidence in
bench/runs/goal4-nvfp4-unpack-pcc-2026-09-05.jsonl.A — checkpoint complete
20.56 GB, all 5 shards, verified against the index. 7.5 GB of stale
.incompleteblobs from the interrupted first attempt cleaned up.C — the format is determined, not assumed
uint8, low nibble firstfloat8_e4m3, group_size 16, along the input dimfloat32, and it dividesNibble order is settled by measurement: high-first scores PCC −0.0004 against the FP8 reference where low-first scores +0.9955. Nine projections across MLP, GDN
linear_attnand fullself_attn, first and last layers: PCC 0.994–0.996, norm ratios 0.984–0.998. That residual is the genuine 4-bit-QAT vs 8-bit-PTQ difference, not unpacker error.scripts/nvfp4-unpack-check.pygates on two things, and the second is the important one. PCC catches anything that scrambles which value goes where. It cannot catch the global-scale direction — that value is a scalar, so inverting it leaves PCC at ~1.0 with every weight wrong by a constant factor. The norm ratio is the only thing standing between that error and the cards.B — patch 0009, and the result that matters is not "it loads"
It loads into the same shape: 851 internal keys on both paths, zero unique to either, zero shape mismatches, zero dtype mismatches. Everything downstream of
load_state_dictis therefore unchanged — the checkpoint format stops mattering at that boundary. Detection is mutually exclusive both ways.Laziness holds, which was most at risk: peak RSS 0.23 GB after indexing 2,687 tensors, 2.20 GB after reading five — and that 2.20 is the two 248320×5120 bf16 tensors materialising on demand, not the projections.
B2 — patch 0010, the step the plan did not have
layer.pybranches onnum_devices > 1, so a(1,1)mesh takes the non-TP modules — and those raise "DeltaNet layer requires the combined qkv_proj weight", because both lazy loaders deliberately keep GDN raw for the TP loaders to shard.Goals 1–3 could not have found this. It would have surfaced as a crash after a multi-hour cold cache build.
as_single_device()supplies the remapped layout without giving up laziness: 851 keys → 947, the conv split reassembles to the original tensor exactly, spec shapes match actual reads.The generalisation is worth more than the fix: every patch this repo carries (0001–0003, 0007, 0008) targets the TP path. The single-device path is essentially unexercised here and more of it is likely to be wrong. Budgeted for, rather than treated as a surprise each time.
A correction I had to make mid-derivation
I first read
LINEAR_Q_DIM/LINEAR_K_DIM = 2048as hardcoded for the 9B and therefore wrong here. They are not — that islinear_num_key_heads × linear_key_head_dim, 16 × 128 in both models, with V taken as the remainder (6144 here vs the 9B's 4096). The genuinely stale constant there isFULL_ATTENTION_LAYERS = {3…31}, which is dead code.In flight
Single-card cold build running now on
vllm-tt:goal4— deliberately at today's dtype policy, not QUASAR's coverage yet, so that G4.1 tests the checkpoint and the single-device path with one variable rather than three. Weights are 25.85 GiB against 30.87 usable, so it fits without the bf4 work; step D then buys the context budget.The log already shows
linear_attn.qkv_proj.weightbeing tilized into a newtensor_cache_bfp8underP150/— both patches live on hardware.layer.py branches on num_devices > 1, so a (1,1) mesh takes the NON-TP modules, and those call load_gdn_weights, which raises "DeltaNet layer requires the combined qkv_proj weight". Both lazy loaders deliberately keep GDN linear_attn.* raw -- fused in_proj_qkv, fused conv1d -- because the TP loaders shard those themselves. Goals 1-3 ran entirely at TP=2 and could not have found this; it would have surfaced as a crash after a multi-hour cold cache build. as_single_device() supplies the remap_qwen36_state_dict layout without giving up laziness: in_proj_qkv renamed to qkv_proj, conv1d recorded as three slice specs that slice on read. 851 keys become 947 (+96 = 48 GDN layers x 2 extra conv keys), the NVFP4 and FP8 single-device key sets are identical, the split reassembles to the original tensor exactly, and the spec shapes match the actual reads -- which matters because the warm-cache marker trusts them. Peak RSS 1.60 GB. model_config passes single_device=(num_devices == 1), so two cards are untouched. I first read LINEAR_Q_DIM/LINEAR_K_DIM = 2048 as hardcoded for the 9B and therefore wrong here. They are not: that is linear_num_key_heads x linear_key_head_dim, 16 x 128 in both models, and V is taken as the remainder -- 6144 here against the 9B's 4096. The stale constant in that block is FULL_ATTENTION_LAYERS = {3..31}, and it is dead code.It runs. 27B on one Blackhole card, from the QUASAR NVFP4 checkpoint.
G4.1 met.
(1,1)mesh, one device, coherent output. Cold build 14 min, warm restart 8 min.G4.2 met, and the budget was 6.1 GiB pessimistic.
The single-device layout is structurally leaner, not merely differently named: no fused prefill
mlp.gate_up.swiglu(5.98 GiB at TP=2), no fusedqkvzab/wqkvduplication. So every KV number in §4 re-derives from 11.106 rather than 8.39 — at bf8 KV that is ~307k tokens, meaning a full 262,144 session fits with margin instead of being 1.1 GiB short, and thetok_embeddingslever held in reserve isn't needed.The TP=1 diagnostic answered — outside both branches it was designed around
GOAL-VLLM-SERVING.mdpredicted "≈14.9 unchanged ⇒ the GDN chain is the cost" or "substantially faster ⇒ the CCL is the cost". Neither: one card is 2.14× slower at essentially identical MBU.Decode scales near-linearly with card count at constant MBU; the TP=2 CCL costs ~7%. What goal 3 measured as ~80% "fixed cost" is fixed with respect to weight bytes, not with respect to hardware — it halves when you add a card. So it is per-card work (compute or per-op dispatch), not a serial latency floor and not DRAM bandwidth. MBU was the wrong instrument for it.
That converts step D from a hope into a prediction: if bf4 at QUASAR's coverage buys much more than its byte share, the cost is matmul fidelity — bf4 selects LoFi at 16 cycles/tile against bf8's HiFi2 at 32. Which is what
QUANTIZATION.mdargued and whatdown_projalone was too small a share to show.One blocker, worked around
QWEN_SDPA_BF8=1makes the cachebfloat8_b, but the non-TP attention module never casts the prefill input to it. The TP path does — which is why goal 2 ran 4×256k on bf8 KV and never saw this. It surfaced only after the full cold build and a successful decode warmup.KV_BF8now gates it, defaulting off on the goal4 profile at the cost of 2× KV memory. Deferred, not closed: fixing it doubles the context budget.That is the second instance of the pattern this PR flagged in advance — every patch here targets the TP path, so the single-device path keeps breaking in places two cards never reach. Predicted, budgeted for, and cheaper each time because of it.
Still open
Note this run used today's dtype policy, not QUASAR's coverage. The checkpoint is loaded natively; the device dtypes are still ours. Half of "natively" is done.
Closing: the content of this PR is merged on master, and its base
agent/claude-auth/goal-2-3is dead (archived as tagarchive/agent/claude-auth/goal-2-3). G4.1 and G4.2 were met: 19.76 GiB of weights on one card and 144 ms/token at TP=1 (bench/runs/goal4-single-card-nvfp4-2026-09-05.jsonl).The open 1-card goals move to #69: G4.3 context ladder, G4.4 accuracy, and Step D QUASAR bf4 coverage. The branch
agent/claude-auth/goal-4-nvfp4-single-cardis left in place because it carries one unmerged doc.agent-gateway closeout (closed): cleared
agent:*labels.Hermes kanban card (if any) is left for Hermes to archive/complete.
Pull request closed