Skip to content

mmq: compacted MoE tiling for RDNA3.5 (default on)#63

Draft
roberteg16 wants to merge 1 commit into
gfx11from
rogarcia.mmq-moe-compact-tiling
Draft

mmq: compacted MoE tiling for RDNA3.5 (default on)#63
roberteg16 wants to merge 1 commit into
gfx11from
rogarcia.mmq-moe-compact-tiling

Conversation

@roberteg16

@roberteg16 roberteg16 commented Jul 21, 2026

Copy link
Copy Markdown

Summary

Compacted grid for the MMQ Mixture-of-Experts matmul on RDNA3.5, enabled by default (previously opt-in via GGML_CUDA_MMQ_MOE_COMPACT, now removed). Modeled on vLLM's moe_align_block_size + fused_moe_kernel.

Instead of the worst-case (nty, ntx, ne02) grid -- where ntx = ceil(ne12/mmq_x) assumes all tokens land on one expert, so most ne02 * ntx column-blocks belong to experts that early-exit -- this launches only the minimum number of mmq_x-wide column blocks implied by the actual per-expert token counts (padded to mmq_x).

  • mmq_build_moe_block_map (single block, parallel prefix sum in shared memory +
    parallel binary-search scatter) builds from expert_bounds:
    • block_start[] -- exclusive prefix sum of ceil(tokens_e / mmq_x)
      (block_start[ne02] = total real blocks, the device-side "num_tokens_post_padded").
    • block_expert[] -- compacted column-block -> expert.
  • The conventional-tiling kernel decodes blockIdx -> (it, jt, zt) via those arrays;
    padding blocks self-exit (m_block >= block_start[n_experts]).
  • Grid is sized to the host-known upper bound
    ceil((ne_get_rows + ne02*(mmq_x-1)) / mmq_x) -> fixed per shape, no host readback
    (graph-safe).

Scope / safety

  • Gated on expert_bounds != nullptr && GGML_CUDA_CC_IS_RDNA3_5(cc). The
    block_expert pointer is the mode flag: non-null only for this launch.
  • Dense, multi-GPU split and stream-K (NVIDIA/CDNA) paths are untouched
    (they pass nullptr, nullptr, 0).
  • The alternate (it, jt, zt, wt) decode only re-enumerates the tiles the legacy grid
    would have kept; wt = 0 is exact (MoE asserts ne13 == 1), zt/jt stay in the
    same ranges, padding blocks return before indexing block_expert (no OOB).
  • Falls back to the legacy grid if max_m_blocks >= MMQ_MAX_GRIDDIM_Y (65535).
  • mm_ids_helper is left unmodified.

Verification (gfx1151, Strix Halo)

Decode is provably unaffected (rocprofiler / GGML_ROOFLINE_OUT)

Per-op kernel traces confirm the compacted path is prefill-only:

  • Decode (tg): MUL_MAT_ID dispatches mul_mat_vec_q (MMVQ) + quantize_q8_1. Zero mul_mat_q and zero mmq_build_moe_block_map dispatches (0 of 603437 kernel launches in the tg128 trace).
  • Prefill (pp): MUL_MAT_ID dispatches mul_mat_q + quantize_mmq_q8_1 + mm_ids_helper + mmq_build_moe_block_map.

So enabling by default carries no decode risk: the MoE GEMM in decode never enters the MMQ path.

Prefill throughput, 4 MoE models (branch vs gfx11 integration baseline)

llama-bench -ngl 999 -n 128 -r 10 -t 8 -fa 1 --poll 50 -ctk f16 -ctv f16, uncontended via gpu-lock, ROCBLAS_USE_HIPBLASLT=1.

model pp128 pp512 pp1024 pp4096
Qwen3-30B-A3B-Instruct-2507 +5.1% +2.1% +1.0% +1.3%
Qwen3.6-35B-A3B (Q4_K_M) +5.4% +4.5% +4.7% +4.4%
Qwen3.5-35B-A3B (Q4_K_M) +3.6% +4.2% +3.5% +3.3%
gemma-4-26B-A4B-it +2.4% +1.9% +3.0% +2.2%

Decode is unaffected (same-build A/B)

Toggling the compacted path on/off in the same build leaves decode throughput at parity (tg128, -n 128 -r 10):

model compact on compact off Δ
Qwen3.6-35B-A3B (Q4_K_M) 58.33 ± 0.08 58.42 ± 0.09 -0.15% (noise)
Qwen3.5-35B-A3B (Q4_K_M) 57.75 ± 0.07 57.75 ± 0.07 0.0%

Test plan

  • Roofline traces: MMQ compact path active in prefill, absent in decode.
  • E2E llama-bench prefill A/B across 4 MoE models (above).
  • test-backend-ops -o MUL_MAT_ID with default-on: 790/790 pass (gfx1151).

@roberteg16
roberteg16 force-pushed the rogarcia.mmq-moe-compact-tiling branch 2 times, most recently from fe413e4 to 3074413 Compare July 21, 2026 09:04
Launch only the minimum number of mmq_x-wide column blocks implied by the
actual per-expert token counts (padded to mmq_x) instead of the worst-case
(ne02 * ceil(ne12/mmq_x)) grid. Modeled on vLLM's moe_align_block_size +
fused_moe_kernel: a device kernel (mmq_build_moe_block_map) builds block_start
(exclusive prefix sum of ceil(tokens_e/mmq_x)) and block_expert (block ->
expert) from expert_bounds; the conventional-tiling kernel decodes
blockIdx -> (it, jt, zt) via those arrays and padding blocks self-exit. The
grid is sized to the host-known upper bound (graph-safe, no host readback).

Gated on expert_bounds != null && RDNA3.5 && env GGML_CUDA_MMQ_MOE_COMPACT
(default off). Dense, multi-GPU split and stream-K paths are unchanged.

E2E Qwen3.6-35B-A3B (gfx1151, Q4_K_M, prefill, -r 40): +2.0-3.4% t/s at
pp128/1024/4096. test-backend-ops MUL_MAT_ID: 790/790 pass.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
@roberteg16
roberteg16 force-pushed the rogarcia.mmq-moe-compact-tiling branch from 3074413 to f8c07eb Compare July 21, 2026 09:47
@roberteg16 roberteg16 changed the title mmq: opt-in compacted MoE tiling for RDNA3.5 mmq: compacted MoE tiling for RDNA3.5 (default on) Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant