Repository navigation
Mario/overlap moe kernel integration #3651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 25 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
4b7857c
feat(moe): add SonicMoE BF16 expert compute backend
S1ro1 49f6407
refactor(moe): consolidate GEMM backends under expert compute
S1ro1 35faa90
fix: pin SAC-safe QuACK and EP-aware SonicMoE autotuning
S1ro1 e1f94fc
feat(trainer): add fused Mega MoE dispatch via DeepGEMM
MarioSieg 5e3e0e2
Integration of mega-moe backward
MarioSieg 4ed24fe
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg 27fb710
Dusal layout support
MarioSieg 5ee3819
Fix weight storage for FSDP all gather
MarioSieg 876ca96
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg 512d88d
Update from main
MarioSieg 9466bfa
AC buffer registration
MarioSieg 29a961f
Merge branch 'main' into mario/overlap-moe-kernel-integration
MarioSieg ce28728
Merge remote-tracking branch 'origin/feat/sonicmoe-bf16-ep' into mari…
MarioSieg 2ec3fbc
refactor(moe): run Mega MoE as an ExpertCompute backend
MarioSieg 15bd003
AC buffer registration
MarioSieg c4fb131
chore(moe): pin deep_gemm at the prime-mega-moe wheel and add mega-mo…
MarioSieg cdf58ec
Merge branch 'main' into mario/overlap-moe-kernel-integration
MarioSieg bc9da58
fix(mega-moe): register autograd on the Mega MoE custom op so the blo…
MarioSieg 36552d4
Remove natural layout flags
MarioSieg d6434c0
Add mxfp8 integration for mega moe
MarioSieg edf5253
Merge branch 'main' into mario/overlap-moe-kernel-integration
MarioSieg 8af6441
chore(mega-moe): keep the public deep_gemm pin, ship the Mega MoE whe…
MarioSieg de23ae9
fix(mega-moe): keep (num_tokens, top_k) routing tensors in the benchmark
MarioSieg d38aec7
Merge branch 'main' into mario/overlap-moe-kernel-integration
MarioSieg b5fb375
fix(mega-moe): read the activation clamp from ClampedSilu
MarioSieg 1d49fee
Fixes
MarioSieg 821d6a8
Fix comments of integration
MarioSieg 916ad8e
Fp8 integration
MarioSieg 19138e9
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg a4cc078
Improve integration, bug fixes and comment fixes
MarioSieg a7070dd
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg e63dded
Auto destroy buffers
MarioSieg ecbab89
Make SM allocation count local
MarioSieg d86fd76
CLearer naming
MarioSieg af43116
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg 405877b
Merge remote-tracking branch 'origin/main' into mario/overlap-moe-ker…
MarioSieg e433a59
Cleanup symmmetric buffer allocation
MarioSieg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| """Benchmark prime-mega-moe's fused Mega MoE kernels (bf16 forward + backward) against prime-rl's | ||
| bf16 EP path (``TorchTokenDispatcher`` + ``BF16ExpertCompute``) for the same routed-expert | ||
| workload, weights, and token counts, and check the gradients agree. | ||
|
|
||
| Requires >=2 GPUs with symmetric-memory support (SM100/Blackwell), PyTorch >= 2.9, and the | ||
|
garrett361 marked this conversation as resolved.
Resolved
|
||
| prime-mega-moe ``deep_gemm`` wheel from Prime Intellect installed over the public one. | ||
| ``--hidden`` must be a multiple of 256 and ``--intermediate`` a multiple of 128. | ||
|
|
||
| Usage: | ||
| torchrun --nproc_per_node=8 benchmarks/mega_moe/bench_mega_moe.py \ | ||
| --num-experts 128 --top-k 8 --hidden 2048 --intermediate 1024 --tokens-per-rank 2048 | ||
| """ | ||
|
|
||
| import argparse | ||
| import os | ||
|
|
||
| import torch | ||
| import torch.distributed as dist | ||
|
|
||
| from prime_rl.trainer.distributed.token_dispatcher import FusedTokenDispatcher, TorchTokenDispatcher | ||
| from prime_rl.trainer.models.layers.expert_compute import BF16ExpertCompute | ||
| from prime_rl.trainer.models.layers.mega_moe import MegaMoEExpertCompute, mega_moe_available | ||
| from prime_rl.trainer.models.layers.moe import GroupedExperts | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--num-experts", type=int, default=128) | ||
| parser.add_argument("--top-k", type=int, default=8) | ||
| parser.add_argument("--hidden", type=int, default=2048) | ||
| parser.add_argument("--intermediate", type=int, default=1024) | ||
| parser.add_argument("--tokens-per-rank", type=int, default=2048) | ||
| parser.add_argument("--warmup", type=int, default=10) | ||
| parser.add_argument("--iters", type=int, default=50) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def bench(fn, warmup: int, iters: int) -> float: | ||
| for _ in range(warmup): | ||
| fn() | ||
| torch.cuda.synchronize() | ||
| dist.barrier() | ||
| start, end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True) | ||
| start.record() | ||
| for _ in range(iters): | ||
| fn() | ||
| end.record() | ||
| torch.cuda.synchronize() | ||
| return start.elapsed_time(end) / iters | ||
|
|
||
|
|
||
| def rel_diff(a: torch.Tensor, b: torch.Tensor) -> float: | ||
| return ((a.float() - b.float()).norm() / b.float().norm().clamp_min(1e-12)).item() | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = parse_args() | ||
| local_rank = int(os.environ["LOCAL_RANK"]) | ||
| torch.cuda.set_device(local_rank) | ||
| dist.init_process_group(backend="nccl", device_id=torch.device("cuda", local_rank)) | ||
| rank, world_size = dist.get_rank(), dist.get_world_size() | ||
| group = dist.group.WORLD | ||
|
|
||
| if not mega_moe_available(): | ||
| raise RuntimeError( | ||
| "Mega MoE requires the prime-mega-moe deep_gemm wheel from Prime Intellect installed over the public one, on an SM100+ GPU." | ||
| ) | ||
| if args.num_experts % world_size: | ||
| raise ValueError(f"num_experts ({args.num_experts}) must be divisible by world_size ({world_size}).") | ||
|
|
||
| torch.manual_seed(rank) | ||
| experts_per_rank = args.num_experts // world_size | ||
| device = torch.device("cuda", local_rank) | ||
|
|
||
| experts = GroupedExperts( | ||
| dim=args.hidden, | ||
| hidden_dim=args.intermediate, | ||
| num_experts=experts_per_rank, | ||
| ).to(device=device, dtype=torch.bfloat16) | ||
| experts.init_weights(init_std=0.02) | ||
| params = [p for p in experts.parameters() if p is not None] | ||
|
|
||
| x = torch.randn(args.tokens_per_rank, args.hidden, dtype=torch.bfloat16, device=device, requires_grad=True) | ||
| dy = torch.randn(args.tokens_per_rank, args.hidden, dtype=torch.bfloat16, device=device) | ||
| scores = torch.softmax( | ||
| torch.randn(args.tokens_per_rank, args.num_experts, device=device, dtype=torch.float32), dim=-1 | ||
| ) | ||
| # Keep the model's (num_tokens, top_k) routing shape: the Mega MoE backward returns dtop_scores in it. | ||
| top_scores, selected_experts_indices = torch.topk(scores, args.top_k, dim=-1) | ||
| top_scores = top_scores.contiguous().requires_grad_(True) | ||
| selected_experts_indices = selected_experts_indices.contiguous() | ||
|
|
||
| bf16_compute = BF16ExpertCompute() | ||
| baseline = ( | ||
| TorchTokenDispatcher( | ||
| num_experts=args.num_experts, | ||
| top_k=args.top_k, | ||
| token_group_alignment=bf16_compute.token_group_alignment, | ||
| group=group, | ||
| ), | ||
| bf16_compute, | ||
| ) | ||
| mega_compute = MegaMoEExpertCompute( | ||
| experts, | ||
| num_experts=args.num_experts, | ||
| top_k=args.top_k, | ||
| group=group, | ||
| max_tokens_per_rank=args.tokens_per_rank, | ||
| ) | ||
| mega = (FusedTokenDispatcher(), mega_compute) | ||
|
|
||
| def run(path, backward: bool): | ||
| dispatcher, compute = path | ||
|
|
||
| def fn(): | ||
| experts.compute = compute | ||
| y = dispatcher.run(x, top_scores, selected_experts_indices, experts, score_before_experts=False) | ||
| if backward: | ||
| y.backward(dy) | ||
| return y | ||
|
|
||
| return fn | ||
|
|
||
| def grads_after(fn): | ||
| for p in params: | ||
| p.grad = None | ||
| x.grad = None | ||
| top_scores.grad = None | ||
| y = fn() | ||
| torch.cuda.synchronize() | ||
| return y.detach().clone(), x.grad.clone(), top_scores.grad.clone(), [p.grad.clone() for p in params] | ||
|
|
||
| y_ref, dx_ref, ds_ref, dp_ref = grads_after(run(baseline, backward=True)) | ||
| y_mega, dx_mega, ds_mega, dp_mega = grads_after(run(mega, backward=True)) | ||
| diffs = { | ||
| "y": rel_diff(y_mega, y_ref), | ||
| "dx": rel_diff(dx_mega, dx_ref), | ||
| "dtop_scores": rel_diff(ds_mega, ds_ref), | ||
| "dexperts": max(rel_diff(a, b) for a, b in zip(dp_mega, dp_ref)), | ||
| } | ||
|
|
||
| def bench_fwd_bwd(path): | ||
| fwd = bench(run(path, backward=False), args.warmup, args.iters) | ||
| total = bench(run(path, backward=True), args.warmup, args.iters) | ||
| return fwd, total | ||
|
|
||
| base_fwd, base_total = bench_fwd_bwd(baseline) | ||
| mega_fwd, mega_total = bench_fwd_bwd(mega) | ||
|
|
||
| if rank == 0: | ||
| print( | ||
| f"world_size={world_size} num_experts={args.num_experts} (per rank={experts_per_rank}) " | ||
| f"top_k={args.top_k} hidden={args.hidden} intermediate={args.intermediate} " | ||
| f"tokens_per_rank={args.tokens_per_rank}" | ||
| ) | ||
| print(f"{'':46} {'fwd':>10} {'bwd':>10} {'fwd+bwd':>10}") | ||
| print( | ||
| f"{'bf16 EP (TorchTokenDispatcher+BF16ExpertCompute)':46} " | ||
| f"{base_fwd:9.3f}ms {base_total - base_fwd:9.3f}ms {base_total:9.3f}ms" | ||
| ) | ||
| print( | ||
| f"{'Mega MoE (fused bf16 fwd + fused bf16 bwd)':46} " | ||
| f"{mega_fwd:9.3f}ms {mega_total - mega_fwd:9.3f}ms {mega_total:9.3f}ms" | ||
| ) | ||
| print( | ||
| f"{'speedup':46} {base_fwd / mega_fwd:9.2f}x " | ||
| f"{(base_total - base_fwd) / (mega_total - mega_fwd):9.2f}x {base_total / mega_total:9.2f}x" | ||
| ) | ||
| print("relative diff vs bf16 EP path: " + ", ".join(f"{k}={v:.4f}" for k, v in diffs.items())) | ||
|
|
||
| mega_compute.buffer.destroy() | ||
| dist.destroy_process_group() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,25 @@ def __call__( | |
| ) -> torch.Tensor: ... | ||
|
|
||
|
|
||
| class FusedDispatchExpertCompute(Protocol): | ||
| """Expert compute that also dispatches tokens to their experts and combines the results. | ||
|
|
||
| It takes this rank's undispatched tokens and their routing, paired with a ``FusedTokenDispatcher``. | ||
| """ | ||
|
|
||
| token_group_alignment: int | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like |
||
|
|
||
| def validate(self, experts: "GroupedExperts") -> None: ... | ||
|
|
||
| def __call__( | ||
| self, | ||
| experts: "GroupedExperts", | ||
| x: torch.Tensor, | ||
| top_scores: torch.Tensor, | ||
| selected_experts_indices: torch.Tensor, | ||
| ) -> torch.Tensor: ... | ||
|
|
||
|
|
||
| def broadcast_expert_bias( | ||
| bias: torch.Tensor, | ||
| num_tokens_per_expert: torch.Tensor, | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.