Skip to content

feat: fused turbo MMA flash-attention decode (turbo4/3/2, GQA tensor-core path) - #4

Merged
thecodacus merged 4 commits into
perffrom
fable5/turbo-mma-decode
Jul 18, 2026
Merged

feat: fused turbo MMA flash-attention decode (turbo4/3/2, GQA tensor-core path)#4
thecodacus merged 4 commits into
perffrom
fable5/turbo-mma-decode

Conversation

@thecodacus

Copy link
Copy Markdown
Owner

Ports the fused turbo MMA flash-attention decode path from TheTom/llama-cpp-turboquant (4 commits, cherry-picked with -x). Until now turbo KV decode always used the vector kernel; the turbo MMA files in-tree were declaration-only scaffolding.

What it does

For decode batches (ne1 ≤ 4) with matching turbo K/V types (turbo4/3/2, head dims 128/256; turbo2 gated to 128), attention now runs on the GQA-packed tensor-core MMA kernel, dequantizing turbo blocks directly into shared memory — no F16 conversion pass, no VEC serialization. Q rotation stays at the graph level (no inline FWHT; this path must not double-rotate).

GGML_TURBO_MMA_FUSED=0 is the kill-switch back to VEC (default ON upstream-of-us: correctness-validated, KLD-parity with VEC per the reference repo; not bit-identical — f16 reduction-order differences can flip a hard-tie greedy token, same as base MMA-vs-VEC f16).

Verified — RTX 3060

  • Coherence: Qwen3.6-35B-A3B, ctk/ctv turbo4, fused path → correct output ("The capital of France is Paris.")
  • Decode at depth (dense gemma-4-12b, -ngl 99, K=V=turbo4, tg64 @ d16384): VEC 26.90 ± 0.46 → MMA 30.11 ± 0.57 t/s (+12%)
  • Short-ctx MoE (35B, ncmoe 26): parity (48.8 vs 48.7) — CPU experts dominate, as expected
  • Prod-config regression (ctk q8_0 + ctv turbo2, mixed → stays on existing path): pp2048 521.5 / tg64 49.1 — unchanged

TheTom added 4 commits July 19, 2026 04:31
…ritbuun fork MMA approach, adapted to graph-rotated Q

Port buun's fused turbo4 tensor-core (MMA) flash-attention DECODE kernel into
the turbo fork as an OPT-IN path (GGML_TURBO_MMA_FUSED=1, default OFF), routing
turbo4-K==turbo4-V, D in {128,256}, decode (Q->ne[1]<=4) onto the GQA-packed MMA
path so KV is read once per head-group instead of once per query head.

How it works:
- fattn-mma-f16.cuh: add flash_attn_ext_turbo4_load_tile (dequant turbo4 blocks
  -> SRAM in the f16 row-major half2 layout) seeded with OUR Lloyd-Max centroids
  (turbo-quant.cuh:297, NOT buun's -0.2415xx table). Thread type_K/type_V through
  iter/process_tile/global flash_attn_ext_f16 (defaults F16 -> existing f16/q8 MMA
  byte-identical). For turbo: force nstages=0 (the cp.async multi-stage path would
  read raw turbo bytes as half2), pass RAW byte pitch nb11/nb21 (not /sizeof(half2)),
  and dequantize K/V in the load tiles. sizeof(block_turbo4_0)-driven pointer math
  (66B block, no rnorm) — never assumes a fixed offset.
- fattn-mma-turbo.cuh + 14 instance files (7 reachable ncols pairs x D in {128,256}):
  host launcher reusing the f16 device kernel with type_K/type_V=TURBO4_0 and
  need_f16_K/V=false so launch_fattn does NOT pre-convert KV to f16.
- fattn.cu: switch_ncols2 -> per-ncols2 dispatchers (only the 7 compiled instances),
  env latch, and the gate (Q untouched — our fork already rotates Q at the graph
  level, so NO inline FWHT / src swap, which would double-rotate Q).

Results (Qwen3.6-35B-A3B turbo4, GQA8, D=256, 5090):
  depth   MMA      VEC+PDL   buun
  8192    213.8    188.5     200    (+6.9% vs buun)
  16384   207.6    171.7     193    (+7.5% vs buun)
  32768   191.3    141.1     187    (+2.3% vs buun)
MMA holds flat at depth where VEC collapses; the gap vs VEC grows with KV length.

Quality: Mean KLD vs f16 base = 0.008396 (== VEC baseline). The MMA path is NOT
strictly token-identical to VEC — MMA and VEC accumulate the P*V (VKQ) reduction in
f16 with different reduction trees, so a near-tie greedy token can flip (~1/25 on a
hard tie; same irreducible f16-order effect that exists between base f16-MMA and
f16-VEC, and within the VEC-vs-base "same-top-p" 96.3% noise floor). Because strict
token-identity does not hold, the gate DEFAULTS OFF and ships as opt-in; VEC remains
the untouched default/kill-switch.

(cherry picked from commit 5450669)
(cherry picked from commit b3e51cf)
…eutral; GGML_TURBO_MMA_FUSED=0 kill-switch)

(cherry picked from commit 726234b)
(cherry picked from commit 38dc665)
…extends the MMA path to turbo3/2)

The GQA-packed tensor-core decode path was turbo4-only; turbo3/turbo2 fell back to
VEC and collapsed at depth (turbo3 tg32 224->124 from d2048->d32768), losing to
spiritbuun there. Added flash_attn_ext_turbo3_load_tile (3-bit split index: 2 low
bits from qs + 1 high bit from signs) and flash_attn_ext_turbo2_load_tile (plain
2-bit), type-dispatched both the K and V MMA load sites, and routed the gate by
K->type. is_turbo_kv already matches any non-f16 KV, so the kernel needed only the
per-type tile loaders + instances (14 instance files now emit all three types).

Correctness: MMA decode is bit-exact to VEC (Mean KLD @2048 chunks=8:
turbo3 0.020278==0.020278, turbo2 0.041490==0.041490, turbo4 0.009296 unchanged).

Decode tg32 (qwen3.6-35B-A3B, RTX 5090), MMA now flat at depth:
  turbo3  2k/8k/16k/32k = 224/225/217/203  (buun 193/193/183/178; was VEC 224/187/160/124)
  turbo2  2k/8k/16k/32k = 228/224/217/203  (buun 213/210/204/200; was VEC 229/218/203/183)

turbo3/turbo2 now hold flat at depth (neither fork had them on the MMA path before). GGML_TURBO_MMA_FUSED=0 restores VEC.

(cherry picked from commit 57ad5ed)
(cherry picked from commit 4e223ee)
Tester @everson isolated a turbo2-only decode regression on RTX 5060 Ti
(Gemma-12B, head_dim 256): turbo2/turbo2 -2.46% at 8K to -1.17% at 200K,
fully restored by GGML_TURBO_MMA_FUSED=0 at every depth, while turbo3 was
neutral and turbo4 exact parity. At 2-bit KV the fused path's GQA-pack
saving is tiny while its dequant/no-pipeline overhead is unchanged, so on
head_dim 256 it is neutral on high-bandwidth GPUs (Ornith-35B and a dense
Gemma-4-12B on RTX 5090, both within noise) and only costs on
bandwidth-limited cards.

turbo2 fused remains a large depth win on dense head_dim 128 (Llama-3.1-8B
on RTX 5090: +7% at 8K, +24% at 32K, +69% at 131K), so this gates turbo2
fused to head_dim 128 only; head_dim 256 turbo2 falls through to VEC, which
is the exact baseline path GGML_TURBO_MMA_FUSED=0 restored. turbo3 and
turbo4 fused are unchanged at both head dims.

Also corrects the stale "default OFF" gate comments to match the
default-ON code.

(cherry picked from commit 539ce5d)
@thecodacus
thecodacus merged commit 40f2036 into perf Jul 18, 2026
6 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants