[Feature] TRITON_MLA_SPARSE backend for SM8x/11x/12x DSA Sparse MLA Support#38476
[Feature] TRITON_MLA_SPARSE backend for SM8x/11x/12x DSA Sparse MLA Support#38476haosdent wants to merge 1 commit into
Conversation
|
Documentation preview: https://vllm--38476.org.readthedocs.build/en/38476/ |
There was a problem hiding this comment.
Code Review
This pull request introduces the TRITON_MLA_SPARSE attention backend, providing a Triton-based fallback for sparse MLA on GPUs like NVIDIA Ampere. It also refactors FP8 MQA logit fallbacks into a new module and updates the sparse attention indexer to use these PyTorch implementations when DeepGEMM is unsupported. A review comment suggests moving a module-level import to the top of the file to comply with PEP 8 guidelines.
| Logits tensor of shape [B * next_n, max_model_len], dtype | ||
| `torch.float32`. | ||
| """ | ||
| from vllm.utils.math_utils import cdiv |
There was a problem hiding this comment.
To adhere to PEP 8 guidelines, module-level imports should be placed at the top of the file. Please move this import statement to the top of the module, for example, after the from vllm.platforms import current_platform import. This improves code readability and consistency.
References
- PEP 8: E402 module level import not at top of file. Imports should be at the top of the file, just after any module comments and docstrings, and before module globals and constants. (link)
FYI, we don’t plan to support a torch native mqa_logits implementation. I also question whether it’s necessary to support sparse MLA on SM80. |
Update: This PR is no longer a torch-native Ampere is still extremely commonly used... We need this for DS3.2 or GLM-5. |
Perhaps we can integrate Triton now? |
|
Hello, after I modified the code according to your PR, the GLM5 model service started normally. However, the response speed is very slow, with only about 3 tokens being responded to per second. My device is also an A800 with 80G of storage capacity. Is this normal? |
|
@workcode-del I believe that it's only expected to be anywhere remotely fast when no PyTorch fallbacks exist. |
Could you please explain how to achieve the condition where there are no PyTorch fallbacks? |
|
This pull request has merge conflicts that must be resolved before it can be |
|
@workcode-del |
|
I was able to use this patch to run GLM-5.1 on an 8-node DGX Spark cluster. Performance is obviously not stellar (~5 t/s) but it's a great first step with compatibility. |
17f68af to
6760f0c
Compare
|
Thanks @ianlevesque , your 8 x DGX Spark is incredible! I just add new triton kernels to try to address the performance issue, may you test again when you are available? |
|
@haosdent retried with the new patch, it did improve to 10 t/s or so. |
|
I am able to run it on 40 GB A100 GPUs. Using the latest
As for the steps I had to combine a bunch of scattered pieces to make it work on my system:
|
Do you run this with 16 A100s? |
|
It seems @haosdent hasn’t been active on this project for some time. Really looking forward to the GLM-5.2 and DeepSeek V4 support once the rebase is done. |
|
Any progress? |
|
An option is for somebody to create a new PR on top of this one (crediting the original author with If you can be responsive, we can get it merged, even. |
|
Following up on the suggestion above (@ehfd) — I've opened #47629, a rebase & takeover of this PR onto current main, with @haosdent's original commit and authorship preserved (thank you for the great work — the fix-38006-6 image has been serving GLM-5.1 on my box for weeks). What's in it beyond the rebase:
Validation on 8×A800-80G PCIe (SM80), GLM-5.2 NVFP4, TP=8, bf16 KV cache:
One note for anyone reproducing the earlier setups in this thread: the IndexShare patch from haosdent#7 is no longer needed — current main's I'll push a prebuilt image for immediate use and link it here shortly. Happy to iterate quickly on review feedback — the 8×A800 box is available for validation runs. |
|
@thomaslwang I greatly appreciate the work!!! However, a few questions. (1) I would like to see pipeline parallelism working with tool/reasoning parsers, because pipeline parallelism allows 1M context on 8x A100 (GLM-5.x is an MLA model; so KV cache is duplicated on all GPUs with tensor parallelism, which pipeline parallelism solves). Were you able to confirm this? (2) Note that SM12x now works through #43477 on fp8 (but not bfloat16) KV cache. This means that |
|
As promised — a prebuilt image for immediate use on SM80 while #47629 goes through review: It follows @RefalMachine's recipe from this thread: Verified on 8×A800-80G PCIe: TRITON_MLA_SPARSE selected, 202k-token KV cache at 131k max-model-len, tool calling OK, ~33 tok/s single-stream / ~107 tok/s at 4-way concurrency. For latest main, use #47629 instead ( |
|
@ehfd Tested PP as requested. Everything below is from 8×A800-80G PCIe (SM80), GLM-5.2 NVFP4, bf16 KV cache, branch #47629. (1) PP + tool/reasoning parsers: works. Single node, TP=2 × PP=4, However, I can reproduce the crash @Ph0enix89 reported, and it is neither tool-parser- nor multi-node-related: single-node TP=2×PP=4 with concurrent long prefills (~50–90k tokens) nondeterministically hits Capacity with bf16 KV (
The final blocker for a true 1M is not KV: it's the decode-path logits buffer ( Also addressed on the branch since your comment:
|
|
Root cause found for the PP illegal-memory-access crash (@Ph0enix89) — fix up at #47644. It's a pinned-buffer reuse race in the core model runner, not in the sparse-MLA kernels:
Validation on 8×A800 (TP=2×PP=4, GLM-5.2 NVFP4): the 9-concurrent-long-prefill stress that reliably crashed within ~2 minutes now passes 27/27 across 3 rounds with correct outputs. @RefalMachine your NaN → |
|
I think it's possible that #38476 (comment) is related to #41623 or #42426. Great! |
@thomaslwang Is this a large task? I don't think we need to use FP8 KV cache, but if this is not a big work, it could be integrated (since a separate PR can also take ages to get reviewed). |
|
@ehfd On the two issues:
On the decode logits buffer: agreed, no need for FP8 KV. Chunking is tractable — the global top-k is exactly recoverable from per-chunk top-k candidates (any token in the global top-2048 is in its chunk's top-2048), so the plan is: window the KV in ~128k-token chunks, run the paged logits kernel per window, per-chunk |
|
@ehfd Update on the 1M question — with a correction first, then good news. Correction: I earlier attributed the 1M shortfall to the indexer's decode logits buffer. That was wrong — at realistic decode concurrency that buffer is only megabytes ( The zero-code fix: give the last stage fewer layers with the existing Results:
So: 1M context on 8×A100/A800 with bf16 KV is real once #47629 + #47644 land — no FP8 KV cache and no new kernels required. I'll fold the partition recipe into the #47629 description. |
|
@thomaslwang Spectacular. Thank you! I didn't know that there was a trick like this. |
|
@thomaslwang Thank you for all the work. Not sure if we should continue here or under the new PR. I took both of your new branches. Initial results looked promising. Tooling worked, things seemed stable. However later weird behavior emerged. On one instance one session kept working fine while starting any new session against the same vllm instance produced gibberish. Then on the other instance I had a session that got stuck without producing any output. In the logs there is nothing out of the ordinary, no errors, occasional warnings. And it claims to be producing tokens but the agent doesn't get anything from it. My command matches yours for the most part. I have TP=4 and PP=16. Also what is the purpose of setting |
|
@Ph0enix89 Did you apply #47644 as well?
This is just for increasing KV cache capacity. |
|
@thomaslwang If there was a container which serves the specific #47629 + #47644, combination, I think more feedback can come in. |
Hi! I'm testing a model with PP=1 and TP=8 + --no-async-scheduling. Should this fix have any impact under these conditions? By the way, thanks for the ready-to-use Docker container, but unfortunately, I'm also having the same issue with !!! tokens. |
|
Following up on the IndexShare note above — that GLM-5.2 sm80 bring-up is now a multi-day production deployment: BF16, 32×A100, TP4×PP8, sustained ~130–155 tok/s decode at ~24 concurrent requests. So this backend has real production mileage on Ampere now, for the "is sm80 sparse-MLA worth supporting?" question — for us it's the only way to run GLM-5.2 on A100 at all. One pointer for the #47629 takeover: I've posted a production illegal-memory-access report on #47644 (the PP pinned-buffer race) — 13/381 serve instances, decode-time under concurrency, on this exact BF16 TP4×PP8 sm80 config — plus an offer to A/B the fix across our fleet. Since the end-to-end GLM-5.2 validation here is NVFP4/TP8, the BF16 + PP8 + sm80 path we run is complementary coverage; happy to be a test site for it. |
|
Great work on this PR! I've tested it on my 8×A800 80G PCIE cluster and share my runtime observations from the log screenshot attached:
No crash, hanging or OOM issues observed during long-running continuous serving test. Thank you for this valuable optimization for prefix caching and engine throughput! |
|
Thank you so much for this exciting work! I would like to know if this PR supports DeepSeek-V4-Flash on 8×A100. |
Not yet. We want stable support first on GLM-5.x and DeepSeek-V3.2 architectures. |
Purpose
Closes #38006. Enables sparse MLA models (GLM-5, DeepSeek-V3.2) on SM80 (A100/A800) and SM121 (GB10/DGX Spark), where DeepGEMM / FlashMLA-Sparse / FlashInfer-MLA-Sparse are unavailable.
Changes
is_deep_gemm_supported()(SM90+ check) replaceshas_deep_gemm()insparse_attn_indexer.py/indexer.py. Stops DeepGEMM kernels from being invoked on SM80/SM121.fp8_mqa_logitsfor the indexer.mqa_logits_triton.pyreproduces DeepGEMM's prefill + paged MQA logits. Prefill takes bf16 q/k (pre-decoded from FP8 in the Python wrapper) and feeds a straighttl.dot; paged decode keeps a 256-entry bf16 LUT for in-kernel FP8 decode. K-side scale applied to the fp32 dot output, per-row K-tile early-exit on the chunked-prefill path.TRITON_MLA_SPARSEbackend.triton_mla_sparse_kernel.pyadds a split-KV decode with N-way online-softmax merge plus a single-pass fast path. Autotune is warmed at init using indexer-derived(n_head, head_dim). Masked-out sentinel is-1e30to avoidNaNfrom(-inf) − (-inf)on all-masked tiles.TritonMLASparseMetadataBuilderadvertisesAttentionCGSupport.UNIFORM_BATCH; flips A100 TP=8 back toFULL_AND_PIECEWISE.mxfp4_experts_quant/silu_and_mul_mxfp4_experts_quantstubs innvfp4_quant_entry.cu. Real impls are SM10.x-only in CMake buttorch_bindings.cppreferences them unconditionally, which breaks source builds on SM 8.x.Benchmarks
8×A100 SXM TP=8,
lukealonso/GLM-5.1-NVFP4, single prompt, decode 200 tokens.cold= first request on a fresh prompt;warm= repeat (prefix cache hit):Tests
tests/kernels/attention/test_mqa_logits_triton.py— 41 cases (DeepGEMM reference + clean/dirtyclean_logits+ 256-byte FP8 decode).tests/kernels/attention/test_triton_mla_sparse_kernel.py— 53 cases (split vs single-pass + auto-heuristic + short-prefill no-NaN).Limitations (follow-up): BF16 KV cache only on SM80/SM121;
VLLM_BATCH_INVARIANTshould forcenum_kv_splits=1— not wired.