clip_grad_norm avoid O(global_params) replication - #46113
Conversation
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
| if placement.is_replicate(): | ||
| continue | ||
| group = mesh.get_group(dim_idx) if mesh.ndim > 1 else mesh.get_group() | ||
| torch.distributed.all_reduce(partial, op=torch.distributed.ReduceOp.SUM, group=group) |
There was a problem hiding this comment.
if i understand properly, you are doing all_reduce over every parameters right ? It's going to be costly i think, is there a way to bucket them ?
There was a problem hiding this comment.
I missed that, you are right ! I'll fix this !
|
buckets params based on the mesh and placements. So we can buckets the all_reduce calls |
|
Nice ! A last polishing pass for readability and it should be good |
| for key, bucket_sum in norm_buckets.items(): | ||
| if key is not None: | ||
| mesh, reduce_placements = key | ||
| bucket_sum = DTensor.from_local(bucket_sum, mesh, reduce_placements).full_tensor() |
There was a problem hiding this comment.
can we use all_reduce to make it more explicit ?
|
View the CircleCI Test Summary for this PR: https://huggingface.co/spaces/transformers-community/circle-ci-viz?pr=46113&sha=7f56bf |
ArthurZucker
left a comment
There was a problem hiding this comment.
PR was reverted but happy to have cc @3outeille
Followup to the #45028 . I've been integrating the new native FSDP+TP to my benchmark.
Current
clip_grad_normcurrently has two issues that compose:_replicate_dtensor(g).to_local()into a Python list, holding all of them live for the duration of the function. Per-rank scratch is O(global_params) bytes — independent of how sharded the model is. On Qwen3-30B-A3B attp_size=8, fsdp_size=2this is a ~60 GB transient peak per rank, OOMing 80 GB H100s regardless of context length, batch size, or optimizer.grads(raw gradient tensors) totorch.nn.utils.clip_grads_with_norm_, but that API expectsparametersand re-extracts.gradinternally. Raw tensors havep.grad = None, so the inner list is empty and the function returns silently. The reportedtotal_normis correct, but no gradient is ever actually scaled:max_normis probably ignored.Mem Profile
torch.cuda.memory._record_memory_history(enabled='all', context='all', stacks='all')at job start (rank 0 only) — captures every allocation/free with full call stack (C++ + Python).torch.cuda.memory.memory_snapshot()+torch.cuda.memory_stats()at 11 named lifecycle stages — pickled to disk per stage so the OOM can't lose the data:S0_after_pg_initS1_after_from_pretrainedS2_after_batch_readyS3_step0_after_forwardS4_step0_after_backwardS5_step0_after_clipS6_step0_after_optim_stepS7_step0_after_zero_gradS3_step1_after_forwardS4_step1_after_backwardS99_OOM_at_step1reservedjumps from 21.55 GB (post-backward step 0) to 81.57 GB (post-clip step 0) — a 60 GB transient that the caching allocator never gives back to the driver. This is exactly the gradient-replicate scratch bufferclip_grad_normbuilds, plus its caching residue. The OOM at step 1 occurs because the second invocation tries to allocate the same 60 GB of scratch on top of 28.6 GB of live tensors plus the ~5 GB FSDP staging + 7 GB autograd retained.Validation
Who can review?