vLLM: высокопроизводительный инференс LLM для production
Введение: Когда локальная LLM нужна не для прототипа, а для тысяч пользователей
Ollama идеальна для разработки и прототипирования. llama.cpp — для встраивания в приложения. Но что если вам нужно обслуживать 1000+ запросов в секунду с локальной LLM? Что если latency должна быть предсказуемой, а throughput — максимальным?
Для этого создан vLLM — open-source движок инференса, разработанный в UC Berkeley. Он использует PagedAttention, speculative decoding и продвинутый менеджмент памяти для достижения throughput, который в 24x выше, чем у базовых реализаций.
В этой статье — полное руководство по vLLM: от установки до production-развёртывания, с бенчмарками, настройками и сравнением с альтернативами.
Что такое vLLM и почему он быстрее всех
vLLM — это движок для быстрого инференса и развёртывания LLM, который объединяет несколько ключевых оптимизаций:
- PagedAttention — аналог виртуальной памяти из ОС, но для KV-кэшей генерации. Разделяет память эффективно, без фрагментации.
- Continuous batching — новые запросы добавляются во время генерации других, а не ждут завершения.
- Optimized CUDA kernels — кастомные ядра для операций внимания.
- Speculative decoding — использование маленькой модели для предложения токенов, которые проверяются большой.
- Streaming decoding — генерация по несколько токенов за итерацию.
# Установка
pip install vllm
# Запуск сервера с Llama 3.1 8B
python -m vllm.entrypoints.api_server --model meta-llama/Llama-3.1-8B-Instruct
# Готово. API на порту 8000.
В чём фишка: vLLM не просто «ещё один сервер для LLM». Он пересматривает саму архитектуру управления памятью при генерации, что даёт кратный прирост throughput без потери качества генерации.
Архитектура: как работает PagedAttention
Проблема традиционного подхода
При генерации текста LLM хранит KV-кэш (key-value cache) для каждого токена. Традиционные системы:
- Выделяют максимально возможный размер контекста для каждого запроса
- Не разделяют память между запросами
- Теряют до 60% памяти на фрагментацию
Решение: PagedAttention
vLLM применяет идеи из операционных систем:
| ОС | vLLM |
|---|---|
| Виртуальная память | KV-кэш токенов |
| Страницы (pages) | Blocks KV-кэша (16 токенов) |
| Page Table | Маппинг блоков для каждого запроса |
| Shared memory | Shared prefixes для prompt batching |
Обычная система:
Request 1: [block1][block2][block3][block4][____][____] ← 50% потеря
Request 2: [block5][block6][block7][block8][____][____] ← 50% потеря
Request 3: [block9][block10][block11][block12][____][____] ← 50% потеря
vLLM с PagedAttention:
Request 1: [block1][block2][block3][block4][block13]
Request 2: [block5][block6][block7][block8][block14]
Request 3: [block9][block10][block11][block12][block15]
← 0% потерь, идеальная упаковка
Это даёт:
- До 24x больше throughput по сравнению с базовыми реализациями
- До 5x меньше памяти для KV-кэша
- Предсказуемую latency
Установка
Требования
| Компонент | Минимум | Рекомендовано |
|---|---|---|
| GPU | 1x NVIDIA (8GB VRAM для 7B) | 1x A100/H100 для 70B |
| CUDA | 11.8 | 12.x |
| Python | 3.9 | 3.11 |
| RAM | 16 GB | 64 GB |
| Disk | 10 GB | 100 GB (для кэша моделей) |
Установка через pip
# Базовая установка
pip install vllm
# С конкретной версией PyTorch
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu121
# Установка с Docker (рекомендуется для production)
docker pull vllm/vllm-openai:latest
Проверка установки
python -c "import vllm; print(vllm.__version__)"
# Проверка GPU
python -c "from vllm import LLM; print('vLLM ready')"
Docker-образ
docker run -d --gpus all \
-p 8000:8000 \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--name vllm-server \
vllm/vllm-openai:latest \
--model meta-llama/Llama-3.1-8B-Instruct
Запуск: от одной модели до кластера
Базовый запуск
# Запуск с HuggingFace моделью
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct
# С кастомным weight-путём
python -m vllm.entrypoints.api_server \
--model /data/models/llama-3.1-8b \
--host 0.0.0.0 \
--port 8000
Ключевые параметры запуска
| Параметр | По умолчанию | Описание |
|---|---|---|
--model |
(required) | Имя модели HF или путь |
--tensor-parallel-size |
1 | Количество GPU (tensor parallel) |
--gpu-memory-utilization |
0.9 | Доля VRAM для модели (0-1) |
--max-model-len |
None | Максимальная длина контекста |
--max-num-seqs |
256 | Максимум параллельных запросов |
--quantization |
None | Метод квантования (awq, gptq, bitsandbytes) |
--dtype |
auto | Тип данных (float16, bfloat16, float32) |
--swap-space |
4 | Размер CPU swap для памяти (GB) |
--enable-chunked-prefill |
False | Chunked prefill для длинных промптов |
--speculative-model |
None | Модель для speculative decoding |
Пример: оптимизированный запуск для 8B на A100
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--max-num-seqs 256 \
--dtype bfloat16 \
--enable-chunked-prefill
Пример: 70B на 4x A100
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.95 \
--max-model-len 4096 \
--max-num-seqs 128 \
--dtype bfloat16
API: OpenAI-совместимый интерфейс
vLLM предоставляет API, полностью совместимый с OpenAI Chat Completions.
Chat Completions
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [
{"role": "system", "content": "Ты технический эксперт."},
{"role": "user", "content": "Объясни, что такое PagedAttention"}
],
"temperature": 0.7,
"max_tokens": 512
}'
Stream-режим
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [
{"role": "user", "content": "Напиши статью о квантовых вычислениях"}
],
"stream": true,
"max_tokens": 1024
}'
Completions API (старый формат)
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"prompt": "Преимущества микросервисной архитектуры:",
"max_tokens": 256,
"temperature": 0.5
}'
List Models
curl http://localhost:8000/v1/models
{
"object": "list",
"data": [
{
"id": "meta-llama/Llama-3.1-8B-Instruct",
"object": "model",
"created": 1714432703,
"owned_by": "meta"
}
]
}
Embeddings (через API)
curl http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"input": ["Первый текст", "Второй текст"]
}'
Python SDK: интеграция с приложениями
Использование с OpenAI SDK
from openai import OpenAI
# Указываем на vLLM сервер
client = OpenAI(
base_url="http://localhost:8000/v1/",
api_key="token-abc123" # любое значение
)
# Базовый запрос
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "user", "content": "Напиши функцию quicksort на Rust"}
],
temperature=0.2,
max_tokens=512
)
print(response.choices[0].message.content)
Stream-обработка
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1/", api_key="token-abc123")
stream = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": "Объясни трансформеры"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
Прямое использование vLLM Python API
from vllm import LLM, SamplingParams
# Инициализация LLM
llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
# Параметры генерации
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512,
stop=["### END"]
)
# Генерация
prompts = [
"Что такое PagedAttention?",
"Как работает continuous batching?",
"Сравните vLLM с Ollama"
]
outputs = llm.generate(prompts, sampling_params)
# Вывод результатов
for prompt, output in zip(prompts, outputs):
print("=" * 50)
print(f"Prompt: {prompt}")
print(f"Output: {output.outputs[0].text}")
print()
Продвинное использование: кастомные параметры
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-70B-Instruct",
tensor_parallel_size=4,
gpu_memory_utilization=0.95,
max_model_len=8192,
dtype="bfloat16"
)
sampling_params = SamplingParams(
temperature=0.3,
top_p=0.85,
top_k=20,
max_tokens=1024,
min_tokens=10,
stop=["USER:", "ASSISTANT:"],
presence_penalty=0.1,
frequency_penalty=0.1,
repetition_penalty=1.05,
use_beam_search=False,
n=1 # количество генераций на запрос
)
# Batch-обработка с разными параметрами
batch = [
{"prompt": "Код для бинарного поиска на Python", "params": {"temperature": 0.1, "max_tokens": 256}},
{"prompt": "Креативная история про ИИ", "params": {"temperature": 0.9, "max_tokens": 512}},
]
for item in batch:
outputs = llm.generate(item["prompt"], SamplingParams(**item["params"]))
print(outputs[0].outputs[0].text)
Speculative Decoding: ускорение генерации
Speculative decoding использует маленькую «assistant» модель для предложения токенов, которые затем проверяются большой «target» модельой.
# Speculative decoding с LM-ассистентом
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--speculative-model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 4
# Speculative decoding с Medusa-ассистентом
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--speculative-model TheBloke/Llama-2-70B-Medusa \
--tensor-parallel-size 4
# Speculative decoding с n-gram
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--ngram-prompt-max-len 128
Ускорение
| Конфигурация | Ускорение |
|---|---|
| 70B + 8B speculative | 1.5-2x |
| 70B + Medusa | 1.8-2.5x |
| 70B + n-gram | 1.2-1.5x |
Квантование в vLLM
vLLM поддерживает несколько методов квантования для уменьшения размера модели и ускорения инференса.
AWQ (Activation-aware Weight Quantization)
# Запуск AWQ-модели
python -m vllm.entrypoints.api_server \
--model TheBloke/Llama-2-7B-AWQ \
--quantization awq
GPTQ
# Запуск GPTQ-модели
python -m vllm.entrypoints.api_server \
--model TheBloke/Llama-2-7B-GPTQ \
--quantization gptq
Bitsandbytes (INT8 / FP8)
# INT8 квантование
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--quantization bitsandbytes \
--load-bytes 8
# FP8 квантование (H100/A100)
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--quantization fp8
Сравнение квантования
| Метод | Размер (8B) | Качество | Скорость | Требование |
|---|---|---|---|---|
| F16 (baseline) | 16 GB | 100% | 1x | — |
| INT4 (AWQ/GPTQ) | 5 GB | 97-99% | 1.5-2x | Квантованная модель |
| INT8 (bitsandbytes) | 8 GB | 99-100% | 1.2x | bitsandbytes |
| FP8 | 8 GB | 98-99% | 1.3-1.5x | Hopper/Ampere GPU |
Бенчмарки и производительность
Сравнение throughput: vLLM vs другие движки
Тест: Llama 3.1 8B, batch size = 128, A100-80GB
| Движок | Throughput (req/s) | Latency (p99, ms) |
|---|---|---|
| vLLM | 3,200 | 450 |
| Ollama | 800 | 1200 |
| llama.cpp server | 650 | 1500 |
| HuggingFace TGI | 2,100 | 600 |
| Text Generation Inference | 2,800 | 500 |
Сравнение: vLLM vs другие для 70B моделей
Тест: Llama 3.1 70B, 4x A100, batch size = 64
| Движок | Throughput (req/s) | VRAM usage |
|---|---|---|
| vLLM | 1,800 | 65 GB |
| TGI | 1,200 | 72 GB |
| Ollama | 200 | 70 GB |
Мониторинг метрик
from vllm.engine.arg_utils import AsyncEngineArgs
from vllm.engine.async_llm_engine import AsyncLLMEngine
# vLLM предоставляет встроенные метрики
# Prometheus-совместимые метрики доступны на /metrics
# Основные метрики:
# - vllm:cpu_cache_usage_perc
# - vllm:gpu_cache_usage_perc
# - vllm:prompt_tokens_total
# - vllm:generation_tokens_total
# - vllm:time_to_first_token_seconds
# - vllm:time_per_output_token_seconds
# - vllm:e2e_request_latency_seconds
# - vllm:request_success
Production-развёртывание
Docker Compose
version: '3.8'
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm-server
ports:
- "8000:8000"
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
volumes:
- model-cache:/root/.cache/huggingface
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
command: >
--model meta-llama/Llama-3.1-8B-Instruct
--tensor-parallel-size 1
--gpu-memory-utilization 0.9
--max-model-len 8192
--max-num-seqs 256
restart: unless-stopped
# Reverse proxy для балансировки
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- vllm
restart: unless-stopped
volumes:
model-cache:
Nginx конфигурация для балансировки
upstream vllm_servers {
server vllm1:8000;
server vllm2:8000;
server vllm3:8000;
}
server {
listen 80;
location / {
proxy_pass http://vllm_servers;
proxy_set_header Host $host;
proxy_read_timeout 300s;
proxy_buffering off; # Для stream
}
}
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-server
spec:
replicas: 1
template:
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args:
- --model
- meta-llama/Llama-3.1-70B-Instruct
- --tensor-parallel-size
- "4"
- --gpu-memory-utilization
- "0.95"
resources:
limits:
nvidia.com/gpu: 4
ports:
- containerPort: 8000
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm-server
ports:
- port: 8000
targetPort: 8000
type: LoadBalancer
Health Checks
# Проверка здоровья сервера
curl http://localhost:8000/health
# Возвращает:
# {"status": "ok"}
# Проверка доступных моделей
curl http://localhost:8000/v1/models
vLLM как бэкенд для RAG-систем
С LangChain
from langchain_community.llms import VLLM
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import SentenceTransformerEmbeddings
from langchain.chains import RetrievalQA
# vLLM как LLM
llm = VLLM(
model="meta-llama/Llama-3.1-8B-Instruct",
trust_remote_code=True, # обязательно для vLLM
max_tokens=512,
temperature=0.1
)
# Векторная БД
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
vectorstore = Chroma(persist_directory="./rag_db", embedding_function=embeddings)
# RAG
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 4, "score_threshold": 0.65}
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
chain_type="stuff"
)
answer = qa_chain.invoke("Какие требования к GPU для 70B моделей?")
print(answer["result"])
С FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from vllm import LLM, SamplingParams
from typing import List, Optional
app = FastAPI(title="vLLM API Gateway")
# Инициализация
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
tensor_parallel_size=1,
gpu_memory_utilization=0.9
)
class ChatRequest(BaseModel):
messages: List[dict]
temperature: float = 0.7
max_tokens: int = 512
stream: bool = False
class ChatResponse(BaseModel):
content: str
tokens_used: int
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
try:
# Формируем промпт
prompt = "\n".join(
f"{m['role']}: {m['content']}" for m in request.messages
)
sampling_params = SamplingParams(
temperature=request.temperature,
max_tokens=request.max_tokens
)
output = llm.generate(prompt, sampling_params)
content = output.outputs[0].text
return ChatResponse(
content=content,
tokens_used=len(output.outputs[0].token_ids)
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/stats")
async def stats():
"""Метрики сервера"""
return {
"gpu_cache_usage": llm.get_cache_block_size(),
"model": llm.llm_engine.model_config.model
}
Много-GPU развёртывание
Tensor Parallelism
Для больших моделей (30B+) vLLM использует tensor parallelism — разделение весов модели между GPU.
# 70B модель на 4 GPU
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4
# 405B модель на 8 GPU (нужен A100-80GB или H100)
python -m vllm.entrypoints.api_server \
--model meta-llama/Meta-Llama-3.1-405B-Instruct \
--tensor-parallel-size 8 \
--dtype bfloat16
Pipeline Parallelism
# Комбинация tensor + pipeline parallelism
python -m vllm.entrypoints.api_server \
--model meta-llama/Meta-Llama-3.1-405B-Instruct \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2
Распределённый режим (Ray)
# Запуск на нескольких узлах через Ray
export VLLM_HOST_IP=10.0.0.1
export OMP_NUM_THREADS=8
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--distributed-init-method tcp://10.0.0.1:25000 \
--tensor-parallel-size 4
Тонкая настройка
Оптимизация для низкой latency
# Для чат-ботов с низкой latency
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 2048 \
--max-num-seqs 512 \
--enable-chunked-prefill
Оптимизация для высокого throughput
# Для batch-обработки и API
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 8192 \
--max-num-seqs 1024 \
--enable-chunked-prefill
Работа с длинным контекстом
# Поддержка 32K контекста
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 32768 \
--gpu-memory-utilization 0.8
Cold Start оптимизация
from vllm import LLM
# Кэширование модели в памяти
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
enforce_eager=False, # Кэширует CUDA-графы для ускорения
max_num_batched_tokens=8192,
max_num_seqs=256
)
# enforce_eager=False: первый запрос медленный (граф), последующие — быстрые
# enforce_eager=True: все запросы одинаковой скорости (без оптимизации графов)
vLLM vs Ollama vs llama.cpp: сравнение
| Критерий | vLLM | Ollama | llama.cpp |
|---|---|---|---|
| Установка | pip / Docker | Одна команда | Компиляция |
| GPU поддержка | NVIDIA, AMD (ROCm) | NVIDIA, AMD, Apple Metal | NVIDIA, AMD, Apple Metal, CPU |
| Throughput | ⭐⭐⭐ Максимальный | Средний | Средний |
| Latency | Низкая | Низкая | Очень низкая (CPU) |
| Мульти-GPU | Tensor + Pipeline | Нет | Нет |
| Speculative decoding | Да | Нет | Нет |
| Квантование | AWQ, GPTQ, FP8, INT8 | GGUF (Q4-Q8) | GGUF (Q0-Q8) |
| API | OpenAI-совместимый | OpenAI-совместимый | OpenAI-совместимый |
| Production | Kubernetes, Docker | Docker, systemd | Сервер |
| Лучший для | High-throughput production | Разработка, прототип | Встраивание, edge |
Правило выбора:
- Production API с 100+ RPS? → vLLM
- Локальная разработка и тесты? → Ollama
- Встраивание в приложение или CPU-only? → llama.cpp
Типичные проблемы и решения
Проблема 1: «CUDA out of memory»
# Решение: уменьшите gpu_memory_utilization
python -m vllm.entrypoints.api_server \
--gpu-memory-utilization 0.8
# Или уменьшите max_model_len
--max-model-len 4096
# Или увеличьте swap space
--swap-space 16
Проблема 2: «Slow cold start»
# Решение: используйте enforce_eager=False (по умолчанию)
llm = LLM(
model="...",
enforce_eager=False # Кэширует CUDA графы
)
# Или используйте prefill caching
--enable-chunked-prefill
Проблема 3: «Модель не загружается с HF»
# Решение: используйте TheBloke версии (квантованные)
--model TheBloke/Llama-3.1-8B-Instruct-AWQ
# Или укажите HuggingFace token
export HUGGING_FACE_HUB_TOKEN=hf_xxxxxx
Проблема 4: «API отвечает 503»
# Сервер перегружен. Увеличьте max_num_seqs:
--max-num-seqs 512
# Или добавьте больше GPU через tensor_parallel_size
--tensor-parallel-size 2
Проблема 5: «FP8 не поддерживается»
# FP8 требует GPU архитектуры Hopper (H100) или Ampere (A100)
# Для других GPU используйте:
--quantization awq # INT4
--quantization gptq # INT4
--dtype bfloat16 # Если GPU поддерживает BF16
Экосистема vLLM
Инструменты
| Инструмент | Описание | Ссылка |
|---|---|---|
| vLLM TGI Bridge | Интеграция с HuggingFace TGI | github.com/vllm-project |
| Open WebUI | WebUI с поддержкой vLLM | github.com/open-webui |
| LangChain | Интеграция с RAG-фреймворками | docs.langchain.com |
| LlamaIndex | Интеграция с индексацией | docs.llamaindex.ai |
| Ray Serve | Масштабирование через Ray | docs.ray.io |
| Triton Inference Server | Деплой через NVIDIA Triton | github.com/triton-inference-server |
Мониторинг
# Prometheus метрики из vLLM
# endpoint: http://localhost:8000/metrics
# Основные метрики:
# vllm:gpu_cache_usage_perc — использование GPU памяти
# vllm:time_per_output_token_seconds — время на токен
# vllm:e2e_request_latency_seconds — общая latency
# vllm:request_success — успешные запросы
Заключение: vLLM — стандарт для production LLM serving
vLLM решает главную проблему production-инференса: как обслужить тысячи запросов с предсказуемой latency и максимальным throughput.
Ключевые выводы:
- PagedAttention — game changer. Пересмотр архитектуры управления памятью даёт кратный прирост.
- OpenAI-совместимый API. Любой клиент работает без изменений.
- Speculative decoding — для скорости. 1.5-2x