Skip to content

feat(peft): support multi-LoRA on grouped MoE expert linears - #5155

Merged
yaoyu-33 merged 10 commits into
NVIDIA-NeMo:mainfrom
radixark:yusheng/multilora-moe-experts
Jul 29, 2026
Merged

feat(peft): support multi-LoRA on grouped MoE expert linears#5155
yaoyu-33 merged 10 commits into
NVIDIA-NeMo:mainfrom
radixark:yusheng/multilora-moe-experts

Conversation

@yushengsu-thu

Copy link
Copy Markdown
Contributor

What does this PR do ?

Extends multi-LoRA (#4218) to grouped MoE expert linears, bringing the multi-adapter path to parity with single-adapter LoRA for grouped experts.

Stacked on #4218 — this branch includes #4218's commits as its base, so the Files tab shows both until #4218 merges. Only the last commit is new here. Ported from radixark#23, where it runs in production on the bridge branch.

Why

MultiLoRA skips every MoE expert linear, so on MoE models the experts — most of the trainable capacity — silently get no adapter.

What

  • MultiLoRAGroupedExpertLinear wraps mlp.experts.linear_fc{1,2} of a TEGroupedMLP with one low-rank pair per (slot, local expert). Each slot is an existing GroupedExpertLinearAdapter, so the packed [num_local_experts, ...] export and checkpoint layout is reused unchanged; subclassing MultiLoRALinear keeps isinstance-based multi-LoRA discovery working with no downstream changes.
  • Slot routing. Inside the experts, rows are the dispatcher's expert-major permutation, so tokens_per_adapter no longer segments them. install_moe_slot_routing puts a forward pre-hook on each MoE layer's experts module that co-permutes an int32 slot-id vector through the dispatcher's own stages (local permute, EP all-to-all, local-expert sort), then stable-sorts by (slot, expert) so one grouped GEMM covers every pair.
  • Every slot's weights stay in the autograd graph (stacked weights; explicit zero term on the empty-batch path) — Megatron's grad buffers expect one grad hook per trainable param.

Rejected configurations

Raised at layer construction, not in the hook (a hook raising on some ranks would strand peers in the companion all-to-all): expert TP > 1, moe_permute_fusion (records TE's row_id_map, not a token gather index), fp8/fp4 experts (input padding desyncs the row order), capacity padding, non-alltoall dispatchers. SequentialMLP experts stay skipped with a clearer warning; normalize_moe_lora / share_expert_adapters / experts_shared_outer_loras raise NotImplementedError.

Bug fix

reset_adapter forked the dense RNG tracker, whose seed varies with the dense TP rank — expert-DP peers differ in exactly that rank whenever TP != ETP, so replicas of a reused slot diverged. It now forks the expert tracker.

Testing

On 8x H200 (radixark fork, same file contents as this PR): test_multi_lora_moe.py 17 passed; all multi-LoRA tests 70 passed. Real-model EP=2 integration (Qwen3-MoE-shaped, real MoEAlltoAllTokenDispatcher): all expert adapter params get grads, and routing tokens to different slots gives different losses. Export through AutoBridge.export_adapter_weights is byte-identical (names and shapes) to single-adapter LoRA.

Changelog

  • Add MultiLoRAGroupedExpertLinear and MoE slot routing (install_moe_slot_routing) to megatron.bridge.peft.multi_lora_layers
  • MultiLoRA now wraps grouped expert linears; sequential (SequentialMLP) experts are still skipped with a clearer warning
  • MultiLoRAGroupedExpertLinear.reset_adapter forks the expert RNG tracker (EDP-consistent slot reuse)
  • New unit tests: tests/unit_tests/peft/test_multi_lora_moe.py (CPU routing math + single-GPU integration)

GitHub Actions CI

See the CI section in the Contributing doc. A Nvidia developer will need to approve and trigger the CI for external contributors.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation?
  • Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
    • Reviewer: Does the PR have correct import guards for all optional libraries?

Additional Information

mathewjhan and others added 9 commits June 8, 2026 16:21
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
Signed-off-by: Mathew Han <mathewjhan@gmail.com>
MultiLoRA previously skipped every MoE expert linear, so on an MoE model the
experts — most of the trainable capacity — silently received no adapter.

Add MultiLoRAGroupedExpertLinear for grouped expert linears
(mlp.experts.linear_fc{1,2} of a TEGroupedMLP). Each slot is a
GroupedExpertLinearAdapter, so the packed [num_local_experts, ...] weight layout
that peft_bridge's grouped-expert export and distributed checkpointing already
understand is reused unchanged. It subclasses MultiLoRALinear so isinstance-based
multi-LoRA discovery (slot lifecycle here, per-slot optimizers and adapter-state
zeroing downstream) picks expert layers up with no changes.

The hard part is that inside the experts the row order is the dispatcher's
expert-major permutation of tokens from every EP rank, so tokens_per_adapter no
longer segments it. install_moe_slot_routing registers a forward pre-hook on each
MoE layer's experts module — the only point with both the dispatcher's
permutation metadata and the final row order — which co-permutes an int32
slot-id vector through the same stages the dispatcher applied and stable-sorts by
(slot, local expert) so one grouped GEMM covers every pair. The companion
all-to-all carries one int32 per dispatched token.

Every slot's weights stay in the autograd graph (via the stacked weights, and an
explicit zero term on the empty-batch path) because Megatron's grad buffers
expect one grad hook per trainable parameter; a slot missing from the graph would
leave its bucket incomplete.

Unsupported configurations are rejected at layer construction, not in the hook:
a hook that raised on only some ranks would leave its peers waiting in the
companion all-to-all. Rejected: expert TP > 1, fp8/fp4 experts, permute fusion
(the fused permute records TE's row_id_map rather than a token gather index),
capacity padding, and non-alltoall dispatchers. Sequential (SequentialMLP)
expert linears are still skipped, with the warning rewritten to say so.

reset_adapter now forks the expert RNG tracker instead of the dense one: the
dense seed varies with the dense TP rank, and expert-data-parallel peers differ
in exactly that rank whenever tensor and expert tensor parallel sizes differ, so
replicas of a reused slot would diverge.

Signed-off-by: Ethan (Yusheng) Su <yushengsu.thu@gmail.com>
Copilot AI review requested due to automatic review settings July 28, 2026 23:18
@copy-pr-bot

copy-pr-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@yaoyu-33 yaoyu-33 added area:peft Parameter-efficient fine-tuning (LoRA, adapters) feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer x-radixark labels Jul 29, 2026
@yaoyu-33

Copy link
Copy Markdown
Contributor

Hi @yushengsu-thu — this needs a rebase before it can be reviewed/merged.

Why it conflicts: #4218 was squash-merged into main as 1f12931e2. This branch still carries #4218's six original (unsquashed) commits as its base, so git sees them as conflicting with the squashed version plus the later lora_layers.py / utils.py refactors on main.

Fix: drop the #4218 base and keep only your one new commit on top of current main. I verified locally that it cherry-picks cleanly — no manual conflict resolution needed:

git fetch upstream main
git checkout yusheng/multilora-moe-experts
git reset --hard upstream/main
git cherry-pick 5fde419eae1cd7f67136f283ac6965dbdf6a69dc
git push --force-with-lease

I also confirmed on the rebased result that every symbol this PR imports still exists after main's peft refactor (GroupedExpertLinearAdapter, all2all_hp2sp, is_grouped_expert_linear, init_method_normal, sort_chunks_by_idxs), and that ruff check + ruff format --check pass.

I would have pushed the rebase for you, but the fork is owned by the radixark org and GitHub's "allow edits by maintainers" does not apply to org-owned forks, so my push is rejected.

Note #5156 is stacked on this branch and will need rebasing after this one lands — see my comment there.

@yaoyu-33 yaoyu-33 added waiting-on-customer Waiting on the original author to respond ready-to-merge PR is approved, current, and only waiting for CI to pass before merge and removed needs-review PR is ready for code review and waiting on a reviewer labels Jul 29, 2026
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>

# Conflicts:
#	src/megatron/bridge/peft/multi_lora.py
#	src/megatron/bridge/peft/multi_lora_layers.py
#	tests/unit_tests/peft/test_multi_lora.py
#	tests/unit_tests/peft/test_multi_lora_layers.py
@yaoyu-33
yaoyu-33 merged commit 05f33f7 into NVIDIA-NeMo:main Jul 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:peft Parameter-efficient fine-tuning (LoRA, adapters) community-request feature New capabilities, enhancements, or enablement work ready-to-merge PR is approved, current, and only waiting for CI to pass before merge waiting-on-customer Waiting on the original author to respond x-radixark

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants