fix(peft): correct dense MultiLoRALinear on replicated base linears - #5156
Conversation
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>
Found by a GLM-5.2 multi-LoRA e2e smoke (MLA q/kv down-projections are TELinear parallel_mode='duplicated'); Qwen3-style models never exercise this case because all their LoRA targets are TP-parallel linears. Two defects, both specific to replicated bases: * The per-slot token spans covered the full micro-batch, but a replicated base under sequence parallelism consumes the SP shard directly (no gather), so the grouped GEMM read out of bounds — a device-side assert, not a shape error. The spans are now intersected with this rank's contiguous token window (the same invariant the MoE slot routing uses for its SP narrow), with the host-side total cached once per micro-batch in set_tokens_per_adapter_slot so the forward needs no device sync. * _gather_output keyed only on input_is_parallel, leaving the adapter output as a [tokens, out/tp] shard while a replicated base produces the full [tokens, out]. It now mirrors ParallelLinearAdapter's lin_out_gather_output: gather for row-parallel AND replicated bases. The TP collective between the two grouped GEMMs stays unconditional: ParallelLinearAdapter shards A on the rank axis whenever input_is_parallel=False, replicated bases included, so the mid all-gather is required there too. Verified: 74 unit tests pass (4 new for the window narrowing), and a GLM-5.2_5layer multi-LoRA e2e (TP=EP=4, ETP=1, SP, thd, dp-attention rollout) completes 3 training steps with per-step adapter upserts and (tp, pp, ep) checkpoint shards, run with CUDA_LAUNCH_BLOCKING=1. Signed-off-by: Ethan (Yusheng) Su <yushengsu.thu@gmail.com>
|
Hi @yushengsu-thu — this needs a rebase too, and it is stacked on #5155, so please rebase that one first. Why it conflicts: #4218 was squash-merged into Fix: after rebasing #5155 ( git checkout yusheng/multilora-replicated-base
git reset --hard yusheng/multilora-moe-experts # already rebased onto upstream/main
git cherry-pick c78e227f795bdcb4f66ef14a3489b4f3028edd1b
git push --force-with-leaseFYI this commit does not apply directly onto I would have pushed the rebase for you, but the fork is owned by the |
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
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com> # Conflicts: # src/megatron/bridge/peft/multi_lora_layers.py
What does this PR do ?
Fixes the dense
MultiLoRALinear(#4218) on replicated base linears —TELinear parallel_mode='duplicated', i.e. the MLA q/kv down-projections of GLM-/DeepSeek-style models.Why
A GLM-5.2 (5-layer) multi-LoRA e2e smoke — the first MLA model through the multi-LoRA path — crashed in the first training forward. Qwen3-style models never reach these paths (all their LoRA targets are TP-parallel linears), which is why the existing e2e coverage missed them.
Two defects, both specific to replicated bases
Per-slot token spans covered the full micro-batch, but a replicated base under sequence parallelism consumes the SP shard directly (TE stores
parallel_mode=Nonefor duplicated linears, so the adapter attribute derivation disables the SP gather). The grouped GEMM's offsets then exceed the row count — an out-of-bounds read surfacing asCUDA error: device-side assert triggered, not a shape error. The spans are now intersected with this rank's contiguous token window (_narrow_token_counts_to_window, the same invariant the MoE slot routing uses for its SP narrow). The host-side total is cached once per micro-batch inset_tokens_per_adapter_slot, so the forward needs no device sync._gather_outputkeyed only oninput_is_parallel, leaving the adapter output as a[tokens, out/tp]shard while a replicated base produces the full[tokens, out]. It now mirrorsParallelLinearAdapter'slin_out_gather_output: gather for row-parallel and replicated bases.Deliberately unchanged: the TP collective between the two grouped GEMMs stays unconditional.
ParallelLinearAdaptershards A on the rank axis wheneverinput_is_parallel=False— replicated bases included — so the mid all-gather is required there too (gating it onbase_linear_is_parallelwas tried and breaks GEMM2 with a contraction mismatch).Testing
(tp, pp, ep)checkpoint shards, executed underCUDA_LAUNCH_BLOCKING=1so no async assert hides.torch._grouped_mmdevice-side assert).Changelog
MultiLoRALinear: narrow per-slot token spans to this rank's SP shard for replicated bases (_narrow_token_counts_to_window); cache the host-side span total inset_tokens_per_adapter_slotMultiLoRALinear: gather the column-sharded adapter output for replicated bases (mirrorParallelLinearAdapter.lin_out_gather_output)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:
Additional Information