Skip to content

Commit fee38ef

Browse files
kmbandyclaude
andcommitted
fix(cuda/paged-attn): finite softmax mask in decode + scalar kernels
Follow-up to 39d38c1 (which fixed the prefill tile). mt_pagedattn_decode.cu and the scalar mt_pagedattn.cu are compiled with -ffast-math (-ffinite-math-only, CMakeLists.txt:218) but still used IEEE -INFINITY for softmax init / causal mask / max-reduction sentinels / equality tests — the same UB class that miscompiled the WMMA prefill tile into "////" garbage. Decode empirically survived, but it's one codegen shift from the same failure. Replace all -INFINITY with a file-local SOFTMAX_MASK_VAL = -1.0e30f (finite, safely below any real logit; expf(-1e30f - x) underflows to 0), mirroring the prefill-tile fix. Verified R9700 Qwen3.6-27B decode coherent post-change, 0 decode failures. Now the entire paged-attn stack (prefill tile + flash decode + scalar) is safe under fast-math. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GxHFe5y6ACxqFmFFzDBwtg
1 parent 39d38c1 commit fee38ef

2 files changed

Lines changed: 31 additions & 19 deletions

File tree

ggml/src/ggml-cuda/mt_pagedattn.cu

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
namespace mt {
1919

20+
// Finite softmax-mask sentinel (mirrors mt_pagedattn_tile.cu). This TU is built
21+
// with -ffast-math (-ffinite-math-only), so IEEE negative-infinity is UB — the compiler
22+
// may delete -inf materialization / equality tests. -1e30f is finite and safely
23+
// below any real logit; expf(-1e30f - x) underflows to 0.
24+
static constexpr float SOFTMAX_MASK_VAL = -1.0e30f;
25+
2026
// ───────────────── env-var: fused vs separate scatter ─────────────────
2127
//
2228
// Default (unset or GGML_PAGED_FUSED=0): mt_scatter_kv_kernel runs first
@@ -145,7 +151,7 @@ __device__ __forceinline__ float block_reduce_max(float v, float * red_smem) {
145151
if (lane == 0) red_smem[warp] = v;
146152
__syncthreads();
147153

148-
float partial = (lane < NUM_WARPS) ? red_smem[lane] : -INFINITY;
154+
float partial = (lane < NUM_WARPS) ? red_smem[lane] : SOFTMAX_MASK_VAL;
149155
if (warp == 0) {
150156
partial = warp_reduce_max(partial);
151157
if (lane == 0) red_smem[0] = partial;
@@ -1415,7 +1421,7 @@ __global__ void mt_paged_attention_kernel(
14151421
// overflow limit on the previous full-pass kernel).
14161422
constexpr int CHUNK_SIZE = 256;
14171423

1418-
float running_max = -INFINITY;
1424+
float running_max = SOFTMAX_MASK_VAL;
14191425
float running_sum = 0.0f;
14201426
float acc[VEC_PER_THREAD];
14211427
#pragma unroll
@@ -1434,7 +1440,7 @@ __global__ void mt_paged_attention_kernel(
14341440
// block. tid==0 stores qk into the chunk-local logits[]. The
14351441
// block_reduce_max at the end of this phase provides the
14361442
// visibility barrier before Phase C reads logits[].
1437-
float chunk_max = -INFINITY;
1443+
float chunk_max = SOFTMAX_MASK_VAL;
14381444
for (int i = 0; i < chunk_len; ++i) {
14391445
const int token = chunk_start + i;
14401446
const int logical_block = token / BLOCK_SIZE;
@@ -1455,7 +1461,7 @@ __global__ void mt_paged_attention_kernel(
14551461
// All threads see the same physical → ternary is uniform;
14561462
// either all call block_reduce_sum or all skip it.
14571463
const float qk = (physical == kInvalidBlockTableEntry)
1458-
? -INFINITY
1464+
? SOFTMAX_MASK_VAL
14591465
: block_reduce_sum<NUM_WARPS>(partial_qk, red_smem) * scale;
14601466

14611467
if (tid == 0) logits[i] = qk;
@@ -1465,7 +1471,7 @@ __global__ void mt_paged_attention_kernel(
14651471

14661472
// ── Phase B: rescale running state to new_max ──
14671473
const float new_max = max(running_max, chunk_max);
1468-
if (running_max != -INFINITY) {
1474+
if (running_max != SOFTMAX_MASK_VAL) {
14691475
const float rescale = __expf(running_max - new_max);
14701476
running_sum *= rescale;
14711477
#pragma unroll

ggml/src/ggml-cuda/mt_pagedattn_decode.cu

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ namespace mt {
3636

3737
using namespace ggml_cuda_mma;
3838

39+
// Finite softmax-mask sentinel (mirrors mt_pagedattn_tile.cu). This TU is built
40+
// with -ffast-math (-ffinite-math-only), so IEEE negative-infinity is UB — the compiler
41+
// may delete -inf materialization / equality tests. -1e30f is finite and safely
42+
// below any real logit; expf(-1e30f - x) underflows to 0.
43+
static constexpr float SOFTMAX_MASK_VAL = -1.0e30f;
44+
3945
// MAD-180 follow-up (2026-05-18): WMMA decode kernel for HEAD_SIZE=128/256.
4046
//
4147
// The original mt_paged_attention_decode_kernel was designed BEFORE the GQA
@@ -495,7 +501,7 @@ static __device__ __forceinline__ float decode_block_reduce_max(
495501
v = decode_warp_reduce_max(v);
496502
if (lane == 0) red_smem[wid] = v;
497503
__syncthreads();
498-
float partial = (threadIdx.x < DECODE_NUM_WARPS) ? red_smem[lane] : -INFINITY;
504+
float partial = (threadIdx.x < DECODE_NUM_WARPS) ? red_smem[lane] : SOFTMAX_MASK_VAL;
499505
if (wid == 0) {
500506
partial = decode_warp_reduce_max(partial);
501507
if (lane == 0) red_smem[0] = partial;
@@ -637,7 +643,7 @@ __global__ void mt_paged_attention_decode_kernel(
637643
float running_sum[DECODE_MAX_Q];
638644
#pragma unroll
639645
for (int qhqi = 0; qhqi < DECODE_MAX_Q; ++qhqi) {
640-
running_max[qhqi] = -INFINITY;
646+
running_max[qhqi] = SOFTMAX_MASK_VAL;
641647
running_sum[qhqi] = 0.0f;
642648
#pragma unroll
643649
for (int v = 0; v < VEC_PER_THREAD; ++v) v_acc[v][qhqi] = 0.0f;
@@ -679,7 +685,7 @@ __global__ void mt_paged_attention_decode_kernel(
679685
const int qi = qhqi % q_len; // all q_heads in a group share q_pos
680686
const int q_pos_qi = q_pos_first + qi;
681687
const bool visible = (t < sub_len) && (token <= q_pos_qi);
682-
smem_logits[qhqi * DECODE_K_TILE_N + t] = visible ? (qk * scale) : -INFINITY;
688+
smem_logits[qhqi * DECODE_K_TILE_N + t] = visible ? (qk * scale) : SOFTMAX_MASK_VAL;
683689
}
684690
}
685691
}
@@ -691,12 +697,12 @@ __global__ void mt_paged_attention_decode_kernel(
691697
for (int qhqi = 0; qhqi < total_q; ++qhqi) {
692698
float local_max = (tid < DECODE_K_TILE_N)
693699
? smem_logits[qhqi * DECODE_K_TILE_N + tid]
694-
: -INFINITY;
700+
: SOFTMAX_MASK_VAL;
695701
const float sub_max = decode_block_reduce_max(local_max, red_smem);
696702

697703
const float new_max = max(running_max[qhqi], sub_max);
698704
float rescale = 1.0f;
699-
if (running_max[qhqi] > -INFINITY) {
705+
if (running_max[qhqi] > SOFTMAX_MASK_VAL) {
700706
rescale = __expf(running_max[qhqi] - new_max);
701707
running_sum[qhqi] *= rescale;
702708
#pragma unroll
@@ -706,7 +712,7 @@ __global__ void mt_paged_attention_decode_kernel(
706712
float local_sum = 0.0f;
707713
if (tid < DECODE_K_TILE_N) {
708714
const float lg = smem_logits[qhqi * DECODE_K_TILE_N + tid];
709-
const float e = (lg == -INFINITY) ? 0.0f : __expf(lg - new_max);
715+
const float e = (lg == SOFTMAX_MASK_VAL) ? 0.0f : __expf(lg - new_max);
710716
smem_logits[qhqi * DECODE_K_TILE_N + tid] = e;
711717
local_sum = e;
712718
}
@@ -864,7 +870,7 @@ __global__ void mt_paged_attention_decode_kernel_wmma(
864870
const size_t off = base + (size_t) qi * (HEAD_SIZE + 2);
865871
for (int d = tid; d < HEAD_SIZE; d += 128) partials[off + d] = 0.0f;
866872
if (tid == 0) {
867-
partials[off + HEAD_SIZE] = -INFINITY;
873+
partials[off + HEAD_SIZE] = SOFTMAX_MASK_VAL;
868874
partials[off + HEAD_SIZE + 1] = 0.0f;
869875
}
870876
}
@@ -924,7 +930,7 @@ __global__ void mt_paged_attention_decode_kernel_wmma(
924930
// Online softmax state. Each lane owns 8 cols of one Q row; the pair lane
925931
// (tid ^ 16) owns the other 8 cols of the same row. shfl_xor(_, 16) merges
926932
// the two halves for per-row reductions.
927-
float running_max = -INFINITY;
933+
float running_max = SOFTMAX_MASK_VAL;
928934
float running_sum = 0.0f;
929935

930936
tile<16, 16, float, DATA_LAYOUT_I_MAJOR> acc[N_INNER];
@@ -994,18 +1000,18 @@ __global__ void mt_paged_attention_decode_kernel_wmma(
9941000
const int col = 8 * (lane / 16) + l;
9951001
const int k_pos = sub_start + col;
9961002
const bool visible = row_valid && (col < sub_len) && (k_pos < valid_ctx_max) && (k_pos <= q_pos_qi);
997-
scores.x[l] = visible ? (scores.x[l] * scale) : -INFINITY;
1003+
scores.x[l] = visible ? (scores.x[l] * scale) : SOFTMAX_MASK_VAL;
9981004
}
9991005

10001006
// Per-row max
1001-
float local_max = -INFINITY;
1007+
float local_max = SOFTMAX_MASK_VAL;
10021008
#pragma unroll
10031009
for (int l = 0; l < scores.ne; ++l) local_max = max(local_max, scores.x[l]);
10041010
const float row_max = max(local_max, __shfl_xor_sync(0xFFFFFFFF, local_max, 16, WARP_SIZE));
10051011

10061012
const float new_max = max(running_max, row_max);
10071013
float rescale = 1.0f;
1008-
if (running_max > -INFINITY) {
1014+
if (running_max > SOFTMAX_MASK_VAL) {
10091015
rescale = __expf(running_max - new_max);
10101016
running_sum *= rescale;
10111017
#pragma unroll
@@ -1018,7 +1024,7 @@ __global__ void mt_paged_attention_decode_kernel_wmma(
10181024
float local_sum = 0.0f;
10191025
#pragma unroll
10201026
for (int l = 0; l < scores.ne; ++l) {
1021-
const float e = (scores.x[l] == -INFINITY) ? 0.0f : __expf(scores.x[l] - new_max);
1027+
const float e = (scores.x[l] == SOFTMAX_MASK_VAL) ? 0.0f : __expf(scores.x[l] - new_max);
10221028
scores.x[l] = e;
10231029
local_sum += e;
10241030
}
@@ -1156,7 +1162,7 @@ __global__ void mt_paged_attention_decode_reduce_kernel(
11561162
const size_t qi_stride = (size_t) qi * (size_t) (HEAD_SIZE + 2);
11571163

11581164
// Pass 1: global max across chunks for this query position.
1159-
float global_max = -INFINITY;
1165+
float global_max = SOFTMAX_MASK_VAL;
11601166
for (int c = 0; c < valid_chunks; ++c) {
11611167
const float m = partials[partial_seq_base + (size_t) c * chunk_stride_q + qi_stride + HEAD_SIZE];
11621168
global_max = max(global_max, m);
@@ -1168,7 +1174,7 @@ __global__ void mt_paged_attention_decode_reduce_kernel(
11681174
for (int c = 0; c < valid_chunks; ++c) {
11691175
const size_t cbase = partial_seq_base + (size_t) c * chunk_stride_q + qi_stride;
11701176
const float c_max = partials[cbase + HEAD_SIZE];
1171-
if (c_max == -INFINITY) continue;
1177+
if (c_max == SOFTMAX_MASK_VAL) continue;
11721178
const float c_sum = partials[cbase + HEAD_SIZE + 1];
11731179
const float w = __expf(c_max - global_max);
11741180
global_sum += c_sum * w;

0 commit comments

Comments
 (0)