Kernels
kernels-bot commited on
Commit
aaf9600
·
verified ·
1 Parent(s): 02904f3

Uploaded using `kernel-builder`.

Browse files
Files changed (29) hide show
  1. build/torch-rocm/__init__.py +32 -0
  2. build/torch-rocm/_kernels/__init__.py +0 -0
  3. build/torch-rocm/_kernels/flash_attn_triton_amd/__init__.py +4 -0
  4. build/torch-rocm/_kernels/flash_attn_triton_amd/bwd.py +0 -0
  5. build/torch-rocm/_kernels/flash_attn_triton_amd/common.py +573 -0
  6. build/torch-rocm/_kernels/flash_attn_triton_amd/fwd_decode.py +1475 -0
  7. build/torch-rocm/_kernels/flash_attn_triton_amd/fwd_prefill.py +1875 -0
  8. build/torch-rocm/_kernels/flash_attn_triton_amd/interface_v2.py +828 -0
  9. build/torch-rocm/_kernels/flash_attn_triton_amd/interface_v3.py +660 -0
  10. build/torch-rocm/_kernels/flash_attn_triton_amd/utils.py +338 -0
  11. build/torch-rocm/_kernels/mha.py +942 -0
  12. build/torch-rocm/_kernels/mha_fused_bwd.py +1073 -0
  13. build/torch-rocm/_kernels/mha_onekernel_bwd.py +1783 -0
  14. build/torch-rocm/_ops.py +38 -0
  15. build/torch-rocm/aiter_flash_attn/__init__.py +26 -0
  16. build/torch-rocm/metadata.json +10 -0
  17. build/torch-rocm/mha.py +1000 -0
  18. build/torch-rocm/mha_fused_bwd.py +317 -0
  19. build/torch-rocm/mha_onekernel_bwd.py +380 -0
  20. build/torch-rocm/utils/__init__.py +0 -0
  21. build/torch-rocm/utils/_triton/__init__.py +0 -0
  22. build/torch-rocm/utils/_triton/arch_info.py +42 -0
  23. build/torch-rocm/utils/_triton/kernel_repr.py +57 -0
  24. build/torch-rocm/utils/_triton/mha_kernel_utils.py +15 -0
  25. build/torch-rocm/utils/_triton/pid_preprocessing.py +100 -0
  26. build/torch-rocm/utils/core.py +5 -0
  27. build/torch-rocm/utils/device_info.py +14 -0
  28. build/torch-rocm/utils/logger.py +47 -0
  29. build/torch-rocm/utils/types.py +85 -0
build/torch-rocm/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AITER Flash Attention kernels for AMD ROCm (Triton MHA).
2
+
3
+ Repackaged from the `aiter.ops.triton.attention.mha` module of the
4
+ `ROCm/aiter <https://github.com/ROCm/aiter>`_ project as a self-contained
5
+ Hugging Face Hub kernel. Exposes the two FlashAttention entry points used by
6
+ the `transformers` ROCm fallback.
7
+ """
8
+
9
+ from .mha import (
10
+ flash_attn_func,
11
+ flash_attn_varlen_func,
12
+ flash_attn_with_kvcache,
13
+ mha_set_impl,
14
+ mha_set_use_fused_bwd_kernel,
15
+ mha_set_use_int64_strides,
16
+ )
17
+
18
+
19
+ __kernel_metadata__ = {
20
+ "license": "mit",
21
+ }
22
+
23
+
24
+ __all__ = [
25
+ "__kernel_metadata__",
26
+ "flash_attn_func",
27
+ "flash_attn_varlen_func",
28
+ "flash_attn_with_kvcache",
29
+ "mha_set_impl",
30
+ "mha_set_use_fused_bwd_kernel",
31
+ "mha_set_use_int64_strides",
32
+ ]
build/torch-rocm/_kernels/__init__.py ADDED
File without changes
build/torch-rocm/_kernels/flash_attn_triton_amd/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from . import interface_v2 as flash_attn_2
2
+ from . import interface_v3 as flash_attn_3
3
+
4
+ __all__ = ["flash_attn_2", "flash_attn_3"]
build/torch-rocm/_kernels/flash_attn_triton_amd/bwd.py ADDED
The diff for this file is too large to render. See raw diff
 
build/torch-rocm/_kernels/flash_attn_triton_amd/common.py ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Triton kernel helper functions shared across flash attention modules.
3
+
4
+ This module contains Triton JIT-compiled helper functions that are used within
5
+ the main attention kernels (fwd_prefill, fwd_decode, bwd). These are kept
6
+ separate from utils.py to allow stricter type checking on pure Python utilities.
7
+ """
8
+
9
+ from typing import Literal, Optional, Tuple, Union
10
+
11
+ import torch
12
+ import triton
13
+ import triton.language as tl
14
+
15
+ from .utils import DEBUG, get_shape_from_layout, get_stride_from_layout, is_fp8
16
+
17
+
18
+ @triton.jit
19
+ def compute_alibi_block(
20
+ alibi_slope, seqlen_q, seqlen_k, offs_m, offs_n, transpose=False
21
+ ):
22
+ """
23
+ Compute ALiBi (Attention with Linear Biases) block.
24
+
25
+ When seqlen_k and seqlen_q are different, the diagonal sticks to the
26
+ bottom right of the attention matrix.
27
+ """
28
+ # e.g. alibi_slope = 1, seqlen_q = 2, seqlen_k = 5
29
+ # offs_m = [0, 1], offs_n = [0, 1, 2, 3, 4]
30
+ # Result: [[-3, -2, -1, 0, -1], [-4, -3, -2, -1, 0]]
31
+ relative_pos_block = offs_m[:, None] + seqlen_k - seqlen_q - offs_n[None, :]
32
+ alibi_block = -1 * alibi_slope * tl.abs(relative_pos_block)
33
+ if transpose:
34
+ return alibi_block.T
35
+ else:
36
+ return alibi_block
37
+
38
+
39
+ @triton.jit
40
+ def compute_fp8_scaling_factors(x, fp8_max: tl.constexpr):
41
+ """Compute FP8 scaling and descaling factors for a block."""
42
+ x_amax = tl.max(tl.abs(x))
43
+ x_amax = tl.where(x_amax <= 1e-9, 1e-9, x_amax)
44
+ scale_x = fp8_max / x_amax
45
+ descale_x = x_amax / fp8_max
46
+ return scale_x, descale_x
47
+
48
+
49
+ @triton.jit
50
+ def _cast_varlen_to_fp8_kernel_2d(
51
+ X,
52
+ X_fp8,
53
+ Descale,
54
+ cu_seqlens,
55
+ H,
56
+ MAX_SEQLEN,
57
+ stride_batch,
58
+ stride_seq,
59
+ stride_head,
60
+ stride_dim,
61
+ stride_out_batch,
62
+ stride_out_seq,
63
+ stride_out_head,
64
+ stride_out_dim,
65
+ stride_desc_batch,
66
+ stride_desc_head,
67
+ FP8_CLAMP_VAL,
68
+ FP8_MAX,
69
+ BLOCK_SIZE: tl.constexpr,
70
+ HEAD_DIM: tl.constexpr,
71
+ ACTUAL_HEAD_DIM: tl.constexpr,
72
+ IS_VARLEN: tl.constexpr,
73
+ ):
74
+ """Cast tensor to FP8 with per-(batch, head) scaling."""
75
+ b_id = tl.program_id(0)
76
+ h_id = tl.program_id(1)
77
+
78
+ # Get sequence bounds for this batch
79
+ if IS_VARLEN:
80
+ seq_start = tl.load(cu_seqlens + b_id)
81
+ seq_end = tl.load(cu_seqlens + b_id + 1)
82
+ seqlen = seq_end - seq_start
83
+ else:
84
+ seq_start = 0
85
+ seqlen = MAX_SEQLEN
86
+
87
+ # initialize max value tracker
88
+ x_max_val = 0.0
89
+
90
+ # STEP 1: Find max absolute value across the entire sequence
91
+ num_of_blocks = tl.cdiv(seqlen, BLOCK_SIZE)
92
+ for blk_idx in range(0, num_of_blocks):
93
+ offs_seq = blk_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
94
+ offs_dim = tl.arange(0, HEAD_DIM)
95
+
96
+ mask_seq = offs_seq[:, None] < seqlen
97
+ if ACTUAL_HEAD_DIM != HEAD_DIM:
98
+ mask_dim = offs_dim[None, :] < ACTUAL_HEAD_DIM
99
+ mask_seq = mask_seq & mask_dim
100
+
101
+ adj_x = (
102
+ b_id * stride_batch
103
+ + h_id * stride_head
104
+ + seq_start * stride_seq
105
+ + offs_seq[:, None] * stride_seq
106
+ + offs_dim[None, :] * stride_dim
107
+ )
108
+ x_block = tl.load(X + adj_x, mask=mask_seq, other=0.0)
109
+ block_max = tl.max(tl.abs(x_block))
110
+ x_max_val = tl.maximum(x_max_val, block_max)
111
+
112
+ # clamp to avoid division by zero
113
+ x_max_val = tl.maximum(x_max_val, FP8_CLAMP_VAL)
114
+
115
+ # compute scale and descale factors
116
+ scale = FP8_MAX / x_max_val
117
+ descale = x_max_val / FP8_MAX
118
+
119
+ # store descale factor
120
+ desc_ptr = Descale + b_id * stride_desc_batch + h_id
121
+ tl.store(desc_ptr, descale)
122
+
123
+ # STEP 2: Apply scaling and convert to FP8
124
+ for blk_idx in range(0, num_of_blocks):
125
+ offs_seq = blk_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
126
+ offs_dim = tl.arange(0, HEAD_DIM)
127
+
128
+ mask_seq = offs_seq[:, None] < seqlen
129
+ if ACTUAL_HEAD_DIM != HEAD_DIM:
130
+ mask_dim = offs_dim[None, :] < ACTUAL_HEAD_DIM
131
+ mask_seq = mask_seq & mask_dim
132
+
133
+ addr = (
134
+ b_id * stride_batch
135
+ + h_id * stride_head
136
+ + seq_start * stride_seq
137
+ + offs_seq[:, None] * stride_seq
138
+ + offs_dim[None, :] * stride_dim
139
+ )
140
+ x_block = tl.load(X + addr, mask=mask_seq, other=0.0)
141
+ x_fp8_block = (x_block * scale).to(X_fp8.type.element_ty)
142
+
143
+ addr_out = (
144
+ b_id * stride_out_batch
145
+ + h_id * stride_out_head
146
+ + seq_start * stride_out_seq
147
+ + offs_seq[:, None] * stride_out_seq
148
+ + offs_dim[None, :] * stride_out_dim
149
+ )
150
+ tl.store(X_fp8 + addr_out, x_fp8_block, mask=mask_seq)
151
+
152
+
153
+ @triton.jit
154
+ def _rotary_kernel(
155
+ OUT,
156
+ X,
157
+ COS,
158
+ SIN,
159
+ CU_SEQLENS,
160
+ SEQLEN_OFFSETS,
161
+ seqlen,
162
+ nheads,
163
+ seqlen_ro,
164
+ stride_out_batch,
165
+ stride_out_seqlen,
166
+ stride_out_nheads,
167
+ stride_out_headdim,
168
+ stride_x_batch,
169
+ stride_x_seqlen,
170
+ stride_x_nheads,
171
+ stride_x_headdim,
172
+ ROTARY_DIM: tl.constexpr,
173
+ IS_SEQLEN_OFFSETS_TENSOR: tl.constexpr,
174
+ IS_VARLEN: tl.constexpr,
175
+ INTERLEAVED: tl.constexpr,
176
+ CONJUGATE: tl.constexpr,
177
+ BLOCK_H: tl.constexpr,
178
+ BLOCK_M: tl.constexpr,
179
+ ):
180
+ """Apply rotary positional embeddings."""
181
+ BLOCK_K: tl.constexpr = triton.next_power_of_2(ROTARY_DIM)
182
+ ROTARY_DIM_HALF = ROTARY_DIM // 2
183
+ pid_head = tl.program_id(axis=0)
184
+ pid_m = tl.program_id(axis=1)
185
+ pid_batch = tl.program_id(axis=2)
186
+
187
+ if not IS_VARLEN:
188
+ X = X + pid_batch * stride_x_batch
189
+ OUT = OUT + pid_batch * stride_out_batch
190
+ else:
191
+ start_idx = tl.load(CU_SEQLENS + pid_batch)
192
+ seqlen = tl.load(CU_SEQLENS + pid_batch + 1) - start_idx
193
+ X = X + start_idx * stride_x_seqlen
194
+ OUT = OUT + start_idx * stride_out_seqlen
195
+
196
+ if pid_m * BLOCK_M >= seqlen:
197
+ return
198
+
199
+ rh = pid_head * BLOCK_H + tl.arange(0, BLOCK_H)
200
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
201
+ if not IS_SEQLEN_OFFSETS_TENSOR:
202
+ rm_cs = rm + SEQLEN_OFFSETS
203
+ else:
204
+ rm_cs = rm + tl.load(SEQLEN_OFFSETS + pid_batch)
205
+
206
+ rk_half = tl.arange(0, BLOCK_K // 2)
207
+ COS = COS + (rm_cs[:, None] * ROTARY_DIM_HALF + rk_half[None, :])
208
+ SIN = SIN + (rm_cs[:, None] * ROTARY_DIM_HALF + rk_half[None, :])
209
+ mask_cs = (rm_cs[:, None] < seqlen_ro) & (rk_half[None, :] < ROTARY_DIM_HALF)
210
+ cos = tl.load(COS, mask=mask_cs, other=1.0).to(tl.float32)
211
+ sin = tl.load(SIN, mask=mask_cs, other=0.0).to(tl.float32)
212
+ if CONJUGATE:
213
+ sin = -sin
214
+
215
+ if not INTERLEAVED:
216
+ X = X + (
217
+ rh[:, None, None] * stride_x_nheads
218
+ + rm[None, :, None] * stride_x_seqlen
219
+ + rk_half[None, None, :] * stride_x_headdim
220
+ )
221
+ OUT = OUT + (
222
+ rh[:, None, None] * stride_out_nheads
223
+ + rm[None, :, None] * stride_out_seqlen
224
+ + rk_half[None, None, :] * stride_out_headdim
225
+ )
226
+ mask = (
227
+ (rh[:, None, None] < nheads)
228
+ & (rm[None, :, None] < seqlen)
229
+ & (rk_half[None, None, :] < ROTARY_DIM_HALF)
230
+ )
231
+ x0 = tl.load(X, mask=mask, other=0.0).to(tl.float32)
232
+ x1 = tl.load(X + ROTARY_DIM_HALF * stride_x_headdim, mask=mask, other=0.0).to(
233
+ tl.float32
234
+ )
235
+ o0 = x0 * cos - x1 * sin
236
+ o1 = x0 * sin + x1 * cos
237
+ tl.store(OUT, o0, mask=mask)
238
+ tl.store(OUT + ROTARY_DIM_HALF * stride_out_headdim, o1, mask=mask)
239
+ else:
240
+ rk = tl.arange(0, BLOCK_K)
241
+ X = X + (
242
+ rh[:, None, None] * stride_x_nheads
243
+ + rm[None, :, None] * stride_x_seqlen
244
+ + rk[None, None, :] * stride_x_headdim
245
+ )
246
+ OUT = OUT + (
247
+ rh[:, None, None] * stride_out_nheads
248
+ + rm[None, :, None] * stride_out_seqlen
249
+ + rk[None, None, :] * stride_out_headdim
250
+ )
251
+ mask = (
252
+ (rh[:, None, None] < nheads)
253
+ & (rm[None, :, None] < seqlen)
254
+ & (rk[None, None, :] < ROTARY_DIM)
255
+ )
256
+ x = tl.load(X, mask=mask, other=0.0).to(tl.float32)
257
+ x0, x1 = tl.split(tl.reshape(x, [BLOCK_H, BLOCK_M, BLOCK_K // 2, 2]))
258
+ o0 = x0 * cos - x1 * sin
259
+ o1 = x0 * sin + x1 * cos
260
+ o = tl.reshape(tl.join(o0, o1), [BLOCK_H, BLOCK_M, BLOCK_K])
261
+ tl.store(OUT, o, mask=mask)
262
+
263
+
264
+ # -------------------------------
265
+ # Python wrappers for Triton kernels
266
+ # -------------------------------
267
+
268
+
269
+ def cast_to_fp8(
270
+ x: torch.Tensor,
271
+ fp8_dtype: torch.dtype,
272
+ layout: Literal["bshd", "thd"],
273
+ clamp_val: float = 1e-9,
274
+ cu_seqlens: Optional[torch.Tensor] = None,
275
+ max_seqlen: Optional[int] = None,
276
+ ) -> tuple[torch.Tensor, torch.Tensor]:
277
+ """Cast tensor to FP8 with per-(batch, head) scaling factors."""
278
+ if DEBUG > 0:
279
+ print()
280
+ print("cast_to_fp8")
281
+ print("x:", x, x.shape)
282
+ print("fp8_dtype:", fp8_dtype)
283
+ print("cu_seqlens:", cu_seqlens)
284
+ print("max_seqlen:", max_seqlen)
285
+ print("clamp_val:", clamp_val)
286
+
287
+ assert x.dtype in {
288
+ torch.float16,
289
+ torch.float32,
290
+ torch.float64,
291
+ torch.bfloat16,
292
+ } and is_fp8(fp8_dtype), f"Cannot cast {x.dtype} to {fp8_dtype}"
293
+
294
+ batch, max_seqlen_final, num_heads, head_dim = get_shape_from_layout(
295
+ x, layout, cu_seqlens, max_seqlen
296
+ )
297
+ is_varlen = layout == "thd"
298
+ fp8_max = torch.finfo(fp8_dtype).max
299
+
300
+ padded_head_dim = 1 << (head_dim - 1).bit_length()
301
+ padded_head_dim = max(padded_head_dim, 32)
302
+
303
+ x_fp8 = torch.zeros_like(x, dtype=fp8_dtype)
304
+ descale_factors = torch.zeros(
305
+ (batch, num_heads), device=x.device, dtype=torch.float32
306
+ )
307
+ BLOCK_SIZE = 128
308
+
309
+ stride_batch, stride_head, stride_seq, stride_dim = get_stride_from_layout(
310
+ x, layout
311
+ )
312
+ stride_out_batch, stride_out_head, stride_out_seq, stride_out_dim = (
313
+ get_stride_from_layout(x_fp8, layout)
314
+ )
315
+ stride_desc_batch, stride_desc_head = descale_factors.stride()
316
+
317
+ grid = (batch, num_heads)
318
+ _cast_varlen_to_fp8_kernel_2d[grid](
319
+ x,
320
+ x_fp8,
321
+ descale_factors,
322
+ cu_seqlens,
323
+ num_heads,
324
+ max_seqlen_final,
325
+ stride_batch,
326
+ stride_seq,
327
+ stride_head,
328
+ stride_dim,
329
+ stride_out_batch,
330
+ stride_out_seq,
331
+ stride_out_head,
332
+ stride_out_dim,
333
+ stride_desc_batch,
334
+ stride_desc_head,
335
+ clamp_val,
336
+ fp8_max,
337
+ BLOCK_SIZE=BLOCK_SIZE,
338
+ HEAD_DIM=padded_head_dim,
339
+ ACTUAL_HEAD_DIM=head_dim,
340
+ IS_VARLEN=is_varlen,
341
+ )
342
+
343
+ return x_fp8, descale_factors
344
+
345
+
346
+ def _apply_rotary_kernel(
347
+ x: torch.Tensor,
348
+ cos: torch.Tensor,
349
+ sin: torch.Tensor,
350
+ seqlen_offsets: Union[int, torch.Tensor] = 0,
351
+ cu_seqlens: Optional[torch.Tensor] = None,
352
+ max_seqlen: Optional[int] = None,
353
+ interleaved: bool = False,
354
+ inplace: bool = False,
355
+ conjugate: bool = False,
356
+ ) -> torch.Tensor:
357
+ """Apply rotary positional embeddings using Triton kernel."""
358
+ is_varlen = cu_seqlens is not None
359
+ if not is_varlen:
360
+ batch, seqlen, nheads, headdim = x.shape
361
+ else:
362
+ assert (
363
+ max_seqlen is not None
364
+ ), "If cu_seqlens is passed, max_seqlen must also be provided"
365
+ total_seqlen, nheads, headdim = x.shape
366
+ assert cu_seqlens is not None
367
+ batch_p_1 = cu_seqlens.shape[0]
368
+ batch = batch_p_1 - 1
369
+ seqlen = max_seqlen
370
+ seqlen_ro, rotary_dim_half = cos.shape
371
+ assert sin.shape == cos.shape
372
+ rotary_dim = 2 * rotary_dim_half
373
+ assert rotary_dim <= headdim
374
+ assert headdim <= 256
375
+ assert seqlen_ro >= seqlen
376
+
377
+ cos, sin = cos.contiguous(), sin.contiguous()
378
+ if isinstance(seqlen_offsets, torch.Tensor):
379
+ assert seqlen_offsets.shape == (batch,)
380
+ assert seqlen_offsets.dtype in (torch.int32, torch.int64)
381
+ seqlen_offsets = seqlen_offsets.contiguous()
382
+ else:
383
+ assert seqlen_offsets + seqlen <= seqlen_ro
384
+
385
+ out = torch.empty_like(x) if not inplace else x
386
+ if rotary_dim < headdim and not inplace:
387
+ out[..., rotary_dim:].copy_(x[..., rotary_dim:])
388
+
389
+ BLOCK_M = 8 if rotary_dim <= 128 else 4
390
+ grid = (
391
+ triton.cdiv(nheads, 2),
392
+ triton.cdiv(seqlen, BLOCK_M),
393
+ batch,
394
+ )
395
+
396
+ with torch.cuda.device(x.device.index):
397
+ torch.library.wrap_triton(_rotary_kernel)[grid](
398
+ out,
399
+ x,
400
+ cos,
401
+ sin,
402
+ cu_seqlens,
403
+ seqlen_offsets,
404
+ seqlen,
405
+ nheads,
406
+ seqlen_ro,
407
+ out.stride(0) if not is_varlen else 0,
408
+ out.stride(-3),
409
+ out.stride(-2),
410
+ out.stride(-1),
411
+ x.stride(0) if not is_varlen else 0,
412
+ x.stride(-3),
413
+ x.stride(-2),
414
+ x.stride(-1),
415
+ rotary_dim,
416
+ isinstance(seqlen_offsets, torch.Tensor),
417
+ is_varlen,
418
+ interleaved,
419
+ conjugate,
420
+ BLOCK_M=BLOCK_M,
421
+ BLOCK_H=2,
422
+ )
423
+ return out
424
+
425
+
426
+ class _ApplyRotary(torch.autograd.Function):
427
+ @staticmethod
428
+ def forward(
429
+ ctx,
430
+ x: torch.Tensor,
431
+ cos: torch.Tensor,
432
+ sin: torch.Tensor,
433
+ interleaved: bool,
434
+ inplace: bool,
435
+ seqlen_offsets: Union[int, torch.Tensor],
436
+ cu_seqlens: Optional[torch.Tensor],
437
+ max_seqlen: Optional[int],
438
+ ) -> torch.Tensor:
439
+ out = _apply_rotary_kernel(
440
+ x,
441
+ cos,
442
+ sin,
443
+ seqlen_offsets=seqlen_offsets,
444
+ cu_seqlens=cu_seqlens,
445
+ max_seqlen=max_seqlen,
446
+ interleaved=interleaved,
447
+ inplace=inplace,
448
+ conjugate=False,
449
+ )
450
+ if isinstance(seqlen_offsets, int):
451
+ ctx.save_for_backward(cos, sin, cu_seqlens)
452
+ ctx.seqlen_offsets = seqlen_offsets
453
+ else:
454
+ ctx.save_for_backward(cos, sin, cu_seqlens, seqlen_offsets)
455
+ ctx.seqlen_offsets = None
456
+ ctx.interleaved = interleaved
457
+ ctx.inplace = inplace
458
+ ctx.max_seqlen = max_seqlen
459
+ return out if not inplace else x
460
+
461
+ @staticmethod
462
+ def backward(
463
+ ctx, do: torch.Tensor
464
+ ) -> tuple[torch.Tensor, None, None, None, None, None, None, None]:
465
+ seqlen_offsets = ctx.seqlen_offsets
466
+ if seqlen_offsets is None:
467
+ cos, sin, cu_seqlens, seqlen_offsets = ctx.saved_tensors
468
+ else:
469
+ cos, sin, cu_seqlens = ctx.saved_tensors
470
+ dx = _apply_rotary_kernel(
471
+ do,
472
+ cos,
473
+ sin,
474
+ seqlen_offsets=seqlen_offsets,
475
+ cu_seqlens=cu_seqlens,
476
+ max_seqlen=ctx.max_seqlen,
477
+ interleaved=ctx.interleaved,
478
+ inplace=ctx.inplace,
479
+ conjugate=True,
480
+ )
481
+ return dx, None, None, None, None, None, None, None
482
+
483
+
484
+ def apply_rotary_emb(
485
+ x: torch.Tensor,
486
+ cos: torch.Tensor,
487
+ sin: torch.Tensor,
488
+ interleaved: bool = False,
489
+ inplace: bool = False,
490
+ seqlen_offsets: Union[int, torch.Tensor] = 0,
491
+ cu_seqlens: Optional[torch.Tensor] = None,
492
+ max_seqlen: Optional[int] = None,
493
+ ) -> torch.Tensor:
494
+ """Apply rotary embeddings to tensor x.
495
+
496
+ Args:
497
+ x: (B, S, H, D) if `cu_seqlens` is None else (total_S, H, D).
498
+ cos, sin: (S_rotary, rotary_dim/2)
499
+ interleaved: GPT-J style if True.
500
+ inplace: modify x in place.
501
+ seqlen_offsets: int or (B,) tensor of starting offsets per sequence.
502
+ cu_seqlens: (B+1,) tensor enabling varlen mode.
503
+ max_seqlen: required when `cu_seqlens` is provided.
504
+ """
505
+ original_dtype = x.dtype
506
+ is_fp8_input = is_fp8(original_dtype)
507
+ if is_fp8_input:
508
+ target_dtype = (
509
+ torch.bfloat16
510
+ if cos.dtype == torch.bfloat16 or torch.cuda.is_bf16_supported()
511
+ else torch.float16
512
+ )
513
+ x_up = x.to(target_dtype)
514
+ cos_up = cos.to(target_dtype) if cos.dtype != target_dtype else cos
515
+ sin_up = sin.to(target_dtype) if sin.dtype != target_dtype else sin
516
+ out_up = _ApplyRotary.apply(
517
+ x_up,
518
+ cos_up,
519
+ sin_up,
520
+ interleaved,
521
+ False,
522
+ seqlen_offsets,
523
+ cu_seqlens,
524
+ max_seqlen,
525
+ )
526
+ if inplace:
527
+ x.copy_(out_up.to(original_dtype))
528
+ return x
529
+ return out_up.to(original_dtype)
530
+ else:
531
+ return _ApplyRotary.apply(
532
+ x, cos, sin, interleaved, inplace, seqlen_offsets, cu_seqlens, max_seqlen
533
+ )
534
+
535
+
536
+ def apply_rotary(
537
+ q: torch.Tensor,
538
+ k_new: Optional[torch.Tensor],
539
+ cos: torch.Tensor,
540
+ sin: torch.Tensor,
541
+ *,
542
+ causal: bool,
543
+ local: bool,
544
+ interleaved: bool = False,
545
+ seqlen_offsets: Union[int, torch.Tensor] = 0,
546
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
547
+ """Apply rotary embeddings to q and optionally k_new.
548
+
549
+ Policy:
550
+ - If causal OR local attention: apply rotary directly on (B, S, H, D).
551
+ - Else (non-causal global): flatten heads into sequence, apply, unflatten.
552
+ - k_new is always rotated directly when provided.
553
+ """
554
+ assert q.ndim == 4, f"Expected q shape (B,S,H,D), got {q.shape}"
555
+ B, S, H, D = q.shape
556
+ use_flatten = (not causal) and (not local)
557
+
558
+ if use_flatten:
559
+ q_flat = q.reshape(B, S * H, D).unsqueeze(1)
560
+ q_flat = apply_rotary_emb(
561
+ q_flat, cos, sin, interleaved=interleaved, seqlen_offsets=seqlen_offsets
562
+ )
563
+ q = q_flat.view(B, 1, S * H, D).reshape(B, S, H, D)
564
+ else:
565
+ q = apply_rotary_emb(
566
+ q, cos, sin, interleaved=interleaved, seqlen_offsets=seqlen_offsets
567
+ )
568
+
569
+ if k_new is not None:
570
+ k_new = apply_rotary_emb(
571
+ k_new, cos, sin, interleaved=interleaved, seqlen_offsets=seqlen_offsets
572
+ )
573
+ return q, k_new
build/torch-rocm/_kernels/flash_attn_triton_amd/fwd_decode.py ADDED
@@ -0,0 +1,1475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ import torch
3
+ import triton
4
+ import triton.language as tl
5
+ from typing import Literal, Optional
6
+ from .common import apply_rotary
7
+ from .utils import (
8
+ DEBUG,
9
+ AUTOTUNE,
10
+ AutotuneMode,
11
+ get_arch,
12
+ get_padded_headsize,
13
+ get_shape_from_layout,
14
+ get_stride_from_layout,
15
+ is_fp8,
16
+ )
17
+
18
+ FWD_DECODE_AUTOTUNE_KEYS = [
19
+ "N_CTX_Q",
20
+ "N_CTX_K",
21
+ "ACTUAL_BLOCK_DMODEL",
22
+ "H_q",
23
+ "H_kv",
24
+ "IS_CAUSAL",
25
+ "IS_GQA",
26
+ ]
27
+
28
+ # Maximum BLOCK_M across all configs (for intermediate tensor allocation)
29
+ MAX_BLOCK_M = 64
30
+
31
+
32
+ def get_fwd_decode_configs(mode: AutotuneMode):
33
+ """
34
+ Returns configs for both the splitK kernel and reduce kernel.
35
+
36
+ Returns:
37
+ (splitk_configs, reduce_config): Tuple of config lists for each kernel
38
+ """
39
+
40
+ if mode == "off":
41
+ arch = get_arch()
42
+ if arch.is_rdna:
43
+ return (
44
+ [
45
+ triton.Config(
46
+ {"BLOCK_M": 32, "BLOCK_N": 32},
47
+ num_stages=1,
48
+ num_warps=4,
49
+ ),
50
+ ],
51
+ [triton.Config({}, num_stages=1, num_warps=4)],
52
+ )
53
+ elif arch.is_cdna:
54
+ return (
55
+ [
56
+ triton.Config(
57
+ {"BLOCK_M": 64, "BLOCK_N": 64, "waves_per_eu": 1},
58
+ num_stages=1,
59
+ num_warps=4,
60
+ ),
61
+ ],
62
+ [triton.Config({}, num_stages=1, num_warps=4)],
63
+ )
64
+ else:
65
+ return (
66
+ [
67
+ triton.Config(
68
+ {"BLOCK_M": 64, "BLOCK_N": 64, "waves_per_eu": 1},
69
+ num_stages=1,
70
+ num_warps=4,
71
+ ),
72
+ ],
73
+ [triton.Config({}, num_stages=1, num_warps=4)],
74
+ )
75
+
76
+ elif mode == "on":
77
+ arch = get_arch()
78
+ if arch.is_rdna:
79
+ return (
80
+ [
81
+ triton.Config(
82
+ {"BLOCK_M": 32, "BLOCK_N": 32},
83
+ num_stages=1,
84
+ num_warps=4,
85
+ ),
86
+ ],
87
+ [triton.Config({}, num_stages=1, num_warps=4)],
88
+ )
89
+ elif arch.is_cdna:
90
+ return (
91
+ [
92
+ triton.Config(
93
+ {"BLOCK_M": 64, "BLOCK_N": 64, "waves_per_eu": 1},
94
+ num_stages=1,
95
+ num_warps=4,
96
+ ),
97
+ ],
98
+ [triton.Config({}, num_stages=1, num_warps=4)],
99
+ )
100
+ else:
101
+ return (
102
+ [
103
+ triton.Config(
104
+ {"BLOCK_M": 64, "BLOCK_N": 64, "waves_per_eu": 1},
105
+ num_stages=1,
106
+ num_warps=4,
107
+ ),
108
+ ],
109
+ [triton.Config({}, num_stages=1, num_warps=4)],
110
+ )
111
+
112
+ else: # sweep
113
+ arch = get_arch()
114
+ splitk_configs = []
115
+
116
+ BLOCK_M_OPTIONS = [64, 32, 16]
117
+ BLOCK_N_OPTIONS = [128, 64, 32, 16]
118
+ NUM_WARPS_OPTIONS = [2, 4]
119
+ NUM_STAGES_OPTIONS = [1]
120
+ WAVES_PER_EU_OPTIONS = [4, 2, 1]
121
+
122
+ # Ensure BLOCK_M options don't exceed MAX_BLOCK_M
123
+ assert all(
124
+ bm <= MAX_BLOCK_M for bm in BLOCK_M_OPTIONS
125
+ ), f"BLOCK_M_OPTIONS {BLOCK_M_OPTIONS} exceeds MAX_BLOCK_M {MAX_BLOCK_M}"
126
+
127
+ for bm in BLOCK_M_OPTIONS:
128
+ for bn in BLOCK_N_OPTIONS:
129
+ for waves in WAVES_PER_EU_OPTIONS:
130
+ for nw in NUM_WARPS_OPTIONS:
131
+ for ns in NUM_STAGES_OPTIONS:
132
+ splitk_configs.append(
133
+ triton.Config(
134
+ {
135
+ "BLOCK_M": bm,
136
+ "BLOCK_N": bn,
137
+ "waves_per_eu": waves,
138
+ },
139
+ num_stages=ns,
140
+ num_warps=nw,
141
+ )
142
+ )
143
+
144
+ # Reduce kernel configs - sweep num_warps
145
+ NUM_WARPS_REDUCE_OPTIONS = [2, 4]
146
+ reduce_configs = [
147
+ triton.Config({}, num_stages=1, num_warps=nw)
148
+ for nw in NUM_WARPS_REDUCE_OPTIONS
149
+ ]
150
+
151
+ return splitk_configs, reduce_configs
152
+
153
+
154
+ fwd_decode_splitk_configs, fwd_decode_reduce_configs = get_fwd_decode_configs(AUTOTUNE)
155
+
156
+
157
+ @triton.jit
158
+ def _attn_fwd_inner(
159
+ q,
160
+ kT,
161
+ v,
162
+ pos,
163
+ col_mask,
164
+ m_i,
165
+ l_i,
166
+ acc,
167
+ pid_m,
168
+ q_descale,
169
+ k_descale,
170
+ v_descale, # FP8 scaling factors
171
+ alibi_slope,
172
+ apply_col_mask,
173
+ IS_FP8: tl.constexpr, # FP8 flag
174
+ BLOCK_M: tl.constexpr,
175
+ BLOCK_N: tl.constexpr,
176
+ N_CTX_Q: tl.constexpr,
177
+ N_CTX_K_FINAL: tl.constexpr,
178
+ USE_ALIBI: tl.constexpr,
179
+ USE_SLIDING_WINDOW: tl.constexpr,
180
+ IS_CAUSAL: tl.constexpr,
181
+ WINDOW_SIZE_LEFT: tl.constexpr,
182
+ WINDOW_SIZE_RIGHT: tl.constexpr,
183
+ ):
184
+ # -- compute qk ---
185
+ qk = tl.zeros([BLOCK_M, BLOCK_N], dtype=tl.float32)
186
+ if IS_FP8:
187
+ qk += tl.dot(q, kT) * q_descale * k_descale # Apply FP8 scaling
188
+ else:
189
+ qk = tl.dot(q, kT, acc=qk) # noqa: F821
190
+
191
+ if USE_ALIBI:
192
+ row_idx = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
193
+ col_idx = pos + tl.arange(0, BLOCK_N)
194
+
195
+ # Compute relative positions
196
+ relative_pos = row_idx[:, None] + N_CTX_K_FINAL - (N_CTX_Q + col_idx[None, :])
197
+ relative_pos = tl.abs(relative_pos)
198
+
199
+ # Compute ALiBi bias
200
+ alibi_bias = -1 * alibi_slope * relative_pos
201
+ qk += alibi_bias * 1.44269504
202
+
203
+ # ------------------------------------------------------------------
204
+ # masking
205
+ # ------------------------------------------------------------------
206
+ if USE_SLIDING_WINDOW:
207
+ row_idx = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) # q positions
208
+ col_idx = pos + tl.arange(0, BLOCK_N) # k positions
209
+ row = row_idx[:, None] # [M,1]
210
+ col = col_idx[None, :] # [1,N]
211
+
212
+ if IS_CAUSAL:
213
+ # -------- causal + window --------
214
+ diag = N_CTX_K_FINAL - N_CTX_Q # sk-sq
215
+ causal_ok = col <= row + diag
216
+ if WINDOW_SIZE_LEFT < 0: # only right window
217
+ win_ok = col <= row + diag + WINDOW_SIZE_RIGHT
218
+ else: # both sides
219
+ win_ok = (col >= row + diag - WINDOW_SIZE_LEFT) & (
220
+ col <= row + diag + WINDOW_SIZE_RIGHT
221
+ )
222
+ mask = ~(causal_ok & win_ok) # True => -inf
223
+ else:
224
+ # -------- non-causal window --------
225
+ sk, sq = N_CTX_K_FINAL, N_CTX_Q
226
+ if WINDOW_SIZE_LEFT < 0:
227
+ mask = col > row + (sk - sq) + WINDOW_SIZE_RIGHT
228
+ else:
229
+ right = tl.minimum(row + (sk - sq) + WINDOW_SIZE_RIGHT, sk)
230
+ left = row + (sk - sq) - WINDOW_SIZE_LEFT
231
+ mask = (col > right) | (col < left)
232
+ qk = tl.where(mask, float("-inf"), qk)
233
+ else:
234
+ if IS_CAUSAL:
235
+ row_idx = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
236
+ col_idx = pos + tl.arange(0, BLOCK_N)
237
+
238
+ # create a N_CTX_Q x kv_len causal mask
239
+ col_offset = N_CTX_K_FINAL - N_CTX_Q
240
+ causal_mask = row_idx[:, None] >= (col_idx[None, :] - col_offset)
241
+
242
+ # Apply the mask
243
+ qk = tl.where(causal_mask, qk, float("-inf"))
244
+
245
+ # Column mask (tail / variable-length). Instead of recomputing an arange each time,
246
+ # we accept a precomputed mask from the caller (col_valid_mask).
247
+ if apply_col_mask:
248
+ # Expect col_mask shape: [BLOCK_N]. True where column is within sequence.
249
+ qk = tl.where(col_mask[None, :], qk, float("-inf"))
250
+
251
+ m_i_new = tl.maximum(m_i, tl.max(qk, 1)) # per-row max so far
252
+
253
+ # rows that are *all* -inf after masking
254
+ valid = m_i_new > float("-inf")
255
+
256
+ # scale previous partial sums safely
257
+ alpha = tl.where(valid, tl.math.exp2(m_i - m_i_new), 0.0)
258
+
259
+ # subtract the row max only on valid rows
260
+ qk = tl.where(valid[:, None], qk - m_i_new[:, None], float("-inf"))
261
+ p = tl.math.exp2(qk)
262
+
263
+ # -- update m_i and l_i --
264
+ l_i = l_i * alpha + tl.sum(p, 1)
265
+ m_i = m_i_new
266
+ p = p.to(q.dtype)
267
+
268
+ # -- scale and update acc --
269
+ acc *= alpha[:, None]
270
+ if IS_FP8:
271
+ acc += tl.dot(p.to(v.dtype), v) * v_descale # Apply FP8 scaling for V
272
+ else:
273
+ acc = tl.dot(p.to(v.dtype), v, acc=acc)
274
+
275
+ return m_i, l_i, acc
276
+
277
+
278
+ @triton.autotune(
279
+ configs=fwd_decode_splitk_configs,
280
+ key=FWD_DECODE_AUTOTUNE_KEYS,
281
+ use_cuda_graph=True,
282
+ )
283
+ @triton.jit
284
+ def _fwd_kernel_splitK(
285
+ Q,
286
+ K,
287
+ V,
288
+ Q_Descale, # FP8 descale factors for Q
289
+ K_Descale, # FP8 descale factors for K
290
+ V_Descale, # FP8 descale factors for V
291
+ sm_scale,
292
+ Out_splitK, # [B*H*G, split_k, Mq, K]
293
+ Metadata, # [B*H*G, 2, split_k, M_ceil] contains [mi, li]
294
+ K_new,
295
+ V_new,
296
+ Cache_seqlens,
297
+ Cache_batch_idx,
298
+ Block_table,
299
+ Alibi_slopes,
300
+ stride_qz,
301
+ stride_qm,
302
+ stride_qg,
303
+ stride_qh,
304
+ stride_qd,
305
+ stride_kz,
306
+ stride_kn,
307
+ stride_kg,
308
+ stride_kh,
309
+ stride_kd,
310
+ stride_vz,
311
+ stride_vn,
312
+ stride_vg,
313
+ stride_vh,
314
+ stride_vd,
315
+ stride_osk_zhg,
316
+ stride_osk_s,
317
+ stride_osk_m,
318
+ stride_osk_d,
319
+ stride_mzhg,
320
+ stride_m2,
321
+ stride_ms,
322
+ stride_mm,
323
+ stride_kn_z,
324
+ stride_kn_n,
325
+ stride_kn_g,
326
+ stride_kn_h,
327
+ stride_kn_d,
328
+ stride_vn_z,
329
+ stride_vn_n,
330
+ stride_vn_g,
331
+ stride_vn_h,
332
+ stride_vn_d,
333
+ stride_bt_b,
334
+ stride_bt_s,
335
+ stride_az,
336
+ stride_ah,
337
+ stride_q_descale_z, # FP8 descale strides
338
+ stride_q_descale_h,
339
+ stride_k_descale_z,
340
+ stride_k_descale_h,
341
+ stride_v_descale_z,
342
+ stride_v_descale_h,
343
+ Z,
344
+ N_CTX_Q,
345
+ N_CTX_K,
346
+ N_CTX_NEW,
347
+ BLOCK_N_PER_SPLIT: tl.constexpr,
348
+ BLOCK_SIZE_K: tl.constexpr,
349
+ H_q: tl.constexpr,
350
+ H_kv: tl.constexpr,
351
+ G_q: tl.constexpr,
352
+ BLOCK_M: tl.constexpr,
353
+ BLOCK_DMODEL: tl.constexpr,
354
+ ACTUAL_BLOCK_DMODEL: tl.constexpr,
355
+ BLOCK_N: tl.constexpr,
356
+ USE_CACHE_SEQLENs: tl.constexpr,
357
+ USE_CACHE_BATCH_IDX: tl.constexpr,
358
+ NEW_KV: tl.constexpr,
359
+ IS_GQA: tl.constexpr,
360
+ IS_CAUSAL: tl.constexpr,
361
+ USE_ALIBI: tl.constexpr,
362
+ PADDED_HEAD: tl.constexpr,
363
+ GROUP_SIZE: tl.constexpr,
364
+ USE_SLIDING_WINDOW: tl.constexpr,
365
+ WINDOW_SIZE_LEFT: tl.constexpr,
366
+ WINDOW_SIZE_RIGHT: tl.constexpr,
367
+ USE_BLOCK_TABLE: tl.constexpr,
368
+ IS_FP8: tl.constexpr, # FP8 flag
369
+ ):
370
+ # Cast strides to int64 to prevent overflow in pointer arithmetic.
371
+ # stride * index can exceed int32 range for large tensors (e.g. batch=64, sk=8192, d=56).
372
+ # See mha.py USE_INT64_STRIDES for the same pattern used in the prefill kernel.
373
+ stride_qz_i64 = tl.cast(stride_qz, tl.int64)
374
+ stride_qm_i64 = tl.cast(stride_qm, tl.int64)
375
+ stride_qg_i64 = tl.cast(stride_qg, tl.int64)
376
+ stride_qh_i64 = tl.cast(stride_qh, tl.int64)
377
+ stride_qd_i64 = tl.cast(stride_qd, tl.int64)
378
+ stride_kz_i64 = tl.cast(stride_kz, tl.int64)
379
+ stride_kn_i64 = tl.cast(stride_kn, tl.int64)
380
+ stride_kg_i64 = tl.cast(stride_kg, tl.int64)
381
+ stride_kh_i64 = tl.cast(stride_kh, tl.int64)
382
+ stride_kd_i64 = tl.cast(stride_kd, tl.int64)
383
+ stride_vz_i64 = tl.cast(stride_vz, tl.int64)
384
+ stride_vn_i64 = tl.cast(stride_vn, tl.int64)
385
+ stride_vg_i64 = tl.cast(stride_vg, tl.int64)
386
+ stride_vh_i64 = tl.cast(stride_vh, tl.int64)
387
+ stride_vd_i64 = tl.cast(stride_vd, tl.int64)
388
+ stride_osk_zhg_i64 = tl.cast(stride_osk_zhg, tl.int64)
389
+ stride_osk_s_i64 = tl.cast(stride_osk_s, tl.int64)
390
+ stride_osk_m_i64 = tl.cast(stride_osk_m, tl.int64)
391
+ stride_osk_d_i64 = tl.cast(stride_osk_d, tl.int64)
392
+ stride_mzhg_i64 = tl.cast(stride_mzhg, tl.int64)
393
+ stride_ms_i64 = tl.cast(stride_ms, tl.int64)
394
+ stride_m2_i64 = tl.cast(stride_m2, tl.int64)
395
+
396
+ # get program ids
397
+ pid_m = tl.program_id(0)
398
+ pid_zhg = tl.program_id(1)
399
+ pid_splitk = tl.program_id(2)
400
+
401
+ # compute z, h and g ids
402
+ z_id = pid_zhg // (H_q * G_q)
403
+ hq_id = (pid_zhg // G_q) % H_q
404
+ g_id = pid_zhg % G_q
405
+
406
+ # is gqa
407
+ if IS_GQA:
408
+ hk_id = hq_id // GROUP_SIZE
409
+ hv_id = hk_id
410
+ else:
411
+ hk_id = hq_id
412
+ hv_id = hq_id
413
+
414
+ # Load FP8 descale factors if needed
415
+ if IS_FP8:
416
+ if IS_GQA:
417
+ # For MQA/GQA, q_descale uses the same indexing as k/v (hk_id)
418
+ q_descale = tl.load(
419
+ Q_Descale + z_id * stride_q_descale_z + hk_id * stride_q_descale_h
420
+ )
421
+ else:
422
+ # For MHA, q_descale uses hq_id
423
+ q_descale = tl.load(
424
+ Q_Descale + z_id * stride_q_descale_z + hq_id * stride_q_descale_h
425
+ )
426
+ k_descale = tl.load(
427
+ K_Descale + z_id * stride_k_descale_z + hk_id * stride_k_descale_h
428
+ )
429
+ v_descale = tl.load(
430
+ V_Descale + z_id * stride_v_descale_z + hv_id * stride_v_descale_h
431
+ )
432
+ else:
433
+ q_descale, k_descale, v_descale = 1.0, 1.0, 1.0
434
+
435
+ # figure out seqlens
436
+ lo = pid_splitk * BLOCK_N_PER_SPLIT
437
+ if USE_CACHE_SEQLENs:
438
+ cache_seqlen_last_idx = tl.load(Cache_seqlens + z_id)
439
+ N_CTX_K_FINAL = cache_seqlen_last_idx
440
+ else:
441
+ N_CTX_K_FINAL = N_CTX_K
442
+ hi = tl.minimum((pid_splitk + 1) * BLOCK_N_PER_SPLIT, N_CTX_K_FINAL)
443
+
444
+ # pick batch index
445
+ if USE_CACHE_BATCH_IDX:
446
+ cache_batch_idx = tl.load(Cache_batch_idx + z_id)
447
+ else:
448
+ cache_batch_idx = z_id
449
+
450
+ # compute offsets
451
+ offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
452
+ offs_n = tl.arange(0, BLOCK_N)
453
+ offs_d = tl.arange(0, BLOCK_DMODEL)
454
+
455
+ # compute ptrs
456
+ q_offset = Q + hq_id * stride_qh_i64 + z_id * stride_qz_i64 + g_id * stride_qg_i64
457
+ q_ptrs = (
458
+ q_offset + offs_m[:, None] * stride_qm_i64 + offs_d[None, :] * stride_qd_i64
459
+ )
460
+
461
+ # Handle block table for paged attention
462
+ if USE_BLOCK_TABLE:
463
+ # K and V now point to paged cache
464
+ # Each batch has its own block table row
465
+ block_table_ptr = Block_table + z_id * stride_bt_b
466
+ else:
467
+ k_offset = (
468
+ K
469
+ + hk_id * stride_kh_i64
470
+ + cache_batch_idx * stride_kz_i64
471
+ + g_id * stride_kg_i64
472
+ )
473
+ v_offset = (
474
+ V
475
+ + hv_id * stride_vh_i64
476
+ + cache_batch_idx * stride_vz_i64
477
+ + g_id * stride_vg_i64
478
+ )
479
+
480
+ # compute masks
481
+ if PADDED_HEAD:
482
+ q_mask = (offs_m < N_CTX_Q)[:, None] & (offs_d < ACTUAL_BLOCK_DMODEL)[None, :]
483
+ kT_mask = (offs_d < ACTUAL_BLOCK_DMODEL)[:, None] & (offs_n < N_CTX_K_FINAL)[
484
+ None, :
485
+ ]
486
+ v_mask = (offs_n < N_CTX_K_FINAL)[:, None] & (offs_d < ACTUAL_BLOCK_DMODEL)[
487
+ None, :
488
+ ]
489
+ osk_mask = (offs_m < N_CTX_Q)[:, None] & (offs_d < ACTUAL_BLOCK_DMODEL)[None, :]
490
+ else:
491
+ q_mask = (offs_m < N_CTX_Q)[:, None]
492
+ kT_mask = (offs_n < N_CTX_K_FINAL)[None, :]
493
+ v_mask = (offs_n < N_CTX_K_FINAL)[:, None]
494
+ osk_mask = (offs_m < N_CTX_Q)[:, None]
495
+
496
+ # scale sm_scale by log_2(e) and use
497
+ # 2^x instead of exp in the loop because CSE and LICM
498
+ # don't work as expected with `exp` in the loop
499
+ qk_scale = sm_scale * 1.44269504
500
+
501
+ # load q: it will stay in SRAM throughout
502
+ q = tl.load(q_ptrs, mask=q_mask, other=0.0)
503
+ q = (q * qk_scale).to(q.dtype)
504
+
505
+ # load ALiBi slope if enabled
506
+ if USE_ALIBI:
507
+ a_offset = z_id * stride_az + hq_id * stride_ah
508
+ alibi_slope = tl.load(Alibi_slopes + a_offset)
509
+ else:
510
+ alibi_slope = None
511
+
512
+ # initialize pointer to m and l
513
+ m_i = tl.full([BLOCK_M], float("-inf"), dtype=tl.float32)
514
+ l_i = tl.zeros([BLOCK_M], dtype=tl.float32)
515
+ acc = tl.zeros([BLOCK_M, BLOCK_DMODEL], dtype=tl.float32) # noqa: F821
516
+
517
+ # loop over k, v and update accumulator
518
+ if USE_BLOCK_TABLE:
519
+ # Paged attention: process all KV blocks from cache
520
+ # Note: Cache should be updated externally before calling this kernel
521
+ num_kv_blocks = (N_CTX_K_FINAL + BLOCK_SIZE_K - 1) // BLOCK_SIZE_K
522
+
523
+ for block_idx in range(num_kv_blocks):
524
+ # Calculate sequence range for this block
525
+ block_start = block_idx * BLOCK_SIZE_K
526
+ block_end = tl.minimum(block_start + BLOCK_SIZE_K, N_CTX_K_FINAL)
527
+
528
+ # Check if block overlaps with our split-k range [lo, hi)
529
+ if block_end > lo and block_start < hi:
530
+ # Load physical block number
531
+ physical_block = tl.load(block_table_ptr + block_idx * stride_bt_s)
532
+
533
+ # Calculate the range within this block that overlaps with [lo, hi)
534
+ process_start = tl.maximum(lo - block_start, 0)
535
+ process_end = tl.minimum(hi - block_start, BLOCK_SIZE_K)
536
+ process_end = tl.minimum(process_end, block_end - block_start)
537
+
538
+ # Instead of forcing a floor alignment to BLOCK_N (which can still skip
539
+ # part of the intended range if start falls mid-tile for small splits),
540
+ # start from the raw (possibly unaligned) process_start rounded *down* but
541
+ # allow the loop to begin earlier (at most BLOCK_N before) so that any
542
+ # partial tile overlapping [lo, hi) is covered. Masking below will remove
543
+ # columns < lo or >= hi ensuring numerically identical coverage without
544
+ # duplication.
545
+ aligned_start = (process_start // BLOCK_N) * BLOCK_N
546
+ if aligned_start > 0 and aligned_start + BLOCK_N > process_start:
547
+ # ensure we include the tile that contains process_start
548
+ process_start = aligned_start
549
+ else:
550
+ process_start = aligned_start
551
+
552
+ for offset in range(process_start, process_end, BLOCK_N):
553
+ # Current position (may begin slightly before logical split range; masking fixes it)
554
+ pos = block_start + offset
555
+ # Proceed unconditionally; masking below enforces [lo, hi)
556
+ # Calculate base addresses for K and V in this physical block
557
+ k_base = (
558
+ K
559
+ + physical_block * BLOCK_SIZE_K * stride_kn_i64
560
+ + hk_id * stride_kh_i64
561
+ + g_id * stride_kg_i64
562
+ )
563
+ v_base = (
564
+ V
565
+ + physical_block * BLOCK_SIZE_K * stride_vn_i64
566
+ + hv_id * stride_vh_i64
567
+ + g_id * stride_vg_i64
568
+ )
569
+
570
+ # Offsets within the current block
571
+ block_offs = offset + offs_n
572
+
573
+ # Masks for valid data respecting:
574
+ # (1) global key length (seq_mask)
575
+ # (2) block bounds (block_mask)
576
+ # (3) current split range [lo, hi)
577
+ seq_mask = (pos + offs_n) < N_CTX_K_FINAL
578
+ block_mask = block_offs < BLOCK_SIZE_K
579
+ end_mask = block_offs < process_end
580
+ split_mask = ((pos + offs_n) >= lo) & ((pos + offs_n) < hi)
581
+ col_mask = seq_mask & block_mask & end_mask & split_mask
582
+
583
+ # Apply masks
584
+ kT_mask_final = kT_mask & col_mask[None, :]
585
+ v_mask_final = v_mask & col_mask[:, None]
586
+
587
+ # Load K and V
588
+ kT_ptrs = (
589
+ k_base
590
+ + offs_d[:, None] * stride_kd_i64
591
+ + block_offs[None, :] * stride_kn_i64
592
+ )
593
+ v_ptrs = (
594
+ v_base
595
+ + block_offs[:, None] * stride_vn_i64
596
+ + offs_d[None, :] * stride_vd_i64
597
+ )
598
+
599
+ kT = tl.load(kT_ptrs, mask=kT_mask_final, other=0.0)
600
+ v = tl.load(v_ptrs, mask=v_mask_final, other=0.0)
601
+
602
+ # Unified inner function handles both paged and contiguous
603
+ m_i, l_i, acc = _attn_fwd_inner(
604
+ q,
605
+ kT,
606
+ v,
607
+ pos,
608
+ col_mask,
609
+ m_i,
610
+ l_i,
611
+ acc,
612
+ pid_m,
613
+ q_descale,
614
+ k_descale,
615
+ v_descale,
616
+ alibi_slope,
617
+ True,
618
+ IS_FP8,
619
+ BLOCK_M,
620
+ BLOCK_N,
621
+ N_CTX_Q,
622
+ N_CTX_K_FINAL,
623
+ USE_ALIBI,
624
+ USE_SLIDING_WINDOW,
625
+ IS_CAUSAL,
626
+ WINDOW_SIZE_LEFT,
627
+ WINDOW_SIZE_RIGHT,
628
+ )
629
+ else:
630
+ # Non-paged attention: process KV from cache
631
+ # Note: Cache should be updated externally before calling this kernel
632
+ # Compute bounds check flag once: needed if split size not aligned to BLOCK_N or variable seqlens
633
+ bounds_checks_n = ((BLOCK_N_PER_SPLIT % BLOCK_N) > 0) | USE_CACHE_SEQLENs
634
+ # loop over k, v and update accumulator
635
+ for start_n in range(lo, hi, BLOCK_N):
636
+ kT_ptrs = (
637
+ k_offset
638
+ + offs_d[:, None] * stride_kd_i64
639
+ + (start_n + offs_n)[None, :] * stride_kn_i64
640
+ )
641
+ V_ptrs = (
642
+ v_offset
643
+ + (start_n + offs_n)[:, None] * stride_vn_i64
644
+ + offs_d[None, :] * stride_vd_i64
645
+ )
646
+
647
+ # load k
648
+ kT = tl.load(kT_ptrs, mask=kT_mask, other=0.0)
649
+ v = tl.load(V_ptrs, mask=v_mask, other=0.0)
650
+
651
+ # Use the same inner loop logic
652
+ # Precompute column validity mask for this tile (all True for full tiles).
653
+ # hi is the upper bound of the overall split range; start_n marks this tile's base.
654
+ col_valid_mask = offs_n < (hi - start_n)
655
+
656
+ m_i, l_i, acc = _attn_fwd_inner(
657
+ q,
658
+ kT,
659
+ v,
660
+ start_n,
661
+ col_valid_mask,
662
+ m_i,
663
+ l_i,
664
+ acc,
665
+ pid_m,
666
+ q_descale,
667
+ k_descale,
668
+ v_descale,
669
+ alibi_slope,
670
+ bounds_checks_n,
671
+ IS_FP8,
672
+ BLOCK_M,
673
+ BLOCK_N,
674
+ N_CTX_Q,
675
+ N_CTX_K_FINAL,
676
+ USE_ALIBI,
677
+ USE_SLIDING_WINDOW,
678
+ IS_CAUSAL,
679
+ WINDOW_SIZE_LEFT,
680
+ WINDOW_SIZE_RIGHT,
681
+ )
682
+
683
+ # write back O
684
+ osk_offset = (
685
+ Out_splitK + pid_zhg * stride_osk_zhg_i64 + pid_splitk * stride_osk_s_i64
686
+ )
687
+ osk_ptrs = (
688
+ osk_offset
689
+ + offs_m[:, None] * stride_osk_m_i64
690
+ + offs_d[None, :] * stride_osk_d_i64
691
+ )
692
+ tl.store(
693
+ osk_ptrs,
694
+ acc,
695
+ mask=osk_mask,
696
+ )
697
+
698
+ # write metadata for split-K reduction
699
+ metadata_offset = Metadata + pid_zhg * stride_mzhg_i64 + pid_splitk * stride_ms_i64
700
+ metadata_ptr = metadata_offset + offs_m
701
+ tl.store(metadata_ptr, m_i)
702
+ tl.store(metadata_ptr + stride_m2_i64, l_i)
703
+
704
+
705
+ FWD_DECODE_REDUCE_AUTOTUNE_KEYS = [
706
+ "BLOCK_DMODEL",
707
+ "split_k",
708
+ ]
709
+
710
+
711
+ @triton.autotune(
712
+ configs=fwd_decode_reduce_configs,
713
+ key=FWD_DECODE_REDUCE_AUTOTUNE_KEYS,
714
+ use_cuda_graph=True,
715
+ )
716
+ @triton.jit
717
+ def _splitK_reduce(
718
+ Out_splitK, # [B*H*G, split_k, Mq, K]
719
+ Metadata, # [B*H*G, 2, split_k, M_ceil] contains [mi, li]
720
+ Out, # [B, H, G, M, K]
721
+ LSE, # [B*H*G, M]
722
+ stride_osk_zhg,
723
+ stride_osk_s,
724
+ stride_osk_m,
725
+ stride_osk_k,
726
+ stride_mzhg,
727
+ stride_m2,
728
+ stride_ms,
729
+ stride_mm,
730
+ stride_oz,
731
+ stride_oh,
732
+ stride_og,
733
+ stride_om,
734
+ stride_ok,
735
+ stride_lse_zhg,
736
+ stride_lse_m,
737
+ K_BLOCK_SIZE: tl.constexpr,
738
+ BLOCK_DMODEL: tl.constexpr,
739
+ ACTUAL_BLOCK_DMODEL: tl.constexpr,
740
+ H: tl.constexpr,
741
+ G: tl.constexpr,
742
+ split_k: tl.constexpr,
743
+ splitK_pow2: tl.constexpr,
744
+ MASK_SPLITK: tl.constexpr,
745
+ PADDED_HEAD: tl.constexpr,
746
+ ):
747
+ # Cast strides to int64 to prevent overflow in pointer arithmetic.
748
+ stride_osk_zhg_i64 = tl.cast(stride_osk_zhg, tl.int64)
749
+ stride_osk_s_i64 = tl.cast(stride_osk_s, tl.int64)
750
+ stride_osk_m_i64 = tl.cast(stride_osk_m, tl.int64)
751
+ stride_osk_k_i64 = tl.cast(stride_osk_k, tl.int64)
752
+ stride_mzhg_i64 = tl.cast(stride_mzhg, tl.int64)
753
+ stride_m2_i64 = tl.cast(stride_m2, tl.int64)
754
+ stride_ms_i64 = tl.cast(stride_ms, tl.int64)
755
+ stride_mm_i64 = tl.cast(stride_mm, tl.int64)
756
+ stride_oz_i64 = tl.cast(stride_oz, tl.int64)
757
+ stride_oh_i64 = tl.cast(stride_oh, tl.int64)
758
+ stride_og_i64 = tl.cast(stride_og, tl.int64)
759
+ stride_om_i64 = tl.cast(stride_om, tl.int64)
760
+ stride_lse_zhg_i64 = tl.cast(stride_lse_zhg, tl.int64)
761
+
762
+ # get pids
763
+ pid_zhg = tl.program_id(0)
764
+ pid_m = tl.program_id(1)
765
+ pid_k = tl.program_id(2)
766
+
767
+ # compute offsets
768
+ offs_splitK = tl.arange(0, splitK_pow2)
769
+ offs_k = pid_k * K_BLOCK_SIZE + tl.arange(0, K_BLOCK_SIZE)
770
+
771
+ # compute masks
772
+ if PADDED_HEAD:
773
+ o_mask = offs_k < ACTUAL_BLOCK_DMODEL
774
+ else:
775
+ o_mask = None
776
+
777
+ # compute ptrs
778
+ metadata_offset = Metadata + pid_zhg * stride_mzhg_i64
779
+ metadata_ptr = metadata_offset + offs_splitK * stride_ms_i64 + pid_m * stride_mm_i64
780
+
781
+ osk_offset = Out_splitK + pid_zhg * stride_osk_zhg_i64 + pid_m * stride_osk_m_i64
782
+ osk_ptr = (
783
+ osk_offset
784
+ + offs_splitK[:, None] * stride_osk_s_i64
785
+ + offs_k[None, :] * stride_osk_k_i64
786
+ )
787
+
788
+ # read max values of each splitK
789
+ if MASK_SPLITK:
790
+ splitK_mask = offs_splitK < split_k
791
+ l_m = tl.load(metadata_ptr, mask=splitK_mask, other=float("-inf"))
792
+ l_sum = tl.load(metadata_ptr + stride_m2_i64, mask=splitK_mask, other=0.0)
793
+ acc = tl.load(osk_ptr, mask=splitK_mask[:, None], other=0.0)
794
+ else:
795
+ l_m = tl.load(metadata_ptr)
796
+ l_sum = tl.load(metadata_ptr + stride_m2_i64)
797
+ acc = tl.load(osk_ptr)
798
+
799
+ g_m = tl.max(l_m, axis=0)
800
+
801
+ alpha = tl.where(l_m > float("-inf"), tl.math.exp2(l_m - g_m), 0.0)
802
+
803
+ # read sum
804
+ l_sum *= alpha
805
+ g_sum = tl.sum(l_sum, axis=0)
806
+ acc = acc * alpha[:, None]
807
+
808
+ g_sum_safe = tl.where(g_sum > 0, g_sum, 1.0)
809
+ acc_out = tl.sum(acc, axis=0) / g_sum_safe
810
+
811
+ # Store output
812
+ z_id = pid_zhg // (H * G)
813
+ h_id = (pid_zhg // G) % H
814
+ g_id = pid_zhg % G
815
+ out_offset = (
816
+ Out + z_id * stride_oz_i64 + h_id * stride_oh_i64 + g_id * stride_og_i64
817
+ )
818
+ out_ptr = out_offset + pid_m * stride_om_i64 + offs_k
819
+ tl.store(out_ptr, acc_out, mask=o_mask)
820
+
821
+ # Store lse
822
+ l_ptrs = LSE + pid_zhg * stride_lse_zhg_i64 + pid_m
823
+ lse_val = tl.where(g_sum > 0, (g_m + tl.math.log2(g_sum)) / 1.44269504, g_m)
824
+ tl.store(l_ptrs, lse_val)
825
+
826
+
827
+ @triton.jit
828
+ def cast_uint32_to_half2(scale_shift):
829
+ # Extract two float16 packed into one int32
830
+ scale = scale_shift & 0xFFFF
831
+ shift = scale_shift >> 16
832
+ scale = scale.to(tl.uint16).to(tl.float16, bitcast=True)
833
+ shift = shift.to(tl.uint16).to(tl.float16, bitcast=True)
834
+ return scale, shift
835
+
836
+
837
+ @triton.jit
838
+ def dequantize(
839
+ x_,
840
+ scale,
841
+ shift,
842
+ PACKED_PER_VAL: tl.constexpr = 8,
843
+ ):
844
+ # PACKED_PER_VAL is the number of values packed into
845
+ # each element x_. For example, for int4 quantization
846
+ # and x_ of type int32, PACKED_PER_VAL is 8.
847
+
848
+ BLOCK_N: tl.constexpr = x_.shape[0]
849
+ BLOCK_DMODEL_PACKED: tl.constexpr = x_.shape[1]
850
+ offsets = tl.arange(0, PACKED_PER_VAL) * 4
851
+ quant_offset = (
852
+ x_[:, None, :] >> offsets[None, :, None]
853
+ ) # (BLOCK_N, PACKED_PER_VAL, D // PACKED_PER_VAL)
854
+
855
+ quant_offset = tl.view(
856
+ quant_offset, (BLOCK_N, BLOCK_DMODEL_PACKED * PACKED_PER_VAL)
857
+ )
858
+ # Trick - instead of converting int4 to float16 we view it as float16
859
+ # and then multiply by 32768 * 512 == 2**24
860
+ quant_offset = (quant_offset & 0xF).to(tl.uint16).to(tl.float16, bitcast=True)
861
+ quant_offset = (quant_offset * 32768.0).to(tl.float16)
862
+ scale_512 = scale * 512
863
+
864
+ dequant = quant_offset * scale_512 + shift
865
+ return dequant
866
+
867
+
868
+ def quantize_kv_int4(k: torch.Tensor, num_groups: int = 1) -> torch.Tensor:
869
+ # Scale and shift are such that quantization linearly maps
870
+ # int4 values range [0..15] to input values range min(k)..max(k)
871
+ # individually for every row
872
+ k = k.reshape(*k.shape[:-1], num_groups, k.shape[-1] // num_groups)
873
+ max_vals = torch.max(k, dim=-1, keepdim=True).values
874
+ min_vals = torch.min(k, dim=-1, keepdim=True).values
875
+ scale_k: torch.Tensor = (max_vals - min_vals) / 15
876
+
877
+ shift_k = torch.min(k, dim=-1, keepdim=True).values
878
+ scale_k = scale_k.to(torch.float16)
879
+ shift_k = shift_k.to(torch.float16)
880
+
881
+ in_bytes = ((k - shift_k.expand(k.shape)) / scale_k.expand(k.shape)) + 0.5
882
+ in_bytes = in_bytes.to(torch.uint8)
883
+ in_int4 = in_bytes & 0xF
884
+ in_int4_packed = in_int4[..., ::2] + (in_int4[..., 1::2] << 4)
885
+ scale_shift = torch.concat(
886
+ [scale_k.view(torch.uint8), shift_k.view(torch.uint8)], dim=-1
887
+ )
888
+ k_quant = torch.concat(
889
+ [
890
+ scale_shift.flatten(start_dim=-2),
891
+ in_int4_packed.flatten(start_dim=-2),
892
+ ],
893
+ dim=-1,
894
+ ).view(torch.int16)
895
+ return k_quant
896
+
897
+
898
+ def dequantize_kv_fp16(quant_k: torch.Tensor, num_groups: int = 1) -> torch.Tensor:
899
+ k_i16 = quant_k.view(torch.int16)
900
+ k_ui8 = k_i16.view(torch.uint8)
901
+
902
+ ss_size = num_groups * 4
903
+ scale_shift_ui8 = k_ui8[..., 0:ss_size]
904
+ scale_shift_ui8 = scale_shift_ui8.reshape(
905
+ *scale_shift_ui8.shape[:-1], num_groups, 4
906
+ )
907
+ scale = scale_shift_ui8[..., 0:2].view(torch.float16)
908
+ shift = scale_shift_ui8[..., 2:4].view(torch.float16)
909
+
910
+ kv_ui8 = k_ui8[..., ss_size:]
911
+ k_ui8 = kv_ui8.reshape(*kv_ui8.shape[:-1], num_groups, -1)
912
+ k1_i4 = k_ui8 & 0xF
913
+ k2_i4 = (k_ui8 & 0xF0) >> 4
914
+ k_shape = k1_i4.shape
915
+ k1_f16 = k1_i4.to(torch.float16) * scale.expand(k_shape) + shift.expand(k_shape)
916
+ k2_f16 = k2_i4.to(torch.float16) * scale.expand(k_shape) + shift.expand(k_shape)
917
+
918
+ out = torch.empty(
919
+ (*k1_f16.shape[:-1], k1_f16.shape[-1] * 2),
920
+ dtype=torch.float16,
921
+ device=quant_k.device,
922
+ )
923
+ out[..., ::2] = k1_f16
924
+ out[..., 1::2] = k2_f16
925
+ out = out.reshape(*k_shape[:-2], -1)
926
+
927
+ return out
928
+
929
+
930
+ def get_split_k(B: int, G: int, H: int, Mk: int) -> int:
931
+ """Heuristic for the number of splits"""
932
+ bh = max(B * H, 1) # NOTE: Handle B*h=0 case
933
+ split_k = max(Mk, 1024) // bh
934
+ max_chunk_size = 64
935
+ while split_k > 0 and Mk / split_k < max_chunk_size:
936
+ split_k = split_k // 2
937
+ while B * H * G * split_k >= 1024:
938
+ split_k = split_k // 2
939
+ split_k = min(split_k, 512)
940
+ split_k = max(split_k, 1)
941
+ return split_k
942
+
943
+
944
+ def attention_forward_decode_triton_impl(
945
+ q: torch.Tensor,
946
+ k_cache: torch.Tensor,
947
+ v_cache: torch.Tensor,
948
+ k_new: Optional[torch.Tensor],
949
+ v_new: Optional[torch.Tensor],
950
+ out: torch.Tensor,
951
+ softmax_lse: torch.Tensor,
952
+ sm_scale: float,
953
+ causal: bool,
954
+ window_size_left: int,
955
+ window_size_right: int,
956
+ alibi_slopes: Optional[torch.Tensor],
957
+ layout: Literal["bshd"],
958
+ cache_seqlens: Optional[torch.Tensor],
959
+ cache_batch_idx: Optional[torch.Tensor],
960
+ block_table: Optional[torch.Tensor] = None,
961
+ q_descale: Optional[torch.Tensor] = None,
962
+ k_descale: Optional[torch.Tensor] = None,
963
+ v_descale: Optional[torch.Tensor] = None,
964
+ # rotary (optional)
965
+ rotary_cos: Optional[torch.Tensor] = None,
966
+ rotary_sin: Optional[torch.Tensor] = None,
967
+ rotary_interleaved: bool = False,
968
+ seqlens_rotary: Optional[torch.Tensor] = None,
969
+ ):
970
+ # Validate layout at entry
971
+ if layout != "bshd":
972
+ raise ValueError(f"{layout} layout is not supported, only 'bshd' is supported")
973
+
974
+ # apply rotary embedding
975
+ if rotary_cos is not None and rotary_sin is not None:
976
+ # Prefer explicitly provided rotary sequence start offsets if given; fall back to cache_seqlens.
977
+ seqlen_offsets = (
978
+ seqlens_rotary
979
+ if seqlens_rotary is not None
980
+ else (cache_seqlens if cache_seqlens is not None else 0)
981
+ )
982
+ local = (window_size_left != -1) or (window_size_right != -1)
983
+ q, k_new = apply_rotary(
984
+ q,
985
+ k_new,
986
+ rotary_cos,
987
+ rotary_sin,
988
+ causal=causal,
989
+ local=local,
990
+ interleaved=rotary_interleaved,
991
+ seqlen_offsets=seqlen_offsets,
992
+ )
993
+
994
+ # handle cache updates
995
+ if k_new is not None and v_new is not None:
996
+ batch_size = k_new.shape[0]
997
+ seqlen_new = k_new.shape[1]
998
+ # shared offset within the new block for every batch element
999
+ token_offsets = torch.arange(seqlen_new, device=k_new.device, dtype=torch.long)
1000
+
1001
+ if block_table is None:
1002
+ # Non-paged attention
1003
+ if cache_seqlens is not None:
1004
+ insert_positions = cache_seqlens.long()[:, None] + token_offsets
1005
+ batch_idx = torch.arange(
1006
+ batch_size, device=k_new.device, dtype=torch.long
1007
+ )[:, None]
1008
+ k_cache[batch_idx, insert_positions] = k_new
1009
+ v_cache[batch_idx, insert_positions] = v_new
1010
+ cache_seqlens.add_(seqlen_new)
1011
+ else:
1012
+ # Append at the end of existing cache
1013
+ seqlen_cache = k_cache.shape[1]
1014
+ k_cache[:, seqlen_cache - seqlen_new :] = k_new
1015
+ v_cache[:, seqlen_cache - seqlen_new :] = v_new
1016
+ else:
1017
+ # Paged attention
1018
+ block_size = k_cache.shape[1]
1019
+
1020
+ if cache_seqlens is not None:
1021
+ start_idx = cache_seqlens.long()
1022
+ else:
1023
+ # Find the first unoccupied position per batch from the block table.
1024
+ invalid_mask = block_table < 0
1025
+ has_invalid = invalid_mask.any(dim=1)
1026
+ first_invalid_idx = invalid_mask.to(torch.int32).argmax(dim=1).long()
1027
+ start_idx = torch.where(
1028
+ has_invalid,
1029
+ first_invalid_idx * block_size,
1030
+ torch.full_like(
1031
+ first_invalid_idx, block_table.shape[1] * block_size
1032
+ ),
1033
+ )
1034
+
1035
+ insert_positions = start_idx[:, None] + token_offsets
1036
+ block_indices = insert_positions // block_size
1037
+ within_block_indices = insert_positions % block_size
1038
+ batch_idx = torch.arange(batch_size, device=k_new.device, dtype=torch.long)[
1039
+ :, None
1040
+ ]
1041
+ physical_blocks = block_table[batch_idx, block_indices]
1042
+
1043
+ flat_phys = physical_blocks.reshape(-1)
1044
+ flat_within = within_block_indices.reshape(-1)
1045
+ k_cache[flat_phys, flat_within] = k_new.reshape(-1, *k_new.shape[2:])
1046
+ v_cache[flat_phys, flat_within] = v_new.reshape(-1, *v_new.shape[2:])
1047
+
1048
+ if cache_seqlens is not None:
1049
+ cache_seqlens.add_(seqlen_new)
1050
+
1051
+ # kernel_configs
1052
+ is_new_kv = False # Cache has been updated, so no new KV in kernel
1053
+ use_alibi, (stride_az, stride_ah) = True if alibi_slopes is not None else False, (
1054
+ alibi_slopes.stride() if alibi_slopes is not None else (None, None)
1055
+ )
1056
+ use_cache_seqlens = cache_seqlens is not None
1057
+ use_sliding_window = window_size_left != -1 or window_size_right != -1
1058
+ use_block_table = block_table is not None
1059
+
1060
+ # get shapes and strides
1061
+ batch_size, seqlen_q, nheads_q, dim_q = get_shape_from_layout(q, layout)
1062
+ stride_qz, stride_qh, stride_qm, stride_qd = get_stride_from_layout(q, layout)
1063
+
1064
+ # Handle paged KV cache layout
1065
+ if use_block_table:
1066
+ # For paged attention, k_cache and v_cache have shape [num_blocks, block_size, nheads, head_dim]
1067
+ num_blocks_kc, block_size_k, nheads_kc, dim_kc = k_cache.shape
1068
+ num_blocks_vc, block_size_v, nheads_vc, dim_vc = v_cache.shape
1069
+ # Get the actual sequence length from cache_seqlens or block_table
1070
+ if cache_seqlens is not None:
1071
+ seqlen_kc = int(cache_seqlens.max().item())
1072
+ else:
1073
+ # Infer from block_table shape [batch_size, num_blocks_per_seq]
1074
+ assert block_table is not None
1075
+ num_blocks_per_seq = block_table.shape[1]
1076
+ seqlen_kc = num_blocks_per_seq * block_size_k
1077
+ seqlen_vc = seqlen_kc
1078
+
1079
+ # Strides for paged layout
1080
+ stride_kc_z = 0 # No batch dimension in paged cache
1081
+ stride_kc_n = k_cache.stride(1) # Sequence stride
1082
+ stride_kc_h = k_cache.stride(2) # Head stride
1083
+ stride_kc_d = k_cache.stride(3) # Dim stride
1084
+
1085
+ stride_vc_z = 0
1086
+ stride_vc_n = v_cache.stride(1)
1087
+ stride_vc_h = v_cache.stride(2)
1088
+ stride_vc_d = v_cache.stride(3)
1089
+ else:
1090
+ _, seqlen_kc, nheads_kc, dim_kc = get_shape_from_layout(k_cache, layout)
1091
+ stride_kc_z, stride_kc_h, stride_kc_n, stride_kc_d = get_stride_from_layout(
1092
+ k_cache, layout
1093
+ )
1094
+ _, seqlen_vc, nheads_vc, dim_vc = get_shape_from_layout(v_cache, layout)
1095
+ stride_vc_z, stride_vc_h, stride_vc_n, stride_vc_d = get_stride_from_layout(
1096
+ v_cache, layout
1097
+ )
1098
+ block_size_k = 0 # Not used
1099
+ if is_new_kv:
1100
+ _, seqlen_kn, nheads_kn, dim_kn = get_shape_from_layout(k_new, layout)
1101
+ stride_kn_z, stride_kn_h, stride_kn_n, stride_kn_d = get_stride_from_layout(
1102
+ k_new, layout
1103
+ )
1104
+ _, seqlen_vn, nheads_vn, dim_vn = get_shape_from_layout(v_new, layout)
1105
+ stride_vn_z, stride_vn_h, stride_vn_n, stride_vn_d = get_stride_from_layout(
1106
+ v_new, layout
1107
+ )
1108
+ else:
1109
+ _, _seqlen_kn, nheads_kn, _dim_kn = None, None, None, None
1110
+ stride_kn_z, stride_kn_h, stride_kn_n, stride_kn_d = None, None, None, None
1111
+ _, _seqlen_vn, nheads_vn, _dim_vn = None, None, None, None
1112
+ stride_vn_z, stride_vn_h, stride_vn_n, stride_vn_d = None, None, None, None
1113
+ _, seqlen_o, nheads_o, dim_o = get_shape_from_layout(out, layout)
1114
+ stride_oz, stride_oh, stride_om, stride_od = get_stride_from_layout(out, layout)
1115
+ assert (
1116
+ dim_q == dim_kc == dim_vc
1117
+ ), f"Dimensions must match: {dim_q}, {dim_kc}, {dim_vc}"
1118
+
1119
+ # add extra information needed by the kernels
1120
+ (n_group_q, heads_per_group_q), stride_qg = (1, nheads_q), stride_qm
1121
+ (_n_group_k, heads_per_group_k), stride_kc_g = (1, nheads_kc), stride_kc_n
1122
+ (_n_group_v, _heads_per_group_v), stride_vc_g = (1, nheads_vc), stride_vc_n
1123
+ if is_new_kv:
1124
+ (_n_group_kn, _heads_per_group_kn), stride_kn_g = (1, nheads_kn), stride_kn_n
1125
+ (_n_group_vn, _heads_per_group_vn), stride_vn_g = (1, nheads_vn), stride_vn_n
1126
+ else:
1127
+ (_n_group_kn, _heads_per_group_kn), stride_kn_g = (None, None), None
1128
+ (_n_group_vn, _heads_per_group_vn), stride_vn_g = (None, None), None
1129
+ (_n_group_o, _heads_per_group_o), stride_og = (1, nheads_o), stride_om
1130
+
1131
+ # get padded size
1132
+ dim_padded = get_padded_headsize(dim_kc)
1133
+ is_padded_head = dim_padded != dim_kc
1134
+
1135
+ # Handle MQA/GQA case
1136
+ group_size = nheads_q // nheads_kc
1137
+ if group_size > 1:
1138
+ is_gqa = True
1139
+ else:
1140
+ is_gqa = False
1141
+
1142
+ # Use heuristics for split_k
1143
+ if use_block_table:
1144
+ # For paged attention, use the actual sequence length from cache_seqlens
1145
+ max_seqlen = (
1146
+ int(cache_seqlens.max().item())
1147
+ if cache_seqlens is not None
1148
+ else block_size_k
1149
+ )
1150
+ split_k = get_split_k(batch_size, n_group_q, heads_per_group_q, max_seqlen)
1151
+ else:
1152
+ split_k = get_split_k(batch_size, n_group_q, heads_per_group_q, seqlen_kc)
1153
+ split_size = (seqlen_kc + split_k - 1) // split_k
1154
+
1155
+ # setup grid - use lambda to get BLOCK_M from autotune
1156
+ # Use MAX_BLOCK_M for intermediate tensor allocation to ensure enough space
1157
+ seqlen_q_ceil = (seqlen_q + MAX_BLOCK_M - 1) // MAX_BLOCK_M * MAX_BLOCK_M
1158
+
1159
+ def grid(META):
1160
+ return (
1161
+ triton.cdiv(seqlen_q, META["BLOCK_M"]),
1162
+ batch_size * n_group_q * heads_per_group_q,
1163
+ split_k,
1164
+ )
1165
+
1166
+ # create intermediate tensors
1167
+ out_splitk = torch.empty(
1168
+ [
1169
+ batch_size * n_group_q * heads_per_group_q,
1170
+ split_k,
1171
+ seqlen_q_ceil,
1172
+ dim_padded,
1173
+ ],
1174
+ dtype=torch.float32,
1175
+ device=q.device,
1176
+ )
1177
+ metadata = torch.empty(
1178
+ [batch_size * n_group_q * heads_per_group_q, 2, split_k, seqlen_q_ceil],
1179
+ dtype=torch.float32,
1180
+ device=q.device,
1181
+ )
1182
+
1183
+ # Validate pre-allocated softmax_lse tensor
1184
+ # Expected shape after view: (batch_size, n_group_q * heads_per_group_q, seqlen_q)
1185
+ # Internal shape: (batch_size * n_group_q * heads_per_group_q, seqlen_q)
1186
+ expected_h_total = batch_size * n_group_q * heads_per_group_q
1187
+ assert (
1188
+ softmax_lse.shape[0] == batch_size
1189
+ ), f"softmax_lse.shape[0] ({softmax_lse.shape[0]}) must equal batch_size ({batch_size})"
1190
+ assert (
1191
+ softmax_lse.shape[1] == n_group_q * heads_per_group_q
1192
+ ), f"softmax_lse.shape[1] ({softmax_lse.shape[1]}) must equal n_group_q * heads_per_group_q ({n_group_q * heads_per_group_q})"
1193
+ assert (
1194
+ softmax_lse.shape[2] >= seqlen_q
1195
+ ), f"softmax_lse.shape[2] ({softmax_lse.shape[2]}) must be >= seqlen_q ({seqlen_q})"
1196
+ assert (
1197
+ softmax_lse.dtype == torch.float32
1198
+ ), f"softmax_lse must be float32, got {softmax_lse.dtype}"
1199
+ assert softmax_lse.device == q.device, "softmax_lse must be on same device as q"
1200
+
1201
+ # Create internal lse view for kernel use
1202
+ lse = softmax_lse.view(expected_h_total, -1)[:, :seqlen_q].contiguous()
1203
+
1204
+ # get intermediate tensor strides
1205
+ stride_osk_zhg, stride_osk_s, stride_osk_m, stride_osk_d = out_splitk.stride()
1206
+ stride_mzhg, stride_m2, stride_ms, stride_mm = metadata.stride()
1207
+ stride_lse_zhg, stride_lse_m = lse.stride()
1208
+
1209
+ # Block table strides
1210
+ if use_block_table:
1211
+ assert block_table is not None
1212
+ stride_bt_b, stride_bt_s = block_table.stride()
1213
+ else:
1214
+ stride_bt_b, stride_bt_s = 0, 0
1215
+
1216
+ # FP8 support
1217
+ IS_FP8 = is_fp8([q, k_cache, v_cache])
1218
+ if IS_FP8:
1219
+ arch = get_arch()
1220
+ if not arch.supports_fp8:
1221
+ raise RuntimeError(f"{arch.name} does not support FP8")
1222
+ rec_dtype = arch.recommended_fp8_dtype(q.dtype)
1223
+ if (
1224
+ q.dtype != rec_dtype
1225
+ or k_cache.dtype != rec_dtype
1226
+ or v_cache.dtype != rec_dtype
1227
+ ):
1228
+ warnings.warn(
1229
+ f"Use {rec_dtype} data type on {arch}. Got q: {q.dtype}, k: {k_cache.dtype}, v: {v_cache.dtype}",
1230
+ UserWarning,
1231
+ )
1232
+ if (q_descale is None) or (k_descale is None) or (v_descale is None):
1233
+ warnings.warn(
1234
+ "FP8 tensors detected but descale factors not provided. Using default scale of 1.0",
1235
+ UserWarning,
1236
+ )
1237
+ # Create default descale tensors if not provided
1238
+ if q_descale is None:
1239
+ q_descale = torch.ones(
1240
+ batch_size, nheads_q, dtype=torch.float32, device=q.device
1241
+ )
1242
+ if k_descale is None:
1243
+ k_descale = torch.ones(
1244
+ batch_size, nheads_kc, dtype=torch.float32, device=q.device
1245
+ )
1246
+ if v_descale is None:
1247
+ v_descale = torch.ones(
1248
+ batch_size, nheads_vc, dtype=torch.float32, device=q.device
1249
+ )
1250
+ else:
1251
+ # Enforce exact expected shapes; no reshaping or normalization.
1252
+ assert (
1253
+ q_descale.dim() == 2
1254
+ and q_descale.shape[0] == batch_size
1255
+ and q_descale.shape[1] == nheads_kc
1256
+ ), f"q_descale expected shape ({batch_size}, {nheads_kc}) got {tuple(q_descale.shape)}"
1257
+ assert (
1258
+ k_descale.dim() == 2
1259
+ and k_descale.shape[0] == batch_size
1260
+ and k_descale.shape[1] == nheads_kc
1261
+ ), f"k_descale expected shape ({batch_size}, {nheads_kc}) got {tuple(k_descale.shape)}"
1262
+ assert (
1263
+ v_descale.dim() == 2
1264
+ and v_descale.shape[0] == batch_size
1265
+ and v_descale.shape[1] == nheads_kc
1266
+ ), f"v_descale expected shape ({batch_size}, {nheads_kc}) got {tuple(v_descale.shape)}"
1267
+ stride_q_descale_z, stride_q_descale_h = q_descale.stride()
1268
+ stride_k_descale_z, stride_k_descale_h = k_descale.stride()
1269
+ stride_v_descale_z, stride_v_descale_h = v_descale.stride()
1270
+ else:
1271
+ q_descale = None
1272
+ k_descale = None
1273
+ v_descale = None
1274
+ stride_q_descale_z = 0
1275
+ stride_q_descale_h = 0
1276
+ stride_k_descale_z = 0
1277
+ stride_k_descale_h = 0
1278
+ stride_v_descale_z = 0
1279
+ stride_v_descale_h = 0
1280
+
1281
+ if DEBUG:
1282
+ print(
1283
+ "batch_size, seqlen_q, nheads_q, dim_q",
1284
+ (batch_size, seqlen_q, nheads_q, dim_q),
1285
+ )
1286
+ print("_, seqlen_kc, nheads_kc, dim_kc", (_, seqlen_kc, nheads_kc, dim_kc))
1287
+ print("dim_padded:", dim_padded)
1288
+ print(
1289
+ "stride_qz, stride_qm, stride_qg, stride_qh, stride_qd",
1290
+ (stride_qz, stride_qm, stride_qg, stride_qh, stride_qd),
1291
+ )
1292
+ print(
1293
+ "stride_kc_z, stride_kc_n, stride_kc_g, stride_kc_h, stride_kc_d",
1294
+ (stride_kc_z, stride_kc_n, stride_kc_g, stride_kc_h, stride_kc_d),
1295
+ )
1296
+ print(
1297
+ "stride_vc_z, stride_vc_n, stride_vc_g, stride_vc_h, stride_vc_d",
1298
+ (stride_vc_z, stride_vc_n, stride_vc_g, stride_vc_h, stride_vc_d),
1299
+ )
1300
+ if is_new_kv:
1301
+ print(
1302
+ "stride_kn_z, stride_kn_n, stride_kn_g, stride_kn_h, stride_kn_d",
1303
+ (stride_kn_z, stride_kn_n, stride_kn_g, stride_kn_h, stride_kn_d),
1304
+ )
1305
+ print(
1306
+ "stride_vn_z, stride_vn_n, stride_vn_g, stride_vn_h, stride_vn_d",
1307
+ (stride_vn_z, stride_vn_n, stride_vn_g, stride_vn_h, stride_vn_d),
1308
+ )
1309
+ print(
1310
+ "stride_oz, stride_om, stride_og, stride_oh, stride_od",
1311
+ (stride_oz, stride_om, stride_og, stride_oh, stride_od),
1312
+ )
1313
+ print(
1314
+ "stride_osk_zhg, stride_osk_s, stride_osk_m, stride_osk_d",
1315
+ (stride_osk_zhg, stride_osk_s, stride_osk_m, stride_osk_d),
1316
+ )
1317
+ print(
1318
+ "stride_mzhg, stride_m2, stride_ms, stride_mm",
1319
+ (stride_mzhg, stride_m2, stride_ms, stride_mm),
1320
+ )
1321
+ print("stride_lse_zhg, stride_lse_m", (stride_lse_zhg, stride_lse_m))
1322
+
1323
+ _fwd_kernel_splitK[grid](
1324
+ Q=q,
1325
+ K=k_cache,
1326
+ V=v_cache,
1327
+ Q_Descale=q_descale,
1328
+ K_Descale=k_descale,
1329
+ V_Descale=v_descale,
1330
+ sm_scale=sm_scale,
1331
+ Out_splitK=out_splitk,
1332
+ Metadata=metadata,
1333
+ K_new=None,
1334
+ V_new=None,
1335
+ Cache_seqlens=cache_seqlens,
1336
+ Cache_batch_idx=cache_batch_idx,
1337
+ Block_table=block_table,
1338
+ Alibi_slopes=alibi_slopes,
1339
+ # q strides
1340
+ stride_qz=stride_qz,
1341
+ stride_qm=stride_qm,
1342
+ stride_qg=stride_qg,
1343
+ stride_qh=stride_qh,
1344
+ stride_qd=stride_qd,
1345
+ # k strides
1346
+ stride_kz=stride_kc_z,
1347
+ stride_kn=stride_kc_n,
1348
+ stride_kg=stride_kc_g,
1349
+ stride_kh=stride_kc_h,
1350
+ stride_kd=stride_kc_d,
1351
+ # v strides
1352
+ stride_vz=stride_vc_z,
1353
+ stride_vn=stride_vc_n,
1354
+ stride_vg=stride_vc_g,
1355
+ stride_vh=stride_vc_h,
1356
+ stride_vd=stride_vc_d,
1357
+ # out_splitk strides
1358
+ stride_osk_zhg=stride_osk_zhg,
1359
+ stride_osk_s=stride_osk_s,
1360
+ stride_osk_m=stride_osk_m,
1361
+ stride_osk_d=stride_osk_d,
1362
+ # metadata strides
1363
+ stride_mzhg=stride_mzhg,
1364
+ stride_m2=stride_m2,
1365
+ stride_ms=stride_ms,
1366
+ stride_mm=stride_mm,
1367
+ # k_new strides
1368
+ stride_kn_z=stride_kn_z,
1369
+ stride_kn_n=stride_kn_n,
1370
+ stride_kn_g=stride_kn_g,
1371
+ stride_kn_h=stride_kn_h,
1372
+ stride_kn_d=stride_kn_d,
1373
+ # v_new strides
1374
+ stride_vn_z=stride_vn_z,
1375
+ stride_vn_n=stride_vn_n,
1376
+ stride_vn_g=stride_vn_g,
1377
+ stride_vn_h=stride_vn_h,
1378
+ stride_vn_d=stride_vn_d,
1379
+ # block table strides
1380
+ stride_bt_b=stride_bt_b,
1381
+ stride_bt_s=stride_bt_s,
1382
+ # alibi strides
1383
+ stride_az=stride_az,
1384
+ stride_ah=stride_ah,
1385
+ # FP8 descale strides
1386
+ stride_q_descale_z=stride_q_descale_z,
1387
+ stride_q_descale_h=stride_q_descale_h,
1388
+ stride_k_descale_z=stride_k_descale_z,
1389
+ stride_k_descale_h=stride_k_descale_h,
1390
+ stride_v_descale_z=stride_v_descale_z,
1391
+ stride_v_descale_h=stride_v_descale_h,
1392
+ Z=batch_size,
1393
+ H_q=heads_per_group_q,
1394
+ H_kv=heads_per_group_k,
1395
+ G_q=n_group_q,
1396
+ N_CTX_Q=seqlen_q,
1397
+ N_CTX_K=seqlen_kc,
1398
+ N_CTX_NEW=0, # No new KV, cache already updated
1399
+ BLOCK_N_PER_SPLIT=split_size,
1400
+ BLOCK_SIZE_K=block_size_k if use_block_table else 256,
1401
+ BLOCK_DMODEL=dim_padded,
1402
+ ACTUAL_BLOCK_DMODEL=dim_kc,
1403
+ USE_CACHE_SEQLENs=use_cache_seqlens,
1404
+ USE_CACHE_BATCH_IDX=cache_batch_idx is not None,
1405
+ NEW_KV=False, # Cache already updated
1406
+ IS_GQA=is_gqa,
1407
+ IS_CAUSAL=causal,
1408
+ USE_ALIBI=use_alibi,
1409
+ PADDED_HEAD=is_padded_head,
1410
+ GROUP_SIZE=group_size,
1411
+ USE_SLIDING_WINDOW=use_sliding_window,
1412
+ WINDOW_SIZE_LEFT=window_size_left,
1413
+ WINDOW_SIZE_RIGHT=window_size_right,
1414
+ USE_BLOCK_TABLE=use_block_table,
1415
+ IS_FP8=IS_FP8,
1416
+ )
1417
+
1418
+ if DEBUG:
1419
+ print("Out_splitK:", out_splitk, out_splitk.shape)
1420
+ print("metadata:", metadata, metadata.shape)
1421
+ print("lse:", lse, lse.shape)
1422
+ print("Out:", out, out.shape)
1423
+
1424
+ # Merge together
1425
+ splitK_pow2 = triton.next_power_of_2(split_k)
1426
+ mask_split_k = splitK_pow2 > split_k
1427
+ if batch_size * n_group_q * heads_per_group_q * seqlen_q >= 512:
1428
+ k_block_num = 1
1429
+ else:
1430
+ k_block_num = 2
1431
+ assert dim_padded % k_block_num == 0
1432
+ k_block_size = dim_padded // k_block_num
1433
+ reduce_grid = (batch_size * n_group_q * heads_per_group_q, seqlen_q, k_block_num)
1434
+
1435
+ if DEBUG:
1436
+ print("splitK_pow2:", splitK_pow2)
1437
+ print("k_block_num:", k_block_num)
1438
+ print("k_block_size:", k_block_size)
1439
+ print("grid:", reduce_grid)
1440
+
1441
+ _splitK_reduce[reduce_grid](
1442
+ out_splitk,
1443
+ metadata,
1444
+ out,
1445
+ lse,
1446
+ # Split-K output strides
1447
+ stride_osk_zhg=stride_osk_zhg,
1448
+ stride_osk_s=stride_osk_s,
1449
+ stride_osk_m=stride_osk_m,
1450
+ stride_osk_k=stride_osk_d,
1451
+ # Metadata strides
1452
+ stride_mzhg=stride_mzhg,
1453
+ stride_m2=stride_m2,
1454
+ stride_ms=stride_ms,
1455
+ stride_mm=stride_mm,
1456
+ # Output tensor strides
1457
+ stride_oz=stride_oz,
1458
+ stride_oh=stride_oh,
1459
+ stride_og=stride_og,
1460
+ stride_om=stride_om,
1461
+ stride_ok=stride_od,
1462
+ # LSE strides
1463
+ stride_lse_zhg=stride_lse_zhg,
1464
+ stride_lse_m=stride_lse_m,
1465
+ K_BLOCK_SIZE=k_block_size,
1466
+ BLOCK_DMODEL=dim_padded,
1467
+ ACTUAL_BLOCK_DMODEL=dim_kc,
1468
+ G=n_group_q,
1469
+ H=heads_per_group_q,
1470
+ # TODO: Tune num_warps
1471
+ split_k=split_k,
1472
+ splitK_pow2=splitK_pow2,
1473
+ MASK_SPLITK=mask_split_k,
1474
+ PADDED_HEAD=is_padded_head,
1475
+ )
build/torch-rocm/_kernels/flash_attn_triton_amd/fwd_prefill.py ADDED
@@ -0,0 +1,1875 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ import torch
3
+ import triton
4
+ import triton.language as tl
5
+ from typing import Literal, Optional
6
+ from .common import compute_alibi_block, compute_fp8_scaling_factors, apply_rotary
7
+ from .utils import (
8
+ AUTOTUNE,
9
+ AutotuneMode,
10
+ DEBUG,
11
+ FWD_CONF_OVERRIDE,
12
+ get_arch,
13
+ is_fp8,
14
+ remap_xcd,
15
+ )
16
+
17
+ FWD_PREFILL_AUTOTUNE_KEYS = [
18
+ "IS_CAUSAL",
19
+ "dropout_p",
20
+ "MAX_SEQLENS_Q",
21
+ "MAX_SEQLENS_K",
22
+ "ACTUAL_BLOCK_DMODEL_QK",
23
+ "ACTUAL_BLOCK_DMODEL_V",
24
+ "IS_VARLEN",
25
+ "HQ",
26
+ "HK",
27
+ ]
28
+
29
+
30
+ def get_fwd_prefill_configs(mode: AutotuneMode):
31
+ # NOTE: Tests expect specific BLOCK_N sizes for attention score renormalization:
32
+ # - CDNA: BLOCK_N=64
33
+ # - RDNA: BLOCK_N=32
34
+ # See _get_block_size_n_triton() in test_flash_attn_triton_amd.py
35
+
36
+ if mode == "off":
37
+ if FWD_CONF_OVERRIDE:
38
+ return [FWD_CONF_OVERRIDE]
39
+ arch = get_arch()
40
+ if arch.name == "gfx950":
41
+ return [
42
+ triton.Config(
43
+ {
44
+ "BLOCK_M": 128,
45
+ "BLOCK_N": 64,
46
+ "waves_per_eu": 2,
47
+ "PRE_LOAD_V": False,
48
+ },
49
+ num_stages=1,
50
+ num_warps=4,
51
+ ),
52
+ ]
53
+ elif arch.name == "gfx942":
54
+ return [
55
+ triton.Config(
56
+ {
57
+ "BLOCK_M": 128,
58
+ "BLOCK_N": 64,
59
+ "waves_per_eu": 2,
60
+ "PRE_LOAD_V": False,
61
+ },
62
+ num_stages=1,
63
+ num_warps=4,
64
+ ),
65
+ ]
66
+ elif arch.is_rdna:
67
+ BLOCK_N = 64 if arch.name == "gfx1100" else 32
68
+ return [
69
+ triton.Config(
70
+ {
71
+ "BLOCK_M": 128,
72
+ "BLOCK_N": BLOCK_N,
73
+ "PRE_LOAD_V": False,
74
+ "waves_per_eu": 6,
75
+ },
76
+ num_stages=1,
77
+ num_warps=8,
78
+ ),
79
+ ]
80
+ else:
81
+ return [
82
+ triton.Config(
83
+ {
84
+ "BLOCK_M": 64,
85
+ "BLOCK_N": 64,
86
+ "waves_per_eu": 2,
87
+ "PRE_LOAD_V": False,
88
+ },
89
+ num_stages=1,
90
+ num_warps=4,
91
+ )
92
+ ]
93
+
94
+ elif mode == "on":
95
+ if FWD_CONF_OVERRIDE:
96
+ return [FWD_CONF_OVERRIDE]
97
+ arch = get_arch()
98
+ if arch.name == "gfx950":
99
+ return [
100
+ triton.Config(
101
+ {
102
+ "BLOCK_M": 128,
103
+ "BLOCK_N": 64,
104
+ "waves_per_eu": 2,
105
+ "PRE_LOAD_V": False,
106
+ },
107
+ num_stages=1,
108
+ num_warps=4,
109
+ ),
110
+ triton.Config(
111
+ {
112
+ "BLOCK_M": 128,
113
+ "BLOCK_N": 64,
114
+ "waves_per_eu": 2,
115
+ "PRE_LOAD_V": False,
116
+ },
117
+ num_stages=1,
118
+ num_warps=2,
119
+ ),
120
+ triton.Config(
121
+ {
122
+ "BLOCK_M": 128,
123
+ "BLOCK_N": 64,
124
+ "waves_per_eu": 2,
125
+ "PRE_LOAD_V": False,
126
+ },
127
+ num_stages=2,
128
+ num_warps=4,
129
+ ),
130
+ triton.Config(
131
+ {
132
+ "BLOCK_M": 128,
133
+ "BLOCK_N": 128,
134
+ "waves_per_eu": 2,
135
+ "PRE_LOAD_V": False,
136
+ },
137
+ num_stages=2,
138
+ num_warps=4,
139
+ ),
140
+ ]
141
+ elif arch.name == "gfx942":
142
+ if arch.cu_count < 304:
143
+ return [
144
+ triton.Config(
145
+ {
146
+ "BLOCK_M": 128,
147
+ "BLOCK_N": 64,
148
+ "waves_per_eu": 2,
149
+ "PRE_LOAD_V": False,
150
+ },
151
+ num_stages=1,
152
+ num_warps=4,
153
+ ),
154
+ ]
155
+ else:
156
+ return [
157
+ triton.Config(
158
+ {
159
+ "BLOCK_M": 128,
160
+ "BLOCK_N": 64,
161
+ "waves_per_eu": 2,
162
+ "PRE_LOAD_V": False,
163
+ },
164
+ num_stages=1,
165
+ num_warps=4,
166
+ )
167
+ ]
168
+ elif arch.is_rdna:
169
+ BLOCK_N = 64 if arch.name == "gfx1100" else 32
170
+ return [
171
+ triton.Config(
172
+ {
173
+ "BLOCK_M": 128,
174
+ "BLOCK_N": BLOCK_N,
175
+ "PRE_LOAD_V": False,
176
+ "waves_per_eu": 6,
177
+ },
178
+ num_stages=1,
179
+ num_warps=8,
180
+ ),
181
+ ]
182
+ else:
183
+ return [
184
+ triton.Config(
185
+ {
186
+ "BLOCK_M": 64,
187
+ "BLOCK_N": 64,
188
+ "waves_per_eu": 2,
189
+ "PRE_LOAD_V": False,
190
+ },
191
+ num_stages=1,
192
+ num_warps=4,
193
+ )
194
+ ]
195
+
196
+ else: # sweep
197
+ configs = []
198
+ BLOCK_M_OPTIONS = [128, 64, 32, 16]
199
+ BLOCK_N_OPTIONS = [128, 64, 32, 16]
200
+ NUM_WARPS_OPTIONS = [2, 4, 8]
201
+ NUM_STAGES_OPTIONS = [1, 2]
202
+ WAVES_PER_EU_OPTIONS = [4, 2, 1]
203
+ PRE_LOAD_V_OPTIONS = [False]
204
+ for bm in BLOCK_M_OPTIONS:
205
+ for bn in BLOCK_N_OPTIONS:
206
+ for waves in WAVES_PER_EU_OPTIONS:
207
+ for nw in NUM_WARPS_OPTIONS:
208
+ for ns in NUM_STAGES_OPTIONS:
209
+ for preload_v in PRE_LOAD_V_OPTIONS:
210
+ configs.append(
211
+ triton.Config(
212
+ {
213
+ "BLOCK_M": bm,
214
+ "BLOCK_N": bn,
215
+ "waves_per_eu": waves,
216
+ "PRE_LOAD_V": preload_v,
217
+ },
218
+ num_stages=ns,
219
+ num_warps=nw,
220
+ )
221
+ )
222
+ return configs
223
+
224
+
225
+ fwd_prefill_autotune_configs = get_fwd_prefill_configs(AUTOTUNE)
226
+
227
+
228
+ @triton.jit
229
+ def _attn_fwd_inner(
230
+ acc,
231
+ l_i,
232
+ m_i,
233
+ q,
234
+ k_base_ptrs,
235
+ v_base_ptrs,
236
+ bias_base_ptrs,
237
+ stride_kn,
238
+ stride_vk,
239
+ stride_bn,
240
+ stride_sn,
241
+ stride_sm,
242
+ start_m,
243
+ seqlen_k,
244
+ seqlen_q,
245
+ dropout_p,
246
+ philox_seed,
247
+ philox_offset_base,
248
+ sd_mask,
249
+ stride_sz,
250
+ stride_sh,
251
+ off_z,
252
+ off_h_q,
253
+ offs_m,
254
+ offs_n,
255
+ offs_d_qk,
256
+ offs_d_v,
257
+ block_min,
258
+ block_max,
259
+ n_extra_tokens,
260
+ alibi_slope,
261
+ q_descale,
262
+ k_descale,
263
+ v_descale,
264
+ IS_FP8: tl.constexpr,
265
+ FP8_MAX: tl.constexpr,
266
+ FP8_P_DESCALE: tl.constexpr,
267
+ APPLY_MASK: tl.constexpr, # True for masked blocks, False for full blocks
268
+ IS_CAUSAL: tl.constexpr,
269
+ BLOCK_M: tl.constexpr,
270
+ BLOCK_DMODEL_QK: tl.constexpr,
271
+ BLOCK_DMODEL_V: tl.constexpr,
272
+ BLOCK_N: tl.constexpr,
273
+ PRE_LOAD_V: tl.constexpr,
274
+ ENABLE_DROPOUT: tl.constexpr,
275
+ PADDED_HEAD_QK: tl.constexpr,
276
+ PADDED_HEAD_V: tl.constexpr,
277
+ ACTUAL_BLOCK_DMODEL_QK: tl.constexpr,
278
+ ACTUAL_BLOCK_DMODEL_V: tl.constexpr,
279
+ SM_SCALE: tl.constexpr,
280
+ USE_ALIBI: tl.constexpr,
281
+ USE_EXP2: tl.constexpr,
282
+ RETURN_SCORES: tl.constexpr,
283
+ USE_SLIDING_WINDOW: tl.constexpr,
284
+ WINDOW_SIZE_LEFT: tl.constexpr,
285
+ WINDOW_SIZE_RIGHT: tl.constexpr,
286
+ ACCUMULATOR_TYPE,
287
+ ):
288
+ """
289
+ Unified attention forward inner loop.
290
+
291
+ APPLY_MASK controls whether causal/window masking is applied:
292
+ - False: Fast path for full blocks (no masking overhead)
293
+ - True: Masked path with causal/window masking support
294
+ """
295
+ if USE_EXP2:
296
+ RCP_LN2: tl.constexpr = 1.4426950408889634
297
+
298
+ # seqlen diff (only used when APPLY_MASK=True)
299
+ seqlen_delta_qk = seqlen_k - seqlen_q
300
+
301
+ # loop over k, v, and update accumulator
302
+ for start_n in range(block_min, block_max, BLOCK_N):
303
+ # get ptrs
304
+ k_ptrs = k_base_ptrs + start_n * stride_kn
305
+ v_ptrs = v_base_ptrs + start_n * stride_vk
306
+
307
+ kv_offs_n = start_n + tl.arange(0, BLOCK_N)
308
+
309
+ # Load K - different masking for APPLY_MASK vs non-masked
310
+ if APPLY_MASK:
311
+ # For masked blocks, check seqlen bounds
312
+ k_mask = kv_offs_n[None, :] < seqlen_k
313
+ v_mask = kv_offs_n[:, None] < seqlen_k
314
+ if PADDED_HEAD_QK:
315
+ k_mask = k_mask & (offs_d_qk[:, None] < ACTUAL_BLOCK_DMODEL_QK)
316
+ if PADDED_HEAD_V:
317
+ v_mask = v_mask & (offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V)
318
+ k = tl.load(k_ptrs, mask=k_mask, other=0.0)
319
+ if PRE_LOAD_V:
320
+ v = tl.load(v_ptrs, mask=v_mask, other=0.0)
321
+ else:
322
+ # For full blocks, only check head dimension padding
323
+ if PADDED_HEAD_QK:
324
+ k_mask = offs_d_qk[:, None] < ACTUAL_BLOCK_DMODEL_QK
325
+ k = tl.load(k_ptrs, mask=k_mask, other=0.0)
326
+ else:
327
+ k = tl.load(k_ptrs)
328
+ if PRE_LOAD_V:
329
+ if PADDED_HEAD_V:
330
+ v_mask = offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V
331
+ v = tl.load(v_ptrs, mask=v_mask, other=0.0)
332
+ else:
333
+ v = tl.load(v_ptrs)
334
+
335
+ # setup qk accumulator
336
+ qk = tl.zeros([BLOCK_M, BLOCK_N], dtype=ACCUMULATOR_TYPE)
337
+
338
+ # Apply extra token masking for partial blocks (only when APPLY_MASK=True)
339
+ if APPLY_MASK:
340
+ if (n_extra_tokens != 0) and (start_n + BLOCK_N == block_max):
341
+ boundary_m = tl.full([BLOCK_M], seqlen_k, dtype=tl.int32)
342
+ size_n = start_n + offs_n[None, :]
343
+ mask = size_n < boundary_m[:, None]
344
+ qk = tl.where(mask, qk, float("-inf"))
345
+
346
+ # -- compute qk ----
347
+ if IS_FP8:
348
+ qk += tl.dot(q, k) * q_descale * k_descale
349
+ else:
350
+ qk = tl.dot(q, k, acc=qk)
351
+ qk_scaled = qk * SM_SCALE
352
+
353
+ if USE_ALIBI:
354
+ # compute the global position of each token within the sequence
355
+ q_offs_m = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
356
+ alibi_block = compute_alibi_block(
357
+ alibi_slope, seqlen_q, seqlen_k, q_offs_m, kv_offs_n
358
+ )
359
+ qk_scaled += alibi_block
360
+
361
+ # Apply causal/sliding window masking (only when APPLY_MASK=True)
362
+ if APPLY_MASK:
363
+ if USE_SLIDING_WINDOW:
364
+ if IS_CAUSAL:
365
+ # ========== CAUSAL SLIDING WINDOW MASKING ==========
366
+ row_idx = offs_m
367
+ col_idx = kv_offs_n
368
+ row_idx_expanded = row_idx[:, None]
369
+ col_idx_expanded = col_idx[None, :]
370
+
371
+ causal_offset = seqlen_k - seqlen_q
372
+ causal_mask = col_idx_expanded > (row_idx_expanded + causal_offset)
373
+
374
+ if WINDOW_SIZE_LEFT < 0:
375
+ window_mask = col_idx_expanded > (
376
+ row_idx_expanded + causal_offset + WINDOW_SIZE_RIGHT
377
+ )
378
+ else:
379
+ left_bound = row_idx_expanded + causal_offset - WINDOW_SIZE_LEFT
380
+ right_bound = (
381
+ row_idx_expanded + causal_offset + WINDOW_SIZE_RIGHT
382
+ )
383
+ window_mask = (col_idx_expanded < left_bound) | (
384
+ col_idx_expanded > right_bound
385
+ )
386
+
387
+ mask = causal_mask | window_mask
388
+ qk_scaled = tl.where(mask, float("-inf"), qk_scaled)
389
+ else:
390
+ # ========== NON-CAUSAL SLIDING WINDOW MASKING ==========
391
+ row_idx = offs_m
392
+ col_idx = kv_offs_n
393
+ sk = seqlen_k
394
+ sq = seqlen_q
395
+ row_idx_expanded = row_idx[:, None]
396
+ col_idx_expanded = col_idx[None, :]
397
+
398
+ if WINDOW_SIZE_LEFT < 0:
399
+ mask = col_idx_expanded > (
400
+ row_idx_expanded + sk - sq + WINDOW_SIZE_RIGHT
401
+ )
402
+ else:
403
+ sk_full = tl.full((1, BLOCK_N), sk, dtype=tl.int32)
404
+ right_bound_val = row_idx_expanded + sk - sq + WINDOW_SIZE_RIGHT
405
+ right_bound = tl.minimum(right_bound_val, sk_full)
406
+ left_bound = row_idx_expanded + sk - sq - WINDOW_SIZE_LEFT
407
+ mask = (col_idx_expanded > right_bound) | (
408
+ col_idx_expanded < left_bound
409
+ )
410
+
411
+ qk_scaled = tl.where(mask, float("-inf"), qk_scaled)
412
+ else:
413
+ if IS_CAUSAL:
414
+ causal_boundary = start_n + offs_n - seqlen_delta_qk
415
+ causal_mask = offs_m[:, None] >= causal_boundary[None, :]
416
+ qk_scaled = tl.where(causal_mask, qk_scaled, float("-inf"))
417
+
418
+ # compute qk mask for bounds checking
419
+ qk_mask = (offs_m[:, None] < seqlen_q) & (kv_offs_n[None, :] < seqlen_k)
420
+
421
+ # compute bias
422
+ if bias_base_ptrs is not None:
423
+ bias_ptrs = bias_base_ptrs + start_n * stride_bn
424
+ bias = tl.load(bias_ptrs, mask=qk_mask, other=0.0)
425
+ qk_scaled += bias
426
+
427
+ # get max scores so far
428
+ m_ij = tl.maximum(m_i, tl.max(qk_scaled, 1))
429
+
430
+ # scale and subtract max
431
+ # Handle the case where all values are -inf
432
+ q_shifted = tl.where(
433
+ m_ij[:, None] == float("-inf"), float("-inf"), qk_scaled - m_ij[:, None]
434
+ )
435
+
436
+ # Compute scaled QK and softmax probabilities
437
+ if USE_EXP2:
438
+ p = tl.math.exp2(q_shifted * RCP_LN2)
439
+ else:
440
+ p = tl.math.exp(q_shifted)
441
+
442
+ # CAVEAT: Must update l_ij before applying dropout
443
+ l_ij = tl.sum(p, 1)
444
+ if ENABLE_DROPOUT:
445
+ # Compute pointers for this block
446
+ philox_base = philox_offset_base + off_z * stride_sz + off_h_q * stride_sh
447
+ philox_ptrs = (
448
+ philox_base
449
+ + offs_m[:, None] * stride_sm
450
+ + kv_offs_n[None, :] * stride_sn
451
+ )
452
+
453
+ # compute dropout mask
454
+ rng_output = tl.rand(philox_seed, philox_ptrs)
455
+ dropout_mask = rng_output > dropout_p
456
+
457
+ # return scores with negative values for dropped vals (only if RETURN_SCORES is True)
458
+ if RETURN_SCORES:
459
+ sd_mask_value = tl.where(dropout_mask, p, -p)
460
+ sd_mask_base = sd_mask + off_z * stride_sz + off_h_q * stride_sh
461
+ sd_mask_ptrs = (
462
+ sd_mask_base
463
+ + offs_m[:, None] * stride_sm
464
+ + kv_offs_n[None, :] * stride_sn
465
+ )
466
+
467
+ sd_store_mask = (offs_m[:, None] < seqlen_q) & (
468
+ kv_offs_n[None, :] < seqlen_k
469
+ )
470
+
471
+ if APPLY_MASK and IS_CAUSAL:
472
+ causal_constraint = kv_offs_n[None, :] <= (
473
+ offs_m[:, None] + seqlen_delta_qk
474
+ )
475
+ sd_store_mask = sd_store_mask & causal_constraint
476
+
477
+ if APPLY_MASK and USE_SLIDING_WINDOW:
478
+ if WINDOW_SIZE_LEFT < 0:
479
+ window_constraint = kv_offs_n[None, :] <= (
480
+ offs_m[:, None] + seqlen_delta_qk + WINDOW_SIZE_RIGHT
481
+ )
482
+ else:
483
+ left_bound = (
484
+ offs_m[:, None] + seqlen_delta_qk - WINDOW_SIZE_LEFT
485
+ )
486
+ right_bound = (
487
+ offs_m[:, None] + seqlen_delta_qk + WINDOW_SIZE_RIGHT
488
+ )
489
+ window_constraint = (kv_offs_n[None, :] >= left_bound) & (
490
+ kv_offs_n[None, :] <= right_bound
491
+ )
492
+ sd_store_mask = sd_store_mask & window_constraint
493
+
494
+ tl.store(sd_mask_ptrs, sd_mask_value, mask=sd_store_mask)
495
+
496
+ # apply dropout mask in place
497
+ p = tl.where(dropout_mask, p, 0.0)
498
+ elif RETURN_SCORES:
499
+ sd_mask_base = sd_mask + off_z * stride_sz + off_h_q * stride_sh
500
+ sd_mask_ptrs = (
501
+ sd_mask_base
502
+ + offs_m[:, None] * stride_sm
503
+ + kv_offs_n[None, :] * stride_sn
504
+ )
505
+
506
+ sd_store_mask = (offs_m[:, None] < seqlen_q) & (
507
+ kv_offs_n[None, :] < seqlen_k
508
+ )
509
+
510
+ if APPLY_MASK and IS_CAUSAL:
511
+ causal_constraint = kv_offs_n[None, :] <= (
512
+ offs_m[:, None] + seqlen_delta_qk
513
+ )
514
+ sd_store_mask = sd_store_mask & causal_constraint
515
+
516
+ if APPLY_MASK and USE_SLIDING_WINDOW:
517
+ if WINDOW_SIZE_LEFT < 0:
518
+ window_constraint = kv_offs_n[None, :] <= (
519
+ offs_m[:, None] + seqlen_delta_qk + WINDOW_SIZE_RIGHT
520
+ )
521
+ else:
522
+ left_bound = offs_m[:, None] + seqlen_delta_qk - WINDOW_SIZE_LEFT
523
+ right_bound = offs_m[:, None] + seqlen_delta_qk + WINDOW_SIZE_RIGHT
524
+ window_constraint = (kv_offs_n[None, :] >= left_bound) & (
525
+ kv_offs_n[None, :] <= right_bound
526
+ )
527
+ sd_store_mask = sd_store_mask & window_constraint
528
+
529
+ tl.store(sd_mask_ptrs, p, mask=sd_store_mask)
530
+
531
+ # -- update output accumulator --
532
+ m_diff = tl.where(m_ij == float("-inf"), float("-inf"), m_i - m_ij)
533
+ if USE_EXP2:
534
+ alpha = tl.math.exp2(m_diff * RCP_LN2)
535
+ else:
536
+ alpha = tl.math.exp(m_diff)
537
+ acc = acc * alpha[:, None]
538
+
539
+ # Load V if not preloaded
540
+ if not PRE_LOAD_V:
541
+ if APPLY_MASK:
542
+ v_mask = kv_offs_n[:, None] < seqlen_k
543
+ if PADDED_HEAD_V:
544
+ v_mask = v_mask & (offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V)
545
+ v = tl.load(v_ptrs, mask=v_mask, other=0.0)
546
+ else:
547
+ if PADDED_HEAD_V:
548
+ v_mask = offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V
549
+ v = tl.load(v_ptrs, mask=v_mask, other=0.0)
550
+ else:
551
+ v = tl.load(v_ptrs)
552
+
553
+ # -- update m_i and l_i
554
+ l_i = l_i * alpha + l_ij
555
+ m_i = m_ij
556
+
557
+ if IS_FP8:
558
+ if FP8_P_DESCALE:
559
+ scale_p, descale_p = compute_fp8_scaling_factors(p, FP8_MAX)
560
+ acc += (
561
+ tl.dot((p * scale_p).to(v.type.element_ty), v)
562
+ * descale_p
563
+ * v_descale
564
+ )
565
+ else:
566
+ acc += tl.dot(p.to(v.type.element_ty), v) * v_descale
567
+ else:
568
+ acc = tl.dot(p.to(v.type.element_ty), v, acc=acc)
569
+
570
+ return acc, l_i, m_i
571
+
572
+
573
+ @triton.jit
574
+ def compute_window_bounds(
575
+ q_start,
576
+ q_end,
577
+ diag,
578
+ seqlen_k,
579
+ WINDOW_SIZE_LEFT: tl.constexpr,
580
+ WINDOW_SIZE_RIGHT: tl.constexpr,
581
+ IS_CAUSAL: tl.constexpr,
582
+ ):
583
+ """Calculate the window boundaries for a query block."""
584
+ # Left boundary
585
+ if WINDOW_SIZE_LEFT < 0:
586
+ left_min = 0
587
+ left_max = 0
588
+ else:
589
+ left_min = tl.maximum(0, q_start + diag - WINDOW_SIZE_LEFT)
590
+ left_max = tl.maximum(0, q_end + diag - WINDOW_SIZE_LEFT)
591
+
592
+ # Right boundary
593
+ if IS_CAUSAL:
594
+ # Causal cap: col <= row + diag
595
+ right_min = tl.minimum(seqlen_k - 1, q_start + diag)
596
+ right_max = tl.minimum(seqlen_k - 1, q_end + diag)
597
+ else:
598
+ if WINDOW_SIZE_RIGHT < 0:
599
+ right_min = tl.minimum(seqlen_k - 1, q_start + diag + WINDOW_SIZE_RIGHT)
600
+ right_max = tl.minimum(seqlen_k - 1, q_end + diag + WINDOW_SIZE_RIGHT)
601
+ else:
602
+ # Non-causal doesn't have the diagonal constraint
603
+ right_min = tl.minimum(seqlen_k - 1, q_start + diag + WINDOW_SIZE_RIGHT)
604
+ right_max = tl.minimum(seqlen_k - 1, q_end + diag + WINDOW_SIZE_RIGHT)
605
+
606
+ return left_min, left_max, right_min, right_max
607
+
608
+
609
+ @triton.jit
610
+ def classify_window_blocks(
611
+ left_min, left_max, right_min, right_max, BLOCK_N: tl.constexpr
612
+ ):
613
+ """Classify blocks based on window boundaries."""
614
+ # First and last blocks that have ANY overlap with window
615
+ first_block = left_min // BLOCK_N
616
+ last_block = right_max // BLOCK_N
617
+
618
+ # First block that is FULLY visible for all rows in Q block
619
+ full_left_block = left_max // BLOCK_N + (left_max % BLOCK_N != 0)
620
+ clipped_left = tl.minimum(full_left_block, last_block + 1)
621
+
622
+ # Last block that is FULLY visible for all rows in Q block
623
+ last_full_block_candidate = right_min // BLOCK_N
624
+ if (last_full_block_candidate + 1) * BLOCK_N - 1 > right_min:
625
+ last_full_block_candidate -= 1
626
+ full_right_block = tl.maximum(last_full_block_candidate, clipped_left - 1)
627
+
628
+ # Calculate counts
629
+ n_front_skip_blocks = first_block
630
+ n_front_masked_blocks = tl.maximum(0, clipped_left - first_block)
631
+ n_full_blocks = tl.maximum(0, full_right_block - clipped_left + 1)
632
+ n_back_masked_blocks = tl.maximum(0, last_block - full_right_block)
633
+
634
+ return (
635
+ n_front_skip_blocks,
636
+ n_front_masked_blocks,
637
+ n_full_blocks,
638
+ n_back_masked_blocks,
639
+ clipped_left,
640
+ ) # Return clipped_left for padded block handling
641
+
642
+
643
+ @triton.jit
644
+ def handle_padded_last_block(
645
+ n_extra_tokens,
646
+ last_block,
647
+ total_k_blocks,
648
+ clipped_left,
649
+ n_front_masked_blocks,
650
+ n_full_blocks,
651
+ n_back_masked_blocks,
652
+ ):
653
+ """Ensure a padded last K-block is never classified as 'full'.
654
+
655
+ We move the padded last block (if visible) into the back-masked bucket.
656
+ If it's already back-masked, we do nothing. If it was counted in the
657
+ front-masked range, we decrement front-masked; if it was counted as full,
658
+ we decrement full. Then we increment back-masked.
659
+ """
660
+ padded_last_k = (n_extra_tokens != 0) & (last_block == total_k_blocks - 1)
661
+
662
+ if padded_last_k:
663
+ # current 'full' range right edge
664
+ full_right_block = clipped_left + n_full_blocks - 1
665
+
666
+ # If last_block is already beyond full_right_block, it's already in back-masked -> nothing to do
667
+ last_already_back_masked = last_block > full_right_block
668
+ if not last_already_back_masked:
669
+ # If the window starts past last_block, it was counted in front-masked
670
+ if clipped_left > last_block:
671
+ n_front_masked_blocks = tl.maximum(0, n_front_masked_blocks - 1)
672
+ else:
673
+ # Otherwise it was counted 'full' -> move it out of full
674
+ n_full_blocks = tl.maximum(0, n_full_blocks - 1)
675
+ # In both cases we need one more back-masked block
676
+ n_back_masked_blocks = n_back_masked_blocks + 1
677
+
678
+ return n_front_masked_blocks, n_full_blocks, n_back_masked_blocks
679
+
680
+
681
+ @triton.jit
682
+ def compute_padding_info(seqlen_k, BLOCK_N: tl.constexpr):
683
+ """Calculate padding information for the last K block."""
684
+ # check if we will need to do masking due either BLOCK_N being bigger than seqlen_k or seqlen_k not being a factor of BLOCK_N
685
+ # n_extra_tokens = 10 % 4 = 2
686
+ # This means the last K block has 2 valid tokens and 2 padding positions
687
+ # K blocks visualization:
688
+ # Block 0 Block 1 Block 2 (last)
689
+ # K0 K1 K2 K3 K4 K5 K6 K7 K8 K9 ?? ??
690
+ # ?---------? ?---------? ?---? ?---?
691
+ # full block full block valid pad
692
+ if seqlen_k < BLOCK_N:
693
+ n_extra_tokens = BLOCK_N - seqlen_k
694
+ elif seqlen_k % BLOCK_N:
695
+ n_extra_tokens = seqlen_k % BLOCK_N
696
+ else:
697
+ n_extra_tokens = 0
698
+ return n_extra_tokens
699
+
700
+
701
+ @triton.jit
702
+ def compute_block_masking(
703
+ seqlen_k,
704
+ seqlen_q,
705
+ start_m,
706
+ IS_CAUSAL: tl.constexpr,
707
+ USE_SLIDING_WINDOW: tl.constexpr,
708
+ WINDOW_SIZE_LEFT: tl.constexpr,
709
+ WINDOW_SIZE_RIGHT: tl.constexpr,
710
+ BLOCK_M: tl.constexpr,
711
+ BLOCK_N: tl.constexpr,
712
+ ):
713
+ """
714
+ Classify K blocks for attention computation with sliding window support.
715
+
716
+ Returns:
717
+ - n_front_skip_blocks: Blocks completely before the window
718
+ - n_front_masked_blocks: Blocks partially overlapping window front
719
+ - n_full_blocks: Blocks completely inside the window
720
+ - n_back_masked_blocks: Blocks partially overlapping window back
721
+ - n_extra_tokens: Padding tokens in last K block
722
+ """
723
+
724
+ # common
725
+ q_start = start_m * BLOCK_M
726
+ q_end = tl.minimum((start_m + 1) * BLOCK_M - 1, seqlen_q - 1)
727
+ diag = seqlen_k - seqlen_q
728
+ total_k_blocks = tl.cdiv(seqlen_k, BLOCK_N)
729
+ n_extra_tokens = compute_padding_info(seqlen_k, BLOCK_N)
730
+
731
+ if USE_SLIDING_WINDOW:
732
+ # get window bounds
733
+ left_min, left_max, right_min, right_max = compute_window_bounds(
734
+ q_start,
735
+ q_end,
736
+ diag,
737
+ seqlen_k,
738
+ WINDOW_SIZE_LEFT,
739
+ WINDOW_SIZE_RIGHT,
740
+ IS_CAUSAL,
741
+ )
742
+
743
+ # window vanishes -> early exit
744
+ if right_max < left_min:
745
+ return 0, 0, 0, 0, n_extra_tokens
746
+
747
+ # classify blocks
748
+ (
749
+ n_front_skip_blocks,
750
+ n_front_masked_blocks,
751
+ n_full_blocks,
752
+ n_back_masked_blocks,
753
+ clipped_left,
754
+ ) = classify_window_blocks(left_min, left_max, right_min, right_max, BLOCK_N)
755
+
756
+ # handle padded last block if needed
757
+ if n_extra_tokens != 0:
758
+ last_block = right_max // BLOCK_N
759
+ n_front_masked_blocks, n_full_blocks, n_back_masked_blocks = (
760
+ handle_padded_last_block(
761
+ n_extra_tokens,
762
+ last_block,
763
+ total_k_blocks,
764
+ clipped_left,
765
+ n_front_masked_blocks,
766
+ n_full_blocks,
767
+ n_back_masked_blocks,
768
+ )
769
+ )
770
+ return (
771
+ n_front_skip_blocks,
772
+ n_front_masked_blocks,
773
+ n_full_blocks,
774
+ n_back_masked_blocks,
775
+ n_extra_tokens,
776
+ )
777
+ else:
778
+ if IS_CAUSAL:
779
+ # ========== CAUSAL MODE: Classify K Blocks ==========
780
+ # Calculate causal boundary for this Q block
781
+ # [K0 K1 K2 K3] [K4 K5 K6 K7] [K8 K9 ?? ??]
782
+ # Q0-Q3: [ 1 0 0 0] [ 0 0 0 0] [ 0 0 -- --] <- Q0
783
+ # [ 1 1 0 0] [ 0 0 0 0] [ 0 0 -- --] <- Q1
784
+ # [ 1 1 1 0] [ 0 0 0 0] [ 0 0 -- --] <- Q2
785
+ # [ 1 1 1 1] [ 1 1 0 0] [ 0 0 -- --] <- Q3
786
+ # /\ can see up to K5
787
+ #
788
+ # Q4-Q7: [ 1 1 1 1] [ 1 1 1 0] [ 0 0 -- --] <- Q4
789
+ # [ 1 1 1 1] [ 1 1 1 1] [ 0 0 -- --] <- Q5
790
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 0 -- --] <- Q6
791
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -- --] <- Q7
792
+
793
+ # ------------------------------------------------------------
794
+ # 1. figure out, in tokens, the right-most K position
795
+ # this Q-block may attend to
796
+ # ------------------------------------------------------------
797
+ k_max_token = q_end + diag # last visible K index
798
+
799
+ # this Q-block is entirely above the diagonal => nothing to do
800
+ if k_max_token < 0:
801
+ return 0, 0, 0, 0, n_extra_tokens
802
+
803
+ k_max_token = tl.minimum(k_max_token, seqlen_k - 1)
804
+
805
+ # ------------------------------------------------------------
806
+ # 2. translate token indices into K-block indices
807
+ # ------------------------------------------------------------
808
+ last_visible_k_block = k_max_token // BLOCK_N
809
+ n_visible_k_blocks = tl.minimum(last_visible_k_block + 1, total_k_blocks)
810
+
811
+ # ------------------------------------------------------------
812
+ # 3. classify those visible blocks
813
+ # - we *never* skip or mask blocks in front, because causal
814
+ # attention always starts at K0
815
+ # - the back side can require several masked blocks:
816
+ # o intersection of the causal diagonal with K-grid
817
+ # (at most ?BLOCK_M / BLOCK_N? blocks)
818
+ # o plus one for partial K blocks at the causal boundary
819
+ # ------------------------------------------------------------
820
+ n_back_masked_blocks = BLOCK_M // BLOCK_N + 1
821
+ n_back_masked_blocks = tl.minimum(n_back_masked_blocks, n_visible_k_blocks)
822
+
823
+ n_front_skip_blocks = 0 # causal never skips the left side
824
+ n_front_masked_blocks = 0 # ditto
825
+ n_full_blocks = n_visible_k_blocks - n_back_masked_blocks
826
+ else:
827
+ # ========== NON-CAUSAL MODE ==========
828
+ # Without causal mask, all positions can attend to all positions
829
+ # Only need to handle the padding in the last block
830
+ # [K0 K1 K2 K3] [K4 K5 K6 K7] [K8 K9 ?? ??]
831
+ # Q0-Q3: [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
832
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
833
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
834
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
835
+ #
836
+ # Q4-Q7: [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
837
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
838
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
839
+ # [ 1 1 1 1] [ 1 1 1 1] [ 1 1 -inf -inf]
840
+
841
+ n_front_skip_blocks = 0 # never skips the left side
842
+ n_front_masked_blocks = 0 # ditto
843
+ if n_extra_tokens != 0:
844
+ n_back_masked_blocks = 1 # Last block needs padding mask
845
+ n_full_blocks = total_k_blocks - 1
846
+ else:
847
+ n_back_masked_blocks = 0 # All blocks are aligned
848
+ n_full_blocks = total_k_blocks
849
+
850
+ return (
851
+ n_front_skip_blocks,
852
+ n_front_masked_blocks,
853
+ n_full_blocks,
854
+ n_back_masked_blocks,
855
+ n_extra_tokens,
856
+ )
857
+
858
+
859
+ @triton.autotune(
860
+ configs=fwd_prefill_autotune_configs,
861
+ key=FWD_PREFILL_AUTOTUNE_KEYS,
862
+ use_cuda_graph=True,
863
+ )
864
+ @triton.jit
865
+ def attn_fwd(
866
+ Q,
867
+ K,
868
+ V,
869
+ bias,
870
+ Q_Descale,
871
+ K_Descale,
872
+ V_Descale,
873
+ stride_q_descale_z,
874
+ stride_k_descale_z,
875
+ stride_v_descale_z,
876
+ LSE,
877
+ Out,
878
+ SD_MASK,
879
+ ALIBI_SLOPES,
880
+ stride_qz,
881
+ stride_qh,
882
+ stride_qm,
883
+ stride_qk,
884
+ stride_kz,
885
+ stride_kh,
886
+ stride_kn,
887
+ stride_kk,
888
+ stride_vz,
889
+ stride_vh,
890
+ stride_vk,
891
+ stride_vn,
892
+ stride_oz,
893
+ stride_oh,
894
+ stride_om,
895
+ stride_on,
896
+ stride_bz,
897
+ stride_bh,
898
+ stride_bm,
899
+ stride_bn,
900
+ stride_az,
901
+ stride_ah,
902
+ stride_sz,
903
+ stride_sh,
904
+ stride_sm,
905
+ stride_sn,
906
+ stride_lse_z,
907
+ stride_lse_h,
908
+ stride_lse_m,
909
+ cu_seqlens_q,
910
+ cu_seqlens_k,
911
+ seqused_q,
912
+ seqused_k, # Add seqused parameters
913
+ dropout_p,
914
+ philox_seed,
915
+ philox_offset_base,
916
+ HQ: tl.constexpr,
917
+ HK: tl.constexpr,
918
+ ACTUAL_BLOCK_DMODEL_QK: tl.constexpr,
919
+ ACTUAL_BLOCK_DMODEL_V: tl.constexpr,
920
+ MAX_SEQLENS_Q: tl.constexpr,
921
+ MAX_SEQLENS_K: tl.constexpr,
922
+ IS_VARLEN: tl.constexpr,
923
+ SM_SCALE: tl.constexpr,
924
+ IS_CAUSAL: tl.constexpr,
925
+ USE_SLIDING_WINDOW: tl.constexpr,
926
+ WINDOW_SIZE_LEFT: tl.constexpr,
927
+ WINDOW_SIZE_RIGHT: tl.constexpr,
928
+ BLOCK_M: tl.constexpr,
929
+ BLOCK_DMODEL_QK: tl.constexpr,
930
+ BLOCK_DMODEL_V: tl.constexpr,
931
+ BLOCK_N: tl.constexpr,
932
+ PRE_LOAD_V: tl.constexpr,
933
+ USE_BIAS: tl.constexpr,
934
+ ENABLE_DROPOUT: tl.constexpr,
935
+ RETURN_SCORES: tl.constexpr,
936
+ USE_ALIBI: tl.constexpr,
937
+ USE_EXP2: tl.constexpr,
938
+ IS_FP8: tl.constexpr,
939
+ FP8_MAX: tl.constexpr,
940
+ FP8_P_DESCALE: tl.constexpr,
941
+ USE_SEQUSED: tl.constexpr,
942
+ FORCE_MASKING: tl.constexpr,
943
+ NUM_XCD: tl.constexpr = 1,
944
+ ):
945
+ # set params
946
+ ACCUMULATOR_TYPE = tl.float32
947
+
948
+ # compute offsets
949
+ off_h_q = tl.program_id(0)
950
+ # apply the xcd remapping for the hq dim
951
+ off_h_q = remap_xcd(off_h_q, HQ, NUM_XCD)
952
+
953
+ start_m = tl.program_id(1)
954
+ off_z = tl.program_id(2)
955
+ # If MQA / GQA, set the K and V head offsets appropriately.
956
+ GROUP_SIZE: tl.constexpr = HQ // HK
957
+ if GROUP_SIZE != 1:
958
+ off_h_k = off_h_q // GROUP_SIZE
959
+ else:
960
+ off_h_k = off_h_q
961
+ # Determine if we need to mask the heads
962
+ PADDED_HEAD_QK: tl.constexpr = ACTUAL_BLOCK_DMODEL_QK != BLOCK_DMODEL_QK
963
+ PADDED_HEAD_V: tl.constexpr = ACTUAL_BLOCK_DMODEL_V != BLOCK_DMODEL_V
964
+
965
+ offs_m = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
966
+ offs_n = tl.arange(0, BLOCK_N)
967
+ offs_d_qk = tl.arange(0, BLOCK_DMODEL_QK)
968
+ offs_d_v = tl.arange(0, BLOCK_DMODEL_V)
969
+
970
+ # handle seqlen
971
+ if IS_VARLEN:
972
+ cu_seqlens_q_start = tl.load(cu_seqlens_q + off_z)
973
+ cu_seqlens_q_end = tl.load(cu_seqlens_q + off_z + 1)
974
+
975
+ # If seqused is provided, use it to limit the actual sequence length
976
+ if USE_SEQUSED:
977
+ actual_seqlen_q = (
978
+ tl.load(seqused_q + off_z)
979
+ if seqused_q is not None
980
+ else cu_seqlens_q_end - cu_seqlens_q_start
981
+ )
982
+ seqlen_q = tl.minimum(
983
+ actual_seqlen_q, cu_seqlens_q_end - cu_seqlens_q_start
984
+ )
985
+ else:
986
+ seqlen_q = cu_seqlens_q_end - cu_seqlens_q_start
987
+
988
+ # we have a one-size-fits-all grid in id(0). Some seqlens might be too small for all start_m so for those we return early.
989
+ if start_m * BLOCK_M > seqlen_q:
990
+ return
991
+ cu_seqlens_k_start = tl.load(cu_seqlens_k + off_z)
992
+ cu_seqlens_k_end = tl.load(cu_seqlens_k + off_z + 1)
993
+
994
+ # If seqused is provided, use it to limit the actual sequence length for keys
995
+ if USE_SEQUSED:
996
+ actual_seqlen_k = (
997
+ tl.load(seqused_k + off_z)
998
+ if seqused_k is not None
999
+ else cu_seqlens_k_end - cu_seqlens_k_start
1000
+ )
1001
+ seqlen_k = tl.minimum(
1002
+ actual_seqlen_k, cu_seqlens_k_end - cu_seqlens_k_start
1003
+ )
1004
+ else:
1005
+ seqlen_k = cu_seqlens_k_end - cu_seqlens_k_start
1006
+ else:
1007
+ cu_seqlens_q_start = 0
1008
+ cu_seqlens_k_start = 0
1009
+ seqlen_q = MAX_SEQLENS_Q
1010
+ seqlen_k = MAX_SEQLENS_K
1011
+
1012
+ # Load scale factors if IS_FP8.
1013
+ if IS_FP8:
1014
+ # For MQA/GQA (GROUP_SIZE != 1), q_descale uses the same indexing as k/v (off_h_k)
1015
+ # For MHA (GROUP_SIZE == 1), q_descale uses off_h_q (same as off_h_k)
1016
+ if GROUP_SIZE != 1:
1017
+ q_descale = tl.load(
1018
+ Q_Descale + off_z * stride_q_descale_z + off_h_k
1019
+ ) # MQA/GQA: broadcast using k/v head index
1020
+ else:
1021
+ q_descale = tl.load(
1022
+ Q_Descale + off_z * stride_q_descale_z + off_h_q
1023
+ ) # MHA: use q head index
1024
+ k_descale = tl.load(K_Descale + off_z * stride_k_descale_z + off_h_k)
1025
+ v_descale = tl.load(V_Descale + off_z * stride_v_descale_z + off_h_k)
1026
+ else:
1027
+ q_descale, k_descale, v_descale = 1.0, 1.0, 1.0
1028
+
1029
+ # figure out masking pattern
1030
+ (
1031
+ n_front_skip_blocks,
1032
+ n_front_masked_blocks,
1033
+ n_full_blocks,
1034
+ n_back_masked_blocks,
1035
+ n_extra_tokens,
1036
+ ) = compute_block_masking(
1037
+ seqlen_k,
1038
+ seqlen_q,
1039
+ start_m,
1040
+ IS_CAUSAL,
1041
+ USE_SLIDING_WINDOW,
1042
+ WINDOW_SIZE_LEFT,
1043
+ WINDOW_SIZE_RIGHT,
1044
+ BLOCK_M,
1045
+ BLOCK_N,
1046
+ )
1047
+
1048
+ # ============================================================
1049
+ # PROGRAM EARLY EXIT (All K Blocks Skipped)
1050
+ # ============================================================
1051
+ total_visible_blocks = n_front_masked_blocks + n_full_blocks + n_back_masked_blocks
1052
+ if total_visible_blocks == 0:
1053
+ """
1054
+ No K blocks visible - write zeros and exit.
1055
+ """
1056
+ # Write zeros to output
1057
+ o_offset = (
1058
+ Out
1059
+ + off_z * stride_oz
1060
+ + off_h_q * stride_oh
1061
+ + cu_seqlens_q_start * stride_om
1062
+ )
1063
+ o_ptrs = o_offset + offs_m[:, None] * stride_om + offs_d_v[None, :] * stride_on
1064
+ o_mask = offs_m[:, None] < seqlen_q
1065
+ if PADDED_HEAD_V:
1066
+ o_mask = o_mask & (offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V)
1067
+ tl.store(
1068
+ o_ptrs,
1069
+ tl.zeros([BLOCK_M, BLOCK_DMODEL_V], dtype=Out.type.element_ty),
1070
+ mask=o_mask,
1071
+ )
1072
+
1073
+ # Write zeros to LSE
1074
+ l_ptrs = (
1075
+ LSE
1076
+ + off_z * stride_lse_z
1077
+ + off_h_q * stride_lse_h
1078
+ + cu_seqlens_q_start * stride_lse_m
1079
+ + offs_m * stride_lse_m
1080
+ )
1081
+ tl.store(l_ptrs, tl.zeros([BLOCK_M], dtype=tl.float32), mask=offs_m < seqlen_q)
1082
+ return
1083
+
1084
+ # ============================================================
1085
+ # NORMAL PROCESSING (Some K Blocks Visible)
1086
+ # ============================================================
1087
+ """
1088
+ This program has visible K blocks to process.
1089
+ We'll use two calls to handle different block types efficiently.
1090
+ """
1091
+
1092
+ # Initialize for processing
1093
+ # Compute pointers for all the tensors used in this kernel.
1094
+ q_offset = (
1095
+ Q + off_z * stride_qz + off_h_q * stride_qh + cu_seqlens_q_start * stride_qm
1096
+ )
1097
+ q_ptrs = q_offset + offs_m[:, None] * stride_qm + offs_d_qk[None, :] * stride_qk
1098
+ k_offset = (
1099
+ K + off_z * stride_kz + off_h_k * stride_kh + cu_seqlens_k_start * stride_kn
1100
+ )
1101
+ k_ptrs = k_offset + offs_d_qk[:, None] * stride_kk + offs_n[None, :] * stride_kn
1102
+ v_offset = (
1103
+ V + off_z * stride_vz + off_h_k * stride_vh + cu_seqlens_k_start * stride_vk
1104
+ )
1105
+ v_ptrs = v_offset + offs_n[:, None] * stride_vk + offs_d_v[None, :] * stride_vn
1106
+ if USE_BIAS:
1107
+ # Note: this might get large enough to overflow on some configs
1108
+ bias_offset = off_h_q * stride_bh
1109
+ bias_ptrs = (
1110
+ bias
1111
+ + bias_offset
1112
+ + offs_m[:, None] * stride_bm
1113
+ + offs_n[None, :] * stride_bn
1114
+ )
1115
+ else:
1116
+ bias_ptrs = None
1117
+
1118
+ if USE_ALIBI:
1119
+ a_offset = off_z * stride_az + off_h_q * stride_ah
1120
+ alibi_slope = tl.load(ALIBI_SLOPES + a_offset)
1121
+ else:
1122
+ alibi_slope = None
1123
+
1124
+ # initialize pointer to m and l
1125
+ m_i = tl.full([BLOCK_M], float("-inf"), dtype=ACCUMULATOR_TYPE)
1126
+ l_i = tl.full([BLOCK_M], 1.0, dtype=ACCUMULATOR_TYPE)
1127
+ acc = tl.zeros([BLOCK_M, BLOCK_DMODEL_V], dtype=ACCUMULATOR_TYPE)
1128
+
1129
+ # Q is loaded once at the beginning and shared by all N blocks.
1130
+ q_ptrs_mask = offs_m[:, None] < seqlen_q
1131
+ if PADDED_HEAD_QK:
1132
+ q_ptrs_mask = q_ptrs_mask & (offs_d_qk[None, :] < ACTUAL_BLOCK_DMODEL_QK)
1133
+ q = tl.load(q_ptrs, mask=q_ptrs_mask, other=0.0)
1134
+
1135
+ # ========== Process MASKED K Blocks in the front ==========
1136
+ # NOTE: we use USE_SLIDING_WINDOW as guard because the compiler will crash other wise. front masking is only for sliding window so that is fine.
1137
+ if n_front_masked_blocks > 0 and USE_SLIDING_WINDOW:
1138
+ block_min = n_front_skip_blocks * BLOCK_N
1139
+ block_max = (n_front_skip_blocks + n_front_masked_blocks) * BLOCK_N
1140
+
1141
+ acc, l_i, m_i = _attn_fwd_inner(
1142
+ acc,
1143
+ l_i,
1144
+ m_i,
1145
+ q,
1146
+ k_ptrs,
1147
+ v_ptrs,
1148
+ bias_ptrs,
1149
+ stride_kn,
1150
+ stride_vk,
1151
+ stride_bn,
1152
+ stride_sn,
1153
+ stride_sm,
1154
+ start_m,
1155
+ seqlen_k,
1156
+ seqlen_q,
1157
+ dropout_p,
1158
+ philox_seed,
1159
+ philox_offset_base,
1160
+ SD_MASK,
1161
+ stride_sz,
1162
+ stride_sh,
1163
+ off_z,
1164
+ off_h_q,
1165
+ offs_m,
1166
+ offs_n,
1167
+ offs_d_qk,
1168
+ offs_d_v,
1169
+ block_min, # Start of front masked blocks
1170
+ block_max, # End of front masked blocks
1171
+ 0, # n_extra_tokens (0 for front blocks, only relevant for last block)
1172
+ alibi_slope,
1173
+ q_descale,
1174
+ k_descale,
1175
+ v_descale,
1176
+ IS_FP8,
1177
+ FP8_MAX,
1178
+ FP8_P_DESCALE,
1179
+ APPLY_MASK=True, # Masked blocks
1180
+ IS_CAUSAL=IS_CAUSAL,
1181
+ BLOCK_M=BLOCK_M,
1182
+ BLOCK_DMODEL_QK=BLOCK_DMODEL_QK,
1183
+ BLOCK_DMODEL_V=BLOCK_DMODEL_V,
1184
+ BLOCK_N=BLOCK_N,
1185
+ PRE_LOAD_V=PRE_LOAD_V,
1186
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1187
+ PADDED_HEAD_QK=PADDED_HEAD_QK,
1188
+ PADDED_HEAD_V=PADDED_HEAD_V,
1189
+ ACTUAL_BLOCK_DMODEL_QK=ACTUAL_BLOCK_DMODEL_QK,
1190
+ ACTUAL_BLOCK_DMODEL_V=ACTUAL_BLOCK_DMODEL_V,
1191
+ SM_SCALE=SM_SCALE,
1192
+ USE_ALIBI=USE_ALIBI,
1193
+ USE_EXP2=USE_EXP2,
1194
+ RETURN_SCORES=RETURN_SCORES,
1195
+ USE_SLIDING_WINDOW=USE_SLIDING_WINDOW,
1196
+ WINDOW_SIZE_LEFT=WINDOW_SIZE_LEFT,
1197
+ WINDOW_SIZE_RIGHT=WINDOW_SIZE_RIGHT,
1198
+ ACCUMULATOR_TYPE=ACCUMULATOR_TYPE,
1199
+ )
1200
+
1201
+ # ========== Process FULL K Blocks (Fast Path) ==========
1202
+ if n_full_blocks > 0:
1203
+ block_min = (n_front_skip_blocks + n_front_masked_blocks) * BLOCK_N
1204
+ block_max = (
1205
+ n_front_skip_blocks + n_front_masked_blocks + n_full_blocks
1206
+ ) * BLOCK_N
1207
+
1208
+ acc, l_i, m_i = _attn_fwd_inner(
1209
+ acc,
1210
+ l_i,
1211
+ m_i,
1212
+ q,
1213
+ k_ptrs,
1214
+ v_ptrs,
1215
+ bias_ptrs,
1216
+ stride_kn,
1217
+ stride_vk,
1218
+ stride_bn,
1219
+ stride_sn,
1220
+ stride_sm,
1221
+ start_m,
1222
+ seqlen_k,
1223
+ seqlen_q,
1224
+ dropout_p,
1225
+ philox_seed,
1226
+ philox_offset_base,
1227
+ SD_MASK,
1228
+ stride_sz,
1229
+ stride_sh,
1230
+ off_z,
1231
+ off_h_q,
1232
+ offs_m,
1233
+ offs_n,
1234
+ offs_d_qk,
1235
+ offs_d_v,
1236
+ block_min, # Start of range: 0
1237
+ block_max, # End of range: n_full_blocks * BLOCK_N
1238
+ 0, # n_extra_tokens (not used for full blocks)
1239
+ alibi_slope,
1240
+ q_descale,
1241
+ k_descale,
1242
+ v_descale,
1243
+ IS_FP8,
1244
+ FP8_MAX,
1245
+ FP8_P_DESCALE,
1246
+ APPLY_MASK=FORCE_MASKING,
1247
+ IS_CAUSAL=IS_CAUSAL,
1248
+ BLOCK_M=BLOCK_M,
1249
+ BLOCK_DMODEL_QK=BLOCK_DMODEL_QK,
1250
+ BLOCK_DMODEL_V=BLOCK_DMODEL_V,
1251
+ BLOCK_N=BLOCK_N,
1252
+ PRE_LOAD_V=PRE_LOAD_V,
1253
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1254
+ PADDED_HEAD_QK=PADDED_HEAD_QK,
1255
+ PADDED_HEAD_V=PADDED_HEAD_V,
1256
+ ACTUAL_BLOCK_DMODEL_QK=ACTUAL_BLOCK_DMODEL_QK,
1257
+ ACTUAL_BLOCK_DMODEL_V=ACTUAL_BLOCK_DMODEL_V,
1258
+ SM_SCALE=SM_SCALE,
1259
+ USE_ALIBI=USE_ALIBI,
1260
+ USE_EXP2=USE_EXP2,
1261
+ RETURN_SCORES=RETURN_SCORES,
1262
+ USE_SLIDING_WINDOW=USE_SLIDING_WINDOW,
1263
+ WINDOW_SIZE_LEFT=WINDOW_SIZE_LEFT,
1264
+ WINDOW_SIZE_RIGHT=WINDOW_SIZE_RIGHT,
1265
+ ACCUMULATOR_TYPE=ACCUMULATOR_TYPE,
1266
+ )
1267
+
1268
+ # ========== Process MASKED K Blocks in the back ==========
1269
+ if n_back_masked_blocks > 0:
1270
+ block_min = (
1271
+ n_front_skip_blocks + n_front_masked_blocks + n_full_blocks
1272
+ ) * BLOCK_N
1273
+ block_max = (
1274
+ n_front_skip_blocks
1275
+ + n_front_masked_blocks
1276
+ + n_full_blocks
1277
+ + n_back_masked_blocks
1278
+ ) * BLOCK_N
1279
+
1280
+ acc, l_i, m_i = _attn_fwd_inner(
1281
+ acc,
1282
+ l_i,
1283
+ m_i,
1284
+ q,
1285
+ k_ptrs,
1286
+ v_ptrs,
1287
+ bias_ptrs,
1288
+ stride_kn,
1289
+ stride_vk,
1290
+ stride_bn,
1291
+ stride_sn,
1292
+ stride_sm,
1293
+ start_m,
1294
+ seqlen_k,
1295
+ seqlen_q,
1296
+ dropout_p,
1297
+ philox_seed,
1298
+ philox_offset_base,
1299
+ SD_MASK,
1300
+ stride_sz,
1301
+ stride_sh,
1302
+ off_z,
1303
+ off_h_q,
1304
+ offs_m,
1305
+ offs_n,
1306
+ offs_d_qk,
1307
+ offs_d_v,
1308
+ block_min, # Start of range: n_full_blocks * BLOCK_N
1309
+ block_max, # End of range: n_visible_k_blocks * BLOCK_N
1310
+ n_extra_tokens, # Padding tokens in last block
1311
+ alibi_slope,
1312
+ q_descale,
1313
+ k_descale,
1314
+ v_descale,
1315
+ IS_FP8,
1316
+ FP8_MAX,
1317
+ FP8_P_DESCALE,
1318
+ APPLY_MASK=True, # Masked blocks
1319
+ IS_CAUSAL=IS_CAUSAL, # Use actual causal flag
1320
+ BLOCK_M=BLOCK_M,
1321
+ BLOCK_DMODEL_QK=BLOCK_DMODEL_QK,
1322
+ BLOCK_DMODEL_V=BLOCK_DMODEL_V,
1323
+ BLOCK_N=BLOCK_N,
1324
+ PRE_LOAD_V=PRE_LOAD_V,
1325
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1326
+ PADDED_HEAD_QK=PADDED_HEAD_QK,
1327
+ PADDED_HEAD_V=PADDED_HEAD_V,
1328
+ ACTUAL_BLOCK_DMODEL_QK=ACTUAL_BLOCK_DMODEL_QK,
1329
+ ACTUAL_BLOCK_DMODEL_V=ACTUAL_BLOCK_DMODEL_V,
1330
+ SM_SCALE=SM_SCALE,
1331
+ USE_ALIBI=USE_ALIBI,
1332
+ USE_EXP2=USE_EXP2,
1333
+ RETURN_SCORES=RETURN_SCORES,
1334
+ USE_SLIDING_WINDOW=USE_SLIDING_WINDOW,
1335
+ WINDOW_SIZE_LEFT=WINDOW_SIZE_LEFT,
1336
+ WINDOW_SIZE_RIGHT=WINDOW_SIZE_RIGHT,
1337
+ ACCUMULATOR_TYPE=ACCUMULATOR_TYPE,
1338
+ )
1339
+
1340
+ # ============================================================
1341
+ # EPILOGUE
1342
+ # ============================================================
1343
+ # Handle invalid rows: rows with no valid keys to attend to.
1344
+ # This occurs with sliding window or causal attention (when seqlen_q > seqlen_k).
1345
+ # For invalid rows: m_i = -inf, l_i = 0, acc = 0.
1346
+ # We set l_i = 1.0 to avoid division by zero and ensure LSE = -inf.
1347
+ invalid_mask = m_i == float("-inf")
1348
+ l_i_safe = tl.where(invalid_mask, 1.0, l_i)
1349
+ l_recip = 1 / l_i_safe[:, None]
1350
+ acc = acc * l_recip
1351
+
1352
+ if ENABLE_DROPOUT:
1353
+ dropout_scale = 1 / (1 - dropout_p)
1354
+ acc = acc * dropout_scale
1355
+
1356
+ # compute log-sum-exp
1357
+ if USE_EXP2:
1358
+ RCP_LN2: tl.constexpr = 1.4426950408889634
1359
+ LN2: tl.constexpr = 0.6931471824645996
1360
+ softmax_lse = (m_i * RCP_LN2 + tl.math.log2(l_i)) * LN2
1361
+ else:
1362
+ softmax_lse = m_i + tl.math.log(l_i)
1363
+
1364
+ # Ensure invalid rows have LSE = -inf
1365
+ softmax_lse = tl.where(invalid_mask, float("-inf"), softmax_lse)
1366
+
1367
+ # write back LSE(Log Sum Exponents), the log of the normalization constant
1368
+ l_offset = (
1369
+ LSE
1370
+ + off_z * stride_lse_z
1371
+ + off_h_q * stride_lse_h
1372
+ + cu_seqlens_q_start * stride_lse_m
1373
+ )
1374
+ l_ptrs = l_offset + offs_m * stride_lse_m
1375
+
1376
+ # If seqlen_q not multiple of BLOCK_M, we need to mask out the last few rows.
1377
+ # This is only true for the last Q block. For others, overflow_size will be -ve
1378
+ end_m_idx = (start_m + 1) * BLOCK_M
1379
+ overflow_size = end_m_idx - seqlen_q
1380
+ if overflow_size > 0:
1381
+ boundary = tl.full((BLOCK_M,), BLOCK_M - overflow_size, dtype=tl.int32)
1382
+ l_ptrs_mask = tl.arange(0, BLOCK_M) < boundary
1383
+ tl.store(l_ptrs, softmax_lse, mask=l_ptrs_mask)
1384
+ else:
1385
+ tl.store(l_ptrs, softmax_lse)
1386
+
1387
+ # write back O
1388
+ o_offset = (
1389
+ Out + off_z * stride_oz + off_h_q * stride_oh + cu_seqlens_q_start * stride_om
1390
+ )
1391
+ o_ptrs = o_offset + offs_m[:, None] * stride_om + offs_d_v[None, :] * stride_on
1392
+ o_ptrs_mask = tl.full([BLOCK_M, BLOCK_DMODEL_V], 1, dtype=tl.int1)
1393
+ if overflow_size > 0:
1394
+ o_ptrs_mask = o_ptrs_mask & (offs_m[:, None] < seqlen_q)
1395
+ if PADDED_HEAD_V:
1396
+ o_ptrs_mask = o_ptrs_mask & (offs_d_v[None, :] < ACTUAL_BLOCK_DMODEL_V)
1397
+
1398
+ tl.store(o_ptrs, acc.to(Out.dtype.element_ty), mask=o_ptrs_mask)
1399
+
1400
+
1401
+ def attention_forward_prefill_triton_impl(
1402
+ q: torch.Tensor,
1403
+ k: torch.Tensor,
1404
+ v: torch.Tensor,
1405
+ o: torch.Tensor,
1406
+ softmax_lse: torch.Tensor,
1407
+ sd_mask: Optional[torch.Tensor],
1408
+ sm_scale: float,
1409
+ alibi_slopes: Optional[torch.Tensor],
1410
+ causal: bool,
1411
+ window_size_left: int,
1412
+ window_size_right: int,
1413
+ bias: Optional[torch.Tensor],
1414
+ layout: Literal["bshd", "bhsd", "thd"],
1415
+ # varlen
1416
+ cu_seqlens_q: Optional[torch.Tensor],
1417
+ cu_seqlens_k: Optional[torch.Tensor],
1418
+ max_seqlens_q: int,
1419
+ max_seqlens_k: int,
1420
+ # dropout
1421
+ dropout_p: float,
1422
+ philox_seed: Optional[int],
1423
+ philox_offset: Optional[int],
1424
+ # misc
1425
+ return_scores: bool,
1426
+ use_exp2: bool,
1427
+ # fp8
1428
+ q_descale: Optional[torch.Tensor],
1429
+ k_descale: Optional[torch.Tensor],
1430
+ v_descale: Optional[torch.Tensor],
1431
+ # seqused for FA v3
1432
+ seqused_q: Optional[torch.Tensor] = None,
1433
+ seqused_k: Optional[torch.Tensor] = None,
1434
+ # rotary (optional)
1435
+ rotary_cos: Optional[torch.Tensor] = None,
1436
+ rotary_sin: Optional[torch.Tensor] = None,
1437
+ rotary_interleaved: bool = False,
1438
+ seqlens_rotary: Optional[torch.Tensor] = None,
1439
+ ):
1440
+ # get params, strides and shape
1441
+ IS_VARLEN = layout == "thd"
1442
+
1443
+ # common assertions
1444
+ assert (
1445
+ 0.0 <= dropout_p <= 1.0
1446
+ ), f"dropout_p must be between 0 and 1, got {dropout_p}"
1447
+ assert (
1448
+ q.device == k.device == v.device == o.device
1449
+ ), f"All tensors must be on the same device. Got: q={q.device}, k={k.device}, v={v.device}, o={o.device}"
1450
+ assert q.dtype == k.dtype == v.dtype, "q, k, v must have the same dtype"
1451
+ current_device = torch.cuda.current_device()
1452
+ assert (
1453
+ q.is_cuda and q.device.index == current_device
1454
+ ), f"Device mismatch: Kernel will launch on cuda:{current_device}, but tensors are on {q.device}"
1455
+
1456
+ # get shapes and strides
1457
+ if IS_VARLEN:
1458
+ # shape
1459
+ total_seqlen_q, nheads_q, head_size_q = q.shape
1460
+ total_seqlen_k, nheads_k, head_size_k = k.shape
1461
+ total_seqlen_v, nheads_v, head_size_v = v.shape
1462
+
1463
+ # assert shapes
1464
+ assert (
1465
+ cu_seqlens_q is not None
1466
+ ), "cu_seqlens_q must be provided for varlen layout"
1467
+ assert (
1468
+ cu_seqlens_k is not None
1469
+ ), "cu_seqlens_k must be provided for varlen layout"
1470
+ assert (
1471
+ max_seqlens_q is not None and max_seqlens_q > 0
1472
+ ), "max_seqlens_q must be provided and positive for varlen layout"
1473
+ assert (
1474
+ max_seqlens_k is not None and max_seqlens_k > 0
1475
+ ), "max_seqlens_k must be provided and positive for varlen layout"
1476
+
1477
+ # assert head dimensions
1478
+ assert (
1479
+ head_size_q == head_size_k
1480
+ ), f"head sizes must match: q={head_size_q}, k={head_size_k}"
1481
+ assert (
1482
+ nheads_k == nheads_v
1483
+ ), f"k and v must have same number of heads: k={nheads_k}, v={nheads_v}"
1484
+ assert (
1485
+ nheads_q % nheads_k == 0
1486
+ ), f"nheads_q {nheads_q} must be divisible by nheads_k {nheads_k} for GQA/MQA"
1487
+
1488
+ # assert output shapes
1489
+ assert o.shape == (
1490
+ total_seqlen_q,
1491
+ nheads_q,
1492
+ head_size_v,
1493
+ ), f"o shape {o.shape} != expected {(total_seqlen_q, nheads_q, head_size_v)}"
1494
+
1495
+ # assert cu_seqlens
1496
+ assert (
1497
+ cu_seqlens_q.dtype == torch.int32
1498
+ ), f"cu_seqlens_q must be int32, got {cu_seqlens_q.dtype}"
1499
+ assert (
1500
+ cu_seqlens_k.dtype == torch.int32
1501
+ ), f"cu_seqlens_k must be int32, got {cu_seqlens_k.dtype}"
1502
+ assert cu_seqlens_q[0] == 0, "cu_seqlens_q must start with 0"
1503
+ assert cu_seqlens_k[0] == 0, "cu_seqlens_k must start with 0"
1504
+ assert (
1505
+ cu_seqlens_q[-1] == total_seqlen_q
1506
+ ), f"cu_seqlens_q[-1] {cu_seqlens_q[-1]} != total_seqlen_q {total_seqlen_q}"
1507
+ assert (
1508
+ cu_seqlens_k[-1] == total_seqlen_k
1509
+ ), f"cu_seqlens_k[-1] {cu_seqlens_k[-1]} != total_seqlen_k {total_seqlen_k}"
1510
+
1511
+ # set vars
1512
+ batch = len(cu_seqlens_q) - 1
1513
+ head_size_qk = head_size_q
1514
+
1515
+ # Assert softmax_lse tensor is large enough
1516
+ assert (
1517
+ softmax_lse.shape[0] >= nheads_q
1518
+ ), f"softmax_lse.shape[0]={softmax_lse.shape[0]} must be >= nheads_q={nheads_q}"
1519
+ assert (
1520
+ softmax_lse.shape[1] >= total_seqlen_q
1521
+ ), f"softmax_lse.shape[1]={softmax_lse.shape[1]} must be >= total_seqlen_q={total_seqlen_q}"
1522
+ assert (
1523
+ softmax_lse.dtype == torch.float32
1524
+ ), f"softmax_lse must be float32, got {softmax_lse.dtype}"
1525
+ assert softmax_lse.device == q.device, "softmax_lse must be on same device as q"
1526
+
1527
+ # strides
1528
+ stride_qb, stride_qh, stride_qm, stride_qd = (
1529
+ 0,
1530
+ q.stride(1),
1531
+ q.stride(0),
1532
+ q.stride(2),
1533
+ )
1534
+ stride_kb, stride_kh, stride_kn, stride_kd = (
1535
+ 0,
1536
+ k.stride(1),
1537
+ k.stride(0),
1538
+ k.stride(2),
1539
+ )
1540
+ stride_vb, stride_vh, stride_vn, stride_vd = (
1541
+ 0,
1542
+ v.stride(1),
1543
+ v.stride(0),
1544
+ v.stride(2),
1545
+ )
1546
+ stride_ob, stride_oh, stride_om, stride_od = (
1547
+ 0,
1548
+ o.stride(1),
1549
+ o.stride(0),
1550
+ o.stride(2),
1551
+ )
1552
+ stride_lse_z, stride_lse_h, stride_lse_m = (
1553
+ 0,
1554
+ softmax_lse.stride(0),
1555
+ softmax_lse.stride(1),
1556
+ )
1557
+ else:
1558
+ # shapes
1559
+ batch_q, seqlen_q, nheads_q, head_size_q = q.shape
1560
+ batch_k, seqlen_k, nheads_k, head_size_k = k.shape
1561
+ batch_v, seqlen_v, nheads_v, head_size_v = v.shape
1562
+
1563
+ # assert batch dimensions
1564
+ assert (
1565
+ batch_q == batch_k == batch_v
1566
+ ), f"batch sizes must match: q={batch_q}, k={batch_k}, v={batch_v}"
1567
+
1568
+ # assert head dimensions
1569
+ assert (
1570
+ head_size_q == head_size_k
1571
+ ), f"head sizes must match: q={head_size_q}, k={head_size_k}"
1572
+ assert (
1573
+ nheads_k == nheads_v
1574
+ ), f"k and v must have same number of heads: k={nheads_k}, v={nheads_v}"
1575
+ assert (
1576
+ nheads_q % nheads_k == 0
1577
+ ), f"nheads_q {nheads_q} must be divisible by nheads_k {nheads_k} for GQA/MQA"
1578
+
1579
+ # assert sequence lengths
1580
+ assert (
1581
+ seqlen_k == seqlen_v
1582
+ ), f"k and v sequence lengths must match: k={seqlen_k}, v={seqlen_v}"
1583
+
1584
+ # assert output shapes
1585
+ assert o.shape == (
1586
+ batch_q,
1587
+ seqlen_q,
1588
+ nheads_q,
1589
+ head_size_v,
1590
+ ), f"o shape {o.shape} != expected {(batch_q, seqlen_q, nheads_q, head_size_v)}"
1591
+
1592
+ # set vars
1593
+ batch = batch_q
1594
+ head_size_qk = head_size_q
1595
+ max_seqlens_q = seqlen_q
1596
+ max_seqlens_k = seqlen_k
1597
+
1598
+ # Assert softmax_lse tensor is large enough
1599
+ assert (
1600
+ softmax_lse.shape[0] >= batch
1601
+ ), f"softmax_lse.shape[0]={softmax_lse.shape[0]} must be >= batch={batch}"
1602
+ assert (
1603
+ softmax_lse.shape[1] >= nheads_q
1604
+ ), f"softmax_lse.shape[1]={softmax_lse.shape[1]} must be >= nheads_q={nheads_q}"
1605
+ assert (
1606
+ softmax_lse.shape[2] >= seqlen_q
1607
+ ), f"softmax_lse.shape[2]={softmax_lse.shape[2]} must be >= seqlen_q={seqlen_q}"
1608
+ assert (
1609
+ softmax_lse.dtype == torch.float32
1610
+ ), f"softmax_lse must be float32, got {softmax_lse.dtype}"
1611
+ assert softmax_lse.device == q.device, "softmax_lse must be on same device as q"
1612
+
1613
+ # strides
1614
+ stride_qb, stride_qh, stride_qm, stride_qd = (
1615
+ q.stride(0),
1616
+ q.stride(2),
1617
+ q.stride(1),
1618
+ q.stride(3),
1619
+ )
1620
+ stride_kb, stride_kh, stride_kn, stride_kd = (
1621
+ k.stride(0),
1622
+ k.stride(2),
1623
+ k.stride(1),
1624
+ k.stride(3),
1625
+ )
1626
+ stride_vb, stride_vh, stride_vn, stride_vd = (
1627
+ v.stride(0),
1628
+ v.stride(2),
1629
+ v.stride(1),
1630
+ v.stride(3),
1631
+ )
1632
+ stride_ob, stride_oh, stride_om, stride_od = (
1633
+ o.stride(0),
1634
+ o.stride(2),
1635
+ o.stride(1),
1636
+ o.stride(3),
1637
+ )
1638
+ stride_lse_z, stride_lse_h, stride_lse_m = softmax_lse.stride()
1639
+
1640
+ # apply rotary embeddings
1641
+ if rotary_cos is not None and rotary_sin is not None:
1642
+ if IS_VARLEN:
1643
+ raise NotImplementedError(
1644
+ "Rotary embeddings with varlen (thd layout) prefill are not implemented yet."
1645
+ )
1646
+ seqlen_offsets = seqlens_rotary if seqlens_rotary is not None else 0
1647
+ local = (window_size_left != -1) or (window_size_right != -1)
1648
+ q, _ = apply_rotary(
1649
+ q,
1650
+ None,
1651
+ rotary_cos,
1652
+ rotary_sin,
1653
+ causal=causal,
1654
+ local=local,
1655
+ interleaved=rotary_interleaved,
1656
+ seqlen_offsets=seqlen_offsets,
1657
+ )
1658
+
1659
+ # fp8 setup and assertions
1660
+ IS_FP8 = is_fp8([q, k, v])
1661
+ if IS_FP8:
1662
+ arch = get_arch()
1663
+ if not arch.supports_fp8:
1664
+ raise RuntimeError(f"{arch.name} does not support FP8")
1665
+ FP8_MAX = torch.finfo(q.dtype).max
1666
+ rec_dtype = arch.recommended_fp8_dtype(q.dtype)
1667
+ if q.dtype != rec_dtype or k.dtype != rec_dtype or v.dtype != rec_dtype:
1668
+ warnings.warn(
1669
+ f"Use {rec_dtype} data type on {arch}. Got q: {q.dtype}, k: {k.dtype}, v: {v.dtype}",
1670
+ UserWarning,
1671
+ )
1672
+
1673
+ if (q_descale is None) or (k_descale is None) or (v_descale is None):
1674
+ warnings.warn(
1675
+ "FP8 tensors detected but descale factors not provided. Using default scale of 1.0",
1676
+ UserWarning,
1677
+ )
1678
+ # Create default descale tensors if not provided
1679
+ if q_descale is None:
1680
+ q_descale = torch.ones(
1681
+ batch, nheads_q, dtype=torch.float32, device=q.device
1682
+ )
1683
+ if k_descale is None:
1684
+ k_descale = torch.ones(
1685
+ batch, nheads_k, dtype=torch.float32, device=q.device
1686
+ )
1687
+ if v_descale is None:
1688
+ v_descale = torch.ones(
1689
+ batch, nheads_k, dtype=torch.float32, device=q.device
1690
+ )
1691
+ else:
1692
+ # Enforce exact expected shapes; no reshaping or normalization.
1693
+ assert (
1694
+ q_descale.dim() == 2
1695
+ and q_descale.shape[0] == batch
1696
+ and q_descale.shape[1] == nheads_k
1697
+ ), f"q_descale expected shape ({batch}, {nheads_k}) got {tuple(q_descale.shape)}"
1698
+ assert (
1699
+ k_descale.dim() == 2
1700
+ and k_descale.shape[0] == batch
1701
+ and k_descale.shape[1] == nheads_k
1702
+ ), f"k_descale expected shape ({batch}, {nheads_k}) got {tuple(k_descale.shape)}"
1703
+ assert (
1704
+ v_descale.dim() == 2
1705
+ and v_descale.shape[0] == batch
1706
+ and v_descale.shape[1] == nheads_k
1707
+ ), f"v_descale expected shape ({batch}, {nheads_k}) got {tuple(v_descale.shape)}"
1708
+
1709
+ # o should be fp32 or fp16/bf16
1710
+ assert o.dtype in [
1711
+ torch.float16,
1712
+ torch.bfloat16,
1713
+ torch.float32,
1714
+ ], f"Output tensor o must be fp16, bf16, or fp32 when using fp8, got {o.dtype}"
1715
+
1716
+ stride_q_descale_z = q_descale.stride(0) if q_descale is not None else 0
1717
+ stride_k_descale_z = k_descale.stride(0) if k_descale is not None else 0
1718
+ stride_v_descale_z = v_descale.stride(0) if v_descale is not None else 0
1719
+
1720
+ if DEBUG:
1721
+ print("FP8 path triggered in fwd_prefill.py")
1722
+ else:
1723
+ FP8_MAX = None
1724
+ q_descale = k_descale = v_descale = None
1725
+ stride_q_descale_z = stride_k_descale_z = stride_v_descale_z = None
1726
+
1727
+ # check output dtype matches input dtype when not using fp8
1728
+ assert (
1729
+ o.dtype == q.dtype
1730
+ ), f"Output dtype {o.dtype} must match input dtype {q.dtype} when not using fp8"
1731
+
1732
+ # check features
1733
+ use_sliding_window = window_size_left != -1 or window_size_right != -1
1734
+ use_alibi, (stride_az, stride_ah) = (
1735
+ (True, alibi_slopes.stride()) if alibi_slopes is not None else (False, (0, 0))
1736
+ )
1737
+ # NOTE: a large bias tensor leads to overflow during pointer arithmetic
1738
+ if bias is not None:
1739
+ assert bias.numel() < 2**31
1740
+
1741
+ # Get closest power of 2 over or equal to 32 for both QK and V dimensions
1742
+ padded_d_model_qk = 1 << (head_size_qk - 1).bit_length()
1743
+ padded_d_model_v = 1 << (head_size_v - 1).bit_length()
1744
+ # Smallest head_dim supported is 16. If smaller, the tile in the
1745
+ # kernel is padded - there is no padding in memory for any dims.
1746
+ padded_d_model_qk = max(padded_d_model_qk, 16)
1747
+ padded_d_model_v = max(padded_d_model_v, 16)
1748
+
1749
+ # sd_mask assertions and strides
1750
+ if sd_mask is not None:
1751
+ assert dropout_p > 0.0 or return_scores, "sd_mask provided but not used"
1752
+ assert (
1753
+ sd_mask is not None
1754
+ ), "sd_mask must be provided when return_scores=True or dropout_p > 0"
1755
+ # Assert sd_mask tensor is large enough
1756
+ assert (
1757
+ sd_mask.shape[0] >= batch
1758
+ ), f"sd_mask.shape[0]={sd_mask.shape[0]} must be >= batch={batch}"
1759
+ assert (
1760
+ sd_mask.shape[1] >= nheads_q
1761
+ ), f"sd_mask.shape[1]={sd_mask.shape[1]} must be >= nheads_q={nheads_q}"
1762
+ assert (
1763
+ sd_mask.shape[2] >= max_seqlens_q
1764
+ ), f"sd_mask.shape[2]={sd_mask.shape[2]} must be >= max_seqlens_q={max_seqlens_q}"
1765
+ assert (
1766
+ sd_mask.shape[3] >= max_seqlens_k
1767
+ ), f"sd_mask.shape[3]={sd_mask.shape[3]} must be >= max_seqlens_k={max_seqlens_k}"
1768
+ assert sd_mask.device == q.device, "sd_mask must be on same device as q"
1769
+
1770
+ stride_sz, stride_sh, stride_sm, stride_sn = (
1771
+ sd_mask.stride(0),
1772
+ sd_mask.stride(1),
1773
+ sd_mask.stride(2),
1774
+ sd_mask.stride(3),
1775
+ )
1776
+ else:
1777
+ stride_sz, stride_sh, stride_sm, stride_sn = (0, 0, 0, 0)
1778
+
1779
+ if bias is not None:
1780
+ stride_bz, stride_bh, stride_bm, stride_bn = (
1781
+ bias.stride(0),
1782
+ bias.stride(1),
1783
+ bias.stride(2),
1784
+ bias.stride(3),
1785
+ )
1786
+ else:
1787
+ stride_bz, stride_bh, stride_bm, stride_bn = (0, 0, 0, 0)
1788
+
1789
+ # Detect if we need to force masking for all blocks (required on some architectures)
1790
+ arch = get_arch()
1791
+ force_masking = arch.is_rdna
1792
+
1793
+ num_xcd = 1 if arch.is_rdna else 8
1794
+
1795
+ # launch kernel
1796
+ def grid(META):
1797
+ return (nheads_q, triton.cdiv(max_seqlens_q, META["BLOCK_M"]), batch)
1798
+
1799
+ attn_fwd[grid](
1800
+ q,
1801
+ k,
1802
+ v,
1803
+ bias,
1804
+ q_descale,
1805
+ k_descale,
1806
+ v_descale,
1807
+ stride_q_descale_z,
1808
+ stride_k_descale_z,
1809
+ stride_v_descale_z,
1810
+ softmax_lse,
1811
+ o,
1812
+ sd_mask,
1813
+ alibi_slopes,
1814
+ stride_qb,
1815
+ stride_qh,
1816
+ stride_qm,
1817
+ stride_qd,
1818
+ stride_kb,
1819
+ stride_kh,
1820
+ stride_kn,
1821
+ stride_kd,
1822
+ stride_vb,
1823
+ stride_vh,
1824
+ stride_vn,
1825
+ stride_vd,
1826
+ stride_ob,
1827
+ stride_oh,
1828
+ stride_om,
1829
+ stride_od,
1830
+ stride_bz,
1831
+ stride_bh,
1832
+ stride_bm,
1833
+ stride_bn,
1834
+ stride_az,
1835
+ stride_ah,
1836
+ stride_sz,
1837
+ stride_sh,
1838
+ stride_sm,
1839
+ stride_sn,
1840
+ stride_lse_z,
1841
+ stride_lse_h,
1842
+ stride_lse_m,
1843
+ cu_seqlens_q,
1844
+ cu_seqlens_k,
1845
+ seqused_q,
1846
+ seqused_k, # Pass seqused tensors
1847
+ dropout_p=dropout_p,
1848
+ philox_seed=philox_seed,
1849
+ philox_offset_base=philox_offset,
1850
+ HQ=nheads_q,
1851
+ HK=nheads_k,
1852
+ ACTUAL_BLOCK_DMODEL_QK=head_size_qk,
1853
+ ACTUAL_BLOCK_DMODEL_V=head_size_v,
1854
+ MAX_SEQLENS_Q=max_seqlens_q,
1855
+ MAX_SEQLENS_K=max_seqlens_k,
1856
+ SM_SCALE=sm_scale,
1857
+ IS_CAUSAL=causal,
1858
+ USE_SLIDING_WINDOW=use_sliding_window,
1859
+ WINDOW_SIZE_LEFT=window_size_left,
1860
+ WINDOW_SIZE_RIGHT=window_size_right,
1861
+ IS_VARLEN=IS_VARLEN,
1862
+ BLOCK_DMODEL_QK=padded_d_model_qk,
1863
+ BLOCK_DMODEL_V=padded_d_model_v,
1864
+ USE_BIAS=False if bias is None else True,
1865
+ USE_ALIBI=use_alibi,
1866
+ ENABLE_DROPOUT=dropout_p > 0.0,
1867
+ USE_EXP2=use_exp2,
1868
+ RETURN_SCORES=return_scores,
1869
+ IS_FP8=IS_FP8,
1870
+ FP8_MAX=FP8_MAX,
1871
+ FP8_P_DESCALE=False,
1872
+ USE_SEQUSED=(seqused_q is not None or seqused_k is not None),
1873
+ FORCE_MASKING=force_masking,
1874
+ NUM_XCD=num_xcd,
1875
+ )
build/torch-rocm/_kernels/flash_attn_triton_amd/interface_v2.py ADDED
@@ -0,0 +1,828 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from typing import Literal, Optional, Union
3
+ from .fwd_prefill import attention_forward_prefill_triton_impl
4
+ from .fwd_decode import attention_forward_decode_triton_impl
5
+ from .bwd import attention_backward_triton_impl
6
+ from .utils import (
7
+ DEBUG,
8
+ USE_EXP2,
9
+ BWD_MODE,
10
+ PHILOX_SEED,
11
+ PHILOX_OFFSET,
12
+ SHAPE_EXPECTATIONS,
13
+ round_multiple,
14
+ )
15
+
16
+
17
+ def fwd(
18
+ q: torch.Tensor,
19
+ k: torch.Tensor,
20
+ v: torch.Tensor,
21
+ out: Optional[torch.Tensor],
22
+ alibi_slopes: Optional[torch.Tensor],
23
+ dropout_p: float,
24
+ softmax_scale: float,
25
+ causal: bool,
26
+ window_size_left: int,
27
+ window_size_right: int,
28
+ softcap: float,
29
+ return_softmax: bool,
30
+ gen_: Optional[torch.Tensor] = None,
31
+ ) -> tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], torch.Tensor]:
32
+
33
+ # Reject FP8 tensors (FA2 AMD path does not support FP8)
34
+ if str(q.dtype).startswith("torch.float8"):
35
+ raise NotImplementedError(
36
+ "FP8 tensors are not supported in the AMD Triton FA2 interface. Use the FA3 path instead."
37
+ )
38
+
39
+ # Unsupported features assertions (keep behavior explicit like v3 shim)
40
+ if softcap != 0.0:
41
+ raise NotImplementedError(
42
+ "softcap is not supported in the AMD Triton FA2 interface (expected 0.0)."
43
+ )
44
+
45
+ if DEBUG:
46
+ print()
47
+ print("flash_attn_triton_amd.py::fwd inputs")
48
+ print("q:", q.shape)
49
+ print("k:", k.shape)
50
+ print("v:", v.shape)
51
+ print("out:", out.shape if out is not None else None)
52
+ print("alibi_slopes:", alibi_slopes.shape if alibi_slopes is not None else None)
53
+ print("dropout_p:", dropout_p)
54
+ print("softmax_scale:", softmax_scale)
55
+ print("causal:", causal)
56
+ print("window_size_left:", window_size_left)
57
+ print("window_size_right:", window_size_right)
58
+ print("softcap:", softcap)
59
+ print("return_softmax:", return_softmax)
60
+
61
+ if out is None:
62
+ out = torch.zeros_like(q)
63
+ else:
64
+ out.zero_()
65
+
66
+ # Layout / shapes
67
+ layout: Literal["bshd", "bhsd", "thd"] = "bshd"
68
+ max_seqlen_q = q.shape[1]
69
+ max_seqlen_k = k.shape[1]
70
+ batch, _, nheads_q, _ = q.shape
71
+
72
+ # Normalize / validate alibi
73
+ if alibi_slopes is not None:
74
+ if alibi_slopes.dim() == 1:
75
+ alibi_slopes = alibi_slopes.unsqueeze(0).expand(batch, -1)
76
+ assert alibi_slopes.is_cuda and alibi_slopes.dim() == 2
77
+ assert alibi_slopes.shape == (batch, nheads_q)
78
+
79
+ # Dropout + RNG seed
80
+ philox_seed, philox_offset = PHILOX_SEED, PHILOX_OFFSET
81
+ rng_state = torch.as_tensor([philox_seed, philox_offset])
82
+
83
+ # argument checks
84
+ assert q.dim() == 4 and k.dim() == 4 and v.dim() == 4
85
+ assert q.shape[-1] == k.shape[-1] == v.shape[-1]
86
+ assert q.dtype == k.dtype == v.dtype
87
+ assert out.shape[:-1] == q.shape[:-1] and out.shape[-1] == v.shape[-1]
88
+ nheads_k = k.shape[2]
89
+ assert (nheads_q % nheads_k) == 0
90
+
91
+ # Create output tensors based on shape expectations
92
+ if SHAPE_EXPECTATIONS == "rounded":
93
+ softmax_lse = torch.zeros(
94
+ (batch, nheads_q, round_multiple(max_seqlen_q, 128)),
95
+ device=q.device,
96
+ dtype=torch.float32,
97
+ )
98
+ if dropout_p > 0.0 or return_softmax:
99
+ sd_mask = torch.zeros(
100
+ (
101
+ batch,
102
+ nheads_q,
103
+ round_multiple(max_seqlen_q, 128),
104
+ round_multiple(max_seqlen_k, 128),
105
+ ),
106
+ device=q.device,
107
+ dtype=torch.float32,
108
+ )
109
+ else:
110
+ sd_mask = None
111
+ else:
112
+ softmax_lse = torch.zeros(
113
+ (batch, nheads_q, max_seqlen_q),
114
+ device=q.device,
115
+ dtype=torch.float32,
116
+ )
117
+ if dropout_p > 0.0 or return_softmax:
118
+ sd_mask = torch.zeros(
119
+ (batch, nheads_q, max_seqlen_q, max_seqlen_k),
120
+ device=q.device,
121
+ dtype=torch.float32,
122
+ )
123
+ else:
124
+ sd_mask = None
125
+
126
+ # call implementation
127
+ if DEBUG:
128
+ print("Using Triton implementation")
129
+ attention_forward_prefill_triton_impl(
130
+ q,
131
+ k,
132
+ v,
133
+ out,
134
+ softmax_lse,
135
+ sd_mask,
136
+ softmax_scale,
137
+ alibi_slopes,
138
+ causal,
139
+ window_size_left,
140
+ window_size_right,
141
+ None,
142
+ layout,
143
+ None,
144
+ None,
145
+ max_seqlen_q,
146
+ max_seqlen_k,
147
+ dropout_p,
148
+ philox_seed,
149
+ philox_offset,
150
+ return_softmax,
151
+ USE_EXP2,
152
+ None,
153
+ None,
154
+ None,
155
+ None,
156
+ None,
157
+ None,
158
+ None,
159
+ )
160
+
161
+ if DEBUG:
162
+ print("flash_attn_triton_amd.py::fwd outputs")
163
+ print("out:", out.shape)
164
+ print("softmax_lse:", softmax_lse.shape)
165
+ print("sd_mask:", sd_mask.shape if sd_mask is not None else None)
166
+ print("rng_state:", rng_state)
167
+
168
+ # --- Assertions (shape + dtype contracts) ---
169
+ # out: (B, Sq, Hq, D)
170
+ assert out.shape == q.shape, f"[fwd] out shape {out.shape} != q shape {q.shape}"
171
+ # softmax_lse dtype
172
+ assert (
173
+ softmax_lse.dtype == torch.float32
174
+ ), f"[fwd] softmax_lse dtype {softmax_lse.dtype} != torch.float32"
175
+ # softmax_lse shape depends on SHAPE_EXPECTATIONS
176
+ if SHAPE_EXPECTATIONS == "rounded":
177
+ expected_lse_shape = (q.shape[0], q.shape[2], round_multiple(q.shape[1], 128))
178
+ else:
179
+ expected_lse_shape = (q.shape[0], q.shape[2], q.shape[1])
180
+ assert (
181
+ softmax_lse.shape == expected_lse_shape
182
+ ), f"[fwd] softmax_lse shape {softmax_lse.shape} != {expected_lse_shape}"
183
+ if return_softmax:
184
+ # sd_mask: (B, Hq, Sq, Sk)
185
+ assert sd_mask is not None, "[fwd] return_softmax=True but sd_mask is None"
186
+ assert sd_mask.dim() == 4, f"[fwd] sd_mask dim {sd_mask.dim()} != 4"
187
+ if SHAPE_EXPECTATIONS == "rounded":
188
+ expected_sq = round_multiple(q.shape[1], 128)
189
+ expected_sk = round_multiple(k.shape[1], 128)
190
+ assert (
191
+ sd_mask.shape[0] == q.shape[0]
192
+ and sd_mask.shape[1] == q.shape[2]
193
+ and sd_mask.shape[2] == expected_sq
194
+ and sd_mask.shape[3] == expected_sk
195
+ ), f"[fwd] sd_mask shape {sd_mask.shape} != (B={q.shape[0]}, Hq={q.shape[2]}, Sq={expected_sq}, Sk={expected_sk})"
196
+ else:
197
+ assert (
198
+ sd_mask.shape[0] == q.shape[0]
199
+ and sd_mask.shape[1] == q.shape[2]
200
+ and sd_mask.shape[2] == q.shape[1]
201
+ ), f"[fwd] sd_mask leading dims {sd_mask.shape[:3]} mismatch (B,Hq,Sq) {(q.shape[0], q.shape[2], q.shape[1])}"
202
+ else:
203
+ assert sd_mask is None, "[fwd] return_softmax=False but sd_mask is not None"
204
+
205
+ return out, softmax_lse, sd_mask, rng_state
206
+
207
+
208
+ def bwd(
209
+ dout: torch.Tensor,
210
+ q: torch.Tensor,
211
+ k: torch.Tensor,
212
+ v: torch.Tensor,
213
+ out: torch.Tensor,
214
+ softmax_lse: torch.Tensor,
215
+ dq: Optional[torch.Tensor],
216
+ dk: Optional[torch.Tensor],
217
+ dv: Optional[torch.Tensor],
218
+ alibi_slopes: Optional[torch.Tensor],
219
+ dropout_p: float,
220
+ softmax_scale: float,
221
+ causal: bool,
222
+ window_size_left: int,
223
+ window_size_right: int,
224
+ softcap: float,
225
+ deterministic: bool,
226
+ gen_: Optional[torch.Tensor] = None,
227
+ rng_state: Optional[torch.Tensor] = None,
228
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
229
+ if softcap != 0.0:
230
+ raise NotImplementedError(
231
+ "softcap is not supported in the AMD Triton FA2 interface (expected 0.0)."
232
+ )
233
+
234
+ # Check for sliding window - backward doesn't support it yet
235
+ is_sliding_window = (window_size_left >= 0) or (window_size_right >= 0)
236
+ if is_sliding_window:
237
+ raise NotImplementedError(
238
+ f"Sliding window attention is not yet supported in the AMD Triton backward pass "
239
+ f"(window_size_left={window_size_left}, window_size_right={window_size_right}). "
240
+ f"Use window_size=(-1, -1) for full attention."
241
+ )
242
+
243
+ if DEBUG:
244
+ print()
245
+ print("flash_attn_triton_amd.py::bwd inputs")
246
+ print("dout:", dout.shape)
247
+ print("q:", q.shape)
248
+ print("k:", k.shape)
249
+ print("v:", v.shape)
250
+ print("out:", out.shape)
251
+ print("softmax_lse:", softmax_lse.shape)
252
+ print("dq:", dq.shape if dq is not None else None)
253
+ print("dk:", dk.shape if dk is not None else None)
254
+ print("dv:", dv.shape if dv is not None else None)
255
+ print("alibi_slopes:", alibi_slopes.shape if alibi_slopes is not None else None)
256
+ print("dropout_p:", dropout_p)
257
+ print("softmax_scale:", softmax_scale)
258
+ print("causal:", causal)
259
+ print("window_size_left:", window_size_left)
260
+ print("window_size_right:", window_size_right)
261
+ print("deterministic:", deterministic)
262
+ print("rng_state:", rng_state)
263
+
264
+ dq = torch.zeros_like(q) if dq is None else dq.zero_()
265
+ dk = torch.zeros_like(k) if dk is None else dk.zero_()
266
+ dv = torch.zeros_like(v) if dv is None else dv.zero_()
267
+
268
+ # get shape
269
+ batch, seqlen_q, nheads_q, _ = q.shape
270
+
271
+ # Create delta tensor with shape based on expectations
272
+ # delta (softmax_d) : (B, Hq, Sq) or (B, Hq, round_multiple(Sq, 128))
273
+ if SHAPE_EXPECTATIONS == "rounded":
274
+ delta = torch.zeros(
275
+ (batch, nheads_q, round_multiple(seqlen_q, 128)),
276
+ device=q.device,
277
+ dtype=torch.float32,
278
+ )
279
+ else:
280
+ delta = torch.zeros(
281
+ (batch, nheads_q, seqlen_q), device=q.device, dtype=torch.float32
282
+ )
283
+
284
+ # Upstream change: base seeding logic on provided rng_state instead of dropout probability.
285
+ if rng_state is not None:
286
+ philox_seed, philox_offset = rng_state[0].item(), rng_state[1].item()
287
+ else:
288
+ philox_seed, philox_offset = None, None
289
+
290
+ if alibi_slopes is not None:
291
+ if alibi_slopes.dim() == 2:
292
+ pass
293
+ elif alibi_slopes.dim() == 1:
294
+ alibi_slopes = alibi_slopes.unsqueeze(0).expand(batch, -1)
295
+ else:
296
+ raise ValueError("Alibi can be (nheads,) or (batch_size, nheads).")
297
+
298
+ # call implementation
299
+ if DEBUG:
300
+ print(f"Using Triton implementation in {BWD_MODE} mode")
301
+ attention_backward_triton_impl(
302
+ do=dout,
303
+ q=q,
304
+ k=k,
305
+ v=v,
306
+ o=out,
307
+ softmax_lse=softmax_lse,
308
+ dq=dq,
309
+ dk=dk,
310
+ dv=dv,
311
+ delta=delta,
312
+ sm_scale=softmax_scale,
313
+ alibi_slopes=alibi_slopes,
314
+ causal=causal,
315
+ layout="bshd",
316
+ cu_seqlens_q=None,
317
+ cu_seqlens_k=None,
318
+ max_seqlen_q=seqlen_q,
319
+ max_seqlen_k=k.shape[1],
320
+ seqused_q=None,
321
+ seqused_k=None,
322
+ dropout_p=dropout_p,
323
+ philox_seed=philox_seed,
324
+ philox_offset=philox_offset,
325
+ use_exp2=USE_EXP2,
326
+ mode=BWD_MODE,
327
+ )
328
+
329
+ if DEBUG:
330
+ print("flash_attn_triton_amd.py::bwd outputs")
331
+ print("dq:", dq.shape)
332
+ print("dk:", dk.shape)
333
+ print("dv:", dv.shape)
334
+ # --- Assertions ---
335
+ assert dq.shape == q.shape, f"[bwd] dq shape {dq.shape} != q shape {q.shape}"
336
+ assert dk.shape == k.shape, f"[bwd] dk shape {dk.shape} != k shape {k.shape}"
337
+ assert dv.shape == v.shape, f"[bwd] dv shape {dv.shape} != v shape {v.shape}"
338
+ # delta (softmax_d) : (B, Hq, Sq)
339
+ if SHAPE_EXPECTATIONS == "rounded":
340
+ expected_delta_shape = (q.shape[0], q.shape[2], round_multiple(q.shape[1], 128))
341
+ else:
342
+ expected_delta_shape = (q.shape[0], q.shape[2], q.shape[1])
343
+ assert (
344
+ delta.shape == expected_delta_shape
345
+ ), f"[bwd] delta shape {delta.shape} != {expected_delta_shape}"
346
+ return dq, dk, dv, delta
347
+
348
+
349
+ def varlen_fwd(
350
+ q: torch.Tensor,
351
+ k: torch.Tensor,
352
+ v: torch.Tensor,
353
+ out: Optional[torch.Tensor],
354
+ cu_seqlens_q: torch.Tensor,
355
+ cu_seqlens_k: torch.Tensor,
356
+ seqused_k: Optional[torch.Tensor],
357
+ leftpad_k: Optional[torch.Tensor],
358
+ block_table_: Optional[torch.Tensor],
359
+ alibi_slopes: Optional[torch.Tensor],
360
+ max_seqlen_q: int,
361
+ max_seqlen_k: int,
362
+ dropout_p: float,
363
+ softmax_scale: float,
364
+ zero_tensors: bool,
365
+ causal: bool,
366
+ window_size_left: int,
367
+ window_size_right: int,
368
+ softcap: float,
369
+ return_softmax: bool,
370
+ gen_: Optional[torch.Tensor] = None,
371
+ num_splits: int = 0,
372
+ ) -> tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], torch.Tensor]:
373
+
374
+ if str(q.dtype).startswith("torch.float8"):
375
+ raise NotImplementedError(
376
+ "FP8 tensors are not supported in the AMD Triton FA2 interface (varlen_fwd). Use the FA3 path instead."
377
+ )
378
+
379
+ if softcap != 0.0:
380
+ raise NotImplementedError(
381
+ "softcap is not supported in varlen_fwd (expected 0.0)."
382
+ )
383
+ if leftpad_k is not None:
384
+ raise NotImplementedError(
385
+ "leftpad_k is not supported in AMD Triton FA2 varlen_fwd."
386
+ )
387
+ if block_table_ is not None:
388
+ raise NotImplementedError(
389
+ "block_table / paged attention is not supported in AMD Triton FA2 varlen_fwd."
390
+ )
391
+ if seqused_k is not None:
392
+ raise NotImplementedError(
393
+ "seqused_k is not supported in AMD Triton FA2 varlen_fwd."
394
+ )
395
+ if num_splits not in (0, 1):
396
+ raise NotImplementedError(
397
+ "num_splits > 1 not supported in AMD Triton FA2 varlen_fwd."
398
+ )
399
+
400
+ if DEBUG:
401
+ print()
402
+ print("flash_attn_triton_amd.py::varlen_fwd")
403
+ print("q:", q.shape)
404
+ print("k:", k.shape)
405
+ print("v:", v.shape)
406
+ print("cu_seqlens_q:", cu_seqlens_q, cu_seqlens_q.shape)
407
+ print("cu_seqlens_k:", cu_seqlens_k, cu_seqlens_k.shape)
408
+ print("alibi_slopes:", alibi_slopes)
409
+ print("max_seqlen_q:", max_seqlen_q)
410
+ print("max_seqlen_k:", max_seqlen_k)
411
+ print("dropout_p:", dropout_p)
412
+ print("softmax_scale:", softmax_scale)
413
+ print("causal:", causal)
414
+ print("window_size_left:", window_size_left)
415
+ print("window_size_right:", window_size_right)
416
+ print("gen_:", gen_)
417
+ out = torch.zeros_like(q) if out is None else out.zero_()
418
+
419
+ # Layout and basic info for varlen
420
+ layout: Literal["bshd", "bhsd", "thd"] = "thd"
421
+ batch = len(cu_seqlens_q) - 1
422
+ total_q, nheads_q, _ = q.shape
423
+
424
+ # Create softmax_lse tensor - varlen always uses exact shape (Hq, Total_Q)
425
+ softmax_lse = torch.zeros((nheads_q, total_q), device=q.device, dtype=torch.float32)
426
+
427
+ # Create sd_mask tensor if needed
428
+ if return_softmax:
429
+ # sd_mask: (B, Hq, Sq, Sk) - shape based on expectations
430
+ if SHAPE_EXPECTATIONS == "rounded":
431
+ sd_mask = torch.zeros(
432
+ (
433
+ batch,
434
+ nheads_q,
435
+ round_multiple(max_seqlen_q, 128),
436
+ round_multiple(max_seqlen_k, 128),
437
+ ),
438
+ device=q.device,
439
+ dtype=q.dtype,
440
+ )
441
+ else:
442
+ sd_mask = torch.zeros(
443
+ (batch, nheads_q, max_seqlen_q, max_seqlen_k),
444
+ device=q.device,
445
+ dtype=q.dtype,
446
+ )
447
+ else:
448
+ sd_mask = None
449
+
450
+ if alibi_slopes is not None:
451
+ if alibi_slopes.dim() == 1:
452
+ alibi_slopes = alibi_slopes.unsqueeze(0).expand(batch, -1)
453
+ assert alibi_slopes.is_cuda and alibi_slopes.dim() == 2
454
+ assert alibi_slopes.shape == (batch, nheads_q)
455
+
456
+ philox_seed, philox_offset = PHILOX_SEED, PHILOX_OFFSET
457
+ rng_state = torch.as_tensor([philox_seed, philox_offset])
458
+
459
+ # Inline checks (subset appropriate for varlen)
460
+ assert q.dim() == 3 and k.dim() == 3 and v.dim() == 3
461
+ assert q.shape[-1] == k.shape[-1] == v.shape[-1]
462
+ assert q.dtype == k.dtype == v.dtype
463
+ assert out.shape == q.shape
464
+ nheads_k = k.shape[1]
465
+ assert (nheads_q % nheads_k) == 0
466
+
467
+ # call implementation
468
+ if DEBUG:
469
+ print("Using Triton implementation")
470
+ attention_forward_prefill_triton_impl(
471
+ q,
472
+ k,
473
+ v,
474
+ out,
475
+ softmax_lse,
476
+ sd_mask,
477
+ softmax_scale,
478
+ alibi_slopes,
479
+ causal,
480
+ window_size_left,
481
+ window_size_right,
482
+ None,
483
+ layout,
484
+ cu_seqlens_q,
485
+ cu_seqlens_k,
486
+ max_seqlen_q,
487
+ max_seqlen_k,
488
+ dropout_p,
489
+ philox_seed,
490
+ philox_offset,
491
+ return_softmax,
492
+ USE_EXP2,
493
+ None,
494
+ None,
495
+ None,
496
+ )
497
+
498
+ if DEBUG:
499
+ print("varlen_fwd outputs")
500
+ print("out:", out, out.shape)
501
+ print("softmax_lse:", softmax_lse, softmax_lse.shape)
502
+ print("sd_mask:", sd_mask, sd_mask.shape if sd_mask is not None else None)
503
+ # --- Assertions ---
504
+ # out: (Total_Q, Hq, D)
505
+ assert (
506
+ out.shape == q.shape
507
+ ), f"[varlen_fwd] out shape {out.shape} != q shape {q.shape}"
508
+ # softmax_lse: (Hq, Total_Q)
509
+ expected_lse_shape = (q.shape[1], q.shape[0])
510
+ assert (
511
+ softmax_lse.shape == expected_lse_shape
512
+ ), f"[varlen_fwd] softmax_lse shape {softmax_lse.shape} != {expected_lse_shape}"
513
+ assert (
514
+ softmax_lse.dtype == torch.float32
515
+ ), f"[varlen_fwd] softmax_lse dtype {softmax_lse.dtype} != torch.float32"
516
+ if return_softmax:
517
+ # sd_mask expected: (B, Hq, max_seqlen_q, max_seqlen_k)
518
+ assert (
519
+ sd_mask is not None
520
+ ), "[varlen_fwd] return_softmax=True but sd_mask is None"
521
+ assert sd_mask.dim() == 4, f"[varlen_fwd] sd_mask dim {sd_mask.dim()} != 4"
522
+ batch = len(cu_seqlens_q) - 1
523
+ assert (
524
+ sd_mask.shape[0] == batch
525
+ ), f"[varlen_fwd] sd_mask batch {sd_mask.shape[0]} != {batch}"
526
+ assert (
527
+ sd_mask.shape[1] == q.shape[1]
528
+ ), f"[varlen_fwd] sd_mask nheads {sd_mask.shape[1]} != {q.shape[1]}"
529
+ if SHAPE_EXPECTATIONS == "rounded":
530
+ expected_sq = round_multiple(max_seqlen_q, 128)
531
+ expected_sk = round_multiple(max_seqlen_k, 128)
532
+ assert (
533
+ sd_mask.shape[2] == expected_sq and sd_mask.shape[3] == expected_sk
534
+ ), f"[varlen_fwd] sd_mask shape {sd_mask.shape} != (B={batch}, Hq={q.shape[1]}, Sq={expected_sq}, Sk={expected_sk})"
535
+ else:
536
+ assert (
537
+ sd_mask.shape[2] == max_seqlen_q and sd_mask.shape[3] == max_seqlen_k
538
+ ), f"[varlen_fwd] sd_mask shape {sd_mask.shape} != (B={batch}, Hq={q.shape[1]}, Sq={max_seqlen_q}, Sk={max_seqlen_k})"
539
+ else:
540
+ assert (
541
+ sd_mask is None
542
+ ), "[varlen_fwd] return_softmax=False but sd_mask is not None"
543
+ return out, softmax_lse, sd_mask, rng_state
544
+
545
+
546
+ def varlen_bwd(
547
+ dout: torch.Tensor,
548
+ q: torch.Tensor,
549
+ k: torch.Tensor,
550
+ v: torch.Tensor,
551
+ out: torch.Tensor,
552
+ softmax_lse: torch.Tensor,
553
+ dq: Optional[torch.Tensor],
554
+ dk: Optional[torch.Tensor],
555
+ dv: Optional[torch.Tensor],
556
+ cu_seqlens_q: torch.Tensor,
557
+ cu_seqlens_k: torch.Tensor,
558
+ alibi_slopes: Optional[torch.Tensor],
559
+ max_seqlen_q: int,
560
+ max_seqlen_k: int,
561
+ dropout_p: float,
562
+ softmax_scale: float,
563
+ zero_tensors: bool,
564
+ causal: bool,
565
+ window_size_left: int,
566
+ window_size_right: int,
567
+ softcap: float,
568
+ deterministic: bool,
569
+ gen_: Optional[torch.Tensor] = None,
570
+ rng_state: Optional[torch.Tensor] = None,
571
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
572
+ if str(q.dtype).startswith("torch.float8"):
573
+ raise NotImplementedError(
574
+ "FP8 tensors are not supported in the AMD Triton FA2 interface (varlen_bwd). Use the FA3 path instead."
575
+ )
576
+ if softcap != 0.0:
577
+ raise NotImplementedError(
578
+ "softcap is not supported in varlen_bwd (expected 0.0)."
579
+ )
580
+
581
+ if DEBUG:
582
+ print()
583
+ print("varlen_bwd")
584
+ print("dout:", dout.shape)
585
+ print("q:", q.shape)
586
+ print("k:", k.shape)
587
+ print("v:", v.shape)
588
+ print("out:", out)
589
+ print("softmax_lse:", softmax_lse.shape)
590
+ print("dq:", dq.shape if dq is not None else None)
591
+ print("dk:", dk.shape if dk is not None else None)
592
+ print("dv:", dv.shape if dv is not None else None)
593
+ print("cu_seqlens_q:", cu_seqlens_q, cu_seqlens_q.shape)
594
+ print("cu_seqlens_k:", cu_seqlens_k, cu_seqlens_k.shape)
595
+ print("alibi_slopes:", alibi_slopes)
596
+ print("max_seqlen_q:", max_seqlen_q)
597
+ print("max_seqlen_k:", max_seqlen_k)
598
+ print("dropout_p:", dropout_p)
599
+ print("softmax_scale:", softmax_scale)
600
+ print("causal:", causal)
601
+ print("window_size_left:", window_size_left)
602
+ print("window_size_right:", window_size_right)
603
+ print("deterministic:", deterministic)
604
+ print("gen_:", gen_)
605
+ print("rng_state:", rng_state)
606
+
607
+ dq = torch.zeros_like(q) if dq is None else dq.zero_()
608
+ dk = torch.zeros_like(k) if dk is None else dk.zero_()
609
+ dv = torch.zeros_like(v) if dv is None else dv.zero_()
610
+
611
+ # get shape
612
+ batch = len(cu_seqlens_q) - 1
613
+ total_q, nheads_q, _ = q.shape
614
+
615
+ # Create delta tensor with shape based on expectations
616
+ # delta (softmax_d) : (Hq, Total_Q) or (Hq, Total_Q + 128*batch)
617
+ if SHAPE_EXPECTATIONS == "rounded":
618
+ delta = torch.zeros(
619
+ (nheads_q, total_q + 128 * batch), device=q.device, dtype=torch.float32
620
+ )
621
+ else:
622
+ delta = torch.zeros((nheads_q, total_q), device=q.device, dtype=torch.float32)
623
+
624
+ # Upstream change: base seeding logic on provided rng_state instead of dropout probability.
625
+ if rng_state is not None:
626
+ philox_seed, philox_offset = rng_state[0].item(), rng_state[1].item()
627
+ else:
628
+ philox_seed, philox_offset = None, None
629
+
630
+ if alibi_slopes is not None:
631
+ if alibi_slopes.dim() == 2:
632
+ pass
633
+ elif alibi_slopes.dim() == 1:
634
+ alibi_slopes = alibi_slopes.unsqueeze(0).expand(batch, -1)
635
+ else:
636
+ raise ValueError("Alibi can be (nheads,) or (batch_size, nheads).")
637
+
638
+ # call implementation
639
+ if DEBUG:
640
+ print(f"Using Triton implementation in {BWD_MODE} mode")
641
+ attention_backward_triton_impl(
642
+ do=dout,
643
+ q=q,
644
+ k=k,
645
+ v=v,
646
+ o=out,
647
+ softmax_lse=softmax_lse,
648
+ dq=dq,
649
+ dk=dk,
650
+ dv=dv,
651
+ delta=delta,
652
+ sm_scale=softmax_scale,
653
+ alibi_slopes=alibi_slopes,
654
+ causal=causal,
655
+ layout="thd",
656
+ cu_seqlens_q=cu_seqlens_q,
657
+ cu_seqlens_k=cu_seqlens_k,
658
+ max_seqlen_q=max_seqlen_q,
659
+ max_seqlen_k=max_seqlen_k,
660
+ seqused_q=None,
661
+ seqused_k=None,
662
+ dropout_p=dropout_p,
663
+ philox_seed=philox_seed,
664
+ philox_offset=philox_offset,
665
+ use_exp2=USE_EXP2,
666
+ mode=BWD_MODE,
667
+ )
668
+
669
+ if DEBUG:
670
+ print("varlen_bwd outputs")
671
+ print("delta:", delta, delta.shape)
672
+ print("dv:", dv, dv.shape)
673
+ print("dk:", dk, dk.shape)
674
+ print("dq:", dq, dq.shape)
675
+ # --- Assertions ---
676
+ assert dq.shape == q.shape, f"[varlen_bwd] dq shape {dq.shape} != q shape {q.shape}"
677
+ assert dk.shape == k.shape, f"[varlen_bwd] dk shape {dk.shape} != k shape {k.shape}"
678
+ assert dv.shape == v.shape, f"[varlen_bwd] dv shape {dv.shape} != v shape {v.shape}"
679
+ if SHAPE_EXPECTATIONS == "rounded":
680
+ batch = len(cu_seqlens_q) - 1
681
+ expected_delta_shape = (q.shape[1], q.shape[0] + 128 * batch)
682
+ else:
683
+ expected_delta_shape = (q.shape[1], q.shape[0]) # (Hq, Total_Q)
684
+ assert (
685
+ delta.shape == expected_delta_shape
686
+ ), f"[varlen_bwd] delta shape {delta.shape} != {expected_delta_shape}"
687
+ return dq, dk, dv, delta
688
+
689
+
690
+ def fwd_kvcache(
691
+ q: torch.Tensor,
692
+ k_cache: torch.Tensor,
693
+ v_cache: torch.Tensor,
694
+ k: Optional[torch.Tensor],
695
+ v: Optional[torch.Tensor],
696
+ cache_seqlens: Optional[Union[int, torch.Tensor]],
697
+ rotary_cos: Optional[torch.Tensor],
698
+ rotary_sin: Optional[torch.Tensor],
699
+ cache_batch_idx: Optional[torch.Tensor],
700
+ cache_leftpad: Optional[torch.Tensor],
701
+ block_table: Optional[torch.Tensor],
702
+ alibi_slopes: Optional[torch.Tensor],
703
+ out: Optional[torch.Tensor],
704
+ softmax_scale: float,
705
+ causal: bool,
706
+ window_size_left: int,
707
+ window_size_right: int,
708
+ softcap: float,
709
+ rotary_interleaved: bool,
710
+ num_splits: int,
711
+ ) -> tuple[torch.Tensor, torch.Tensor]:
712
+
713
+ if softcap != 0.0:
714
+ raise NotImplementedError(
715
+ "softcap is not supported in fwd_kvcache (expected 0.0)."
716
+ )
717
+ if num_splits not in (0, 1):
718
+ raise NotImplementedError(
719
+ "num_splits > 1 not supported in AMD Triton FA2 fwd_kvcache."
720
+ )
721
+
722
+ if DEBUG:
723
+ print()
724
+ print("flash_attn_triton_amd.py::fwd_kvcache inputs")
725
+ print("q:", q, q.shape)
726
+ print("k_cache:", k_cache, k_cache.shape)
727
+ print("v_cache:", v_cache, v_cache.shape)
728
+ print("k:", k, k.shape if k is not None else None)
729
+ print("v:", v, v.shape if v is not None else None)
730
+ print("cache_seqlens:", cache_seqlens)
731
+ print("rotary_cos:", rotary_cos)
732
+ print("rotary_sin:", rotary_sin)
733
+ print("cache_batch_idx:", cache_batch_idx)
734
+ print("cache_leftpad:", cache_leftpad)
735
+ print("block_table:", block_table)
736
+ print("alibi_slopes:", alibi_slopes)
737
+ print("out:", out)
738
+ print("softmax_scale:", softmax_scale)
739
+ print("causal:", causal)
740
+ print("window_size_left:", window_size_left)
741
+ print("window_size_right:", window_size_right)
742
+ print("softcap:", softcap)
743
+ print("rotary_interleaved:", rotary_interleaved)
744
+ print("num_splits:", num_splits)
745
+
746
+ # output
747
+ out = torch.zeros_like(q) if out is None else out.zero_()
748
+
749
+ # Basic layout info for decode path
750
+ layout: Literal["bshd"] = "bshd"
751
+ q.shape[1]
752
+ k_cache.shape[1]
753
+ cache_seqlens_tensor = (
754
+ torch.tensor(cache_seqlens, device=q.device)
755
+ if isinstance(cache_seqlens, int)
756
+ else cache_seqlens
757
+ )
758
+ window_left = (
759
+ int(window_size_left.item())
760
+ if isinstance(window_size_left, torch.Tensor)
761
+ else window_size_left
762
+ )
763
+ window_right = (
764
+ int(window_size_right.item())
765
+ if isinstance(window_size_right, torch.Tensor)
766
+ else window_size_right
767
+ )
768
+
769
+ k_new = k
770
+ v_new = v
771
+
772
+ # get shape
773
+ batch, seqlen_q, nheads_q, _ = q.shape
774
+
775
+ # Create softmax_lse tensor - decode always uses exact shape (B, Hq, Sq)
776
+ softmax_lse = torch.zeros(
777
+ (batch, nheads_q, seqlen_q), device=q.device, dtype=torch.float32
778
+ )
779
+
780
+ if alibi_slopes is not None:
781
+ if alibi_slopes.dim() == 1:
782
+ alibi_slopes = alibi_slopes.unsqueeze(0).expand(batch, -1)
783
+ assert alibi_slopes.is_cuda and alibi_slopes.dim() == 2
784
+ assert alibi_slopes.shape == (batch, nheads_q)
785
+
786
+ # launch kernel
787
+ if DEBUG:
788
+ print("Using Triton implementation")
789
+ attention_forward_decode_triton_impl(
790
+ q,
791
+ k_cache,
792
+ v_cache,
793
+ k_new,
794
+ v_new,
795
+ out,
796
+ softmax_lse,
797
+ softmax_scale,
798
+ causal,
799
+ window_left,
800
+ window_right,
801
+ alibi_slopes,
802
+ layout,
803
+ cache_seqlens_tensor,
804
+ cache_batch_idx,
805
+ block_table,
806
+ None,
807
+ None,
808
+ None,
809
+ rotary_cos=rotary_cos,
810
+ rotary_sin=rotary_sin,
811
+ rotary_interleaved=rotary_interleaved,
812
+ )
813
+
814
+ if DEBUG:
815
+ print("out:", out, out.shape)
816
+ print("softmax_lse:", softmax_lse, softmax_lse.shape)
817
+ # --- Assertions ---
818
+ assert (
819
+ out.shape == q.shape
820
+ ), f"[fwd_kvcache] out shape {out.shape} != q shape {q.shape}"
821
+ expected_lse_shape = (q.shape[0], q.shape[2], q.shape[1])
822
+ assert (
823
+ softmax_lse.shape == expected_lse_shape
824
+ ), f"[fwd_kvcache] softmax_lse shape {softmax_lse.shape} != {expected_lse_shape}"
825
+ assert (
826
+ softmax_lse.dtype == torch.float32
827
+ ), f"[fwd_kvcache] softmax_lse dtype {softmax_lse.dtype} != torch.float32"
828
+ return out, softmax_lse
build/torch-rocm/_kernels/flash_attn_triton_amd/interface_v3.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from typing import Literal, Optional, Tuple
3
+ from .fwd_prefill import attention_forward_prefill_triton_impl
4
+ from .fwd_decode import attention_forward_decode_triton_impl
5
+ from .bwd import attention_backward_triton_impl
6
+ from .utils import (
7
+ DEBUG,
8
+ USE_EXP2,
9
+ BWD_MODE,
10
+ PHILOX_SEED,
11
+ PHILOX_OFFSET,
12
+ is_fp8,
13
+ )
14
+
15
+
16
+ def fwd(
17
+ q: torch.Tensor,
18
+ k: torch.Tensor,
19
+ v: torch.Tensor,
20
+ k_new: Optional[torch.Tensor],
21
+ v_new: Optional[torch.Tensor],
22
+ qv: Optional[torch.Tensor],
23
+ out: Optional[torch.Tensor],
24
+ cu_seqlens_q: Optional[torch.Tensor],
25
+ cu_seqlens_k: Optional[torch.Tensor],
26
+ cu_seqlens_k_new: Optional[torch.Tensor],
27
+ seqused_q: Optional[torch.Tensor],
28
+ seqused_k: Optional[torch.Tensor],
29
+ max_seqlen_q: Optional[int],
30
+ max_seqlen_k: Optional[int],
31
+ page_table: Optional[torch.Tensor],
32
+ kv_batch_idx: Optional[torch.Tensor],
33
+ leftpad_k: Optional[torch.Tensor],
34
+ rotary_cos: Optional[torch.Tensor],
35
+ rotary_sin: Optional[torch.Tensor],
36
+ seqlens_rotary: Optional[torch.Tensor],
37
+ q_descale: Optional[torch.Tensor],
38
+ k_descale: Optional[torch.Tensor],
39
+ v_descale: Optional[torch.Tensor],
40
+ softmax_scale: float,
41
+ causal: bool,
42
+ window_size_left: int,
43
+ window_size_right: int,
44
+ attention_chunk: int,
45
+ softcap: float,
46
+ rotary_interleaved: bool,
47
+ scheduler_metadata: Optional[torch.Tensor] = None,
48
+ num_splits: int = 1,
49
+ pack_gqa: Optional[bool] = None,
50
+ sm_margin: int = 0,
51
+ ) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor]]:
52
+ """
53
+ Flash Attention v3 forward pass compatible interface for AMD Triton implementation.
54
+
55
+ This function maps v3 parameters to the existing AMD Triton implementation.
56
+ """
57
+
58
+ if DEBUG:
59
+ print()
60
+ print("interface_fa_v3.py::fwd inputs")
61
+ print("q:", q.shape)
62
+ print("k:", k.shape)
63
+ print("v:", v.shape)
64
+ print("k_new:", k_new.shape if k_new is not None else None)
65
+ print("v_new:", v_new.shape if v_new is not None else None)
66
+ print("qv:", qv.shape if qv is not None else None)
67
+ print("out:", out.shape if out is not None else None)
68
+ print("cu_seqlens_q:", cu_seqlens_q.shape if cu_seqlens_q is not None else None)
69
+ print("cu_seqlens_k:", cu_seqlens_k.shape if cu_seqlens_k is not None else None)
70
+ print(
71
+ "cu_seqlens_k_new:",
72
+ cu_seqlens_k_new.shape if cu_seqlens_k_new is not None else None,
73
+ )
74
+ print("seqused_q:", seqused_q.shape if seqused_q is not None else None)
75
+ print("seqused_k:", seqused_k.shape if seqused_k is not None else None)
76
+ print("max_seqlen_q:", max_seqlen_q)
77
+ print("max_seqlen_k:", max_seqlen_k)
78
+ print("page_table:", page_table.shape if page_table is not None else None)
79
+ print("kv_batch_idx:", kv_batch_idx.shape if kv_batch_idx is not None else None)
80
+ print("leftpad_k:", leftpad_k.shape if leftpad_k is not None else None)
81
+ print("rotary_cos:", rotary_cos.shape if rotary_cos is not None else None)
82
+ print("rotary_sin:", rotary_sin.shape if rotary_sin is not None else None)
83
+ print(
84
+ "seqlens_rotary:",
85
+ seqlens_rotary.shape if seqlens_rotary is not None else None,
86
+ )
87
+ print("q_descale:", q_descale.shape if q_descale is not None else None)
88
+ print("k_descale:", k_descale.shape if k_descale is not None else None)
89
+ print("v_descale:", v_descale.shape if v_descale is not None else None)
90
+ print("softmax_scale:", softmax_scale)
91
+ print("causal:", causal)
92
+ print("window_size_left:", window_size_left)
93
+ print("window_size_right:", window_size_right)
94
+ print("attention_chunk:", attention_chunk)
95
+ print("softcap:", softcap)
96
+ print("rotary_interleaved:", rotary_interleaved)
97
+ print("scheduler_metadata:", scheduler_metadata)
98
+ print("num_splits:", num_splits)
99
+ print("pack_gqa:", pack_gqa)
100
+ print("sm_margin:", sm_margin)
101
+
102
+ # Handle qv packed input
103
+ if qv is not None:
104
+ raise NotImplementedError(
105
+ "QV packed input is not yet supported in the AMD Triton backend"
106
+ )
107
+
108
+ # Handle softcap
109
+ if softcap != 0.0:
110
+ raise NotImplementedError(
111
+ f"Softcap is not yet supported in the AMD Triton backend (got softcap={softcap}, expected 0.0)"
112
+ )
113
+
114
+ # Handle attention_chunk
115
+ if attention_chunk != 0 and attention_chunk != 1:
116
+ raise NotImplementedError(
117
+ f"attention_chunk is not yet supported in the AMD Triton backend (got attention_chunk={attention_chunk})"
118
+ )
119
+
120
+ # Handle scheduler metadata
121
+ if scheduler_metadata is not None:
122
+ raise NotImplementedError(
123
+ "Scheduler metadata is not yet supported in the AMD Triton backend"
124
+ )
125
+
126
+ # Handle pack_gqa
127
+ if pack_gqa is not None and pack_gqa is not False:
128
+ raise NotImplementedError(
129
+ f"pack_gqa is not yet supported in the AMD Triton backend (got pack_gqa={pack_gqa})"
130
+ )
131
+
132
+ # Handle num_splits
133
+ if num_splits != 1:
134
+ raise NotImplementedError(
135
+ f"Split attention (num_splits > 1) is not yet supported in the AMD Triton backend (got num_splits={num_splits})"
136
+ )
137
+
138
+ # Handle sm_margin
139
+ if sm_margin != 0:
140
+ raise NotImplementedError(
141
+ f"sm_margin is not yet supported in the AMD Triton backend (got sm_margin={sm_margin}, expected 0)"
142
+ )
143
+
144
+ # Handle leftpad_k
145
+ if leftpad_k is not None:
146
+ raise NotImplementedError(
147
+ "Left padding (leftpad_k) is not yet supported in the AMD Triton backend"
148
+ )
149
+
150
+ # Handle cu_seqlens_k_new
151
+ if cu_seqlens_k_new is not None:
152
+ raise NotImplementedError(
153
+ "cu_seqlens_k_new is not yet supported in the AMD Triton backend"
154
+ )
155
+
156
+ # establish layout / varlen & max seq lens
157
+ if cu_seqlens_q is not None:
158
+ if len(q.shape) != 3:
159
+ raise ValueError(
160
+ f"cu_seqlens_q provided but q has shape {q.shape}, expected 3D tensor for varlen"
161
+ )
162
+ layout: Literal["bshd", "thd"] = "thd"
163
+ cu_seqlens_q_local = cu_seqlens_q
164
+ assert max_seqlen_q is not None, "max_seqlen_q required for varlen mode"
165
+ max_seqlens_q_local = max_seqlen_q
166
+ if cu_seqlens_k is not None:
167
+ cu_seqlens_k_local = cu_seqlens_k
168
+ assert (
169
+ max_seqlen_k is not None
170
+ ), "max_seqlen_k required when cu_seqlens_k provided"
171
+ max_seqlens_k_local = max_seqlen_k
172
+ else:
173
+ cu_seqlens_k_local = None
174
+ if len(k.shape) == 4:
175
+ max_seqlens_k_local = k.shape[1]
176
+ else:
177
+ assert max_seqlen_k is not None, "max_seqlen_k required for varlen mode"
178
+ max_seqlens_k_local = max_seqlen_k
179
+ else:
180
+ layout = "bshd"
181
+ cu_seqlens_q_local = None
182
+ cu_seqlens_k_local = None
183
+ max_seqlens_q_local = q.shape[1] if max_seqlen_q is None else max_seqlen_q
184
+ max_seqlens_k_local = k.shape[1] if max_seqlen_k is None else max_seqlen_k
185
+
186
+ # Now determine if we should use decode or prefill kernel
187
+ # Decode kernel should be used for KV cache scenarios where:
188
+ # 1. k_new/v_new are provided - incremental KV cache update (primary KV cache indicator)
189
+ # 2. kv_batch_idx is provided - KV cache batch indexing (primary KV cache indicator)
190
+ # 3. seqused_k without seqused_q - indicates KV cache fill levels (not varlen masking)
191
+ # Note: In varlen, both seqused_q and seqused_k are used for sequence masking
192
+ # In KV cache, only seqused_k is used to track cache fill levels
193
+ # Detect KV cache scenarios:
194
+ # - Clear KV cache indicators (k_new, v_new, kv_batch_idx)
195
+ # - OR seqused_k without seqused_q (KV cache fill tracking, not varlen masking)
196
+ use_decode = (
197
+ k_new is not None # Have new KV to append (KV cache indicator)
198
+ or v_new is not None # Have new KV to append (KV cache indicator)
199
+ or kv_batch_idx is not None # Have KV cache batch indexing (KV cache indicator)
200
+ or (
201
+ seqused_k is not None and seqused_q is None
202
+ ) # KV cache fill levels (not varlen)
203
+ )
204
+
205
+ # Check for unsupported features with decode kernel
206
+ if use_decode:
207
+ if layout == "thd":
208
+ raise NotImplementedError(
209
+ "Varlen is not yet supported with the decode kernel in the AMD Triton backend"
210
+ )
211
+ if kv_batch_idx is not None:
212
+ raise NotImplementedError(
213
+ "kv_batch_idx is not yet supported with the decode kernel in the AMD Triton backend"
214
+ )
215
+
216
+ if out is None:
217
+ # NOTE: Using types that are lower precision than float32 such as bfloat16 for fp8 causes mismatches on a small set of tests.
218
+ out_dtype = torch.float32 if is_fp8([q, k, v]) else q.dtype
219
+ if layout == "bshd":
220
+ out = torch.zeros(
221
+ q.shape[0],
222
+ q.shape[1],
223
+ q.shape[2],
224
+ v.shape[-1],
225
+ dtype=out_dtype,
226
+ device=q.device,
227
+ )
228
+ elif layout == "thd":
229
+ out = torch.zeros(
230
+ q.shape[0], q.shape[1], v.shape[-1], dtype=out_dtype, device=q.device
231
+ )
232
+ else:
233
+ raise ValueError(
234
+ f"Unsupported layout: {layout}. Only 'bshd' and 'thd' layouts are supported."
235
+ )
236
+ else:
237
+ out = out.zero_()
238
+
239
+ # Handle causal mask
240
+ causal_flag = bool(causal)
241
+
242
+ # Handle alibi slopes
243
+ alibi_slopes = None
244
+
245
+ # Handle dropout
246
+ dropout_p = 0.0
247
+ return_softmax = False
248
+ philox_seed = PHILOX_SEED
249
+ philox_offset = PHILOX_OFFSET
250
+
251
+ # Call implementation
252
+ if DEBUG:
253
+ print("Using Triton implementation")
254
+
255
+ if use_decode:
256
+ if DEBUG:
257
+ print(
258
+ f"Using Decode Triton implementation (cache_seqlens={seqused_k is not None}, k_new={k_new is not None}, v_new={v_new is not None}, kv_batch_idx={kv_batch_idx is not None})"
259
+ )
260
+
261
+ # Create softmax_lse tensor for decode - always exact shape (B, Hq, Sq)
262
+ batch, seqlen_q, nheads_q, _ = q.shape
263
+ softmax_lse = torch.zeros(
264
+ (batch, nheads_q, seqlen_q), device=q.device, dtype=torch.float32
265
+ )
266
+
267
+ # Decode only supports bshd layout
268
+ assert layout == "bshd", f"decode requires bshd layout, got {layout}"
269
+ attention_forward_decode_triton_impl(
270
+ q,
271
+ k,
272
+ v,
273
+ k_new,
274
+ v_new,
275
+ out,
276
+ softmax_lse,
277
+ softmax_scale,
278
+ causal_flag,
279
+ window_size_left,
280
+ window_size_right,
281
+ alibi_slopes,
282
+ layout,
283
+ seqused_k,
284
+ kv_batch_idx,
285
+ page_table,
286
+ q_descale,
287
+ k_descale,
288
+ v_descale,
289
+ rotary_cos=rotary_cos,
290
+ rotary_sin=rotary_sin,
291
+ rotary_interleaved=rotary_interleaved,
292
+ seqlens_rotary=seqlens_rotary,
293
+ )
294
+ else:
295
+ if DEBUG:
296
+ print("Using Prefill Triton implementation")
297
+
298
+ # Create softmax_lse tensor - FA3 always uses exact shapes
299
+ if layout == "thd":
300
+ # varlen: (Hq, Total_Q)
301
+ total_q, nheads_q, _ = q.shape
302
+ softmax_lse = torch.zeros(
303
+ (nheads_q, total_q), device=q.device, dtype=torch.float32
304
+ )
305
+ else:
306
+ # bshd: (B, Hq, Sq)
307
+ batch, seqlen_q, nheads_q, _ = q.shape
308
+ softmax_lse = torch.zeros(
309
+ (batch, nheads_q, seqlen_q), device=q.device, dtype=torch.float32
310
+ )
311
+
312
+ # sd_mask is not returned in v3 interface
313
+ sd_mask = None
314
+
315
+ attention_forward_prefill_triton_impl(
316
+ q,
317
+ k,
318
+ v,
319
+ out,
320
+ softmax_lse,
321
+ sd_mask,
322
+ softmax_scale,
323
+ alibi_slopes,
324
+ causal_flag,
325
+ window_size_left,
326
+ window_size_right,
327
+ None,
328
+ layout,
329
+ cu_seqlens_q_local,
330
+ cu_seqlens_k_local,
331
+ max_seqlens_q_local,
332
+ max_seqlens_k_local,
333
+ dropout_p,
334
+ philox_seed,
335
+ philox_offset,
336
+ return_softmax,
337
+ USE_EXP2,
338
+ q_descale,
339
+ k_descale,
340
+ v_descale,
341
+ seqused_q,
342
+ seqused_k,
343
+ rotary_cos=rotary_cos,
344
+ rotary_sin=rotary_sin,
345
+ rotary_interleaved=rotary_interleaved,
346
+ seqlens_rotary=seqlens_rotary,
347
+ )
348
+
349
+ if DEBUG:
350
+ print("interface_fa_v3.py::fwd outputs")
351
+ print("out:", out.shape)
352
+ print("softmax_lse:", softmax_lse.shape)
353
+
354
+ # --- Assertions (FA3 always expects exact shapes) ---
355
+ # out: same shape as q except last dim is v's head_dim
356
+ if layout == "thd":
357
+ # varlen: (Total_Q, Hq, Dv)
358
+ assert (
359
+ out.shape[0] == q.shape[0]
360
+ ), f"[fwd_v3] out.shape[0] {out.shape[0]} != q.shape[0] {q.shape[0]}"
361
+ assert (
362
+ out.shape[1] == q.shape[1]
363
+ ), f"[fwd_v3] out.shape[1] {out.shape[1]} != q.shape[1] {q.shape[1]}"
364
+ assert (
365
+ out.shape[2] == v.shape[-1]
366
+ ), f"[fwd_v3] out.shape[2] {out.shape[2]} != v.shape[-1] {v.shape[-1]}"
367
+ else:
368
+ # bshd: (B, Sq, Hq, Dv)
369
+ assert (
370
+ out.shape[0] == q.shape[0]
371
+ ), f"[fwd_v3] out.shape[0] {out.shape[0]} != q.shape[0] {q.shape[0]}"
372
+ assert (
373
+ out.shape[1] == q.shape[1]
374
+ ), f"[fwd_v3] out.shape[1] {out.shape[1]} != q.shape[1] {q.shape[1]}"
375
+ assert (
376
+ out.shape[2] == q.shape[2]
377
+ ), f"[fwd_v3] out.shape[2] {out.shape[2]} != q.shape[2] {q.shape[2]}"
378
+ assert (
379
+ out.shape[3] == v.shape[-1]
380
+ ), f"[fwd_v3] out.shape[3] {out.shape[3]} != v.shape[-1] {v.shape[-1]}"
381
+
382
+ # softmax_lse dtype
383
+ assert (
384
+ softmax_lse.dtype == torch.float32
385
+ ), f"[fwd_v3] softmax_lse dtype {softmax_lse.dtype} != torch.float32"
386
+ # softmax_lse shape depends on layout
387
+ expected_lse_shape: tuple[int, ...]
388
+ if layout == "thd":
389
+ # varlen: (Hq, Total_Q)
390
+ expected_lse_shape = (q.shape[1], q.shape[0])
391
+ else:
392
+ # bshd: (B, Hq, Sq)
393
+ expected_lse_shape = (q.shape[0], q.shape[2], q.shape[1])
394
+ assert (
395
+ softmax_lse.shape == expected_lse_shape
396
+ ), f"[fwd_v3] softmax_lse shape {softmax_lse.shape} != {expected_lse_shape}"
397
+
398
+ # Return format compatible with v3
399
+ # V3 returns (out, softmax_lse, out_accum, softmax_lse_accum)
400
+ # out_accum and softmax_lse_accum are None for Triton AMD (no split-k accumulation)
401
+ return out, softmax_lse, None, None
402
+
403
+
404
+ def bwd(
405
+ dout: torch.Tensor,
406
+ q: torch.Tensor,
407
+ k: torch.Tensor,
408
+ v: torch.Tensor,
409
+ out: torch.Tensor,
410
+ softmax_lse: torch.Tensor,
411
+ dq: Optional[torch.Tensor],
412
+ dk: Optional[torch.Tensor],
413
+ dv: Optional[torch.Tensor],
414
+ cu_seqlens_q: Optional[torch.Tensor],
415
+ cu_seqlens_k: Optional[torch.Tensor],
416
+ seqused_q: Optional[torch.Tensor],
417
+ seqused_k: Optional[torch.Tensor],
418
+ max_seqlen_q: Optional[int],
419
+ max_seqlen_k: Optional[int],
420
+ softmax_scale: float,
421
+ causal: bool,
422
+ window_size_left: int,
423
+ window_size_right: int,
424
+ softcap: float,
425
+ deterministic: bool,
426
+ sm_margin: int = 0,
427
+ *,
428
+ q_descale: Optional[torch.Tensor] = None,
429
+ k_descale: Optional[torch.Tensor] = None,
430
+ v_descale: Optional[torch.Tensor] = None,
431
+ ) -> Tuple[torch.Tensor]:
432
+ """
433
+ Flash Attention v3 backward pass compatible interface for AMD Triton implementation.
434
+
435
+ This function maps v3 parameters to the existing AMD Triton implementation.
436
+ Gradients are written in-place to dq, dk, dv if provided (or allocated internally).
437
+ Returns (delta,) matching the upstream FA3 API.
438
+
439
+ Args:
440
+ q_descale: Optional descale tensor for FP8 query (batch, nheads_k)
441
+ k_descale: Optional descale tensor for FP8 key (batch, nheads_k)
442
+ v_descale: Optional descale tensor for FP8 value (batch, nheads_k)
443
+ """
444
+
445
+ if DEBUG:
446
+ print()
447
+ print("interface_fa_v3.py::bwd inputs")
448
+ print("dout:", dout.shape)
449
+ print("q:", q.shape)
450
+ print("k:", k.shape)
451
+ print("v:", v.shape)
452
+ print("out:", out.shape)
453
+ print("softmax_lse:", softmax_lse.shape)
454
+ print("dq:", dq.shape if dq is not None else None)
455
+ print("dk:", dk.shape if dk is not None else None)
456
+ print("dv:", dv.shape if dv is not None else None)
457
+ print("cu_seqlens_q:", cu_seqlens_q.shape if cu_seqlens_q is not None else None)
458
+ print("cu_seqlens_k:", cu_seqlens_k.shape if cu_seqlens_k is not None else None)
459
+ print("seqused_q:", seqused_q.shape if seqused_q is not None else None)
460
+ print("seqused_k:", seqused_k.shape if seqused_k is not None else None)
461
+ print("max_seqlen_q:", max_seqlen_q)
462
+ print("max_seqlen_k:", max_seqlen_k)
463
+ print("softmax_scale:", softmax_scale)
464
+ print("causal:", causal)
465
+ print("window_size_left:", window_size_left)
466
+ print("window_size_right:", window_size_right)
467
+ print("softcap:", softcap)
468
+ print("deterministic:", deterministic)
469
+ print("sm_margin:", sm_margin)
470
+
471
+ # Check for unsupported features in backward pass
472
+
473
+ # Handle sliding window - backward doesn't support it yet
474
+ is_sliding_window = (window_size_left >= 0) or (window_size_right >= 0)
475
+ if is_sliding_window:
476
+ raise NotImplementedError(
477
+ f"Sliding window attention is not yet supported in the AMD Triton backward pass "
478
+ f"(window_size_left={window_size_left}, window_size_right={window_size_right}). "
479
+ f"Use window_size=(-1, -1) for full attention."
480
+ )
481
+
482
+ # Handle softcap
483
+ if softcap != 0.0:
484
+ raise NotImplementedError(
485
+ f"Softcap is not yet supported in the AMD Triton backend backward pass (got softcap={softcap}, expected 0.0)"
486
+ )
487
+
488
+ # Handle sm_margin
489
+ if sm_margin != 0:
490
+ raise NotImplementedError(
491
+ f"sm_margin is not yet supported in the AMD Triton backend backward pass (got sm_margin={sm_margin}, expected 0)"
492
+ )
493
+
494
+ # Initialize gradient tensors if not provided
495
+ # NOTE: Using types that are lower precision than float32 such as bfloat16 for fp8 causes mismatches on a small set of tests.
496
+ grad_dtype = torch.float32 if is_fp8([q, k, v]) else q.dtype
497
+ dq = torch.zeros_like(q, dtype=grad_dtype) if dq is None else dq.zero_()
498
+ dk = torch.zeros_like(k, dtype=grad_dtype) if dk is None else dk.zero_()
499
+ dv = torch.zeros_like(v, dtype=grad_dtype) if dv is None else dv.zero_()
500
+
501
+ # Determine layout and set max sequence lengths
502
+ layout: Literal["bshd", "bhsd", "thd"]
503
+ if cu_seqlens_q is not None and cu_seqlens_k is not None:
504
+ # Variable length sequence mode
505
+ layout = "thd"
506
+ batch = len(cu_seqlens_q) - 1
507
+ total_q, nheads_q, _ = q.shape
508
+ # Create delta tensor - varlen: (Hq, Total_Q)
509
+ delta = torch.zeros((nheads_q, total_q), device=q.device, dtype=torch.float32)
510
+ else:
511
+ # Regular batch mode
512
+ layout = "bshd"
513
+ batch, seqlen_q, nheads_q, _ = q.shape
514
+ # Create delta tensor - bshd: (B, Hq, Sq)
515
+ delta = torch.zeros(
516
+ (batch, nheads_q, seqlen_q), device=q.device, dtype=torch.float32
517
+ )
518
+
519
+ # Set max sequence lengths (infer from tensor shapes if not provided)
520
+ max_seqlen_q = q.shape[1] if max_seqlen_q is None else max_seqlen_q
521
+ max_seqlen_k = k.shape[1] if max_seqlen_k is None else max_seqlen_k
522
+
523
+ # V3 backward doesn't have dropout or alibi slopes
524
+ dropout_p = 0.0
525
+ philox_seed, philox_offset = None, None
526
+ alibi_slopes = None
527
+
528
+ # Call implementation
529
+ if DEBUG:
530
+ print(f"Using Triton implementation in {BWD_MODE} mode")
531
+ attention_backward_triton_impl(
532
+ do=dout,
533
+ q=q,
534
+ k=k,
535
+ v=v,
536
+ o=out,
537
+ softmax_lse=softmax_lse,
538
+ dq=dq,
539
+ dk=dk,
540
+ dv=dv,
541
+ delta=delta,
542
+ sm_scale=softmax_scale,
543
+ alibi_slopes=alibi_slopes,
544
+ causal=causal,
545
+ layout=layout,
546
+ cu_seqlens_q=cu_seqlens_q,
547
+ cu_seqlens_k=cu_seqlens_k,
548
+ max_seqlen_q=max_seqlen_q,
549
+ max_seqlen_k=max_seqlen_k,
550
+ seqused_q=seqused_q,
551
+ seqused_k=seqused_k,
552
+ dropout_p=dropout_p,
553
+ philox_seed=philox_seed,
554
+ philox_offset=philox_offset,
555
+ use_exp2=USE_EXP2,
556
+ mode=BWD_MODE,
557
+ q_descale=q_descale,
558
+ k_descale=k_descale,
559
+ v_descale=v_descale,
560
+ )
561
+
562
+ if DEBUG:
563
+ print("interface_fa_v3.py::bwd outputs")
564
+ print("dq:", dq.shape)
565
+ print("dk:", dk.shape)
566
+ print("dv:", dv.shape)
567
+ print("delta:", delta.shape)
568
+
569
+ # --- Assertions (FA3 always expects exact shapes) ---
570
+ # Gradients should match input shapes
571
+ assert dq.shape == q.shape, f"[bwd_v3] dq shape {dq.shape} != q shape {q.shape}"
572
+ assert dk.shape == k.shape, f"[bwd_v3] dk shape {dk.shape} != k shape {k.shape}"
573
+ assert dv.shape == v.shape, f"[bwd_v3] dv shape {dv.shape} != v shape {v.shape}"
574
+ # delta (softmax_d) should match softmax_lse shape
575
+ assert (
576
+ delta.dtype == torch.float32
577
+ ), f"[bwd_v3] delta dtype {delta.dtype} != torch.float32"
578
+ expected_delta_shape: tuple[int, ...]
579
+ if layout == "thd":
580
+ # varlen: (Hq, Total_Q)
581
+ expected_delta_shape = (q.shape[1], q.shape[0])
582
+ else:
583
+ # bshd: (B, Hq, Sq)
584
+ expected_delta_shape = (q.shape[0], q.shape[2], q.shape[1])
585
+ assert (
586
+ delta.shape == expected_delta_shape
587
+ ), f"[bwd_v3] delta shape {delta.shape} != {expected_delta_shape}"
588
+
589
+ # Return only delta to match upstream FA3 API
590
+ # dq, dk, dv are mutated in-place by attention_backward_triton_impl
591
+ return (delta,)
592
+
593
+
594
+ def fwd_combine(
595
+ out_partial: torch.Tensor,
596
+ lse_partial: torch.Tensor,
597
+ out: Optional[torch.Tensor] = None,
598
+ out_dtype: Optional[torch.dtype] = None,
599
+ ) -> torch.Tensor:
600
+ """
601
+ Combine partial outputs from split attention computation.
602
+
603
+ This is used when num_splits > 1 to combine the partial results.
604
+
605
+ Args:
606
+ out_partial: Partial output tensor from split computation
607
+ lse_partial: Partial log-sum-exp tensor
608
+ out: Optional output tensor to write to
609
+ out_dtype: Optional dtype for output
610
+
611
+ Returns:
612
+ Combined output tensor
613
+ """
614
+ raise NotImplementedError(
615
+ "fwd_combine is not yet implemented in the AMD Triton backend"
616
+ )
617
+
618
+
619
+ def get_scheduler_metadata(
620
+ batch_size: int,
621
+ max_seqlen_q: int,
622
+ max_seqlen_k: int,
623
+ num_heads_q: int,
624
+ num_heads_kv: int,
625
+ headdim: int,
626
+ headdim_v: int,
627
+ qkv_dtype: torch.dtype,
628
+ cache_seqlens: torch.Tensor,
629
+ cu_seqlens_q: Optional[torch.Tensor] = None,
630
+ cu_seqlens_k: Optional[torch.Tensor] = None,
631
+ cu_seqlens_k_new: Optional[torch.Tensor] = None,
632
+ seqused_q: Optional[torch.Tensor] = None,
633
+ cache_leftpad: Optional[torch.Tensor] = None,
634
+ page_size: Optional[int] = None,
635
+ max_seqlen_k_new: int = 0,
636
+ causal: bool = False,
637
+ window_size_left: int = -1,
638
+ window_size_right: int = -1,
639
+ attention_chunk: int = 0,
640
+ has_softcap: bool = False,
641
+ num_splits: int = 0,
642
+ pack_gqa: Optional[bool] = None,
643
+ sm_margin: int = 0,
644
+ ) -> None:
645
+ """
646
+ Get scheduler metadata for optimized kernel selection.
647
+
648
+ This function is used to precompute metadata for kernel scheduling in FA3.
649
+ The AMD Triton backend currently doesn't use scheduler metadata, so this
650
+ raises an error.
651
+
652
+ Args:
653
+ Various attention parameters used for scheduling decisions
654
+
655
+ Returns:
656
+ None - scheduler metadata is not used in AMD Triton backend
657
+ """
658
+ raise NotImplementedError(
659
+ "get_scheduler_metadata is not supported in the AMD Triton backend yet."
660
+ )
build/torch-rocm/_kernels/flash_attn_triton_amd/utils.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utilities for Flash Attention Triton AMD backend.
3
+
4
+ This module contains essential runtime utilities:
5
+ - GPU architecture detection
6
+ - Global configuration flags
7
+ - Tensor shape/stride helpers
8
+ - FP8 type detection
9
+ """
10
+
11
+ import functools
12
+ import os
13
+ import json
14
+ import logging
15
+ from dataclasses import dataclass
16
+ from typing import Literal, Optional, Union
17
+ import triton.language as tl
18
+
19
+ import torch
20
+ import triton
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ AutotuneMode = Literal["off", "on", "sweep"]
25
+
26
+ __all__ = [
27
+ # Runtime info
28
+ "get_arch",
29
+ "is_hip",
30
+ # Global config
31
+ "AutotuneMode",
32
+ "AUTOTUNE",
33
+ "DEBUG",
34
+ "USE_TRITON_ROCM",
35
+ "BWD_MODE",
36
+ "USE_EXP2",
37
+ "PHILOX_SEED",
38
+ "PHILOX_OFFSET",
39
+ "SHAPE_EXPECTATIONS",
40
+ # FP8
41
+ "is_fp8",
42
+ # Shape/stride helpers
43
+ "get_shape_from_layout",
44
+ "get_stride_from_layout",
45
+ "get_padded_headsize",
46
+ # Misc helpers
47
+ "round_multiple",
48
+ ]
49
+
50
+
51
+ # -------------------------------
52
+ # GPU Architecture
53
+ # -------------------------------
54
+ ArchFamily = Literal["cdna", "rdna"]
55
+
56
+ CDNA_ARCHS = frozenset({"gfx908", "gfx90a", "gfx940", "gfx941", "gfx942", "gfx950"})
57
+ RDNA_ARCHS = frozenset(
58
+ {
59
+ "gfx1030",
60
+ "gfx1100",
61
+ "gfx1101",
62
+ "gfx1102",
63
+ "gfx1150",
64
+ "gfx1151",
65
+ "gfx1200",
66
+ "gfx1201",
67
+ }
68
+ )
69
+ FP8_ARCHS = frozenset({"gfx942", "gfx950", "gfx1200", "gfx1201"})
70
+
71
+ _RECOMMENDED_FP8_REPLACEMENTS: dict[str, dict[torch.dtype, torch.dtype]] = {
72
+ "gfx942": {
73
+ torch.float8_e4m3fn: torch.float8_e4m3fnuz,
74
+ torch.float8_e5m2: torch.float8_e5m2fnuz,
75
+ },
76
+ }
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class GpuArch:
81
+ """GPU architecture information."""
82
+
83
+ name: str # e.g., "gfx942", "gfx1100"
84
+ family: Optional[ArchFamily] = None
85
+
86
+ @property
87
+ def is_cdna(self) -> bool:
88
+ return self.family == "cdna"
89
+
90
+ @property
91
+ def is_rdna(self) -> bool:
92
+ return self.family == "rdna"
93
+
94
+ @property
95
+ def supports_fp8(self) -> bool:
96
+ """Check if this architecture supports FP8."""
97
+ return self.name in FP8_ARCHS
98
+
99
+ def recommended_fp8_dtype(self, dtype: torch.dtype) -> torch.dtype:
100
+ """Get the recommended FP8 dtype for this architecture.
101
+
102
+ Some architectures prefer different FP8 variants (e.g., fnuz vs fn).
103
+ Returns the input dtype unchanged if no replacement is recommended.
104
+ """
105
+ return _RECOMMENDED_FP8_REPLACEMENTS.get(self.name, {}).get(dtype, dtype)
106
+
107
+ @property
108
+ def cu_count(self) -> int:
109
+ """Get the number of compute units on the current GPU."""
110
+ return int(
111
+ torch.cuda.get_device_properties(
112
+ torch.cuda.current_device()
113
+ ).multi_processor_count
114
+ )
115
+
116
+
117
+ # -------------------------------
118
+ # Global Variables
119
+ # -------------------------------
120
+ USE_TRITON_ROCM = os.getenv("FLASH_ATTENTION_TRITON_AMD_ENABLE", "FALSE") == "TRUE"
121
+ AUTOTUNE: AutotuneMode = (
122
+ "on"
123
+ if os.environ.get("FLASH_ATTENTION_TRITON_AMD_AUTOTUNE", "1").lower()
124
+ in ("1", "true", "yes", "on")
125
+ else "off"
126
+ )
127
+
128
+ # User override config json for attn_fwd.
129
+ # Note: Ignored if FLASH_ATTENTION_TRITON_AMD_AUTOTUNE is enabled.
130
+ #
131
+ # e.g. FLASH_ATTENTION_FWD_TRITON_AMD_CONFIG_JSON='{"BLOCK_M":32,"BLOCK_N":32,"waves_per_eu":1,"PRE_LOAD_V":false,"num_stages":1,"num_warps":4}'
132
+ FWD_CONF_OVERRIDE = None
133
+ try:
134
+ conf_json = os.getenv("FLASH_ATTENTION_FWD_TRITON_AMD_CONFIG_JSON")
135
+ if conf_json:
136
+ conf = json.loads(conf_json)
137
+ FWD_CONF_OVERRIDE = triton.Config(
138
+ conf,
139
+ num_stages=conf.pop("num_stages", 1),
140
+ num_warps=conf.pop("num_warps", 4),
141
+ )
142
+ except Exception as e:
143
+ logger.warning(f"FLASH_ATTENTION_FWD_TRITON_AMD_CONFIG_JSON parse error: {e}")
144
+
145
+ # Unified debug level:
146
+ # 0 = off (default)
147
+ # 1 = basic debug info (shapes, tensor stats, kernel params)
148
+ # 2 = detailed debug (includes Triton interpreter prints in kernels)
149
+ #
150
+ # Set via: FLASH_ATTENTION_TRITON_AMD_DEBUG=0|1|2
151
+ DEBUG: int = int(os.environ.get("FLASH_ATTENTION_TRITON_AMD_DEBUG", "0"))
152
+ if AUTOTUNE != "off" or DEBUG > 0:
153
+ os.environ["TRITON_PRINT_AUTOTUNING"] = "1"
154
+ if DEBUG >= 2:
155
+ os.environ["TRITON_INTERPRET"] = "1"
156
+ BWD_MODE: Literal["fused", "fused_atomic", "split"] = "fused"
157
+ USE_EXP2 = True
158
+ PHILOX_SEED = 0x1BF58
159
+ PHILOX_OFFSET = 0x1D4B49
160
+ SHAPE_EXPECTATIONS: Literal["exact", "rounded"] = "exact"
161
+
162
+
163
+ # -------------------------------
164
+ # FP8
165
+ # -------------------------------
166
+ _FP8_DTYPES = frozenset(
167
+ {
168
+ torch.float8_e4m3fnuz,
169
+ torch.float8_e4m3fn,
170
+ torch.float8_e5m2,
171
+ torch.float8_e5m2fnuz,
172
+ }
173
+ )
174
+
175
+
176
+ def is_fp8(
177
+ x: Union[torch.dtype, torch.Tensor, list[torch.Tensor], tuple[torch.Tensor, ...]],
178
+ ) -> bool:
179
+ """Check if dtype/tensor(s) are FP8.
180
+
181
+ This is a pure function - it only checks dtypes, not architecture support.
182
+ Use `get_arch().supports_fp8` to check if the current GPU supports FP8.
183
+
184
+ Args:
185
+ x: A dtype, tensor, or list/tuple of tensors to check.
186
+
187
+ Returns:
188
+ True if FP8, False otherwise.
189
+
190
+ Rules for multiple tensors:
191
+ - If all tensors are FP8 -> return True.
192
+ - If none are FP8 -> return False.
193
+ - If a mix of FP8 and non-FP8 -> raise ValueError.
194
+
195
+ Empty list/tuple returns False.
196
+ """
197
+ # Handle dtype directly
198
+ if isinstance(x, torch.dtype):
199
+ return x in _FP8_DTYPES
200
+
201
+ # Handle single tensor
202
+ if isinstance(x, torch.Tensor):
203
+ return x.dtype in _FP8_DTYPES
204
+
205
+ # Handle list/tuple of tensors
206
+ if isinstance(x, (list, tuple)):
207
+ if len(x) == 0:
208
+ return False
209
+ flags = [t.dtype in _FP8_DTYPES for t in x]
210
+ if all(flags):
211
+ return True
212
+ if not any(flags):
213
+ return False
214
+ raise ValueError(
215
+ "Mixed FP8 and non-FP8 tensors provided; either all or none must be FP8."
216
+ )
217
+
218
+ raise TypeError(f"Expected dtype, Tensor, or sequence of Tensors, got {type(x)}")
219
+
220
+
221
+ # -------------------------------
222
+ # Shape/Stride Helpers
223
+ # -------------------------------
224
+ def get_shape_from_layout(
225
+ x: torch.Tensor,
226
+ layout: Literal["bshd", "bhsd", "thd"],
227
+ cu_seqlens: Optional[torch.Tensor] = None,
228
+ max_seqlen: Optional[int] = None,
229
+ ) -> tuple[int, int, int, int]:
230
+ """Extract (batch, max_seqlen, num_heads, head_dim) from tensor based on layout."""
231
+ if layout == "bhsd":
232
+ batch, num_heads, max_seqlen_final, head_dim = x.shape
233
+ elif layout == "bshd":
234
+ batch, max_seqlen_final, num_heads, head_dim = x.shape
235
+ elif layout == "thd":
236
+ total_seqlen, num_heads, head_dim = x.shape
237
+ if cu_seqlens is None:
238
+ raise ValueError("cu_seqlens must be provided for varlen (thd) layout")
239
+ if max_seqlen is None:
240
+ raise ValueError("max_seqlen must be provided for varlen (thd) layout")
241
+
242
+ batch, max_seqlen_final, num_heads, head_dim = (
243
+ len(cu_seqlens) - 1,
244
+ max_seqlen,
245
+ num_heads,
246
+ head_dim,
247
+ )
248
+ else:
249
+ raise ValueError(f"Got unsupported layout: {layout}")
250
+
251
+ return batch, max_seqlen_final, num_heads, head_dim
252
+
253
+
254
+ def get_stride_from_layout(
255
+ x: torch.Tensor, layout: Literal["bshd", "bhsd", "thd"]
256
+ ) -> tuple[int, int, int, int]:
257
+ """Get strides in (batch, head, seq, dim) order for the given layout."""
258
+ if layout == "thd":
259
+ strides = (0, x.stride(1), x.stride(0), x.stride(2))
260
+ elif layout == "bhsd":
261
+ strides = (x.stride(0), x.stride(1), x.stride(2), x.stride(3))
262
+ elif layout == "bshd":
263
+ strides = (x.stride(0), x.stride(2), x.stride(1), x.stride(3))
264
+ else:
265
+ raise ValueError(f"Got unsupported layout: {layout}")
266
+ return strides
267
+
268
+
269
+ def get_padded_headsize(size: int) -> int:
270
+ """Get closest power of 2 over or equal to 32."""
271
+ # Smallest head_dim supported is 16. If smaller, the tile in the
272
+ # kernel is padded - there is no padding in memory for any dims.
273
+ padded_d_model = 1 << (size - 1).bit_length()
274
+ padded_d_model = max(padded_d_model, 16)
275
+ return padded_d_model
276
+
277
+
278
+ # -------------------------------
279
+ # Misc helpers
280
+ # -------------------------------
281
+ def round_multiple(x: int, m: int) -> int:
282
+ """Round x up to the nearest multiple of m."""
283
+ return (x + m - 1) // m * m
284
+
285
+
286
+ # -------------------------------
287
+ # Runtime info
288
+ # -------------------------------
289
+ @functools.cache
290
+ def is_hip() -> bool:
291
+ """Check if running on HIP (AMD) backend."""
292
+ return bool(triton.runtime.driver.active.get_current_target().backend == "hip")
293
+
294
+
295
+ @functools.cache
296
+ def get_arch() -> GpuArch:
297
+ """Get the current GPU architecture."""
298
+ try:
299
+ name: str = triton.runtime.driver.active.get_current_target().arch
300
+ except RuntimeError:
301
+ # No GPU available (e.g. import-only on Windows/CPU)
302
+ return GpuArch(name="unknown")
303
+ if name in CDNA_ARCHS:
304
+ return GpuArch(name=name, family="cdna")
305
+ elif name in RDNA_ARCHS:
306
+ return GpuArch(name=name, family="rdna")
307
+ else:
308
+ return GpuArch(name=name)
309
+
310
+
311
+ @triton.jit
312
+ def remap_xcd(pid, GRID_MN, NUM_XCDS: tl.constexpr = 8):
313
+ ## pid remapping on xcds
314
+ # Number of pids per XCD in the new arrangement
315
+ pids_per_xcd = (GRID_MN + NUM_XCDS - 1) // NUM_XCDS
316
+ # When GRID_MN cannot divide NUM_XCDS, some xcds will have
317
+ # pids_per_xcd pids, the other will have pids_per_xcd - 1 pids.
318
+ # We calculate the number of xcds that have pids_per_xcd pids as
319
+ # tall_xcds
320
+ tall_xcds = GRID_MN % NUM_XCDS
321
+ tall_xcds = NUM_XCDS if tall_xcds == 0 else tall_xcds
322
+ # Compute current XCD and local pid within the XCD
323
+ xcd = pid % NUM_XCDS
324
+ local_pid = pid // NUM_XCDS
325
+ # Calculate new pid based on the new grouping
326
+ # Note that we need to consider the following two cases:
327
+ # 1. the current pid is on a tall xcd
328
+ # 2. the current pid is on a short xcd
329
+ if xcd < tall_xcds:
330
+ pid = xcd * pids_per_xcd + local_pid
331
+ else:
332
+ pid = (
333
+ tall_xcds * pids_per_xcd
334
+ + (xcd - tall_xcds) * (pids_per_xcd - 1)
335
+ + local_pid
336
+ )
337
+
338
+ return pid
build/torch-rocm/_kernels/mha.py ADDED
@@ -0,0 +1,942 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import functools
5
+ import json
6
+ import torch
7
+ import triton
8
+ import triton.language as tl
9
+
10
+ from ..utils._triton import arch_info
11
+ from ..utils.core import AITER_TRITON_CONFIGS_PATH
12
+ from ..utils._triton.pid_preprocessing import remap_xcd
13
+ from ..utils._triton.mha_kernel_utils import _compute_fp8_scaling_factors
14
+ from ..utils._triton.kernel_repr import make_kernel_repr
15
+
16
+
17
+ @triton.jit
18
+ def _cdiv_fn(x, y):
19
+ return (x + y - 1) // y
20
+
21
+
22
+ @triton.jit
23
+ def _load_fn(ptrs, offset_first, offset_second, boundary_first, boundary_second):
24
+ if offset_first is not None and offset_second is not None:
25
+ mask = (offset_first[:, None] < boundary_first) & (
26
+ offset_second[None, :] < boundary_second
27
+ )
28
+ tensor = tl.load(ptrs, mask=mask, other=0.0)
29
+ elif offset_first is not None:
30
+ mask = offset_first[:, None] < boundary_first
31
+ tensor = tl.load(ptrs, mask=mask, other=0.0)
32
+ elif offset_second is not None:
33
+ mask = offset_second[None, :] < boundary_second
34
+ tensor = tl.load(ptrs, mask=mask, other=0.0)
35
+ else:
36
+ tensor = tl.load(ptrs)
37
+ return tensor
38
+
39
+
40
+ @triton.jit
41
+ def _compute_alibi_block(
42
+ alibi_slope, seqlen_q, seqlen_k, offs_m, offs_n, transpose=False
43
+ ):
44
+ # when seqlen_k and seqlen_q are different we want the diagonal to stick to the bottom right of the attention matrix
45
+ # for casual mask we want something like this where (1 is kept and 0 is masked)
46
+ # seqlen_q = 2 and seqlen_k = 5
47
+ # 1 1 1 1 0
48
+ # 1 1 1 1 1
49
+ # seqlen_q = 5 and seqlen_k = 2
50
+ # 0 0
51
+ # 0 0
52
+ # 0 0
53
+ # 1 0
54
+ # 1 1
55
+ # for alibi the diagonal is 0 indicating no penalty for attending to that spot and increasing penalty for attending further from the diagonal
56
+ # e.g. alibi_slope = 1, seqlen_q = 2, seqlen_k = 5, offs_m = [0, 1, 2, 3], offs_n = [0, 1, 2, 3, 4], transpose = False
57
+ # 1. offs_m[:,None] = [[0],
58
+ # [1],
59
+ # 2. offs_m[:,None] + seqlen_k = [[5],
60
+ # [6],
61
+ # 3. offs_m[:,None] + seqlen_k - seqlen_q = [[3],
62
+ # [4],
63
+ # 4. offs_m[:,None] + seqlen_k - seqlen_q - offs_n[None,:] = [[3], - [[0, 1, 2, 3, 4]] = [[ 3, 2, 1, 0,-1],
64
+ # [4], [ 4, 3, 2, 1, 0]]
65
+ # 5. -1 * alibi_slope * tl.abs(relative_pos_block) = [[ -3, -2, -1, 0,-1],
66
+ # [ -4, -3, -2, -1, 0]],
67
+ relative_pos_block = offs_m[:, None] + seqlen_k - seqlen_q - offs_n[None, :]
68
+ alibi_block = -1 * alibi_slope * tl.abs(relative_pos_block)
69
+ if transpose:
70
+ return alibi_block.T
71
+ else:
72
+ return alibi_block
73
+
74
+
75
+ @triton.jit
76
+ def _attn_fwd_inner(
77
+ acc,
78
+ l_i,
79
+ m_i,
80
+ q,
81
+ q_pe,
82
+ k_ptrs,
83
+ k_pe_ptrs,
84
+ v_ptrs,
85
+ stride_kn,
86
+ stride_vk,
87
+ stride_sn,
88
+ start_m,
89
+ seqlen_k,
90
+ seqlen_q,
91
+ dropout_p,
92
+ sd_mask_ptrs,
93
+ dropout_mask_ptrs,
94
+ philox_seed,
95
+ philox_ptrs,
96
+ block_min,
97
+ block_max,
98
+ offs_n_causal,
99
+ masked_blocks,
100
+ n_extra_tokens,
101
+ alibi_slope,
102
+ descale_q,
103
+ descale_k,
104
+ descale_v,
105
+ OFFS_M: tl.constexpr,
106
+ OFFS_N: tl.constexpr,
107
+ PRELOAD_V: tl.constexpr,
108
+ BLOCK_M: tl.constexpr,
109
+ BLOCK_N: tl.constexpr,
110
+ BLOCK_DMODEL: tl.constexpr,
111
+ BLOCK_DMODEL_POW2: tl.constexpr,
112
+ BLOCK_DMODEL_PE: tl.constexpr, # it's zero or a power of 2
113
+ SM_SCALE: tl.constexpr,
114
+ IS_CAUSAL: tl.constexpr,
115
+ MASK_STEPS: tl.constexpr,
116
+ ENABLE_DROPOUT: tl.constexpr,
117
+ RETURN_SCORES: tl.constexpr,
118
+ PADDED_HEAD: tl.constexpr,
119
+ IS_FP8: tl.constexpr,
120
+ FP8_MAX: tl.constexpr,
121
+ ENABLE_PIPELINING: tl.constexpr,
122
+ SLIDING_WINDOW: tl.constexpr,
123
+ ):
124
+ RCP_LN2: tl.constexpr = 1.4426950408889634
125
+ HAS_PE: tl.constexpr = BLOCK_DMODEL_PE > 0
126
+
127
+ # loop over k, v, and update accumulator
128
+
129
+ num_stages: tl.constexpr = (
130
+ None if ENABLE_PIPELINING else 1
131
+ ) # Set num_stages==1 if we want to disable pipelining
132
+ for start_n in tl.range(block_min, block_max, BLOCK_N, num_stages=num_stages):
133
+ # For padded blocks, we will overrun the tensor size if
134
+ # we load all BLOCK_N. For others, the blocks are all within range.
135
+ if MASK_STEPS:
136
+ k_offs_n = start_n + tl.arange(0, BLOCK_N)
137
+ else:
138
+ k_offs_n = None
139
+ k_offs_k = None if not PADDED_HEAD else tl.arange(0, BLOCK_DMODEL_POW2)
140
+ k = _load_fn(k_ptrs, k_offs_k, k_offs_n, BLOCK_DMODEL, seqlen_k)
141
+ if HAS_PE:
142
+ k_pe = _load_fn(
143
+ k_pe_ptrs,
144
+ None,
145
+ k_offs_n,
146
+ (BLOCK_DMODEL + BLOCK_DMODEL_PE),
147
+ seqlen_k,
148
+ )
149
+ if PRELOAD_V:
150
+ v = _load_fn(v_ptrs, k_offs_n, k_offs_k, seqlen_k, BLOCK_DMODEL)
151
+
152
+ # We start from end of seqlen_k so only the first iteration would need
153
+ # to be checked for padding if it is not a multiple of block_n
154
+ # TODO: This can be optimized to only be true for the padded block.
155
+ mask = tl.full([BLOCK_M, BLOCK_N], True, dtype=tl.int1)
156
+ if MASK_STEPS:
157
+ # If this is the last block / iteration, we want to
158
+ # mask if the sequence length is not a multiple of block size
159
+ # a solution is to always do BLOCK_M // BLOCK_N + 1 steps if not is_modulo_mn.
160
+ # last step might get wasted but that is okay. check if this masking works For
161
+ # that case.
162
+
163
+ # remove the old if condition
164
+ # if (start_n + BLOCK_N == block_max) and (n_extra_tokens != 0):
165
+ # Though this will unconditionally compute mask_partial at runtime,
166
+ # the causal for loop does not have the if-else block any more, which
167
+ # helps instruction scheduling and register pressure.
168
+ bound_cond = (start_n + BLOCK_N == block_max) and (n_extra_tokens != 0)
169
+ size_n = start_n + OFFS_N[None, :]
170
+ mask_partial = size_n < seqlen_k
171
+ mask = tl.where(bound_cond, mask_partial, mask)
172
+
173
+ # compute masks
174
+ q_mask = OFFS_M[:, None] < seqlen_q
175
+ k_mask = (start_n + tl.arange(0, BLOCK_N))[None, :] < seqlen_k
176
+ p_mask = q_mask & k_mask
177
+ qk_scale = SM_SCALE * RCP_LN2
178
+ # -- compute qk ----
179
+ qk = tl.zeros([BLOCK_M, BLOCK_N], dtype=tl.float32)
180
+ if HAS_PE:
181
+ qk = tl.dot(q_pe, k_pe, acc=qk)
182
+ qk = tl.dot(q, k, acc=qk)
183
+ if IS_FP8:
184
+ qk = qk * (qk_scale * descale_q * descale_k)
185
+ else:
186
+ qk = qk * qk_scale
187
+ if IS_CAUSAL:
188
+ causal_boundary = start_n + offs_n_causal
189
+ causal_mask = OFFS_M[:, None] >= causal_boundary[None, :]
190
+ mask = mask & causal_mask
191
+
192
+ if SLIDING_WINDOW > 0:
193
+ k_pos = start_n + tl.arange(0, BLOCK_N)
194
+ q_adj = OFFS_M + seqlen_k - seqlen_q
195
+ window_mask = k_pos[None, :] >= (q_adj[:, None] - SLIDING_WINDOW)
196
+ mask = mask & window_mask
197
+
198
+ qk = tl.where(mask, qk, float("-inf"))
199
+
200
+ if alibi_slope is not None:
201
+ # Compute the global position of each token within the sequence
202
+ global_m_positions = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
203
+ global_n_positions = start_n + tl.arange(0, BLOCK_N)
204
+ alibi_block = _compute_alibi_block(
205
+ alibi_slope, seqlen_q, seqlen_k, global_m_positions, global_n_positions
206
+ )
207
+ qk += alibi_block * RCP_LN2
208
+ # get max scores so far
209
+ m_ij = tl.maximum(m_i, tl.max(qk, 1))
210
+
211
+ # Compute scaled QK and softmax probabilities
212
+ p = tl.math.exp2(qk - m_ij[:, None])
213
+
214
+ if SLIDING_WINDOW > 0:
215
+ # When all qk in a row are -inf (fully out-of-window block) and m_i was -inf,
216
+ # exp2(-inf - (-inf)) = NaN. Sanitize by zeroing masked elements.
217
+ p = tl.where(mask, p, 0.0)
218
+
219
+ # CAVEAT: Must update l_ij before applying dropout
220
+ l_ij = tl.sum(p, 1)
221
+ if ENABLE_DROPOUT:
222
+ rng_output = tl.rand(
223
+ philox_seed, philox_ptrs
224
+ ) # TODO: use tl.randint for better performance
225
+ dropout_mask = rng_output > dropout_p
226
+ tl.store(dropout_mask_ptrs, dropout_mask, mask=p_mask)
227
+
228
+ # return scores with negative values for dropped vals
229
+ sd_mask = tl.where(dropout_mask, p, -p)
230
+ tl.store(sd_mask_ptrs, sd_mask, mask=p_mask)
231
+
232
+ # apply dropout mask in place
233
+ p = tl.where(dropout_mask, p, 0.0)
234
+ elif RETURN_SCORES:
235
+ # NOTE: the returned score is not the same as the reference because we need to adjust as we find new maxes per block. We are not doing that
236
+ tl.store(sd_mask_ptrs, p, mask=p_mask)
237
+
238
+ # -- update output accumulator --
239
+ # alpha is an adjustment factor for acc and li as we loop and find new maxes
240
+ # store the diff in maxes to adjust acc and li as we discover new maxes
241
+ alpha = tl.math.exp2(m_i - m_ij)
242
+ if SLIDING_WINDOW > 0:
243
+ # When m_i == m_ij == -inf, exp2(-inf - (-inf)) = NaN. alpha should be 1.0
244
+ # (no rescaling needed since max didn't change).
245
+ alpha = tl.where(m_i == m_ij, 1.0, alpha)
246
+ acc = acc * alpha[:, None]
247
+ # -- update m_i and l_i
248
+ l_i = l_i * alpha + l_ij
249
+ # update m_i and l_i
250
+ m_i = m_ij
251
+ if not PRELOAD_V:
252
+ v = _load_fn(v_ptrs, k_offs_n, k_offs_k, seqlen_k, BLOCK_DMODEL)
253
+ if IS_FP8:
254
+ scale_p, descale_p = _compute_fp8_scaling_factors(p, FP8_MAX)
255
+ acc += (
256
+ tl.dot((p * scale_p).to(v.type.element_ty), v) * descale_p * descale_v
257
+ )
258
+ else:
259
+ acc = tl.dot(p.to(v.type.element_ty), v, acc=acc)
260
+
261
+ k_ptrs += BLOCK_N * stride_kn
262
+ if HAS_PE:
263
+ k_pe_ptrs += BLOCK_N * stride_kn
264
+ v_ptrs += BLOCK_N * stride_vk
265
+ if RETURN_SCORES:
266
+ sd_mask_ptrs += BLOCK_N * stride_sn
267
+
268
+ if ENABLE_DROPOUT:
269
+ dropout_mask_ptrs += BLOCK_N * stride_sn
270
+ philox_ptrs += BLOCK_N * stride_sn
271
+
272
+ return acc, l_i, m_i
273
+
274
+
275
+ _attn_fwd_repr = make_kernel_repr(
276
+ "_attn_fwd",
277
+ [
278
+ "IS_CAUSAL",
279
+ "NUM_Q_HEADS",
280
+ "NUM_K_HEADS",
281
+ "BLOCK_M",
282
+ "BLOCK_N",
283
+ "BLOCK_DMODEL",
284
+ "RETURN_SCORES",
285
+ "ENABLE_DROPOUT",
286
+ "IS_FP8",
287
+ "VARLEN",
288
+ "NUM_XCD",
289
+ "USE_INT64_STRIDES",
290
+ "ENABLE_SINK",
291
+ "SLIDING_WINDOW",
292
+ ],
293
+ )
294
+
295
+
296
+ @triton.jit(repr=_attn_fwd_repr)
297
+ def _attn_fwd(
298
+ q_ptr: torch.Tensor,
299
+ k_ptr: torch.Tensor,
300
+ v_ptr: torch.Tensor,
301
+ descale_q_ptr: torch.Tensor,
302
+ descale_k_ptr: torch.Tensor,
303
+ descale_v_ptr: torch.Tensor,
304
+ out_ptr: torch.Tensor,
305
+ alibi_slopes_ptr: torch.Tensor,
306
+ s_dmask_ptr: torch.Tensor,
307
+ dropout_mask_ptr: torch.Tensor,
308
+ softmax_lse_ptr: torch.Tensor,
309
+ sink_ptr: torch.Tensor,
310
+ stride_qz_in,
311
+ stride_qh_in,
312
+ stride_qm_in,
313
+ stride_qk_in,
314
+ stride_kz_in,
315
+ stride_kh_in,
316
+ stride_kn_in,
317
+ stride_kk_in,
318
+ stride_vz_in,
319
+ stride_vh_in,
320
+ stride_vn_in,
321
+ stride_vk_in,
322
+ stride_descale_q_z_in,
323
+ stride_descale_k_z_in,
324
+ stride_descale_v_z_in,
325
+ stride_oz_in,
326
+ stride_oh_in,
327
+ stride_om_in,
328
+ stride_on_in,
329
+ stride_alibi_z_in,
330
+ stride_alibi_h_in,
331
+ stride_sd_z_in,
332
+ stride_sd_h_in,
333
+ stride_sd_m_in,
334
+ stride_sd_n_in,
335
+ stride_lse_z_in,
336
+ stride_lse_h_in,
337
+ stride_lse_m_in,
338
+ sm_scale,
339
+ cu_seqlens_q,
340
+ cu_seqlens_k,
341
+ dropout_p,
342
+ philox_seed,
343
+ philox_offset_base_in,
344
+ SEQLEN_Q,
345
+ SEQLEN_K,
346
+ IS_CAUSAL: tl.constexpr,
347
+ NUM_Q_HEADS: tl.constexpr,
348
+ NUM_K_HEADS: tl.constexpr,
349
+ PRELOAD_V: tl.constexpr,
350
+ BLOCK_M: tl.constexpr,
351
+ BLOCK_N: tl.constexpr,
352
+ BLOCK_DMODEL: tl.constexpr,
353
+ BLOCK_DMODEL_POW2: tl.constexpr,
354
+ BLOCK_DMODEL_PE: tl.constexpr, # it's zero or a power of 2
355
+ RETURN_SCORES: tl.constexpr,
356
+ ENABLE_DROPOUT: tl.constexpr,
357
+ IS_FP8: tl.constexpr,
358
+ FP8_MAX: tl.constexpr,
359
+ VARLEN: tl.constexpr,
360
+ BATCH,
361
+ NUM_XCD: tl.constexpr,
362
+ USE_INT64_STRIDES: tl.constexpr,
363
+ ENABLE_SINK: tl.constexpr,
364
+ SLIDING_WINDOW: tl.constexpr,
365
+ ):
366
+ NUM_BLOCKS = (SEQLEN_Q + BLOCK_M - 1) // BLOCK_M
367
+ # calculate offsets
368
+ wid = tl.program_id(
369
+ 0
370
+ ) # workgroup id ranging: 0,1,2,...., (BATCH * NUM_Q_HEADS * NUM_BLOCKS - 1)
371
+ # num blocks along seqlen
372
+
373
+ off_q_head = wid % NUM_Q_HEADS
374
+ off_q_head = remap_xcd(off_q_head, NUM_Q_HEADS, NUM_XCD)
375
+ start_m = (wid // NUM_Q_HEADS) % NUM_BLOCKS
376
+ off_z = (wid // (NUM_BLOCKS * NUM_Q_HEADS)) % BATCH
377
+
378
+ # offsets
379
+ offs_m = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
380
+ offs_n = tl.arange(0, BLOCK_N)
381
+ offs_d = tl.arange(0, BLOCK_DMODEL_POW2)
382
+ HAS_PE: tl.constexpr = BLOCK_DMODEL_PE > 0
383
+ if HAS_PE:
384
+ offs_pe = BLOCK_DMODEL + tl.arange(0, BLOCK_DMODEL_PE)
385
+
386
+ # NOTE:
387
+ # Workaround for int64 strides, In the absence of strides being int64, parts of the offset
388
+ # computation is done in 32 bit and overflows resulting in segfaults
389
+ # If input strides are defined as int64, it disables vectorized loads which drops perf
390
+ # If we define new strides as stride_x = stride_x_in.to(tl.int64), that does not work
391
+ # because strides are tl.constexpr and cannot be upcasted
392
+ # If we define new strides as stride_x: tl.int64 = stride_x_in, segfault remains
393
+ # The permanent solution is to enable upcasting of tl.constexpr
394
+ # In the meantime, the following workaround provides correctness and does not drop perf
395
+ if USE_INT64_STRIDES:
396
+ stride_qz = tl.cast(stride_qz_in, tl.int64)
397
+ stride_qh = tl.cast(stride_qh_in, tl.int64)
398
+ stride_qm = tl.cast(stride_qm_in, tl.int64)
399
+ stride_qk = tl.cast(stride_qk_in, tl.int64)
400
+ stride_kz = tl.cast(stride_kz_in, tl.int64)
401
+ stride_kh = tl.cast(stride_kh_in, tl.int64)
402
+ stride_kn = tl.cast(stride_kn_in, tl.int64)
403
+ stride_kk = tl.cast(stride_kk_in, tl.int64)
404
+ stride_vz = tl.cast(stride_vz_in, tl.int64)
405
+ stride_vh = tl.cast(stride_vh_in, tl.int64)
406
+ stride_vn = tl.cast(stride_vn_in, tl.int64)
407
+ stride_vk = tl.cast(stride_vk_in, tl.int64)
408
+ if IS_FP8:
409
+ stride_descale_q_z = tl.cast(stride_descale_q_z_in, tl.int64)
410
+ stride_descale_k_z = tl.cast(stride_descale_k_z_in, tl.int64)
411
+ stride_descale_v_z = tl.cast(stride_descale_v_z_in, tl.int64)
412
+ stride_oz = tl.cast(stride_oz_in, tl.int64)
413
+ stride_oh = tl.cast(stride_oh_in, tl.int64)
414
+ stride_om = tl.cast(stride_om_in, tl.int64)
415
+ stride_on = tl.cast(stride_on_in, tl.int64)
416
+ stride_alibi_z = tl.cast(stride_alibi_z_in, tl.int64)
417
+ stride_alibi_h = tl.cast(stride_alibi_h_in, tl.int64)
418
+
419
+ # NOTE: philox offset is need in dropout pointer calculations
420
+ philox_offset_base = tl.cast(philox_offset_base_in, tl.int64)
421
+ stride_sd_z = tl.cast(stride_sd_z_in, tl.int64)
422
+ stride_sd_h = tl.cast(stride_sd_h_in, tl.int64)
423
+ stride_sd_m = tl.cast(stride_sd_m_in, tl.int64)
424
+ stride_sd_n = tl.cast(stride_sd_n_in, tl.int64)
425
+ stride_lse_z = tl.cast(stride_lse_z_in, tl.int64)
426
+ stride_lse_h = tl.cast(stride_lse_h_in, tl.int64)
427
+ stride_lse_m = tl.cast(stride_lse_m_in, tl.int64)
428
+ else:
429
+ stride_qz = stride_qz_in
430
+ stride_qm = stride_qm_in
431
+ stride_qk = stride_qk_in
432
+ stride_qh = stride_qh_in
433
+ stride_kz = stride_kz_in
434
+ stride_kh = stride_kh_in
435
+ stride_kn = stride_kn_in
436
+ stride_kk = stride_kk_in
437
+ stride_vz = stride_vz_in
438
+ stride_vh = stride_vh_in
439
+ stride_vn = stride_vn_in
440
+ stride_vk = stride_vk_in
441
+ stride_descale_q_z = stride_descale_q_z_in
442
+ stride_descale_k_z = stride_descale_k_z_in
443
+ stride_descale_v_z = stride_descale_v_z_in
444
+ stride_oz = stride_oz_in
445
+ stride_oh = stride_oh_in
446
+ stride_om = stride_om_in
447
+ stride_on = stride_on_in
448
+ stride_alibi_z = stride_alibi_z_in
449
+ stride_alibi_h = stride_alibi_h_in
450
+ philox_offset_base = philox_offset_base_in
451
+ stride_sd_z = stride_sd_z_in
452
+ stride_sd_h = stride_sd_h_in
453
+ stride_sd_m = stride_sd_m_in
454
+ stride_sd_n = stride_sd_n_in
455
+ stride_lse_z = stride_lse_z_in
456
+ stride_lse_h = stride_lse_h_in
457
+ stride_lse_m = stride_lse_m_in
458
+
459
+ tl.assume(stride_qz_in >= 0)
460
+ tl.assume(stride_qh_in >= 0)
461
+ tl.assume(stride_qm_in >= 0)
462
+ tl.assume(stride_qk_in >= 0)
463
+ tl.assume(stride_kz_in >= 0)
464
+ tl.assume(stride_kh_in >= 0)
465
+ tl.assume(stride_kn_in >= 0)
466
+ tl.assume(stride_kk_in >= 0)
467
+ tl.assume(stride_vz_in >= 0)
468
+ tl.assume(stride_vh_in >= 0)
469
+ tl.assume(stride_vn_in >= 0)
470
+ tl.assume(stride_vk_in >= 0)
471
+ if IS_FP8:
472
+ tl.assume(stride_descale_q_z_in >= 0)
473
+ tl.assume(stride_descale_k_z_in >= 0)
474
+ tl.assume(stride_descale_v_z_in >= 0)
475
+ tl.assume(stride_oz_in >= 0)
476
+ tl.assume(stride_oh_in >= 0)
477
+ tl.assume(stride_om_in >= 0)
478
+ tl.assume(stride_on_in >= 0)
479
+ tl.assume(stride_alibi_z_in >= 0)
480
+ tl.assume(stride_alibi_h_in >= 0)
481
+ # NOTE: philox offset is need in dropout pointer calculations
482
+ tl.assume(philox_offset_base_in >= 0)
483
+ tl.assume(stride_sd_z_in >= 0)
484
+ tl.assume(stride_sd_h_in >= 0)
485
+ tl.assume(stride_sd_m_in >= 0)
486
+ tl.assume(stride_sd_n_in >= 0)
487
+ tl.assume(stride_lse_z_in >= 0)
488
+ tl.assume(stride_lse_h_in >= 0)
489
+ tl.assume(stride_lse_m_in >= 0)
490
+
491
+ if VARLEN:
492
+ cu_seqlens_q_start = tl.load(cu_seqlens_q + off_z)
493
+ cu_seqlens_q_end = tl.load(cu_seqlens_q + off_z + 1)
494
+
495
+ seqlen_q = cu_seqlens_q_end - cu_seqlens_q_start
496
+ # We have a one-size-fits-all grid in id(0). Some seqlens might be too
497
+ # small for all start_m so for those we return early.
498
+ if start_m * BLOCK_M > seqlen_q:
499
+ return
500
+ cu_seqlens_k_start = tl.load(cu_seqlens_k + off_z)
501
+ cu_seqlens_k_end = tl.load(cu_seqlens_k + off_z + 1)
502
+ seqlen_k = cu_seqlens_k_end - cu_seqlens_k_start
503
+ else:
504
+ cu_seqlens_q_start = 0
505
+ cu_seqlens_k_start = 0
506
+ seqlen_q = SEQLEN_Q
507
+ seqlen_k = SEQLEN_K
508
+
509
+ n_blocks = _cdiv_fn(seqlen_k, BLOCK_N)
510
+
511
+ # Now we compute whether we need to exit early due to causal masking.
512
+ # This is because for seqlen_q > seqlen_k, M rows of the attn scores
513
+ # are completely masked, resulting in 0s written to the output, and
514
+ # inf written to LSE. We don't need to do any GEMMs in this case.
515
+ # This block of code determines what N is, and if this WG is operating
516
+ # on those M rows.
517
+ if IS_CAUSAL:
518
+ # If seqlen_q == seqlen_k, the attn scores are a square matrix.
519
+ # If seqlen_q != seqlen_k, attn scores are rectangular which means
520
+ # the causal mask boundary is bottom right aligned, and ends at either
521
+ # the top edge (seqlen_q < seqlen_k) or left edge.
522
+
523
+ # This captures the decrease in n_blocks if we have a rectangular attn matrix
524
+ n_blocks_seqlen = _cdiv_fn(
525
+ (start_m + 1) * BLOCK_M + seqlen_k - seqlen_q, BLOCK_N
526
+ )
527
+
528
+ # This is what adjusts the block_max for the current WG, only
529
+ # if IS_CAUSAL. Otherwise we want to always iterate through all n_blocks
530
+ n_blocks = min(n_blocks, n_blocks_seqlen)
531
+
532
+ # If we have no blocks after adjusting for seqlen deltas, this WG is part of
533
+ # the blocks that are all 0. We exit early.
534
+ if n_blocks <= 0:
535
+ offs_out = (
536
+ off_z * stride_oz
537
+ + off_q_head * stride_oh
538
+ + cu_seqlens_q_start * stride_om
539
+ + offs_m[:, None] * stride_om
540
+ + offs_d[None, :] * stride_on
541
+ )
542
+ acc = tl.zeros([BLOCK_M, BLOCK_DMODEL_POW2], dtype=out_ptr.type.element_ty)
543
+ out_mask = (offs_m[:, None] < seqlen_q) & (offs_d[None, :] < BLOCK_DMODEL)
544
+ tl.store(out_ptr + offs_out, acc, mask=out_mask)
545
+
546
+ if softmax_lse_ptr is not None:
547
+ offs_lse = (
548
+ off_z * stride_lse_z
549
+ + off_q_head * stride_lse_h
550
+ + cu_seqlens_q_start * stride_lse_m
551
+ + offs_m * stride_lse_m
552
+ )
553
+ lse_mask = offs_m < SEQLEN_Q
554
+ lse = tl.full([BLOCK_M], value=0.0, dtype=tl.float32)
555
+ tl.store(softmax_lse_ptr + offs_lse, lse, mask=lse_mask)
556
+ # TODO: Should dropout and return encoded softmax be handled here too?
557
+
558
+ return
559
+
560
+ grp_sz: tl.constexpr = NUM_Q_HEADS // NUM_K_HEADS
561
+ if grp_sz != 1: # Grouped Query Attention
562
+ off_k_head = off_q_head // grp_sz
563
+ else:
564
+ off_k_head = off_q_head
565
+
566
+ # q,k,v offsets
567
+ q_offs = (
568
+ off_z * stride_qz
569
+ + off_q_head * stride_qh
570
+ + cu_seqlens_q_start * stride_qm
571
+ + offs_m[:, None] * stride_qm
572
+ + offs_d[None, :] * stride_qk
573
+ )
574
+ q_ptrs = q_ptr + q_offs
575
+ if HAS_PE:
576
+ q_pe_offs = (
577
+ off_z * stride_qz
578
+ + off_q_head * stride_qh
579
+ + cu_seqlens_q_start * stride_qm
580
+ + offs_m[:, None] * stride_qm
581
+ + offs_pe[None, :] * stride_qk
582
+ )
583
+ q_pe_ptrs = q_ptr + q_pe_offs
584
+ else:
585
+ q_pe_ptrs = None
586
+
587
+ k_offs = (
588
+ off_z * stride_kz
589
+ + off_k_head * stride_kh
590
+ + cu_seqlens_k_start * stride_kn
591
+ + offs_d[:, None] * stride_kk
592
+ + offs_n[None, :] * stride_kn
593
+ )
594
+ k_ptrs = k_ptr + k_offs
595
+ if HAS_PE:
596
+ k_pe_offs = (
597
+ off_z * stride_kz
598
+ + off_k_head * stride_kh
599
+ + cu_seqlens_k_start * stride_kn
600
+ + offs_pe[:, None] * stride_kk
601
+ + offs_n[None, :] * stride_kn
602
+ )
603
+ k_pe_ptrs = k_ptr + k_pe_offs
604
+ else:
605
+ k_pe_ptrs = None
606
+
607
+ v_offs = (
608
+ off_z * stride_vz
609
+ + off_k_head * stride_vh
610
+ + cu_seqlens_k_start * stride_vn
611
+ + offs_n[:, None] * stride_vn
612
+ + offs_d[None, :] * stride_vk
613
+ )
614
+ v_ptrs = v_ptr + v_offs
615
+
616
+ # alibi slopes
617
+ if alibi_slopes_ptr is not None:
618
+ alibi_offs = off_z * stride_alibi_z + off_q_head * stride_alibi_h
619
+ alibi_slope = tl.load(alibi_slopes_ptr + alibi_offs)
620
+ else:
621
+ alibi_slope = None
622
+
623
+ # s_dmask (return_scores)
624
+ if s_dmask_ptr is not None:
625
+ s_dmask_offs = (
626
+ off_z * stride_sd_z
627
+ + off_q_head * stride_sd_h
628
+ + offs_m[:, None] * stride_sd_m
629
+ + offs_n[None, :] * stride_sd_n
630
+ )
631
+ s_dmask_ptrs = s_dmask_ptr + s_dmask_offs
632
+ else:
633
+ s_dmask_ptrs = None
634
+
635
+ # dropout
636
+ if dropout_mask_ptr is not None:
637
+ dropout_mask_offs = (
638
+ off_z * stride_sd_z
639
+ + off_q_head * stride_sd_h
640
+ + offs_m[:, None] * stride_sd_m
641
+ + offs_n[None, :] * stride_sd_n
642
+ )
643
+ dropout_mask_ptrs = dropout_mask_ptr + dropout_mask_offs
644
+ philox_ptrs = (
645
+ philox_offset_base
646
+ + off_z * stride_sd_z
647
+ + off_q_head * stride_sd_h
648
+ + offs_m[:, None] * stride_sd_m
649
+ + offs_n[None, :] * stride_sd_n
650
+ )
651
+ else:
652
+ dropout_mask_ptrs = None
653
+ philox_ptrs = None
654
+
655
+ if ENABLE_SINK:
656
+ RCP_LN2: tl.constexpr = 1.4426950408889634
657
+ m_i_value = tl.load(sink_ptr + off_q_head).to(tl.float32) * RCP_LN2
658
+ else:
659
+ m_i_value = float("-inf")
660
+
661
+ m_i = tl.full([BLOCK_M], m_i_value, dtype=tl.float32)
662
+ l_i = tl.full([BLOCK_M], 1.0, dtype=tl.float32)
663
+ acc = tl.zeros([BLOCK_M, BLOCK_DMODEL_POW2], dtype=tl.float32)
664
+ if BLOCK_DMODEL == BLOCK_DMODEL_POW2:
665
+ q_mask = offs_m[:, None] < seqlen_q
666
+ else:
667
+ q_mask = (offs_m[:, None] < seqlen_q) & (offs_d[None, :] < BLOCK_DMODEL)
668
+
669
+ if BLOCK_M >= NUM_Q_HEADS:
670
+ q_cache_mod: tl.constexpr = ".cg"
671
+ else:
672
+ q_cache_mod: tl.constexpr = ""
673
+
674
+ if HAS_PE:
675
+ q_pe = tl.load(q_pe_ptrs, mask=q_mask, other=0.0, cache_modifier=q_cache_mod)
676
+ else:
677
+ q_pe = None
678
+ q = tl.load(q_ptrs, mask=q_mask, other=0.0, cache_modifier=q_cache_mod)
679
+ if IS_FP8:
680
+ descale_q = tl.load(descale_q_ptr + off_z * stride_descale_q_z + off_q_head)
681
+ descale_k = tl.load(descale_k_ptr + off_z * stride_descale_k_z + off_k_head)
682
+ descale_v = tl.load(descale_v_ptr + off_z * stride_descale_v_z + off_k_head)
683
+ else:
684
+ descale_q, descale_k, descale_v = 1.0, 1.0, 1.0
685
+
686
+ n_extra_tokens = 0
687
+ if seqlen_k < BLOCK_N:
688
+ n_extra_tokens = BLOCK_N - seqlen_k
689
+ elif seqlen_k % BLOCK_N:
690
+ n_extra_tokens = seqlen_k % BLOCK_N
691
+
692
+ # if CAUSAL, then determine masked_blocks and full blocks
693
+ # Here we compute how many full and masked blocks we have.
694
+ padded_block_k = n_extra_tokens != 0
695
+ is_modulo_mn = not padded_block_k and (seqlen_q % BLOCK_M == 0)
696
+ skipped_blocks = 0
697
+ if SLIDING_WINDOW > 0:
698
+ # Skip K blocks that are fully left of the earliest key position
699
+ # reachable by this Q block. The first retained block can still be
700
+ # partially outside the window, so we keep the per-element mask below.
701
+ window_start_n = start_m * BLOCK_M + seqlen_k - seqlen_q - SLIDING_WINDOW
702
+ skipped_blocks = tl.maximum(window_start_n, 0) // BLOCK_N
703
+ skipped_blocks = tl.minimum(skipped_blocks, n_blocks)
704
+ if IS_CAUSAL:
705
+ # There are always at least BLOCK_M // BLOCK_N masked blocks.
706
+ # Additionally there might be one more due to dissimilar seqlens.
707
+ masked_blocks = BLOCK_M // BLOCK_N + (not is_modulo_mn)
708
+ else:
709
+ # Padding on Q does not need to be masked in the FA loop.
710
+ masked_blocks = padded_block_k
711
+ # if IS_CAUSAL, not is_modulo_mn does not always result in an additional block.
712
+ # In this case we might exceed n_blocks so pick the min.
713
+ visible_blocks = n_blocks - skipped_blocks
714
+ masked_blocks = min(masked_blocks, visible_blocks)
715
+ n_full_blocks = visible_blocks - masked_blocks
716
+ block_min = skipped_blocks * BLOCK_N
717
+ block_max = n_blocks * BLOCK_N
718
+ if skipped_blocks > 0:
719
+ k_ptrs += skipped_blocks * BLOCK_N * stride_kn
720
+ if HAS_PE:
721
+ k_pe_ptrs += skipped_blocks * BLOCK_N * stride_kn
722
+ v_ptrs += skipped_blocks * BLOCK_N * stride_vn
723
+ if RETURN_SCORES:
724
+ s_dmask_ptrs += skipped_blocks * BLOCK_N * stride_sd_n
725
+ if ENABLE_DROPOUT:
726
+ dropout_mask_ptrs += skipped_blocks * BLOCK_N * stride_sd_n
727
+ philox_ptrs += skipped_blocks * BLOCK_N * stride_sd_n
728
+ # Compute for full blocks. Here we set causal to false regardless of its actual
729
+ # value because there is no masking. Similarly we do not need padding.
730
+ if n_full_blocks > 0:
731
+ block_max = block_min + n_full_blocks * BLOCK_N
732
+ acc, l_i, m_i = _attn_fwd_inner(
733
+ acc,
734
+ l_i,
735
+ m_i,
736
+ q,
737
+ q_pe,
738
+ k_ptrs,
739
+ k_pe_ptrs,
740
+ v_ptrs,
741
+ stride_kn,
742
+ stride_vn,
743
+ stride_sd_n,
744
+ start_m,
745
+ seqlen_k,
746
+ seqlen_q,
747
+ dropout_p,
748
+ s_dmask_ptrs,
749
+ dropout_mask_ptrs,
750
+ philox_seed,
751
+ philox_ptrs,
752
+ block_min,
753
+ block_max,
754
+ 0,
755
+ 0,
756
+ 0,
757
+ alibi_slope,
758
+ descale_q,
759
+ descale_k,
760
+ descale_v,
761
+ offs_m,
762
+ offs_n,
763
+ PRELOAD_V,
764
+ BLOCK_M,
765
+ BLOCK_N,
766
+ BLOCK_DMODEL,
767
+ BLOCK_DMODEL_POW2,
768
+ BLOCK_DMODEL_PE,
769
+ sm_scale,
770
+ False,
771
+ MASK_STEPS=False,
772
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
773
+ RETURN_SCORES=RETURN_SCORES,
774
+ PADDED_HEAD=BLOCK_DMODEL != BLOCK_DMODEL_POW2,
775
+ IS_FP8=IS_FP8,
776
+ FP8_MAX=FP8_MAX,
777
+ ENABLE_PIPELINING=True,
778
+ SLIDING_WINDOW=SLIDING_WINDOW,
779
+ )
780
+ block_min = block_max
781
+ block_max = n_blocks * BLOCK_N
782
+
783
+ # Remaining blocks, if any, are full / not masked.
784
+ if masked_blocks > 0:
785
+ if IS_CAUSAL:
786
+ offs_n_causal = offs_n + (seqlen_q - seqlen_k)
787
+ else:
788
+ offs_n_causal = 0
789
+ k_ptrs += n_full_blocks * BLOCK_N * stride_kn
790
+ if HAS_PE:
791
+ k_pe_ptrs += n_full_blocks * BLOCK_N * stride_kn
792
+ v_ptrs += n_full_blocks * BLOCK_N * stride_vn
793
+ if RETURN_SCORES:
794
+ s_dmask_ptrs += n_full_blocks * BLOCK_N * stride_sd_n
795
+ if ENABLE_DROPOUT:
796
+ dropout_mask_ptrs += n_full_blocks * BLOCK_N * stride_sd_n
797
+ acc, l_i, m_i = _attn_fwd_inner(
798
+ acc,
799
+ l_i,
800
+ m_i,
801
+ q,
802
+ q_pe,
803
+ k_ptrs,
804
+ k_pe_ptrs,
805
+ v_ptrs,
806
+ stride_kn,
807
+ stride_vn,
808
+ stride_sd_n,
809
+ start_m,
810
+ seqlen_k,
811
+ seqlen_q,
812
+ dropout_p,
813
+ s_dmask_ptrs,
814
+ dropout_mask_ptrs,
815
+ philox_seed,
816
+ philox_ptrs,
817
+ block_min,
818
+ block_max,
819
+ offs_n_causal,
820
+ masked_blocks,
821
+ n_extra_tokens,
822
+ alibi_slope,
823
+ descale_q,
824
+ descale_k,
825
+ descale_v,
826
+ offs_m,
827
+ offs_n,
828
+ PRELOAD_V,
829
+ BLOCK_M,
830
+ BLOCK_N,
831
+ BLOCK_DMODEL,
832
+ BLOCK_DMODEL_POW2,
833
+ BLOCK_DMODEL_PE,
834
+ sm_scale,
835
+ IS_CAUSAL,
836
+ MASK_STEPS=True,
837
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
838
+ RETURN_SCORES=RETURN_SCORES,
839
+ PADDED_HEAD=BLOCK_DMODEL != BLOCK_DMODEL_POW2,
840
+ IS_FP8=IS_FP8,
841
+ FP8_MAX=FP8_MAX,
842
+ ENABLE_PIPELINING=False,
843
+ SLIDING_WINDOW=SLIDING_WINDOW,
844
+ )
845
+ # epilogue
846
+ # This helps the compiler do Newton Raphson on l_i vs on acc which is much larger.
847
+ l_recip = 1 / l_i[:, None]
848
+ acc = acc * l_recip
849
+ if ENABLE_DROPOUT:
850
+ dropout_scale = 1 / (1 - dropout_p)
851
+ acc = acc * dropout_scale
852
+ # If seqlen_q > seqlen_k but the delta is not a multiple of BLOCK_M,
853
+ # then we have one block with a row of all NaNs which come from computing
854
+ # softmax over a row of all -infs (-inf - inf = NaN). We check for that here
855
+ # and store 0s where there are NaNs as these rows should've been zeroed out.
856
+ end_m_idx = (start_m + 1) * BLOCK_M
857
+ start_m_idx = start_m * BLOCK_M
858
+ causal_start_idx = seqlen_q - seqlen_k
859
+ if IS_CAUSAL:
860
+ if causal_start_idx > start_m_idx and causal_start_idx < end_m_idx:
861
+ out_mask_boundary = tl.full(
862
+ (BLOCK_DMODEL_POW2,), causal_start_idx, dtype=tl.int32
863
+ )
864
+ mask_m_offsets = start_m_idx + tl.arange(0, BLOCK_M)
865
+ out_ptrs_mask = mask_m_offsets[:, None] >= out_mask_boundary[None, :]
866
+ z = 0.0
867
+ acc = tl.where(out_ptrs_mask, acc, z.to(acc.type.element_ty))
868
+
869
+ # write back LSE(Log Sum Exponents), the log of the normalization constant
870
+ overflow_size = end_m_idx - seqlen_q
871
+ if softmax_lse_ptr is not None:
872
+ LN2: tl.constexpr = 0.6931471824645996
873
+ # compute log-sum-exp in base 2 units
874
+ softmax_lse = m_i + tl.math.log2(l_i)
875
+ # convert back to natural units
876
+ softmax_lse *= LN2
877
+
878
+ if IS_CAUSAL:
879
+ # zero out nans caused by -infs when doing causal
880
+ lse_causal_mask = (start_m_idx + tl.arange(0, BLOCK_M)) < causal_start_idx
881
+ softmax_lse = tl.where(lse_causal_mask, 0.0, softmax_lse)
882
+
883
+ # If seqlen_q not multiple of BLOCK_M, we need to mask out the last few rows.
884
+ # This is only true for the last M block. For others, overflow_size will be -ve
885
+ offs_lse = (
886
+ off_z * stride_lse_z
887
+ + off_q_head * stride_lse_h
888
+ + cu_seqlens_q_start * stride_lse_m
889
+ + offs_m * stride_lse_m
890
+ )
891
+ if overflow_size > 0:
892
+ boundary = tl.full((BLOCK_M,), BLOCK_M - overflow_size, dtype=tl.int32)
893
+ lse_mask = tl.arange(0, BLOCK_M) < boundary
894
+ tl.store(
895
+ softmax_lse_ptr + offs_lse, softmax_lse, mask=lse_mask
896
+ ) # the log of the normalization constant
897
+ else:
898
+ tl.store(
899
+ softmax_lse_ptr + offs_lse, softmax_lse
900
+ ) # the log of the normalization constant
901
+
902
+ # write back O
903
+ offs_out = (
904
+ off_z * stride_oz
905
+ + off_q_head * stride_oh
906
+ + cu_seqlens_q_start * stride_om
907
+ + offs_m[:, None] * stride_om
908
+ + offs_d[None, :] * stride_on
909
+ )
910
+ out_mask = tl.full([BLOCK_M, 1], 1, dtype=tl.int1)
911
+ if overflow_size > 0:
912
+ out_mask = out_mask & (offs_m[:, None] < seqlen_q)
913
+ if BLOCK_DMODEL != BLOCK_DMODEL_POW2:
914
+ out_mask = out_mask & (offs_d[None, :] < BLOCK_DMODEL)
915
+ op = acc.to(out_ptr.dtype.element_ty)
916
+ tl.store(out_ptr + offs_out, op, mask=out_mask)
917
+
918
+
919
+ @functools.lru_cache(maxsize=1024)
920
+ def _get_config(
921
+ enable_dropout: bool,
922
+ dtype: torch.dtype,
923
+ has_pe: bool = False,
924
+ ):
925
+ if not hasattr(_get_config, "_config_dict"):
926
+ dev = arch_info.get_arch()
927
+ _get_config._config_dict = {}
928
+ fpath = f"{AITER_TRITON_CONFIGS_PATH}/{dev}-MHA-DEFAULT.json"
929
+ with open(fpath, "r") as file:
930
+ config = json.load(file)
931
+ _get_config._config_dict["default"] = config
932
+ fwd_cfg = _get_config._config_dict["default"]["fwd"]
933
+ has_dropout_or_fp32 = enable_dropout or dtype == torch.float32
934
+ # TODO: pe + dropout is not tuned
935
+ if has_pe and has_dropout_or_fp32 and "pe_dropout_or_fp32" in fwd_cfg:
936
+ return fwd_cfg["pe_dropout_or_fp32"]
937
+ elif has_pe and "pe" in fwd_cfg:
938
+ return fwd_cfg["pe"]
939
+ elif enable_dropout or dtype == torch.float32:
940
+ return fwd_cfg["dropout_or_fp32"]
941
+ else:
942
+ return fwd_cfg["default"]
build/torch-rocm/_kernels/mha_fused_bwd.py ADDED
@@ -0,0 +1,1073 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import functools
5
+ import json
6
+ import triton
7
+ import triton.language as tl
8
+
9
+
10
+ from ..utils._triton import arch_info
11
+ from ..utils.core import AITER_TRITON_CONFIGS_PATH
12
+ from ..utils._triton.pid_preprocessing import remap_xcd
13
+ from ..utils._triton.mha_kernel_utils import _compute_fp8_scaling_factors
14
+ from ..utils._triton.kernel_repr import make_kernel_repr
15
+
16
+ # This function computes delta given output Out and gradient DO
17
+ # Here is the I/O shape:
18
+ # Out: (batch, nhead_q, max_seqlens_q, headDim)
19
+ # DO: (batch, nhead_q, max_seqlens_q, headDim)
20
+ # Delta: (batch, nheads_q, max_seqlens_q), same as softmax_lse defined at
21
+ _bwd_preprocess_repr = make_kernel_repr(
22
+ "_bwd_preprocess",
23
+ [
24
+ "BLOCK_M",
25
+ "BLOCK_D_MODEL",
26
+ "IS_VARLEN",
27
+ "IS_FP8",
28
+ ],
29
+ )
30
+
31
+
32
+ @triton.jit(repr=_bwd_preprocess_repr)
33
+ def _bwd_preprocess(
34
+ o_ptr,
35
+ do_ptr, # noqa: E741
36
+ delta_ptr,
37
+ stride_o_b,
38
+ stride_o_h,
39
+ stride_o_m,
40
+ stride_o_k,
41
+ stride_do_b,
42
+ stride_do_h,
43
+ stride_do_m,
44
+ stride_do_k,
45
+ stride_delta_b,
46
+ stride_delta_h,
47
+ stride_delta_m,
48
+ stride_descale_do_z,
49
+ cu_seqlens_q,
50
+ max_seqlen_q,
51
+ descale_do_ptr,
52
+ BLOCK_M: tl.constexpr,
53
+ BLOCK_D_MODEL: tl.constexpr,
54
+ BLOCK_D_MODEL_POW2: tl.constexpr,
55
+ IS_VARLEN: tl.constexpr,
56
+ IS_FP8: tl.constexpr,
57
+ ):
58
+ pid_m = tl.program_id(0) # seqlen
59
+ bid = tl.program_id(1) # batch
60
+ hid = tl.program_id(2) # head
61
+
62
+ # Handle varlen
63
+ q_start = 0
64
+ seqlen_q = max_seqlen_q
65
+ if IS_VARLEN:
66
+ q_start = tl.load(cu_seqlens_q + bid)
67
+ q_end = tl.load(cu_seqlens_q + bid + 1)
68
+ seqlen_q = q_end - q_start
69
+ else:
70
+ q_start = 0
71
+ seqlen_q = max_seqlen_q
72
+
73
+ # Compute offsets
74
+ offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
75
+ offs_k = tl.arange(0, BLOCK_D_MODEL_POW2)
76
+
77
+ # O and DO may have different strides (e.g. BSHD vs SBHD memory layout),
78
+ # so address each with its own strides.
79
+ offs_o = (
80
+ bid * stride_o_b
81
+ + hid * stride_o_h
82
+ + q_start * stride_o_m
83
+ + offs_m[:, None] * stride_o_m
84
+ + offs_k[None, :] * stride_o_k
85
+ )
86
+ offs_do = (
87
+ bid * stride_do_b
88
+ + hid * stride_do_h
89
+ + q_start * stride_do_m
90
+ + offs_m[:, None] * stride_do_m
91
+ + offs_k[None, :] * stride_do_k
92
+ )
93
+
94
+ # create masks
95
+ mask_m = offs_m < seqlen_q
96
+ mask = mask_m[:, None]
97
+ PADDED_HEAD: tl.constexpr = BLOCK_D_MODEL != BLOCK_D_MODEL_POW2
98
+ if PADDED_HEAD:
99
+ mask &= offs_k[None, :] < BLOCK_D_MODEL
100
+
101
+ # load [BLOCK_M, BLOCK_D_MODEL_POW2]
102
+ o = tl.load(o_ptr + offs_o, mask=mask, other=0.0)
103
+ do = tl.load(do_ptr + offs_do, mask=mask, other=0.0)
104
+
105
+ # compute and write-back to delta
106
+ if IS_FP8:
107
+ descale_do = tl.load(descale_do_ptr + bid * stride_descale_do_z + hid)
108
+
109
+ # NOTE: do is in the fp8 range and o is not in fp8
110
+ delta = tl.sum(o.to(tl.float32) * (do.to(tl.float32) * descale_do), axis=1)
111
+ else:
112
+ delta = tl.sum(o.to(tl.float32) * do.to(tl.float32), axis=1)
113
+
114
+ offs_delta = (
115
+ bid * stride_delta_b
116
+ + hid * stride_delta_h
117
+ + q_start * stride_delta_m
118
+ + offs_m * stride_delta_m
119
+ )
120
+ tl.store(delta_ptr + offs_delta, delta, mask=mask_m)
121
+
122
+
123
+ @triton.jit
124
+ def _bwd_dkdvdq_inner(
125
+ dk,
126
+ dv,
127
+ Q,
128
+ k,
129
+ v,
130
+ DO,
131
+ DQ,
132
+ M,
133
+ D,
134
+ sm_scale,
135
+ stride_q_m,
136
+ stride_q_k,
137
+ stride_dq_m,
138
+ stride_dq_k,
139
+ stride_do_m,
140
+ stride_do_k,
141
+ stride_dropout_m,
142
+ stride_dropout_n,
143
+ stride_deltam,
144
+ dropout_p,
145
+ philox_seed,
146
+ batch_philox_offset,
147
+ dropout_offset,
148
+ seqlen_q,
149
+ seqlen_k,
150
+ start_n,
151
+ start_m,
152
+ num_steps,
153
+ descale_q,
154
+ descale_k,
155
+ descale_v,
156
+ descale_do,
157
+ BLOCK_M: tl.constexpr,
158
+ BLOCK_N: tl.constexpr,
159
+ BLOCK_D_MODEL: tl.constexpr,
160
+ BLOCK_D_MODEL_POW2: tl.constexpr,
161
+ MASK: tl.constexpr,
162
+ ENABLE_DROPOUT: tl.constexpr,
163
+ IS_FP8: tl.constexpr,
164
+ FP8_MAX: tl.constexpr,
165
+ workgroup_id,
166
+ ):
167
+ tl.assume(stride_q_m >= 0)
168
+ tl.assume(stride_q_k >= 0)
169
+ tl.assume(stride_dq_m >= 0)
170
+ tl.assume(stride_dq_k >= 0)
171
+ tl.assume(stride_do_m >= 0)
172
+ tl.assume(stride_do_k >= 0)
173
+ tl.assume(stride_deltam >= 0)
174
+
175
+ PADDED_HEAD: tl.constexpr = BLOCK_D_MODEL != BLOCK_D_MODEL_POW2
176
+ delta_qk = seqlen_q - seqlen_k
177
+ offs_m = start_m + tl.arange(0, BLOCK_M)
178
+ offs_n = start_n + tl.arange(0, BLOCK_N)
179
+ offs_k = tl.arange(0, BLOCK_D_MODEL_POW2)
180
+
181
+ # mask to make sure not OOB of seqlen_q
182
+ mask_n = offs_n < seqlen_k
183
+
184
+ qT_ptrs_start = (
185
+ Q + offs_m[None, :] * stride_q_m + offs_k[:, None] * stride_q_k
186
+ ) # [BLOCK_D_MODEL_POW2, BLOCK_M]
187
+ dq_ptrs_start = (
188
+ DQ + offs_m[:, None] * stride_dq_m + offs_k[None, :] * stride_dq_k
189
+ ) # [BLOCK_M, BLOCK_D_MODEL_POW2]
190
+
191
+ do_ptrs_start = DO + offs_m[:, None] * stride_do_m + offs_k[None, :] * stride_do_k
192
+ curr_m = start_m
193
+ step_m = BLOCK_M
194
+ curr_philox_offset = batch_philox_offset
195
+
196
+ # Iterate over blocks(BLOCK_M size) of Q while calculating
197
+ # a fixed block(BLOCK_N) of dk and dv. Note, during backward
198
+ # pass P has to be recomputed. However, this kernel computes
199
+ # dV and dK, so we compute we need P^T and S^T. See backward pass
200
+ # equations
201
+ #
202
+ # From Flash Attention Paper:
203
+ # ForwardPass: S = QkT, P=softmax(S), O=PV
204
+ #
205
+ # BackwardPass equations
206
+ # dV = P^TdO
207
+ # dP = dOV^T
208
+ # dS = dsoftmax(dP)
209
+ # dQ = dSK
210
+ # dK = QdS^T
211
+
212
+ for iter in range(num_steps):
213
+ # Permute the iteration order to reduce the probability that concurrent workgroups (that share the same q head idx and batch idx) are at the same iteration
214
+ blk_idx = (iter + workgroup_id) % num_steps
215
+
216
+ curr_m = start_m + blk_idx * step_m
217
+ qT_ptrs = qT_ptrs_start + blk_idx * step_m * stride_q_m
218
+ dq_ptrs = dq_ptrs_start + blk_idx * step_m * stride_dq_m
219
+ do_ptrs = do_ptrs_start + blk_idx * step_m * stride_do_m
220
+
221
+ offs_m = curr_m + tl.arange(0, BLOCK_M)
222
+ mask_m = offs_m < seqlen_q
223
+ mask_qT = mask_m[None, :]
224
+ mask_do = mask_m[:, None]
225
+ mask_nm = mask_n[:, None] & (offs_m[None, :] < seqlen_q)
226
+
227
+ if PADDED_HEAD:
228
+ mask_qT &= offs_k[:, None] < BLOCK_D_MODEL
229
+ mask_do &= offs_k[None, :] < BLOCK_D_MODEL
230
+
231
+ # load qT
232
+ qT = tl.load(qT_ptrs, mask=mask_qT, other=0.0)
233
+
234
+ # dropout
235
+ if ENABLE_DROPOUT:
236
+ # NOTE: dropout is transposed because it is used to mask pT
237
+ philox_offs = (
238
+ curr_philox_offset
239
+ + offs_m[None, :] * stride_dropout_m
240
+ + offs_n[:, None] * stride_dropout_n
241
+ )
242
+ rand_vals = tl.rand(philox_seed, philox_offs)
243
+ dropout_mask = rand_vals > dropout_p
244
+ dropout_scale = 1.0 / (1 - dropout_p)
245
+
246
+ # Load M
247
+ m = tl.load(M + offs_m * stride_deltam, mask=mask_m, other=0.0)
248
+
249
+ # Compute qkT
250
+ if IS_FP8:
251
+ qkT = tl.dot(k, qT) * descale_q * descale_k
252
+ else:
253
+ qkT = tl.dot(k, qT)
254
+
255
+ # Compute pT(use m and also apply sm_scale)
256
+ pT = tl.math.exp(qkT * sm_scale - m[None, :])
257
+
258
+ if MASK:
259
+ causal_mask = (offs_m[None, :] - delta_qk) >= (offs_n[:, None])
260
+ mask = causal_mask & mask_nm
261
+ pT = tl.where(mask, pT, 0.0)
262
+
263
+ # load DO
264
+ do = tl.load(do_ptrs, mask=mask_do, other=0.0)
265
+
266
+ # dV
267
+ if ENABLE_DROPOUT:
268
+ pT_dropout = tl.where(dropout_mask, pT, 0.0) * dropout_scale
269
+ if IS_FP8:
270
+ scale_p_dropout, descale_p_dropout = _compute_fp8_scaling_factors(
271
+ pT_dropout, FP8_MAX
272
+ )
273
+ dv += (
274
+ tl.dot((pT_dropout * scale_p_dropout).to(do.type.element_ty), do)
275
+ * descale_p_dropout
276
+ * descale_do
277
+ )
278
+ else:
279
+ dv = tl.dot(pT_dropout.to(do.type.element_ty), do, acc=dv)
280
+ else:
281
+ if IS_FP8:
282
+ scale_pT, descale_pT = _compute_fp8_scaling_factors(pT, FP8_MAX)
283
+ dv += (
284
+ tl.dot((pT * scale_pT).to(do.type.element_ty), do)
285
+ * descale_pT
286
+ * descale_do
287
+ )
288
+ else:
289
+ dv = tl.dot(pT.to(do.type.element_ty), do, acc=dv)
290
+
291
+ # Load delta
292
+ Di = tl.load(D + offs_m * stride_deltam, mask=mask_m)
293
+
294
+ # Compute dP and dS
295
+ if IS_FP8:
296
+ dpT = tl.dot(v, tl.trans(do)) * descale_v * descale_do
297
+ else:
298
+ dpT = tl.dot(v, tl.trans(do))
299
+
300
+ if ENABLE_DROPOUT:
301
+ dpT = tl.where(dropout_mask, dpT, 0.0) * dropout_scale
302
+
303
+ delta_i = Di[None, :]
304
+ dsT = pT * (dpT - delta_i)
305
+
306
+ # compute dk
307
+ if IS_FP8:
308
+ scale_dsT, descale_dsT = _compute_fp8_scaling_factors(dsT, FP8_MAX)
309
+ dk += (
310
+ tl.dot((dsT * scale_dsT).to(qT.type.element_ty), tl.trans(qT))
311
+ * descale_dsT
312
+ * descale_q
313
+ )
314
+ else:
315
+ dk = tl.dot(dsT.to(qT.type.element_ty), tl.trans(qT), acc=dk)
316
+
317
+ # We can compute the dq_partial here and do a atomic add to the correct memory location
318
+ # NOTE: Possible problems with the atomic add: contention, is inside a loop which has achieved bad perf before
319
+ # (BLOCK_M, BLOCK_N) x (BLOCK_N, D)
320
+ if IS_FP8:
321
+ dq_partial = (
322
+ tl.dot((dsT * scale_dsT).to(k.dtype).T, k) * descale_dsT * descale_k
323
+ )
324
+ else:
325
+ dq_partial = tl.dot(dsT.to(k.dtype).T, k)
326
+ tl.atomic_add(
327
+ dq_ptrs,
328
+ dq_partial * sm_scale,
329
+ mask=mask_m[:, None] & (offs_k[None, :] < BLOCK_D_MODEL),
330
+ sem="relaxed",
331
+ )
332
+
333
+ return dk, dv
334
+
335
+
336
+ _bwd_kernel_dkdvdq_causal_repr = make_kernel_repr(
337
+ "_bwd_kernel_dkdvdq_causal",
338
+ [
339
+ "NUM_Q_HEADS",
340
+ "NUM_K_HEADS",
341
+ "BLOCK_M",
342
+ "BLOCK_N",
343
+ "BLK_SLICE_FACTOR",
344
+ "BLOCK_D_MODEL",
345
+ "ENABLE_DROPOUT",
346
+ "IS_VARLEN",
347
+ "IS_FP8",
348
+ "USE_INT64_STRIDES",
349
+ "NUM_XCD",
350
+ ],
351
+ )
352
+
353
+
354
+ @triton.jit(repr=_bwd_kernel_dkdvdq_causal_repr)
355
+ def _bwd_kernel_dkdvdq_causal(
356
+ q_ptr,
357
+ k_ptr,
358
+ v_ptr,
359
+ sm_scale,
360
+ do_ptr,
361
+ dk_ptr,
362
+ dv_ptr,
363
+ dq_ptr,
364
+ m_ptr,
365
+ delta_ptr,
366
+ stride_q_b_in,
367
+ stride_q_h_in,
368
+ stride_q_m_in,
369
+ stride_q_k_in,
370
+ stride_k_b_in,
371
+ stride_k_h_in,
372
+ stride_k_n_in,
373
+ stride_k_k_in,
374
+ stride_v_b_in,
375
+ stride_v_h_in,
376
+ stride_v_n_in,
377
+ stride_v_k_in,
378
+ stride_dk_b_in,
379
+ stride_dk_h_in,
380
+ stride_dk_n_in,
381
+ stride_dk_k_in,
382
+ stride_dq_b_in,
383
+ stride_dq_h_in,
384
+ stride_dq_m_in,
385
+ stride_dq_k_in,
386
+ stride_delta_b_in,
387
+ stride_delta_h_in,
388
+ stride_delta_m_in,
389
+ stride_do_b_in,
390
+ stride_do_h_in,
391
+ stride_do_m_in,
392
+ stride_do_k_in,
393
+ stride_dropout_b_in,
394
+ stride_dropout_h_in,
395
+ stride_dropout_m_in,
396
+ stride_dropout_n_in,
397
+ stride_descale_q_z_in,
398
+ stride_descale_k_z_in,
399
+ stride_descale_v_z_in,
400
+ stride_descale_do_z_in,
401
+ cu_seqlens_q,
402
+ cu_seqlens_k,
403
+ max_seqlen_q,
404
+ max_seqlen_k,
405
+ dropout_mask,
406
+ dropout_p,
407
+ philox_seed,
408
+ philox_offset_base_in,
409
+ descale_q_ptr,
410
+ descale_k_ptr,
411
+ descale_v_ptr,
412
+ descale_do_ptr,
413
+ NUM_Q_HEADS: tl.constexpr,
414
+ NUM_K_HEADS: tl.constexpr,
415
+ BATCH,
416
+ NUM_K_PIDS,
417
+ BLOCK_M: tl.constexpr,
418
+ BLOCK_N: tl.constexpr,
419
+ BLK_SLICE_FACTOR: tl.constexpr,
420
+ BLOCK_D_MODEL: tl.constexpr,
421
+ BLOCK_D_MODEL_POW2: tl.constexpr,
422
+ ENABLE_DROPOUT: tl.constexpr,
423
+ IS_VARLEN: tl.constexpr,
424
+ IS_FP8: tl.constexpr,
425
+ FP8_MAX: tl.constexpr,
426
+ USE_INT64_STRIDES: tl.constexpr,
427
+ NUM_XCD: tl.constexpr,
428
+ ):
429
+ if USE_INT64_STRIDES:
430
+ stride_q_b = tl.cast(stride_q_b_in, tl.int64)
431
+ stride_q_h = tl.cast(stride_q_h_in, tl.int64)
432
+ stride_q_m = tl.cast(stride_q_m_in, tl.int64)
433
+ stride_q_k = tl.cast(stride_q_k_in, tl.int64)
434
+ stride_k_b = tl.cast(stride_k_b_in, tl.int64)
435
+ stride_k_h = tl.cast(stride_k_h_in, tl.int64)
436
+ stride_k_n = tl.cast(stride_k_n_in, tl.int64)
437
+ stride_k_k = tl.cast(stride_k_k_in, tl.int64)
438
+ stride_v_b = tl.cast(stride_v_b_in, tl.int64)
439
+ stride_v_h = tl.cast(stride_v_h_in, tl.int64)
440
+ stride_v_n = tl.cast(stride_v_n_in, tl.int64)
441
+ stride_v_k = tl.cast(stride_v_k_in, tl.int64)
442
+ stride_dk_b = tl.cast(stride_dk_b_in, tl.int64)
443
+ stride_dk_h = tl.cast(stride_dk_h_in, tl.int64)
444
+ stride_dk_n = tl.cast(stride_dk_n_in, tl.int64)
445
+ stride_dk_k = tl.cast(stride_dk_k_in, tl.int64)
446
+ stride_dq_b = tl.cast(stride_dq_b_in, tl.int64)
447
+ stride_dq_h = tl.cast(stride_dq_h_in, tl.int64)
448
+ stride_dq_m = tl.cast(stride_dq_m_in, tl.int64)
449
+ stride_dq_k = tl.cast(stride_dq_k_in, tl.int64)
450
+ stride_delta_b = tl.cast(stride_delta_b_in, tl.int64)
451
+ stride_delta_h = tl.cast(stride_delta_h_in, tl.int64)
452
+ stride_delta_m = tl.cast(stride_delta_m_in, tl.int64)
453
+ stride_do_b = tl.cast(stride_do_b_in, tl.int64)
454
+ stride_do_h = tl.cast(stride_do_h_in, tl.int64)
455
+ stride_do_m = tl.cast(stride_do_m_in, tl.int64)
456
+ stride_do_k = tl.cast(stride_do_k_in, tl.int64)
457
+ stride_dropout_b = tl.cast(stride_dropout_b_in, tl.int64)
458
+ stride_dropout_h = tl.cast(stride_dropout_h_in, tl.int64)
459
+ stride_dropout_m = tl.cast(stride_dropout_m_in, tl.int64)
460
+ stride_dropout_n = tl.cast(stride_dropout_n_in, tl.int64)
461
+ philox_offset_base = tl.cast(philox_offset_base_in, tl.int64)
462
+ if IS_FP8:
463
+ stride_descale_q_z = tl.cast(stride_descale_q_z_in, tl.int64)
464
+ stride_descale_k_z = tl.cast(stride_descale_k_z_in, tl.int64)
465
+ stride_descale_v_z = tl.cast(stride_descale_v_z_in, tl.int64)
466
+ stride_descale_do_z = tl.cast(stride_descale_do_z_in, tl.int64)
467
+ else:
468
+ stride_q_b = stride_q_b_in
469
+ stride_q_h = stride_q_h_in
470
+ stride_q_m = stride_q_m_in
471
+ stride_q_k = stride_q_k_in
472
+ stride_k_b = stride_k_b_in
473
+ stride_k_h = stride_k_h_in
474
+ stride_k_n = stride_k_n_in
475
+ stride_k_k = stride_k_k_in
476
+ stride_v_b = stride_v_b_in
477
+ stride_v_h = stride_v_h_in
478
+ stride_v_n = stride_v_n_in
479
+ stride_v_k = stride_v_k_in
480
+ stride_dk_b = stride_dk_b_in
481
+ stride_dk_h = stride_dk_h_in
482
+ stride_dk_n = stride_dk_n_in
483
+ stride_dk_k = stride_dk_k_in
484
+ stride_dq_b = stride_dq_b_in
485
+ stride_dq_h = stride_dq_h_in
486
+ stride_dq_m = stride_dq_m_in
487
+ stride_dq_k = stride_dq_k_in
488
+ stride_delta_b = stride_delta_b_in
489
+ stride_delta_h = stride_delta_h_in
490
+ stride_delta_m = stride_delta_m_in
491
+ stride_do_b = stride_do_b_in
492
+ stride_do_h = stride_do_h_in
493
+ stride_do_m = stride_do_m_in
494
+ stride_do_k = stride_do_k_in
495
+ stride_dropout_b = stride_dropout_b_in
496
+ stride_dropout_h = stride_dropout_h_in
497
+ stride_dropout_m = stride_dropout_m_in
498
+ stride_dropout_n = stride_dropout_n_in
499
+ philox_offset_base = philox_offset_base_in
500
+ stride_descale_q_z = stride_descale_q_z_in
501
+ stride_descale_k_z = stride_descale_k_z_in
502
+ stride_descale_v_z = stride_descale_v_z_in
503
+ stride_descale_do_z = stride_descale_do_z_in
504
+
505
+ GROUP_SIZE = NUM_Q_HEADS // NUM_K_HEADS
506
+ wid = tl.program_id(0) # workgoup id: 0, ..., NUM_Q_PIDS * BATCH * NUM_K_HEADS - 1
507
+
508
+ head_q_idx = wid % NUM_Q_HEADS
509
+ head_q_idx = remap_xcd(head_q_idx, NUM_Q_HEADS, NUM_XCD)
510
+ seq_k_blk_idx = (wid // NUM_Q_HEADS) % NUM_K_PIDS
511
+ batch_idx = (wid // (NUM_K_PIDS * NUM_Q_HEADS)) % BATCH
512
+
513
+ # In the backward we dont want concurrent workgroups to handle consecutive heads or blocks, so remap them to be far apart.
514
+ head_q_idx = (head_q_idx * 29) % NUM_Q_HEADS
515
+ # seq_k_blk_idx = (seq_k_blk_idx * 29) % NUM_K_PIDS
516
+
517
+ head_k_idx = head_q_idx // GROUP_SIZE
518
+
519
+ # Determine q and k start along with seqlen_q and seqlen_k
520
+ q_start = 0
521
+ k_start = 0
522
+ seqlen_q = max_seqlen_q
523
+ seqlen_k = max_seqlen_k
524
+ if IS_VARLEN:
525
+ q_start = tl.load(cu_seqlens_q + batch_idx)
526
+ q_end = tl.load(cu_seqlens_q + batch_idx + 1)
527
+ k_start = tl.load(cu_seqlens_k + batch_idx)
528
+ k_end = tl.load(cu_seqlens_k + batch_idx + 1)
529
+ seqlen_q = q_end - q_start
530
+ seqlen_k = k_end - k_start
531
+
532
+ dk = tl.zeros([BLOCK_N, BLOCK_D_MODEL_POW2], dtype=tl.float32)
533
+ dv = tl.zeros([BLOCK_N, BLOCK_D_MODEL_POW2], dtype=tl.float32)
534
+
535
+ # Figure out causal starting block since we have seqlen_q >=< seqlen_k.
536
+ # Unlike forward pass where we tile on M dim and iterate on N dim, so that
537
+ # we can skip some M blocks, in backward pass, we tile on the N dim for kv
538
+ # and iterate over the M. In this way, we cannot skip N blocks, but only to
539
+ # determine the starting M blocks to skip some initial blocks masked by
540
+ # causal.
541
+ delta_qk = seqlen_q - seqlen_k
542
+
543
+ # q < k: some blocks will have no Masked block, other needs to re-calc
544
+ # starting position
545
+ # delta_qk is negative so flip it, only multiple of BLOCK_N can skip the
546
+ # masked op
547
+ num_blocks_skip = -delta_qk // BLOCK_N
548
+ delta_aligned = (num_blocks_skip + 1) * BLOCK_N + delta_qk
549
+ start_delta_q_lt_k = delta_aligned // BLOCK_M * BLOCK_M
550
+ if delta_qk >= 0:
551
+ start_delta = delta_qk
552
+ else:
553
+ start_delta = start_delta_q_lt_k
554
+
555
+ start_n = seq_k_blk_idx * BLOCK_N
556
+
557
+ offs_k = tl.arange(0, BLOCK_D_MODEL_POW2)
558
+ offs_n = start_n + tl.arange(0, BLOCK_N)
559
+ # Mask for loading K and V
560
+ mask_kv = offs_n[:, None] < seqlen_k
561
+ PADDED_HEAD: tl.constexpr = BLOCK_D_MODEL != BLOCK_D_MODEL_POW2
562
+ if PADDED_HEAD:
563
+ mask_k = offs_k < BLOCK_D_MODEL
564
+ mask_kv &= mask_k[None, :]
565
+
566
+ GROUP_SIZE = NUM_Q_HEADS // NUM_K_HEADS
567
+ adj_k = (
568
+ batch_idx * stride_k_b
569
+ + head_k_idx * stride_k_h
570
+ + k_start * stride_k_n
571
+ + offs_n[:, None] * stride_k_n
572
+ + offs_k[None, :] * stride_k_k
573
+ )
574
+ adj_v = (
575
+ batch_idx * stride_v_b
576
+ + head_k_idx * stride_v_h
577
+ + k_start * stride_v_n
578
+ + offs_n[:, None] * stride_v_n
579
+ + offs_k[None, :] * stride_v_k
580
+ )
581
+ # load K and V: they stay in SRAM throughout the inner loop.
582
+ k = tl.load(k_ptr + adj_k, mask=mask_kv, other=0.0)
583
+ v = tl.load(v_ptr + adj_v, mask=mask_kv, other=0.0)
584
+
585
+ # If MQA / GQA, set the K and V head offsets appropriately.
586
+ # for head_q_idx in range(head_k_idx * GROUP_SIZE, head_k_idx * GROUP_SIZE + GROUP_SIZE):
587
+ if delta_qk >= 0:
588
+ start_m = start_n + start_delta
589
+ len_m = BLOCK_N
590
+ else:
591
+ start_m = max(start_n + delta_qk, 0)
592
+ start_m = (start_m // BLOCK_M) * BLOCK_M
593
+ # because we might shift the masked blocks up, we are deeper into
594
+ # the masked out region, so we would potentially increase the total
595
+ # steps with masked operation to get out of it
596
+ residue_m = max(start_n + delta_qk - start_m, 0)
597
+ len_m = BLOCK_N + residue_m
598
+
599
+ # offset input and output tensor by batch and Q/K heads
600
+ adj_q = batch_idx * stride_q_b + head_q_idx * stride_q_h + q_start * stride_q_m
601
+ adj_dq = batch_idx * stride_dq_b + head_q_idx * stride_dq_h + q_start * stride_dq_m
602
+
603
+ q_ptr_adj = q_ptr + adj_q
604
+ dq_ptr_adj = dq_ptr + adj_dq
605
+
606
+ adj_do = batch_idx * stride_do_b + head_q_idx * stride_do_h + q_start * stride_do_m
607
+ do_ptr_adj = do_ptr + adj_do
608
+ adj_delta = (
609
+ batch_idx * stride_delta_b
610
+ + head_q_idx * stride_delta_h
611
+ + q_start * stride_delta_m
612
+ )
613
+ m_ptr_adj = m_ptr + adj_delta
614
+ delta_ptr_adj = delta_ptr + adj_delta
615
+
616
+ # batch_philox_offset is the ACTUALLY dropout offset
617
+ # dropout_offset is for debug purpose and will be removed later
618
+ batch_philox_offset = 0
619
+ dropout_offset = 0
620
+ if ENABLE_DROPOUT:
621
+ batch_philox_offset = (
622
+ philox_offset_base
623
+ + batch_idx * stride_dropout_b
624
+ + head_q_idx * stride_dropout_h
625
+ )
626
+ dropout_offset = (
627
+ dropout_mask + batch_idx * stride_dropout_b + head_q_idx * stride_dropout_h
628
+ )
629
+
630
+ MASK_BLOCK_M: tl.constexpr = BLOCK_M // BLK_SLICE_FACTOR
631
+ # bound the masked operation to q len so it does not have to wast cycles
632
+ len_m = min(len_m, seqlen_q)
633
+ num_steps = tl.cdiv(len_m, MASK_BLOCK_M)
634
+
635
+ # when q < k, we may skip the initial masked op
636
+ if seq_k_blk_idx < num_blocks_skip:
637
+ num_steps = 0
638
+
639
+ if IS_FP8:
640
+ descale_q = tl.load(descale_q_ptr + batch_idx * stride_descale_q_z + head_q_idx)
641
+ descale_k = tl.load(descale_k_ptr + batch_idx * stride_descale_k_z + head_k_idx)
642
+ descale_v = tl.load(descale_v_ptr + batch_idx * stride_descale_v_z + head_k_idx)
643
+ descale_do = tl.load(
644
+ descale_do_ptr + batch_idx * stride_descale_do_z + head_q_idx
645
+ )
646
+ else:
647
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
648
+
649
+ # if unaligned start_m is negative, the current N-tile has no block on the
650
+ # diagonal of causal mask, so everything have no causal mask
651
+ dk, dv = _bwd_dkdvdq_inner(
652
+ dk,
653
+ dv, # output tensors
654
+ q_ptr_adj,
655
+ k,
656
+ v,
657
+ do_ptr_adj,
658
+ dq_ptr_adj,
659
+ m_ptr_adj,
660
+ delta_ptr_adj,
661
+ sm_scale, # input tensors
662
+ stride_q_m,
663
+ stride_q_k, # strides for q
664
+ stride_dq_m,
665
+ stride_dq_k, # strides for q
666
+ stride_do_m,
667
+ stride_do_k, # strides for o
668
+ stride_dropout_m,
669
+ stride_dropout_n, # strides for dropout
670
+ stride_delta_m,
671
+ dropout_p,
672
+ philox_seed,
673
+ batch_philox_offset,
674
+ dropout_offset, #
675
+ seqlen_q,
676
+ seqlen_k, # max sequence length for q and k
677
+ start_n,
678
+ start_m,
679
+ num_steps, # iteration numbers
680
+ descale_q,
681
+ descale_k,
682
+ descale_v,
683
+ descale_do, # fp8 descale factors from user
684
+ MASK_BLOCK_M,
685
+ BLOCK_N, # block dim
686
+ BLOCK_D_MODEL,
687
+ BLOCK_D_MODEL_POW2, # head dim
688
+ MASK=True, # causal masking
689
+ ENABLE_DROPOUT=ENABLE_DROPOUT, # activate dropout
690
+ IS_FP8=IS_FP8,
691
+ FP8_MAX=FP8_MAX,
692
+ workgroup_id=seq_k_blk_idx,
693
+ )
694
+
695
+ start_m += num_steps * MASK_BLOCK_M
696
+ num_steps = tl.cdiv(seqlen_q - start_m, BLOCK_M)
697
+
698
+ dk, dv = _bwd_dkdvdq_inner(
699
+ dk,
700
+ dv, # output tensors
701
+ q_ptr_adj,
702
+ k,
703
+ v,
704
+ do_ptr_adj,
705
+ dq_ptr_adj,
706
+ m_ptr_adj,
707
+ delta_ptr_adj,
708
+ sm_scale, # input tensors
709
+ stride_q_m,
710
+ stride_q_k, # strides for q
711
+ stride_dq_m,
712
+ stride_dq_k, # strides for dq
713
+ stride_do_m,
714
+ stride_do_k, # strides for o
715
+ stride_dropout_m,
716
+ stride_dropout_n, # strides for dropout
717
+ stride_delta_m,
718
+ dropout_p,
719
+ philox_seed,
720
+ batch_philox_offset,
721
+ dropout_offset, #
722
+ seqlen_q,
723
+ seqlen_k, # max sequence length for q and k
724
+ start_n,
725
+ start_m,
726
+ num_steps, # iteration numbers
727
+ descale_q,
728
+ descale_k,
729
+ descale_v,
730
+ descale_do, # fp8 descale factors from user
731
+ BLOCK_M,
732
+ BLOCK_N, # block dim
733
+ BLOCK_D_MODEL,
734
+ BLOCK_D_MODEL_POW2, # head dim
735
+ MASK=False, # causal masking
736
+ ENABLE_DROPOUT=ENABLE_DROPOUT, # activate dropout
737
+ IS_FP8=IS_FP8,
738
+ FP8_MAX=FP8_MAX,
739
+ workgroup_id=seq_k_blk_idx,
740
+ )
741
+
742
+ # Write back dV and dK.
743
+ offs_dkdv = (
744
+ batch_idx * stride_dk_b
745
+ + head_k_idx * stride_dk_h
746
+ + k_start * stride_dk_n
747
+ + offs_n[:, None] * stride_dk_n
748
+ + offs_k[None, :] * stride_dk_k
749
+ )
750
+ tl.atomic_add(dv_ptr + offs_dkdv, dv, mask=mask_kv, sem="relaxed")
751
+ dk *= sm_scale
752
+ tl.atomic_add(dk_ptr + offs_dkdv, dk, mask=mask_kv, sem="relaxed")
753
+
754
+
755
+ _bwd_kernel_dkdvdq_noncausal_repr = make_kernel_repr(
756
+ "_bwd_kernel_dkdvdq_noncausal",
757
+ [
758
+ "NUM_Q_HEADS",
759
+ "NUM_K_HEADS",
760
+ "BLOCK_M",
761
+ "BLOCK_N",
762
+ "BLOCK_D_MODEL",
763
+ "ENABLE_DROPOUT",
764
+ "IS_VARLEN",
765
+ "IS_FP8",
766
+ "USE_INT64_STRIDES",
767
+ ],
768
+ )
769
+
770
+
771
+ @triton.jit(repr=_bwd_kernel_dkdvdq_noncausal_repr)
772
+ def _bwd_kernel_dkdvdq_noncausal(
773
+ Q,
774
+ K,
775
+ V,
776
+ sm_scale,
777
+ DO,
778
+ DK,
779
+ DV,
780
+ DQ,
781
+ M,
782
+ Delta,
783
+ stride_qb_in,
784
+ stride_qh_in,
785
+ stride_qm_in,
786
+ stride_qk_in,
787
+ stride_kb_in,
788
+ stride_kh_in,
789
+ stride_kn_in,
790
+ stride_kk_in,
791
+ stride_vb_in,
792
+ stride_vh_in,
793
+ stride_vn_in,
794
+ stride_vk_in,
795
+ stride_dkb_in,
796
+ stride_dkh_in,
797
+ stride_dkn_in,
798
+ stride_dkk_in,
799
+ stride_dqb_in,
800
+ stride_dqh_in,
801
+ stride_dqm_in,
802
+ stride_dqk_in,
803
+ stride_deltab_in,
804
+ stride_deltah_in,
805
+ stride_deltam_in,
806
+ stride_dob_in,
807
+ stride_doh_in,
808
+ stride_dom_in,
809
+ stride_dok_in,
810
+ stride_dropoutb_in,
811
+ stride_dropouth_in,
812
+ stride_dropoutm_in,
813
+ stride_dropoutn_in,
814
+ stride_descale_q_z_in,
815
+ stride_descale_k_z_in,
816
+ stride_descale_v_z_in,
817
+ stride_descale_do_z_in,
818
+ cu_seqlens_q,
819
+ cu_seqlens_k,
820
+ max_seqlen_q,
821
+ max_seqlen_k,
822
+ dropout_mask,
823
+ dropout_p,
824
+ philox_seed,
825
+ philox_offset,
826
+ descale_q_ptr,
827
+ descale_k_ptr,
828
+ descale_v_ptr,
829
+ descale_do_ptr,
830
+ NUM_Q_HEADS: tl.constexpr,
831
+ NUM_K_HEADS: tl.constexpr,
832
+ BATCH,
833
+ NUM_K_PIDS,
834
+ BLOCK_M: tl.constexpr,
835
+ BLOCK_N: tl.constexpr,
836
+ BLK_SLICE_FACTOR: tl.constexpr,
837
+ BLOCK_D_MODEL: tl.constexpr,
838
+ BLOCK_D_MODEL_POW2: tl.constexpr,
839
+ ENABLE_DROPOUT: tl.constexpr,
840
+ IS_VARLEN: tl.constexpr,
841
+ IS_FP8: tl.constexpr,
842
+ FP8_MAX: tl.constexpr,
843
+ USE_INT64_STRIDES: tl.constexpr,
844
+ ):
845
+ if USE_INT64_STRIDES:
846
+ stride_qb = tl.cast(stride_qb_in, tl.int64)
847
+ stride_qh = tl.cast(stride_qh_in, tl.int64)
848
+ stride_qm = tl.cast(stride_qm_in, tl.int64)
849
+ stride_qk = tl.cast(stride_qk_in, tl.int64)
850
+ stride_kb = tl.cast(stride_kb_in, tl.int64)
851
+ stride_kh = tl.cast(stride_kh_in, tl.int64)
852
+ stride_kn = tl.cast(stride_kn_in, tl.int64)
853
+ stride_kk = tl.cast(stride_kk_in, tl.int64)
854
+ stride_vb = tl.cast(stride_vb_in, tl.int64)
855
+ stride_vh = tl.cast(stride_vh_in, tl.int64)
856
+ stride_vn = tl.cast(stride_vn_in, tl.int64)
857
+ stride_vk = tl.cast(stride_vk_in, tl.int64)
858
+ stride_dkb = tl.cast(stride_dkb_in, tl.int64)
859
+ stride_dkh = tl.cast(stride_dkh_in, tl.int64)
860
+ stride_dkn = tl.cast(stride_dkn_in, tl.int64)
861
+ stride_dkk = tl.cast(stride_dkk_in, tl.int64)
862
+ stride_dqb = tl.cast(stride_dqb_in, tl.int64)
863
+ stride_dqh = tl.cast(stride_dqh_in, tl.int64)
864
+ stride_dqm = tl.cast(stride_dqm_in, tl.int64)
865
+ stride_dqk = tl.cast(stride_dqk_in, tl.int64)
866
+ stride_deltab = tl.cast(stride_deltab_in, tl.int64)
867
+ stride_deltah = tl.cast(stride_deltah_in, tl.int64)
868
+ stride_deltam = tl.cast(stride_deltam_in, tl.int64)
869
+ stride_dob = tl.cast(stride_dob_in, tl.int64)
870
+ stride_doh = tl.cast(stride_doh_in, tl.int64)
871
+ stride_dom = tl.cast(stride_dom_in, tl.int64)
872
+ stride_dok = tl.cast(stride_dok_in, tl.int64)
873
+ stride_dropoutb = tl.cast(stride_dropoutb_in, tl.int64)
874
+ stride_dropouth = tl.cast(stride_dropouth_in, tl.int64)
875
+ stride_dropoutm = tl.cast(stride_dropoutm_in, tl.int64)
876
+ stride_dropoutn = tl.cast(stride_dropoutn_in, tl.int64)
877
+ if IS_FP8:
878
+ stride_descale_q_z = tl.cast(stride_descale_q_z_in, tl.int64)
879
+ stride_descale_k_z = tl.cast(stride_descale_k_z_in, tl.int64)
880
+ stride_descale_v_z = tl.cast(stride_descale_v_z_in, tl.int64)
881
+ stride_descale_do_z = tl.cast(stride_descale_do_z_in, tl.int64)
882
+ else:
883
+ stride_qb = stride_qb_in
884
+ stride_qh = stride_qh_in
885
+ stride_qm = stride_qm_in
886
+ stride_qk = stride_qk_in
887
+ stride_kb = stride_kb_in
888
+ stride_kh = stride_kh_in
889
+ stride_kn = stride_kn_in
890
+ stride_kk = stride_kk_in
891
+ stride_vb = stride_vb_in
892
+ stride_vh = stride_vh_in
893
+ stride_vn = stride_vn_in
894
+ stride_vk = stride_vk_in
895
+ stride_dkb = stride_dkb_in
896
+ stride_dkh = stride_dkh_in
897
+ stride_dkn = stride_dkn_in
898
+ stride_dkk = stride_dkk_in
899
+ stride_dqb = stride_dqb_in
900
+ stride_dqh = stride_dqh_in
901
+ stride_dqm = stride_dqm_in
902
+ stride_dqk = stride_dqk_in
903
+ stride_deltab = stride_deltab_in
904
+ stride_deltah = stride_deltah_in
905
+ stride_deltam = stride_deltam_in
906
+ stride_dob = stride_dob_in
907
+ stride_doh = stride_doh_in
908
+ stride_dom = stride_dom_in
909
+ stride_dok = stride_dok_in
910
+ stride_dropoutb = stride_dropoutb_in
911
+ stride_dropouth = stride_dropouth_in
912
+ stride_dropoutm = stride_dropoutm_in
913
+ stride_dropoutn = stride_dropoutn_in
914
+ stride_descale_q_z = stride_descale_q_z_in
915
+ stride_descale_k_z = stride_descale_k_z_in
916
+ stride_descale_v_z = stride_descale_v_z_in
917
+ stride_descale_do_z = stride_descale_do_z_in
918
+
919
+ # workgroup id
920
+ wid = tl.program_id(0) # 0, ..., NUM_K_PIDS * BATCH * NUM_K_HEADS - 1
921
+
922
+ # Workgroups get launched first along batch dim, then in head_k dim, and then in seq k block dim
923
+ # This is in order to avoid contention for the tl.atomic_add (inside _bwd_dkdvdq_inner) that happens between workgroups that share the same batch and head_k.
924
+ bid = wid % BATCH
925
+ hkid = wid // BATCH % NUM_K_HEADS
926
+ pid = wid // (BATCH * NUM_K_HEADS) % NUM_K_PIDS
927
+
928
+ q_start = 0
929
+ k_start = 0
930
+ seqlen_q = max_seqlen_q
931
+ seqlen_k = max_seqlen_k
932
+
933
+ if IS_VARLEN:
934
+ q_start = tl.load(cu_seqlens_q + bid)
935
+ q_end = tl.load(cu_seqlens_q + bid + 1)
936
+ k_start = tl.load(cu_seqlens_k + bid)
937
+ k_end = tl.load(cu_seqlens_k + bid + 1)
938
+ seqlen_q = q_end - q_start
939
+ seqlen_k = k_end - k_start
940
+
941
+ dk = tl.zeros([BLOCK_N, BLOCK_D_MODEL_POW2], dtype=tl.float32)
942
+ dv = tl.zeros([BLOCK_N, BLOCK_D_MODEL_POW2], dtype=tl.float32)
943
+
944
+ start_n = pid * BLOCK_N
945
+
946
+ offs_k = tl.arange(0, BLOCK_D_MODEL_POW2)
947
+ offs_n = start_n + tl.arange(0, BLOCK_N)
948
+ mask_kv = offs_n[:, None] < seqlen_k
949
+ PADDED_HEAD: tl.constexpr = BLOCK_D_MODEL != BLOCK_D_MODEL_POW2
950
+ if PADDED_HEAD:
951
+ mask_kv &= offs_k < BLOCK_D_MODEL
952
+
953
+ GROUP_SIZE = NUM_Q_HEADS // NUM_K_HEADS
954
+ adj_k = (
955
+ bid * stride_kb
956
+ + hkid * stride_kh
957
+ + k_start * stride_kn
958
+ + offs_n[:, None] * stride_kn
959
+ + offs_k[None, :] * stride_kk
960
+ )
961
+ adj_v = (
962
+ bid * stride_vb
963
+ + hkid * stride_vh
964
+ + k_start * stride_vn
965
+ + offs_n[:, None] * stride_vn
966
+ + offs_k[None, :] * stride_vk
967
+ )
968
+
969
+ k = tl.load(K + adj_k, mask=mask_kv, other=0.0)
970
+ v = tl.load(V + adj_v, mask=mask_kv, other=0.0)
971
+
972
+ for hqid in range(hkid * GROUP_SIZE, hkid * GROUP_SIZE + GROUP_SIZE):
973
+ adj_q = bid * stride_qb + hqid * stride_qh + q_start * stride_qm
974
+ adj_dq = bid * stride_dqb + hqid * stride_dqh + q_start * stride_dqm
975
+
976
+ Q_ptr = Q + adj_q
977
+ DQ_ptr = DQ + adj_dq
978
+
979
+ adj_do = bid * stride_dob + hqid * stride_doh + q_start * stride_dom
980
+ DO_ptr = DO + adj_do
981
+ adj_delta = bid * stride_deltab + hqid * stride_deltah + q_start * stride_deltam
982
+ M_ptr = M + adj_delta
983
+ Delta_ptr = Delta + adj_delta
984
+
985
+ # dropout
986
+ batch_philox_offset = 0
987
+ dropout_offset = 0
988
+ if ENABLE_DROPOUT:
989
+ batch_philox_offset = (
990
+ philox_offset + bid * stride_dropoutb + hqid * stride_dropouth
991
+ )
992
+ dropout_offset = (
993
+ dropout_mask + bid * stride_dropoutb + hqid * stride_dropouth
994
+ )
995
+
996
+ if IS_FP8:
997
+ descale_q = tl.load(descale_q_ptr + bid * stride_descale_q_z + hqid)
998
+ descale_k = tl.load(descale_k_ptr + bid * stride_descale_k_z + hkid)
999
+ descale_v = tl.load(descale_v_ptr + bid * stride_descale_v_z + hkid)
1000
+ descale_do = tl.load(descale_do_ptr + bid * stride_descale_do_z + hqid)
1001
+ else:
1002
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
1003
+
1004
+ start_m = 0
1005
+ num_steps = tl.cdiv(seqlen_q, BLOCK_M)
1006
+
1007
+ dk, dv = _bwd_dkdvdq_inner(
1008
+ dk,
1009
+ dv,
1010
+ Q_ptr,
1011
+ k,
1012
+ v,
1013
+ DO_ptr,
1014
+ DQ_ptr,
1015
+ M_ptr,
1016
+ Delta_ptr,
1017
+ sm_scale,
1018
+ stride_qm,
1019
+ stride_qk,
1020
+ stride_dqm,
1021
+ stride_dqk,
1022
+ stride_dom,
1023
+ stride_dok,
1024
+ stride_dropoutm,
1025
+ stride_dropoutn,
1026
+ stride_deltam,
1027
+ dropout_p,
1028
+ philox_seed,
1029
+ batch_philox_offset,
1030
+ dropout_offset,
1031
+ seqlen_q,
1032
+ seqlen_k,
1033
+ start_n,
1034
+ start_m,
1035
+ num_steps,
1036
+ descale_q,
1037
+ descale_k,
1038
+ descale_v,
1039
+ descale_do,
1040
+ BLOCK_M,
1041
+ BLOCK_N,
1042
+ BLOCK_D_MODEL,
1043
+ BLOCK_D_MODEL_POW2,
1044
+ MASK=False,
1045
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1046
+ IS_FP8=IS_FP8,
1047
+ FP8_MAX=FP8_MAX,
1048
+ workgroup_id=wid,
1049
+ )
1050
+
1051
+ adj_dkdv = (
1052
+ bid * stride_dkb
1053
+ + hkid * stride_dkh
1054
+ + k_start * stride_dkn
1055
+ + offs_n[:, None] * stride_dkn
1056
+ + offs_k[None, :] * stride_dkk
1057
+ )
1058
+ tl.store(DV + adj_dkdv, dv, mask=mask_kv)
1059
+ dk *= sm_scale
1060
+ tl.store(DK + adj_dkdv, dk, mask=mask_kv)
1061
+
1062
+
1063
+ @functools.lru_cache(maxsize=1024)
1064
+ def _get_config():
1065
+ if not hasattr(_get_config, "_config_dict"):
1066
+ dev = arch_info.get_arch()
1067
+ _get_config._config_dict = {}
1068
+ fpath = f"{AITER_TRITON_CONFIGS_PATH}/{dev}-MHA-DEFAULT.json"
1069
+ with open(fpath, "r") as file:
1070
+ config = json.load(file)
1071
+ _get_config._config_dict = config
1072
+
1073
+ return _get_config._config_dict["bkwd_fused"]
build/torch-rocm/_kernels/mha_onekernel_bwd.py ADDED
@@ -0,0 +1,1783 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import functools
5
+ import json
6
+ import triton # type: ignore
7
+ import triton.language as tl # type: ignore
8
+ from ..utils._triton import arch_info
9
+ from ..utils.core import AITER_TRITON_CONFIGS_PATH
10
+ from ..utils._triton.mha_kernel_utils import _compute_fp8_scaling_factors
11
+ from ..utils._triton.kernel_repr import make_kernel_repr
12
+
13
+ # NOTE: triton fails to import tl.constexprs so create them here for the file
14
+ DROPOUT_USE_PYTORCH = False
15
+ DROPOUT_DUMP = False
16
+
17
+ tl_DROPOUT_USE_PYTORCH: tl.constexpr = triton.language.constexpr(DROPOUT_USE_PYTORCH)
18
+ tl_DROPOUT_DUMP: tl.constexpr = triton.language.constexpr(DROPOUT_DUMP)
19
+
20
+
21
+ # This function computes delta given output Out and gradient DO
22
+ # Here is the I/O shape:
23
+ # Out: (batch, nhead_q, max_seqlens_q, headDim)
24
+ # DO: (batch, nhead_q, max_seqlens_q, headDim)
25
+ # Delta: (batch, nheads_q, max_seqlens_q), same as softmax_lse defined at
26
+ _bwd_preprocess_repr = make_kernel_repr(
27
+ "_bwd_preprocess",
28
+ [
29
+ "BLOCK_M",
30
+ "BLOCK_D_MODEL",
31
+ "IS_VARLEN",
32
+ "IS_FP8",
33
+ ],
34
+ )
35
+
36
+
37
+ @triton.jit(repr=_bwd_preprocess_repr)
38
+ def _bwd_preprocess(
39
+ o_ptr,
40
+ do_ptr, # noqa: E741
41
+ delta_ptr,
42
+ stride_o_b,
43
+ stride_o_h,
44
+ stride_o_m,
45
+ stride_o_k,
46
+ stride_do_b,
47
+ stride_do_h,
48
+ stride_do_m,
49
+ stride_do_k,
50
+ stride_delta_b,
51
+ stride_delta_h,
52
+ stride_delta_m,
53
+ stride_descale_do_z,
54
+ cu_seqlens_q,
55
+ max_seqlen_q,
56
+ descale_do_ptr,
57
+ BLOCK_M: tl.constexpr,
58
+ BLOCK_D_MODEL: tl.constexpr,
59
+ BLOCK_D_MODEL_POW2: tl.constexpr,
60
+ IS_VARLEN: tl.constexpr,
61
+ IS_FP8: tl.constexpr,
62
+ ):
63
+ pid_m = tl.program_id(0) # seqlen
64
+ bid = tl.program_id(1) # batch
65
+ hid = tl.program_id(2) # head
66
+
67
+ # Handle varlen
68
+ q_start = 0
69
+ seqlen_q = max_seqlen_q
70
+ if IS_VARLEN:
71
+ q_start = tl.load(cu_seqlens_q + bid)
72
+ q_end = tl.load(cu_seqlens_q + bid + 1)
73
+ seqlen_q = q_end - q_start
74
+ else:
75
+ q_start = 0
76
+ seqlen_q = max_seqlen_q
77
+
78
+ # Compute offsets
79
+ offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
80
+ offs_k = tl.arange(0, BLOCK_D_MODEL_POW2)
81
+
82
+ # O and DO may have different strides (e.g. BSHD vs SBHD memory layout),
83
+ # so address each with its own strides.
84
+ offs_o = (
85
+ bid * stride_o_b
86
+ + hid * stride_o_h
87
+ + q_start * stride_o_m
88
+ + offs_m[:, None] * stride_o_m
89
+ + offs_k[None, :] * stride_o_k
90
+ )
91
+ offs_do = (
92
+ bid * stride_do_b
93
+ + hid * stride_do_h
94
+ + q_start * stride_do_m
95
+ + offs_m[:, None] * stride_do_m
96
+ + offs_k[None, :] * stride_do_k
97
+ )
98
+
99
+ # create masks
100
+ mask_m = offs_m < seqlen_q
101
+ mask = mask_m[:, None]
102
+ PADDED_HEAD: tl.constexpr = BLOCK_D_MODEL != BLOCK_D_MODEL_POW2
103
+ if PADDED_HEAD:
104
+ mask &= offs_k[None, :] < BLOCK_D_MODEL
105
+
106
+ # load [BLOCK_M, BLOCK_D_MODEL_POW2]
107
+ o = tl.load(o_ptr + offs_o, mask=mask, other=0.0)
108
+ do = tl.load(do_ptr + offs_do, mask=mask, other=0.0)
109
+
110
+ # compute and write-back to delta
111
+ if IS_FP8:
112
+ descale_do = tl.load(descale_do_ptr + bid * stride_descale_do_z + hid)
113
+
114
+ # NOTE: do is in the fp8 range and o is not in fp8
115
+ delta = tl.sum(o.to(tl.float32) * (do.to(tl.float32) * descale_do), axis=1)
116
+ else:
117
+ delta = tl.sum(o.to(tl.float32) * do.to(tl.float32), axis=1)
118
+
119
+ offs_delta = (
120
+ bid * stride_delta_b
121
+ + hid * stride_delta_h
122
+ + q_start * stride_delta_m
123
+ + offs_m * stride_delta_m
124
+ )
125
+ tl.store(delta_ptr + offs_delta, delta, mask=mask_m)
126
+
127
+
128
+ # The main inner-loop logic for computing dK and dV.
129
+ @triton.jit
130
+ def _bwd_dkdv_inner(
131
+ dk, # output
132
+ dk_pe, # optional output, pass None for non-PE case
133
+ dv, # output
134
+ Q,
135
+ k,
136
+ k_pe,
137
+ v,
138
+ DO,
139
+ M,
140
+ D,
141
+ sm_scale, # input tensor
142
+ stride_qm,
143
+ stride_qk,
144
+ stride_dom,
145
+ stride_dok,
146
+ stride_dropoutm,
147
+ stride_dropoutn,
148
+ stride_deltam,
149
+ BLOCK_M: tl.constexpr, # 16
150
+ BLOCK_N: tl.constexpr, # 128
151
+ HEAD_DIM: tl.constexpr, #
152
+ ACTUAL_HEAD_DIM: tl.constexpr, #
153
+ PE_HEAD_DIM: tl.constexpr,
154
+ dropout_p,
155
+ philox_seed,
156
+ batch_philox_offset,
157
+ dropout_offset,
158
+ alibi_slope,
159
+ seqlen_q,
160
+ seqlen_k, # max sequence length for q and k
161
+ # Filled in by the wrapper.
162
+ start_n,
163
+ start_m,
164
+ num_steps, # iteration numbers
165
+ descale_q,
166
+ descale_k,
167
+ descale_v,
168
+ descale_do, # fp8 descale factors from user
169
+ MASK: tl.constexpr, # causal masking, only apply to tiles on mask diagonal
170
+ ENABLE_DROPOUT: tl.constexpr, # activate dropout
171
+ USE_ALIBI: tl.constexpr,
172
+ USE_EXP2: tl.constexpr, # activate exp2
173
+ IS_FP8: tl.constexpr,
174
+ FP8_MAX: tl.constexpr,
175
+ DEBUG_TRITON: tl.constexpr,
176
+ DEBUG_TRITON_DETAIL: tl.constexpr,
177
+ SLIDING_WINDOW: tl.constexpr,
178
+ ):
179
+ # if HEAD_DIM is padded
180
+ PADDED_HEAD: tl.constexpr = ACTUAL_HEAD_DIM != HEAD_DIM
181
+ HAS_PE: tl.constexpr = PE_HEAD_DIM > 0
182
+ delta_qk = seqlen_q - seqlen_k
183
+ offs_m = start_m + tl.arange(0, BLOCK_M) # start_m + (0, 15)
184
+ offs_n = start_n + tl.arange(0, BLOCK_N) # start_m + (0, 127)
185
+ offs_k = tl.arange(0, HEAD_DIM)
186
+ if HAS_PE:
187
+ offs_k_pe = HEAD_DIM + tl.arange(0, PE_HEAD_DIM)
188
+ # mask to make sure not OOB of seqlen_q
189
+ mask_n = offs_n < seqlen_k
190
+ # Q and DO are (seqlen_q, head_dim)
191
+ # qT_ptrs = (1, BLOCK_M) + (HEAD_DIM, 1), transpose of q
192
+ qT_ptrs = Q + offs_m[None, :] * stride_qm + offs_k[:, None] * stride_qk
193
+ if HAS_PE:
194
+ qT_pe_ptrs = Q + offs_m[None, :] * stride_qm + offs_k_pe[:, None] * stride_qk
195
+ # do_ptrs = (BLOCK_M, 1) + (1, HEAD_DIM), NOT transposed
196
+ do_ptrs = DO + offs_m[:, None] * stride_dom + offs_k[None, :] * stride_dok
197
+ # BLOCK_N must be a multiple of BLOCK_M, otherwise the code wouldn't work.
198
+ tl.static_assert(BLOCK_N % BLOCK_M == 0)
199
+ curr_m = start_m
200
+ step_m = BLOCK_M
201
+ curr_philox_offset = batch_philox_offset
202
+ curr_dropout_offset = dropout_offset
203
+ RCP_LN2: tl.constexpr = 1.4426950408889634 # = 1.0 / ln(2)
204
+
205
+ for blk_idx in range(num_steps):
206
+ if DEBUG_TRITON:
207
+ print(f"iter {blk_idx}: curr_m = {curr_m}") # noqa: E701
208
+ offs_m = curr_m + tl.arange(0, BLOCK_M)
209
+ # update the mask because offs_m advanced
210
+ mask_m = offs_m < seqlen_q
211
+ mask_qT = mask_m[None, :]
212
+ mask_do = mask_m[:, None]
213
+ mask_nm = mask_n[:, None] & (offs_m[None, :] < seqlen_q)
214
+ if PADDED_HEAD:
215
+ mask_qT &= offs_k[:, None] < ACTUAL_HEAD_DIM
216
+ mask_do &= offs_k[None, :] < ACTUAL_HEAD_DIM
217
+ qT = tl.load(qT_ptrs, mask=mask_qT, other=0.0)
218
+ if HAS_PE:
219
+ qT_pe = tl.load(qT_pe_ptrs, mask=mask_qT, other=0.0)
220
+ # generate dropout mask
221
+ if ENABLE_DROPOUT:
222
+ # NOTE: dropout is transposed because it is used to mask pT
223
+ philox_offs = (
224
+ curr_philox_offset
225
+ + offs_m[None, :] * stride_dropoutm
226
+ + offs_n[:, None] * stride_dropoutn
227
+ )
228
+ if tl_DROPOUT_USE_PYTORCH:
229
+ dropout_offs = (
230
+ offs_m[None, :] * stride_dropoutm
231
+ + offs_n[:, None] * stride_dropoutn
232
+ )
233
+ dropout_mask = tl.load(curr_dropout_offset + dropout_offs, mask=mask_nm)
234
+ else:
235
+ rand_vals = tl.rand(philox_seed, philox_offs)
236
+ dropout_mask = rand_vals > dropout_p
237
+ dropout_scale = 1.0 / (1 - dropout_p)
238
+ # Load m before computing qk to reduce pipeline stall.
239
+ m = tl.load(M + offs_m * stride_deltam, mask=mask_m, other=0.0)
240
+ if IS_FP8:
241
+ qkT = tl.dot(k, qT) * descale_q * descale_k
242
+ else:
243
+ qkT = tl.dot(k, qT)
244
+ if HAS_PE:
245
+ qkT = tl.dot(k_pe, qT_pe, acc=qkT)
246
+ qkT_scaled = qkT * sm_scale
247
+
248
+ if USE_ALIBI:
249
+ relative_pos_block = offs_n[:, None] + seqlen_q - seqlen_k - offs_m[None, :]
250
+ alibi_block = -1 * alibi_slope * tl.abs(relative_pos_block)
251
+ qkT_scaled += alibi_block
252
+
253
+ if DEBUG_TRITON_DETAIL:
254
+ if start_n == 256:
255
+ print(f"qT: {qT.shape}\n", qT)
256
+ print(f"k: {k.shape}\n", k)
257
+ print(f"qkT scaled: {qkT.shape}\n", qkT_scaled)
258
+ # TODO: remove the scaling of m later when we removed re-scaling in fwd
259
+ if USE_EXP2:
260
+ pT = tl.math.exp2(qkT_scaled * RCP_LN2 - m[None, :] * RCP_LN2)
261
+ else:
262
+ pT = tl.math.exp(qkT_scaled - m[None, :])
263
+
264
+ # Autoregressive masking.
265
+ if MASK:
266
+ # offset offs_m with delta_qk since the causal mask starts at
267
+ # bottom right of the (seqlen_q, seqlen_k) matrix
268
+ causal_mask = (offs_m[None, :] - delta_qk) >= offs_n[:, None]
269
+ mask = causal_mask & mask_nm
270
+ if DEBUG_TRITON_DETAIL:
271
+ if start_n == 256:
272
+ print(f"causal_mask: {causal_mask.shape}\n", causal_mask)
273
+ print(
274
+ f"qkT after causal: {qkT.shape}\n",
275
+ tl.where(causal_mask, qkT * sm_scale, 0.0),
276
+ )
277
+ pT = tl.where(mask, pT, 0.0)
278
+ if SLIDING_WINDOW > 0:
279
+ window_mask = offs_n[:, None] >= (
280
+ offs_m[None, :] - delta_qk - SLIDING_WINDOW
281
+ )
282
+ pT = tl.where(window_mask, pT, 0.0)
283
+ do = tl.load(do_ptrs, mask=mask_do, other=0.0)
284
+ # Compute dV.
285
+ if ENABLE_DROPOUT:
286
+ pT_dropout = tl.where(dropout_mask, pT, 0.0) * dropout_scale
287
+ if IS_FP8:
288
+ scale_p_dropout, descale_p_dropout = _compute_fp8_scaling_factors(
289
+ pT_dropout, FP8_MAX
290
+ )
291
+ dv += (
292
+ tl.dot((pT_dropout * scale_p_dropout).to(do.type.element_ty), do)
293
+ * descale_p_dropout
294
+ * descale_do
295
+ )
296
+ else:
297
+ dv = tl.dot(pT_dropout.to(do.type.element_ty), do, acc=dv)
298
+ else:
299
+ if IS_FP8:
300
+ scale_pT, descale_pT = _compute_fp8_scaling_factors(pT, FP8_MAX)
301
+ dv += (
302
+ tl.dot((pT * scale_pT).to(do.type.element_ty), do)
303
+ * descale_pT
304
+ * descale_do
305
+ )
306
+ else:
307
+ dv = tl.dot(pT.to(do.type.element_ty), do, acc=dv)
308
+
309
+ if DEBUG_TRITON_DETAIL:
310
+ if start_n == 256:
311
+ print(f"pT: {pT.shape}\n", pT)
312
+ # D (= delta) is pre-divided by ds_scale.
313
+ Di = tl.load(D + offs_m * stride_deltam, mask=mask_m)
314
+ # Compute dP and dS.
315
+ if IS_FP8:
316
+ dpT = tl.dot(v, tl.trans(do)) * descale_v * descale_do
317
+ else:
318
+ dpT = tl.dot(v, tl.trans(do))
319
+ if ENABLE_DROPOUT:
320
+ dpT = tl.where(dropout_mask, dpT, 0.0) * dropout_scale
321
+ delta_i = Di[None, :]
322
+ dsT = pT * (dpT - delta_i)
323
+ if IS_FP8:
324
+ scale_dsT, descale_dsT = _compute_fp8_scaling_factors(dsT, FP8_MAX)
325
+ dk += (
326
+ tl.dot((dsT * scale_dsT).to(qT.type.element_ty), tl.trans(qT))
327
+ * descale_dsT
328
+ * descale_q
329
+ )
330
+ else:
331
+ dk = tl.dot(dsT.to(qT.type.element_ty), tl.trans(qT), acc=dk)
332
+ if HAS_PE:
333
+ dk_pe = tl.dot(
334
+ dsT.to(qT_pe.type.element_ty), tl.trans(qT_pe), acc=dk_pe
335
+ )
336
+ # Increment pointers.
337
+ curr_m += step_m
338
+ qT_ptrs += step_m * stride_qm
339
+ if HAS_PE:
340
+ qT_pe_ptrs += step_m * stride_qm
341
+ do_ptrs += step_m * stride_dom
342
+ return dk, dk_pe, dv
343
+
344
+
345
+ # the main inner-loop logic for computing dQ
346
+ @triton.jit
347
+ def _bwd_dq_inner(
348
+ dq, # output
349
+ dq_pe, # optional output, pass None for non-PE case
350
+ q,
351
+ q_pe,
352
+ K,
353
+ V,
354
+ do,
355
+ m,
356
+ Di, # D (= delta) is pre-divided by ds_scale.
357
+ sm_scale, # input
358
+ # shared by Q/K/V.
359
+ stride_qm,
360
+ stride_qk,
361
+ stride_kn,
362
+ stride_kk,
363
+ stride_vn,
364
+ stride_vk,
365
+ stride_dropoutm,
366
+ stride_dropoutn, # stride for dropout
367
+ seqlen_q,
368
+ seqlen_k, #
369
+ BLOCK_M2: tl.constexpr, #
370
+ BLOCK_N2: tl.constexpr, #
371
+ HEAD_DIM: tl.constexpr,
372
+ ACTUAL_HEAD_DIM: tl.constexpr, #
373
+ PE_HEAD_DIM: tl.constexpr,
374
+ dropout_p,
375
+ philox_seed,
376
+ batch_philox_offset,
377
+ dropout_offset,
378
+ alibi_slope,
379
+ # Filled in by the wrapper.
380
+ start_m,
381
+ start_n,
382
+ end_n,
383
+ num_steps, #
384
+ descale_q,
385
+ descale_k,
386
+ descale_v,
387
+ descale_do, # fp8 descale factors from user
388
+ MASK: tl.constexpr,
389
+ ENABLE_DROPOUT: tl.constexpr,
390
+ USE_ALIBI: tl.constexpr,
391
+ USE_EXP2: tl.constexpr,
392
+ IS_FP8: tl.constexpr,
393
+ FP8_MAX: tl.constexpr,
394
+ DEBUG_TRITON: tl.constexpr,
395
+ DEBUG_TRITON_DETAIL: tl.constexpr,
396
+ SLIDING_WINDOW: tl.constexpr,
397
+ ENABLE_SINK: tl.constexpr,
398
+ ):
399
+ # if HEAD_DIM is padded
400
+ PADDED_HEAD: tl.constexpr = ACTUAL_HEAD_DIM != HEAD_DIM
401
+ HAS_PE: tl.constexpr = PE_HEAD_DIM > 0
402
+ delta_qk = seqlen_q - seqlen_k
403
+ offs_m = start_m + tl.arange(0, BLOCK_M2)
404
+ offs_n = start_n + tl.arange(0, BLOCK_N2)
405
+ offs_k = tl.arange(0, HEAD_DIM)
406
+ if HAS_PE:
407
+ offs_k_pe = HEAD_DIM + tl.arange(0, PE_HEAD_DIM)
408
+
409
+ # mask to make sure not OOB of seqlen_q
410
+ mask_m = offs_m < seqlen_q
411
+
412
+ kT_ptrs = K + offs_n[None, :] * stride_kn + offs_k[:, None] * stride_kk
413
+ if HAS_PE:
414
+ kT_pe_ptrs = K + offs_n[None, :] * stride_kn + offs_k_pe[:, None] * stride_kk
415
+ vT_ptrs = V + offs_n[None, :] * stride_vn + offs_k[:, None] * stride_vk
416
+ # BLOCK_M2 must be a multiple of BLOCK_N2, otherwise the code wouldn't work.
417
+ tl.static_assert(BLOCK_M2 % BLOCK_N2 == 0)
418
+ curr_n = start_n
419
+ step_n = BLOCK_N2
420
+ curr_philox_offset = batch_philox_offset
421
+ curr_dropout_offset = dropout_offset
422
+ RCP_LN2: tl.constexpr = 1.4426950408889634 # = 1.0 / ln(2)
423
+ delta_recomp = tl.zeros([BLOCK_M2], dtype=tl.float32)
424
+ for blk_idx in range(num_steps):
425
+ if DEBUG_TRITON:
426
+ print(f"iter {blk_idx}: curr_n = {curr_n}") # noqa: E701
427
+ offs_n = curr_n + tl.arange(0, BLOCK_N2)
428
+ # end_n is needed because the end of causal True might not be perfectly
429
+ # aligned with the end of the block
430
+ mask_n = offs_n < end_n
431
+ if DEBUG_TRITON_DETAIL:
432
+ print(
433
+ f"start_n = {start_n}, end_n = {end_n}, offs_n: {offs_n.shape}\n{offs_n}"
434
+ ) # noqa: E701
435
+ if DEBUG_TRITON_DETAIL:
436
+ print(f"mask_n: {mask_n.shape}\n{mask_n}") # noqa: E701
437
+ mask_kT = mask_n[None, :]
438
+ mask_mn = mask_m[:, None] & (offs_n[None, :] < end_n)
439
+ if PADDED_HEAD:
440
+ mask_kT &= offs_k[:, None] < ACTUAL_HEAD_DIM
441
+
442
+ kT = tl.load(kT_ptrs, mask=mask_kT, other=0.0)
443
+ if HAS_PE:
444
+ kT_pe = tl.load(kT_pe_ptrs, mask=mask_kT, other=0.0)
445
+ vT = tl.load(vT_ptrs, mask=mask_kT, other=0.0)
446
+
447
+ if ENABLE_DROPOUT:
448
+ # NOTE: dropout is transposed because it is used to mask pT
449
+ philox_offs = (
450
+ curr_philox_offset
451
+ + offs_m[:, None] * stride_dropoutm
452
+ + offs_n[None, :] * stride_dropoutn
453
+ )
454
+ if tl_DROPOUT_USE_PYTORCH:
455
+ dropout_offs = (
456
+ offs_m[:, None] * stride_dropoutm
457
+ + offs_n[None, :] * stride_dropoutn
458
+ )
459
+ dropout_mask = tl.load(curr_dropout_offset + dropout_offs, mask=mask_mn)
460
+ else:
461
+ rand_vals = tl.rand(philox_seed, philox_offs)
462
+ dropout_mask = rand_vals > dropout_p
463
+ dropout_scale = 1 / (1 - dropout_p)
464
+
465
+ if IS_FP8:
466
+ qk = tl.dot(q, kT) * descale_q * descale_k
467
+ else:
468
+ qk = tl.dot(q, kT)
469
+ if HAS_PE:
470
+ qk = tl.dot(q_pe, kT_pe, acc=qk)
471
+ qk_scaled = qk * sm_scale
472
+
473
+ if USE_ALIBI:
474
+ relative_pos_block = offs_m[:, None] + seqlen_k - seqlen_q - offs_n[None, :]
475
+ alibi_block = -1 * alibi_slope * tl.abs(relative_pos_block)
476
+ qk_scaled += alibi_block
477
+
478
+ if DEBUG_TRITON_DETAIL:
479
+ print(f"qk scaled: {qk.shape}\n", qk_scaled) # noqa: E701
480
+ if USE_EXP2:
481
+ p = tl.math.exp2(qk_scaled * RCP_LN2 - m * RCP_LN2)
482
+ else:
483
+ p = tl.math.exp(qk_scaled - m)
484
+
485
+ # Autoregressive masking.
486
+ if MASK:
487
+ causal_mask = (offs_m[:, None] - delta_qk) >= offs_n[None, :]
488
+ mask = causal_mask & mask_mn
489
+ p = tl.where(mask, p, 0.0)
490
+ if SLIDING_WINDOW > 0:
491
+ window_mask = offs_n[None, :] >= (
492
+ offs_m[:, None] - delta_qk - SLIDING_WINDOW
493
+ )
494
+ p = tl.where(window_mask, p, 0.0)
495
+ # Compute dP and dS.
496
+ if IS_FP8:
497
+ dp = tl.dot(do, vT) * descale_do * descale_v
498
+ else:
499
+ dp = tl.dot(do, vT)
500
+ if ENABLE_DROPOUT:
501
+ dp = tl.where(dropout_mask, dp, 0.0) * dropout_scale
502
+ if ENABLE_SINK:
503
+ # Equal to do_i . o_i but skips the bf16 round-trip through forward `o`.
504
+ delta_recomp += tl.sum(p * dp, axis=1)
505
+ delta_i = Di[:, None]
506
+ ds = p * (dp - delta_i)
507
+ # Compute dQ.
508
+ # NOTE: We need to de-scale dq in the end, because kT was pre-scaled.
509
+ if IS_FP8:
510
+ scale_ds, descale_ds = _compute_fp8_scaling_factors(ds, FP8_MAX)
511
+ dq += (
512
+ tl.dot((ds * scale_ds).to(kT.type.element_ty), tl.trans(kT))
513
+ * descale_ds
514
+ * descale_k
515
+ )
516
+ else:
517
+ dq = tl.dot(ds.to(kT.type.element_ty), tl.trans(kT), acc=dq)
518
+ if HAS_PE:
519
+ dq_pe = tl.dot(ds.to(kT_pe.type.element_ty), tl.trans(kT_pe), acc=dq_pe)
520
+ # Increment pointers.
521
+ curr_n += step_n
522
+ kT_ptrs += step_n * stride_kn
523
+ if HAS_PE:
524
+ kT_pe_ptrs += step_n * stride_kn
525
+ vT_ptrs += step_n * stride_vn
526
+ return dq, dq_pe, delta_recomp
527
+
528
+
529
+ _bwd_kernel_causal_repr = make_kernel_repr(
530
+ "bwd_kernel_causal",
531
+ [
532
+ "BLOCK_M1",
533
+ "BLOCK_N1",
534
+ "BLOCK_M2",
535
+ "BLOCK_N2",
536
+ "BLK_SLICE_FACTOR",
537
+ "HEAD_DIM",
538
+ "ENABLE_DROPOUT",
539
+ "IS_VARLEN",
540
+ "USE_ALIBI",
541
+ "USE_EXP2",
542
+ "IS_FP8",
543
+ "USE_INT64_STRIDES",
544
+ "ENABLE_SINK",
545
+ "SLIDING_WINDOW",
546
+ ],
547
+ )
548
+
549
+
550
+ @triton.jit(repr=_bwd_kernel_causal_repr)
551
+ def bwd_kernel_causal( # grid = (tl.cdiv(max_seqlen_q // BLOCK_M2), batch, nheads_q)
552
+ Q,
553
+ K,
554
+ V,
555
+ Sink,
556
+ sm_scale,
557
+ DO,
558
+ DQ,
559
+ DK,
560
+ DV,
561
+ DSink,
562
+ M,
563
+ Delta,
564
+ stride_qb_in,
565
+ stride_qh_in,
566
+ stride_qm_in,
567
+ stride_qd_in,
568
+ stride_kb_in,
569
+ stride_kh_in,
570
+ stride_kn_in,
571
+ stride_kd_in,
572
+ stride_vb_in,
573
+ stride_vh_in,
574
+ stride_vn_in,
575
+ stride_vd_in,
576
+ stride_dqb_in,
577
+ stride_dqh_in,
578
+ stride_dqm_in,
579
+ stride_dqd_in,
580
+ stride_dkb_in,
581
+ stride_dkh_in,
582
+ stride_dkn_in,
583
+ stride_dkd_in,
584
+ stride_dvb_in,
585
+ stride_dvh_in,
586
+ stride_dvn_in,
587
+ stride_dvd_in,
588
+ stride_deltab_in,
589
+ stride_deltah_in,
590
+ stride_deltam_in,
591
+ stride_dob_in,
592
+ stride_doh_in,
593
+ stride_dom_in,
594
+ stride_dod_in,
595
+ stride_dropoutb_in,
596
+ stride_dropouth_in,
597
+ stride_dropoutm_in,
598
+ stride_dropoutn_in,
599
+ stride_descale_q_z_in,
600
+ stride_descale_k_z_in,
601
+ stride_descale_v_z_in,
602
+ stride_descale_do_z_in,
603
+ stride_az_in,
604
+ stride_ah_in,
605
+ HQ,
606
+ HK,
607
+ cu_seqlens_q,
608
+ cu_seqlens_k,
609
+ max_seqlen_q,
610
+ max_seqlen_k,
611
+ Dropout_mask,
612
+ dropout_p,
613
+ philox_seed,
614
+ philox_offset_base_in,
615
+ Alibi_slopes,
616
+ Descale_q,
617
+ Descale_k,
618
+ Descale_v,
619
+ Descale_do,
620
+ BLOCK_M1: tl.constexpr,
621
+ BLOCK_N1: tl.constexpr,
622
+ BLOCK_M2: tl.constexpr,
623
+ BLOCK_N2: tl.constexpr,
624
+ BLK_SLICE_FACTOR: tl.constexpr,
625
+ HEAD_DIM: tl.constexpr,
626
+ ACTUAL_HEAD_DIM: tl.constexpr,
627
+ PE_HEAD_DIM: tl.constexpr,
628
+ ENABLE_DROPOUT: tl.constexpr,
629
+ IS_VARLEN: tl.constexpr,
630
+ USE_ALIBI: tl.constexpr,
631
+ USE_EXP2: tl.constexpr,
632
+ IS_FP8: tl.constexpr,
633
+ FP8_MAX: tl.constexpr,
634
+ DEBUG_TRITON: tl.constexpr,
635
+ DEBUG_TRITON_DETAIL: tl.constexpr,
636
+ USE_INT64_STRIDES: tl.constexpr,
637
+ ENABLE_SINK: tl.constexpr,
638
+ SLIDING_WINDOW: tl.constexpr,
639
+ ):
640
+ if USE_INT64_STRIDES:
641
+ stride_qb = tl.cast(stride_qb_in, tl.int64)
642
+ stride_qh = tl.cast(stride_qh_in, tl.int64)
643
+ stride_qm = tl.cast(stride_qm_in, tl.int64)
644
+ stride_qd = tl.cast(stride_qd_in, tl.int64)
645
+ stride_kb = tl.cast(stride_kb_in, tl.int64)
646
+ stride_kh = tl.cast(stride_kh_in, tl.int64)
647
+ stride_kn = tl.cast(stride_kn_in, tl.int64)
648
+ stride_kd = tl.cast(stride_kd_in, tl.int64)
649
+ stride_vb = tl.cast(stride_vb_in, tl.int64)
650
+ stride_vh = tl.cast(stride_vh_in, tl.int64)
651
+ stride_vn = tl.cast(stride_vn_in, tl.int64)
652
+ stride_vd = tl.cast(stride_vd_in, tl.int64)
653
+ stride_dqb = tl.cast(stride_dqb_in, tl.int64)
654
+ stride_dqh = tl.cast(stride_dqh_in, tl.int64)
655
+ stride_dqm = tl.cast(stride_dqm_in, tl.int64)
656
+ stride_dqd = tl.cast(stride_dqd_in, tl.int64)
657
+ stride_dkb = tl.cast(stride_dkb_in, tl.int64)
658
+ stride_dkh = tl.cast(stride_dkh_in, tl.int64)
659
+ stride_dkn = tl.cast(stride_dkn_in, tl.int64)
660
+ stride_dkd = tl.cast(stride_dkd_in, tl.int64)
661
+ stride_dvb = tl.cast(stride_dvb_in, tl.int64)
662
+ stride_dvh = tl.cast(stride_dvh_in, tl.int64)
663
+ stride_dvn = tl.cast(stride_dvn_in, tl.int64)
664
+ stride_dvd = tl.cast(stride_dvd_in, tl.int64)
665
+ stride_deltab = tl.cast(stride_deltab_in, tl.int64)
666
+ stride_deltah = tl.cast(stride_deltah_in, tl.int64)
667
+ stride_deltam = tl.cast(stride_deltam_in, tl.int64)
668
+ stride_dob = tl.cast(stride_dob_in, tl.int64)
669
+ stride_doh = tl.cast(stride_doh_in, tl.int64)
670
+ stride_dom = tl.cast(stride_dom_in, tl.int64)
671
+ stride_dod = tl.cast(stride_dod_in, tl.int64)
672
+ philox_offset_base = tl.cast(philox_offset_base_in, tl.int64)
673
+ stride_dropoutb = tl.cast(stride_dropoutb_in, tl.int64)
674
+ stride_dropouth = tl.cast(stride_dropouth_in, tl.int64)
675
+ stride_dropoutm = tl.cast(stride_dropoutm_in, tl.int64)
676
+ stride_dropoutn = tl.cast(stride_dropoutn_in, tl.int64)
677
+ if IS_FP8:
678
+ stride_descale_q_z = tl.cast(stride_descale_q_z_in, tl.int64)
679
+ stride_descale_k_z = tl.cast(stride_descale_k_z_in, tl.int64)
680
+ stride_descale_v_z = tl.cast(stride_descale_v_z_in, tl.int64)
681
+ stride_descale_do_z = tl.cast(stride_descale_do_z_in, tl.int64)
682
+ stride_az = tl.cast(stride_az_in, tl.int64)
683
+ stride_ah = tl.cast(stride_ah_in, tl.int64)
684
+ else:
685
+ stride_qb = stride_qb_in
686
+ stride_qh = stride_qh_in
687
+ stride_qm = stride_qm_in
688
+ stride_qd = stride_qd_in
689
+ stride_kb = stride_kb_in
690
+ stride_kh = stride_kh_in
691
+ stride_kn = stride_kn_in
692
+ stride_kd = stride_kd_in
693
+ stride_vb = stride_vb_in
694
+ stride_vh = stride_vh_in
695
+ stride_vn = stride_vn_in
696
+ stride_vd = stride_vd_in
697
+ stride_dqb = stride_dqb_in
698
+ stride_dqh = stride_dqh_in
699
+ stride_dqm = stride_dqm_in
700
+ stride_dqd = stride_dqd_in
701
+ stride_dkb = stride_dkb_in
702
+ stride_dkh = stride_dkh_in
703
+ stride_dkn = stride_dkn_in
704
+ stride_dkd = stride_dkd_in
705
+ stride_dvb = stride_dvb_in
706
+ stride_dvh = stride_dvh_in
707
+ stride_dvn = stride_dvn_in
708
+ stride_dvd = stride_dvd_in
709
+ stride_deltab = stride_deltab_in
710
+ stride_deltah = stride_deltah_in
711
+ stride_deltam = stride_deltam_in
712
+ stride_dob = stride_dob_in
713
+ stride_doh = stride_doh_in
714
+ stride_dom = stride_dom_in
715
+ stride_dod = stride_dod_in
716
+ philox_offset_base = philox_offset_base_in
717
+ stride_dropoutb = stride_dropoutb_in
718
+ stride_dropouth = stride_dropouth_in
719
+ stride_dropoutm = stride_dropoutm_in
720
+ stride_dropoutn = stride_dropoutn_in
721
+ stride_descale_q_z = stride_descale_q_z_in
722
+ stride_descale_k_z = stride_descale_k_z_in
723
+ stride_descale_v_z = stride_descale_v_z_in
724
+ stride_descale_do_z = stride_descale_do_z_in
725
+ stride_az = stride_az_in
726
+ stride_ah = stride_ah_in
727
+
728
+ # program ids
729
+ hkid = tl.program_id(0)
730
+ pid = tl.program_id(1)
731
+ bid = tl.program_id(2)
732
+ if DEBUG_TRITON:
733
+ print(f"\npid: {pid}, bid: {bid}, hkid: {hkid}") # noqa: E701
734
+ # figure out varlen start and end
735
+ q_start = 0
736
+ k_start = 0
737
+ seqlen_q = max_seqlen_q
738
+ seqlen_k = max_seqlen_k
739
+ if IS_VARLEN:
740
+ # Compute actual sequence lengths
741
+ q_start = tl.load(cu_seqlens_q + bid)
742
+ q_end = tl.load(cu_seqlens_q + bid + 1)
743
+ k_start = tl.load(cu_seqlens_k + bid)
744
+ k_end = tl.load(cu_seqlens_k + bid + 1)
745
+ seqlen_q = q_end - q_start
746
+ seqlen_k = k_end - k_start
747
+
748
+ delta_qk = seqlen_q - seqlen_k
749
+ if DEBUG_TRITON:
750
+ print(f"delta_qk = {delta_qk}") # noqa: E701
751
+ PADDED_HEAD: tl.constexpr = ACTUAL_HEAD_DIM != HEAD_DIM
752
+ HAS_PE: tl.constexpr = PE_HEAD_DIM > 0
753
+ offs_d = tl.arange(0, HEAD_DIM)
754
+ if HAS_PE:
755
+ offs_d_pe = HEAD_DIM + tl.arange(0, PE_HEAD_DIM)
756
+ GROUP_SIZE: tl.constexpr = HQ // HK
757
+
758
+ # align the delta_qk
759
+ start_n = pid * BLOCK_N1
760
+ if start_n < seqlen_k:
761
+ # This section does dk and dv
762
+ dk = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
763
+ if HAS_PE:
764
+ dk_pe = tl.zeros([BLOCK_N1, PE_HEAD_DIM], dtype=tl.float32)
765
+ else:
766
+ # Couldn't assign None to dk_pe because _bwd_dkdv_inner can't return None.
767
+ dk_pe = dk
768
+ dv = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
769
+
770
+ # q > k: diretcly skip all the way until the start of causal block
771
+ start_delta_q_gt_k = delta_qk
772
+ # q < k: some blocks will have no Masked block, other needs to re-calc
773
+ # starting position
774
+ # delta_qk is negative so flip it, only multiple of BLOCK_N can skip the
775
+ # masked op
776
+ num_blocks_skip = -delta_qk // BLOCK_N1
777
+ delta_aligned = (num_blocks_skip + 1) * BLOCK_N1 + delta_qk
778
+ start_delta_q_lt_k = delta_aligned // BLOCK_M1 * BLOCK_M1
779
+ if delta_qk >= 0:
780
+ start_delta = delta_qk
781
+ if DEBUG_TRITON:
782
+ print(
783
+ f"q >= k: start_delta = delta_qk aligned to BLOCK_M = {start_delta_q_gt_k}"
784
+ ) # noqa: E701
785
+ else:
786
+ start_delta = start_delta_q_lt_k
787
+ if DEBUG_TRITON:
788
+ print(
789
+ f"q < k: start_delta = residue btw multiple BLOCK_N and delta_qk = {delta_aligned} = aligned to BLOCK_M = {start_delta_q_lt_k}"
790
+ ) # noqa: E701
791
+
792
+ offs_n = start_n + tl.arange(0, BLOCK_N1)
793
+ # Mask for loading K and V
794
+ mask_kv = offs_n[:, None] < seqlen_k
795
+ if PADDED_HEAD:
796
+ mask_d = offs_d < ACTUAL_HEAD_DIM
797
+ mask_kv &= mask_d[None, :]
798
+
799
+ # K/V tensors not changed for the group
800
+ adj_k = (
801
+ bid * stride_kb
802
+ + hkid * stride_kh
803
+ + k_start * stride_kn
804
+ + offs_n[:, None] * stride_kn
805
+ + offs_d[None, :] * stride_kd
806
+ )
807
+ if HAS_PE:
808
+ adj_k_pe = (
809
+ bid * stride_kb
810
+ + hkid * stride_kh
811
+ + k_start * stride_kn
812
+ + offs_n[:, None] * stride_kn
813
+ + offs_d_pe[None, :] * stride_kd
814
+ )
815
+ adj_v = (
816
+ bid * stride_vb
817
+ + hkid * stride_vh
818
+ + k_start * stride_vn
819
+ + offs_n[:, None] * stride_vn
820
+ + offs_d[None, :] * stride_vd
821
+ )
822
+ # load K and V: they stay in SRAM throughout the inner loop.
823
+ k = tl.load(K + adj_k, mask=mask_kv, other=0.0)
824
+ if HAS_PE:
825
+ k_pe = tl.load(K + adj_k_pe, mask=mask_kv, other=0.0)
826
+ else:
827
+ k_pe = None
828
+ v = tl.load(V + adj_v, mask=mask_kv, other=0.0)
829
+ # If MQA / GQA, set the K and V head offsets appropriately.
830
+ # hqid = hkid
831
+ for hqid in range(hkid * GROUP_SIZE, hkid * GROUP_SIZE + GROUP_SIZE):
832
+ if delta_qk >= 0:
833
+ start_m = start_n + start_delta
834
+ len_m = BLOCK_N1
835
+ else:
836
+ start_m = max(start_n + delta_qk, 0)
837
+ start_m = start_m // BLOCK_M1 * BLOCK_M1
838
+ # because we might shift the masked blocks up, we are deeper into
839
+ # the masked out region, so we would potentially increase the total
840
+ # steps with masked operation to get out of it
841
+ residue_m = max(start_n + delta_qk - start_m, 0)
842
+ len_m = BLOCK_N1 + residue_m
843
+ if DEBUG_TRITON:
844
+ print(f"residue_m = {residue_m}") # noqa: E701
845
+
846
+ # offset input and output tensor by batch and Q/K heads
847
+ adj_q = bid * stride_qb + hqid * stride_qh + q_start * stride_qm
848
+ Q_ptr = Q + adj_q
849
+ adj_do = bid * stride_dob + hqid * stride_doh + q_start * stride_dom
850
+ DO_ptr = DO + adj_do
851
+ adj_delta = (
852
+ bid * stride_deltab + hqid * stride_deltah + q_start * stride_deltam
853
+ )
854
+ M_ptr = M + adj_delta
855
+ Delta_ptr = Delta + adj_delta
856
+
857
+ if USE_ALIBI:
858
+ alibi_offset = bid * stride_az + hqid * stride_ah
859
+ alibi_slope = tl.load(Alibi_slopes + alibi_offset)
860
+ else:
861
+ alibi_slope = None
862
+
863
+ # batch_philox_offset is the ACTUALLY dropout offset
864
+ # dropout_offset is for debug purpose and will be removed later
865
+ batch_philox_offset = 0
866
+ dropout_offset = 0
867
+ if ENABLE_DROPOUT:
868
+ batch_philox_offset = (
869
+ philox_offset_base + bid * stride_dropoutb + hqid * stride_dropouth
870
+ )
871
+ dropout_offset = (
872
+ Dropout_mask + bid * stride_dropoutb + hqid * stride_dropouth
873
+ )
874
+
875
+ if IS_FP8:
876
+ descale_q = tl.load(Descale_q + bid * stride_descale_q_z + hqid)
877
+ descale_k = tl.load(Descale_k + bid * stride_descale_k_z + hkid)
878
+ descale_v = tl.load(Descale_v + bid * stride_descale_v_z + hkid)
879
+ descale_do = tl.load(Descale_do + bid * stride_descale_do_z + hqid)
880
+ else:
881
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
882
+
883
+ MASK_BLOCK_M1: tl.constexpr = BLOCK_M1 // BLK_SLICE_FACTOR
884
+ # bound the masked operation to q len so it does not have to wast cycles
885
+ len_m = min(len_m, seqlen_q)
886
+ num_steps = tl.cdiv(len_m, MASK_BLOCK_M1)
887
+ # when q < k, we may skip the initial masked op
888
+ if pid < num_blocks_skip:
889
+ num_steps = 0
890
+
891
+ # if start_m is negative, the current N-tile has no block on the
892
+ # diagonal of causal mask, so everything have no causal mask
893
+ if DEBUG_TRITON:
894
+ print(
895
+ f"Masked: start_n: {start_n}; start_m: {start_m}, num_steps: {num_steps}"
896
+ ) # noqa: E701
897
+ dk, dk_pe, dv = _bwd_dkdv_inner(
898
+ dk, # output tensor
899
+ dk_pe, # optional output tensor
900
+ dv, # output tensor
901
+ Q_ptr,
902
+ k,
903
+ k_pe,
904
+ v,
905
+ DO_ptr,
906
+ M_ptr,
907
+ Delta_ptr,
908
+ sm_scale, # input tensors
909
+ stride_qm,
910
+ stride_qd, # strides for q
911
+ stride_dom,
912
+ stride_dod, # strides for o
913
+ stride_dropoutm,
914
+ stride_dropoutn, # strides for dropout
915
+ stride_deltam,
916
+ MASK_BLOCK_M1,
917
+ BLOCK_N1, # block dim
918
+ HEAD_DIM,
919
+ ACTUAL_HEAD_DIM, # head dim
920
+ PE_HEAD_DIM,
921
+ dropout_p,
922
+ philox_seed,
923
+ batch_philox_offset,
924
+ dropout_offset,
925
+ alibi_slope,
926
+ seqlen_q,
927
+ seqlen_k, # max sequence length for q and k
928
+ start_n,
929
+ start_m,
930
+ num_steps, # iteration numbers
931
+ descale_q,
932
+ descale_k,
933
+ descale_v,
934
+ descale_do,
935
+ MASK=True, # causal masking
936
+ ENABLE_DROPOUT=ENABLE_DROPOUT, # activate dropout
937
+ USE_ALIBI=USE_ALIBI,
938
+ USE_EXP2=USE_EXP2,
939
+ IS_FP8=IS_FP8,
940
+ FP8_MAX=FP8_MAX,
941
+ DEBUG_TRITON=DEBUG_TRITON,
942
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
943
+ SLIDING_WINDOW=SLIDING_WINDOW,
944
+ )
945
+ start_m += num_steps * MASK_BLOCK_M1
946
+ end_m = seqlen_q
947
+ if SLIDING_WINDOW > 0:
948
+ end_m = min(start_n + BLOCK_N1 + delta_qk + SLIDING_WINDOW, seqlen_q)
949
+ num_steps = tl.cdiv(max(end_m - start_m, 0), BLOCK_M1)
950
+ end_m = start_m + num_steps * BLOCK_M1
951
+
952
+ if DEBUG_TRITON:
953
+ print(
954
+ f"start_m after Masked step: {start_m}; num_steps: {num_steps}"
955
+ ) # noqa: E701
956
+ if DEBUG_TRITON:
957
+ print(
958
+ f"unMasked: start_n: {start_n}, start_m: {start_m}, end_m: {end_m}, num_steps: {num_steps}"
959
+ ) # noqa: E701
960
+ if DEBUG_TRITON:
961
+ print("unMasked") # noqa: E701
962
+ dk, dk_pe, dv = _bwd_dkdv_inner(
963
+ dk, # output tensor
964
+ dk_pe, # optional output tensor
965
+ dv, # output tensor
966
+ Q_ptr,
967
+ k,
968
+ k_pe,
969
+ v,
970
+ DO_ptr,
971
+ M_ptr,
972
+ Delta_ptr,
973
+ sm_scale, # input tensors
974
+ stride_qm,
975
+ stride_qd, # strides for q
976
+ stride_dom,
977
+ stride_dod, # strides for o
978
+ stride_dropoutm,
979
+ stride_dropoutn, # strides for dropout
980
+ stride_deltam,
981
+ BLOCK_M1,
982
+ BLOCK_N1, # block dim
983
+ HEAD_DIM,
984
+ ACTUAL_HEAD_DIM, # head dim
985
+ PE_HEAD_DIM,
986
+ dropout_p,
987
+ philox_seed,
988
+ batch_philox_offset,
989
+ dropout_offset,
990
+ alibi_slope,
991
+ seqlen_q,
992
+ seqlen_k, # max sequence length for q and k
993
+ start_n,
994
+ start_m,
995
+ num_steps, # iteration numbers
996
+ descale_q,
997
+ descale_k,
998
+ descale_v,
999
+ descale_do,
1000
+ MASK=False, # causal masking
1001
+ ENABLE_DROPOUT=ENABLE_DROPOUT, # activate dropout
1002
+ USE_ALIBI=USE_ALIBI,
1003
+ USE_EXP2=USE_EXP2,
1004
+ IS_FP8=IS_FP8,
1005
+ FP8_MAX=FP8_MAX,
1006
+ DEBUG_TRITON=DEBUG_TRITON,
1007
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
1008
+ SLIDING_WINDOW=SLIDING_WINDOW,
1009
+ )
1010
+ # end of GQA/MQA of dkdv
1011
+ # Write back dV
1012
+ adj_dv = bid * stride_dvb + hkid * stride_dvh + k_start * stride_dvn
1013
+ offs_dv = offs_n[:, None] * stride_dvn + offs_d[None, :] * stride_dvd
1014
+ tl.store(DV + adj_dv + offs_dv, dv, mask=mask_kv)
1015
+ # write back dk
1016
+ adj_dk = bid * stride_dkb + hkid * stride_dkh + k_start * stride_dkn
1017
+ offs_dk = offs_n[:, None] * stride_dkn + offs_d[None, :] * stride_dkd
1018
+ dk *= sm_scale
1019
+ tl.store(DK + adj_dk + offs_dk, dk, mask=mask_kv)
1020
+ if HAS_PE:
1021
+ offs_dk_pe = offs_n[:, None] * stride_dkn + offs_d_pe[None, :] * stride_dkd
1022
+ dk_pe *= sm_scale
1023
+ tl.store(DK + adj_dk + offs_dk_pe, dk_pe, mask=mask_kv)
1024
+
1025
+ # This part does dq
1026
+ start_m = pid * BLOCK_M2
1027
+ if start_m < seqlen_q:
1028
+ # seqlen_q > seqlen_k, no need to process these tile for dq
1029
+ if DEBUG_TRITON:
1030
+ print(
1031
+ f"end_n = start_m + BLOCK_M = {start_m} + {BLOCK_M2} = {start_m + BLOCK_M2}"
1032
+ ) # noqa: E701
1033
+ if start_m + BLOCK_M2 < delta_qk:
1034
+ if DEBUG_TRITON:
1035
+ print(
1036
+ f"start_m + BLOCK_M2 = {start_m} + {BLOCK_M2} = {start_m + BLOCK_M2} < delta_qk of {delta_qk}"
1037
+ ) # noqa: E701
1038
+ return
1039
+
1040
+ offs_m = start_m + tl.arange(0, BLOCK_M2)
1041
+ # Mask for loading K and V
1042
+ mask_q = offs_m[:, None] < seqlen_q
1043
+ if PADDED_HEAD:
1044
+ mask_d = offs_d < ACTUAL_HEAD_DIM
1045
+ mask_q &= mask_d[None, :]
1046
+ offs_q = offs_m[:, None] * stride_qm + offs_d[None, :] * stride_qd
1047
+ if HAS_PE:
1048
+ offs_q_pe = offs_m[:, None] * stride_qm + offs_d_pe[None, :] * stride_qd
1049
+ offs_do = offs_m[:, None] * stride_dom + offs_d[None, :] * stride_dod
1050
+ # NOTE: don't assume that the strides for k and v are the same!
1051
+ K += bid * stride_kb + hkid * stride_kh + k_start * stride_kn
1052
+ V += bid * stride_vb + hkid * stride_vh + k_start * stride_vn
1053
+
1054
+ # If MQA / GQA, set the K and V head offsets appropriately.
1055
+ for hqid in range(hkid * GROUP_SIZE, hkid * GROUP_SIZE + GROUP_SIZE):
1056
+ # seqlen_q < seqlen_k: delta_qk more kv tokens are added at the front
1057
+ # for every M-tile
1058
+ end_n = start_m + BLOCK_M2 - delta_qk
1059
+ # clamp end_n at [0, seqlen_k]
1060
+ end_n = max(min(end_n, seqlen_k), 0)
1061
+ if DEBUG_TRITON:
1062
+ print(f"delta_qk: {delta_qk}; end_n: {end_n}") # noqa: E701
1063
+ # offset input and output tensor by batch and Q/K heads
1064
+ adj_q = bid * stride_qb + hqid * stride_qh + q_start * stride_qm
1065
+ adj_do = bid * stride_dob + hqid * stride_doh + q_start * stride_dom
1066
+ adj_delta = (
1067
+ bid * stride_deltab + hqid * stride_deltah + q_start * stride_deltam
1068
+ )
1069
+ Delta_ptr = Delta + adj_delta
1070
+
1071
+ if USE_ALIBI:
1072
+ alibi_offset = bid * stride_az + hqid * stride_ah
1073
+ alibi_slope = tl.load(Alibi_slopes + alibi_offset)
1074
+ else:
1075
+ alibi_slope = None
1076
+
1077
+ # batch_philox_offset is the ACTUALLY dropout offset
1078
+ # dropout_offset is for debug purpose and will be removed later
1079
+ batch_philox_offset = 0
1080
+ dropout_offset = 0
1081
+ if ENABLE_DROPOUT:
1082
+ batch_philox_offset = (
1083
+ philox_offset_base + bid * stride_dropoutb + hqid * stride_dropouth
1084
+ )
1085
+ dropout_offset = (
1086
+ Dropout_mask + bid * stride_dropoutb + hqid * stride_dropouth
1087
+ )
1088
+ q = tl.load(Q + adj_q + offs_q, mask=mask_q, other=0.0)
1089
+ if HAS_PE:
1090
+ q_pe = tl.load(Q + adj_q + offs_q_pe, mask=mask_q, other=0.0)
1091
+ else:
1092
+ q_pe = None
1093
+ do = tl.load(DO + adj_do + offs_do, mask=mask_q, other=0.0)
1094
+ mask_m = offs_m < seqlen_q
1095
+ m = tl.load(M + adj_delta + offs_m * stride_deltam, mask=mask_m, other=0.0)
1096
+ m = m[:, None]
1097
+ delta = tl.load(Delta_ptr + offs_m * stride_deltam, mask=mask_m, other=0.0)
1098
+
1099
+ MASK_BLOCK_N2: tl.constexpr = BLOCK_N2 // BLK_SLICE_FACTOR
1100
+ # start can only be 0 at minimum
1101
+ start_n = max(end_n - BLOCK_M2, 0)
1102
+ num_steps = tl.cdiv(end_n - start_n, MASK_BLOCK_N2)
1103
+
1104
+ if IS_FP8:
1105
+ descale_q = tl.load(Descale_q + bid * stride_descale_q_z + hqid)
1106
+ descale_k = tl.load(Descale_k + bid * stride_descale_k_z + hkid)
1107
+ descale_v = tl.load(Descale_v + bid * stride_descale_v_z + hkid)
1108
+ descale_do = tl.load(Descale_do + bid * stride_descale_do_z + hqid)
1109
+ else:
1110
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
1111
+
1112
+ dq = tl.zeros([BLOCK_M2, HEAD_DIM], dtype=tl.float32)
1113
+ if HAS_PE:
1114
+ dq_pe = tl.zeros([BLOCK_M2, PE_HEAD_DIM], dtype=tl.float32)
1115
+ else:
1116
+ dq_pe = dq # Couldn't assign None to dq_pe because _bwd_dq_inner can't return None.
1117
+ dq, dq_pe, delta_recomp_masked = _bwd_dq_inner(
1118
+ dq, # output tensor
1119
+ dq_pe, # optional output tensor
1120
+ q,
1121
+ q_pe,
1122
+ K,
1123
+ V,
1124
+ do,
1125
+ m,
1126
+ delta,
1127
+ sm_scale,
1128
+ stride_qm,
1129
+ stride_qd,
1130
+ stride_kn,
1131
+ stride_kd,
1132
+ stride_vn,
1133
+ stride_vd,
1134
+ stride_dropoutm,
1135
+ stride_dropoutn,
1136
+ seqlen_q,
1137
+ seqlen_k,
1138
+ BLOCK_M2,
1139
+ MASK_BLOCK_N2,
1140
+ HEAD_DIM,
1141
+ ACTUAL_HEAD_DIM,
1142
+ PE_HEAD_DIM,
1143
+ dropout_p,
1144
+ philox_seed,
1145
+ batch_philox_offset,
1146
+ dropout_offset,
1147
+ alibi_slope,
1148
+ start_m,
1149
+ start_n,
1150
+ end_n,
1151
+ num_steps,
1152
+ descale_q,
1153
+ descale_k,
1154
+ descale_v,
1155
+ descale_do,
1156
+ MASK=True, #
1157
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1158
+ USE_ALIBI=USE_ALIBI,
1159
+ USE_EXP2=USE_EXP2,
1160
+ IS_FP8=IS_FP8,
1161
+ FP8_MAX=FP8_MAX,
1162
+ DEBUG_TRITON=DEBUG_TRITON,
1163
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
1164
+ SLIDING_WINDOW=SLIDING_WINDOW,
1165
+ ENABLE_SINK=ENABLE_SINK,
1166
+ )
1167
+ end_n -= num_steps * MASK_BLOCK_N2
1168
+ window_start_n = 0
1169
+ if SLIDING_WINDOW > 0:
1170
+ window_start_n = max(start_m - delta_qk - SLIDING_WINDOW, 0)
1171
+ start_n = window_start_n // BLOCK_N2 * BLOCK_N2
1172
+ num_steps = tl.cdiv(max(end_n - start_n, 0), BLOCK_N2)
1173
+ if DEBUG_TRITON:
1174
+ print(
1175
+ f"unMasked: start_m: {start_m}, start_n: {start_n}, end_n: {end_n}, num_steps: {num_steps}"
1176
+ ) # noqa: E701
1177
+ dq, dq_pe, delta_recomp_unmasked = _bwd_dq_inner(
1178
+ dq, # output tensor
1179
+ dq_pe, # optional output tensor
1180
+ q,
1181
+ q_pe,
1182
+ K,
1183
+ V,
1184
+ do,
1185
+ m,
1186
+ delta,
1187
+ sm_scale,
1188
+ stride_qm,
1189
+ stride_qd,
1190
+ stride_kn,
1191
+ stride_kd,
1192
+ stride_vn,
1193
+ stride_vd,
1194
+ stride_dropoutm,
1195
+ stride_dropoutn,
1196
+ seqlen_q,
1197
+ seqlen_k,
1198
+ BLOCK_M2,
1199
+ BLOCK_N2,
1200
+ HEAD_DIM,
1201
+ ACTUAL_HEAD_DIM,
1202
+ PE_HEAD_DIM,
1203
+ dropout_p,
1204
+ philox_seed,
1205
+ batch_philox_offset,
1206
+ dropout_offset,
1207
+ alibi_slope,
1208
+ start_m,
1209
+ start_n,
1210
+ end_n,
1211
+ num_steps,
1212
+ descale_q,
1213
+ descale_k,
1214
+ descale_v,
1215
+ descale_do,
1216
+ MASK=False,
1217
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1218
+ USE_ALIBI=USE_ALIBI,
1219
+ USE_EXP2=USE_EXP2,
1220
+ IS_FP8=IS_FP8,
1221
+ FP8_MAX=FP8_MAX,
1222
+ DEBUG_TRITON=DEBUG_TRITON,
1223
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
1224
+ SLIDING_WINDOW=SLIDING_WINDOW,
1225
+ ENABLE_SINK=ENABLE_SINK,
1226
+ )
1227
+ if ENABLE_SINK:
1228
+ sink = tl.load(Sink + hqid).to(tl.float32)
1229
+ if USE_EXP2:
1230
+ RCP_LN2: tl.constexpr = 1.4426950408889634
1231
+ psink = tl.math.exp2(sink * RCP_LN2 - m * RCP_LN2)
1232
+ else:
1233
+ psink = tl.math.exp(sink - m)
1234
+ delta_recomp = delta_recomp_masked + delta_recomp_unmasked
1235
+ dsink = tl.sum(-psink * delta_recomp[:, None])
1236
+ tl.atomic_add(DSink + hqid, dsink, sem="relaxed")
1237
+ # Write back dQ.
1238
+ adj_dq = bid * stride_dqb + hqid * stride_dqh + q_start * stride_dqm
1239
+ offs_dq = offs_m[:, None] * stride_dqm + offs_d[None, :] * stride_dqd
1240
+ dq *= sm_scale
1241
+ tl.store(DQ + adj_dq + offs_dq, dq, mask=mask_q)
1242
+ if HAS_PE:
1243
+ offs_dq_pe = (
1244
+ offs_m[:, None] * stride_dqm + offs_d_pe[None, :] * stride_dqd
1245
+ )
1246
+ dq_pe *= sm_scale
1247
+ tl.store(DQ + adj_dq + offs_dq_pe, dq_pe, mask=mask_q)
1248
+ # end of GQA/MQA of dq
1249
+
1250
+
1251
+ _bwd_kernel_noncausal_repr = make_kernel_repr(
1252
+ "bwd_kernel_noncausal",
1253
+ [
1254
+ "BLOCK_M1",
1255
+ "BLOCK_N1",
1256
+ "BLOCK_M2",
1257
+ "BLOCK_N2",
1258
+ "BLK_SLICE_FACTOR",
1259
+ "HEAD_DIM",
1260
+ "ENABLE_DROPOUT",
1261
+ "IS_VARLEN",
1262
+ "USE_ALIBI",
1263
+ "USE_EXP2",
1264
+ "IS_FP8",
1265
+ "USE_INT64_STRIDES",
1266
+ "ENABLE_SINK",
1267
+ "SLIDING_WINDOW",
1268
+ ],
1269
+ )
1270
+
1271
+
1272
+ @triton.jit(repr=_bwd_kernel_noncausal_repr)
1273
+ def bwd_kernel_noncausal(
1274
+ Q,
1275
+ K,
1276
+ V,
1277
+ Sink,
1278
+ sm_scale,
1279
+ DO,
1280
+ DQ,
1281
+ DK,
1282
+ DV,
1283
+ DSink,
1284
+ M,
1285
+ Delta,
1286
+ stride_qb_in,
1287
+ stride_qh_in,
1288
+ stride_qm_in,
1289
+ stride_qd_in,
1290
+ stride_kb_in,
1291
+ stride_kh_in,
1292
+ stride_kn_in,
1293
+ stride_kd_in,
1294
+ stride_vb_in,
1295
+ stride_vh_in,
1296
+ stride_vn_in,
1297
+ stride_vd_in,
1298
+ stride_dqb_in,
1299
+ stride_dqh_in,
1300
+ stride_dqm_in,
1301
+ stride_dqd_in,
1302
+ stride_dkb_in,
1303
+ stride_dkh_in,
1304
+ stride_dkn_in,
1305
+ stride_dkd_in,
1306
+ stride_dvb_in,
1307
+ stride_dvh_in,
1308
+ stride_dvn_in,
1309
+ stride_dvd_in,
1310
+ stride_deltab_in,
1311
+ stride_deltah_in,
1312
+ stride_deltam_in,
1313
+ stride_dob_in,
1314
+ stride_doh_in,
1315
+ stride_dom_in,
1316
+ stride_dod_in,
1317
+ stride_dropoutb_in,
1318
+ stride_dropouth_in,
1319
+ stride_dropoutm_in,
1320
+ stride_dropoutn_in,
1321
+ stride_descale_q_z_in,
1322
+ stride_descale_k_z_in,
1323
+ stride_descale_v_z_in,
1324
+ stride_descale_do_z_in,
1325
+ stride_az_in,
1326
+ stride_ah_in,
1327
+ HQ,
1328
+ HK,
1329
+ cu_seqlens_q,
1330
+ cu_seqlens_k,
1331
+ max_seqlen_q,
1332
+ max_seqlen_k,
1333
+ Dropout_mask,
1334
+ dropout_p,
1335
+ philox_seed,
1336
+ philox_offset_base_in,
1337
+ Alibi_slopes,
1338
+ Descale_q,
1339
+ Descale_k,
1340
+ Descale_v,
1341
+ Descale_do,
1342
+ BLOCK_M1: tl.constexpr, # 32
1343
+ BLOCK_N1: tl.constexpr, # 128
1344
+ BLOCK_M2: tl.constexpr, # 128
1345
+ BLOCK_N2: tl.constexpr, # 32
1346
+ BLK_SLICE_FACTOR: tl.constexpr,
1347
+ HEAD_DIM: tl.constexpr,
1348
+ ACTUAL_HEAD_DIM: tl.constexpr,
1349
+ PE_HEAD_DIM: tl.constexpr,
1350
+ ENABLE_DROPOUT: tl.constexpr,
1351
+ IS_VARLEN: tl.constexpr,
1352
+ USE_ALIBI: tl.constexpr,
1353
+ USE_EXP2: tl.constexpr,
1354
+ IS_FP8: tl.constexpr,
1355
+ FP8_MAX: tl.constexpr,
1356
+ DEBUG_TRITON: tl.constexpr,
1357
+ DEBUG_TRITON_DETAIL: tl.constexpr,
1358
+ USE_INT64_STRIDES: tl.constexpr,
1359
+ ENABLE_SINK: tl.constexpr,
1360
+ SLIDING_WINDOW: tl.constexpr,
1361
+ ):
1362
+ if USE_INT64_STRIDES:
1363
+ stride_qb = tl.cast(stride_qb_in, tl.int64)
1364
+ stride_qh = tl.cast(stride_qh_in, tl.int64)
1365
+ stride_qm = tl.cast(stride_qm_in, tl.int64)
1366
+ stride_qd = tl.cast(stride_qd_in, tl.int64)
1367
+ stride_kb = tl.cast(stride_kb_in, tl.int64)
1368
+ stride_kh = tl.cast(stride_kh_in, tl.int64)
1369
+ stride_kn = tl.cast(stride_kn_in, tl.int64)
1370
+ stride_kd = tl.cast(stride_kd_in, tl.int64)
1371
+ stride_vb = tl.cast(stride_vb_in, tl.int64)
1372
+ stride_vh = tl.cast(stride_vh_in, tl.int64)
1373
+ stride_vn = tl.cast(stride_vn_in, tl.int64)
1374
+ stride_vd = tl.cast(stride_vd_in, tl.int64)
1375
+ stride_dqb = tl.cast(stride_dqb_in, tl.int64)
1376
+ stride_dqh = tl.cast(stride_dqh_in, tl.int64)
1377
+ stride_dqm = tl.cast(stride_dqm_in, tl.int64)
1378
+ stride_dqd = tl.cast(stride_dqd_in, tl.int64)
1379
+ stride_dkb = tl.cast(stride_dkb_in, tl.int64)
1380
+ stride_dkh = tl.cast(stride_dkh_in, tl.int64)
1381
+ stride_dkn = tl.cast(stride_dkn_in, tl.int64)
1382
+ stride_dkd = tl.cast(stride_dkd_in, tl.int64)
1383
+ stride_dvb = tl.cast(stride_dvb_in, tl.int64)
1384
+ stride_dvh = tl.cast(stride_dvh_in, tl.int64)
1385
+ stride_dvn = tl.cast(stride_dvn_in, tl.int64)
1386
+ stride_dvd = tl.cast(stride_dvd_in, tl.int64)
1387
+ stride_deltab = tl.cast(stride_deltab_in, tl.int64)
1388
+ stride_deltah = tl.cast(stride_deltah_in, tl.int64)
1389
+ stride_deltam = tl.cast(stride_deltam_in, tl.int64)
1390
+ stride_dob = tl.cast(stride_dob_in, tl.int64)
1391
+ stride_doh = tl.cast(stride_doh_in, tl.int64)
1392
+ stride_dom = tl.cast(stride_dom_in, tl.int64)
1393
+ stride_dod = tl.cast(stride_dod_in, tl.int64)
1394
+ philox_offset_base = tl.cast(philox_offset_base_in, tl.int64)
1395
+ stride_dropoutb = tl.cast(stride_dropoutb_in, tl.int64)
1396
+ stride_dropouth = tl.cast(stride_dropouth_in, tl.int64)
1397
+ stride_dropoutm = tl.cast(stride_dropoutm_in, tl.int64)
1398
+ stride_dropoutn = tl.cast(stride_dropoutn_in, tl.int64)
1399
+ if IS_FP8:
1400
+ stride_descale_q_z = tl.cast(stride_descale_q_z_in, tl.int64)
1401
+ stride_descale_k_z = tl.cast(stride_descale_k_z_in, tl.int64)
1402
+ stride_descale_v_z = tl.cast(stride_descale_v_z_in, tl.int64)
1403
+ stride_descale_do_z = tl.cast(stride_descale_do_z_in, tl.int64)
1404
+ stride_az = tl.cast(stride_az_in, tl.int64)
1405
+ stride_ah = tl.cast(stride_ah_in, tl.int64)
1406
+ else:
1407
+ stride_qb = stride_qb_in
1408
+ stride_qh = stride_qh_in
1409
+ stride_qm = stride_qm_in
1410
+ stride_qd = stride_qd_in
1411
+ stride_kb = stride_kb_in
1412
+ stride_kh = stride_kh_in
1413
+ stride_kn = stride_kn_in
1414
+ stride_kd = stride_kd_in
1415
+ stride_vb = stride_vb_in
1416
+ stride_vh = stride_vh_in
1417
+ stride_vn = stride_vn_in
1418
+ stride_vd = stride_vd_in
1419
+ stride_dqb = stride_dqb_in
1420
+ stride_dqh = stride_dqh_in
1421
+ stride_dqm = stride_dqm_in
1422
+ stride_dqd = stride_dqd_in
1423
+ stride_dkb = stride_dkb_in
1424
+ stride_dkh = stride_dkh_in
1425
+ stride_dkn = stride_dkn_in
1426
+ stride_dkd = stride_dkd_in
1427
+ stride_dvb = stride_dvb_in
1428
+ stride_dvh = stride_dvh_in
1429
+ stride_dvn = stride_dvn_in
1430
+ stride_dvd = stride_dvd_in
1431
+ stride_deltab = stride_deltab_in
1432
+ stride_deltah = stride_deltah_in
1433
+ stride_deltam = stride_deltam_in
1434
+ stride_dob = stride_dob_in
1435
+ stride_doh = stride_doh_in
1436
+ stride_dom = stride_dom_in
1437
+ stride_dod = stride_dod_in
1438
+ philox_offset_base = philox_offset_base_in
1439
+ stride_dropoutb = stride_dropoutb_in
1440
+ stride_dropouth = stride_dropouth_in
1441
+ stride_dropoutm = stride_dropoutm_in
1442
+ stride_dropoutn = stride_dropoutn_in
1443
+ stride_descale_q_z = stride_descale_q_z_in
1444
+ stride_descale_k_z = stride_descale_k_z_in
1445
+ stride_descale_v_z = stride_descale_v_z_in
1446
+ stride_descale_do_z = stride_descale_do_z_in
1447
+ stride_az = stride_az_in
1448
+ stride_ah = stride_ah_in
1449
+
1450
+ # program ids
1451
+ hkid = tl.program_id(0)
1452
+ pid = tl.program_id(1)
1453
+ bid = tl.program_id(2)
1454
+ if DEBUG_TRITON:
1455
+ print(f"\npid: {pid}, bid: {bid}, hkid: {hkid}") # noqa: E701
1456
+ # figure out varlen start and end
1457
+ q_start = 0
1458
+ k_start = 0
1459
+ seqlen_q = max_seqlen_q
1460
+ seqlen_k = max_seqlen_k
1461
+ if IS_VARLEN:
1462
+ # Compute actual sequence lengths
1463
+ q_start = tl.load(cu_seqlens_q + bid)
1464
+ q_end = tl.load(cu_seqlens_q + bid + 1)
1465
+ k_start = tl.load(cu_seqlens_k + bid)
1466
+ k_end = tl.load(cu_seqlens_k + bid + 1)
1467
+ seqlen_q = q_end - q_start
1468
+ seqlen_k = k_end - k_start
1469
+
1470
+ PADDED_HEAD: tl.constexpr = ACTUAL_HEAD_DIM != HEAD_DIM
1471
+ HAS_PE: tl.constexpr = PE_HEAD_DIM > 0
1472
+ offs_d = tl.arange(0, HEAD_DIM)
1473
+ if HAS_PE:
1474
+ offs_d_pe = HEAD_DIM + tl.arange(0, PE_HEAD_DIM)
1475
+ GROUP_SIZE: tl.constexpr = HQ // HK
1476
+
1477
+ start_n = pid * BLOCK_N1
1478
+ if start_n < seqlen_k:
1479
+ dk = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
1480
+ if HAS_PE:
1481
+ dk_pe = tl.zeros([BLOCK_N1, PE_HEAD_DIM], dtype=tl.float32)
1482
+ else:
1483
+ # Couldn't assign None to dk_pe because _bwd_dkdv_inner can't return None.
1484
+ dk_pe = dk
1485
+ dv = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
1486
+
1487
+ offs_n = start_n + tl.arange(0, BLOCK_N1)
1488
+ # Mask for loading K and V
1489
+ mask_kv = offs_n[:, None] < seqlen_k
1490
+ if PADDED_HEAD:
1491
+ mask_d = offs_d < ACTUAL_HEAD_DIM
1492
+ mask_kv &= mask_d[None, :]
1493
+ # NOTE: don't assume that the strides for k and v are the same!
1494
+ # K/V tensors not changed for the group
1495
+ adj_k = (
1496
+ bid * stride_kb
1497
+ + hkid * stride_kh
1498
+ + k_start * stride_kn
1499
+ + offs_n[:, None] * stride_kn
1500
+ + offs_d[None, :] * stride_kd
1501
+ )
1502
+ if HAS_PE:
1503
+ adj_k_pe = (
1504
+ bid * stride_kb
1505
+ + hkid * stride_kh
1506
+ + k_start * stride_kn
1507
+ + offs_n[:, None] * stride_kn
1508
+ + offs_d_pe[None, :] * stride_kd
1509
+ )
1510
+ adj_v = (
1511
+ bid * stride_vb
1512
+ + hkid * stride_vh
1513
+ + k_start * stride_vn
1514
+ + offs_n[:, None] * stride_vn
1515
+ + offs_d[None, :] * stride_vd
1516
+ )
1517
+ # load K and V: they stay in SRAM throughout the inner loop.
1518
+ k = tl.load(K + adj_k, mask=mask_kv, other=0.0)
1519
+ if HAS_PE:
1520
+ k_pe = tl.load(K + adj_k_pe, mask=mask_kv, other=0.0)
1521
+ else:
1522
+ k_pe = None
1523
+ v = tl.load(V + adj_v, mask=mask_kv, other=0.0)
1524
+ # If MQA / GQA, set the K and V head offsets appropriately.
1525
+ for hqid in range(hkid * GROUP_SIZE, hkid * GROUP_SIZE + GROUP_SIZE):
1526
+ # offset input and output tensor by batch and Q/K heads
1527
+ adj_q = bid * stride_qb + hqid * stride_qh + q_start * stride_qm
1528
+ Q_ptr = Q + adj_q
1529
+ adj_do = bid * stride_dob + hqid * stride_doh + q_start * stride_dom
1530
+ DO_ptr = DO + adj_do
1531
+ adj_delta = (
1532
+ bid * stride_deltab + hqid * stride_deltah + q_start * stride_deltam
1533
+ )
1534
+ M_ptr = M + adj_delta
1535
+ Delta_ptr = Delta + adj_delta
1536
+
1537
+ if USE_ALIBI:
1538
+ alibi_offset = bid * stride_az + hqid * stride_ah
1539
+ alibi_slope = tl.load(Alibi_slopes + alibi_offset)
1540
+ else:
1541
+ alibi_slope = None
1542
+
1543
+ # batch_philox_offset is the ACTUALLY dropout offset
1544
+ # dropout_offset is for debug purpose and will be removed later
1545
+ batch_philox_offset = 0
1546
+ dropout_offset = 0
1547
+ if ENABLE_DROPOUT:
1548
+ batch_philox_offset = (
1549
+ philox_offset_base + bid * stride_dropoutb + hqid * stride_dropouth
1550
+ )
1551
+ dropout_offset = (
1552
+ Dropout_mask + bid * stride_dropoutb + hqid * stride_dropouth
1553
+ )
1554
+
1555
+ if IS_FP8:
1556
+ descale_q = tl.load(Descale_q + bid * stride_descale_q_z + hqid)
1557
+ descale_k = tl.load(Descale_k + bid * stride_descale_k_z + hkid)
1558
+ descale_v = tl.load(Descale_v + bid * stride_descale_v_z + hkid)
1559
+ descale_do = tl.load(Descale_do + bid * stride_descale_do_z + hqid)
1560
+ else:
1561
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
1562
+
1563
+ # because there is no causal, we always start from the beginning
1564
+ start_m = 0
1565
+ num_steps = tl.cdiv(seqlen_q, BLOCK_M1)
1566
+ dk, dk_pe, dv = _bwd_dkdv_inner(
1567
+ dk, # output tensor
1568
+ dk_pe, # optional output tensor
1569
+ dv, # output tensor
1570
+ Q_ptr,
1571
+ k,
1572
+ k_pe,
1573
+ v,
1574
+ DO_ptr,
1575
+ M_ptr,
1576
+ Delta_ptr,
1577
+ sm_scale, # input tensors
1578
+ stride_qm,
1579
+ stride_qd, # strides for q
1580
+ stride_dom,
1581
+ stride_dod, # strides for o
1582
+ stride_dropoutm,
1583
+ stride_dropoutn, # strides for dropout
1584
+ stride_deltam,
1585
+ BLOCK_M1,
1586
+ BLOCK_N1, # block dim
1587
+ HEAD_DIM,
1588
+ ACTUAL_HEAD_DIM, # head dim
1589
+ PE_HEAD_DIM,
1590
+ dropout_p,
1591
+ philox_seed,
1592
+ batch_philox_offset,
1593
+ dropout_offset, #
1594
+ alibi_slope,
1595
+ seqlen_q,
1596
+ seqlen_k, # max sequence length for q and k
1597
+ start_n,
1598
+ start_m,
1599
+ num_steps, # iteration numbers
1600
+ descale_q,
1601
+ descale_k,
1602
+ descale_v,
1603
+ descale_do, # fp8 descale factors from user
1604
+ MASK=False, # causal masking
1605
+ ENABLE_DROPOUT=ENABLE_DROPOUT, # activate dropout
1606
+ USE_ALIBI=USE_ALIBI,
1607
+ USE_EXP2=USE_EXP2,
1608
+ IS_FP8=IS_FP8,
1609
+ FP8_MAX=FP8_MAX,
1610
+ DEBUG_TRITON=DEBUG_TRITON,
1611
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
1612
+ SLIDING_WINDOW=SLIDING_WINDOW,
1613
+ )
1614
+
1615
+ # Write back dV
1616
+ adj_dv = bid * stride_dvb + hkid * stride_dvh + k_start * stride_dvn
1617
+ offs_dv = offs_n[:, None] * stride_dvn + offs_d[None, :] * stride_dvd
1618
+ tl.store(DV + adj_dv + offs_dv, dv, mask=mask_kv)
1619
+ # write back dk
1620
+ adj_dk = bid * stride_dkb + hkid * stride_dkh + k_start * stride_dkn
1621
+ offs_dk = offs_n[:, None] * stride_dkn + offs_d[None, :] * stride_dkd
1622
+ dk *= sm_scale
1623
+ tl.store(DK + adj_dk + offs_dk, dk, mask=mask_kv)
1624
+ if HAS_PE:
1625
+ offs_dk_pe = offs_n[:, None] * stride_dkn + offs_d_pe[None, :] * stride_dkd
1626
+ dk_pe *= sm_scale
1627
+ tl.store(DK + adj_dk + offs_dk_pe, dk_pe, mask=mask_kv)
1628
+
1629
+ # THIS PART DOES DQ
1630
+ start_m = pid * BLOCK_M2
1631
+ if start_m < seqlen_q:
1632
+ offs_m = start_m + tl.arange(0, BLOCK_M2)
1633
+ # Mask for loading K and V
1634
+ mask_q = offs_m[:, None] < seqlen_q
1635
+ if PADDED_HEAD:
1636
+ mask_d = offs_d < ACTUAL_HEAD_DIM
1637
+ mask_q &= mask_d[None, :]
1638
+ offs_q = offs_m[:, None] * stride_qm + offs_d[None, :] * stride_qd
1639
+ if HAS_PE:
1640
+ offs_q_pe = offs_m[:, None] * stride_qm + offs_d_pe[None, :] * stride_qd
1641
+ offs_do = offs_m[:, None] * stride_dom + offs_d[None, :] * stride_dod
1642
+ K += bid * stride_kb + hkid * stride_kh + k_start * stride_kn
1643
+ V += bid * stride_vb + hkid * stride_vh + k_start * stride_vn
1644
+ # If MQA / GQA, set the K and V head offsets appropriately.
1645
+ for hqid in range(hkid * GROUP_SIZE, hkid * GROUP_SIZE + GROUP_SIZE):
1646
+ # offset input and output tensor by batch and Q/K heads
1647
+ adj_q = bid * stride_qb + hqid * stride_qh + q_start * stride_qm
1648
+ adj_do = bid * stride_dob + hqid * stride_doh + q_start * stride_dom
1649
+ adj_delta = (
1650
+ bid * stride_deltab + hqid * stride_deltah + q_start * stride_deltam
1651
+ )
1652
+ Delta_ptr = Delta + adj_delta
1653
+
1654
+ if USE_ALIBI:
1655
+ alibi_offset = bid * stride_az + hqid * stride_ah
1656
+ alibi_slope = tl.load(Alibi_slopes + alibi_offset)
1657
+ else:
1658
+ alibi_slope = None
1659
+
1660
+ # batch_philox_offset is the ACTUALLY dropout offset
1661
+ # dropout_offset is for debug purpose and will be removed later
1662
+ batch_philox_offset = 0
1663
+ dropout_offset = 0
1664
+ if ENABLE_DROPOUT:
1665
+ batch_philox_offset = (
1666
+ philox_offset_base + bid * stride_dropoutb + hqid * stride_dropouth
1667
+ )
1668
+ dropout_offset = (
1669
+ Dropout_mask + bid * stride_dropoutb + hqid * stride_dropouth
1670
+ )
1671
+
1672
+ q = tl.load(Q + adj_q + offs_q, mask=mask_q, other=0.0)
1673
+ if HAS_PE:
1674
+ q_pe = tl.load(Q + adj_q + offs_q_pe, mask=mask_q, other=0.0)
1675
+ else:
1676
+ q_pe = None
1677
+ do = tl.load(DO + adj_do + offs_do, mask=mask_q, other=0.0)
1678
+ mask_m = offs_m < seqlen_q
1679
+ m = tl.load(M + adj_delta + offs_m * stride_deltam, mask=mask_m, other=0.0)
1680
+ m = m[:, None]
1681
+ delta = tl.load(Delta_ptr + offs_m * stride_deltam, mask=mask_m, other=0.0)
1682
+
1683
+ if IS_FP8:
1684
+ descale_q = tl.load(Descale_q + bid * stride_descale_q_z + hqid)
1685
+ descale_k = tl.load(Descale_k + bid * stride_descale_k_z + hkid)
1686
+ descale_v = tl.load(Descale_v + bid * stride_descale_v_z + hkid)
1687
+ descale_do = tl.load(Descale_do + bid * stride_descale_do_z + hqid)
1688
+ else:
1689
+ descale_q, descale_k, descale_v, descale_do = 1.0, 1.0, 1.0, 1.0
1690
+
1691
+ # start can only be 0 at minimum
1692
+ start_n = 0
1693
+ end_n = seqlen_k
1694
+ num_steps = tl.cdiv(seqlen_k, BLOCK_N2)
1695
+
1696
+ dq = tl.zeros([BLOCK_M2, HEAD_DIM], dtype=tl.float32)
1697
+ if HAS_PE:
1698
+ dq_pe = tl.zeros([BLOCK_M2, PE_HEAD_DIM], dtype=tl.float32)
1699
+ else:
1700
+ dq_pe = dq # Couldn't assign None to dq_pe because _bwd_dq_inner can't return None.
1701
+ dq, dq_pe, delta_recomp = _bwd_dq_inner(
1702
+ dq, # output tensor
1703
+ dq_pe, # optional output tensor
1704
+ q,
1705
+ q_pe,
1706
+ K,
1707
+ V,
1708
+ do,
1709
+ m,
1710
+ delta,
1711
+ sm_scale,
1712
+ stride_qm,
1713
+ stride_qd,
1714
+ stride_kn,
1715
+ stride_kd,
1716
+ stride_vn,
1717
+ stride_vd,
1718
+ stride_dropoutm,
1719
+ stride_dropoutn,
1720
+ seqlen_q,
1721
+ seqlen_k,
1722
+ BLOCK_M2,
1723
+ BLOCK_N2,
1724
+ HEAD_DIM,
1725
+ ACTUAL_HEAD_DIM,
1726
+ PE_HEAD_DIM,
1727
+ dropout_p,
1728
+ philox_seed,
1729
+ batch_philox_offset,
1730
+ dropout_offset,
1731
+ alibi_slope,
1732
+ start_m,
1733
+ start_n,
1734
+ end_n,
1735
+ num_steps,
1736
+ descale_q,
1737
+ descale_k,
1738
+ descale_v,
1739
+ descale_do,
1740
+ MASK=False,
1741
+ ENABLE_DROPOUT=ENABLE_DROPOUT,
1742
+ USE_ALIBI=USE_ALIBI,
1743
+ USE_EXP2=USE_EXP2,
1744
+ IS_FP8=IS_FP8,
1745
+ FP8_MAX=FP8_MAX,
1746
+ DEBUG_TRITON=DEBUG_TRITON,
1747
+ DEBUG_TRITON_DETAIL=DEBUG_TRITON_DETAIL,
1748
+ SLIDING_WINDOW=SLIDING_WINDOW,
1749
+ ENABLE_SINK=ENABLE_SINK,
1750
+ )
1751
+ if ENABLE_SINK:
1752
+ sink = tl.load(Sink + hqid).to(tl.float32)
1753
+ if USE_EXP2:
1754
+ RCP_LN2: tl.constexpr = 1.4426950408889634
1755
+ psink = tl.math.exp2(sink * RCP_LN2 - m * RCP_LN2)
1756
+ else:
1757
+ psink = tl.math.exp(sink - m)
1758
+ dsink = tl.sum(-psink * delta_recomp[:, None])
1759
+ tl.atomic_add(DSink + hqid, dsink, sem="relaxed")
1760
+ # Write back dQ.
1761
+ adj_dq = bid * stride_dqb + hqid * stride_dqh + q_start * stride_dqm
1762
+ offs_dq = offs_m[:, None] * stride_dqm + offs_d[None, :] * stride_dqd
1763
+ dq *= sm_scale
1764
+ tl.store(DQ + adj_dq + offs_dq, dq, mask=mask_q)
1765
+ if HAS_PE:
1766
+ offs_dq_pe = (
1767
+ offs_m[:, None] * stride_dqm + offs_d_pe[None, :] * stride_dqd
1768
+ )
1769
+ dq_pe *= sm_scale
1770
+ tl.store(DQ + adj_dq + offs_dq_pe, dq_pe, mask=mask_q)
1771
+
1772
+
1773
+ @functools.lru_cache(maxsize=1024)
1774
+ def _get_config():
1775
+ if not hasattr(_get_config, "_config_dict"):
1776
+ dev = arch_info.get_arch()
1777
+ _get_config._config_dict = {}
1778
+ fpath = f"{AITER_TRITON_CONFIGS_PATH}/{dev}-MHA-DEFAULT.json"
1779
+ with open(fpath, "r") as file:
1780
+ config = json.load(file)
1781
+ _get_config._config_dict = config
1782
+
1783
+ return _get_config._config_dict["bkwd_onekernel"]
build/torch-rocm/_ops.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ def get_backend() -> str:
4
+ """Detect the backend by inspecting torch."""
5
+ import torch
6
+
7
+ if hasattr(torch, "neuron"):
8
+ # Needs to be sorted before specific Torch builds, since Neuron
9
+ # extension can be loaded into e.g. CUDA Torch builds.
10
+ return "neuron"
11
+ elif torch.version.cuda is not None:
12
+ return "cuda"
13
+ elif torch.version.hip is not None:
14
+ return "rocm"
15
+ elif torch.backends.mps.is_available():
16
+ return "metal"
17
+ elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
18
+ return "xpu"
19
+ else:
20
+ return "cpu"
21
+
22
+
23
+ def _find_ops_name() -> str:
24
+ kernel_name = "aiter_flash_attn"
25
+ unique_id = "27f5cac"
26
+ backend = get_backend()
27
+ return f"_{kernel_name}_{backend}_{unique_id}"
28
+
29
+
30
+ _OPS_NAME = _find_ops_name()
31
+
32
+ ops = getattr(torch.ops, _OPS_NAME)
33
+
34
+ def add_op_namespace_prefix(op_name: str) -> str:
35
+ """
36
+ Prefix op by namespace.
37
+ """
38
+ return f"{_OPS_NAME}::{op_name}"
build/torch-rocm/aiter_flash_attn/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch-rocm/metadata.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "aiter-flash-attn",
3
+ "id": "_aiter_flash_attn_rocm_27f5cac",
4
+ "version": 1,
5
+ "license": "MIT",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "rocm"
9
+ }
10
+ }
build/torch-rocm/mha.py ADDED
@@ -0,0 +1,1000 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ from typing import Literal, Optional, Tuple, Union
5
+ import torch
6
+ import triton
7
+ import triton.language as tl
8
+
9
+ from .utils import types
10
+ from .mha_onekernel_bwd import flash_attn_onekernel_backward
11
+ from .mha_fused_bwd import flash_attn_fused_backward
12
+ from .utils.logger import AiterTritonLogger
13
+ from .utils.device_info import get_num_xcds
14
+ from ._kernels.mha import _attn_fwd, _get_config
15
+ from ._kernels.flash_attn_triton_amd import flash_attn_2
16
+
17
+ _LOGGER = AiterTritonLogger()
18
+
19
+ global _USE_FUSED_BWD_KERNEL
20
+ _USE_FUSED_BWD_KERNEL = False
21
+
22
+
23
+ def mha_set_use_fused_bwd_kernel(value: bool):
24
+ """
25
+ Set whether to use fused backward kernel (with atomics) or one-kernel backward (without atomics).
26
+ Fused backward is faster but doesn't support positional encoding.
27
+ """
28
+ global _USE_FUSED_BWD_KERNEL
29
+ _USE_FUSED_BWD_KERNEL = value
30
+
31
+
32
+ _MHA_IMPL: Literal["default", "dao_ai"] = "default"
33
+
34
+
35
+ def mha_set_impl(impl: Literal["default", "dao_ai"]):
36
+ """Set MHA forward implementation: 'default' (_attn_fwd) or 'dao_ai' (flash_attn_triton_amd)."""
37
+ global _MHA_IMPL
38
+ _MHA_IMPL = impl
39
+
40
+
41
+ _USE_INT64_STRIDES = True
42
+
43
+
44
+ def mha_set_use_int64_strides(value: bool):
45
+ """Use 64-bit integer strides to prevent integer overflows with very large tensors."""
46
+ global _USE_INT64_STRIDES
47
+ _USE_INT64_STRIDES = value
48
+
49
+
50
+ def _get_sliding_window_size(window_size: Tuple[int, int]) -> int:
51
+ return int(window_size[0]) if int(window_size[0]) >= 0 else 0
52
+
53
+
54
+ def _flash_attn_forward(
55
+ q: torch.Tensor,
56
+ k: torch.Tensor,
57
+ v: torch.Tensor,
58
+ dropout_p: float,
59
+ softmax_scale: float,
60
+ causal: bool,
61
+ window_size_left: int,
62
+ window_size_right: int,
63
+ bias: Optional[torch.Tensor],
64
+ alibi_slopes: Optional[torch.Tensor],
65
+ return_lse: bool, # Not used
66
+ return_softmax: bool,
67
+ max_seqlen_q: int,
68
+ max_seqlen_k: int,
69
+ cu_seqlens_q: Optional[torch.Tensor] = None,
70
+ cu_seqlens_k: Optional[torch.Tensor] = None,
71
+ descale_q: Optional[torch.Tensor] = None,
72
+ descale_k: Optional[torch.Tensor] = None,
73
+ descale_v: Optional[torch.Tensor] = None,
74
+ sink: Optional[torch.Tensor] = None,
75
+ config: Optional[dict[str, any]] = None,
76
+ ) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], int, int]:
77
+
78
+ if bias is not None:
79
+ raise ValueError("Bias is not supported yet in the Triton Backend")
80
+ if window_size_right != -1:
81
+ raise ValueError("window_size_right is not supported yet in the Triton Backend")
82
+ sliding_window = window_size_left if window_size_left >= 0 else 0
83
+
84
+ # Triton cannot specialize on numpy scalar types; ensure native Python int
85
+ max_seqlen_q = int(max_seqlen_q)
86
+ max_seqlen_k = int(max_seqlen_k)
87
+
88
+ # FP8
89
+ IS_FP8 = types._is_fp8(q)
90
+ FP8_MAX: tl.constexpr = torch.finfo(q.dtype).max
91
+ is_varlen = True if cu_seqlens_q is not None else False
92
+
93
+ if IS_FP8:
94
+ o = torch.zeros(
95
+ (q.shape[:-1] + v.shape[-1:]), dtype=torch.float32, device=q.device
96
+ )
97
+ else:
98
+ o = torch.zeros((q.shape[:-1] + v.shape[-1:]), dtype=q.dtype, device=q.device)
99
+ if is_varlen:
100
+ # Layout is thd.
101
+ # q and k are [total_tokens, num_head, head_dim_qk].
102
+ # v is [total_tokens, num_head, head_dim_v].
103
+ batch, seqlen_q, num_q_heads = (
104
+ len(cu_seqlens_q) - 1,
105
+ max_seqlen_q,
106
+ q.shape[1],
107
+ )
108
+ num_k_heads = k.shape[1]
109
+ q_strides = (0, q.stride(1), q.stride(0), q.stride(2))
110
+ k_strides = (0, k.stride(1), k.stride(0), k.stride(2))
111
+ v_strides = (0, v.stride(1), v.stride(0), v.stride(2))
112
+ o_strides = (0, o.stride(1), o.stride(0), o.stride(2))
113
+ else:
114
+ # Layout is bshd.
115
+ # q and k are [batch, seq_len, num_head, head_dim_qk].
116
+ # v is [batch, seq_len, num_head, head_dim_v].
117
+ batch, seqlen_q, num_q_heads = (int(x) for x in q.shape[:-1])
118
+ num_k_heads = k.shape[2]
119
+ q_strides = (q.stride(0), q.stride(2), q.stride(1), q.stride(3))
120
+ k_strides = (k.stride(0), k.stride(2), k.stride(1), k.stride(3))
121
+ v_strides = (v.stride(0), v.stride(2), v.stride(1), v.stride(3))
122
+ o_strides = (o.stride(0), o.stride(2), o.stride(1), o.stride(3))
123
+
124
+ qk_head_dim = q.shape[-1]
125
+ v_head_dim = v.shape[-1]
126
+ pe_head_dim = qk_head_dim - v_head_dim
127
+ # padding for head_dim. Power of 2 or 16
128
+ BLOCK_DMODEL_POW2 = max(triton.next_power_of_2(v_head_dim), 16)
129
+ BLOCK_DMODEL_PE_POW2 = (
130
+ 0 if pe_head_dim == 0 else max(triton.next_power_of_2(pe_head_dim), 16)
131
+ )
132
+ assert (pe_head_dim == 0 and BLOCK_DMODEL_PE_POW2 == 0) or (
133
+ v_head_dim == BLOCK_DMODEL_POW2 and pe_head_dim == BLOCK_DMODEL_PE_POW2
134
+ ), "Positional encoding support requires NOPE and PE head sizes to be unpadded powers of 2."
135
+ assert (not IS_FP8) or (
136
+ IS_FP8 and pe_head_dim == 0
137
+ ), "Positional encoding doesn't support FP8."
138
+
139
+ assert (sink is None) or (
140
+ sink is not None and sink.dim() == 1 and sink.shape[0] == num_q_heads
141
+ ), "Sink must be 1D and have one element per query head."
142
+
143
+ # softmax_lse [batch, num_q_heads, seqlen_q]
144
+ if is_varlen:
145
+ softmax_lse = torch.zeros(
146
+ (q.shape[0], num_q_heads), device=q.device, dtype=torch.float32
147
+ )
148
+ stride_lse_z, stride_lse_h, stride_lse_m = (
149
+ 0,
150
+ softmax_lse.stride(1),
151
+ softmax_lse.stride(0),
152
+ )
153
+ else:
154
+ softmax_lse = torch.zeros(
155
+ (batch, num_q_heads, max_seqlen_q), device=q.device, dtype=torch.float32
156
+ )
157
+ stride_lse_z, stride_lse_h, stride_lse_m = softmax_lse.stride()
158
+
159
+ # exp_scores [batch, num_q_heads, seqlen_q, seqlen_k]
160
+ enable_dropout = dropout_p > 0.0
161
+ if enable_dropout:
162
+ philox_seed = torch.randint(0, 0xFFFFFF, (1,))[
163
+ 0
164
+ ].item() # No specific reason to restrict range to 0xffffff
165
+ philox_offset = torch.randint(0, 0xFFFFFF, (1,))[
166
+ 0
167
+ ].item() # Pass in an int, not Tensor
168
+ else:
169
+ philox_seed = 0
170
+ philox_offset = 0
171
+ if return_softmax or enable_dropout:
172
+ s_dmask = torch.zeros(
173
+ (batch, num_q_heads, max_seqlen_q, max_seqlen_k),
174
+ device=q.device,
175
+ dtype=torch.float32,
176
+ )
177
+ dropout_mask = torch.zeros(
178
+ (batch, num_q_heads, max_seqlen_q, max_seqlen_k),
179
+ device=q.device,
180
+ dtype=torch.float32,
181
+ )
182
+ else:
183
+ s_dmask = None
184
+ dropout_mask = None
185
+
186
+ if _MHA_IMPL == "dao_ai":
187
+ assert sink is None, "dao_ai impl does not support attention sink."
188
+ assert (
189
+ pe_head_dim == 0
190
+ ), "dao_ai impl does not support positional encoding (pe_head_dim > 0)."
191
+ assert (
192
+ not IS_FP8
193
+ ), "dao_ai impl does not support FP8. Use the default impl or FA3 path."
194
+ assert (
195
+ window_size_left == -1 and window_size_right == -1
196
+ ), "dao_ai impl does not support sliding window attention."
197
+ if is_varlen:
198
+ o, softmax_lse, s_dmask, _ = flash_attn_2.varlen_fwd(
199
+ q,
200
+ k,
201
+ v,
202
+ o,
203
+ cu_seqlens_q,
204
+ cu_seqlens_k,
205
+ seqused_k=None,
206
+ leftpad_k=None,
207
+ block_table_=None,
208
+ alibi_slopes=alibi_slopes,
209
+ max_seqlen_q=max_seqlen_q,
210
+ max_seqlen_k=max_seqlen_k,
211
+ dropout_p=dropout_p,
212
+ softmax_scale=softmax_scale,
213
+ zero_tensors=False,
214
+ causal=causal,
215
+ window_size_left=-1,
216
+ window_size_right=-1,
217
+ softcap=0.0,
218
+ return_softmax=return_softmax,
219
+ )
220
+ else:
221
+ o, softmax_lse, s_dmask, _ = flash_attn_2.fwd(
222
+ q,
223
+ k,
224
+ v,
225
+ o,
226
+ alibi_slopes,
227
+ dropout_p,
228
+ softmax_scale,
229
+ causal,
230
+ window_size_left=-1,
231
+ window_size_right=-1,
232
+ softcap=0.0,
233
+ return_softmax=return_softmax,
234
+ )
235
+ # Verify softmax_lse shape contract:
236
+ # non-varlen: (batch, nheads_q, seqlen_q)
237
+ # varlen: (nheads_q, total_q) — transposed vs default impl
238
+ if is_varlen:
239
+ assert softmax_lse.shape == (
240
+ num_q_heads,
241
+ q.shape[0],
242
+ ), f"dao_ai varlen softmax_lse shape {softmax_lse.shape} != expected ({num_q_heads}, {q.shape[0]})"
243
+ else:
244
+ assert (
245
+ softmax_lse.shape[0] == batch and softmax_lse.shape[1] == num_q_heads
246
+ ), f"dao_ai softmax_lse shape {softmax_lse.shape} != expected (batch={batch}, nheads={num_q_heads}, ...)"
247
+ else:
248
+ if config is None:
249
+ config = _get_config(enable_dropout, q.dtype, has_pe=pe_head_dim > 0)
250
+
251
+ grid = lambda META: ( # noqa: E731
252
+ batch * num_q_heads * triton.cdiv(seqlen_q, META["BLOCK_M"]),
253
+ )
254
+
255
+ _attn_fwd[grid](
256
+ q,
257
+ k,
258
+ v,
259
+ descale_q,
260
+ descale_k,
261
+ descale_v,
262
+ o,
263
+ alibi_slopes,
264
+ s_dmask,
265
+ dropout_mask,
266
+ softmax_lse,
267
+ sink,
268
+ *q_strides,
269
+ *k_strides,
270
+ *v_strides,
271
+ descale_q.stride(0) if descale_q is not None else 0,
272
+ descale_k.stride(0) if descale_k is not None else 0,
273
+ descale_v.stride(0) if descale_v is not None else 0,
274
+ *o_strides,
275
+ alibi_slopes.stride(0) if alibi_slopes is not None else 0,
276
+ alibi_slopes.stride(1) if alibi_slopes is not None else 0,
277
+ s_dmask.stride(0) if s_dmask is not None else 0,
278
+ s_dmask.stride(1) if s_dmask is not None else 0,
279
+ s_dmask.stride(2) if s_dmask is not None else 0,
280
+ s_dmask.stride(3) if s_dmask is not None else 0,
281
+ stride_lse_z if softmax_lse is not None else 0,
282
+ stride_lse_h if softmax_lse is not None else 0,
283
+ stride_lse_m if softmax_lse is not None else 0,
284
+ softmax_scale,
285
+ cu_seqlens_q,
286
+ cu_seqlens_k,
287
+ dropout_p,
288
+ philox_seed,
289
+ philox_offset,
290
+ SEQLEN_Q=max_seqlen_q,
291
+ SEQLEN_K=max_seqlen_k,
292
+ IS_CAUSAL=causal,
293
+ NUM_Q_HEADS=num_q_heads,
294
+ NUM_K_HEADS=num_k_heads,
295
+ BLOCK_DMODEL=v_head_dim,
296
+ BLOCK_DMODEL_POW2=BLOCK_DMODEL_POW2,
297
+ BLOCK_DMODEL_PE=pe_head_dim,
298
+ RETURN_SCORES=return_softmax,
299
+ ENABLE_DROPOUT=enable_dropout,
300
+ IS_FP8=IS_FP8,
301
+ FP8_MAX=FP8_MAX,
302
+ VARLEN=is_varlen,
303
+ BATCH=batch,
304
+ NUM_XCD=get_num_xcds(),
305
+ USE_INT64_STRIDES=_USE_INT64_STRIDES,
306
+ ENABLE_SINK=sink is not None,
307
+ SLIDING_WINDOW=sliding_window,
308
+ **config,
309
+ )
310
+
311
+ return o, softmax_lse, s_dmask, philox_seed, philox_offset
312
+
313
+
314
+ class _FlashAttnFunc(torch.autograd.Function):
315
+ @staticmethod
316
+ def forward(
317
+ ctx,
318
+ q,
319
+ k,
320
+ v,
321
+ dropout_p,
322
+ softmax_scale,
323
+ causal,
324
+ window_size,
325
+ bias,
326
+ alibi_slopes,
327
+ deterministic,
328
+ return_lse,
329
+ return_softmax,
330
+ sink,
331
+ is_grad_enabled,
332
+ config=None,
333
+ ):
334
+ is_grad = is_grad_enabled and any(
335
+ x is not None and x.requires_grad for x in [q, k, v, sink]
336
+ )
337
+ if softmax_scale is None:
338
+ softmax_scale = q.shape[-1] ** (-0.5)
339
+ head_size_og = q.size(3)
340
+ if head_size_og % 8 != 0:
341
+ q = torch.nn.functional.pad(q, [0, 8 - head_size_og % 8])
342
+ k = torch.nn.functional.pad(k, [0, 8 - head_size_og % 8])
343
+ v = torch.nn.functional.pad(v, [0, 8 - head_size_og % 8])
344
+ out_padded, softmax_lse, S_dmask, philox_seed, philox_offset = (
345
+ _flash_attn_forward(
346
+ q,
347
+ k,
348
+ v,
349
+ dropout_p,
350
+ softmax_scale,
351
+ causal=causal,
352
+ window_size_left=int(window_size[0]),
353
+ window_size_right=int(window_size[1]),
354
+ bias=bias,
355
+ alibi_slopes=alibi_slopes,
356
+ return_lse=return_lse,
357
+ return_softmax=return_softmax and dropout_p > 0,
358
+ max_seqlen_q=q.shape[1],
359
+ max_seqlen_k=k.shape[1],
360
+ sink=sink,
361
+ config=config,
362
+ )
363
+ )
364
+
365
+ if is_grad:
366
+ ctx.save_for_backward(q, k, v, out_padded, softmax_lse, sink)
367
+ ctx.philox_seed = philox_seed
368
+ ctx.philox_offset = philox_offset
369
+ ctx.dropout_p = dropout_p
370
+ ctx.softmax_scale = softmax_scale
371
+ ctx.causal = causal
372
+ ctx.bias = bias
373
+ ctx.window_size = window_size
374
+ ctx.alibi_slopes = alibi_slopes
375
+ ctx.deterministic = deterministic
376
+
377
+ out = out_padded[..., :head_size_og]
378
+ result = [out]
379
+ if return_lse:
380
+ result.append(softmax_lse)
381
+ if return_softmax:
382
+ result.append(S_dmask)
383
+
384
+ return result[0] if len(result) == 1 else tuple(result)
385
+
386
+ @staticmethod
387
+ def backward(ctx, do, *args):
388
+ q, k, v, out, softmax_lse, sink = ctx.saved_tensors
389
+ bias = ctx.bias
390
+ dbias = torch.empty_like(bias) if bias is not None else None
391
+ dq, dk, dv = torch.zeros_like(q), torch.empty_like(k), torch.empty_like(v)
392
+ dsink = (
393
+ torch.zeros_like(sink, dtype=torch.float32) if sink is not None else None
394
+ )
395
+ head_size_v_og = do.size(3)
396
+ do_padded = do
397
+ if head_size_v_og % 8 != 0:
398
+ do_padded = torch.nn.functional.pad(do, [0, 8 - head_size_v_og % 8])
399
+ sliding_window = _get_sliding_window_size(ctx.window_size)
400
+
401
+ if _MHA_IMPL == "dao_ai":
402
+ assert sink is None, "dao_ai impl does not support attention sink."
403
+ flash_attn_2.bwd(
404
+ do_padded,
405
+ q,
406
+ k,
407
+ v,
408
+ out,
409
+ softmax_lse,
410
+ dq,
411
+ dk,
412
+ dv,
413
+ ctx.alibi_slopes,
414
+ ctx.dropout_p,
415
+ ctx.softmax_scale,
416
+ ctx.causal,
417
+ window_size_left=-1,
418
+ window_size_right=-1,
419
+ softcap=0.0,
420
+ deterministic=ctx.deterministic,
421
+ )
422
+ else:
423
+ if _USE_FUSED_BWD_KERNEL:
424
+ if sliding_window > 0:
425
+ raise ValueError(
426
+ "Fused backward doesn't support sliding window attention. "
427
+ "Disable fused backward or use the one-kernel backward."
428
+ )
429
+ assert (
430
+ sink is None and dsink is None
431
+ ), "Fused backward doesn't support sinks."
432
+ flash_attn_fused_backward(
433
+ do_padded,
434
+ q,
435
+ k,
436
+ v,
437
+ out,
438
+ softmax_lse,
439
+ dq,
440
+ dk,
441
+ dv,
442
+ dbias,
443
+ ctx.softmax_scale,
444
+ ctx.alibi_slopes,
445
+ ctx.causal,
446
+ None,
447
+ None,
448
+ max_seqlen_q=q.shape[1],
449
+ max_seqlen_k=k.shape[1],
450
+ dropout_p=ctx.dropout_p,
451
+ philox_seed=ctx.philox_seed,
452
+ philox_offset=ctx.philox_offset,
453
+ USE_INT64_STRIDES=_USE_INT64_STRIDES,
454
+ )
455
+ else:
456
+ flash_attn_onekernel_backward(
457
+ do_padded,
458
+ q,
459
+ k,
460
+ v,
461
+ out,
462
+ softmax_lse,
463
+ dq,
464
+ dk,
465
+ dv,
466
+ dbias,
467
+ ctx.softmax_scale,
468
+ ctx.alibi_slopes,
469
+ ctx.causal,
470
+ None,
471
+ None,
472
+ max_seqlen_q=q.shape[1],
473
+ max_seqlen_k=k.shape[1],
474
+ dropout_p=ctx.dropout_p,
475
+ philox_seed=ctx.philox_seed,
476
+ philox_offset=ctx.philox_offset,
477
+ USE_INT64_STRIDES=_USE_INT64_STRIDES,
478
+ sink=sink,
479
+ dsink=dsink,
480
+ sliding_window=sliding_window,
481
+ )
482
+
483
+ dq = dq[..., : q.shape[-1]] # We could have padded the head dimension
484
+ dk = dk[..., : k.shape[-1]]
485
+ dv = dv[..., : v.shape[-1]]
486
+ return (
487
+ dq,
488
+ dk,
489
+ dv,
490
+ None, # dropout_p
491
+ None, # softmax_scale
492
+ None, # causal
493
+ None, # window_size
494
+ dbias,
495
+ None, # alibi_slopes
496
+ None, # deterministic
497
+ None, # return_lse
498
+ None, # return_softmax
499
+ dsink,
500
+ None, # is_grad_enabled
501
+ None, # config
502
+ )
503
+
504
+
505
+ def flash_attn_func(
506
+ q,
507
+ k,
508
+ v,
509
+ dropout_p=0.0,
510
+ softmax_scale=None,
511
+ causal=False,
512
+ window_size=(-1, -1), # -1 means infinite context window
513
+ bias=None,
514
+ alibi_slopes=None,
515
+ deterministic=True,
516
+ return_lse=False,
517
+ return_attn_probs=False,
518
+ sink=None,
519
+ config: Optional[dict[str, any]] = None,
520
+ ):
521
+ """dropout_p should be set to 0.0 during evaluation
522
+ Supports multi-query and grouped-query attention (MQA/GQA) by passing in KV with fewer heads
523
+ than Q. Note that the number of heads in Q must be divisible by the number of heads in KV.
524
+ For example, if Q has 6 heads and K, V have 2 heads, head 0, 1, 2 of Q will attention to head
525
+ 0 of K, V, and head 3, 4, 5 of Q will attention to head 1 of K, V.
526
+
527
+ If causal=True, the causal mask is aligned to the bottom right corner of the attention matrix.
528
+ For example, if seqlen_q = 2 and seqlen_k = 5, the causal mask (1 = keep, 0 = masked out) is:
529
+ 1 1 1 1 0
530
+ 1 1 1 1 1
531
+ If seqlen_q = 5 and seqlen_k = 2, the causal mask is:
532
+ 0 0
533
+ 0 0
534
+ 0 0
535
+ 1 0
536
+ 1 1
537
+ If the row of the mask is all zero, the output will be zero.
538
+
539
+ If window_size != (-1, -1), implements sliding window local attention. Query at position i
540
+ will only attend to keys between
541
+ [i + seqlen_k - seqlen_q - window_size[0], i + seqlen_k - seqlen_q + window_size[1]] inclusive.
542
+
543
+ Arguments:
544
+ q: (batch_size, seqlen, nheads, headdim)
545
+ k: (batch_size, seqlen, nheads_k, headdim)
546
+ v: (batch_size, seqlen, nheads_k, headdim)
547
+ dropout_p: float. Dropout probability.
548
+ softmax_scale: float. The scaling of QK^T before applying softmax.
549
+ Default to 1 / sqrt(headdim_q).
550
+ causal: bool. Whether to apply causal attention mask (e.g., for auto-regressive modeling).
551
+ window_size: (left, right). If not (-1, -1), implements sliding window local attention.
552
+ bias: (seqlen_q, seqlen_k)
553
+ alibi_slopes: (nheads,) or (batch_size, nheads), fp32. A bias of
554
+ (-alibi_slope * |i + seqlen_k - seqlen_q - j|)
555
+ is added to the attention score of query i and key j.
556
+ deterministic: bool. Whether to use the deterministic implementation of the backward pass,
557
+ which is slightly slower and uses more memory. The forward pass is always deterministic.
558
+ return_attn_probs: bool. Whether to return the attention probabilities. This option is for
559
+ testing only. The returned probabilities are not guaranteed to be correct
560
+ (they might not have the right scaling).
561
+ sink: (nheads,), attention sink scores (one per Q head), or None
562
+ Return:
563
+ out: (batch_size, seqlen, nheads, headdim).
564
+ softmax_lse [optional, if return_attn_probs=True]: (batch_size, nheads, seqlen). The
565
+ logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax
566
+ normalization factor).
567
+ S_dmask [optional, if return_attn_probs=True]: (batch_size, nheads, seqlen, seqlen).
568
+ The output of softmax (possibly with different scaling). It also encodes the dropout
569
+ pattern (negative means that location was dropped, nonnegative means it was kept).
570
+ """
571
+ _LOGGER.info(
572
+ f"FLASH_ATTN: q={tuple(q.shape)} k={tuple(k.shape)} v={tuple(v.shape)}"
573
+ )
574
+ return _FlashAttnFunc.apply(
575
+ q,
576
+ k,
577
+ v,
578
+ dropout_p,
579
+ softmax_scale,
580
+ causal,
581
+ window_size,
582
+ bias,
583
+ alibi_slopes,
584
+ deterministic,
585
+ return_lse,
586
+ return_attn_probs,
587
+ sink,
588
+ torch.is_grad_enabled(),
589
+ config,
590
+ )
591
+
592
+
593
+ class _FlashAttnVarlenFunc(torch.autograd.Function):
594
+ @staticmethod
595
+ def forward(
596
+ ctx,
597
+ q,
598
+ k,
599
+ v,
600
+ cu_seqlens_q,
601
+ cu_seqlens_k,
602
+ max_seqlen_q,
603
+ max_seqlen_k,
604
+ dropout_p,
605
+ softmax_scale,
606
+ causal,
607
+ window_size,
608
+ bias,
609
+ alibi_slopes,
610
+ deterministic,
611
+ return_lse,
612
+ return_softmax,
613
+ block_table,
614
+ out,
615
+ sink,
616
+ is_grad_enabled,
617
+ config=None,
618
+ ):
619
+ is_grad = is_grad_enabled and any(
620
+ x is not None and x.requires_grad for x in [q, k, v, sink]
621
+ )
622
+ if softmax_scale is None:
623
+ softmax_scale = q.shape[-1] ** (-0.5)
624
+ head_size_og = q.size(2)
625
+ if head_size_og % 8 != 0:
626
+ q = torch.nn.functional.pad(q, [0, 8 - head_size_og % 8])
627
+ k = torch.nn.functional.pad(k, [0, 8 - head_size_og % 8])
628
+ v = torch.nn.functional.pad(v, [0, 8 - head_size_og % 8])
629
+ out_padded, softmax_lse, S_dmask, philox_seed, philox_offset = (
630
+ _flash_attn_forward(
631
+ q,
632
+ k,
633
+ v,
634
+ dropout_p,
635
+ softmax_scale,
636
+ causal=causal,
637
+ window_size_left=int(window_size[0]),
638
+ window_size_right=int(window_size[1]),
639
+ bias=bias,
640
+ alibi_slopes=alibi_slopes,
641
+ return_lse=return_lse,
642
+ return_softmax=return_softmax and dropout_p > 0.0,
643
+ max_seqlen_q=max_seqlen_q,
644
+ max_seqlen_k=max_seqlen_k,
645
+ cu_seqlens_q=cu_seqlens_q,
646
+ cu_seqlens_k=cu_seqlens_k,
647
+ sink=sink,
648
+ config=config,
649
+ )
650
+ )
651
+ if is_grad:
652
+ ctx.save_for_backward(
653
+ q, k, v, out_padded, softmax_lse, cu_seqlens_q, cu_seqlens_k, sink
654
+ )
655
+ ctx.max_seqlen_q = max_seqlen_q
656
+ ctx.max_seqlen_k = max_seqlen_k
657
+ ctx.philox_seed = philox_seed
658
+ ctx.philox_offset = philox_offset
659
+ ctx.dropout_p = dropout_p
660
+ ctx.softmax_scale = softmax_scale
661
+ ctx.causal = causal
662
+ ctx.window_size = window_size
663
+ ctx.bias = bias
664
+ ctx.alibi_slopes = alibi_slopes
665
+ out = out_padded[..., :head_size_og]
666
+
667
+ result = [out]
668
+ if return_lse:
669
+ result.append(softmax_lse)
670
+ if return_softmax:
671
+ result.append(S_dmask)
672
+
673
+ return result[0] if len(result) == 1 else tuple(result)
674
+
675
+ @staticmethod
676
+ def backward(ctx, do, *args):
677
+ q, k, v, out, softmax_lse, cu_seqlens_q, cu_seqlens_k, sink = ctx.saved_tensors
678
+ dq, dk, dv = torch.zeros_like(q), torch.empty_like(k), torch.empty_like(v)
679
+ bias = ctx.bias
680
+ dbias = torch.empty_like(bias) if bias is not None else None
681
+ dsink = (
682
+ torch.zeros_like(sink, dtype=torch.float32) if sink is not None else None
683
+ )
684
+ head_size_og = do.size(2)
685
+ do_padded = do
686
+ if head_size_og % 8 != 0:
687
+ do_padded = torch.nn.functional.pad(do, [0, 8 - head_size_og % 8])
688
+ sliding_window = _get_sliding_window_size(ctx.window_size)
689
+
690
+ if _MHA_IMPL == "dao_ai":
691
+ assert sink is None, "dao_ai impl does not support attention sink."
692
+ flash_attn_2.varlen_bwd(
693
+ do_padded,
694
+ q,
695
+ k,
696
+ v,
697
+ out,
698
+ softmax_lse,
699
+ dq,
700
+ dk,
701
+ dv,
702
+ cu_seqlens_q,
703
+ cu_seqlens_k,
704
+ ctx.alibi_slopes,
705
+ max_seqlen_q=ctx.max_seqlen_q,
706
+ max_seqlen_k=ctx.max_seqlen_k,
707
+ dropout_p=ctx.dropout_p,
708
+ softmax_scale=ctx.softmax_scale,
709
+ zero_tensors=False,
710
+ causal=ctx.causal,
711
+ window_size_left=-1,
712
+ window_size_right=-1,
713
+ softcap=0.0,
714
+ deterministic=False,
715
+ )
716
+ else:
717
+ if _USE_FUSED_BWD_KERNEL:
718
+ if sliding_window > 0:
719
+ raise ValueError(
720
+ "Fused backward doesn't support sliding window attention. "
721
+ "Disable fused backward or use the one-kernel backward."
722
+ )
723
+ assert (
724
+ sink is None and dsink is None
725
+ ), "Fused backward doesn't support sinks."
726
+ flash_attn_fused_backward(
727
+ do_padded,
728
+ q,
729
+ k,
730
+ v,
731
+ out,
732
+ softmax_lse,
733
+ dq,
734
+ dk,
735
+ dv,
736
+ dbias,
737
+ ctx.softmax_scale,
738
+ ctx.alibi_slopes,
739
+ ctx.causal,
740
+ cu_seqlens_q,
741
+ cu_seqlens_k,
742
+ max_seqlen_q=ctx.max_seqlen_q,
743
+ max_seqlen_k=ctx.max_seqlen_k,
744
+ dropout_p=ctx.dropout_p,
745
+ philox_seed=ctx.philox_seed,
746
+ philox_offset=ctx.philox_offset,
747
+ USE_INT64_STRIDES=_USE_INT64_STRIDES,
748
+ )
749
+ else:
750
+ flash_attn_onekernel_backward(
751
+ do_padded,
752
+ q,
753
+ k,
754
+ v,
755
+ out,
756
+ softmax_lse,
757
+ dq,
758
+ dk,
759
+ dv,
760
+ dbias,
761
+ ctx.softmax_scale,
762
+ ctx.alibi_slopes,
763
+ ctx.causal,
764
+ cu_seqlens_q,
765
+ cu_seqlens_k,
766
+ max_seqlen_q=ctx.max_seqlen_q,
767
+ max_seqlen_k=ctx.max_seqlen_k,
768
+ dropout_p=ctx.dropout_p,
769
+ philox_seed=ctx.philox_seed,
770
+ philox_offset=ctx.philox_offset,
771
+ USE_INT64_STRIDES=_USE_INT64_STRIDES,
772
+ sink=sink,
773
+ dsink=dsink,
774
+ sliding_window=sliding_window,
775
+ )
776
+
777
+ dq = dq[..., : q.shape[-1]] # We could have padded the head dimension
778
+ dk = dk[..., : k.shape[-1]]
779
+ dv = dv[..., : v.shape[-1]]
780
+ return (
781
+ dq,
782
+ dk,
783
+ dv,
784
+ None, # cu_seqlens_q,
785
+ None, # cu_seqlens_k
786
+ None, # max_seqlen_q
787
+ None, # max_seqlen_k
788
+ None, # dropout_p
789
+ None, # softmax_scale
790
+ None, # causal
791
+ None, # window_size
792
+ dbias,
793
+ None, # alibi_slopes
794
+ None, # deterministic
795
+ None, # return_lse
796
+ None, # return_softmax
797
+ None, # block_table
798
+ None, # out
799
+ dsink,
800
+ None, # is_grad_enabled
801
+ None, # config
802
+ )
803
+
804
+
805
+ def flash_attn_varlen_func(
806
+ q,
807
+ k,
808
+ v,
809
+ cu_seqlens_q,
810
+ cu_seqlens_k,
811
+ max_seqlen_q,
812
+ max_seqlen_k,
813
+ dropout_p=0.0,
814
+ softmax_scale=None,
815
+ causal=False,
816
+ window_size=(-1, -1), # -1 means infinite context window
817
+ bias=None,
818
+ alibi_slopes=None,
819
+ deterministic=False,
820
+ return_lse=False,
821
+ return_attn_probs=False,
822
+ block_table=None,
823
+ out=None,
824
+ sink=None,
825
+ config: Optional[dict[str, any]] = None,
826
+ ):
827
+ """dropout_p should be set to 0.0 during evaluation
828
+ Supports multi-query and grouped-query attention (MQA/GQA) by passing in K, V with fewer heads
829
+ than Q. Note that the number of heads in Q must be divisible by the number of heads in KV.
830
+ For example, if Q has 6 heads and K, V have 2 heads, head 0, 1, 2 of Q will attention to head
831
+ 0 of K, V, and head 3, 4, 5 of Q will attention to head 1 of K, V.
832
+
833
+ If causal=True, the causal mask is aligned to the bottom right corner of the attention matrix.
834
+ For example, if seqlen_q = 2 and seqlen_k = 5, the causal mask (1 = keep, 0 = masked out) is:
835
+ 1 1 1 1 0
836
+ 1 1 1 1 1
837
+ If seqlen_q = 5 and seqlen_k = 2, the causal mask is:
838
+ 0 0
839
+ 0 0
840
+ 0 0
841
+ 1 0
842
+ 1 1
843
+ If the row of the mask is all zero, the output will be zero.
844
+
845
+ If window_size != (-1, -1), implements sliding window local attention. Query at position i
846
+ will only attend to keys between
847
+ [i + seqlen_k - seqlen_q - window_size[0], i + seqlen_k - seqlen_q + window_size[1]] inclusive.
848
+
849
+ Arguments:
850
+ q: (total_q, nheads, headdim), where total_q = total number of query tokens in the batch.
851
+ k: (total_k, nheads_k, headdim), where total_k = total number of key tokens in the batch.
852
+ v: (total_k, nheads_k, headdim), where total_k = total number of key tokens in the batch.
853
+ cu_seqlens_q: (batch_size + 1,), dtype torch.int32. The cumulative sequence lengths
854
+ of the sequences in the batch, used to index into q.
855
+ cu_seqlens_k: (batch_size + 1,), dtype torch.int32. The cumulative sequence lengths
856
+ of the sequences in the batch, used to index into kv.
857
+ max_seqlen_q: int. Maximum query sequence length in the batch.
858
+ max_seqlen_k: int. Maximum key sequence length in the batch.
859
+ dropout_p: float. Dropout probability.
860
+ softmax_scale: float. The scaling of QK^T before applying softmax.
861
+ Default to 1 / sqrt(headdim).
862
+ causal: bool. Whether to apply causal attention mask (e.g., for auto-regressive modeling).
863
+ window_size: (left, right). If not (-1, -1), implements sliding window local attention.
864
+ bias: (seqlen_q, seqlen_k)
865
+ alibi_slopes: (nheads,) or (batch_size, nheads), fp32. A bias of
866
+ (-alibi_slope * |i + seqlen_k - seqlen_q - j|)
867
+ is added to the attention score of query i and key j.
868
+ deterministic: bool. Whether to use the deterministic implementation of the backward pass,
869
+ which is slightly slower and uses more memory. The forward pass is always deterministic.
870
+ return_attn_probs: bool. Whether to return the attention probabilities. This option is for
871
+ testing only. The returned probabilities are not guaranteed to be correct
872
+ (they might not have the right scaling).
873
+ sink: (nheads,), attention sink scores (one per Q head), or None
874
+ Return:
875
+ out: (total, nheads, headdim).
876
+ softmax_lse [optional, if return_attn_probs=True]: (nheads, total_q_seqlen). The
877
+ logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax
878
+ normalization factor).
879
+ S_dmask [optional, if return_attn_probs=True]: (batch_size, nheads, seqlen, seqlen).
880
+ The output of softmax (possibly with different scaling). It also encodes the dropout
881
+ pattern (negative means that location was dropped, nonnegative means it was kept).
882
+ """
883
+
884
+ _LOGGER.info(
885
+ f"FLASH_ATTN_VARLEN: q={tuple(q.shape)} k={tuple(k.shape)} v={tuple(v.shape)}"
886
+ )
887
+ return _FlashAttnVarlenFunc.apply(
888
+ q,
889
+ k,
890
+ v,
891
+ cu_seqlens_q,
892
+ cu_seqlens_k,
893
+ max_seqlen_q,
894
+ max_seqlen_k,
895
+ dropout_p,
896
+ softmax_scale,
897
+ causal,
898
+ window_size,
899
+ bias,
900
+ alibi_slopes,
901
+ deterministic,
902
+ return_lse,
903
+ return_attn_probs,
904
+ block_table,
905
+ out,
906
+ sink,
907
+ torch.is_grad_enabled(),
908
+ config,
909
+ )
910
+
911
+
912
+ def flash_attn_with_kvcache(
913
+ q: torch.Tensor,
914
+ k_cache: torch.Tensor,
915
+ v_cache: torch.Tensor,
916
+ k: Optional[torch.Tensor] = None,
917
+ v: Optional[torch.Tensor] = None,
918
+ cache_seqlens: Optional[Union[torch.Tensor, int]] = None,
919
+ softmax_scale: Optional[float] = None,
920
+ causal: bool = True,
921
+ window_size: tuple[int, int] = (-1, -1),
922
+ softcap: float = 0.0,
923
+ num_splits: int = 0,
924
+ rotary_cos: Optional[torch.Tensor] = None,
925
+ rotary_sin: Optional[torch.Tensor] = None,
926
+ cache_batch_idx: Optional[torch.Tensor] = None,
927
+ cache_leftpad: Optional[torch.Tensor] = None,
928
+ block_table: Optional[torch.Tensor] = None,
929
+ alibi_slopes: Optional[torch.Tensor] = None,
930
+ rotary_interleaved: bool = True,
931
+ return_softmax_lse: bool = False,
932
+ ):
933
+ """
934
+ This mirrors the public flash_attn v2 interface for KV cache using the AMD Triton backend.
935
+
936
+ Args:
937
+ q: (batch, seqlen_q, nheads_q, headdim)
938
+ k_cache / v_cache: Either contiguous (batch, seqlen_cache, nheads_k, headdim) or paged
939
+ (num_blocks, page_block_size, nheads_k, headdim) when block_table provided.
940
+ k, v: Optional incremental tokens to append in-place (appended logically after existing cache).
941
+ cache_seqlens: int or (batch,) current valid lengths per batch entry.
942
+ softmax_scale: Optional override; defaults to 1/sqrt(headdim).
943
+ causal: Apply causal masking.
944
+ window_size: (left, right) local attention window; (-1,-1) = full.
945
+ softcap: (float) currently must be 0.0 (backend limitation).
946
+ num_splits: 0 or 1 only (backend limitation >1).
947
+ rotary_cos/rotary_sin: Optional rotary embeddings (applied if provided) - interleaving flag unused here.
948
+ cache_batch_idx/cache_leftpad: Optional indexing / left padding metadata.
949
+ block_table: Optional paging table mapping logical blocks for paged KV cache.
950
+ alibi_slopes: (nheads,) or (batch,nheads) bias slopes (currently ignored if provided - placeholder).
951
+ rotary_interleaved: Flag kept for parity (currently forwarded as True constant to backend which ignores it).
952
+ return_softmax_lse: If True returns (out, lse) else out.
953
+
954
+ Returns:
955
+ out (and optionally softmax_lse): (batch, seqlen_q, nheads_q, headdim)
956
+ """
957
+ # Feature guards / normalization
958
+ if softcap != 0.0:
959
+ raise NotImplementedError(
960
+ "softcap != 0 not supported in v2 KV cache backend yet"
961
+ )
962
+ if num_splits not in (0, 1):
963
+ raise NotImplementedError(
964
+ "num_splits > 1 not supported in v2 KV cache backend yet"
965
+ )
966
+
967
+ if softmax_scale is None:
968
+ softmax_scale = q.shape[-1] ** (-0.5)
969
+
970
+ if cache_seqlens is not None and isinstance(cache_seqlens, int):
971
+ cache_seqlens = torch.full(
972
+ (k_cache.shape[0],), cache_seqlens, dtype=torch.int32, device=k_cache.device
973
+ )
974
+
975
+ # Contiguity (align last dim contiguous requirement similar to v3 path assumptions)
976
+ assert q.stride(-1) == 1 and k_cache.stride(-1) == 1 and v_cache.stride(-1) == 1
977
+
978
+ out, softmax_lse = flash_attn_2.fwd_kvcache(
979
+ q,
980
+ k_cache,
981
+ v_cache,
982
+ k,
983
+ v,
984
+ cache_seqlens,
985
+ rotary_cos,
986
+ rotary_sin,
987
+ cache_batch_idx,
988
+ cache_leftpad,
989
+ block_table,
990
+ alibi_slopes,
991
+ None, # out tensor
992
+ softmax_scale,
993
+ causal,
994
+ int(window_size[0]),
995
+ int(window_size[1]),
996
+ 0.0, # softcap (guarded)
997
+ rotary_interleaved,
998
+ num_splits,
999
+ )
1000
+ return (out, softmax_lse) if return_softmax_lse else out
build/torch-rocm/mha_fused_bwd.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ from typing import Optional, Dict
5
+ import torch
6
+ import triton
7
+
8
+ from .utils.types import _is_fp8
9
+ from .utils.logger import AiterTritonLogger
10
+ from ._kernels.mha_fused_bwd import (
11
+ _bwd_preprocess,
12
+ _bwd_kernel_dkdvdq_causal,
13
+ _bwd_kernel_dkdvdq_noncausal,
14
+ _get_config,
15
+ )
16
+ from .utils.device_info import get_num_xcds
17
+
18
+ _LOGGER = AiterTritonLogger()
19
+
20
+
21
+ def flash_attn_fused_backward(
22
+ do: torch.Tensor,
23
+ q: torch.Tensor,
24
+ k: torch.Tensor,
25
+ v: torch.Tensor,
26
+ o: torch.Tensor,
27
+ softmax_lse: torch.Tensor,
28
+ dq: torch.Tensor,
29
+ dk: torch.Tensor,
30
+ dv: torch.Tensor,
31
+ dbias: torch.Tensor,
32
+ sm_scale: float,
33
+ alibi_slopes: Optional[torch.Tensor],
34
+ causal: bool,
35
+ cu_seqlens_q: Optional[torch.Tensor],
36
+ cu_seqlens_k: Optional[torch.Tensor],
37
+ max_seqlen_q: int,
38
+ max_seqlen_k: int,
39
+ dropout_p: float,
40
+ philox_seed: Optional[int] = 0,
41
+ philox_offset: Optional[int] = 0,
42
+ descale_q: Optional[torch.Tensor] = None,
43
+ descale_k: Optional[torch.Tensor] = None,
44
+ descale_v: Optional[torch.Tensor] = None,
45
+ descale_do: Optional[torch.Tensor] = None,
46
+ USE_INT64_STRIDES: Optional[bool] = False,
47
+ config: Optional[Dict[str, any]] = None,
48
+ ):
49
+ """
50
+ Flash Attention fused backward pass computing dQ, dK, dV in a single kernel using atomics.
51
+ Supports variable-length sequences, GQA, FP8 quantization, and dropout.
52
+
53
+ Args:
54
+ do (torch.Tensor): Output gradient. Shape (batch, seqlen_q, num_q_heads, head_dim)
55
+ or (total_tokens, num_q_heads, head_dim) for varlen.
56
+ q (torch.Tensor): Query tensor from forward pass with same shape as do.
57
+ k (torch.Tensor): Key tensor with shape (batch, seqlen_k, num_k_heads, head_dim)
58
+ or (total_tokens_k, num_k_heads, head_dim) for varlen.
59
+ v (torch.Tensor): Value tensor with same shape as k.
60
+ o (torch.Tensor): Output from forward pass with same shape as q.
61
+ softmax_lse (torch.Tensor): Log-sum-exp from forward pass with shape
62
+ (batch, num_q_heads, seqlen_q) or (total_tokens, num_q_heads) for varlen.
63
+ dq (torch.Tensor): Pre-allocated query gradient with same shape as q.
64
+ dk (torch.Tensor): Pre-allocated key gradient with same shape as k.
65
+ dv (torch.Tensor): Pre-allocated value gradient with same shape as v.
66
+ dbias (torch.Tensor): Bias gradient (not supported, must be None).
67
+ sm_scale (float): Softmax scale, typically 1/sqrt(head_dim).
68
+ alibi_slopes (Optional[torch.Tensor]): ALiBi position bias slopes.
69
+ causal (bool): Apply causal masking.
70
+ cu_seqlens_q (Optional[torch.Tensor]): Cumulative sequence lengths for query with shape
71
+ (batch + 1,). Enables variable-length mode.
72
+ cu_seqlens_k (Optional[torch.Tensor]): Cumulative sequence lengths for key with shape
73
+ (batch + 1,).
74
+ max_seqlen_q (int): Maximum query sequence length in batch.
75
+ max_seqlen_k (int): Maximum key sequence length in batch.
76
+ dropout_p (float): Dropout probability. 0.0 disables dropout.
77
+ philox_seed (Optional[int]): Random seed for dropout.
78
+ philox_offset (Optional[int]): Random offset for dropout.
79
+ descale_q (Optional[torch.Tensor]): FP8 descaling factor for q.
80
+ descale_k (Optional[torch.Tensor]): FP8 descaling factor for k.
81
+ descale_v (Optional[torch.Tensor]): FP8 descaling factor for v.
82
+ descale_do (Optional[torch.Tensor]): FP8 descaling factor for do.
83
+ USE_INT64_STRIDES (Optional[bool]): Use 64-bit stride indexing for large tensors.
84
+ config (Optional[Dict[str, any]]): Kernel tuning parameters (preprocess_kernel,
85
+ dkdvdq_kernel_N64, dkdvdq_kernel_N128).
86
+
87
+ Returns:
88
+ torch.Tensor: Delta tensor (element-wise product of do and o) with shape matching softmax_lse.
89
+ """
90
+ _LOGGER.info(
91
+ f"FLASH_ATTN_FUSED_BKWD: do={tuple(do.shape)} q={tuple(q.shape)} k={tuple(k.shape)} v={tuple(v.shape)} "
92
+ + f"dq={tuple(dq.shape)} dk={tuple(dk.shape)} dv={tuple(dv.shape)}"
93
+ )
94
+ if dbias is not None:
95
+ raise ValueError("Bias is not supported yet in the Triton Backend")
96
+ if q.shape[-1] == k.shape[-1] and k.shape[-1] > v.shape[-1]:
97
+ raise ValueError(
98
+ "'Fused' backward doesn't support Positional Encoding (PE). Please use 'one kernel' backward implementation for PE."
99
+ )
100
+
101
+ IS_FP8 = _is_fp8(q)
102
+ if IS_FP8:
103
+ FP8_MAX = torch.finfo(q.dtype).max
104
+ descale_strides = (
105
+ descale_q.stride(0),
106
+ descale_k.stride(0),
107
+ descale_v.stride(0),
108
+ descale_do.stride(0),
109
+ )
110
+ else:
111
+ FP8_MAX = None
112
+ stride_descale_q_z = stride_descale_k_z = stride_descale_v_z = (
113
+ stride_descale_do_z
114
+ ) = None
115
+ descale_strides = (
116
+ stride_descale_q_z,
117
+ stride_descale_k_z,
118
+ stride_descale_v_z,
119
+ stride_descale_do_z,
120
+ )
121
+
122
+ IS_VARLEN = True if cu_seqlens_q is not None else False
123
+
124
+ # get strides and shape
125
+ if IS_VARLEN:
126
+ # Layout for q,k,v is thd ie [total tokens, num_head, head_dim]
127
+ batch, seqlen_q, num_q_heads, head_sz = (
128
+ len(cu_seqlens_q) - 1,
129
+ max_seqlen_q,
130
+ q.shape[1],
131
+ q.shape[2],
132
+ )
133
+ _, num_k_heads = max_seqlen_k, k.shape[1]
134
+ q_strides = (0, q.stride(1), q.stride(0), q.stride(2))
135
+ q_strides = (0, q.stride(1), q.stride(0), q.stride(2))
136
+ k_strides = (0, k.stride(1), k.stride(0), k.stride(2))
137
+ v_strides = (0, v.stride(1), v.stride(0), v.stride(2))
138
+ o_strides = (0, o.stride(1), o.stride(0), o.stride(2))
139
+ dq_strides = (0, dq.stride(1), dq.stride(0), dq.stride(2))
140
+ dk_strides = (0, dk.stride(1), dk.stride(0), dk.stride(2))
141
+ do_strides = (0, do.stride(1), do.stride(0), do.stride(2))
142
+ else:
143
+ # Layout for q,k,v is bshd ie [batch, seq_len, num_head, head_dim]
144
+ batch, seqlen_q, num_q_heads, head_sz = q.shape
145
+ _, num_k_heads = k.shape[1], k.shape[2]
146
+ q_strides = (q.stride(0), q.stride(2), q.stride(1), q.stride(3))
147
+ k_strides = (k.stride(0), k.stride(2), k.stride(1), k.stride(3))
148
+ v_strides = (v.stride(0), v.stride(2), v.stride(1), v.stride(3))
149
+ o_strides = (o.stride(0), o.stride(2), o.stride(1), o.stride(3))
150
+ dq_strides = (dq.stride(0), dq.stride(2), dq.stride(1), dq.stride(3))
151
+ dk_strides = (dk.stride(0), dk.stride(2), dk.stride(1), dk.stride(3))
152
+ do_strides = (do.stride(0), do.stride(2), do.stride(1), do.stride(3))
153
+
154
+ # BLOCK_D_MODEL, BLOCK_D_MODEL_POW2
155
+ # padding for head_dim. Power of 2 or 16
156
+ BLOCK_D_MODEL_POW2 = triton.next_power_of_2(head_sz)
157
+ BLOCK_D_MODEL_POW2 = max(BLOCK_D_MODEL_POW2, 16)
158
+
159
+ # init delta
160
+ delta = torch.zeros_like(softmax_lse)
161
+ if IS_VARLEN:
162
+ # [total_tokens, num_q_heads, seqlen_q]
163
+ delta_strides = (0, delta.stride(1), delta.stride(0))
164
+ else:
165
+ # [batch, num_q_heads, seqlen_q]
166
+ delta_strides = delta.stride()
167
+
168
+ # preprocess
169
+ # compute D(delta) = rowsum(dO*O). Note, multiplication is element-wise.
170
+ if config is None:
171
+ config = _get_config()
172
+
173
+ pre_grid = (
174
+ triton.cdiv(max_seqlen_q, config["preprocess_kernel"]["PRE_BLOCK"]),
175
+ batch,
176
+ num_q_heads,
177
+ )
178
+
179
+ _bwd_preprocess[pre_grid](
180
+ o,
181
+ do,
182
+ delta,
183
+ *o_strides,
184
+ *do_strides,
185
+ *delta_strides,
186
+ descale_strides[3],
187
+ cu_seqlens_q,
188
+ max_seqlen_q,
189
+ descale_do,
190
+ BLOCK_M=config["preprocess_kernel"]["PRE_BLOCK"],
191
+ BLOCK_D_MODEL=head_sz,
192
+ BLOCK_D_MODEL_POW2=BLOCK_D_MODEL_POW2,
193
+ IS_VARLEN=IS_VARLEN,
194
+ IS_FP8=IS_FP8,
195
+ )
196
+ # dropout_mask
197
+ use_dropout = dropout_p > 0.0
198
+ if use_dropout:
199
+ dropout_mask = torch.zeros(
200
+ (batch, num_q_heads, max_seqlen_q, max_seqlen_k),
201
+ device=q.device,
202
+ dtype=torch.float32,
203
+ )
204
+ dropout_strides = dropout_mask.stride()
205
+ else:
206
+ dropout_mask = None
207
+ dropout_strides = (0, 0, 0, 0)
208
+
209
+ # Fuses dk,dv and dq computations into one kernel using atomics
210
+ if BLOCK_D_MODEL_POW2 > 160 or q.dtype == torch.float32:
211
+ config_dkdvdq = config["dkdvdq_kernel_N64"]
212
+ else:
213
+ config_dkdvdq = config["dkdvdq_kernel_N128"]
214
+
215
+ num_k_pids = (max_seqlen_k + config_dkdvdq["BLOCK_N"] - 1) // config_dkdvdq[
216
+ "BLOCK_N"
217
+ ]
218
+ # NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
219
+ if causal:
220
+ grid_dkdvdq = (batch * num_q_heads * num_k_pids,)
221
+
222
+ _bwd_kernel_dkdvdq_causal[grid_dkdvdq](
223
+ q,
224
+ k,
225
+ v,
226
+ sm_scale,
227
+ do,
228
+ dk,
229
+ dv,
230
+ dq,
231
+ softmax_lse,
232
+ delta,
233
+ *q_strides,
234
+ *k_strides,
235
+ *v_strides,
236
+ *dk_strides,
237
+ *dq_strides,
238
+ *delta_strides,
239
+ *do_strides,
240
+ *dropout_strides,
241
+ *descale_strides,
242
+ cu_seqlens_q,
243
+ cu_seqlens_k,
244
+ max_seqlen_q,
245
+ max_seqlen_k,
246
+ dropout_mask,
247
+ dropout_p,
248
+ philox_seed,
249
+ philox_offset,
250
+ descale_q,
251
+ descale_k,
252
+ descale_v,
253
+ descale_do,
254
+ NUM_Q_HEADS=num_q_heads,
255
+ NUM_K_HEADS=num_k_heads,
256
+ BATCH=batch,
257
+ NUM_K_PIDS=num_k_pids,
258
+ BLOCK_D_MODEL=head_sz,
259
+ BLOCK_D_MODEL_POW2=BLOCK_D_MODEL_POW2,
260
+ ENABLE_DROPOUT=use_dropout,
261
+ IS_VARLEN=IS_VARLEN,
262
+ IS_FP8=IS_FP8,
263
+ FP8_MAX=FP8_MAX,
264
+ USE_INT64_STRIDES=USE_INT64_STRIDES,
265
+ NUM_XCD=get_num_xcds(),
266
+ **config_dkdvdq,
267
+ )
268
+ else:
269
+ # in non causal inner loop over grouped q heads
270
+ grid_dkdvdq = (batch * num_k_heads * num_k_pids,)
271
+ _bwd_kernel_dkdvdq_noncausal[grid_dkdvdq](
272
+ q,
273
+ k,
274
+ v,
275
+ sm_scale,
276
+ do,
277
+ dk,
278
+ dv,
279
+ dq,
280
+ softmax_lse,
281
+ delta,
282
+ *q_strides,
283
+ *k_strides,
284
+ *v_strides,
285
+ *dk_strides,
286
+ *dq_strides,
287
+ *delta_strides,
288
+ *do_strides,
289
+ *dropout_strides,
290
+ *descale_strides,
291
+ cu_seqlens_q,
292
+ cu_seqlens_k,
293
+ max_seqlen_q,
294
+ max_seqlen_k,
295
+ dropout_mask,
296
+ dropout_p,
297
+ philox_seed,
298
+ philox_offset,
299
+ descale_q,
300
+ descale_k,
301
+ descale_v,
302
+ descale_do,
303
+ NUM_Q_HEADS=num_q_heads,
304
+ NUM_K_HEADS=num_k_heads,
305
+ BATCH=batch,
306
+ NUM_K_PIDS=num_k_pids,
307
+ BLOCK_D_MODEL=head_sz,
308
+ BLOCK_D_MODEL_POW2=BLOCK_D_MODEL_POW2,
309
+ ENABLE_DROPOUT=use_dropout,
310
+ IS_VARLEN=IS_VARLEN,
311
+ IS_FP8=IS_FP8,
312
+ FP8_MAX=FP8_MAX,
313
+ USE_INT64_STRIDES=USE_INT64_STRIDES,
314
+ **config_dkdvdq,
315
+ )
316
+
317
+ return delta
build/torch-rocm/mha_onekernel_bwd.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ from typing import Optional, Dict
5
+ import torch
6
+ import triton # type: ignore
7
+ import triton.language as tl # type: ignore
8
+ from .utils.types import _is_fp8
9
+ from .utils.logger import AiterTritonLogger
10
+
11
+ from ._kernels.mha_onekernel_bwd import (
12
+ _bwd_preprocess,
13
+ bwd_kernel_causal,
14
+ bwd_kernel_noncausal,
15
+ _get_config,
16
+ )
17
+
18
+ _LOGGER = AiterTritonLogger()
19
+
20
+
21
+ # NOTE: triton fails to import tl.constexprs so create them here for the file
22
+ DROPOUT_USE_PYTORCH = False
23
+ DROPOUT_DUMP = False
24
+
25
+ tl_DROPOUT_USE_PYTORCH: tl.constexpr = triton.language.constexpr(DROPOUT_USE_PYTORCH)
26
+ tl_DROPOUT_DUMP: tl.constexpr = triton.language.constexpr(DROPOUT_DUMP)
27
+
28
+
29
+ def flash_attn_onekernel_backward(
30
+ do: torch.Tensor,
31
+ q: torch.Tensor,
32
+ k: torch.Tensor,
33
+ v: torch.Tensor,
34
+ o: torch.Tensor,
35
+ softmax_lse: torch.Tensor,
36
+ dq: torch.Tensor,
37
+ dk: torch.Tensor,
38
+ dv: torch.Tensor,
39
+ dbias: torch.Tensor,
40
+ sm_scale: float,
41
+ alibi_slopes: Optional[torch.Tensor],
42
+ causal: bool,
43
+ cu_seqlens_q: Optional[torch.Tensor],
44
+ cu_seqlens_k: Optional[torch.Tensor],
45
+ max_seqlen_q: int,
46
+ max_seqlen_k: int,
47
+ dropout_p: float,
48
+ philox_seed: Optional[int] = 0,
49
+ philox_offset: Optional[int] = 0,
50
+ descale_q: Optional[torch.Tensor] = None,
51
+ descale_k: Optional[torch.Tensor] = None,
52
+ descale_v: Optional[torch.Tensor] = None,
53
+ descale_do: Optional[torch.Tensor] = None,
54
+ USE_INT64_STRIDES: Optional[bool] = False,
55
+ sink: Optional[torch.Tensor] = None,
56
+ dsink: Optional[torch.Tensor] = None,
57
+ config: Optional[Dict[str, any]] = None,
58
+ sliding_window: int = 0,
59
+ ):
60
+ """
61
+ Flash Attention one-kernel backward pass with positional encoding support.
62
+ Computes dQ, dK, dV in separate passes without atomics. Supports Q/K head dimensions
63
+ larger than V for positional encoding.
64
+
65
+ Args:
66
+ do (torch.Tensor): Output gradient. Shape (batch, seqlen_q, num_q_heads, v_head_dim)
67
+ or (total_tokens, num_q_heads, v_head_dim) for varlen.
68
+ q (torch.Tensor): Query tensor with shape (batch, seqlen_q, num_q_heads, qk_head_dim).
69
+ qk_head_dim may be larger than v_head_dim for positional encoding.
70
+ k (torch.Tensor): Key tensor with shape (batch, seqlen_k, num_k_heads, qk_head_dim).
71
+ v (torch.Tensor): Value tensor with shape (batch, seqlen_k, num_k_heads, v_head_dim).
72
+ o (torch.Tensor): Output from forward pass with same shape as do.
73
+ softmax_lse (torch.Tensor): Log-sum-exp from forward pass with shape
74
+ (batch, num_q_heads, seqlen_q) or (total_tokens, num_q_heads) for varlen.
75
+ dq (torch.Tensor): Pre-allocated query gradient with same shape as q.
76
+ dk (torch.Tensor): Pre-allocated key gradient with same shape as k.
77
+ dv (torch.Tensor): Pre-allocated value gradient with same shape as v.
78
+ dbias (torch.Tensor): Bias gradient (not supported, must be None).
79
+ sm_scale (float): Softmax scale, typically 1/sqrt(head_dim).
80
+ alibi_slopes (Optional[torch.Tensor]): ALiBi position bias slopes with shape (num_q_heads,).
81
+ causal (bool): Apply causal masking.
82
+ cu_seqlens_q (Optional[torch.Tensor]): Cumulative sequence lengths for query with shape
83
+ (batch + 1,). Enables variable-length mode.
84
+ cu_seqlens_k (Optional[torch.Tensor]): Cumulative sequence lengths for key with shape
85
+ (batch + 1,).
86
+ max_seqlen_q (int): Maximum query sequence length in batch.
87
+ max_seqlen_k (int): Maximum key sequence length in batch.
88
+ dropout_p (float): Dropout probability. 0.0 disables dropout.
89
+ philox_seed (Optional[int]): Random seed for dropout.
90
+ philox_offset (Optional[int]): Random offset for dropout.
91
+ descale_q (Optional[torch.Tensor]): FP8 descaling factor for q.
92
+ descale_k (Optional[torch.Tensor]): FP8 descaling factor for k.
93
+ descale_v (Optional[torch.Tensor]): FP8 descaling factor for v.
94
+ descale_do (Optional[torch.Tensor]): FP8 descaling factor for do.
95
+ USE_INT64_STRIDES (Optional[bool]): Use 64-bit stride indexing for large tensors.
96
+ sink (Optional[torch.Tensor]): Attention sink scores (one per Q head). Shape (num_q_heads,).
97
+ dsink (Optional[torch.Tensor]): Pre-allocated sink gradient with same shape as sink.
98
+ config (Optional[Dict[str, any]]): Kernel tuning parameters (preprocess_kernel,
99
+ onekernel, onekernel_pe).
100
+
101
+ Returns:
102
+ torch.Tensor: Delta tensor (element-wise product of do and o) with shape matching softmax_lse.
103
+ """
104
+ _LOGGER.info(
105
+ f"FLASH_ATTN_ONEKERNEL_BKWD: do={tuple(do.shape)} q={tuple(q.shape)} k={tuple(k.shape)} v={tuple(v.shape)} "
106
+ + f"dq={tuple(dq.shape)} dk={tuple(dk.shape)} dv={tuple(dv.shape)}"
107
+ )
108
+ if dbias is not None:
109
+ raise ValueError("Bias is not supported yet in the Triton Backend")
110
+
111
+ use_alibi, (stride_az, stride_ah) = (
112
+ (True, alibi_slopes.stride()) if alibi_slopes is not None else (False, (0, 0))
113
+ )
114
+
115
+ IS_FP8 = _is_fp8(q)
116
+ if IS_FP8:
117
+ FP8_MAX = torch.finfo(q.dtype).max
118
+ descale_strides = (
119
+ descale_q.stride(0),
120
+ descale_k.stride(0),
121
+ descale_v.stride(0),
122
+ descale_do.stride(0),
123
+ )
124
+ else:
125
+ FP8_MAX = None
126
+ stride_descale_q_z = stride_descale_k_z = stride_descale_v_z = (
127
+ stride_descale_do_z
128
+ ) = None
129
+ descale_strides = (
130
+ stride_descale_q_z,
131
+ stride_descale_k_z,
132
+ stride_descale_v_z,
133
+ stride_descale_do_z,
134
+ )
135
+
136
+ IS_VARLEN = True if cu_seqlens_q is not None else False
137
+
138
+ # get strides and shape
139
+ if IS_VARLEN:
140
+ # Layout is thd.
141
+ # q and k are [total_tokens, num_head, head_dim_qk].
142
+ # v is [total_tokens, num_head, head_dim_v].
143
+ batch, seqlen_q, num_q_heads = (
144
+ len(cu_seqlens_q) - 1,
145
+ max_seqlen_q,
146
+ q.shape[1],
147
+ )
148
+ _, num_k_heads = max_seqlen_k, k.shape[1]
149
+ q_strides = (0, q.stride(1), q.stride(0), q.stride(2))
150
+ q_strides = (0, q.stride(1), q.stride(0), q.stride(2))
151
+ k_strides = (0, k.stride(1), k.stride(0), k.stride(2))
152
+ v_strides = (0, v.stride(1), v.stride(0), v.stride(2))
153
+ o_strides = (0, o.stride(1), o.stride(0), o.stride(2))
154
+ dq_strides = (0, dq.stride(1), dq.stride(0), dq.stride(2))
155
+ dk_strides = (0, dk.stride(1), dk.stride(0), dk.stride(2))
156
+ dv_strides = (0, dv.stride(1), dv.stride(0), dv.stride(2))
157
+ do_strides = (0, do.stride(1), do.stride(0), do.stride(2))
158
+ else:
159
+ # Layout is bshd.
160
+ # q and k are [batch, seq_len, num_head, head_dim_qk].
161
+ # v is [batch, seq_len, num_head, head_dim_v]
162
+ batch, seqlen_q, num_q_heads = q.shape[:-1]
163
+ _, num_k_heads = k.shape[1], k.shape[2]
164
+ q_strides = (q.stride(0), q.stride(2), q.stride(1), q.stride(3))
165
+ k_strides = (k.stride(0), k.stride(2), k.stride(1), k.stride(3))
166
+ v_strides = (v.stride(0), v.stride(2), v.stride(1), v.stride(3))
167
+ o_strides = (o.stride(0), o.stride(2), o.stride(1), o.stride(3))
168
+ dq_strides = (dq.stride(0), dq.stride(2), dq.stride(1), dq.stride(3))
169
+ dk_strides = (dk.stride(0), dk.stride(2), dk.stride(1), dk.stride(3))
170
+ dv_strides = (dv.stride(0), dv.stride(2), dv.stride(1), dv.stride(3))
171
+ do_strides = (do.stride(0), do.stride(2), do.stride(1), do.stride(3))
172
+
173
+ qk_head_dim = q.shape[-1]
174
+ v_head_dim = v.shape[-1]
175
+ pe_head_dim = qk_head_dim - v_head_dim
176
+ # BLOCK_D_MODEL, BLOCK_D_MODEL_POW2
177
+ # padding for head_dim. Power of 2 or 16
178
+ BLOCK_D_MODEL_POW2 = max(triton.next_power_of_2(v_head_dim), 16)
179
+ BLOCK_D_MODEL_PE_POW2 = (
180
+ 0 if pe_head_dim == 0 else max(triton.next_power_of_2(pe_head_dim), 16)
181
+ )
182
+ assert (pe_head_dim == 0 and BLOCK_D_MODEL_PE_POW2 == 0) or (
183
+ v_head_dim == BLOCK_D_MODEL_POW2 and pe_head_dim == BLOCK_D_MODEL_PE_POW2
184
+ ), "Positional encoding support requires NOPE and PE head sizes to be unpadded powers of 2."
185
+ assert (not IS_FP8) or (
186
+ IS_FP8 and pe_head_dim == 0
187
+ ), "Positional encoding doesn't support FP8."
188
+
189
+ assert (sink is None) or (
190
+ sink is not None and sink.dim() == 1 and sink.shape[0] == num_q_heads
191
+ ), "Sink must be 1D and have one element per query head."
192
+ assert (dsink is None) or (
193
+ dsink is not None and dsink.dim() == 1 and dsink.shape[0] == num_q_heads
194
+ ), "Sink gradient must be 1D and have one element per query head."
195
+ assert (sink is None) == (
196
+ dsink is None
197
+ ), "Sink and its gradient must be both present or absent."
198
+
199
+ # Configs
200
+ if config is None:
201
+ config = _get_config()
202
+
203
+ # init delta
204
+ delta = torch.zeros_like(softmax_lse)
205
+ if IS_VARLEN:
206
+ # [total_tokens, num_q_heads, seqlen_q]
207
+ delta_strides = (0, delta.stride(1), delta.stride(0))
208
+ else:
209
+ # [batch, num_q_heads, seqlen_q]
210
+ delta_strides = delta.stride()
211
+
212
+ # preprocess
213
+ # compute D(delta) = rowsum(dO*O). Note, multiplication is element-wise.
214
+ pre_grid = (
215
+ triton.cdiv(max_seqlen_q, config["preprocess_kernel"]["PRE_BLOCK"]),
216
+ batch,
217
+ num_q_heads,
218
+ )
219
+ _bwd_preprocess[pre_grid](
220
+ o,
221
+ do,
222
+ delta,
223
+ *o_strides,
224
+ *do_strides,
225
+ *delta_strides,
226
+ descale_strides[3],
227
+ cu_seqlens_q,
228
+ max_seqlen_q,
229
+ descale_do,
230
+ BLOCK_M=config["preprocess_kernel"]["PRE_BLOCK"],
231
+ BLOCK_D_MODEL=v_head_dim,
232
+ BLOCK_D_MODEL_POW2=BLOCK_D_MODEL_POW2,
233
+ IS_VARLEN=IS_VARLEN,
234
+ IS_FP8=IS_FP8,
235
+ )
236
+
237
+ # dropout_mask
238
+ use_dropout = dropout_p > 0.0
239
+ if use_dropout:
240
+ dropout_mask = torch.zeros(
241
+ (batch, num_q_heads, max_seqlen_q, max_seqlen_k),
242
+ device=q.device,
243
+ dtype=torch.float32,
244
+ )
245
+ dropout_strides = dropout_mask.stride()
246
+ else:
247
+ dropout_mask = None
248
+ dropout_strides = (0, 0, 0, 0)
249
+
250
+ seqlen = max(max_seqlen_q, max_seqlen_k)
251
+
252
+ # "onekernel_pe" is for Positional Encoding (PE) causal case, it's going to be
253
+ # used if present. Otherwise, fallback to default "onekernel" config.
254
+ config_onekernel = (
255
+ config["onekernel_pe"]
256
+ if (pe_head_dim > 0 and causal and "onekernel_pe" in config)
257
+ else config["onekernel"]
258
+ )
259
+ grid = (
260
+ num_k_heads,
261
+ triton.cdiv(seqlen, config_onekernel["BLOCK_N1"]),
262
+ batch,
263
+ )
264
+
265
+ if causal:
266
+ bwd_kernel_causal[grid](
267
+ q,
268
+ k,
269
+ v,
270
+ sink,
271
+ sm_scale,
272
+ do,
273
+ dq,
274
+ dk,
275
+ dv,
276
+ dsink,
277
+ softmax_lse,
278
+ delta,
279
+ *q_strides,
280
+ *k_strides,
281
+ *v_strides,
282
+ *dq_strides,
283
+ *dk_strides,
284
+ *dv_strides,
285
+ *delta_strides,
286
+ *do_strides,
287
+ *dropout_strides,
288
+ *descale_strides,
289
+ stride_az,
290
+ stride_ah,
291
+ num_q_heads,
292
+ num_k_heads,
293
+ cu_seqlens_q,
294
+ cu_seqlens_k,
295
+ max_seqlen_q,
296
+ max_seqlen_k,
297
+ dropout_mask,
298
+ dropout_p,
299
+ philox_seed,
300
+ philox_offset,
301
+ alibi_slopes,
302
+ descale_q,
303
+ descale_k,
304
+ descale_v,
305
+ descale_do,
306
+ HEAD_DIM=BLOCK_D_MODEL_POW2,
307
+ ACTUAL_HEAD_DIM=v_head_dim,
308
+ PE_HEAD_DIM=pe_head_dim,
309
+ ENABLE_DROPOUT=use_dropout,
310
+ IS_VARLEN=IS_VARLEN,
311
+ USE_ALIBI=use_alibi,
312
+ USE_EXP2=True,
313
+ IS_FP8=IS_FP8,
314
+ FP8_MAX=FP8_MAX,
315
+ DEBUG_TRITON=False,
316
+ DEBUG_TRITON_DETAIL=False,
317
+ USE_INT64_STRIDES=USE_INT64_STRIDES,
318
+ ENABLE_SINK=sink is not None,
319
+ SLIDING_WINDOW=sliding_window,
320
+ **config_onekernel,
321
+ )
322
+ else:
323
+ bwd_kernel_noncausal[grid](
324
+ q,
325
+ k,
326
+ v,
327
+ sink,
328
+ sm_scale,
329
+ do,
330
+ dq,
331
+ dk,
332
+ dv,
333
+ dsink,
334
+ softmax_lse,
335
+ delta,
336
+ *q_strides,
337
+ *k_strides,
338
+ *v_strides,
339
+ *dq_strides,
340
+ *dk_strides,
341
+ *dv_strides,
342
+ *delta_strides,
343
+ *do_strides,
344
+ *dropout_strides,
345
+ *descale_strides,
346
+ stride_az,
347
+ stride_ah,
348
+ num_q_heads,
349
+ num_k_heads,
350
+ cu_seqlens_q,
351
+ cu_seqlens_k,
352
+ max_seqlen_q,
353
+ max_seqlen_k,
354
+ dropout_mask,
355
+ dropout_p,
356
+ philox_seed,
357
+ philox_offset,
358
+ alibi_slopes,
359
+ descale_q,
360
+ descale_k,
361
+ descale_v,
362
+ descale_do,
363
+ HEAD_DIM=BLOCK_D_MODEL_POW2,
364
+ ACTUAL_HEAD_DIM=v_head_dim,
365
+ PE_HEAD_DIM=pe_head_dim,
366
+ ENABLE_DROPOUT=use_dropout,
367
+ IS_VARLEN=IS_VARLEN,
368
+ USE_ALIBI=use_alibi,
369
+ USE_EXP2=True,
370
+ IS_FP8=IS_FP8,
371
+ FP8_MAX=FP8_MAX,
372
+ DEBUG_TRITON=False,
373
+ DEBUG_TRITON_DETAIL=False,
374
+ USE_INT64_STRIDES=USE_INT64_STRIDES,
375
+ ENABLE_SINK=sink is not None,
376
+ SLIDING_WINDOW=sliding_window,
377
+ **config_onekernel,
378
+ )
379
+
380
+ return delta
build/torch-rocm/utils/__init__.py ADDED
File without changes
build/torch-rocm/utils/_triton/__init__.py ADDED
File without changes
build/torch-rocm/utils/_triton/arch_info.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+
3
+
4
+ _CACHED_ARCH = None
5
+
6
+
7
+ def get_arch():
8
+ # Lazy-detect on first call so the module is importable in environments without an
9
+ # active GPU driver (e.g. the kernel-builder Nix build sandbox running
10
+ # `get_local_kernel` for layout checks).
11
+ global _CACHED_ARCH
12
+ if _CACHED_ARCH is None:
13
+ try:
14
+ _CACHED_ARCH = triton.runtime.driver.active.get_current_target().arch
15
+ except RuntimeError:
16
+ try:
17
+ from jax._src.lib import gpu_triton as triton_kernel_call_lib
18
+
19
+ _CACHED_ARCH = triton_kernel_call_lib.get_arch_details("0").split(":")[0]
20
+ except ImportError:
21
+ _CACHED_ARCH = ""
22
+ return _CACHED_ARCH
23
+
24
+
25
+ def is_gluon_avail():
26
+ return get_arch() in ("gfx950", "gfx1250")
27
+
28
+
29
+ def is_fp4_avail():
30
+ return get_arch() in ("gfx950", "gfx1250")
31
+
32
+
33
+ def is_fp8_avail():
34
+ return get_arch() in ("gfx942", "gfx950", "gfx1250", "gfx1200", "gfx1201")
35
+
36
+
37
+ def is_mx_scale_preshuffling_avail():
38
+ return get_arch() in ("gfx950", "gfx1250")
39
+
40
+
41
+ def is_tdm_avail():
42
+ return get_arch() in ("gfx1250",)
build/torch-rocm/utils/_triton/kernel_repr.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def _sanitize_constexpr_value(value):
2
+ if value is None:
3
+ return "NONE"
4
+ if isinstance(value, bool):
5
+ return str(int(value))
6
+ if isinstance(value, int):
7
+ return str(value)
8
+ if isinstance(value, float):
9
+ if value.is_integer():
10
+ return str(int(value))
11
+ return str(value)
12
+
13
+ # for lists, tuples, sets - recursively join each
14
+ if isinstance(value, (list, tuple, set)):
15
+ items = sorted(value, key=str) if isinstance(value, set) else value
16
+ sanitized_items = [_sanitize_constexpr_value(item) for item in items]
17
+ joined = "_".join(sanitized_items)
18
+ return joined if joined else "NONE"
19
+
20
+ if isinstance(value, str):
21
+ cleaned_value = "".join(ch if ch.isalnum() else "_" for ch in value).strip("_")
22
+ return cleaned_value.upper() if cleaned_value else "NONE"
23
+
24
+ cleaned_value = "".join(ch if ch.isalnum() else "_" for ch in str(value)).strip("_")
25
+ return cleaned_value.upper() if cleaned_value else "NONE"
26
+
27
+
28
+ def make_kernel_repr(base_name, config_keys, name_key=None):
29
+ # When name_key is set, the base name is taken from the matching constexpr
30
+ # kwarg at call time (falling back to base_name if missing/empty). Lets a
31
+ # single shared kernel produce caller-specific names in compiled artifacts.
32
+ def _repr(specialization):
33
+ constants = specialization.constants
34
+
35
+ name = base_name
36
+ if name_key is not None:
37
+ override = constants.get(name_key, None)
38
+ if override:
39
+ cleaned = "".join(
40
+ ch if ch.isalnum() or ch == "_" else "_" for ch in str(override)
41
+ )
42
+ if cleaned:
43
+ name = cleaned
44
+
45
+ name_parts = []
46
+ for key in config_keys:
47
+ value = constants.get(key, None)
48
+ symbol = _sanitize_constexpr_value(value)
49
+ name_parts.append(f"{key}_{symbol}")
50
+
51
+ if not name_parts:
52
+ return name
53
+
54
+ suffix = "_".join(name_parts)
55
+ return f"{name}_{suffix}"
56
+
57
+ return _repr
build/torch-rocm/utils/_triton/mha_kernel_utils.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2025, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import triton
5
+ import triton.language as tl
6
+
7
+
8
+ @triton.jit
9
+ def _compute_fp8_scaling_factors(x, fp8_max: tl.constexpr):
10
+ # compute fp8 scaling and descaling factor for a block
11
+ x_amax = tl.max(tl.abs(x)) # NOTE: abs deals with negative values
12
+ x_amax = tl.where(x_amax <= 1e-9, 1e-9, x_amax)
13
+ scale_x = fp8_max / x_amax
14
+ descale_x = x_amax / fp8_max
15
+ return scale_x, descale_x
build/torch-rocm/utils/_triton/pid_preprocessing.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
4
+
5
+ import triton
6
+ import triton.language as tl
7
+
8
+
9
+ @triton.jit
10
+ def remap_xcd_chunked(
11
+ pid, GRID_MN, NUM_XCDS: tl.constexpr = 8, CHUNK_SIZE: tl.constexpr = 2
12
+ ):
13
+ # Compute current XCD and local PID
14
+ xcd = pid % NUM_XCDS
15
+ # distribute the modulo pids in round robin
16
+ if pid > (GRID_MN // (NUM_XCDS * CHUNK_SIZE)) * (NUM_XCDS * CHUNK_SIZE):
17
+ return pid
18
+ local_pid = pid // NUM_XCDS
19
+ # Calculate chunk index and position within chunk
20
+ chunk_idx = local_pid // CHUNK_SIZE
21
+ pos_in_chunk = local_pid % CHUNK_SIZE
22
+ # Calculate new PID
23
+ new_pid = chunk_idx * NUM_XCDS * CHUNK_SIZE + xcd * CHUNK_SIZE + pos_in_chunk
24
+ return new_pid
25
+
26
+
27
+ @triton.jit
28
+ def remap_xcd(pid, GRID_MN, NUM_XCDS: tl.constexpr = 8):
29
+ ## pid remapping on xcds
30
+ # Number of pids per XCD in the new arrangement
31
+ pids_per_xcd = (GRID_MN + NUM_XCDS - 1) // NUM_XCDS
32
+ # When GRID_MN cannot divide NUM_XCDS, some xcds will have
33
+ # pids_per_xcd pids, the other will have pids_per_xcd - 1 pids.
34
+ # We calculate the number of xcds that have pids_per_xcd pids as
35
+ # tall_xcds
36
+ tall_xcds = GRID_MN % NUM_XCDS
37
+ tall_xcds = NUM_XCDS if tall_xcds == 0 else tall_xcds
38
+ # Compute current XCD and local pid within the XCD
39
+ xcd = pid % NUM_XCDS
40
+ local_pid = pid // NUM_XCDS
41
+ # Calculate new pid based on the new grouping
42
+ # Note that we need to consider the following two cases:
43
+ # 1. the current pid is on a tall xcd
44
+ # 2. the current pid is on a short xcd
45
+ if xcd < tall_xcds:
46
+ pid = xcd * pids_per_xcd + local_pid
47
+ else:
48
+ pid = (
49
+ tall_xcds * pids_per_xcd
50
+ + (xcd - tall_xcds) * (pids_per_xcd - 1)
51
+ + local_pid
52
+ )
53
+
54
+ return pid
55
+
56
+
57
+ @triton.jit
58
+ def pid_grid(pid: int, num_pid_m: int, num_pid_n: int, GROUP_SIZE_M: tl.constexpr = 1):
59
+ """
60
+ Maps 1D pid to 2D grid coords (pid_m, pid_n).
61
+
62
+ Args:
63
+ - pid: 1D pid
64
+ - num_pid_m: grid m size
65
+ - num_pid_n: grid n size
66
+ - GROUP_SIZE_M: tl.constexpr: default is 1
67
+ """
68
+ if GROUP_SIZE_M == 1:
69
+ pid_m = pid // num_pid_n
70
+ pid_n = pid % num_pid_n
71
+ else:
72
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
73
+ group_id = pid // num_pid_in_group
74
+ first_pid_m = group_id * GROUP_SIZE_M
75
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
76
+ tl.assume(group_size_m >= 0)
77
+ pid_m = first_pid_m + (pid % group_size_m)
78
+ pid_n = (pid % num_pid_in_group) // group_size_m
79
+
80
+ return pid_m, pid_n
81
+
82
+
83
+ @triton.jit
84
+ def pid_grid_3d(pid: int, num_pid_m: int, num_pid_n: int, num_pid_k):
85
+ """
86
+ Maps 1D pid to 3D grid coords (pid_m, pid_n, pid_k).
87
+ Args:
88
+ - pid: 1D pid
89
+ - num_pid_m: grid m size
90
+ - num_pid_n: grid n size
91
+ - num_pid_k: grid k size
92
+
93
+ Returns:
94
+ - pid_m, pid_n, pid_k: 3D grid coordinates
95
+ """
96
+ pid_m = pid % num_pid_m
97
+ pid_n = (pid // num_pid_m) % num_pid_n
98
+ pid_k = pid // (num_pid_m * num_pid_n) % num_pid_k
99
+
100
+ return pid_m, pid_n, pid_k
build/torch-rocm/utils/core.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import os
2
+
3
+ this_dir = os.path.dirname(os.path.abspath(__file__))
4
+ AITER_TRITON_OPS_PATH = os.path.abspath(f"{this_dir}/../")
5
+ AITER_TRITON_CONFIGS_PATH = os.path.abspath(f"{this_dir}/../configs")
build/torch-rocm/utils/device_info.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+
4
+ def get_num_sms():
5
+ # Returns the Compute Unit count of the current device
6
+ current_device_index = torch.cuda.current_device()
7
+ current_device = torch.cuda.get_device_properties(current_device_index)
8
+ num_sms = current_device.multi_processor_count
9
+ return num_sms
10
+
11
+
12
+ def get_num_xcds():
13
+ # Currently, you can't query this programmatically. For gfx942/gfx950 it's 8, so we hardcode that here.
14
+ return 8
build/torch-rocm/utils/logger.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+
4
+
5
+ # AITER Triton Logger which is singleton object around python logging.
6
+ # Note: Python logging is also a singleton object, but we want to read the
7
+ # env var AITER_LOG_LEVEL once at the beginning. Another alternative is to do
8
+ # this in __init__.py. In fact, that's how CK logger is setup. We can look at
9
+ # switching to that at some point
10
+ #
11
+ # AITER_LOG_LEVEL follows python logging levels
12
+ # DEBUG
13
+ # INFO
14
+ # WARNING
15
+ # ERROR
16
+ # CRITICAL
17
+ #
18
+ class AiterTritonLogger(object):
19
+ _instance = None
20
+
21
+ def __new__(cls):
22
+ if cls._instance is None:
23
+ cls._instance = super(AiterTritonLogger, cls).__new__(cls)
24
+ log_level_str = os.getenv("AITER_TRITON_LOG_LEVEL", "WARNING").upper()
25
+ numeric_level = getattr(logging, log_level_str, logging.WARNING)
26
+ cls._instance._logger = logging.getLogger("AITER_TRITON")
27
+ cls._instance._logger.setLevel(numeric_level)
28
+
29
+ return cls._instance
30
+
31
+ def get_logger(self):
32
+ return self._logger
33
+
34
+ def debug(self, msg):
35
+ self._logger.debug(msg)
36
+
37
+ def info(self, msg):
38
+ self._logger.info(msg)
39
+
40
+ def warning(self, msg):
41
+ self._logger.warning(msg)
42
+
43
+ def error(self, msg):
44
+ self._logger.error(msg)
45
+
46
+ def critical(self, msg):
47
+ self._logger.critical(msg)
build/torch-rocm/utils/types.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton.language as tl
3
+ from ._triton import arch_info
4
+
5
+
6
+ def get_dtype_max(dtype):
7
+ if torch.is_floating_point(torch.tensor([], dtype=dtype)):
8
+ return torch.finfo(dtype).max
9
+ else:
10
+ return torch.iinfo(dtype).max
11
+
12
+
13
+ def get_fp8_dtypes():
14
+ if arch_info.get_arch() in ("gfx950", "gfx1250", "gfx1200", "gfx1201"):
15
+ e5m2_dtype = torch.float8_e5m2
16
+ e4m3_dtype = torch.float8_e4m3fn
17
+ else:
18
+ e5m2_dtype = torch.float8_e5m2fnuz
19
+ e4m3_dtype = torch.float8_e4m3fnuz
20
+
21
+ return e5m2_dtype, e4m3_dtype
22
+
23
+
24
+ def get_fp8_e4m3_dtype():
25
+ if arch_info.get_arch() in ("gfx950", "gfx1250", "gfx1200", "gfx1201"):
26
+ e4m3_dtype = torch.float8_e4m3fn
27
+ else:
28
+ e4m3_dtype = torch.float8_e4m3fnuz
29
+
30
+ return e4m3_dtype
31
+
32
+
33
+ e5m2_dtype, e4m3_dtype = get_fp8_dtypes()
34
+ str_to_torch_dtype = {
35
+ "float64": torch.float64,
36
+ "float32": torch.float32,
37
+ "fp32": torch.float32,
38
+ "bfloat16": torch.bfloat16,
39
+ "bf16": torch.bfloat16,
40
+ "float16": torch.float16,
41
+ "fp16": torch.float16,
42
+ "float8_e5m2": e5m2_dtype,
43
+ "float8_e4m3fn": e4m3_dtype,
44
+ "e5m2fnuz": e5m2_dtype,
45
+ "e4m3fnuz": e4m3_dtype,
46
+ "fp8e4m3": e4m3_dtype,
47
+ "fp8e5m2": e5m2_dtype,
48
+ "int64": torch.int64,
49
+ "int32": torch.int32,
50
+ "int16": torch.int16,
51
+ "uint8": torch.uint8,
52
+ "int8": torch.int8,
53
+ "mxfp4_e2m1": torch.uint8, # OCP MXFP4 packs two 4-bits into 8-bit
54
+ }
55
+
56
+ torch_to_triton_dtype = {
57
+ torch.float64: tl.float64,
58
+ torch.float32: tl.float32,
59
+ torch.float16: tl.float16,
60
+ torch.bfloat16: tl.bfloat16,
61
+ torch.float8_e4m3fn: tl.float8e4nv,
62
+ torch.float8_e4m3fnuz: tl.float8e4b8,
63
+ torch.float8_e5m2: tl.float8e5,
64
+ torch.float8_e5m2fnuz: tl.float8e5b16,
65
+ torch.int64: tl.int64,
66
+ torch.int32: tl.int32,
67
+ torch.int16: tl.int16,
68
+ torch.int8: tl.int8,
69
+ torch.uint8: tl.uint8,
70
+ }
71
+
72
+
73
+ def _is_fp8(x):
74
+ if x.dtype in {
75
+ torch.float8_e4m3fnuz,
76
+ torch.float8_e4m3fn,
77
+ torch.float8_e5m2,
78
+ torch.float8_e5m2fnuz,
79
+ }:
80
+ if arch_info.is_fp8_avail():
81
+ return True
82
+ else:
83
+ raise RuntimeError("This device does not support fp8")
84
+ else:
85
+ return False