Skip to content

Packed MXFP4 & NVFP4 GEMM + mega-MoE (SM100, true 2-CTA multicast)#2

Draft
ipiszy wants to merge 9 commits into
mainfrom
mxfp4-mxfp4-mega-moe
Draft

Packed MXFP4 & NVFP4 GEMM + mega-MoE (SM100, true 2-CTA multicast)#2
ipiszy wants to merge 9 commits into
mainfrom
mxfp4-mxfp4-mega-moe

Conversation

@ipiszy

@ipiszy ipiszy commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Adds packed-FP4 × packed-FP4 GEMM and mega-MoE kernels for SM100 (Blackwell), supporting both MXFP4 and NVFP4 through a single unified kernel keyed on MmaKind:

  • Standalone GEMM: mxfp4_gemm_nt, nvfp4_gemm_nt[M,K] @ [N,K].T -> [M,N] (BF16 out).
  • Mega-MoE: mxfp4_mxfp4_mega_moe, nvfp4_nvfp4_mega_moe (mma_type="mxfp4xmxfp4" / "nvfp4xnvfp4").

Both operands are packed E2M1 (2 elems/byte). MXFP4 uses UE8M0 scale factors at gran-32; NVFP4 uses E4M3/UE4M3 scale factors at gran-16 with per-tensor global scales passed as CPU-side scalar kernel params (dequant: acc * gs_act * gs_weight; the MoE L1 output is requantized with the L2-activation global scale).

Key technical details

  • True 2-CTA cta_group::2 multicast TMA (SM100_TMA_2SM_LOAD) for activations/weights, with leader-routed barriers and per-CTA scale-factor loads.
  • MMA atoms: kind::mxf4 (block32) for MXFP4, kind::mxf4nvf4 (block16 / scale_vec::4X) for NVFP4; UE8M0 vs UE4M3 instruction descriptors.
  • gran-16 scale-factor handling (the subtle part): NVFP4 has 2 SF int32s per K-block vs 1 for MXFP4. The per-K-block SF stride, smem/tmem sizing, and UTCCP placement are sized accordingly. Crucially, crossing K-uint32s is done via the SF tensor-memory address — not sf_id, which is a 2-bit descriptor field (a_sf_id_/b_sf_id_) and only selects within a K-uint32.
  • Unified on the refactored mega-MoE API (parse_mma_kind / ring-token buffering / sub-byte token byte math via get_element_bits).
  • The shared swap-AB epilogue gains an optional out_scale (default 1.0, no-op for fp8/bf16/mxfp4).

Validation (B200)

Test diff
standalone MXFP4 GEMM 0.00000
standalone NVFP4 GEMM 0.00000
MXFP4 mega-MoE 7.5e-4
NVFP4 mega-MoE 8.4e-4 (sweep: 6 configs, BLOCK_K 128 & 256)

Benchmark (device time via bench_kineto)

  • mega-MoE: nvfp4 ≈ 1.04–1.06× the mxfp4 time
  • standalone GEMM: nvfp4 ≈ 1.09–1.16× the mxfp4 time

The residual nvfp4 overhead is gran-16 doubling the scale factors (2× UTCCP copies + 2× SF traffic).

Tests

tests/test_mxfp4_gemm.py, tests/test_nvfp4_gemm.py, tests/test_mxfp4_mega_moe.py, tests/test_nvfp4_mega_moe.py, tests/bench_packed_fp4.py.

Known limitations / follow-ups

  • Single-rank only in tests; multi-rank / expert-parallel dispatch+combine for packed-FP4 is untested.
  • Coverage vs fp8_fp4: only gemm_nt (standalone) + mega-MoE; no nn/tn/tt, m_grouped, or k_grouped variants yet. The standalone GEMM is a fixed-config "de-risk" kernel (no autotuning; requires N%256, M%128, K%128).
  • Weight global scales are single per-tensor CPU scalars, not per-expert device tensors.
  • L2-input (intermediate) global scale must be pre-calibrated (CPU param).
  • MXFP4 BLOCK_K=256 (small-token configs) is routed through the new address-based SF path but only BLOCK_K=128 is covered by mxfp4 tests; nvfp4 BLOCK_K=256 is tested.
  • nvfp4 GEMM overhead (~9–16%) is optimizable (fewer UTCCP copies / better SF staging).

Note

High Risk
Large new SM100 kernel surface (GEMM + fused mega-MoE/EP) with distinct MXFP4 vs NVFP4 numerics and symmetric-buffer layout changes; regressions would affect core MoE performance and correctness on Blackwell.

Overview
Adds SM100-only packed FP4×FP4 compute: standalone mxfp4_gemm_nt / nvfp4_gemm_nt and fused mxfp4_mxfp4_mega_moe / nvfp4_nvfp4_mega_moe, wired through Python/C++ APIs and new JIT CUDA kernels (2-CTA mxf4 / NVF4 MMA, gran-32 vs gran-16 scale factors, NVFP4 global-scale dequant via optional out_scale on the swap-AB epilogue).

Mega-MoE plumbing is extended for sub-byte activations: new MmaKind::MXFP4 / NVFP4, get_element_bits / get_sf_gran_k, mma_type strings mxfp4xmxfp4 / nvfp4xnvfp4, and symmetric-buffer views that use int8 packed tokens and SF layouts sized by granularity (NVFP4 MoE takes per-expert gate_alpha / up_alpha / l2_input_global_scale / down_alpha).

Ships a benchmarks/ suite (internal MXFP4 vs NVFP4, DeepGEMM vs FlashInfer single-GPU, multi-GPU EP vs NCCL combine) plus a README with captured B200 numbers and run instructions.

Reviewed by Cursor Bugbot for commit 5de63a6. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds a standalone packed-FP4 x packed-FP4 GEMM (`mxfp4_gemm_nt`) and a
packed MXFP4 mega-MoE (`mxfp4_mxfp4_mega_moe`) for SM100, both using a
true 2-CTA `cta_group::2` multicast TMA load path (`SM100_TMA_2SM_LOAD`)
with leader-routed barriers and per-CTA scale-factor loads.

Integrated onto the unified mega-MoE API (`mma_type`/`parse_mma_kind`,
ring-token buffering): adds `MmaKind::MXFP4` with sub-byte (`get_element_bits`)
token byte math, packed-FP4 symmetric-buffer slicing, and the packed-FP4
epilogue that writes E2M1 nibbles directly to their [token][inter] positions.

Validated on B200: standalone diff=0.0; mega-MoE diff=7.5e-4.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ipiszy ipiszy force-pushed the mxfp4-mxfp4-mega-moe branch from 38b8e75 to dd1a973 Compare June 27, 2026 00:29
ipiszy and others added 2 commits June 27, 2026 02:40
Extends the unified packed-FP4 kernels (standalone GEMM and mega-MoE) to
also support NVFP4 (E2M1 data, E4M3/UE4M3 scale factors at gran-16, with
per-tensor global scales), keyed on `MmaKind`. Adds `nvfp4_gemm_nt` and
`nvfp4_nvfp4_mega_moe` entries (mma_type="nvfp4xnvfp4"), the NVF4 2-CTA
`kind::mxf4nvf4` MMA atom, NVFP4 quant utils, and tests.

Global scales are CPU-side scalar kernel params: the GEMM/MoE accumulators
are dequantized by gs_act * gs_weight, and the MoE L1 output is requantized
with the L2 activation global scale.

Key gran-16 scale-factor handling (vs gran-32 MXFP4): 2 scale-factor
int32s per K-block (stride/smem/tmem sized accordingly), and crossing
K-uint32s via the SF tensor-memory ADDRESS since `a_sf_id_`/`b_sf_id_` are
2-bit descriptor fields (sf_id only selects within a K-uint32).

Validated on B200: standalone GEMM diff=0.0; mega-MoE diff=8.4e-4; MXFP4
paths unchanged (regression intact).

Co-authored-by: Cursor <cursoragent@cursor.com>
Benchmarks the standalone GEMM and mega-MoE for both MXFP4 and NVFP4 using
`bench_kineto` (pure device time, kernel-name filtered + L2-flushed, the
repo-standard method), reporting per-shape latency, GEMM TFLOPS, and the
nvfp4/mxfp4 ratio.

Device-time results (B200): mega-MoE nvfp4 is ~1.04-1.06x the mxfp4 time,
standalone GEMM ~1.09-1.16x. The residual is gran-16 doubling the scale
factors (2x UTCCP copies + 2x SF traffic) vs gran-32 MXFP4.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ipiszy ipiszy changed the title Packed MXFP4 × MXFP4 GEMM + mega-MoE (SM100, true 2-CTA multicast) Packed MXFP4 & NVFP4 GEMM + mega-MoE (SM100, true 2-CTA multicast) Jun 27, 2026
ipiszy and others added 5 commits June 27, 2026 05:33
Switch the NVFP4 mega-MoE from per-tensor scalar global scales to per-expert
(num_experts_per_rank,) float32 device tensors, matching the TRT-LLM format:
  - gate_alpha / up_alpha = 1/(l1_input_gs * gate|up_weight_gs)  -> L1 acc dequant
    (applied per gate/up column before SwiGLU)
  - l2_input_global_scale  (= 448*6/amax)                        -> L1-output requant
  - down_alpha            = 1/(l2_input_gs * down_weight_gs)     -> L2 acc dequant
All indexed per local expert in the fused epilogue. Updates the C++/Python
entries, the test (per-expert gate/up/down weight scales + per-expert
intermediate global scale), and the benchmark.

Validated on B200: nvfp4 mega-MoE diff=5.8e-4; mxfp4 + standalone GEMMs unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
The per-expert global scales (gate/up/down alpha, l2_input_global_scale)
were re-read from global memory inside the per-atom epilogue loops. Hoist
them to once-per-block registers. NVFP4 mega-MoE overhead drops from
~1.10x to ~1.05-1.08x vs MXFP4 (the residual is gran-16 SF traffic, not
scale loading). Correctness unchanged (diff=5.8e-4).

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds device-time benchmarks comparing DeepGEMM mega-MoE (fp8xfp4 / nvfp4)
against FlashInfer's NVFP4 MoE backends (cute_dsl, cutlass, trtllm-gen):

- tests/bench_flashinfer_vs_deepgemm.py: single-device comparison (CUDA-graph
  device time) across all backends; EP-aware builders (local_expert_offset /
  ep_rank) for cute_dsl and cutlass.
- tests/bench_ep_multi_gpu.py: multi-GPU expert-parallel benchmark. DeepGEMM
  uses its native fused dispatch+combine; FlashInfer uses replicated input +
  expert-sharded compute + all_reduce combine. Per-kernel device time summed
  from traces; moe/all_reduce breakdown; NVLS combine via symm-registered
  buffer; sleep-aligned collective timing; avg-over-ranks aggregation.
- tests/bench_kernel_breakdown.py: per-kernel device-time breakdown per backend.
- tests/fi_trtllm.py + tests/_fi_vendor/: thin driver over FlashInfer's
  trtllm_fp4_block_scale_moe, reusing the vendored (v0.6.11) weight-shuffle and
  routing harness.
- tests/bench_packed_fp4.py: add fp8xfp4 mega-MoE column to the mega benchmark.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reorganize the FP4 MoE handoff note under a new doc/ directory and
rename it to reflect its subject.

Co-Authored-By: Claude <noreply@anthropic.com>
* Move the four bench_*.py scripts from tests/ into a new benchmarks/
  directory; fix their sys.path (add tests/) and repoint helper imports
  at the consolidated test modules (signatures preserved via aliases).
* Consolidate the mxfp4/nvfp4 test pairs into two parameterized scripts:
  tests/test_fp4_gemm.py and tests/test_fp4_mega_moe.py, each looping
  over ('mxfp4', 'nvfp4') via a per-format strategy. Delete the four
  old test_mxfp4_*/test_nvfp4_* files. Update doc/fp4_moe.md test paths.
* Expand mega-MoE coverage: 1-rank shape matrix (8 shapes: small, odd,
  single-token, large-asymmetric, masked) and a multi-rank EP matrix
  (world 2/4/8) via torch.multiprocessing.spawn + init_dist. Multi-rank
  uses replicated x/weights/routing so the NVFP4 per-expert l2act_gs
  reference is computable without a cross-rank all-gather; each rank
  asserts against the full reference. Helper signatures kept stable for
  the benchmark imports.

Verified on B200: 32 mega-MoE cases pass (baseline mxfp4 0.00075 /
nvfp4 0.00058 unchanged; 1-rank 16 + EP2 6 + EP4 4 + EP8 4, all
diff < 0.05, incl. masked routing); GEMM 10/10 diff=0.0.

Co-Authored-By: Claude <noreply@anthropic.com>
5) stop timer
so its number includes the cross-device communication cost.

We report the worst-rank average latency (max over ranks).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EP benchmark uses mean not max

Low Severity

The module header and several helpers claim worst-rank (max-over-ranks) latency, but rank_avg_ms all-reduces with SUM and divides by world size, so printed DeepGEMM and FlashInfer EP numbers are means. Straggler ranks are under-represented versus the documented metric.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d7c72a. Configure here.

…; add README

* Fold the per-kernel diagnostic into bench_flashinfer_vs_deepgemm.py as a
  --breakdown mode (kernel_breakdown + show_breakdown helpers, breakdown
  branch in bench_one, --breakdown CLI flag). Delete the standalone
  bench_kernel_breakdown.py (nothing imported it).
* Add benchmarks/README.md documenting the three benchmarks (FP4-format,
  single-device DeepGEMM-vs-FlashInfer, multi-GPU EP), prerequisites, how
  to run, and captured B200 results (DeepGEMM ~1.6-1.9x faster than
  FlashInfer cutlass single-device; ~1.7x faster fused-EP at world=2),
  plus a --breakdown example and compatibility notes.

Co-Authored-By: Claude <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5de63a6. Configure here.

world = dist.get_world_size(group)
t = torch.tensor([per_iter_ms], device='cuda', dtype=torch.float64)
dist.all_reduce(t, op=dist.ReduceOp.SUM, group=group)
return t.item() / world

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EP bench mean not worst-rank

Medium Severity

The multi-GPU EP benchmark text says it reports worst-rank latency, but rank_avg_ms all-reduces per-rank times with SUM and divides by world size, so printed DeepGEMM and FlashInfer numbers are cross-rank means. Straggler ranks are smoothed out, which misstates end-to-end EP latency when ranks diverge.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5de63a6. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant