中文翻译暂不可用,显示俄语原文。
Как вы делаете load testing для LLM endpoint? Какие метрики ключевые?
Краткий тезис
Нагрузочное тестирование (load testing) LLM endpoint — это процесс имитации реальной пользовательской нагрузки для оценки производительности, стабильности и выявления узких мест. В отличие от обычного REST API, LLM endpoint требует учёта специфических метрик: TTFT (Time to First Token), TPOT (Time Per Output Token), пропускной способности в токенах/с, утилизации GPU и длины очереди запросов. Ключевые инструменты — Locust, k6 и кастомные скрипты на Python с asyncio. Тестовые данные должны включать реальные промпты из production с разной длиной и сложностью.
1. Термины и определения
Load testing — вид тестирования, при котором система нагружается запросами, близкими к ожидаемым пиковым значениям, для проверки времени отклика, пропускной способности и частоты ошибок.
LLM endpoint — HTTP-сервер (например, на базе vLLM, TGI, Ollama или собственного инференса), который принимает промпты и возвращает сгенерированный текст (потоком или целиком).
Latency — задержка ответа. Для LLM делится на:
- TTFT (Time to First Token) — время от отправки запроса до получения первого токена.
- TPOT (Time Per Output Token) — среднее время генерации одного последующего токена.
Throughput — пропускная способность: количество запросов в секунду (req/s) или токенов в секунду (tokens/s).
Error rate — доля запросов, завершившихся с HTTP-кодом 4xx (клиентская ошибка) или 5xx (серверная ошибка), включая таймауты.
GPU utilization — загрузка GPU в процентах (память, compute). Ключевой ресурс для LLM.
Queue length — количество запросов, ожидающих обработки (в буфере сервера или балансировщика).
2. Зачем нужно нагрузочное тестирование LLM endpoint?
LLM endpoint принципиально отличается от обычного REST API:
- Генерация текста — ответ не фиксированного размера, время обработки зависит от длины промпта и количества генерируемых токенов.
- Высокое потребление GPU — каждый запрос требует значительных вычислительных ресурсов, особенно при больших моделях (7B, 13B, 70B параметров).
- Асинхронность и батчинг — сервер может обрабатывать запросы батчами (batch inference), что усложняет прогнозирование latency.
- Потоковая vs не-потоковая передача — streaming (Server-Sent Events) меняет метрики: TTFT становится критичным для UX.
Без нагрузочного тестирования невозможно:
- Определить максимальное количество одновременных пользователей.
- Выставить корректные лимиты (rate limiting, max concurrency).
- Обнаружить утечки памяти или деградацию под длительной нагрузкой.
- Сравнить разные конфигурации деплоя (batch size, quantization, tensor parallelism).
3. Инструменты нагрузочного тестирования
| Инструмент | Язык | Особенности | Плюсы | Минусы |
|---|---|---|---|---|
| Locust | Python | Декларативное описание сценариев, веб-интерфейс | Простота, поддержка asyncio, распределённый режим | Меньше встроенных метрик для LLM |
| k6 | JavaScript | Высокая производительность, много метрик | Быстрый, встроенные пороги (thresholds), облачные опции | Нужно писать на JS, сложнее кастомизация |
| Gatling | Scala | Мощный, для enterprise | Хорошие отчёты, поддержка WebSocket | Тяжёлый, неудобен для быстрых прототипов |
| Custom (Python + aiohttp) | Python | Полный контроль | Гибкость, можно замерять TTFT/TPOT напрямую | Нужно писать с нуля, нет готовых отчётов |
Для LLM endpoint чаще всего используют Locust (из-за простоты и Python) или кастомные скрипты (для точного измерения TTFT/TPOT).
4. Ключевые метрики
4.1 Latency
TTFT (Time to First Token) — критично для потоковых приложений (чат-боты). Измеряется как время от отправки запроса до получения первого байта ответа (при streaming) или первого токена.
TPOT (Time Per Output Token) — среднее время генерации одного токена после первого. Вычисляется как (total_time - TTFT) / (num_output_tokens - 1).
Percentiles (p50, p95, p99) — распределение задержек. p99 показывает худшие случаи (tail latency).
Пример измерения в Python:
import time
import aiohttp
async def measure_llm(prompt):
start = time.monotonic()
async with aiohttp.ClientSession() as session:
async with session.post(url, json={"prompt": prompt, "stream": True}) as resp:
first_token_time = None
tokens = 0
async for chunk in resp.content:
if first_token_time is None:
first_token_time = time.monotonic()
tokens += 1
end = time.monotonic()
ttft = first_token_time - start
tpot = (end - first_token_time) / max(tokens - 1, 1)
return ttft, tpot, tokens
4.2 Throughput
- Requests per second (req/s) — сколько запросов сервер обрабатывает в секунду.
- Tokens per second (tokens/s) — общая скорость генерации токенов (сумма output токенов всех запросов за секунду). Более информативно для LLM, так как запросы разной длины.
4.3 Error rate
- 4xx — превышение лимитов (429 Too Many Requests), некорректные запросы.
- 5xx — внутренние ошибки сервера (перегрузка, OOM, падение модели).
- Timeout — запрос не завершился за заданный таймаут (обычно 30–60 с).
4.4 GPU utilization
- GPU memory used — объём занятой видеопамяти (важно для batch size).
- GPU compute utilization — процент времени, когда GPU занят вычислениями.
- Power consumption — энергопотребление (для оценки стоимости).
4.5 Queue length
Количество запросов, ожидающих в очереди перед обработкой. Если очередь растёт — сервер не справляется, latency будет увеличиваться.
5. Как измерять: мониторинг и логирование
Для сбора метрик во время теста необходимо:
- Логировать каждый запрос: timestamp, TTFT, TPOT, количество токенов, HTTP-статус.
- Использовать системные метрики: nvidia-smi (GPU), psutil (CPU, RAM), iostat (диск).
- Агрегировать в реальном времени: Prometheus + Grafana или встроенные дашборды Locust.
Пример сбора GPU метрик в Python:
import subprocess
import json
def get_gpu_metrics():
result = subprocess.run(
["nvidia-smi", "--query-gpu=utilization.gpu,memory.used,memory.total",
"--format=csv,noheader,nounits"],
capture_output=True, text=True
)
lines = result.stdout.strip().split("\n")
metrics = []
for line in lines:
parts = line.split(", ")
metrics.append({
"gpu_util": float(parts[0]),
"mem_used": float(parts[1]),
"mem_total": float(parts[2])
})
return metrics
6. Тестовые данные
Используйте реальные промпты из production логов — это даст наиболее релевантную нагрузку. Если production нет, синтезируйте датасет:
- Короткие промпты (1–50 токенов) — имитация простых вопросов.
- Средние (50–500 токенов) — типичные запросы с контекстом.
- Длинные (500–2000+ токенов) — обработка больших документов.
- Разное количество output токенов — настройте параметр
max_tokensот 50 до 2048.
Распределение должно повторять реальное (например, 60% коротких, 30% средних, 10% длинных).
7. Сценарии тестирования
| Сценарий | Описание | Цель |
|---|---|---|
| Ramp-up | Постепенное увеличение числа пользователей (например, +1 пользователь/сек) | Определить точку насыщения |
| Steady state | Фиксированная нагрузка (например, 50 req/s) в течение 30 минут | Проверить стабильность, утечки памяти |
| Spike test | Резкое увеличение нагрузки (с 10 до 100 req/s за 1 сек) | Проверить поведение при внезапных пиках |
| Soak test | Длительная нагрузка (несколько часов) | Выявить деградацию со временем |
8. Анализ результатов и бутылочные горлышки
Типичные узкие места:
- GPU compute — высокая утилизация GPU (>95%) и рост TPOT. Решение: уменьшить batch size, использовать меньшую модель, добавить GPU.
- GPU memory — OOM (out of memory) при большом batch size. Решение: уменьшить batch size, использовать quantization (int8, fp16).
- CPU / Network — низкая GPU утилизация, но высокий latency. Значит, узкое место в пред/постобработке или сети. Решение: оптимизировать токенизацию, увеличить bandwidth.
- Queue length — растёт, latency растёт. Решение: добавить реплики, настроить балансировщик, увеличить max concurrency.
SLA (Service Level Agreement) — определите целевые значения:
- p95 TTFT < 500 мс для потоковых приложений.
- p95 TPOT < 50 мс/токен.
- Error rate < 1%.
- Throughput не ниже N tokens/s.
9. Пример кастомного нагрузочного теста на Python
import asyncio
import aiohttp
import time
import statistics
async def worker(sem, url, prompt, results):
async with sem:
start = time.monotonic()
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json={"prompt": prompt, "max_tokens": 100}) as resp:
data = await resp.json()
latency = time.monotonic() - start
results.append({
"latency": latency,
"status": resp.status,
"tokens": len(data.get("choices", [{}])[0].get("text", "").split())
})
except Exception as e:
results.append({"latency": time.monotonic() - start, "status": 0, "error": str(e)})
async def run_load(url, prompts, concurrency=10):
sem = asyncio.Semaphore(concurrency)
results = []
tasks = [worker(sem, url, p, results) for p in prompts]
await asyncio.gather(*tasks)
return results
# Использование
url = "http://localhost:8000/v1/completions"
prompts = ["What is AI?"] * 100 # 100 одинаковых запросов
results = asyncio.run(run_load(url, prompts, concurrency=20))
latencies = [r["latency"] for r in results if r["status"] == 200]
print(f"p50: {statistics.median(latencies):.3f}s")
print(f"p95: {sorted(latencies)[int(len(latencies)*0.95)]:.3f}s")
print(f"Error rate: {sum(1 for r in results if r['status'] != 200)/len(results)*100:.1f}%")
10. Best practices
- Прогревайте модель — первые запросы могут быть медленнее из-за инициализации кэша. Выполните 10–20 «прогревочных» запросов перед замером.
- Учитывайте batch size — сервер может батчить запросы, что снижает latency для отдельных запросов, но увеличивает TTFT для всех в батче.
- Rate limiting — настройте ограничения на стороне клиента, чтобы не перегружать сервер раньше времени.
- Мониторинг GPU — используйте
nvidia-smi dmonили Prometheus exporter для сбора в реальном времени. - Повторяйте тесты — минимум 3 раза для статистической значимости.
Пет-проект для закрепления
Задача: Написать нагрузочный тест для локального LLM endpoint (например, через Ollama или vLLM) с измерением TTFT, TPOT, throughput и GPU utilization.
Инструменты: Python, aiohttp, asyncio, nvidia-smi (или pynvml).
Шаги:
- Разверните LLM endpoint локально (например,
ollama run llama3.2). - Создайте датасет из 50 промптов разной длины (от 10 до 500 токенов).
- Напишите скрипт, который отправляет запросы с разной степенью конкурентности (1, 5, 10, 20).
- Для каждого уровня замерьте:
- Средний TTFT и TPOT (p50, p95).
- Throughput в req/s и tokens/s.
- Error rate.
- GPU utilization (с помощью
nvidia-smi --query).
- Постройте графики зависимости latency и throughput от конкурентности.
- Определите максимальную конкурентность, при которой p95 TTFT < 1 с и error rate < 1%.
Ожидаемый результат: Отчёт с графиками и рекомендациями по оптимальному количеству одновременных пользователей для данного endpoint.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 450 | Деплой LLM модели в production |
| 452 | Мониторинг LLM endpoint в реальном времени |
| 453 | A/B тестирование разных версий модели |
| 454 | Оптимизация latency LLM инференса |
| 455 | Масштабирование LLM сервиса (горизонтальное/вертикальное) |
| 460 | Выбор hardware для LLM инференса |
Навигация
- Предыдущий: 450
- Следующий: 452
- Индекс: 00. Индекс разборов