Repository navigation
[Attention] FlashMLA sparse: stage fp8 mixed-batch q/out in the workspace - #60220
NickLucche wants to merge 1 commit into
Conversation
…pace The fp8_ds_mla mixed-batch path runs the sparse decode kernel with s_q = all tokens of the step, so the head-padded query and the kernel output scale with max_num_batched_tokens (~4.25 GiB at 32K tokens with 8 heads/rank padded to 64). Both were allocated per call and are not seen by the profile run, which skips attention, so large steps could OOM at runtime. Stage them in the padded-q/padded-out buffers FlashMLASparseImpl already reserves in the workspace for max_num_batched_tokens, passing out= to the kernel. Results are bit-identical to the allocating path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
Only the bf16 prefill KV buffers scale with max_model_len, and only the separate prefill/decode path reads them. The padded query and output buffers sized by max_num_batched_tokens are reused by vllm-project#60220 to stage the mixed-batch decode kernel's q and out, which unpacks them from the end of workspace_specs; without them that path fails with "not enough values to unpack" at the first forward. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Kostryukov <mike@triptrack.net>
|
#59319 (ours, open) stops reserving the bf16 prefill workspace on the fp8 mixed-batch path. Its first revision skipped the whole block, including the three The current revision of #59319 skips only the
AI assistance was used for this comment. |
|
This pull request has merge conflicts that must be resolved before it can be |
…n the fp8 mixed-batch path FlashMLASparseImpl reserved the bf16 prefill KV workspace (factor * max_model_len * head_size * 2 bytes) for every quantized DS-MLA cache, but only the separate prefill/decode path reads it. The mixed-batch path taken below MIN_HEADS_FOR_BF16_PREFILL heads per rank paid for it out of the KV cache budget: 4.22 GiB per rank for GLM-5.3 at max_model_len 786432 with 16 heads per rank. Move the mixed-batch condition into a FlashMLASparseImpl static helper shared with the metadata builder, reserve the prefill KV buffers only when the separate path is selected, and assert in forward_mqa that the metadata and the impl agree on the path. The padded query and output buffers sized by max_num_batched_tokens stay on both paths: vllm-project#60220 stages the mixed-batch decode kernel's q and out in them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Kostryukov <mike@triptrack.net>
…n the fp8 mixed-batch path FlashMLASparseImpl reserved the bf16 prefill KV workspace (factor * max_model_len * head_size * 2 bytes) for every quantized DS-MLA cache, but only the separate prefill/decode path reads it. The mixed-batch path taken below MIN_HEADS_FOR_BF16_PREFILL heads per rank paid for it out of the KV cache budget: 4.22 GiB per rank for GLM-5.3 at max_model_len 786432 with 16 heads per rank. Move the mixed-batch condition into a FlashMLASparseImpl static helper shared with the metadata builder, reserve the prefill KV buffers only when the separate path is selected, and assert in forward_mqa that the metadata and the impl agree on the path. The padded query and output buffers sized by max_num_batched_tokens stay on both paths: vllm-project#60220 stages the mixed-batch decode kernel's q and out in them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Kostryukov <mike@triptrack.net>
…n the fp8 mixed-batch path FlashMLASparseImpl reserved the bf16 prefill KV workspace (factor * max_model_len * head_size * 2 bytes) for every quantized DS-MLA cache, but only the separate prefill/decode path reads it. The mixed-batch path taken below MIN_HEADS_FOR_BF16_PREFILL heads per rank paid for it out of the KV cache budget: 4.22 GiB per rank for GLM-5.3 at max_model_len 786432 with 16 heads per rank. Move the mixed-batch condition into a FlashMLASparseImpl static helper shared with the metadata builder, reserve the prefill KV buffers only when the separate path is selected, and assert in forward_mqa that the metadata and the impl agree on the path. The padded query and output buffers sized by max_num_batched_tokens stay on both paths: vllm-project#60220 stages the mixed-batch decode kernel's q and out in them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Kostryukov <mike@triptrack.net>
Overview
The FlashMLA sparse
fp8_ds_mlamixed-batch path now writes the head-padded query and the kernel output into the workspace buffers thatFlashMLASparseImplalready reserves formax_num_batched_tokens, so it no longer allocates them on every call. This removes about 4.25 GiB of per-call allocation at 32K tokens per step. That allocation isn't counted by the startup memory profile, so it can cause runtime OOMs.Related: #59305, #57878. Complements vllm-project/FlashMLA#29, which removes the kernel-internal fp32 split-KV
o_accumbuffer (the other 8 GiB at T=32K). This PR only covers the wrapper side.Claims
T x 64 x 576bf16) and the output (T x 64 x 512bf16) are written into the reserved workspace, and the output buffer is passed to the kernel viaout=. At T=32768 this saves 4.25 GiB per call, measured. The workspace was already reserved, so KV cache capacity does not change.max_num_batched_tokens=32768,gpu_memory_utilization=0.85) previously hit a runtime OOM inflash_mla_with_kvcacheduring warmup. With this change it serves the same workload without errors, and GSM8K accuracy matches.Validation
Per-call peak allocation. Single H100. GLM-5.3-Flash TP8 shapes: 8 heads/rank padded to 64, topk 2048,
s_q = T. Measured throughFlashMLASparseImpl._fp8_flash_mla_kernel:The remaining ~8 GiB at T=32768 is the kernel's
o_accum, which is addressed by vllm-project/FlashMLA#29.End-to-end, previously crashing config. GLM-5.3-Flash FP8, 8xH100,
--tensor-parallel-size 8 --max-num-seqs 256 --attention-backend FLASHMLA_SPARSE --kv-cache-dtype fp8_ds_mla --max-num-batched-tokens 32768 --gpu-memory-utilization 0.85 --max-model-len 131072.torch.OutOfMemoryError(tried to allocate 8 GiB with 5.29 GiB free), and 255 of 256 requests failed.nvidia-smimemory.used was 80923 / 81559 MiB. This is tight becauseo_accumis still allocated per call until FlashMLA#29 lands.vllm bench serveresults, random dataset,--ignore-eos. The comparison is against the existing MNBT=16384 config, which has a larger KV pool (2.66M vs 1.88M tokens), so this is a sanity check rather than an A/B:Accuracy. GSM8K, 5-shot, all 1319 questions, using
tests/evals/gsm8k/gsm8k_eval.pyagainst the server:Tests.
pytest tests/v1/attention/test_sparse_mla_backends.py -k test_flashmla_fp8_mixed_batch_stages_in_workspace(H100). 2 passed.pytest tests/v1/attention/test_sparse_mla_backends.py -k "test_sparse_backend_decode_correctness and FlashMLA and fp8_ds_mla"(H100). 84 passed, 84 skipped. These include the TP>1 cases that use the mixed-batch path, run with the workspace locked.Details
Root cause. For
fp8_ds_mlawith fewer thanMIN_HEADS_FOR_BF16_PREFILLheads per rank (fp8_use_mixed_batch), every step makes a singleflash_mla_with_kvcachecall withs_q = num_tokens. The wrapper pads q to 64 heads withnew_zeros, and the kernel allocatesout,lseando_accum, all O(T * 64). The profile run skips attention, so none of this is counted when the KV cache is sized. At largemax_num_batched_tokens, a full step therefore OOMs after startup.Fix.
__init__already reserves(max_tokens, padded_heads, 576)and(max_tokens, padded_heads, 512)bf16 workspace slots for the BF16 prefill path, which is not used in mixed-batch mode._fp8_flash_mla_kernel(use_workspace=True)takes flat views of those slots sized for the FP8 decode padding (the reserved head padding is at least as wide). It zeroes only the padding heads and passesout=to the kernel.v_upprojection or the DCP merge before any other workspace user runs. The separate prefill/decode path already returns workspace-backed output the same way.lseis still allocated by the kernel (T x 64 x 4 B = 8 MiB at 32K).Limitations. The kernel-internal
o_accum(2 x T x 64 x 512 x 4 B = 8 GiB at T=32K on SM90) is still allocated per call and isn't counted by the profile run. That is fixed in vllm-project/FlashMLA#29. This PR removes the wrapper-side share.Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.
🤖 Generated with Claude Code