Метрики производительности LLM
Updated Jul 2026
Почему производительность LLM отличается
Функции на базе LLM вводят характеристики производительности, которые традиционное нагрузочное тестирование не учитывает. В отличие от REST API, который возвращает полный ответ за один обмен данными, эндпоинты LLM имеют переменное время ответа, ограничения пропускной способности на основе токенов, задержку холодного старта и лимиты запросов со стороны провайдера. QA-архитектор, применяющий традиционные перцентили задержки к эндпоинтам LLM, упустит метрики, которые действительно важны для пользователей.
Ключевые метрики производительности LLM
Основные метрики
| Метрика | Описание | Типичный диапазон | Почему это важно |
|---|---|---|---|
| Time to First Token (TTFT) | Задержка до генерации первого токена | 200 мс - 5 с | Воспринимаемая отзывчивость — пользователи замечают, когда начинается стриминг |
| Tokens per Second (TPS) | Пропускная способность генерации после первого токена | 30-100 ток/с | Пользовательский опыт при стриминге ответов |
| Общее время генерации | Сквозное время, включая все токены | 1-60 с | Планирование таймаутов запросов и определение SLO |
| Задержка холодного старта | Первый запрос после периода простоя (serverless) | 5-30 с | Планирование serverless-деплоя |
| Запас по лимиту запросов | Расстояние до лимита провайдера (токенов/мин или запросов/мин) | Зависит от тарифа | Ёмкость обработки пиковых нагрузок |
| Утилизация контекстного окна | Соотношение токенов промпта и завершения | 10-100% | Корреляция стоимости и задержки |
Вторичные метрики
| Метрика | Описание | Почему это важно |
|---|---|---|
| Соблюдение бюджета токенов | Процент ответов в пределах max_tokens | Предотвращает неконтролируемый рост затрат |
| Частота повторных попыток | Процент запросов, требующих повтора (429, 500, таймаут) | Надёжность сервиса |
| Частота переключения на резервного провайдера | Как часто система переключается на вторичного провайдера | Стабильность основного провайдера |
| Попадание в кеш | Процент запросов, обслуженных из семантического кеша | Оптимизация затрат и задержки |
| Частота обрыва стриминга | Процент потоков, отключённых до завершения | Надёжность сети |
Понимание TTFT и общего времени генерации
Request sent Response complete
| |
|--[TTFT]-->| |
| |---[Token streaming]--->|
| | |
| First token Last token
| |
|--------[Total Generation Time]---->|
TTFT определяет воспринимаемую отзывчивость. Пользователь, смотрящий на пустой экран 3 секунды, воспринимает систему как медленную, даже если полный ответ приходит за 5 секунд. При стриминге TTFT в 500 мс с последующими 4,5 секундами потоковой выдачи токенов воспринимается гораздо быстрее, чем 5-секундное ожидание полного ответа.
Практическое правило:
- TTFT < 1 с: пользователи воспринимают ответ как «мгновенный»
- TTFT 1-3 с: приемлемо с индикатором загрузки
- TTFT > 3 с: пользователи начинают терять внимание
Измерение метрик LLM
Измерение без стриминга
Для эндпоинтов без стриминга вы можете напрямую измерить только общее время генерации. TTFT приходится оценивать:
# llm_metrics_collector.py
import time
import json
from dataclasses import dataclass
@dataclass
class LLMMetrics:
ttft_ms: float
total_generation_ms: float
tokens_per_second: float
prompt_tokens: int
completion_tokens: int
total_tokens: int
model: str
def measure_non_streaming(client, prompt: str, model: str = "gpt-4o") -> LLMMetrics:
"""Measure LLM performance for a non-streaming request."""
start = time.perf_counter()
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=256,
stream=False,
)
total_ms = (time.perf_counter() - start) * 1000
completion_tokens = response.usage.completion_tokens
tps = completion_tokens / (total_ms / 1000) if total_ms > 0 else 0
return LLMMetrics(
ttft_ms=total_ms * 0.15, # heuristic: ~15% of total time is prefill
total_generation_ms=total_ms,
tokens_per_second=tps,
prompt_tokens=response.usage.prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=response.usage.total_tokens,
model=model,
)
Измерение со стримингом (точный TTFT)
Для точного TTFT необходимо использовать потоковый API:
import time
def measure_streaming(client, prompt: str, model: str = "gpt-4o") -> LLMMetrics:
"""Measure LLM performance for a streaming request with accurate TTFT."""
start = time.perf_counter()
ttft = None
token_count = 0
full_response = ""
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=256,
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
if ttft is None:
ttft = (time.perf_counter() - start) * 1000
full_response += chunk.choices[0].delta.content
token_count += 1
# Usage is in the final chunk when stream_options is set
if hasattr(chunk, 'usage') and chunk.usage:
prompt_tokens = chunk.usage.prompt_tokens
completion_tokens = chunk.usage.completion_tokens
total_ms = (time.perf_counter() - start) * 1000
generation_time = total_ms - (ttft or 0)
tps = completion_tokens / (generation_time / 1000) if generation_time > 0 else 0
return LLMMetrics(
ttft_ms=ttft or total_ms,
total_generation_ms=total_ms,
tokens_per_second=tps,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
model=model,
)
Экономика контекстного окна
Размер промпта напрямую влияет на задержку и стоимость. Понимание этой взаимосвязи критически важно для оптимизации производительности:
| Использование контекста | Типичное влияние на TTFT | Влияние на стоимость | Оптимизация |
|---|---|---|---|
| < 1K токенов | Базовая линия | Базовая линия | Не требуется |
| 1-4K токенов | +100-300 мс | 2-4x | Суммаризация контекста |
| 4-16K токенов | +300 мс - 1 с | 4-16x | RAG с фильтрацией по релевантности |
| 16-64K токенов | +1-5 с | 16-64x | Агрессивная обрезка контекста |
| 64-128K токенов | +5-15 с | 64-128x | Перепроектирование подхода |
Практические стратегии оптимизации:
- Кеширование промптов. Многие провайдеры кешируют префикс повторяющихся промптов, уменьшая TTFT для последующих запросов с одинаковым системным промптом.
- Обрезка контекста. Удаляйте нерелевантную историю разговора перед отправкой в модель.
- Семантическое кеширование. Кешируйте ответы для семантически похожих запросов, чтобы полностью избежать вызовов LLM.
- Маршрутизация моделей. Отправляйте простые запросы на меньшие, более быстрые модели. Резервируйте большие модели для сложных задач.
Установка SLO для функций LLM
SLO для LLM следует определять отдельно от традиционных SLO для API:
# llm-slo-definition.yaml
service: ai-chatbot
llm_slos:
- name: streaming_responsiveness
metric: time_to_first_token
target: p95 < 2000ms
window: 7d
- name: generation_throughput
metric: tokens_per_second
target: avg > 40 tok/s
window: 7d
- name: completion_time
metric: total_generation_time
target: p95 < 10000ms
window: 7d
- name: availability
metric: successful_requests / total_requests
target: 99.5%
window: 30d
# Note: lower than typical API SLOs because LLM providers
# have higher baseline error rates
- name: rate_limit_headroom
metric: requests_used / rate_limit
target: peak < 80%
window: 1d
alert_at: 70%
Бенчмаркинг разных провайдеров
При оценке провайдеров LLM запускайте стандартизированные бенчмарки:
# llm_benchmark.py
BENCHMARK_PROMPTS = [
{"name": "short_qa", "prompt": "What is 2+2?", "expected_tokens": 10},
{"name": "medium_summary", "prompt": "Summarize the key differences between REST and GraphQL in 3 sentences.", "expected_tokens": 80},
{"name": "long_generation", "prompt": "Write a Python function that implements binary search with detailed docstring.", "expected_tokens": 200},
]
def benchmark_provider(client, model: str, runs: int = 10) -> dict:
"""Benchmark a provider/model combination."""
results = {}
for prompt_config in BENCHMARK_PROMPTS:
metrics = []
for _ in range(runs):
m = measure_streaming(client, prompt_config["prompt"], model)
metrics.append(m)
results[prompt_config["name"]] = {
"ttft_p50": sorted([m.ttft_ms for m in metrics])[len(metrics)//2],
"ttft_p95": sorted([m.ttft_ms for m in metrics])[int(len(metrics)*0.95)],
"tps_avg": sum(m.tokens_per_second for m in metrics) / len(metrics),
"total_p50": sorted([m.total_generation_ms for m in metrics])[len(metrics)//2],
}
return results
Эти данные определяют выбор провайдера, приоритизацию резервных вариантов и калибровку SLO. Запускайте бенчмарки еженедельно для отслеживания трендов производительности провайдеров.