Skip to content

Commit 289b9dc

Browse files
TheTomclaude
andcommitted
sparse V: tile-level skip for CUDA tile FA (opt-in)
Replaces the per-element zero from the original PR #98 (which never saved compute since the V matmul still ran with val=0) with a tile-uniform skip of the entire V matmul block when no position contributes meaningfully. Per-warp shfl reduction of the per-thread max softmax probability, then a per-block fan-out via shared memory. Branch on the result so all threads take the same path — no warp divergence (was the cause of the April 24 revert in commit f2dc968 on the VEC kernel). Off by default. Opt in at build time: cmake -DGGML_CUDA_FLAGS=-DGGML_CUDA_TURBO_SPARSE_V_TILE # threshold defaults to 0.001f; override with # -DGGML_CUDA_FLAGS='-DGGML_CUDA_TURBO_SPARSE_V_TILE -DGGML_CUDA_TURBO_SPARSE_V_THRESHOLD=0.0001f' Mirror of vllm-project/vllm#41422 which validated this pattern on AMD MI300X: +7.13% decode @ 32K with PPL bit-identical and NIAH all-pass on Qwen3-8B. Default-off path is byte-identical to upstream (verified on M5 Max Metal: Qwen2.5-7B Q8_0 sym turbo3 PPL 6.6594, exact match). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e0954d1 commit 289b9dc

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

ggml/src/ggml-cuda/fattn-tile.cuh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
#include "fattn-common.cuh"
33
#include "fattn-wmma-f16.cuh"
44

5+
// Tile-level sparse V skip (TurboQuant). Off by default; opt in by defining
6+
// GGML_CUDA_TURBO_SPARSE_V_TILE at build time. Threshold defaults to 0.001
7+
// (matches vllm-project/vllm#41422 — bit-identical PPL + NIAH all-pass on
8+
// Qwen3-8B at 32K). Override with -DGGML_CUDA_TURBO_SPARSE_V_THRESHOLD=<val>.
9+
#if defined(GGML_CUDA_TURBO_SPARSE_V_TILE) && !defined(GGML_CUDA_TURBO_SPARSE_V_THRESHOLD)
10+
#define GGML_CUDA_TURBO_SPARSE_V_THRESHOLD 0.001f
11+
#endif
12+
513
// nbatch_fa == number of KQ rows to process per iteration
614
// nbatch_K == number of K columns to load in parallel for KQ calculation
715

@@ -642,6 +650,19 @@ static __device__ __forceinline__ void flash_attn_tile_iter(
642650
KQ_max_new[0] = warp_reduce_max<np>(KQ_max_new[0]);
643651
}
644652

653+
// Tile-level sparse V skip (TurboQuant): cheaply track the max softmax
654+
// probability seen across this FA tile so we can skip the whole V matmul
655+
// block when no position contributes meaningfully. Opt-in via build flag
656+
// -DGGML_CUDA_TURBO_SPARSE_V_TILE -DGGML_CUDA_TURBO_SPARSE_V_THRESHOLD=0.001f.
657+
// The decision is block-uniform (all threads take the same branch via
658+
// shared-memory reduction), avoiding the per-lane warp divergence that
659+
// motivated the earlier April 24 revert (commit f2dc968).
660+
// Mirror of vllm-project/vllm#41422 which validated +7.13% decode @ 32K
661+
// on AMD MI300X with PPL bit-identical and NIAH all-pass.
662+
#ifdef GGML_CUDA_TURBO_SPARSE_V_TILE
663+
float thread_max_val = 0.0f;
664+
#endif
665+
645666
// Calculate KQ softmax, write to shared KQ buffer, re-scale VKQ accumulators:
646667
#pragma unroll
647668
for (int jc0 = 0; jc0 < cpw; jc0 += KQ_cs) {
@@ -665,6 +686,9 @@ static __device__ __forceinline__ void flash_attn_tile_iter(
665686
expf(KQ_acc[(i0/(np*warp_size))*cpw + jc] - KQ_max[jc]) : 0.0f;
666687
KQ_sum_add += val;
667688
tmp[i0/(np*warp_size)][jc1] = val;
689+
#ifdef GGML_CUDA_TURBO_SPARSE_V_TILE
690+
thread_max_val = fmaxf(thread_max_val, val);
691+
#endif
668692
}
669693
KQ_sum[jc] = KQ_sum[jc]*KQ_max_scale + KQ_sum_add;
670694

@@ -693,12 +717,32 @@ static __device__ __forceinline__ void flash_attn_tile_iter(
693717
}
694718
}
695719

720+
#ifdef GGML_CUDA_TURBO_SPARSE_V_TILE
721+
// Block-level reduction of thread_max_val. Per-warp shfl reduce, write to
722+
// shared, syncthreads, fan back out — leaves block_max_val identical and
723+
// warp-uniform across all threads. Cost: one __syncthreads + cheap fp ops.
724+
thread_max_val = warp_reduce_max<warp_size>(thread_max_val);
725+
__shared__ float sparse_v_warp_max[nwarps];
726+
if (threadIdx.x == 0) sparse_v_warp_max[threadIdx.y] = thread_max_val;
727+
__syncthreads();
728+
float block_max_val = 0.0f;
729+
#pragma unroll
730+
for (int w = 0; w < nwarps; ++w) {
731+
block_max_val = fmaxf(block_max_val, sparse_v_warp_max[w]);
732+
}
733+
constexpr float sparse_v_threshold = GGML_CUDA_TURBO_SPARSE_V_THRESHOLD;
734+
const bool skip_v_matmul = block_max_val < sparse_v_threshold;
735+
#endif
736+
696737
// VKQ = V @ KQ matrix multiplication:
697738
static_assert(DV <= DKQ, "bad DV");
698739
static_assert(DV % nbatch_K == 0 || (nbatch_K % 3 == 0 && DV % (nbatch_K*2/3) == 0), "bad nbatch_K");
699740
constexpr int nbatch_V = (DV % nbatch_K == 0 ? nbatch_K : nbatch_K*2/3) * nbatch_fa / DV; // Number of V columns that fit in SRAM for K.
700741
static_assert(nbatch_fa % nbatch_V == 0, "bad nbatch_V");
701742
static_assert(nbatch_V % np == 0, "bad nbatch_V");
743+
#ifdef GGML_CUDA_TURBO_SPARSE_V_TILE
744+
if (!skip_v_matmul) {
745+
#endif
702746
#pragma unroll
703747
for (int k0 = 0; k0 < nbatch_fa; k0 += nbatch_V) {
704748
flash_attn_tile_load_tile<warp_size, nwarps, nbatch_V, DV, 0, oob_check>
@@ -769,6 +813,9 @@ static __device__ __forceinline__ void flash_attn_tile_iter(
769813

770814
__syncthreads();
771815
}
816+
#ifdef GGML_CUDA_TURBO_SPARSE_V_TILE
817+
} // close if (!skip_v_matmul)
818+
#endif
772819
}
773820

774821
template<int DKQ, int DV, int ncols1, int ncols2, bool use_logit_softcap> // D == head size

0 commit comments

Comments
 (0)