Repository navigation
Pass uniform_q_len to the FlashInfer target-verify plan for EAGLE speculative decoding - #44115
brandonfzhang wants to merge 5 commits into
Conversation
…culative decoding EAGLE target verify gives every request exactly num_tokens_per_req (== draft_token_num) query rows: EagleVerifyInput builds qo_indptr as a constant-stride arange. FlashInfer's FA2 CUDA-graph planner can use that promise (BatchPrefillWithPagedKVCacheWrapper.plan(..., uniform_q_len=N), new in flashinfer-ai/flashinfer#6144) to size its query tile for N * gqa_group_size packed rows instead of the ragged worst case total_rows - bs + 1; without it the verify attention of a 32-head / TP8 / 4-draft-token model runs on a 64-row tile for a 16-row problem. call_begin_forward now adds uniform_q_len=num_tokens_per_req to the paged begin_forward kwargs when the input is an EAGLE verify batch, the installed FlashInfer's plan() takes the keyword (checked once per wrapper class with inspect.signature; **kwargs-only fails closed) and the plan is the real one (fast_prefill_plan never serves the custom-mask verify plan). On an older FlashInfer the call is byte-for-byte the previous one. FlashInfer re-checks the promise against qo_indptr on the host and plans without the hint if it does not hold, so the keyword cannot break a plan. Other verify families (DFLASH, NGRAM) and draft extends keep the stock plan. No runtime effect until the FlashInfer pin includes sgl-project#6144. With it, measured on Qwen3.5-397B-FP8 with EAGLE on 8x H200 at 10 concurrent agentic sessions: the planner hint alone -15.0 % ITL p50; with FlashInfer's specialized SM90 fp8-KV verify kernel, which dispatches behind the same hint, -19.4 % ITL p50, +6 % output tok/s, accept length unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Overall the code logic LGTM. Maybe we can wait for the FlashInfer version bump, then retest the performance and run CI. |
nvpohanh
left a comment
There was a problem hiding this comment.
[by Claude Code] Severity: style | Confidence: High
Issue
The Benchmarks section gives results but no commands, so others cannot rerun them. This PR has no effect with the pinned FlashInfer 0.7.1, so the FlashInfer build also matters.
Fix
Please add:
- The server launch command for Qwen3.5-397B-A17B-FP8 on 8x H200 (TP8/EP8, EAGLE 3/1/4), and which FlashInfer build you used (one that includes flashinfer-ai/flashinfer#6144). For the "hint only" row, say how the SM90 kernel was turned off, for example
FLASHINFER_DISABLE_EAGLE_VERIFY_FP8KV_SM90=1. - The benchmark command for the 256k-context agentic workload, with the trace or dataset and its length and session settings. If the trace is private, a public long-context run such as
python -m sglang.bench_servingagainst the same server is enough. - The GSM8K command behind "197 vs 196 / 200".
STACK=qwennext-stack-204-2026-10-15, code tip c8792c2. Entries: 200 sgl-project#43527 QSA cache sharding (default OFF), 201 sgl-project#39716 tokenizer-clone preprocessing (DEFAULT-ON), 202 sgl-project#38650 reconcile (upstream adopted, entry-156 helper dropped), 203 sgl-project#42388 linear lossless-verify (default OFF, replayssm-exclusive A/B arm), 204 no-GPU test maintenance trio. Header/CAVEATS: FI 0.7.1 venv bump marked resolved (entries 162-164 PCIe-IPC ACTIVE next restart; 10-09/10-14 INERT notes annotated HISTORY), sgl-project#44115 re-skipped at 0.7.1 (uniform_q_len still absent from wrapper plan()), former DEFER/DEDICATED-WINDOW block replaced with resolution pointers, new BEHAVIOR WATCH bullet for the restart. Co-Authored-By: opencode <noreply@opencode.ai>
…tune-gate test fix (b0fce3b), and correct the PCIe-IPC "ACTIVE" claim Entry 228 records the FI bump in both halves: the clean cherry-pick of upstream 44be30d/sgl-project#44591 (2 Dockerfile ARGs, the sole pyproject pin at python/pyproject.toml:42, and the engine.py:1925 assert_pkg_version FLOOR) and the operator's prod-venv swap 0.7.1 -> 0.7.2 (one-line freeze diff, no jit-cache/cubin companions). It carries the 53-module/198- symbol API audit (1 benign missing symbol), the pin-vs-wheel reasoning (assert_pkg_version is a FLOOR -- the real hazard was `uv pip install -e 'python[dev]'` silently downgrading the wheel back to 0.7.1), the deliberate sglang-kernel 0.4.9 floor with the sgl-project#44590 pairing constraint, the rollback order, the first-boot cost (0.7.2/120f holds nothing but a 0-byte log => re-JIT of sampling + XQA, autotune sweep cold; 147 s baseline), sgl-project#44115 as PERMANENTLY not-applicable (plan positional list byte-identical, no uniform_q_len), sgl-project#43471 as never version-gated, the native-ReplaySSM convergence lever and the new `cake` mm_fp4 backend. CORRECTION: entries 162-164 (PCIe-IPC vocab gathers) were never ACTIVE. The wheel satisfies a dependency; the transport is behind SGLANG_ENABLE_PCIE_IPC_ALLREDUCE = EnvBool(False) with no CLI flag, unset in the prod run script and absent from the live process env. Prod TP4 vocab gathers and the bf16 decode all-reduce remain on NCCL; testing it is the top pending A/B (harness arms pcie_ipc_on / pcie_ipc_on_ll / pcie_ipc_off_recheck). Annotated in place at all eight stale claims (header STACK block, 10-15 updated block, FOURTH-merge synced line, entry 162 bracket, merge-4 entry line, the merge-4 battery bullet, the entries-200-204 BEHAVIOR WATCH bullet, the LIVE VENV bullet). Entry 229 records the wheel-independent autotune-gate fix: 7P/1F -> 9P (stale auto-Mock met entry 225's b12x arm; the fp8_utils per-tensor patch pins a real production probe, so it is legitimate, not a workaround; WIP DBG scaffolding stripped pre-commit; diff is +29/-2, not +31/-2). Header/CAVEATS: STACK 227 -> 229, tip -> 2b46270, new updated: block for 228-229, MERGE sgl-project#5 STILL OWED refreshed (origin/main 716c915, +325), the FI-0.7.2 ADDENDUM to the pinned-baseline bullet (autotune_gate 8P -> 9P chain, NEW test_jit_cache 42P, kernels_namespace REMAINS 76P -- the "78P" claim is false and appears nowhere in this file), a new top-priority BEHAVIOR WATCH bullet for the first 0.7.2 boot, and the supersede-in-place annotation on the "NOT TAKEN (a) FLASHINFER IS NOT BUMPED" bullet (direction-correct, mechanism-wrong). Push state recorded truthfully: detain carries the branch up to 3c44685; these commits are unpushed. stack.txt only; LIVE ONLY AFTER RESTART.
…k-274, wanted_stack LANDED marks, CAVEATS docs-only ledger items (A) Header: STACK line 260->274; three wave lines (+spec-mamba-2026-10-28 (pr45491+pr45435+pr45472+pr45670), +admission-2026-10-28(pr46272*+ pr43484*+pr46496*+pr46643*+pr45607*+pr45789*), +parsers-2026-10-28 (pr44124+pr45631+pr45855+pr46167)); tip line -> 2714457; updated 261-274 headline block (entry-119 SUPERSEDED by upstream sgl-project#45435/sgl-project#45472; classic-pool mamba admission gate w/ upstream ImportError CAUGHT; UNCONDITIONAL latch reset = TTFT watch; coder-parser union; logits trilogy as CONTRACT UNIT; 14 picks, 0 integrator conflicts). (B) wanted_stack.txt: FAM-A5 + FAM-B234 blocks deleted -> LANDED records (chain shas, interlock audit, supersession + behavior-change pointers into entries 261-270); SECTION-B sgl-project#44124/trilogy blocks -> LANDED; sgl-project#37958/sgl-project#45559 block -> DONE (verdict now in CAVEATS). (C) DOCS-ONLY LEDGER ITEMS: CAVEATS gains the sgl-project#37958/sgl-project#45559 VERIFIED- INERT bullet (two independent hard gates, coarse-barrier fallback, do-not-re-litigate-at-merge-sgl-project#5 line, revisit condition = dropping NEXTN or HiCache) and the dated C0b/C0c corrections (sgl-project#44030 obsolete rationale, sgl-project#44858 REVERSED by sgl-project#45289 [ported-pending wt-moe], sgl-project#44115 permanent N/A, 10-28 open-status re-verification list, no-pin-moves note: 0.7.2/0.4.9 floor unchanged) with the C0a tick (sgl-project#37625/sgl-project#37941/sgl-project#39727 still outstanding; only sgl-project#43927/sgl-project#44849 partially satisfied). C0b/C0c/C8 marked DONE in wanted_stack; C0a stays open.
nvpohanh
left a comment
There was a problem hiding this comment.
[by Claude Code] This review is done by agents. Please feel free to disagree if they do not make sense.
…robe Capture fixes the verify kernel's query tile from uniform_q_len, so every plan through a captured verify wrapper must carry the same hint. The SGLANG_OPT_FLASHINFER_VERIFY_HOST_PLAN replay path (fast_prefill_plan) passed a hard-coded 0; it now takes uniform_q_len and the call site sets the hint for both plan paths. With the pin moving past FlashInfer 0.7.2.post1 (sgl-project#46926), plan() always has the keyword: the inspect-based capability probe, its imports and its test cases are dropped, and the helper docstring now states the captured- wrapper contract instead of describing a host-side fallback that does not hold across replays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
SGLang now always hints the EAGLE verify plan, and production captures go through the hinted path, so the test's direct reference plan and its step-0 capture stand-in must pass uniform_q_len too for the plan_info-equality contract to compare like with like. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
- Rewrite the call_begin_forward and host-plan test comments in plain English. - Explain when num_tokens_per_req <= 0 in _verify_plan_uniform_q_len. - Remove test_verify_plan_uniform_q_len.py; it only mocked the helper on CPU. test_flashinfer_host_plan.py covers the hint on real wrappers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Required checks failed on b06b088; this PR cannot be merged until they pass. PR title (run)
|
Motivation
EAGLE target verify gives every request exactly
num_tokens_per_req(=draft_token_num) query rows:EagleVerifyInputbuildsqo_indptras a constant-stride arange. FlashInfer's FA2 CUDA-graph planner cannot see that from the ragged CSR alone, so it sizes its query tile for the worst case (total_rows - bs + 1rows per request). For a typical verify batch (32 q-heads, TP8, 4 draft tokens) that means running a 64-row tile on a 16-packed-row problem.flashinfer-ai/flashinfer#6144 added a
uniform_q_lenkeyword toBatchPrefillWithPagedKVCacheWrapper.plan(): a host-side promise that every request has exactly that many query rows, letting the planner size the tile foruniform_q_len * gqa_group_sizepacked rows. The same hint is the dispatch key for FlashInfer's specialized SM90 fp8-KV verify kernel from that PR.Modifications
One wiring change in
flashinfer_backend.py:call_begin_forwardaddsuniform_q_len=num_tokens_per_reqto the paged plan kwargs for EAGLE verify batches (SpecInputType.EAGLE_VERIFY; DFLASH / NGRAM verify and draft extends keep the stock plan).Capture fixes the verify kernel's query tile from the hint, so every plan through a captured verify wrapper must carry the same hint: the hint is passed on both plan paths, the real
plan()and theSGLANG_OPT_FLASHINFER_VERIFY_HOST_PLANsync-free replay (fast_prefill_plan, which previously pinned the argument to 0).Requires the FlashInfer >= 0.7.2.post1 pin (#46926), the first release with the
plan(uniform_q_len=...)keyword; this PR should land after it. An earlier revision carried aninspect.signaturecapability probe to tolerate older FlashInfer; per review it is dropped.Adds a CPU unit test for the gating (only EAGLE verify batches are hinted, unset
num_tokens_per_reqis not a promise).Benchmarks
Qwen3.5-397B-A17B-FP8, TP8/EP8 on 8x H200, EAGLE (
steps 3 / topk 1 / draft 4), 256k-context agentic workload at 10 concurrent sessions, 1 h steady state per arm. Reported metric: normalized interactivity (output tokens/s per request over each request's own decode, so it isolates decode speed from TTFT and queueing). Each arm is compared against a production run of the same day on the same hardware; cross-day control drift was 1.8% at p50 and 3.7% at p90.uniform_q_lenhint (planner tile only)Corresponding ITL p50: −15.0% and −19.4%. Output tok/s +6%, accept length unchanged (3.30 vs 3.28), output lengths unchanged, GSM8K accuracy at parity (197 vs 196 / 200 correct, paired).
Checklist
pre-commit run --all-files🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #41449065916
Latest PR Test (Extra): ❌ Run #41449065830
Latest PR Test (AMD ROCm 10): ❌ Run #41449065807
Tip
Base CI only covers the core cases, and its workload may not exercise your change. Use
/rerun-testto run the tests your change touches, or copy this prompt for an agent: