1- From 80d308615f1dc9fc601f556123861a651b7cffa5 Mon Sep 17 00:00:00 2001
1+ From 784b441f8b098863343e69e0345ef62d2ff580af Mon Sep 17 00:00:00 2001
22From: Daniel Han <
[email protected] >
33Date: Sat, 3 Oct 2026 18:27:29 -0700
44Subject: [PATCH] ggml-cuda: optional cuDNN attention for unmasked
@@ -11,7 +11,7 @@ only cudnn.h is needed at build time and libcudnn.so.9 is dlopen'ed on first use
1111(GGML_CUDA_CUDNN_LIB overrides the name), so the binary has no cuDNN link
1212dependency and runs unchanged where cuDNN is missing.
1313
14- On NVIDIA sm80+ it takes unmasked, sink/ALiBi/softcap-free attention with equal
14+ On NVIDIA sm80+ it can take unmasked, sink/ALiBi/softcap-free attention with equal
1515Q/K/V head sizes (multiple of 8, up to 256) and at least 2^20 attention scores,
1616GQA and batch included, for FLASH_ATTN_EXT and for SAGE_ATTN in AUTO mode (an
1717explicitly requested Sage variant keeps its kernel). Q (F32) is narrowed to F16
@@ -22,24 +22,28 @@ kernels' needs). Plans are built once per shape and cached; a shape cuDNN
2222declines, a failed build or execute, stream capture, Turing and older, or a
2323missing library keep the existing kernels.
2424
25- GGML_CUDA_CUDNN_ATTN=0 disables it, GGML_CUDA_CUDNN_SAGE=0 keeps the Sage kernel,
26- GGML_CUDA_CUDNN_ATTN_BF16=1 runs BF16, GGML_CUDA_CUDNN_ATTN_LOG=1 logs each call.
25+ By default FLASH_ATTN_EXT takes it on sm89 and sm100 and SAGE_ATTN on sm100,
26+ where it measured faster; the existing kernels were faster on sm86 (both ops)
27+ and sm89 (Sage), and other architectures were not measured.
28+ GGML_CUDA_CUDNN_ATTN=0|1 and GGML_CUDA_CUDNN_SAGE=0|1 turn it off or on for every
29+ sm80+ GPU, GGML_CUDA_CUDNN_ATTN_BF16=1 runs BF16, GGML_CUDA_CUDNN_ATTN_LOG=1
30+ logs each call.
2731
2832test-backend-ops: unmasked cases at the MiniMax-H3 head layout (odd and aligned
2933lengths, GQA with batch, F32 K/V, permuted Q) and unmasked perf cases.
3034---
3135 CMakeLists.txt | 3 +
3236 src/ggml-cuda/CMakeLists.txt | 49 ++++
33- src/ggml-cuda/cudnn-attn-graph.cpp | 258 ++++++++++++++++++++
37+ src/ggml-cuda/cudnn-attn-graph.cpp | 258 +++++++++++++++++++
3438 src/ggml-cuda/cudnn-attn-graph.h | 31 +++
35- src/ggml-cuda/cudnn-attn.cu | 367 +++++++++++++++++++++++++++++
36- src/ggml-cuda/cudnn-attn.cuh | 25 ++
39+ src/ggml-cuda/cudnn-attn.cu | 384 +++++++++++++++++++++++++++++
40+ src/ggml-cuda/cudnn-attn.cuh | 26 ++
3741 src/ggml-cuda/fattn.cu | 7 +-
3842 src/ggml-cuda/ggml-cuda.cu | 2 +-
3943 src/ggml-cuda/sage-attn.cu | 12 +-
4044 src/ggml-cuda/sage-attn.cuh | 2 +-
4145 tests/test-backend-ops.cpp | 25 ++
42- 11 files changed, 775 insertions(+), 6 deletions(-)
46+ 11 files changed, 793 insertions(+), 6 deletions(-)
4347 create mode 100644 src/ggml-cuda/cudnn-attn-graph.cpp
4448 create mode 100644 src/ggml-cuda/cudnn-attn-graph.h
4549 create mode 100644 src/ggml-cuda/cudnn-attn.cu
@@ -427,10 +431,10 @@ index 00000000..9749f2ab
427431+ void * q, void * k, void * v, void * o, void * workspace);
428432diff --git a/src/ggml-cuda/cudnn-attn.cu b/src/ggml-cuda/cudnn-attn.cu
429433new file mode 100644
430- index 00000000..b54c29af
434+ index 00000000..a5b9de14
431435--- /dev/null
432436+++ b/src/ggml-cuda/cudnn-attn.cu
433- @@ -0,0 +1,367 @@
437+ @@ -0,0 +1,384 @@
434438+ #include "cudnn-attn.cuh"
435439+
436440+ #if defined(GGML_CUDA_USE_CUDNN) && !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
@@ -470,11 +474,20 @@ index 00000000..b54c29af
470474+ return t->nb[0] == ggml_type_size(t->type) && t->nb[1] % 16 == 0 && t->nb[2] % 16 == 0 && t->nb[3] % 16 == 0;
471475+ }
472476+
477+ + // -1 when unset, otherwise 0 or 1.
478+ + static int cudnn_attn_env_mode(const char * name) {
479+ + const char * v = std::getenv(name);
480+ + if (v == nullptr || v[0] == '\0') {
481+ + return -1;
482+ + }
483+ + return cudnn_attn_env_set(name, true) ? 1 : 0;
484+ + }
485+ +
473486+ static bool cudnn_attn_make_plan(int device, const ggml_tensor * dst, cudnn_attn_plan & plan) {
474- + static const bool enabled = cudnn_attn_env_set ("GGML_CUDA_CUDNN_ATTN", true );
475- + static const bool sage = cudnn_attn_env_set ("GGML_CUDA_CUDNN_SAGE", true );
487+ + static const int attn_mode = cudnn_attn_env_mode ("GGML_CUDA_CUDNN_ATTN");
488+ + static const int sage_mode = cudnn_attn_env_mode ("GGML_CUDA_CUDNN_SAGE");
476489+ static const bool want_bf16 = cudnn_attn_env_set("GGML_CUDA_CUDNN_ATTN_BF16", false);
477- + if (!enabled || (dst->op == GGML_OP_SAGE_ATTN && !sage )) {
490+ + if (attn_mode == 0 || (dst->op == GGML_OP_SAGE_ATTN && sage_mode == 0 )) {
478491+ return false;
479492+ }
480493+ if (dst->op != GGML_OP_FLASH_ATTN_EXT && dst->op != GGML_OP_SAGE_ATTN) {
@@ -488,6 +501,14 @@ index 00000000..b54c29af
488501+ if (!GGML_CUDA_CC_IS_NVIDIA(cc) || cc < GGML_CUDA_CC_AMPERE) {
489502+ return false;
490503+ }
504+ + // Unset switches keep the existing kernels except where cuDNN measured faster at H3 shapes: against the flash
505+ + // attention kernel on sm89 and sm100 (slower on sm86), against the Sage kernel on sm100 (slower on sm86 and
506+ + // sm89). Other architectures were not measured. =1 opts in on any sm80+ GPU.
507+ + const bool sm100 = cc >= 1000 && cc < GGML_CUDA_CC_BLACKWELL;
508+ + const bool sm89 = cc >= GGML_CUDA_CC_ADA_LOVELACE && cc < GGML_CUDA_CC_HOPPER;
509+ + if (dst->op == GGML_OP_SAGE_ATTN ? sage_mode < 0 && !sm100 : attn_mode < 0 && !sm89 && !sm100) {
510+ + return false;
511+ + }
491512+
492513+ const ggml_tensor * Q = dst->src[0];
493514+ const ggml_tensor * K = dst->src[1];
@@ -800,10 +821,10 @@ index 00000000..b54c29af
800821+ #endif
801822diff --git a/src/ggml-cuda/cudnn-attn.cuh b/src/ggml-cuda/cudnn-attn.cuh
802823new file mode 100644
803- index 00000000..f59689d5
824+ index 00000000..067bea63
804825--- /dev/null
805826+++ b/src/ggml-cuda/cudnn-attn.cuh
806- @@ -0,0 +1,25 @@
827+ @@ -0,0 +1,26 @@
807828+ #pragma once
808829+
809830+ #include "common.cuh"
@@ -814,10 +835,11 @@ index 00000000..f59689d5
814835+ // least 2^20 attention scores is taken; everything else, and every shape cuDNN rejects, keeps the
815836+ // existing kernels.
816837+ //
838+ + // By default FLASH_ATTN_EXT takes it on sm89 and sm100 and SAGE_ATTN on sm100, where it measured faster.
817839+ // Environment:
818- + // GGML_CUDA_CUDNN_ATTN=0 never use cuDNN for attention
819- + // GGML_CUDA_CUDNN_SAGE=0 keep the SageAttention kernels for GGML_OP_SAGE_ATTN (AUTO mode;
820- + // an explicitly requested Sage variant always keeps its kernel)
840+ + // GGML_CUDA_CUDNN_ATTN=0|1 never use cuDNN for attention / use it on every sm80+ GPU
841+ + // GGML_CUDA_CUDNN_SAGE=0|1 keep the SageAttention kernels for GGML_OP_SAGE_ATTN / use cuDNN on every
842+ + // sm80+ GPU (AUTO mode; an explicitly requested Sage variant keeps its kernel)
821843+ // GGML_CUDA_CUDNN_ATTN_BF16=1 run cuDNN in BF16 instead of F16 (F16 K/V are narrowed)
822844+ // GGML_CUDA_CUDNN_LIB=path library to open instead of libcudnn.so.9
823845+ // GGML_CUDA_CUDNN_ATTN_LOG=1 log every call that takes (or falls back from) the cuDNN path
0 commit comments