[Bugfix] PP + MTP + prefix caching corrupted mamba recurrent state (Qwen3.8 "duct" loops) - #63
Open
Karl0007 wants to merge 1 commit into
Open
[Bugfix] PP + MTP + prefix caching corrupted mamba recurrent state (Qwen3.8 "duct" loops)#63Karl0007 wants to merge 1 commit into
Karl0007 wants to merge 1 commit into
Conversation
…ops) With pipeline parallelism + MTP + prefix caching (the MODE=pp Qwen3.8-Flash-Next recipe enabled by wtdcode#47), 14-33% of requests degenerated into a constant-token loop ("duct", token 1023 = what the sampler emits for an all-NaN logits row). MambaSpecDecodeGPUContext captures the block tables' raw data_ptrs once, and the align copy kernels resolved the table row by batch row, while the V2 runner bound that capture to the per-step gathered input_block_tables (batch-ordered, re-gathered every step). Under async PP a non-last rank runs postprocess pp_size steps late, so it walked the current tables with a stale batch mapping and copied GDN/PLE state through other requests' freed/reallocated block ids -- which in the CSA unified layout alias every cache tensor in the same page. Bind the context to the source per-request-slot tables and index rows by req_idx (the contract V1 already relies on). Also port vllm#53142 (align state-seed divisor must be the mamba group's block size) and vllm#48375 (MambaManager ignored drop_eagle_block, so an MTP resume landed on state snapshotted over rejected draft tokens). Tests: permute idx_mapping AND vary the per-slot copy decisions; the existing suite used an identity mapping, where batch row == request slot, so it could not see the bug. Fails on the old indexing (~10.6% of destination elements corrupted), passes here. Measured on 4x CMP 170HX: 16/48 loops -> 0/56, acceptance length 2.80 -> 4.90, 294K/509K needle both exact. No perf cost: same kernels, same bytes copied. Upstream: vllm#55506 (core + tests), vllm#55507 (divisor). Credit: root cause found first by @zebgop-ops, https://github.com/zebgop-ops/qwen38-flashnext-pp (FINDINGS.md 11-19, patch 0010).
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.
PP + MTP + prefix caching corrupted mamba recurrent state (Qwen3.8-Flash-Next "duct" loops)
Fixes the silent state corruption that made 14–33 % of requests degenerate into a constant-token loop whenever pipeline parallelism, MTP and prefix caching were combined — i.e. exactly the
MODE=ppQwen3.8-Flash-Next recipe this repo enabled in #47. Reproduced here at PP413,13,13,9+ MTP4 +--enable-prefix-caching --mamba-cache-mode align+FULL_AND_PIECEWISE,max_num_seqs 8.Root cause
MambaSpecDecodeGPUContextcaptures the block tables' rawdata_ptronce (idempotentinitialize_from_forward_context), and the align copy kernels resolved the table row asbatch_idx if HAS_IDX_MAPPING else req_idx— while the V2 runner bound that capture to the per-step gatheredinput_block_tables, which are batch-ordered and re-gathered every step.Under async PP, a non-last rank runs postprocess
pp_sizesteps late, so its batch mapping is stale: the kernel walks the current tables with a stale row mapping and copies GDN/PLE state through block ids belonging to (or reallocated to) other requests. The CSA unified layout aliases main KV, GDN conv/SSM and PLE conv inside one page, distinguished only by block-id ownership — so foreign bytes land in live state. Symptomatically that is all-NaN logits; the sampler turns an all-NaN row into token1023=duct, forever. On sm_121 the same misdirected read faults outright (vllm#54173 / #54199).Changes
vllm/v1/worker/mamba_utils.pyreq_idx; comment corrected to state the real invariantvllm/v1/worker/gpu/model_runner.pyBlockTables.block_tables[i].gpu), not the gathered viewsvllm/v1/worker/gpu/model_states/mamba_hybrid.pyvllm/v1/core/single_type_kv_cache_manager.pyMambaManager.find_longest_cache_hitaccepteddrop_eagle_blockand ignored it, so an MTP prefix-cache resume landed on a page whose recurrent-state snapshot was taken over draft tokens verification had rejectedtests/kernels/mamba/test_precopy_mamba_align.pyidx_mappingplus per-slot-differing decisions. The existing suite only ever usedidx_mapping = arange(n), where batch row == request slot, so it structurally could not see this bugMeasured (4× CMP 170HX, sm_80)
bench/qwen_longctx_needle.py)No perf cost by construction: same kernels, same launch counts, same copied bytes — only the row index changed.
Do not "fix" this with a table pool
A rotating pool of gathered-table sets is not a substitute: with full CUDA graphs a captured graph bakes whichever slot's pointer was current at capture, and the other replays run against stale tables — the same corruption by another road. With this fix the ctx never touches gathered tables, so there is nothing to rotate.
Upstream
Filed against
vllm-project/vllmas vllm#55506 (core fix + tests) and vllm#55507 (#53142divisor).#48375is left to its own open PR; this branch carries the port so production is correct today.Credit: root cause and fix direction identified first by @zebgop-ops — forensics at https://github.com/zebgop-ops/qwen38-flashnext-pp (
FINDINGS.mdaddenda 11–19, patches0010/0011/0012; their ablation established that req-indexed source tables are necessary and sufficient).