fmt=7: fp8-e4m3-b128 passthrough weight format — CPU + Metal + repack tool (#524)#528
fmt=7: fp8-e4m3-b128 passthrough weight format — CPU + Metal + repack tool (#524)#528monotophic wants to merge 12 commits into
Conversation
|
Working on the Windows / MinGW fail. Will update with a fix shortly. |
bf42a0f to
fdd41f0
Compare
|
Fixed and ready for review |
|
I have to block this one, and I'm sorry to do it on work this careful — but the refusal logic fires on a shape GLM-5.2 actually has, so merging it can stop the engine from loading real containers. The collision includes GLM-5.2's own
|
Maintainer review, JustVugg#528 (the load-bearing blocker): self_attn.o_proj.weight loads as [D,H*v_head] = [6144,16384] on GLM-5.2 -- nblkO=ceil(6144/128)=48, nblkI=ceil(16384/128)=128, product=6144==O, so a genuine, valid, pre-existing int8-row o_proj tensor satisfies BOTH the per-row (fmt=1) and per-128x128-block (fmt=7) scale-byte-count conventions at once. qt_resolve_fmt's prior behavior was to refuse this shape unconditionally (exit(1)) -- a real regression on an ordinary, already-shipping model, strictly worse than the misread it guarded against. Confirmed at shard-header level on this repo's own glm52_v1_e8x4g64 container: o_proj weight U8 100,663,296 B (==6144*16384), .qs scale blob 24,576 B (==6144*4==48*128*4, satisfying both formulas at once). colibri.c: qt_resolve_fmt's is_row&&is_blk branch no longer exits -- it falls through to fmt=1 (no explicit assignment needed: fmt is already 1 entering this branch, and the is_blk&&!is_row check further down evaluates false whenever is_row is true, per the comment left in its place). Sound specifically because the WRITER now refuses to ever emit an fmt=7 container at a shape satisfying this same predicate (see repack_fp8_passthrough.py below): an unstamped ambiguous tensor reaching this function is never a genuine fmt=7 candidate, so resolving to the incumbent format isn't a guess. The SECOND DESIGN LANDMINE (fmt=6 vs fmt=7 collision at I=98) and the UE8M0 recognized-not-implemented refusals are unrelated collision predicates and are unchanged -- still unconditional refusals. tools/repack_fp8_passthrough.py: _check_geometry() now also refuses to repack any [O,I] where nblkO*nblkI==O -- the exact shape the reader's collision predicate describes -- making the module docstring's existing promise ("never anything that could coincide with O*4 by accident") true rather than merely asserted. tools/fp8_collision_census.py (new): enumerates every resident-role tensor shape reachable from a container's config.json + safetensors headers (JSON header parse only, no tensor data, no model load) and evaluates the collision predicate over all of them -- run against this repo's own two GLM-5.2 containers (glm52_v1_e8x4g64, glm52_i4), the complete family is self_attn.o_proj at [6144,16384] (79 instances per container, one per layer including the MTP head layer) and nothing else among gate/up/down, q_a/q_b/kv_a/kv_b_proj, or routed/shared experts. tests/test_fp8_load.c: the three shapes the review named explicitly ([128,16384],[256,16384],[384,16384]) plus every other degenerate case in the same sweep (they all hit the identical is_row&&is_blk branch) flip from expect_refuse to expect_fmt(...,1,...); new explicit regression for the real o_proj shape [6144,16384]. tests/test_fp8_repack.py: new writer-refusal test for a well-formed-but-ambiguous [2,256] fixture (the smallest instance of the same family, distinct from the pre-existing malformed-shape refusal test). RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 153 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0.
fdd41f0 to
d1a6897
Compare
Maintainer review, JustVugg#528: qt_scale_bytes() already existed and had the right fmt=7 branch (per-128x128-block scale count), but neither qt_wire_mmap nor qt_unwire_mmap actually called it -- both independently hardcoded scale_b=(int64_t)t->O*4 (per-row) inline, so the function compiled but was never referenced by its intended call sites. It only compiled clean because -Wno-unused-function is in CFLAGS: the claimed fmt=4/fmt=5/fmt=7 expert-pinning fix was never applied, and an fmt=7 tensor reaching qt_wire_mmap would get weight_b overshooting the allocation whenever nblk>O (mlocking/munlocking the wrong byte ranges on both halves). colibri.c: new qt_wire_split(t,&weight_b,&scale_b) -- the ONE place both call sites now get their weight/scale byte split from, calling qt_scale_bytes() internally. qt_wire_mmap and qt_unwire_mmap both replace their inline scale_b/weight_b computation with a call to it. qt_scale_bytes() is consequently no longer an orphan: it is exercised both directly (existing qt_bytes/qt_scale_bytes arithmetic tests) and through the real call sites via qt_wire_split. tests/test_fp8_load.c: check_wire_split() calls qt_wire_split() directly (no mlock syscall -- mem_wire's actual RLIMIT_MEMLOCK behavior is environment-dependent and not what changed) across fmt=1 (unaffected, per-row), fmt=4/fmt=5 (grouped formats qt_scale_bytes' own comment already named as previously broken too), and fmt=7 at two nblk>O shapes -- each asserting the split matches qt_scale_bytes()/qt_bytes() AND that the fmt=7 cases have actually moved off the old O*4 constant, not just coincidentally matched it. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 153 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0. One-shot build with -Wno-unused-function removed from CFLAGS: qt_scale_bytes and qt_wire_split are both silent (no longer orphans); 8 unrelated, pre-existing orphan functions remain (st_init, st_prefetch, st_read_slice_f32, tier_pick_swap, stops_arm, cmp_fdesc, attention, and qt_unwire_mmap itself -- the last one only because this translation unit was built without -DCOLI_CUDA, its sole caller's #ifdef guard), all out of this PR's scope -- see the report.
Self-review while responding to the JustVugg#528 maintainer round: attention_rows' and layer_forward_rows' fmt!=7 guard comments claimed "coli_metal_attn_decode's WP_() macro and the mm_gemv shader it dispatches through (bind_gemv) have no fmt=7 case in this build" -- stale since this PR's own Metal kernel commit (96baf22, "Metal mm_gemv kernel for fmt=7 native FP8-e4m3 passthrough") added exactly that: mm_gemv's shader body has a real, working fmt==7 branch (fp8-e4m3 decode + per-128x128-block scale lookup, backend_metal.mm). The shader is not the gap. The actual reason these two fused-kernel paths still fail closed for fmt=7 is the WP_() macro immediately below each guard: `(q).fmt==1?(const void*)(q).q8:(const void*)(q).q4` picks q8 only for fmt==1 and q4 otherwise -- and q4 is NULL/unallocated for fmt=7 (same convention as fmt=1, see the QT struct comment), so an fmt=7 tensor reaching either fused path through WP_() would hand the kernel a NULL weight pointer, not a shader that misreads bytes as f32. Both guard comments now name the real blocker and note that fixing WP_() and threading fmt=7 through bind_gemv is a deferred follow-up (this PR's own GPU-path note), not attempted in this round. No code changes: the guards (fmt!=7 exclusions) and their behavior are unchanged, only the comments explaining WHY they exist. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make glm METAL=1 -> clean rebuild, zero warnings.
FIX ROUND, audit finding (SHOULD-FIX): qt_scale_bytes() had no explicit fmt=6 branch and fell back to the per-row O*4 formula. Verified at source before implementing (per instructions, not trusting the audit's own derivation): qt_from_disk's fmt==6 branch allocates t->s via qsalloc(1) -- ONE float, 4 bytes total, independent of O -- and its st_read_f32_cap call right below validates the SAME cardinality (`fmt==6 ? (int64_t)1 : ...`). qt_bytes()'s own fmt==6 branch already treats the scale as a fixed `+4` literal, never O*4. All three sites agree: fmt=6's scale allocation is a fixed 4 bytes, confirming the audit's claim rather than contradicting it. This function's own header comment used to ALSO claim fmt=6 tensors "never reach qt_wire_mmap/qt_unwire_mmap's mlock path in this build" because they "stay CPU-side" -- that conflated two unrelated mechanisms: qt_cuda_upload decides GPU-VRAM upload eligibility (fmt=5/6 genuinely excluded there, no CUDA kernel for either), while qt_wire_mmap/qt_unwire_mmap mlock HOST RAM pages under COLI_MMAP, an entirely CPU-side concern -- staying CPU-side is exactly what makes a tensor a wiring CANDIDATE, not exempt from it. expert_load_impl assigns fmt=6 to ESlot g/u/d exactly like fmt=4/5 (same qt_resolve_fmt call site), and pin_wire calls qt_wire_mmap/qt_unwire_mmap on every pinned ESlot's g/u/d unconditionally, with no format filter -- so a pinned E8/IQ3 expert under COLI_MMAP + mem_should_wire() does reach here. Before this fix, that path's O*4 fallback would return a scale_b wildly larger than the tensor's real 4-byte allocation, then mlock/munlock that many bytes starting at t->s -- past the end of a 4-byte allocation. This is a strict correction, not scope creep introduced by wiring the helper: pre-existing behavior at the old hardcoded call sites (scale_b=(int64_t)t->O*4, inline, before qt_wire_split existed) was identically wrong for fmt=6 already -- this closes the same gap the JustVugg#528 fix closed for fmt=4/5/7, for the one format that fix's own review round didn't reach. colibri.c: qt_scale_bytes() gains `if(t->fmt==6) return 4;`, and the header comment's stale reachability claim is corrected. tests/test_fp8_load.c: check_wire_split(6, ...) at a realistic expert shape (verifying the value moved off the old O*4 fallback) and at the O=1 degenerate shape (where O*4 coincidentally also equals 4, so only the new direct `want_scale==4` assertion -- not the generic "moved off O*4" guard -- catches a regression there; both are asserted). PROVEN TO BITE (RAN): removed the `if(t->fmt==6) return 4;` line only, rebuilt, ran the suite: FAIL tests/test_fp8_load.c:574: want_scale == 4 fp8 loader-seam tests: N FAILED Reverted (diffed back to byte-identical with the pre-mutation file) and reran clean: "fp8 loader-seam tests: ok", exit 0. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 155 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0. One-shot -Wno-unused-function-stripped build: same 8 pre-existing, unrelated orphans as before this round, no new ones.
FIX ROUND 2, item 3 (clean-room conformance trial finding, spec I6 -- loud failure, every refusal names its condition): a real (non-dry-run) run of tools/repack_fp8_passthrough.py whose --indir contains FP8 tensors but none matching a repack-target kind used to exit 0 having emitted nothing -- trial-verified with a non-resident-named FP8 tensor selecting silently. That is a silent trap: an empty "container" (just the resume/params sidecars, no actual .safetensors output) nobody asked for, with no caller-side check positioned to catch it. main() now refuses (nonzero exit, stderr naming --indir and the shard count checked) when every shard -- this run's AND any prior resumed run's, via the `done` progress dict, one entry per shard -- has NEVER produced an output. --dry-run is deliberately NOT covered: printing "0 tensor(s) selected" already IS the loud, honest answer dry-run exists to give, not a silent no-op, so turning it into a refusal would be wrong, not thorough (this judgment call implemented directly, per the coordinator's own "or escalate the disagreement" allowance -- no disagreement here, both readings agree once dry-run's actual purpose is considered). tests/test_fp8_repack.py: new ZeroTargetRefusalTest -- a fixture shard with FP8 tensors but none matching a repack-target kind (routed expert + f32 norm, mirroring the trial's own scenario) must refuse on a real run and still exit 0 on --dry-run. Also updates the module's kv_b_proj-exclusion comment (repack tool docstring), which the JustVugg#528+FIX-ROUND-2 qt_addrow/qt_matvec_rows fix made stale: it claimed an fmt=7 kv_b_proj tensor "would silently misread fp8 bytes as int2-packed data" -- true only for fmt=6; fmt=7 actually SIGSEGV'd (t->q4 is NULL for fmt=7). Both now refuse loudly at the absorb call site instead -- corrected to describe the actual (fixed) behavior, while keeping the underlying exclusion decision itself unchanged (refusing loudly there is still not the same as supporting fmt=7 for kv_b_proj). PROVEN TO BITE (RAN): removed the new zero-target check only, ran the new test -- `test_zero_targets_refuses_loudly` failed exactly as expected (returncode 0 instead of nonzero); `test_zero_targets_dry_run_unaffected` still passed (confirming the dry-run path was never touched by the mutation). Restored (diffed back to byte-identical with the pre-mutation file), reran: both new tests green. RAN (this commit): python3 -m unittest tests.test_fp8_repack -v -> 14/14 pass. make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 157 tests, 10 skipped, 0 failures, exit 0 (2 more than before this round: the new ZeroTargetRefusalTest cases).
|
Authored by Fable 5 in Claude Code, analysis in partnership with @monotophic You were right, and the original wasn't up to the standard we want our The revision (head
How it was verified — the part that changed most on our side:
This PR was revised against a written contract; every invariant below is a
Full evidence matrix (instruments and results per requirement)
Deeper artifacts (specific transcripts, census tables, suite design) Thanks again for the review feedback, this was a valuable reset. |
…tVugg#528 restack Maintainer review, JustVugg#529 (shared with JustVugg#528's fix): FORMATS.md cites specific c/colibri.c line numbers and branch/commit SHAs for every format row and for the metadata-stamp mechanism. Restacking fmt7/stamp-registry onto the revised fmt7/fp8-passthrough-rev1 (the JustVugg#528 INVERSION/qt_wire_split/stale-comment commits) shifted every colibri.c line number after qt_bytes(): qt_scale_bytes 216->223, qt_alloc 893->910, qt_resolve_fmt (and its SECOND DESIGN LANDMINE comment) 1144->1161, the FMT_NAMES table 1104->1121. Verified against the actual restacked file (grep for each cited function's definition line), not recomputed by offset arithmetic. The "Known formats" intro and "Sources for all rows" preamble also named the pre-restack branch/commit (fmt7/stamp-registry @ base 9a1690e) -- updated to fmt7/stamp-registry-rev1 @ base 696ce96, restacked on fmt7/fp8-passthrough-rev1 @ a77421d, with a note that these line numbers need re-verifying again if the branch is rebased further. No code changes, no test changes -- doc-only.
|
CI caught a Linux-only short-read in our test harness's stderr capture (the refusal fired; the test's read was truncated) — fixed, head now 9f6b4ab. |
Ports fmt6/fp8-passthrough-r2@150f15b (the CPU read path -- quant.h's E4M3_LUT/e4m3_decode/matmul_fp8, colibri.c's qt_bytes/qt_scale_bytes/ qt_from_disk/qt_resolve_fmt fmt=1-vs-fp8 disambiguation) onto dev 9baae9b, renumbered fmt=100 -> fmt=7 throughout (code, tests, comments): the maintainer assigned fmt=7 as this format's public ordinal on JustVugg#524, so this branch stops using the private-block number the format developed under. Mechanical rename per the PRIVATE ORDINAL BLOCK convention's own "find-and-replace, zero on-disk impact" promise -- qt_resolve_fmt still infers format from byte arithmetic alone; nothing on disk ever encoded the number 100 or the number 7. Two structural differences from the source commit, both scoped by the maintainer's JustVugg#524 reply (PR 1 = CPU+repack+collision/refusal+Metal; registry+stamp move to a separate PR): - qt_resolve_fmt has NO stamped_name parameter and never will in this branch -- its signature is the plain 6-argument form every call site already uses (name,O,I,nb,ns,gs). A self-describing container stamp that could resolve a genuine byte-collision instead of refusing it is a follow-up proposal, called out as such in this function's own header comment and inside THE DESIGN LANDMINE's comment, not implemented here. - fmt=7's scale ENCODING is documented as a declared property of the format, not a hardcoded constant (QT struct comment + qt_resolve_fmt's new "SCALE ENCODING IS A DECLARED PROPERTY" comment). f32 (4 bytes/ block) is what this build implements. UE8M0 (1 byte/block, power-of-two exponent) is a REAL, distinct encoding the same weight geometry ships with -- DeepSeek-V4, per the maintainer's JustVugg#524 finding -- recognized by its own byte signature (ns==ceil(O/128)*ceil(I/128)) inside the fmt=1-vs-fmt=7 landmine and refused BY NAME rather than silently misread as truncated/corrupt f32 or matched to the wrong candidate. Checked for collision against fmt=1's and fmt=7-f32's own ns arithmetic: realistically distinct for GLM-sized shapes, but not categorically -- the same small-O regime that already makes the f32 landmine possible also makes a ue8m0 collision possible (documented in-code, new test case, still refuses either way). tests/test_fp8_load.c's disambiguation suite (Part A) carries forward unchanged in substance (renumbered), plus a new Part A3 (test_ue8m0_scale_refusal) covering both the clean UE8M0 signature and the small-O ue8m0-vs-fmt=1 collision. Part C (check_fp8_bytes) carries forward: it exercises qt_bytes()/qt_scale_bytes(), CPU-read-path functionality added in this same commit, not the metadata stamp it was originally bundled with upstream in the source branch's history. Does NOT yet include: the fmt=6-vs-fmt=7 "SECOND DESIGN LANDMINE" collision reconciliation (source branch's final commit) -- qt_resolve_fmt here still has dev's unconditional fmt=6 early-return, same gap the source branch's first port commit deliberately left open. Follow-up commit on this branch closes it. Does NOT yet include the Metal kernel, the repack tool, or the end-to-end loader test -- following commits on this branch. FIX ROUND (Windows CI): test_fp8_load.c's expect_refuse() forked a child to catch qt_resolve_fmt's exit(1) refusals -- MinGW has no fork()/sys/wait.h. Gated <unistd.h>/<sys/wait.h> and expect_refuse's body behind #ifndef _WIN32, mirroring tests/test_st_pread.c's own established Windows arm for exactly this problem (fork-based exit(1) checks): the Windows arm prints an explicit "skipped on Windows (no fork): <tag>" line per skipped case (visible, never silent) and returns pass-through (1) rather than failing the build; every non-refusing case (expect_fmt and its callers) still runs and asserts for real on Windows. No product code touched by this fix -- test-file-only, per the CI failure's own diagnosis (fatal error on tests/test_fp8_load.c:51, compile-time only). RAN (this commit): make glm METAL=1 (clean rebuild) -- zero warnings. ./tests/test_fp8_passthrough -> "fp8 passthrough CPU tests: ok". ./tests/test_fp8_load -> "fp8 loader-seam tests: ok" (Part A + A3 + B + C, including both new ue8m0 refusal cases and the renumbered fmt=1-vs-fmt=7 suite; POSIX refusal path confirmed still running, not skipped, on this platform -- zero "skipped on Windows" lines in the output). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports fmt6/fp8-passthrough-r2@7baaada onto this branch's fmt=7 renumbering (previous commit): mm_gemv shader's fmt==7 branch (in-kernel e4m3 bit decode, OCP E4M3-FN NaN policy matching quant.h's e4m3_decode exactly; block-scale indexed scale[(o/128)*nblkI+i/128] and folded into acc), fmt_bytes()/fmt_scale_bytes() fmt==7 cases, coli_metal_matmul's explicit allow-list entry (fmt>3 && fmt!=7, since 7 is no longer adjacent to the public 0-3 range this file otherwise supports), all renumbered from the source commit's fmt=100. coli_metal_gemm/bind_gemv unchanged: fmt=7 was never in scope for either (matches matmul_qt_ex's CPU-dispatch exclusion from the previous commit). tests/test_backend_metal.mm: the fmt=7 GPU test group (own bit- manipulation CPU reference decode, magnitude-relative block-scale oracle, 7 shapes incl. block-edge/degenerate/non-square-stride-audit cases, an exhaustive 256-code LUT-exactness check run through the actual GPU kernel, and a gate-off check confirming coli_metal_gemm refuses fmt=7/returns 0), renumbered. Also carries forward run_fp8_moe_gate (originally landed alongside the source branch's metadata-stamp commit, but content-wise it is pure Metal-gate regression coverage with no stamp dependency: proves BY TEST, with deliberately-invalid 0xdeadbeef weight/ scale pointers that must never be dereferenced, that coli_metal_moe_block/moe_submit's existing `fmt != 1 && fmt != 2` gate already refuses fmt=7 before MB_BUILD's pointer-selection ternary could submit the wrong pointer -- in scope for this PR's "Metal kernel + all its tests", independent of the registry/stamp PR). RAN (this commit): make glm METAL=1 (clean rebuild) -- zero warnings. make metal-test under COLI_METAL_RESSET=0: full suite green, exit 0, including all 11 fp8 GPU lines (LUT 0/256 mismatches; 7 shape cases worst_rel in [3e-8, 7e-8]; GEMM gate rc=0; MB_BUILD/moe_submit gate rc=0) and every pre-existing case (int8/int4/int2/f32, moe_block, gemm S=64, fused attention, top-8 select) unaffected. make metal-test under COLI_METAL_RESSET=1: same, plus "[METAL] residency-set: on (macOS 15+, moe_submit skips per-buffer useResource:)" confirmed active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports fmt6/fp8-passthrough-r2@b927911 (repack tool folded with its kv_b_proj self-review fix, as the source branch shipped it), renumbered fmt=100 -> fmt=7. This is the tool's PRE-metadata-stamp shape: it writes no __metadata__["colibri.fmt"] stamp, matching this PR's scope (stamp writer + reader + registry are a separate, follow-up PR per the maintainer's JustVugg#524 reply). The module docstring says so explicitly now, and documents the scale-encoding property this tool implements (f32 block scales only -- Z.ai's GLM-5.2-FP8 checkpoints ship f32 weight_scale_inv, so there is no UE8M0 source data this tool would ever need to convert). tools/repack_fp8_passthrough.py: unlike convert_fp8_to_int4.py (dequant -> requant to a lossy format), copies fp8 weight bytes AS-IS and only renames/reshapes the _scale_inv sidecar to the engine's .qs convention (flat F32, ceil(O/128)*ceil(I/128) elements). Selection reuses convert_fp8_to_int4's classify()/check_or_record_params to target resident kinds only: shared expert, o_proj, other attention projections, dense-MLP first layers, and the generic resident fallback. Routed experts stay on the existing int4-g64 path; kv_b_proj is excluded because the CPU MLA-absorb path (qt_addrow/qt_matvec_rows) and the CUDA absorb kernels have no fmt=7 case and would silently misread it as int2-packed data. _check_geometry refuses (ValueError) rather than silently repack a shard whose _scale_inv shape doesn't match ceil(O/128)xceil(I/128) -- the write-side twin of qt_resolve_fmt's read-side "THE DESIGN LANDMINE" refusal. tests/test_fp8_repack.py: synthetic-fixture-only suite (glm_fp8_emit's exact real-checkpoint FP8 layout, no real Z.ai shard read or written) covering selection (resident kinds byte-preserved; routed experts/io/ f32/kv_b_proj excluded), byte-for-byte weight preservation + .qs rename, the write-side geometry-refusal path, --dry-run writing nothing, and the JustVugg#383-class resume/params-guard idiom. Renumbered from the source's fmt=100. tools/README.md: one-line entry for the tool, renumbered. RAN: python3 -m unittest tests.test_fp8_repack -v -- 9/9 tests pass (this commit, torch + safetensors present in this environment). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports fmt6/fp8-passthrough-r2@b2aea6c, renumbered fmt=100 -> fmt=7 and
adapted to drop the metadata-stamp check the source commit's version
carried: this branch's repack tool (previous commit) writes no
__metadata__ stamp, so there is nothing to sanity-check on that axis.
Closes the last coverage gap: neither test_fp8_load.c (hand-authored
wire-format fixtures -- proves the LOADER's own logic in isolation) nor
test_fp8_repack.py (proves the WRITER's Python-side output shape, never
invokes the C loader) exercises the real round trip.
tests/test_fp8_e2e_loader.c: minimal harness (`#define main
coli_glm_main_unused; #include "../colibri.c"`, same pattern as every
other colibri.c-embedding test in this suite). Takes a container
directory plus (name,O,I) triples on argv, calls the REAL qt_from_disk
for each, and asserts fmt==7 resolved, q8/s both non-NULL, and every
dequantized value finite.
tests/test_fp8_e2e_repack_load.py: builds a synthetic FP8 checkpoint via
tools/glm_fp8_emit.py, repacks it with the REAL
tools/repack_fp8_passthrough.py via subprocess (the actual CLI entry
point, not an imported-function reimplementation), sanity-checks the
real tool's own selection by tensor presence before ever invoking the C
side (so a failure localizes to tool vs. loader) -- and, since this PR's
tool writes no container metadata stamp, additionally asserts
__metadata__ is ABSENT from the repacked output (a regression guard that
this PR's tool really stays out of the stamp PR's scope, not just an
omitted assertion) -- compiles test_fp8_e2e_loader.c with
production-mirroring flags (asserted zero warnings), and runs it against
the real repacked output directory. Includes a routed-expert tensor and
an f32 norm as negative controls, proving the real tool's selection
logic held on an actual round trip, not just in test_fp8_repack.py's own
synthetic-fixture suite.
This closes out Branch A / PR 1's content: CPU read path + matmul_fp8,
Metal kernel, repack tool (no stamp), collision/refusal logic
(fmt=1-vs-fmt=7 THE DESIGN LANDMINE + fmt=6-vs-fmt=7 SECOND DESIGN
LANDMINE, both refuse-unconditionally, no stamped_name parameter
anywhere), the UE8M0 scale-encoding recognition seam, and now this
round-trip test. The metadata stamp itself (writer, qt_verify_fmt_stamp,
stamped_name plumbing, docs/FORMATS.md) is Branch B / PR 2, stacked on
top of this branch.
RAN (this commit): python3 -m unittest tests.test_fp8_e2e_repack_load -v
-> 1/1 pass ("ok"). make clean && make glm METAL=1 -> clean rebuild,
zero warnings. make test-c -> full C suite green, exit 0 (includes
test_fp8_passthrough, test_fp8_load, test_int3/test_int3_load, and every
other pre-existing case). make test-python -> 152 tests, 10 skipped, 0
failures, exit 0 (includes this branch's test_fp8_repack and
test_fp8_e2e_repack_load, plus dev's own suite). make metal-test under
COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0, all fp8 GPU
lines ok, RESSET=1 confirmed active via "[METAL] residency-set: on".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Maintainer review, JustVugg#528 (the load-bearing blocker): self_attn.o_proj.weight loads as [D,H*v_head] = [6144,16384] on GLM-5.2 -- nblkO=ceil(6144/128)=48, nblkI=ceil(16384/128)=128, product=6144==O, so a genuine, valid, pre-existing int8-row o_proj tensor satisfies BOTH the per-row (fmt=1) and per-128x128-block (fmt=7) scale-byte-count conventions at once. qt_resolve_fmt's prior behavior was to refuse this shape unconditionally (exit(1)) -- a real regression on an ordinary, already-shipping model, strictly worse than the misread it guarded against. Confirmed at shard-header level on this repo's own glm52_v1_e8x4g64 container: o_proj weight U8 100,663,296 B (==6144*16384), .qs scale blob 24,576 B (==6144*4==48*128*4, satisfying both formulas at once). colibri.c: qt_resolve_fmt's is_row&&is_blk branch no longer exits -- it falls through to fmt=1 (no explicit assignment needed: fmt is already 1 entering this branch, and the is_blk&&!is_row check further down evaluates false whenever is_row is true, per the comment left in its place). Sound specifically because the WRITER now refuses to ever emit an fmt=7 container at a shape satisfying this same predicate (see repack_fp8_passthrough.py below): an unstamped ambiguous tensor reaching this function is never a genuine fmt=7 candidate, so resolving to the incumbent format isn't a guess. The SECOND DESIGN LANDMINE (fmt=6 vs fmt=7 collision at I=98) and the UE8M0 recognized-not-implemented refusals are unrelated collision predicates and are unchanged -- still unconditional refusals. tools/repack_fp8_passthrough.py: _check_geometry() now also refuses to repack any [O,I] where nblkO*nblkI==O -- the exact shape the reader's collision predicate describes -- making the module docstring's existing promise ("never anything that could coincide with O*4 by accident") true rather than merely asserted. tools/fp8_collision_census.py (new): enumerates every resident-role tensor shape reachable from a container's config.json + safetensors headers (JSON header parse only, no tensor data, no model load) and evaluates the collision predicate over all of them -- run against this repo's own two GLM-5.2 containers (glm52_v1_e8x4g64, glm52_i4), the complete family is self_attn.o_proj at [6144,16384] (79 instances per container, one per layer including the MTP head layer) and nothing else among gate/up/down, q_a/q_b/kv_a/kv_b_proj, or routed/shared experts. tests/test_fp8_load.c: the three shapes the review named explicitly ([128,16384],[256,16384],[384,16384]) plus every other degenerate case in the same sweep (they all hit the identical is_row&&is_blk branch) flip from expect_refuse to expect_fmt(...,1,...); new explicit regression for the real o_proj shape [6144,16384]. tests/test_fp8_repack.py: new writer-refusal test for a well-formed-but-ambiguous [2,256] fixture (the smallest instance of the same family, distinct from the pre-existing malformed-shape refusal test). RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 153 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0.
Maintainer review, JustVugg#528: qt_scale_bytes() already existed and had the right fmt=7 branch (per-128x128-block scale count), but neither qt_wire_mmap nor qt_unwire_mmap actually called it -- both independently hardcoded scale_b=(int64_t)t->O*4 (per-row) inline, so the function compiled but was never referenced by its intended call sites. It only compiled clean because -Wno-unused-function is in CFLAGS: the claimed fmt=4/fmt=5/fmt=7 expert-pinning fix was never applied, and an fmt=7 tensor reaching qt_wire_mmap would get weight_b overshooting the allocation whenever nblk>O (mlocking/munlocking the wrong byte ranges on both halves). colibri.c: new qt_wire_split(t,&weight_b,&scale_b) -- the ONE place both call sites now get their weight/scale byte split from, calling qt_scale_bytes() internally. qt_wire_mmap and qt_unwire_mmap both replace their inline scale_b/weight_b computation with a call to it. qt_scale_bytes() is consequently no longer an orphan: it is exercised both directly (existing qt_bytes/qt_scale_bytes arithmetic tests) and through the real call sites via qt_wire_split. tests/test_fp8_load.c: check_wire_split() calls qt_wire_split() directly (no mlock syscall -- mem_wire's actual RLIMIT_MEMLOCK behavior is environment-dependent and not what changed) across fmt=1 (unaffected, per-row), fmt=4/fmt=5 (grouped formats qt_scale_bytes' own comment already named as previously broken too), and fmt=7 at two nblk>O shapes -- each asserting the split matches qt_scale_bytes()/qt_bytes() AND that the fmt=7 cases have actually moved off the old O*4 constant, not just coincidentally matched it. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 153 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0. One-shot build with -Wno-unused-function removed from CFLAGS: qt_scale_bytes and qt_wire_split are both silent (no longer orphans); 8 unrelated, pre-existing orphan functions remain (st_init, st_prefetch, st_read_slice_f32, tier_pick_swap, stops_arm, cmp_fdesc, attention, and qt_unwire_mmap itself -- the last one only because this translation unit was built without -DCOLI_CUDA, its sole caller's #ifdef guard), all out of this PR's scope -- see the report.
Self-review while responding to the JustVugg#528 maintainer round: attention_rows' and layer_forward_rows' fmt!=7 guard comments claimed "coli_metal_attn_decode's WP_() macro and the mm_gemv shader it dispatches through (bind_gemv) have no fmt=7 case in this build" -- stale since this PR's own Metal kernel commit (96baf22, "Metal mm_gemv kernel for fmt=7 native FP8-e4m3 passthrough") added exactly that: mm_gemv's shader body has a real, working fmt==7 branch (fp8-e4m3 decode + per-128x128-block scale lookup, backend_metal.mm). The shader is not the gap. The actual reason these two fused-kernel paths still fail closed for fmt=7 is the WP_() macro immediately below each guard: `(q).fmt==1?(const void*)(q).q8:(const void*)(q).q4` picks q8 only for fmt==1 and q4 otherwise -- and q4 is NULL/unallocated for fmt=7 (same convention as fmt=1, see the QT struct comment), so an fmt=7 tensor reaching either fused path through WP_() would hand the kernel a NULL weight pointer, not a shader that misreads bytes as f32. Both guard comments now name the real blocker and note that fixing WP_() and threading fmt=7 through bind_gemv is a deferred follow-up (this PR's own GPU-path note), not attempted in this round. No code changes: the guards (fmt!=7 exclusions) and their behavior are unchanged, only the comments explaining WHY they exist. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make glm METAL=1 -> clean rebuild, zero warnings.
FIX ROUND, validator finding (mutation-proven gap): check_wire_split()
exercises qt_wire_split() directly, so it does NOT notice a mutation that
reverts ONLY qt_wire_mmap's and qt_unwire_mmap's call sites back to the old
inline scale_b=(int64_t)t->O*4 hardcode, leaving qt_wire_split() itself
untouched -- the two sites would simply stop calling the now-orphaned-again
helper, and nothing existing would catch it.
tests/test_fp8_load.c: new observer seam (#define mlock/munlock to
per-file names before the colibri.c #include, then define those names as
self-contained recorders after it -- both are external POSIX library
functions with no body in this translation unit, so there is nothing to
accidentally shadow-out-of-existence, unlike an attempted mem_wire()-level
shadow which was tried first and found NOT to work: mem_wire is a real,
internally-defined function, so renaming it renames its call sites too,
and qt_wire_mmap ends up calling the renamed-but-still-real function
directly, bypassing anything defined under the freed-up old name --
caught by inspecting the preprocessed output before it could hide a
broken test, see the comment left in its place). test_wire_site_regression()
calls the real qt_wire_mmap()/qt_unwire_mmap() and asserts, through that
seam, that the (addr,len) each site actually passes down to the platform
lock call matches qt_scale_bytes()/qt_bytes() -- observing the call sites'
own behavior, not a reimplementation of it. POSIX-only (mlock/munlock are
only called on the __APPLE__/__linux__/__FreeBSD__ arm; Windows uses
compat_mlock/compat_munlock, untouched here, matching this file's existing
POSIX-only test-seam convention).
PROVEN TO BITE (RAN): applied the exact mutation this test exists to
catch -- reverted only the two call sites to `int64_t scale_b=(int64_t)
t->O*4; int64_t weight_b=qt_bytes(t)-scale_b;`, leaving qt_wire_split()
itself byte-for-byte untouched (diffed against the pre-mutation file to
confirm) -- rebuilt, and got:
FAIL wire-site regression: qt_wire_mmap's SCALE mlock() call got len=8 want=512 (this is exactly what a reverted scale_b=O*4 hardcode breaks)
FAIL wire-site regression: qt_unwire_mmap's SCALE munlock() call got len=8 want=512 (this is exactly what a reverted scale_b=O*4 hardcode breaks)
fp8 loader-seam tests: 4 FAILED
(4 distinct CHECK() failures -- weight+scale on each of wire/unwire;
each line appears repeated in the raw run due to a pre-existing,
unrelated stdio-buffering artifact where this file's LATER fork()-based
stamp-refusal tests each flush a copy of the still-buffered output when
their child calls exit(1) -- confirmed by counting: exactly as many
repeats as later fork+exit(1) children, same total whether piped or
redirected to a file, and the "N FAILED" summary counts the real 4
distinct failures once each). Reverted the mutation (diffed back to
byte-identical with the pre-mutation file) and reran: "fp8 loader-seam
tests: ok", exit 0.
RAN (this commit): make clean && make portable -> clean rebuild, zero
warnings. make test-c -> full C suite green, exit 0. make test-python ->
155 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean
rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1:
both full suite green, exit 0.
FIX ROUND, audit finding (SHOULD-FIX): qt_scale_bytes() had no explicit fmt=6 branch and fell back to the per-row O*4 formula. Verified at source before implementing (per instructions, not trusting the audit's own derivation): qt_from_disk's fmt==6 branch allocates t->s via qsalloc(1) -- ONE float, 4 bytes total, independent of O -- and its st_read_f32_cap call right below validates the SAME cardinality (`fmt==6 ? (int64_t)1 : ...`). qt_bytes()'s own fmt==6 branch already treats the scale as a fixed `+4` literal, never O*4. All three sites agree: fmt=6's scale allocation is a fixed 4 bytes, confirming the audit's claim rather than contradicting it. This function's own header comment used to ALSO claim fmt=6 tensors "never reach qt_wire_mmap/qt_unwire_mmap's mlock path in this build" because they "stay CPU-side" -- that conflated two unrelated mechanisms: qt_cuda_upload decides GPU-VRAM upload eligibility (fmt=5/6 genuinely excluded there, no CUDA kernel for either), while qt_wire_mmap/qt_unwire_mmap mlock HOST RAM pages under COLI_MMAP, an entirely CPU-side concern -- staying CPU-side is exactly what makes a tensor a wiring CANDIDATE, not exempt from it. expert_load_impl assigns fmt=6 to ESlot g/u/d exactly like fmt=4/5 (same qt_resolve_fmt call site), and pin_wire calls qt_wire_mmap/qt_unwire_mmap on every pinned ESlot's g/u/d unconditionally, with no format filter -- so a pinned E8/IQ3 expert under COLI_MMAP + mem_should_wire() does reach here. Before this fix, that path's O*4 fallback would return a scale_b wildly larger than the tensor's real 4-byte allocation, then mlock/munlock that many bytes starting at t->s -- past the end of a 4-byte allocation. This is a strict correction, not scope creep introduced by wiring the helper: pre-existing behavior at the old hardcoded call sites (scale_b=(int64_t)t->O*4, inline, before qt_wire_split existed) was identically wrong for fmt=6 already -- this closes the same gap the JustVugg#528 fix closed for fmt=4/5/7, for the one format that fix's own review round didn't reach. colibri.c: qt_scale_bytes() gains `if(t->fmt==6) return 4;`, and the header comment's stale reachability claim is corrected. tests/test_fp8_load.c: check_wire_split(6, ...) at a realistic expert shape (verifying the value moved off the old O*4 fallback) and at the O=1 degenerate shape (where O*4 coincidentally also equals 4, so only the new direct `want_scale==4` assertion -- not the generic "moved off O*4" guard -- catches a regression there; both are asserted). PROVEN TO BITE (RAN): removed the `if(t->fmt==6) return 4;` line only, rebuilt, ran the suite: FAIL tests/test_fp8_load.c:574: want_scale == 4 fp8 loader-seam tests: N FAILED Reverted (diffed back to byte-identical with the pre-mutation file) and reran clean: "fp8 loader-seam tests: ok", exit 0. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 155 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0. One-shot -Wno-unused-function-stripped build: same 8 pre-existing, unrelated orphans as before this round, no new ones.
FIX ROUND 2, engine defect (clean-room conformance trial finding, the headline item): qt_addrow and qt_matvec_rows (colibri.c) serve the kv_b MLA-absorption CPU path. Both dispatch fmt 0/4/5 explicitly, then fall through assuming a PER-ROW scale (t->s[row]) followed by fmt=1/2/3 (qt_addrow) or fmt=0/1/2/3/4/5 via an if/else-if chain ending in a bare `else` (qt_matvec_rows) -- nothing stopped any OTHER fmt from reaching that fall-through. Reproduced (trial-verified, re-confirmed here): a fmt=7 QT (t->s holds ceil(O/128)*ceil(I/128) per-block floats, not O -- a [130,130] tensor has nblk=4, so t->s[row] overreads for every row past 3) reaches the fall-through's `t->q4+(int64_t)row*((I+3)/4)` -- t->q4 is NULL for fmt=7 (raw bytes live in t->q8 instead, same convention as fmt=1) -- and dereferences NULL-plus-offset: SIGSEGV. A fmt=6 QT (t->s is a FIXED 4-byte tag, qsalloc(1), not O floats) overreads t->s[row] for row>0 and then silently misreads the real E8/IQ3 lattice bytes in t->q4 as int2-packed data -- same bug SHAPE as JustVugg#298's CUDA absorb-kernel fix (this file's own fmt=4/5 branches exist for exactly this reason; fmt=6 was simply missed). Both functions now refuse loudly (stderr naming the function and the fmt, exit(1)) for any fmt they don't explicitly handle: qt_addrow gets an explicit `fmt!=1 && fmt!=2 && fmt!=3` guard before the per-row-scale read (fmt 0/4/5 already returned above); qt_matvec_rows' final bare `else` becomes `else if(fmt==3)` plus a new refusing `else`. Reachability note (context, not a scope excuse): both functions serve ONLY the kv_b absorb path, and tools/repack_fp8_passthrough.py deliberately excludes kv_b_proj from fmt=7 repacking -- so this fires only via a hand-slotted or ambiguous-collision container, never this repo's own tooling's output. Crash-instead-of-refuse is still a real defect (loud failure, every refusal names its condition), and the fmt=7 QT surface these functions can now be handed is one this same PR pair created. tests/tests/test_qt_addrow.c (new): fork+pipe+waitpid refusal tests (this suite's house pattern) for fmt=6 and fmt=7 through BOTH functions, using the coordinator's own [130,130] partial-block repro shape for fmt=7; the harness also detects a raw SIGSEGV (WIFSIGNALED) and reports it as the specific failure it is, rather than hanging or mis-reporting. Byte- identity checks for every format both functions still handle (0/1/2/3/4/5) against an independently-written reference dequantizer (not copy-pasted from either function -- restructured as a single per-element loop per format), epsilon-compared (float multiplication reassociation, e.g. qt_addrow's precomputed c=coef*scale vs the reference's coef*(scale*w[i]), is not bit-exact even though mathematically identical -- confirmed by inspecting actual failures at strict equality before relaxing, all last-ULP-only, no shape/decode errors). PROVEN TO BITE (RAN, full transcript in the report): reverted the guard in qt_addrow only, rebuilt, ran the new test -- reproduced the exact SIGSEGV (signal 11) for fmt=7 and the silent non-refusal for fmt=6, caught cleanly by the test's own signal-aware harness. Reverted (diffed back to byte-identical with the pre-mutation file), reran clean. Repeated independently for qt_matvec_rows' guard alone (qt_addrow's guard left intact) -- same SIGSEGV/silent-non-refusal pair reproduced and reverted. RAN (this commit): make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0, includes the new tests/test_qt_addrow binary. make test-python -> 153 tests, 10 skipped, 0 failures, exit 0. make glm METAL=1 -> clean rebuild, zero warnings. make metal-test under COLI_METAL_RESSET=0 AND =1: both full suite green, exit 0. One-shot -Wno-unused-function-stripped build: same 8 pre-existing, unrelated orphans as before this round, no new ones.
FIX ROUND 2, item 3 (clean-room conformance trial finding, spec I6 -- loud failure, every refusal names its condition): a real (non-dry-run) run of tools/repack_fp8_passthrough.py whose --indir contains FP8 tensors but none matching a repack-target kind used to exit 0 having emitted nothing -- trial-verified with a non-resident-named FP8 tensor selecting silently. That is a silent trap: an empty "container" (just the resume/params sidecars, no actual .safetensors output) nobody asked for, with no caller-side check positioned to catch it. main() now refuses (nonzero exit, stderr naming --indir and the shard count checked) when every shard -- this run's AND any prior resumed run's, via the `done` progress dict, one entry per shard -- has NEVER produced an output. --dry-run is deliberately NOT covered: printing "0 tensor(s) selected" already IS the loud, honest answer dry-run exists to give, not a silent no-op, so turning it into a refusal would be wrong, not thorough (this judgment call implemented directly, per the coordinator's own "or escalate the disagreement" allowance -- no disagreement here, both readings agree once dry-run's actual purpose is considered). tests/test_fp8_repack.py: new ZeroTargetRefusalTest -- a fixture shard with FP8 tensors but none matching a repack-target kind (routed expert + f32 norm, mirroring the trial's own scenario) must refuse on a real run and still exit 0 on --dry-run. Also updates the module's kv_b_proj-exclusion comment (repack tool docstring), which the JustVugg#528+FIX-ROUND-2 qt_addrow/qt_matvec_rows fix made stale: it claimed an fmt=7 kv_b_proj tensor "would silently misread fp8 bytes as int2-packed data" -- true only for fmt=6; fmt=7 actually SIGSEGV'd (t->q4 is NULL for fmt=7). Both now refuse loudly at the absorb call site instead -- corrected to describe the actual (fixed) behavior, while keeping the underlying exclusion decision itself unchanged (refusing loudly there is still not the same as supporting fmt=7 for kv_b_proj). PROVEN TO BITE (RAN): removed the new zero-target check only, ran the new test -- `test_zero_targets_refuses_loudly` failed exactly as expected (returncode 0 instead of nonzero); `test_zero_targets_dry_run_unaffected` still passed (confirming the dry-run path was never touched by the mutation). Restored (diffed back to byte-identical with the pre-mutation file), reran: both new tests green. RAN (this commit): python3 -m unittest tests.test_fp8_repack -v -> 14/14 pass. make clean && make portable -> clean rebuild, zero warnings. make test-c -> full C suite green, exit 0. make test-python -> 157 tests, 10 skipped, 0 failures, exit 0 (2 more than before this round: the new ZeroTargetRefusalTest cases).
A single read() on the capture pipe can return short on Linux (glibc's unbuffered stderr arrives in multiple chunks), truncating the refusal message before its marker -- the linux CI job caught expect_refuse's [64,98] fmt=6/fmt=7 collision case failing exactly this way while the refusal itself fired correctly. Loop the read to EOF; same fix in test_qt_addrow.c's identical helper. macOS coalesced the chunks, which is why local runs never saw it.
9f6b4ab to
6313f32
Compare
…tVugg#528 restack Maintainer review, JustVugg#529 (shared with JustVugg#528's fix): FORMATS.md cites specific c/colibri.c line numbers and branch/commit SHAs for every format row and for the metadata-stamp mechanism. Restacking fmt7/stamp-registry onto the revised fmt7/fp8-passthrough-rev1 (the JustVugg#528 INVERSION/qt_wire_split/stale-comment commits) shifted every colibri.c line number after qt_bytes(): qt_scale_bytes 216->223, qt_alloc 893->910, qt_resolve_fmt (and its SECOND DESIGN LANDMINE comment) 1144->1161, the FMT_NAMES table 1104->1121. Verified against the actual restacked file (grep for each cited function's definition line), not recomputed by offset arithmetic. The "Known formats" intro and "Sources for all rows" preamble also named the pre-restack branch/commit (fmt7/stamp-registry @ base 9a1690e) -- updated to fmt7/stamp-registry-rev1 @ base 696ce96, restacked on fmt7/fp8-passthrough-rev1 @ a77421d, with a note that these line numbers need re-verifying again if the branch is rebased further. No code changes, no test changes -- doc-only.
|
Authored by Fable 5 in Claude Code, analysis in partnership with @monotophic Rebased over today's wave: rev3, head |
Authored by Fable 5 in Claude Code (We/our), analysis in partnership with @monotophic (me/my).
Revised per review (2026-07-23): collision policy inverted; see the comment of this date for the full revision + evidence.
fmt=7: native FP8-e4m3 passthrough — CPU path, Metal kernel, repack tool
Branch:
fmt7/fp8-passthrough. Base: dev9baae9b. Closes the FP8-passthroughFeature Request; implements the format the maintainer assigned
fmt=7on#524. Companion PR (
fmt7/stamp-registry, stacked on this one) adds thecontainer metadata stamp and the
docs/FORMATS.mdregistry — split out perthe maintainer's own scoping request, so this PR is CPU path + repack tool +
collision/refusal logic + the Metal kernel only.
What / why
Z.ai ships GLM-5.2's release-native checkpoint in FP8-e4m3
(
zai-org/GLM-5.2-FP8) — to our knowledge there is no public bf16/fp16upstream of it. Every existing path into this engine from that checkpoint
first dequantizes to f32 and requantizes to a lossy format (int4/int3),
paying a real second quantization error on top of whatever the FP8 already
cost. This PR adds a different path for tensors where the extra fidelity is
worth the extra 8 bits/weight over int4: repack, not requantize. The
repack tool copies the fp8 weight bytes AS-IS (
.view(torch.uint8), a purebit-reinterpret, verified byte-identical against
torch.float8_e4m3fn's ownstorage) and only renames/reshapes the
weight_scale_invsidecar to theengine's
.qsconvention. Same 8 bits/weight streaming cost as the FP8source, zero additional quantization loss — a resident/quality-core tier
sitting above int4/int3 in fidelity, below f32 in size.
Selection: which tensor roles get fmt=7
tools/repack_fp8_passthrough.pyreusesconvert_fp8_to_int4.classify()—the engine's existing tensor-role taxonomy — rather than inventing a new one:
classify()kind)sh)o_proj(o)attn, incl.q_a_proj)dmlp)q)kv_b_proj(kvb)x)f32)io)Blending selected fmt=7 residents with int4-g64 routed experts in the same
loadout directory is separate, deferred "loadout index-rewrite blending"
work — out of scope here; the tool only ever emits a
fmt=7container forthe roles in the table.
Design
Decode LUT (
quant.h'sE4M3_LUT, 256 entries): a static compile-timetable, cross-checked byte-for-byte against
torch.float8_e4m3fnfor all 256codes including both signs of zero, subnormals, and the OCP E4M3-FN NaN
encoding (
exp==0xF && mant==0x7only —exp==0xFis NOT reserved forinfinity in this format). Static rather than lazily initialized because
matmul_fp8runs inside#pragma omp parallel forand a lazy-init globalwould race under concurrent first use. The Metal
mm_gemvshader'sfmt==7branch does the identical bit-decode in-kernel (no LUT texture — BW-bound
kernel, decode ALU is free) and is verified to byte-match the CPU LUT via an
exhaustive 256/256-code GPU test (see Metal validation below).
Block scales: one
f32per 128×128 block of the[O,I]weight matrix(
ceil(O/128)*ceil(I/128)entries, row-major block order), not one peroutput row. Dequant is
w[o,i] = e4m3_decode(byte) * scale[o/128, i/128](multiply, not divide) — matches
tools/convert_fp8_to_int4.py's owndequant(), the authoritative reference for how Z.ai's checkpoints read.Scale encoding is a declared property of the format, not a hardcoded
constant. This was an open design question we'd have gotten wrong by
defaulting to "f32, full stop" — the maintainer's own finding, surfaced on
#524, is what settled it: DeepSeek-V4 ships this identical weight geometry
(FP8 E4M3, 128×128 blocks) with UE8M0 scales instead of f32 — one byte per
block, a power-of-two exponent, dtype
F8_E8M0. Concretely, in this PR:qt_resolve_fmt's own documentation; f32 is the implemented value(what Z.ai's GLM-5.2-FP8 checkpoints ship, and what this PR's CPU kernel,
Metal kernel, and repack tool all read/write).
rather than misreading it: the two encodings differ cleanly in
scale-sidecar arithmetic (
ceil(O/128)*ceil(I/128)*4bytes for f32 vs*1for UE8M0), a real, distinct discriminator (never coincidentallyequal for any
O,I >= 1, since one is exactly 4× the other). Anunstamped container with a 1-byte-per-block sidecar gets "fp8-e4m3-b128
with ue8m0 scales recognized but not implemented; only f32 block scales
are supported in this build" instead of garbage.
already applies to the CUDA tier: whoever reaches DeepSeek-V4 first (that
may be us, later) lands it into a seam that already exists rather than a
near-duplicate format. The MXFP4 routed-expert question (E2M1 + UE8M0 g32)
raised alongside this finding is a separate format and a separate
conversation — but the same scale-encoding-as-property mechanism serves
it too.
The collision/refusal analysis
qt_resolve_fmtinfers a tensor's format from byte arithmetic alone — thecontainer carries no format ordinal. fmt=7's weight bytes are
byte-identical to fmt=1 (int8):
O*Iraw bytes either way. That createsthree genuine byte-collision shapes this PR's resolver must catch and
refuse rather than silently misread:
O(≤128) and/orsmall
I,ceil(O/128)*ceil(I/128)can equalOexactly (e.g.O=1, I≤128;O=2, I∈[129,256];O=256, I∈(16256,16384]) — a scale arraythat satisfies BOTH fmt=1's per-row convention (
O*4bytes) and fmt=7'sper-block convention (
ceil(O/128)*ceil(I/128)*4bytes) at once.Guessing either way silently corrupts every weight. Refused.
fmt=6 (E8/IQ3, engine: fmt=6 E8/IQ3 lattice container — CPU decode kernel and dispatch (#452 step 3) #465) tags itself with
ns==4 && nb==O*e8_rowbytes(I),checked unconditionally before any fmt=7-aware logic runs.
e8_rowbytes(I) = ceil(I/256)*98is the constant 98 for everyIin(0,256], so that check collapses tonb==O*98— which is ALSO fp8'sraw weight-byte count (
O*I) at exactlyI=98. At that sameI=98, asingle-block fp8 tensor (
O≤128, f32 scales) carries exactly onef32block scale (ns==4, the same tag fmt=6 uses); a four-blockfp8 tensor (
O∈(384,512], UE8M0 scales) also lands atns==4(4blocks × 1 byte/block). A straight, un-reconciled rebase onto post-engine: fmt=6 E8/IQ3 lattice container — CPU decode kernel and dispatch (#452 step 3) #465
dev would silently misread either as E8/IQ3-lattice-decoded garbage —
demonstrated fail-before during development (a standalone probe at
O=64, I=98returnedfmt=6with no error before the fix). Refusedafter the fix, for both the f32-scaled and UE8M0-scaled variants.
O==1triple collision. AtO=1, I=98, fmt=1's own per-row tag(
O*4) is ALSO 4 — a genuine three-way ambiguity (fmt=1 / fmt=6 /fmt=7) at one shape. Refused.
This PR's refusal posture is unconditional: it carries no container
metadata stamp (that's the companion PR), so every one of these three
collisions refuses regardless of the tensor's true identity — "nothing real
is lost: no real checkpoint tensor has these shapes" (the maintainer's own
framing, #524). The companion PR's stamp is the only thing that can turn a
refusal into a resolution, and even then only for the f32-scaled candidates
— a stamp can confirm a WEIGHT format, it can't grant a decoder this build
doesn't have, so the UE8M0 corners of collisions 2 and 3 stay refused even
under a (correct) stamp.
Test coverage:
tests/test_fp8_load.c's Part A (15 cases: golden paths, 6degenerate collisions, 4 boundary-adjacent non-collisions) + Part A2 (the
fmt=6 collision at
I=98, both boundary shapes, the three-wayO=1case, plus 2 non-colliding fmt=6 regression fixtures proving dev's own
E8/IQ3 inference stays unbroken) + Part A3 (2 UE8M0-signature refusal
cases, including the small-O collision variant).
kv_b_projexclusionkv_b_proj(kindkvb) is a valid resident tensorclassify()wouldotherwise select, and it's deliberately excluded anyway: colibri.c's
MLA-absorption CPU path (
qt_addrow/qt_matvec_rows, the always-availablefallback whenever the Metal fused decode kernel isn't used) has no fmt=7
case and would silently misread fp8 bytes as int2-packed data; the CUDA
absorb kernels (
coli_cuda_attention_absorb/_kvdevinbackend_cuda.cu)are similarly int4-specific. Repacking
kv_b_projto fmt=7 needs thatCPU+CUDA absorb-path work first — out of scope for this PR, so the tool
refuses to produce a container the engine cannot safely read
(
tests/test_fp8_repack.py::test_kv_b_proj_excludedis the regressionguard).
Metal validation record
This is the evidence we'd ask you to merge on, absent Apple hardware on
your side — every fmt=7 code path exercised on real M-series Metal, not
just described:
ACTUAL compiled
mm_gemvshader (not a CPU-only spot check) — proves thein-kernel bit-decode agrees with
quant.h's reference LUT for everypossible byte value, including both zeros, all subnormals, and the NaN
encoding.
O,Iboth non-multiples of128), degenerate 1×1 (single sub-block), a non-square block grid
(
nblkO=3, nblkI=48— a stride-audit case that would surface atransposed or swapped block index), worst observed relative error
3.0e-8to7.0e-8across all cases (tolerance1e-4).S>1gate test: the spec-shaped[2048,6144]case run atS=4(batched decode), not just
S=1.coli_metal_gemmexplicitlyrefuses fmt=7 (
rc=0, CPU-fallback signal) — the large-batch sync GEMMpath was never in scope for this format.
coli_metal_moe_block/moe_submit's existingfmt != 1 && fmt != 2allowlist already refusesfmt=7 before any pointer is dereferenced — proven BY TEST with
deliberately-invalid
0xdeadbeefweight/scale pointers that must neverbe read, not left as an artifact of the allowlist happening not to
include 7 (yet).
COLI_METAL_RESSETstates:make metal-testfull green underCOLI_METAL_RESSET=0ANDCOLI_METAL_RESSET=1(residency-set pathconfirmed active via
"[METAL] residency-set: on"), all 11 fp8 GPUlines identical in both.
Full case-by-case output (RESSET=0; RESSET=1 is line-identical apart from
the residency-set banner):
Every pre-existing Metal case (int8/int4/int2/f32, moe_block, gemm S=64,
fused attention, top-8 select) unaffected, in the same run.
Durable vs. current-state (review map)
Per this project's own convention for delineating what's structural from
what has a foreseeable resolution:
O*Iraw e4m3), E4M3 decode LUT, block-scale indexingqt_resolve_fmt's fmt=1 candidate branch + a new dispatch case) — invited follow-up, may be uskv_b_projexcluded from fmt=7 selectionqt_addrow/qt_matvec_rows) + CUDA MLA-absorb kernels gaining an fmt=7 casefmt7/stamp-registrymerging — a stamped, unambiguous container resolves instead of refusing (except the UE8M0 corners, which stay current-state per row 3)9baae9bBehavioral contract
This PR was revised against a written contract; every invariant below is a
registered claim (stated before the revision was built), each with its
decisive evidence:
[6144,16384]int8-row (GLM-5.2's realo_proj): refused by the previous head (exit 1, quoted in-thread), loads as fmt=1 here[2048,16384]); round-trip load verified on non-ambiguous outputqt_addrow/qt_matvec_rowsrefuse unhandled formats by name (previously: SIGSEGV for fmt=7, silent misread for fmt=6 — found by the acceptance suite, fixed here)o_projis the only ambiguous family (79 instances/container)Full evidence matrix (instruments and results per requirement)
containers, per-run usage-state control, telemetry-excised output
comparison; plus same-binary control run proving the method's
discrimination. Result: identical loads, per-tensor resolutions, notices,
and generated text, all four comparisons.
behaviorally (collision-family fixtures at 4 real shapes + 1
non-collision control resolve fmt=1); the three previously-refusing test
shapes plus four degenerate cases now assert fmt=1; census tool
regenerable upstream (
c/tools/fp8_collision_census.py).derived E4M3-FN oracle (derivation caught an internal contradiction in
the OFP8 spec's own bias statement, resolved via its worked examples);
bit-exact on NaN-free data; NaN weights verified to poison their row
through the dot product (IEEE-correct propagation, 130/130 rows).
qt_scale_bytes()wired via sharedqt_wire_split(); seam test intercepts actualmlock/munlockcalls —a sites-only regression to
O*4fails 4 named assertions; fmt=6's4-byte-tag case corrected while making the helper authoritative.
functions (previously crash/silent); repack tool refuses on zero
repack-target tensors (previously silent success).
make check; METAL=1 zero warnings;metal-testunder bothCOLI_METAL_RESSETstates; per-commit bisect builds; suppression-stripped
-Wunused-functionbuild; POSIX-construct check (Windows CIpattern) on all new tests.
Deeper artifacts (specific transcripts, census tables, suite design)
available on request.
Commits in this PR
fp8: CPU read path + matmul_fp8, as fmt=7 (public ordinal, #524)—quant.h's LUT/decode/kernel, colibri.c's byte-accounting and THE DESIGN
LANDMINE refusal, the UE8M0 recognition seam,
qt_resolve_fmtin itsplain 6-argument (no stamp) form.
fp8: Metal mm_gemv kernel for fmt=7 native FP8-e4m3 passthrough— theGPU kernel and its full adversarial test suite, including the
moe_submitgate regression test.fp8: repack tool (Z.ai FP8 shards -> byte-preserved fmt=7 container)—the writer side, no metadata stamp.
fp8: end-to-end test, real repack tool -> real C loader— the realround trip, closing the last coverage gap.