Skip to content

Commit 7aaebf6

Browse files
ruanjmcoderfeliclaude
authored
[FlyDSL] [CI] FlyDSL FMHA forward-prefill A16W16 kernel for gfx1250 (fmha_fwd_prefill_m32x8) (#5168)
* infrastructure for gfx1250 fdsl mha m16x8 * complete qmgr * draft of kmanager * save 20260731 * Add VMgr and OMgr and put all mgrs in a dedicated file * Implement QK GEMM * draft of softmax * use permlanex16 in softmax * draft of pv gemm * 1st version of working mha * opt OMgr * rename test_mha_flydsl_varlen.py -> test_mha_flydsl.py * modify tests. rename test_mha_flydsl.py and make it support _batch and gqa * bug fix. no load next kv tile at last iter. * add attn sink aaa aa * Fix test issue in THD kernels * add support to 2-elem window size. * sched-mode 2. But there is still quality issue in large scale test * Draft of refactor QMgr for SCHED_MODE=2. There should be no regression. But there is still quality issue somewhere else. No issue in SCHED_MODE=0 * Refactor QMgr Part 2 * Greatly fix the quality issue in mode-2. still not perfect. * use llvm hint to enable SCHED_MODE=2 * remove bug and wa for manual sched_mode=2 * refactor how kmgr and vmgr load data from lds to reg. * remove unroll of 2 iter of main loop * skip oob check on middle iterations. * deferred oaccu rescale * use imm offset in cluster load * tree add of softmax sum and max * fix rebase issue * m32x8 * Improve softmax tree reduce for m32 * draft of warp specialization * warp-spec * Introduce KMgrV2 and VMgrV2 which uses TDM. * remove wait_dscnt before qk gemm. * Introduce QMgrV2 * Introduce OMgrV2 and V3 * Add support to qk_hdim=192 and 256. Default to new 8 waves kernel when qk_hdim = 128 and 192 due to better perf. * clean up and format code * fix via ruff * Fix issues raised by reviewdog and Copilot * fix issue raised by black. * Fix issues raised by Copliot * rename files and misc fixes. * adjust zero_fill and misc fixes. * fix comments. * simplify fix of softmax inf issue. * fix routing logic * doc fixes. * Fix issues raised by Copilot * fix epilogue nan issue * misc fixes * add dtype and hdim check on kv tensors * Add support to fp16 * Fix issues from reviewer * fixes * fix issue under gqa > 32 * refine unittest and add comments to old pipeline * Fix ruff SIM102 and repair LDS helper import after main merge Ruff flagged SIM102 (collapsible nested if) in _flydsl_serves_thd. Naming the predicate collapses the nesting without tripping SIM103, which the plain single-if form would have. The main merge also silently broke an import: #5116 deleted aiter/ops/flydsl/utils.py, which this kernel imported get_shared_memory_per_block from. Git reported no conflict because the two sides touched different files. Switch to the replacement, get_lds_capacity_bytes("gfx1250") from aiter.jit.utils.chip_info, which is the convention the other gfx1250 kernels on main already use and returns the same 327680 bytes the old fallback did. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Felix Li <felix.li@amd.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 10b192f commit 7aaebf6

12 files changed

Lines changed: 5369 additions & 532 deletions

.github/scripts/split_tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ if [[ "$TEST_TYPE" == "aiter" ]]; then
169169
FILE_TIMES[op_tests/test_gemm_a8w8_bpreshuffle_pad_k.py]=7
170170
FILE_TIMES[op_tests/test_gemm_codegen.py]=7
171171
FILE_TIMES[op_tests/test_jit_arch_guard.py]=7
172-
FILE_TIMES[op_tests/test_mha_flydsl_varlen.py]=7
172+
FILE_TIMES[op_tests/test_mha_flydsl.py]=7
173173
FILE_TIMES[op_tests/test_mla_decode_pagesize64.py]=7
174174
FILE_TIMES[op_tests/test_mla_v40_persistent.py]=7
175175
FILE_TIMES[op_tests/test_mla_v4_kargpreld.py]=7

aiter/ops/flydsl/fmha_kernels.py

Lines changed: 140 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@
2828
import torch.nn.functional as F
2929

3030
from .kernels.flash_attn_func_gfx1201 import build_flash_attn_func_module
31+
from .kernels.fmha_gfx1250.fmha_fwd_prefill_a16w16_m32x8 import (
32+
flash_attn_batch_m32x8,
33+
flash_attn_varlen_m32x8,
34+
)
3135
from .kernels.fmha_gfx1250.fmha_kernel import flash_attn_varlen_d192_gfx1250
3236

3337
__all__ = [
38+
"flydsl_flash_attn_batch_func",
3439
"flydsl_flash_attn_func",
3540
"flydsl_flash_attn_varlen_func",
3641
]
@@ -234,31 +239,87 @@ def flydsl_flash_attn_varlen_func(
234239
Returns the result if FlyDSL can handle this configuration,
235240
otherwise returns None so the caller falls through to Triton/CK.
236241
"""
242+
from ...jit.core import is_experimental_enabled
237243
from ...jit.utils.chip_info import get_gfx
238244

239-
# FlyDSL handles only plain MHA. Any unsupported feature (bias, alibi, sink,
240-
# dropout, sliding window, paging, probs/deterministic) falls through to
245+
# FlyDSL m32x8 serves plain MHA plus attention-sink and sliding-window; other
246+
# features (bias, alibi, dropout, paging, return_attn_probs) fall through to
241247
# CK/Triton instead of being silently dropped.
248+
#
249+
# Routing (D_v=128, bf16, gfx1250): our m32x8 kernel is the DEFAULT for qk_hdim 128 and 192.
250+
# qk_hdim==256 routes to us only under AITER_ENABLE_EXPERIMENTAL=1 (else CK). The production
251+
# d192 SIBLING is reached only for qk_hdim==192 under experimental (an A/B fallback to the old
252+
# kernel); by default 192 takes ours (it's ~16% faster).
253+
qk_hdim = q.shape[-1]
254+
exp = is_experimental_enabled()
255+
# Attention sink + finite sliding window are served only by our m32x8 path; the d192
256+
# sibling is full/causal only. So under experimental the sibling is the 192 A/B
257+
# fallback ONLY for plain full/causal — a sink or finite window on 192 stays on ours.
258+
_needs_m32x8 = sink is not None or tuple(window_size[:2]) != (-1, -1)
259+
# Sibling d192 is bf16-only; fp16 at 192 always takes ours.
260+
_use_sibling = (
261+
qk_hdim == 192 and exp and not _needs_m32x8 and q.dtype == torch.bfloat16
262+
)
263+
_use_fdsl_wave8_fmha = (
264+
qk_hdim == 128
265+
or (qk_hdim == 192 and not _use_sibling)
266+
or (qk_hdim == 256 and exp)
267+
)
268+
# sink must route to ours AND be a valid [nheads_q] fp32 tensor; heads must divide;
269+
# window_size[2] (sink_size) is unsupported (reject so it is never silently dropped).
270+
_nq, _nkv = q.shape[-2], k.shape[-2]
271+
_sink_ok = sink is None or (
272+
_use_fdsl_wave8_fmha
273+
and torch.is_tensor(sink)
274+
and sink.dtype == torch.float32
275+
and sink.shape == (_nq,)
276+
)
277+
_window_ok = tuple(window_size[:2]) == (-1, -1) or _use_fdsl_wave8_fmha
242278
supported = (
243279
get_gfx() == "gfx1250"
244-
and q.shape[-1] == 192
280+
and (_use_fdsl_wave8_fmha or _use_sibling)
245281
and v.shape[-1] == 128
246-
and q.dtype == torch.bfloat16
282+
and k.shape[-1] == qk_hdim
283+
and q.dtype in (torch.bfloat16, torch.float16)
284+
and k.dtype == q.dtype
285+
and v.dtype == q.dtype
286+
and _nkv > 0
287+
and _nq % _nkv == 0
247288
and dropout_p == 0.0
248-
and tuple(window_size[:2]) == (-1, -1)
289+
and _window_ok
290+
and (len(window_size) < 3 or window_size[2] == 0)
249291
and block_table is None
250292
and bias is None
251293
and alibi_slopes is None
252-
and sink is None
253-
and not deterministic
294+
and _sink_ok
254295
and not return_attn_probs
255296
)
256297
if not supported:
257298
return None
258299

259-
# gfx1250 — varlen THD, D_qk=192 D_v=128, bf16
300+
# gfx1250 — varlen THD, D_v=128, bf16
260301
if out is None:
261302
out = torch.empty_like(q[:, :, : v.shape[-1]])
303+
304+
if _use_fdsl_wave8_fmha:
305+
# New clean-DSL 8-wave prefill kernel (m32x8), D_qk in {128,192,256}, D_v=128.
306+
return flash_attn_varlen_m32x8(
307+
q,
308+
k,
309+
v,
310+
cu_seqlens_q,
311+
cu_seqlens_k,
312+
max_seqlen_q,
313+
max_seqlen_k,
314+
softmax_scale=softmax_scale,
315+
causal=causal,
316+
window_size=window_size,
317+
out=out,
318+
return_lse=return_lse,
319+
sink=sink,
320+
)
321+
322+
# _use_sibling: qk_hdim==192 under experimental -> production d192 (D_qk=192, D_v=128).
262323
return flash_attn_varlen_d192_gfx1250(
263324
q,
264325
k,
@@ -272,3 +333,74 @@ def flydsl_flash_attn_varlen_func(
272333
out=out,
273334
return_lse=return_lse,
274335
)
336+
337+
338+
def flydsl_flash_attn_batch_func(
339+
q: torch.Tensor,
340+
k: torch.Tensor,
341+
v: torch.Tensor,
342+
softmax_scale: float | None = None,
343+
causal: bool = False,
344+
return_lse: bool = False,
345+
dropout_p: float = 0.0,
346+
window_size=(-1, -1),
347+
bias=None,
348+
alibi_slopes=None,
349+
deterministic=False,
350+
return_attn_probs=False,
351+
sink=None,
352+
out=None,
353+
):
354+
"""FlyDSL MHA forward, batched BSHD ``[B, S, H, D]`` layout.
355+
356+
Routes to the dedicated BSHD m32x8 kernel (uniform ``seq_len``, no
357+
``cu_seqlens`` — CUDA-graph safe). Returns the result if FlyDSL can handle
358+
this configuration, otherwise returns ``None`` so the caller falls through
359+
to Triton/CK.
360+
"""
361+
from ...jit.core import is_experimental_enabled
362+
from ...jit.utils.chip_info import get_gfx
363+
364+
# BSHD routes to the m32x8 kernel (no d192 sibling exists for BSHD). D_v=128. D_qk 128/192 are
365+
# the DEFAULT; D_qk==256 needs AITER_ENABLE_EXPERIMENTAL=1 (else CK).
366+
qk_hdim = q.shape[-1]
367+
# Head count (BSHD [B,S,H,D]) and sink must satisfy the kernel's asserts, else validate
368+
# up front so an unsupported request returns None instead of tripping a kernel assert.
369+
_nq, _nkv = q.shape[-2], k.shape[-2]
370+
_sink_ok = sink is None or (
371+
torch.is_tensor(sink) and sink.dtype == torch.float32 and sink.shape == (_nq,)
372+
)
373+
supported = (
374+
get_gfx() == "gfx1250"
375+
and q.dim() == 4
376+
and (qk_hdim in (128, 192) or (qk_hdim == 256 and is_experimental_enabled()))
377+
and v.shape[-1] == 128
378+
and k.shape[-1] == qk_hdim
379+
and q.dtype in (torch.bfloat16, torch.float16)
380+
and k.dtype == q.dtype
381+
and v.dtype == q.dtype
382+
and _nkv > 0
383+
and _nq % _nkv == 0
384+
and _sink_ok
385+
and dropout_p == 0.0
386+
and bias is None
387+
and alibi_slopes is None
388+
and (len(window_size) < 3 or window_size[2] == 0)
389+
and not return_attn_probs
390+
)
391+
# No `not deterministic` gate: it is a backward-only flag (this forward is atomic-free
392+
# / deterministic), and flash_attn_func defaults it True — gating would reject all.
393+
if not supported:
394+
return None
395+
396+
return flash_attn_batch_m32x8(
397+
q,
398+
k,
399+
v,
400+
softmax_scale=softmax_scale,
401+
causal=causal,
402+
window_size=window_size,
403+
out=out,
404+
return_lse=return_lse,
405+
sink=sink,
406+
)

0 commit comments

Comments
 (0)