Skip to content

Commit c1e509b

Browse files
diego-urgellmeta-codesync[bot]
authored andcommitted
Add gradient_accumulation_no_sync flag to AutoUnit (#1057)
Summary: Pull Request resolved: #1057 ### Context TorchTNT's AutoUnit.train_step calls set_requires_gradient_sync(False) on FSDP2 modules during gradient accumulation micro-batches. This tells FSDP2 to skip the gradient reduce-scatter in backward, keeping full unsharded gradients in memory on each rank. For large models this is catastrophic: - 26B model on 16 GPUs: sharded gradients = 3.25 GB/rank, unsharded = ~52 GB/rank. Observed memory jump from 38% → 86% GPU utilization — a 48% increase from gradient accumulation alone. - Small models (2B-4B): only 5-8% increase, because the absolute gradient size is small relative to 80 GB GPU memory. The FSDP2 API already supports the memory-efficient alternative: simply don't disable gradient sync. Gradients reduce-scatter every micro-batch and accumulate in .grad in sharded form. This is exactly what torchtitan does. The only tradeoff is N× more reduce-scatter communication (N = gradient_accumulation_steps). Goal: Add a single flag to AutoUnit that controls whether no_sync is used during gradient accumulation, enabling users to trade communication for memory. ### Approach Add gradient_accumulation_no_sync: bool = True to AutoUnit.__init__. When False, skip both: - FSDP2: set_requires_gradient_sync(False) call - DDP/FSDP1: module.no_sync() context manager Gradients then reduce-scatter (FSDP2) or all-reduce (DDP) on every micro-batch and accumulate in sharded .grad. The math is identical — reduce is associative over micro-batches. No changes needed in HuggingfaceSFTUnit or MediaAutoUnit — the flag flows through **kwargs. Reviewed By: galrotem Differential Revision: D99706122 fbshipit-source-id: 321a130aaaacfa98af9551f9d4579273823e7c23
1 parent 861ca2c commit c1e509b

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

tests/framework/test_auto_unit.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,27 @@ def test_gradient_accumulation_fsdp2(self, _) -> None:
781781

782782
auto_unit.train_progress.increment_step()
783783

784+
@patch("torchtnt.framework.auto_unit._is_fsdp2_module", return_value=True)
785+
def test_gradient_accumulation_fsdp2_sync_true(self, _) -> None:
786+
"""When gradient_accumulation_sync=True, set_requires_gradient_sync
787+
should never be called — gradients sync on every micro-batch."""
788+
auto_unit = DummyAutoUnit(
789+
module=torch.nn.Linear(1, 1),
790+
gradient_accumulation_steps=3,
791+
gradient_accumulation_sync=True,
792+
)
793+
794+
fsdp_module_mock = MagicMock()
795+
auto_unit.module.set_requires_gradient_sync = fsdp_module_mock
796+
auto_unit._is_last_batch = False
797+
798+
state = get_dummy_train_state()
799+
800+
for _step in range(4):
801+
auto_unit.train_step(state, (torch.rand(1, 1), torch.rand(1, 1)))
802+
fsdp_module_mock.assert_not_called()
803+
auto_unit.train_progress.increment_step()
804+
784805
@patch("torchtnt.framework.auto_unit.prepare_module")
785806
def test_global_mesh(self, mock_prepare_module: Mock) -> None:
786807
"""

torchtnt/framework/auto_unit.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,11 @@ class AutoUnit(
474474
step_lr_interval: whether to step lr_scheduler every step or every epoch. Defaults to every epoch.
475475
precision: the precision to use in training/evaluation (using automatic mixed precision), as either a string or a torch.dtype. Acceptable strings are ``'fp32'``, ``'fp16'``, and ``'bf16'``.
476476
gradient_accumulation_steps: how many batches to accumulate gradients over.
477+
gradient_accumulation_sync: if True, gradients are synchronized (reduce-scatter for FSDP2,
478+
all-reduce for DDP) on every micro-batch during gradient accumulation and accumulated in sharded form.
479+
This uses more communication but significantly less memory — recommended for large models.
480+
If False (default), synchronization is skipped during accumulation micro-batches using ``no_sync`` / ``set_requires_gradient_sync(False)``,
481+
which reduces communication but keeps full unsharded gradients in memory.
477482
detect_anomaly: whether to enable anomaly detection for the autograd engine https://pytorch.org/docs/stable/autograd.html#anomaly-detection
478483
clip_grad_norm: max norm of the gradients for clipping https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html
479484
clip_grad_value: max value of the gradients for clipping https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_value_.html
@@ -515,6 +520,7 @@ def __init__(
515520
step_lr_interval: Literal["step", "epoch"] = "epoch",
516521
precision: Optional[Union[str, torch.dtype]] = None,
517522
gradient_accumulation_steps: int = 1,
523+
gradient_accumulation_sync: bool = False,
518524
detect_anomaly: Optional[bool] = None,
519525
clip_grad_norm: Optional[float] = None,
520526
clip_grad_value: Optional[float] = None,
@@ -585,6 +591,7 @@ def __init__(
585591
self.step_lr_interval = step_lr_interval
586592

587593
self.gradient_accumulation_steps = gradient_accumulation_steps
594+
self.gradient_accumulation_sync = gradient_accumulation_sync
588595

589596
self.clip_grad_norm = clip_grad_norm
590597
self.clip_grad_value = clip_grad_value
@@ -697,11 +704,12 @@ def train_step(self, state: State, data: TData) -> Tuple[torch.Tensor, Any]:
697704
maybe_no_sync = (
698705
module.no_sync()
699706
if not should_update_weights
707+
and not self.gradient_accumulation_sync
700708
and (isinstance(module, DDP) or isinstance(module, FSDP))
701709
else contextlib.nullcontext()
702710
)
703711
# fsdp2 has separate way of disabling gradient sync
704-
if _is_fsdp2_module(module):
712+
if _is_fsdp2_module(module) and not self.gradient_accumulation_sync:
705713
if not should_update_weights:
706714
cast(FSDPModule, module).set_requires_gradient_sync(False)
707715
elif should_update_weights and self.gradient_accumulation_steps > 1:

0 commit comments

Comments
 (0)