Skip to content

perf(cuda): Q6_K decode MMQ kernel — per-16 int8 scales vs the m16n8k32 fragment (#203 item 1, ~5 ms/step at N=8) #204

Description

@pekkah

Child of #203 (umbrella; per-shape data there). After #201/PR #202, the Q6_K tensors are the largest batched-decode chunk still on the bit-exact WS matvecs at N=8:

shape ms/step vs floor
ffn_down [4096x12288] (Q6_K half) 4.77 ~3.2x
lm-head [151936x4096] 2.76 ~2.7x
attn_v [1024x4096] 0.77 ~16x

The Q4_K n16 decode MMQ (llm_mmq_q4k_soa_acts_n16) showed the shape of the win (gate/up 11.6 -> 6.9 ms/step); Q6_K needs its own kernel because its int8 scales are per 16 elements while the m16n8k32 mma reduces k=32 into one s32 accumulator — the two scale groups inside a fragment cannot be folded after the dot.

Candidate designs (pick after a probe):

  1. k-permuted fragments: permute the k-dimension (same permutation on A and B) so each mma`s k=32 covers... still mixes two scale groups in the accumulator — only works combined with (2).
  2. Two mmas per 32 elements with zero-masked halves — 2x mma count, simple, scales fold per-16 cleanly; mma is cheap at decode tile sizes (the n16 kernel is staging-bound, not mma-bound).
  3. **Lift llama.cpps mmq q6_K tiling** (their vec_dot_q6_K_q8_1_mma` path) wholesale.
  4. Skip the lm-head: it runs once per step and is already 2.7x floor; ffn_down/attn_v are the per-layer wins.

Contract: argmax-stable under the existing SHARPI_BATCH_DECODE_MMQ=1 gate, same per-tensor dispatch fallback as Q4_K. A Q6_K SoA weight repack (like #156`s Q4_K one) may be a prerequisite for coalesced staging — the 210-B AoS block is word-hostile.

Validation pattern: CudaDecodeMmqTests (fp32 CPU reference per-RMS tolerance) + the BatchForwardMulti MMQ argmax oracle.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions