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.

@@ -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