Tensor Parallelism и multi-GPU инференс: как распределить большую модель по нескольким видеокартам

Когда модель не помещается на одну GPU​


Современные LLM с десятками и сотнями миллиардов параметров требуют десятков гигабайт памяти только для весов. Если модель не помещается в VRAM одной видеокарты, есть два основных пути: квантование (уменьшение точности весов) или распределение модели по нескольким GPU. Tensor parallelism — второй путь, причём он сохраняет исходную точность и не вносит потерь качества.

Идея проста: каждая матрица весов внутри трансформерного слоя разрезается на части, и каждая GPU хранит и обрабатывает свой фрагмент. После вычисления фрагментов результаты синхронизируются через коллективные операции (обычно AllReduce или AllGather). Это позволяет обрабатывать один запрос одновременно на нескольких устройствах.

Механика: что именно разрезается​


В трансформерном блоке основные матричные умножения происходят в:

  • Attention-слоях — проекции Q, K, V и выходная проекция.
  • FFN/MLP-слоях — две линейные проекции (up и down).

При tensor parallelism каждая из этих матриц разбивается по столбцам или строкам между GPU. Например, при tensor_parallel_size=4 матрица весов размером [hidden, 4*hidden] делится на четыре блока [hidden, hidden], каждый из которых размещается на отдельной GPU. После локального умножения выполняется AllReduce для суммирования частичных результатов.

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

Требования к оборудованию и топологии​


ФакторВлияние на tensor parallelism
NVLink между GPUНизкая задержка, высокая пропускная способность — оптимально
PCIe Gen4/Gen5Работает, но задержка выше; при большом tp_size деградация заметна
Отсутствие NVLink (например, L40S)Лучше использовать pipeline parallelism вместо tensor parallelism
InfiniBand между узламиНеобходимо для эффективного меж узлового tensor parallelism
Обычный Ethernet (TCP)NCCL будет использовать сокеты — крайне неэффективно для TP между узлами

Практическое правило: tensor_parallel_size устанавливают равным числу GPU в одном узле. Если модель не помещается на один узел, добавляют pipeline_parallel_size — число узлов.

Настройка в vLLM: один узел, несколько GPU​


vLLM реализует tensor parallelism на основе алгоритма из Megatron-LM. Для запуска на четырёх GPU одного сервера достаточно одного параметра:

Python:
from vllm import LLM

llm = LLM("facebook/opt-13b", tensor_parallel_size=4)
output = llm.generate("San Francisco is a")

Для серверного режима:

Bash:
vllm serve facebook/opt-13b \
  --tensor-parallel-size 4

По умолчанию на одном узле vLLM использует нативный Python multiprocessing как рантайм. Это не требует установки дополнительных зависимостей.

Комбинация с pipeline parallelism​


Если число GPU не делит размер модели равномерно (например, 70B-модель на 3 GPU), tensor parallelism может не сработать. В этом случае включают pipeline parallelism, который разрезает модель по слоям и поддерживает неравномерное разбиение:

Bash:
vllm serve meta-llama/Llama-2-70b-hf \
  --tensor-parallel-size 1 \
  --pipeline-parallel-size 3

Для двух узлов по 8 GPU каждый:

Bash:
vllm serve /path/to/model \
  --tensor-parallel-size 8 \
  --pipeline-parallel-size 2 \
  --distributed-executor-backend ray

Здесь tensor_parallel_size=8 — число GPU внутри узла, pipeline_parallel_size=2 — число узлов. Меж узловой обмен идёт через Ray или multiprocessing.

Multi-node: Ray и multiprocessing​


Для нескольких узлов vLLM поддерживает два рантайма:

  • Ray — распределённый фреймворк, обеспечивает fault tolerance и масштабирование. Требует явной установки (pip install ray).
  • Multiprocessing — встроенный механизм, не требует дополнительных пакетов.

Пример с multiprocessing на двух узлах:

Bash:
## Head node (rank 0)
vllm serve /path/to/model \
  --tensor-parallel-size 8 --pipeline-parallel-size 2 \
  --nnodes 2 --node-rank 0 \
  --master-addr <HEAD_NODE_IP>

## Worker node (rank 1)
vllm serve /path/to/model \
  --tensor-parallel-size 8 --pipeline-parallel-size 2 \
  --nnodes 2 --node-rank 1 \
  --master-addr <HEAD_NODE_IP> --headless

Оба узла должны иметь идентичное окружение: одинаковые пути к модели, одинаковые версии пакетов. Контейнерные образы — самый надёжный способ обеспечить это.

Проверка результата: KV cache и concurrency​


После запуска vLLM выводит диагностические строки:

Код:
INFO 07-23 13:56:04 [kv_cache_utils.py:775] GPU KV cache size: 643,232 tokens
INFO 07-23 13:56:04 [kv_cache_utils.py:779] Maximum concurrency for 40,960 tokens per request: 15.70x

Первая строка показывает общий объём KV cache в токенах, доступный на всех GPU. Вторая — сколько одновременных запросов можно обслужить при максимальной длине контекста модели. Если значения ниже требуемых, добавляют GPU или узлы.

Оптимизация меж узловой сети: InfiniBand и GPUDirect RDMA​


Tensor parallelism генерирует интенсивный обмен данными на каждом токене. Для меж узлового TP критична пропускная способность сети.

InfiniBand — предпочтительный вариант. При использовании контейнеров добавляют флаги --privileged -e NCCL_IB_HCA=mlx5 (имя устройства уточняют у администратора).

GPUDirect RDMA позволяет сетевому адаптеру обращаться к памяти GPU напрямую, минуя CPU и системную память. Для включения в Docker:

Bash:
docker run --gpus all \
  --ipc=host \
  --shm-size=16G \
  -v /dev/shm:/dev/shm \
  vllm/vllm-openai

В Kubernetes добавляют capability IPC_LOCK и монтируют /dev/shm как emptyDir с medium: Memory.

Диагностика: как убедиться, что RDMA работает​


Запускают vLLM с подробным логированием NCCL:

Bash:
NCCL_DEBUG=TRACE vllm serve ...

В логах ищут строку [send] via NET/IB/GDRDMA — это означает, что используется InfiniBand с GPUDirect RDMA. Если вместо этого [send] via NET/Socket — трафик идёт через TCP, что неприемлемо для меж узлового tensor parallelism.

MoE-модели: отдельная стратегия для экспертов​


Для Mixture-of-Experts моделей (Mixtral, DeepSeek-V3, Qwen-MoE) vLLM поддерживает комбинирование: attention-слои распределяются через Data Parallel, а экспертные слои — через Expert Parallel или Tensor Parallel. Это позволяет эффективнее использовать память, поскольку не все эксперты активны для каждого токена.

Типичные ошибки и ограничения​


  • tensor_parallel_size не делит число attention heads. Если у модели 32 головы, а tp_size=3, запуск завершится ошибкой. В таком случае используют pipeline parallelism.
  • Смешивание разных GPU в одном узле. vLLM ожидает однородные устройства. Разные модели GPU в одном tensor_parallel_size не поддерживаются.
  • Забытая переменная VLLM_HOST_IP. При multi-node каждый воркер должен иметь уникальный VLLM_HOST_IP, указывающий на его собственный адрес в приватной сети.
  • Открытая сеть. Трафик между узлами vLLM не шифруется. Формат данных позволяет выполнить произвольный код при перехвате. Развёртывание допустимо только в изолированной приватной сети.
  • Модель не скачана заранее. При использовании Hugging Face Hub модель загружается на каждом узле. Если путь или токен недоступны на одном из узлов, запуск падает. Рекомендуется предзагрузить модель на общий путь или распределённую файловую систему.

Выбор стратегии: краткая схема​


СитуацияСтратегия
Модель помещается на 1 GPUБез распределения
Модель не помещается на 1 GPU, но входит в узелtensor_parallel_size = N (число GPU в узле)
Число GPU не делит модель равномерноpipeline_parallel_size = N, tensor_parallel_size = 1
GPU без NVLink (L40S и аналоги)Pipeline parallelism предпочтительнее
Модель не входит в один узелtensor_parallel_size = GPU на узел, pipeline_parallel_size = число узлов
MoE-модельData Parallel для attention + Expert/Tensor Parallel для экспертов

Эта схема покрывает большинство практических сценариев инференса от 7B до 400B+ параметров без потери точности.

Источники​


 
Назад
Верх Низ