Оптимізація інференсу LLM за допомогою vLLM
vLLM - найбільш популярний open-source двигун для високопродуктивного LLM-інференсу. Ключове нововведення - PagedAttention: управління KV-кешем за аналогією з віртуальною пам'яттю ОС, що усуває фрагментацію і збільшує через 15-24 рази в порівнянні з наївною реалізацією.
Чому стандартний HuggingFace transformers недостатній
HF transformers добре для експериментів, погано для production:
- Кожен запит обробляється незалежно – немає батчингу запитів
- KV-кеш зберігається повністю для кожної послідовності - VRAM витрачається неефективно
- Ні prefill/decode поділу
- Throughput: ~10–50 tokens/sec на одному запиті
vLLM на тих же GPU: 500-2000 tokens/sec через concurrent batching.
Базове розгортання vLLM
# Установка
pip install vllm
# Запуск сервера (OpenAI-совместимый API)
python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mistral-7B-Instruct-v0.3 \
--tensor-parallel-size 1 \ # для одной GPU
--max-model-len 8192 \
--max-num-seqs 256 \ # максимальный concurrent batch
--gpu-memory-utilization 0.90 \ # 90% VRAM для модели
--host 0.0.0.0 \
--port 8000
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
response = client.chat.completions.create(
model="mistralai/Mistral-7B-Instruct-v0.3",
messages=[{"role": "user", "content": "Explain transformer attention"}],
max_tokens=500,
temperature=0.7
)
PagedAttention: як це працює
Стандартний KV-кеш: для кожної послідовності виділяється безперервний VRAM блок на максимальну довжину. Фрагментація між послідовностями втрачається до 60% пам'яті.
PagedAttention: KV-кеш розбивається на сторінки фіксованого розміру (зазвичай 16 токенів). Сторінки виділяються на вимогу, можуть бути несуміжними. Prefix sharing: якщо дві послідовності мають загальний prefix (system prompt), сторінки з prefix'ом використовуються спільно - економія VRAM при однакових system prompts.
Tensor Parallelism для великих моделей
# LLaMA-70B на 4xA100 80GB
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-70b-instruct \
--tensor-parallel-size 4 \ # шардирование по 4 GPU
--dtype bfloat16 \
--max-model-len 16384 \
--gpu-memory-utilization 0.95
Tensor parallelism розбиває матриці attention heads і FFN по GPU. Для 70B моделі: 4xA100 80GB = достатньо у BF16.
Quantization для економії VRAM
# AWQ квантизация (лучшее качество среди 4-bit методов)
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Mistral-7B-Instruct-v0.3-AWQ \
--quantization awq \
--dtype auto
# GPTQ
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Llama-2-13B-GPTQ \
--quantization gptq
# FP8 (для H100)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-8b \
--quantization fp8
Результат: 7B модель в AWQ 4-bit займає ~4 GB VRAM замість ~14 GB у BF16.
Speculative Decoding
Прискорення декодування через draft model: маленька модель (draft) генерує кілька токенів, велика (target) верифікує їх паралельно. При збігу приймаємо всі токени за один forward pass.
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-70b-instruct \
--speculative-model meta-llama/Llama-3-8b-instruct \
--num-speculative-tokens 5 \
--tensor-parallel-size 4
Приріст: 1.5-2.5x speedup для типових текстів при <1% зміні якості.
тюнінг продуктивності
# Параметры для максимального throughput (не latency)
VLLM_CONFIG = {
"max_num_seqs": 512, # больше concurrent запросов
"max_num_batched_tokens": 32768, # токены в одном forward pass
"block_size": 32, # размер страницы KV-кеша
"swap_space": 4, # GB для CPU offload при VRAM OOM
}
# Параметры для минимальной latency (не throughput)
VLLM_CONFIG_LATENCY = {
"max_num_seqs": 32,
"max_num_batched_tokens": 4096,
"disable_async_output_proc": False,
}
Бенчмарк продуктивності
На одному A100 80GB, Mistral-7B-Instruct, 500-токенні відповіді:
| Реалізація | Скрізьput (req/s) | P99 Latency |
|---|---|---|
| HF transformers (batch=1) | 1.2 | 8.5s |
| HF transformers (batch=16) | 4.1 | 22s |
| vLLM (256 concurrent) | 28.5 | 12s |
| vLLM + AWQ 4-bit | 52.3 | 7s |







