[Bugfix][Attention] Stabilize sparse-MLA DCP for GLM PCP evals - #55879
Conversation
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: khluu <khluu000@gmail.com>
|
merged main to pick up #55499, which I think was breaking the CI for this PR |
…-headroom Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
CUDA graph profiling now counts 6.15 GiB of capture memory and leaves the TP1/PCP4/DCP4 config with a negative KV cache budget at the implicit 0.92 utilization. Use 0.96, just above the runtime's suggested 0.9545, while remaining below the 0.9838 free-memory fraction observed on B200. Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
|
Expanded this draft after the exact merged-main lane exposed a second config-specific capacity failure.
The new commit was added after the prior approval, so please treat that approval as stale until the exact lane is terminal and the PCP4 line is re-reviewed. |
Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
…ontext-parallel Under DCP the sparse MLA decode query is all-gathered in the head dim and LSE is requested, so FlashInfer's trtllm-gen launcher carves a softmax-stats slab of sizeof(float2) * (heads/rank * dcp_size) * step_tokens * 256 + 1 MiB from the shared workspace. The static 394 MiB default has no DCP/head/batch awareness, so any step with more than ~3K tokens overflows the buffer and a single long-prompt request kills every DCP worker (vllm-project#50781; the reported 1,611,661,312-byte request is reproduced byte-exactly by this formula for GLM-5.2 at TP=8/DCP=8 with a 12,288-token step). Compute the requirement up front (default base + exact slab for max_num_batched_tokens) and pre-allocate in the metadata builder constructor, before warmup/capture: cudagraph support here is UNIFORM_BATCH and the buffer address is baked into captured graphs, so no lazy regrow is possible. Explicitly-set VLLM_FLASHINFER_WORKSPACE_BUFFER_SIZE values are respected verbatim, with a warning when below the computed requirement. Non-DCP configs allocate exactly as before. FIX vllm-project#50781 Signed-off-by: Jason Yao <wsyjh8@gmail.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
Cap each scheduler step at the configured 4,096-token model length so the DCP-aware FlashInfer workspace is 2.39 GiB instead of 16.39 GiB. This keeps the allocation within the B200 reserve while preserving the evaluation context-length contract. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
Current main has a separate SM120 sparse-MLA backend that did not exist at the source fix base. Preallocate the trtllm-gen softmax slab only in the SM100 TRTLLM metadata builder so SM120 keeps its own workspace behavior. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
A DCP rank can receive no local sequences while its captured LSE buffer still has padded rows. Treat every row as an empty shard before the generic sequence-index lookup. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
|
The prior #89050 run was launched against Head Replacement exact gate #89065 is running on the actual PR branch |
PCP-DCP prefill supplies already-localized row starts, so its runtime metadata kernel dispatches with a normalized DCP rank/world key. Include that key in startup warmup to prevent first-request JIT from desynchronizing the parallel workers. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
|
Actual-branch gate #89065 reached all three GLM configurations at head
Head Replacement actual-branch exact gate: Buildkite #89076, with PR #55879 metadata and only |
|
#89076 did not execute the B200 lane. Its Buildkite pre-commit proxy received a transient GitHub API HTTP 504 after five successful polls and exited 1; both actual GitHub Rebuilt the same actual branch/head and PR metadata as #89083, still selecting only |
PCP stores cumulative lengths in a packed multi-row allocation, so the runtime pointer may not have Triton's 16-byte alignment specialization. Cover both alignment classes alongside the normalized DCP key to prevent rank-local compilation at the first collective. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
|
Exact actual-branch gate #89083 at
New signed head Validation:
No duplicate PR was opened; this extends the existing canonical #55879 repair. A replacement actual-branch B200 PCP-only gate is next. |
FlashInfer sparse MLA does not gather the full cross-DCP KV context before rank-local PCP prefill. Its LSE combine therefore all-gathers unequal PCP query row counts and hangs. Reject this unsupported backend combination so automatic selection falls back to FlashMLA sparse, which implements the required gather. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
|
Root cause for the post-warmup DCP hang is now isolated and patched at Diagnostic #89089 reproduced the stall on the first PCP+DCP request with no late JIT. NCCL recorded rank 0 entering the LSE all-gather with count 9,792 while ranks 1-3 entered with 10,240—the exact
The new capability check rejects combined PCP+DCP for FlashInfer sparse MLA, so auto-selection falls back to FlashMLA sparse; explicit incompatible selection now gets an actionable error. Focused CPU coverage is 10/10, including PCP-only and DCP-only controls, and all applicable pre-commit hooks pass. Replacement exact actual-branch gate: https://buildkite.com/vllm/ci/builds/89093 (PR metadata present; only |
The sparse MLA decode path converted the static DCP rank into a device scalar on every metadata build, which violates the runtime GPU sync guard. Materialize it once during builder initialization and reuse it for decode sequence localization. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com>
|
#89093 confirmed that the backend-selection repair works: the DCP configuration chose It then exposed the next deterministic edge on the first decode: Head Replacement actual-branch exact gate: https://buildkite.com/vllm/ci/builds/89097 — complete PR metadata, literal head, and only |
|
Final exact validation is green: https://buildkite.com/vllm/ci/builds/89097 At literal head
The combined DCP case selected |
…project#55879) Signed-off-by: khluu <khluu000@gmail.com> Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Tyler Michael Smith <tlrmchlsmth@gmail.com> Co-authored-by: Jason Yao <wsyjh8@gmail.com>
Purpose
Make the B200 GLM-5.2 PCP evaluation complete reliably across its TP/PCP/DCP configurations.
Exact main build #89033 first exposed two independent memory-budget failures after merged backend repair #56677. Successive literal-head and actual-branch gates then exposed the workspace, empty-rank, warmup-key, packed-pointer, and backend-combination edges described below.
Root causes and fixes
TP2/PCP2 autotuning headroom
FlashInfer CUTLASS autotuning requests 12.22 GiB with only about 7.6 GiB free after the implicit 0.92 budget reserves 33.46 GiB for KV cache. Set
--gpu-memory-utilization 0.85to leave about 12.5 GiB more autotuning headroom.TP1/PCP4/DCP4 KV-cache budget
CUDA graph profiling accounts for 6.15 GiB and reports total non-KV memory of 161.16 GiB, leaving a negative 3.23 GiB KV budget at 0.92. The runtime calculates 0.9545 as the utilization needed to preserve the old effective KV budget. Set 0.96, which produces a positive KV-cache budget.
FlashInfer sparse-MLA DCP workspace
Under DCP, the TRTLLM-gen launcher requests LSE and carves a softmax-stats slab proportional to gathered heads and scheduled tokens. The old fixed workspace was 413,138,944 bytes; an earlier gate requested 470,810,624 bytes during warmup and failed before evaluation.
This PR carries Jason Yao's byte-derived sizing fix from #50791 with its original author commit, then adapts it to current main's split sparse-MLA backends: preallocation occurs only in
FlashInferMLASparseTRTLLMMetadataBuilder(SM100), so the separate SM120 backend keeps its own behavior. ExplicitVLLM_FLASHINFER_WORKSPACE_BUFFER_SIZEoverrides remain respected and warn if undersized; non-DCP allocation is unchanged.The GLM DCP eval now caps
--max-num-batched-tokensat its existing 4,096-token model length. With 64 heads per TP1 rank and DCP4, that bounds the computed workspace to exactly 2,561,671,168 bytes (2.39 GiB) rather than 16.39 GiB at the previous 32,768-token scheduler cap. This preserves the evaluation's context-length and accuracy contract while keeping the up-front buffer inside the measured B200 reserve.Zero-local-sequence DCP warmup rank
CUDA graph warmup can give one DCP rank no local sequences while its padded LSE buffer still has rows. The eager empty-shard mask previously clamped the derived sequence index to
-1and indexed an emptyseq_lenstensor. When the statically shaped sequence count is zero, the mask now fills every padded LSE row with-infand returns before the generic lookup. A focused CPU regression covers the exact shape.PCP+DCP first-request JIT keys
Actual-branch gate #89065 showed DCP4 clearing engine initialization and graph capture, then stalling its first real inference batch after an unexpected
BuildPrefillChunkMetadataKernelJIT. PCP chunk plans normalize this dispatch to(DCP_RANK=0, DCP_WORLD=1), while startup warmup previously compiled only the configured per-process(rank, world=4)key. Warmup now includes both key families.Actual-branch gate #89083 narrowed the remaining miss: the PCP plan stores
global_cuas the second row of one packed3 x (rows+1)allocation, so that input pointer is not always 16-byte aligned. Warmup had modeled alignment only foruncompressed_seq_lensand always replayedglobal_cuas aligned. It now covers the Cartesian product of both runtime alignment classes for both pointers, alongside the normalized DCP key.Unsupported FlashInfer sparse-MLA PCP+DCP combination
Actual-branch #89087 proved the preceding fixes: TP2/PCP2 and TP1/PCP4 passed; DCP4 initialized with 29.45 GiB of KV cache, completed graph capture with no late JIT, and then hung on its first real batch. Diagnostic #89089 reproduced the stall with NCCL tracing. The first PCP+DCP request had 153 query rows on rank 0 and 160 on ranks 1-3; the final DCP all-gather consequently entered with counts 9,792 versus 10,240 (
query_rows x 64 heads).The selected
FLASHINFER_MLA_SPARSEbackend filters top-k KV indices to the local DCP shard and returns LSE for every rank-local PCP query, but does not gather the full cross-DCP KV context first. The shared sparse-MLA wrapper then attempts to DCP-combine unequal PCP query-row tensors.FLASHMLA_SPARSEis the implementation that gathers each DCP KV shard before running rank-local PCP prefill queries and returns only the decode LSE that needs DCP combination.FlashInferMLASparseTRTLLMBackend.supports_combinationnow rejects combined PCP+DCP. Automatic selection therefore falls back toFLASHMLA_SPARSE; an explicit incompatible backend request receives an actionable error. CPU regressions cover both rejection of the combined mode and continued support for PCP-only and DCP-only modes.DCP decode rank scalar
Actual-branch gate #89093 confirmed the backend fix: DCP selected
FLASHMLA_SPARSE, completed graph capture and warmup, and began serving mixed prefill/decode traffic. The first decode then hit the runtime sync guard becauseget_dcp_local_seq_lensconstructed the static integer DCP rank as a new device tensor inside metadatabuild().The metadata builder now materializes that scalar once on its device during initialization and passes the tensor through the localization helper. The helper accepts and directly reuses a correctly typed, colocated scalar tensor; a CPU regression replaces
torch.tensorwith a hard failure during this path to prove that no per-build scalar construction remains.Existing work / duplicate check
ag_rsbackend repair is active.Validation
a4149f66f: both PCP configurations passed their corrected budget stages, then reproduced FlashInfer MLA decode workspace buffer overflow with decode-context-parallel #50781 at the fixed workspace boundary.main.2f9bdae78: both non-DCP configs passed; DCP4 cleared sizing, initialization, and graph warmup, then exposed the normalized JIT-key miss.2d93cf0efwas infrastructure-only: its Buildkite pre-commit proxy received a GitHub API 504, so no B200 job ran.2d93cf0ef: both non-DCP configs passed; DCP4 reached its first real request and isolated the remaining rank-0 unaligned-pointer JIT key.5d2d5c905: both non-DCP configs passed; DCP4 completed sizing, initialization, graph capture, and all warmups with no late JIT, then isolated the incompatible backend collective.5d2d5c905: NCCL tracing recorded the exact unequal DCP all-gather counts (9,792 vs 10,240) on the first PCP+DCP request.pytest -q tests/v1/attention/test_flashinfer_sparse_mla_workspace.py: 10 passed.pytest tests/v1/attention/test_indexer_deepseek_v4_slot_mapping.py -k indexer_warmup: 2 passed.server_argscontain exactly the intended memory-utilization and scheduler-cap values.git diff --check: passed.7b2f95468: both non-DCP configs passed; DCP4 selectedFLASHMLA_SPARSE, completed startup, served mixed prefill/decode traffic, and then exposed the runtime DCP-rank scalar construction in decode localization.pytest -q tests/v1/attention/test_indexer_dcp_localize.py -k get_dcp_local_seq_lens: 13 passed.05d607a8b: bootstrap, GitHub pre-commit, CUDA image, and the sole B200 PCP lane all passed without retry. TP2/PCP2 scored 0.9121, TP1/PCP4 scored 0.9454, and TP1/PCP4/DCP4 scored 0.8832; each completed all 1,319 questions with invalid rate 0.000. The exact job finished 3 passed in 17m54s.AI assistance was used for investigation, current-main adaptation, CI configuration, and validation. No human review is claimed.