[DSA] Trim top-k v2 output modes and tighten its PDL waits (#35041)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DarkSharpness
2026-08-19 13:09:21 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 1c82955861
commit 746418a1ec
5 changed files with 156 additions and 155 deletions
@@ -141,28 +141,41 @@ def _reference(scores, seq_lens, k):
return ref
def _plan(seq_lens):
"""Plan, then break stream adjacency with the transform launch.
The transform kernel prefetches the plan metadata BEFORE its PDL wait, which
is only legal while the plan kernel is not the immediately preceding kernel
in the stream -- in production the plan is built during per-forward metadata
prep, a whole model forward earlier. Launching the two back to back would
read the plan through a programmatic dependency that guarantees no memory
visibility, so mirror the production separation instead.
"""
metadata = plan_topk_v2(seq_lens)
torch.cuda.synchronize()
return metadata
def _run(scores, seq_lens, page_table, inv_cpu, k):
batch = scores.shape[0]
metadata = _plan(seq_lens)
out = torch.full((batch, k), -1, dtype=torch.int32, device=scores.device)
metadata = plan_topk_v2(seq_lens)
topk_transform_512_v2(scores, seq_lens, page_table, out, PAGE_SIZE, metadata)
torch.cuda.synchronize()
out_cpu = out.cpu().tolist()
return [_invert(out_cpu[i], inv_cpu[i]) for i in range(batch)]
def _run_raw(scores, seq_lens, page_table, k):
"""Run the kernel and return its optional raw (pre-transform) top-k index
output per row, dropping -1 padding -- the selected positions themselves,
NOT the page-table transform of them."""
def _run_raw(scores, seq_lens, k):
"""Run with no page table and return the selected indices per row, dropping
-1 padding -- the selected positions themselves, NOT a page transform."""
batch = scores.shape[0]
metadata = _plan(seq_lens)
out = torch.full((batch, k), -1, dtype=torch.int32, device=scores.device)
raw = torch.full((batch, k), -1, dtype=torch.int32, device=scores.device)
metadata = plan_topk_v2(seq_lens)
topk_transform_512_v2(scores, seq_lens, page_table, out, PAGE_SIZE, metadata, raw)
topk_transform_512_v2(scores, seq_lens, None, out, PAGE_SIZE, metadata)
torch.cuda.synchronize()
raw_cpu = raw.cpu().tolist()
return [[v for v in raw_cpu[i] if v != -1] for i in range(batch)]
out_cpu = out.cpu().tolist()
return [[v for v in out_cpu[i] if v != -1] for i in range(batch)]
@pytest.mark.parametrize("page_mode", ["identity", "perm"])
@@ -229,68 +242,25 @@ def test_topk_v2_ragged(batch: int, shape: str, k: int, per_row_pt: bool) -> Non
_assert_topk_close(scores.cpu(), ref_raw, our_raw, batch, lengths.cpu(), k)
@pytest.mark.parametrize("page_mode", ["identity", "perm"])
@pytest.mark.parametrize(
"batch,seq",
[
(8, 256), # trivial
(8, 4096), # register
(4, 131072), # fused small-batch cluster
(64, 131072), # persistent cluster + main<3> epilogue
(256, 131072), # non-cluster streaming
],
)
@torch.inference_mode()
def test_topk_v2_raw_indices(batch: int, seq: int, page_mode: str) -> None:
"""The optional raw-index output must be the pre-transform position of each
transformed output slot (out[j] == page_to_indices(raw[j])), and -1 aligns."""
k = 512
torch.manual_seed(batch * 131 + seq)
device = "cuda"
width = (seq + 3) & ~3
scores = torch.randn(batch, width, dtype=torch.float32, device=device)[:, :seq]
seq_lens = torch.full((batch,), seq, dtype=torch.int32, device=device)
num_pages = (seq + PAGE_SIZE - 1) // PAGE_SIZE
page_table, inv_cpu = _make_page_table(batch, num_pages, page_mode, device)
out = torch.full((batch, k), -1, dtype=torch.int32, device=device)
raw = torch.full((batch, k), -1, dtype=torch.int32, device=device)
metadata = plan_topk_v2(seq_lens)
topk_transform_512_v2(scores, seq_lens, page_table, out, PAGE_SIZE, metadata, raw)
torch.cuda.synchronize()
out_cpu, raw_cpu = out.cpu().tolist(), raw.cpu().tolist()
for i in range(batch):
for j in range(k):
o, r = out_cpu[i][j], raw_cpu[i][j]
if o == -1:
assert r == -1, f"b={i} j={j}: out=-1 but raw={r}"
else:
inv = (int(inv_cpu[i][o >> PAGE_BITS]) << PAGE_BITS) | (o & PAGE_MASK)
assert r == inv, f"b={i} j={j}: raw={r} != inverse(out)={inv}"
@pytest.mark.parametrize("k", [512, 1024, 2048])
@pytest.mark.parametrize("batch,seq", FIXED_CONFIGS)
@torch.inference_mode()
def test_topk_v2_output_indices(batch: int, seq: int, k: int) -> None:
"""Validate the raw (pre-transform) index output DIRECTLY against torch.topk.
"""Validate the selected indices DIRECTLY against torch.topk.
Unlike ``test_topk_v2`` -- which checks the page-transformed output and inverts
it through the page table -- this exercises the selected indices themselves, so
it isolates the top-k selection from the page-table transform. A permuted page
table is used so raw != out, catching any bug that leaks transformed page
indices into the raw buffer. Covers every dispatch template/boundary.
Runs the no-page-table mode, so the output is the selected positions
themselves. Unlike ``test_topk_v2`` -- which checks the page-transformed
output and inverts it through the page table -- this isolates the top-k
selection from the transform, and it is the only coverage of that mode.
Covers every dispatch template/boundary.
"""
torch.manual_seed(batch * 100003 + seq * 7 + k + 1)
device = "cuda"
width = (seq + 3) & ~3
scores = torch.randn(batch, width, dtype=torch.float32, device=device)[:, :seq]
seq_lens = torch.full((batch,), seq, dtype=torch.int32, device=device)
num_pages = (seq + PAGE_SIZE - 1) // PAGE_SIZE
page_table, _ = _make_page_table(batch, num_pages, "perm", device)
our_raw = _run_raw(scores, seq_lens, page_table, k)
our_raw = _run_raw(scores, seq_lens, k)
ref_raw = _reference(scores, seq_lens, k)
_assert_topk_close(scores.cpu(), ref_raw, our_raw, batch, seq_lens.cpu(), k)