Repository navigation
Conversation
|
This pull request has merge conflicts that must be resolved before it can be |
|
@stu-cao Merge conflicts |
0e2b281 to
40287a4
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
40287a4 to
75356ca
Compare
e7f148e to
759ec32
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
…8 staging FLASHINFER_MLA_SPARSE now accepts the nvfp4_ds_mla KV-cache dtype. The TRTLLM-gen sparse MLA kernel reads only FP8 rows, so each layer stages the rows it needs as FP8 (dequantized value / k_scale) and runs the unchanged FP8 kernel over them: decode and spec-decode tokens gather their top-k rows, and prefill tokens share one context gather per request and chunk. The staging workspace is reserved up front so the memory profile accounts for it. The prefill gather is planned on the host for every batch with prefills, so the backend reports UNIFORM_BATCH CUDA-graph support for this dtype. The dtype needs kv_lora_rank 512 and qk_rope_head_dim 64, and is rejected with context parallelism and with HiSparse. Co-authored-by: Chi McIsaac <chi@primeintellect.ai> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Stu Cao <stu@primeintellect.ai>
…ernel FLASHINFER_MLA_SPARSE now decodes nvfp4_ds_mla with flashinfer.mla.nvfp4_sparse_mla_decode when FlashInfer provides it, the layer has 16 heads per rank, the top-k width is 512, 1024 or 2048, and the GPU is SM100 or SM103. The kernel reads the cache rows as stored, so decode skips the FP8 staging copy and its workspace; prefill is still staged. Both fp8 bmm scales are divided by k_scale, because the staged rows they were built for hold dequant / k_scale while the native kernel reads exact values. Everything else keeps the staged path. The new test runs both decode paths over a per-layer cache whose blocks alternate with another layer's, with k_scale 1 and 0.5, and checks them against an exact FP32 reference. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Stu Cao <stu@primeintellect.ai>
…SPARSE test_sparse_backend_decode_correctness already parametrizes nvfp4_ds_mla, and skipped it for the FlashInfer backend until that backend accepted the dtype. Its mock layer quantized the query to fp8 only for fp8 caches, while MLAAttention does so for every quantized cache, so the backend received a bf16 query and raised. Quantize for every quantized cache, as the layer does, and give the FlashInfer nvfp4_ds_mla cases the fp8 tolerance: the backend reads them with an fp8 query and staged fp8 rows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Stu Cao <stu@primeintellect.ai>
Keep the tested CUDA backend explicit now that FlashInfer also offers cake. Preserve mainline CUDA-graph metadata behavior and remove stale staging-only descriptions. Assisted-by: OpenAI Codex Signed-off-by: Stu Cao <stu@primeintellect.ai>
…ing the native kernel flashinfer-python 0.7.2 (now vLLM's pin) exposes flashinfer.mla.nvfp4_sparse_mla_decode but its wheel does not ship the kernel's csrc/ directory (flashinfer-ai/flashinfer#6454), so the entry point imports and the first call fails in the JIT's source digest with FileNotFoundError. has_flashinfer_nvfp4_sparse_mla_decode() now also requires nvfp4_sparse_mla_decode.cu to be present in the installed package, logs once when it is not, and the backend keeps the FP8-staged path; the native-decode tests skip through the same probe. Signed-off-by: Stu Cao <stu@primeintellect.ai>
759ec32 to
02f5bbf
Compare
|
GSM8K rerun on the rebased head
The NVFP4 server ran the native decode kernel (it logged "FLASHINFER_MLA_SPARSE decodes nvfp4_ds_mla with FlashInfer's native NVFP4 kernel; prefill is staged to FP8"), with FlashInfer |
## 📌 Description `include-package-data = false` means a feature's `csrc/` only reaches a wheel when it is listed under `[tool.setuptools.package-data]`. The entry for `flashinfer.experimental.nvfp4_sparse_mla_decode` was missing, so a non-editable install (wheel, or `pip install git+...@main`) ships the Python but not `csrc/nvfp4_sparse_mla_decode.cu`; the first call then fails in `gen_nvfp4_sparse_mla_decode_module` → `_source_digest` with `FileNotFoundError` instead of compiling. Editable installs, which is how the feature was developed and tested, are unaffected. This adds the same `"csrc/**"` entry every neighbouring experimental feature has; it also covers the `csrc/cake_nvfp4_sparse_mla_decode/` sources of the `cake` backend. Verified by building wheels from `main` (53e3168) on the same machine: without this line the wheel holds 5 files for the feature (all `.py`); with it, 22, including `csrc/nvfp4_sparse_mla_decode.cu`, `.cuh`, `_jit_binding.cu` and the 14 Cake `*_kernel.cu` / `*_binding.cu` files. Found while running vLLM's `nvfp4_ds_mla` decode tests (vllm-project/vllm#59342) against `pip install git+...flashinfer@main`: all 10 native-decode cases failed with the `FileNotFoundError` above and pass again with an editable install of the same commit. ## 🔍 Related Issues Tracking: #5716 (DSA NVFP4 sparse MLA decode); kernel landed in #5717 / #5970. ## 🚀 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 - [x] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [x] I have installed the hooks with `pre-commit install`. - [x] 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](https://pre-commit.com/). ## 🧪 Tests - [ ] Tests have been added or updated as needed. - [x] All tests are passing (`unittest`, etc.). No test change: packaging only. `tests/experimental/test_nvfp4_sparse_mla_decode.py` exercises the kernel; the missing-file failure only reproduces from a built wheel, which the wheel listing above covers. ## 🔬 Experimental Track - [x] This PR is **experimental**: it adds or changes code under `flashinfer/experimental/` and/or an `@flashinfer_experimental_api`. Tracking issue: #5716 - [x] The tracking issue names an owner, the reason for the experimental path, and a graduation plan with a target release. - [x] Core changes are limited to a thin entry point (signature, shared validation, feature-gate check, backend selection, handoff). - [x] Tests live in `tests/experimental/` and were validated on the intended hardware; a runnable example is included. - [x] Nothing is registered in `flashinfer/aot.py`, and no experimental backend is reachable from `backend="auto"` without `FLASHINFER_ALLOW_EXPERIMENTAL_AUTO_BACKENDS=1`. (Calling an `@flashinfer_experimental_api` or naming a backend explicitly is itself the opt-in and needs no environment variable.) - [x] **Test scope declared below.** The experimental CI lane runs exactly these targets, so keep them as narrow as the change allows. ```experimental-tests tests/experimental/test_nvfp4_sparse_mla_decode.py ``` ## Reviewer Notes One line in `pyproject.toml`. AI assistance (Claude) was used to track the failure down and draft this; I reviewed the change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Improvements** - Package distributions now include the source assets needed by the NVFP4 sparse MLA decode component. These assets are available when the package is installed, improving the completeness of installed distributions. This packaging update does not change the component’s behavior or public interface. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Stu Cao <stu@primeintellect.ai>
Status (2026-10-16)
mainpinsflashinfer-python==0.7.2since [Dependency] Upgrade FlashInfer to 0.7.2 and restore KDA auto backend #61913. This PR keeps the testedcudakernel (backend="cuda", FlashInfer's default).flashinfer/experimental/nvfp4_sparse_mla_decode/csrc/*is missing from the package-data allowlist), sonvfp4_sparse_mla_decodeimports yet cannot compile. Fix: fix(nvfp4_sparse_mla_decode): ship the JIT sources in wheels flashinfer-ai/flashinfer#6454. Until a release carries it,has_flashinfer_nvfp4_sparse_mla_decode()(fifth commit) also requires the source file to be present; on a wheel without it the backend logs once and uses the FP8-staged path, so the dtype works everywhere and the native kernel switches on by itself once FlashInfer ships the source.mainatfb3fec970e(head02f5bbf1ad, 5 commits). [Attention] Enable FA4 sparse MQA decode on Blackwell #55866 moved the generic sparse-MQA path intoSparseMLACommonImpl.forward_mqa; this backend'sforward_mqais now a thin override that defers to the base for non-NVFP4 caches, and the NVFP4 staged-row kernel calls go through the new_forward_mqa_kernel(layer=, block_size=FP8_STAGING_PAGE_SIZE, is_decode=)entry. No NVFP4 method logic changed.test_flashmla_nope_sm90_backend_selection.py+test_flashinfer_mla_sparse_nvfp4_native_decode.py+test_sparse_mla_backends.py -k 'nvfp4 or sparse or FLASHINFER or sm100':flashinfer-python==0.7.2wheel: 639 passed, 0 failed; the 10 native-decode cases skip ("needs SM100/SM103 and FlashInfer's native NVFP4 sparse MLA decode") because the probe sees no JIT source — the staged path serves every case;maincheckout (source present): 649 passed, 0 failed, native-decode 10/10.nvfp4_ds_mla90.5 % vsfp890.3 %, 0 invalid responses in both, same flags as the table below (the server logged "decodes nvfp4_ds_mla with FlashInfer's native NVFP4 kernel"). KV cache at--gpu-memory-utilization 0.9, TP4: 4,512,384 vs 2,861,440 tokens (1.58x). The end-to-endcudavscakecomparison below is unchanged.Purpose
FLASHINFER_MLA_SPARSE(the TRTLLM-gen sparse MLA backend on SM10x) now accepts--kv-cache-dtype nvfp4_ds_mla, which onlyFLASHMLA_SPARSEaccepted. The NVFP4 cache holds 1.6x the tokens of an FP8 one (352 vs 576 bytes per token).Four commits:
Co-authored-byin the commit). The TRTLLM-gen kernel reads FP8 rows only, so each layer stages the rows it attends to as FP8 (dequant / k_scale): decode and spec-decode tokens gather their top-k rows, prefill tokens share one context gather per request and chunk (Triton kernels innvfp4_ds_mla_fp8_gather.py). The staging workspace is reserved up front so the memory profile accounts for it. The dtype needskv_lora_rank512 andqk_rope_head_dim64 and is rejected with context parallelism and with HiSparse. The rebase preserves the current TRTLLM builder'sUNIFORM_BATCHCUDA-graph support and varlen decode limit; host-planned staged prefill does not widen graph capture. The impl and builder retain class-level defaults for tests that build them withobject.__new__.flashinfer.mla.nvfp4_sparse_mla_decode(experimental; the original feat(nvfp4_sparse_mla_decode): add experimental NVFP4 sparse MLA decode on SM100/SM103 flashinfer-ai/flashinfer#5717 implementation is now included in merged feat(cake_backend): generated backend="cake" for nvfp4_sparse_mla_decode on SM100 / SM103 flashinfer-ai/flashinfer#5970), the layer has 16 heads per rank, the top-k width is 512, 1024 or 2048 and the GPU is SM100 or SM103, decode reads the cache rows as stored: no staging copy, no decode staging workspace, and no e4m3 re-rounding of the dequantized values. Both fp8 bmm scales are divided byk_scale, because the staged rows they were built for holddequant / k_scalewhile the native kernel reads exact values. Everything else keeps the staged path. The runtime capability probe (has_flashinfer_nvfp4_sparse_mla_decode()) keeps older FlashInfer packages on the fallback; the op is on FlashInfermain, but this PR does not assume a particular release. The native call explicitly selectsbackend="cuda"to preserve the B300-tested behavior; see Status for thecakecomparison.test_sparse_backend_decode_correctnessalready parametrizesnvfp4_ds_mlaand skipped it for this backend. Once the backend accepts the dtype those cases run, and they failed: the mock layer quantized the query to fp8 only forfp8*caches, whileMLAAttentiondoes it for every quantized cache. The mock now does the same, and the FlashInfernvfp4_ds_mlacases use the fp8 tolerance (the backend reads them with an fp8 query, native NVFP4 decode where supported, and staged FP8 rows elsewhere).Backend selection is unchanged (part of the first commit). On SM10x, auto-selection prefers
FLASHINFER_MLA_SPARSEfor every quantized KV dtype, so acceptingnvfp4_ds_mlawould have moved existing users of that dtype fromFLASHMLA_SPARSEto the new path without an end-to-end benchmark.nvfp4_ds_mlakeepsFLASHMLA_SPARSEfirst andFLASHINFER_MLA_SPARSEas the fallback; this path is opt-in with--attention-backend FLASHINFER_MLA_SPARSE, andtest_sm100_quantized_sparse_prioritypins the order. Making it the default needs numbers against FlashMLA's NVFP4 kernels, which this PR does not have.Not a duplicate. Checks run as
AGENTS.mdprescribes, 2026-09-29:gh pr list --repo vllm-project/vllm --state open --searchwith"nvfp4_ds_mla","FLASHINFER_MLA_SPARSE","nvfp4 flashinfer sparse mla"and"FlashInfer nvfp4 decode MLA". No open PR addsnvfp4_ds_mlatoFLASHINFER_MLA_SPARSE, andflashinfer_mla_sparse.pyonmainhas no NVFP4 support. Related, separate: #58744 (mine; touchesmla_attention.py's chunked-context gather, not this backend) and #53770 (afp8_k_nvfp4_vKV format for the non-MLA FlashInfer backend).AI assistance: the original PR was prepared with Claude Code (Claude Opus 5.5 and Sonnet 5.5); the rebase and upstream-FlashInfer alignment were assisted by OpenAI Codex. The commits carry assistance/attribution trailers.
Test Plan
Historical hardware run: one B300 SXM6 (SM103, driver 580.173.02), CUDA 13.0, torch 2.13.0+cu130, FlashInfer at PR flashinfer-ai/flashinfer#5717. These GPU tests have not yet been rerun after the rebase (see Status).
The first file is new.
test_native_decode_matches_staged_decoderuns FlashInfer's native decode and the FP8-staged decode over a per-layer cache whose blocks alternate with another layer's, withk_scale1 and 0.5, and checks both against an exact FP32 reference (native under 1 %, staged under 6 %, native vs staged under 6 %).test_backend_native_decode_folds_k_scalecalls the backend's own_nvfp4_decode(impl built without__init__and the index conversion stubbed, as the neighbouring tests do) and checks it against the same reference withk_scale1 and 0.5, so a missing1 / k_scaleon either bmm scale fails it.Test Result
Historical B300 results: PR head
0e2b2810e8against upstreammain4e0a414c44(before the rebase):maintest_flashinfer_mla_sparse_nvfp4_native_decode.py(new)test_sparse_mla_backends.pytest_sparse_mla_kv_cache_layout.py,test_flashinfer_sparse_mla_workspace.pyThe 84 extra passes in
test_sparse_mla_backends.pyare the FlashInfer xnvfp4_ds_mlacases oftest_sparse_backend_decode_correctness, which the backend used to skip. The 6 extra passes in the other files are the newtest_sm100_quantized_sparse_prioritycases. The 7 other files aretest_dspark_noncausal_sparse_mla.py,test_flashinfer_sparse_mla_sm120_api.py,test_flashmla_nope_sm90_backend_selection.py,test_cuda_backend_probe_errors.py,test_flashinfer_mla_sparse_sm90.py,tests/kernels/attention/test_attention_selector.pyandtests/kernels/attention/test_flashinfer_mla_decode.py.test_mla_backends.py(335 dense-MLA tests) was not run to completion; none of its tests reach code this PR changes.GSM8K,
tests/evals/gsm8k/gsm8k_eval.py, 20-shot, 1319 questions, temperature 0, GLM-5.3 with NVFP4 weights, TP4 on four B300 GPUs,FLASHINFER_MLA_SPARSE, no speculative decoding:--kv-cache-dtype02f5bbf1ad(2026-10-17)fp8nvfp4_ds_mlaThe NVFP4 server logged that decode used FlashInfer's native kernel. The 20-shot prompts are 3,545 to 3,708 tokens, above the 2,048 top-k, so prefill took the staged sparse path. At the same memory budget the KV cache held 4,526,592 tokens with
nvfp4_ds_mlaand 2,870,464 withfp8(1.58x). One run per arm, each server on a different node. The servers ran an earlier revision of this branch, before the class-level defaults, theNoneguard, the selection-order change and two tests. The eval names--attention-backend FLASHINFER_MLA_SPARSEexplicitly, and none of those changes alters an instance built through__init__.Limits of the evidence:
nvfp4_ds_mlacases intest_sparse_backend_decode_correctnessuse 128 total heads at TP 1, 2 and 4 and top-k 128, so they run the staged path end to end. The native branch is covered by the backend-level test above (top-k index conversion stubbed) and by the GSM8K run (16 heads per rank at TP4).🤖 Generated with Claude Code
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.