Решения

vLLM: быстрый сервинг LLM и PagedAttention

Задача страницы. Показать, как поднять vLLM на облачных GPU, настроить PagedAttention, батчинг/стриминг и кэш префиксов, спланировать TPS/латентность и стоимость.

TL;DR

  • PagedAttention даёт «виртуальную память» для KV‑кэша → меньше фрагментации, больше одновременных сессий.
  • Continuous batching: vLLM динамически добавляет/удаляет запросы на каждом шаге декода → высокий TPS без «холостых» простоев.
  • Prefix‑кэш: общий системный промпт/инструкции считаются один раз и переиспользуются.
  • Для низкой цены за 1M токенов — квантизация + агрессивный батчинг; для низкой p95 — ограничиваем длины, держим тёплый кэш, повышаем приоритет стриминга.

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

  • Нужен максимальный TPS при умеренной латентности (чат‑сервисы, РАГ‑API, массовая генерация).
  • Большой пул одновременных запросов и общие префиксы (системные подсказки/инструкции).
  • Требуется стриминг токенов (SSE/WebSocket) и OpenAI‑совместимый HTTP‑интерфейс.

Альтернативы: /solutions/llm-inference/tensorrt-llm/ для минимальной p95, /solutions/llm-inference/tgi/ для «универсального» REST, /solutions/llm-inference/llama-cpp/ для тонких инстансов.

Быстрый старт (один узел)

Запуск сервера

				
					vllm serve meta-llama/Llama-3-8B \
  --port 8000 \
  --tensor-parallel-size 1 \
  --dtype bfloat16 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90 \
  --enable-prefix-caching \
  --enforce-eager \
  --served-model-name llama3-8b

				
			

Пояснения по ключам:

  • —tensor-parallel-size — распараллеливание весов по нескольким GPU узла.
  • —dtype — тип весов; на Ampere/Hopper обычно bf16/fp16.
  • —max-model-len — лимит суммарного контекста (вход+выход).
  • —gpu-memory-utilization — целевая доля HBM под веса+кэш.
  • —enable-prefix-caching — кэширование общих префиксов.
  • —enforce-eager — быстрый старт без долгих граф‑компиляций.

Пример запроса (стриминг SSE)

				
					curl -N http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama3-8b",
    "messages": [{"role":"system","content":"You are a helpful assistant"},
                 {"role":"user","content":"Объясни attention простыми словами"}],
    "stream": true,
    "temperature": 0.2,
    "max_tokens": 300
  }'

				
			

Python‑клиент (OpenAI‑совместимый)

				
					from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="unused")
resp = client.chat.completions.create(
    model="llama3-8b",
    messages=[{"role":"user","content":"Кратко про PagedAttention"}],
    stream=True
)
for chunk in resp: print(chunk.choices[0].delta.content or "", end="", flush=True)

				
			

Как работает PagedAttention (и почему это быстрее)

Классический KV‑кэш растёт ~линейно от длины контекста и числа сессий и легко фрагментируется (память занята «дырками»).
PagedAttention разбивает KV‑кэш на фиксированные блоки («страницы») и управляет ими как виртуальной памятью:

  • меньше фрагментации → больше активных сессий в той же VRAM;
  • быстрая эвикция/реплейсмент блоков;
  • более предсказуемая латентность на больших пулах запросов.

Практика: держите общий системный префикс и шаблоны промптов одинаковыми — так префикс‑кэш в vLLM срабатывает максимально часто.

Батчинг и планировщик

vLLM использует continuous batching: на каждом шаге декода планировщик пересобирает батч из активных последовательностей, добивая GPU до целевого заполнения.

Ключевые параметры (ориентиры):

  • Размер «эффективного» батча на декоде растёт с числом одновременных запросов.
  • Ограничивайте длины контекста (—max-model-len) и максимальный выход (max_tokens в запросе), чтобы не «зажимать» декод длинными сессиями.
  • Для высоких QPS: отдайте приоритет мелким/средним запросам, а длинные — через отдельный пул.

Полезные приёмы:

  • Группируйте запросы по схожей длине входа (prefill) — это повышает утилизацию на первых шагах.
  • Для «длинных» приложений (аналитика/программирование) — применяйте prefix‑кэш, чтобы prefill не «съедал» львиную долю времени.

Память на инференсе: прикидка KV‑кэша

Для LLM с L слоёв и H голов на d‑размерных ключах/значениях, приблизительная память KV:

KV_bytes ≈ 2 * L * (L_in + L_out_avg) * H * d * bytes_per_elem

где коэффициент 2 — для K и V. Снижаем память:

  • уменьшить max-model-len (или жёстко ограничить max_tokens);
  • включить paged‑KV (встроено), держать общие префиксы;
  • по возможности — хранить KV в более «лёгком» dtype (если конфигурация поддерживает);
  • изолировать «длинные» запросы в отдельный пул или на отдельный инстанс.

Квантизация, адаптеры и мульти‑модель

  • Квантизация весов (INT4/INT8/AWQ/GPTQ) снижают цену за 1M токенов; проверяйте совместимость модели/режима при запуске.
  • LoRA‑адаптеры: можно держать несколько адаптеров поверх одной базы (горячая смена домена/стиля) — удобно для мульти‑тенантного сервинга.
  • Мульти‑модельный пул: разделите VRAM между несколькими весами или держите «горячую» одну модель + «тёплый» вторичный набор.

Связанные разделы:
/solutions/llm-inference/quantization/, /solutions/llm-inference/multi-model/, /solutions/llm-inference/agents/.

Профили конфигураций (ориентиры)

A) «Минимальная цена за 1M токенов» (массовый TPS)

  • Квантизация (INT8/INT4), —gpu-memory-utilization 0.90–0.95, высокий пул одновременных запросов, строгие лимиты max_tokens.
  • Prefix‑кэш включён; «длинные» сценарии вынесены на отдельный пул.

B) «Сбалансированный»

  • bf16/fp16, —gpu-memory-utilization 0.85–0.9, разумные лимиты контекста, префикс‑кэш, приоритет стриминга.

C) «Низкая p95»

  • Жёсткие лимиты на длины (короткие ответы), отдельный пул для «длинных», больше GPU на меньшее число одновременных сессий, тщательная настройка тайм‑аутов.
Производительность и стоимость: краткая методика

Снимите реальные TPS_decode и prefill_rate на тестовом трафике.

Оцените латентность запроса:
T ≈ (L_in / prefill_rate) + (L_out / (TPS_decode / B)) + overhead

Прикиньте QPS и стоимость за 1M токенов:
QPS ≈ B / T

Cost_per_1M ≈ (GPU_hour_price × Num_GPU) / (TPS_decode × 3600 / 1e6)

Подберите лимиты max_tokens, размер пулов и число GPU до выполнения SLA.

Дополнительно: /solutions/llm-inference/costs/, /solutions/cost-planner/.

Мониторинг, алерты, логи

Минимальный набор метрик:

  • Latency p50/p95/p99, TPS_decode, prefill_rate, доля timeouts/errors.
  • GPU: utilization, HBM usage, «давление» на KV‑кэш (число активных сессий, доля эвикций).
  • Кэш: hit‑rate prefix‑кэша, доля повторно используемых префиксов.
  • Система: CPU (токенизация), сеть (стриминг), диск (логи/кэш).

Смотрите: /solutions/monitoring-logging/, /solutions/llm-inference/observability/.

Траблшутинг

  • Высокая p95 / «хвосты». Сократите max_model_len/max_tokens, вынесите длинные запросы в отдельный пул, увеличьте размер пула GPU или снизьте конкуренцию.
  • GPU‑OOM. Понизьте лимиты длины, уменьшите долю —gpu-memory-utilization, примените квантизацию или распределите модель по нескольким GPU (—tensor-parallel-size).
  • Низкий TPS. Увеличьте одновременность, проверьте CPU‑узкие места (токенизация), удостоверьтесь, что стриминг включён и кэш префиксов реально используется.
  • «Пила» латентности на prefill. Укоротите префиксы, унифицируйте системные подсказки (для кэша), используйте «тёплую» загрузку модели при рестартах.
  • Нестабильность после обновления образа. Зафиксируйте версии CUDA/драйвера и контейнеров, прогоните короткий нагрузочный тест перед переключением трафика.

Как это запускать в cloudcompute.ru

Чек‑лист перед продом

  • Зафиксированы SLA (p95, TPS), лимиты max_tokens и max_model_len.
  • Выбран профиль конфигурации (стоимость/баланс/латентность).
  • Включены prefix‑кэш и стриминг; шаблоны промптов унифицированы.
  • Проверены QPS/TPS на синтетике и на реальном сэмпле трафика.
  • Настроены алерты по latency/TPS/KV‑кэшу, включён сбор логов.
  • Описан план фоллбеков (вторая модель, деградация длины/температуры/beam).

Навигация по разделу «Инференс LLM»