KV cache quantization: fp8 (KV8) + 4-bit TurboQuant (KV_TQ) on CPU, CUDA, and Metal#399
KV cache quantization: fp8 (KV8) + 4-bit TurboQuant (KV_TQ) on CPU, CUDA, and Metal#399NeuralNotwerk wants to merge 1 commit into
Conversation
dc2bf2c to
613236f
Compare
|
Reviewed against post-v1.0.0 How the ragged branch interacts with the fp8 device shadow (does a ragged gather read the shadow rows, or does it re-upload?) is a semantic call I'm not comfortable resolving for you, and I have no CUDA hardware here to validate it. Could you rebase the series onto current |
|
Rebased onto current The ragged Γ fp8-shadow call, made explicit in the code (comment at the gate in Also folded in during the rebase: the batch-absorb gate keeps its |
b02bc92 to
606fe19
Compare
|
Heads-up: |
Upstream PR JustVugg#399 (KV8 fp8 / TQ4 quantized latent KV) leaves Lc/Rc NULL when a quantized tier is active β the mirror sync would deref NULL. Guard on the arrays themselves (env-independent), falling back to the CPU attention path; an fp8-aware VK mirror (upload Lc8+scale, decode e4m3 in the shader, 4x less mirror traffic) is the follow-up once JustVugg#399 merges.
CUDA validation on fleet hardware (consolidated)Ran the rebased series on our fleet box β 4Γ RTX 5090 (sm_120) + RTX 4090 (sm_89), EPYC 7532, 503 GB RAM, CUDA 12.8.1. (Context for the sparse CUDA numbers lately: this box pulls ~4.5 kW and North Carolina is mid-heat-wave, so it only comes up in cool windows.) Build & tests: real Correctness, live on GLM-5.2 744B ( Performance β measured at the production operating point (serve mode over HTTP, exact prod env: On the #391 heads-up ( |
|
Tested this branch on an M5 Pro, 64 GB, macOS 26.5.2, GLM-5.2 REAP-504B int4 container, expert-pruned to 168 per layer, streamed from disk. Two findings, one of which you probably want before merge.
Caveat on both: the CLI prompt is untemplated and the model degenerates into a token loop after about 30 tokens in every arm, merge-base included. The degeneration is deterministic, so the first-token divergence and the relative throughput stand, but the absolute tok/s is not a chat workload. I can rerun with different knobs or capture fuller logs if that helps narrow down the qmode question. |
Review fixes for JustVugg#399 (michael-denyer, M5 Pro): (1) f32 regression: both KV tiers OFF diverged from merge-base at the first decode token. Root cause: coli_metal_init compiled the WHOLE MTLLibrary with MTLMathModeSafe (added for the fp8 RNE encoder), silently recompiling every f32 kernel fast-math-off. A byte-identical A/B proved mm_gemv alone drifts ~4e-6, compounding to flip greedy argmax at token 1. Fix: two libraries β libFast (fast-math, all f32/MoE + quantized consumers; restores merge-base parity) and libSafe (only a_fp8enc/a_tq_enc/a_tq_enc1). fp8 stays byte-exact. (2) TQ4 ~2x slower than f32: it re-dequantized the ENTIRE cache to f32 staging every layer every token, then ran plain f32 score/clat. codec-1 (rotated int4) fix uses the Hadamard orthogonality identity q.x_hat == rotate(q).c and sum_t w_t x_hat_t == unrotate(sum_t w_t c_t): rotate the query once per head (a_tq_qrot), dot packed nibbles through the codebook (a_score_tq), accumulate (a_clat_tq), unrotate once (a_tq_unrot); parallel producer a_tq_enc1. codec-0 (PolarQuant) keeps the a_tq_deq staging path. Native vs staging: 1.4-1.9x faster, gap widens with context, equivalence ~5e-7. Tests: test_kv_tq.c gains the score/context orthogonality identities + a full MLA-consumer native-vs-dequant check; test_backend_metal.mm gains a native-vs-staging equivalence + speedup benchmark. make metal-test + test-c green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ready for review β validated on Metal, CPU, and CUDABoth review findings are root-caused and fixed, and the KV-quant stack is now hardware-validated across all three backends (incl. an end-to-end run on GLM-5.2 744B). @michael-denyer's findings β both fixed
Validation
Also in this push
RemainingOne open item: the rebase onto the |
6c238fa to
7d62599
Compare
β¦β rebased onto colibri.c Rebases the JustVugg#399 series onto current dev (colibri.c + extracted headers, post-JustVugg#391), which also picks up JustVugg#445's CUDA_RELEASE_HOST pin-budget fix for free. The KV-quant work is additive β dev had no KV8/TQ code β so the split just relocated scaffolding: * colibri.c β KV8/TQ globals + KVState fp8/packed byte caches, kv_alloc, the attention consumers (CPU rotated-int4 orthogonality path; Metal/CUDA native fused kernels dispatch), env parsing, the pin_load KV8-shadow VRAM projection (merged with JustVugg#445's additive prefix budget). * kv_fp8.h / kv_tq.h β the fp8 e4m3 and rotated-int4/PolarQuant codecs (new headers). * kv_persist.h β .coli_kv disk format v2 (fp8) / v3 (TQ): magic-tagged, on-load format detection, v1->v2/v3 in-RAM upgrade + self-heal, fsync durability. * backend_metal.mm β two-library split (fixes the f32 fast-math regression: fast-math for f32/consumer kernels, safe-math only for the RNE encoders) + native codec-1 TQ4 fused attention (Hadamard-orthogonality: rotate the query once, dot packed nibbles, unrotate the context; no f32 restage). * backend_cuda.cu/.h β native codec-1 TQ4 kernel (attention_absorb_kernel_tq) + host entry. * openai_server.py β dispatcher skips unrecognized engine telemetry frames instead of tearing down (unified single/multi-slot interface, version-skew tolerant). Note: the KV-tier flag definitions (g_kv8/g_tq/g_tq_bits/g_tq_codec/g_kv_shadow) moved above the kv_persist.h include so the disk format can see them. Validated on this rebase: `make colibri` clean; `make test-c` green (incl. kv_fp8, kv_tq identities + MLA-consumer equivalence, and kv_disk v1/v2/v3 round-trip + upgrade + reject + self-heal); `make metal-test` green (f32 byte-exact, native TQ4 1.3-1.5x faster than staging, layer decode ok). CUDA compiles clean on the fleet box (sm_89+sm_120, cuda-test incl. attention_absorb_tq) and ran coherent end-to-end on GLM-5.2 744B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Rebased onto current Rebased tree is green locally: |
β¦β rebased onto colibri.c Rebases the JustVugg#399 series onto current dev (colibri.c + extracted headers, post-JustVugg#391), which also picks up JustVugg#445's CUDA_RELEASE_HOST pin-budget fix for free. The KV-quant work is additive β dev had no KV8/TQ code β so the split just relocated scaffolding: * colibri.c β KV8/TQ globals + KVState fp8/packed byte caches, kv_alloc, the attention consumers (CPU rotated-int4 orthogonality path; Metal/CUDA native fused kernels dispatch), env parsing, the pin_load KV8-shadow VRAM projection (merged with JustVugg#445's additive prefix budget). * kv_fp8.h / kv_tq.h β the fp8 e4m3 and rotated-int4/PolarQuant codecs (new headers). * kv_persist.h β .coli_kv disk format v2 (fp8) / v3 (TQ): magic-tagged, on-load format detection, v1->v2/v3 in-RAM upgrade + self-heal, fsync durability. * backend_metal.mm β two-library split (fixes the f32 fast-math regression: fast-math for f32/consumer kernels, safe-math only for the RNE encoders) + native codec-1 TQ4 fused attention (Hadamard-orthogonality: rotate the query once, dot packed nibbles, unrotate the context; no f32 restage). * backend_cuda.cu/.h β native codec-1 TQ4 kernel (attention_absorb_kernel_tq) + host entry. * openai_server.py β dispatcher skips unrecognized engine telemetry frames instead of tearing down (unified single/multi-slot interface, version-skew tolerant). Note: the KV-tier flag definitions (g_kv8/g_tq/g_tq_bits/g_tq_codec/g_kv_shadow) moved above the kv_persist.h include so the disk format can see them. Validated on this rebase: `make colibri` clean; `make test-c` green (incl. kv_fp8, kv_tq identities + MLA-consumer equivalence, and kv_disk v1/v2/v3 round-trip + upgrade + reject + self-heal); `make metal-test` green (f32 byte-exact, native TQ4 1.3-1.5x faster than staging, layer decode ok). CUDA compiles clean on the fleet box (sm_89+sm_120, cuda-test incl. attention_absorb_tq) and ran coherent end-to-end on GLM-5.2 744B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7d62599 to
9b7c577
Compare
Summary
Quantized KV cache for the MLA latent across all three backends, as opt-in tiers:
KV8=1β fp8 e4m3 + per-row scale (~3.9Γ less KV RAM than f32), CPU + CUDA + Metal.KV_TQ=4β 4-bit KV (~7.8Γ less): rotated-int4 + Lloyd-Max codebook by default (full reconstruction, so it corrects the MLA key and value), or PolarQuant (arXiv 2502.02617,KV_TQ_POLAR=1, variable 2-6 bit). CPU + Metal; under CUDA the quantized paths self-disable (guarded, falls back to CPU attention).COLI_METAL=0to force CPU) β Apple unified memory has no separate VRAM pool to overflow, so the CUDA-style opt-in gate doesn't apply.Supersedes #299/#300, which I withdrew for fleet soak-testing: the VRAM-budget interaction I flagged there (fp8 shadow vs
CUDA_EXPERT_GB=auto) is fixed in this series ("KV8 shadow-aware auto VRAM budget" + "KV_SHADOW auto keyed on ragged decode"), and the stack has since gathered the review fixes, vectorized fp8 kernels (15.8Γ prefill, 4-6Γ decode vs scalar), the 4-bit tier, and a second architecture (Metal) worth of validation.Commits (bisectable, each builds)
.coli_kvv2 β encoder bit-exact with the engine's consumers (RNE, per-row amax/448 scale); disk format v2 with quantize-on-upgrade from v1, reject-on-mismatch, self-heal.kv_tq.hcodecs (randomized-Hadamard rotation + recursive polar / rotated-int4 + Lloyd codebook), MSL kernelsa_fp8enc/a_score8/a_clat8/a_tq_enc/a_tq_deq, 3-wayqmodein the fused layer decode,.coli_kvv3 (COLIKV3,h[7]=codec<<8|bits), Metal opt-out default, tests.Why rotated-int4 for the 4-bit tier
MLA's latent is both key and value, so score-only fixes (QJL-style) leave the value error uncorrected. Full reconstruction through a randomized-Hadamard rotation + fixed 16-level Lloyd-Max codebook (N(0,1)-optimal, scaled by radius/βn) measures ~0.096 rel-L2 at 4 bits vs ~0.147 for recursive-polar β and the rotation makes the radius invariant, so one per-row scale survives. 4-bit KV is a quality trade (tiny-oracle: f32 32/32, KV8 30/32, TQ4-int4 23/32) β that's the physics of 4 bits against MLA, not codec slack; KV8 is the lossless-feeling tier, TQ4 the memory-constrained one.
Validation
make test-cgreen (new suites:test_kv_fp8exhaustive e4m3 roundtrip + RNE;test_kv_tqrotation self-inverse, roundtrip, distortion, inert rows;test_kv_diskv1/v2/v3 round-trip + upgrade/reject/self-heal;test_kv_alloc).make metal-testgreen β fp8 GPU encoder byte-exact vscoli_fp8_enc; TQ GPUβCPU roundtrip ~3e-8; fused attention parity vs CPU ~5e-6 for fp8/int4/polar; full-layer residual parity for f32/KV8/TQ (~4-5e-6).DRAFT=2decodes at 0.30 tok/s with 100% draft acceptance on a counting prompt (MTP head repaired per tools: repair int4-converted MTP heads in place; warn on --mtp --ebits <8Β #397 β the two features compose).!g_tqguards on the absorb gates β no TQ CUDA kernels exist yet, phase 2). Not re-compiled on this macOS machine; last CUDA build/test was the fleet run.Notes for review
KV8/KV_TQunset β byte-identical behavior; the quantized byte-caches are NULL and every consumer branches ong_kv8||g_tq).COLI_METAL=0) and non-Metal builds behave exactly as the CPU tier.π€ Generated with Claude Code