Repository navigation
cuda, hip: GDN chunked kernel - #29353
Conversation
Ported-from: tetherto#257 Source-commit: 53f0027 Co-authored-by: Aman Gupta <aman.gupta@tether.io>
|
I noticed the kernel only activates when |
|
it is a prefill kernel. So it won't be activated while drafting (i.e. when K > 1) |
|
I mean prefill with speculation enabled. |
|
Yes that's correct - prefill with speculation enabled would not use this kernel for now. It would require some changes from the llama.cpp side to use it for prefill, perhaps something like |
|
Also, would be pretty easy to port this to rdna4's wmma regsiter layout and wave64 for mfma, might not perform better but might as well support everything in the kernel since its minimal effort, do you want to do it or shall i do a followup? |
|
@IMbackK I don't have RDNA4 hardware. You can do it in a follow-up. |
|
When required, I can check it on gfx1201/RDNA (R9700, Windows 11, ROCm 10.0.0/HIP) |
Enable SM86 and SM89 after RTX 3090 and RTX 4090 correctness and prefill benchmarks. Keep other untested architectures on the existing fallback. Assisted-by: Codex
JohannesGaessler
left a comment
There was a problem hiding this comment.
I currently still consider the swizzling code in mma.cuh to be unstable. If you intend to use it in this PR I would ask that we delay merging it until I've taken another look at it.
|
I intentionally used it for reuse purpose. I can keep it completely separate if it's easier for review. |
|
I've started work on a refactor of the swizzling code to include AMD GPUs as well as strides that are not multiples of 128 bytes. I should be able to open a corresponding PR by the end of the weekend at the latest. |
|
Porbubly easier to just separate the prs? just remove the swizzling in this pr and add back later as as followup. |
Changes made satisfy identified defects, but i have not fully re-reviewed so no approval for now.
|
I have a prototype Vulkan version of this. One thing I found is that CUDA's GDN scan kernel is pretty bad, about half as fast as Vulkan's, which blows up the relative improvement shown here. |
| constexpr int N = 8; | ||
| #endif // GGML_USE_HIP | ||
| static_assert(C >= 16 && (C & (C - 1)) == 0 && V % N == 0 && D % V == 0, "invalid GDN tile"); | ||
| extern __shared__ __align__(128) unsigned char bytes[]; |
There was a problem hiding this comment.
Given we only have one source of __shared__ in the kernel, we should be good. But note that extern __shared__ __align__ + __shared__ with optional align can get you to SMEM-bank-conflict-land quickly (it's not clearly documented how these are supposed to be handled/aligned iirc)
| __syncthreads(); | ||
| for (int ti = warp; ti < (C / 16) * (V / N); ti += WARPS) { | ||
| const int r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N; | ||
| acc x; | ||
| product<C, false, true>(x, s.inverse, s.delta, r, c); | ||
| store(s.scratch.solved, x, r, c); | ||
| } | ||
| __syncthreads(); |
There was a problem hiding this comment.
Feels like warp-specialization may help here, but something that can be done in a follow-up PR
It's tuned specifically for bs=1 - given the increasing popularity for spec-dec we could try to revive #22587
Swizzling may (or may not) be required for good performance of the implementation at hand.
Would love if we could prioritize this, as spec-dec is increasingly popular. LMK if I can assist here, though I'll focus on FP8 support for the time being |
|
Refactor of the swizzling code: #29612 . Sorry, I forgot that I had conflicting obligations on the weekend so it was a bit delayed. Practically speaking the way the interface changed is that you need to explicitly call |
…s K == 1, we run K = 3); prefill unchanged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018co7fcJoEQXMxLezg7HpMU
There was a problem hiding this comment.
From my side I'm generally fine with accepting & co-maintaining this, especially as it keeps accuracy high by approximating F32 via 2 BF16s and keeping states in F32 (though some more comments/variable naming could help). What I would like for us to do before merging:
- Resolve roll-back-after-prefill-with-chunked-GDN gap (though this should not happen with current control flow)
- Adapt swizzling to refactored #29612
- Potentially recapture perf numbers as #30087 was merged in the mean-time. Given it brings ~4-8%, ~5-10% should be gained by this chunked implementation I guess.
For the interested, I looked to optimize it a bit more & to understand the code. Changes are over at am17an#59. It's about 10% operator-level speedup on a B4500 (and thus 0.5% E2E speed-up), so not really required to get this merged. Still was fun/helpful to me
|
@ggerganov can you guide on how to make 1) in the above happen? |
|
@am17an For what batch sizes does the new kernel run in the CUDA backend? |
|
It runs for batch sizes greater than 2048 mostly |
|
I think the simplest way is to declare that Lines 2641 to 2661 in 097cf33 With this restriction we'll later add guardrails in So for this PR, I think we can just update the |
|
If we agree on that, then the following tests would also need to be removed: llama.cpp/tests/test-backend-ops.cpp Lines 11930 to 11932 in 097cf33 |
# Conflicts: # ggml/src/ggml-cuda/gated_delta_net.cu # ggml/src/ggml-cuda/mma.cuh
|
@ORippler @ggerganov PTAL |
ggerganov
left a comment
There was a problem hiding this comment.
The ggml comment as discussed is OK. I will follow-up with the necessary libllama changes.
Prefill-sized GATED_DELTA_NET ops run a chunked tensor-core kernel instead of the per-token recurrent kernel. The raw gates are activated into temporaries first. With K > 1 state snapshots (one sequence), the MMA kernel runs the first n - K tokens and the recurrent kernel the last K, which write the snapshot slots. The fused state gather is supported: the live state rows are copied to a contiguous s0 first. One WARN line when the chunked path first engages, and one when a prefill-sized op skips it. BF16 MMA on Ampere GA10x / Ada and newer. Other cards are checked before any temporaries are allocated and keep the recurrent kernel (one WARN line with the device cc). GGML_CUDA_GDN_MMA_DISABLE=1 turns it off. Tests in the 27B shape. RTX 4070: prefill +3 to +6 % (drafting on); perplexity 8.0714 -> 8.0733 (+0.024 %, -c 2048, 32 chunks); exact copies and needles unchanged. RTX 2060 SUPER (sm_75): not taken; 32k prefill and decode unchanged.
…lama.cpp#29353) (#40) * patches: 0054 chunked gated-delta-net MMA prefill (port of ggml-org/llama.cpp#29353); README: rows 0050 and 0054, patch counts 54 * patches: 0054 checks the device before the GDN MMA temporaries (RTX 20: clean skip line, no allocations) --------- Co-authored-by: Cary Palmer <24235924+professorpalmer@users.noreply.github.com>
…#29353) Prefill-sized GATED_DELTA_NET ops run a chunked tensor-core kernel instead of the per-token recurrent kernel. The raw gates are activated into temporaries first. With K > 1 state snapshots (one sequence), the MMA kernel runs the first n - K tokens and the recurrent kernel the last K, which write the snapshot slots. The fused state gather is supported: the live state rows are copied to a contiguous s0 first. One WARN line when the chunked path first engages, and one when a prefill-sized op skips it. BF16 MMA on Ampere GA10x / Ada and newer. Other cards are checked before any temporaries are allocated and keep the recurrent kernel (one WARN line with the device cc). GGML_CUDA_GDN_MMA_DISABLE=1 turns it off. Tests in the 27B shape. RTX 4070: prefill +3 to +6 % (drafting on); perplexity 8.0714 -> 8.0733 (+0.024 %, -c 2048, 32 chunks); exact copies and needles unchanged. RTX 2060 SUPER (sm_75): not taken; 32k prefill and decode unchanged.
…ml-org#29963 rewrite re-tested and rejected
153 upstream commits (merge-base 1623d8c). Conflicts (6 files) and semantic fixes: - gated_delta_net.cu: fork kernels (column_lanes, indexed state, RDNA4 chunked WY prefill) kept; upstream GDN MMA path (ggml-org#29353, HIP only on RDNA3.5) runs after ours, skipped when the state gather was deferred to the op. - ggml-cuda.cu: n_stable reset kept, warmup reset log behind GGML_CUDA_GRAPH_DEBUG (ggml-org#30327). - llama-batch: upstream state embeddings for MTP hooks (ggml-org#30257) next to the fork embd alias; R57 extended to them: the allocator points at the batch state rows and a ubatch of consecutive rows reads them in place (no copies). - llama-context: upstream sched_new (realloc callback, ggml-org#30329) also used for the second MTP scheduler. - llama-graph: draft LM head and logits top-k kept next to build_hadamard_input / build_logits; the draft head applies the Hadamard input transform too. - llama-model: load_tensors_hadamard, then build_mtp_draft_head. - common: R59 full-ubatch warmup also sets zero state embeddings.
… of ggml-org#29353) Ported from professorpalmer/llama.cpp-ada-mirai b6bf51cdd.
The 16-head cases of ggml-org/llama.cpp#29353 (b11632): 3 and 4 sequences, GQA 2-4, 257..2048 tokens. Taken now so they also cover our chunked kernels. One V100: 56/56 GATED_DELTA_NET pass with GGML_CUDA_GDN_CHUNKED on and off; under ncu the cases from 257 tokens run chunk_prep / chunk_scan<32> / chunk_out, the 64/65 token cases the sequential kernel. The n_tokens > K snapshot cases that the PR deletes stay: our rollback reads those slots. Assisted-by: Claude Opus 5.5



upstreaming a kernel developed in downstream QVAC fabric. Credits also to @lukdmine for initial idea for the kernel.
Performance
All rows use
-b 2048 -ub 2048 -fa 1, full GPU offload and F16 KV cache. Retested on 2026-10-13 against master 296538a (includes #30087). TL;DR 3-6% better PP on dense 27B, 11-21% on 35B-A3B on RTX 4090/5090/PRO 6000, 3-6% on Spark.Details 👇
-lzm on)-lzm on)Strix Halo (Radeon 8060S) was not retested this round. On the previous master it was +6.75% (pp2048) and +6.98% (pp4096) on Qwen3.8-27B-UD-Q8_K_L.
The RTX 3090 reported thermal throttling during these measurements, so its numbers should be read with that limitation.
Correctness
llama-perplexity --kl-divergenceon wikitext-2Requirements