[Анализ исходного кода] Распределенный PyTorch (8) -------- Бумага DistributedDataParallel

глубокое обучение PyTorch

0x00 сводка

Если вы хотите делать свою работу хорошо, вы должны сначала отточить свои инструменты.Чтобы лучше анализировать код, давайте сначала изучим соответствующие статьи.

Разработчики PyTorch выпустили документ во время реализации: [PyTorch Distributed: Experiences on Accelerating Data Parallel Training] Шен Ли, Янли Чжао, Рохан Варма, Омкар Салпекар, Питер Нордхуис, Тенг Ли, Адам Пашке, Джефф Смит, Брайан Воан, Притам Дамания, Soumith Чинталь.

Его адрес:Ууху. Лицо Ви было удалено dB/Vol13 в .org/PV…

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

Другие статьи из этой серии:

[Анализ исходного кода] Распространение PyTorch (1) ------ история и обзор

[Анализ исходного кода] Как PyTorch использует GPU

[Анализ исходного кода] Распределенный PyTorch (2) ----- DataParallel (включен)

[Анализ исходного кода] Распределенный PyTorch (3) ----- DataParallel (ниже)

[Анализ исходного кода] Распределенный PyTorch (4) ------ Основная концепция распределенного приложения

[Анализ исходного кода] Распределенный PyTorch (5) ------ Обзор DistributedDataParallel и способы его использования

[Анализ исходного кода] Распределенный PyTorch (6) ---DistributedDataParallel -- инициализация и хранение

[Анализ исходного кода] Распределенный PyTorch (7) ----- Группа процессов DistributedDataParallel

0x01 Резюме исходного текста

Недавние достижения в области глубокого обучения продемонстрировали ценность больших наборов данных и больших моделей, которые требуют масштабирования обучения моделей на большем количестве вычислительных ресурсов. Благодаря простому принципу и широкой применимости параллелизм данных стал популярным решением для распределенного обучения. Как правило, распределенный параллелизм данных реплицирует модель на каждом источнике вычислений для создания градиентов независимо для каждого рабочего процесса, а затем передает эти градиенты на каждой итерации, чтобы сохранить согласованность копии модели. Несмотря на простоту технической концепции, тонкие зависимости между вычислениями и обменом данными делают важным оптимизацию эффективности распределенного обучения. Начиная с версии 1.5, Pytorch предоставляет несколько методов ускорения параллелизма распределенных данных, включая градиенты с группировкой, передачу перекрывающихся вычислений и пропуск синхронизации градиента. Оценки показывают, что при правильной настройке параллельный модуль распределенных данных Pyrotch достигает приблизительно линейной масштабируемости с использованием 256 графических процессоров.

0x02 Введение

Обучение модели DNN обычно повторяет следующие три шага:

  • Прямой проход для расчета потерь.
  • Обратное распространение для вычисления градиентов.
  • и шаг оптимизатора для обновления параметров.

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

2.1 Проблемы

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

Чтобы предоставить общий пакет параллельных распределенных данных, необходимо решить три задачи.

  • Математическая эквивалентность: цель параллелизма данных — ускорить обучение на больших наборах данных. Приложение ожидает такую ​​же результирующую модель, как если бы все обучение выполнялось локально, без репликации модели. Это требует, чтобы, несмотря на распределенное обучение, оно было математически эквивалентно локальному обучению.
  • Ненавязчивые и перехватывающие API: Разработка приложения обычно начинается с локальной модели, а затем расширяется по мере необходимости. Таким образом, должен быть процесс, начиная с локальной модели и изменяя код, чтобы приспособиться к дистрибутиву.
    • Чтобы избежать этого громоздкого перехода от локальной модели к распределенной модели, API не должен вторгаться в код приложения.
    • С другой стороны, API также должен позволять внутренней реализации своевременно перехватывать различные сигналы, чтобы выполнять обмен данными и оптимизацию системы.
  • высокая производительность: Параллельное обучение данных зависит от тонкой зависимости между вычислениями и обменом данными. Проектирование и реализация должны исследовать пространство решений, чтобы эффективно преобразовывать больше ресурсов в более высокую производительность обучения.

2.2 Реализация и оценка

PyTorch обеспечивает распределенный параллелизм данных в форме класса nn.Module, где приложения предоставляют свои модели в виде подмодулей во время сборки. Чтобы гарантировать математическую эквивалентность, все реплики начинаются с одинаковых начальных значений параметров модели и синхронизируют градиенты, чтобы параметры оставались согласованными на всех итерациях обучения. Чтобы свести к минимуму интеграцию, реализация (параллельная модель распределенных данных) предоставляет тот же прямой API, что и пользовательская модель, что позволяет приложениям беспрепятственно заменять ранее представленную пользовательскую модель объектами параллельной модели распределенных данных, не требуя дополнительных изменений кода. В дизайн интегрировано несколько методов для обеспечения высокопроизводительного обучения, включая градиенты группирования, перекрывающуюся связь с вычислениями и синхронизацию с пропусками.

Оценки проводились на выделенном кластере из 32 графических процессоров и 256 графических процессоров в более крупном общем ресурсе. Мы разработали тесты для оценки распределенных пакетов в разных масштабах, чтобы получить представление о влиянии на производительность различных методов оптимизации и конфигураций. Эксперименты также включают сравнение коммуникационных библиотек NCCL и Gloo. Результаты показывают:

  1. Коммуникация является основным фактором, влияющим на задержку обучения, и ее влияние увеличивается с увеличением размера модели;
  2. Размер корзины сильно влияет на эффективность связи и при правильной настройке может привести к ускорению более чем в 2 раза;
  3. Надлежащий пропуск синхронизации значительно снизит амортизированные накладные расходы на связь без значительного замедления конвергенции.

0x03 фон

3.1 PyTorch

PyTorch организует значения в тензоры, которые представляют собой n-мерные массивы общего назначения с богатым набором операций с данными. Блок определяет преобразование входного значения в выходное значение, а его поведение во время прямого прохода определяется его прямой функцией-членом. Модули могут содержать тензоры в качестве аргументов. Например, линейный модуль содержит параметр веса и параметр смещения, а его прямая функция выдает результат путем умножения входных данных на вес и добавления смещения.

Приложения формируют свои собственные модули, объединяя нативные модули (такие как линейные, сверточные и т. д.) и функции в пользовательских прямых функциях (таких как relu, pool и т. д.). Типичная итерация обучения состоит из прямого прохода, который использует входные данные и метки для генерации потерь, обратного прохода, который вычисляет градиенты параметров, и шага оптимизатора, который использует градиенты для обновления параметров. В частности, во время прямого распространения PyTorch строит график автоградации для записи выполненных действий. Затем при обратном проходе выполняется обратное распространение с использованием графа автоградации для создания градиентов. Наконец, оптимизатор применяет градиенты для обновления параметров. Процесс обучения повторяет эти три шага до тех пор, пока модель не сойдется.

3.2 Параллелизм данных

PyTorch предоставляет несколько инструментов для облегчения распределенного обучения, в том числе:

  • DataParallel для параллельного обучения данных с однопроцессорной многопоточностью с использованием нескольких графических процессоров на одном компьютере.

  • DistributedDataParallel для параллельного обучения данных с несколькими процессами на графических процессорах и машинах.

  • RPC для общего параллельного обучения распределенной модели (например, серверы параметров).

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

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

  • Усреднение параметров может привести к совершенно другим результатам, чем локальное обучение, что иногда может отрицательно сказаться на точности модели. Основная причина заключается в том, что усреднение параметров математически не эквивалентно локальной обработке всех входных данных, особенно когда оптимизатор полагается на прошлые локальные значения градиента, такие как импульс. Поскольку разные копии модели могут видеть разные градиенты, состояния в оптимизаторах могут постепенно расходиться, что приводит к конфликту направлений градиентного спуска. Это может привести к необъяснимым различиям в производительности при переключении с локально оптимизированной модели на модель крупномасштабного развертывания.

  • Структура вычисления координат усреднения параметров (т. е. обратного прохода) и передачи (т. е. вычисления среднего) на непересекающиеся этапы с использованием функции оптимизатора step() в качестве точки жесткого разделения. Независимо от того, насколько агрессивно мы оптимизируем вычисления или связь, один тип ресурсов будет простаивать в любой момент времени, упуская множество возможностей оптимизации производительности.

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

3.3 AllReduce

AllReduce — это базовый коммуникационный API, который используется DistributedDataParallel для вычисления суммы градиентов для всех процессов.

AllReduce предоставляется несколькими коммуникационными библиотеками, включая NCCL, Gloo и MPI. Операция AllReduce требует, чтобы каждый участвующий процесс предоставил тензор одинакового размера, затем применяет заданную арифметическую операцию (такую ​​как sum, prod, min, max) к входным тензорам всех процессов и возвращает то же самое каждому участнику. .

Простая реализация AllReduce может просто заставить каждый процесс транслировать свои входные тензоры всем одноранговым процессам, а затем независимо применять арифметические операции. Однако, поскольку AllReduce оказывает значительное влияние на скорость распределенного обучения, коммуникационные библиотеки реализуют более сложные и эффективные алгоритмы, такие как AllReduce на основе кольца и AllReduce на основе дерева. Поскольку операция AllReduce не может быть запущена до тех пор, пока не будут присоединены все процессы, она считается синхронной связью, а не связью P2P, используемой на сервере параметров.

0x04 Дизайн системы

PyTorch предоставляет модуль распределенного параллелизма данных (DDP), который помогает легко распараллелить обучение между несколькими процессами и машинами. При распределенном обучении у каждого процесса есть своя локальная копия модели и локальный оптимизатор. Для корректности распределенное параллельное обучение данных и локальное обучение должны быть математически эквивалентны. DDP может обеспечить правильность обучения за счет:

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

  • Таким образом, несмотря на то, что оптимизаторы из разных процессов независимы, они должны иметь возможность переводить свои локальные копии модели в одно и то же состояние в конце каждой итерации.

На рисунке ниже показаны стандартные блоки DDP, которые включают в себя внешний интерфейс Python API, базовый алгоритм градиентного слияния C++ и используют коллективную коммуникационную библиотеку c10d. Следующие разделы отображаются в порядке сверху вниз на этой диаграмме стека.

В разделе 4.1 представлены общие принципы, лежащие в основе разработки API DDP. В разделе 4.2 описывается расширенный метод слияния градиентов, используемый в параллельном пакете распределенных данных Pyrotch. Наконец, в Разделе 4.3 обсуждаются возможности коллективной коммуникации для DDP.

4.1 API

При разработке API мы определили две цели проектирования для достижения необходимой функциональности.

  • неинвазивный: API не должен навязываться приложению. Разработчики приложений обычно начинают с написания локальных обучающих сценариев и расширяют масштабы, когда достигаются ограничения ресурсов на одном компьютере. На данный момент требование к разработчикам переписывать целые приложения для поддержки распределенного параллельного обучения данных неприемлемо. Вместо этого разработчики должны иметь возможность повторно использовать локальные обучающие сценарии с минимальными изменениями.
  • перехватывать: API должен позволять реализациям перехватывать различные сигналы, чтобы вовремя запускать соответствующий алгоритм. Распределенный параллелизм данных направлен на ускорение обучения за счет использования большего количества вычислительных ресурсов. Этот процесс требует тонкой оптимизации вычислений и связи для достижения оптимальной производительности. Поэтому API должен предоставлять как можно больше возможностей оптимизации внутренней реализации.

Учитывая вышеуказанные требования, мы реализуем распределенный параллелизм данных в виде nn-модуля, который принимает локальную модель в качестве параметра конструктора и прозрачно синхронизирует данные в обратном процессе. Фрагмент кода ниже показывает пример использования модуля DDP.

  • В этом примере слой nn.Linear используется для создания локальной модели в строке 10.
  • Затем он преобразует локальную модель в модель распределенного обучения в строке 11 и устанавливает оптимизатор в строке 12.
  • Строки с 14 по 23 являются типичными реализациями прямого прохода, обратного прохода и шага оптимизатора.

В этом игрушечном примере распределенного обучения строка 11 является единственной разницей в преобразовании локального обучающего приложения в распределенное приложение, которое удовлетворяет требованию ненавязчивости, а также удовлетворяет требованию интерактивности. Конструкторы позволяют DDP проверять структуру и параметры модели. После создания локальная модель будет заменена распределенной моделью, которая затем может легко перехватывать вызовы forward() для выполнения соответствующих необходимых действий. Для обратного распространения DDP полагается на обратные перехватчики для запуска уменьшения градиента, т. е. когда reverse() выполняется для тензора потерь, механизм autograd выполнит уменьшение градиента.

4.2 Уменьшение градиента

Алгоритм уменьшения градиента в DDP претерпел изменения в прошлых выпусках. Чтобы представить структуру текущей реализации, давайте начнем с простого решения, постепенно усложняя его и используя текущую версию в PyTorch v1.5.0. Это также объяснит, как тот же самый простой API, описанный в Разделе 4.1, позволяет нам устанавливать различные алгоритмы оптимизации производительности.

4.2.1 A Naive Solution

Как описано в начале раздела 4, DDP гарантирует правильность за счет того, что все процессы обучения (1) начинаются с одного и того же состояния модели и (2) используют одни и те же градиенты на каждой итерации. Первое может быть достигнуто путем широковещательной передачи состояния модели от одного процесса ко всем другим процессам во время сборки DDP. Для достижения последнего есть простое решение: фаза синхронизации градиента может быть вставлена ​​после локального обратного распространения и перед обновлением локальных параметров. Однако API, показанный в Разделе 4.1, не предоставляет явной точки входа для этой стадии, так как между back() и step() нет ничего. К счастью, движок автоградации PyTorch поддерживает настраиваемые обратные перехватчики. DDP может регистрировать перехватчики автоградации для запуска вычислений после каждого обратного прохода. Когда функция ловушки запускается, каждая ловушка сканирует все параметры локальной модели и извлекает тензоры градиента из каждого параметра. Затем он использует операцию коллективной связи AllReduce для вычисления среднего градиента для каждого параметра по всем процессам и записывает результат обратно в тензор градиента.

Наивное решение работает нормально, но есть две проблемы с производительностью:

  • Ансамблевая связь плохо работает с небольшими тензорами, особенно с большими моделями с большим количеством малых параметров.

  • Отделение вычисления градиента от синхронизации лишает возможности перекрытия вычислений и связи из-за жесткой границы между ними.

4.2.2 Gradient Bucketing

Идея градиентной корзины основана на наблюдении, что коллективная коммуникация более эффективна на больших тензорах. На рисунках (a) и (b) ниже представлено количественное представление, показывающее общее время выполнения AllReduce 60M torch.float32. Количество параметров для каждого AllReduce разное.

Чтобы максимизировать использование пропускной способности, все операции сокращения запускаются асинхронно и блокируются, ожидая всех операций одновременно, чтобы имитировать алгоритм градиентного слияния DDP. Эксперименты проводились на сервере с поддержкой NVLink[3] с двумя графическими процессорами NVIDIA Quadro GP100. NCCL AllReduce работает непосредственно с тензорами ввода CUDA, в то время как Gloo AllReduce работает с тензорами ввода ЦП, чтобы исключить накладные расходы на копирование памяти CUDA в память ЦП при использовании бэкенда Gloo. Для NCCL и Gloo общее время связи значительно сокращается при использовании больших входных тензоров. Gloo достигает максимума примерно в 500 тысяч параметров на входной тензор, в то время как NCCL на NVLink даже не имеет заметного сигнала насыщения тензоров графического процессора с 20 миллионами параметров.

Эти эксперименты показывают, что DDP может достичь более высокой пропускной способности и меньшей задержки, если он ожидает и сохраняет несколько градиентов в одной операции AllReduce в течение короткого периода времени, а не запускает выделенное хранилище, как только каждое хранилище градиентов становится доступным. Это особенно полезно для моделей с большим количеством малых параметров. Однако DDP не должен передавать все данные в одном AllReduce, иначе связь не может быть инициирована до конца вычислений. На рисунках (c) и (d) выше показано время обратного вычисления GPU и CPU для ResNet152, содержащего около 60 миллионов параметров. По оси X откладывается количество подготовленных градиентов, а по оси Y — время, прошедшее с момента начала обратного распространения. Обратное распространение на графическом процессоре занимает около 250 мс, что примерно того же порядка, что и NCCL на NVLink. Этот вывод также относится к обратному распространению Gloo и CPU. Эти измерения подразумевают, что для относительно небольших размеров сегментов DDP может инициировать операции AllReduce при обратном распространении, чтобы перекрыть связь с вычислениями, что изменит задержку каждой итерации.

4.2.3 Overlap Computation with Communication

Операции AllReduce с градиентами могут начаться до того, как будет завершено локальное обратное распространение. При разделении DDP необходимо дождаться всего в одном сегменте, прежде чем начать связь.

В этой настройке просто запуска AllReduce в конце обратного распространения уже недостаточно. Нам нужно реагировать на более частые сигналы и быстрее запускать AllReduce. Поэтому DDP регистрирует хук автоградации для каждого аккумулятора градиента. Хук запускается после того, как соответствующий аккумулятор обновит градиент и проверит ведро, к которому он принадлежит. Если хуки для всех градиентов в одном сегменте сработали, последний хук вызовет асинхронный AllReduce для этого сегмента.

Следует отметить два момента.

  • Во-первых, порядок слияния всех процессов должен быть одинаковым, иначе содержимое AllReduce может не совпадать, что приведет к неправильным результатам слияния или сбоям программы. Однако PyTorch динамически строит график автоградации при каждом прямом проходе, и разные процессы могут не соответствовать порядку готовности градиента. Пример приведен на рисунке (а) ниже, где две вертикальные оси представляют время, а пунктирная линия представляет время, когда градиент готов. В процессе 1 четыре градиента вычисляются последовательно, но градиент g2 вычисляется после g3 и g4 в процессе 2. В этом случае, если все корзины AllReduce будут обрабатываться сразу после того, как они будут готовы, содержимое AllReduce не будет совпадать. Следовательно, все процессы должны использовать один и тот же порядок группирования, и ни один процесс не может запустить AllReduce в корзине i+1 до загрузки корзины i. Если сегмент 0 является последним готовым сегментом, то связь не может перекрываться с вычислением [I: Поскольку сегмент 0 является последним готовым сегментом, другие сегменты не будут вычисляться до этого и не могут пересекаться с сеансом связи]. PyTorch v1.5.0 решает эту проблему, используя обратный порядок model.parameters() в качестве порядка группирования. Мы делаем следующее предположение, что слои могут быть зарегистрированы в том же порядке, в котором они вызывались в прямом процессе. Следовательно, его обратный порядок является приблизительным представлением порядка вычисления градиента в обратном процессе. Конечно, это не идеальное решение, но это приближение, которое мы можем реализовать с минимальными затратами на разработку.
  • Во-вторых, обучающая итерация может включать в модель только один подграф, и подграф может быть разным в каждой итерации, что означает, что некоторые градиенты могут быть пропущены в некоторых итерациях. Однако, поскольку сопоставление градиента с сегментом определяется во время сборки, из-за этих отсутствующих градиентов некоторые сегменты никогда не увидят окончательный хук автозагрузки и не смогут пометить сегмент как готовый. Поэтому обратное распространение может быть приостановлено. На рисунке (b) ниже показан пример, в котором параметр, соответствующий градиенту g3, пропускается в одной итерации, в результате чего g3 не имеет сигнала готовности. Чтобы решить эту проблему, DDP просматривает граф автоградации из выходных тензоров с прямым распространением, чтобы найти все участвующие параметры. Готовность этих участвующих тензоров является достоверным сигналом о том, что обратное распространение в конце завершено. Таким образом, DDP может избежать ожидания, активно отмечая оставшиеся градиенты параметров в конце прямого распространения. Обратите внимание, что это изменение не мешает нам разрабатывать ненавязчивый API, поскольку приложения могут напрямую вызывать функцию переадресации в DDP, а DDP может легко вставить этот шаг в свои функции-члены.

Приведенный ниже алгоритм дает псевдокод DDP.

Конструктор состоит из двух основных шагов: трансляции состояния модели и установки хуков autograd. Функция forwad DDP — это простая оболочка для функции forwad нативной модели. Он просматривает график autograd, чтобы соответствующим образом пометить неиспользуемые параметры. Хук autograd принимает на вход внутренний индекс параметра, который помогает найти тензор параметра и область видимости, которой он принадлежит. Он записывает локальный градиент с правильным смещением в корзину, а затем запускает асинхронную операцию AllReduce. В псевдокоде пропущен дополнительный конечный шаг, который ожидает операции AllReduce и записывает значения обратно в градиент в конце обратного процесса.

На рисунке ниже показано, как DDP взаимодействует с локальной моделью во время прямого и обратного распространения.

Приведенное выше решение работает для большинства случаев использования. Однако, поскольку DDP всегда вычисляет среднее значение всех градиентов и записывает их обратно в поле parameter.grad, оптимизатор не может различить, участвовали ли градиенты в последнем обратном проходе. Из-за несвязанной конструкции DDP и оптимизатора DDP не имеет побочного канала для передачи этой информации оптимизатору. Без этой информации процесс обучения может пострадать от снижения точности модели, например, когда оптимизатор использует информацию о градиенте, чтобы пропустить обновления значения импульса. Чтобы решить эту проблему, DDP должен касаться только тех градиентов, которые включают обратное распространение. Однако, поскольку в одноранговом DDP-процессе прямой/обратный процесс может по-прежнему включать локальные отсутствующие градиенты, невозможно извлечь эту информацию только из локального графа автоградации. Поэтому DDP использует растровое изображение для отслеживания участников локальных параметров и запускает еще один AllReduce для сбора глобально неиспользуемых параметров. К сожалению, DDP не может включить это растровое изображение в другие операции AllReduce с градиентом из-за возможного несоответствия типов элементов. Эти дополнительные накладные расходы возникают только тогда, когда приложение явно указывает DDP искать неиспользуемые параметры, поэтому оно платит только в случае необходимости.

4.2.4 Gradient Accumulation

Распространенным методом ускорения параллельного обучения на распределенных данных является уменьшение частоты градиентной синхронизации. Вместо того, чтобы запускать AllReduce на каждой итерации, приложение может выполнить n локальных итераций обучения перед глобальной синхронизацией градиентов. Это также полезно, если входной пакет слишком велик для размещения на устройстве, поскольку приложение может разделить входной пакет на несколько микропакетов, выполняя частичное распространение вперед и назад для каждого микропакета, и инициировать синхронизацию градиента только в границы крупных партий. Теоретически это должно давать те же результаты, что и обработка больших пакетов данных за один раз, поскольку градиенты просто будут накапливаться до одних и тех же тензоров. Однако это в некоторой степени противоречит алгоритму слияния градиентов, обсуждаемому в разделе 4.2.3. Алгоритм будет помечать неиспользуемые параметры как готовые в конце каждого прямого прохода, в то время как параметры, не использованные в одной итерации, могут по-прежнему участвовать в последующих итерациях. Кроме того, DDP не может различить, должно ли приложение вызывать optimizer.step() сразу после обратного вызова или после накопления градиентов в течение нескольких итераций. Поэтому нам нужно ввести дополнительный интерфейс (например, no_sync ) для этого варианта использования.

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

4.3 Collective Communication

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

DDP построен на основе библиотеки коллективных коммуникаций и включает в себя три варианта: NCCL, Gloo и MPI. DDP берут API из трех библиотек и заключают их в один и тот же API ProcessGroup. Название подразумевает, что ProcessGroup ожидает, что несколько процессов будут работать вместе как группа.

Все экземпляры ProcessGroup создаются одновременно с помощью службы рандеву, где первый экземпляр блокируется, ожидая, пока присоединится последний экземпляр. Для серверных частей NCCL ProcessGroup поддерживает выделенный набор потоков CUDA для связи, чтобы связь не блокировала вычисления в потоке по умолчанию. Поскольку все коммуникации являются операциями установки, последующие операции для всех экземпляров ProcessGroup должны совпадать по размеру и типу и выполняться в том же порядке. Использование одного API ProcessGroup для всех библиотек позволяет нам экспериментировать с различными алгоритмами связи, используя одну и ту же реализацию DDP. Например, PyTorch v1.5 предоставляет реализацию ProcessGroup с циклическим перебором, которая принимает список экземпляров ProcessGroup и отправляет коллективные сообщения этим экземплярам ProcessGroup в циклическом режиме. Используя циклическую ProcessGroup, DDP может добиться более высокого использования пропускной способности в ситуациях, когда одна группа обработки NCCL, Gloo или MPI не может насытить пропускную способность канала.

0x05 Реализация

Реализация DDP претерпела несколько изменений за последние несколько выпусков. В этом разделе основное внимание уделяется текущему состоянию PyTorch v1.5.0. Реализация DDP существует как в файлах Python, так и в файлах C++. Часть Python включает в себя общедоступные API и некритичные для производительности компоненты. C++ предоставляет основной алгоритм слияния градиентов. API Python вызывает ядро ​​C++ через Pybind11.

5.1 Внешний интерфейс Python

Модуль DDP nn.module реализован в файле Distributed.py, который содержит компоненты, ориентированные на пользователя. Компоненты включают конструкторы, прямые функции и менеджеры контекста no_sync. В дополнение к общим идеям, изложенным в разделе 4, во внешнем интерфейсе Python есть несколько деталей реализации, которые определяют поведение DDP.

Настраиваемые ручки доступны в API-интерфейсе конструктора DDP, в том числе

  • process_group, используемый для указания экземпляра группы процессов DDP, в котором работает AllReduce, что помогает избежать путаницы с группой процессов по умолчанию;

  • Bucket_cap_mb, используемый для управления размером корзины AllReduce, приложение должно настроить его для оптимизации скорости обучения.

  • find_unused_parameters, чтобы переключить, должен ли DDP обнаруживать неиспользуемые параметры.DDP завершает обнаружение, просматривая граф автоградации.

Привязка модели к устройству в локальной модели также управляет поведением DDP, особенно когда модель охватывает несколько устройств, что часто бывает, когда модель слишком велика для размещения на одном устройстве. Для больших моделей приложения могут размещать разные слои модели на разных устройствах и использовать API Tensor.to(device) для перемещения промежуточных выходных данных с одного устройства на другое. DDP также работает с моделями с несколькими устройствами. Пока для параметра device_ids установлено значение None или пустой список, DDP будет проверять модель, выполнять проверки работоспособности и соответствующим образом применять конфигурацию. Затем рассмотрим модель с несколькими устройствами в целом.

Буферы модели необходимы, когда слою необходимо отслеживать такие состояния, как текущая дисперсия и скользящее среднее значение (например, BatchNorm). DDP работает, позволяя процессам ранга 0 получить разрешение на поддержку буферов моделей. Если модель содержит буферы, DDP рассылает значения буфера из процесса ранга 0 всем остальным процессам перед запуском прямого прохода локальной модели. Это поведение также совместимо с режимом no_sync. Когда режим no_sync включен, он правильно устанавливает флаг на прямом проходе, чтобы указать, ожидает ли он уменьшения градиента на следующем обратном проходе. Если происходит обмен данными, DDP будет передавать буфер перед последующим проходом вперед.

5.2 Core Gradient Reduction

Основные усилия при разработке тратятся на уменьшение градиента, так как это шаг, наиболее связанный с производительностью в DDP. Реализация существует в reducer.cpp и состоит из четырех основных компонентов, а именно:

  • Построить карту параметров для корзин.
  • Установить крюк автоград.
  • Стартовое ведро AllReduce
  • Обнаружение глобально неиспользуемых параметров.

Далее мы опишем эти четыре компонента.

Сопоставление параметра с сегментом оказывает значительное влияние на скорость DDP. При каждом обратном проходе тензоры из всех градиентов параметров копируются в ведра, а средние градиенты копируются обратно в ведра после AllReduce. Для ускорения операций копирования сегменты всегда создаются на том же устройстве, что и параметры. Если модель охватывает несколько устройств, DDP учитывает привязку устройств, чтобы гарантировать, что все параметры в одном сегменте находятся на одном устройстве. Порядок AllReduce также влияет на результат, так как он определяет, насколько обмен данными может пересекаться с вычислением. DDP запускает AllReduce в обратном порядке model.parameters().

Autograd Hook — это точка входа DDP в обратном распространении. Во время построения DDP просматривает все параметры в модели, находит аккумуляторы градиента для каждого параметра и устанавливает одну и ту же функцию пост-ловушки для каждого аккумулятора градиента. Аккумулятор градиента активирует пост-перехватчики, когда соответствующий градиент будет готов, а DDP вычислит, когда будет готов весь сегмент, что может запустить операцию AllReduce. Однако, поскольку порядок подготовки градиента не может быть гарантирован, DDP не может выборочно выбирать параметры монтажных крюков. В текущей реализации каждое ведро хранит ожидающий счетчик градиента. Каждая функция post-hook ведет обратный отсчет, и когда счетчик достигает нуля, DDP помечает корзину как готовую. При следующем прямом проходе DDP заполняет ожидающий накопленный счетчик для каждого сегмента.

Bucket AllReduce — основной источник коммуникационных издержек в DDP. С одной стороны, размещение большего количества градиентов в одной корзине уменьшит амортизированную систему накладных расходов на связь. С другой стороны, использование корзины большего размера приведет к большей задержке слияния, так как каждой корзине нужно ждать большего количества градиентов. Таким образом, размер ковша является ключевым компромиссом. По умолчанию размер каждого сегмента составляет 25 МБ. Приложение должно эмпирически измерить свое влияние и установить для него оптимальное значение для своего варианта использования.

Градиенты глобально неиспользуемых параметров должны оставаться постоянными во время прямого и обратного прохода. Для обнаружения неиспользуемых параметров требуется глобальная информация, поскольку в процессе DDP параметр может не существовать в одной операции, но может участвовать в обучении в той же итерации другого процесса. Поэтому DDP сохраняет локально неиспользуемую информацию о параметрах в растровом изображении и запускает дополнительный AllReduce для сбора глобального растрового изображения. Поскольку растровые изображения намного меньше по размеру, чем тензоры, все параметры в модели используют одно и то же растровое изображение вместо создания растровых изображений для каждого сегмента. Битовая карта находится на ЦП, чтобы избежать запуска выделенного ядра CUDA для каждого обновления. Однако некоторые серверные части ProcessGroup могут не запускать AllReduce на тензорах ЦП. Например, ProcessGroupNCCL поддерживает только тензоры CUDA. Кроме того, поскольку DDP должен работать с любой пользовательской серверной частью ProcessGroup, нельзя предполагать, что все серверные части поддерживают тензоры ЦП. Чтобы решить эту проблему, DDP поддерживает другое растровое изображение на том же устройстве в качестве первого параметра модели и вызывает операцию неблокирующего копирования для перемещения растрового изображения ЦП на растровое изображение устройства для коллективной связи.

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

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

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

ссылка 0xFF

Ууху. Лицо Ви было удалено dB/Vol13 в .org/PV…