0x00 сводка
Эта статья основана на официальной документации PyTorch.py torch.org/tutorials/i…Основываясь на этом, я представил, как писать распределённые, и добавил своё собственное понимание.
Распределенный пакет PyTorch (т.torch.distributed
) позволяет исследователям и практикам легко распараллеливать вычисления между процессами и кластерами машин. Он использует семантику передачи сообщений, чтобы позволить каждому процессу обмениваться данными с любым другим процессом. с многопроцессорностью (torch.multiprocessing
) вместо этого, процессы могут использовать разные коммуникационные серверные части и не ограничиваются выполнением на одном компьютере.
В этом коротком руководстве мы познакомимся с распределенным пакетом PyTorch. Мы увидим, как настроить дистрибутив, использовать различные коммуникационные стратегии и понять некоторые внутренности пакета.
Другие статьи из этой серии:
Автоматическая дифференциация инструментов глубокого обучения (1)
Автоматическая дифференциация инструментов глубокого обучения (2)
Автоматическая дифференциация оружия глубокого обучения (3) --- Пример интерпретации
[Анализ исходного кода] Как PyTorch реализует прямое распространение (1) --- Базовый класс (1)
[Анализ исходного кода] Как PyTorch реализует прямое распространение (2) --- Базовый класс (ниже)
[Анализ исходного кода] Как PyTorch реализует прямое распространение (3) --- конкретная реализация
[Анализ исходного кода] Как Pytorch реализует обратное распространение (1) ---- вызов движка
[Анализ исходного кода] Как PyTorch реализует обратное распространение (4) ---- конкретный алгоритм
[Анализ исходного кода] Распространение PyTorch (1) ------ история и обзор
[Анализ исходного кода] Как PyTorch использует GPU
[Анализ исходного кода] Распределенный PyTorch (2) ----- DataParallel (включен)
[Анализ исходного кода] Распределенный PyTorch (3) ----- DataParallel (ниже)
0x01 Основная концепция
Мы начнем с введения некоторых ключевых понятий в torch.distributed, которые имеют решающее значение при написании программ.
-
Node
- Физический экземпляр или контейнер. -
Worker
- Распределить работников в учебной среде. -
Группа: подмножество всех наших процессов, используемое для коллективного общения и т. д.
- По умолчанию существует только одна группа, а задание — это группа, то есть мир.
- Когда требуется более детальная коммуникация, можно создать новую группу, используя подмножество мира через интерфейс new_group.
-
Серверная часть: Библиотека связи процессов. PyTorch поддерживает NCCL, GLOO, MPI.
-
World_size : количество процессов в группе процессов, которое можно рассматривать как количество глобальных процессов.
-
Ранг: уникальный идентификатор, назначаемый каждому процессу в группе распределенных процессов.
- Непрерывное целое число от 0 до world_size можно понимать как номер программы, который используется для межпроцессного взаимодействия.
- Хост с рангом = 0 является главным узлом.
- Набор рангов можно рассматривать как глобальный список ресурсов графического процессора.
-
локальный ранг: номер графического процессора в процессе, неявный параметр, который обычно указывается внутри torch.distributed.launch. Например, rank = 3, local_rank = 0 означает 1-й GPU в 3-м процессе.
0x02 Идеи дизайна
Основная проблема распределенного обучения заключается в том, как общаться между работниками. Чтобы решить проблему связи, PyTorch вводит несколько концепций: сначала мы анализируем требования к коммуникации, а затем смотрим, как PyTorch удовлетворяет требованиям с помощью этих концепций.
2.1 Требования к связи
Резюмируем конкретные требования распределенного обучения:
- Как работники узнают друг друга?
- Как наладить одноранговую связь между работниками?
- Как сделать коллективное общение между работниками?
- Как связать тренировочный процесс с ансамблевым общением?
Далее мы проанализируем эти вопросы и содержание документа.
2.2 Концепция
Для требований к связи PyTorch предоставляет несколько концепций: группа процессов, серверная часть, инициализация, хранилище.
-
группа процессов: DDP — это настоящее распределенное обучение, которое может использовать несколько машин для формирования задачи параллельной работы. Чтобы обеспечить связь между различными рабочими DDP, PyTorch устанавливает концепцию групп процессов. Группа — это подмножество всех наших процессов.
- Посмотрите на его суть, это процессы, которые общаются.
- С точки зрения кода создайте поток связи для каждого процесса обучения и общайтесь в фоновом режиме. Например, для ProcessGroupMPI к коммуникационному потоку добавляется очередь для буферной и асинхронной обработки.
-
задняя часть: Backend — это логичное понятие.
- По сути, серверная часть представляет собой механизм связи IPC. Поскольку PyTorch может обмениваться данными между различными процессами, он должен полагаться на некоторые механизмы связи IPC.Эти механизмы связи обычно реализуются тремя сторонами, отличными от PyTorch, такими как ProcessGroupMPI или ProcessGroupGloo в серверной части.
- Бэкенды позволяют процессам взаимодействовать друг с другом, делясь своим местоположением. Для пользователей это то, какой метод использовать для коллективного общения, а с точки зрения кода — за каким процессом (рядом процессов) следовать...
-
инициализация: Хотя у нас есть концепция бэкенда и группы процессов, как сделать так, чтобы рабочие обнаруживали друг друга перед созданием группы процессов? Для этого требуется метод инициализации, сообщающий всем о передаче сообщения: как связаться с процессом на других машинах. В настоящее время модуль DDP поддерживает 3 метода инициализации.
-
Store: Распределенный пакет имеет распределенную службу хранилища ключей и значений, которая обменивается информацией между процессами в группе и инициализирует распределенный пакет (путем явного создания хранилища как
init_method
заменять). -
Инициализировать или хранить:
- init_method бесполезен, когда MPI является серверной частью.
- В не-MPI бэкенде, если нет параметра store, то store строится с помощью init_method, так что в итоге он падает на store.
Для этих концепций мы используем следующую диаграмму, чтобы увидеть, как DDP использует эти концепции.
Предположим, что DDP состоит из двух работников для обучения, где каждый работник будет:
- Выполняйте обучение в основном потоке и выполняйте все сокращения в редюсере, в частности, отправляя инструкции в workerThread_ из ProcessGroupMPI.
- workerThread_ будет вызывать MPI_Allreduce для коллективной связи, используя бэкенд MPI.
0x03 настройки
Во-первых, нам нужно иметь возможность запускать несколько процессов одновременно. Если у вас есть доступ к вычислительному кластеру, вам следует проконсультироваться с локальным системным администратором или использовать свой любимый инструмент координации (например,pdsh,clustershellилиразное). В этой статье мы будем использовать следующий шаблон для разветвления нескольких процессов на одном компьютере.
"""run.py:"""
#!/usr/bin/env python
import os
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
def run(rank, size):
""" Distributed function to be implemented later. """
pass
def init_process(rank, size, fn, backend='gloo'):
""" Initialize the distributed environment. """
os.environ['MASTER_ADDR'] = '127.0.0.1'
os.environ['MASTER_PORT'] = '29500'
dist.init_process_group(backend, rank=rank, world_size=size)
fn(rank, size)
if __name__ == "__main__":
size = 2
processes = []
mp.set_start_method("spawn")
for rank in range(size):
p = mp.Process(target=init_process, args=(rank, size, run))
p.start()
processes.append(p)
for p in processes:
p.join()
Приведенный выше сценарий порождает два процесса, каждый из которых настраивает распределенную среду и инициализирует группу процессов (dist.init_process_group
) и, наконец, выполнить заданныйrun
функция.
0x04 двухточечная связь
Ниже приведена принципиальная схема двухточечной связи: отправка и получение.
Передача данных от одного процесса к другому называется двухточечной связью. Это черезsend
иrecv
функция илиisend
иirecv
быть реализованным.
"""Blocking point-to-point communication."""
def run(rank, size):
tensor = torch.zeros(1)
if rank == 0:
tensor += 1
# Send the tensor to process 1
dist.send(tensor=tensor, dst=1)
else:
# Receive tensor from process 0
dist.recv(tensor=tensor, src=0)
print('Rank ', rank, ' has data ', tensor[0])
В приведенном выше примере оба процесса начинаются с нулевого тензора, затем процесс 0 увеличивает тензор и отправляет его процессу 1, так что они оба заканчиваются 1,0. Обратите внимание, что процессу 1 необходимо выделить память для хранения данных, которые он получит.
Также обратите вниманиеsend
/recv
даблокироватьРеализация: оба процесса останавливаются до тех пор, пока связь не будет завершена. с другой стороны,isend
иirecv
данеблокирующий, в неблокирующем случае скрипт продолжает выполняться, метод возвращаетWork
объект, на котором мы можем выбратьwait()
.
"""Non-blocking point-to-point communication."""
def run(rank, size):
tensor = torch.zeros(1)
req = None
if rank == 0:
tensor += 1
# Send the tensor to process 1
req = dist.isend(tensor=tensor, dst=1)
print('Rank 0 started sending')
else:
# Receive tensor from process 0
req = dist.irecv(tensor=tensor, src=0)
print('Rank 1 started receiving')
req.wait()
print('Rank ', rank, ' has data ', tensor[0])
использоватьisend
иirecv
, мы должны использовать его с осторожностью. Поскольку мы не знаем, когда данные будут доставлены другим процессам, мы не должныreq.wait()
Измените отправленный тензор или получите доступ к полученному тензору до завершения. другими словами,
-
существует
dist.isend()
писать послеtensor
, приведет к неопределенному поведению. -
существует
dist.irecv()
читать послеtensor
, приведет к неопределенному поведению.
Но когдаreq.wait()
После выполнения мы можем гарантировать, что коммуникация произошла и сохраненныеtensor[0]
Значение равно 1,0.
0x05 коллективная связь
Ниже приведена схема коллективного общения.
Scatter | Gather |
---|---|
Reduce | All-Reduce |
Broadcast | All-Gather |
В отличие от связи точка-точка, коллекция позволяетв группеРежим, в котором взаимодействуют все процессы. Группа — это подмножество всех наших процессов. Чтобы создать группу, мы можем передать список рангов вdist.new_group(group)
. По умолчанию коллективная коммуникация выполняется для всех процессов, «всех процессов», также известных какworld. Например, чтобы получить сумму всех тензоров во всех процессах, мы можем использоватьdist.all_reduce(tensor, op, group)
.
""" All-Reduce example."""
def run(rank, size):
""" Simple collective communication. """
group = dist.new_group([0, 1])
tensor = torch.ones(1)
dist.all_reduce(tensor, op=dist.ReduceOp.SUM, group=group)
print('Rank ', rank, ' has data ', tensor[0])
Поскольку нам нужна сумма всех тензоров в группе, мы делаем этоdist.ReduceOp.SUM
Используется как оператор редукции. В общем случае в качестве оператора можно использовать любую коммутативную математическую операцию. PyTorch поставляется с 4 такими операторами из коробки, и все они работают на уровне элемента:
-
dist.ReduceOp.SUM
, -
dist.ReduceOp.PRODUCT
, -
dist.ReduceOp.MAX
, -
dist.ReduceOp.MIN
.
Кромеdist.all_reduce(tensor, op, group)
Кроме того, в настоящее время в PyTorch реализованы следующие операции над множествами.
-
dist.broadcast(tensor, src, group)
:отsrc
копироватьtensor
ко всем другим процессам. -
dist.reduce(tensor, dst, op, group)
: применятьop
всеtensor
, и сохранить результат вdst
. -
dist.all_reduce(tensor, op, group)
: То же, что и операция сокращения, но результат сохраняется во всех процессах. -
dist.scatter(tensor, scatter_list, src, group)
: скопировать список тензоровscatter_list[i]
Бтензоры кпроцесс. -
dist.gather(tensor, gather_list, dst, group)
: копировать из всех процессовtensor
прибытьdst
. -
dist.all_gather(tensor_list, tensor, group)
: Поверх всех процессов выполнить копирование из всех процессовtensor
прибытьtensor_list
операция. -
dist.barrier(group)
: блокирует все процессы в группе до тех пор, пока каждый процесс не войдет в функцию.
0x06 Распределенное обучение
**ПРИМЕЧАНИЕ.** Вы можетев этом репозитории GitHubНайдите примеры сценариев в этом разделе.
Теперь, когда мы поняли, как работают распределенные модули, давайте напишем с их помощью что-нибудь полезное. Наша цель — повторитьDistributedDataParallelфункция. Конечно, это будет учебный пример, в реальной ситуации вы должны использовать полностью протестированную и оптимизированную официальную версию, указанную выше.
Мы хотим реализовать распределенную версию стохастического градиентного спуска. В нашем сценарии все процессы будут вычислять градиенты локальной модели на пакете данных, которые у них есть локально, а затем усреднять их градиенты. Чтобы обеспечить аналогичные результаты сходимости при изменении количества процессов, мы сначала должны разбить наш набор данных (вы также можете использоватьtnt.dataset.SplitDataset, вместо фрагмента ниже).
""" Dataset partitioning helper """
class Partition(object):
def __init__(self, data, index):
self.data = data
self.index = index
def __len__(self):
return len(self.index)
def __getitem__(self, index):
data_idx = self.index[index]
return self.data[data_idx]
class DataPartitioner(object):
def __init__(self, data, sizes=[0.7, 0.2, 0.1], seed=1234):
self.data = data
self.partitions = []
rng = Random()
rng.seed(seed)
data_len = len(data)
indexes = [x for x in range(0, data_len)]
rng.shuffle(indexes)
for frac in sizes:
part_len = int(frac * data_len)
self.partitions.append(indexes[0:part_len])
indexes = indexes[part_len:]
def use(self, partition):
return Partition(self.data, self.partitions[partition])
Используя приведенный выше фрагмент кода, теперь мы можем просто разделить любой набор данных, используя следующие строки:
""" Partitioning MNIST """
def partition_dataset():
dataset = datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
size = dist.get_world_size()
bsz = 128 / float(size)
partition_sizes = [1.0 / size for _ in range(size)]
partition = DataPartitioner(dataset, partition_sizes)
partition = partition.use(dist.get_rank())
train_set = torch.utils.data.DataLoader(partition,
batch_size=bsz,
shuffle=True)
return train_set, bsz
Предполагая, что у нас есть 2 реплики, каждый процесс имеетtrain_set
будет включать 60000 / 2 = 30000 образцов. Мы также делим размер партии на количество сохраняемых реплик.общийРазмер партии 128 штук.
Теперь мы можем написать обычный обучающий код с оптимизацией вперед-назад и добавить вызов функции для усреднения градиентов нашей модели (следующее во многом зависит отPyTorch MNISTофициальныйпримервдохновленный).
""" Distributed Synchronous SGD Example """
def run(rank, size):
torch.manual_seed(1234)
train_set, bsz = partition_dataset()
model = Net()
optimizer = optim.SGD(model.parameters(),
lr=0.01, momentum=0.5)
num_batches = ceil(len(train_set.dataset) / float(bsz))
for epoch in range(10):
epoch_loss = 0.0
for data, target in train_set:
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
epoch_loss += loss.item()
loss.backward()
average_gradients(model)
optimizer.step()
print('Rank ', dist.get_rank(), ', epoch ',
epoch, ': ', epoch_loss / num_batches)
еще нужно реализоватьaverage_gradients(model)
функция, которая просто берет модель и усредняет ее градиенты по всему миру (все процессы обучения).
""" Gradient averaging. """
def average_gradients(model):
size = float(dist.get_world_size())
for param in model.parameters():
dist.all_reduce(param.grad.data, op=dist.ReduceOp.SUM)
param.grad.data /= size
Теперь мы успешно внедрили распределенный синхронный SGD и можем обучать любую модель на большом компьютерном кластере.
**ПРИМЕЧАНИЕ.** Хотя последнее предложениетехническиправильно, но для реализации синхронного SGD на рабочем уровне требуетсябольше советов. использовать сноваПротестированный и оптимизированный контент.
0x07 Ring-Allreduce
В качестве дополнительной задачи предположим, что мы хотим реализовать эффективное кольцо allreduce для DeepSpeech. Этого легко добиться с помощью одноранговых коллекций.
""" Implementation of a ring-reduce with addition. """
def allreduce(send, recv):
rank = dist.get_rank()
size = dist.get_world_size()
send_buff = send.clone()
recv_buff = send.clone()
accum = send.clone()
left = ((rank - 1) + size) % size
right = (rank + 1) % size
for i in range(size - 1):
if i % 2 == 0:
# Send send_buff
send_req = dist.isend(send_buff, right)
dist.recv(recv_buff, left)
accum[:] += recv_buff[:]
else:
# Send recv_buff
send_req = dist.isend(recv_buff, right)
dist.recv(send_buff, left)
accum[:] += send_buff[:]
send_req.wait()
recv[:] = accum[:]
В приведенном выше сценарииallreduce(send, recv)
Сигнатура функции немного отличается от сигнатуры функции в PyTorch. он принимаетrecv
тензор и всеsend
В нем хранится сумма тензоров. В качестве упражнения для читателя: между нашей версией и версией в DeepSpeech все же есть одно отличие: их реализация разбивает тензор градиента накусок, для оптимального использования полосы пропускания связи (подсказка:torch.chunk).
0x08 Дополнительные темы
Так как есть много, чтобы охватить, этот раздел разделен на два подраздела:
- Коммуникационная серверная часть: мы узнаем, как использовать MPI и Gloo для связи GPU-GPU.
- Метод инициализации: мы понимаем, что в
dist.init_process_group()
Как установить начальную фазу координации.
8.1 Коммуникационный сервер
torch.distributed
Одним из самых элегантных аспектов является его способность абстрагироваться и строиться на основе различных бэкендов. Как уже упоминалось, в настоящее время в PyTorch реализованы три бэкенда: Gloo, NCCL и MPI. Каждый из них имеет разные спецификации и компромиссы, в зависимости от желаемого варианта использования. Доступны наздесьНайдите сравнительную таблицу поддерживаемых функций.
Следующая информация взята изpy torch.org/docs/stable…
8.1.1 Типы серверных частей
torch.distributed
Поддерживаются три встроенных бэкенда, каждый с разными возможностями. В следующей таблице показано, какие функции доступны для тензоров CPU/CUDA.
Backend | gloo |
mpi |
nccl |
|||
---|---|---|---|---|---|---|
Device | CPU | GPU | CPU | GPU | CPU | GPU |
send | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
recv | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
broadcast | ✓ | ✓ | ✓ | ? | ✘ | ✓ |
all_reduce | ✓ | ✓ | ✓ | ? | ✘ | ✓ |
reduce | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
all_gather | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
gather | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
scatter | ✓ | ✘ | ✓ | ? | ✘ | ✘ |
reduce_scatter | ✘ | ✘ | ✘ | ✘ | ✘ | ✓ |
all_to_all | ✘ | ✘ | ✓ | ? | ✘ | ✓ |
barrier | ✓ | ✘ | ✓ | ? | ✘ | ✓ |
Дистрибутив PyTorch поддерживает Linux (стабильная версия), MacOS (стабильная версия) и Windows (прототип). Для Linux серверные части Gloo и NCCL включены в распространяемый PyTorch по умолчанию (NCCL поддерживается только при сборке с помощью CUDA). MPI — это необязательный бэкенд, который можно включать только при сборке PyTorch из исходного кода (например, при компиляции PyTorch на хосте, где установлен MPI).
8.1.2 Какой сервер использовать?
В прошлом люди часто спрашивали: «Какой бэкенд мне использовать»? Вот ответ:
- Практическое правило
- Распространяется с серверной частью NCCLGPUтренироваться
- Распространяется с серверной частью GlooCPUтренироваться.
- Если хост GPU имеет межсоединение InfiniBand
- Используйте NCCL, так как в настоящее время это единственный сервер, поддерживающий InfiniBand и GPUDirect.
- Если хост GPU имеет межсоединение Ethernet
- Используйте NCCL, так как в настоящее время он обеспечивает наилучшую производительность распределенного обучения GPU, особенно для распределенного обучения с несколькими процессами на одном или нескольких узлах. Если у вас возникли проблемы с использованием NCCL, используйте Gloo в качестве запасного варианта. (Обратите внимание, что Gloo в настоящее время работает медленнее, чем NCCL для обучения GPU.)
- Хост ЦП с межсоединением InfiniBand
- Если на вашем InfiniBand включен IP over IB, используйте Gloo, в противном случае вместо этого используйте MPI. Мы планируем добавить поддержку InfiniBand для Gloo в следующем выпуске.
- Хост ЦП с соединением Ethernet
- Используйте Gloo, если у вас нет особой причины использовать MPI.
8.1.3 Серверная часть Gloo
уже,глухая серверная частьшироко используется. Это очень удобная платформа для разработки, поскольку она включена в предварительно скомпилированные двоичные файлы PyTorch и доступна для Linux (начиная с 0.2) и macOS (начиная с 1.3). Он поддерживает все одноранговые и коллективные операции на ЦП и все коллективные операции на ГП. Но его реализация для операций с наборами тензоров CUDA не так хорошо оптимизирована, как бэкэнд NCCL.
Вы, должно быть, заметили, что наш пример распределенного SGD не будет работать, если ваша модель использует GPU. Чтобы использовать несколько графических процессоров, мы также вносим следующие изменения:
device = torch.device("cuda:{}".format(rank))
-
model = Net()
model = Net().to(device)
data, target = data.to(device), target.to(device)
С указанными выше изменениями наша модель теперь может быть обучена на двух графических процессорах, и вы можете использовать файлы .watch nvidia-smi
для контроля использования.
8.1.4 Серверная часть MPI
Интерфейс передачи сообщений (MPI) — это стандартизированный инструмент из мира высокопроизводительных вычислений. Он позволяет осуществлять одноранговую и коллективную коммуникацию.torch.distributed
главный источник вдохновения. Сегодня существуют различные реализации MPI (например,Open-MPI,MVAPICH2,Intel MPI), каждый из которых оптимизирован для разных целей. Преимущество использования серверной части MPI заключается в широкой доступности MPI и высокой оптимизации на больших компьютерных кластерах.недавнийНемного выполнитьОн также использует преимущества технологии CUDA IPC и GPU Direct, что позволяет избежать копирования памяти через ЦП.
К сожалению, двоичные файлы PyTorch не могут содержать реализацию MPI, и нам приходится вручную перекомпилировать ее. К счастью, этот процесс довольно прост, потому что во время компиляции PyTorchсамостоятельноНайдите доступные реализации MPI. Проходят следующие этапыиз источникаУстановите ПиТорчПриходитьУстановите серверную часть MPI.
- Создайте и активируйте свою среду Anaconda в соответствии сthe guideУстановите все следующие предварительные требования, ноНетбегать
python setup.py install
. - Выберите и установите свою любимую реализацию MPI. Обратите внимание, что включение MPI с поддержкой CUDA может потребовать некоторых дополнительных действий. В нашем примере мы будем использоватьнетOpen-MPI с поддержкой графического процессора:
conda install -c conda-forge openmpi
. - Теперь перейдите в свой клонированный репозиторий PyTorch и выполните .
python setup.py install
.
Чтобы протестировать наш недавно установленный бэкэнд, необходимы некоторые модификации.
- Пучок
if __name__ == '__main__':
заменитьinit_process(0, 0, run, backend='mpi')
- бегать
mpirun -n 4 python myscript.py
Причина этих изменений в том, что MPI необходимо создать свою собственную среду перед запуском процесса. MPI также создаст собственный процесс и выполнит его.в методе инициализацииописывает операцию рукопожатия, тем самым позволяяinit_process_group
изrank
иsize
параметры становятся избыточными. На самом деле это очень мощно, потому что вы можете передавать дополнительные параметры вmpirun
Адаптация вычислительных ресурсов для каждого процесса (например, количество ядер для каждого процесса, ручное присвоение машинам определенных рангови т.д). Делая это, вы должны получить тот же знакомый результат, что и другие коммуникационные серверы.
8.1.5 Серверная часть NCCL
ДолженСерверная часть NCCLПредоставляет оптимизированный набор операций, реализованных на тензорах CUDA. Если вы используете тензоры CUDA только для операций сбора, рассмотрите возможность использования этого бэкенда для достижения наилучшей производительности. Серверная часть NCCL включена в готовые двоичные файлы с поддержкой CUDA.
Полное название NCCLКонвергентная коммуникационная библиотека Nvidia(NVIDIA Collective Communications Library) — это библиотека, которая может реализовать агрегированную связь между несколькими графическими процессорами и несколькими узлами и может обеспечить высокую скорость связи на PCIe, Nvlink и InfiniBand.
NCCL высокооптимизирован и совместим с MPI, а также может определять топологию графического процессора, обеспечивать ускорение работы нескольких графических процессоров на нескольких узлах и максимально использовать полосу пропускания в графическом процессоре, поэтому исследователи, работающие в рамках глубокого обучения, могут воспользоваться преимуществами NCCL на нескольких узлах.Все доступные графические процессоры могут быть полностью использованы внутри страны или за ее пределами.
NCCL хорошо поддерживает как ЦП, так и ГП, а torch.distributed также предоставляет для него встроенную поддержку.
В ситуациях, когда на одном хосте используется несколько процессов, можно использовать NCCL, чтобы максимизировать производительность. Внутри каждого процесса не разрешается иметь исключительные права на используемые им графические процессоры. Взаимоблокировки могут возникать, если GPU совместно используются процессами.
8.2 Метод инициализации
Чтобы закончить этот урок, давайте поговорим о первой функции, которую мы вызываем.dist.init_process_group(backend, init_method)
. Мы представим различные методы инициализации, отвечающие за начальный этап координации между каждым процессом. Эти методы позволяют определить, как осуществляется эта координация. В зависимости от настройки вашего оборудования один из этих методов, естественно, должен быть более подходящим, чем другой. В дополнение к следующим разделам вам также следует просмотретьофициальная документация.
переменная среды
На протяжении всего этого руководства мы использовали метод инициализации переменных среды. Этот метод считывает конфигурацию из переменных среды, позволяя полностью настроить способ получения информации. Установив следующие четыре переменные среды на всех машинах, все процессы могут нормально подключаться к главному (то есть процессу ранга 0), получать информацию о других процессах и, наконец, обмениваться с ними рукопожатием.
-
MASTER_PORT
: свободный порт на машине, на которой размещен процесс ранга 0. -
MASTER_ADDR
: IP-адрес машины, на которой размещен процесс ранга 0. -
WORLD_SIZE
: общее количество процессов, чтобы мастер знал, сколько рабочих ждать. -
RANK
: ранг каждого процесса, чтобы они знали, являются ли они мастером.
общая файловая система
Общая файловая система требует, чтобы все процессы имели доступ к общей файловой системе и координировали их через общие файлы. Это означает, что каждый процесс откроет файл, запишет в него информацию и будет ждать, пока все это сделают. После этого вся необходимая информация будет доступна всем процессам. Чтобы избежать состояния гонки, файловая система должна пройтиfcntlБлокировка поддерживается.
dist.init_process_group(
init_method='file:///mnt/nfs/sharedfile',
rank=args.rank,
world_size=4)
TCP
Метод инициализации TCP реализуется путем предоставления IP-адреса и порта процесса ранга 0, где все рабочие процессы могут подключаться к процессу ранга 0 и обмениваться информацией о том, как связаться друг с другом.
dist.init_process_group(
init_method='tcp://10.1.1.20:23456',
rank=args.rank,
world_size=4)
0xEE Личная информация
★★★★★★Думая о жизни и технологиях★★★★★★
Публичный аккаунт WeChat:мысли Росси