[Perf][Spec Decode] dflash/dspark: prefix-cache last-block-drop exemption + decode-path overhead reductions - #54092
Closed
puririshi98 wants to merge 4 commits into
Closed
puririshi98 wants to merge 4 commits into
puririshi98 wants to merge 4 commits into
Conversation
…-block drop The EAGLE last-block drop exists because EAGLE-family drafters combine the prefill-lookahead token (one past a chunked-prefill boundary) with the chunk's final hidden state and write the result into the drafter KV cache, so the last block of a prefix-cache hit may hold KV polluted by a continuation the matching request does not share. dflash/dspark drafters never cache lookahead-polluted KV: their context KV is projected from target hidden states and positions only (precompute_and_store_context_kv), and the lookahead (anchor) token writes KV only at positions past the chunk end, in a block that is overwritten with clean context KV before it can be completed and hashed. The drop therefore protects nothing against stale lookahead-polluted KV for them, while costing one full scheduler block of recompute on every prefix-cache hit. (It does incidentally force target recompute of one block whose draft context KV is otherwise unwritten on cache hits on current main -- the restored-region defect PR vllm-project#47926 masks out of the draft context; the measured real-rejection acceptance parity bounds that interaction at nil.) On hybrid mamba models in align mode, use_eagle also backs the chunk-split's last_cache_position off one block, which enforces the same one-block loss on the mamba side; the KV cache coordinator min()-reconciles hit lengths across groups, so both gates must move together for any token to be recovered. Add SpeculativeConfig.prefix_cache_needs_last_block_drop() -- use_eagle() minus dflash/dspark, i.e. True only for eagle/eagle3/mtp today and fail-closed for future eagle-family methods -- and wire it, instead of use_eagle(), into the KV cache manager's drop and the mamba align chunk-split backoff. num_prefill_lookahead is unchanged (dflash/dspark still read one token ahead mid-prefill), and eagle/eagle3/mtp behavior is byte-identical. Deliberately not covered (conservative direction: shorter hit, never stale KV): the offloading/mooncake connector drops, and the deepseek_v4 eagle-group annotation, which still flags the last-layer KV group for any use_eagle() method -- so dspark on deepseek_v4-family targets keeps the drop via the coordinator's min()-reconciliation. Measured A/B (stock/fix arms interleaved within each job on one node; GB200 sm100 across two jobs on distinct nodes, same day, plus GB300 sm103; Nemotron-3.5-Lightning-30B-A3B-NVFP4 + DSpark drafter, aiperf 32K shared prefix / 2K ISL / 256 OSL, C=1, temp 0, mamba-cache-mode align, scheduler block 2192): steady-state cache-hit cached_tokens rise 28,496 -> 30,688 (hit recompute 6,336 -> 4,144 tokens) in every repeat on both platforms; cache-hit TTFT 256.6 -> 199.6 ms on GB200 (5 pairs x 10 requests) and 257.4 -> 207.0 ms on GB300. Decode throughput is flat within run-to-run noise: pooled over the 5 GB200 pairs, 743.3 -> 734.3 t/s (-1.2%), inside the stock arms' own +/-3.3% same-config spread with the per-pair delta flipping sign; GB300 733.9 -> 734.2 t/s; acceptance length pinned at 3.00 with identical per-position rates in all arms. Stale-KV check: byte-equality is invalid on this stack (the unpatched server fails its own determinism control via kernel-level wobble on near-argmax ties even with stochastic rounding off and standard rejection), so hits were compared to the cold miss at the logit level: with the fix reusing the previously-dropped block (cached_tokens 32,880), the position-0 top-20 logprob gap vs the miss is 0.36-0.41 - below the 0.41-0.48 within-hit noise floor and the same order as the unpatched control's 0.25-0.30 - with the greedy argmax stable across all requests, and real-rejection draft acceptance at parity. Signed-off-by: Rishi Puri <riship@nvidia.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…niform spec-decode query lengths
The 3D (split-KV) launch of the unified attention kernel was restricted to
max_seqlen_q == 1. Speculative-decode drafters and verify passes run small
uniform query lengths (1 bonus + N draft tokens) that are still
decode-shaped: with few sequences the 2D grid is (num_q_blocks x
num_kv_heads) CTAs and the KV read serializes into one latency-bound tile
loop per CTA. Measured on the DSpark drafter geometry (hq=32, hkv=2,
hd=128, sliding_window=1024 + sinks, GB200): q=3 2D full-range pass
1011 us vs the q=1 3D full-range control on the same KV read --
113.4 us at the shipped default segment count
(NUM_PAR_SOFTMAX_SEGMENTS=16, 8.9x), 61.9 us at the sweep's 32-segment
point (16.3x) -- a serialization gap the q>1 restriction leaves on the
table (46 us/layer/step in production).
Three coordinated changes:
1. Launcher: admit max_seqlen_q <= MAX_QUERY_LEN_3D (=4) to the 3D kernel,
gated fail-closed on the caller opting in via a new max_query_len_3d
argument (default None keeps the exact previous behavior for every
existing call site) AND on the softmax_segm_* buffers holding one row
per query token. The decision is factored into _should_use_3d() and
unit-tested. Batch-invariance still forces 2D, unchanged.
2. Sliding-window segment layout: the default 3D layout splits
[0, seq_len) into NUM_SEGMENTS equal tile slices, so a sliding window
lights only ~1-2 segments and keeps the KV read serialized. When a
sliding window is active (and no mm_prefix / R-SWA / chunked masking
can widen the visible range), re-base the segments on
[window_lower_bound_tile, num_tiles). The layout is computed by a
shared helper (compute_window_segment_layout) used identically by
kernel_unified_attention and reduce_segments, and is derived per
sequence from its first query token so every q-block of a sequence
computes the same segment layout. Where q-blocks overlap a shared
boundary token (non-pow2 num_queries_per_kv only), the racing writes
are not bitwise-identical (empty-init M=-inf/L=1 vs masked-out
M=0/L=0 partials) but both reduce to a zero contribution in
reduce_segments -- the same benign FP-nondeterministic overlap class
the 2D path already carries; production num_queries_per_kv=16 has no
overlap. The window layout is gated on the same max_query_len_3d
opt-in (> 1) as the q>1 admission: every non-opted launch --
including all pre-existing q=1 sliding-window 3D decode, e.g.
Gemma3-family on TRITON_ATTN or an eagle-family target with
sliding-window layers at a zero-draft-token step -- keeps the exact
previous full-sequence segment layout (the USE_SW_SEGMENTATION=False
branches are the unchanged upstream layout computation). Extending
the window layout to the default q=1 population is a plausible win
but is deferred to a follow-up with its own model evaluation.
3. TritonAttentionMetadataBuilder: raise max_query_len_3d above 1 only
for dflash/dspark drafters (new spec_decode_max_query_len_3d(),
fail-closed: eagle/eagle3/mtp and any unaudited speculative method
keep 1, so their target verify passes stay byte-identical on the
pre-existing 2D launch and their q=1 3D decode keeps the previous
segment layout), size the per-segment softmax buffers per query
token (seq_threshold_3D * max_query_len_3d) and plumb the value
through the metadata. Without speculative decoding -- and for every
non-dflash/dspark method -- buffer sizes and behavior are unchanged.
With one, the three persistent workspaces grow by at most 4x
(MAX_QUERY_LEN_3D), on top of a base size set by seq_threshold_3D.
With decode cudagraphs disabled that threshold keeps its
grid-derived value: at the production drafter geometry (hq=32,
hkv=2, hd=128 -> seq_threshold_3D=64) the growth is +48 MiB per
builder instance (softmax_segm_output 16 MiB -> 64 MiB;
segm_max/expsum 0.125 MiB -> 0.5 MiB each). With decode cudagraphs
enabled the threshold first snaps to the closest capture size, so
the validated production recipe (cudagraph_mode FULL, capture sizes
[1,2,4] -> threshold 4) pays ~+3 MiB per builder
(softmax_segm_output 1 MiB -> 4 MiB; segm_max/expsum +24 KiB each).
Numerics: 2D and 3D are already treated as numerically-equivalent-but-not
-bitwise upstream (3D is the default for all q=1 decode, with the
batch-invariance opt-out); this extends the same policy surface to q<=4
and to the window-based segment layout on the opted-in dflash/dspark
methods only -- every non-opted launch keeps its previous kernel path and
segment layout unchanged. New tests assert 3D output matches both the
reference and the 2D kernel on spec-decode shapes (q_len 1-4 x
sliding_window {off, 1024} x sinks {off, on} x pow2 and non-pow2
num_queries_per_kv, including non-divisible segment tails), with a poison
check that the 3D path actually executed, plus CPU tests for the
_should_use_3d() admission and the fail-closed method gate; the
opted-in matrix covers the window layout (its mixes include q=1 rows).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Rishi Puri <riship@nvidia.com>
…der FULL cudagraph replay At BS=1 speculative decode (DSpark, GB200) the worker host is co-saturated with the GPU (~4.0-4.1 ms of host work vs ~4.1-4.2 ms of GPU per cycle, measured with per-span instrumentation over 1000 steady cycles), so dead host work on the propose path converts directly to ITL. Under FULL cudagraph replay the DFlash/DSpark propose path rebuilds the draft attention metadata and the per-layer slot-mapping dict every cycle and then discards both: run_fullgraph reads only persistent input buffers. The rebuild survived only for its builder-state side effects. Measured host cost of the dead rebuild on the production DSpark geometry: 0.125 ms/cycle, dropping to 0.007 ms with the skip. New builder flag supports_skip_draft_rebuild (default False, fail-closed) lets a builder declare its build() free of side effects a replayed graph depends on. TritonAttentionMetadataBuilder sets it in __init__ to True iff R-SWA is inactive (rswa_window is init-constant; the same instance-attribute pattern as supports_draft_decode_metadata_update in the flash-attn/MLA builders) -- restaging persistent_rswa_prefix_lens is the only build() branch whose effect a captured graph reads. The speculator skips the rebuild only when every draft builder is skip-safe AND cp_size == 1 (DCP refreshes dcp_local_seq_lens inside the build); otherwise the rebuild still runs purely for its builder-state updates (the pure slot-mapping dict build, which has no side effects, is dropped under FULL unconditionally). Backends that restage persistent decode state in build() (e.g. FlashInfer planning) keep the rebuild by default. Applies to DSpark/DFlash2 via inheritance. Numerics: the skipped work was unconsumed on the skip path; no metadata value changes anywhere, no kernel or sampler is touched. New tests assert the fail-closed gating: no builders, any unsafe builder anywhere, the base-class default, and the Triton R-SWA branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Rishi Puri <riship@nvidia.com>
The hybrid-mamba builder's _compute_common_metadata re-derives, on every decode step, values that are value-determined in steady decode: the single-token-prefill reclassification chain (torch.diff + boolean masks + .item() on CPU tensors) and split_decodes_and_prefills both reduce to constants when no row is prefilling and every query fits the decode threshold -- which is every step of a long generation, where target-side input prep measures 1.86 ms/cycle of host time on a co-saturated (host ~= GPU) spec-decode cycle. Two value-identical shortcuts, both fail-closed to the full path: 1. uniform_decode_split(): when is_prefilling has no True row and max_query_len <= decode_threshold, no row can reclassify and the split is all-decode by contract -- return (num_reqs, 0, num_actual_tokens, 0) directly. Any other batch (prefill chunks, single-token prefill rows, long extends, missing is_prefilling) takes the existing chain unchanged. A parametrized test asserts the fast path fires only when its result equals the full reclassification + split chain. 2. Cache the step-invariant arange(1 + num_speculative_blocks) gather offsets that mamba_get_block_table_tensor re-materializes on the device per build in align mode (one allocation + one launch per step), with an identity test. No metadata value changes on any path; numerics untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Rishi Puri <riship@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Batch-1 speculative decode with the dflash/dspark drafter family loses throughput in three independent places, each measured with per-span instrumentation over 1000 steady decode cycles on GB200 (Nemotron-3.5-Lightning-30B-A3B-NVFP4 + DSpark, production recipe). The worker host and the GPU are co-saturated on this path (~4.0 ms host vs ~4.1 ms GPU per cycle), so both GPU-side and host-side dead weight convert directly to inter-token latency:
use_eagle()gates the EAGLE last-block drop, but dflash/dspark never cache lookahead-polluted KV (context KV is projected from target hidden states and positions only; the anchor token writes past the chunk end and is overwritten before its block can be hashed). The drop protects nothing for them and costs one full scheduler block (2,192 tokens, ~50 ms TTFT) per cache hit — on the mamba side too, since the align chunk-split backoff shares the predicate and the KV coordinator min()-reconciles.max_seqlen_q == 1; drafter/verify passes run small uniform q (1 bonus + N draft) that is still decode-shaped. On the drafter geometry (hq=32, hkv=2, hd=128, SW 1024 + sinks), the q=3 2D full-range pass takes 1011 us; the microbenchmark's q=1 3D full-range control on the same KV read measures 113.4 us at the shipped default segment count (NUM_PAR_SOFTMAX_SEGMENTS= 16, ~8.9x) and 61.9 us at the sweep's 32-segment point (~16.3x) — and a sliding window lights only ~1-2 of the equal-slice segments, so the segment layout is re-based on the visible window.run_fullgraphreads only persistent input buffers) — 0.125 ms/cycle. The hybrid-mamba builder re-derives the single-token-prefill reclassification chain and the decode/prefill split from scratch each step, though both are value-determined constants in steady uniform decode.Changes (four self-contained, independently revertable commits)
[Perf][Spec Decode]Exempt dflash/dspark from the EAGLE prefix-cache last-block drop — newSpeculativeConfig.prefix_cache_needs_last_block_drop()(True for eagle/eagle3/mtp, fail-closed for unknown eagle-family methods) wired into the KV-cache manager drop and the mamba-align backoff;num_prefill_lookaheadunchanged.[Perf][Kernel]Extend 3D split-KV toq <= 4, opt-in via a newmax_query_len_3dargument (default None = exact previous behavior, batch-invariance still forces 2D), with per-query-token segm buffers and a window-based segment layout shared by main kernel and reduce. The window layout is gated on the same opt-in (max_query_len_3d > 1): every non-opted launch — including all existing q=1 sliding-window 3D decode (e.g. Gemma3 on TRITON_ATTN) — keeps the exact previous full-sequence segment layout (theUSE_SW_SEGMENTATION=Falsebranches are the unchanged upstream layout computation); extending the window layout to that default population is deferred to a follow-up with its own model eval. Method-gated fail-closed: only dflash/dspark raise the admission above q=1; eagle/eagle3/mtp and unknown speculative methods keep their verify passes byte-identical on the 2D launch and their q=1 3D decode on the previous segment layout.[Perf][Spec Decode]Skip the dead rebuild on FULL replay behind a new fail-closed builder flagsupports_skip_draft_rebuild(base False; Triton builder True iff R-SWA is inactive) ANDcp_size == 1; any other builder/config keeps the rebuild for its side effects.[Perf][Mamba]Fast uniform-decode metadata build: an all-decode shortcut that fires only when no row is prefilling andmax_query_len <= decode_threshold(provably value-identical to the full reclassification + split chain), plus caching of the step-invariant gather-offset arange. No metadata value changes on any path.Test Plan
Commands run (from the repo root,
.venvper AGENTS.md; GPU suites inside the GB200 container):num_queries_per_kv, non-divisible segment tails, NaN-poison liveness; asserted viatorch.testing.assert_closeat atol 1.5e-2 / rtol 1e-2 against both references — the tighter maxdiff 8e-6 is from the standalone GB200 microbenchmark's q=1 3D-vs-2D full-range control, not from these tests) + 2 CPU gating tests (admission predicate; dflash/dspark-only method gate, fail-closed for eagle/eagle3/mtp/unknown). The GB200 GPU selection collected the 24 matrix tests plus the admission gating test: 25 passed.git am-clean on current main (re-verified 2026-08-27 on freshly-fetched origin/mainfd57c4b7, after rebasing over [Model Runner V2][Spec Decode] Skip DP sync before EAGLE/MTP draft prefill #53694;git am --keep-non-patchof the whole 4-patch series in a throwaway worktree, all 17 resulting files byte-identical to branch HEAD, author/DCO preserved; no upstream commit inf25c580a..fd57c4b7touches any changed file); ruff check/format clean; pre-existing GPU-test failures in our areas reproduce identically on the unpatched container (era drift, not introduced).Test Result
A/B on GB200 and GB300, interleaved stock/patched arms on the same node with sha-verified overlays, aiperf 32K shared prefix / 2K in / 256 out, concurrency 1, temp 0, synthetic acceptance length 3. Two campaigns, labeled by code state: the validation jobs (GB200 + GB300) measured the pre-final code; because three post-validation changes touched functional lines (a mypy-driven attribute-form change, the rebase over #53694, and the split-KV opt-in gate in the hot kernel file), a shipped-HEAD recheck (GB200 job 1962848, interleaved stock/series 2v2, overlay regenerated in-job from the final patches and sha-verified = HEAD, failed_arms=0) re-ran the full A/B on exactly this branch HEAD. All pre-registered per-commit gates PASS on the recheck; it is the primary column below.
cached_tokens28,496 → 30,688, +2,192 = exactly the block; bands exact in every arm)Rows 2-4 are within-job attribution cells (per-span instrumentation) measured against the commit-1-instrumented baseline arm — commit 1 does not touch the decode path, so it stands in for stock on these spans; their stock column is that validation-phase baseline arm, their PR column the shipped-HEAD recheck cell.
Across the two GB200 campaigns the joint decode gain envelope is +33.3..+69.0 t/s (stock draw range across jobs 727–763 t/s, series 776–814): the recheck landed at the low end — reproducing an earlier same-day stock-vs-series cell (+36.6 t/s) — and the validation job at the high end. Three of six pre-registered aggregate recheck gates missed, each dispositioned in the artifact set, none implicating a commit: (a) joint decode +33.3 vs the ≥ +35 t/s gate and ITL −0.0577 vs ≤ −0.06 — missed by 1.7 t/s / 0.0023 ms under a uniform +1-6% host / +0.7-1.4% GPU drift measured on series-touched AND series-untouched spans alike (an environment signature; no series-specific span regressed); (b) a miscalibrated real-rejection acceptance gate — series 37.2% vs same-job stock control 52.0%, but the stock control's own run-to-run intervals span 28.4-52.0%, wider than the gate itself, and the series intervals (28.4-45.7%) sit inside the pooled stock envelope; (c) one greedy-argmax flip at a documented near-tie (details in the numerics paragraph below).
Memory: commit 2 sizes the three persistent split-KV softmax workspaces per admitted query token, so they grow only when a dflash/dspark drafter is configured, capped at 4x (
MAX_QUERY_LEN_3D) on top of a base set byseq_threshold_3D. With decode cudagraphs disabled the threshold keeps its grid-derived value — at the production drafter geometry (hq=32, hkv=2, hd=128 →seq_threshold_3D=64) that is +48 MiB per builder instance (softmax_segm_output16 MiB → 64 MiB;segm_max/expsum0.125 → 0.5 MiB each). With decode cudagraphs enabled the threshold first snaps to the closest capture size, so the validated production recipe (cudagraph_modeFULL, capture sizes [1,2,4] → threshold 4) pays ~+3 MiB per builder (softmax_segm_output1 → 4 MiB;segm_max/expsum+24 KiB each). Without a dflash/dspark drafter, sizes are unchanged.Numerics gates, every production arm on both platforms and both campaigns: acceptance length pinned at 3.00 with per-position acceptance 1.000/1.000/0.000; with real rejection, acceptance parity within the measured small-sample floor (the series does not touch the sampler). Temp-0 stability: the patched hit-vs-miss top-20 logprob gap sits at/below each arm's own within-arm noise in every series arm — 0.25-0.32 in the three argmax-stable arms, matching the unpatched stock controls (0.24-0.30, one per EQ2 campaign). Greedy argmax was stable across all requests in those three arms; the fourth series EQ2 arm (shipped-HEAD recheck, job 1962848) flipped greedy argmax once, at the same documented 0.125-margin pos-0 near-tie the prior arm recorded — a flip at the greedy floor, not a numerics regression: that arm's gap (0.661) still sits below its own within-hit noise (0.666 = 5.3× the margin), and the unpatched stock controls themselves fail temp-0 self-consistency on the same probe (
gen_text_a_eq_b = falsein both stock runs). 2D-vs-3D is the same numerically-equivalent policy surface upstream already applies to all q=1 decode. Sub-fixes that did not reproduce their predicted span on hardware were dropped from the series before submission; every line above ships only what measured.Related issues/PRs
Commit-1 area (searches dated 2026-08-26T09:42Z): not a duplicate. #53388 (draft) adds an opt-in family-wide
disable_eagle_block_dropknob over the same scheduler sites; commit 1 changes the default by drafter-KV provenance, no knob, and the two compose. #47926 fixes the complementary dflash/dspark restored-region draft-KV defect (zero file overlap; acceptance-parity arms bound the interaction at nil). #53479, #50897 orthogonal; #53614/#53598 touch adjacent lines, mechanical rebase either order. Issues #51771, #47930. Re-swept 2026-08-27T08:56Z (the one area not refreshed alongside the 08-27 sweeps below), plus a 2026-08-27 audit pass that caught a miss of both sweeps — two additional hits in total, one from each. From the re-sweep, created after the original sweep: #53945 (OPEN, non-draft, created 2026-08-26T19:36Z) caches the Mamba state at the block-grid position of EAGLE resume and overlaps 4 of commit 1's 6 files (vllm/v1/core/sched/scheduler.py+ all three shared test files); both of its scheduler hunks land insideScheduler._mamba_block_aligned_split, the same function as commit 1's backoff hunk. Not a duplicate, but the composition is semantically non-trivial: its newtail_boundary/junction_stoplogic is keyed onself.use_eaglewith a rationale that only holds while the last-block drop applies — post-commit-1,use_eaglestays True for dflash/dspark while the drop no longer applies, so whichever lands second should re-key that logic onself.drop_prefix_cache_tail(commit 1's predicate); the shared test-file hunks are disjoint/adjacent, mechanical either order. From the audit, an original-sweep miss (created before that sweep) which the re-sweep also failed to surface: #51295 (OPEN, draft, created 2026-08-06T18:15:36Z) fixes a hybrid-attention cache miss caused by the EAGLE drop and overlaps the same 4 of commit 1's 6 files (vllm/v1/core/sched/scheduler.py+ all three shared test files). Its single scheduler hunk (@@ -404) adds aself.use_eagle-keyed hash-unit backoff (tail_boundary = max(tail_boundary - self.hash_block_size, 0)) insideScheduler._mamba_block_aligned_split— the same function as commit 1's backoff hunk (@@ -409), adjacent sub-blocks with no direct textual conflict, but the composition hazard is identical to #53945's: post-commit-1,use_eaglestays True for dflash/dspark while the drop no longer applies, so itstail_boundarybackoff would fire for a drop that never happens; whichever lands second should re-key onself.drop_prefix_cache_tail. Not a duplicate (its mechanism is the hybrid-KV lookup alignment, not the drop exemption); the shared test-file hunks are mechanical either order.Commit-2 area (searches dated 2026-08-27T02:41Z): #45450 (OPEN, non-draft, updated 2026-08-26) implements both of commit 2's mechanisms on the same three files — spec-decode 3D admission via a
speculative_config-derived query length with per-query-token segment buffers, and window-relative 3D segmentation via a shared@triton.jithelper used by mainloop and reduce — measured on B300/NVFP4 with MTP over a v0.22.1 base (its body: not re-validated on main). Material differences here: (a) fail-closed method gate — only dflash/dspark opt in, eagle/eagle3/mtp verify numerics stay byte-identical, whereas #45450 admits MTP/EAGLE; (b) R-SWA and chunked-masking exclusions; (c) validated on current main-era containers with per-commit attribution cells. (Scope note: this PR's window layout is gated on the same opt-in, so existing q=1 sliding-window 3D decode is byte-unchanged here — matching #45450's q=1 exclusion rather than differing on it.) Also adjacent: #52879 (OPEN draft, same files, references #48076), #44652 (OPEN, relaxes the same 3D-launch gate for multi-query verify + non-causal support), and issue #48076 (batch>=12 3D->2D fallback — different trigger: thenum_seqsthreshold, which this series does not change). Re-sweep 2026-08-27T08:56Z surfaced a miss of the original sweep: #53930 (OPEN, non-draft, created 2026-08-26T17:51Z — about 9 h before the sweep above) adds alogger.warning_onceinTritonAttentionMetadataBuilder.__init__anchored exactly between the segm-buffer allocations commit 2 re-shapes and therswa_windowline commit 3 appends after, so it conflicts textually with this series whichever lands second (mechanical to resolve); more materially, its warning fires unconditionally wheneverspeculative_config is not Noneand asserts "Decode runs on the 2D path" (citing #48076) — factually wrong for dflash/dspark once commit 2 lands, so if it merges the warning needs scoping to speculative methods the 3D admission does not cover (spec_decode_max_query_len_3d() == 1).Commit-3 area (searches dated 2026-08-27T04:10Z): not a duplicate. #53426 (OPEN, updated 2026-08-24) is the nearest neighbor — it also removes dead drafter-side work and touches the same
dflash/speculator.py(plus commit 1'sspeculative.py; hunks disjoint from ours in both files), but skips a different thing under a different policy: an explicit opt-in, default-off knob to drop the K=0 draft sync forward when dynamic spec decode resolves K=0 (accepted for mtp/dflash only; dspark is refused), whereas commit 3 is a no-knob, fail-closed builder-flag skip of the draft metadata rebuild whose result FULL cudagraph replay discards — the draft forward still runs (as a replay) and the skip fires on ordinary K>0 steady decode for dflash AND dspark. Orthogonal dead work; the two compose, worst case a mechanical rebase inpropose()(adjacent, non-overlapping hunks). #52782 (OPEN, NVTX/torch-profiler annotations to the model runner + DFlash speculator) and #53096 (OPEN, capture-log guard onneeds_capture()) touch the same file with no semantic overlap — mechanical rebase either order; same class, added on the 2026-08-27T08:56Z re-sweep (a miss of the sweeps above, which it predates): #53978 (OPEN, non-draft, created 2026-08-27T02:32Z) hardens DFlash2 spec warmup against unfilled draft buffers indflash/speculator.pyhunks (__init__,_run_model) disjoint from commit 3's — no semantic overlap, mechanical rebase either order. Two more same-file opens the 08:56Z re-sweep missed (both added by a 2026-08-27 audit): #53970 (OPEN, non-draft, created 2026-08-27T01:12Z — inside the window that re-sweep demonstrably scanned) fixes the dflash batched token budget indflash/speculator.pyhunks (__init__@@ -47,propose@@ -415) disjoint from commit 3's (@@ -31 / -184 / -456; itsproposehunk sits ~35-40 lines above ours) — no semantic overlap, mechanical rebase either order; #53929 (OPEN, non-draft, created 2026-08-26T17:38Z) validates ragged GDN decode with Qwen3.5 DSpark and touches two series files — commit 1'svllm/config/speculative.py(@@ -844 vs c1's @@ -1795, disjoint) and commit 3'svllm/v1/attention/backend.py(@@ -569, inAttentionCGSupport, vs commit 3's @@ -597 inAttentionMetadataBuilder— disjoint, different classes) — no semantic overlap with either commit's mechanism, mechanical rebase either order. #53694 (MERGED 2026-08-27; re-swept 2026-08-27T04:56Z) landed in the samepropose()lines commit 3 rewrites — it hoists the DP-syncnum_tokens_across_dpread out of the dispatch call's return; orthogonal mechanism (DP-sync plumbing, not a metadata-rebuild skip), inert at DP=1 (dispatch_cg_and_sync_dpreturns no sync state whendp_size == 1, the measured recipe), and this series is rebased over it (commit 3's only conflict, context-only). No open PR implements a replay-time metadata-rebuild skip (dflash-speculator and draft-metadata sweeps).Commit-4 area (searches dated 2026-08-27T04:10Z): not a duplicate. #39936 (OPEN, last updated 2026-06-04) cheapens
split_decodes_and_prefillsitself (env-gated CPU-launch-overhead reduction inside the function, plusblock_table.pyslot-mapping changes, Blackwell-targeted); commit 4 instead avoids re-running the reclassification chain + split at all on provably all-decode steady steps, and itsutils.pychange is confined tomamba_get_block_table_tensor— zero hunk overlap with #39936'ssplit_decodes_and_prefillshunks, and #39936 does not touchmamba_attn.py. Different mechanism, composes: #39936 speeds the general path, commit 4 removes it from the steady-decode loop while keeping it as the fail-closed fallback. Also in the area, an enumeration miss of the 04:10Z sweep (added by a 2026-08-27 audit): #48188 (OPEN, non-draft, created 2026-07-09) speeds up_compute_chunk_metadatainternals by ~6x in the samemamba_attn.py— its single hunk (@@ -281,43, spanning lines 281-323) is disjoint from all of commit 4's hunks (26 / 197 / 450 / 529 / 815), and its mechanism (cheapening the chunk-metadata computation itself) is different from commit 4's (skipping the reclassification chain + split entirely on provably all-decode steady steps); the two compose — commit 4's fallback path would simply run #48188's faster computation — mechanical rebase either order. Thesplit_decodes_and_prefills/ mamba-metadata-builder sweeps surfaced no open PR with commit 4's shortcut (that statement stands; #48188 is the nearest same-file neighbor, not a duplicate).AI assistance disclosure
Developed with AI assistance (Claude Code): profiling, root-cause tracing, the fixes, and the hardware A/Bs. I have reviewed every changed line and can defend the series end-to-end.
Signed-off-by: Rishi Puri riship@nvidia.com
🤖 Generated with Claude Code