Связки коммуникации DDP
Связка коммуникации DDP — это общий интерфейс для управления тем, как передавать градиенты между рабочими процессами, переопределяя стандартный allreduce в DistributedDataParallel. Предоставляется несколько встроенных связок коммуникации, и пользователи могут легко применять любую из этих связок для оптимизации коммуникации. Кроме того, интерфейс связки также может поддерживать пользовательские стратегии коммуникации для более сложных случаев использования.
Как использовать связку коммуникации?
Для использования связки коммуникации пользователю достаточно разрешить модели DDP зарегистрировать связку перед циклом обучения, как показано ниже.
torch.nn.parallel.DistributedDataParallel.register_comm_hook()
Что обрабатывает связка коммуникации?
Связка коммуникации предоставляет гибкий способ выполнения allreduce для градиентов. Поэтому она в основном обрабатывает градиенты на каждой реплике перед allreduce, которые разбиваются на пакеты для увеличения перекрытия между коммуникацией и вычислением. В частности, torch.distributed.GradBucket представляет собой пакет тензоров градиента, которые необходимо allreduce.
-
class torch.distributed.GradBucket -
Этот класс в основном передает сплющенный тензор градиента (возвращаемый
buffer()) в связку коммуникации DDP. Этот тензор можно далее разбить на список тензоров параметров в этом пакете (возвращаемыйget_per_parameter_tensors()) для применения операций на уровне слоя.
-
torch.distributed.GradBucket.index(self: torch._C._distributed_c10d.GradBucket) → int -
Предупреждение
Поскольку пакеты перестраиваются после первой итерации, не следует полагаться на индексы в начале обучения.
- Возвращает
-
Индекс пакета, который хранит градиенты нескольких смежных слоев. Все градиенты разбиты на пакеты.
-
torch.distributed.GradBucket.buffer(self: torch._C._distributed_c10d.GradBucket) → torch.Tensor -
- Возвращает
-
Сплющенный одномерный
torch.Tensorбуфер, который можно далее разбить на список тензоров параметров в этом пакете.
-
torch.distributed.GradBucket.gradients(self: torch._C._distributed_c10d.GradBucket) → List[torch.Tensor] -
- Возвращает
-
Список
torch.Tensor. Каждый тензор в списке соответствует градиенту.
-
torch.distributed.GradBucket.is_last(self: torch._C._distributed_c10d.GradBucket) → bool -
- Возвращает
-
Является ли этот пакет последним пакетом для allreduce в итерации. Это также означает, что этот пакет соответствует первым нескольким слоям в прямом проходе.
-
torch.distributed.GradBucket.set_buffer(self: torch._C._distributed_c10d.GradBucket, buffer: torch.Tensor) → None -
Заменяет тензор в пакете входным тензорным буфером.
-
torch.distributed.GradBucket.parameters(self: torch._C._distributed_c10d.GradBucket) → List[torch.Tensor] -
- Возвращает
-
Список
torch.Tensor. Каждый тензор в списке соответствует параметру модели.
Связки коммуникации по умолчанию
Связки коммуникации по умолчанию являются простыми связками без состояния (stateless), поэтому входное состояние в register_comm_hook — это либо группа процессов, либо None. Вход bucket — это объект torch.distributed.GradBucket.
-
torch.distributed.algorithms.ddp_comm_hooks.default_hooks.allreduce_hook(process_group, bucket)[source] -
Эта связка коммуникации DDP просто вызывает
allreduceс использованием тензоровGradBucket. После агрегирования тензоров градиента по всем рабочим процессам её обратный вызовthenвычисляет среднее значение и возвращает результат. Если пользователь зарегистрирует эту связку, результаты DDP должны быть такими же, как в случае, когда связка не зарегистрирована. Таким образом, это не изменит поведение DDP, и пользователь может использовать эту связку в качестве справки или изменить её для ведения полезной информации или любых других целей, не влияя на поведение DDP.- Пример::
-
>>> ddp_model.register_comm_hook(process_group, allreduce_hook)
-
torch.distributed.algorithms.ddp_comm_hooks.default_hooks.fp16_compress_hook(process_group, bucket)[source] -
Эта связка коммуникации DDP реализует простой подход к сжатию градиента, который преобразует тензор
GradBucketв формат с плавающей точкой полуточной точности (torch.float16) и затем делит его на размер группы процессов. Он allreduce теfloat16тензоры градиента. После того, как сжатые тензоры градиента будут allreduce, цепной обратный вызовdecompressпреобразует его обратно в исходный тип данных (например,float32).- Пример::
-
>>> ddp_model.register_comm_hook(process_group, fp16_compress_hook)
-
torch.distributed.algorithms.ddp_comm_hooks.default_hooks.bf16_compress_hook(process_group, bucket)[source] -
Предупреждение: Этот API является экспериментальным и требует версии NCCL, более поздней, чем 2.9.6.
Эта связка коммуникации DDP реализует простой подход к сжатию градиента, который преобразует тензор
GradBucketв формат с плавающей точкой Brain (torch.bfloat16) и затем делит его на размер группы процессов. Он allreduce теbfloat16тензоры градиента. После того, как сжатые тензоры градиента будут allreduce, цепной обратный вызовdecompressпреобразует его обратно в исходный тип данных (например,float32).- Пример::
-
>>> ddp_model.register_comm_hook(process_group, bf16_compress_hook)
Кроме того, предоставляется оболочка связки коммуникации для поддержки fp16_compress_hook() или bf16_compress_hook() в качестве оболочки, которая может быть объединена с другими связками коммуникации.
-
torch.distributed.algorithms.ddp_comm_hooks.default_hooks.fp16_compress_wrapper(hook)[source] -
Эта оболочка преобразует входной тензор градиента заданной связки коммуникации DDP в формат с плавающей точкой полуточной точности (
torch.float16), и преобразует результирующий тензор заданной связки обратно в исходный тип данных, напримерfloat32.Поэтому,
fp16_compress_hookэквивалентноfp16_compress_wrapper(allreduce_hook).- Пример::
-
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1, start_powerSGD_iter=10) >>> ddp_model.register_comm_hook(state, fp16_compress_wrapper(powerSGD_hook))
- Тип возвращаемого значения
-
Callable[[Any, GradBucket], Future[Tensor]]
-
torch.distributed.algorithms.ddp_comm_hooks.default_hooks.bf16_compress_wrapper(hook)[source] -
Предупреждение: Этот API экспериментальный и требует версию NCCL, более позднюю, чем 2.9.6.
Этот обёртка преобразует входной тензор градиента заданного DDP-коммуникационного хука в формат с половинной точностью
Brain floating point format <https://en.wikipedia.org/wiki/Bfloat16_floating-point_format> `_ (``torch.bfloat16`, и преобразует результирующий тензор заданного хука обратно в исходный тип данных, напримерfloat32.Таким образом,
bf16_compress_hookэквивалентноbf16_compress_wrapper(allreduce_hook).- Пример::
-
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1, start_powerSGD_iter=10) >>> ddp_model.register_comm_hook(state, bf16_compress_wrapper(powerSGD_hook))
- Тип возвращаемого значения
-
Callable[[Any, GradBucket], Future[Тензор]]
Хук связи PowerSGD
PowerSGD (Vogels et al., NeurIPS 2019) — алгоритм сжатия градиентов, который может обеспечивать очень высокие скорости сжатия и ускорять распределённое обучение, ограниченное пропускной способностью. Этот алгоритм должен поддерживать как некоторые гиперпараметры, так и внутреннее состояние. Поэтому хук связи PowerSGD является состоятельным хуком, и пользователь должен предоставить объект состояния, определённый ниже.
Состояние PowerSGD
-
class torch.distributed.algorithms.ddp_comm_hooks.powerSGD_hook.PowerSGDState(process_group, matrix_approximation_rank=1, start_powerSGD_iter=1000, min_compression_rate=2, use_error_feedback=True, warm_start=True, orthogonalization_epsilon=0, random_seed=0, compression_stats_logging_frequency=10000, batch_tensors_with_same_shape=False)[source] -
Хранит как гиперпараметры алгоритма, так и внутреннее состояние для всех градиентов во время обучения. В частности,
matrix_approximation_rankиstart_powerSGD_iter— основные гиперпараметры, которые должен настраивать пользователь. Для повышения производительности рекомендуется держать двоичные гиперпараметрыuse_error_feedbackиwarm_startвключёнными.-
matrix_approximation_rankуправляет размером сжатых тензоров низкого ранга, что определяет скорость сжатия. Чем ниже ранг, тем сильнее сжатие.1.1. Если
matrix_approximation_rankслишком низкое, модели потребуется больше шагов обучения, чтобы достичь или никогда не достичь требуемого качества и ухудшится точность.1.2. Увеличение
matrix_approximation_rankзначительно увеличивает вычислительные затраты на сжатие, и точность может не улучшиться сверх определённого порогаmatrix_approximation_rank.
Для настройки
matrix_approximation_rankрекомендуется начинать с 1 и увеличивать в два раза (как экспоненциальный поиск по сетке, 1, 2, 4, …), пока не будет достигнута удовлетворительная точность. Обычно используется небольшое значение 1-4. Для некоторых задач NLP (как показано в приложении D оригинальной статьи) это значение было увеличено до 32.-
start_powerSGD_iterоткладывает сжатие PowerSGD до шагаstart_powerSGD_iter, а обычный allreduce выполняется до шагаstart_powerSGD_iter. Эта гибридная схема обычный allreduce + PowerSGD может эффективно повысить точность, даже если используется относительно маленькое значениеmatrix_approximation_rank. Это связано с тем, что начало этапа обучения обычно очень чувствительно к неточным градиентам, и сжатие градиентов слишком рано может привести к тому, что обучение быстро перейдёт по не оптимальной траектории, что может привести к невосполнимому влиянию на точность.
Для настройки
start_powerSGD_iterрекомендуется начинать с 10% от общего числа шагов обучения и увеличивать его, пока не будет достигнута удовлетворительная точность. Если в обучении есть этап разогрева,start_powerSGD_iterобычно должно быть не меньше числа шагов разогрева.-
min_compression_rate— минимальная требуемая скорость сжатия, когда слой сжимается. Из-за вычислительных накладных расходов, связанных со сжатием, тензор стоит сжимать только в том случае, если можно достаточно сэкономить на пропускной способности, где(num_rows + num_cols) * matrix_approximation_rank * min_compression_rate < num_rows * num_cols. Если заданный порог скорости сжатия не может быть удовлетворён, тензор будет непосредственно allreduced без сжатия.
Статистика сжатия регистрируется каждые
compression_stats_logging_frequencyитераций после начала сжатия PowerSGD.-
orthogonalization_epsilonможет быть очень малым значением (например, 1e-8), добавляемым к каждой нормализованной колонке матрицы на шаге ортогонализации, чтобы предотвратить ошибку деления на ноль, если какая-либо колонка имеет все нули. Если это можно предотвратить (например, с помощью нормализации по батчам), рекомендуется использовать epsilon равное 0 для точности. -
batch_tensors_with_same_shapeуправляет тем, следует ли сжимать и распаковывать тензоры с одинаковой формой в пакетной операции для достижения большей параллельности. Обратите внимание, что также необходимо увеличить размер пакета (т.е., аргументbucket_cap_mbв конструкторе DDP), чтобы в одном пакете появилось больше тензоров с одинаковой формой, однако это может уменьшить перекрытие между вычислениями и обменом, а также увеличить объём памяти из-за объединения тензоров с одинаковой формой. Установите вTrueв случае, если вычисления сжатия/распаковки являются узким местом.
Предупреждение
Если включена обратная связь об ошибках или разогрев, минимальное допустимое значение
start_powerSGD_iterв DDP составляет 2. Это связано с тем, что есть другая внутренняя оптимизация, которая перестраивает пакеты на итерации 1 в DDP, и это может конфликтовать с любыми тензорами, запомненными до процесса перестройки. -
Хуки PowerSGD
Предупреждение
PowerSGD обычно требует дополнительной памяти, равной размеру градиентов модели, для включения обратной связи об ошибках, которая может компенсировать смещённый сжатый обмен и улучшить точность.
Предупреждение
Хуки PowerSGD могут конфликтовать с пакетом автоматической смешанной точности Apex https://github.com/NVIDIA/apex. Используйте вместо него пакет автоматической смешанной точности PyTorch https://pytorch.org/docs/stable/amp.html.
-
torch.distributed.algorithms.ddp_comm_hooks.powerSGD_hook.powerSGD_hook(state, bucket)[source] -
Этот DDP-коммуникационный хук реализует алгоритм сжатия градиентов PowerSGD, описанный в статье. После агрегирования тензоров градиента по всем рабочим узлам этот хук применяет сжатие следующим образом:
-
Представляет входной сглаженный тензор градиента 1D как список тензоров на параметр и делит все тензоры на две группы:
1.1 Тензоры, которые должны быть сжаты перед allreduce, так как сжатие может обеспечить достаточную экономию пропускной способности.
1.2 Остальные тензоры будут непосредственно allreduced без сжатия, включая все векторные тензоры (для смещений).
-
Обрабатывает несжатые тензоры:
2.1 Выделяет непрерывную память для этих несжатых тензоров и allreduces все несжатые тензоры как пакет без сжатия;
2.2 Копирует отдельные несжатые тензоры из непрерывной памяти обратно в входной тензор.
-
Обрабатывает тензоры, которые должны быть сжаты с помощью сжатия PowerSGD:
3.1 Для каждого тензора M создаёт два тензора низкого ранга P и Q для разложения M, так что M = PQ^T, где Q инициализируется из стандартного нормального распределения и ортогонализируется;
3.2 Вычисляет каждый P в Ps, который равен MQ;
3.3 Allreduces Ps как пакет;
3.4 Ортогонализирует каждый P в Ps;
3.5 Вычисляет каждый Q в Qs, который приблизительно равен M^TP;
3.6 Allreduces Qs как пакет;
3.7 Вычисляет каждый M среди всех сжатых тензоров, который приблизительно равен PQ^T.
Обратите внимание, что этот коммуникационный хук принудительно использует обычный allreduce в течение первых
state.start_powerSGD_iterитераций. Это не только даёт пользователю больший контроль над балансом между ускорением и точностью, но и помогает абстрагировать некоторые сложности внутренней оптимизации DDP для будущих разработчиков коммуникационных хуков.- Параметры
-
-
state (PowerSGDState) – Информация о состоянии для настройки скорости сжатия и поддержки обратной связи об ошибках, теплового старта и т. д. Для настройки параметров сжатия необходимо настроить, в основном,
matrix_approximation_rank,start_powerSGD_iterиmin_compression_rate. - bucket (dist.GradBucket) — Пакет, хранящий сглаженный тензор градиента 1D, который группирует несколько тензоров на переменную. Обратите внимание, что поскольку DDP comm hook поддерживает только режим одного процесса на одном устройстве, в этом пакете хранится ровно один тензор.
-
state (PowerSGDState) – Информация о состоянии для настройки скорости сжатия и поддержки обратной связи об ошибках, теплового старта и т. д. Для настройки параметров сжатия необходимо настроить, в основном,
- Возвращаемое значение
-
Обработчик будущего коммуникации, который обновляет градиенты на месте.
- Тип возвращаемого значения
- Пример::
-
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1, start_powerSGD_iter=10, min_compression_rate=0.5) >>> ddp_model.register_comm_hook(state, powerSGD_hook)
-
-
torch.distributed.algorithms.ddp_comm_hooks.powerSGD_hook.batched_powerSGD_hook(state, bucket)[source] -
Этот DDP-коммуникационный хук реализует упрощённый алгоритм сжатия градиентов PowerSGD, описанный в статье. Этот вариант не сжимает градиенты по слоям, а вместо этого сжимает уплощённый входной тензор, объединяющий все градиенты. Поэтому он быстрее, чем
powerSGD_hook(), но обычно приводит к намного более низкой точности, еслиmatrix_approximation_rankравно 1.Предупреждение
Увеличение
matrix_approximation_rankздесь может не обязательно повысить точность, потому что объединение тензоров по параметрам без выравнивания столбцов/строк может разрушить низкоранговую структуру. Поэтому пользователь должен всегда сначала рассмотретьpowerSGD_hook(), и рассматривать только этот вариант, когда удовлетворительная точность достигается приmatrix_approximation_rankравном 1.После агрегирования тензоров градиентов по всем рабочим узлам, этот хук применяет сжатие следующим образом:
- Представляет входной уплощённый одномерный тензор градиента как квадратный тензор M с 0-выми дополнениями;
- Создаёт два низкоранговых тензора P и Q для разложения M, такие что M = PQT, где Q инициализируется из стандартного нормального распределения и ортогонализируется;
- Вычисляет P, которое равно MQ;
- Выполняет allreduce для P;
- Ортогонализирует P;
- Вычисляет Q, которое приблизительно равно MTP;
- Выполняет allreduce для Q;
- Вычисляет M, которое приблизительно равно PQT.
- Обрезает входной тензор до первоначальной длины.
Обратите внимание, что этот коммуникационный хук применяет обычный allreduce для первых
state.start_powerSGD_iterитераций. Это не только даёт пользователю больше контроля над компромиссом между ускорением и точностью, но также помогает абстрагировать некоторые сложности внутренней оптимизации DDP для разработчиков будущих коммуникационных хуков.- Параметры
-
-
state (PowerSGDState) – Информация о состоянии для настройки скорости сжатия и поддержки обратной связи об ошибках, теплого запуска и т.д. Для настройки параметров сжатия, необходимо, в основном, настраивать
matrix_approximation_rankиstart_powerSGD_iter. - bucket (dist.GradBucket) – Бакет, который хранит одномерный уплощённый тензор градиента, объединяющий несколько тензоров на переменную. Обратите внимание, что так как DDP comm hook поддерживает только режим одного процесса и одного устройства, в этом бакете хранится ровно один тензор.
-
state (PowerSGDState) – Информация о состоянии для настройки скорости сжатия и поддержки обратной связи об ошибках, теплого запуска и т.д. Для настройки параметров сжатия, необходимо, в основном, настраивать
- Возвращаемое значение
-
Обработчик будущего коммуникации, который обновляет градиенты на месте.
- Тип возвращаемого значения
- Пример::
-
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1) >>> ddp_model.register_comm_hook(state, batched_powerSGD_hook)
Отладка коммуникационных хуков
Как следует из названия, отладка коммуникационных хуков только используется для целей отладки и оптимизации производительности.
Предупреждение
Отладочные коммуникационные хуки не обязательно выводят правильные результаты.
-
torch.distributed.algorithms.ddp_comm_hooks.debugging_hooks.noop_hook(_, bucket)[source] -
Этот DDP-коммуникационный хук возвращает будущее, которое оборачивает вход, поэтому он является «ничего не делающим» и не требует коммуникационных накладных расходов.
Этот хук следует только использовать для анализа резерва оптимизации allreduce, а не для обычной синхронизации градиентов. Например, если после регистрации этого хука наблюдается ускорение обучения менее чем на 10%, это обычно означает, что allreduce не является узким местом в производительности в данном случае. Такая инструментация может быть особенно полезна, если трассировки GPU получить трудно, или анализ трассировки усложняется такими факторами, как перекрытие allreduce и вычислений, или десинхронизация между ранками.
- Пример::
-
>>> ddp_model.register_comm_hook(None, noop_hook)
Сохранение коммуникационных хуков
Состоятельный коммуникационный хук можно сохранить как часть сохранения модели, чтобы позволить возобновить обучение. Чтобы сделать хук сериализуемым, необходимо определить __setstate__ и __getstate__.
Предупреждение
__getstate__ должен исключать несериализуемые атрибуты из возвращаемого словаря.
Предупреждение
__setstate__ должен правильно инициализировать несериализуемые атрибуты, исключённые из предоставленного state.
PowerSGDState имеет реализованные __setstate__ и __getstate__ и может использоваться в качестве справки.
- classtorch.distributed.algorithms.ddp_comm_hooks.powerSGD_hook.PowerSGDState[source]
-
-
__getstate__()[source] -
Возвращает
Dict[str, Any], который будет сериализован и сохранён.process_groupне сериализуем и исключается из возвращаемого состояния.
-
__setstate__(state)[source] -
Принимает предоставленный
stateи извлекаетPowerSGDState.process_groupустанавливается по умолчанию.
-
Вот простой пример «от начала до конца» сохранения и перезагрузки состояния PowerSGD и хука.
import os
import sys
import tempfile
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel
from torch.distributed.algorithms.ddp_comm_hooks import powerSGD_hook as powerSGD
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(24,24)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(24,12)
def forward(self, x):
return self.fc2(self.relu(self.fc1(x)))
def setup(rank, world_size):
os.environ['MASTER_ADDR'] = 'localhost'
os.environ['MASTER_PORT'] = '12355'
# initialize the process group
dist.init_process_group("nccl", rank=rank, world_size=world_size)
def cleanup():
dist.destroy_process_group()
def run_demo(demo_fn, world_size):
mp.spawn(
demo_fn,
args=(world_size,),
nprocs=world_size,
join=True)
def demo_serialization(rank, world_size):
setup(rank, world_size)
CHECKPOINT = tempfile.gettempdir() + "/checkpoint.pt"
model = SimpleModel().to(rank)
ddp_model = DistributedDataParallel(model, device_ids=[rank])
powersgd_hook = powerSGD.powerSGD_hook
powersgd_state = powerSGD.PowerSGDState(process_group=None)
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
ddp_model.register_comm_hook(powersgd_state, powersgd_hook)
state = {
'state_dict': ddp_model.state_dict(),
'comm_hook': powersgd_hook,
'comm_hook_state': powersgd_state}
if rank == 0:
torch.save(state, CHECKPOINT)
dist.barrier()
map_location = {'cuda:%d' % 0: 'cuda:%d' % rank}
checkpoint = torch.load(CHECKPOINT, map_location=map_location)
new_ddp_model = DistributedDataParallel(SimpleModel().to(rank), device_ids=[rank])
new_ddp_model.load_state_dict(checkpoint['state_dict'])
powersgd_hook = checkpoint['comm_hook']
powersgd_state = checkpoint['comm_hook_state']
new_ddp_model.register_comm_hook(powersgd_state, powersgd_hook)
if rank == 0:
os.remove(CHECKPOINT)
cleanup()
if __name__ == "__main__":
n_gpus = torch.cuda.device_count()
assert n_gpus >= 2, f"Requires at least 2 GPUs to run, but got {n_gpus}"
world_size = n_gpus
run_demo(demo_serialization, world_size)
Благодарности
Большое спасибо автору статьи PowerSGD, Thijs Vogels, за рецензирование кода хука PowerSGD, а также за эксперименты сравнения, показавшие, что производительность PowerSGD коммуникационного хука соответствует реализации в оригинальной статье.
© 2024, PyTorch Contributors
PyTorch has a BSD-style license, as found in the LICENSE file.
https://pytorch.org/docs/2.1/ddp_comm_hooks.html