Repository navigation
Conversation
Dispatch the exact BF16 multi-token recurrent KDA contract to exported SM100a M64/M128 kernels while retaining the existing CuTe backend for decode, speculative decode, GQA, and unsupported shapes. Add graph-safe caller workspaces, JIT/AOT integration, frozen-source integrity checks, correctness coverage, trace updates, documentation, and the six-shape CUPTI benchmark. Signed-off-by: Yingyi Huang <averyh@nvidia.com>
Signed-off-by: Yingyi Huang <averyh@nvidia.com>
yyihuang
pushed a commit
that referenced
this pull request
Oct 19, 2026
<!-- .github/pull_request_template.md --> ## 📌 Description <!-- What does this PR do? Briefly describe the changes and why they’re needed. --> This achieved about 10x less MoE time per decode step than FlashInfer main for dsv4.1-flash workload. ## Commits | # | commit | what it does | gate | |---|---|---|---| | 1 | fix(moe_ep/sm107): phase-interleave FC12 scheduler keeps cluster-uniform state (EP deadlock) | Stops two CTAs of one cluster from disagreeing on `fc1_exhausted`. Without it, the shared-expert overlap with the phase-interleave schedule hung 3 of 3 vLLM boots during adaptive-verification profiling. | always (phase-interleave schedule) | | 2 | perf(moe_ep/sm107): an SM budget launches the MegaMoE as a uniform cluster grid | `max_sm_count` now builds a workspace instead of raising, so a side-stream shared-expert MLP can run next to the megakernel. | only when `max_sm_count` is set | | 5 | feat(quantization): nvfp4_quantize_cute_dsl can write into caller-provided buffers (out=) | Adds an `out=` argument; the quantizer itself is unchanged. | caller opt-in | | 7 | perf(moe_ep/sm107): bind the per-call NVFP4 epilogue scalars as launch arguments | Binds fc1/fc2 alpha and the fc1 norm constant as launch arguments, removing 3 device copies per layer. | always (NVFP4) | | 8 | perf(moe_ep/sm107): fold the caller's padding mask into the one-launch route staging | Adds `MoEEpTensors.route_padding_mask`, so callers drop their own masking kernel. | only when the caller sets the field | 14 files, +726 / -56, on `main` 2895577. The steps are cumulative from `main` (top) to this series plus the companion vLLM change (bottom). Rows marked *[vLLM]* belong to the companion vLLM change and are shown because the steps were measured together. Columns are tokens per rank @ workspace capacity. | step | 1260 @ 3072 (µs) | 2046 @ 2048 (µs) | 2304 @ 3072 (µs) | 4608 @ 6144 (µs) | MoE per step (ms) | output vs previous step | |---|---|---|---|---|---|---| | `main` (start) | 1892.8 | 2782.5 | 3098.0 | 5992.8 | 94.40 | – | | #1 scheduler fix + #2 SM-budget launch (+ *[vLLM]* SM107 enablement) | +0.2 | -0.2 | +1.0 | +0.1 | -0.001 | bit-identical | | #7 bound epilogue scalars | -6.7 | -7.0 | -8.7 | -4.2 | -0.274 | bit-identical | | #8 padding-mask fold (+ *[vLLM]* hand-off), no padding rows | -0.2 | -0.2 | 0.0 | -0.6 | -0.007 | bit-identical | | **this series + vLLM change (end)** | **232.5** | **257.2** | **265.8** | **438.4** | **9.82** | | ## 🔍 Related Issues <!-- Link any related issues here --> ## 🚀 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](https://pre-commit.com/). ## 🧪 Tests - [ ] Tests have been added or updated as needed. - [ ] All tests are passing (`unittest`, etc.). ## 🔬 Experimental Track <!-- Only for PRs submitted under the experimental policy (CONTRIBUTING.md → "Experimental APIs and Backends"). Leave this section untouched for normal PRs. --> - [ ] This PR is **experimental**: it adds or changes code under `flashinfer/experimental/` and/or an `@flashinfer_experimental_api`. Tracking issue: # - [ ] The tracking issue names an owner, the reason for the experimental path, and a graduation plan with a target release. - [ ] Core changes are limited to a thin entry point (signature, shared validation, feature-gate check, backend selection, handoff). - [ ] Tests live in `tests/experimental/` and were validated on the intended hardware; a runnable example is included. - [ ] 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.) - [ ] **Test scope declared below.** The experimental CI lane runs exactly these targets, so keep them as narrow as the change allows. <!-- Required for experimental PRs. Replace the commented lines below with your targets. Do not delete the fence or change its `experimental-tests` tag — the experimental-track watcher reads it verbatim to decide which targets to ask CI for. --> ```experimental-tests # One target per line: a directory or a file. (A pytest ::selector is not # supported -- the sharding runner cannot consume one.) Must be under # tests/experimental/ and must exist. Delete these comment lines and add yours, e.g. # # tests/experimental/test_my_backend.py # tests/experimental/my_backend/ # # Declaring the whole tree (tests/experimental/) is allowed but means every # experimental PR pays for every other feature's tests, in every matrix cell. ``` ## Reviewer Notes <!-- Optional: anything you'd like reviewers to focus on, concerns, etc. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added faster staging for supported SM107 MoE routes and NVFP4 inputs, with fallback handling for unsupported inputs. * Added an optimized BF16 inference reduction path that masks invalid routes without first clearing their combine slots. * NVFP4 quantization can now write into caller-provided output buffers. * Per-call scalar tensors can be passed directly to supported launches. * **Bug Fixes** * Improved reliability of SM107 work scheduling to prevent CTAs from disagreeing about which work phase is active. * Preserved route validation for inputs that use fallback staging. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
yyihuang
pushed a commit
that referenced
this pull request
Nov 5, 2026
…ed set A forward that keeps no saved set (save_for_backward=False under boundary_mode="saved") binds the arena r at #1 of the fused form when the in-kernel Q / K norm is on, and binds c1_recv / stage_m from the workspace instead of fresh tensors. gdn_det_workspace_layout_kcp takes save_for_backward (default True: the saved-set figures are unchanged) and reports fwd.qk_rstd; False requires backward=False. An overridden P now sizes the staging planes from the plan of that P (the plan the host allocates for), not from the default plan's segment count. A test checks the peak of a caller-held workspace against the layout at tolerance 0 for both forms at P = 1 and P = 2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 Description
This PR adds an optimized SM100a BF16 recurrent-KDA prefill backend and
dispatches to it through the existing
recurrent_kdaAPI.The new backend coexists with the current CuTe-DSL implementation. Decode
(
T=1), speculative decode, GQA, state-pool/checkpoint features, unsupportedlayouts and dtypes, and non-B200 devices continue to use the existing backend
unchanged.
Dispatch contract
The optimized path is selected only when all of the following hold:
T > 1, or packed inputwith more tokens than sequences;
[B, T, H, 128]tensors with a sharedhead count, and beta is contiguous BF16
[B, T, H];A_logis contiguous FP32[H], whiledt_biasis contiguous FP32[H, 128]or[H * 128];use_qk_l2norm_in_kernel=True,use_gate_in_kernel=True,beta_is_logit=True, andlower_boundis finite and negative;accepted-token/checkpoint features are disabled.
Packed input uses
B=1and contiguous CUDA int32/int64cu_seqlens. Thebinding consumes int64 offsets; CUDA graph capture therefore requires callers
to supply int64 offsets directly. An optional contiguous CUDA int32
seq_ordercan order packed sequences for better tail utilization.Calls that do not exactly match this contract fall back to the existing
CuTe-DSL path.
Physical schedules and true in-place state update
B=1, H=64.calls.
[N, H, V, K]withV=K=128.initial_stateis passed as both the initial- and final-statekernel pointer. The kernel updates that allocation directly; there is no
state scratch allocation and no post-kernel
copy_/memcpy.(sequence, head). M64 assigns two CTAs whose64-row value partitions are disjoint. Each CTA loads all initial-state rows
it owns before storing those same final-state rows, making exact pointer
aliasing safe.
alias and rejects partial overlap.
initial_state=None, final-state storage is created only ifoutput_final_state=True.The generated source emits
__restrict__for every pointer and has noper-argument alias escape hatch, so the removal of
__restrict__from only thetwo state parameters is an explicitly delimited FlashInfer ABI integration
patch. The frozen-source test restores those two qualifiers, removes the
existing tensor-map acquire integration prologue, and verifies the remaining
generated source against its normalized SHA256.
CUDA graph and tensor-map safety
RecurrentKDAPrefillWorkspaceowns optional final-state scratch for callswithout an initial state, beta padding, and separate 768-byte M64/M128
tensor-map descriptor blocks. It binds to one stream, is warmed eagerly
against the exact tensor signature, and is used by one captured
recurrent_kdainvocation. A workspace that has participated in capture isrejected by later Python calls; graph replay remains valid because it does not
re-enter Python.
Tensor maps are prepared outside capture and published to stable global
storage on the caller stream. Every consumer CTA executes
fence.proxy.tensormap::generic.acquire.gpufor all six maps, followed by aCTA barrier, before any TMA instruction can consume them.
Other integration
seq_orderandprefill_workspace.CUDA graph tests.
including a commit- and binary-verified MoonshotAI/FlashKDA peer.
🧪 Validation
Final validation was run on pushed head
f6c3b0c486787e64f76b2be91ddb239ea3ba66ce, on an NVIDIA B200(compute capability 10.0), PyTorch
2.13.0.dev20260503+cu132, CUDA 13.2,and Python 3.13.13.
compute-sanitizer --tool memcheckERROR SUMMARY: 0 errorscompute-sanitizer --tool synccheckERROR SUMMARY: 0 errorsThe full repository test suite was not run.
Fresh B200 CUPTI benchmark
The candidate is invoked only through the public
flashinfer.kda_decode.recurrent_kdaentry point and performs its state updatein place inside the kernel. Its timed region contains no state memcpy.
The comparison uses six fixed/packed shapes with deterministic seeds, matching
tensor distributions, BF16 state, and
scale=1/sqrt(128). Preinitializedrotating state buffers ensure each timed candidate invocation receives the
same initial state. Allocation, metadata creation, sequence ordering,
state-pool reset, and JIT/cache warmup are outside the measured region.
The peer is
MoonshotAI/FlashKDA._fwd_raw, from source commitd2ff19a6and CUTLASS
5c149f52.The loaded extension SHA256 is
997c3a1d1338f8bf9dba3c1a01386b1b74448214c294d64409454cc11141c04c.The benchmark records the source revision and independently computed extension
digest.
Timing uses CUPTI activity tracing, cold-L2 flushing, no CUDA graph, 20 ms
warmup, and 100 ms measurement per block. Each value is the median of two
independent block medians; the reported scopes occupy symmetric
PR/raw/.../raw/PRpositions. Speedup isFlashKDA / this PR.[8192][1300, 547, 2048, 963, 271, 3063][1024] × 8[8192][1300, 547, 2048, 963, 271, 3063][1024] × 8All six output/state comparisons against FlashKDA passed at
atol=rtol=1e-2; the maximum observed absolute errors were0.0009765625for output and
0.015625for state. The benchmark JSON SHA256 isc34b27f9e8fb4e4dfbc500e6976e63668fcd0bff2bb00086cd516de59491f259.Upstream recurrent-KDA comparison
Pinned upstream main does not implement ordinary multi-token recurrent-KDA
prefill through its public API: fixed
T != 1is rejected, and packed standarddecode processes one token per sequence. Speculative decode has different
checkpoint/state semantics and is not an equivalent workload. The upstream
prefill comparison is therefore unsupported / N/A, rather than timing a
different operation.
🚀 Pull Request Checklist
Reviewer Notes
The main review surfaces are:
integrity normalization.