Skip to content

feat(cake_kda): add optimized B200 recurrent prefill backend - #4262

Merged
yyihuang merged 9 commits into
flashinfer-ai:mainfrom
yyihuang:codex/flashkda-prefill-export
Aug 3, 2026
Merged

feat(cake_kda): add optimized B200 recurrent prefill backend#4262
yyihuang merged 9 commits into
flashinfer-ai:mainfrom
yyihuang:codex/flashkda-prefill-export

Conversation

@yyihuang

@yyihuang yyihuang commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

📌 Description

This PR adds an CAKE-generated optimized SM100a BF16 recurrent-KDA prefill backend and
dispatches to it through the existing recurrent_kda API.

The new backend coexists with the current CuTe-DSL implementation. Decode
(T=1), speculative decode, GQA, state-pool/checkpoint features, unsupported
layouts 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:

  • the device is exactly compute capability 10.0 (B200 / SM100a);
  • the call is ordinary multi-token prefill: fixed T > 1, or packed input
    with more tokens than sequences;
  • Q, K, V, and G are contiguous BF16 [B, T, H, 128] tensors with a shared
    head count, and beta is contiguous BF16 [B, T, H];
  • A_log is contiguous FP32 [H], while dt_bias is contiguous FP32
    [H, 128] or [H * 128];
  • use_qk_l2norm_in_kernel=True, use_gate_in_kernel=True,
    beta_is_logit=True, and lower_bound is finite and negative;
  • speculative decode, GQA, state indices, committed-state sources, and
    accepted-token/checkpoint features are disabled.

Packed input uses B=1 and contiguous CUDA int32/int64 cu_seqlens. The
binding consumes int64 offsets; CUDA graph capture therefore requires callers
to supply int64 offsets directly. An optional contiguous CUDA int32
seq_order can 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

  • M64 is used only for fixed-layout B=1, H=64.
  • M128 handles every other eligible fixed-layout call and all eligible packed
    calls.
  • State layout remains BF16 [N, H, V, K] with V=K=128.
  • A supplied initial_state is passed as both the initial- and final-state
    kernel pointer. The kernel updates that allocation directly; there is no
    state scratch allocation and no post-kernel copy_/memcpy.
  • M128 assigns one CTA to each (sequence, head). M64 assigns two CTAs whose
    64-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.
  • The binding accepts either disjoint state buffers or an exact full-range
    alias and rejects partial overlap.
  • When initial_state=None, final-state storage is created only if
    output_final_state=True.

The generated source emits __restrict__ for every pointer and has no
per-argument alias escape hatch, so the removal of __restrict__ from only the
two 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

RecurrentKDAPrefillWorkspace owns optional final-state scratch for calls
without 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_kda invocation. A workspace that has participated in capture is
rejected 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.gpu for all six maps, followed by a
CTA barrier, before any TMA instruction can consume them.

Other integration

  • Adds JIT and AOT registration for separate M64 and M128 translation units.
  • Updates tracing and API documentation for seq_order and
    prefill_workspace.
  • Adds correctness, routing/fallback, ABI, stream, overlap, workspace, and
    CUDA graph tests.
  • Adds a CUPTI cold-L2 benchmark for the six fixed/packed contract shapes,
    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.

Gate Result
Frozen-source/JIT specification tests 8 passed
Full targeted recurrent-KDA prefill suite, including fixed/packed correctness, M64/M128, non-default streams, and CUDA graph capture/replay 28 passed
Focused M64/M128 compute-sanitizer --tool memcheck 5 passed; ERROR SUMMARY: 0 errors
Focused M64/M128 compute-sanitizer --tool synccheck 5 passed; ERROR SUMMARY: 0 errors
Pre-commit hooks on the change Passed

The full repository test suite was not run.

Fresh B200 CUPTI benchmark

The candidate is invoked only through the public
flashinfer.kda_decode.recurrent_kda entry point and performs its state update
in 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). Preinitialized
rotating 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 commit
d2ff19a6
and 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/PR positions. Speedup is FlashKDA / this PR.

Case Layout / sequence lengths Variant This PR public in-place API (ms) FlashKDA raw peer (ms) Speedup
H=96 fixed [8192] M128 0.506095 1.085277 2.1444x
H=96 mixed [1300, 547, 2048, 963, 271, 3063] M128 0.391135 0.894702 2.2875x
H=96 uniform [1024] × 8 M128 0.434992 0.735535 1.6909x
H=64 fixed [8192] M64 0.469711 0.992638 2.1133x
H=64 mixed [1300, 547, 2048, 963, 271, 3063] M128 0.269903 0.676574 2.5067x
H=64 uniform [1024] × 8 M128 0.292567 0.495871 1.6949x
Geometric mean 2.0512x

All six output/state comparisons against FlashKDA passed at
atol=rtol=1e-2; the maximum observed absolute errors were 0.0009765625
for output and 0.015625 for state. The benchmark JSON SHA256 is
c34b27f9e8fb4e4dfbc500e6976e63668fcd0bff2bb00086cd516de59491f259.

Upstream recurrent-KDA comparison

Pinned upstream main does not implement ordinary multi-token recurrent-KDA
prefill through its public API: fixed T != 1 is rejected, and packed standard
decode 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

  • Tests were added or updated.
  • Targeted tests and pre-commit hooks pass.
  • Full repository test suite was not run.

Reviewer Notes

The main review surfaces are:

  1. strict eligibility and unchanged fallback behavior;
  2. exact-alias state ABI and direct in-kernel update;
  3. workspace lifetime, stream binding, and CUDA graph rules;
  4. tensor-map publication/acquire ordering;
  5. the two explicitly delimited frozen-CUDA integration patches and their
    integrity normalization.

Related to #4254

Summary by CodeRabbit

  • New Features

    • Added optimized recurrent Kimi Delta Attention prefill support for eligible B200/SM100a configurations.
    • Added M64 and M128 execution variants with eager execution and CUDA graph compatibility.
    • Added a phase-neutral recurrent_kda API supporting decode, speculative decode, and prefill workflows.
    • Added reusable prefill workspace support and public package exports.
  • Documentation

    • Added API documentation covering prefill behavior, dispatch, workspaces, and supported configurations.
  • Tests

    • Added comprehensive coverage for validation, routing, numerical correctness, workspace reuse, and CUDA graphs.

averyhNV and others added 5 commits July 29, 2026 15:28
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>
@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 30, 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: c7a8d300-563f-46fe-962a-b53aee6e2222

📥 Commits

Reviewing files that changed from the base of the PR and between 07e4af6 and e835e0f.

📒 Files selected for processing (8)
  • benchmarks/bench_recurrent_kda_prefill.py
  • docs/api/kda.rst
  • docs/api/kda_prefill.rst
  • docs/index.rst
  • flashinfer/__init__.py
  • flashinfer/kda.py
  • flashinfer/kda_prefill.py
  • tests/kda/test_recurrent_kda_prefill.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/index.rst
  • docs/api/kda_prefill.rst
  • benchmarks/bench_recurrent_kda_prefill.py
  • flashinfer/kda_prefill.py

📝 Walkthrough

Walkthrough

Adds SM100a BF16 FlashKDA M64/M128 kernels, TVM bindings, JIT/AOT packaging, recurrent prefill dispatch, workspace and CUDA graph handling, tests, API documentation, and a CUPTI benchmark.

Changes

FlashKDA kernel and binding integration

Layer / File(s) Summary
Binding validation and TMA descriptors
csrc/kda/flashkda_binding_common.cuh, csrc/kda/*_binding.cu
Adds input, dtype, alias, architecture, shared-memory, descriptor, and TMA validation. Adds M64/M128 launchers and TVM FFI exports.
M64 and M128 fused kernels
csrc/kda/flashkda_bf16_fused_m64.cu, csrc/kda/flashkda_bf16_fused_m128.cu
Adds warp-specialized BF16 kernels with staged loading, preparation, MMA, state updates, output writeback, barriers, TMA, and TMEM operations.

JIT, AOT, and recurrent prefill API

Layer / File(s) Summary
JIT and AOT kernel packaging
flashinfer/jit/flash_kda.py, flashinfer/jit/__init__.py, flashinfer/aot.py, tests/jit/test_flash_kda_jit.py
Adds variant-specific FlashKDA source discovery, JIT specs, loaders, public exports, exact-SM100a detection, conditional AOT packaging, and contract tests.
Recurrent prefill dispatch and workspaces
flashinfer/kda.py, flashinfer/kda_prefill.py, flashinfer/__init__.py
Adds phase-neutral recurrent_kda routing, eligibility checks, per-stream and caller-owned workspaces, packed scheduling, state handling, descriptor preparation, capture checks, and package exports.
Prefill routing and capture validation
tests/kda/test_recurrent_kda_prefill.py
Tests backend routing, FFI arguments, streams, aliasing, state updates, packed ordering, workspace reuse, numerical results, CUDA graph replay, and workspace isolation.

Documentation and benchmarking

Layer / File(s) Summary
API documentation and terminology
docs/api/kda.rst, docs/api/kda_prefill.rst, docs/index.rst, flashinfer/kda_kernels/__init__.py
Documents the KDA facade, recurrent prefill workspace, supported layouts, state behavior, capture requirements, and API navigation.
Benchmark execution and provenance
benchmarks/bench_recurrent_kda_prefill.py
Adds deterministic benchmark cases, rotating states, correctness checks, CUPTI timing, peer comparisons, provenance validation, CLI options, reporting, and JSON output.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: op: attention

Suggested reviewers: aleozlx, dhiraj113, sricketts

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% 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 clearly identifies the optimized B200 recurrent prefill backend added by this pull request.
Description check ✅ Passed The description covers the change, dispatch contract, validation results, tests, benchmarks, checklist, related issue, and reviewer focus areas.
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: 2

🧹 Nitpick comments (4)
csrc/kda/flashkda_binding_common.cuh (1)

195-200: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting beta_tma's row length is a multiple of 8 (TMA stride alignment).

EncodeBetaTma derives global_strides[0] = d1 * sizeof(__nv_bfloat16) from this same dimension. cuTensorMapEncodeTiled requires global strides to be 16-byte aligned, so any num_heads that is not a multiple of 8 (and > 8) makes the encode fail later with only a raw CUresult. The EncodeQkTma path already guards this explicitly via d1 % 64 == 0; mirroring that here keeps the error message actionable.

♻️ Suggested guard
   const int64_t beta_tma_heads = std::max<int64_t>(num_heads, 8);
+  TVM_FFI_ICHECK(beta_tma_heads % 8 == 0)
+      << "beta_tma row length must be a multiple of 8 for TMA stride alignment, got "
+      << beta_tma_heads;
   TVM_FFI_ICHECK(beta_tma.ndim() >= 2 && beta_tma.size(beta_tma.ndim() - 1) == beta_tma_heads &&
🤖 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 `@csrc/kda/flashkda_binding_common.cuh` around lines 195 - 200, Update the
beta_tma validation near beta_tma_heads to require the row dimension used by
EncodeBetaTma to be a multiple of 8, while preserving the existing size checks.
Include the alignment requirement in the assertion message so invalid num_heads
values fail with an actionable error before cuTensorMapEncodeTiled.
csrc/kda/flashkda_bf16_fused_m64_binding.cu (1)

17-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Typedef-isolation blocks in both bindings silently depend on include ordering. Both generated TUs include <cuda_bf16.h> and <math_constants.h> after the uint*_t/int*_t renames are in effect; that is only safe because flashkda_binding_common.cuh already resolved those headers (its lines 20-22), so the nested includes hit their guards. Reordering or trimming those includes would textually rewrite system-header declarations with no obvious diagnostic. The root cause is the same in both files; document the dependency at each site.

  • csrc/kda/flashkda_bf16_fused_m64_binding.cu#L17-L35: extend the existing rationale comment to state that flashkda_binding_common.cuh must be included first and must keep providing <cuda_bf16.h> and <math_constants.h>.
  • csrc/kda/flashkda_bf16_fused_m128_binding.cu#L19-L32: line 19 already defers to the M64 binding for the rationale, so no duplicate prose is needed — just confirm the same include-first ordering holds here once the M64 comment is expanded.
🤖 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 `@csrc/kda/flashkda_bf16_fused_m64_binding.cu` around lines 17 - 35, Expand the
rationale comment above the typedef isolation in
csrc/kda/flashkda_bf16_fused_m64_binding.cu:17-35 to state that
flashkda_binding_common.cuh must remain included first and must continue
providing cuda_bf16.h and math_constants.h before the generated source is
included. In csrc/kda/flashkda_bf16_fused_m128_binding.cu:19-32, make no
duplicate comment change; confirm its existing delegation to the M64 rationale
preserves the same include-first dependency.
flashinfer/kda_decode.py (1)

47-52: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Stream-pointer-keyed caches are never evicted.

_flash_kda_tensor_cache and _flash_kda_stream_workspaces are keyed on the raw cuda_stream integer and hold device allocations for process lifetime. Workloads that create/destroy many side streams accumulate one workspace (descriptor storages + beta padding + possible state scratch) and one set of metadata tensors per stream, and a recycled stream pointer silently adopts the previous entry. Contents are stream-independent so results stay correct, but consider keying the metadata cache by device only and bounding/weak-referencing the per-stream workspaces.

Also applies to: 328-343

🤖 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/kda_decode.py` around lines 47 - 52, Update
_flash_kda_tensor_cache to use device-based keys because its contents are
stream-independent, and redesign _flash_kda_stream_workspaces so per-stream
allocations are bounded or weakly referenced rather than retained for process
lifetime. Ensure recycled stream pointers cannot inherit stale workspace entries
and that descriptor, beta-padding, and state-scratch allocations are released
when streams disappear.
benchmarks/bench_recurrent_kda_prefill.py (1)

362-371: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive variant from the case, not a hardcoded name comparison.

"m64" if case.name == "h64_fixed8192" else "m128" silently misreports the schedule if CASES gains another M64-eligible shape. Since the variant is part of the published result, make it an explicit Case field.

♻️ Proposed refactor
 `@dataclass`(frozen=True)
 class Case:
     name: str
     num_heads: int
     seq_lens: tuple[int, ...]
     packed: bool
     seed: int
+    variant: str
-        "variant": "m64" if case.name == "h64_fixed8192" else "m128",
+        "variant": case.variant,
🤖 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 `@benchmarks/bench_recurrent_kda_prefill.py` around lines 362 - 371, Update the
Case definition and its CASES entries to carry an explicit variant field, then
set metadata["variant"] from case.variant in the metadata construction block.
Remove the case.name comparison so every M64-eligible shape reports its declared
variant accurately.
🤖 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 `@benchmarks/bench_recurrent_kda_prefill.py`:
- Around line 247-253: Update the RuntimeError raised in candidate_run and the
corresponding exhaustion check in the other applicable block to mention the
--state-rotations option, while preserving the existing state index and limit
details so users know which setting to increase.

In `@tests/jit/test_flash_kda_jit.py`:
- Around line 40-48: Ensure the test clears the cached gen_flash_kda_module
result after execution as well as before patching TARGET_CUDA_ARCHS. Prefer
adding a pytest fixture with setup and teardown cache_clear calls, then use it
for this test so specs created under the fake architecture cannot leak into
later tests.

---

Nitpick comments:
In `@benchmarks/bench_recurrent_kda_prefill.py`:
- Around line 362-371: Update the Case definition and its CASES entries to carry
an explicit variant field, then set metadata["variant"] from case.variant in the
metadata construction block. Remove the case.name comparison so every
M64-eligible shape reports its declared variant accurately.

In `@csrc/kda/flashkda_bf16_fused_m64_binding.cu`:
- Around line 17-35: Expand the rationale comment above the typedef isolation in
csrc/kda/flashkda_bf16_fused_m64_binding.cu:17-35 to state that
flashkda_binding_common.cuh must remain included first and must continue
providing cuda_bf16.h and math_constants.h before the generated source is
included. In csrc/kda/flashkda_bf16_fused_m128_binding.cu:19-32, make no
duplicate comment change; confirm its existing delegation to the M64 rationale
preserves the same include-first dependency.

In `@csrc/kda/flashkda_binding_common.cuh`:
- Around line 195-200: Update the beta_tma validation near beta_tma_heads to
require the row dimension used by EncodeBetaTma to be a multiple of 8, while
preserving the existing size checks. Include the alignment requirement in the
assertion message so invalid num_heads values fail with an actionable error
before cuTensorMapEncodeTiled.

In `@flashinfer/kda_decode.py`:
- Around line 47-52: Update _flash_kda_tensor_cache to use device-based keys
because its contents are stream-independent, and redesign
_flash_kda_stream_workspaces so per-stream allocations are bounded or weakly
referenced rather than retained for process lifetime. Ensure recycled stream
pointers cannot inherit stale workspace entries and that descriptor,
beta-padding, and state-scratch allocations are released when streams disappear.
🪄 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: 3b622346-9139-4327-bb75-d8b370dc307b

📥 Commits

Reviewing files that changed from the base of the PR and between 4b964ec and 845b118.

📒 Files selected for processing (16)
  • benchmarks/bench_recurrent_kda_prefill.py
  • csrc/kda/flashkda_bf16_fused_m128.cu
  • csrc/kda/flashkda_bf16_fused_m128_binding.cu
  • csrc/kda/flashkda_bf16_fused_m64.cu
  • csrc/kda/flashkda_bf16_fused_m64_binding.cu
  • csrc/kda/flashkda_binding_common.cuh
  • docs/api/kda_decode.rst
  • flashinfer/__init__.py
  • flashinfer/aot.py
  • flashinfer/jit/__init__.py
  • flashinfer/jit/flash_kda.py
  • flashinfer/kda_decode.py
  • flashinfer/trace/templates/kda.py
  • tests/jit/test_flash_kda_jit.py
  • tests/kda/test_recurrent_kda_prefill.py
  • tests/trace/fi_trace_out/recurrent_kda_q8_v16_d128.json

Comment on lines +247 to +253
def candidate_run():
state_index = state_cursors["pr"][0]
if state_index >= state_rotations:
raise RuntimeError(
f"PR state rotations exhausted: {state_index} >= {state_rotations}"
)
state_cursors["pr"][0] += 1

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

State-rotation exhaustion can abort a run mid-benchmark; point the user at the knob.

bench_gpu_time sizes iteration count from --warmup-ms/--bench-ms, so the number of calls per block scales inversely with kernel time. For the fastest shapes a 120 ms block can exceed 512 slots and kill the whole run. Mention --state-rotations in the error so the recovery is obvious.

♻️ Proposed message change
             raise RuntimeError(
-                f"PR state rotations exhausted: {state_index} >= {state_rotations}"
+                f"PR state rotations exhausted: {state_index} >= {state_rotations}; "
+                "increase --state-rotations or lower --bench-ms/--warmup-ms"
             )

Also applies to: 326-333

🤖 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 `@benchmarks/bench_recurrent_kda_prefill.py` around lines 247 - 253, Update the
RuntimeError raised in candidate_run and the corresponding exhaustion check in
the other applicable block to mention the --state-rotations option, while
preserving the existing state index and limit details so users know which
setting to increase.

Comment on lines +40 to +48
monkeypatch.setattr(
jit_core.current_compilation_context,
"TARGET_CUDA_ARCHS",
{(10, "0a")},
)
flash_kda.gen_flash_kda_module.cache_clear()

uri = flash_kda.get_flash_kda_uri(variant)
spec = flash_kda.gen_flash_kda_module(variant)

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clear the JIT spec cache after the test too, not only before.

gen_flash_kda_module is @functools.cached, so the spec generated under the monkeypatched TARGET_CUDA_ARCHS survives after monkeypatch restores the real arch set. Later tests (or any AOT/JIT code) in the same process will reuse a spec built from the fake {(10, "0a")} context.

🧹 Proposed cleanup
     monkeypatch.setattr(
         jit_core.current_compilation_context,
         "TARGET_CUDA_ARCHS",
         {(10, "0a")},
     )
     flash_kda.gen_flash_kda_module.cache_clear()
+    monkeypatch.setattr(
+        flash_kda.gen_flash_kda_module,
+        "__wrapped__",
+        flash_kda.gen_flash_kda_module.__wrapped__,
+        raising=False,
+    )

Simpler and preferred — use a fixture/finalizer so the cache is dropped on exit as well:

`@pytest.fixture`
def clean_flash_kda_cache():
    flash_kda.gen_flash_kda_module.cache_clear()
    yield
    flash_kda.gen_flash_kda_module.cache_clear()
🤖 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 `@tests/jit/test_flash_kda_jit.py` around lines 40 - 48, Ensure the test clears
the cached gen_flash_kda_module result after execution as well as before
patching TARGET_CUDA_ARCHS. Prefer adding a pytest fixture with setup and
teardown cache_clear calls, then use it for this test so specs created under the
fake architecture cannot leak into later tests.

yyihuang pushed a commit that referenced this pull request Aug 5, 2026
…ts with bit-identical outputs (#4274)

## 📌 Description

This PR adds `tinygemm2_sm100`: four CAKE-generated SM100/SM103 device
kernels specializing `csrc/tinygemm2.cu` (the TensorRT-LLM tinygemm2
port
behind `tinygemm_bf16`) for Blackwell, with **bit-identical outputs**
and
lower latency. The bias path
of `tinygemm_bf16` automatically dispatches to them on compute
capability
10.0/10.3; every other path and architecture is unchanged, and
`FLASHINFER_DISABLE_TINYGEMM2_SM100=1` forces the reference
implementation.

The kernels are CAKE-generated Loom schedules, following the
frozen-generated-source pattern of the CAKE-generated FlashKDA prefill
export (#4262): generated kernel sources checked in verbatim
(`clang-format off`, provenance headers, no host-library dependency),
concatenated into a single translation unit
(`csrc/tinygemm2_sm100/tinygemm2_sm100.cu`) with per-variant kernel
symbol
renames and a small hand-written binding section doing validation, TMA
descriptor encode, and launch — one TU, one JIT module, mirroring the
incumbent `csrc/tinygemm2.cu` layout. The single-TU restructure is
SASS-verified: all four kernels are byte-identical to per-variant builds
on
both sm_100a and sm_103a (`cuobjdump -sass`, symbol names normalized).

The variants are {deep, shallow} pipeline ring x {PDL on, off}:

- stage selection at the Python layer follows the measured B200
crossover axes
  (shallow ring for `K <= 1024` or grids past 2x the SM count);
- PDL variants compile the `griddepcontrol` pair in-kernel and launch
with
  programmatic stream serialization, matching the reference kernel's
  `USE_PDL=true` instantiation;
- the activation TMA descriptor allows an out-of-bounds box on the batch
axis
  (TMA zero-fills), covering batch 1-7 decode shapes.

**Correctness contract: bitwise equality with `csrc/tinygemm2.cu`, not a
tolerance.** On B200: `torch.equal` parity across batch
{1,2,4,7,8,13,16,64}
x five (M, K) pairs x PDL on/off, per-variant direct launches, PDL
back-to-back replay, dispatch/escape-hatch — 23/23; existing
`test_tinygemm2.py` unchanged and green (59/59) with the bias path
routed to
the new backend. Internally, the same generated kernels passed a 239-row
bitwise shape sweep and end-to-end gpt-oss-120b serving with zero token
flips.

**Performance** (torch.profiler kernel-time medians, B200, bias path,
200
launches): (1,128,720) 2.24us vs 2.34us reference; (16,1024,1024) 2.50
vs
2.58; (64,4096,3072) 18.22 vs 32.58 (1.79x). Small decode shapes are
launch-bound; a wider internal CUPTI sweep (cold L2, 35 canonical + 239
regression shapes) measured an 18-23% geometric-mean kernel-time
reduction.

Limitations: the nobias path stays on the reference kernel (no generated
nobias variant yet); hardware validation was on B200 — sm_103a is
compiled
from the textually identical source and gated on, but not yet run on
B300.

## 🔍 Related Issues

Related to #4254 (long-term CAKE-generated kernel progress tracker).
Follows the frozen generated-kernel export pattern introduced in #4262.

## 🚀 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.

## 🧪 Tests

- [x] Tests have been added or updated as needed.
- [x] All tests are passing (`unittest`, etc.).

## Reviewer Notes

The generated kernel sections are frozen artifacts (regeneration happens
outside this repo, as with FlashKDA); review focus is best spent on the
binding section at the tail of
`csrc/tinygemm2_sm100/tinygemm2_sm100.cu`, the
dispatch in `flashinfer/gemm/routergemm.py`, and the parity tests. The
TMA descriptor
encode in the binding is field-for-field identical to the reference
kernel's
own `cuTensorMapEncodeTiled` calls.


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

* **New Features**
  * Added optimized BF16 GEMM execution for eligible SM100/SM103 GPUs.
* Added automatic selection among multiple kernel stages, including PDL
variants, based on workload and device capabilities.
* Added an environment-variable override to disable the optimization and
retain the existing execution path.

* **Bug Fixes**
* Improved routing for bias-enabled BF16 operations while preserving
existing behavior on unsupported devices and bias-free operations.

* **Tests**
* Added coverage for correctness, parity, repeated launches, variant
execution, dispatch, and fallback behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Shanli Xing <shanlix@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
yyihuang added a commit that referenced this pull request Aug 5, 2026
Follow-up to merged #4262 and #4313.

## What changed

- pad the beta-only TMA source to `round_up(H, 8)` instead of only
padding `H < 8`;
- teach the beta pack kernel and binding validation to use the dynamic
padded-head stride;
- retain the caller-visible `H`, state shape, launch grid, and frozen
M64/M128 CUDA bodies unchanged;
- add H=12 eager, packed, full-chunk-plus-tail, final-state, and CUDA
graph replay coverage.

The frozen kernels load beta in 8-head TMA boxes. For H=12, the original
BF16 row stride is 24 bytes and `cuTensorMapEncodeTiled` rejects it.
Padding the descriptor source to 16 heads gives a 32-byte row stride;
heads 12–15 are padding only and are never assigned CTAs.

This generalizes the fix to every positive head count that is not
divisible by eight, while aligned head counts retain the existing
zero-copy beta path.

## Correctness

Both runs used the public `flashinfer.recurrent_kda` facade and compared
BF16 output plus the complete final state against the PyTorch reference
with `atol=rtol=1e-2`.

| GPU | Compute capability | CUDA | Result |
| --- | --- | --- | --- |
| NVIDIA B200 | 10.0 | 12.9 | `62 passed, 0 skipped, 0 failed` |
| NVIDIA GB300 | 10.3 | 12.9 | `62 passed, 0 skipped, 0 failed` |

The test gate runs:

```text
tests/jit/test_flash_kda_jit.py
tests/kda/test_recurrent_kda_prefill.py
```

H=12 coverage includes fixed T=32, fixed T=33 (one full TMA chunk plus
the direct-load tail), packed sequence lengths `[32, 3]`, in-place
initial/final state, and CUDA graph replay after beta is changed. The
JIT contract test also verifies that the frozen generated M64/M128
bodies remain unchanged.

`pre-commit run --files <changed files>` passes.

## Performance

The H=12 path was benchmarked through the public
`flashinfer.recurrent_kda` facade with fallback forbidden. Speedup is
the official FlashKDA raw GPU span divided by this PR's public-API GPU
span. The baseline is the same official FlashKDA source used for #4262:
[`MoonshotAI/FlashKDA@d2ff19a`](MoonshotAI/FlashKDA@d2ff19a),
with CUTLASS `5c149f5`.

Measurements use strict CUPTI first-to-last correlated compute-kernel
span, cold L2, no CUDA Graph, and two independent 128-sample blocks in
symmetric ABCCBA order. The PR span includes both the beta pack and
frozen M128 recurrence kernels. All six benchmark shapes passed output
and complete-final-state correctness against the official peer with BF16
`atol=rtol=1e-2`.

| H=12 shape | SM100 / B200: PR / baseline | Speedup | SM103 / GB300: PR
/ baseline | Speedup |
| --- | ---: | ---: | ---: | ---: |
| packed `[512] x 32` | 136.159 / 240.239 us | **1.7644x** | 128.264 /
233.496 us | **1.8204x** |
| packed `[128] x 8` | 23.760 / 46.448 us | **1.9549x** | 25.712 /
52.904 us | **2.0576x** |
| fixed `[512]` | 46.184 / 76.383 us | **1.6539x** | 47.712 / 82.240 us
| **1.7237x** |
| fixed `[8192]` | 514.197 / 814.435 us | **1.5839x** | 487.161 /
779.426 us | **1.5999x** |
| mixed `[1300, 547, 2048, 963, 271, 3063]` | 208.647 / 351.550 us |
**1.6849x** | 198.216 / 340.913 us | **1.7199x** |
| uniform `[1024] x 8` | 82.080 / 162.703 us | **1.9823x** | 78.440 /
162.505 us | **2.0717x** |
| **Six-shape geometric mean** | | **1.7645x** | | **1.8238x** |

| Comparison | Result |
| --- | --- |
| FlashInfer upstream main at H=12 | `unsupported / N/A` |

Upstream main fails before kernel launch with `cuTensorMapEncodeTiled
failed for beta_tma with CUresult=1`, so there is no valid upstream H=12
timing or speedup claim. This PR leaves the frozen compute kernel
unchanged; it adds the required beta packing only for non-8-aligned head
counts.

Related to #4254.


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

## Summary by CodeRabbit

- **Bug Fixes**
- Improved beta padding for head counts that are not divisible by eight.
- Ensured packed inputs correctly handle larger, non-aligned head
counts.
  - Added validation for padded storage requirements.

- **Documentation**
  - Clarified beta padding behavior and public tensor shapes.

- **Tests**
- Expanded coverage for 12-head inputs, varied sequence lengths, chunk
boundaries, packed inputs, and CUDA graph updates.

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

Co-authored-by: Yingyi Huang <averyh@nvidia.com>
@yyihuang
yyihuang deleted the codex/flashkda-prefill-export branch August 6, 2026 01:06
@yyihuang
yyihuang restored the codex/flashkda-prefill-export branch August 6, 2026 01:23
bkryu pushed a commit that referenced this pull request Aug 11, 2026
…ge-K shapes (#4423)

## 📌 Description

Adds a third ring-depth tier to the `tinygemm2_sm100` family (#4274) and
moves ring selection into the binding. All six kernels are regenerated
from
the current CAKE generator state, which also updates the tensor-map
parameter passing to a single by-value `__grid_constant__` pack.

- **New STAGES=16 ring for single-wave large-K shapes.** The 4/8-stage
rings were tuned for K up to ~4K; at larger K the 8-deep ring no longer
covers the weight-stream latency when the working set is not
L2-resident,
and trails the reference kernel's own 16-deep configuration. The new
tier
closes that: at N=8/M=128/K=7168 (bias path) it measures **4.93 µs vs
the
reference's 5.22 µs on GB300, and 5.31 µs vs 5.56 µs on B200** (cold-L2
  CUPTI).
- **Ring selection in the binding**, following the reference
`csrc/tinygemm2.cu` launcher convention: stage 4 for K <= 1024 or grids
  past 2x the SM count (unchanged), stage 16 for single-wave grids with
K >= 4608 (measured crossover on both GB300 and B200), stage 8
otherwise.
  The Python dispatcher becomes a single call into the combined op.
- **Dispatch gates on exact compute capabilities (10, 0)/(10, 3).**
SM107
passes the previous `major == 10` predicate but must keep the reference
  path instead of erroring in the binding.
- The dynamic-SMEM attribute is now set once per (kernel, device)
instead
  of on every launch.

**Correctness contract unchanged: bitwise equality with
`csrc/tinygemm2.cu`** — verified per-variant and through the dispatcher
(`torch.equal`, batch 1-64, K to 7168, M to 4096, on B200 and GB300),
and
end-to-end in SGLang serving with zero token flips (gpt-oss-120b, and
Mistral-Large-3 whose router GEMM sits in the new tier).
`tests/model_optimizations/test_tinygemm2_sm100.py` extends to the
stage-16
variants and long-K parity shapes. compute-sanitizer synccheck is clean
on
all six variants.

Limitations: unchanged from #4274 — the nobias path stays on the
reference
kernel.

## 📊 End-to-end serving validation — Mistral-Large-3 (675B FP8), SGLang,
TP4, GB300

(CUDA graphs on, production defaults)

**Correctness**: 32 fixed greedy prompts — token streams bitwise
identical between arms; GSM8K-200: CAKE 0.950 / ref 0.945.

**Performance** (CAKE = this PR's kernels via default dispatch, ref =
reference `tinygemm2`; throughput in mean output tok/s, ITL is median in
ms):

| concurrency | throughput (CAKE) | throughput (ref) | ITL (CAKE) | ITL
(ref) |
|---|---|---|---|---|
| 1 | 100.7 | 101.0 | 9.47 | 9.48 |
| 8 | 488.6 | 487.9 | 15.47 | 15.48 |
| 32 | 1068 | 1073 | 28.90 | 28.92 |
| 128 | 3000 | 2997 | 41.31 | 41.44 |

## 🔍 Related Issues

Follow-up to #4274. Related to #4254 (CAKE-generated kernel progress
tracker).

## 🚀 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.

## 🧪 Tests

- [x] Tests have been added or updated as needed.
- [x] All tests are passing (`unittest`, etc.).

## Reviewer Notes

The generated device sections are frozen artifacts (regeneration happens
outside this repo, as with #4262/#4274); review focus is best spent on
the
binding section of `csrc/tinygemm2_sm100.cu` (pack construction, stage
selection, launch attributes), the dispatcher in
`flashinfer/gemm/routergemm.py`, and the test additions. The single-TU
merge follows the mechanical transform documented in the file header.


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

## Summary by CodeRabbit

* **New Features**
* Added support for larger matrix workloads through new stage16
execution variants.
* Automatically selects the appropriate execution stage based on
workload size and hardware.
  * Added direct launch support for all available execution variants.
* Expanded compatibility for supported compute capabilities and newer
CUDA versions.

* **Bug Fixes**
  * Improved handling of large reduction sizes and deep-ring workloads.

* **Tests**
* Added coverage for larger K dimensions, including 7168 and 14336, and
stage16 variants.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
spectrometerHBH added a commit to spectrometerHBH/TIRx-kernels that referenced this pull request Aug 11, 2026
The m128 reference kernel landed in flashinfer-ai/flashinfer#4262 and ships
in the installed release, so `from flashinfer.kda import recurrent_kda`
resolves on its own. Remove the worktree override, along with the sys.modules
purge that existed to displace an already-imported mainline flashinfer, and
the README note telling people to set the variable.

Also retitle the README to "TIRx kernels"; the distribution name in the
pip line is unchanged.
spectrometerHBH added a commit to mlc-ai/TIRx-kernels that referenced this pull request Aug 11, 2026
The m128 reference kernel landed in flashinfer-ai/flashinfer#4262 and ships
in the installed release, so `from flashinfer.kda import recurrent_kda`
resolves on its own. Remove the worktree override, along with the sys.modules
purge that existed to displace an already-imported mainline flashinfer, and
the README note telling people to set the variable.

Also retitle the README to "TIRx kernels"; the distribution name in the
pip line is unchanged.
@yiakwy-xpu-ml-framework-team

Copy link
Copy Markdown

Hi @yyihuang after checking the code, I found Cake backend is more like a TVM-FFI jit module warpper to compile the CUDA sources.

We are actually did this in flash float : https://github.com/yiakwy-xpu-ml-framework-team/flash-float-jit-kernels

flashFloat : Pure cuda with zero dependency of Cutlass and cutile; This is follow up of original design of flashinfer.

Is that true ?

Note my motivation is that after trying to support many subprojects and I found CUDA is the best cross platform languages so that I can support for different devices.

@flashinfer-ai flashinfer-ai deleted a comment from flashinfer-bot Aug 18, 2026
@flashinfer-ai flashinfer-ai deleted a comment from flashinfer-bot Aug 18, 2026
@flashinfer-ai flashinfer-ai deleted a comment from flashinfer-bot Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants