Repository navigation
feat(kernels): port standalone Kimi K3 kernels - #32890
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
How does this compile? Should we move this to AOT kernel first?
There was a problem hiding this comment.
It builds through torch.utils.cpp_extension.load (ninja), not the sglang JIT — the only kernel under kernels/jit/ that does, cached separately in ~/.cache/torch_extensions.
Measured on B300: 109.2 s cold, 5.0 s warm, and the .so carries only sm_103a, so B200/GB200 get no kernel image although the cookbook lists B200. Agreed on AOT.
There was a problem hiding this comment.
Is this already moved to AOT? We can defer the migration of this kernel? I remember torch.utils.cpp_extension.load is not concurrent-safe?
| def is_flash_attention_v4_available() -> bool: | ||
| return _flash_attn_varlen_func is not None |
There was a problem hiding this comment.
Is this related to this PR?
There was a problem hiding this comment.
You are right to ask. The file is byte-identical to main so it looks unrelated, but this PR is meant to export all the kimi-k3 kernel changes and is_flash_attention_v4_available() was missing. Restored.
It also prompted a file-by-file audit against kimi-k3, which found more lossy exports — dropped PDL in pack_topk_ids and concat_mla, the radix-select fast path (3.1-3.5x) and the num_warps heuristic in moe_fused_gate, v2p_ptr/PAGE_MULT in kv_indices (this one hard-crashes the K3 runtime), HEAD_DIM masking in verify_splitkv, the stride guard in kda_blackwell/kernel_h, do_not_specialize in gpu_tensor_hash, and the ROCm --use_fast_math strip in jit/utils/compile.py. All restored; the kernel tree is now byte-identical to kimi-k3 apart from the fixes in this review.
Validation of 22cb347d47Numbers in that commit message were taken mid-way through the restore, before the last batch of lossy exports was put back. Here is the full set, and it supersedes them. Setup. 8× B300 (sm_103) TP8 single node, bs=1
The 8k TTFT match matters on its own: that figure is the 8192-token prefill, and the review fixes in this PR touch the MLA KV write path ( Throughput point, 8192/1024 concurrency 24 (the SLO-optimal cell for this recipe). First reading was TTFT 5649 ms, which did not reproduce — it was the first load after a cold JIT cache. Three repeats:
Repeats agree within 0.1%. Steady state is +1.7% throughput, TTFT +3.3%, TPOT +2.1%. Worth noting the shape: theoretical output is roughly conc/TPOT, so baseline 24/22.87 = 1049 against 843 measured is 80.4% utilisation, while 24/23.35 = 1028 against 858 is 83.5%. Slightly slower per step, meaningfully less pipeline idle — the direction you would expect from PDL being back. Accuracy. GSM8K 200q, three runs: 0.995 / 0.995 / 0.995, invalid 0.005 each. Baseline 0.990 — one question out of 200. One process note. The checklist currently says "Speed Tests and Profiling: Not applicable to this extraction". The |
22cb347 to
ca065ce
Compare
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Pushed Correcting the
|
017c367079 (before) |
f79e7b8327 (after) |
|
|---|---|---|
| bs=1 output tok/s | 105.42 | 106.38 |
| bs=1 TPOT | 8.47 ms | 8.47 ms |
| bs=1 TTFT p50 | 261.01 ms | 239.31 ms |
| C=24 output tok/s | 1000.94 | 1001.36 |
| C=24 TPOT | 19.66 ms | 19.71 ms |
| GSM8K (200q) | 0.995 | 0.995 |
8×B300 SXM6, TP8, --mem-fraction-static 0.85, --mamba-full-memory-ratio 7.21, MoE flashinfer_mxfp4 via the 0617 SiTU pool, attention auto-resolved to trtllm_mla. Both arms booted to the identical pool split (max_mamba_cache_size=476, max_total_num_tokens=136128), and random-range-ratio 1.0 fixes in=1024 / out=256 exactly.
TPOT is the indicative number here: it excludes TTFT, so it is the part shared activation / radix / mbarrier code could actually move, and it is flat in both regimes.
One honest note on the first pass: the after-arm initially measured 98.70 bs=1 tok/s, a 6.4% deficit. That was cold JIT — the consolidation changes source hashes, so its modules had to build while the before-arm hit an already-populated ~/.cache/tvm-ffi. The tell was that only mean/p99 TTFT moved (438/1679 ms vs 266/305) while median TTFT was already better and TPOT was unchanged; the warmup pass in the same run showed p99 5436 ms. Re-running the after-arm against a warm cache gives the 106.38 above. Reporting it because "re-ran it and it got better" deserves the mechanism attached.
Covering the path the main run cannot reach
flashinfer_mxfp4 never calls moe_align_single_token, so none of the above would have caught the removal. Separate arm, system interpreter (flashinfer 0.6.16rc5), no pool, --moe-runner-backend marlin:
- single-request generation correct (
17 × 23→391); - GSM8K 100q: accuracy 1.000, invalid 0.000;
- and the load is verified rather than inferred —
moe_align_single_token…sois mapped in every scheduler process (/proc/<pid>/maps), which only happens via thatif M == 1import.
Verification of the consolidation itself
Each of the three is a move of existing text, not a retype, and two are checked bit-exactly:
- SiTU activation (
kimi_k3/situ_activation.cuh) — bit-identical across three shapes covering bothkHasLinearBetabranches. - Radix primitives (
moe/radix_select_common.cuh) — bit-identical across 16 tensors (ids and weights × four token counts × radix fast path and fused gate). The first attempt here was a retype and it silently changed every selected expert id while leaving the weights identical; that is why these are lifted verbatim now. - mbarrier wrappers (
sgl_kernel/mbarrier.cuh) — same globalnamespace ptxboth consumers already open, so no call site changes.
All ten touched JIT modules cold-compile (artifacts confirmed newly built in ~/.cache/tvm-ffi, not cache hits).
One caveat on comparability
These runs are TP8 without DCP, so the absolute numbers are not comparable to the cookbook's B300 TP8+DCP8 cell. That is a branch-side version knot, not something this PR causes: cutedsl_mla_backend.py:329 passes enable_dcp=True, a kwarg flashinfer 0.6.15.post1 (the pyproject.toml pin) does not have, while 0.6.16rc5 cannot use the 0617 SiTU pool because the pool's overlay/include shadows the wheel's KernelRunner.h (TrtllmGenBatchedGemmRunnerOptions has no member perTokenSfDtype). Of the two reachable configurations I picked the one that exercises what this commit touches — the SiTU kernels are on the flashinfer_mxfp4 path, not the Marlin one. Both arms ran identically, so the A/B stands.
cc58c92 to
b87597b
Compare
CI load: the branch registered ~1540s of new test time, almost all of it on GPU runners. This removes 18 files / 1218s and leaves test_ar_fusion.py as the only addition on 8-gpu-h200. What made most of it safe is that #32890 already landed consolidated coverage on main for the same ops: test_kimi_k3_prerequisite_ops.py add3, tiny_gemm, route_radix, route_quant_fused, vision_rope, concat_q bf16+fp8, set_mla_kv_buffer, concat_mla, topk_sum, pack_topk_ids, per_token_group_quant, image, fused_sigmoid_gating, kda_replayssm ring fold, top_p_renorm kimi_k3/test_collectives.py all_reduce_push, gemm_all_gather, gemm_all_reduce, sequence_parallel, attention_residual_direct_all_gather kimi_k3/test_compute.py situ_and_mul, situ_mul_quant, moe_front, mla_output_gate, attn_residual_and_prefix_write attention/test_kda_prefill.py, attention/test_kda_fused_decode.py Fourteen of the eighteen fall into that bucket: the op keeps a test, it just runs once on main's file instead of twice. Kept: test_ar_fusion.py. main's test_collectives has a single all_reduce_push case; this file has ten, covering pull 2-shot, both tuning grids, norm and finalize variants, mixed stress and graph capture. Dropping it would have been the one deletion that silently thins the most failure-prone subsystem here. Four deletions are a real loss of coverage with nothing on main behind them, and they are called out rather than folded into the count: test_symm_buffers.py symmetric-buffer invariants -- created once, offsets uniform across ranks, offsets survive rank-asymmetric churn, slices aligned and the two buffers disjoint. These are bookkeeping assertions of the kind that catch "someone extended X without updating Y". test_trtllm_gen_moe.py trtllm-gen MoE against the fork kernel. test_attn_res_aggregate_stream.py the aggregate-stream attn-res path. test_kda_mtp_cutedsl_replayssm_ring.py nightly-only, so it was not part of the per-commit cost in the first place. The first is the one I would argue hardest to keep if anyone wants to put one back: multi-rank buffer layout has no other test, and its failure mode is silent corruption rather than a crash. Deleted files have no dangling references; run_suite.py discovers tests by AST scan, so nothing enumerates them by name.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: hnyls2002 <lsyincs@gmail.com> Co-authored-by: zhangxiaolei <zhangxiaolei.666@bytedance.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: hnyls2002 <lsyincs@gmail.com> Co-authored-by: zhangxiaolei <zhangxiaolei.666@bytedance.com>
…shortcut for the mid and large Kimi-K3 fused router batches (SM100/SM103) (#5568) ## Kimi-K3 fused MoE router: split cooperative joins and a warp-level round-0 shortcut for the mid and large batches (SM100 / SM103) Follow-up to #5531 / #5548 / #5564 (same source family; regenerated programs only, no host change). Same K3 semantics (sigmoid gate, top-16 on `sigmoid(logit) + bias`, renormalized unbiased weights, lower expert id on ties) and the same expert-aligned route plan for `block_m` 8/16; the public entries `flashinfer.fused_moe.kimi_k3_fused_router` / `prepare_kimi_k3_fused_router`, the route table and the arm set are unchanged. The exporter regenerates all 14 programs per arch; for the 8 small-batch programs (`L`, `LC2`, `LC4`, `LC8` x `block_m` 8/16) the kernel and binding sources are identical to #5564 up to the module identifier (the module name is a content fingerprint that also covers exporter-side build metadata), so only the 6 mid/large programs per arch carry code changes. ### What changed The 12 routed shapes with `num_tokens` 256 … 8192 (`block_m` 8/16) get new kernel code (arms `M`, `Q4S`, `GW`); the 16 small-batch shapes keep their kernel code. - **Split cooperative join in the mid-batch arms (`M`, `Q4S`; `num_tokens` 256 … 2048).** The single grid-wide join between expert selection and route-plan build becomes an arrive / wait pair (`cooperative_groups::grid_group::barrier_arrive` / `barrier_wait`): every CTA arrives right after its last selection row, only the CTAs that own plan-build work wait, the others retire instead of spinning in `this_grid().sync()`. - **Split first join in the large-batch arm (`GW`; `num_tokens` 4096 / 8192).** The join that publishes the zeroed expert counts is split into an arrive after the grid-strided clear and one wait between a CTA's first-row selection and its first ticket atomic, so the slowest CTA's clear overlaps one row of selection. Every CTA arrives and waits exactly once per round (a CTA without rows waits once after its empty loop). - **Fence-free barrier sites in `GW`.** The three per-thread device-scope fences in front of the GW grid barriers are dropped: `__syncthreads()` followed by the barrier's own device-scope release already orders the preceding stores (the same reasoning as #5548, previously applied only to the mid-batch arms). - **Round-0 max-bin shortcut in the `GW` radix selection.** The top byte of the ordered key is sign + 7 exponent bits, so a row's 896 keys concentrate in one or two round-0 histogram bins and the 28 conditional shared-memory atomics per lane serialized on one address. Round 0 now takes the maximum top byte and its population from two warp reductions (`redux.sync`); when that bin holds at least 16 keys it is exactly the bin the histogram round selects (nothing lies above it) and the state update is identical, otherwise the unchanged histogram round runs. Rounds 1-3 are unchanged, so the selected experts and their order are bit-identical. Source-side paired cold-L2 CUPTI A/B against the #5564 programs (same session, same node, two runs per arch on different GPUs; `previous / new` kernel time): | row | B200 run 1 / run 2 | GB300 run 1 / run 2 | |---|---|---| | m256 bm8 / bm16 | 1.027 / 1.028, 1.027 / 1.036 | 1.016 / 1.021, 1.012 / 1.034 | | m512 bm8 / bm16 | 1.057 / 1.057, 1.075 / 1.051 | 1.075 / 1.079, 1.071 / 1.074 | | m1024 bm8 / bm16 | 1.067 / 1.062, 1.041 / 1.058 | 1.050 / 1.058, 1.047 / 1.057 | | m2048 bm8 / bm16 | 1.028 / 1.023, 1.011 / 1.026 | 1.035 / 1.056, 1.039 / 1.058 | | m4096 bm8 / bm16 | 1.093 / 1.091, 1.092 / 1.082 | 1.088 / 1.092, 1.087 / 1.094 | | m8192 bm8 / bm16 | 1.093 / 1.092, 1.094 / 1.090 | 1.083 / 1.077, 1.083 / 1.079 | | geomean (12 rows) | 1.060, 1.057 | 1.060, 1.061 | Every changed row is faster than #5564 in every run on both architectures. ### Exactness Source-side gates on both arches: exact43 43/43, affected slice + registered e2e, compute-sanitizer synccheck and racecheck (separate runs, 8 frozen shapes + all 28 routed rows): `ERROR SUMMARY: 0 errors` / `RACECHECK SUMMARY: 0 hazards` on every run. Export receipts (source <-> export bitwise parity on all 28 shapes per arch, generated grid == source grid): B200 (sm_100a) 28/28 rows correct, GB300 (sm_103a) 28/28 rows correct; the delivered patch produced from the two architectures is byte-identical (SHA-256 `eb051e8e130f00c2dffa69f1095fdcc6fdb70a97c55f9d5425e49cda65fe4c22`). `tests/experimental/test_cake_kimi_k3_fused_router.py`: 44 passed on both arches. Pre-commit (ruff, mypy, clang-format opt-out for the generated `csrc/`) clean. ### Benchmark `benchmarks/bench_cake_kimi_k3_fused_router.py --cupti --rounds 5` (interleaved A/B rounds, CUPTI kernel time with a cold L2) against SGLang `route_radix` + `moe_align_block_size` (pinned SGLang revision 83bd2c47, kernel-only): | row | arm (B200 / GB300) | B200 fused us | B200 SGLang us | B200 speedup | GB300 fused us | GB300 SGLang us | GB300 speedup | |---|---|---:|---:|---:|---:|---:|---:| | m1_bm8 | L / L | 5.79 | 9.31 | **1.608** | 5.54 | 12.64 | **2.283** | | m2_bm8 | LC / LC | 5.28 | 9.63 | **1.825** | 5.22 | 12.64 | **2.423** | | m4_bm8 | LC / LC | 5.92 | 9.50 | **1.606** | 5.60 | 12.58 | **2.246** | | m8_bm8 | LC / LC | 5.95 | 9.73 | **1.635** | 5.76 | 12.45 | **2.161** | | m16_bm8 | L / L | 6.14 | 9.57 | **1.557** | 6.08 | 12.83 | **2.111** | | m32_bm8 | L / L | 6.27 | 9.79 | **1.561** | 6.21 | 12.93 | **2.082** | | m64_bm8 | L / L | 7.01 | 10.02 | **1.429** | 6.66 | 12.99 | **1.952** | | m128_bm8 | L / L | 8.06 | 10.40 | **1.290** | 7.68 | 13.18 | **1.717** | | m256_bm8 | M / M | 8.16 | 10.94 | **1.341** | 7.97 | 14.14 | **1.775** | | m512_bm8 | Q4S / Q4S | 9.89 | 12.96 | **1.311** | 9.47 | 15.33 | **1.618** | | m1024_bm8 | Q4S / Q4S | 13.70 | 18.34 | **1.339** | 12.80 | 18.53 | **1.447** | | m2048_bm8 | Q4S / Q4S | 20.74 | 28.19 | **1.360** | 19.20 | 26.78 | **1.395** | | m4096_bm8 | GW / GW | 28.35 | 47.52 | **1.676** | 26.75 | 45.25 | **1.691** | | m8192_bm8 | GW / GW | 43.33 | 87.20 | **2.013** | 40.90 | 82.08 | **2.007** | | m1_bm16 | L / L | 6.46 | 9.85 | **1.525** | 6.05 | 12.51 | **2.069** | | m2_bm16 | LC / LC | 5.79 | 9.50 | **1.641** | 5.54 | 12.70 | **2.295** | | m4_bm16 | LC / LC | 5.60 | 9.89 | **1.766** | 5.50 | 12.74 | **2.314** | | m8_bm16 | LC / LC | 5.70 | 9.79 | **1.719** | 5.79 | 13.12 | **2.265** | | m16_bm16 | L / L | 5.86 | 9.98 | **1.705** | 6.05 | 12.93 | **2.138** | | m32_bm16 | L / L | 6.43 | 9.95 | **1.547** | 6.11 | 13.18 | **2.157** | | m64_bm16 | L / L | 7.14 | 10.05 | **1.408** | 6.91 | 13.09 | **1.894** | | m128_bm16 | L / L | 8.26 | 10.40 | **1.260** | 7.81 | 13.60 | **1.742** | | m256_bm16 | M / M | 8.22 | 11.33 | **1.377** | 7.97 | 13.89 | **1.743** | | m512_bm16 | Q4S / Q4S | 9.95 | 12.54 | **1.260** | 9.34 | 15.46 | **1.654** | | m1024_bm16 | Q4S / Q4S | 13.38 | 17.76 | **1.328** | 12.80 | 18.53 | **1.447** | | m2048_bm16 | Q4S / Q4S | 20.70 | 26.66 | **1.287** | 19.14 | 25.82 | **1.349** | | m4096_bm16 | GW / GW | 28.74 | 44.89 | **1.562** | 27.46 | 43.20 | **1.573** | | m8192_bm16 | GW / GW | 43.45 | 81.60 | **1.878** | 41.63 | 77.79 | **1.869** | Geomean speedup vs SGLang: **NVIDIA B200 1.516** (min `1.260`, `28`/28 rows > 1.0), **NVIDIA GB300 1.882** (min `1.349`, `28`/28 rows > 1.0). #5564 on the same benchmark: B200 1.492 (min 1.220), GB300 1.873 (min 1.319). ### Baselines and their source PRs - **Previous programs (A/B "previous" arm and the geomean reference):** #5564 (merge 78c6e1f, 2026-09-26), on top of #5548 (c894711, 2026-09-25) and #5531 (afad701, 2026-09-25). Files: `flashinfer/experimental/kimi_k3_fused_router/{cake_backend.py, cake_jit.py, csrc/cake_kimi_k3_fused_router/sm_10{0,3}a/*}`, `flashinfer/fused_moe/kimi_k3_fused_router.py`, `benchmarks/bench_cake_kimi_k3_fused_router.py`, `tests/experimental/test_cake_kimi_k3_fused_router.py`. This branch's merge-base with `main` is 78c6e1f (= #5564's merge commit); `git log 78c6e1f..origin/main -- <those files>` is empty, so no upstream commit after the merge-base touched them. - **SGLang reference (benchmark denominator and the test oracle's semantics):** `sglang.kernels.ops.moe.moe_route_radix.route_radix` + `sglang.kernels.ops.moe.moe_align.moe_align_block_size` at the pinned SGLang revision 83bd2c473f173404216f6d9851ad7a95c1e3c565, imported kernel-only (no SGLang runtime); `moe_route_radix.py` last changed by sgl-project/sglang#32890 (fb207b72b, 2026-08-01, "port standalone Kimi K3 kernels") and `moe_align.py` by sgl-project/sglang#32045 (74338e94f, 2026-07-22), both before the pinned revision; the pin is unchanged since #5531. - **Test oracle:** the PyTorch reference in `tests/experimental/test_cake_kimi_k3_fused_router.py` from #5531 (unchanged since). ### Notes - No change to `cake_backend.py`; `cake_jit.py` only changes the 14 module records per arch (module names follow the generated program fingerprint). Same-session controls on the 16 unchanged small rows: geomean 0.994 (B200) / 1.006 (GB300) with both arms loading the same module, so the small rows are unchanged within noise. - Shapes outside the routed set still raise `NotImplementedError`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Performance** * Improved fused routing efficiency across supported GPU architectures and workloads, including faster top-k selection and more targeted synchronization. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Yingyi Huang <averyh@nvidia.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…rm on the 32-128-token shapes it wins, per-architecture route tables, regenerated programs (#5621) ## Kimi-K3 fused MoE router: register-prefetch variant of the 32-128-token kernel on the shapes where it wins (SM100 / SM103) Follow-up to #5531 / #5548 / #5564 / #5568 / #5589 / #5611 (same source family). This branch is stacked on the open #5611: its base commit is #5611's head (7a46a28), so it merges after #5611 (or is rebased onto `main` once #5611 lands). Same K3 semantics (sigmoid gate, top-16 on `sigmoid(logit) + bias`, renormalized unbiased weights, lower expert id on ties) and the same expert-aligned route plan for `block_m` 8/16; the public entries `flashinfer.fused_moe.kimi_k3_fused_router` / `prepare_kimi_k3_fused_router` are unchanged. The arm set gains one member: `LP`, a second kernel of the `L` family (same thread count, cooperative launch, admission guard and grid rule) that loads the bias and the CTA's first row's logits into registers before its prologue barrier, so their cold-L2 round trip overlaps the winner seed, the plan-bitmap clear and the CTA barrier instead of waiting behind them. This round carries a **host change** next to the regenerated programs: the two route tables are no longer identical -- `LP` serves `(32, 8)`, `(32, 16)`, `(64, 8)` and `(64, 16)` on SM100 and `(32, 8)`, `(32, 16)`, `(64, 8)` and `(128, 8)` on SM103, the other 32-128-token cells keep arm `L` -- and the `L` launch-grid / admission rules apply to both members of the family (`ARM_L_FAMILY = ("L", "LP")`). The exporter emits one module per (architecture, arm, `block_m`) that serves a routed shape: 22 records on SM100 and 21 on SM103 (no SM103 shape routes to `L` at `block_m` 8 any more, so that record is retired); only the four `lp` records are new kernel code, every other program is untouched. ### What changed 4 of the 28 routed shapes per architecture get new kernel code (SM100: `num_tokens` 32 x `block_m` 8 / 16, 64 x 8 / 16; SM103: 32 x 8 / 16, 64 x 8, 128 x 8); the other 24 rows keep theirs and are the same-session controls. Ratios below are `previous / new` kernel time from same-session paired cold-L2 CUPTI A/Bs against the #5611 programs (`block_m` 8 / 16 per row, two runs per arch); a change ships only when every changed row reads > 1.0 in both runs on every architecture it changes. - **`LP` (the `L` body with the bias / first-row logits loads above the prologue barrier).** The `L` prologue seeds the winner slots, zeroes the plan bitmap (16 shared stores per thread) and passes a CTA barrier before loading the bias float4 and the row's logits float4; neither load depends on the barrier, so `LP` issues both above it (the next row's logits are refilled at the same program position at which `L` issued its load; all four prefetch registers are always defined so the consumed row's values do not stay live through the selection). `__syncthreads` count unchanged (11 on SM103, 10 on SM100 whose `L` family carries the one-barrier prefix of #5611), 71 registers, no spills. Measured as a whole-arm change on all six 32-128-token rows first: five rows per architecture win in every run (SM100 1.005-1.037, SM103 1.005-1.022) while one row per architecture ties the previous kernel at the 32 ns CUPTI tick deterministically (SM100 128 tokens / `block_m` 8: 6.368 = 6.368 us in two of three runs; SM103 64 tokens / `block_m` 16: 5.920 = 5.920 us in all three runs) and the 128-token / `block_m` 16 row ties in the routed program's own runs on both architectures (SM100 0.9998 / 0.9950, SM103 1.0000 / 1.0000). Hence the per-cell route: `LP` only where it wins, the tie rows keep the #5611 `L` kernel byte-for-byte. Routed program, source-side paired cold-L2 CUPTI A/B against the #5611 programs (same session, same node; `previous / new` kernel time; two runs per arch): | row | B200 run 1 / run 2 | GB300 run 1 / run 2 | |---|---|---| | m32 bm8 | 1.0317 / 1.0263 | 1.0219 / 1.0275 | | m32 bm16 | 1.0365 / 1.0311 | 1.0053 / 1.0053 | | m64 bm8 | 1.0211 / 1.0209 | 1.0220 / 1.0164 | | m64 bm16 | 1.0105 / 1.0052 | same kernel code as #5611 | | m128 bm8 | same kernel code as #5611 | 1.0208 / 1.0208 | | geomean (changed rows) | 1.0249 / 1.0208 | 1.0175 / 1.0175 | The 24 unchanged rows per architecture load kernel code that is byte-identical to #5611 (GPU-free trace identity of the eleven existing arms at `block_m` 8 / 16 on both architectures). ### Exactness Source-side gates on both arches: exact43 43/43, affected slice + registered e2e 56/56 per arch (28 registered cases + the 28-row extension), compute-sanitizer synccheck and racecheck (separate runs) on the `LP` body (eight 32-128-token labels, 13 logs per run, both arches): `ERROR SUMMARY: 0 errors` / `RACECHECK SUMMARY: 0 hazards` on every run. Export receipts (source <-> export bitwise parity on all 28 shapes per arch, generated grid == source grid, including the `LP` arm and its grid): B200 (sm_100a) 28/28 shapes pass (bitwise source/export parity, grid parity; export-to-source kernel time 0.951-1.020), GB300 (sm_103a) 28/28 shapes pass (bitwise source/export parity, grid parity; export-to-source kernel time 0.964-1.025); the delivered patch produced from the two architectures is byte-identical (sha256 afba09a58ed98557c02efe199b44a8c0fa9511a2c18416d8495435fc81c6bc97, 144,130 bytes). `tests/experimental/test_cake_kimi_k3_fused_router.py`: 44 passed (B200), 44 passed (GB300). Pre-commit (ruff, mypy, clang-format opt-out for the generated `csrc/`): all hooks pass on both export trees. ### Benchmark `benchmarks/bench_cake_kimi_k3_fused_router.py --cupti --rounds 5` (interleaved A/B rounds, CUPTI kernel time with a cold L2) against SGLang `route_radix` + `moe_align_block_size` (pinned SGLang revision 83bd2c47, kernel-only): | row | arm (B200 / GB300) | B200 fused us | B200 SGLang us | B200 speedup | GB300 fused us | GB300 SGLang us | GB300 speedup | |---|---|---:|---:|---:|---:|---:|---:| | 1 tokens, block_m 8 | LC | 4.00 | 9.15 | 2.288 | 4.10 | 13.12 | 3.203 | | 1 tokens, block_m 16 | LC | 4.38 | 9.70 | 2.212 | 4.45 | 13.09 | 2.942 | | 2 tokens, block_m 8 | LC | 5.22 | 9.34 | 1.791 | 5.31 | 12.96 | 2.440 | | 2 tokens, block_m 16 | LC | 5.76 | 9.25 | 1.605 | 5.60 | 13.15 | 2.349 | | 4 tokens, block_m 8 | LC | 5.60 | 9.41 | 1.680 | 5.38 | 13.09 | 2.435 | | 4 tokens, block_m 16 | LC | 5.28 | 9.79 | 1.855 | 5.28 | 13.12 | 2.485 | | 8 tokens, block_m 8 | LC | 5.66 | 9.54 | 1.684 | 5.50 | 12.70 | 2.308 | | 8 tokens, block_m 16 | LC | 5.44 | 9.66 | 1.776 | 5.57 | 13.28 | 2.385 | | 16 tokens, block_m 8 | LC | 6.05 | 9.44 | 1.561 | 5.82 | 13.15 | 2.258 | | 16 tokens, block_m 16 | LC | 5.95 | 9.98 | 1.677 | 5.76 | 12.93 | 2.244 | | 32 tokens, block_m 8 | LP | 5.73 | 9.70 | 1.693 | 5.79 | 12.99 | 2.243 | | 32 tokens, block_m 16 | LP | 5.98 | 9.57 | 1.599 | 5.76 | 13.06 | 2.267 | | 64 tokens, block_m 8 | LP | 6.05 | 9.70 | 1.603 | 5.95 | 12.99 | 2.183 | | 64 tokens, block_m 16 | LP / L | 6.14 | 9.76 | 1.589 | 6.11 | 13.54 | 2.215 | | 128 tokens, block_m 8 | L / LP | 6.27 | 10.21 | 1.628 | 6.05 | 13.02 | 2.153 | | 128 tokens, block_m 16 | L | 6.50 | 10.14 | 1.562 | 6.24 | 13.50 | 2.164 | | 256 tokens, block_m 8 | M | 7.39 | 10.69 | 1.446 | 7.14 | 14.30 | 2.004 | | 256 tokens, block_m 16 | M | 7.58 | 10.98 | 1.447 | 7.20 | 13.86 | 1.924 | | 512 tokens, block_m 8 | Q4S | 9.54 | 12.83 | 1.346 | 9.22 | 15.71 | 1.705 | | 512 tokens, block_m 16 | Q4S | 9.63 | 12.51 | 1.299 | 9.25 | 15.52 | 1.678 | | 1024 tokens, block_m 8 | Q4S | 13.18 | 18.30 | 1.388 | 12.54 | 18.82 | 1.500 | | 1024 tokens, block_m 16 | Q4S | 13.12 | 17.66 | 1.346 | 12.67 | 18.34 | 1.447 | | 2048 tokens, block_m 8 | Q4SP | 19.81 | 28.06 | 1.417 | 17.98 | 27.01 | 1.502 | | 2048 tokens, block_m 16 | Q4SP | 19.90 | 26.69 | 1.341 | 17.98 | 26.08 | 1.450 | | 4096 tokens, block_m 8 | GW | 26.66 | 47.17 | 1.770 | 24.90 | 45.66 | 1.834 | | 4096 tokens, block_m 16 | GW | 26.66 | 44.83 | 1.682 | 25.47 | 43.78 | 1.719 | | 8192 tokens, block_m 8 | GW | 40.51 | 86.82 | 2.143 | 38.30 | 82.85 | 2.163 | | 8192 tokens, block_m 16 | GW | 40.19 | 81.09 | 2.018 | 39.74 | 79.10 | 1.990 | Geomean speedup vs SGLang: **NVIDIA B200 1.640 (28/28 > 1.0, min 1.299 at 512 tokens / block_m 16)**, **NVIDIA GB300 2.074 (28/28 > 1.0, min 1.447 at 1024 tokens / block_m 16)**. #5611 on the same benchmark: B200 1.642 (min 1.299), GB300 2.057 (min 1.450); #5589: B200 1.673 (min 1.327), GB300 2.030 (min 1.358); #5568: B200 1.516 (min 1.260), GB300 1.882 (min 1.349). The source-side sealed interleaved paired run of the same programs against the same SGLang pin (same session, A/B/B/A order) reads B200 geomean 1.6061, 28/28 rows > 1.0, min 1.3019 (`m512_bm16`), max 2.187 (`m8192_bm8`); GB300 geomean 1.7627, 28/28 rows > 1.0, min 1.3573 (`m1024_bm16`), max 2.515 (`m1_bm16`) (#5611 programs under the same protocol: B200 1.5884, GB300 1.7752). ### Baselines and their source PRs - **Previous programs (A/B "previous" arm and the geomean reference):** #5611 (head 7a46a28, open; the base commit of this branch), on top of #5589 (head 1551f0c, open), #5568 (merge d67061a, 2026-09-26), #5564 (78c6e1f, 2026-09-26), #5548 (c894711, 2026-09-25) and #5531 (afad701, 2026-09-25). Files: `flashinfer/experimental/kimi_k3_fused_router/{cake_backend.py, cake_jit.py, README.md, csrc/cake_kimi_k3_fused_router/sm_10{0,3}a/*}`, `flashinfer/fused_moe/kimi_k3_fused_router.py`, `benchmarks/bench_cake_kimi_k3_fused_router.py`, `tests/experimental/test_cake_kimi_k3_fused_router.py`. This branch's merge-base with `main` is d67061a (#5568's merge commit, as for #5589 / #5611); `git log d67061a..origin/main -- flashinfer/experimental/kimi_k3_fused_router flashinfer/fused_moe/kimi_k3_fused_router.py benchmarks/bench_cake_kimi_k3_fused_router.py tests/experimental/test_cake_kimi_k3_fused_router.py` is empty at `main` b9fe0f4 (26 commits after the merge-base, none touching these paths), so the branch rebases onto `main` without conflicts once #5589 / #5611 land. - **SGLang reference (benchmark denominator and the test oracle's semantics):** `sglang.kernels.ops.moe.moe_route_radix.route_radix` + `sglang.kernels.ops.moe.moe_align.moe_align_block_size` at the pinned SGLang revision 83bd2c473f173404216f6d9851ad7a95c1e3c565, imported kernel-only (no SGLang runtime); `moe_route_radix.py` last changed by sgl-project/sglang#32890 (fb207b72b, 2026-08-01, "port standalone Kimi K3 kernels") and `moe_align.py` by sgl-project/sglang#32045 (74338e94f, 2026-07-22), both before the pinned revision; the pin is unchanged since #5531. - **Test oracle:** the PyTorch reference in `tests/experimental/test_cake_kimi_k3_fused_router.py` from #5531 (unchanged since; only the route-table and launch-grid assertions of that file change in this PR). ### Notes - **Route tables (per architecture; they differ in exactly two cells).** `cake_backend._SM103_SHAPE_ROUTE` is now `_SM100_SHAPE_ROUTE` with `(64, 16) -> L` and `(128, 8) -> LP`: | num_tokens x block_m | 1-16 (any) | 32 x 8 / 16 | 64 x 8 | 64 x 16 | 128 x 8 | 128 x 16 | 256 | 512-1024 | 2048 | 4096-8192 | |---|---|---|---|---|---|---|---|---|---|---| | sm_100a (B200) | LC | LP | LP | LP | L | L | M | Q4S | Q4SP | GW | | sm_103a (GB300) | LC | LP | LP | L | LP | L | M | Q4S | Q4SP | GW | - **`L` family.** `ARM_L_FAMILY = ("L", "LP")`; `launch_grid(arm, n, ...)` applies the `L` rule to both members (grid = `min(max(n, 128), CTAs-per-SM x SM count)`, at most 512 tokens), and `cake_jit.ARMS` lists `LP`. `test_route_tables_cover_the_routed_shapes` asserts the two differing cells and four `LP` / two `L` cells per architecture; `test_launch_grid_rules` asserts `launch_grid("LP", n, ...) == launch_grid("L", n, ...)` for 32 / 64 / 128 tokens and the same admission error above 512 tokens; every other test is unchanged. - **Per-architecture programs.** The SM100 and SM103 `lp` programs differ exactly as the `l` programs do (SM100 carries the one-barrier prefix of #5611); all other programs are generated from the same source on both architectures. The `lp` module records carry `block: [224, 1, 1]`, no cluster and `cooperative: true`, like `l`. The `l` record at `block_m` 8 on SM103 is removed because no routed SM103 shape uses it (the SM103 `L` cells are 64 and 128 tokens at `block_m` 16); its SM100 counterpart stays (128 tokens x `block_m` 8). - Shapes outside the routed set still raise `NotImplementedError`. --- **v59 update (commit `dfc02223`): LC r16 launch bound made portable across ptxas versions.** The internal CI run of this PR (and of #5611) failed `tests/experimental/test_cake_kimi_k3_fused_router.py::test_fused_router_matches_reference[16-8]` / `[16-16]` on B200 / GB200 / GB300 with CUDA 12.9 and 13.0 at JIT build time: the 16-token kernel (`lc16`, one 16-CTA non-portable cluster) was declared `__launch_bounds__(224, 1, 16) __cluster_dims__(16,1,1)`; nvcc 12.9 / 13.0 emit both `.reqnctapercluster` and `.maxclusterrank` for that pair and their ptxas rejects the combination ("Conflicting directives"), while nvcc / NVRTC 13.4 drop the redundant `.maxclusterrank` (which is why the CUDA 13.4 jobs and the source-side gates passed). The regenerated `lc16` programs (both architectures, both `block_m`) drop the third launch-bound operand -- `__cluster_dims__` already fixes the cluster rank -- and differ from the previous programs in that one attribute line only; the other 18 module records are byte-identical. Source-side evidence (Cake `cdb62e8c654`): exact43 43/43 metadata cases on both architectures; paired cold-L2 CUPTI 28 rows vs the previous programs M16 GB300 1.0057 / 1.0273, B200 0.9947 / 0.9695 in run 1 and 1.0000 / 1.0002 in the M16-only run 2, with the 26 identical-code control rows scattering 0.95-1.035 (`previous / new`); NVRTC-cubin SASS of `lc16` identical (2904 SASS lines, 0 differing) and the cubins byte-identical under CUDA 13.3, both architectures, both `block_m`; export receipts 28/28 rows per architecture (bitwise source/export parity, grid parity), identical 32,408-byte patch from both architectures; FlashInfer pytest / bench / pre-commit 44 passed on each GPU; bench `--cupti --rounds 5` vs SGLang 28/28 rows > 1 (GB300 geomean 2.041, B200 1.727); all pre-commit hooks pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Expanded Kimi K3 fused-router support across additional batch sizes, including single-token and 2,048-token workloads. * Added optimized routing options for different workload sizes and supported GPU architectures. * **Improvements** * Improved top-expert selection and route gathering, with more efficient handling of routing outputs. * Distributed expert metadata generation across processing units to improve handling of larger workloads. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Yingyi Huang <averyh@nvidia.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Motivation
Port the standalone kernels from the Kimi K3 Day0 work to
mainfirst. Keepingthe kernels and their direct tests separate makes the reusable pieces reviewable
before the more invasive model, scheduler, and serving integration changes.
Modifications
K3.
NVIDIA/PTX prefill implementations.
attention residuals, MTP, and image preprocessing.
sequence-parallel collective kernels.
B200 compute, B200 collective, and Blackwell prefill correctness.
changes; keep model, scheduler, speculative decoding, multimodal, and serving
runtime integration out of this PR.
Accuracy Tests
pre-commit run --all-filesgit diff --checkpython3 -m compileall -q python/sglang/kernels test/registered/kernels test/registered/jitcovers MLA BF16/FP8 scatter and concat, generic prerequisite kernels, and the
representative ReplaySSM ring parity path.
through their architecture guards.
the B200/GB300 CI jobs. They were not run locally because no Blackwell machine
was reachable; H100 results are not presented as Blackwell validation.
Speed Tests and Profiling
Not applicable to this extraction. It does not enable the Kimi K3 serving path,
and no benchmark files are added. Performance validation belongs with the
follow-up runtime integration after the Blackwell kernel jobs pass.
Checklist
this PR does not enable a serving path.
Review and Merge Process
architecture-specific acceptance matrix.
CI States
Latest PR Test (Base): ❌ Run #30679474553
Latest PR Test (Extra): ✅ Run #30679474497