Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Index permutations between the standard [h, d] layout and the layouts the FlashMLA fused sparse-attention kernel reads (Q, 16-element chunks interleaved across heads) and writes (O, 32-element chunks interleaved across the 8 heads of a wo_a group), plus in-place row/column permutation of MXFP8 wq_b / wo_a shards and their per-32 scales. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
FlashMLA's fused sparse kernels take int32 token positions; copy the batch positions once per step into a graph-stable buffer shared by all layers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
dsv4_fused_attention selects FlashMLA's fused Q-RoPE + sparse attention + inverse-RoPE + FP8-cast kernel (None: auto when available); the fused decode kernel has no split-KV, so dsv4_fused_decode_min_tokens routes small decode batches to the split-KV kernel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
… V4.1 kernels commit Point flashmla.cmake at deepseek-ai/FlashMLA@07a1089 ("Add kernels for DeepSeek v4.1 (#221)"): V4.1 fp8 / fp4 paged KV formats, native head128 sparse decode and the fused norm + RoPE + sparse attention + RoPE + FP8-cast kernel. Upstream is a pybind11 module, so _flashmla_C is built as a regular extension linked against torch_python (no stable-ABI flags), the vendored Python files import vllm._flashmla_C, and the SM90 dense FP8 decode extension is only built when the source tree has csrc/extension. The vllm-project fork's V3.2 nvfp4_ds_mla instantiation is not in upstream. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
flash_mla_fused_sparse_prefill / _decode call FlashMLA's fused Q-RoPE + sparse attention + inverse-RoPE + FP8-cast kernel (DeepSeek V4.1 settings: no Q norm, per-32 ue8m0 output scales in DeepGEMM's layout). The compiled entry points resolve on either the upstream pybind11 module or the fork's torch.ops namespace, the fork-only out= parameter of flash_mla_with_kvcache / flash_mla_sparse_fwd is emulated with a copy when the vendored interface lacks it, and the SM90 dense FP8 extension is no longer required for the sparse paths. Tests check the fused kernel against the split-KV pipeline at the lse and at the wo_a einsum output, including sliced groups (TP > 1). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Pads the local-head Q that the permuted wq_b produces to the fused kernel's head count, either keeping the fused chunk layout or un-permuting to the standard layout with GPT-J RoPE for the split-KV fallback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
permuted_output stores chunk (h, c) of each wo_a group at chunk position c * G + h, values and packed ue8m0 scale bytes alike, so the split-KV fallback feeds the same permuted wo_a as the fused kernel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
forward() now asks the platform subclass for the buffer the eager attention region writes (_alloc_attn_out), how Q is prepared and KV inserted (_prepare_q_and_insert_kv) and how the buffer is projected afterwards (_finish_o_proj); the defaults reproduce the existing behavior. Adds the per-layer finalize_loaded_weights post-load hook, run before quant-method packing so subclasses can permute raw MXFP8 shards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
DeepseekV4FlashMLAFusedAttention runs Q RoPE, sparse attention, inverse RoPE and the FP8 cast in FlashMLA's fused kernel and feeds the wo_a einsum directly, writing the post-wo_a activation from the eager region. wq_b rows and wo_a columns are permuted once at load (finalize_loaded_weights, keyed on the parameters each load touched) so the MXFP8 GEMMs speak the kernel's layouts; the SWA cache insert reuses rope_quant_insert. Decode steps below dsv4_fused_decode_min_tokens fall back to the split-KV kernel with the permuted-output O quant. Selected by attention_config.dsv4_fused_attention (auto when the kernel is available and o_groups gives 8 heads per group). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
…peline Decode (topk 128 SWA + 512 compressed, h_q 64) and prefill (topk 640), eager and under CUDA graphs. On GB200 the fused decode kernel matches the split-KV attention kernel at s_q = 1 and halves the segment once the Q-RoPE and O-quant kernels are counted; prefill is 4-13x faster. Results recorded in the spec. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
…4 compressed) --kv-cache-dtype nvfp4_ds_mla stores the sliding-window cache as FlashMLA's V4.1 fp8 rows (528 B/token: all 512 dims e4m3, 16 ue8m0 scales per 32) and the compressed cache as V4.1 fp4 rows (288 B/token: e2m1 pairs with 32 e4m3 scales per 16). A layout table (DSv4KVLayout) drives both cache specs; rope_quant_insert and dequantize_and_gather_k_cache dispatch on bytes per token to new Triton kernels (exact ceil-log2 ue8m0 via the fp32 bit pattern, IEEE division before the e2m1 conversion so exact ties round like the reference). Requires the fused attention layer (SM100); the DSpark context insert now goes through rope_quant_insert for every packed layout. Tests cover insert/gather against torch ports of FlashMLA's reference quantizer and fused decode over V4.1 fp8 + fp4 caches against the split-KV kernel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
DeepseekV41ForConditionalGeneration was missing from the auto-enable set (the V4.0 wrapper is listed), so the LLM-class path hit the piecewise CUDA-graph error that vllm serve avoids. Also ignore the vendored FlashMLA fused-kernel Python file generated by cmake. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
…megakernel work Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
…ames AutoWeightsLoader hands a child module's load_weights names relative to that child, so under the DeepseekV41ForConditionalGeneration wrapper the loaded names (model.layers.N...) never matched the attention prefix (language_model.model.layers.N.attn) and wq_b / wo_a were left unpermuted while the fused kernel read Q as permuted, producing garbage. Match on the layers.<id>.attn tail instead, record that each permutation ran, and refuse to run forward_mqa on a layer whose weights were not permuted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
…nt fixes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…_indices The prefill paths pass a reused workspace through `out=`, so slots past `combined_lens` kept stale indices. `flash_mla_sparse_fwd` caps reads at `topk_length`, but the FlashMLA fused prefill reads the whole row and propagates NaN from any stale index that names a NaN row of the gathered workspace. With the fused layer this turned 72 % of gsm8k generations into 4096 tokens of NaN-argmax output (0.229 vs 0.889 unfused). The kernel now fills the tail of every row with -1, matching the `out=None` path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
… the spec Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r cache test DeepseekV4Attention._prepare_and_attn now reaches the SWA insert through _prepare_q_and_insert_kv (platform subclasses override it); the stubbed attention layer in test_v41_attention_joins_cache_writes_before_consumption must provide that name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed layer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n items Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove the kernel/config test files, the torch reference quantizer, the integration spec and plan, and the inverse-permutation helpers that only the tests used. The benchmark and the layer keep the remaining helpers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ynced) The fork now carries deepseek-ai/FlashMLA#221 (commit 07a1089) behind its ABI-stable torch.ops._flashmla_C registration, so the pybind11 workarounds go away: no torch_python link, USE_SABI 3 and TORCH_TARGET_VERSION restored, _flashmla_extension_C built unconditionally, the vendored Python interfaces only need the torch.ops shim, and the V3.2 nvfp4 decode instantiation is back. The op wrapper drops the pybind/torch.ops dual lookup and the out= emulation; the fused prefill/decode wrappers call torch.ops._flashmla_C directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
Documentation preview: https://vllm--56344.org.readthedocs.build/en/56344/ |
Stable ABI dispatch should not be so expensive: #26946 (comment). And if it is 50us per call, then 50.1us does not make sense. Any misunderstanding here? |
Yea I was originally use the upstream flashmla branch for initial integration and later switch to our branch after that's done. So will update the number here. |
|
Hi @zyongye nice work! Can you also add mega kernel support to ROCm path? cc @ChuanLi1101 |
| """With ``dsv4_fused_attention``, decode steps with fewer query tokens than | ||
| this use the split-KV FlashMLA decode kernel instead (the fused kernel runs | ||
| one query token per CTA and has no split-KV). 0 always uses the fused | ||
| kernel.""" |
There was a problem hiding this comment.
should we rethink how we do model specific attention configs? this feels like it could get out of hand very fast
Purpose
Integrate FlashMLA PR #221's fused norm + RoPE + sparse-attention + inverse-RoPE + FP8-cast kernels into DeepSeek V4.1 attention and add the
nvfp4_ds_mlaKV cache (V4.1 fp8 528 B SWA + V4.1 fp4 288 B compressed)..flashmla.cmakepinsvllm-project/FlashMLA@c112cc1(the fork's sync of Add kernels for DeepSeek v4.1 deepseek-ai/FlashMLA#221 behind the stabletorch.ops._flashmla_CABI): same build structure as before, source list mirroring the fork'ssetup.py, both Python interface files vendored,include/cccladded for CUDA 13 host compiles.vllm/v1/attention/ops/flashmla.pyadds thin wrappers for the fused prefill/decode ops.DeepseekV4FlashMLAFusedAttention(vllm/models/deepseek_v4_1/nvidia/flashmla_fused.py), selected by--attention-config '{"dsv4_fused_attention": true}'. It permuteswq_brows andwo_acolumns at load into the kernel's chunk-interleaved layouts, feeds the kernel's FP8 output straight into the DeepGEMMwo_aeinsum, and keeps the split-KV path as a fallback belowdsv4_fused_decode_min_tokens. The base layer gains_alloc_attn_out/_prepare_q_and_insert_kv/_finish_o_projhooks so platform subclasses can change the eager-region output shape. Every decode token carries its own metadata row (positions, SWA indices, compressed-cache indices), so the kernel runs on the flattened token batch at any concurrency.nvfp4_ds_mlaKV layout table,SlidingWindowMLASpecplumbing, Triton insert/gather kernels for both V4.1 formats, SM100 gating, DSpark context insert.combine_topk_swa_indicesonly wrote the valid prefix of each row and relied on the caller's-1pre-fill, but both prefill paths reuse anout=workspace.flash_mla_sparse_fwdcaps reads attopk_length; the upstream fused prefill reads the whole row and propagates NaN from stale indices into uninitialized gathered rows, which turned 72 % of gsm8k generations into 4096 tokens of NaN-argmax output. The kernel now writes the sentinels itself.DeepseekV41ForConditionalGenerationjoins the breakable-CUDA-graph default list;positions_int32added to the sparse SWA metadata.Performance
All numbers: GB200, TP4, DeepSeek V4.1 (
ckpt20260903),fp8_ds_mlaunless stated, same branch for fused and unfused.Kernel level (
benchmarks/kernels/benchmark_dsv41_fused_attention.py, h_q = 64, topk 128 SWA + 512 compressed, µs per call). The fused kernel replaces the split-KV attention kernel plus the separate inverse-RoPE + FP8 quant kernels; the unfused columns time the attention kernel alone and the whole chain.Eager numbers include ~50 µs of stable-ABI boxed dispatch per op call (both fused and the fork's
sparse_decode_fwdpay it); under CUDA graphs the kernels alone are 22.7 µs at s_q = 1.Serving, bs1 (
vllm bench serve, 20 random prompts, concurrency 1, no speculative decoding,--max-model-len 40960), TPOT mean / p50 ms:At bs1 the fused layer is ~4 % slower per token even though its kernel is faster: the attention runs in the eager region of the breakable CUDA graph, and the fused path adds the Q layout kernel, the op call with fresh output allocations and the DeepGEMM
wo_aeinsum to that region, whereas the base layer's o-projection is inside the captured graph. Follow-up: persistentout_fp8/out_sfbuffers and the einsum in_finish_o_proj.dsv4_fused_attentiontherefore defaults to off.Serving, high concurrency (gsm8k, 1319 five-shot prompts sent at concurrency 1024, ~1000 decode tokens per step, 4096-token cap): unfused 67 s, fused 73 s, fused +
nvfp4_ds_mla58 s wall time for the whole eval.KV capacity:
nvfp4_ds_mlaraises the KV token budget at 8k context from 2.95 M to 4.08 M tokens (1.38x).Accuracy
gsm8k, 1319 problems, 5-shot, greedy:
dsv4_fused_attention: false)fp8_ds_mlanvfp4_ds_mlaBefore the sentinel fix the fused run scored 0.229 (949 / 1319 generations hit the 4096-token cap with empty text). The long-context accuracy of the fp4 compressed path (gpqa, 32k task) is not yet measured.
Test Plan
124 existing SWA / FlashMLA / compressor / DSpark tests pass on the new FlashMLA build. Development-time kernel tests (layout permutations, Q padding kernel, V4.1 fp8/fp4 insert + gather against a torch port of FlashMLA's reference quantizer, fused vs split-KV decode/prefill equivalence, combined-index sentinels) were run on GB200 and passed but are not part of this PR.
AI assistance: this PR was developed with Claude Code (Claude Fable 5.1); every change was reviewed and the tests and evals above were run by the submitter.
🤖 Generated with Claude Code