| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- #!/usr/bin/env python3
- """T8: does the SM90 path see the tail tokens?
- Builds indexer rows exactly as the GLM kpool decode path leaves them in
- topk_indices_buffer (width 2176): pool tokens at columns [0, 4*pool_len),
- -1 up to column 2048, the 0-3 tail tokens at columns 2048.., -1 after.
- Runs triton_convert_req_index_to_global_index(return_valid_counts=True) as
- FlashInferMLASparseSM90Impl.forward_mqa does and checks that the first `ctx`
- compacted entries (what _SM90State plans for) contain every causal slot,
- in particular the tail. Also runs it inside a CUDA graph.
- """
- import sys
- import torch
- from vllm.v1.attention.backends.mla.sparse_utils import (
- triton_convert_req_index_to_global_index,
- )
- dev = torch.device("cuda")
- WIDTH = 2176
- TOPK = 2048
- KPOOL = 4
- BLOCK = 2304
- def build_rows(positions, block_table_row):
- R = len(positions)
- rows = torch.full((R, WIDTH), -1, dtype=torch.int32, device=dev)
- for r, pos in enumerate(positions):
- seq = pos + 1
- pool_len = seq // KPOOL
- n_hist = min(pool_len, TOPK // KPOOL) * KPOOL
- # pools are selected in an arbitrary order by top-k; use a permutation
- pools = torch.randperm(pool_len, device=dev)[: TOPK // KPOOL]
- toks = (pools[:, None] * KPOOL + torch.arange(KPOOL, device=dev)[None, :]).reshape(-1)
- rows[r, : toks.numel()] = toks.to(torch.int32)
- tail = torch.arange(pool_len * KPOOL, seq, device=dev, dtype=torch.int32)
- rows[r, TOPK : TOPK + tail.numel()] = tail
- return rows
- bad = 0
- for positions in ([99, 100, 101, 102, 103, 104, 105, 106], [600, 601, 602, 603], [2047, 2048, 2049, 2050, 2051, 2052, 2053, 2054], [5000, 5001, 5002, 5003]):
- R = len(positions)
- req_id = torch.zeros(R, dtype=torch.int32, device=dev)
- block_table = torch.tensor([[7, 3, 11] + [0] * 5], dtype=torch.int32, device=dev)
- rows = build_rows(positions, block_table[0])
- for graph in (False, True):
- out = torch.empty_like(rows)
- counts = torch.empty(R, dtype=torch.int32, device=dev)
- def run():
- triton_convert_req_index_to_global_index(
- req_id, block_table, rows, BLOCK_SIZE=BLOCK, NUM_TOPK_TOKENS=WIDTH,
- return_valid_counts=True, out=out, valid_counts_out=counts)
- if graph:
- s = torch.cuda.Stream(); s.wait_stream(torch.cuda.current_stream())
- with torch.cuda.stream(s):
- run(); run()
- torch.cuda.current_stream().wait_stream(s)
- g = torch.cuda.CUDAGraph()
- with torch.cuda.graph(g):
- run()
- rows2 = build_rows(positions, block_table[0]); rows.copy_(rows2)
- g.replay()
- else:
- run()
- torch.cuda.synchronize()
- for r, pos in enumerate(positions):
- ctx = pos + 1
- lens = ctx if ctx <= TOPK else TOPK + ctx % KPOOL
- got = out[r, :lens].tolist()
- # expected slots: block_table[tok // BLOCK]*BLOCK + tok % BLOCK for all causal toks
- toks = torch.arange(ctx, device=dev)
- if ctx > TOPK:
- exp_toks = set((toks[-(ctx % KPOOL):] if ctx % KPOOL else toks[:0]).tolist())
- need_tail_only = True
- else:
- exp_toks = set(toks.tolist()); need_tail_only = False
- exp_slots = {int(block_table[0, t // BLOCK]) * BLOCK + t % BLOCK for t in exp_toks}
- gotset = set(got)
- missing = exp_slots - gotset
- tail_slots = {int(block_table[0, t // BLOCK]) * BLOCK + t % BLOCK for t in range((ctx // KPOOL) * KPOOL, ctx)}
- tail_missing = tail_slots - gotset
- neg = sum(1 for v in got if v < 0)
- ok = (not tail_missing) and (need_tail_only or not missing) and int(counts[r]) == lens and neg == 0
- if not ok:
- bad += 1
- print(f" graph={graph} pos={pos} ctx={ctx} planned_lens={lens} valid_count={int(counts[r])} "
- f"neg_in_prefix={neg} tail_missing={sorted(tail_missing)} missing={len(missing)} -> {'ok' if ok else 'BAD'}")
- print("T8:", "PASS" if bad == 0 else f"FAIL ({bad})")
- sys.exit(0 if bad == 0 else 1)
|