Skip to content

feat: support native qk_rope_head_dim=0 sparse MLA decode in trtllm-gen - #4108

Merged
saltyminty merged 4 commits into
flashinfer-ai:mainfrom
JustinTong0323:feat/native-nope-trtllm-mla
Jul 30, 2026
Merged

saltyminty merged 4 commits into
flashinfer-ai:mainfrom
JustinTong0323:feat/native-nope-trtllm-mla

Conversation

@JustinTong0323

@JustinTong0323 JustinTong0323 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📌 Description

Adds a native TRTLLM-GEN sparse MLA decode path for the shape with no rotary tail: kv_lora_rank=512, qk_rope_head_dim=0.

For this shape the query carries no RoPE component, and both KV TMA descriptors address a single 512-wide cache pool, so the kernel needs the per-request active lengths to bound the sparse gather. Today trtllm_batch_decode_with_kv_cache_mla only accepts deepseek_mla_dimensions and smaller_mla_dimensions, so a qk_rope_head_dim=0 request is rejected as an unsupported MLA dimension.

This PR registers the new dimension set and threads an optional sparse_mla_top_k_lens tensor down to the launcher so the shape can be served natively.

Changes

  • csrc/trtllm_fmha_kernel_launcher.cu — add an optional sparse_mla_top_k_lens argument to trtllm_paged_attention_decode. When the single-pool dynamic sparse MLA shape is detected (sparse_mla_top_k_lens present and MLA decode), pass the key cache as the sliding-window KV pool so the kernel reads the active per-token lengths. The launcher already rejects combining block-sparse attention with sparse MLA (sparse_mla_top_k <= 0 check), so the two stay mutually exclusive.
  • flashinfer/mla/_core.py — register nope_mla_dimensions (kv_lora_rank=512, qk_rope_head_dim=0); require sparse_mla_top_k > 0 and a sparse_mla_top_k_lens tensor for this shape; thread the autotune profiling length through the decode tuning config so different top_k values key distinct autotune configs; expose sparse_mla_top_k_lens on the public trtllm_batch_decode_with_kv_cache_mla.
  • flashinfer/decode.py — forward the new optional argument at the two existing kernel call sites.
  • flashinfer/trace/templates/attention.py — declare the optional sparse_mla_top_k_lens input on the sparse MLA decode trace template so the trace schema matches the kernel signature.

The new argument is optional and defaults to None, so the deepseek_mla_dimensions / smaller_mla_dimensions decode paths are unchanged. sparse_mla_top_k_lens (one int32 active length per query token) is supplied by the caller.

🔍 Related Issues

None.

🚀 Pull Request Checklist

Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete.

✅ Pre-commit Checks

  • I have installed pre-commit by running pip install pre-commit (or used your preferred method).
  • I have installed the hooks with pre-commit install.
  • I have run the hooks manually with pre-commit run --all-files and fixed any reported issues.

If you are unsure about how to set up pre-commit, see the pre-commit documentation.

🧪 Tests

  • Tests have been added or updated as needed.
  • All tests are passing (unittest, etc.).

Static checks pass (clang-format / ruff / mypy via pre-commit). The new path has been exercised end-to-end in a downstream serving stack that computes sparse_mla_top_k_lens from the page table and drives this decode path. Happy to add a focused in-tree unit test for the qk_rope_head_dim=0 dimension registration + argument threading — see Reviewer Notes.

Reviewer Notes

  • Backward compatibility: sparse_mla_top_k_lens is optional and defaults to None; all existing callers and the two established MLA dimension sets keep their current behavior.
  • Mutual exclusion: block-sparse attention and sparse MLA are already mutually exclusive in the launcher (sparse_mla_top_k <= 0 check), so the new single-pool path cannot be entered together with block-sparse.
  • Autotune keying: the profiling length is threaded through the decode tuning config and into the cache key, so a dense request (len(inputs)==4) and a sparse request (len(inputs)==5) resolve to distinct autotune configs rather than mis-keying.
  • I can add a unit test covering the dimension registration and the optional-argument threading if you'd like it in-tree — let me know the preferred test shape.

Summary by CodeRabbit

  • New Features

    • Added support for native no-RoPE MLA decoding.
    • Added optional per-query sparse attention lengths for supported MLA decode workloads.
    • Added validation for sparse attention length tensor type, shape, device, and contiguity.
    • Integrated sparse MLA inputs with direct decoding and autotuning paths.
  • Bug Fixes

    • Improved handling of supported MLA head configurations during decode dispatch.

Add a native decode path for the sparse MLA shape with no rotary tail
(kv_lora_rank=512, qk_rope_head_dim=0). The query carries no RoPE
component and both KV TMA descriptors address a single 512-wide cache
pool, so the kernel needs the per-request active lengths to bound the
sparse gather.

- csrc/trtllm_fmha_kernel_launcher.cu: add an optional
  sparse_mla_top_k_lens argument to trtllm_paged_attention_decode; when
  the single-pool dynamic sparse MLA shape is detected, pass the key
  cache as the sliding-window KV pool so the kernel reads the active
  lengths. The launcher already rejects combining block-sparse
  attention with sparse MLA, so the two paths stay mutually exclusive.

- flashinfer/mla/_core.py: register nope_mla_dimensions
  (kv_lora_rank=512, qk_rope_head_dim=0), require sparse_mla_top_k > 0
  and sparse_mla_top_k_lens for this shape, thread the autotune
  profiling length through the decode tuning config so different top_k
  values key distinct configs, and expose sparse_mla_top_k_lens on
  trtllm_batch_decode_with_kv_cache_mla.

- flashinfer/decode.py: forward the new optional argument at the two
  existing kernel call sites.

- flashinfer/trace/templates/attention.py: declare the optional
  sparse_mla_top_k_lens input on the sparse MLA decode trace template.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4832daa3-e049-4319-801a-70b16dc2c95d

📥 Commits

Reviewing files that changed from the base of the PR and between b53f500 and e6e38ca.

📒 Files selected for processing (2)
  • flashinfer/decode.py
  • flashinfer/mla/_core.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • flashinfer/decode.py
  • flashinfer/mla/_core.py

📝 Walkthrough

Walkthrough

Adds optional per-query sparse MLA top-k lengths, supports native no-RoPE MLA dimensions, validates and autotunes the new tensor, and forwards it through Python dispatch into the TRTLLM paged-attention launcher.

Changes

Dynamic sparse MLA decode

Layer / File(s) Summary
Native MLA API and validation
flashinfer/mla/_core.py
Adds the native no-RoPE MLA shape, exposes and documents sparse_mla_top_k_lens, and validates its required dimensions, dtype, device, contiguity, and query-token length.
Runner tuning and dispatch
flashinfer/mla/_core.py, flashinfer/decode.py
Adds optional fifth-tensor autotuning support, threads sparse lengths through the runner and direct TRTLLM-GEN dispatch, and supplies None through existing compatibility calls.
Kernel decode plumbing
csrc/trtllm_fmha_kernel_launcher.cu
Extends the decode entrypoint, validates the native tensor view, and conditionally forwards sparse lengths with the key-cache sliding-window pool.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant trtllm_batch_decode_with_kv_cache_mla
  participant TrtllmGenMlaDecodeRunner
  participant trtllm_paged_attention_decode
  participant trtllm_paged_attention_launcher
  Caller->>trtllm_batch_decode_with_kv_cache_mla: pass sparse_mla_top_k_lens
  trtllm_batch_decode_with_kv_cache_mla->>TrtllmGenMlaDecodeRunner: provide validated tensor
  TrtllmGenMlaDecodeRunner->>trtllm_paged_attention_decode: forward optional fifth input
  trtllm_paged_attention_decode->>trtllm_paged_attention_launcher: forward lengths and conditional key-cache pool
Loading

Suggested reviewers: sricketts, dhiraj113, aleozlx, yongwww, yzh119

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: native sparse MLA decode support for qk_rope_head_dim=0 in trtllm-gen.
Description check ✅ Passed The description includes the required sections and enough implementation, tests, and reviewer notes detail, even though some checklist items are unchecked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
flashinfer/mla/_core.py (1)

2175-2224: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep flattened sparse lengths proportional to q_len.

The sweep sets sparse_mla_top_k_lens.shape[0] to the batch bucket, but the kernel requires batch_size * q_len. For q_len > 1, autotune profiles pass a too-short tensor and fail the [sumQ] validation. Generate this tensor with a constraint/initializer sized to the synthesized query’s first two dimensions instead of sweeping it as a peer batch axis.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flashinfer/mla/_core.py` around lines 2175 - 2224, Update the sparse-length
handling in the TuningConfig construction and init_sparse_top_k_lens so
sparse_mla_top_k_lens is not swept as an independent batch-axis tensor. Size its
flattened output from the synthesized query’s first two dimensions (batch_size *
q_len), using the appropriate constraint/initializer while retaining
sparse_top_k_width values and the existing non-sparse behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@csrc/trtllm_fmha_kernel_launcher.cu`:
- Around line 434-447: Update the sparse_mla_top_k_lens validation in the
optional tensor handling block to require top_k_lens.IsContiguous() before
extracting its raw pointer. Keep the existing dtype, shape, size, and device
checks unchanged so the FFI entrypoint rejects strided tensors before the kernel
reads them as contiguous.

In `@flashinfer/mla/_core.py`:
- Around line 2802-2807: Update the nearby sparse MLA documentation to
distinguish the no-RoPE TRTLLM-GEN configuration, stating that it uses
qk_rope_head_dim=0 while preserving the existing qk_rope_head_dim=64 description
for other sparse paths.

In `@flashinfer/trace/templates/attention.py`:
- Around line 2630-2632: In the sparse trace template definitions and
dispatcher, add a ragged sparse template keyed by num_query_tokens that declares
the cum_seq_lens_q layout: 3D flattened query and 2D compact block_tables.
Update dispatch so calls with cum_seq_lens_q select this template, while other
sparse calls continue using the existing template.

---

Outside diff comments:
In `@flashinfer/mla/_core.py`:
- Around line 2175-2224: Update the sparse-length handling in the TuningConfig
construction and init_sparse_top_k_lens so sparse_mla_top_k_lens is not swept as
an independent batch-axis tensor. Size its flattened output from the synthesized
query’s first two dimensions (batch_size * q_len), using the appropriate
constraint/initializer while retaining sparse_top_k_width values and the
existing non-sparse behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c836125-81fa-44a3-850c-1de820865c6d

📥 Commits

Reviewing files that changed from the base of the PR and between e4b8594 and d22279b.

📒 Files selected for processing (4)
  • csrc/trtllm_fmha_kernel_launcher.cu
  • flashinfer/decode.py
  • flashinfer/mla/_core.py
  • flashinfer/trace/templates/attention.py

Comment thread csrc/trtllm_fmha_kernel_launcher.cu
Comment thread flashinfer/mla/_core.py
Comment thread flashinfer/trace/templates/attention.py Outdated
Comment on lines +2630 to +2632
"num_query_tokens": Var(
description="Flattened number of query tokens with active top-k lengths."
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add a ragged sparse trace template.

The dispatcher selects this template for every sparse call, including cum_seq_lens_q calls. Those use a 3D flattened query and 2D compact block_tables, while this template declares 4D and 3D tensors. Add a ragged sparse template keyed by num_query_tokens and dispatch to it when cum_seq_lens_q is present.

Also applies to: 2666-2671

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flashinfer/trace/templates/attention.py` around lines 2630 - 2632, In the
sparse trace template definitions and dispatcher, add a ragged sparse template
keyed by num_query_tokens that declares the cum_seq_lens_q layout: 3D flattened
query and 2D compact block_tables. Update dispatch so calls with cum_seq_lens_q
select this template, while other sparse calls continue using the existing
template.

…head_dim=0 docs

- Add a contiguity check in the FFI entrypoint so a strided 1D tensor is
  not read as contiguous by the kernel (Python normalizes today, but the
  exported entrypoint must preserve the contract).
- Clarify that the native no-RoPE path uses qk_rope_head_dim=0 while other
  sparse MLA paths use 64.
@leejnau

leejnau commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

/bot run tests/attention

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1028 has been created, and the CI pipeline #59286008 is currently running. I'll report back once the pipeline job completes.

@leejnau leejnau added the run-ci label Jul 23, 2026
@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[SUCCESS] Pipeline #59286008: 18/18 executed test jobs passed

The sparse_mla_top_k_lens trace template addition declared a
num_query_tokens Var without a matching init kwarg, failing
tests/trace/test_template_init.py on H100. The trace/AOT schema is not
needed for the runtime decode path; defer trace support (including the
ragged cum_seq_lens_q layout) to a dedicated follow-up.
@leejnau

leejnau commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/bot run tests/attention

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1028 has been updated with latest changes, and the CI pipeline #59596789 is currently running. I'll report back once the pipeline job completes.

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[FAILED] Pipeline #59596789 — 16/18 executed test jobs passed

Compared with nightly #59545175.

Unit Tests

GPU CUDA 12.9 CUDA 13.0 Notes
5090 ⚠️ Infra ⚠️ Infra Infrastructure: CI infrastructure failure (2 jobs; CUDA 12.9, CUDA 13.0)
B300 ✅ Pass ✅ Pass
GB200 ✅ Pass ✅ Pass
GB300 ✅ Pass ✅ Pass
H100 ✅ Pass ✅ Pass
RTX Pro 6000 Blackwell ✅ Pass ✅ Pass

✅ Pass · 🟡 Old failure · ❌ New failure · ⏱ Test timeout · ⚠️ Infrastructure · ❔ Unknown or unclassified · — Not run

Multi-GPU and Multi-Node Tests — 6/6 passed

GPU CUDA 12.9 CUDA 13.0 Notes
B300 (multi-GPU) ✅ Pass ✅ Pass
GB200 (multi-node) ✅ Pass ✅ Pass
GB300 (multi-node) ✅ Pass ✅ Pass
Failure details

Timeouts, infrastructure, or incomplete jobs

Reconcile the native qk_rope_head_dim=0 sparse MLA decode path with
upstream's refactor (launcher now carries sparse_mla_top_k_lens; DCP
added to the CuTeDSL MLA path). The autotune fifth batch-swept tensor is
shared: sparse top-k lengths (trtllm-gen) and DCP causal bound
(cute-dsl) are mutually exclusive, routed to disjoint runners
(enable_dcp forces backend=cute-dsl).
@JustinTong0323
JustinTong0323 force-pushed the feat/native-nope-trtllm-mla branch from d77a325 to e6e38ca Compare July 29, 2026 08:45
@leejnau

leejnau commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

/bot run tests/attention

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1028 has been updated with latest changes, and the CI pipeline #60139273 is currently running. I'll report back once the pipeline job completes.

@saltyminty

Copy link
Copy Markdown
Collaborator

CI looks good

@saltyminty saltyminty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one comment but approved

Comment thread flashinfer/mla/_core.py
if backend == "auto":
cc = get_compute_capability(query.device)
if cc[0] == 12 and sparse_mla_top_k > 0:
backend = "sparse"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intended that we don't have a auto branch for nope?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, on SM100/SM103 the call intentionally stays on the generic auto path. Since the no-RoPE shape requires sparse MLA, CuTeDSL is filtered out and trtllm-gen is the only remaining runner, so a separate branch would duplicate the existing routing.

@saltyminty
saltyminty merged commit a02d94d into flashinfer-ai:main Jul 30, 2026
30 of 36 checks passed
JustinTong0323 added a commit to JustinTong0323/sglang that referenced this pull request Jul 31, 2026
…ackend

Consume flashinfer's new sparse_mla_top_k_lens argument so the DSA
trtllm-gen backend can serve the native no-rotary-tail MLA shape
(kv_lora_rank=512, qk_rope_head_dim=0).

- transform_index: add prepare_trtllm_nope_sparse_metadata, a Triton
  kernel that derives per-query active top-k lengths from the packed
  page table and points fully-empty CUDA-graph padding rows at a valid
  dummy token (the native H512 kernel produces NaNs for empty rows).
- dsa_backend: for the qk_rope_head_dim == 0 shape, build the active
  lengths and pass them to trtllm_batch_decode_with_kv_cache_mla.

Requires flashinfer with sparse_mla_top_k_lens support
(flashinfer-ai/flashinfer#4108).
bkryu pushed a commit that referenced this pull request Sep 8, 2026
…backends (#4947)

## Summary

`trtllm_batch_decode_with_kv_cache_mla` rejects the native NoPE form
(`kv_lora_rank=512`, `qk_rope_head_dim=0`) at API entry unless
`sparse_mla_top_k_lens` is provided. That requirement belongs to the
native no-rope trtllm-gen/cute-dsl kernels (#4108), which consume the
per-token active top-k length. The SM120 sparse backend
(`backend="sparse"`, the v32 / GLM53_NOPE families) bounds each row by
its `-1` page-table entries and never reads `sparse_mla_top_k_lens` — so
the entry-level check makes the SM120 GLM-5.3-Flash NoPE path
uncallable. (#4842 hit the same wall and dropped the check wholesale;
this PR keeps the guard where the consuming kernels are instead.)

Move the requirement past backend resolution and apply it only when a
non-`sparse` backend will run. The `sparse_mla_top_k_lens` shape/dtype
validation for callers that do pass it is unchanged, as is the SM100
native-NoPE contract.

## Testing

- vLLM `FLASHINFER_MLA_SPARSE_SM120` + GLM-5.3-Flash-NVFP4, TP4 on 4×RTX
PRO 6000 (SM120): previously raised `Native qk_rope_head_dim=0
TRTLLM-GEN MLA requires sparse_mla_top_k_lens` during CUDA graph
capture; with this change the server boots and serves (companion vLLM
PR: vllm-project/vllm#55277). GSM8K strict-match 0.9325.
- Existing trtllm-gen NoPE callers are unaffected: the requirement still
fires for `trtllm-gen` / `cute-dsl` / `xqa` / unresolved `auto` on
non-SM120 parts.

Signed-off-by: Zihua Wu <zihuaw@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved validation for sparse attention configurations with positive
top-k values and per-token top-k lengths.
* Updated backend-specific handling so SM120 uses per-token sequence
lengths and rejects unsupported sparse top-k length settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Zihua Wu <zihuaw@nvidia.com>
Co-authored-by: Kimi Code <noreply@moonshot.cn>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants