Как вы дебажите медленную меж-GPU коммуникацию в multi-node инференсе?

Краткий тезис

Медленная меж-GPU коммуникация в multi-node инференсе — типичная проблема, вызванная топологией соединений (NVLink/InfiniBand), настройками библиотеки NCCL или неоптимальным распределением тензоров. Для дебага используется бенчмаркинг (nccl-tests), проверка физической топологии (nvidia-smi topo -m, ibstatus), логирование NCCL с NCCL_DEBUG=INFO и профилирование с помощью Nsight Systems. Ключевые рычаги — выбор протокола Ring/Tree, увеличение размера буфера (NCCL_BUFFSIZE) и настройка количества потоков (NCCL_NTHREADS).


1. Термин: меж-GPU коммуникация и её роль в multi-node инференсе

При инференсе больших языковых моделей на нескольких GPU (один узел или несколько узлов) модель разбивается на части с помощью tensor parallelism (TP) и pipeline parallelism (PP).

  • Tensor Parallelism (внутри узла): каждый слой делится между GPU, для forward pass требуется AllReduce (или ReduceScatter + AllGather) для обмена частичными суммами после каждого под-слоя.
  • Pipeline Parallelism (между узлами): разные слои находятся на разных устройствах, передача активаций — Point-to-Point (P2P) коммуникации.

Если коммуникация медленная, время одного forward шага растёт, и throughput падает. Для LLM с десятками миллиардов параметров (например, LLaMA-70B на 4 узлах × 8 GPU) накладные расходы на AllReduce могут составлять 30-50% общего времени инференса.


2. Инструменты бенчмаркинга: nccl-tests и ib_write_bw

2.1 nccl-tests — универсальный бенчмарк NCCL

nccl-tests — репозиторий с программами для измерения пропускной способности и задержки операций AllReduce, AllGather, ReduceScatter и др.

Типичные команды

# AllReduce с размером сообщения 256 MB, 8 GPU на одном узле
./build/all_reduce_perf -b 256M -e 256M -f 2 -g 8

# AllReduce между узлами (нужно запустить на каждом узле с mpirun)
mpirun -np 16 -host node1:8,node2:8 ./build/all_reduce_perf -b 256M -e 256M -f 2 -g 8

Ключевые метрики вывода:

  • algbw — алгоритмическая пропускная способность (GB/s).
  • busbw — пропускная способность шины (bus bandwidth), обычно меньше algbw из-за протокольных накладных расходов.
  • time — время операции.

Сравните busbw с теоретическим максимумом:

  • NVLink 3.0 (A100): 600 GB/s (12× 50 GB/s)
  • InfiniBand HDR (200 Gbit/s → 25 GB/s на линк) (сдвоенный — 50 GB/s)

Если busbw значительно ниже — проблема в топологии или настройках.

2.2 ib_write_bw — бенчмарк InfiniBand

Для межузловых коммуникаций (InfiniBand) проверьте пропускную способность RDMA-линков:

# На сервере
ib_write_bw -a -d mlx5_0

# На клиенте
ib_write_bw -a -d mlx5_0 192.168.1.1

Ожидаемая пропускная способность для одного порта HDR100 — 12.5 GB/s, для HDR200 — 25 GB/s. Если результаты ниже (например, 5 GB/s), возможны проблемы с драйвером, кабелями или настройками MTU.


3. Проверка топологии: nvidia-smi topo -m и ibstatus

3.1 nvidia-smi topo -m — видеть физическую связность

Выводит матрицу топологии между GPU. Пример для узла с 8× A100:

        GPU0    GPU1    …
GPU0     X      NV2
GPU1    NV2      X
…
  • NV2NVLink v2 (или NV3, NV4).
  • PHBPCIe Host Bridge (GPU на разных CPU-сокетах).
  • PXBPCIe switch (дополнительные задержки).
  • SOC — на одном чипе (для ARM).

Что проверять

  • Все ли GPU на узле соединены NVLink (в идеале — полная NVSwitch-топология).
  • Между узлами — InfiniBand, а не Ethernet (сеть с TCP накладными расходами).

Если топология показывает PHB для пары GPU, коммуникация идёт через PCIe, что медленнее NVLink. В таком случае для Tensor Parallelism стоит использовать подгруппы GPU, соединённых NVLink.

3.2 ibstatus — состояние InfiniBand

ibstatus

Показывает скорость, MTU, состояние линка. Пример вывода:

state: ACTIVE, physical state: LinkUp, rate: 200 Gb/sec (4X HDR)

Убедитесь, что линк активен и скорость совпадает с ожидаемой (200 Gb/sec = 25 GB/s).


4. Логирование NCCL: NCCL_DEBUG

4.1 Уровни отладки

Установите переменную окружения:

export NCCL_DEBUG=INFO   # подробные логи выбора алгоритма и коммуникации
export NCCL_DEBUG=WARN   # только варнинги
export NCCL_DEBUG=VERSION # только версия (минимально)

NCCL_DEBUG=INFO — золотой стандарт для анализа.

Что искать в логах

  • NCCL INFO Ring 01 : 0[0] -> 1[10] via NET/IB/0 — указание на протокол (Ring) и тип линка (через InfiniBand).
  • NCCL WARN Cuda failure 'out of memory' — проблемы с буфером.
  • NCCL INFO Trees enabled or disabled — какой протокол выбран.

4.2 Интерпретация: Ring vs Tree

  • Ring: алгоритм по кольцу. Хорош для больших сообщений (>= 256 MB), но задержка растёт линейно с числом GPU.
  • Tree: иерархический (двоичное дерево). Лучше для малых сообщений (<= 1 MB), но может быть медленнее при больших.

Когда выбирать

  • Для TP (размер AllReduce ≈ размер скрытого слоя, например 16,384 токенов × 8192 параметров = 0.13 MB при fp16) — чаще Tree.
  • Для PP (P2P передача активаций) — зависит от размера тензора.

Если NCCL выбирает Ring, когда Tree быстрее (или наоборот), можно форсировать протокол NCCL_ALGO.

4.3 Дополнительные логи: NCCL_DEBUG_SUBSYS

export NCCL_DEBUG_SUBSYS=INIT,GRAPH,ENV
  • INIT — инициализация.
  • GRAPH — построение графа коммуникаций.
  • ENV — использование NVLink, InfiniBand.

5. Протоколы NCCL и ключевые параметры

NCCL поддерживает два протокола:

  • Simple (базовый) — надёжный, но может иметь большие накладные расходы.
  • LL (Low Latency) — для малых сообщений, использует пиринговую память (CUDA IPC).
  • LL128 — оптимизация для 128 байт.

Выбор протокола влияет на производительность. Можно принудительно задать:

export NCCL_PROTO=Simple  # или LL, LL128

Для больших AllReduce (например, 256 MB) Simple — хороший выбор.

Параметры для тонкой настройки:

ПеременнаяНазначениеРекомендация
NCCL_BUFFSIZEРазмер буфера для операций (по умолчанию 4 MB)Увеличьте до 16-32 MB для больших сообщений: export NCCL_BUFFSIZE=16777216
NCCL_NTHREADSКоличество потоков CPU, участвующих в коммуникации (по умолчанию 1)Для NVLink увеличьте до 8-16: export NCCL_NTHREADS=8
NCCL_RINGSЯвное указание порядка колец (Ring order)Обычно не требуется, но можно задать: NCCL_RINGS="0,1,2,3,4,5,6,7"
NCCL_ALGOПринудительный алгоритм: Ring, Tree или CollnetПример: NCCL_ALGO=Tree
NCCL_IB_GID_INDEXИндекс GID (для InfiniBand)export NCCL_IB_GID_INDEX=3 (если ошибки)
NCCL_IB_DISABLEОтключение IB (1) или принудительное включение (0)export NCCL_IB_DISABLE=0 (убедитесь, что IB используется)

6. Профилирование с Nsight Systems

Для системного анализа всего пайплайна используйте NVIDIA Nsight Systems.

Шаги:

  1. Запустите инференс с профилировщиком:
    nsys profile -o trace_output python run_inference.py
    
  2. Откройте trace_output.qdrep в Nsight Systems GUI.
  3. Найдите ось GPU (NCCL kernel) и ось CPU (MPI/network).

Что искать

  • Gaps — промежутки между kernel launches, когда GPU простаивает, ожидая коммуникации.
  • NCCL kernel durations — если AllReduce занимает >10% всего времени, это зона оптимизации.
  • P2P latency — для Pipeline Parallelism.

Можно использовать бенчмарк nccl-tests с флагом -t для времени.


7. Типичные проблемы и их решения

7.1 Медленная внутриузловая коммуникация (NVLink не задействован)

  • Симптом nvidia-smi topo -m показывает PHB для всех GPU.
  • Решение Разместите модель на GPU, соединённых NVLink с помощью CUDA_VISIBLE_DEVICES или torch.distributed с правильной группой.

7.2 Медленная межузловая коммуникация (InfiniBand не используется)

  • Симптом ibstatus показывает state: DOWN или низкую скорость.
  • Решение Проверьте кабель, порт, драйвер, монтаж. Используйте ib_write_bw для диагностики.

7.3 NCCL выбирает неправильный алгоритм

  • Симптом В логах NCCL INFO using Ring, но Tree был бы быстрее.
  • Решение Форсировать NCCL_ALGO=Tree или увеличить NCCL_BUFFSIZE.

7.4 Проблемы с памятью (OOM)

  • Симптом Cuda failure 'out of memory' при больших буферах.
  • Решение Уменьшите NCCL_BUFFSIZE или используйте градиентное чекпоинтинг (для обучения; для инференса — распределите модель так, чтобы буфера помещались).

7.5 Низкая пропускная способность из-за NUMA

  • Решение Привяжите процесс к близкому CPU-сокету с помощью numactl:
    numactl --cpunodebind=0 --membind=0 python inference.py
    

8. Автоматизация диагностики: скрипт быстрой проверки

# quick_comm_check.py
import subprocess, sys

def run_cmd(cmd):
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return result.stdout

print("=== Topology ===")
print(run_cmd("nvidia-smi topo -m"))

print("=== NCCL Debug (set env NCCL_DEBUG=INFO) ===")
# Запускаем nccl-tests allreduce (если есть)
print(run_cmd("mpirun -np [[4. Какую векторную БД вы выберете для production-системы с больше 1 млн векторов|4]] ./build/all_reduce_perf -b 256M -e 256M -f [[2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2]] -g [[2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2]] [[2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2]]>&[[1. Как бы вы спроектировали RAG-систему для 10 000 документов с разной структурой|1]] | head -[[20. Как вы обеспечиваете, что RAG работает с документами на русском и английском одновременно|20]]"))

Скрипт можно расширить, добавляя проверки ibstatus и сравнение с теоретическими показателями.


Пет-проект для закрепления

Задача Создать простой бенчмарк коммуникаций для двух узлов с помощью PyTorch Distributed и NCCL, который измеряет AllReduce latency для разных размеров сообщений.

Инструменты Python 3, PyTorch с CUDA, torch.distributed, torch.cuda.nccl, mpi4py (или torchrun).

Шаги:

  1. Установите PyTorch с поддержкой NCCL.
  2. Напишите скрипт bench_allreduce.py:
    import torch
    import torch.distributed as dist
    import time
    
    def bench_allreduce(size_mb, rank, world_size):
        tensor = torch.rand(1, size_mb * 1024 * 1024 // 4, device='cuda')  # fp32
        dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
        torch.cuda.synchronize()
        start = time.time()
        for _ in range(100):
            dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
        torch.cuda.synchronize()
        elapsed = time.time() - start
        latency = elapsed / 100 * 1e3  # ms
        algo_bw = size_mb / (elapsed / 100)  # MB/s
        print(f"[{rank}] Size {size_mb} MB: Latency {latency:.2f} ms, AlgoBW {algo_bw:.1f} MB/s")
        return latency
    
    if __name__ == "__main__":
        dist.init_process_group(backend='nccl')
        rank = dist.get_rank()
        world_size = dist.get_world_size()
        for size in [1, 4, 16, 64, 256]:
            bench_allreduce(size, rank, world_size)
        dist.destroy_process_group()
    
  3. Запустите на двух узлах:
    torchrun --nnodes=2 --nproc_per_node=8 --rdzv_endpoint=node1:29500 bench_allreduce.py
    
  4. Сравните результаты с nccl-tests на тех же узлах (должны быть близки).

Ожидаемый результат На одном узле с NVLink latency < 1 ms для 256 MB, пропускная способность > 500 GB/s. Между узлами через InfiniBand — < 5 ms, пропускная способность ~25 GB/s на линк.


Связь с другими вопросами

ВопросТема
426Реализация tensor parallelism для multi-node инференса
425Стратегии коммуникации в distributed inference
428Оптимизация памяти при multi-node инференсе
429Деплой LLM на несколько GPU
430Сравнение DeepSpeed Inference и vLLM
436Архитектура Pipeline Parallelism

Навигация