Files
hao-ai-lab-FastVideo/fastvideo-kernel/tests/test_attn_qat_sm100_long_sequence.py
T
Wenbo Ji 嵇文博 b7bd7775ed [perf] Default SM100 long-sequence Attn-QAT tiling (#1899)
On SM100, the Attn-QAT training kernel now uses a faster route for non-causal
BF16 attention with equal query and key lengths of 16384 tokens or more. The
forward pass processes full KV tiles without bounds masks, and a separate masked
step handles the partial tail tile. The backward pass uses 64x128 dQ and dK/dV
tiles. Outputs, softmax statistics and all gradients are bitwise identical to the
previous route. FASTVIDEO_ATTN_QAT_SM100_OPTIMIZED=0 turns off the SM100 route,
and FASTVIDEO_ATTN_QAT_SM100_WIDE_BWD=0 keeps the 64x64 backward tiles.

On GB200 with 6 heads and 31200 tokens, the forward kernel time goes from 9.14 ms
to 6.96 ms and the backward kernel time from 17.41 ms to 15.85 ms.
2026-10-07 15:14:38 -07:00

99 lines
4.7 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""SM100 long-sequence route and exact output/backward regression tests."""
import pytest
import torch
from fastvideo_kernel.triton_kernels import attn_qat_train as kernel
@pytest.fixture(autouse=True)
def sm100_options(monkeypatch):
for key, value in {"SM100_OPTIMIZED": "1", "SM100_WIDE_BWD": "1", "FWD_MODE": "fast", "FWD_EXACT_M": "0"}.items():
monkeypatch.setenv("FASTVIDEO_ATTN_QAT_" + key, value)
@pytest.mark.parametrize("qlen,kvlen,dtype,optimized,mode", [
(2112, 2112, torch.bfloat16, True, "fast"), (16384, 16400, torch.bfloat16, True, "fast"),
(31200, 31200, torch.float16, True, "fast"), (31200, 31200, torch.float32, True, "fast"),
(31200, 31200, torch.bfloat16, False, "fast"), (31200, 31200, torch.bfloat16, True, "balanced"),
(31200, 31200, torch.bfloat16, True, "reference"),
])
def test_long_sequence_route_unsupported_shapes(qlen, kvlen, dtype, optimized, mode):
assert not kernel._sm100_long_sequence_route(qlen, kvlen, dtype, optimized, mode)
def test_long_sequence_route_rejects_exact_m(monkeypatch):
monkeypatch.setenv("FASTVIDEO_ATTN_QAT_FWD_EXACT_M", "1")
assert not kernel._sm100_long_sequence_route(31200, 31200, torch.bfloat16, True, "fast")
def test_long_sequence_route_enabled_for_production_shape():
assert kernel._sm100_long_sequence_route(31200, 31200, torch.bfloat16, True, "fast")
def test_wide_backward_launch_config():
tuned = kernel._sm100_backward_launch_config(128, torch.bfloat16)
legacy = kernel._sm100_backward_launch_config(64, torch.bfloat16)
assert tuned == ((4, 2), (8, 3))
assert legacy == ((8, 2), (8, 3))
def _legacy_route_patches(patch):
patch.setattr(kernel, "_sm100_long_sequence_route", lambda *args, **kwargs: False)
patch.setattr(kernel, "_select_sm100_backward_blocks", lambda *args, **kwargs: (64, 64))
patch.setattr(kernel, "_sm100_backward_launch_config", lambda block_n, dtype: ((8, 2), (8, 3)))
requires_sm100 = pytest.mark.skipif(not torch.cuda.is_available() or torch.cuda.get_device_capability() != (10, 0),
reason="SM100 bitwise production/tail regression")
def _assert_bitwise_matches_legacy_route(monkeypatch, batch, heads, qlen, kvlen):
torch.manual_seed(1000)
inputs = tuple(torch.randn((batch, heads, length, 128), device="cuda", dtype=torch.bfloat16,
requires_grad=True) for length in (qlen, kvlen, kvlen))
grad = torch.randn_like(inputs[0])
def run(legacy: bool):
with monkeypatch.context() as patch:
if legacy:
_legacy_route_patches(patch)
out = kernel.attention(*inputs, False, 128**-0.5,
True, False, True, True, False, True, True, False, False, False)
saved = out.grad_fn.saved_tensors
# _attention.backward reads high_prec_o then M from ctx.saved_tensors.
high, m = saved[-2], saved[-1]
gradients = torch.autograd.grad(out, inputs, grad)
return out.detach(), *gradients, high.detach(), m.detach()
reference = run(True)
actual = run(False)
for name, a, b in zip(("output", "dQ", "dK", "dV", "high_output", "M"), reference, actual):
integer = torch.int32 if a.element_size() == 4 else torch.int16
assert torch.isfinite(b).all(), name
assert torch.equal(a.contiguous().view(integer), b.contiguous().view(integer)), name
@requires_sm100
@pytest.mark.parametrize("batch,heads,qlen,kvlen", [
(1, 1, 16384, 16384), (1, 1, 16400, 16400), (1, 6, 31200, 31200), (1, 1, 31232, 31232),
(1, 1, 2112, 2112), (1, 1, 2112, 2080), (1, 1, 16384, 16400),
(2, 3, 16384, 16384), (2, 3, 16400, 16400),
# Non-16-multiple lengths: the forward tail tile crosses a 16-column
# quant group (tail 17 and tail 127 over the 128-wide KV tile).
(1, 1, 16401, 16401), (1, 1, 16511, 16511),
])
def test_long_sequence_route_bitwise_output_statistics_and_gradients(monkeypatch, batch, heads, qlen, kvlen):
_assert_bitwise_matches_legacy_route(monkeypatch, batch, heads, qlen, kvlen)
@requires_sm100
@pytest.mark.parametrize("mode,exact_m", [("balanced", "0"), ("reference", "0"), ("fast", "1"), ("balanced", "1")])
@pytest.mark.parametrize("qlen", [16384, 16400])
def test_wide_backward_bitwise_in_comparison_modes(monkeypatch, mode, exact_m, qlen):
# These modes keep the masked forward loop but still take the 64x128 backward.
monkeypatch.setenv("FASTVIDEO_ATTN_QAT_FWD_MODE", mode)
monkeypatch.setenv("FASTVIDEO_ATTN_QAT_FWD_EXACT_M", exact_m)
assert not kernel._sm100_long_sequence_route(qlen, qlen, torch.bfloat16, True, mode)
assert kernel._select_sm100_backward_blocks(qlen, qlen) == (64, 128)
_assert_bitwise_matches_legacy_route(monkeypatch, 1, 1, qlen, qlen)