WIP - feat(cuda): CUTLASS grouped block-scaled NVFP4 MoE GEMM#282
WIP - feat(cuda): CUTLASS grouped block-scaled NVFP4 MoE GEMM#282TheTom wants to merge 3 commits into
Conversation
Add an AOT-built CUTLASS grouped block-scaled NVFP4 (e2m1) MoE GEMM for the CUDA runtime, with f16 output and per-group device alpha pointers. - cutlass_moe_fp4.cu: grouped e2m1 GEMM with f16 D and per-group device alpha pointers. prepare/run/release API — prepare bakes every M-independent section (expert pointers, strides, worst-case scale-factor layouts, alpha pointer array) into a persistent device blob + workspace and initializes with null host problem shapes; run = ONE small fill kernel deriving the per-group problem/operand/scale/output pointers from the router offsets on device, then gemm.run. Zero host work per call, no per-call allocations, graph-safe. - build.rs: CUTLASS_DIR hook compiles the fp4 unit with the block-scaled mma availability define for the target arch (sm_120a / sm_121a). - runtime wrappers for the one-shot and persistent-handle forms. TILE_K=256 (ThreadBlockShape<128,128,256>): the deeper K-tile for the K=2688 contraction halves TMA load iterations on this weight-streaming- bound grouped GEMM. nsys cuda_gpu_kern_sum on GB10 (decode-free, 20 prefill replays): grouped-problem kernel avg 2191.9us -> 1999.7us/call (-8.8%), MoE-GEMM prefill bucket 26.6% -> 24.2%. Bit-exact: argmax 1104 EXACT, cosine 0.971 unchanged vs TILE_K=128.
The scale-factor format names `ue4m3` / `ue8m0` tokenize with a leading `ue` that typos reads as a misspelling of `use`. Add an identifier regex for the `(u)eXmY` number-format family so these domain terms pass the spell check without enumerating each one.
Run moe_grouped_cutlass_fp4 on a small grouped NVFP4 problem (4 experts, M per group 16-64, N=256, K=128) and compare the f16 output against an f32 oracle that dequantizes the same e2m1 + ue4m3 inputs and does the grouped GEMM on the host. The fp4 inputs are bit-identical on both sides, so the only deviation is the f16 output round-trip — the match is near bit-exact (cosine 1.000000, max|Δ| 0.125 on the 5090 / sm_120a). The oracle fills and decodes the per-16-block scale factors through the identical 512-byte swizzle the kernel reads, so the test pins the block-scale-factor layout, not just the multiply-accumulate. Skips (no failure) when there is no CUDA device or the runtime was built without CUTLASS, keeping the CUDA-no-CUTLASS CI shard green.
There was a problem hiding this comment.
CUTLASS kernels not wired into the model executor
After checking out the branch: the CUDA codegen already exists (metaltile-codegen/src/cuda/) and all 3 backend PRs are merged — my earlier context was wrong, sorry.
The actual concern is different. moe_grouped_cutlass from #274 and this new FP4 variant both follow the same pattern: raw CudaDevice methods with no callers anywhere outside tests — not in std, not in the model executor, not in the fusion planner. Neither kernel is reachable from inference today.
The grouped NVFP4 MoE GEMM isn't a leaf utility — it sits in the middle of a full MoE forward pass: router → topk → sort → grouped_gemm(gate) → silu → grouped_gemm(up) → scatter. For it to actually run during inference the model executor needs to know it exists, which means it either needs an Op::InlineCuda escape hatch (parallel to the existing Op::InlineMsl) or a KernelEntry-registered stub the planner can dispatch by name.
If there's a wiring PR already planned, happy to merge this as-is — just want to make sure these don't get stranded as FFI stubs. If not, worth at minimum opening a tracking issue before this lands so the pattern from #274 doesn't keep compounding.
Smaller notes (still apply)
- 11 positional args mixing
CUdeviceptr/ host-slices /usize— a typed descriptor struct would prevent silent pointer transposition at call sites. releaseis a manual unsafe free — aDropimpl on a handle newtype makes it non-opt-in.- Three CI checks still failing; GB10 hardware validation still
[ ].
|
sorry my agent tried closing this lol |
|
Addressed the grouped-GEMM dispatch concern. Opened #285 to track full executor integration for the grouped-cutlass family, and pushed a follow-up (branch
A first-class GB10 validation: the NVFP4 grouped GEMM runs as the production MoE path on GB10, K256 config bit-exact vs the f32 oracle at the 12288-row x 128-expert grouped shape. With the family-level integration tracked in #285 and the by-name wiring + ergonomics on the follow-up branch, this should be safe to merge as-is, consistent with #274. |
|
Migrating active metaltile development to thewafflehaus/metaltile per thewafflehaus/iron#12 — |
Summary
cutlass_moe_fp4.cu— with f16 output and per-group device alpha pointers, exposed over themetaltile-runtimeFFI as one-shot and persistent-handle (prepare/run/release) forms.prepare;runis ONE small fill kernel that derives the per-group problem/operand/scale/output pointers from the router offsets on device, thengemm.run. Zero host work per call, no per-call allocations, graph-safe.CUTLASS_DIRbuild hook inbuild.rsto compile the fp4 unit with the block-scaled mma availability define for the target arch (sm_120a/sm_121a); the non-fp4 unit is unchanged.TILE_K=256config (ThreadBlockShape<128,128,256>) as the default.e2m1,ue4m3,ue8m0) in the typos config via a format-family identifier regex (the leadingueotherwise reads as a typo foruse).Motivation
The grouped NVFP4 MoE GEMM is the foundational prefill kernel for block-scaled MoE inference on the GB10 (sm_121a) CUDA path. The deeper K-tile (
TILE_K=256) halves TMA load iterations for the K=2688 contraction on this weight-streaming-bound GEMM.Originated from / validated against thewafflehaus/butter#20 (the Nemotron-Nano-30B-A3B NVFP4 GB10 prefill/decode work) — these kernels were produced and validated while testing that prefill path.
Validation
Validated on GB10 per the originating branch (CI to confirm the CUDA build):
TILE_K=256vsTILE_K=128: argmax 1104 EXACT, cosine 0.971 unchanged.nsys cuda_gpu_kern_sum(decode-free, 20 prefill replays) grouped-problem kernel avg 2191.9us → 1999.7us/call; MoE-GEMM prefill bucket 26.6% → 24.2%.Locally (M5 Max, no CUDA toolkit) the Rust side is green:
Test plan
cargo fmt --check -p metaltile-runtimecargo check -p metaltile-runtime(default / no-cuda)cargo check -p metaltile-runtime --features cuda(Rust FFI typechecks;build.rsskipsnvccwhenCUTLASS_DIRis unset)cargo clippy -p metaltile-runtime --features cuda -- -D warningstypos --config .github/configs/typos-cli.tomlnsysprefill bench on GB10 (CUTLASS_DIRset) — CI / hardware runner