This commit is contained in:
Liangsheng Yin
2026-09-14 03:04:07 -07:00
committed by GitHub
parent 5aa9b8fb3e
commit 66c7bc838e
9 changed files with 49 additions and 218 deletions
@@ -3,13 +3,9 @@ import unittest
import torch
from sglang.srt.model_executor.forward_batch_info import ForwardMode
from sglang.srt.model_executor.forward_context import ForwardContext, forward_context
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.attention_unittest.attention_methods.dense_attention import (
DENSE_ATOL,
DENSE_RTOL,
DenseAttentionCase,
build_dense_attention_fixture,
make_dense_cases,
run_dense_attention_case,
)
@@ -427,102 +423,6 @@ class TestFA4DenseAttentionBackendCorrectness(CustomTestCase):
hidden_size=self.HIDDEN_SIZE,
)
RETURN_LSE_CASES = (
DenseAttentionCase(
name="return_lse_mha_extend",
backend="fa4",
forward_mode=ForwardMode.EXTEND,
num_heads=4,
num_kv_heads=4,
page_size=1,
prefix_lens=(2, 4),
extend_lens=(3, 1),
),
DenseAttentionCase(
name="return_lse_gqa_decode",
backend="fa4",
forward_mode=ForwardMode.DECODE,
num_heads=8,
num_kv_heads=2,
page_size=1,
prefix_lens=(5, 9),
),
)
def _reference_out_and_lse(self, fixture):
"""Causal fp32 reference: output and per-query LSE."""
case, module = fixture.case, fixture.reference_module
dim = module.head_dim
rep = case.num_heads // case.num_kv_heads
q, k, v = module.project_qkv(fixture.input_hidden)
q = q.view(-1, case.num_heads, dim).float()
k = k.view(-1, case.num_kv_heads, dim).float()
v = v.view(-1, case.num_kv_heads, dim).float()
outs, lses, seen = [], [], 0
for req, prefix in enumerate(fixture.prefix_hidden):
_, prefix_k, prefix_v = module.project_qkv(prefix)
n = case.input_lens[req]
keys = torch.cat(
[prefix_k.view(-1, case.num_kv_heads, dim).float(), k[seen : seen + n]]
).repeat_interleave(rep, dim=1)
values = torch.cat(
[prefix_v.view(-1, case.num_kv_heads, dim).float(), v[seen : seen + n]]
).repeat_interleave(rep, dim=1)
for offset in range(n):
end = case.prefix_lens[req] + offset + 1
scores = (
torch.einsum("hd,khd->hk", q[seen + offset], keys[:end])
* module.scaling
)
probs = torch.softmax(scores, dim=-1)
outs.append(torch.einsum("hk,khd->hd", probs, values[:end]).reshape(-1))
lses.append(torch.logsumexp(scores, dim=-1))
seen += n
return torch.stack(outs), torch.stack(lses)
def test_return_lse(self):
"""Calls the backend directly: return_lse is a backend-level contract,
and the RadixAttention dispatcher's custom-op schema cannot carry it.
"""
for case in self.RETURN_LSE_CASES:
with self.subTest(case=case.name):
fixture = build_dense_attention_fixture(
self, case, head_dim=self.HEAD_DIM, hidden_size=self.HIDDEN_SIZE
)
module = fixture.actual_module
forward = (
fixture.backend.forward_decode
if case.forward_mode.is_decode()
else fixture.backend.forward_extend
)
with (
torch.no_grad(),
forward_context(ForwardContext(attn_backend=fixture.backend)),
):
fixture.backend.init_forward_metadata(fixture.forward_batch)
q, k, v = module.project_qkv(fixture.input_hidden)
result = forward(
q, k, v, module.attn, fixture.forward_batch, return_lse=True
)
self.assertIsInstance(result, tuple)
out, lse = result
self.assertEqual(
tuple(lse.shape), (case.num_input_tokens, case.num_heads)
)
expected_out, expected_lse = self._reference_out_and_lse(fixture)
torch.testing.assert_close(
lse.float(), expected_lse, atol=DENSE_ATOL, rtol=DENSE_RTOL
)
torch.testing.assert_close(
out.float().reshape(expected_out.shape),
expected_out,
atol=DENSE_ATOL,
rtol=DENSE_RTOL,
)
if __name__ == "__main__":
unittest.main()
@@ -121,24 +121,6 @@ class TestCPStrategyUnit(CustomTestCase):
):
self.assertFalse(is_dsa_enable_prefill_cp())
def test_disabled_dsa_cp_skips_platform_probes(self):
parallel = SimpleNamespace(attn_cp_size=1)
with (
patch(
"sglang.srt.layers.attention.dsa.utils.get_parallel",
return_value=parallel,
),
patch("sglang.srt.layers.attention.dsa.utils.is_hip") as mock_is_hip,
patch("sglang.srt.layers.attention.dsa.utils.is_npu") as mock_is_npu,
patch("sglang.srt.layers.attention.dsa.utils.is_musa") as mock_is_musa,
):
self.assertFalse(is_dsa_enable_prefill_cp())
mock_is_hip.assert_not_called()
mock_is_npu.assert_not_called()
mock_is_musa.assert_not_called()
class TestPrefillCPBCGReplay(CustomTestCase):
def tearDown(self):
@@ -166,12 +166,12 @@ def _compare_case(case, num_warps):
idx_vals = inp["idx_vals"]
valid_rows = [i for i, slot in enumerate(idx_vals) if slot >= 0]
touched_slots = [slot for slot in idx_vals if slot >= 0]
o_ref_v = o_ref.reshape(B, T, HV, V)[valid_rows]
o_fus_v = o_fus.reshape(B, T, HV, V)[valid_rows]
assert torch.equal(o_ref_v, o_fus_v)
# conv_state is read-only in verify; the commit scatter advances it.
assert torch.equal(inp["conv_pool"], conv_fus)
assert torch.equal(conv_ref[touched_slots], conv_fus[touched_slots])
assert torch.equal(win_ref[valid_rows], win_fus[valid_rows])
torch.testing.assert_close(
ic_ref[valid_rows], ic_fus[valid_rows], atol=4e-3, rtol=0
@@ -183,27 +183,5 @@ def test_matches_unfused_reference(case):
_compare_case(case, num_warps=4)
def test_output_does_not_depend_on_cta_scheduling():
"""The verify output must not change with how the CTAs happen to be
scheduled. H=1 with HV=16 shares one Q/K history across 16 V tiles."""
if torch.cuda.get_device_capability()[0] < 9:
pytest.skip("green contexts need SM90 or newer")
from flashinfer.green_ctx import split_device_green_ctx_by_sm_count
case = (1, 6, 1, 16, 128, 128, 4, False, None, False, 1)
B, T, H, HV, K, V, W, has_bias, lower_bound, neg_slot, seed = case
inp = _make_inputs(B, T, H, HV, K, V, W, has_bias, neg_slot, seed)
full = _run_fused(inp, B, T, H, HV, K, V, lower_bound, num_warps=4)[0]
streams, _ = split_device_green_ctx_by_sm_count(torch.device("cuda:0"), [8])
# The green stream is non-blocking, so it must be told to wait for the
# inputs produced above; synchronize() afterwards only waits on the consumer.
streams[0].wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(streams[0]):
squeezed = _run_fused(inp, B, T, H, HV, K, V, lower_bound, num_warps=4)[0]
streams[0].synchronize()
assert torch.equal(full, squeezed)
if __name__ == "__main__":
sys.exit(pytest.main([__file__]))
-15
View File
@@ -7,7 +7,6 @@ from sglang.srt.utils.common import (
flatten_arrays_to_int64_tensor,
get_device_sm_nvidia_smi,
get_nvidia_driver_version_str,
is_musa,
)
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
from sglang.test.test_utils import CustomTestCase
@@ -16,20 +15,6 @@ register_cuda_ci(est_time=10, stage="base-b", runner_config="1-gpu-small")
register_amd_ci(est_time=5, stage="stage-b", runner_config="1-gpu-small-amd")
class TestMusaDetection(CustomTestCase):
def test_is_musa_is_torch_compile_safe(self):
is_musa.cache_clear()
@torch.compile(backend="eager", fullgraph=True)
def add_platform_offset(value):
return value + 1 if is_musa() else value - 1
value = torch.zeros(1)
actual = add_platform_offset(value)
expected = torch.ones(1) if is_musa() else -torch.ones(1)
torch.testing.assert_close(actual, expected)
@unittest.skipUnless(torch.cuda.is_available(), "requires CUDA")
class TestFlattenArraysToInt64Tensor(CustomTestCase):
"""`flatten_arrays_to_int64_tensor` is invoked by `prepare_for_extend`