Больше интересного про инференс, AI-инфраструктуру и практику с LLM я публикую в Telegram-канале @fuckup_files.
Локальную LLM в 2026 уже нельзя нормально сравнивать одной цифрой tokens/sec. Эта цифра удобная, но почти всегда обманывает: один человек меряет короткий prompt на холодном сервере, второй гоняет 128K контекст с prefix cache, третий смотрит только decode, четвёртый включает speculative decoding и получает красивый throughput, который разваливается на реальном agent workload.
Если вы хотите понять, как бенчмаркать локальную LLM в 2026, смотрите минимум на пять вещей: TTFT, TPOT, KV cache, context length и VRAM. Именно они отвечают на практические вопросы: будет ли чат отвечать быстро, потянет ли RTX 3090/4090 длинный контекст, почему в vLLM всё быстро на коротких запросах и внезапно плохо на агентных задачах, и где на самом деле узкое место — модель, память, batching или scheduler.
Ниже — практический разбор, как я бы снимал бенчмарк локальной LLM, чтобы цифрам можно было верить.
TL;DR
-
tokens/secбез контекста почти бесполезен. Всегда пишите модель, quantization, GPU, engine, версию engine, context length, prompt/output tokens и concurrency. -
TTFT показывает, сколько пользователь ждёт первый токен. Он сильно растёт на длинном prompt, потому что prefill должен построить KV cache.
-
TPOT/ITL показывают, насколько плавно идёт генерация после первого токена. Тут чаще упираемся в memory bandwidth и размер batch.
-
KV cache — главный пожиратель VRAM на длинных контекстах. Вес модели фиксирован, а KV cache растёт с количеством токенов.
-
Для vLLM используйте
vllm bench serve, сохраняйте JSON и percentile metrics:ttft,tpot,itl,e2el. -
Бенчмарк должен включать несколько профилей: короткий чат, длинный prompt, agent loop, high concurrency и warm cache.
Почему tokens/sec недостаточно
Представим два результата:
Setup A: 120 tok/s
Setup B: 80 tok/s
На первый взгляд A лучше. Но теперь добавим детали:
Setup A:
- prompt: 256 токенов
- output: 256 токенов
- concurrency: 1
- context: 4K
- TTFT: 0.4s
Setup B:
- prompt: 32K токенов
- output: 1024 токена
- concurrency: 16
- context: 128K
- TTFT: 3.2s
Это уже не один и тот же тест. Первый больше похож на короткий чат. Второй — на RAG, coding-agent или длинный документ. Сравнивать их одной цифрой всё равно что сравнивать скорость велосипеда и грузовика по одному спидометру без веса, дороги и груза.
В 2026 локальные LLM всё чаще используют не для “напиши анекдот”, а для агентных задач: coding agents, длинные tool traces, большие system prompts, RAG, multi-turn history, structured output. Там важны не только токены в секунду, а форма задержки.
Базовые метрики: TTFT, TPOT, ITL, E2E
В официальном блоге vLLM метрики формулируются достаточно прямо: TTFT — время от отправки запроса до первого output token, ITL — задержка между соседними токенами, TPOT — средний ITL по output tokens внутри запроса, E2E latency — полное время от запроса до последнего токена. У vLLM есть отдельный CLI vllm bench {serve,latency,throughput} для таких измерений: vLLM blog: Anatomy of a High-Throughput LLM Inference System.
На практике:
| Метрика | Что показывает | Где болит |
|---|---|---|
TTFT | сколько ждать первый токен | prefill, длинный prompt, cold cache, очередь |
TPOT | среднее время на output token | decode, batch size, memory bandwidth |
ITL | интервал между токенами | плавность streaming UX |
E2E | полная задержка запроса | весь pipeline целиком |
Throughput | токены/сек или requests/sec | полезно для capacity planning |
Goodput | throughput в рамках SLO | сколько реально “годного” throughput |
Anyscale в своих docs отдельно подчёркивает различие TPOT и ITL: для одного запроса TPOT можно считать как (E2E latency - TTFT) / (#output_tokens - 1), а на множестве запросов средний TPOT и средний ITL агрегируются по-разному: Anyscale docs: LLM latency and throughput metrics.
Это важно: если один запрос сгенерировал 2 токена, а другой 1000, средний TPOT и token-weighted ITL будут вести себя по-разному. Для пользовательского UX смотрим percentiles, для capacity planning — throughput и goodput.
Как prefill и decode ломают простые выводы
LLM inference состоит из двух разных фаз:
-
Prefill — модель читает весь prompt и строит KV cache. Это обычно compute-heavy фаза.
-
Decode — модель генерирует токены один за другим, постоянно читая KV cache. Это часто memory-bandwidth-heavy фаза.
Отсюда простой вывод:
-
длинный prompt ухудшает
TTFT; -
большой output ухудшает
E2E; -
большой batch улучшает throughput, но может ухудшить latency;
-
большой context увеличивает KV cache и начинает давить на VRAM;
-
prefix caching может резко улучшить TTFT, но только если префиксы действительно совпадают.
Если вы уже читали мой разбор KV-cache в моделях transformers, то это ровно тот же механизм, только теперь мы смотрим на него не как на теорию, а как на источник конкретных цифр в бенчмарке.
Что обязательно записывать в каждом прогоне
Хороший локальный бенчмарк — это не только результат, но и metadata. Без metadata вы через неделю сами не поймёте, что именно измеряли.
Минимальный паспорт прогона:
model: Qwen/Qwen3.5-4B
engine: vLLM
engine_version: 0.20.0
backend: openai-compatible server
gpu:
model: RTX 4090
count: 1
vram_gb: 24
driver:
cuda: 13.x
pytorch: 2.11.x
model_format:
dtype: fp8
quantization: null
serving_flags:
max_model_len: 32768
gpu_memory_utilization: 0.90
max_num_seqs: 16
enable_prefix_caching: true
enable_chunked_prefill: true
benchmark:
input_tokens: 4096
output_tokens: 512
concurrency: 8
request_rate: 2
warmup_requests: 16
metrics:
ttft_p50_ms: 0
ttft_p95_ms: 0
tpot_p50_ms: 0
tpot_p95_ms: 0
e2e_p95_ms: 0
output_tok_s: 0
peak_vram_gb: 0
В свежем посте про LocalMaxxing обсуждали похожую идею: бенчмарк локальной LLM должен фиксировать не только модель и скорость, но и platform, GPU model/quantity, engine/version/flags, quantization, context length, batch size, input/output tokens, TTFT и peak VRAM: пример обсуждения в X.
Это правильное направление. Без таких полей локальный benchmark превращается в “у меня быстрее, чем у тебя”, а не в инженерное измерение.
Как запустить vLLM benchmark serve
Для online-serving сценария используйте vllm bench serve. В актуальной CLI-документации vLLM есть параметры для сохранения результата в JSON, detailed per-request info, percentile metrics, goodput и SLO по ttft/tpot/e2el: vLLM docs: vllm bench serve.
Сначала поднимаем сервер:
vllm serve Qwen/Qwen3.5-4B \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.90 \
--max-model-len 32768 \
--enable-prefix-caching \
--enable-chunked-prefill
Потом прогоняем serving benchmark:
vllm bench serve \
--backend vllm \
--base-url http://127.0.0.1:8000 \
--model Qwen/Qwen3.5-4B \
--dataset-name random \
--random-input-len 4096 \
--random-output-len 512 \
--num-prompts 256 \
--request-rate 2 \
--max-concurrency 8 \
--num-warmups 16 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,90,95,99 \
--save-result \
--save-detailed \
--metadata engine=vllm \
--metadata test=local-llm-4096x512-c8
Ключевые параметры:
-
--random-input-len— длина prompt в токенах. -
--random-output-len— сколько токенов генерировать. -
--request-rate— входящий RPS.infпревращает тест в “завалить сервер всем сразу”, что полезно для throughput, но не всегда похоже на реальный traffic. -
--max-concurrency— верхняя граница одновременных запросов. -
--num-warmups— прогрев. -
--save-detailed— per-request данные, без них сложнее объяснить хвосты. -
--goodput ttft:1000 tpot:50— можно считать не просто throughput, а throughput, который уложился в SLO.
Пять профилей, которые стоит гонять
Один benchmark ничего не доказывает. Я бы делал минимум пять профилей.
1. Короткий чат
input: 512 токенов
output: 256 токенов
concurrency: 1-8
Показывает обычный chat UX. Тут важны TTFT p50/p95 и ITL/TPOT. Если этот тест плохой, всё остальное будет ещё хуже.
2. Длинный prompt
input: 16K-64K токенов
output: 256-512 токенов
concurrency: 1-4
Это тест на prefill. Он отвечает на вопрос: “что будет, если скормить модели документацию, большой issue, длинную историю чата или RAG context”.
3. Agent loop
input: 8K-64K токенов
output: 512-2048 токенов
concurrency: 4-16
shared prefix: включить отдельный прогон
Агентные задачи часто имеют длинный стабильный system prompt, tool definitions и растущую историю. Тут важно сравнить:
-
без prefix caching;
-
с prefix caching;
-
с чуть изменённым prompt, который ломает cache hit.
Если prefix caching даёт красивый результат только в синтетике, а в реальном agent loop hit rate низкий — это не оптимизация, а иллюзия.
Подробнее про отличие vLLM block-level caching и SGLang radix tree я уже разбирал в статье vLLM vs SGLang: radix tree против block-level prefix caching.
4. High concurrency
input: 1K-4K токенов
output: 256-1024 токена
concurrency: 16-128
request_rate: ступеньками
Это capacity planning. Тут смотрим не только средние, а p95/p99. Сервер может красиво держать средний TPOT, но раздавать p99 TTFT в десятки секунд.
5. Long-context VRAM test
input: 32K / 64K / 128K
output: 128-512
concurrency: 1
Это тест на предел VRAM. Hardware Corner хорошо формулирует базовую механику: context window — это количество информации в токенах, а KV cache растёт линейно с длиной контекста; на очень длинных контекстах KV cache может стать больше, чем сами веса модели: What Is Context Length in LLMs and How It Impacts Your VRAM.
Именно поэтому “модель влезла” не значит “модель нормально работает на 128K context”.
Как смотреть VRAM правильно
nvidia-smi полезен, но его мало. Он показывает занятость памяти, но не объясняет, почему она занята. Для локального benchmark фиксируйте:
nvidia-smi --query-gpu=timestamp,name,memory.used,memory.total,utilization.gpu,utilization.memory,power.draw \
--format=csv -l 1
И параллельно сохраняйте логи engine:
vllm serve ... 2>&1 | tee vllm-server.log
Что искать:
-
peak VRAM на старте;
-
peak VRAM после прогрева;
-
VRAM на длинном prompt;
-
падает ли throughput после нескольких минут;
-
появляются ли OOM/retry/swap/offload;
-
растёт ли память после завершения запросов.
Если видите странный рост памяти в vLLM или distributed serving — это уже не benchmark, а debugging. Для такого случая у меня есть отдельный разбор Куча лжи: отладка утечки памяти в vLLM.
Как context length влияет на результат
Context length влияет сразу на три вещи:
-
TTFT — длинный input дольше prefill-ится.
-
VRAM — KV cache растёт вместе с числом токенов.
-
TPOT/ITL — decode читает всё больше cache, особенно на длинной истории.
Очень грубая модель:
VRAM_total ≈ weights + KV_cache + activations + runtime_overhead
Вес модели почти фиксирован:
weights ≈ params * bytes_per_param
KV cache зависит от архитектуры:
KV_cache ∝ layers * kv_heads * head_dim * context_tokens * bytes_per_value
Из этого следуют практические выводы:
-
4-bit веса не делают KV cache 4-bit автоматически.
-
24 GB VRAM могут быть нормальны для 32B/70B в quantized setup на коротком context, но развалиться на 64K+.
-
max_model_len— не просто “разрешить больше токенов”, а зарезервировать потенциально огромный memory budget. -
KV cache quantization может дать больше пользы на long-context, чем очередное ужатие весов.
Что сравнивать: холодный, прогретый и повторяющийся workload
Для каждого профиля делайте три режима.
Cold
Сервер только поднялся, cache пустой, всё впервые. Это худший realistic сценарий после deploy/restart.
Warm
Сделали warmup requests, kernels прогрелись, allocator стабилизировался. Это нормальный steady-state.
Repeated prefix
Одинаковый system prompt, одинаковые tool definitions, разные user inputs. Это тест на prefix caching.
system prompt: одинаковый
tools: одинаковые и в одном порядке
few-shot examples: одинаковые
user question: меняется
Если в начало prompt попадает timestamp, request id или случайный порядок JSON tool definitions — prefix caching может сломаться. Тогда вы будете платить prefill снова и снова.
Как не обмануть себя benchmark-флагами
Вот типичные ловушки:
request_rate=inf
Хорошо для максимального throughput, плохо для имитации реального сервиса. Для capacity planning лучше прогонять ступеньки:
RPS: 0.5 → 1 → 2 → 4 → 8
И смотреть, где p95 TTFT/TPOT пересекают SLO.
Слишком короткий prompt
Если вы тестируете только 512 токенов, вы ничего не знаете про RAG, coding agents и long-context.
Только batch=1
Это полезно для интерактивного single-user UX, но ничего не говорит о сервере под несколькими пользователями.
Только средние значения
Средний TTFT может быть 600 ms, а p99 — 18 секунд. Пользователь запомнит p99.
Без версии engine
vLLM/SGLang меняются быстро. Результат без версии engine и flags почти нельзя воспроизвести.
Минимальная таблица результата
Я бы публиковал результаты примерно так:
| Profile | Input | Output | Concurrency | TTFT p50/p95 | TPOT p50/p95 | Output tok/s | Peak VRAM |
|---|---|---|---|---|---|---|---|
| short-chat | 512 | 256 | 1 | 180/260 ms | 18/25 ms | 55 | 18.2 GB |
| long-prompt | 32768 | 512 | 1 | 2400/3100 ms | 24/31 ms | 41 | 22.7 GB |
| agent-loop | 16384 | 1024 | 8 | 900/1800 ms | 38/61 ms | 180 | 23.4 GB |
| high-concurrency | 2048 | 512 | 32 | 1300/6500 ms | 45/120 ms | 410 | 23.8 GB |
Цифры здесь примерные, не результат конкретного теста. Важен формат: сразу видно, какой profile измерялся и почему один результат нельзя напрямую сравнивать с другим.
Какой benchmark нужен для RTX 3090/4090
Для 24 GB карт я бы начинал с такого набора:
1. 7B/8B model, BF16 или FP8
2. 14B/32B model, 4-bit/FP8
3. 30B/32B MoE или dense, если engine нормально поддерживает формат
4. 64K context stress test
5. agent-loop с prefix caching
Почему не начинать с 70B? Потому что вы сначала хотите понять форму кривой: как растёт TTFT, где упирается VRAM, как ведёт себя TPOT, что происходит при concurrency. Большая модель без baseline только запутает.
Для multi-GPU уже добавляются отдельные вопросы:
-
tensor parallel или pipeline parallel;
-
есть ли NVLink;
-
PCIe bottleneck;
-
NCCL настройки;
-
насколько стабилен long-context;
-
насколько сильно растёт TTFT при распределении.
Если вы идёте в multi-node, полезно сначала прочитать Распределенный запуск LLM на нескольких GPU с помощью Ray и vLLM, потому что там начинаются проблемы уже не benchmark-уровня, а сетевого и runtime-уровня.
Итоговый чеклист
Перед тем как верить локальному benchmark LLM, проверьте:
-
указаны модель, quantization, engine, версия engine и flags;
-
есть input/output token lengths;
-
есть context length и
max_model_len; -
есть concurrency и request rate;
-
есть TTFT p50/p95/p99;
-
есть TPOT или ITL p50/p95/p99;
-
есть E2E latency;
-
есть peak VRAM;
-
есть warmup;
-
есть cold/warm сравнение;
-
есть long-context профиль;
-
есть agent/repeated-prefix профиль;
-
есть сохранённый JSON результата;
-
есть raw logs или хотя бы команда запуска.
Если этого нет, перед вами не benchmark, а скриншот настроения.
Частые вопросы
Что важнее: TTFT или tokens/sec?
Для чата и agent UX важнее TTFT и p95/p99 latency. Для batch processing важнее throughput. Для production serving нужен баланс: throughput, который укладывается в SLO, то есть goodput.
Почему на коротком prompt всё быстро, а на длинном резко плохо?
Потому что длинный prompt увеличивает prefill и KV cache. TTFT растёт, VRAM забивается, decode начинает читать больше данных.
Можно ли сравнивать llama.cpp, Ollama, vLLM и SGLang одной таблицей?
Можно, но только если одинаковые model weights, quantization, prompt/output lengths, concurrency, context и hardware. Иначе это сравнение разных задач.
Почему включил prefix caching, а ускорения нет?
Скорее всего, нет стабильного общего префикса. Проверьте system prompt, порядок tool definitions, timestamps, random ids и формат chat template.
Почему GPU utilization низкий, но latency высокая?
Decode часто упирается не в compute, а в memory bandwidth и scheduler. Низкая GPU utilization не всегда означает, что “GPU простаивает”; иногда она ждёт память, batch, сеть или CPU-side orchestration.
Вывод
Бенчмарк локальной LLM в 2026 — это не “сколько токенов в секунду выдаёт моя RTX 4090”. Нормальный benchmark отвечает на более полезные вопросы:
-
сколько ждать первый токен;
-
насколько плавно идёт stream;
-
какой context реально помещается в VRAM;
-
что происходит при concurrency;
-
помогает ли prefix caching;
-
где падает goodput;
-
можно ли повторить результат через месяц.
Если вы снимаете только tok/s, вы видите одну тень системы. Если снимаете TTFT, TPOT, KV cache, context length и VRAM вместе — вы наконец начинаете видеть саму систему.
Заинтересовало? Больше практических разборов про LLM, инференс и AI-инфраструктуру — в моём Telegram-канале @fuckup_files.