Repository navigation
[Mamba] Support speculative decoding with extra_buffer_lazy - #30437
Merged
Merged
Conversation
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
YAMY1234
marked this pull request as ready for review
July 8, 2026 21:06
YAMY1234
requested review from
Qiaolin-Yu,
Ying1123,
hnyls2002,
merrymercy and
xiezhq-hermann
as code owners
July 8, 2026 21:06
YAMY1234
force-pushed
the
lazy-extra-buffer-mtp
branch
4 times, most recently
from
July 16, 2026 01:30
8eefff2 to
93aab75
Compare
extra_buffer_lazy was guarded off for spec decoding (sgl-project#27118 follow-up). Pending-slot design: - mamba_lazy_spec_prepare (scheduler phase): when a track-interval crossing is reachable this verify step (window on kv_committed_len, widened 2x for the overlap lag), ensure the on-demand pending slot exists without swapping mamba_next_track_idx - crossings are uncertain until accept lengths are known. The per-req scatter position is recorded on the batch and rides the result-queue copy. - prepare_mamba_track_for_verify gathers the planned positions, so the post-verify commit's mask-gated scatter writes a crossing state into the pending slot and never overwrites the only confirmed checkpoint. - _mamba_lazy_spec_update (result processing): promote pending -> keep once the crossing is confirmed; a finishing req skips the radix insert when its keep slot was (or may still be) a scatter target. - No evict for transient slots (also applied to the non-spec boundary prealloc): a transient slot is never worth evicting a cached checkpoint; on allocation failure the verify tracks in place and the finish protections apply. - Guards: lazy still rejects dflash (verify bypasses prepare_mamba_track_for_verify) and PD disaggregation (decode pool not wired for lazy).
YAMY1234
force-pushed
the
lazy-extra-buffer-mtp
branch
from
July 16, 2026 21:42
93aab75 to
b1ff775
Compare
hanming-lu
approved these changes
Jul 21, 2026
hanming-lu
left a comment
Collaborator
There was a problem hiding this comment.
LGTM as long as CI passes
Zhylkaaa
pushed a commit
to Zhylkaaa/sglang
that referenced
this pull request
Jul 29, 2026
Jane1019-Libra
added a commit
to Jane1019-Libra/sglang
that referenced
this pull request
Aug 4, 2026
RecoverSSM caches no intermediate h, so its interval checkpoint is a second full per-GDN-layer recovery sweep rather than a scatter out of a cache. It was armed by `mamba_track_indices is not None`, i.e. by extra_buffer being on rather than by this step actually reaching a boundary, so it ran every verify: 30 of the 60 recovery launches per step on Qwen3.5-35B-A3B, 98.8us and 48.6% of recovery time in a TP4 bs=1 trace. With the default 256-token interval and 4 draft tokens a crossing is reachable on 8 of 256 residues, so ~97% of those sweeps only refolded h_0 into the reserved discard slot. Drop the track plan in prepare_mamba_track_for_verify on steps where no request can reach a boundary. Both the SSM sweep and its conv counterpart already gate on `mamba_track_indices is not None`, so the commit path needs no change. The predicate reuses mamba_lazy_spec_in_window (sgl-project#30437), whose 2x draft-token window absorbs kv_committed_len lagging device seq_lens under overlap, so it reports "impossible", never merely "absent" -- which is what keeps it consistent with the independently computed ping-pong advance in BatchResultProcessor._mamba_check_track_boundary. Gated on cache_mode=none: full and ReplaySSM checkpoint with a scatter that does not scale with the layer count, and keep their existing behaviour.
Atituiset
pushed a commit
to Atituiset/sglang
that referenced
this pull request
Sep 10, 2026
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.
Motivation
extra_buffer_lazy(#27118) reduces each running request's mamba checkpoint footprint from 2 slots to 1, but was guarded off for speculative decoding ("Follow-up: Support MTP"). This PR implements that follow-up. The freed slots matter because the mamba pool is shared: whatever the running requests do not pin becomes extra RadixCache capacity for cached checkpoints (quantified below).Modifications
Non-spec lazy allocates the second slot and swaps the write pointer right at the boundary — each decode step advances exactly 1 token, so the boundary is known at prepare time. Under MTP a verify step advances 1..num_draft_tokens accepted tokens and a crossing is only known after verify, so the swap is deferred: prepare a spare slot first, swap only once the crossing is confirmed.
ScheduleBatch.mamba_lazy_spec_prepare(scheduler phase, before each verify):mamba_next_track_idx— the slot may end up never written;_mamba_lazy_spec_update(result processing, accept counts now known):mamba_next_track_idx;extra_buffer_lazystill rejects DFLASH/DSPARK (their verify bypassesprepare_mamba_track_for_verify) and PD disaggregation (the decode-side pool is not wired for lazy).Accuracy Tests
Qwen3-Next-80B-A3B-Instruct, NEXTN (steps=3, topk=1, draft=4), TP4. "Pool" is the mamba state pool (
--max-mamba-cache-size), shared by running requests and cached checkpoints.KL compares logprobs of a cache-hit generation against a no-cache recompute (existing KL kit). *With the small pool, every cached entry is evicted before the kit's second turn (0/32 hits, identical for the extra_buffer baseline), so there is nothing to compare — the first two rows carry the KL evidence.
Checkpoint-boundary finish test. The risky corner of this feature is a request finishing right at a checkpoint boundary while the overlapped next verify may still write its checkpoint slot — a stale write landing after the slot enters the RadixCache would silently corrupt it. Setup: prompt of 500 tokens, checkpoint interval 64 (checkpoints at 448, 512, ..., 768, 832, ...); requests are forced (exact
max_new_tokens) to finish at chosen offsets around the boundary at 832, then we generate a 32-token continuation from the finished sequence twice — reusing the cache vs after flushing it — and compare. The "exhausted" rows force every spare-slot allocation to fail during the test, emulating a pool with no free slot at any boundary (worst case).cached_tokensof the continuation reveals which checkpoint the finished request left in the cache:The logprob diff is the per-token log-ratio between the two runs — numerical-noise level throughout. The same grid with MTP off also passes, all token-identical (28 cells in total).
Speed Tests and Profiling
What lazy buys is memory. RadixCache reuse benchmark: 48 independent conversations × 3 turns; on turns 2–3 we report the fraction of the prompt served from cache (
cached_tokens / prompt_tokens);max_running_requests=16for both strategies.With an unconstrained pool the strategies are identical. Under a constrained pool, lazy's 16 running requests pin 32 slots instead of 48, leaving 64 vs 48 slots for cached checkpoints — about 7–9× higher reuse in this setup. The middle column keeps lazy but restores the old evict-on-allocation-failure behavior: the evictions consume exactly the capacity lazy freed, collapsing reuse back to the extra_buffer level — which is why this PR removes the evict (a temporary slot is never worth a cached checkpoint). Accept length is unchanged in every configuration (2.36–2.43, including forced allocation failure): checkpoint tracking never touches the sampling path.
Checklist
CI States
Latest PR Test (Base): ✅ Run #29730013349
Latest PR Test (Extra): ✅ Run #29730013194