Remove NSA spec-v2 graph metadata host syncs
NSA spec-v2 draft-extend graph replay was still using host-derived sequence lengths and the draft-decode backend allowlist. That kept NSA on eager draft-extend for spec-v2 and left seq_lens_cpu.max()/list-to-GPU tensor construction on the decode critical path. This ports the small upstream DSA metadata fixes into the local NSA backend: size the captured graph page table to req_to_token width, use the static captured page-table width for graph replay, split v2 draft-extend from variable-length v1 draft-extend, and decide draft-extend graph support from the prefill-style backend. Constraint: Current branch does not have the full upstream needs_cpu_seq_lens scheduler/FutureMap infra. Rejected: Cherry-pick the full DSA fused metadata generation series | too broad and overlaps with local NSA fused metadata-copy code. Confidence: medium Scope-risk: moderate Directive: Do not collapse DRAFT_EXTEND and DRAFT_EXTEND_V2 here; v1 keeps variable accept lengths while v2 must stay graph-static. Tested: local pytest test/registered/spec/eagle/test_eagle_v2_draft_extend_contract.py -q (19 passed) Tested: local py_compile on nsa_backend.py, nsa_backend_mtp_precompute.py, eagle_worker_v2.py Tested: remote g0034 cjy-glm5-new pytest test/registered/spec/eagle/test_eagle_v2_draft_extend_contract.py -q (19 passed) Tested: remote g0034 cjy-glm5-new py_compile on nsa_backend.py, nsa_backend_mtp_precompute.py, eagle_worker_v2.py Not-tested: full decode E2E with SGLANG_ENABLE_SPEC_V2=1
This commit is contained in:
@@ -163,6 +163,86 @@ def test_eagle_v2_binds_draft_runner_to_draft_extend_attention_backend():
|
||||
raise AssertionError("draft_runner.attn_backend is not bound to draft_extend backend")
|
||||
|
||||
|
||||
def test_nsa_cuda_graph_page_table_is_sized_to_req_to_token_width():
|
||||
"""NSA graph page tables must match req_to_token's allocated width.
|
||||
|
||||
Spec decode can transiently exceed max_context_len. Sizing the captured
|
||||
page table from max_context_len makes replay copy widths depend on a
|
||||
narrower fake table than req_to_token actually owns.
|
||||
"""
|
||||
|
||||
tree = _parse_module("python/sglang/srt/layers/attention/nsa_backend.py")
|
||||
cls = _find_class(tree, "NativeSparseAttnBackend")
|
||||
func = _find_method(cls, "init_cuda_graph_state")
|
||||
text = ast.unparse(func)
|
||||
|
||||
assert "self.req_to_token.shape[1]" in text
|
||||
assert (
|
||||
"self.max_context_len + (self.speculative_num_draft_tokens or 0)"
|
||||
not in text
|
||||
)
|
||||
|
||||
|
||||
def test_nsa_cuda_graph_replay_uses_static_page_table_width():
|
||||
"""Replay metadata should not read seq_lens_cpu to choose copy width.
|
||||
|
||||
CUDA graph replay can copy the captured page-table width because the NSA
|
||||
kernels bound row access with cache_seqlens. Reading seq_lens_cpu.max()
|
||||
introduces a host sync in the decode hot path.
|
||||
"""
|
||||
|
||||
tree = _parse_module("python/sglang/srt/layers/attention/nsa_backend.py")
|
||||
cls = _find_class(tree, "NativeSparseAttnBackend")
|
||||
func = _find_method(cls, "init_forward_metadata_replay_cuda_graph")
|
||||
text = ast.unparse(func)
|
||||
|
||||
graph_sync_free_prefix = text.split("elif forward_mode.is_draft_extend():", 1)[0]
|
||||
assert "seq_lens_cpu.max().item()" not in graph_sync_free_prefix
|
||||
assert "metadata.page_table_1.shape[1]" in text
|
||||
assert "assert seq_lens_cpu is not None" not in graph_sync_free_prefix
|
||||
|
||||
|
||||
def test_nsa_cuda_graph_replay_keeps_draft_extend_v1_v2_semantics_separate():
|
||||
"""Spec-v2 draft-extend graph uses static width; spec-v1 stays variable.
|
||||
|
||||
The v2 graph path must not use accept_length/tolist to resize replay
|
||||
tensors. The v1 path still needs variable accepted lengths.
|
||||
"""
|
||||
|
||||
tree = _parse_module("python/sglang/srt/layers/attention/nsa_backend.py")
|
||||
cls = _find_class(tree, "NativeSparseAttnBackend")
|
||||
func = _find_method(cls, "init_forward_metadata_replay_cuda_graph")
|
||||
text = ast.unparse(func)
|
||||
|
||||
assert "forward_mode.is_draft_extend_v2()" in text
|
||||
assert "forward_mode.is_draft_extend(include_v2=True)" not in text
|
||||
assert "torch.full((bs,), self.speculative_num_draft_tokens" in text
|
||||
assert "extend_seq_lens = spec_info.accept_length[:bs]" in text
|
||||
assert "elif forward_mode.is_draft_extend():" in text
|
||||
|
||||
v1_branch = text.split("elif forward_mode.is_draft_extend():", 1)[1]
|
||||
assert "seq_lens_cpu.max().item()" in v1_branch
|
||||
|
||||
|
||||
def test_eagle_v2_cuda_draft_extend_graph_allows_nsa_prefill_backend():
|
||||
"""Spec-v2 should decide draft-extend graph support from prefill backend.
|
||||
|
||||
NSA creates a multi-step backend for draft decode and a NativeSparseAttnBackend
|
||||
for draft extend. Checking the decode backend keeps NSA on the eager
|
||||
draft-extend path even after the replay metadata is graph-safe.
|
||||
"""
|
||||
|
||||
tree = _parse_module("python/sglang/srt/speculative/eagle_worker_v2.py")
|
||||
cls = _find_class(tree, "EagleDraftWorker")
|
||||
func = _find_method(cls, "init_cuda_graphs")
|
||||
text = ast.unparse(func)
|
||||
|
||||
assert "NativeSparseAttnBackend" in text
|
||||
assert "self.draft_extend_attn_backend" in text
|
||||
assert "self.draft_attn_backend, TritonMultiStepDraftBackend" not in text
|
||||
assert "self.draft_attn_backend, TRTLLMMLAMultiStepDraftBackend" not in text
|
||||
|
||||
|
||||
|
||||
def _function_calls(func: ast.FunctionDef, name: str) -> int:
|
||||
count = 0
|
||||
|
||||
Reference in New Issue
Block a user