[AMD] Fix Triton 3.7 gfx950 extend-attention spills (#34741)
This commit is contained in:
@@ -35,6 +35,14 @@ if _is_cuda:
|
|||||||
_is_hip = is_hip()
|
_is_hip = is_hip()
|
||||||
_is_gfx95 = _is_hip and is_gfx95_supported()
|
_is_gfx95 = _is_hip and is_gfx95_supported()
|
||||||
|
|
||||||
|
try:
|
||||||
|
_triton_version_parts = tuple(
|
||||||
|
int(part) for part in triton.__version__.split(".")[:2]
|
||||||
|
)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
_triton_version_parts = (0, 0)
|
||||||
|
_is_triton_ge_37 = _triton_version_parts >= (3, 7)
|
||||||
|
|
||||||
|
|
||||||
def _get_block_sizes_for_extend_attention(Lq: int, Lv: int):
|
def _get_block_sizes_for_extend_attention(Lq: int, Lv: int):
|
||||||
"""
|
"""
|
||||||
@@ -65,7 +73,14 @@ def _get_block_sizes_for_extend_attention(Lq: int, Lv: int):
|
|||||||
|
|
||||||
# Determine BLOCK_M, BLOCK_N, and num_warps based on hardware
|
# Determine BLOCK_M, BLOCK_N, and num_warps based on hardware
|
||||||
if _is_hip:
|
if _is_hip:
|
||||||
if _is_gfx95 and 128 < Lq <= 256:
|
if _is_gfx95 and _is_triton_ge_37 and Lq == 576 and Lv == 512:
|
||||||
|
# Triton 3.7's N64 codegen reaches 512 VGPRs and spills 472 bytes
|
||||||
|
# of scratch on gfx950. N32 keeps BLOCK_M/launch work unchanged,
|
||||||
|
# uses <=433 VGPRs without scratch, and restores the isolated
|
||||||
|
# late-prefill kernel from ~12.57 ms to ~5.24 ms.
|
||||||
|
BLOCK_M, BLOCK_N = (64, 32)
|
||||||
|
num_warps = 4
|
||||||
|
elif _is_gfx95 and 128 < Lq <= 256:
|
||||||
# gfx950 (CDNA4), 128 < head_dim <= 256: a larger query tile halves KV bytes
|
# gfx950 (CDNA4), 128 < head_dim <= 256: a larger query tile halves KV bytes
|
||||||
# streamed per call (each workgroup reads the whole prefix); 8 warps
|
# streamed per call (each workgroup reads the whole prefix); 8 warps
|
||||||
# hide the loads. Measured on MI350X head_dim 256: -36% kernel time,
|
# hide the loads. Measured on MI350X head_dim 256: -36% kernel time,
|
||||||
|
|||||||
@@ -335,10 +335,70 @@ class TestTritonAttention(CustomTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ea._get_block_sizes_for_extend_attention(256, 256)[3:], expected
|
ea._get_block_sizes_for_extend_attention(256, 256)[3:], expected
|
||||||
)
|
)
|
||||||
# head_dim > 256: falls back to the default on all HIP archs
|
# head_dim > 256 falls back to the default unless the automatic
|
||||||
|
# Triton-3.7 gfx950 Lq=576/Lv=512 spill workaround applies.
|
||||||
|
with unittest.mock.patch.object(ea, "_is_triton_ge_37", True):
|
||||||
|
expected = (64, 32, 4) if ea._is_gfx95 else (64, 64, 4)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ea._get_block_sizes_for_extend_attention(576, 576)[3:], (64, 64, 4)
|
ea._get_block_sizes_for_extend_attention(576, 512)[3:],
|
||||||
|
expected,
|
||||||
)
|
)
|
||||||
|
with unittest.mock.patch.object(ea, "_is_triton_ge_37", False):
|
||||||
|
self.assertEqual(
|
||||||
|
ea._get_block_sizes_for_extend_attention(576, 512)[3:],
|
||||||
|
(64, 64, 4),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_extend_attention_triton37_lq576_n32(self):
|
||||||
|
from sglang.kernels.ops.attention import extend_attention as ea
|
||||||
|
|
||||||
|
if not (ea._is_gfx95 and ea._is_triton_ge_37):
|
||||||
|
self.skipTest("Triton >=3.7 gfx950-only spill workaround")
|
||||||
|
|
||||||
|
device = get_device()
|
||||||
|
dtype = torch.bfloat16
|
||||||
|
extend_lens = [32, 23]
|
||||||
|
prefix_lens = [64, 96]
|
||||||
|
h_q, h_kv, l_q, l_v = 12, 1, 576, 512
|
||||||
|
n_ext, n_prefix = sum(extend_lens), sum(prefix_lens)
|
||||||
|
|
||||||
|
q = torch.randn(n_ext, h_q, l_q, dtype=dtype, device=device)
|
||||||
|
k = torch.randn(n_ext, h_kv, l_q, dtype=dtype, device=device)
|
||||||
|
v = torch.randn(n_ext, h_kv, l_v, dtype=dtype, device=device)
|
||||||
|
k_buffer = torch.randn(n_prefix, h_kv, l_q, dtype=dtype, device=device)
|
||||||
|
v_buffer = torch.randn(n_prefix, h_kv, l_v, dtype=dtype, device=device)
|
||||||
|
qo_indptr = torch.tensor([0, 32, 55], dtype=torch.int32, device=device)
|
||||||
|
kv_indptr = torch.tensor([0, 64, 160], dtype=torch.int32, device=device)
|
||||||
|
kv_indices = torch.arange(n_prefix, dtype=torch.int64, device=device)
|
||||||
|
reference = torch.empty(n_ext, h_q, l_v, dtype=dtype, device=device)
|
||||||
|
candidate = torch.empty_like(reference)
|
||||||
|
|
||||||
|
def run(output):
|
||||||
|
extend_attention_fwd(
|
||||||
|
q,
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
output,
|
||||||
|
k_buffer,
|
||||||
|
v_buffer,
|
||||||
|
qo_indptr,
|
||||||
|
kv_indptr,
|
||||||
|
kv_indices,
|
||||||
|
None,
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
max(extend_lens),
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
sm_scale=1.0 / (l_q**0.5),
|
||||||
|
extend_seq_lens_cpu=extend_lens,
|
||||||
|
)
|
||||||
|
|
||||||
|
with unittest.mock.patch.object(ea, "_is_triton_ge_37", False):
|
||||||
|
run(reference)
|
||||||
|
with unittest.mock.patch.object(ea, "_is_triton_ge_37", True):
|
||||||
|
run(candidate)
|
||||||
|
torch.testing.assert_close(candidate, reference, atol=2e-2, rtol=1e-2)
|
||||||
|
|
||||||
def test_compact_extend_attention_tile_count(self):
|
def test_compact_extend_attention_tile_count(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
Reference in New Issue
Block a user