Как вы измеряете TTFT (Time To First Token) и TPOT (Time Per Output Token)?
Краткий тезис
TTFT — время до первого токена, критично для ощущения отзывчивости; TPOT — среднее время на каждый последующий токен, определяет общую задержку генерации. Обе метрики измеряют на стороне инференса, разделяя фазы prefill и decode. Для RAG-систем TTFT становится особенно важным, так как включает время retrieval, а TPOT влияет на восприятие скорости ответа при стриминге. Измеряют с помощью p50/p95/p99 перцентилей, используя инструменты логирования (например, Langfuse, OpenTelemetry) и встроенные хуки в LLM-серверах (vLLM, TGI).
1. Термины: TTFT, TPOT, prefill и decode
- TTFT (Time To First Token) — промежуток от момента получения системой полного запроса пользователя до момента выдачи первого сгенерированного токена. Включает фазу prefill (параллельная обработка промпта) и, возможно, время на retrieval в RAG.
- TPOT (Time Per Output Token) — среднее время, затрачиваемое моделью на генерацию одного токена после первого. Относится к фазе decode (последовательная авторегрессия).
- Prefill — этап, когда модель обрабатывает входной текст (промпт) целиком, вычисляя key-value cache для всех его токенов параллельно. Вычислительно интенсивен, зависит от длины промпта.
- Decode — этап, когда модель генерирует токены один за другим, используя ранее вычисленный KV cache. Время decode почти не зависит от длины промпта, но сильно зависит от размера модели и количества генерируемых токенов.
| Фаза | Характер вычислений | Зависимость от длины промпта | Влияние на TTFT/TPOT |
|---|---|---|---|
| Prefill | Параллельный | Высокая (≈ O(prompt_len)) | TTFT (вся фаза) |
| Decode (первый токен уже на выходе prefill) | Последовательный | Низкая | TPOT (для каждого токена) |
Фактически TTFT ≈ время prefill + latency сетевого вызова, а TPOT ≈ среднее время decode.
2. Почему TTFT и TPOT измеряют раздельно?
- Разная природа задержек: prefill — memory-bound для коротких промптов и compute-bound для длинных; decode — всегда memory-bound из-за загрузки KV cache.
- Влияние на UX: TTFT отвечает за первое впечатление («система начала отвечать?»), TPOT — за плавность стриминга (токены появляются с комфортной скоростью).
- Оптимизации: методы вроде chunked prefill уменьшают TTFT, а speculative decoding — TPOT. Раздельное измерение помогает выбирать правильный рычаг.
В RAG-системе TTFT дополнительно включает вызов retrieval (поиск в векторной БД, переранжирование). Поэтому измерять TTFT нужно как сквозное время от отправки запроса пользователем до первого токена ответа LLM, включая все этапы.
3. Как измерить TTFT
Базовый подход — отметка времени на каждом этапе пайплайна:
- Клиент отправляет запрос → фиксируем
t_start. - Система получает запрос, выполняет retrieval, формирует промпт, отдаёт LLM.
- Модель начинает генерацию → фиксируем
t_first_token. TTFT = t_first_token - t_start.
Метрику обычно агрегируют по всем запросам, считая перцентили.
Пример на Python (псевдокод)
import time
import asyncio
from statistics import median, quantiles
ttft_values = []
async def handle_request(prompt: str):
t_start = time.monotonic()
# Здесь вызов RAG (retrieve + rerank) и формирование полного промпта
# ...
# Генерация первого токена (например, через streaming)
async for token in llm.generate_stream(prompt):
t_first = time.monotonic()
ttft = t_first - t_start
ttft_values.append(ttft)
break # после первого токена выходим
# продолжаем генерацию остальных токенов для TPOT
# ...
# После накопления данных
p50 = median(ttft_values)
p95 = quantiles(ttft_values, n=20)[18] # для 95-го перцентиля
p99 = quantiles(ttft_values, n=100)[98]
print(f"TTFT p50: {p50*1000:.1f}ms, p95: {p95*1000:.1f}ms, p99: {p99*1000:.1f}ms")
Важно: для точности используйте time.monotonic() (не time.time()) во избежание скачков системного времени.
4. Как измерить TPOT
TPOT считаем после первого токена: замеряем полное время генерации, вычитаем TTFT, делим на количество последующих токенов.
Формула
TPOT = (total_time - TTFT) / (num_tokens - 1)
Где total_time — время от t_start до получения конца последовательности (EOS), num_tokens — общее число токенов в ответе (включая первый).
Пример измерения:
async def measure_latency(prompt: str):
t_start = time.monotonic()
tokens = []
async for token in llm.generate_stream(prompt):
tokens.append(token)
if len(tokens) == 1:
t_first = time.monotonic()
ttft = t_first - t_start
t_end = time.monotonic()
total_time = t_end - t_start
num_tokens = len(tokens)
if num_tokens > 1:
tpot = (total_time - ttft) / (num_tokens - 1)
else:
tpot = None # только один токен (редкий случай)
return ttft, tpot
Особенность: TPOT может неравномерно распределяться: первые несколько токенов могут быть медленнее из-за прогрева decode. Поэтому часто считают медианный TPOT на каждом шаге, а не средний.
5. Распределения: p50, p95, p99
Метрики latency имеют тяжёлый хвост — редкие, но очень долгие запросы. Использовать только среднее бессмысленно.
- p50 — типичное время для большинства пользователей.
- p95 — худший сценарий для 5% запросов; обычно целевая метрика оптимизации.
- p99 — события-выбросы (например, при перегрузке сервера или длинном промпте).
| Перцентиль | Интерпретация | Целевое значение для TTFT | Целевое значение для TPOT (модель 7B) |
|---|---|---|---|
| p50 | «нормальный» пользователь | < 500ms | < 30ms |
| p95 | порог терпения | < 2s | < 100ms |
| p99 | аномалии (алерт) | < 5s | < 200ms |
В реальных RAG-системах TTFT p95 часто превышает 2 секунды из-за retrieval, поэтому отдельно мониторят TTFT без retrieval (чисто LLM) и TTFT с retrieval.
6. Инструменты для измерения
- vLLM / TGI — встроенные метрики (например,
time_to_first_token_msв выхлопе). - Langfuse — logging latency с трейсингом шагов (retrieval → generation).
- OpenTelemetry — распределённая трассировка для microservices RAG.
- Ваш код — кастомные декораторы и middleware (как в примере выше).
- Prometheus + Grafana — для дашбордов p50/p95/p99 в реальном времени.
Пример конфигурации для vLLM (параметр --log-stats):
vllm serve meta-llama/Llama-2-7b-chat-hf --enable-lora --log-stats
Статистика выводится в stdout, её можно парсить и отправлять в систему мониторинга.
7. Оптимизация TTFT: chunked prefill и кэширование
- Chunked prefill — разбивка длинного промпта на части и параллельный запуск prefill для каждой; позволяет уменьшить задержку до первого токена, но может увеличить TPOT. Применяется в TensorRT-LLM и vLLM (грядёт).
- KV cache reuse — для повторяющихся промптов (системный контекст) кэш сохраняется между запросами, сокращая prefill. Актуально в Agentic RAG, где один и тот же system prompt используется многократно.
- Сокращение длины промпта — более агрессивный chunking или ретривер с высоким hit rate (меньше документов в контексте).
8. Оптимизация TPOT: speculative decoding и flash attention
- Speculative decoding — генерация «черновика» из нескольких токентов маленькой моделью, затем верификация большой моделью. Позволяет увеличить throughput и уменьшить воспринимаемый TPOT (параллелизация нескольких decode-шагов).
- Flash Attention — эффективная реализация attention, уменьшающая время decode за счёт оптимизации memory access (IO-aware).
- PagedAttention / vLLM — эффективное управление KV cache, снижающее latency decode (особенно при concurrent запросах).
- Quantisation (FP16 → INT8/INT4) — уменьшает размер модели и bandwidth на decode, но может влиять на качество.
9. Компромиссы: TTFT vs TPOT и GPU utilization
Иногда оптимизация одной метрики ухудшает другую:
| Оптимизация | Влияние на TTFT | Влияние на TPOT | Комментарий |
|---|---|---|---|
| Chunked prefill | Уменьшает | Может расти | Prefill конкурирует за ресурсы с decode |
| Увеличение batch size | Увеличивает | Уменьшает | More queuing, better throughput per token |
| Speculative decoding | Незначительно | Сильно уменьшает | Потенциально увеличивает TTFT из-за overhead |
| Более мощная модель | Увеличивает | Увеличивает | Trade-off качество vs скорость |
В Agentic RAG (многошаговые агенты, вызовы инструментов) каждый шаг имеет свой TTFT и TPOT. Общая воспринимаемая задержка складывается из последовательности TTFT+TPOT×num_tokens на шаг.
10. Влияние на RAG-системы
- TTFT критически важен для интерактивных приложений (чат-боты, где пользователь ждёт первое слово).
- TPOT важен для стриминга: если TPOT > 50-100ms, токены появляются рывками, ухудшая UX.
- Горячий старт / кэширование: первый запрос к RAG (холодный старт) имеет большой TTFT из-за загрузки модели и построения retrieval индекса. Измеряйте отдельно cold TTFT и warm TTFT.
- Пайплайны с горутинами: измеряйте сквозной TTFT от клиента до первого токена, включая сетевые задержки, ретраи retrieval и т.д.
11. Пет-проект для закрепления
Задача: Написать простой LLM-сервер на FastAPI с использованием transformers, который логирует TTFT и TPOT для каждого запроса, а затем выводит статистику p50/p95/p99.
Инструменты: Python 3.10+, FastAPI, transformers (или openai-клиент), numpy, matplotlib.
Шаги:
- Реализуйте эндпоинт
/generate, который принимает{"prompt": "..."}и возвращает ответ через streaming (SSE). - Внутри замеряйте:
t_start = time.monotonic()при получении запроса.t_firstпосле первого токена (флагis_first_token).t_endпосле отправки последнего токена.- Сохраняйте ttft, total_time, num_tokens.
- После 1000 запросов (можно имитировать набором промптов) постройте гистограммы TTFT и распределение TPOT.
- Добавьте логирование для каждого запроса в CSV.
- Экспериментируйте с разными hyperparameters (max_new_tokens, temperature), чтобы увидеть влияние на TPOT.
Ожидаемый результат: Дашборд (matplotlib/plotly) с перцентилями и гистограммами. Вы узнаете, как ведёт себя модель под нагрузкой, и на что влияет длина промпта.
12. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 7 | Как вы уменьшаете latency RAG-системы? |
| 443 | Что такое TTLT (Time To Last Token) и как его считать? |
| 444 | Какие стратегии batching применяются при инференсе? |
| 446 | Что такое speculative decoding и как он уменьшает TPOT? |
| 447 | Что такое chunked prefill и когда его применять? |
Навигация
- Предыдущий: 444
- Следующий: 446
- Индекс: 00. Индекс разборов