fix(cp): preserve gradients when sharding VLM inputs#2931
Conversation
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
|
/claude review |
|
/ok to test d741578 |
…tach Depends on #2931 (out-of-place sharding of grad-bearing inputs_embeds in the generic CP path); assumes it merges. With the resize_() constraint handled there, the remaining grad-blocking workarounds around CP pre-embedding are obsolete and harmful: - prepare_cp_forward loses pre_embed_no_grad. Its two users were the VLM eval site (redundant: _run_validation_epoch is already @torch.no_grad()) and the VLM KD student prep, where blocking gradients to trainable input embeddings and the vision tower is the same defect class #1914 removed from the train path. - minimax_m3_vl stops detaching its pre-embedded inputs_embeds — the detach existed only to survive context_parallel's in-place resize and silently froze the embeddings/vision tower under CP; this aligns it with qwen3_5/qwen3_5_moe/nemotron_omni. Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
| # mutate only the integer/mask buffers. | ||
| primary_seq_tensor = cp_buffers[0] | ||
| if primary_seq_tensor.requires_grad: | ||
| batch[primary_key] = _shard_grad_buffer_for_cp(primary_seq_tensor, cp_seq_dims[0], cp_mesh) |
There was a problem hiding this comment.
Hi @HuiyingLi, how have you confirmed per-token grad scaling when primary_seq_tensor has pad tokens? I see in line 452 cp_buffers[i] = torch.cat([buf, pad_val], dim=dim) so i'm expecting some ranks to receive pad values, how can we make sure that any gradient scaling takes this into account?
There was a problem hiding this comment.
two cases I'm worried about (a) maintenance / regression and (b) correctness are:
- gradient scaling with padding + cp
- correctness with HSDP, because i saw it was using local rank -- I think it should be ok, but want to make sure.
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Follow-up: padding and gradient-scaling correctnessThe ordering in the training path is what makes the gradient scaling correct for the affected non-PP Qwen/Omni configurations:
Thus CP-added Exact 200-padding scaling testCommit
Real Qwen3.5 distributed checkConfiguration: For the stress run, the identical 512-row batch was extended with exactly 200 zero embedding rows, 200
The PR path and PyTorch legacy sharder therefore produce exactly the same forward loss even with 200 forced padding rows, establishing that the out-of-place sharding order/values match PyTorch while retaining autograd. The 200-row Qwen case is deliberately artificial: CP=2 can naturally append at most 3 rows. Comparing 512 vs. 712 also showed Qwen's BF16 CP kernels have some sequence-length sensitivity (0.19% loss and 4.53% grad-norm difference), but this is identical across the PR and legacy sharding paths and is not introduced by this change. The earlier real |
|
/ok to test 70526ab |
What does this PR do ?
Fix context-parallel VLM finetuning when the full-sequence
inputs_embedstensor retains its autograd graph. This affects the shared CP path used by Nemotron Omni and Qwen3.5 after the VLM pre-embeddingtorch.no_grad()wrapper was removed in #1914.Related Linear issue: AM-621
Root cause
VLM CP must build text and multimodal embeddings on the complete sequence before sharding. The shared recipe therefore calls the model's pre-embedding path and places the resulting
[batch, sequence, hidden]inputs_embedstensor in the CP batch.The old
torch.no_grad()wrapper made that tensor resizeable, but it also detached the token-embedding and optional multimodal gradient paths. PR #1914 intentionally removed the wrapper so trainable input embeddings and multimodal components receive gradients. Gemma4 uses its own model-owned_cp_make_batch_fn, but Nemotron Omni and Qwen3.5 continue through the shared PyTorch CP buffer path.PyTorch's public
context_parallel(..., buffers=...)implementation is the legacy path: it shards registered buffers in place withresize_/copy_. Resizing an autograd-tracked tensor is forbidden, so training fails before step 0 with:The failure is not specific to the latest PyTorch container; the same minimal reproducer fails on the tested PyTorch 2.11 and 2.12 builds.
Fix
narrow()andtorch.cat()operations.resize_and double-sharding.input_idsand non-gradient buffer paths unchanged.Backward now propagates from each local shard to the correct full-sequence positions and then to the input embedding or multimodal parameters.
PyTorch CP API direction
PyTorch has a newer internal CP design that avoids this mutation model:
_context_parallel_shardreturns new local shards out of place._ContextParallelapplies CP through module wrappers and DTensor dispatch rather than the legacy global SDPA monkey-patch path.These APIs are still private/underscored and experimental, so migrating Automodel's entire CP attention path to them would be a broader compatibility change. This fix adopts the relevant out-of-place, autograd-preserving sharding behavior while retaining the current CP context for attention and ordinary buffers.
Changelog
inputs_embeds.Test plan
ruff check nemo_automodel/components/distributed/cp_utils.py tests/unit_tests/distributed/test_cp_utils_inputs_embeds.pyruff format --check nemo_automodel/components/distributed/cp_utils.py tests/unit_tests/distributed/test_cp_utils_inputs_embeds.pyval_loss=2.0496)Before your PR is "Ready for review"
Pre checks: