Инженерия

Как самостоятельно развернуть инференс LLM с vLLM

Пошаговое руководство с прицелом на продакшен: подберите GPU, запустите OpenAI-совместимый сервер vLLM, настройте память и параллелизм, а затем защитите его и обеспечьте бесперебойную работу.

9 мин чтения

Чтобы самостоятельно развернуть инференс LLM (больших языковых моделей) с vLLM, нужны три вещи: GPU, памяти которого хватает на веса и KV-кэш, сервер vLLM с OpenAI-совместимым API и привычный для продакшена набор мер вокруг него — аутентификация, TLS, файрвол, менеджер служб и мониторинг. В этом руководстве каждый шаг разобран с командами, которые можно адаптировать под себя, на примере моделей Llama 3.1 и сервера Ubuntu с уже установленными драйверами NVIDIA.

Зачем самостоятельно разворачивать инференс LLM с vLLM

vLLM — движок инференса с открытым исходным кодом, построенный на страничном KV-кэше и непрерывном батчинге (continuous batching): благодаря им один GPU может обслуживать множество запросов одновременно. Если запускать его самостоятельно, а не обращаться к API стороннего сервиса, вы получаете четыре преимущества:

  • Контроль над данными. Промпты и ответы остаются на машине, которую вы контролируете, и не нужно разбираться в чужих правилах хранения данных.
  • Фиксированные расходы. При оплате за токены счёт растёт вместе с использованием. Выделенный GPU стоит одинаково, обслуживает ли он миллион токенов или миллиард, поэтому стоимость токена — это цена в месяц, делённая на число обслуженных токенов, и она снижается по мере роста загрузки.
  • Задержка под вашим контролем. Никакой общей очереди, сервер рядом с вашим приложением и собственный баланс между размером батча и временем ответа.
  • Гибкая настройка. Любая открытая модель, ваши собственные модели после дообучения (fine-tuning) и адаптеры LoRA, структурированный вывод и ограничения контекста, подходящие вашему приложению.

Но и компромиссы вполне реальны. Стек обслуживаете вы: обновления, безопасность, мощности и отказоустойчивость — ваша забота. Вы платите и за часы простоя. Закрытые фронтирные модели развернуть у себя нельзя. А если объём запросов невелик или нагрузка скачкообразная, API с оплатой за токены часто обходится дешевле.

Подбор GPU: веса плюс KV-кэш

Что вы сможете обслуживать, определяет память. Веса занимают число параметров × число байт на параметр: около 16 ГБ для Llama 3.1 8B в BF16 и около 70 ГБ для Llama 3.1 70B в FP8 или 140 ГБ в BF16. KV-кэш занимает 2 × число слоёв × число KV-голов × размерность головы × число байт на значение для каждого токена в обработке: около 0,13 МБ на токен для модели 8B и 0,33 МБ для 70B в BF16.

Пример расчёта: Llama 3.1 70B в FP8 на одном H200 со 141 ГБ при --gpu-memory-utilization 0.92. vLLM может использовать около 130 ГБ. После примерно 70 ГБ весов и нескольких гигабайт на активации на кэш остаётся около 55 ГБ: это примерно 170 000 токенов в BF16, или 20 одновременных диалогов с полным контекстом по 8 192 токена. Кэш в FP8 удваивает это число — примерно до 40. Полная методика и другие примеры — в нашем руководстве по видеопамяти (VRAM) для LLM.

Установка vLLM и запуск OpenAI-совместимого сервера

Установите vLLM в виртуальное окружение. Его wheel-пакеты включают собственную сборку PyTorch, поэтому от системы нужен только свежий драйвер NVIDIA; проверьте его командой nvidia-smi. См. также нашу документацию по PyTorch и vLLM.

sudo apt-get update
sudo apt-get install -y python3-venv
python3 -m venv ~/vllm-env
source ~/vllm-env/bin/activate
pip install vllm

Доступ к моделям Llama на Hugging Face ограничен: примите лицензию на странице модели, создайте токен доступа и экспортируйте его. Затем сгенерируйте API-ключ и запустите сервер с привязкой к localhost:

export HF_TOKEN=hf_your_token
export VLLM_API_KEY=$(openssl rand -hex 32)

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 127.0.0.1 --port 8000 \
  --api-key "$VLLM_API_KEY" \
  --max-model-len 32768

При первом запуске веса скачиваются в кэш Hugging Face; если домашний каталог небольшой, укажите в HF_HOME каталог на локальном NVMe. Затем проверьте эндпоинт:

curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $VLLM_API_KEY" \
  -d '{
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "messages": [
      {"role": "user", "content": "Explain the KV cache in one sentence."}
    ],
    "max_tokens": 100
  }'

Поскольку API следует формату OpenAI, существующие клиенты заработают, как только вы поменяете базовый URL и ключ:

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="YOUR_KEY")
reply = client.chat.completions.create(
    model="meta-llama/Llama-3.1-8B-Instruct",
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)

Если вы предпочитаете Docker, официальный образ vllm/vllm-openai принимает те же аргументы. Публикуйте порт только на 127.0.0.1, а в продакшене фиксируйте тег релиза вместо latest:

docker run --gpus all --ipc=host \
  -p 127.0.0.1:8000:8000 \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  -e HF_TOKEN \
  vllm/vllm-openai:latest \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --api-key "$VLLM_API_KEY"

Тензорный параллелизм на нескольких GPU

Когда модель не помещается на один GPU, параметр --tensor-parallel-size делит каждый слой между несколькими GPU. Веса и KV-кэш распределяются между GPU, которые обмениваются активациями на каждом слое, поэтому здесь важен NVLink. Это значение должно быть делителем числа голов внимания модели: у Llama 3.1 70B их 64, поэтому используйте 2, 4 или 8.

На сервере 8× H100 можно запустить две копии Llama 3.1 70B в BF16, каждую на четырёх GPU:

CUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 --host 127.0.0.1 --port 8001

CUDA_VISIBLE_DEVICES=4,5,6,7 vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 --host 127.0.0.1 --port 8002

Каждая копия распределяет 140 ГБ весов по 320 ГБ памяти — около 35 ГБ на GPU, — так что при стандартной настройке памяти на каждом GPU для кэша остаётся примерно столько же. Обратный прокси распределяет запросы между двумя портами.

Эмпирическое правило: используйте тензорный параллелизм, когда модель не помещается на один GPU или когда нужна меньшая задержка на запрос. В остальных случаях запускайте по одной копии на каждом GPU за балансировщиком нагрузки — так трафика между GPU не будет вовсе. На картах, связанных только через PCIe, таких как L40S или RTX PRO 6000, лучше запускать отдельные копии или попробовать --pipeline-parallel-size: этот режим требует меньше обмена данными, чем тензорный параллелизм.

Настройка памяти: FP8, кэширование префиксов и ограничения

Несколько параметров определяют, сколько поместится в память и насколько эффективно используется кэш:

  • --quantization fp8 преобразует веса из BF16 в FP8 при загрузке модели и вдвое сокращает память под веса на GPU Hopper, Ada и Blackwell. Проверьте, как это делает ваша версия vLLM: старые версии сначала загружали веса в полной точности, поэтому модель в BF16 должна была поместиться в память до преобразования. Для модели 70B на одном GPU используйте чекпоинт, уже квантованный в FP8; vLLM определит формат по конфигурации чекпоинта.
  • --kv-cache-dtype fp8 вдвое уменьшает кэш и вдвое увеличивает число токенов, которые в нём помещаются. Прежде чем включать этот параметр, проверьте качество на задачах с длинным контекстом.
  • Кэширование префиксов позволяет повторно использовать кэш для общих префиксов промптов: системных промптов, примеров few-shot, предыдущих реплик диалога и документов, которые повторяются в разных RAG-запросах. В последних версиях оно включено по умолчанию; в более старых передайте --enable-prefix-caching. Чтобы извлечь из него пользу, ставьте в промптах неизменное содержимое в начало, а изменяющееся — в конец.
  • --max-model-len ограничивает контекст одного запроса. По умолчанию это максимум модели — 131 072 токена для Llama 3.1, и vLLM не запустится, если одна последовательность такой длины не помещается в кэш, поэтому задайте значение, которое вам действительно нужно.
  • --gpu-memory-utilization — доля памяти каждого GPU, которую vLLM может занять под веса, активации и кэш; по умолчанию 0,9. На выделенном GPU её можно поднять примерно до 0,95, чтобы получить больше кэша; уменьшите её, если GPU использует что-то ещё.
  • --max-num-seqs ограничивает число последовательностей, которые обрабатываются одновременно, — простой способ удержать задержку под нагрузкой в разумных пределах.

Если собрать всё вместе, запуск заранее квантованного чекпоинта модели 70B в FP8 на одном H200 выглядит так:

vllm serve /models/llama-3.1-70b-instruct-fp8 \
  --served-model-name llama-3.1-70b \
  --max-model-len 32768 \
  --gpu-memory-utilization 0.92 \
  --kv-cache-dtype fp8 \
  --host 127.0.0.1 --port 8000

Чем квантование оборачивается для качества, разобрано в нашей статье о точности FP8 и FP4.

Защита эндпоинта

Открытый эндпоинт инференса рано или поздно найдут и начнут использовать посторонние. Защищайте его в несколько уровней.

Привязка к localhost и обязательный ключ

В примерах выше vLLM привязан к 127.0.0.1, поэтому снаружи сервера к нему напрямую не обратиться. Параметр --api-key или переменная окружения VLLM_API_KEY заставляет клиентов передавать ключ в заголовке Authorization: Bearer. Ключ защищает маршруты API /v1; не рассчитывайте, что он закрывает и все остальные маршруты сервера, например метрики, — это ещё одна причина не открывать порт наружу. Если эндпоинтом пользуются несколько команд или клиентов, поставьте перед ним API-шлюз с отдельными ключами для пользователей и ограничением частоты запросов.

Файрвол

Разрешите SSH, HTTP и HTTPS, а всё остальное запретите:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Если подключаться должны только известные клиенты, замените правило для порта 443 отдельным правилом для каждого адреса источника, например sudo ufw allow from 203.0.113.10 to any port 443 proto tcp. Есть одна ловушка: порты, опубликованные Docker, обходят правила UFW, — именно поэтому в примере с Docker порт публикуется только на 127.0.0.1. Подробнее — в нашем руководстве по сети и файрволу. Кроме того, на наших серверах публичные IP-адреса защищены от DDoS: это дополняет аутентификацию, но не заменяет её.

Обратный прокси с TLS

Caddy автоматически получает и продлевает сертификат, как только ваша DNS-запись указывает на сервер, а порты 80 и 443 открыты. После установки из официального репозитория пакетов вся конфигурация в /etc/caddy/Caddyfile выглядит так:

llm.example.com {
    reverse_proxy 127.0.0.1:8000
}

Перезагрузите конфигурацию командой sudo systemctl reload caddy. Потоковые ответы передаются клиенту по мере генерации. Чтобы распределять запросы между двумя копиями vLLM, перечислите оба бэкенда: reverse_proxy 127.0.0.1:8001 127.0.0.1:8002. Если вместо Caddy вы используете nginx, отключите proxy_buffering для блока location с API и увеличьте proxy_read_timeout, иначе потоковые токены будут приходить рывками, а длинные запросы — обрываться по тайм-ауту.

Запуск vLLM как службы и мониторинг

В продакшене запускайте vLLM под systemd от имени непривилегированного пользователя, чтобы он стартовал при загрузке системы и перезапускался после сбоя:

sudo useradd --system --user-group --create-home \
  --home-dir /opt/vllm --shell /usr/sbin/nologin vllm
sudo -u vllm python3 -m venv /opt/vllm/venv
sudo -u vllm /opt/vllm/venv/bin/pip install vllm
sudo mkdir -p /etc/vllm

Храните секреты в файле /etc/vllm/vllm.env, доступном для чтения только root (sudo chmod 600 /etc/vllm/vllm.env); systemd читает этот файл до того, как понижает привилегии:

HF_TOKEN=hf_your_token
HF_HOME=/opt/vllm/hf-cache
VLLM_API_KEY=your-long-random-key

Затем создайте /etc/systemd/system/vllm.service:

[Unit]
Description=vLLM OpenAI-compatible server
After=network-online.target
Wants=network-online.target

[Service]
User=vllm
Group=vllm
EnvironmentFile=/etc/vllm/vllm.env
ExecStart=/opt/vllm/venv/bin/vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 127.0.0.1 --port 8000 --max-model-len 32768
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now vllm
journalctl -u vllm -f

Здесь vLLM берёт ключ из VLLM_API_KEY, поэтому он никогда не появляется в командной строке. При мониторинге следите за тремя уровнями:

  • Работоспособность. GET /health возвращает 200, когда сервер работает; настройте на него проверку доступности.
  • Метрики инференса. /metrics отдаёт метрики Prometheus, в том числе число выполняемых и ожидающих запросов (vllm:num_requests_running и vllm:num_requests_waiting), время до первого токена, сквозную задержку и заполненность KV-кэша. Настройте оповещения на случаи, когда запросы ждут по нескольку минут, когда растёт время до первого токена или когда кэш остаётся почти заполненным, что вынуждает vLLM прибегать к вытеснению (preemption) запросов.
  • Метрики GPU. Для быстрой проверки используйте nvidia-smi, а для Prometheus — экспортер DCGM от NVIDIA. Не удивляйтесь, что память показывает занятость около 90% даже в простое: vLLM резервирует свою долю при запуске. Следите за температурой, энергопотреблением и ошибками GPU в журнале ядра (journalctl -k).

Подробнее о том, что происходит на стороне GPU, — в нашем руководстве по мониторингу GPU.

Выбор GPU

Подбирайте GPU под модель, точность и нужное вам число одновременных запросов. Скорость генерации во многом определяется пропускной способностью памяти, поэтому это второй показатель, который стоит сравнивать после объёма.

Модель и точностьВесаПодходящий GPUЗаявленная пропускная способность на GPUЦена в месяц
8B в BF16Около 16 ГБRTX 5090 (32 ГБ) или L40S (48 ГБ)1,79 ТБ/с или 864 ГБ/с449 $ или 890 $
32B в FP8 или 70B в 4-битном формате32–40 ГБRTX PRO 6000 (96 ГБ)1,6 ТБ/с1 390 $
32B в BF16Около 64 ГБH100 (80 ГБ)3,35 ТБ/с2 190 $
70B в FP8Около 70 ГБH200 (141 ГБ)4,8 ТБ/с3 090 $
70B в BF16Около 140 ГБB200 (180 ГБ) или B300 (288 ГБ)8 ТБ/с4 590 $ или 5 190 $
405B в FP8Около 405 ГБ8× H100 (640 ГБ) или 8× H200 (1 128 ГБ)3,35 или 4,8 ТБ/с16 900 $ или 23 900 $
DeepSeek-V3 в FP8Около 670 ГБ8× H200 или 8× B200 (1 440 ГБ)4,8 или 8 ТБ/с23 900 $ или 34 900 $

Модель 70B в FP8 помещается и на два H100 с тензорным параллелизмом, но за 4 380 $ в месяц это дороже одного H200, да ещё и добавляет трафик между GPU. vLLM поддерживает и GPU AMD через ROCm, поэтому для очень больших моделей подойдёт сервер 8× MI355X с 288 ГБ на GPU; сначала проверьте на нём свою модель. При фиксированной цене в месяц стоимость собственного эндпоинта известна заранее, а в наши тарифы входит 20 ТБ (один GPU) или 50 ТБ (8 GPU) исходящего трафика в месяц — гораздо больше, чем обычно нужно для потоковой передачи текста. Подробное сравнение — в нашем руководстве о лучшем GPU для инференса LLM или на странице NVIDIA H200.

Часто задаваемые вопросы

Какой GPU нужен, чтобы самостоятельно развернуть модель 70B с vLLM?

В FP8 веса занимают около 70 ГБ, поэтому один H200 со 141 ГБ обслуживает такую модель с запасом для одновременных запросов, а на H100 с 80 ГБ она помещается лишь впритык. В BF16 одни только веса занимают около 140 ГБ — значит, нужен B200, B300 или тензорный параллелизм на нескольких GPU.

Совместим ли vLLM с API OpenAI?

Да. vLLM предоставляет эндпоинты в стиле OpenAI, такие как /v1/chat/completions, /v1/completions и /v1/models, поэтому большинство клиентских библиотек OpenAI работают, как только вы поменяете базовый URL и API-ключ.

Как защитить эндпоинт vLLM?

Привяжите vLLM к 127.0.0.1, сделайте API-ключ обязательным и открывайте доступ только через обратный прокси с TLS за файрволом, который пропускает лишь SSH, HTTP и HTTPS. В Docker публикуйте порт на 127.0.0.1, потому что порты, опубликованные Docker, обходят UFW.

Почему nvidia-smi показывает, что GPU почти заполнен, когда vLLM простаивает?

При запуске vLLM резервирует фиксированную долю памяти GPU — по умолчанию 90% — и отдаёт большую её часть под блоки KV-кэша. Это ожидаемое поведение, а не утечка памяти.

Что выбрать: тензорный параллелизм или несколько реплик?

Тензорный параллелизм нужен, когда модель не помещается на один GPU или когда требуется меньшая задержка на запрос, — в идеале через NVLink. Если модель помещается на один GPU, отдельные реплики за балансировщиком нагрузки избавляют от трафика между GPU и обычно дают большую суммарную производительность.

Выделенные GPU с помесячной оплатой

Целые GPU от 449 $ в месяц, root-доступ, трафик включён. Оплата криптовалютой.

Смотреть GPU и цены Подберите GPU с помощью калькулятора видеопамяти

Развернуть GPU-сервер

Получите GPU-сервер уже в этом месяце.

Выберите конфигурацию, создайте аккаунт в той же форме и оплатите первый месяц с баланса. Сервер выдаётся менее чем за 10 минут.

  • Выдача менее чем за 10 минут
  • Отмена в любом месяце
  • Оплата криптовалютой, бонус до +50%

Вход в FullGPU

Управляйте GPU-серверами, балансом и счетами.

Впервые в FullGPU? Создать аккаунт