Skip to content
This repository was archived by the owner on Mar 20, 2026. It is now read-only.

Commit 5d7ed6a

Browse files
Initial support for ZeRO optimizer state sharding (#1259)
Summary: FairseqOSS will work with any optimizer and dtype. TODO(future PR): * support reduce instead of all_reduce * support gradient sharding * support parameter sharding Pull Request resolved: https://github.com/fairinternal/fairseq-py/pull/1259 Test Plan: Verified that checkpoint save and restore work. Verified that grad_norm, loss, and ppl are identical with and without sharding enable. Before: $ fairseq-train --task language_modeling data-bin/wikitext-103 --save-dir checkpoints/transformer_wikitext-103 --arch transformer_lm --share-decoder-input-output-embed --dropout 0.1 --optimizer adam --adam-betas '(0.9, 0.98)' --weight-decay 0.01 --clip-norm 0.0 --lr 0.0005 --lr-scheduler inverse_sqrt --warmup-updates 4000 --warmup-init-lr 1e-07 --tokens-per-sample 512 --sample-break-mode none --max-tokens 2048 --update-freq 16 --max-update 50000 --memory-efficient-fp16 --no-progress-bar --log-interval 1 --seed 4 --max-epoch 1 --max-update 50 ... 2020-08-27 22:24:51 | INFO | train_inner | epoch 001: 49 / 394 loss=18.84, ppl=469411, wps=269226, ups=1.03, wpb=262144, bsz=512, num_updates=45, lr=5.72388e-06, gnorm=5.769, loss_scale=8, train_wall=1, wall=68 2020-08-27 22:24:52 | INFO | train_inner | epoch 001: 50 / 394 loss=18.787, ppl=452312, wps=256992, ups=0.98, wpb=262144, bsz=512, num_updates=46, lr=5.84885e-06, gnorm=5.512, loss_scale=8, train_wall=1, wall=69 2020-08-27 22:24:53 | INFO | train_inner | epoch 001: 51 / 394 loss=18.74, ppl=437735, wps=259178, ups=0.99, wpb=262144, bsz=512, num_updates=47, lr=5.97383e-06, gnorm=5.298, loss_scale=8, train_wall=1, wall=70 2020-08-27 22:24:54 | INFO | train_inner | epoch 001: 52 / 394 loss=18.683, ppl=420727, wps=257710, ups=0.98, wpb=262144, bsz=512, num_updates=48, lr=6.0988e-06, gnorm=5.094, loss_scale=8, train_wall=1, wall=71 2020-08-27 22:24:55 | INFO | train_inner | epoch 001: 53 / 394 loss=18.623, ppl=403794, wps=269279, ups=1.03, wpb=262144, bsz=512, num_updates=49, lr=6.22378e-06, gnorm=4.893, loss_scale=8, train_wall=1, wall=72 2020-08-27 22:24:56 | INFO | train_inner | epoch 001: 54 / 394 loss=18.574, ppl=390255, wps=264616, ups=1.01, wpb=262144, bsz=512, num_updates=50, lr=6.34875e-06, gnorm=4.684, loss_scale=8, train_wall=1, wall=73 2020-08-27 22:24:56 | INFO | fairseq_cli.train | begin save checkpoint 2020-08-27 22:24:56 | INFO | fairseq_cli.train | end of epoch 1 (average epoch stats below) 2020-08-27 22:24:56 | INFO | train | epoch 001 | loss 19.736 | ppl 873122 | wps 264825 | ups 1.01 | wpb 262144 | bsz 512 | num_updates 50 | lr 6.34875e-06 | gnorm 8.898 | loss_scale 8 | train_wall 66 | wall 73 2020-08-27 22:24:56 | INFO | fairseq_cli.train | done training in 72.2 seconds After: $ fairseq-train --task language_modeling data-bin/wikitext-103 --save-dir checkpoints/transformer_wikitext-103 --arch transformer_lm --share-decoder-input-output-embed --dropout 0.1 --optimizer adam --adam-betas '(0.9, 0.98)' --weight-decay 0.01 --clip-norm 0.0 --lr 0.0005 --lr-scheduler inverse_sqrt --warmup-updates 4000 --warmup-init-lr 1e-07 --tokens-per-sample 512 --sample-break-mode none --max-tokens 2048 --update-freq 16 --max-update 50000 --memory-efficient-fp16 --no-progress-bar --log-interval 1 --seed 4 --max-epoch 1 --max-update 50 --zero-sharding os ... 2020-08-27 22:22:55 | INFO | train_inner | epoch 001: 49 / 394 loss=18.84, ppl=469411, wps=267663, ups=1.02, wpb=262144, bsz=512, num_updates=45, lr=5.72388e-06, gnorm=5.769, loss_scale=8, train_wall=1, wall=68 2020-08-27 22:22:56 | INFO | train_inner | epoch 001: 50 / 394 loss=18.787, ppl=452312, wps=252797, ups=0.96, wpb=262144, bsz=512, num_updates=46, lr=5.84885e-06, gnorm=5.512, loss_scale=8, train_wall=1, wall=69 2020-08-27 22:22:57 | INFO | train_inner | epoch 001: 51 / 394 loss=18.74, ppl=437735, wps=267692, ups=1.02, wpb=262144, bsz=512, num_updates=47, lr=5.97383e-06, gnorm=5.298, loss_scale=8, train_wall=1, wall=70 2020-08-27 22:22:58 | INFO | train_inner | epoch 001: 52 / 394 loss=18.683, ppl=420727, wps=267507, ups=1.02, wpb=262144, bsz=512, num_updates=48, lr=6.0988e-06, gnorm=5.094, loss_scale=8, train_wall=1, wall=71 2020-08-27 22:22:59 | INFO | train_inner | epoch 001: 53 / 394 loss=18.623, ppl=403794, wps=254410, ups=0.97, wpb=262144, bsz=512, num_updates=49, lr=6.22378e-06, gnorm=4.893, loss_scale=8, train_wall=1, wall=72 2020-08-27 22:23:00 | INFO | train_inner | epoch 001: 54 / 394 loss=18.574, ppl=390255, wps=268234, ups=1.02, wpb=262144, bsz=512, num_updates=50, lr=6.34875e-06, gnorm=4.684, loss_scale=8, train_wall=1, wall=73 2020-08-27 22:23:00 | INFO | fairseq_cli.train | begin save checkpoint 2020-08-27 22:23:00 | INFO | fairseq_cli.train | end of epoch 1 (average epoch stats below) 2020-08-27 22:23:00 | INFO | train | epoch 001 | loss 19.736 | ppl 873122 | wps 263570 | ups 1.01 | wpb 262144 | bsz 512 | num_updates 50 | lr 6.34875e-06 | gnorm 8.898 | loss_scale 8 | train_wall 66 | wall 73 2020-08-27 22:23:00 | INFO | fairseq_cli.train | done training in 72.3 seconds # Before submitting - [ ] Was this discussed/approved via a Github issue? (no need for typos, doc improvements) - [ ] Did you read the [contributor guideline](https://github.com/pytorch/fairseq/blob/master/CONTRIBUTING.md)? - [ ] Did you make sure to update the docs? - [ ] Did you write any new necessary tests? ## What does this PR do? Fixes # (issue). ## PR review Anyone in the community is free to review the PR once the tests have passed. If we didn't discuss your PR in Github issues there's a high chance it will not be merged. ## Did you have fun? Make sure you had fun coding � Reviewed By: myleott Differential Revision: D23432082 Pulled By: msbaines fbshipit-source-id: 6a020b25e36a3d9283582b7d89a6a53038e5b181
1 parent 251c869 commit 5d7ed6a

7 files changed

Lines changed: 93 additions & 14 deletions

File tree

‎fairseq/checkpoint_utils.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ def save_checkpoint(args, trainer, epoch_itr, val_loss):
3232
best_function = max if args.maximize_best_checkpoint_metric else min
3333
save_checkpoint.best = best_function(val_loss, prev_best)
3434

35-
if args.no_save or not trainer.is_data_parallel_master:
35+
if args.no_save:
36+
return
37+
38+
trainer.consolidate_optimizer()
39+
40+
if not trainer.is_data_parallel_master:
3641
return
3742

3843
def is_better(a, b):

‎fairseq/optim/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
from fairseq.optim.fairseq_optimizer import FairseqOptimizer
1111
from fairseq.optim.fp16_optimizer import FP16Optimizer, MemoryEfficientFP16Optimizer
1212
from fairseq.optim.bmuf import FairseqBMUF # noqa
13+
from fairseq.optim.shard import shard_
1314

1415

1516
__all__ = [
1617
'FairseqOptimizer',
1718
'FP16Optimizer',
1819
'MemoryEfficientFP16Optimizer',
20+
'shard_',
1921
]
2022

2123

‎fairseq/optim/fairseq_optimizer.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ def optimizer(self):
2828
raise ValueError('_optimizer must be an instance of torch.optim.Optimizer')
2929
return self._optimizer
3030

31+
@optimizer.setter
32+
def optimizer(self, optimizer):
33+
"""Reset optimizer instance."""
34+
if not hasattr(self, '_optimizer'):
35+
raise NotImplementedError
36+
if not isinstance(self._optimizer, torch.optim.Optimizer):
37+
raise ValueError('_optimizer must be an instance of torch.optim.Optimizer')
38+
self._optimizer = optimizer
39+
3140
@property
3241
def optimizer_config(self):
3342
"""

‎fairseq/optim/fp16_optimizer.py‎

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ def build_optimizer(cls, args, params):
232232
def optimizer(self):
233233
return self.fp32_optimizer.optimizer
234234

235+
@optimizer.setter
236+
def optimizer(self, optimizer):
237+
self.fp32_optimizer.optimizer = optimizer
238+
235239
@property
236240
def optimizer_config(self):
237241
return self.fp32_optimizer.optimizer_config
@@ -279,19 +283,20 @@ def load_state_dict(self, state_dict, optimizer_overrides=None):
279283
# params are FP16 while the optimizer state is FP32 and we don't want
280284
# to cast. A workaround is to manually copy back the original state
281285
# after the optimizer has been loaded.
282-
groups = self.optimizer.param_groups
283-
saved_groups = state_dict['param_groups']
284-
id_map = {
285-
old_id: p
286-
for old_id, p in zip(
287-
chain(*(g['params'] for g in saved_groups)),
288-
chain(*(g['params'] for g in groups))
289-
)
290-
}
291-
for k, v in state_dict['state'].items():
292-
if k in id_map:
293-
param = id_map[k]
294-
self.optimizer.state[param] = v
286+
if not getattr(self.optimizer, 'disable_mem_eff_fp16_loading_hack', False):
287+
groups = self.optimizer.param_groups
288+
saved_groups = state_dict['param_groups']
289+
id_map = {
290+
old_id: p
291+
for old_id, p in zip(
292+
chain(*(g['params'] for g in saved_groups)),
293+
chain(*(g['params'] for g in groups))
294+
)
295+
}
296+
for k, v in state_dict['state'].items():
297+
if k in id_map:
298+
param = id_map[k]
299+
self.optimizer.state[param] = v
295300

296301
def backward(self, loss):
297302
"""Computes the sum of gradients of the given tensor w.r.t. graph leaves.
@@ -412,6 +417,10 @@ def build_optimizer(cls, args, params):
412417
def optimizer(self):
413418
return self.wrapped_optimizer.optimizer
414419

420+
@optimizer.setter
421+
def optimizer(self, optimizer):
422+
self.wrapped_optimizer.optimizer = optimizer
423+
415424
@property
416425
def optimizer_config(self):
417426
return self.wrapped_optimizer.optimizer_config

‎fairseq/optim/shard.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
7+
try:
8+
from fairscale.optim import OSS
9+
_has_fairscale = True
10+
except ImportError:
11+
_has_fairscale = False
12+
13+
14+
def shard_(args, optimizer):
15+
if not _has_fairscale:
16+
raise ImportError(
17+
'\n\nPlease install the fairscale package:'
18+
'\n\n pip install fairscale'
19+
)
20+
21+
class FairseqOSS(OSS):
22+
@property
23+
def disable_mem_eff_fp16_loading_hack(self):
24+
return True
25+
26+
def __getattr__(self, name):
27+
if name.startswith("supports") and hasattr(self.optim, name):
28+
return getattr(self.optim, name)
29+
raise AttributeError("'FairseqOSS' object has no attribute {0!r}".format(name))
30+
31+
torch_optimizer = optimizer.optimizer
32+
optim_cls = type(torch_optimizer)
33+
optimizer.optimizer = FairseqOSS(torch_optimizer.param_groups, optim_cls, **optimizer.optimizer_config)

‎fairseq/options.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ def add_distributed_training_args(parser, default_world_size=None):
448448
help='number of GPUs in each node. An allreduce operation across GPUs in '
449449
'a node is very fast. Hence, we do allreduce across GPUs in a node, '
450450
'and gossip across different nodes')
451+
# Add argument for ZeRO sharding of OptimizerState(os), gradients(g) and parameters(p)
452+
group.add_argument('--zero-sharding', default='none', type=str,
453+
choices=['none', 'os'],
454+
help='ZeRO sharding')
451455
# fmt: on
452456
return group
453457

‎fairseq/trainer.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,28 @@ def _build_optimizer(self):
214214
if self.args.use_bmuf:
215215
self._optimizer = optim.FairseqBMUF(self.args, self._optimizer)
216216

217+
if self.args.zero_sharding == 'os':
218+
if (self.args.fp16
219+
and not self.args.memory_efficient_fp16
220+
and not self.args.memory_efficient_bf16
221+
) and not self.args.fp16_no_flatten_grads:
222+
raise ValueError(
223+
"ZeRO is incomptabile with fp16 and flattened grads. "
224+
"Please use --fp16-no-flatten-grads"
225+
)
226+
else:
227+
optim.shard_(self.args, self._optimizer)
228+
217229
# We should initialize the learning rate scheduler immediately after
218230
# building the optimizer, so that the initial learning rate is set.
219231
self._lr_scheduler = lr_scheduler.build_lr_scheduler(self.args, self.optimizer)
220232
self._lr_scheduler.step_update(0)
221233

234+
def consolidate_optimizer(self):
235+
"""For OSS, we need to consolidate the state dict."""
236+
if hasattr(self.optimizer.optimizer, "consolidate_state_dict"):
237+
self.optimizer.optimizer.consolidate_state_dict()
238+
222239
def save_checkpoint(self, filename, extra_state):
223240
"""Save all training state in a checkpoint file."""
224241
if self.is_data_parallel_master: # only save one checkpoint

0 commit comments

Comments
 (0)