[AMD] [GLM-5.3-Flash Day 0] Build the fused DSA k-pool top-k JIT kernel on HIP (#39339)

Co-authored-by: Raiden-Makoto <Raiden-Makoto@users.noreply.github.com>
Co-authored-by: Thomas Wang <thomawan@amd.com>
Co-authored-by: Kevin Mi <mikevin920@yahoo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jacob0226
2026-09-21 15:24:44 -07:00
committed by GitHub
co-authored by Raiden-Makoto Thomas Wang Kevin Mi Cursor
parent 2261c2e618
commit 8bde82c0ad
3 changed files with 113 additions and 0 deletions
@@ -11,7 +11,11 @@
#include <bit>
#include <cstddef>
#include <cstdint>
#ifndef USE_ROCM
#include <cuda_fp16.h>
#else
#include <hip/hip_fp16.h>
#endif
namespace sglang {
namespace {
@@ -53,6 +53,8 @@ inline constexpr auto cudaSuccess = hipSuccess;
#define cudaDeviceGetAttribute hipDeviceGetAttribute
#define cudaDevAttrComputeCapabilityMajor hipDeviceAttributeComputeCapabilityMajor
#define cudaDevAttrComputeCapabilityMinor hipDeviceAttributeComputeCapabilityMinor
#define cudaFuncSetAttribute hipFuncSetAttribute
#define cudaFuncAttributeMaxDynamicSharedMemorySize hipFuncAttributeMaxDynamicSharedMemorySize
#endif
namespace sglang {
@@ -0,0 +1,107 @@
"""Parity coverage for the fused DSA k-pool top-k / pool-expansion / tail JIT kernel.
``fast_kpool_topk_transform_fused`` is the only implementation for the pooled
group budgets GLM-5.3-Flash uses (``index_topk=2048`` over ``index_kpool=4``
gives ``group_topk=512``); ``kpool_fp8_index`` has no Python fallback in that
range, so a build or numerical break here takes the model down rather than
making it slower.
The radix selector does not specify an output order and DSA attention is
permutation-invariant over the selected set, so the pooled columns are compared
as a set. The tail columns are positional and are compared exactly.
Registered for AMD only: the kernel had no direct coverage on any platform, and
adding CUDA coverage for it is not this change's call to make.
"""
import unittest
import torch
from sglang.kernels.ops.moe.kpool_topk_transform import fast_kpool_topk_transform_fused
from sglang.test.ci.ci_register import register_amd_ci
from sglang.test.test_utils import CustomTestCase
register_amd_ci(est_time=60, stage="jit-kernel-unit", runner_config="amd")
@unittest.skipUnless(torch.cuda.is_available(), "Test requires a GPU")
class TestKpoolTopkTransformFused(CustomTestCase):
POOL_SIZE = 4
def _distinct_scores(self, rows: int, groups: int) -> torch.Tensor:
"""Strictly distinct scores per row, so top-k selection has no ties to break."""
return torch.stack(
[torch.randperm(groups, dtype=torch.float32) for _ in range(rows)]
).cuda()
def _expected_tokens(
self, score_row: torch.Tensor, group_topk: int
) -> torch.Tensor:
"""The pooled top-k groups expanded to their ``pool_size`` token ids."""
groups = torch.topk(score_row.float().cpu(), group_topk).indices
offsets = torch.arange(self.POOL_SIZE, dtype=torch.int64)
return (groups.unsqueeze(1) * self.POOL_SIZE + offsets).reshape(-1)
def _run(self, rows, groups, topk, seq_lens_host=None):
torch.manual_seed(0)
score = self._distinct_scores(rows, groups)
lengths = torch.full((rows,), groups, dtype=torch.int32, device="cuda")
seq_lens = (
torch.tensor(seq_lens_host, dtype=torch.int32, device="cuda")
if seq_lens_host is not None
else None
)
out = fast_kpool_topk_transform_fused(
score=score,
lengths=lengths,
pool_size=self.POOL_SIZE,
topk=topk,
seq_lens=seq_lens,
)
return score, out.cpu()
def _assert_pooled_columns(self, score, out, topk):
group_topk = topk // self.POOL_SIZE
for row in range(score.shape[0]):
selected = out[row, :topk]
expected = self._expected_tokens(score[row], group_topk)
self.assertEqual(
sorted(selected.tolist()),
sorted(expected.tolist()),
msg=f"row {row}: selected token set differs from torch.topk",
)
def test_group_topk_512_matches_reference(self):
# GLM-5.3-Flash: index_topk=2048 over index_kpool=4.
score, out = self._run(rows=2, groups=1024, topk=2048)
self._assert_pooled_columns(score, out, topk=2048)
def test_group_topk_128_matches_reference(self):
score, out = self._run(rows=2, groups=512, topk=512)
self._assert_pooled_columns(score, out, topk=512)
def test_tail_columns_hold_the_trailing_partial_pool(self):
groups, topk = 1024, 2048
for extra in range(self.POOL_SIZE):
with self.subTest(tail=extra):
seq_len = groups * self.POOL_SIZE + extra
score, out = self._run(
rows=2, groups=groups, topk=topk, seq_lens_host=[seq_len] * 2
)
self._assert_pooled_columns(score, out, topk=topk)
expected_tail = [seq_len - extra + i for i in range(extra)]
expected_tail += [-1] * (self.POOL_SIZE - 1 - extra)
for row in range(out.shape[0]):
self.assertEqual(out[row, topk:].tolist(), expected_tail)
def test_output_width_carries_the_tail_columns(self):
# kpool_fp8_index feeds this width straight into the page-table transform,
# so it is 2048 + 3 = 2051 for GLM-5.3-Flash rather than a round 2048.
topk = 2048
_, out = self._run(rows=1, groups=1024, topk=topk, seq_lens_host=[1024 * 4 + 1])
self.assertEqual(tuple(out.shape), (1, topk + self.POOL_SIZE - 1))
if __name__ == "__main__":
unittest.main()