Skip to content

Dispatch MoE experts with ragged grouped GEMM - #201

Open
amazloumi wants to merge 3 commits into
mainfrom
feat/moe-ragged-dispatch
Open

amazloumi wants to merge 3 commits into
mainfrom
feat/moe-ragged-dispatch

Conversation

@amazloumi

Copy link
Copy Markdown
Member

Summary

  • Replace the padded (E, max_tokens, dim) expert batches in grouped_expert_forward / grouped_expert_forward_packed with one ragged torch._grouped_mm(..., offs=) call per projection, driven by the int32 prefix sum of the per-expert token counts. Activation memory and compute now scale with the routed tokens, not with the busiest expert.
  • Token counts stay on the device (no .tolist() sync). The per-expert gradient-scaling loops in MoEMLP._local_forward and ep_dispatch_and_compute are vectorised (scale_by_expert_load); the EP path keeps one zero-token guard for FSDP2.
  • Bit-identical to the padded path in the forward and in every gradient (pinned by tests/integration/test_moe_grouped_gemm.py); 40 training steps of a small MoE log identical losses under both dispatches.
  • Docs: docs/moe/capacity-and-dispatch.md, docs/distributed/expert-parallelism.md; changelog entry.

One MoE layer, forward + backward, 8192 tokens, bf16, H200:

E K d H routing M_max padded ms ragged ms speedup padded peak GiB ragged peak GiB
8 2 768 2048 balanced 2078 2.3 1.2 1.9x 0.47 0.47
8 2 768 2048 60% to one expert 10619 6.4 1.2 5.3x 2.14 0.47
64 8 768 256 balanced 1079 25.4 1.1 23.4x 0.69 0.43
64 8 768 256 60% to one expert 39724 394.5 1.1 368.6x 16.08 0.43
256 8 768 256 balanced 301 99.4 2.2 45.2x 0.95 0.63
256 8 768 256 6% to one expert 4189 671.3 2.2 301.3x 7.12 0.63
256 8 768 256 60% to one expert 39430 5866.0 2.2 2621.4x 63.10 0.63

Every row: forward output and all gradients torch.equal to the padded path.

Testing

  • uv run ruff check kempnerforge/ tests/ passes
  • uv run ruff format --check kempnerforge/ tests/ scripts/ passes
  • uv run pyright kempnerforge/ passes (0 errors)
  • uv run pytest tests/unit/ -v --timeout=60 passes (1819 passed; TestHuggingFaceDataset::test_len times out on the login node without network, unrelated)
  • uv run pytest tests/integration/ -v — 92 passed (1 H200)
  • uv run torchrun --nproc_per_node=4 -m pytest tests/distributed/ -v — 102 passed, 2 pre-existing skips (4 H200)
  • uv run pytest tests/e2e/ --e2e -v — 27 passed, 1 skipped (slow); the 4 failures (test_checkpoint_save_and_resume, test_pp_checkpoint_save_and_resume, test_sigterm_triggers_emergency_checkpoint, test_moe_checkpoint_resume) predate this branch: they assert the string "Checkpoint saved" while the log line is Sync checkpoint saved, and three of them are dense-only
  • uv run make -C docs strict

Closes #200

Replace the padded (E, max_tokens, dim) expert batches with one ragged
torch._grouped_mm call per projection, driven by the int32 prefix sum of
the per-expert token counts. Outputs and gradients are bit-identical to
the padded path, and activation memory and time no longer scale with the
busiest expert. Token counts stay on the device, and the per-expert
gradient-scaling loops in MoEMLP and ep_dispatch_and_compute are
vectorised.
@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
kempnerforge/distributed/expert_parallel.py 58.57% <100.00%> (+48.04%) ⬆️
kempnerforge/model/moe.py 100.00% <100.00%> (+10.69%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Run the same ragged grouped GEMM on CPU: with the gradient hook in place torch's
CPU kernel accepts expanded upstream gradients, so the per-group loop is not
needed. Add single-rank CPU tests for ep_dispatch_and_compute and for the bf16
grouped path inside MoEMLP.

@Naeemkh Naeemkh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please make sure this test runs successfully, if it does, feel free to merge:

tests/integration/test_moe_grouped_gemm.py

@Naeemkh Naeemkh added the core Affecting core label Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Affecting core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ragged grouped GEMM dispatch for MoE experts

2 participants