Skip to content

fix: EP collective deadlock with variable-length token counts (LoRA flavor) - #3631

Open
akx wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
akx:ragged
Open

fix: EP collective deadlock with variable-length token counts (LoRA flavor)#3631
akx wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
akx:ragged

Conversation

@akx

@akx akx commented Aug 22, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes NCCL communication deadlocks when using Expert Parallelism when training LoRAs.

Port of #1365's fix (8f2b685) to lora_experts.py, and this subclass.

Changelog

See #1365; this is a port of it.

Before your PR is "Ready for review"

Pre checks:

If you haven't finished some of the above items you can still open "Draft" PR.

Additional Information

See the below block for Claude Fable's analysis.

Details **Symptom:** Expert-parallel LoRA training with unpacked, unpadded batches (`packed_sequence_size: 0`, `padding: false`, `local_batch_size: 1`) hangs forever on the first MoE layer of the first forward. Every rank sits in the same `all_gather_into_tensor` (native stack: `ProcessGroupNCCL::allgather_into_tensor_coalesced` issued from `lora_experts.py:184` via `DTensor.full_tensor()`), ~200 W per GPU, no error, no timeout until the NCCL heartbeat. Reproduced on 1×8 and 2×8 nodes, `experts: torch` and `torch_mm`, with and without activation checkpointing / dynamo.

Root cause: the LoRA forward gathers tokens across the EP mesh with

x = DTensor.from_local(x, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor(grad_placements=[Partial()])

from_local(..., Shard(0)) does not exchange sizes — it assumes every rank's local tensor has the same shape and infers the global shape as local × ep_size. With ragged per-rank token counts (e.g. 68 / 401 / 1607 tokens across ranks), each rank issues a differently sized all-gather, which NCCL cannot detect; it waits forever. The non-LoRA parent GroupedExperts.forward handles this correctly (exchanges lengths, pads, _AllGatherConcatVarlenFn, then all-reduce + narrow to combine). The subclass docstring says it "mirrors GroupedExperts.forward" but uses the naive DTensor gather. It only works when batches are packed or padded — which every shipped recipe does, so the bug is invisible in-tree.

Repro: any MoE LoRA config with ep_size > 1, packed_sequence_size: 0, padding: false, local_batch_size: 1, on a dataset with variable-length samples (we used HuggingFaceH4/no_robots with Kimi-K2.5; a 2-layer slice of the checkpoint reproduces it in under 2 minutes on one node). Log a x.shape[0] per rank before the gather to see the mismatch.

…lavor)

GroupedExpertsLoRA.forward still gathers tokens across the EP mesh with DTensor.from_local(x, [Shard(0)]).full_tensor(),
which assumes uniform token counts across ranks.

PR NVIDIA-NeMo#1365 (8f2b685) fixed this in GroupedExperts but not in the LoRA subclass,
so LoRA on MoE experts with unpacked or unpadded batches deadlocks in NCCL on the first MoE layer.

Port of NVIDIA-NeMo#1365's fix to lora_experts.py: pad+all_gather+trim on the way
in, all_reduce+narrow on the way out, gradient anchor so every rank
enters the backward collectives.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Aarni Koskela <akx@iki.fi>
@akx
akx requested a review from a team as a code owner August 22, 2026 15:00
@copy-pr-bot

copy-pr-bot Bot commented Aug 22, 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant