Repository navigation
[Cake 10/13] moe_grouped_gemm: ragged BF16 MoE grouped GEMM, det and fast - #10
Merged
Zhichenzzz merged 114 commits intoNov 5, 2026
Merged
Conversation
This was referenced Nov 4, 2026
Collaborator
Review of layer 10 (moe_grouped_gemm) — suggest approve with nitsConfirmed on GPU
Cleared on GPU
Verified on two GB300 nodes (sm_103a), CUDA 13.0, torch 2.14.1+cu130, at the stack head |
Vendor FlashInfer's two Cake short-conv packages at FlashInfer
c5db8d3ccd3fa3842812280f7f208718dc0b8610 into miles_ext_kernels/cake/_vendor/, one level deep:
- cake_causal_conv1d_det_train_backend (flashinfer/cake_causal_conv1d_det_train_backend,
87 files): det, fixed-order weight-gradient reduction, with its context-parallel route
- cake_causal_conv1d_train_backend (flashinfer/experimental/cake/cake_causal_conv1d_train_backend,
75 files): fast, unordered weight-gradient reduction, with its context-parallel route
Both kcp.py modules read one environment variable, a private test hook of the package; the manifest
lists them in allow.env_read. Every file is byte for byte FlashInfer's.
Command (from the repository root, a FlashInfer clone next to it):
python tools/vendor_cake.py fi --checkout ../flashinfer \
--commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 --op conv1d \
--package flashinfer/cake_causal_conv1d_det_train_backend \
--package flashinfer/experimental/cake/cake_causal_conv1d_train_backend \
--allow-env-read cake_causal_conv1d_det_train_backend:kcp.py \
--allow-env-read cake_causal_conv1d_train_backend:kcp.py
Co-Authored-By: Claude <noreply@anthropic.com>
The two Cake MoE grouped GEMM packages of FlashInfer
c5db8d3ccd3fa3842812280f7f208718dc0b8610, byte for byte, one level deep
under miles_ext_kernels/cake/_vendor/ and recorded under
ops.moe_grouped_gemm of _vendor/VENDORED.json:
cake_moe_grouped_det_gemm det flashinfer/experimental/cake_moe_grouped_det_gemm
cake_moe_grouped_gemm fast flashinfer/experimental/cake/cake_moe_grouped_gemm
Every blob and both Cake revisions are the ones the previous layout
vendored from the same commit. No vendored module reads the environment
or imports flashinfer, so neither package has an allow entry.
Vendored with:
python tools/vendor_cake.py fi --checkout ../flashinfer \
--commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 --op moe_grouped_gemm \
--package flashinfer/experimental/cake_moe_grouped_det_gemm \
--package flashinfer/experimental/cake/cake_moe_grouped_gemm
Co-Authored-By: Claude <noreply@anthropic.com>
Vendor FlashInfer's Cake packed causal GQA training attention package
(head dim 256, SM100a / SM103a / SM107a) from flashinfer-ai/flashinfer
c5db8d3ccd3fa3842812280f7f208718dc0b8610
flashinfer/experimental/cake_gqa_train
byte for byte into miles_ext_kernels/cake/_vendor/cake_gqa_train/, and
record it as the gqa op's block of _vendor/VENDORED.json, with
python tools/vendor_cake.py fi --checkout ../flashinfer \
--commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 --op gqa \
--package flashinfer/experimental/cake_gqa_train
run in this repository with a FlashInfer checkout of that commit next to
it. The package has 21 files; their git blobs and its Cake revision
(7ab5de0c9d4e42a367ba3e9e218d98208f9bd8fa) are the ones it had before
this layout, from the same FlashInfer commit. Nothing outside _vendor/
changes; the op that loads the package comes in the next commit.
Co-Authored-By: Claude <noreply@anthropic.com>
miles_ext_kernels.cake.moe_grouped_gemm declares the op and imports
nothing but the runtime's spec: det and fast, selected by
--moe-grouped-gemm-backend cake (which requires --train-backend
torchtitan), the two vendored packages, and the entry points
grouped_mm, check_grouped_dims and max_grouped_rows. The implementation
lives in cake/_impl/moe_grouped_gemm/ and builds its libraries through
the runtime's loader.
Changes from the previous layout:
- grouped_mm reads the configured variant at every forward, and its
backward runs the package of its forward. The variant= argument, the
module-level VARIANT and the op's own configure are gone: miles
configures the process with miles_ext_kernels.configure(...). A
process that never configured the op runs det; miles' command line
still defaults to fast. A fast call under
torch.use_deterministic_algorithms(True) runs det and logs it once,
as before: the fast weight gradient raises torch's alert there.
- The 32-bit limits are checked before anything runs. The kernels
address each output with 32-bit element offsets: sum_m * N,
sum_m * K and E * N * K must stay at most 2**31 - 1. grouped_mm checks
all three at the forward call, so a call whose input gradient would
overflow fails there instead of in the backward after the forward ran
(at N = 4096, K = 7168, 300,000 rows used to pass the forward).
check_grouped_dims takes num_groups and max_rows, so the model build
can refuse a configuration whose most rows per call exceed the bound,
and max_grouped_rows(N, K) names the bound (299,593 rows at N = 4096,
K = 7168).
- prebuild("moe_grouped_gemm") builds both variants' programs for the
device (no variant argument, no model parameter) and, on sm_100a and
sm_103a, the input gradient's probe of the memory domains, which the
first probed call used to compile: after it no call compiles.
- release() drops the workspaces both packages keep per device and
stream, with the queue splits probed on them.
- An unsupported device raises NotImplementedError (was RuntimeError).
README: the op's rows in the Ops and Prebuild tables.
Co-Authored-By: Claude <noreply@anthropic.com>
The three FlashInfer Cake mHC training packages at c5db8d3ccd3fa3842812280f7f208718dc0b8610,
byte for byte, each one level under miles_ext_kernels/cake/_vendor/ and in the mhc block of
VENDORED.json:
- cake_mhc_det_train_backend (flashinfer/cake_mhc_det_train_backend): det, the forward and a
backward whose parameter gradients are fixed-order reductions; 221 files.
- cake_mhc_train_backend (flashinfer/experimental/cake/cake_mhc_train_backend): fast, copies of
the det forward programs and a backward that adds the dfn partials with FP32 atomics; 181 files.
- cake_mhc_det_train_head_backend (flashinfer/cake_mhc_det_train_head_backend): the model head's
stream mean and its gradient; 12 files.
The package trees are the ones this op shipped before the layout change (same git trees). No
vendored module reads the environment or imports flashinfer, so the entries list no allow hooks.
Vendored by running, in this repository with a FlashInfer checkout of that commit next to it:
python tools/vendor_cake.py fi --checkout ../flashinfer \
--commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 --op mhc \
--package flashinfer/cake_mhc_det_train_backend \
--package flashinfer/cake_mhc_det_train_head_backend \
--package flashinfer/experimental/cake/cake_mhc_train_backend
Co-Authored-By: Claude <noreply@anthropic.com>
miles_ext_kernels.cake.mhc declares the op (torch-free): variants det and fast, selected by
--mhc-backend cake, --cake-mhc-variant, the three vendored packages, and the entry points
mhc_prepare, mhc_finish, mhc_head, is_supported, supported_hidden_sizes, HIDDEN_SIZES and STREAMS,
loaded from miles_ext_kernels.cake._impl.mhc on first use. Its validate rejects a --hidden-size
the kernels do not run.
miles_ext_kernels.cake._impl.mhc.ops routes a connection to the det or the fast package by the
variant configured when mhc_prepare runs; the handle carries it to mhc_finish and the backward.
Changes in behaviour:
- A process that never configured mhc runs det, and configure(deterministic_mode=True) without a
variant runs det (it ran fast). An explicit fast in deterministic mode is rejected.
- The variant comes from the configuration only: torch.use_deterministic_algorithms(True) no longer
turns a fast call into det; the fast backward then raises torch's alert for an operation without
a deterministic implementation. The per-call variant argument is gone.
- prebuild("mhc", *, device=None, max_workers=None, hidden, sinkhorn_iters) takes both model
parameters, without defaults, and builds every library of both variants and the head at that
shape. A Sinkhorn count other than 20 runs run-time-count programs of the coefficient and
token-owner passes, which a prebuild at the default count left to compile inside the first call.
The epsilons are launch arguments and select no library.
- BF16 fn, scale and base (FSDP2 with param_dtype=torch.bfloat16) are cast to FP32 at the call,
which is exact; their gradients return in BF16. They raised ValueError.
- Workspaces come from the runtime's workspace pool: each launch of the forward, the statistics
replay and the backward takes a buffer when it runs (roles det-fwd, det-bwd, fast-fwd, fast-bwd,
sized for the call's power-of-two token-count bucket), so they stay under
--cake-workspace-pool-bytes and miles_ext_kernels.release() frees them. The packages' own
per-device default workspaces, which lived for the whole process, are no longer allocated.
- release() drops the launch plans and the loaded libraries; the next call reloads the libraries
from the cache.
The packages resolve every launch workspace through one function, which the op replaces with the
pooled one. The kernels' work tickets, the first 256 bytes of every workspace layout, must be zero
before a launch, and the content of a workspace is undefined: every launch zeroes them first (256
bytes on the stream of the call), from the pool or not.
Co-Authored-By: Claude <noreply@anthropic.com>
miles_ext_kernels.cake.gqa declares the op (torch-free) and
miles_ext_kernels.cake._impl.gqa runs FlashInfer's cake_gqa_train
package through the runtime's loader: varlen_gqa_attention, packed
causal GQA attention over variable-length segments, BF16 q
[T_q, H_q, 256] and k / v [T_k, H_kv, 256] with an even number of query
heads per key/value head, forward and backward, on SM100a, SM103a and
SM107a. miles selects it with --full-attention-backend cake.
Variants. The op ships one variant, fast: the output, the LSE and dK /
dV repeat bit for bit run to run, dQ is summed with FP32 atomic adds.
There is no det variant, so the runtime rejects the op under
--deterministic-mode, and a process configured with
deterministic_mode=True raises at the call. A process that never
configured the op runs fast.
max_seqlen_k is now a required keyword-only host int and a documented
hard contract: at least every segment's key count, as FlashAttention's.
It sizes the backward grid, so a smaller value leaves key blocks out of
the backward and gives wrong dQ / dK / dV without an error, while the
output is unaffected. The kernels never read the boundaries on the
host; the new option --cake-gqa-check-seqlens (configure
cake_gqa_check_seqlens=True) checks at every call that cu_seqlens_q /
cu_seqlens_k start at 0, never decrease and end at the token counts,
and that no segment has more keys than max_seqlen_k, at the price of
one device synchronization per call. It is off by default.
The implicit None bound is gone: it sized the backward grid by T_k,
which is correct but launches CTA pairs that only retire, and made the
backward slower the more segments a call packs. A caller without the
longest segment can still pass T_k explicitly. The output, the LSE and
dK / dV do not depend on the bound given.
check_supported(num_heads_q, num_heads_kv, head_dim=256) and
is_supported(...) tell, without torch, whether the kernels run a head
layout. validate_args applies them to the heads one tensor-parallel
rank of miles holds (num_attention_heads / TP query heads over
max(num_query_groups, TP) / TP key/value heads, head dim from
--kv-channels or hidden_size / num_attention_heads), so a layout such as
24 query heads over 4 groups at TP 8 (3 query heads over 1 key/value
head per rank) is rejected at startup rather than at the first forward.
Memory. Each backward takes its scratch from the runtime's workspace
pool on the current stream, and each forward zeroes a fresh tile
counter; nothing goes through the package's per-stream default
workspaces, which grow to the largest call and live as long as the
process. release() therefore frees all of it, and calls on two streams
never share a buffer. prebuild("gqa") builds the four libraries of the
forward and the BF16 dK / dV backward for the device's architecture; it
takes no model parameter.
The op's names move from miles_ext_kernels.gqa:
SUPPORTED_CAPABILITIES, device_arch and prebuild are the runtime's
ARCH_BY_CAPABILITY, device_arch and prebuild("gqa"). The README lists
the op in its Ops and Prebuild tables.
Co-Authored-By: Claude <noreply@anthropic.com>
tests/cake/gqa/, CPU: - test_arguments.py (torch-free): the op registers one variant and no variant flag; --cake-gqa-check-seqlens is off by default and needs --full-attention-backend cake; --deterministic-mode (also through the typed configuration) rejects the op; check_supported / is_supported on head layouts; validate_args on the heads of one tensor-parallel rank (24 query heads over 4 groups at TP 8 and 8 over 8 at TP 8 rejected, 16 over 4 at TP 8 accepted, head dim from --kv-channels or hidden_size / num_attention_heads); configure and option. - test_gqa_cpu_plan.py (torch-free): every stage the entry point launches has one library per architecture, which the oldest toolkit the runtime accepts for it (MIN_CUDA) builds; the grids cover every tile; the backward grid covers the key blocks of max_seqlen_k and no more; the scratch layout. - test_gqa_host.py (torch on the CPU): deterministic mode raises at the call and configure() lets the next call through; max_seqlen_k is a required host int; check_seqlens accepts the contract and rejects a bound below the longest segment and malformed boundaries; the option runs the check before any launch; with the pool on, the backward scratch is one pooled buffer in the package's layout, and at the default bound, 0, each backward takes a buffer of its own that the pool does not keep; prebuild takes no model parameter, raises NotImplementedError on another GPU and RuntimeError when the toolkit is older than CUDA 13.4 on SM107a. GPU (skip without SM100a / SM103a / SM107a): - test_gqa_gpu.py: FlashInfer's suite layouts and miles' head pairs against an FP64 reference (assert_rel_rms) within FlashInfer's budget capped at twice the error measured on a B200, noted next to it: FlashInfer's budget for out, dQ, dK and dV (1.08 to 1.12 times the measured error), twice the measured error for the LSE, whose errors are at FP32 rounding; out, LSE, dK and dV bit for bit run to run in one process and against a new process, for any valid max_seqlen_k (exact, larger, T_k), on alternating default and side streams, and pooled against the pool turned off; dQ within its FP32 reordering; the check rejects max_seqlen_k 1024, 1920 and 2047 on 2048-key segments and changes no bit when valid; document isolation; checkpointing; no device synchronization in steady state (and one with the check); with the pool on, the pooled scratch and release(); rejected operands; two GPUs give the same bits (multi_gpu). - test_prebuild_gpu.py: ROUTES for every call-time path (exact and T_k max_seqlen_k, groups 2 / 4 / 6 / 8, keys fewer than queries, empty segments, long keys, one segment, no query, no key, a softmax scale, the check option, a side stream, the pool on) after one prebuild under no_build(); prebuild returns the four libraries; a fresh process loads only those and never imports flashinfer. Co-Authored-By: Claude <noreply@anthropic.com>
CPU (no GPU): the arguments and the variant rules through the hooks miles calls, deterministic mode without a variant included; the vendored packages against the routes, constants and hidden sizes the op relies on; what prebuild builds per Sinkhorn count, checked against the programs the host rules select at every token count; BF16 parameters reaching the kernels as FP32; the variant read at each call; with the pool on, every launch workspace taken from the pool, its tickets zeroed at every launch, and at the default bound, 0, none kept; what release drops. GPU: det and fast against an FP64 reference at every hidden size the device serves, the fast outputs against det's bits, another Sinkhorn count and other epsilons, BF16 parameters, det bit for bit in a new process, on a side stream and on a second GPU (multi_gpu), with the pool on: the bits under any pool bound, from a buffer a larger call sized and with interleaved connections, release; the head, and test_prebuild_gpu.py: ROUTES over every call-time switch at the Sinkhorn count 7, checked from an empty library cache. Co-Authored-By: Claude <noreply@anthropic.com>
miles_ext_kernels/cake/conv1d.py declares the op: variants det and fast, selected by
--linear-attention-conv-backend cake, the option --cake-conv1d-silu-grad-preactivation, the two
vendored packages and the entry points causal_conv1d, causal_conv1d_kcp, causal_conv1d_kcp_multi and
check_supported. miles_ext_kernels/cake/_impl/conv1d/ implements them on the runtime's loader, config
and prebuild. The op is appended to OPS and to the README's Ops and Prebuild tables.
Changes in behavior:
- A process that never configured the op runs det; it ran fast. miles' command line still defaults
to fast: validate_args resolves --cake-conv1d-variant to fast and writes it back for configure.
- Each call reads its variant and SiLU' policy when it runs; the backward runs those of its forward.
- The entry points take no variant argument: only configure selects the variant, so a process
configured with deterministic_mode runs det on every call. A call could pass variant="fast" before.
- --cake-conv1d-silu-grad-preactivation defaults to bf16; it had no default. Without
--linear-attention-conv-backend cake, an explicit bf16 is now accepted (it raised before); any
other value still requires the cake backend.
- causal_conv1d without cu_seqlens builds the prefix sums of its one document on the device. It copied
them from pageable host memory before, which synchronized the host on every call.
- check_supported(..., has_bias=True) raises: the entry points take no bias, so a biased layer
would lose its bias.
- prebuild("conv1d", weight_dtype=...) builds every library of both variants, both SiLU' policies and
the context-parallel route. It replaces prebuild(dtype) and prebuild_kcp().
- release() drops the packages' host plans.
Co-Authored-By: Claude <noreply@anthropic.com>
Move the conv1d tests to tests/cake/conv1d/ and run them through the runtime (configure, variant, prebuild, release). - CPU: the arguments and the configuration (det unconfigured, fast from miles' command line, an explicit default SiLU' policy without the cake backend), the variant and SiLU' policy read at each call, no entry point taking a variant (det on every call under deterministic_mode), the one-document cu_seqlens built on the device, the rejected bias, prebuild's arguments and its libraries against the packages' program selection on sm_100a, sm_103a and sm_107a, release, the vendored packages and their loading without flashinfer. - GPU: the accuracy and determinism tests as before, each variant selected with configure, and new ones: det bitwise in a new process, cu_seqlens=None without a host synchronization or a wait for queued work, calls on a side stream interleaved with the default stream, an unconfigured process running det, and test_prebuild_gpu.py, whose ROUTES cover every call-time path and run under no_build() after one prebuild, for each taps dtype in a new process with an empty library cache of its own. - The multi-GPU context-parallel test is marked multi_gpu. Co-Authored-By: Claude <noreply@anthropic.com>
tests/cake/moe_grouped_gemm/: - test_arguments.py (CPU, torch-free): the variant flag through miles' hooks, deterministic mode, the torchtitan requirement, configure, and an import without torch. - test_limits.py (CPU, torch): the 32-bit bounds of check_grouped_dims and max_grouped_rows, and grouped_mm refusing at the forward, on meta tensors, a call whose input gradient or weight gradient would overflow. - test_moe_grouped_gemm_gpu.py (GPU): every output and gradient against a per-group FP32 reference with relative-RMS budgets that fail at a 2 % error (assert_close(atol=rtol=1e-2) passed y scaled by 1.05); det repeating bit for bit in one process and in a new one; fast giving det's forward and input-gradient bits and its unordered weight-gradient tail within the budget; the variant read at each call; det under torch's deterministic algorithms; calls alternating between the default and a side stream; release(); the 32-bit row bound at N = 4096, K = 7168 (300,000 rows refused at the forward; at 299,593 rows the last 2,048 rows of the output and of the input gradient and the whole weight gradient within the budget against closed-form FP64 references); empty input; padding rows past offs[-1]; refused calls; the toolkit check; a call for a device other than the current one (multi_gpu). - test_prebuild_gpu.py (GPU): ROUTES (every layer shape, empty experts, no rows, uneven routing over a sweep of row counts, padding rows, the 512-wide k tile, the stage twins at many rows per expert, a side stream) run with every variant after one prebuild, with no nvcc and no inline tvm-ffi build; a variant configured after a call runs without a build; every program the planners bind is prebuilt. Co-Authored-By: Claude <noreply@anthropic.com>
The four Cake KDA training packages of FlashInfer 7a13d7ba9b75c9bb9c02452df36e3f34417b5d57, byte for byte, one
level deep under miles_ext_kernels/cake/_vendor/ and recorded under ops.kda of VENDORED.json:
- cake_kda_det_train (flashinfer/experimental/cake_kda_det_train): det, single rank.
- cake_kda_det_train_kcp (flashinfer/experimental/cake_kda_det_train_kcp): det, context parallel.
- cake_kda_train (flashinfer/experimental/cake/cake_kda_train): fast (deterministic=False), single rank.
- cake_kda_train_kcp (flashinfer/experimental/cake_kda_train_kcp): fast, context parallel.
One FlashInfer commit for the family, as the context-parallel packages import their single-rank siblings
(..cake_kda_det_train and ..cake.cake_kda_train; the runtime resolves the second to _vendor.cake_kda_train).
7a13d7ba rather than c5db8d3c: it carries FlashInfer #8629, a correctness fix of the det single-rank package (with
the in-kernel q / k L2 norm, its replay backward over a batched arena, KdaDetArena(batch_tokens=...), read up to 63
rows of the next document group before they were written), and the other changes of these packages since c5db8d3c:
#8552, #8620, #8625, #8630 and #8652 (performance; #8625 runs the fused L2 norm's GLM intra stage with another
program on SM103a, #8620 sums the fast packages' raw-gate parameter gradients in FP64 on SM100a / SM103a). The entry
points of this repository pass no arena, so #8629 does not change their results. The later commits up to FlashInfer
main change none of the four directories.
cake_kda_train/cake_backend.py keeps two FlashInfer hooks, listed under allow: it reads CAKE_KDA_CKPT_HANDOFF
(the checkpoint hand-off override of its chunk_kda), and _jit_nvcc_version imports flashinfer.jit.cpp_ext inside
its body to name the toolkit of the libraries; the kda implementation rebinds that function to the runtime loader's
nvcc when it is imported.
Vendored with, from the root of this checkout next to a FlashInfer clone at ../flashinfer:
python tools/vendor_cake.py fi --checkout ../flashinfer --commit 7a13d7ba9b75c9bb9c02452df36e3f34417b5d57 \
--op kda \
--package flashinfer/experimental/cake_kda_det_train \
--package flashinfer/experimental/cake_kda_det_train_kcp \
--package flashinfer/experimental/cake/cake_kda_train \
--package flashinfer/experimental/cake_kda_train_kcp \
--allow-env-read cake_kda_train:cake_backend.py \
--allow-flashinfer-import cake_kda_train:cake_backend.py:_jit_nvcc_version
Co-Authored-By: Claude <noreply@anthropic.com>
FlashInfer c5db8d3ccd3fa3842812280f7f208718dc0b8610: the SiLU-gated RMSNorm training packages, deterministic
(cake_rmsnorm_silu_gate_det_train_backend, 14 files) and non-deterministic (cake_rmsnorm_silu_gate_train_backend,
30 files), each one level deep under miles_ext_kernels/cake/_vendor/, and their block ops.gated_rmsnorm in
_vendor/VENDORED.json. Written from a FlashInfer checkout by:
python tools/vendor_cake.py fi --checkout ../flashinfer --commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 \
--op gated_rmsnorm --package flashinfer/cake_rmsnorm_silu_gate_det_train_backend \
--package flashinfer/cake_rmsnorm_silu_gate_train_backend
Co-Authored-By: Claude <noreply@anthropic.com>
Vendor FlashInfer's Cake RMSNorm training packages of flashinfer-ai/flashinfer eb3eb68d7deebbe619e05d93ecfd9796e4eaf2e3 into miles_ext_kernels/cake/_vendor/, byte for byte, as the packages of the op rmsnorm in _vendor/VENDORED.json: - cake_rmsnorm_det_train_backend (flashinfer/cake_rmsnorm_det_train_backend, 582 files): the deterministic forward and backward. - cake_rmsnorm_train_backend (flashinfer/cake_rmsnorm_train_backend, 581 files): the backward with a non-deterministic weight gradient in some token-count windows; it runs its deterministic sibling for deterministic=True, so the two packages move together. eb3eb68d includes flashinfer-ai/flashinfer#8512: on SM100a and SM103a the deterministic backward of the smallest token counts sums each weight-gradient column in a fixed row order, so its bits no longer depend on the SM count. The files and their blobs are the ones the previous rmsnorm package vendored from the same commit. python tools/vendor_cake.py fi --checkout ../flashinfer --commit eb3eb68d7deebbe619e05d93ecfd9796e4eaf2e3 \ --op rmsnorm --package flashinfer/cake_rmsnorm_det_train_backend \ --package flashinfer/cake_rmsnorm_train_backend Co-Authored-By: Claude <noreply@anthropic.com>
Vendor the DSA kernel packages into miles_ext_kernels/cake/_vendor/ with tools/vendor_cake.py, byte for byte, and record them under ops.dsa of VENDORED.json: - cake_dsa_train: FlashInfer's DSA training kernels for 64 local query heads (the fast variant: dKV and dk_rope accumulate in FP32 with red.global). - cake_dsa_indexer_det: FlashInfer's lightning-indexer scores and deterministic exact top-k. - cake_dsa_sparse_det: the DSA det version, the generated sparse attention forward / backward and lightning-indexer logits / backward kernels of radixark/miles#3599 (86 sources and registry.json; no floating-point atomics, key-side gradients reduced from FP32 partials in a fixed 4-way tree). The tool writes the TVM-FFI host surface in place of the upstream torch-extension one and a build.json generated from the registry, so these kernels build with nvcc and load with tvm-ffi through the same loader as the FlashInfer packages; SM107a compiles the SM103a records. Both FlashInfer packages come from c5db8d3, the commit these kernels were validated at: cake_dsa_train is byte for byte the same at 7a13d7ba, and cake_dsa_indexer_det differs there only by a later performance change (flashinfer-ai/flashinfer#8691). Commands (run from the repository root, with a FlashInfer checkout at ../flashinfer and a miles checkout at ../miles): python tools/vendor_cake.py fi --checkout ../flashinfer \ --commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 --op dsa \ --package flashinfer/experimental/cake_dsa_train \ --package flashinfer/experimental/cake_dsa_indexer_det python tools/vendor_cake.py miles-pr --checkout ../miles \ --pr 3599 --commit a1b763c82360afeb00c551aeb7ea93b30870e5bf \ --op dsa --name cake_dsa_sparse_det \ --path miles/kernels/attention/dsa/cake \ --include csrc --include registry.json \ --exclude csrc/cake_host_shim.h --arch-from sm_107a=sm_103a Co-Authored-By: Claude <noreply@anthropic.com>
The KDA op of the cake namespace: miles_ext_kernels/cake/kda.py declares it (OpSpec, selected by --kda-backend
cake), miles_ext_kernels/cake/_impl/kda/ implements it on the four vendored packages, OPS gains "kda" and the
README its Ops and Prebuild rows. The entry points are those of the kda family before this layout: chunk_kda,
chunk_kda_fwd, chunk_kda_bwd, and partition_kcp, build_context_kcp, chunk_kda_kcp, chunk_kda_fwd_kcp,
chunk_kda_bwd_kcp, reduce_gate_param_grads_kcp. Each call reads config.variant("kda") when it runs: det unless
configured.
Variants. det (cake_kda_det_train, cake_kda_det_train_kcp) repeats bit for bit. fast (cake_kda_train,
cake_kda_train_kcp, deterministic=False) runs the S1 gate-gradient programs on some backward rows of the activated
gate mode and, in the raw gate mode on SM100a / SM103a, reduces the gate parameters' gradients in FP64 in no fixed
order. fast has no in-kernel q / k L2 norm: such a call runs det and logs it once (config.warn_runs_det). Every
miles call takes that L2 norm, so cli_variants is ("det",): --cake-kda-variant is registered hidden, validate_args
writes det back for every process, and a fast choice from miles, which would change no program, is refused by
argparse. Direct callers configure either variant. A backward reads its saved set's package (a saved set is read by
the package that wrote it); saved=None replays the forward in the package of the call. Every call hands its package
use_qk_l2norm_in_kernel as given, so the backward of a fast saved set asked for the L2 norm, a mode its forward did
not run, is the fast package's ValueError rather than a gradient without the norm. Partitions and contexts hold no
variant.
Operands as miles hands them over. beta may be FP32 (miles gives flash-linear-attention the FP32 sigmoid) and v a
column slice of the packed projection: chunk_kda* round beta to BF16 and make v dense before the packages' checks,
inside the autograd graph, and return dbeta in beta's dtype. Before, both raised ValueError in the packages.
Prebuild. prebuild(num_heads=...) checks 1 <= num_heads <= 64 (an int, not a bool) before anything else and then
builds every library of the four packages for the device's architecture (188 / 186 / 189 on SM100a / SM103a /
SM107a): the record a launch takes depends on its token and document counts, its modes, its path and, under context
parallelism, the rank's shard, so no smaller set holds for every call of a layer. This replaces the family's
route_libraries probe, which planned a fixed list of problems and dropped every problem it could not plan (an
unknown gate mode gave an empty list, not an error). release() drops the packages' per-stream entry pools and the
captured graphs of a loaded context-parallel package.
The fast package's _jit_nvcc_version hook (its checkpoint hand-off keys row records on the toolkit) is rebound to
the runtime loader's nvcc when the implementation is imported: flashinfer is never imported. --kda-backend cake
rejects context parallelism (--context-parallel-size > 1) and a layout other than thd at startup, as before.
Co-Authored-By: Claude <noreply@anthropic.com>
The output norm of miles' GDN layers, RMSNorm(x) * weight * SiLU(z) over rows of 128, becomes the op
miles_ext_kernels.cake.gated_rmsnorm: OP declares its two variants, the miles selector
--linear-attention-output-norm-backend cake, its two vendored packages and the entry points rmsnorm_silu_gate and
check_supported; miles_ext_kernels.cake._impl.gated_rmsnorm implements them.
- A call reads the configured variant when its forward runs, and its backward runs the same package. A process that
never configured the op runs det (it ran fast); validate_args still resolves miles' command-line default to fast,
and deterministic mode resolves det and rejects an explicit fast. While torch.use_deterministic_algorithms(True)
is in effect, which the non-deterministic backward refuses, fast runs det and logs it once.
- prebuild(device=None, max_workers=None), reached through cake.prebuild("gated_rmsnorm"), builds every library of
both packages on the device's architecture, every row-count route of each, so no call compiles afterwards.
release() has nothing to drop.
- rmsnorm_silu_gate documents when a gate that is a column slice of a wider projection is read in place, and that
the weight gradient sums over the rows of one call: under tensor parallelism the caller all-reduces it over the
tensor-parallel group.
- registry.OPS lists gated_rmsnorm; the README's Ops and Prebuild tables list its rows.
Gone with the old package: the module-level VARIANT, the op's own arguments module and configure, the per-call
variant keyword, and SUPPORTED_CAPABILITIES (miles_ext_kernels.cake.ARCH_BY_CAPABILITY names the same devices).
Co-Authored-By: Claude <noreply@anthropic.com>
miles_ext_kernels.cake.rmsnorm declares the op: selected by --linear-attention-input-norm cake, variants det and fast
(--cake-rmsnorm-variant), the two vendored packages. miles_ext_kernels.cake._impl.rmsnorm implements it:
ZeroCenteredRMSNorm, zero_centered_rmsnorm, check_supported, supported_hidden_sizes, prebuild(hidden_size=...) and
release. The op is appended to OPS and to the README's Ops and Prebuild tables.
Changes from the previous rmsnorm package:
- A call reads the configured variant when it runs (det in a process that never configured the op), and its backward
runs the variant of its forward. The module no longer reads or pins a variant at construction; its variant=
argument is gone, configure is the one switch.
- Constructing the module compiles nothing. prebuild("rmsnorm", hidden_size=H) builds every library of both variants
for H (forward and backward, every token-count window), so a variant configured after a module was built runs at
its next call without a build.
- The weight is allocated where torch's factory functions allocate (a with torch.device("meta") block,
torch.set_default_device), else on the current CUDA device; device="meta" builds without touching CUDA. The device is
checked when it is a CUDA device; a hidden size the kernels do not serve is refused on any device.
- reset_parameters() zeroes the weight, also after to_empty().
- sequence_parallel= (default None) is set on the weight when given, at construction and by reset_parameters, as
Transformer Engine's norms set it, so Megatron all-reduces its gradient across the tensor-parallel ranks under
sequence parallelism. None leaves the attribute to the caller: reset_parameters keeps a mark set on the weight.
- An unsupported device raises NotImplementedError (was ValueError), as the runtime's device_arch does.
- The package's configure, prebuild(hidden, device, variant=) and SUPPORTED_CAPABILITIES are replaced by
miles_ext_kernels.configure, miles_ext_kernels.cake.prebuild("rmsnorm", hidden_size=...) and
miles_ext_kernels.cake.ARCH_BY_CAPABILITY.
fast still runs det, logged once, under torch.use_deterministic_algorithms(True), which the non-deterministic package
refuses.
On SM107a, hidden size 4096 launches two programs above the 232448 B shared-memory opt-in (271360 B, CUDA 13.4's
oversized shared-memory mode): det's backward reduce above 4096 tokens and its copy in fast's backward from 4097 to
24064 tokens. A process whose CUDA runtime is older than 13.4, such as the 13.0 runtime wheel a cu130 torch installs,
rejects that mode at the launch ("cudaFuncSetAttribute failed: invalid argument"), and the error it leaves pending
fails the next CUDA call too. The op now checks the loaded runtime (libcudart of the toolkit's major) and the
driver's oversized ceiling for every program of the width that sets the mode, at construction on a CUDA device,
check_supported, prebuild and the first call of a width on a device, and raises RuntimeError naming the programs,
the runtime and the remedy before any launch. Other widths and SM100a / SM103a launch no such program. The README's
Ops and Prebuild rows state the limit.
Co-Authored-By: Claude <noreply@anthropic.com>
Add the DSA op: miles_ext_kernels/cake/dsa.py (its OpSpec, variants det and fast, --cake-dsa-bwd-workspace-bytes and --cake-dsa-lazy-build) and its implementation under miles_ext_kernels/cake/_impl/dsa/: - ops.py: sparse_attention, the lightning indexer, the det forward and backward entry points, prebuild and release. The variant is read when a call runs; a call the fast variant does not serve (other than 64 heads, a sink, another d_qk) runs det and logs it once per reason. - native.py: the DSA det version (cake_dsa_sparse_det) built and loaded through the runtime loader, one library per stage and architecture. Every argument binds by its exported name; a TMA source binds as a tensor whose descriptor the stage's launcher encodes on the host. - kernels.py, reduction.py, layouts.py, indexer_topk.py: the det host side, the fixed-order reduction, the layout adapters and the det indexer top-k (cake_dsa_indexer_det). Register the op in OPS and add its rows to the README's Ops and Prebuild tables. Behaviour, compared with the previous layout: - sm_scale is required: the d_qk ** -0.5 default is not the scale of the absorbed MLA these kernels serve. - A call takes no variant argument; configure() selects it. - A batch-local bshd index outside its batch element's keys is an empty slot; it no longer reads the next element's keys. So is an indexer index past the keys. - --cake-dsa-bwd-workspace-bytes bounds the det backward's partials and the index scratch of their sort (one sort of every chunk when it fits, else one per chunk); the chunking, and with it the det bits, follows from it. - The fast backward receives the packed segment count (num_segments; the batch for bshd) to plan its key passes; the bits do not change. - A call without query rows returns empty outputs and zero gradients, forward and backward, attention and indexer (and the indexer's top-k selection, with or without key rows). - The indexer serves 8, 16, 32 or 64 heads of dimension 128 in its logits, its backward and the selected scores alike; 24 is rejected before any launch. - A selected key with a -inf score (outside its query's window) gets no gradient, even a NaN one: the backward sees it as an empty slot. Co-Authored-By: Claude <noreply@anthropic.com>
tests/cake/kda/: the kda family's tests on the cake layout, with per-tensor accuracy budgets. - test_arguments.py (torch-free): the spec, the hidden --cake-kda-variant (det written back, fast refused), the settings validate_args rejects, configure for direct callers, an import without torch. - test_kda_variant.py (CPU, torch): the package of every entry point under det and fast, the in-kernel L2 norm call (logged once), the backward of a saved set of either package (a fast set asked for the in-kernel L2 norm is the fast package's ValueError), the beta / v seam and its gradients, the head-count check before any package or build runs, the fast toolkit hook, that fast plans programs det does not ship (S1 in the activated gate mode on every architecture, R1c in the raw gate mode on SM100a / SM103a), that prebuild builds the same 188 / 186 / 189 libraries for every head count, and release(). - test_kda_gpu.py: det in miles' call (q / k / v column slices, raw gate, in-kernel L2 norm) against the exact FP64 recurrence, every output and gradient within twice the largest error measured (_budgets.py, one B200 and one GB300): the token tensors in relative RMS error (a 1.02 scale of out, dq, dk, dv or dbeta fails it), dA_log and ddt_bias in error over the root sum of squares of the terms of their exact sums, free of the shape's cancellation; bit-identical repeats, packed views and an FP32 beta against contiguous BF16 operands, autograd / saved set / replay / checkpoint bits, default and side stream bits, a fresh process's bits, and flash-linear-attention parity on every shape (each tensor within twice fla's error on the same inputs). - test_kda_fast_gpu.py: fast against the same reference within twice det's error on the same inputs (no bit checks: fast promises none), its forward / backward split and checkpointed node, saved sets of either package, and the in-kernel L2 norm call that runs det giving det's bits. - test_kda_kcp_gpu.py: the context-parallel entry points on 2 and 4 GPUs, marked multi_gpu, so a one-GPU run deselects it (-m "not multi_gpu") instead of reporting 20 skips; with fewer GPUs than a group needs it skips with that count. - test_prebuild_gpu.py: ROUTES over every call-time switch (gate and L2 norm modes, packed views and FP32 beta, explicit forward and backward, replay, both checkpoints, the segmented route with saved states, 1 to 64 heads, more than 256 documents, a token sweep with empty documents, the context-parallel entry points on a one-rank partition with host cu_seqlens), checked after one prebuild with no build allowed. The elementwise gate the family used (atol = rtol = 1e-2 on every tensor) let a 2 % error of out, dv or dbeta pass; the relative RMS budgets do not, and the elementwise gate is not carried over. dg is exempt from the 1.02 detection check, as its own error is about 1 %. dA_log and ddt_bias are sums over every token of a head. Their relative error is set by how far the terms cancel, which varies with the shape (on one B200 and one GB300, 0.8 % to 29 % for dA_log and 4.1 % to 6.3 % for ddt_bias), and dA_log has one value per head, so a relative budget per shape is loose at one head. Over the root sum of squares of the terms (_reference.gate_param_terms) the error is 0.6 % to 3.3 % for dA_log and 4.7 % to 6.3 % for ddt_bias, its largest at one head; fla's on the same inputs is 1.2 % to 2.8 % and 4.6 % to 5.6 % (the fla parity rows print both). These budgets are twice the largest. Co-Authored-By: Claude <noreply@anthropic.com>
tests/cake/gated_rmsnorm/: - test_arguments.py (CPU): the declaration and its variant flag; an unconfigured process and an unconfigured call run det; miles' resolution (fast by default, det in deterministic mode, explicit values kept, fast rejected in deterministic mode, the flag rejected without the backend); the variant read at every call and det under torch's deterministic algorithms, with the packages replaced by recorders; check_supported's rejections before any device query; import and configuration without torch. - inputs.py: miles' GDN layout. The gate is a column slice of the fused [q | k | v | z | b | a] projection, whose width (2 * Hk * 128 + 2 * Hv * 128 + 2 * Hv, padded to a multiple of 8) is the gate's token stride: the kernels read the gate in place when it is a multiple of 16 elements and copy it otherwise. Every forward + backward checks the documented result types: y BF16 of x's shape, every gradient BF16, dw of shape [128]. - test_gated_rmsnorm_gpu.py: every value-head count (12, 16, 32, 48) with the gate read in place and copied, the path asserted, among them 16 key / 32 value heads (width 12352, in place) and its twin with 8 more columns (12360, copied), at 1 to 32771 tokens: y, dx and dz within one BF16 ulp of FP64 and dw within det's bound, every gate failing a 2 % error; det bitwise three times in one process and once in a new process; a contiguous gate the same bits; fast's y, dx and dz det's bits. Also calls alternating between the default and a side stream, checkpointing, accumulation, a tensor-parallel head split (each rank's dw covers its own heads, their sum is the layer's) and the toolkit check. - test_prebuild_gpu.py: ROUTES (both gate paths at every head count, a 2-D input, a token sweep that reaches every row-count module of the non-deterministic package, checked against the device's SM count, a weight without a gradient, torch's deterministic algorithms) under check_prebuild; prebuild's libraries and idempotence; configure after a call switches the next call without a build; no flashinfer module is imported. Co-Authored-By: Claude <noreply@anthropic.com>
tests/cake/rmsnorm/: - test_arguments.py (torch-free): registration, variant resolution with and without miles' deterministic mode, rejections, configure, det in an unconfigured process. - test_module.py (CPU and meta tensors, the kernels replaced by a recorder): construction on the meta device, reset_parameters after to_empty, the sequence_parallel attribute (none by default; set from the argument; a mark the caller set kept by reset_parameters) and Megatron's selection rule, refusal of widths the kernels do not serve, construction without a build, the variant each call reads, fast running det under torch's deterministic algorithms, the arguments of prebuild (its refusal without a CUDA device in a child process that sees none, so it runs on GPU nodes too); the programs that launch in the oversized shared-memory mode (SM107a at 4096 only) and the refusal of their width, with the CUDA runtime and driver facts replaced (runtime 13.0, none loaded, no oversized ceiling, a ceiling too small). - test_rmsnorm_gpu.py: accuracy against FP64 at FlashInfer's budgets; det bit for bit three times in one process, once in a fresh process, at the smallest token counts and on a side stream (hidden sizes 4096 and 5120); fast's y and dx against det; strided rows, checkpointing, no tokens, a module built on the meta device, the toolkit check, a backward that runs its forward's variant. A width this process cannot launch (SM107a at 4096 without a CUDA 13.4 runtime) is refused instead of run (_refusal.py); the refusal holds exactly where a fresh process with the check disabled fails to launch the width, and with the runtime taken as 13.0 SM107a refuses 4096 at check_supported, prebuild, construction and the first call, while every other width and architecture runs. - test_prebuild_gpu.py: ROUTES (every token-count window of both packages, [T, H] input, strided rows, FP32 weight, no tokens, checkpointing, torch's deterministic algorithms) for every width after one prebuild, with no build allowed (prebuild of a width this process cannot launch raises its refusal); a variant configured after construction runs without a build; no flashinfer module is imported. - test_sequence_parallel_gpu.py (multi_gpu, 2 GPUs): under tensor and sequence parallelism the all-reduce rule selects the weight, and the ranks keep one bit-identical weight that follows the single-GPU run. Co-Authored-By: Claude <noreply@anthropic.com>
Add tests/cake/dsa/: - test_arguments.py, test_package.py, test_dispatch.py and test_indexer_topk.py (CPU): the arguments and configuration, the three packages and the CUDA 13.4 floor of SM107a whatever a package records, the stage selection, the argument binding of every det stage (TMA sources included), the calls fast serves, the required sm_scale, the segment count, the index ranges, the det backward's memory plan, calls without query rows (the indexer forward and backward and its top-k selection included), the indexer head counts and the gradient mask of the selected scores. - test_dsa_gpu.py and test_indexer_topk_gpu.py: accuracy against FP32 and FP64 references at every head count from 4 to 128, det bits repeated in one process, in a fresh process, on a side stream and on a second GPU, the det backward within its workspace budget, and the reviewed limits on a GPU (batch-local and packed indices outside the keys are empty slots). The two-GPU tests are marked multi_gpu. - test_prebuild_gpu.py: ROUTES over every call-time path (each head count from 4 to 128, 24 included; RoPE and NoPE; with and without a sink; thd, bshd and sbhd; no query rows; the indexer and its det top-k), each group checked after one prebuild. Co-Authored-By: Claude <noreply@anthropic.com>
FlashInfer c5db8d3ccd3fa3842812280f7f208718dc0b8610: the dense projection GEMM training packages, deterministic
(dense_projection_det_gemm, 190 files) and non-deterministic (dense_projection_gemm, 30 files), each one level deep
under miles_ext_kernels/cake/_vendor/, and their block ops.dense_gemm in _vendor/VENDORED.json. Both come from one
commit: the non-deterministic package runs its deterministic rows on its sibling. Written from a FlashInfer checkout
by:
python tools/vendor_cake.py fi --checkout ../flashinfer --commit c5db8d3ccd3fa3842812280f7f208718dc0b8610 \
--op dense_gemm --package flashinfer/experimental/dense_projection_det_gemm \
--package flashinfer/experimental/dense_projection_gemm
Co-Authored-By: Claude <noreply@anthropic.com>
Vendor the five packages of the gdn op into miles_ext_kernels/cake/_vendor/ with tools/vendor_cake.py, one level deep, recorded under ops.gdn of _vendor/VENDORED.json: - cake_gdn_det_train, cake_gdn_det_train_kcp, cake_gdn_train and cake_gdn_train_kcp: the FlashInfer Cake packages of flashinfer-ai/flashinfer eb3eb68d7deebbe619e05d93ecfd9796e4eaf2e3, byte for byte (85, 59, 80 and 67 files). This commit carries the fixes of the context-parallel packages, flashinfer-ai/flashinfer#8585 and #8623. - cake_gdn_fla_cp_det: the det kernels with FLA's context-parallel state passing generated for radixark/miles#3597, at 9d7543812b676a5b50dde107518782933b804b48: registry.json and the 56 kernel and launcher sources of miles/kernels/attention/gdn/cake/csrc. The tool adds an empty __init__.py, FlashInfer's loader cake_build.py, the TVM-FFI host surface csrc/cake_host_shim.h in place of the torch-extension one (excluded, its blob recorded) and a build.json generated from the registry, so these sources build with nvcc and load with tvm-ffi like the FlashInfer packages; sm_107a compiles the sm_103a records. The vendored files have the blobs the gdn packages, kernel sources and registry had in the previous layout. Commands, from the repository root, with a FlashInfer checkout at ../flashinfer and a miles checkout at ../miles: python tools/vendor_cake.py fi --checkout ../flashinfer \ --commit eb3eb68d7deebbe619e05d93ecfd9796e4eaf2e3 --op gdn \ --package flashinfer/experimental/cake_gdn_det_train \ --package flashinfer/experimental/cake_gdn_det_train_kcp \ --package flashinfer/experimental/cake/cake_gdn_train \ --package flashinfer/experimental/cake_gdn_train_kcp python tools/vendor_cake.py miles-pr --checkout ../miles --pr 3597 \ --commit 9d7543812b676a5b50dde107518782933b804b48 --op gdn \ --name cake_gdn_fla_cp_det --path miles/kernels/attention/gdn/cake \ --include csrc --include registry.json \ --exclude csrc/cake_host_shim.h --arch-from sm_107a=sm_103a Co-Authored-By: Claude <noreply@anthropic.com>
… not the README The README no longer lists ops in a table; the op module and implementation docstrings already state that neither variant is batch-invariant.
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.
Stack 10/13 · base: main
Summary
This PR adds
miles_ext_kernels.cake.moe_grouped_gemmon the shared cake runtime. Its entry point isgrouped_mm(x, w, offs), the ragged BF16 MoE grouped GEMM with forward and backward. It has the semantics oftorch._grouped_mm(x, w.transpose(-2, -1), offs=offs). The PR vendors two packages from FlashInferc5db8d3:cake_moe_grouped_det_gemm(det): every output is reduced in a fixed order.cake_moe_grouped_gemm(fast): it differs only in the weight gradient with the 512-wide k tile.In miles,
--moe-grouped-gemm-backend cakeselects the op and requires--train-backend torchtitan(radixark/miles#4685, the top of the miles stack). When--cake-moe-grouped-gemm-variant {det,fast}is unset, it resolves tofaston the miles CLI and todetunder deterministic mode. A process that never configured the runtime runsdet.Changes from the previous version of this layer:
variant=argument, the module-levelVARIANTand the op's ownconfigureare removed.prebuildalso builds the input gradient's queue probe.Stack
This is layer 10 of 13, on top of layer 9 (
dense_gemm). The stack was rewritten as[Cake 0/13] layoutplus 13 op layers of three commits each (vendor / op / tests). Every PR targets main, so this diff contains layers 0 to 10. The previous version of this layer was reviewed in #10 ([Cake 10/11] moe_grouped_gemm: ragged MoE grouped GEMM, det and fast).This layer's commits:
220e42bvendor: moe_grouped_gemm packages2edabb2moe_grouped_gemm: the ragged BF16 MoE grouped GEMM on the cake runtime701c627tests: moe_grouped_gemmChanges
From the layer 9 head to the layer 10 head: 112 files, +131,847 / −0. Per commit:
220e42b101 files +130,711;2edabb25 files +278;701c6276 files +858.cake/_vendor/cake_moe_grouped_det_gemm/cake/_vendor/cake_moe_grouped_gemm/cake/_vendor/VENDORED.json(ops.moe_grouped_gemmblock)cake/moe_grouped_gemm.py,cake/_impl/moe_grouped_gemm/{__init__,ops}.pytests/cake/moe_grouped_gemm/There are no
pyproject.tomlor tooling changes.Packages
cake_moe_grouped_det_gemmflashinfer/experimental/cake_moe_grouped_det_gemm(51 files, Cake revision0716c911b456a7fd3ea2e0f668310d8b2f9a0ba9)det: every output reduced in a fixed ordercake_moe_grouped_gemmflashinfer/experimental/cake/cake_moe_grouped_gemm(49 files, Cake revisionc7f1937c65c2e7f108ba359a0e0c3476ac2b67f2)fast: the same forward and input-gradient programs; the weight gradient with the 512-wide k tile adds the split tail of its last wave with unordered FP32 atomicsBoth packages are copied byte for byte, one level deep under
cake/_vendor/. Every blob and both Cake revisions are equal to the previous layout's. There are no allow entries, because the vendored modules read no environment variable and do not import flashinfer. There are also no local files and no KCP variant.API
OPisname="moe_grouped_gemm",variants=("det", "fast")(both are CLI choices),selected_by=(("moe_grouped_gemm_backend", "cake"),), with both packages and no extra flags. It exportscheck_grouped_dims,grouped_mmandmax_grouped_rows. The module is torch-free.grouped_mm(x, w, offs)computesY[offs[e-1]:offs[e]] = X[offs[e-1]:offs[e]] @ W[e].T.x [sum_m, K]andw [E, N, K], both with unit last-axis stride.offs [E]holds the int32 cumulative row ends on x's device. It is read on the device, with no host sync.xandwand returns[sum_m, N]in BF16.x.gradis BF16, andw.gradhas w's dtype.offs[-1]are left alone.check_grouped_dims(n, k, *, num_groups=None, max_rows=None)raisesValueErrorunless N and K are multiples of 256. Withnum_groups,E * N * Kmust fit 32-bit element offsets. Withmax_rows,max_rows * max(N, K)must fit too.max_grouped_rows(n, k)returns(2**31 - 1) // max(N, K), which is 299,593 rows at N = 4096, K = 7168.OP.validate:--moe-grouped-gemm-backend cakerequires--train-backend torchtitan. megatron, fsdp and unset are rejected.--cake-moe-grouped-gemm-variantwithout the cake backend is rejected.NotImplementedError(the old layout raisedRuntimeError). A toolkit too old for the architecture raisesRuntimeError(CUDA 13.4 for sm_107a).sm_100a,sm_103a,sm_107a; no prebuild parameters; tests intests/cake/moe_grouped_gemm/.32-bit addressing
sum_m * N(output),sum_m * K(input gradient) andE * N * K(weight gradient) must stay at or below2**31 - 1.grouped_mmchecks all three at the forward call, before any launch. A call whose input gradient or weight gradient would overflow now fails at its forward, instead of in the backward after the forward has run.check_grouped_dims(n, k, num_groups=, max_rows=)lets the model build refuse a configuration, andmax_grouped_rowsgives the row bound.Variant routing
det--moe-grouped-gemm-backend cake, flag unset, not deterministicfast, written back tocake_moe_grouped_gemm_variant(the miles CLI default stays fast)deterministic_mode=Truein the namespace, flag unset ordetdetfastwithdeterministic_mode(CLI orconfigure)ValueError("not bit-reproducible")configure()with an unknown variantValueErrorconfig.variant; its backward runs the package of its forwardfastundertorch.use_deterministic_algorithms(True)det, logged once (warn_runs_det), because the fast weight gradient would raise torch's non-determinism alertfastchanges only the weight gradient, and only where it picks the 512-wide k tile. That requires K to be a multiple of 512, with at least 4096 rows per group on average. There, the split tail of its last wave is added with unordered FP32 atomics, instead of in a second, ordered reduction launch. This gives last-bit differences from run to run. The forward and the input gradient run det's programs and give det's bits.Prebuild
cake.prebuild("moe_grouped_gemm", *, device=None, max_workers=None)takes no variant argument and no model parameter, and returns the library paths. It builds:If the probe does not build, a warning is logged and the input gradient splits its work queues evenly, with the same bits. After a prebuild, no call compiles.
The README Prebuild row reads:
moe_grouped_gemm | none: both variants' forward, input-gradient and weight-gradient programs (both k tiles, every stage) for any expert shape, and the input gradient's queue probe on sm_100a / sm_103a.Memory
release()(throughmiles_ext_kernels.release()) drops both packages' per-stream workspaces and the queue splits probed on them. The README Memory section lists them as out-of-pool per-stream workspaces that grow to the largest call.Review items
These items come from the review of the previous version: #10 (comment). The measurements are the reviewer's, taken on GB300.
sum_m = 300000passedcheck_grouped_dimsand the forward, and then the backward raised; 299,593 worked. The reviewer asked for a config-time check.grouped_mmnow checkssum_m * N,sum_m * KandE * N * Kat the forward call, before any launch.check_grouped_dimsgainsnum_groups/max_rows, andmax_grouped_rows(N, K)gives the bound (299,593 at N = 4096, K = 7168). A model build can checkE * N * Kby passingnum_groups. Per-call rows vary with routing, so they are checked at the forward.test_limits.py::test_the_row_bound_is_the_32_bit_span_of_the_wider_output,::test_the_dims_check_takes_the_most_rows_of_the_configured_run,::test_the_dims_check_takes_the_expert_count_of_the_weight_gradient,::test_the_dims_check_wants_multiples_of_256,::test_a_call_past_the_row_bound_fails_at_the_forward_before_it_reaches_the_device,::test_a_call_past_the_weight_gradient_bound_fails_at_the_forward. GPU:test_moe_grouped_gemm_gpu.py::test_the_32_bit_row_limit_fails_at_the_forward_before_any_launch. This test refuses 299,594 rows (one past the bound) at the forward. At 299,593 rows, it compares the last 2,048 rows of the output and of the input gradient, and the whole weight gradient, with closed-form FP64 references within the budgetassert_close(atol=rtol=1e-2).assert_rel_rms, against a per-group FP32 reference:{"y": 3.4e-3, "dx": 3.4e-3, "dw": 3.4e-3}. That is twice the measured value of about 1.7e-3 on sm_103a for y at K = 7168, N = 4096. Withdetect_scale=1.02, a 2 % error in y, dx or dw fails.test_moe_grouped_gemm_gpu.py::test_det_matches_the_reference_and_repeats_bit_for_bit[6 cases],::test_fast_gives_the_det_bits_where_its_reductions_are_ordered[6],::test_the_fast_weight_gradient_adds_its_split_tail_unordered_within_the_budget::test_short_groups_between_large_ones_repeat_bit_for_bit[w13/w2]sum_m = 0) gave an exact-zero dw.::test_empty_input_gives_an_exact_zero_weight_gradient[det/fast]offs[-1](torchtitan padding) left valid y / dx alone and dw correct, with zero and random tails.::test_rows_past_the_last_group_end_leave_the_valid_results_alone[det/fast × zero/random]Cross-stack items from the review of #1 that touch this layer:
OP.validate, and its ownarguments.py,configureandVARIANTare removed.::test_calls_alternating_between_the_default_and_a_side_stream_give_the_same_bits[det/fast],::test_release_drops_the_stream_workspaces_and_later_calls_give_the_same_bits.The "known doc mismatch" in the previous body no longer applies. It said that deterministic mode never applies under torchtitan and that the default is always fast. The runtime rule is now explicit:
deterministic_mode: det;Tests
All tests are in
tests/cake/moe_grouped_gemm/. The GPU tests use three layer shapes, given as (hidden H, expert intermediate I, local experts E) with w13[E, 2I, H]and w2[E, H, I]: (2048, 768, 16), (2048, 1536, 8) and (4096, 2048, 9).CPU, torch-free:
test_arguments.py(11).CPU, torch on meta tensors:
test_limits.py(6). These tests are listed under review item 10-1.GPU,
test_moe_grouped_gemm_gpu.py(17). Besides the tests listed under Review items, it covers:[det/fast]);[det/fast],multi_gpu);GPU,
test_prebuild_gpu.py(4): one prebuild, then 18ROUTESwith every variant, underno_build()and with no tvm-ffi inline build either. The routes are:one-expert(6000 rows on one of 8 experts) andno-rows;padded(300 rows pastoffs[-1]);k512-split-tail(N=4864, K=1024, sizes 16400 / 0 / 16666);wide-k-many-rows(2 × 16400 rows, 4096 × 4096) andnarrow-k-many-rows(2 × 9000 rows, N=4096, K=2048);side-stream.The other tests check four things: that no route compiles after the prebuild; that a variant configured after a call runs at the next call without building; that the prebuild builds every program the planner binds; and that the prebuild is idempotent.
Validation
CPU, at each commit
The image and commands are the same as for layer 8:
pytest testsrun in a torch-free venv and with the image Python;vendor_cake.py --checkandpre-commit run --all-files, with CUDA hidden.At the layer head, two more checks ran:
vendor_cake.py --check moe_grouped_gemm --upstreamandpytest -m 'not multi_gpu' tests/cake/moe_grouped_gemm.The image is
radixark/miles:dev-202611032236(Python 3.12.16, torch 2.14.1+cu130, apache-tvm-ffi 0.1.11).220e42b(vendor)--checkrc 0; pre-commit rc 0, no difftest_vendored.py::test_every_vendored_op_is_registeredfails, as the per-commit rule allows2edabb2(op)--checkrc 0; pre-commit rc 0, no difftest_op_contract.py::test_the_op_has_cpu_tests_and_a_prebuild_test[moe_grouped_gemm]fails, as allowed701c627(tests)--checkrc 0; pre-commit rc 0, no diff;--check moe_grouped_gemm --upstreamrc 0;tests/cake/moe_grouped_gemm(image, CPU) 29 passed, 2 skippedvendor_cake.py --check --upstreamat the top of the stack (layer 13, with the sameops.moe_grouped_gemmblock and package trees) returned rc 0.GPU
The GPU tests ran on the top of the stack (layer 13). Its moe_grouped_gemm files and its
ops.moe_grouped_gemmblock are byte-identical to this layer's. These arecake/moe_grouped_gemm.py,_impl/moe_grouped_gemm/, both_vendor/cake_moe_grouped_*trees andtests/cake/moe_grouped_gemm/.The container was
radixark/miles:dev-202611032236(amd64 on B200, arm64 on GB300), with Python 3.12.16, torch 2.14.1+cu130, nvcc 13.0.88 (CUDA 13.0), apache-tvm-ffi 0.1.11 and pytest 9.2.0. Each op had its own JIT cache.Commands:
PYTHONPATH=. python -m pytest -rfEs -p no:cacheprovider tests/cake/moe_grouped_gemm -m "not multi_gpu".-m multi_gpuon 2 GPUs.Gates on both B200 and GB300, with 0 failures and 0 skips:
test_det_matches_the_reference_and_repeats_bit_for_bit(6 layer shapes, within the rel-RMS budget),test_det_repeats_bit_for_bit_in_a_new_processandtest_short_groups_between_large_ones_repeat_bit_for_bit[w13/w2]passed.test_calls_alternating_between_the_default_and_a_side_stream_give_the_same_bits[det/fast]andtest_release_drops_the_stream_workspaces_and_later_calls_give_the_same_bitspassed.test_fast_gives_the_det_bits_where_its_reductions_are_ordered(6),test_the_fast_weight_gradient_adds_its_split_tail_unordered_within_the_budgetandtest_torch_deterministic_algorithms_run_det_and_say_so_oncepassed.test_the_32_bit_row_limit_fails_at_the_forward_before_any_launchpassed.test_a_call_for_a_device_other_than_the_current_one_gives_its_bits[det/fast]passed on 2 GPUs.no_build()and without any inline tvm-ffi build.Performance
The performance section does not fit in this description. It is posted in full in the three comments below: Performance (continued, 1 of 3), (2 of 3) and (3 of 3). Every number there is copied from FlashInfer pull request bodies; nothing was measured for this PR. The comments cover:
torch._grouped_mmwith autograd. The rows are checkpointed single GEMMs and checkpointed expert blocks, with early stop on and off. Its body states: "Every early-stop-on row is faster than both baselines."dWand checkpointed rows on B200 and GB300, against det Cake, the previous fast package and the fastest oftorch._grouped_mm, a per-expert cuBLAS loop and cuDNN.ckpt ESratios against det do not describe the vendored det package.Notes
cake_moe_grouped_det_gemmships 20 sm_107a libraries andcake_moe_grouped_gemmships 16. Both need CUDA 13.4 to build them, and an older toolkit raisesRuntimeError. The queue probe is built on sm_100a / sm_103a only. No GPU run on sm_107a has been recorded for this tree yet.E * N * Kand the rows per call (max_grouped_rows). Rows per call vary with routing and are checked at the forward.fast, the weight gradient with the 512-wide k tile differs in its last bits from run to run.release().🤖 Generated with Claude Code