[Анализ исходного кода] Параллельная реализация конвейера PyTorch (1) -- базовые знания

машинное обучение глубокое обучение

0x00 сводка

Эта серия начинается с введения в конвейерную параллельную реализацию PyTorch. По сути, PyTorch — это PyTorch-версия GPipe. Это программное обеспечение с открытым исходным кодом заимствует идеи друг у друга и учится друг у друга.Из комментариев к исходному коду PyTorch вы можете увидеть ссылки или ссылки на документы некоторых фреймворков/библиотек, которые мы представили ранее.

Ссылки на другие статьи о конвейерном параллелизме:

[Анализ исходного кода] Конвейер глубокого обучения, параллельный Gpipe(1) --- Базовая реализация конвейера

[Анализ исходного кода] Конвейер глубокого обучения, параллельный GPipe (2) ----- накопление градиента

[Анализ исходного кода] Конвейер глубокого обучения, параллельный GPipe(3) -- перерасчет

[Анализ исходного кода] PipeDream (1) --- Этап профиля параллельного конвейера глубокого обучения

[Анализ исходного кода] Параллельный конвейер глубокого обучения PipeDream(2) --- Вычислительный раздел

[Анализ исходного кода] Параллельный конвейер глубокого обучения PipeDream (3) --- модель преобразования

[Анализ исходного кода] Параллельный конвейер глубокого обучения PipeDream(4) --- механизм выполнения

[Анализ исходного кода] Параллельный конвейер глубокого обучения PipeDream (5) --- коммуникационный модуль

[Анализ исходного кода] Параллельный конвейер глубокого обучения PipeDream (6) --- Стратегия 1F1B

Изображение в этой статье взято из бумаги и исходного кода github.

0x01 История

Начнем с плюсов и минусов.

1.1 GPipe

Как мы знаем из предыдущей серии статей, GPipe — это масштабируемая конвейерная параллельная библиотека, выпущенная Google Brain, которая позволяет эффективно обучать модели с большим потреблением памяти. Его документы:

`GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism
<https://arxiv.org/abs/1811.06965>`

GPipe, по сути, является библиотекой, параллельной модели, когда размер модели слишком велик для одного графического процессора, обучение большой модели может привести к нехватке памяти. Для обучения такой большой модели GPipe разбивает многоуровневую сеть на несколько составных слоев, после чего каждый составной слой развертывается на GPU/TPU. Однако эти несколько составных слоев можно распараллелить только последовательно, что серьезно влияет на скорость обучения. Таким образом, GPipe представляет параллельный механизм конвейера (pipeline parallelism), конвейеризация слоев между различными устройствами GPU. Кроме того, GPipe также использует трюк пересчета для уменьшения памяти, чтобы можно было обучать более крупные модели.

Gpipe сначала разбивает модель на разные устройства, где на каждом устройстве размещается сегмент модели. Фрагменты могут представлять собой один слой или серию слоев. Затем Gpipe разбивает небольшой пакет данных на микропакеты и отправляет микропакеты на устройство, на котором размещен первый сегмент. Слои на каждом устройстве выполняют следующие функции:

  • Обработайте полученные микропакеты и отправьте вывод на последующие устройства.
  • В то же время он готов к обработке микропакетов с предыдущего устройства.

Направляя вход таким образом, Gpipe может сократить время простоя устройства. Может упростить обучение больших моделей.

img

1.2 torchgpipe

Поскольку GPipe — это библиотека, основанная на TensorFlow (это продукт Google), некоторые инженеры kakaobrain использовали PyTorch для реализации GPipe и его открытого исходного кода.Это torchgpipe, и его адрес:GitHub.com/kakobrain/…, пользователи могут установить и использовать через pip install torchgpipe.

Группа авторов также опубликовала статью следующего содержания:АР Вест V.org/PDF/2004.09…

1.3 fairscale

FairScale — это библиотека расширений PyTorch от Facebook для высокопроизводительного и крупномасштабного обучения. Эта библиотека расширяет базовую функциональность PyTorch, добавляя новые методы масштабирования SOTA. FairScale предоставляет современные методы распределенного обучения в виде компонуемых модулей и простых API. Эти API могут служить базовыми инструментами для исследователей, поскольку они могут обучать большие модели с ограниченными ресурсами.

Его открытый исходный адрес:GitHub.com/Facebook Рес….

Судя по его внутренней структуре исходного кода и документации, я лично думаю, что это тестовое поле для сбора/экспериментирования/исследования различных новейших библиотек глубокого обучения на рынке в рамках Facebook. Если эксперимент созреет, Facebook объединит части кода в PyTorch.

Например, как вы можете видеть ниже, Facebook экспериментирует со следующим:

fairscale.nn.pipe is forked from torchgpipe, Copyright 2019, Kakao Brain, licensed under Apache License.

fairscale.nn.model_parallel is forked from Megatron-LM, Copyright 2020, NVIDIA CORPORATION, licensed under Apache License.

fairscale.optim.adascale is forked from AdaptDL, Copyright 2020, Petuum, Inc., licensed under Apache License.

fairscale.nn.misc.flatten_params_wrapper is forked from PyTorch-Reparam-Module, Copyright 2018, Tongzhou Wang, licensed under MIT License.

1.4 PyTorch

Видно от 18 марта 2021 г., примечания к выпуску PyTorch 1.8.0.

  • Upstream fairscale.nn.Pipe into PyTorch as torch.distributed.pipeline (#44090)

Трубопровод официально введен здесь.

GitHub.com/py torch/Пак Ючон…Содержание:

Stack from ghstack:

  • #44090 Pull in fairscale.nn.Pipe into PyTorch.

    This is an initial commit pulling in the torchgpipe fork at GitHub.com/Facebook Рес….

    The purpose of this commit is to just pull in the code and ensure all tests and builds work fine. We will slowly modify this to match our intended API mentioned in #44827. Follow up PRs would address further changes needed on top of the initial commit..

    We're pulling the code into the torch.distributed._pipeline.sync package. The package is private on purpose since there is a lot of work (ex: docs, API changes etc.) that needs to go in before we can actually officially support this.

Следует отметить, что код torchgpipe объединен с torch/distributed/pipeline/sync, что означает, что PyTorch может включать асинхронную реализацию, например PipeDream.

1.5 базовая версия

Потому что эта часть исходного кода практически не изменилась в PyTorch. Поэтому мы по-прежнему используем исходный код torchgpipe в качестве примера для иллюстрации.

В этой статье для объяснения выбраны только важные функции, такие какDeferred Batch NormalizationиSkip ConnectionsОн не будет анализироваться, если читатели заинтересованы, они могут изучить его самостоятельно.

0x02 Основы

Внутри исходного кода torchgpipe и fairscale задействовано и внедрено много соответствующих знаний.Понимание этих знаний поможет нам лучше изучить исходный код, а также мы сможем вернуться и проверить анализ GPipe.

Некоторые знания могут быть продублированы из предыдущих статей, но Torchpipe и Fairscale дают свои собственные идеи, о которых мы можем узнать больше.

2.1 Конвейерный параллелизм

GPipe разбивает модель на несколько разделов и размещает каждый раздел на отдельном устройстве, что увеличивает емкость контента. Например, мы можем разделить модель, занимающую 40 ГБ памяти CUDA, на 4 раздела, каждый из которых занимает 10 ГБ.

Такой подход называется «модельный параллелизм». Однако типичные модели глубокого обучения состоят из последовательных слоев. Другими словами, последующие слои не будут работать, пока не будет завершен предыдущий слой. Если модель состоит из полностью непрерывных слоев, даже если мы расширим модель до двух или более слоев, одновременно может использоваться только одно устройство.

image.png

GPipe разбивает мини-пакет на несколько микропакетов, чтобы устройства работали параллельно, насколько это возможно, что называется «конвейерным параллелизмом».

По сути, конвейерный параллелизм — это набор небольших параллелизмов данных. Когда каждый раздел завершает обработку микропакета, он может перенаправить вывод в следующий раздел и немедленно начать работу над следующим микропакетом, чтобы разделы могли перекрываться.

image.png

Поскольку каждый раздел должен ждать, пока ввод предыдущего раздела будет обработан как первый микропакет, над конвейером все еще остается время простоя, которое мы называем «пузырем».

Выбирая микропартии меньшего размера, можно уменьшить «пузырь». Но, как правило, более крупные пакеты могут использовать GPU более эффективно. Следовательно, если выбранный микропакет слишком мал, GPU может быть недогружен. Кроме того, более быстрые разделы должны ждать соседних более медленных разделов, дисбаланс между разделами также может привести к недостаточной загрузке графического процессора. Таким образом, общая производительность определяется самым медленным разделом.

2.2 Checkpointing

2.2.1 Основные понятия

Контрольные точки — это метод, используемый для уменьшения использования памяти графического процессора во время обучения. Это достигается за счет предотвращения сохранения промежуточных тензоров активации во время прямого прохода. В частности, Checkpointing запоминает только тензоры на границах разделов во время прямого распространения, все остальные промежуточные тензоры не запоминаются, но отслеживают исходный ввод во время обратного распространения для пересчета прямого распространения. Поэтому скрытый слой потребляет ровно столько памяти, сколько одиночный микропакет с контрольными точками.

Контрольные точки - это компромисс между производительностью и памятью, поскольку, если они вообще пересчитываются, это занимает столько же времени, сколько и прямое распространение. Но контрольные точки уменьшают потребность в хранении больших тензоров активации, позволяя нам увеличить размер пакета и увеличить чистую пропускную способность модели.

2.2.2 Использование

В GPipe контрольные точки применяются к каждому разделу, чтобы минимизировать общее потребление памяти моделью.

Контрольные точки значительно уменьшат использование памяти, но общая скорость обучения снизится примерно на 25%. Вы можете управлять тем, как контрольные точки применяются к модели. Контрольные точки имеют только три варианта и не могут указывать какие-то определенные точки:

  • «всегда»: применять контрольные точки ко всем микропакетам.
  • «except_last»: применять контрольные точки после последнего микропакета.
  • «never»: никогда не применять контрольные точки.
@pytest.mark.parametrize('checkpoint', ['never', 'always', 'except_last'])

Часто чекпойнт на последнем микробатче может быть бесполезен, так как сохраненная память тут же будет перестроена. Вот почему мы выбрали «except_last» в качестве опции по умолчанию. Если вы решили вообще не использовать контрольные точки, то<torch.nn.DataParallel>Вероятно, более эффективен, чем GPipe.

2.2.3 Обзор реализации

Контрольные точки были реализованы как часть API "torch.utils.checkpoint.checkpoint_wrapper", с помощью которого можно оборачивать разные модули в прямом процессе.

Контрольные точки реализованы путем переопределения "torch.autograd.Function". В функции «вперед», которая обрабатывает прямой проход модуля, если используется «no_grad», мы можем предотвратить создание прямого графа и материализацию промежуточных тензоров активации в течение длительного времени (т.е. до момента обратного распространения). И наоборот, во время обратного прохода снова выполняется прямой проход, за которым следует обратный проход.

Ввод для прямого прохода сохраняется с использованием объекта контекста, к которому затем осуществляется доступ во время обратного прохода для извлечения исходного ввода. PyTorch также сохраняет состояние RNG (генератора случайных чисел) для прямого и обратного распространения, как это требуется для слоев Dropout.

Вот несколько замечаний:

  1. Экономия памяти полностью зависит от модели и сегментации, которую выполняет контрольная точка. Каждый backprop состоит из нескольких процессов mini-forward и backprop. Выигрыш полностью зависит от памяти, занимаемой активациями каждого слоя.
  2. При использовании BatchNormalization может потребоваться заморозить вычисление статистики, так как мы запускаем два прохода вперед.
  3. Убедитесь, что для поля «requires_grad» входного тензора установлено значение «Истина». Чтобы вызвать функцию обратного распространения, в выходных данных должно быть установлено это поле. Установив это поле во входном тензоре, мы можем гарантировать, что оно будет распространено на выход и вызвать «обратную» функцию.

2.3 Количество микропартий

Выбор размера микропакета влияет на загрузку графического процессора. Меньшие микропакеты могут уменьшить задержку в ожидании вывода предыдущих микропакетов, но более крупные микропакеты могут лучше использовать GPU. Следовательно, что касается количества микропакетов, существует компромисс между использованием графического процессора на микропакет и общей площадью пузырьков, и пользователю необходимо найти оптимальное количество микропакетов для модели.

Графический процессор может работать медленнее при обработке множества небольших микропакетов по сравнению с большими микропакетами. Если каждое ядро ​​CUDA слишком дешево для вычислений, GPU не будет использоваться полностью, поэтому слишком маленькие микропакеты приведут к недоиспользованию. С другой стороны, по мере уменьшения размера каждой микропартии соответственно уменьшается площадь пузырьков. В идеале пользователи должны выбрать максимальное количество микропакетов, которые могут улучшить использование графического процессора.

В качестве дополнительного примечания, чем меньше размер партии, тем хуже производительность. Большое количество микропакетов может негативно повлиять на окончательную производительность модели с использованием BatchNorm, напримерtorch.nn.DataParallelТуда.

2.4 Проверка пересчета

Контрольная точка в GPipe выполняет два прохода вперед. Второй прямой проход называется «пересчетом».

Такие как<torch.nn.BatchNorm2d>Модули, подобные этому, могут вызвать проблемы, если они обновляют свою пакетную статистику каждый раз, когда пересылают данные. Таким образом, текущие оценки не должны повторно обновляться во время пересчета. Чтобы избежать повторного обновления текущей оценки, «прямой» метод модуля должен быть в состоянии определить, что это перерасчет.

~torchgpipe.is_recomputingметод может обнаружить пересчет, при повторном запуске этот метод вернетTrue.

   class Counter(nn.Module):
       def __init__(self):
           super().__init__()
           self.counter = 0
​
       def forward(self, input):
           if not is_recomputing():
               self.counter += 1
           return input

Кроме того, если~torchgpipe.GPipeпеременная-членdeferred_batch_normУстановите значение True, чтобы предотвратить повторное обновление текущей статистики.

0x03 использовать

3.1 Пример

Чтобы обучить модуль с помощью GPipe, просто используйтеtorchgpipe.GPipeчтобы обернуть его, но пользовательский модуль должен быть<torch.nn.Sequential>пример.

GPipe автоматически разбивает модули на разделы, представляющие собой непрерывный набор слоев, работающих вместе на одном устройстве, где:

balanceПараметр определяет количество слоев в каждом разделе.

chunksПараметр указывает количество микропакетов.

В приведенном ниже примере кода показано, как разделить модуль с четырьмя слоями на два раздела, каждый из которых состоит из двух слоев. Этот код также разбивает мини-пакет на 8 микропакетов.

   from torchgpipe import GPipe
​
   model = nn.Sequential(a, b, c, d)
   model = GPipe(model, balance=[2, 2], chunks=8)
​
   # 1st partition: nn.Sequential(a, b) on cuda:0
   # 2nd partition: nn.Sequential(c, d) on cuda:1
​
   for input in data_loader:
       output = model(input)

~torchgpipe.GPipeИспользуйте CUDA для обучения. Пользователям не нужно самостоятельно перемещать модули на GPU, т.к.~torchgpipe.GPipeАвтоматически перемещайте каждый раздел на другое устройство. По умолчанию доступны графические процессоры.cuda:0Запустите и выберите доступные графические процессоры для каждого раздела по порядку. Пользователи также могут воспользоватьсяdeviceПараметр указывает использовать GPU.

   model = GPipe(model,
                 balance=[2, 2],
                 devices=[4, 2],  # Specify GPUs.
                 chunks=8)

3.2 Ввод и вывод

В отличие от типичных модулей, в GPipe устройства ввода отличаются от устройств вывода, если только раздел не один. Это связано с тем, что первый раздел и последний раздел размещены на разных устройствах. Следовательно, вход и цель должны быть перемещены на соответствующее устройство. Это можно сделать с помощью свойства torchgpipe.GPipe.devices, которое содержит список устройств для каждого раздела.

   in_device = model.devices[0]
   out_device = model.devices[-1]
​
   for input, target in data_loader:
       # input on in_device
       input = input.to(in_device, non_blocking=True)
​
       # target on out_device
       target = target.to(out_device, non_blocking=True)
​
       # output on out_device
       output = model(input)
       loss = F.cross_entropy(output, target)
       loss.backward()
       ...

3.3 Вложенные последовательности

когда~torchgpipe.GPipeразделить один<torch.nn.Sequential>модуль, он рассматривает каждый подмодуль модуля как единый неделимый слой. Однако модель не обязательно должна быть такой, и некоторые подмодули могут быть еще одним последовательным модулем, который может потребовать дальнейшего разделения.

GPipe не поддерживает эти вложенные модули Sequentials, поэтому пользователям необходимо сгладить модуль. К счастью, в PyTorch это несложно. В следующем фрагменте кода показано, как выравниваются вложенные последовательные модули:

   _3_layers = nn.Sequential(...)  # len(_3_layers) == 3
   _4_layers = nn.Sequential(...)  # len(_4_layers) == 4
   model = nn.Sequential(_3_layers, _4_layers)  # len(model) == 2
​
   def flatten_sequential(module):
       def _flatten(module):
           for name, child in module.named_children():
               if isinstance(child, nn.Sequential):
                   for sub_name, sub_child in _flatten(child):
                       yield (f'{name}_{sub_name}', sub_child)
               else:
                   yield (name, child)
       return nn.Sequential(OrderedDict(_flatten(module)))
​
   model = flatten_sequential(model)  # len(model) == 7
   model = GPipe(model, balance=[2, 3, 2], chunks=4)

3.4 Типичный параллелизм моделей

Типичный модельный параллелизм — это частный случай GPipe. Модельный параллелизм является эквивалентом GPipe с отключенными микропакетами и контрольными точками и может быть выполнен с помощьюchunks=1иcheckpoint='never'сделать это.

model = GPipe(model, balance=[2, 2], chunks=1, checkpoint='never')

До сих пор были представлены история и базовые знания о библиотеке.В следующей статье мы представим Автобаланс.

0xEE Личная информация

★★★★★★Думая о жизни и технологиях★★★★★★

Публичный аккаунт WeChat:мысли Росси

ссылка 0xFF

Использование формулы уценки

Учебник по редактированию формул в уценке

docs.NVIDIA.com/Bulky/Bulky-Day…

Обучение CUDA: краткое изложение основ

Использование Stream в очерках CUDA

Архитекторы решений NVIDIA глубоко анализируют крупномасштабную параметрическую языковую модель Megatron-BERT

Accelerating Wide & Deep Recommender Inference on GPUs

HugeCTR: High-Performance Click-Through Rate Estimation Training

обсудить.py torch.org/he/how-to-cheat…

GitHub.com/NVIDIA/апекс…

GitHub.com/just и URI St…

py torch.org/tutorials/i…

py torch.org/docs/stable…

Пишите на torch.org/docs/notes/…

zhuanlan.zhihu.com/p/61765561

py torch.Apache N.org/docs/1.7/64…

instructive.com/afraid/217999.contract…