Сначала изучите топологию
Связи между GPU определяют, какой вид параллелизма обойдётся дешевле. Проверяйте их, прежде чем выбирать:
nvidia-smi topo -m # матрица соединений между GPU, NIC и CPU
nvidia-smi topo -p2p r # какие пары GPU поддерживают P2P-чтение
amd-smi topology # AMD MI355X
| Обозначение | Путь между двумя GPU |
|---|---|
NV# | NVLink с # объединёнными линиями; на платах HGX — через NVSwitch |
PIX | Не более одного переключателя PCIe |
PXB | Несколько переключателей PCIe без пересечения хост-моста CPU |
PHB | Хост-мост PCIe, что обычно означает связь через CPU |
NODE | PCIe плюс соединение между хост-мостами внутри одного узла NUMA |
SYS | PCIe плюс связь между процессорными сокетами: самый медленный путь |
Столбцы CPU Affinity и NUMA Affinity показывают, какие ядра ближе всего к каждому GPU. Закрепляйте на этих ядрах задачи загрузки данных, если узким местом становится CPU.
Запуск задач на 2, 4 или 8 GPU с torchrun
torchrun запускает один процесс на каждый GPU и присваивает каждому RANK, LOCAL_RANK и WORLD_SIZE. Перед длительной задачей проверьте настройку коротким скриптом:
# ddp_check.py
import os
import torch
import torch.distributed as dist
local_rank = int(os.environ["LOCAL_RANK"])
torch.cuda.set_device(local_rank)
dist.init_process_group("nccl")
x = torch.ones(1, device="cuda") * dist.get_rank()
dist.all_reduce(x) # суммирует ранги процессов по всем GPU
print(f"rank {dist.get_rank()} of {dist.get_world_size()}: sum = {x.item():.0f}")
dist.destroy_process_group()
torchrun --standalone --nproc-per-node=gpu ddp_check.py # один процесс на каждый видимый GPU
torchrun --standalone --nproc-per-node=4 train.py # четыре процесса
CUDA_VISIBLE_DEVICES=4,5,6,7 torchrun --standalone --nproc-per-node=4 train.py # только GPU с 4 по 7
Каждый ранг должен вывести одну и ту же сумму, 0 + 1 + … + (n − 1): 28 для восьми GPU. CUDA_VISIBLE_DEVICES позволяет двум независимым задачам делить один сервер, каждой — на своих GPU. torchrun выставляет OMP_NUM_THREADS=1 для каждого процесса, если вы не задали это значение сами; увеличьте его, если CPU-часть внутри процессов обучения работает медленно.
Главное о NCCL
NCCL — это библиотека, стоящая за бэкендом nccl в PyTorch. Она сама определяет топологию и выбирает NVLink, PCIe P2P, общую память или сеть, и её настройки по умолчанию подходят для большинства задач. Эти переменные показывают и помогают отладить то, что она выбрала:
| Переменная | Назначение |
|---|---|
NCCL_DEBUG=INFO | Выводит версию NCCL, обнаруженную топологию и транспорт, используемый между GPU (строки вида via P2P или via SHM). Для обычных запусков используйте WARN. |
NCCL_DEBUG_SUBSYS=INIT,GRAPH | Ограничивает вывод INFO инициализацией и решениями по топологии. |
NCCL_DEBUG_FILE=/tmp/nccl.%h.%p.log | Записывает журнал в отдельный файл для каждого процесса (%h — хост, %p — ID процесса). |
NCCL_P2P_DISABLE=1 | Отключает прямую передачу данных между GPU. Диагностический инструмент для зависаний, а не настройка для постоянного использования. |
NCCL_P2P_LEVEL=PXB | Разрешает P2P только до заданной дистанции: LOC, NVL, PIX, PXB, PHB или SYS. |
NCCL_SHM_DISABLE=1 | Отключает транспорт через общую память (только для отладки). |
NCCL_TOPO_DUMP_FILE=/tmp/topo.xml | Сохраняет топологию, обнаруженную NCCL. |
NCCL_SOCKET_IFNAME, NCCL_IB_DISABLE | Настройки сети. Важны только для задач, охватывающих несколько серверов. |
NCCL_DEBUG=INFO torchrun --standalone --nproc-per-node=gpu ddp_check.py 2>&1 | grep -E 'via (P2P|SHM|NET)' | head
NCCL описывает NCCL_ALGO, NCCL_PROTO и похожие переменные тонкой настройки как инструменты отладки. Не оставляйте их в продакшен-скриптах: они могут привести к снижению производительности или зависаниям.
Измерьте пропускную способность шины с nccl-tests
nccl-tests измеряет реальную пропускную способность интерконнекта. Для него нужны CUDA toolkit и файлы разработки NCCL из репозитория NVIDIA (см. Драйверы NVIDIA и CUDA); в контейнере NGC PyTorch обе эти вещи уже есть.
sudo apt install -y libnccl2 libnccl-dev
git clone https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests
make -j # CUDA_HOME по умолчанию указывает на /usr/local/cuda
./build/all_reduce_perf -b 8 -e 2G -f 2 -g 8 # -g: количество GPU в сервере
-b и -e задают наименьший и наибольший размер сообщения, -f — множитель между размерами, а -g — количество GPU. Каждая строка вывода соответствует одному размеру сообщения:
- algbw: объём данных, делённый на время.
- busbw: algbw с поправкой на количество GPU (для all-reduce — × 2(n − 1)/n). Отражает скорость самого медленного участка пути (NVLink, PCIe или связь между процессорными сокетами), и именно эту величину нужно сравнивать между серверами.
#wrongдолжен оставаться равным 0. Запуск завершается строкой Avg bus bandwidth.
Смотрите строки с крупными сообщениями. Записывайте результат, когда сервер работает исправно, чтобы иметь ориентир на случай, если позже производительность изменится.
Карты с PCIe и платформы с NVLink
- 8-GPU-серверы H100, H200, B200 и B300 (HGX): каждый GPU напрямую связан с любым другим через NVSwitch на полной скорости NVLink — 900 ГБ/с на GPU у Hopper и 1,8 ТБ/с у Blackwell. Тензорный параллелизм на всех восьми GPU работает хорошо. Fabric Manager должен быть запущен (см. Драйверы NVIDIA и CUDA). На серверах этих моделей с 2 и 4 GPU проверьте
nvidia-smi topo -m, чтобы увидеть, как связаны GPU. - MI355X: GPU связаны через AMD Infinity Fabric, которую использует RCCL (см. ниже).
- RTX PRO 6000 и L40S: только PCIe (Gen5 и Gen4). Возможность обмена данными между GPU по P2P зависит от карты и платформы; проверьте с помощью
nvidia-smi topo -p2p r. - GeForce RTX 5090, 4090 и 5080: только PCIe, без NVLink, и P2P обычно недоступен, поэтому NCCL передаёт данные через память хоста. Это работает, но обмен данными обходится дороже.
На серверах с PCIe отдавайте предпочтение параллелизму данных (DDP или FSDP) с достаточным объёмом работы на шаг и используйте накопление градиентов, чтобы обмениваться данными реже. Тензорный параллелизм обменивается данными на каждом слое и больше всего выигрывает от NVLink.
Если задача зависает на первой коллективной операции на сервере с PCIe, запустите её один раз с NCCL_P2P_DISABLE=1. Если это помогает, проблема именно в пути P2P: сообщите об этом через форму обратной связи.
Модели крупнее: FSDP и DeepSpeed
- DDP (DistributedDataParallel): каждый GPU хранит полную копию модели и состояния оптимизатора. Проще и быстрее всего, когда всё помещается в память одного GPU.
- FSDP2 (
torch.distributed.fsdp.fully_shard): шардирует параметры, градиенты и состояние оптимизатора между GPU, поэтому модель должна помещаться только в их суммарную память. Запускается через torchrun, как и DDP; см. руководство по FSDP2 от PyTorch. - DeepSpeed ZeRO: стадии с 1 по 3 шардируют сначала состояние оптимизатора, затем градиенты, затем параметры, с возможной разгрузкой в память CPU или на NVMe.
pip install deepspeed; см. Начало работы. - Тензорный и конвейерный параллелизм (например, в стиле Megatron): разбивает слои или группы слоёв между GPU. Используется для самых больших моделей; тензорный параллелизм требует быстрого интерконнекта.
Hugging Face Accelerate и Transformers Trainer оборачивают DDP, FSDP и DeepSpeed конфигурационным файлом. Если вам нужен инференс, а не обучение, см. Инференс-сервер.
RCCL на AMD MI355X
На MI355X тот же код работает без изменений. Бэкенд nccl в PyTorch использует RCCL — реализацию API NCCL от AMD; torchrun работает точно так же, а RCCL читает те же переменные NCCL_*, например NCCL_DEBUG=INFO. Выбирайте GPU через HIP_VISIBLE_DEVICES; CUDA_VISIBLE_DEVICES также поддерживается.
amd-smi topology # связи между GPU
git clone https://github.com/ROCm/rccl-tests.git
cd rccl-tests
make GPU_TARGETS=gfx950 # добавьте HIP_HOME=..., если ROCm не в /opt/rocm
./build/all_reduce_perf -b 8 -e 2G -f 2 -g 8
Проще всего собрать rccl-tests в контейнере rocm/pytorch, где ROCm и RCCL уже установлены. Установка и выбор GPU описаны в ROCm на MI355X.
Нужна помощь с этим руководством?
Расскажите нам о своём GPU, командах, которые вы выполнили, и результате, который получили, через форму обратной связи.
