Бенчмаркинг LLM Serving: методология и метрики¶
~7 минут чтения
Как правильно измерять производительность LLM в продакшене. Метрики (TTFT, TPOT, throughput, SLO attainment), методология, инструменты (llmperf, genai-perf), типичные ошибки
Предварительно: Оптимизация инференса LLM, Сравнение движков инференса
Зачем это нужно. Неправильный бенчмарк стоит денег: выбор движка на основе теста с concurrency=1 вместо реальных 100 может привести к 3-5x перерасходу на GPU ($50K-200K/год на одном кластере). BentoML в 2024 показал, что их движок "на 40% быстрее vLLM" -- но при concurrency=1 и 128 output tokens. При реалистичной нагрузке (concurrency=32, variable lengths) разница была <5%. Правильная методология бенчмаркинга -- это не "запустить curl", а инженерная дисциплина с warmup, sweep по concurrency, и обязательным SLO attainment rate.
Зачем отдельная статья про бенчмарки?¶
Выбрать движок инференса (vLLM, SGLang, TRT-LLM) -- полдела. Правильно измерить его производительность -- вторая половина, и именно тут большинство ошибается. Два инженера могут получить противоположные результаты на одном и том же движке, потому что:
- Один тестировал с concurrency=1, другой с 100
- Один считал throughput по output tokens, другой включал input
- Один разогрел CUDA, другой нет
Бенчмаркинг LLM serving -- это не time curl. Это инженерная дисциплина.
Ключевые метрики¶
Latency-метрики¶
| Метрика | Что измеряет | Почему важна | Target (production) |
|---|---|---|---|
| TTFT (Time to First Token) | Задержка до первого токена | UX: пользователь видит "ответ начался" | < 200ms (chat), < 500ms (batch) |
| TPOT (Time Per Output Token) | Среднее время генерации одного токена | Скорость "печати" ответа | < 30ms (40+ tok/s для чтения) |
| E2E Latency (p50/p95/p99) | Полная задержка от запроса до последнего токена | Хвостовые задержки убивают SLO | p99 < 5s для 512-token ответа |
| ITL (Inter-Token Latency) | Разброс между токенами | "Заикания" в стриминге | Стабильный, без spikes |
Связь метрик
Для запроса с \(n\) output tokens:
Пример: TTFT=150ms, TPOT=25ms, n=200 tokens:
$$E2E = 150 + 199 \times 25 = 5125\text{ms} \approx 5.1\text{s}$$
TTFT доминирует при коротких ответах, TPOT -- при длинных.
Throughput-метрики¶
| Метрика | Формула | Типичные значения (A100, Llama 70B) |
|---|---|---|
| Output tokens/sec | \(\frac{\text{total output tokens}}{\text{wall time}}\) | 500-2000 tok/s (batch) |
| Requests/sec | \(\frac{\text{completed requests}}{\text{wall time}}\) | 5-50 req/s |
| Effective throughput | Output tokens/sec при соблюдении SLO | Меньше raw throughput на 10-30% |
Business-метрика: SLO Attainment Rate¶
Самая важная метрика для продакшена -- какой процент запросов укладывается в SLO:
def slo_attainment(latencies: list[float], slo_ms: float) -> float:
"""Доля запросов, уложившихся в SLO."""
within_slo = sum(1 for lat in latencies if lat <= slo_ms)
return within_slo / len(latencies)
# Пример: SLO = 3 секунды для chat
latencies = [1.2, 2.5, 3.1, 0.8, 2.9, 4.2, 1.5, 2.0]
print(f"SLO attainment: {slo_attainment(latencies, 3.0):.0%}")
# SLO attainment: 62% -- ПЛОХО, нужно > 95%
Raw throughput vs Effective throughput
Движок может показывать 2000 tok/s throughput, но если 40% запросов нарушают SLO -- это бесполезно. Effective throughput считает только токены из запросов, которые уложились в SLO. Всегда спрашивайте: "при каком SLO attainment rate?"
Методология бенчмаркинга¶
Что нужно зафиксировать¶
Бенчмарк без фиксации условий -- не бенчмарк:
| Параметр | Что варьируют | Что фиксируют |
|---|---|---|
| Модель | Размер (7B/13B/70B) | Квантизация, формат весов |
| Hardware | GPU тип | Driver version, CUDA version |
| Workload | Concurrency, input/output длина | Распределение длин |
| Engine | Версия, конфигурация | Все параметры (batch size, cache) |
| Warmup | -- | Обязательно 10-50 запросов |
Паттерны нагрузки¶
Реальный трафик -- не постоянный поток одинаковых запросов:
import numpy as np
# Паттерн 1: Чат (короткие in/out)
chat_workload = {
"input_len": np.random.lognormal(mean=5, sigma=0.8, size=1000).astype(int), # ~150 tokens median
"output_len": np.random.lognormal(mean=4, sigma=0.7, size=1000).astype(int), # ~55 tokens median
"concurrency": 50,
}
# Паттерн 2: Code generation (короткий in, длинный out)
code_workload = {
"input_len": np.random.lognormal(mean=5.5, sigma=0.5, size=1000).astype(int), # ~245 tokens
"output_len": np.random.lognormal(mean=6, sigma=0.8, size=1000).astype(int), # ~400 tokens
"concurrency": 20,
}
# Паттерн 3: RAG/Summarization (длинный in, короткий out)
rag_workload = {
"input_len": np.random.lognormal(mean=7.5, sigma=0.5, size=1000).astype(int), # ~1800 tokens
"output_len": np.random.lognormal(mean=4.5, sigma=0.5, size=1000).astype(int), # ~90 tokens
"concurrency": 30,
}
Ключевой инсайт: движок, который лидирует на chat workload, может проигрывать на RAG. Бенчмарк на одном паттерне -- не бенчмарк.
Инструменты¶
1. genai-perf (NVIDIA)¶
Стандарт для бенчмаркинга OpenAI-compatible API:
# Установка
pip install genai-perf
# Бенчмарк vLLM-сервера
genai-perf profile \
-m meta-llama/Llama-3.1-8B-Instruct \
--service-kind openai \
--endpoint-type chat \
--streaming \
--concurrency 32 \
--measurement-interval 60000 \
--url localhost:8000
Выдает: TTFT (p50/p95/p99), TPOT, throughput, request latency distribution.
2. llmperf (Anyscale)¶
Open-source, фокус на SLO attainment:
pip install llmperf
python -m llmperf.token_benchmark_ray \
--model meta-llama/Llama-3.1-8B-Instruct \
--mean-input-tokens 256 \
--stddev-input-tokens 64 \
--mean-output-tokens 128 \
--stddev-output-tokens 32 \
--num-concurrent-requests 32 \
--max-num-completed-requests 500 \
--llm-api openai
3. Своя нагрузка (для нестандартных сценариев)¶
import asyncio
import time
import aiohttp
from dataclasses import dataclass
@dataclass
class RequestMetrics:
ttft: float
tpot: float
total_tokens: int
e2e_latency: float
async def benchmark_request(
session: aiohttp.ClientSession,
url: str,
prompt: str,
max_tokens: int,
) -> RequestMetrics:
start = time.perf_counter()
first_token_time = None
token_count = 0
payload = {
"model": "llama-3.1-8b",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"stream": True,
}
async with session.post(url, json=payload) as resp:
async for line in resp.content:
decoded = line.decode().strip()
if decoded.startswith("data: ") and decoded != "data: [DONE]":
if first_token_time is None:
first_token_time = time.perf_counter()
token_count += 1
end = time.perf_counter()
ttft = first_token_time - start if first_token_time else end - start
generation_time = end - first_token_time if first_token_time else 0
tpot = generation_time / max(token_count - 1, 1)
return RequestMetrics(
ttft=ttft,
tpot=tpot,
total_tokens=token_count,
e2e_latency=end - start,
)
Benchmark Results (2025-2026)¶
Throughput при разной concurrency (Llama 3.1 8B, A100 80GB)¶
| Concurrency | vLLM 0.6+ | SGLang 0.4+ | TRT-LLM | TGI 2.x |
|---|---|---|---|---|
| 1 | 85 tok/s | 90 tok/s | 105 tok/s | 70 tok/s |
| 8 | 550 tok/s | 580 tok/s | 620 tok/s | 420 tok/s |
| 32 | 1800 tok/s | 1950 tok/s | 1700 tok/s | 1200 tok/s |
| 128 | 2400 tok/s | 2500 tok/s | degraded | 1500 tok/s |
Паттерн: TRT-LLM лидирует при низкой concurrency (оптимизированные CUDA kernels). SGLang и vLLM обгоняют при высокой (лучший scheduling, continuous batching).
TTFT (p50, ms)¶
| Concurrency | vLLM | SGLang | TRT-LLM | TGI |
|---|---|---|---|---|
| 1 | 45 | 40 | 30 | 55 |
| 32 | 120 | 100 | 180 | 150 |
| 128 | 250 | 280 | timeout | 400 |
Decision Framework¶
graph TD
START["Выбор движка инференса"] --> LAT{"Главный приоритет?"}
LAT -->|"Min latency<br/>(concurrency < 16)"| TRT["TensorRT-LLM"]
LAT -->|"Max throughput<br/>(concurrency 32+)"| THROUGHPUT{"Structured output?"}
LAT -->|"Простота деплоя"| SIMPLE{"Нужен Enterprise?"}
THROUGHPUT -->|"Да, JSON/tools"| SGL["SGLang"]
THROUGHPUT -->|"Нет, general"| VLLM["vLLM"]
SIMPLE -->|"Да"| TGI["TGI / Inference Endpoints"]
SIMPLE -->|"Нет"| VLLM2["vLLM"]
style START fill:#e8eaf6,stroke:#3f51b5
style TRT fill:#fff3e0,stroke:#ef6c00
style SGL fill:#e8f5e9,stroke:#4caf50
style VLLM fill:#e8f5e9,stroke:#4caf50
style TGI fill:#f3e5f5,stroke:#9c27b0
style VLLM2 fill:#e8f5e9,stroke:#4caf50
Типичные ошибки бенчмаркинга¶
Бенчмарк без warmup
Первые 10-50 запросов к LLM движку значительно медленнее: JIT-компиляция CUDA kernels, заполнение KV cache, инициализация memory pools. Без warmup TTFT может быть в 5-10x хуже реального. Всегда делайте warmup и отбрасывайте первые запросы.
Тестирование при concurrency=1
Реальный production трафик -- десятки-сотни параллельных запросов. При concurrency=1 выигрывает движок с лучшей single-request latency (TRT-LLM), но при concurrency=100 лидер может смениться (vLLM, SGLang). Тестируйте при реалистичной нагрузке.
Фиксированные input/output длины
Запросы "Hello, tell me about X" -> 128 tokens -- нерелевантный бенчмарк. Реальные распределения длин лог-нормальные с длинным хвостом. Используйте вариацию: mean +/- 2*stddev.
Как читать чужие бенчмарки¶
Чеклист при чтении benchmark report:
- Кто автор? Бенчмарк от BentoML может быть предвзят к BentoML. Проверяйте independent sources.
- Какая версия? vLLM 0.3 vs 0.6 -- разные движки. Результаты устаревают за месяцы.
- Какой workload? Один input/output length vs распределение. Один concurrency vs sweep.
- Warmup? Если не указано -- скорее всего нет, и числа хуже реальных.
- Hardware? A100 40GB vs 80GB -- разные результаты. H100 vs A100 -- другая лига.
- Quantization? FP16 vs INT8 vs INT4 -- несопоставимо напрямую.
Самопроверка
Вам показывают бенчмарк: "Наш движок X на 40% быстрее vLLM". Input: 128 tokens, output: 128 tokens, concurrency: 1, GPU: H100, модель: Llama 3.1 8B.
Какие вопросы вы зададите? (Подумайте перед тем как читать дальше.)
Ответ: (1) Какая версия vLLM? (2) Был ли warmup? (3) Что при concurrency=32/128? (4) А на 70B модели? (5) Какой TTFT vs throughput отдельно? (6) Какой SLO attainment?
Interview Questions¶
Q: Чем TTFT отличается от TPOT и когда каждая метрика важнее?
Red flag: "TTFT -- время первого токена, TPOT -- время каждого токена. TTFT для чата, TPOT для батчей."
Strong answer: "TTFT (Time to First Token) -- задержка до первого токена, определяет UX восприятие: пользователь видит 'ответ начался'. TPOT (Time Per Output Token) -- средняя скорость генерации, определяет скорость 'печати'. Формула связи: E2E = TTFT + (n-1) * TPOT. Для chat c коротким ответом (50 tokens) доминирует TTFT -- target < 200ms. Для batch/long generation (500+ tokens) доминирует TPOT -- target < 30ms (40+ tok/s для комфортного чтения). TTFT зависит от prefill phase, TPOT -- от decode phase. Разные фазы -> разные bottlenecks -> разные оптимизации."
Q: Что такое effective throughput и почему он важнее raw throughput?
Red flag: "Effective throughput -- это throughput с учетом ошибок."
Strong answer: "Effective throughput считает только токены из запросов, уложившихся в SLO. Движок может показывать 2000 tok/s raw throughput, но если 40% запросов нарушают SLO (>3s latency) -- effective throughput = 1200 tok/s. При 50% SLO attainment effective = 50% от raw. Это ключевая business-метрика: пользователю всё равно, что движок 'в среднем быстрый', если его конкретный запрос зависает на 10 секунд."
Q: Как бы вы спроектировали бенчмарк для выбора движка инференса?
Red flag: "Отправить 1000 одинаковых запросов и замерить среднее время."
Strong answer: "5 шагов: (1) Определить реальный workload -- распределение input/output длин (лог-нормальное, не фиксированное), concurrency pattern. (2) Зафиксировать условия: GPU, driver, CUDA version, engine version, quantization. (3) Warmup 10-50 запросов, отбросить. (4) Sweep по concurrency (1, 8, 32, 128) -- лидер может меняться. (5) Мерить TTFT p50/p95/p99 + TPOT + throughput + SLO attainment. Минимум 3 прогона для статистической значимости. Обязательно тестировать на своем реальном workload, а не на синтетическом 'Hello, tell me about X -> 128 tokens'."
Q: Напишите функцию, которая вычисляет SLO attainment rate и effective throughput.
Red flag: Считает только средний latency без SLO проверки.
Strong answer: "Функция принимает список RequestMetrics (ttft, tpot, total_tokens, e2e_latency) и SLO порог. SLO attainment = доля запросов с e2e_latency <= SLO. Effective throughput = сумма total_tokens только для запросов в SLO, деленная на wall time. Production target: SLO attainment >= 95%, иначе нужно снижать concurrency или масштабировать GPU."
Заблуждение: throughput в tokens/sec -- универсальная метрика
Разные бенчмарки считают throughput по-разному: одни включают input tokens, другие только output. Output-only throughput в 2-3x ниже total throughput на одном и том же движке. Кроме того, throughput при batch size=1 и при batch size=128 -- разные числа в 10-20x. Всегда уточняйте: output-only или total, при какой concurrency, с каким SLO attainment rate.
Заблуждение: один бенчмарк -- один winner
Движок, лидирующий на chat workload (короткий in/out, concurrency=50), может проигрывать на RAG workload (длинный input, короткий output, concurrency=30). TRT-LLM лидирует при concurrency < 16, но SGLang обгоняет при 32+. Бенчмарк на одном паттерне нагрузки не дает ответа "какой движок лучше" -- только "какой лучше для этого конкретного сценария".
See Also¶
- Движки инференса LLM -- детальное сравнение vLLM, SGLang, TRT-LLM, llama.cpp
- vLLM и PagedAttention -- архитектура PagedAttention и continuous batching
- Квантизация LLM -- INT8/INT4, GPTQ, AWQ, GGUF -- влияние на throughput
- Продакшен деплой LLM -- от бенчмарка к production deployment
Sources¶
- genai-perf Documentation -- NVIDIA
- llmperf -- Anyscale/Ray
- BentoML LLM Benchmark -- June 2024
- SGLang Benchmark Report -- LMSYS, 2024
- Anyscale LLMPerf Leaderboard -- comparative results