Skip to content

Keep fused MoE weights intact through FSDP gather (1.5% perf improvement on DeepSeek-v3)#4448

Closed
abhinavgoel95 wants to merge 64 commits into
AI-Hypercomputer:mainfrom
abhinavgoel95:abgoel/fused-moe-mlp-single-all-gather
Closed

Keep fused MoE weights intact through FSDP gather (1.5% perf improvement on DeepSeek-v3)#4448
abhinavgoel95 wants to merge 64 commits into
AI-Hypercomputer:mainfrom
abhinavgoel95:abgoel/fused-moe-mlp-single-all-gather

Conversation

@abhinavgoel95

Copy link
Copy Markdown
Contributor

Description

Preserve prefused MoE input weights through shard_map so FSDP performs one all-gather of the combined weight tensor before the grouped GEMM.

Previously, MaxText split the prefused [experts, embed, 2 * mlp] weight into separate w0 and w1 tensors before shard_map. This could cause XLA to all-gather each half independently and concatenate them again immediately before the grouped GEMM.

The updated flow is:

prefused weight
    -> one FSDP all-gather
    -> one grouped GEMM
    -> split output activations for SwiGLU

Only the GMM output is split into gate and up-projection activations. This split is required for SwiGLU and is considerably cheaper than splitting, gathering, and concatenating the large weight tensors.

The implementation:

  • Passes the prefused weight directly into the sparse MoE shard_map.
  • Uses one partition specification for the combined weight.
  • Preserves the combined weight through one-stage and two-stage FSDP all-gather paths.
  • Runs one grouped GEMM using the combined weight.
  • Splits the resulting activations before applying the configured FFN activation.
  • Preserves existing behavior for quantized serving, weight sparsity, dense MoE computation, and vLLM RPA inference.

The optimization currently applies to sparse MoE training when prefused weights can be consumed directly. Quantized serving and sparsified-weight paths continue using separate w0 and w1 tensors. Supporting fused quantized-weight retrieval could be considered as a future improvement.

Tests

The original experimental change was profiled with a DeepSeek MoE workload and removed the separate weight-slice operations before the grouped GEMM.

The version rebased onto current main has not been run end-to-end. The patch was checked with:

git diff --check

To reproduce the performance comparison, run the same sparse MoE workload with:

prefuse_moe_weights=true
sparse_matmul=true

Compare the generated HLO or GPU profile before and after this change. The updated version should contain one FSDP all-gather for the combined MoE input weight and should not split and concatenate the weight before the grouped GEMM.

Checklist

  • I have performed a self-review of my code. For an optional AI review, add the gemini-review label.
  • I have necessary comments in my code, particularly in hard-to-understand areas.
  • I have run end-to-end tests tests and provided workload links above if applicable.
  • I have made or will make corresponding changes to the doc if needed, including adding new documentation pages to the relevant Table of Contents (toctree directive) as explained in our documentation.

tdophung and others added 30 commits January 21, 2026 11:21
Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
…e with padding when EP>1

Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
…ndex

Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
…ame when EP > 1

Signed-off-by: JAX Toolbox <jax@nvidia.com>
…adding non-ring of expert with EP=1.

Signed-off-by: JAX Toolbox <jax@nvidia.com>
…to_all params computation between te and mt impl to use the same function.

Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: Teddy Do Phung <tdophung@login-ptyche02.ptyche.clusters.nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: Jeremy Berchtold <jberchtold@nvidia.com>
Squash of all integrate_te_permutation commits:
- Add te_permutation.py with wrappers for TE token_dispatch/combine/sort_chunks_by_index
- Add te_permutation_impl and te_permutation_align_size config flags
- Refactor moe.py to support both MT and TE permutation/unpermute paths
- Handle ring-of-experts with TE permutation
- Fix Nan/Inf issue for TE padding with EP=1
Signed-off-by: tdophung <tdophung@nvidia.com>
Signed-off-by: Jeremy Berchtold <jberchtold@nvidia.com>
Signed-off-by: Jeremy Berchtold <jberchtold@nvidia.com>
Signed-off-by: tdophung <tdophung@nvidia.com>
…old/gmm-for-merge

[TE] Integrate TE Grouped GEMM v2
Signed-off-by: tdophung <tdophung@nvidia.com>
…old/fix-te-permute-silent-fallback

[TE] Fix TE permute silent fallback issue and fix integration of TE permute
Signed-off-by: tdophung <tdophung@nvidia.com>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sync issue? I assume this file should be untouched in this PR, please note if this understanding is not correct

fused_qkv: false
fused_mlp: false
fused_qkv: False
fused_mla_lora_proj: False # Fuse MLA Q+KV LoRA up-projections (wq_a+wkv_a) into a single matmul. Requires q_lora_rank > 0.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can this be put into a separate PR? Keep PRs small for easier readability and reviewing

FP8 = "fp8"
NANOO_FP8 = "nanoo_fp8"
FP8_NANO_V2 = "fp8_nanoo"
FP8_GPU = "fp8_gpu"
FP8_FULL = "fp8_full"
TE_NO_QUANT = "te_no_quant"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can this be part of a separate PR? Is this quantization necessary?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see maybe this quantization technique has some dependency with fused vs non fused (which is surprising to me), maybe since fused just doesn't support quantization for now?

def train_loop(config, recorder, state=None):
"""Main Training loop."""
# Initialize HybridEP buffer manager BEFORE setup_train_loop, because model creation
# during setup traces shard_map which calls hybrid_ep_dispatch_ffi (needs buffer manager).
if config.use_hybrid_ep:

@gobbleturk gobbleturk Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would prefer to put this in a method (to keep train_loop as lean and readable as possible). Either a method defined somewhere outside of train_loop, or possibly in a new te_utils file

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.

6 participants