kernels: attention sinks + sliding window in decode attention

decode_attention_bf16_kernel gains an optional per-head sink logit and a
sliding-window kv_start. The sink joins the softmax max+denominator but
contributes no value (rebase max to include it, add exp(sink-m) to the
denom, scale O accordingly); window>0 restricts keys to the last `window`.
New launch_decode_attention_sink_bf16 + decode_attention_sink() wrapper;
the existing launch_decode_attention_bf16 passes nullptr/0 so qwen3's
decode path is byte-for-byte unchanged. Builds green on dash5.

First piece of the gpt-oss performance path (GPU sink-attention).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 21:28:27 +08:00
parent 2a515de7df
commit b4db9535db

View File

@@ -216,7 +216,9 @@ __global__ void decode_attention_bf16_kernel(
__nv_bfloat16* __restrict__ O,
int num_q_heads, int num_kv_heads,
int kv_len, int head_dim,
float scale
float scale,
const float* __restrict__ sinks, // [num_q_heads] or nullptr (gpt-oss)
int window // >0: only attend last `window` keys; 0: full
) {
int bh = blockIdx.x;
int batch_idx = bh / num_q_heads;
@@ -228,6 +230,10 @@ __global__ void decode_attention_bf16_kernel(
int tid = threadIdx.x;
// Sliding window: the query is at position kv_len-1 (decode appends it), so it
// attends to keys [kv_start, kv_len). kv_start=0 for full attention.
int kv_start = (window > 0 && kv_len > window) ? (kv_len - window) : 0;
// Pointers to this batch/head's data
// Q: [batch, num_q_heads, 1, head_dim]
const __nv_bfloat16* Q_ptr = Q + ((long long)batch_idx * num_q_heads + q_head) * head_dim;
@@ -251,7 +257,7 @@ __global__ void decode_attention_bf16_kernel(
local_O[d] = 0.0f;
}
for (int pos = tid; pos < kv_len; pos += DECODE_THREADS) {
for (int pos = kv_start + tid; pos < kv_len; pos += DECODE_THREADS) {
// Compute dot(Q, K[pos]) * scale
const __nv_bfloat16* K_pos = K_base + pos * head_dim;
float dot = 0.0f;
@@ -336,8 +342,20 @@ __global__ void decode_attention_bf16_kernel(
__syncthreads();
global_sum = smem_sum[0];
// gpt-oss attention sink: a per-head learned logit that joins the softmax
// denominator but contributes no value. Rebase max to include it, then add
// exp(sink - m_new) to the denominator. O was accumulated relative to
// global_max, so it picks up a factor exp(global_max - m_new).
float o_scale = 1.0f;
if (sinks != nullptr) {
float sink = sinks[q_head];
float m_new = fmaxf(global_max, sink);
o_scale = expf(global_max - m_new);
global_sum = global_sum * o_scale + expf(sink - m_new);
}
// Step 4: Reduce O across block (dimension by dimension using shared mem)
float inv_sum = (global_sum > 0.0f) ? (1.0f / global_sum) : 0.0f;
float inv_sum = (global_sum > 0.0f) ? (o_scale / global_sum) : 0.0f;
// Process head_dim in chunks: each iteration reduces one dimension
// Use shared memory accumulator: each warp contributes via warp reduction + atomic