metal: fmt=6 (E8/IQ3) expert decode — GPU path + on-GPU FWHT rotation - #732
Conversation
moe_gemv gains an fmt==6 branch decoding the 98-byte super-blocks in-kernel (grid table generated from quant.h e8_grid; signs close by odd parity; scales live in-block so the saddr multiply is skipped). A new moe_fwht kernel rotates the GPU-resident down-projection input between silu and the down GEMV, with the exact block-diagonal tiling of e8_rot_rows; tiles that exceed threadgroup memory reject at submit (CPU fallback) rather than splitting differently from the converter. The staged gate/up input is rotated host-side in colibri.c (metal_stage_rot_e8, one FWHT per distinct source row). Sign streams are regenerated with the same xorshift draw as quant.h e8_signs; the metal-test oracle compares the full block path end-to-end against matmul_e8/e8_rot_rows (nerr ~5e-7), so codec drift between the copies fails the build. quant.h: two explicit casts so it compiles as C++ (the oracle includes it). convert_fp8_to_int4.py: --jobs N runs e8 encodes across a process pool (the python codec is ~5.5s per expert matrix; 504B serial would be ~81h). Pool output verified byte-identical to serial on the bench fixture (1135 tensors).
|
End-to-end numbers on Apple Silicon, now that I have a clean container to measure. Setup: M5 Pro, 64 GB, GLM-5.2 REAP-504B. Two colibri containers built from the same source
fmt=6 is +24% on this hardware, reproducible across all three pairs with low variance. Two Two notes for anyone reproducing this on Metal:
fmt=6 pays on Apple Silicon: faster decode, denser pinning, a 22% smaller container, and per |
Summary
Metal decode for fmt=6 (E8/IQ3) routed experts. Since #465 the container loads on Apple hardware but every expert decodes through the scalar CPU kernel, which on an M5 Pro costs ~3x the disk time the format saves (measured below). This PR gives the format its first GPU path: a
moe_gemvfmt==6 branch that decodes the 98-byte super-blocks in-kernel, amoe_fwhtkernel that rotates the GPU-resident down-projection input between silu and the down GEMV, and host-side rotation of the staged gate/up input incolibri.c.Mechanism
moe_gemvfmt==6: one 32-weight sub-block per lane step. Grid magnitudes come from aconstant uchar4 E8G[256]table generated from quant.he8_grid; the j=7 sign closes by odd parity of the other seven; the fp16 block scale and 4-bit sub-scale apply in-kernel, so thesaddrper-row multiply is skipped (the.qsrider is a tag, resolved but unused).moe_fwht: block-diagonal FWHT with the exact tiling rule ofe8_rot_rows(largest power of two dividing the remainder, capped 32768). Tile sizes are validated at submit; a tile over threadgroup memory rejects to the CPU path instead of splitting differently from the converter, so GPU and container tiling cannot diverge.metal_stage_rot_e8in colibri.c rotates each distinct staged source row once and copies to duplicates. For S=1 decode that is one FWHT per token. The CPU fallback path keeps its own rotation and is untouched.e8_signs. The two copies cannot drift silently: the metal-test oracle runs the full block path end-to-end againstmatmul_e8/e8_rot_rows, so any divergence fails the build.Heeding the #587 lesson about silent CPU fallback: the
moe_submitfmt gate is lifted only together with the expert kernel, in one commit, and the oracle exercises the whole submit path. A fmt=6 run should showfallback CPU 0in the METAL line.Also included
convert_fp8_to_int4.py --jobs N: the e8 encodes of one shard run across a process pool. The python codec measures ~5.5 s per expert matrix single-threaded, which puts a full GLM-5.2 REAP conversion at ~81 h serial; 14 workers bring it to ~6 h. Works on both--indirand the--repodisk-safe path; untested in combination with the new--workers, and the help text says so. Pool output verified byte-identical to serial on the bench fixture (1,135 tensors), re-verified after rebasing onto current dev.Validation
make metal-test: two new fmt=6 moe_block cases (decode nb=8, ragged nb=6) at GLM dims, D=6144 and I=1536 so the multi-tile 512+1024 FWHT path is exercised. nerr 4.8e-7 and 5.1e-7 against the scalar reference. The existing suite, including the metal: grouped-int4 (fmt=4) GEMV support + two latent fmt=4 fixes #457 fmt=4 cases, stays green.make test-c: green.Pending
An end-to-end A/B on a real GLM-5.2 REAP-504B fmt=6 container is in progress (the conversion is long). Projection from the measured decode profile on this host (28.1 s disk-wait, 7.4 s GPU experts per 128 tokens): ~+18% decode from the byte reduction alone, before the pinned-expert coverage gain in the same RAM budget. I will post measured numbers on this thread when the container is done.