megatron-core 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megatron-core might be problematic. Click here for more details.
- megatron/core/__init__.py +12 -0
- megatron/core/enums.py +7 -0
- megatron/core/package_info.py +23 -0
- megatron/core/parallel_state.py +570 -0
- megatron/core/pipeline_parallel/__init__.py +1 -0
- megatron/core/pipeline_parallel/p2p_communication.py +456 -0
- megatron/core/pipeline_parallel/schedules.py +1050 -0
- megatron/core/tensor_parallel/__init__.py +65 -0
- megatron/core/tensor_parallel/cross_entropy.py +143 -0
- megatron/core/tensor_parallel/data.py +105 -0
- megatron/core/tensor_parallel/layers.py +716 -0
- megatron/core/tensor_parallel/mappings.py +279 -0
- megatron/core/tensor_parallel/random.py +253 -0
- megatron/core/tensor_parallel/utils.py +108 -0
- megatron/core/utils.py +137 -0
- megatron_core-0.1.0.dist-info/LICENSE +376 -0
- megatron_core-0.1.0.dist-info/METADATA +35 -0
- megatron_core-0.1.0.dist-info/RECORD +20 -0
- megatron_core-0.1.0.dist-info/WHEEL +5 -0
- megatron_core-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
from typing import Callable, Iterator, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch.autograd.variable import Variable
|
|
8
|
+
from torch.nn.parallel.distributed import DistributedDataParallel as torchDDP
|
|
9
|
+
|
|
10
|
+
from megatron.core import parallel_state
|
|
11
|
+
from megatron.core.pipeline_parallel import p2p_communication
|
|
12
|
+
from megatron.core.enums import ModelType
|
|
13
|
+
from megatron.core.utils import get_attr_wrapped_model, get_model_type
|
|
14
|
+
|
|
15
|
+
# Types
|
|
16
|
+
Shape = Union[List[int], torch.Size]
|
|
17
|
+
|
|
18
|
+
def get_forward_backward_func():
|
|
19
|
+
"""Retrieves the appropriate forward_backward function given the
|
|
20
|
+
configuration of parallel_state.
|
|
21
|
+
|
|
22
|
+
Returns a function that will perform all of the forward and
|
|
23
|
+
backward passes of the model given the pipeline model parallel
|
|
24
|
+
world size and virtual pipeline model parallel world size in the
|
|
25
|
+
global parallel_state.
|
|
26
|
+
|
|
27
|
+
The function returned takes the following arguments:
|
|
28
|
+
|
|
29
|
+
forward_step_func (required): A function that takes a data
|
|
30
|
+
iterator and a model as its arguments and return the model's
|
|
31
|
+
forward output and the loss function. The loss function should
|
|
32
|
+
take one torch.Tensor and return a torch.Tensor of loss and a
|
|
33
|
+
dictionary of string -> torch.Tensor.
|
|
34
|
+
|
|
35
|
+
For example:
|
|
36
|
+
|
|
37
|
+
def loss_func(loss_mask, output_tensor):
|
|
38
|
+
losses = output_tensor.float()
|
|
39
|
+
loss_mask = loss_mask.view(-1).float()
|
|
40
|
+
loss = torch.sum(losses.view(-1) * loss_mask) / loss_mask.sum()
|
|
41
|
+
|
|
42
|
+
# Reduce loss for logging.
|
|
43
|
+
averaged_loss = average_losses_across_data_parallel_group([loss])
|
|
44
|
+
|
|
45
|
+
return loss, {'lm loss': averaged_loss[0]}
|
|
46
|
+
|
|
47
|
+
def forward_step(data_iterator, model):
|
|
48
|
+
data, loss_mask = next(data_iterator)
|
|
49
|
+
output = model(data)
|
|
50
|
+
return output, partial(loss_func, loss_mask)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
forward_backward_func(forward_step_func=forward_step, ...)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
data_iterator (required): an iterator over the data, will be
|
|
57
|
+
passed as is to forward_step_func. Expected to be a list of
|
|
58
|
+
iterators in the case of interleaved pipeline parallelism.
|
|
59
|
+
|
|
60
|
+
model (required): the actual model. Expected to be a list of
|
|
61
|
+
modules in the case of interleaved pipeline parallelism.
|
|
62
|
+
|
|
63
|
+
num_microbatches (int, required):
|
|
64
|
+
The number of microbatches to go through
|
|
65
|
+
|
|
66
|
+
dtype (required when using pipeline parallelism): dtype used in
|
|
67
|
+
p2p communication, usually params_dtype
|
|
68
|
+
|
|
69
|
+
tensor_shape (required when using pipeline parallelism): Shape of
|
|
70
|
+
tensor. The tensor is expected to be 3D and its order of
|
|
71
|
+
dimension is supposed to be ``(sequence, batch, hidden)``.
|
|
72
|
+
|
|
73
|
+
decoder_seq_length (int, required for ModelType.encoder_and_decoder models):
|
|
74
|
+
Sequence length of the decoder portion, used to determine tensor shapes.
|
|
75
|
+
|
|
76
|
+
grad_scaler (optional, default=None): If using loss scaling,
|
|
77
|
+
this function should take the loss and return the scaled
|
|
78
|
+
loss. If None, no function is called on the loss.
|
|
79
|
+
|
|
80
|
+
sequence_parallel (optional, default=False):
|
|
81
|
+
Set to :obj:`True` for this function to handle sequence
|
|
82
|
+
length. When :obj:`True`, the sequence length on each tensor
|
|
83
|
+
model parallel rank is updated to
|
|
84
|
+
:math:`original\_sequence\_length /
|
|
85
|
+
tensor\_model\_parallel\_world\_size`.
|
|
86
|
+
TODO: Do we need this? Just roll into tensor_shape arg?
|
|
87
|
+
|
|
88
|
+
forward_only (optional, default=False): Perform only the forward step
|
|
89
|
+
|
|
90
|
+
timers (optional, default=None): TODO
|
|
91
|
+
|
|
92
|
+
collect_non_loss_data: TODO
|
|
93
|
+
|
|
94
|
+
enable_autocast (optional, default=False): If True, runs the
|
|
95
|
+
forward_step_func call inside torch.autocast context
|
|
96
|
+
|
|
97
|
+
deallocate_pipeline_outputs (optional, default=False): If True, output data
|
|
98
|
+
is deallocated after the tensor is sent to the next pipeline stage.
|
|
99
|
+
Helps with saving memory, does nothing when pipeline parallel is
|
|
100
|
+
not used.
|
|
101
|
+
|
|
102
|
+
no_sync_func (optional): Function that creates a context that
|
|
103
|
+
suppresses asynchronous data-parallel communication. If the
|
|
104
|
+
model is an instance of torch.nn.DistributedDataParallel, the
|
|
105
|
+
default is to use torch.nn.DistributedDataParallel.no_sync.
|
|
106
|
+
|
|
107
|
+
grad_sync_func (optional): Function that launches asynchronous
|
|
108
|
+
gradient reductions (e.g. distributed optimizer gradient
|
|
109
|
+
reduce-scatters). The function should take one argument: an
|
|
110
|
+
iterable of parameters whose gradients are to be synchronized.
|
|
111
|
+
|
|
112
|
+
param_sync_func (optional): Function that launches asynchronous
|
|
113
|
+
parameter synchronizations (e.g. distributed optimizer
|
|
114
|
+
parameter all-gathers). The function should take one argument:
|
|
115
|
+
an iterable of parameters to be synchronized.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
pipeline_model_parallel_size = parallel_state.get_pipeline_model_parallel_world_size()
|
|
119
|
+
if pipeline_model_parallel_size > 1:
|
|
120
|
+
if parallel_state.get_virtual_pipeline_model_parallel_world_size() is not None:
|
|
121
|
+
forward_backward_func = forward_backward_pipelining_with_interleaving
|
|
122
|
+
else:
|
|
123
|
+
forward_backward_func = forward_backward_pipelining_without_interleaving
|
|
124
|
+
else:
|
|
125
|
+
forward_backward_func = forward_backward_no_pipelining
|
|
126
|
+
return forward_backward_func
|
|
127
|
+
|
|
128
|
+
def deallocate_output_tensor(out, deallocate_pipeline_outputs=False):
|
|
129
|
+
'''Pseudo-deallocate (i.e., set to scalar) the output tensor's '.data' field.
|
|
130
|
+
|
|
131
|
+
This method should be called right after the output tensor has been
|
|
132
|
+
sent to the next pipeline stage. At this point, the output tensor is
|
|
133
|
+
only useful for its '.grad_fn' field, and not its '.data'.
|
|
134
|
+
'''
|
|
135
|
+
if (out is None) or (not deallocate_pipeline_outputs):
|
|
136
|
+
return
|
|
137
|
+
assert isinstance(out, torch.Tensor), \
|
|
138
|
+
"expected Tensor, found %s." % type(out).__name__
|
|
139
|
+
assert out._base is None, \
|
|
140
|
+
"counter-productive to free a view of another tensor."
|
|
141
|
+
out.data = torch.empty(
|
|
142
|
+
(1,),
|
|
143
|
+
device = out.device,
|
|
144
|
+
dtype = out.dtype,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def custom_backward(output, grad_output):
|
|
148
|
+
'''Directly call C++ autograd engine.
|
|
149
|
+
|
|
150
|
+
To make the 'deallocate_output_tensor' (above) optimization work, the C++
|
|
151
|
+
autograd engine must be called directly, bypassing Pytorch's
|
|
152
|
+
torch.autograd.backward. Pytorch's 'backward' checks that the output and
|
|
153
|
+
grad have the same shape, while C++'s 'backward' does not.
|
|
154
|
+
'''
|
|
155
|
+
|
|
156
|
+
assert output.numel() == 1, \
|
|
157
|
+
"output should be pseudo-'freed' in schedule, to optimize memory"
|
|
158
|
+
assert isinstance(output, torch.Tensor), \
|
|
159
|
+
"output == '%s'." % type(output).__name__
|
|
160
|
+
assert isinstance(grad_output, (torch.Tensor, type(None))), \
|
|
161
|
+
"grad_output == '%s'." % type(grad_output).__name__
|
|
162
|
+
|
|
163
|
+
# Handle scalar output
|
|
164
|
+
if grad_output is None:
|
|
165
|
+
assert output.numel() == 1, "implicit grad requires scalar output."
|
|
166
|
+
grad_output = torch.ones_like(
|
|
167
|
+
output,
|
|
168
|
+
memory_format = torch.preserve_format,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Call c++ engine [ see torch/csrc/autograd/python_engine.cpp ]
|
|
172
|
+
Variable._execution_engine.run_backward(
|
|
173
|
+
tensors = (output,),
|
|
174
|
+
grad_tensors = (grad_output,),
|
|
175
|
+
keep_graph = False,
|
|
176
|
+
create_graph = False,
|
|
177
|
+
inputs = tuple(),
|
|
178
|
+
allow_unreachable=True,
|
|
179
|
+
accumulate_grad=True,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def forward_step(forward_step_func,
|
|
187
|
+
data_iterator,
|
|
188
|
+
model,
|
|
189
|
+
num_microbatches,
|
|
190
|
+
input_tensor,
|
|
191
|
+
forward_data_store,
|
|
192
|
+
timers,
|
|
193
|
+
collect_non_loss_data=False,
|
|
194
|
+
autocast_dtype=torch.float,
|
|
195
|
+
enable_autocast=False):
|
|
196
|
+
"""Forward step for passed-in model.
|
|
197
|
+
|
|
198
|
+
If first stage, input tensor is obtained from data_iterator, otherwise
|
|
199
|
+
passed-in input_tensor is used.
|
|
200
|
+
|
|
201
|
+
Returns output tensor."""
|
|
202
|
+
if timers is not None:
|
|
203
|
+
timers('forward-compute', log_level=2).start()
|
|
204
|
+
|
|
205
|
+
unwrap_output_tensor = False
|
|
206
|
+
if not isinstance(input_tensor, list):
|
|
207
|
+
input_tensor = [input_tensor]
|
|
208
|
+
unwrap_output_tensor = True
|
|
209
|
+
|
|
210
|
+
set_input_tensor = get_attr_wrapped_model(model, "set_input_tensor")
|
|
211
|
+
set_input_tensor(input_tensor)
|
|
212
|
+
|
|
213
|
+
if enable_autocast:
|
|
214
|
+
context_manager = torch.autocast("cuda", dtype=autocast_dtype)
|
|
215
|
+
else:
|
|
216
|
+
context_manager = contextlib.nullcontext()
|
|
217
|
+
with context_manager:
|
|
218
|
+
output_tensor, loss_func = forward_step_func(data_iterator, model)
|
|
219
|
+
|
|
220
|
+
if parallel_state.is_pipeline_last_stage():
|
|
221
|
+
if not collect_non_loss_data:
|
|
222
|
+
output_tensor = loss_func(output_tensor)
|
|
223
|
+
loss, loss_reduced = output_tensor
|
|
224
|
+
output_tensor = loss / num_microbatches
|
|
225
|
+
forward_data_store.append(loss_reduced)
|
|
226
|
+
else:
|
|
227
|
+
data = loss_func(output_tensor, non_loss_data=True)
|
|
228
|
+
forward_data_store.append(data)
|
|
229
|
+
|
|
230
|
+
if timers is not None:
|
|
231
|
+
timers('forward-compute').stop()
|
|
232
|
+
|
|
233
|
+
# If T5 model (or other model with encoder and decoder)
|
|
234
|
+
# and in decoder stack, then send encoder_hidden_state
|
|
235
|
+
# downstream as well.
|
|
236
|
+
model_type = get_model_type(model)
|
|
237
|
+
|
|
238
|
+
if parallel_state.is_pipeline_stage_after_split() and \
|
|
239
|
+
model_type == ModelType.encoder_and_decoder:
|
|
240
|
+
return [output_tensor, input_tensor[-1]]
|
|
241
|
+
if unwrap_output_tensor:
|
|
242
|
+
return output_tensor
|
|
243
|
+
return [output_tensor]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def backward_step(grad_scaler, input_tensor, output_tensor,
|
|
247
|
+
output_tensor_grad, model_type, timers, deallocate_pipeline_outputs=False):
|
|
248
|
+
"""Backward step through passed-in output tensor.
|
|
249
|
+
|
|
250
|
+
If last stage, output_tensor_grad is None, otherwise gradient of loss
|
|
251
|
+
with respect to stage's output tensor.
|
|
252
|
+
|
|
253
|
+
Returns gradient of loss with respect to input tensor (None if first
|
|
254
|
+
stage)."""
|
|
255
|
+
|
|
256
|
+
# NOTE: This code currently can handle at most one skip connection. It
|
|
257
|
+
# needs to be modified slightly to support arbitrary numbers of skip
|
|
258
|
+
# connections.
|
|
259
|
+
|
|
260
|
+
if timers is not None:
|
|
261
|
+
timers('backward-compute', log_level=2).start()
|
|
262
|
+
|
|
263
|
+
# Retain the grad on the input_tensor.
|
|
264
|
+
unwrap_input_tensor_grad = False
|
|
265
|
+
if not isinstance(input_tensor, list):
|
|
266
|
+
input_tensor = [input_tensor]
|
|
267
|
+
unwrap_input_tensor_grad = True
|
|
268
|
+
for x in input_tensor:
|
|
269
|
+
if x is not None:
|
|
270
|
+
x.retain_grad()
|
|
271
|
+
|
|
272
|
+
if not isinstance(output_tensor, list):
|
|
273
|
+
output_tensor = [output_tensor]
|
|
274
|
+
if not isinstance(output_tensor_grad, list):
|
|
275
|
+
output_tensor_grad = [output_tensor_grad]
|
|
276
|
+
|
|
277
|
+
# Backward pass.
|
|
278
|
+
if output_tensor_grad[0] is None and grad_scaler is not None:
|
|
279
|
+
output_tensor = grad_scaler(output_tensor[0])
|
|
280
|
+
|
|
281
|
+
if deallocate_pipeline_outputs:
|
|
282
|
+
custom_backward(output_tensor[0], output_tensor_grad[0])
|
|
283
|
+
else:
|
|
284
|
+
torch.autograd.backward(output_tensor[0], grad_tensors=output_tensor_grad[0])
|
|
285
|
+
|
|
286
|
+
# Collect the grad of the input_tensor.
|
|
287
|
+
input_tensor_grad = [None]
|
|
288
|
+
if input_tensor is not None:
|
|
289
|
+
input_tensor_grad = []
|
|
290
|
+
for x in input_tensor:
|
|
291
|
+
if x is None:
|
|
292
|
+
input_tensor_grad.append(None)
|
|
293
|
+
else:
|
|
294
|
+
input_tensor_grad.append(x.grad)
|
|
295
|
+
|
|
296
|
+
# Handle single skip connection if it exists (encoder_hidden_state in
|
|
297
|
+
# model with encoder and decoder).
|
|
298
|
+
if parallel_state.get_pipeline_model_parallel_world_size() > 1 and \
|
|
299
|
+
parallel_state.is_pipeline_stage_after_split() and \
|
|
300
|
+
model_type == ModelType.encoder_and_decoder:
|
|
301
|
+
if output_tensor_grad[1] is not None:
|
|
302
|
+
input_tensor_grad[-1].add_(output_tensor_grad[1])
|
|
303
|
+
if unwrap_input_tensor_grad:
|
|
304
|
+
input_tensor_grad = input_tensor_grad[0]
|
|
305
|
+
|
|
306
|
+
if timers is not None:
|
|
307
|
+
timers('backward-compute').stop()
|
|
308
|
+
|
|
309
|
+
return input_tensor_grad
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def forward_backward_no_pipelining(*,
|
|
313
|
+
forward_step_func,
|
|
314
|
+
data_iterator: Union[Iterator, List[Iterator]],
|
|
315
|
+
model: Union[torch.nn.Module, List[torch.nn.Module]],
|
|
316
|
+
num_microbatches: int,
|
|
317
|
+
dtype: Optional[torch.dtype] = None,
|
|
318
|
+
tensor_shape: Optional[Shape] = None, # unused
|
|
319
|
+
decoder_seq_length: Optional[int] = None, # unused
|
|
320
|
+
grad_scaler: Callable = None,
|
|
321
|
+
sequence_parallel: bool = False, # unused
|
|
322
|
+
forward_only: bool = False,
|
|
323
|
+
timers: Callable = None,
|
|
324
|
+
collect_non_loss_data: bool = False,
|
|
325
|
+
enable_autocast: bool = False,
|
|
326
|
+
deallocate_pipeline_outputs: bool = False,
|
|
327
|
+
no_sync_func: Optional[Callable] = None,
|
|
328
|
+
grad_sync_func: Optional[Callable] = None, # unused
|
|
329
|
+
param_sync_func: Optional[Callable] = None, # unused
|
|
330
|
+
):
|
|
331
|
+
"""Run forward and backward passes with no pipeline parallelism
|
|
332
|
+
(no inter-stage communication).
|
|
333
|
+
|
|
334
|
+
Returns dictionary with losses.
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
See get_forward_backward_func() for argument details
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
if isinstance(model, list):
|
|
341
|
+
assert len(model) == 1, \
|
|
342
|
+
"non-pipeline-parallel schedule does not support model chunking"
|
|
343
|
+
model = model[0]
|
|
344
|
+
if isinstance(data_iterator, list):
|
|
345
|
+
assert len(data_iterator) == 1, \
|
|
346
|
+
"non-pipeline-parallel schedule does not support model chunking"
|
|
347
|
+
data_iterator = data_iterator[0]
|
|
348
|
+
|
|
349
|
+
if no_sync_func is None and isinstance(model, torchDDP):
|
|
350
|
+
no_sync_func = model.no_sync
|
|
351
|
+
if no_sync_func is None:
|
|
352
|
+
no_sync_func = contextlib.nullcontext
|
|
353
|
+
|
|
354
|
+
model_type = get_model_type(model)
|
|
355
|
+
|
|
356
|
+
forward_data_store = []
|
|
357
|
+
input_tensor, output_tensor_grad = None, None
|
|
358
|
+
with no_sync_func():
|
|
359
|
+
for i in range(num_microbatches - 1):
|
|
360
|
+
output_tensor = forward_step(forward_step_func, data_iterator,
|
|
361
|
+
model, num_microbatches, input_tensor, forward_data_store,
|
|
362
|
+
timers, collect_non_loss_data, dtype, enable_autocast)
|
|
363
|
+
if not forward_only:
|
|
364
|
+
backward_step(grad_scaler, input_tensor, output_tensor,
|
|
365
|
+
output_tensor_grad, model_type, timers, deallocate_pipeline_outputs)
|
|
366
|
+
|
|
367
|
+
# Run computation for last microbatch out of context handler (want to
|
|
368
|
+
# synchronize gradients).
|
|
369
|
+
output_tensor = forward_step(forward_step_func, data_iterator,
|
|
370
|
+
model, num_microbatches, input_tensor, forward_data_store,
|
|
371
|
+
timers, collect_non_loss_data, dtype, enable_autocast)
|
|
372
|
+
|
|
373
|
+
if not forward_only:
|
|
374
|
+
backward_step(grad_scaler, input_tensor, output_tensor,
|
|
375
|
+
output_tensor_grad, model_type, timers, deallocate_pipeline_outputs)
|
|
376
|
+
|
|
377
|
+
return forward_data_store
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def forward_backward_pipelining_with_interleaving(*,
|
|
381
|
+
forward_step_func,
|
|
382
|
+
data_iterator: Union[Iterator, List[Iterator]],
|
|
383
|
+
model: Union[torch.nn.Module, List[torch.nn.Module]],
|
|
384
|
+
num_microbatches: int,
|
|
385
|
+
dtype: torch.dtype,
|
|
386
|
+
tensor_shape: Shape,
|
|
387
|
+
decoder_seq_length: Optional[int] = None,
|
|
388
|
+
grad_scaler: Callable = None,
|
|
389
|
+
sequence_parallel: bool = False,
|
|
390
|
+
forward_only: bool = False,
|
|
391
|
+
timers: Callable = None,
|
|
392
|
+
collect_non_loss_data: bool = False,
|
|
393
|
+
enable_autocast: bool = False,
|
|
394
|
+
deallocate_pipeline_outputs: bool = False,
|
|
395
|
+
no_sync_func: Optional[Callable] = None,
|
|
396
|
+
grad_sync_func: Optional[Callable] = None,
|
|
397
|
+
param_sync_func: Optional[Callable] = None,
|
|
398
|
+
):
|
|
399
|
+
"""Run interleaved 1F1B schedule (model split into model chunks), with
|
|
400
|
+
communication between pipeline stages as needed.
|
|
401
|
+
|
|
402
|
+
Returns dictionary with losses if the last stage, empty dict otherwise."""
|
|
403
|
+
assert isinstance(model, list), \
|
|
404
|
+
"interleaved pipeline parallelism expected model chunking"
|
|
405
|
+
assert all(isinstance(chunk, torch.nn.Module) for chunk in model), \
|
|
406
|
+
"invalid model chunking"
|
|
407
|
+
assert isinstance(data_iterator, list), \
|
|
408
|
+
"interleaved pipeline parallelism expected each model chunk to have a data iterator"
|
|
409
|
+
|
|
410
|
+
# Disable async grad reductions
|
|
411
|
+
if no_sync_func is None and all(isinstance(chunk, torchDDP) for chunk in model):
|
|
412
|
+
def multi_no_sync():
|
|
413
|
+
stack = contextlib.ExitStack()
|
|
414
|
+
for chunk in model:
|
|
415
|
+
stack.enter_context(chunk.no_sync())
|
|
416
|
+
return stack
|
|
417
|
+
no_sync_func = multi_no_sync
|
|
418
|
+
if no_sync_func is None:
|
|
419
|
+
no_sync_func = contextlib.nullcontext
|
|
420
|
+
no_sync_context = None
|
|
421
|
+
def disable_grad_sync():
|
|
422
|
+
"""Disable asynchronous grad reductions"""
|
|
423
|
+
nonlocal no_sync_context
|
|
424
|
+
if no_sync_context is None:
|
|
425
|
+
no_sync_context = no_sync_func()
|
|
426
|
+
no_sync_context.__enter__()
|
|
427
|
+
def enable_grad_sync():
|
|
428
|
+
"""Enable asynchronous grad reductions"""
|
|
429
|
+
nonlocal no_sync_context
|
|
430
|
+
if no_sync_context is not None:
|
|
431
|
+
no_sync_context.__exit__(None, None, None)
|
|
432
|
+
no_sync_context = None
|
|
433
|
+
disable_grad_sync()
|
|
434
|
+
|
|
435
|
+
# Model chunk IDs with synchronized grads
|
|
436
|
+
synchronized_model_chunks = set()
|
|
437
|
+
|
|
438
|
+
input_tensors = [[] for _ in range(len(model))]
|
|
439
|
+
output_tensors = [[] for _ in range(len(model))]
|
|
440
|
+
forward_data_store = []
|
|
441
|
+
if not forward_only:
|
|
442
|
+
output_tensor_grads = [[] for _ in range(len(model))]
|
|
443
|
+
|
|
444
|
+
pipeline_parallel_size = parallel_state.get_pipeline_model_parallel_world_size()
|
|
445
|
+
pipeline_parallel_rank = parallel_state.get_pipeline_model_parallel_rank()
|
|
446
|
+
|
|
447
|
+
if num_microbatches % pipeline_parallel_size != 0:
|
|
448
|
+
msg = f'number of microbatches ({num_microbatches}) is not divisible by '
|
|
449
|
+
msg += f'pipeline-model-parallel-size ({pipeline_parallel_size}) '
|
|
450
|
+
msg += 'when using interleaved schedule'
|
|
451
|
+
raise RuntimeError(msg)
|
|
452
|
+
|
|
453
|
+
model_type = get_model_type(model[0])
|
|
454
|
+
if model_type == ModelType.encoder_and_decoder:
|
|
455
|
+
raise RuntimeError("Interleaving is not supported with an encoder and decoder model.")
|
|
456
|
+
|
|
457
|
+
if decoder_seq_length is not None and decoder_seq_length != tensor_shape[0]:
|
|
458
|
+
raise RuntimeError("Interleaving is not supported with a different decoder sequence length.")
|
|
459
|
+
|
|
460
|
+
if sequence_parallel:
|
|
461
|
+
seq_length, batch_size, hidden = tensor_shape
|
|
462
|
+
tensor_shape = (
|
|
463
|
+
seq_length // parallel_state.get_tensor_model_parallel_world_size(),
|
|
464
|
+
batch_size,
|
|
465
|
+
hidden,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Compute number of warmup and remaining microbatches.
|
|
469
|
+
num_model_chunks = len(model)
|
|
470
|
+
total_num_microbatches = num_microbatches * num_model_chunks
|
|
471
|
+
all_warmup_microbatches = False
|
|
472
|
+
if forward_only:
|
|
473
|
+
num_warmup_microbatches = total_num_microbatches
|
|
474
|
+
else:
|
|
475
|
+
# Run all forward passes and then all backward passes if number of
|
|
476
|
+
# microbatches is just the number of pipeline stages.
|
|
477
|
+
# Otherwise, perform (num_model_chunks-1)*pipeline_parallel_size on
|
|
478
|
+
# all workers, followed by more microbatches after depending on
|
|
479
|
+
# stage ID (more forward passes for earlier stages, later stages can
|
|
480
|
+
# immediately start with 1F1B).
|
|
481
|
+
if num_microbatches == pipeline_parallel_size:
|
|
482
|
+
num_warmup_microbatches = total_num_microbatches
|
|
483
|
+
all_warmup_microbatches = True
|
|
484
|
+
else:
|
|
485
|
+
num_warmup_microbatches = \
|
|
486
|
+
(pipeline_parallel_size - pipeline_parallel_rank - 1) * 2
|
|
487
|
+
num_warmup_microbatches += (
|
|
488
|
+
num_model_chunks - 1) * pipeline_parallel_size
|
|
489
|
+
num_warmup_microbatches = min(num_warmup_microbatches,
|
|
490
|
+
total_num_microbatches)
|
|
491
|
+
num_microbatches_remaining = \
|
|
492
|
+
total_num_microbatches - num_warmup_microbatches
|
|
493
|
+
|
|
494
|
+
# Synchronize params for first two model chunks
|
|
495
|
+
if param_sync_func is not None:
|
|
496
|
+
param_sync_func(model[0].parameters())
|
|
497
|
+
param_sync_func(model[1].parameters())
|
|
498
|
+
|
|
499
|
+
def get_model_chunk_id(microbatch_id, forward):
|
|
500
|
+
"""Helper method to get the model chunk ID given the iteration number."""
|
|
501
|
+
microbatch_id_in_group = microbatch_id % (pipeline_parallel_size * num_model_chunks)
|
|
502
|
+
model_chunk_id = microbatch_id_in_group // pipeline_parallel_size
|
|
503
|
+
if not forward:
|
|
504
|
+
model_chunk_id = (num_model_chunks - model_chunk_id - 1)
|
|
505
|
+
return model_chunk_id
|
|
506
|
+
|
|
507
|
+
def is_first_microbatch_for_model_chunk(microbatch_id: int) -> bool:
|
|
508
|
+
"""Check if an iteration is the first for a model chunk."""
|
|
509
|
+
microbatch_group_size = pipeline_parallel_size * num_model_chunks
|
|
510
|
+
num_microbatch_groups = num_microbatches // microbatch_group_size
|
|
511
|
+
microbatch_group_id = microbatch_id // microbatch_group_size
|
|
512
|
+
microbatch_id_in_group = microbatch_id % microbatch_group_size
|
|
513
|
+
if microbatch_group_id == 0:
|
|
514
|
+
return microbatch_id_in_group % pipeline_parallel_size == 0
|
|
515
|
+
else:
|
|
516
|
+
return False
|
|
517
|
+
|
|
518
|
+
def is_last_microbatch_for_model_chunk(microbatch_id: int) -> bool:
|
|
519
|
+
"""Check if an iteration is the last for a model chunk."""
|
|
520
|
+
microbatch_group_size = pipeline_parallel_size * num_model_chunks
|
|
521
|
+
num_microbatch_groups = num_microbatches // microbatch_group_size
|
|
522
|
+
microbatch_group_id = microbatch_id // microbatch_group_size
|
|
523
|
+
microbatch_id_in_group = microbatch_id % microbatch_group_size
|
|
524
|
+
if microbatch_group_id == num_microbatch_groups - 1:
|
|
525
|
+
return microbatch_id_in_group % pipeline_parallel_size == pipeline_parallel_size - 1
|
|
526
|
+
else:
|
|
527
|
+
return False
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def forward_step_helper(microbatch_id):
|
|
531
|
+
"""Helper method to run forward step with model split into chunks
|
|
532
|
+
(run set_virtual_pipeline_model_parallel_rank() before calling
|
|
533
|
+
forward_step())."""
|
|
534
|
+
model_chunk_id = get_model_chunk_id(microbatch_id, forward=True)
|
|
535
|
+
parallel_state.set_virtual_pipeline_model_parallel_rank(model_chunk_id)
|
|
536
|
+
|
|
537
|
+
# launch param synchronization for next model chunk
|
|
538
|
+
# Note: Asynchronous communication tends to slow down compute.
|
|
539
|
+
# To reduce idling from mismatched microbatch times, we launch
|
|
540
|
+
# asynchronous communication at the same time across the
|
|
541
|
+
# pipeline-parallel group.
|
|
542
|
+
if param_sync_func is not None:
|
|
543
|
+
param_sync_microbatch_id = microbatch_id + pipeline_parallel_rank
|
|
544
|
+
if param_sync_microbatch_id < num_microbatches and is_first_microbatch_for_model_chunk(param_sync_microbatch_id):
|
|
545
|
+
param_sync_chunk_id = get_model_chunk_id(param_sync_microbatch_id, forward=True) + 1
|
|
546
|
+
if 1 < param_sync_chunk_id < num_model_chunks:
|
|
547
|
+
param_sync_func(model[param_sync_chunk_id].parameters())
|
|
548
|
+
|
|
549
|
+
# forward step
|
|
550
|
+
if parallel_state.is_pipeline_first_stage():
|
|
551
|
+
if len(input_tensors[model_chunk_id]) == \
|
|
552
|
+
len(output_tensors[model_chunk_id]):
|
|
553
|
+
input_tensors[model_chunk_id].append(None)
|
|
554
|
+
input_tensor = input_tensors[model_chunk_id][-1]
|
|
555
|
+
output_tensor = forward_step(forward_step_func,
|
|
556
|
+
data_iterator[model_chunk_id],
|
|
557
|
+
model[model_chunk_id],
|
|
558
|
+
num_microbatches,
|
|
559
|
+
input_tensor,
|
|
560
|
+
forward_data_store,
|
|
561
|
+
timers,
|
|
562
|
+
collect_non_loss_data,
|
|
563
|
+
dtype,
|
|
564
|
+
enable_autocast)
|
|
565
|
+
output_tensors[model_chunk_id].append(output_tensor)
|
|
566
|
+
|
|
567
|
+
# if forward-only, no need to save tensors for a backward pass
|
|
568
|
+
if forward_only:
|
|
569
|
+
input_tensors[model_chunk_id].pop()
|
|
570
|
+
output_tensors[model_chunk_id].pop()
|
|
571
|
+
|
|
572
|
+
return output_tensor
|
|
573
|
+
|
|
574
|
+
def backward_step_helper(microbatch_id):
|
|
575
|
+
"""Helper method to run backward step with model split into chunks
|
|
576
|
+
(run set_virtual_pipeline_model_parallel_rank() before calling
|
|
577
|
+
backward_step())."""
|
|
578
|
+
model_chunk_id = get_model_chunk_id(microbatch_id, forward=False)
|
|
579
|
+
parallel_state.set_virtual_pipeline_model_parallel_rank(model_chunk_id)
|
|
580
|
+
|
|
581
|
+
# launch grad synchronization (default)
|
|
582
|
+
if grad_sync_func is None and is_last_microbatch_for_model_chunk(microbatch_id):
|
|
583
|
+
enable_grad_sync()
|
|
584
|
+
synchronized_model_chunks.add(model_chunk_id)
|
|
585
|
+
|
|
586
|
+
if parallel_state.is_pipeline_last_stage():
|
|
587
|
+
if len(output_tensor_grads[model_chunk_id]) == 0:
|
|
588
|
+
output_tensor_grads[model_chunk_id].append(None)
|
|
589
|
+
input_tensor = input_tensors[model_chunk_id].pop(0)
|
|
590
|
+
output_tensor = output_tensors[model_chunk_id].pop(0)
|
|
591
|
+
output_tensor_grad = output_tensor_grads[model_chunk_id].pop(0)
|
|
592
|
+
input_tensor_grad = \
|
|
593
|
+
backward_step(grad_scaler,
|
|
594
|
+
input_tensor,
|
|
595
|
+
output_tensor,
|
|
596
|
+
output_tensor_grad,
|
|
597
|
+
model_type,
|
|
598
|
+
timers,
|
|
599
|
+
deallocate_pipeline_outputs)
|
|
600
|
+
|
|
601
|
+
# launch grad synchronization (custom grad sync)
|
|
602
|
+
# Note: Asynchronous communication tends to slow down compute.
|
|
603
|
+
# To reduce idling from mismatched microbatch times, we launch
|
|
604
|
+
# asynchronous communication at the same time across the
|
|
605
|
+
# pipeline-parallel group.
|
|
606
|
+
if grad_sync_func is not None:
|
|
607
|
+
grad_sync_microbatch_id = microbatch_id - pipeline_parallel_rank
|
|
608
|
+
if grad_sync_microbatch_id >= 0 and is_last_microbatch_for_model_chunk(grad_sync_microbatch_id):
|
|
609
|
+
grad_sync_chunk_id = get_model_chunk_id(grad_sync_microbatch_id, forward=False)
|
|
610
|
+
enable_grad_sync()
|
|
611
|
+
grad_sync_func(model[grad_sync_chunk_id].parameters())
|
|
612
|
+
synchronized_model_chunks.add(grad_sync_chunk_id)
|
|
613
|
+
disable_grad_sync()
|
|
614
|
+
|
|
615
|
+
return input_tensor_grad
|
|
616
|
+
|
|
617
|
+
# Run warmup forward passes.
|
|
618
|
+
parallel_state.set_virtual_pipeline_model_parallel_rank(0)
|
|
619
|
+
input_tensors[0].append(
|
|
620
|
+
p2p_communication.recv_forward(tensor_shape, dtype, timers=timers))
|
|
621
|
+
for k in range(num_warmup_microbatches):
|
|
622
|
+
output_tensor = forward_step_helper(k)
|
|
623
|
+
|
|
624
|
+
# Determine if tensor should be received from previous stage.
|
|
625
|
+
next_forward_model_chunk_id = get_model_chunk_id(k+1, forward=True)
|
|
626
|
+
recv_prev = True
|
|
627
|
+
if parallel_state.is_pipeline_first_stage(ignore_virtual=True):
|
|
628
|
+
if next_forward_model_chunk_id == 0:
|
|
629
|
+
recv_prev = False
|
|
630
|
+
if k == (total_num_microbatches - 1):
|
|
631
|
+
recv_prev = False
|
|
632
|
+
|
|
633
|
+
# Don't send tensor downstream if on last stage.
|
|
634
|
+
if parallel_state.is_pipeline_last_stage():
|
|
635
|
+
output_tensor = None
|
|
636
|
+
|
|
637
|
+
# Send and receive tensors as appropriate (send tensors computed
|
|
638
|
+
# in this iteration; receive tensors for next iteration).
|
|
639
|
+
if k == (num_warmup_microbatches - 1) and not forward_only and \
|
|
640
|
+
not all_warmup_microbatches:
|
|
641
|
+
input_tensor_grad = None
|
|
642
|
+
recv_next = True
|
|
643
|
+
if parallel_state.is_pipeline_last_stage(ignore_virtual=True):
|
|
644
|
+
recv_next = False
|
|
645
|
+
input_tensor, output_tensor_grad = \
|
|
646
|
+
p2p_communication.send_forward_backward_recv_forward_backward(
|
|
647
|
+
output_tensor, input_tensor_grad,
|
|
648
|
+
recv_prev=recv_prev, recv_next=recv_next,
|
|
649
|
+
tensor_shape=tensor_shape, dtype=dtype,
|
|
650
|
+
timers=timers)
|
|
651
|
+
output_tensor_grads[num_model_chunks-1].append(output_tensor_grad)
|
|
652
|
+
else:
|
|
653
|
+
input_tensor = \
|
|
654
|
+
p2p_communication.send_forward_recv_forward(
|
|
655
|
+
output_tensor, recv_prev=recv_prev,
|
|
656
|
+
tensor_shape=tensor_shape, dtype=dtype,
|
|
657
|
+
timers=timers)
|
|
658
|
+
input_tensors[next_forward_model_chunk_id].append(input_tensor)
|
|
659
|
+
deallocate_output_tensor(output_tensor, deallocate_pipeline_outputs)
|
|
660
|
+
|
|
661
|
+
# Run 1F1B in steady state.
|
|
662
|
+
for k in range(num_microbatches_remaining):
|
|
663
|
+
# Forward pass.
|
|
664
|
+
forward_k = k + num_warmup_microbatches
|
|
665
|
+
output_tensor = forward_step_helper(forward_k)
|
|
666
|
+
|
|
667
|
+
# Backward pass.
|
|
668
|
+
backward_k = k
|
|
669
|
+
input_tensor_grad = backward_step_helper(backward_k)
|
|
670
|
+
|
|
671
|
+
# Send output_tensor and input_tensor_grad, receive input_tensor
|
|
672
|
+
# and output_tensor_grad.
|
|
673
|
+
|
|
674
|
+
# Determine if current stage has anything to send in either direction,
|
|
675
|
+
# otherwise set tensor to None.
|
|
676
|
+
forward_model_chunk_id = get_model_chunk_id(forward_k, forward=True)
|
|
677
|
+
parallel_state.set_virtual_pipeline_model_parallel_rank(forward_model_chunk_id)
|
|
678
|
+
if parallel_state.is_pipeline_last_stage():
|
|
679
|
+
output_tensor = None
|
|
680
|
+
|
|
681
|
+
backward_model_chunk_id = get_model_chunk_id(backward_k, forward=False)
|
|
682
|
+
parallel_state.set_virtual_pipeline_model_parallel_rank(backward_model_chunk_id)
|
|
683
|
+
if parallel_state.is_pipeline_first_stage():
|
|
684
|
+
input_tensor_grad = None
|
|
685
|
+
|
|
686
|
+
# Determine if peers are sending, and where in data structure to put
|
|
687
|
+
# received tensors.
|
|
688
|
+
recv_prev = True
|
|
689
|
+
if parallel_state.is_pipeline_first_stage(ignore_virtual=True):
|
|
690
|
+
# First stage is ahead of last stage by (pipeline_parallel_size - 1).
|
|
691
|
+
next_forward_model_chunk_id = get_model_chunk_id(
|
|
692
|
+
forward_k - (pipeline_parallel_size - 1), forward=True)
|
|
693
|
+
if next_forward_model_chunk_id == (num_model_chunks - 1):
|
|
694
|
+
recv_prev = False
|
|
695
|
+
next_forward_model_chunk_id += 1
|
|
696
|
+
else:
|
|
697
|
+
next_forward_model_chunk_id = get_model_chunk_id(forward_k + 1,
|
|
698
|
+
forward=True)
|
|
699
|
+
|
|
700
|
+
recv_next = True
|
|
701
|
+
if parallel_state.is_pipeline_last_stage(ignore_virtual=True):
|
|
702
|
+
# Last stage is ahead of first stage by (pipeline_parallel_size - 1).
|
|
703
|
+
next_backward_model_chunk_id = get_model_chunk_id(
|
|
704
|
+
backward_k - (pipeline_parallel_size - 1), forward=False)
|
|
705
|
+
if next_backward_model_chunk_id == 0:
|
|
706
|
+
recv_next = False
|
|
707
|
+
next_backward_model_chunk_id -= 1
|
|
708
|
+
else:
|
|
709
|
+
next_backward_model_chunk_id = get_model_chunk_id(backward_k + 1,
|
|
710
|
+
forward=False)
|
|
711
|
+
|
|
712
|
+
# If last iteration, don't receive; we already received one extra
|
|
713
|
+
# before the start of the for loop.
|
|
714
|
+
if k == (num_microbatches_remaining - 1):
|
|
715
|
+
recv_prev = False
|
|
716
|
+
|
|
717
|
+
# Communicate tensors.
|
|
718
|
+
input_tensor, output_tensor_grad = \
|
|
719
|
+
p2p_communication.send_forward_backward_recv_forward_backward(
|
|
720
|
+
output_tensor, input_tensor_grad,
|
|
721
|
+
recv_prev=recv_prev, recv_next=recv_next,
|
|
722
|
+
tensor_shape=tensor_shape, dtype=dtype, timers=timers)
|
|
723
|
+
deallocate_output_tensor(output_tensor, deallocate_pipeline_outputs)
|
|
724
|
+
|
|
725
|
+
# Put input_tensor and output_tensor_grad in data structures in the
|
|
726
|
+
# right location.
|
|
727
|
+
if recv_prev:
|
|
728
|
+
input_tensors[next_forward_model_chunk_id].append(input_tensor)
|
|
729
|
+
if recv_next:
|
|
730
|
+
output_tensor_grads[next_backward_model_chunk_id].append(
|
|
731
|
+
output_tensor_grad)
|
|
732
|
+
|
|
733
|
+
# Run cooldown backward passes (flush out pipeline).
|
|
734
|
+
if not forward_only:
|
|
735
|
+
if all_warmup_microbatches:
|
|
736
|
+
output_tensor_grads[num_model_chunks-1].append(
|
|
737
|
+
p2p_communication.recv_backward(tensor_shape, dtype=dtype, timers=timers))
|
|
738
|
+
for k in range(num_microbatches_remaining, total_num_microbatches):
|
|
739
|
+
input_tensor_grad = backward_step_helper(k)
|
|
740
|
+
next_backward_model_chunk_id = get_model_chunk_id(k+1, forward=False)
|
|
741
|
+
recv_next = True
|
|
742
|
+
if parallel_state.is_pipeline_last_stage(ignore_virtual=True):
|
|
743
|
+
if next_backward_model_chunk_id == (num_model_chunks - 1):
|
|
744
|
+
recv_next = False
|
|
745
|
+
if k == (total_num_microbatches - 1):
|
|
746
|
+
recv_next = False
|
|
747
|
+
output_tensor_grads[next_backward_model_chunk_id].append(
|
|
748
|
+
p2p_communication.send_backward_recv_backward(
|
|
749
|
+
input_tensor_grad, recv_next=recv_next,
|
|
750
|
+
tensor_shape=tensor_shape, dtype=dtype,
|
|
751
|
+
timers=timers))
|
|
752
|
+
|
|
753
|
+
# Launch any remaining grad reductions
|
|
754
|
+
enable_grad_sync()
|
|
755
|
+
if grad_sync_func is not None:
|
|
756
|
+
params = []
|
|
757
|
+
for model_chunk_id in range(num_model_chunks):
|
|
758
|
+
if model_chunk_id not in synchronized_model_chunks:
|
|
759
|
+
params.extend(model[model_chunk_id].parameters())
|
|
760
|
+
synchronized_model_chunks.add(model_chunk_id)
|
|
761
|
+
if params:
|
|
762
|
+
grad_sync_func(params)
|
|
763
|
+
|
|
764
|
+
return forward_data_store
|
|
765
|
+
|
|
766
|
+
def get_tensor_shapes(*,
|
|
767
|
+
rank: int,
|
|
768
|
+
model_type: ModelType,
|
|
769
|
+
tensor_shape: Shape,
|
|
770
|
+
decoder_seq_length: int,
|
|
771
|
+
sequence_parallel: bool):
|
|
772
|
+
# Determine right tensor sizes (based on position of rank with respect to split
|
|
773
|
+
# rank) and model size.
|
|
774
|
+
# Send two tensors if model is T5 and rank is in decoder stage:
|
|
775
|
+
# first tensor is decoder (pre-transpose),
|
|
776
|
+
# second tensor is encoder (post-transpose).
|
|
777
|
+
# If model is T5 and rank is at the boundary:
|
|
778
|
+
# send one tensor (post-transpose from encoder).
|
|
779
|
+
# Otherwise, send one tensor (pre-transpose).
|
|
780
|
+
tensor_shapes = []
|
|
781
|
+
|
|
782
|
+
assert (
|
|
783
|
+
len(tensor_shape) == 3
|
|
784
|
+
), f"`tensor_shape` should be [sequence_length, micro_batch_size, hidden_size] but {tensor_shape}"
|
|
785
|
+
|
|
786
|
+
seq_length, micro_batch_size, hidden_size = tensor_shape
|
|
787
|
+
|
|
788
|
+
if sequence_parallel:
|
|
789
|
+
seq_length = seq_length // parallel_state.get_tensor_model_parallel_world_size()
|
|
790
|
+
|
|
791
|
+
if model_type == ModelType.encoder_and_decoder:
|
|
792
|
+
if sequence_parallel:
|
|
793
|
+
decoder_seq_length = decoder_seq_length // parallel_state.get_tensor_model_parallel_world_size()
|
|
794
|
+
|
|
795
|
+
if parallel_state.is_pipeline_stage_before_split(rank):
|
|
796
|
+
tensor_shapes.append((seq_length, micro_batch_size, hidden_size))
|
|
797
|
+
else:
|
|
798
|
+
tensor_shapes.append((decoder_seq_length, micro_batch_size, hidden_size))
|
|
799
|
+
tensor_shapes.append((seq_length, micro_batch_size, hidden_size))
|
|
800
|
+
else:
|
|
801
|
+
tensor_shapes.append((seq_length, micro_batch_size, hidden_size))
|
|
802
|
+
return tensor_shapes
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def recv_forward(tensor_shapes, dtype, timers):
|
|
807
|
+
input_tensors = []
|
|
808
|
+
for tensor_shape in tensor_shapes:
|
|
809
|
+
if tensor_shape is None:
|
|
810
|
+
input_tensors.append(None)
|
|
811
|
+
else:
|
|
812
|
+
input_tensors.append(p2p_communication.recv_forward(tensor_shape, dtype,
|
|
813
|
+
timers=timers))
|
|
814
|
+
return input_tensors
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def recv_backward(tensor_shapes, dtype, timers):
|
|
818
|
+
output_tensor_grads = []
|
|
819
|
+
for tensor_shape in tensor_shapes:
|
|
820
|
+
if tensor_shape is None:
|
|
821
|
+
output_tensor_grads.append(None)
|
|
822
|
+
else:
|
|
823
|
+
output_tensor_grads.append(p2p_communication.recv_backward(tensor_shape, dtype,
|
|
824
|
+
timers=timers))
|
|
825
|
+
return output_tensor_grads
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def send_forward(output_tensors, tensor_shapes, timers):
|
|
829
|
+
if not isinstance(output_tensors, list):
|
|
830
|
+
output_tensors = [output_tensors]
|
|
831
|
+
for (output_tensor, tensor_shape) in zip(output_tensors, tensor_shapes):
|
|
832
|
+
if tensor_shape is None:
|
|
833
|
+
continue
|
|
834
|
+
p2p_communication.send_forward(output_tensor, timers=timers)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def send_backward(input_tensor_grads, tensor_shapes, timers):
|
|
838
|
+
if not isinstance(input_tensor_grads, list):
|
|
839
|
+
input_tensor_grads = [input_tensor_grads]
|
|
840
|
+
for (input_tensor_grad, tensor_shape) in zip(input_tensor_grads, tensor_shapes):
|
|
841
|
+
if tensor_shape is None:
|
|
842
|
+
continue
|
|
843
|
+
p2p_communication.send_backward(input_tensor_grad, timers=timers)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def send_forward_recv_backward(output_tensors, tensor_shapes, dtype, timers):
|
|
847
|
+
if not isinstance(output_tensors, list):
|
|
848
|
+
output_tensors = [output_tensors]
|
|
849
|
+
output_tensor_grads = []
|
|
850
|
+
for (output_tensor, tensor_shape) in zip(output_tensors, tensor_shapes):
|
|
851
|
+
if tensor_shape is None:
|
|
852
|
+
output_tensor_grads.append(None)
|
|
853
|
+
continue
|
|
854
|
+
output_tensor_grad = p2p_communication.send_forward_recv_backward(
|
|
855
|
+
output_tensor, tensor_shape, dtype, timers=timers)
|
|
856
|
+
output_tensor_grads.append(output_tensor_grad)
|
|
857
|
+
return output_tensor_grads
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def send_backward_recv_forward(input_tensor_grads, tensor_shapes, dtype, timers):
|
|
861
|
+
if not isinstance(input_tensor_grads, list):
|
|
862
|
+
input_tensor_grads = [input_tensor_grads]
|
|
863
|
+
input_tensors = []
|
|
864
|
+
for (input_tensor_grad, tensor_shape) in zip(input_tensor_grads, tensor_shapes):
|
|
865
|
+
if tensor_shape is None:
|
|
866
|
+
input_tensors.append(None)
|
|
867
|
+
continue
|
|
868
|
+
input_tensor = p2p_communication.send_backward_recv_forward(
|
|
869
|
+
input_tensor_grad, tensor_shape, dtype, timers=timers)
|
|
870
|
+
input_tensors.append(input_tensor)
|
|
871
|
+
return input_tensors
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def forward_backward_pipelining_without_interleaving(*,
|
|
875
|
+
forward_step_func,
|
|
876
|
+
data_iterator: Union[Iterator, List[Iterator]],
|
|
877
|
+
model: Union[torch.nn.Module, List[torch.nn.Module]],
|
|
878
|
+
num_microbatches: int,
|
|
879
|
+
dtype: torch.dtype,
|
|
880
|
+
tensor_shape: Shape,
|
|
881
|
+
decoder_seq_length: Optional[int] = None,
|
|
882
|
+
grad_scaler: Callable = None,
|
|
883
|
+
sequence_parallel: bool = False,
|
|
884
|
+
forward_only: bool = False,
|
|
885
|
+
timers: Callable = None,
|
|
886
|
+
collect_non_loss_data: bool = False,
|
|
887
|
+
enable_autocast: bool = False,
|
|
888
|
+
deallocate_pipeline_outputs: bool = False,
|
|
889
|
+
no_sync_func: Optional[Callable] = None,
|
|
890
|
+
grad_sync_func: Optional[Callable] = None,
|
|
891
|
+
param_sync_func: Optional[Callable] = None, # unused
|
|
892
|
+
):
|
|
893
|
+
"""Run non-interleaved 1F1B schedule, with communication between pipeline
|
|
894
|
+
stages.
|
|
895
|
+
|
|
896
|
+
Returns dictionary with losses if the last stage, empty dict otherwise."""
|
|
897
|
+
|
|
898
|
+
if isinstance(model, list):
|
|
899
|
+
assert len(model) == 1, \
|
|
900
|
+
"non-interleaved pipeline parallelism does not support model chunking"
|
|
901
|
+
model = model[0]
|
|
902
|
+
if isinstance(data_iterator, list):
|
|
903
|
+
assert len(data_iterator) == 1, \
|
|
904
|
+
"non-pipeline-parallel schedule does not support model chunking"
|
|
905
|
+
data_iterator = data_iterator[0]
|
|
906
|
+
|
|
907
|
+
# Disable async grad reductions
|
|
908
|
+
if no_sync_func is None and isinstance(model, torchDDP):
|
|
909
|
+
no_sync_func = model.no_sync
|
|
910
|
+
if no_sync_func is None:
|
|
911
|
+
no_sync_func = contextlib.nullcontext
|
|
912
|
+
no_sync_context = None
|
|
913
|
+
def disable_grad_sync():
|
|
914
|
+
"""Disable asynchronous grad reductions"""
|
|
915
|
+
nonlocal no_sync_context
|
|
916
|
+
if no_sync_context is None:
|
|
917
|
+
no_sync_context = no_sync_func()
|
|
918
|
+
no_sync_context.__enter__()
|
|
919
|
+
def enable_grad_sync():
|
|
920
|
+
"""Enable asynchronous grad reductions"""
|
|
921
|
+
nonlocal no_sync_context
|
|
922
|
+
if no_sync_context is not None:
|
|
923
|
+
no_sync_context.__exit__(None, None, None)
|
|
924
|
+
no_sync_context = None
|
|
925
|
+
disable_grad_sync()
|
|
926
|
+
|
|
927
|
+
# Compute number of warmup microbatches.
|
|
928
|
+
num_warmup_microbatches = \
|
|
929
|
+
(parallel_state.get_pipeline_model_parallel_world_size() -
|
|
930
|
+
parallel_state.get_pipeline_model_parallel_rank() - 1)
|
|
931
|
+
num_warmup_microbatches = min(
|
|
932
|
+
num_warmup_microbatches,
|
|
933
|
+
num_microbatches)
|
|
934
|
+
num_microbatches_remaining = \
|
|
935
|
+
num_microbatches - num_warmup_microbatches
|
|
936
|
+
|
|
937
|
+
model_type = get_model_type(model)
|
|
938
|
+
|
|
939
|
+
rank = parallel_state.get_pipeline_model_parallel_rank()
|
|
940
|
+
recv_tensor_shapes = get_tensor_shapes(rank=rank-1,
|
|
941
|
+
model_type=model_type,
|
|
942
|
+
tensor_shape=tensor_shape,
|
|
943
|
+
decoder_seq_length=decoder_seq_length,
|
|
944
|
+
sequence_parallel=sequence_parallel)
|
|
945
|
+
send_tensor_shapes = get_tensor_shapes(rank=rank,
|
|
946
|
+
model_type=model_type,
|
|
947
|
+
tensor_shape=tensor_shape,
|
|
948
|
+
decoder_seq_length=decoder_seq_length,
|
|
949
|
+
sequence_parallel=sequence_parallel)
|
|
950
|
+
|
|
951
|
+
# Input, output tensors only need to be saved when doing backward passes
|
|
952
|
+
input_tensors = None
|
|
953
|
+
output_tensors = None
|
|
954
|
+
if not forward_only:
|
|
955
|
+
input_tensors = []
|
|
956
|
+
output_tensors = []
|
|
957
|
+
forward_data_store = []
|
|
958
|
+
|
|
959
|
+
# Run warmup forward passes.
|
|
960
|
+
for i in range(num_warmup_microbatches):
|
|
961
|
+
input_tensor = recv_forward(recv_tensor_shapes, dtype, timers=timers)
|
|
962
|
+
output_tensor = forward_step(forward_step_func, data_iterator, model, num_microbatches,
|
|
963
|
+
input_tensor, forward_data_store,
|
|
964
|
+
timers, collect_non_loss_data, dtype, enable_autocast)
|
|
965
|
+
send_forward(output_tensor, send_tensor_shapes, timers=timers)
|
|
966
|
+
|
|
967
|
+
if not forward_only:
|
|
968
|
+
input_tensors.append(input_tensor)
|
|
969
|
+
output_tensors.append(output_tensor)
|
|
970
|
+
deallocate_output_tensor(output_tensor[0], deallocate_pipeline_outputs)
|
|
971
|
+
|
|
972
|
+
# Before running 1F1B, need to receive first forward tensor.
|
|
973
|
+
# If all microbatches are run in warmup / cooldown phase, then no need to
|
|
974
|
+
# receive this tensor here.
|
|
975
|
+
if num_microbatches_remaining > 0:
|
|
976
|
+
input_tensor = recv_forward(recv_tensor_shapes, dtype, timers=timers)
|
|
977
|
+
|
|
978
|
+
# Run 1F1B in steady state.
|
|
979
|
+
for i in range(num_microbatches_remaining):
|
|
980
|
+
last_iteration = (i == (num_microbatches_remaining - 1))
|
|
981
|
+
|
|
982
|
+
output_tensor = forward_step(forward_step_func, data_iterator, model, num_microbatches,
|
|
983
|
+
input_tensor, forward_data_store,
|
|
984
|
+
timers, collect_non_loss_data, dtype, enable_autocast)
|
|
985
|
+
|
|
986
|
+
if forward_only:
|
|
987
|
+
send_forward(output_tensor, send_tensor_shapes, timers=timers)
|
|
988
|
+
|
|
989
|
+
if not last_iteration:
|
|
990
|
+
input_tensor = recv_forward(recv_tensor_shapes, dtype, timers=timers)
|
|
991
|
+
|
|
992
|
+
else:
|
|
993
|
+
output_tensor_grad = \
|
|
994
|
+
send_forward_recv_backward(output_tensor,
|
|
995
|
+
send_tensor_shapes, dtype,
|
|
996
|
+
timers=timers)
|
|
997
|
+
|
|
998
|
+
# Add input_tensor and output_tensor to end of list.
|
|
999
|
+
input_tensors.append(input_tensor)
|
|
1000
|
+
output_tensors.append(output_tensor)
|
|
1001
|
+
deallocate_output_tensor(output_tensor[0], deallocate_pipeline_outputs)
|
|
1002
|
+
|
|
1003
|
+
# Pop input_tensor and output_tensor from the start of the list for
|
|
1004
|
+
# the backward pass.
|
|
1005
|
+
input_tensor = input_tensors.pop(0)
|
|
1006
|
+
output_tensor = output_tensors.pop(0)
|
|
1007
|
+
|
|
1008
|
+
input_tensor_grad = \
|
|
1009
|
+
backward_step(grad_scaler, input_tensor, output_tensor,
|
|
1010
|
+
output_tensor_grad, model_type, timers, deallocate_pipeline_outputs)
|
|
1011
|
+
|
|
1012
|
+
if last_iteration:
|
|
1013
|
+
input_tensor = None
|
|
1014
|
+
send_backward(input_tensor_grad, recv_tensor_shapes, timers=timers)
|
|
1015
|
+
else:
|
|
1016
|
+
input_tensor = \
|
|
1017
|
+
send_backward_recv_forward(
|
|
1018
|
+
input_tensor_grad, recv_tensor_shapes, dtype, timers=timers)
|
|
1019
|
+
|
|
1020
|
+
# Run cooldown backward passes.
|
|
1021
|
+
if not forward_only:
|
|
1022
|
+
for i in range(num_warmup_microbatches):
|
|
1023
|
+
|
|
1024
|
+
# Enable async grad reduction in the last backward pass
|
|
1025
|
+
# Note: If grad sync function is provided, only enable
|
|
1026
|
+
# async grad reduction in first pipeline stage. Other
|
|
1027
|
+
# pipeline stages do grad reduction during pipeline
|
|
1028
|
+
# bubble.
|
|
1029
|
+
if i == num_warmup_microbatches-1:
|
|
1030
|
+
if grad_sync_func is None or rank == 0:
|
|
1031
|
+
enable_grad_sync()
|
|
1032
|
+
|
|
1033
|
+
input_tensor = input_tensors.pop(0)
|
|
1034
|
+
output_tensor = output_tensors.pop(0)
|
|
1035
|
+
|
|
1036
|
+
output_tensor_grad = recv_backward(send_tensor_shapes, dtype, timers=timers)
|
|
1037
|
+
|
|
1038
|
+
input_tensor_grad = \
|
|
1039
|
+
backward_step(grad_scaler, input_tensor, output_tensor,
|
|
1040
|
+
output_tensor_grad, model_type, timers, deallocate_pipeline_outputs)
|
|
1041
|
+
|
|
1042
|
+
send_backward(input_tensor_grad, recv_tensor_shapes, timers=timers)
|
|
1043
|
+
|
|
1044
|
+
# Launch any remaining grad reductions
|
|
1045
|
+
if no_sync_context is not None:
|
|
1046
|
+
enable_grad_sync()
|
|
1047
|
+
if grad_sync_func is not None:
|
|
1048
|
+
grad_sync_func(model.parameters())
|
|
1049
|
+
|
|
1050
|
+
return forward_data_store
|