Skip to content

RDNA3.5 (gfx11 / gfx1151) MMQ prefill optimizations#32

Draft
liangliangchang wants to merge 6 commits into
ROCm:gfx11from
liangliangchang:lichang.mmq_opt
Draft

RDNA3.5 (gfx11 / gfx1151) MMQ prefill optimizations#32
liangliangchang wants to merge 6 commits into
ROCm:gfx11from
liangliangchang:lichang.mmq_opt

Conversation

@liangliangchang

@liangliangchang liangliangchang commented Jun 30, 2026

Copy link
Copy Markdown

Summary

RDNA3.5 (gfx11 / gfx1151) MMQ prefill optimizations and a documented HIP/clang codegen workaround.

MMQ optimizations (mmq.cuh)

  • Occupancy / tile sizing: reduce mmq_x when LDS limits workgroups/CU; dual-WG mmq_x=64 for narrow-N K-quant prefill (Q6_K FFN-down), gated to K-quants only.
  • Q4_K: improved dequant/vec_dot for WMMA; ldmatrix-friendly qs layout; precompute scale factors before WMMA accumulate.
  • Q6_K: hoist vec_dot scales; mmq_x=128 for narrow-N prefill; batched B-tile loads (load_generic); pipelined B loads in vec_dot; restore load_ldmatrix path.
  • Tiny-M prefill: specialized path for gate Q8_0 on RDNA3.5.
  • LDS / pipelining: tuned LDS layout; activation y-pipeline experiments were tried and the net-negative y-pipeline was dropped.

Benchmarks (gfx1151, ROCm)

Model TTFT Before TTFT After
Qwen3.5-9B_Q4_K_M_GGUF_128 117.21 104.07
Qwen3.5-4B_Q4_K_M_GGUF_128 70.65 64.43
llama-2-7b.Q4_K_M_128 100.6 87.85
Qwen2.5-7B-Instruct_Q4_K_M_GGUF_128 96.4 85.04
Qwen2.5-0.5B-Instruct_Q4_K_M_GGUF_128 14.4 12.04

Run with llama-bench (-p 128 -n 0 -ngl 999, flash-attn on). Rebuild MMQ after mmq.cuh changes: touch ggml/src/ggml-cuda/mmq.cuh && cmake --build build -j$(nproc).

Files changed

  • ggml/src/ggml-cuda/mmq.cuh — RDNA3.5 MMQ optimizations + MMQ_CODEGEN_SPLIT_COLD
  • docs/backend/HIP-MMQ-compiler-codegen.md — compiler issue write-up (new)

Test plan

  • Build with GGML_HIP=ON, target gfx11 / gfx1151
  • llama-bench on Q4_K_M and Q6_K models: pp128, narrow-N FFN-down shapes
  • Compare tok/s vs gfx11 base; confirm no regression vs pre-strip branch with codegen split
  • Optional ASM check: compile template-instances/mmq-instance-q4_k.cu and verify hot-path ds_load_b128 ≈ 48 (not 24) per mmq_x=128 kernel body — see doc

AI usage disclosure

Used Cursor/AI for profiling analysis, ASM comparison, codegen-split investigation, and documentation.

@liangliangchang liangliangchang changed the title Lichang.mmq opt RDNA3.5 (gfx11 / gfx1151) MMQ prefill optimizations Jun 30, 2026
@liangliangchang liangliangchang marked this pull request as ready for review June 30, 2026 23:22
Comment thread ggml/src/ggml-cuda/mmq.cuh Outdated
#if defined(GGML_USE_HIP)
// ROCm/clang WMMA codegen workaround: an unprovable-false branch keeps full unroll/inline
// on the hot K-loop. blockDim.z is always 1 for mul_mat_q. See docs/backend/HIP-MMQ-compiler-codegen.md.
#define MMQ_CODEGEN_SPLIT_COLD (threadIdx.z > 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach looks brittle. Let's please investigate further what affects the unroll decision. Can you try with an explicit unroll pragma?

@mgehre-amd mgehre-amd requested a review from roberteg16 July 1, 2026 08:57
@liangliangchang liangliangchang force-pushed the lichang.mmq_opt branch 2 times, most recently from b94c48c to 7483e49 Compare July 9, 2026 19:09
@liangliangchang

Copy link
Copy Markdown
Author

I ran this change with all models. Here is the current result: http://fisweb:8080/wrk/xcohdnobkup2/lichang/regression_comparison.html, will investigate it more.

@liangliangchang liangliangchang marked this pull request as draft July 10, 2026 05:03
@mgehre-amd

Copy link
Copy Markdown

I suggest to split this PR for improvements across kernels types (e.g. one PR to focus on Q8_0).
Then extract all shapes of of Q8_0 GEMMs appearing in our QoR suite, and use/build a kernel benchmark that runs the Q8_0 kernel with those shapes. (That benchmark should be committed; I don't know if llama.cpp already has one). Then you can run all shapes before your changes, and after your changes, and ensure that there are no regressions and some improvements.

Important: Benchmarking can be quite hard (dynamic frequency, thermal ramp up, ...) so it's important the the kernel benchmark reproduces the same per-kernel GPU time that the trace of the full model shows in our QoR suite.

liangliangchang and others added 6 commits July 10, 2026 09:36
- Add kernel dimension logging for profiling analysis
- Reduce mmq_x when LDS limits WGs/CU
- Improve LDS layout and activation load pipelining for prefill

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add opt-in HIP MMQ phase profiling to split dequant vs MMA time
- Refactor Q4_K qs load for WMMA ldmatrix layout
- Precompute Q4_K vec_dot scale factors before WMMA accumulate
- Gate dimension logs behind GGML_CUDA_MM_LOG
- Fix MMQ phase profiler clock64 deltas on HIP

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Hoist Q6_K vec_dot scales on RDNA3.5 WMMA
- Use mmq_x=128 for Q6_K narrow-N prefill; dual-WG mmq_x=64 where needed
- Pipeline Q6_K B-tile loads; batch B loads with load_generic
- Remove always-on MMQ profiling overhead on hot path
- Limit dual-WG mmq_x downgrade to K-quants

Co-authored-by: Cursor <cursoragent@cursor.com>
Add MMQ_HIP_TILE_BARRIER() tile-scope __syncthreads() at K-loop phase
boundaries on HIP+RDNA3.5 to restore pp128 throughput after removing dead
threadIdx.z cold/hot loop split.

Co-authored-by: Cursor <cursoragent@cursor.com>
The pipelined load_ldmatrix for B0 omitted the k01 term, reading the
wrong activation slice and corrupting Q6_K matmul results. Restores
sanity-check accuracy on Qwen2.5 and Llama-2 while keeping the prefetch.

Co-authored-by: Cursor <cursoragent@cursor.com>
@liangliangchang

Copy link
Copy Markdown
Author

I suggest to split this PR for improvements across kernels types (e.g. one PR to focus on Q8_0). Then extract all shapes of of Q8_0 GEMMs appearing in our QoR suite, and use/build a kernel benchmark that runs the Q8_0 kernel with those shapes. (That benchmark should be committed; I don't know if llama.cpp already has one). Then you can run all shapes before your changes, and after your changes, and ensure that there are no regressions and some improvements.

Important: Benchmarking can be quite hard (dynamic frequency, thermal ramp up, ...) so it's important the the kernel benchmark reproduces the same per-kernel GPU time that the trace of the full model shows in our QoR suite.

Thanks a lot @mgehre-amd, this sounds good to me. I'm aware we have one in vLLM for kernel benchmarking, will learn from it, and integrate with the existing one in llama.cpp.

@Annieren

Copy link
Copy Markdown

Looks like rocprofiler-compute (https://rocm.docs.amd.com/projects/rocprofiler-compute/en/latest/) can do a pretty good on profiling kernel but I haven't dig it deep. Fyr.

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.

3 participants