Permute-Free Grouped GEMM for MoE (bf16, gfx950) - #694
Draft
sudhu2k wants to merge 44 commits into
Draft
Conversation
Add dedicated FlyDSL FP8 NN and NT kernels alongside the existing TN path. * dispatch TN, NN, and NT to specialized kernels * select matching TE rowwise or columnwise storage without copies * preserve operand scales and independent FP8 dtypes * derive M/N/K from each kernel’s physical layout * preserve TE output shapes while flattening only for launch * validate unsupported layouts and shapes for controlled fallback
Route all tensorwise FP8 layouts through the common FP8 GEMM core after wrapper-side storage backing selection. Remove the redundant FP8 NN and NT kernel variants since columnwise FP8 storage already provides the required materialized transpose.
Replace the eager PyTorch MXFP8 scale-packing path with stride-aware FlyDSL kernels that directly convert TE E8M0 scales into the HK/MFMA-ready [K/128, dim] packed layout. The previous implementation composed packing from arange, indexing, casts, shifts, masks, ORs, transposes, and contiguous copies. PyTorch lowered these into dozens of small GPU kernels around every GEMM, which dominated end-to-end runtime despite the FlyDSL GEMMs themselves being faster. The new path: - launches one fused scale-pack kernel per GEMM operand - supports both rowwise and columnwise TE scale layouts - consumes non-contiguous scale views using their actual strides - eliminates the intermediate iteration-major scale tensor - removes eager transpose/contiguous preparation from the scale path - preserves the existing HK/MFMA-ready packed representation
…roupedLinear Introduces FlyDSL-based permute-free MoE grouped GEMM support for ROCm: - New tensor shim helpers for FlyDSL pointer/resource handling. - Shared permute-free GEMM utilities and a dense 4-quadrant pipelined MMA loop for bf16. - Forward, dgrad, and wgrad kernels for permute-free gather/route-read grouped GEMMs. - PyTorch integration exposing the permute-free path in `GroupedLinear` behind an environment flag.
Replaces the compact [num_routes, ...] route indexing with a block-padded [em_max, ...] slot canonical layout across forward, dgrad, wgrad, activation, and routing metadata. This removes route_start/route_to_token from MoERoutingMetadata, updates the FlyDSL kernels and wrappers to index by padded slot, and fixes the GroupedLinear permute-free return tuple and reshape. Also adds a new benchmark comparing permute-free vs permute+grouped GEMM backends, unit tests for the route-list kernels, and Qwen3-235B/optional DSV3-GateUP grouped GEMM test cases.
…accumulation Adds `swap_gather` and `out_dtype` options to the FlyDSL wgrad kernel so it can compute the FC2 weight gradient directly in the native `[E, H, F]` layout (token-gathering `grad_output`, contiguous walk over block-padded `fc2_input`) without a transpose. Also enables fp32 `dw` output, allowing fp32 `main_grad` sinks to accumulate in-kernel instead of writing a bf16 scratch buffer and folding separately. Updates the PyTorch wrappers and `GroupedLinear` backward path to use these new kernel variants.
…dd FC2 path Removes fused activation, dispatched_probs, and preact_out handling from the FlyDSL forward wrapper so all plain gather/route-read GEMMs use the v3 MegaMOE-ported kernel. Gated SiLU activation and route-prob scaling now use standalone Triton helpers (permute_free_gated_act_recompute/bwd). Adds permute-free FC2 forward and dgrad variants (permute_free_grouped_gemm_bf16_fc2 and _fc2_dgrad), simplifies block-size selection for the plain-GEMM path, and removes the obsolete get_default_moe_kernel_config helper. Updates the benchmark and unit tests to exercise the standalone activation and FC2 kernels.
…backward tests Adds a `slot_expert_ids` field to `MoERoutingMetadata` that `prepare_moe_align` populates by broadcasting block-level `expert_ids` to the padded slot layout. `_expert_per_route` now returns the cached tensor when available, avoiding redundant recomputation for the standalone gated-activation kernels. Also adds unit tests covering the permute-free grouped GEMM backward paths: FC1 dgrad/wgrad, FC2 dgrad with `grad_probs`, and the GeLU variant of the route-list gated-activation backward.
…an up stale fusion code Removes the `PermuteFreeForwardResult` wrapper so `permute_free_grouped_gemm_forward` returns the output tensor directly, and drops it from the public `moe` exports. Updates `GroupedLinear` and the unit tests to stop accessing `.out`. Revises docstrings/comments in `GroupedLinear`, `moe_routing`, and the permute-free kernels to describe the FC2 standalone gated-activation pass instead of the old fused-prologue path. Removes dead helpers (`_pick_warps`, `_default_block_n`, `_env_int`), the skipped `apply_route_probs` test, and the unused `_FLYDSL_FWD_LARGE_TIER` tier, simplifying forward block-size selection.
Introduces a mutable `WgradAlign` holder in `MoERoutingMetadata` so the original metadata and its `dataclasses.replace` copy (FC2 route-space view) build the block-`CONTRACT_M` align buffers only once. Replaces the standalone `wgrad_*` fields with the shared holder and updates the permute-free grouped GEMM wgrad path to read from it.
Adds a `return_fc2_input` option to `permute_free_gated_act_bwd` (and `emit_act` in the Triton kernel) that re-materializes the `F`-wide FC2 input while the kernel already streams the `2F` preactivation. `permute_free_grouped_gemm_backward` now uses this fused path when both dgrad and wgrad are required, letting the wgrad consume the emitted activation instead of running a separate recompute. Renames `permute_free_gated_act_recompute` to `permute_free_gated_act_fwd` everywhere and adds a unit test verifying the fused dgrad+wgrad output matches the split references.
Switches the permute-free grouped GEMM forward path to the fixed-tile v3 kernel directly, removing `flydsl_moe_fwd_autotuned`, the `_FWD_CACHE`, and unused tile knobs (`block_n`, `block_k`, `warps_m`, `warps_n`) from the wrapper and kernel signatures. Also drops the unused `c_m` argument from the FlyDSL compile helpers, the `block_start` routing argument, the `perm_free_route_space` variable, and unused imports/constants across the MoE routing, grouped linear, helper kernels, and benchmark files.
Removes leftover "v3" references from docstrings and comments across the FlyDSL permute-free kernels and tests. Simplifies forward block_m selection to a single token-threshold helper and deletes the unused picker/autotuner remnants in `pf_fwd_wrapper`. Unifies dgrad dispatch by passing the forward weight tensor directly with a new `dgrad` flag instead of building a transposed view, and renames the wrapper dispatch helper from `_run_v3_fwd` to `_run_gather_gemm`.
…rd support check Replaces the ambiguous "compact" terminology across the permute-free MoE grouped GEMM code with explicit layout names: **dense route-ordered** ``[num_routes, F]``, **block-padded route-ordered** ``[em_max, F]``, and **token-ordered** ``[num_recv_tokens, F]``. Renames the test helper ``_compact_route_order`` to ``_dense_route_order`` and updates docstrings, comments, and error messages in the FlyDSL kernels, wrappers, ``GroupedLinear``, and routing metadata to use the new vocabulary. Removes the ``flydsl_moe_fwd_supported`` helper and the runtime capability check in ``_pf_moe_fwd``; the wrapper now calls the FlyDSL launcher directly. Also renames the intermediate dgrad buffer from ``compact`` to ``route_buf`` for clarity.
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.
Description
This PR adds a permute-free execution path for MoE
GroupedLinearon ROCm. Instead ofphysically permuting activations into expert-contiguous order before each grouped GEMM (and
un-permuting after), the gather is folded into the GEMM: the kernel reads each expert's rows
directly from the token-ordered activation buffer using a precomputed route list. This eliminates
the permute/unpermute activation copies, lets SwiGLU be treated as "just an op" (recomputed in the
backward rather than checkpointed), matches the MegaMOE reference kernel numerically, beats the
other bf16 grouped-GEMM backends currently in TE, and is fully CUDA-graph capturable (the
routing/align path is sync-free).
Opt-in via
NVTE_PERMUTE_FREE_GROUPED_GEMM=1(ROCm builds only). ~6.9k LOC across 16 files; newcode is self-contained under
transformer_engine/pytorch/moe/andtransformer_engine/pytorch/flydsl_kernels/, wired intoGroupedLinear.1. Motivation: what's expensive about the standard MoE path
A conventional MoE layer does, per micro-batch:
Two sources of avoidable activation memory / bandwidth:
[num_routes, hidden]) is a full extramaterialization of the activations, written before FC1 and read/scattered after FC2 — plus the
symmetric copies in the backward.
F-wide activated tensor (and/or the2Fpre-activation) for the backward, so the activation output lives across the whole fwd→bwd
boundary.
For large expert counts and top-k this permute + activation traffic is a meaningful fraction of MoE
step time and peak memory.
2. Core idea: gather-in-GEMM over a route-list layout
Rather than moving data, we build a compact route list once and let the GEMM kernels do the
gather while they load their
Atiles:[num_recv_tokens, hidden]), neverpermuted.
sorted_slot_ids) thatmaps each padded GEMM slot → the received-token row to gather.
A = activations,B = [E, N, K]weights) readsA[sorted_slot_ids[slot], :]inside the tile load — the gather is free (it rides the existing global loads), and no permuted
copy is ever written.
gather-combine (each output token pulls its own routes) instead of an atomic scatter.
Because the whole routing/align path is over-allocated to static, shape-derived upper bounds and
carries the real extents as device scalars (no
.item(), no host sync), the entire flow isCUDA-graph capturable.
3. End-to-end flow
The same built routing metadata drives both FCs; a
route_spaceflag selects direction (FC1 vsFC2), so FC1 and FC2 share a single align build (
dataclasses.replace(meta, route_space=True)).route_space=False) — input in token order. Forward gathers per expert(
index_a_by_route_pos=False) into the padded[em_max, 2F]raw[gate|up]buffer. Dgradcombines the input gradient back to token rows via the contention-free gather-combine.
route_space=True) — input already in route order (FC1's output). The gated activationact(gate)*up[*prob]is applied in a standalone pass into anF-wide transient, then a plaingather-GEMM + gather-combine →
[num_recv, out]. Dgrad gathers the token-space grad back into thecompact route buffer.
4. Data structures
All routing state lives on
MoERoutingMetadata/PermuteFreeMetadata(
transformer_engine/pytorch/moe/moe_routing.py). Everything below is built sync-free andover-allocated to static bounds; real extents ride as device scalars.
routing_map[num_recv_tokens, num_experts]boolnum_routes = routing_map.sum().topkT*Edown toT*min(topk,E)→ smallerem_max(and less zero-init).sorted_slot_ids[T*min(topk,E)]i32num_recv_tokensfor padding/tail → OOB read returns 0).expert_ids[blocks_max]i32BLOCK_SIZE_Mblock (-1past the real count; skipped). Drives the tile→expert map in the GEMM grid.slot_expert_ids[T*min(topk,E)]i32expert_ids[slot // block_size_m]), for the standalone activation kernels.num_tokens_post_padded[1]i32 device scalarem; bounds the kernels without a host sync.block_start[num_experts]i32e's slots start atblock_start[e]*block_size_m. (Consumed by backward wgrad to locate grad rows — see §6.)token_routes/token_route_count[T, min(topk,E)]i32 /[T]i32block_size_mBLOCK_SIZE_Mthe fwd/dgrad layout is padded to.route_counts/route_withinwgrad_alignWgradAlign(shared holder)CONTRACT_M(=32) align for the wgrad contraction (sorted_slot_ids,block_start,blocks_per_expert). Shared by reference across the FC1 metadata and its FC2replacecopy so the wgrad align is built once.Direction tag (
PermuteFreeMetadata):route_space(FC1 vs FC2) and an optionalactivationhint(
"silu"/"gelu") consumed on the FC2 direction. Per-route gating probabilities are not storedhere — they need a gradient, so they flow as a separate autograd tensor.
5. Kernels
Routing / metadata (Triton,
pf_helper_kernels.py)route_list_scan— block-size-independent per-expert counts + within-expert ranks (shared acrossFC1/FC2/wgrad).
route_list_align— buildssorted_slot_ids,expert_ids,block_start,blocks_per_expert,and the
token_routesinverse map. Fully sync-free / graph-safe.route_gather_combine— contention-free per-token reduction over a token's routes (replacesatomic scatter for the FC2 combine and FC1 dgrad reduction).
fused_gated_act_prob_fwd/fused_gated_act_prob_bwd— SwiGLU/GELU recompute + backward (see§6).
exp2-based silu/gelu, no libdevice dependency.Grouped GEMMs (FlyDSL,
flydsl_kernels/permute_free_grouped_gemm/)pf_fwd.py— gather-in-GEMM forward. Tile-centric grid:TILE_TO_GROUP(=expert_ids) mapseach M-tile to its expert;
Arows are gathered bysorted_slot_ids(FC1) or read by routeposition (FC2). MegaMOE-ported fixed tile geometry (
32×32×16MFMA,BLOCK_N=256,GROUP_M=4,XCD swizzle over the real tile range).
pf_dgrad.py— data-gradient GEMM (NN contract against the transposed-weight view).pf_wgrad.py— weight-gradient GEMM. Expert-centric gridN×K×E; contracts over the tokenaxis using a coalesced LDS fill + hardware transpose-read (
ds_read_tr16) so the token slotbecomes the matrix-core contraction axis with no strided global gather. Each
dWtile is owned byexactly one workgroup → race-free store; supports bf16 or fp32 output for in-kernel
main_gradaccumulation, and aswap_gathervariant for FC2's nativedWlayout.Thin dispatch/plumbing:
pf_fwd_wrapper.py,pf_wgrad_wrapper.py,tensor_shim.py; orchestrationin
permute_free_grouped_gemm.py.6. Why this saves memory (the headline benefits)
Removes the permute activation memory. The activations are never copied into expert-contiguous
order. The GEMM gathers rows on the fly via
sorted_slot_ids, so the[num_routes, hidden]permutebuffer (and its unpermute twin) simply doesn't exist — in both forward and backward. The token
combine is a gather (each token pulls its routes) rather than a scatter, so no atomic-scatter
scratch either.
Removes the persisted SwiGLU activation. FC1 emits the raw
2F[gate|up]pre-activation; thegated activation
act(gate)*up*probis materialized into a short-livedF-wide transient thatFC2 consumes immediately and is never saved. Across the fwd→bwd boundary we persist only the
2Fpre-activation — the
F-wide activated tensor that the standard path checkpoints is gone.Fuses the recompute into the SwiGLU backward — "swiglu is just an op." In the backward,
fused_gated_act_prob_fwdre-materializes theF-wide activation from the saved2Fpreact, andfused_gated_act_prob_bwdcomputes the grad w.r.t. the preact (and optionally the route-prob grad)in the same pass. The recompute feeds the unchanged FC2 wgrad, so that kernel stays at full
stored-activation speed while only the
2Fpreact is kept live. The whole SwiGLU becomes a singlefused op with no separately-checkpointed subgraph.
Parity with the MegaMOE GEMM kernel. The PF fwd GEMM kernels are ported from MegaMOE GEMM kernels, while wgrad is a custom written kernel specifically for Permute free.
Faster than the other TE bf16 grouped-GEMM backends.
benchmarks/microbenchmarks/benchmark_perm_free_grouped_gemm.pycomparespermute_freeagainsthipblaslt,ck, andtritonon the DeepSeek/Grok/Qwen-style shapes; the permute-free path leadsfor bf16 grouped GEMM.
CUDA-graph capturable. The align/routing pass has no host sync — it over-allocates to static,
shape-derived bounds and carries real extents as device scalars — and the GEMMs use fixed geometry
(no first-call autotune benchmarking). The full MoE step can therefore be captured into a HIP/CUDA
graph.
7. Integration & enablement
transformer_engine/pytorch/module/grouped_linear.py(forward + backward dispatch,activation/route-prob handling).
transformer_engine/pytorch/moe/__init__.py.NVTE_PERMUTE_FREE_GROUPED_GEMM=1(ROCm/HIP builds only; gated byIS_HIP_EXTENSION).Off by default — zero impact on existing paths when unset.
8. Testing
tests/pytorch/test_perm_free_grouped_linear.py(22 tests): FC1/FC2 fwd, dgrad, wgrad (incl. fp32accumulate and recompute-from-preact), the standalone gated-act fwd/bwd for both silu and gelu, the
full FC1→FC2 gated pipeline, fwd/dgrad/wgrad consistency, gather-combine dispatch, sync-free align,
and the enable flag.
2.8.0+rocm7.2.1): 22 passed.Suggested reading order
moe_routing.py— the metadata contract and layout (start here).permute_free_grouped_gemm.pymodule docstring +permute_free_grouped_gemm_forward/_backward— the dispatch/flow.pf_helper_kernels.py— align, gated-act recompute/bwd, gather-combine.flydsl_kernels/permute_free_grouped_gemm/pf_fwd.py, thenpf_dgrad.py, thenpf_wgrad.py.grouped_linear.pydiff — the integration seam.Type of change
Checklist: