vLLM у Python: посібник з продакшн-сервінгу LLM у 2026
Практичний посібник із vLLM для продакшн-сервінгу LLM у Python: установка, OpenAI-сумісний сервер, tensor parallelism, FP8/AWQ квантизація, моніторинг і типові граблі з польового досвіду.
vLLM — це Python-фреймворк для високопродуктивного інференсу великих мовних моделей, який завдяки PagedAttention та continuous batching дає у 5–24 рази вищу пропускну здатність порівняно з наївним Hugging Face Transformers і на 2026 рік є фактичним стандартом для self-hosted LLM-сервінгу. Чесно кажучи, я пройшов через TGI, TensorRT-LLM, Triton і власні саморобні сервери на FastAPI, і vLLM перемагає в більшості сценаріїв, де важливі latency під SLO і cost-per-token. Втім, він має свої підводні камені, про які демонстраційні туторіали мовчать.
vLLM 0.6.x на 2026 рік підтримує PagedAttention v2, continuous batching, prefix caching, speculative decoding та AWQ/GPTQ/FP8 квантизацію з коробки.
OpenAI-сумісний HTTP-сервер запускається однією командою vllm serve, і клієнти на openai SDK працюють без змін коду.
PagedAttention зменшує фрагментацію KV-кешу до 4% (проти 60–80% у наївних реалізаціях), що дозволяє вмістити у 2–4 рази більше одночасних запитів на одну GPU.
Tensor parallelism через --tensor-parallel-size N масштабує модель на N GPU; для моделей понад 70B параметрів додатково потрібен pipeline parallelism.
На продакшн-навантаженнях критично моніторити time-to-first-token, inter-token latency, GPU KV-cache utilization і queue depth, бо стандартні CPU/RAM-метрики тут марні.
vLLM не замінює оркестрацію: для autoscaling, A/B-роутингу та канаркових деплоїв все одно потрібен KServe, Ray Serve або власний шар поверх.
Що таке vLLM і чому він швидший за Hugging Face Transformers
vLLM, це open-source движок LLM-інференсу, який народився у Berkeley Sky Computing Lab у 2023 році й тепер підтримується спільнотою з понад 600 контриб'юторів. Ядро написане на Python з критичними CUDA-ядрами на C++/Triton. Головна ідея полягає в тому, щоб переосмислити, як обчислюється attention під час інференсу, адже саме там у наївних реалізаціях втрачається 60–80% пам'яті GPU на фрагментацію KV-кешу.
Класичний Hugging Face generate() працює у режимі static batching: ви збираєте батч із N запитів, чекаєте, поки усі N згенерують останній токен, і тільки тоді приймаєте наступний батч. Якщо один запит це 4-токенне «yes», а інший — есе на 2000 токенів, GPU простоюватиме 99% часу. vLLM натомість використовує continuous batching: щойно один запит завершується, на його місце з черги одразу підставляється наступний, на рівні окремих токенів, а не запитів. На реальних чат-навантаженнях це дає 5–10× приріст throughput; на завданнях з різнорідними довжинами до 24×.
Друге ноу-хау, це PagedAttention, описаний у оригінальній статті Kwon et al.. Замість того щоб резервувати безперервний блок пам'яті під максимальну довжину контексту для кожного запиту (як це робить наївна реалізація), vLLM розбиває KV-кеш на блоки фіксованого розміру (типово 16 токенів) і керує ними так, як операційна система керує сторінками віртуальної пам'яті. Це майже повністю усуває внутрішню фрагментацію. На типових навантаженнях я бачив падіння wasted-memory з 70% до 3–4%.
Встановлення та перший запуск vLLM у Python
vLLM 0.6.x вимагає Python 3.9–3.12, CUDA 12.1+ і Linux. Для macOS і Windows офіційної підтримки GPU немає: на Mac можна запускати тільки CPU-режим, що для будь-чого серйознішого за дебаг марно. Базова установка:
# Створіть ізольоване середовище, бо vLLM конфліктує
# з версіями torch і flash-attn у інших проєктах
python -m venv .venv-vllm
source .venv-vllm/bin/activate
pip install --upgrade pip
pip install vllm==0.6.4
# Перевірте, що CUDA видно
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
Найпростіший inline-приклад, це оффлайн-генерація без HTTP-сервера. Корисно для batch-обробки, оцінки якості моделей або генерації синтетичних даних:
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
dtype="bfloat16",
gpu_memory_utilization=0.90, # vLLM зарезервує 90% VRAM під ваги + KV-cache
max_model_len=8192, # обріжте контекст, якщо вам не потрібні всі 128k
)
sampling = SamplingParams(
temperature=0.7,
top_p=0.95,
max_tokens=512,
)
prompts = [
"Поясни PagedAttention двома реченнями.",
"Напиши SQL-запит для топ-5 клієнтів за виручкою.",
]
outputs = llm.generate(prompts, sampling)
for out in outputs:
print(out.prompt)
print("→", out.outputs[0].text)
print("---")
Як підняти OpenAI-сумісний HTTP-сервер
Це, мабуть, найцінніша фіча vLLM для продакшну: одна команда, і у вас є HTTP API, повністю сумісний з ендпоінтами /v1/chat/completions та /v1/completions OpenAI. Будь-який клієнт, написаний під openai SDK, працює без змін; досить підмінити base_url:
Клієнтський код на чистому Python, це той самий openai SDK, що ви вже використовуєте для GPT-4:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="sk-internal-secret-token",
)
resp = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": "Ти data engineer."},
{"role": "user", "content": "Дай схему таблиці orders для e-commerce."},
],
temperature=0.3,
max_tokens=400,
stream=True,
)
for chunk in resp:
delta = chunk.choices[0].delta.content or ""
print(delta, end="", flush=True)
За кадром vLLM запускає FastAPI-сервер на uvicorn, який приймає HTTP-запити, ставить їх у асинхронну чергу, і LLM-движок безперервно вибирає з неї запити для додавання до поточного батча. Streaming через SSE підтримується нативно: клієнт бачить перший токен через 50–150 мс замість того, щоб чекати кілька секунд на повну відповідь. У моєму досвіді саме streaming є найбільшим UX-стрибком при міграції з прихованого OpenAI API на self-hosted: ви контролюєте мережеву топологію та маєте суттєво нижчий p99 TTFT.
PagedAttention, continuous batching і prefix caching
Усі три механізми вмикаються переважно прапорцями і дають дуже різний ефект залежно від типу навантаження. Розберімося, коли який має значення.
Continuous batching
Увімкнений за замовчуванням, відключити його окремо не можна, це і є серце vLLM. На відміну від динамічного батчингу в TorchServe (де ви налаштовуєте batch_size і max_batch_delay), у vLLM батч переоцінюється на кожному кроці декодера. Параметр, який реально на нього впливає, це --max-num-seqs (типово 256). Якщо ваші запити короткі (chat), збільшуйте до 512–1024. Якщо вони довгі (RAG з 32k контекстом), зменшуйте до 32–64, інакше KV-cache закінчиться і запити почнуть preempt-итись.
Prefix caching
Прапорець --enable-prefix-caching вмикає кешування KV-блоків системного промпта та шаблонної частини. Якщо у вас 200 однакових системних промптів на 1500 токенів (типово для RAG або агентів), то vLLM рахує attention для них рівно один раз. Я бачив 3–4× прискорення TTFT після увімкнення префікс-кешу на проді з агентами, які мали ідентичний 2k-токенний system prompt.
Speculative decoding
З версії 0.6 у vLLM є нативна підтримка spec decoding через draft-моделі (--speculative-model) або n-gram-проспекцію. На моделях типу Llama-3.1-70B з 1B-draft-моделлю реалістично отримати 1.5–2× прискорення на single-stream latency, але throughput при високому навантаженні може навіть впасти. Це trade-off, який треба заміряти на вашому трафіку.
Tensor parallelism і робота з кількома GPU
Llama-3.1-70B у bfloat16 займає 140 GB лише на вагах, тож в одну A100 80GB вона не влізе. Рішення це tensor parallelism: розрізаємо матриці шарів навпіл (або на 4/8 частин) між GPU і виконуємо all-reduce після кожного шару. У vLLM це одна опція:
Важливо: --tensor-parallel-size має бути дільником кількості heads у моделі (для Llama-3.1 це 64, тож 1, 2, 4 або 8 валідні). vLLM піднімає multiprocessing-воркери (один на GPU) і використовує NCCL для комунікації. Якщо у вас GPU без NVLink (наприклад PCIe-карти у дешевших хмарах), all-reduce стає вузьким місцем; на L40S без NVLink я бачив падіння throughput на 30–40% порівняно з H100 SXM.
Для дуже великих моделей (понад 200B параметрів, як Llama-3.1-405B або DeepSeek-V3) одного tensor parallelism замало, додається pipeline parallelism через --pipeline-parallel-size. У моїй практиці комбінація TP=8, PP=2 на 16 H100, це робоча конфігурація для 405B на bfloat16. Альтернатива, це FP8-квантизація, до якої перейдемо нижче.
Квантизація: AWQ, GPTQ, FP8 і коли що обирати
Якщо вам не потрібні всі 16 біт точності (а у 99% задач не потрібні), квантизація вдвічі або вчетверо зменшує VRAM-витрати й часто прискорює інференс. vLLM підтримує всі поширені формати; вибір залежить від моделі та GPU.
Формат
Біт на вагу
Якість vs FP16
Прискорення
Коли обирати
FP16 / BF16
16
baseline
1.0×
Достатньо VRAM, потрібна максимальна точність
FP8 (W8A8)
8
−0.1 до −0.5%
1.5–1.8× на H100
H100/H200, продакшн, баланс якість/швидкість
AWQ (W4A16)
4 (ваги)
−0.5 до −1.5%
1.2–1.5×
Обмежений VRAM, A100/L40S
GPTQ (W4A16)
4 (ваги)
−1 до −2%
1.1–1.4×
Старі моделі, для яких немає AWQ-варіанту
INT8 (W8A8)
8
−1 до −2%
1.3–1.5×
GPU без FP8 (A100, V100)
На практиці у 2026 році я майже завжди обираю FP8, якщо стоять H100: підтримка нативна в hopper-архітектурі, втрати точності зазвичай у межах шуму benchmark-ів. Для оренди дешевшого compute на L40S/A100, це AWQ. GPTQ використовую тільки для legacy-моделей без AWQ-варіанту на Hugging Face Hub. Запуск AWQ-моделі тривіальний:
Питання «що використовувати?» залежить від того, де ви на спектрі від «один розробник з gaming-GPU» до «продакшн на 1000 RPS». Я працював зі всіма чотирма і маю чіткі шаблони використання. Зокрема, схожі компроміси я описав у статті про XGBoost, LightGBM і CatBoost: там теж немає універсального переможця, тільки правильний інструмент під задачу.
vLLM vs TGI (Hugging Face Text Generation Inference)
TGI має кращу out-of-the-box інтеграцію з HF Hub і офіційний Docker-образ від Hugging Face. Throughput близький до vLLM, але vLLM швидше отримує підтримку нових моделей (DeepSeek, Qwen-3, Llama-4 типово з'являються у vLLM на 1–2 тижні раніше). Якщо ваш стек, це інші продукти HF (Inference Endpoints, Spaces), TGI логічніший. Інакше vLLM.
vLLM vs TensorRT-LLM
TensorRT-LLM від NVIDIA дає на 10–25% вищий throughput на H100 та помітно нижчий p99 latency завдяки оффлайн-компіляції engine. Але ціна, це багатоденне компілювання engines під кожну конфігурацію (модель, batch size, max-seq-len), нестабільність API між версіями TRT і фактична прив'язка до NVIDIA-стеку. На моєму останньому проєкті ми витратили 6 тижнів на TRT-LLM міграцію, виграли 18% throughput і втратили двох інженерів через супровід. Для більшості команд, не варто.
vLLM vs Ollama
Ollama, це чудовий інструмент для локальної розробки і демо. У продакшн його ставити не треба: серіальний інференс, відсутність continuous batching, обмежений моніторинг. Якщо ви бачите Ollama в архітектурній діаграмі продакшн-сервісу, це червоний прапор.
Що моніторити у vLLM-продакшні
Стандартні Kubernetes-метрики (CPU, RAM) для LLM-сервісу майже марні. GPU на 100%, це норма; RAM рідко є вузьким місцем. Реальні SLO для LLM сервісу формулюються через інші метрики, і саме на них треба мати алерти. Це аналогічно тому, як для класичних ML-моделей моніторять не системні ресурси, а drift і latency. Я писав про це в контексті сучасних scikit-learn ML-пайплайнів.
vLLM з коробки експортує Prometheus-метрики на ендпоінті /metrics. Найважливіші:
vllm:time_to_first_token_seconds, це TTFT, найкритичніша UX-метрика для чат-продуктів. SLO зазвичай p99 < 500 мс.
vllm:time_per_output_token_seconds, це ITL (inter-token latency). Визначає сприйману швидкість генерації; SLO p99 < 50 мс комфортно для чату.
vllm:gpu_cache_usage_perc, заповненість KV-cache. Якщо постійно >90%, у вас preemption, треба зменшити max-num-seqs або додати реплік.
vllm:num_requests_waiting, глибина черги. Стабільно >0 означає, що ви недо-провіжнені.
vllm:num_preemptions_total, лічильник вивантажень запитів через брак пам'яті. Не повинно зростати лінійно у часі.
Окремо я завжди логую довжини промптів і відповідей у percentile-форматі: це швидко покаже, чи зміна трафіку (наприклад, новий клієнт почав слати 30k-токенні документи) ламає вашу capacity-модель.
Типові помилки і як їх уникнути
За два роки експлуатації vLLM у проді зібралася стабільна колекція граблів. Найчастіші:
OOM на першому ж сплеску трафіку, хоча на навантажувальних тестах все було добре. Причина, gpu_memory_utilization розраховано без буфера на драйвер і ваш sidecar. Лікування: ставте 0.88–0.92, не вище.
«Слабка» якість після квантизації. Часто проблема не в квантизації, а в тому, що ви забули вказати --chat-template або використовуєте дефолтний, який не збігається з training-template моделі. Спершу перевірте якість на FP16 з тим самим темплейтом.
Drift у latency через 2 тижні після релізу. Зазвичай, заповнення префікс-кешу різноманітними системними промптами через мульті-тенантність. Рішення: hash-routing запитів за system_prompt на різні репліки.
Streaming працює локально, але ламається через nginx/ALB. SSE-з'єднання часто закриваються проксі через ідл-таймаут. Поставте proxy_buffering off в nginx або відповідні налаштування на ALB.
Гайди радять --enforce-eager для дебагу, і люди забувають вимкнути на проді. Це повністю вимикає CUDA graphs і ріже throughput на 20–30%. Перевірте свою команду запуску.
Для глибшого занурення у патерни деплою рекомендую почати з офіційної документації vLLM та release notes на GitHub: ядро еволюціонує дуже швидко, і кожен мінорний реліз приносить як нові фічі, так і ребрейкінги внутрішнього API.
Поширені запитання
vLLM кращий за llama.cpp для продакшну?
Так, якщо у вас є GPU. llama.cpp оптимізований під CPU та Apple Silicon з GGUF-квантизаціями і відмінний для edge/локальних кейсів, але на серверних GPU vLLM дає у 5–15 разів вищий throughput через PagedAttention і continuous batching. Для продакшн-сервісу з декількома GPU вибір однозначний на користь vLLM.
Скільки GPU-пам'яті потрібно для запуску Llama-3.1-70B?
У bfloat16, мінімум 140 GB лише на ваги, тобто 2× A100 80GB або 2× H100 80GB з tensor parallelism. З AWQ-квантизацією на 4 біти ваги вміщуються в одну A100 80GB, але реалістично потрібно дві, щоб залишити VRAM під KV-cache при максимальному контексті 16k+. З FP8 на H100 одна H100 80GB цілком справляється з робочим max_model_len 8192.
Чи можна запускати vLLM без GPU?
Є експериментальна CPU-збірка (VLLM_TARGET_DEVICE=cpu), але вона у 50–200 разів повільніша за GPU-варіант і призначена тільки для smoke-тестів CI. Для серйозного інференсу на CPU краще використовувати llama.cpp або OpenVINO. AMD MI300, Intel Gaudi 2/3 та AWS Inferentia 2 підтримуються через окремі бекенди vLLM.
Як vLLM обробляє декілька LoRA-адаптерів одночасно?
Через прапорці --enable-lora та --max-loras N ви можете тримати в пам'яті N різних LoRA-адаптерів і вибирати потрібний у запиті полем model. vLLM батчить запити з різними LoRA в один проход через базову модель, що дешевше, ніж тримати окрему репліку на кожен fine-tune. Реалістично N = 4–16 на одну GPU без помітної просадки latency.
Що краще використовувати з vLLM, Ray Serve чи Kubernetes напряму?
Для команд, які вже на K8s, простіше деплоїти vLLM як звичайний Deployment з GPU-resources і HPA на queue-depth метрику. Ray Serve має сенс, якщо у вас гетерогенний інференс-стек (vLLM + embedding-моделі + reranker в одному пайплайні) і вам потрібен composition. KServe, це компроміс із готовими CRD для канаркових деплоїв і traffic-splitting, який я б порекомендував командам, що хочуть «продакшн з коробки».
Практичний посібник з Apache Arrow і PyArrow 18 у Python: zero-copy конверсія з pandas та Polars, робота з Parquet, Compute API, Arrow Flight RPC і ADBC.
Повний практичний посібник з DuckDB 1.5 у Python: встановлення, zero-copy інтеграція з pandas і Polars, запити над Parquet/CSV/JSON, AsOf joins і бенчмарки 2026 року.
Повний практичний посібник з інженерії ознак у Python: числові трансформації, кодування категорій, обробка пропусків, автоматизація з feature-engine та Featuretools, побудова пайплайнів scikit-learn 1.8.