bfp-quant: EXL3-style Hessian-aware offline quantizer → native bfp4_b (grid-snapped), + weight accuracy gate #62
Labels
No labels
human-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
bitpartner/tt-stack#62
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Tracking issue for the recommended path out of #61: take EXL3's quantization method (Hessian-aware LDLQ/GPTQ rounding, optional incoherence rotation), not its trellis bitstream, and emit weights that sit exactly on the native
bfp4_bgrid. The Tensix unpacker reads them for free, so there is no decode cost and no runtime or kernel change.Why
QWEN36_MLP_DOWN_BF4=1) has no accuracy record at all.goal3-down-proj-bf4-2026-09-05.jsonlsays "unverified".Phases (details and go/no-go in #61 "Integration plan")
bfp4_b/bfp8_bpacker (convert_u32_to_bfp), bit-verified against ttnn host APIs.tr(E H Eᵀ)/tr(W H Wᵀ)per linear group for:ref.py)tensor_cache_*suffix: a stale cache silently serves old weights.nix run .#bfp-quant.bench/ab/sweep-32k.shplus a prod-weight-mix control arm, interleaved.MatmulCustomCompressed(E3). E3 is blocked on upstream #42841/#42586.Rules
bench/runs/*.jsonl./tmp/ttlock, withMemAvailable≥4 GB checked before every boot.Effort (ESTIMATE): P0–P3 ≈ 5–8 engineer-weeks, 8–10 card holds.
Refs: #61 (research), #60.
P0 progress (branch
bfp-quant, 3 commits, pushed, no PR). No card time used.Everything below ran on host
orchestra, which has no/dev/tenstorrent. The only access to cfx-llm2 was read-onlyscpof checkpoint shards.What was built (
tools/bfp-quant/)bfp.py: exact emulation of the tt-metal host packer for bfp8_b and bfp4_b (convert_u32_to_bfp,pack_as_bfp_tiles, and the unpackers). It providessnap_to_bfp4(w)andcheck_on_grid(fails closed).packer-harness/. The verbatim upstreamblockfloat_common.cpp,bfloat4.cppandbfloat8.cppat the flake-pinned rev76983d00, compiled standalone against a shim that replaces only logging, tracy,MetalContextandTile.ttnn.from_torch(device=None). Its host path constructsMetalContext, which constructstt::Cluster, which opens/dev/tenstorrenton a card host.nix build .#ttnncurrently fails eval with a narHash mismatch on the local tt-metal input.quant.py: RTN and GPTQ/LDLQ onto the bfp grid.bfp_quant.py quantizewrites grid-snapped bf16 safetensors for the tensors that--includeselects. RTN works without Hessians; GPTQ takes--hessians DIR. Untouched shards are symlinked.tensor_cachesuffix, to avoid the stale-cache trap.modules_to_not_convert/quantization_config.ignore.proxy_study.py: streamed, per-tensor, Hessian-free error map. It also has--remote host:/dirto copy one shard at a time.nix run .#bfp-quant -- {test,verify-packer,synthetic,proxy,quantize}(nix/bfp-quant.nix). Tested end to end.Verification (all MEASURED)
bench/runs/bfp-packer-emulation-20260924T093051Z.jsonl: 68 cases x {bfp8_b, bfp4_b}.bench/runs/bfp-packer-emulation-20260924T093819Z.jsonl: a snapped 9B gate/up checkpoint written byquantizeis a fixed point of the REAL packer. The unsnapped down_proj fails the same--expect-snappedgate, as it must.bench/runs/bfp-quant-synthetic-20260924T094254Z.jsonl: synthetic only. GPTQ beats RTN ontr(E H Eᵀ)/tr(W H Wᵀ)in every synthetic layer, by 16-23x.bench/runs/bfp-proxy-hfree-20260924T092920Z.jsonl: Qwen3.8-27B-FP8, layers 0-3, 30, 31, 62, 63 plus embed and lm_head. Hessian-free proxy (H = I), measured against the FP8 weights dequantised the way prod does.Compute findings and blockers
nixos(andorchestra, a container on it) has 47 GB RAM, 20 cores and an AMD 6900 XT (ROCm, no CUDA). It is the build/gaming host.nixos, which would load the shared host.Qwen/Qwen3.8-27B-FP8exists, on cfx-llm2 at/var/lib/models/hf/hub/models--Qwen--Qwen3.8-27B-FP8, snapshot017b9c7a. So bf4-from-bf16 vs bf4-from-FP8 is not measured.orchestrahas 20 GB of free disk and cannot stage 54 GB.-FP8) once gated access is granted. Nothing in the tool assumes Qwen paths or the name of the bf16-kept-modules key.ref.py(MIT) only reconstructs tensors from an existing EXL3 checkpoint. It needs an EXL3 4.0/3.0 bpw checkpoint of the same base, plus the bf16 reference, to be meaningful, so it is not done yet.Next step
Hper module as<module>.safetensors, whichquantize --hessiansalready reads.ref.py.P0 checkbox is left unticked: the emulation and quantizer parts are done, but the Hessian-weighted study is blocked on compute.
P0 result: calibration-Hessian proxy study on ThinkingCap (branch
bfp-quant,90ac45b)Calibration (MEASURED, cfx-llm0, 4x RTX 3080, owner-approved window).
<think>), both from Mixture-of-Thoughts.Qwen3_5DecoderLayerreference. 21.5 min for all 64 layers.cfx-llm0:/var/lib/models/calib/H-thinkingcap-bf16(118 GB, withmanifest.json).Downtime. vllm-pod was down 2026-09-24 10:52:24Z - 11:17:30Z (~25 min).
Study (MEASURED proxy,
bench/runs/bfp-quant-hessian-proxy-20260924T192517Z.jsonl).Go/no-go.
Findings that matter for the next step.
Next (starting now):
Runbook:
.claude/skills/bfp-quant/SKILL.md.Gate wording corrected (owner, 2026-09-24): the FP8 checkpoint is the reference, not bf16
The reference is now the native ThinkingCap-Qwen3.8-27B-FP8 checkpoint (e4m3, 128x128 blocks), dequantised the way production does it. We treat it as lossless.
What changes:
bench/runs/bfp-quant-hessian-proxy-20260924T192517Z.jsonl) is kept, but it is superseded as the headline. It is re-run against W8.tools/bfp-quant/calib/quantize_ckpt.pyruns GPTQ from W8 (dequant_fp8_block, exactly as prod does).P1 arms (each with its own TT tensor cache dir; same prod-argmax serve and sources as tc-baseline):
QWEN36_ATTN_GDN_BF4=1)quantization_config.ignore. Prod's lazy FP8 loader passes bf16 keys through untouched, so the model code does not change.tools/bfp-quant/calib/verify_arm.pypacks every per-device matrix through the real tt-metal packer. It passes only if all of them are bitwise fixed points.Battery per arm (
bench/bfpq/tt-arm.sh, detached, tt-card-slot lock, waits and does not preempt):Downtime so far today:
P1 status: GPTQ-from-FP8 bfp4_b checkpoint built, verified and on-card tested; FP8 teacher gate still PENDING
Branch
bfp-quant@e5e29ec. All numbers below are MEASURED unless marked otherwise.1. Checkpoint and verification
bench/runs/bfp-quant-arm-roundtrip-20260925T0030Z.jsonl.2. Proxy error vs FP8
Relative output error tr(dW H dWᵀ)/tr(W H Wᵀ), where W = dequantized FP8. Record:
bench/runs/bfp-quant-hessian-proxy-fp8ref-20260925T0030Z.jsonl.In-sample, all 64 layers (unit means):
Held-out, layers 3/31/63: GPTQ is fitted on half the calibration sequences and scored on the other half.
Notes on the held-out table:
3. On cards (cfx-llm2, prod-argmax, CTX 34816, SEQS 8, bf8 KV + device argmax)
Each arm has its own tensor cache. Every arm shows the same prod banners. The attn/GDN bf4 arms show
QWEN36_ATTN_GDN_BF4 engagedx64; the MLP-only arms show it x0.Speed (n=1 per cell). TG = decode ms/token. Makespan is for 1024 output tokens.
Full 16-cell table for all four arms:
bench/runs/bfp-quant-p1-20260925T0045Z.jsonl. Per-arm raw data:bench/runs/bfpq-p1-arms/.What the on-card results show:
4. FP8 teacher (KL/top-1 at deep positions, plus FP8 GSM8K/kv rows): PENDING
Three short cfx-llm0 windows failed, each on an environment difference between the job container and prod:
ninja, which the image lacks; prod hides this behind its JIT cache.Each restore was health-checked with a real completion. The fix is in
e5e29ec:The next run needs one continuous ≤45-min window.
Provisional go/no-go vs the FP8-referenced gates
P1 final: the FP8 teacher gate has landed. G2 (GPTQ-from-FP8, all bf4) is GO; R2 (RTN, all bf4) is NO-GO
Branch
bfp-quant@2b1504d. Record:bench/runs/bfp-quant-p1-20260925T0645Z.jsonl. All numbers are MEASURED.Teacher. Native FP8 ThinkingCap served with vLLM 0.27.1 on cfx-llm0: TP=4, bf16 KV, no prefix caching.
KL / top-1 vs FP8
Task accuracy as a delta vs FP8
Gates
The gate is non-inferiority against the deployed prod arm, with everything measured vs FP8.
Caveats
teacher_fp8.pyis resumable.Next: build a compact packed bfp4 checkpoint of G2 and publish it to the private HF repo (owner request).
G2 published as a compact bfp4 checkpoint (
bfp4-packed-v1): 20.56 GB, private HF repoBranch
bfp-quant@65ebb7d. All numbers MEASURED.Format. Each of the 496 snapped tensors is stored as its native tt-metal bfp4_b encoding:
[in, out/2]), plus one exponent byte per 16-block (uint8[in, out/16]), in the TT packer's own block order.weight_scale_inv= 896 keys.mtp.*,visual.*) is copied byte for byte.quantization_configis removed, since no FP8 tensor is left.Trap found by the packer's fail-closed check. GDN
in_proj_a/in_proj_bare not on the grid as standalone tensors.[a_d|b_d], 24 rows each), and GPTQ snapped them in that order, so a 16-block straddles a and b....in_proj_ab.weight, with its TP=2 split recorded. That is 448 packed entries for 496 tensors.Tools.
tools/bfp-quant/bfp4_pack.pyandbfp4_verify.py.tools/bfp-quant/bfp4_unpack.py: libraryunpack/unpack_entry, plus the CLIbfp4_unpack.py PACKED_DIR OUT_DIRfor non-TT users, which writes a plain bf16 checkpoint.bench/bfpq/sources/weight-mapping.py, the image'stt/weight_mapping.py(sha256 7e24c7dd…) plus the unpack path. It printsQWEN36_BFP4_PACKED engaged, andtt-arm.sh ARM_PACKED=1fails closed without it.Gate 1, round-trip: PASS (
bench/runs/bfp4-packed-roundtrip-20260925T072327Z.jsonl).Gate 2, TT boot from packed with a fresh cache: QUEUED, not run. The card lock has been held continuously by other agents (
mtp-g2-ab, thentt-quant:gptq), and I did not preempt them.bench/bfpq/packed-gate.shis queued on cfx-llm2 under the lock protocol (log:/var/lib/models/bfpq/arm-PK.nohup)./var/lib/models-sata, with a fresh cache). It passes only if every tensorbin it writes is sha256-identical to G2's cache (980 files, hashes already taken) and the 44 deep probes are token-identical to G2's.…/stage/PK/runs/*-PK/packed-gate.json.ttnn.as_tensor.Published:
donach/ThinkingCap-Qwen3.8-27B-bfp4-GPTQ-tt. The HF API confirms it is private, with 22 files totalling 20,582,480,358 bytes and every size matching the local dir. Record:bench/runs/bfp4-packed-hf-publish-20260925T075700Z.jsonl..gitattributes, LICENSE, LICENSE-Apache-2.0-Qwen.txt, LICENSE-PolyForm-Small-Business-1.0.0.txt, NOTICE, README.md, bfp4_packed.json, bfp4_unpack.py, chat_template.jinja, config.json, generation_config.json, merges.txt, model-0000{1..4}-of-00004.safetensors (7.14/5.59/4.44/3.39 GB), model.safetensors.index.json, preprocessor_config.json, tokenizer.json, tokenizer_config.json, video_preprocessor_config.json, vocab.json.No new speed numbers. The packed checkpoint gives the device bit-identical weights, so G2's measured PP/TG apply once gate 2 confirms it.
"MLP bf4 retired" (old ledger 0aaa) and "attn/GDN stay 8-bit" are reopened. Neither was measured for accuracy, and the owner said "keep BF4" on 09-15. Your P1 result is now the record: G2 is non-inferior against the FP8 teacher (
bfp-quant:bench/runs/bfp-quant-p1-20260925T0645Z.jsonl). lm_head bf4 is still untested.Reopened under the new rule (owner, 2026-09-25): a closure needs an on-hardware record at the stated scope; estimates, code-reads and third-party numbers close nothing. See docs/LEDGER.md "Reopened" (PR #68).
Packed G2: on-card gate PASSED
bench/runs/bfp4-packed-tt-boot-gate-20260925T110520Z.jsonl(MEASURED, cfx-llm2, 2x P150a, TP=2, prod-argmax).Setup: cold boot from the
bfp4-packed-v1checkpoint (20.56 GB), using a freshTT_CACHE_PATHand the bfpq loader hook. The cold build took 1271 s.Results:
QWEN36_BFP4_PACKED engaged, andQWEN36_ATTN_GDN_BF4 engagedx64.So the packed checkpoint is bit-identical on the device to the one that passed P1. All G2 accuracy and speed numbers carry over. There are no new speed numbers.
Earlier attempt: it died when another agent filled
/var/lib/models-sata, after 849/849 identical files. That attempt is recorded as...-try1.This run: it completed at 11:05Z, before today's host reboot, so nothing needed re-queueing. Future bfpq TP=2 runs go through the ttx mesh slot (
bench/bfpq/mesh-slot-run.sh).HF: the model card on
donach/ThinkingCap-Qwen3.8-27B-bfp4-GPTQ-ttnow states this result. Re-verified via API: private, 22 files, sizes match.Branch
bfp-quant@ HEAD.