Как измерить токены в секунду для локальной LLM
Узнайте, как правильно рассчитать пропускную способность GPU через замеры с разной степенью параллелизма. Определите порог рентабельности аренды сервера вместо оплаты API.
Почему количество токенов в секунду определяет рентабельность GPU
Количество токенов в секунду — это скорость, с которой ваш сервер генерирует выходной текст. Именно этот показатель определяет, выгоднее ли арендовать GPU, чем оплачивать API за каждый токен. Аренда GPU оплачивается по часам независимо от того, простаивает сервер или работает. API оплачивается по количеству токенов. Поэтому использование GPU выгодно только в том случае, если вы поддерживаете достаточно высокую скорость генерации на протяжении большей части оплачиваемого времени.
Это означает, что вам нужны собственные измерения, а не теоретические цифры из документации. На этой странице определены четыре показателя, которые стоит фиксировать, приведены команды для их получения и формулы, позволяющие принять решение.
Почему опубликованный показатель токенов в секунду не является вашим числом
В июле 2026 года DigitalOcean опубликовала показатели пропускной способности для одного NVIDIA H200, работающего с llama3.3-70b-instruct в формате FP8 (8-битное число с плавающей запятой) под управлением vLLM. Эти цифры полезны, но они не относятся к вашим задачам.
The data behind this chart
[
{
"config": "H200, one stream",
"tok_s": "47"
},
{
"config": "H100, saturated",
"tok_s": "236"
},
{
"config": "H200, saturated",
"tok_s": "2,036"
},
{
"config": "H200, saturated, in+out",
"tok_s": "4,071.6"
}
]Каждая строка выше взята с той страницы, причем две из них представляют собой нижнюю границу указанного диапазона, поэтому воспринимайте их как минимальные значения. Ни один из показателей в этой таблице не был измерен нами.
Начнем с последних двух строк. Заголовок гласит 4,071.6 ток/с, в то время как скорость только вывода составляет 2,036 ток/с. Заголовок учитывает входные и выходные токены вместе. В этом тесте использовалось 1024 входных токена на 1024 выходных, поэтому почти ровно половина заголовка приходится на вывод. Это разделение важно, так как вывод — это та половина, за которую вы платите, и она является медленной. Prefill (чтение промпта) обрабатывает все входные токены за один проход. Decode (генерация ответа) создает по одному токену за раз. Общий показатель пропускной способности усредняет дешевое значение с дорогим.
Теперь первая строка. Тот же H200, обслуживающий один запрос за раз, выдает 47 ток/с, поэтому показатель при полной загрузке на идентичном оборудовании более чем в сорок раз выше. Этот разрыв существует, потому что один шаг декодирования заставляет GPU большую часть времени ожидать данные из памяти, а параллельные запросы заполняют это время простоя. Вторая строка, 236 ток/с, — это один H100 на той же модели, ограниченный KV cache (кэш ключей и значений, память на каждый запрос, которую обслуживаемый диалог хранит на карте). Карта на 80 GB вмещает меньше параллельных запросов для модели 70B, поэтому точка насыщения у нее ниже.
Измените модель или соотношение входных данных к выходным, и каждое число выше изменится. Опубликованные цифры формируют ваши ожидания, а не ваш бюджет. Это то же самое правило, которое применяется при честном тестировании производительности VPS для диска и сети.
Четыре ключевых показателя
- Время до первого токена (TTFT). Задержка между отправкой запроса и получением первого выходного токена. Это сумма времени предварительного заполнения (prefill) и времени ожидания в очереди. Пользователь ощущает этот показатель напрямую.
- Количество выходных токенов в секунду на поток. Скорость генерации ответа после начала вывода. Значение выше примерно 20 токенов/с превышает скорость чтения большинства людей, поэтому дальнейшее увеличение скорости дает незначительный эффект.
- Максимальная общая пропускная способность. Сумма всех параллельных потоков при полной загрузке сервера. Это показатель емкости, который определяет эффективность использования GPU.
- p50 и p99 TTFT при параллельной нагрузке. p50 — медианный запрос. p99 — значение, в которое укладываются 99 из 100 запросов. Очереди всегда проявляются в первую очередь в показателе p99.
Первые два показателя улучшаются при низкой нагрузке на сервер. Третий показатель растет при высокой нагрузке. Они противоречат друг другу, поэтому ни одно число не может полностью описать производительность сервера.
Приведите длины входных и выходных данных к единому значению перед проведением замеров
Пропускная способность зависит от структуры трафика. Промпт на 4,000 токенов с ответом на 50 токенов — это задача с высокой нагрузкой на этап prefill. Промпт на 200 токенов с ответом на 2,000 токенов — это задача с высокой нагрузкой на этап decode. Один и тот же сервер покажет совершенно разные значения токенов в секунду для этих сценариев, поэтому выберите одно соотношение, записывайте его рядом с каждым полученным числом и никогда не сравнивайте результаты с разными соотношениями. Соотношение 1,024 на входе к 1,024 на выходе является разумным стандартом, так как многие поставщики публикуют свои показатели именно для него. Если вы знаете характеристики своего реального трафика, используйте их.
Также принудительно задавайте длину выходных данных. Модель, которая достигает стоп-токена после 60 токенов, выдает более короткий результат, который выглядит быстрее, так как TTFT в этом случае занимает большую долю времени выполнения. Флаг --ignore-eos в клиенте для тестирования vLLM заставляет каждый запрос генерировать строго заданное количество токенов, благодаря чему два запуска остаются сопоставимыми. Выбор модели влияет на эти показатели сильнее, чем любой флаг: в статье размещение модели Qwen 3 на одном GPU виртуального сервера рассматриваются аспекты выбора модели с точки зрения потребления памяти.
Измерение одного потока
Начните с самого простого случая. Это проверка работоспособности и определение верхнего предела производительности. Ollama выводит собственные показатели времени выполнения.
ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."Строка, на которую нужно обратить внимание — eval rate, это количество токенов в секунду. prompt eval rate — скорость префилла (prefill), а load duration — время загрузки модели в VRAM. При первом вызове после «холодного» старта значение load duration велико, поэтому total duration может быть обманчивым. Запустите команду дважды и используйте второй результат. По умолчанию Ollama выгружает неактивную модель через пять минут, поэтому длительная пауза между запусками вернет вас к состоянию «холодного» старта.
Те же поля доступны через API, что упрощает автоматизацию.
curl -s http://127.0.0.1:11434/api/generate -d '{
"model": "llama3.1:8b",
"prompt": "Write 200 words about disk scheduling.",
"stream": false
}' | jq '{eval_count, eval_duration,
tok_s: (.eval_count / (.eval_duration / 1000000000))}'eval_duration измеряется в наносекундах, поэтому деление на 1,000,000,000 дает результат в секундах. Именно такое деление предписано документацией API Ollama для расчета токенов в секунду. Если сервер еще не запущен, в статье самостоятельный хостинг LLM с помощью Ollama на VPS описаны установка и настройка systemd-юнита.
Для измерения TTFT (Time To First Token) требуется потоковый запрос, и утилита curl может замерить это время для вас.
curl -s -o /dev/null -N \
-w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen/Qwen3-8B",
"messages":[{"role":"user","content":"Write 200 words about disk scheduling."}],
"stream":true,"max_tokens":256}'time_starttransfer — это момент получения первого байта тела ответа. В потоковом чате этот байт относится к первому событию, отправленному сервером (server-sent event): это либо первый токен содержимого, либо дельта с указанием роли, отправленная непосредственно перед ним. Поэтому считайте это значение равным TTFT с погрешностью в одно событие. Этого достаточно для сравнения двух запусков на одном сервере.
Показатели для одного потока завышают возможности сервера. Значение TTFT здесь максимально возможное, так как перед вами нет очереди запросов. Скорость для одного потока также является максимально возможной, так как вся видеокарта обслуживает только один запрос. Ни один из этих показателей не отражает реальную нагрузочную способность сервера.
Как выполнить проверку параллелизма?
Проверка (sweep) запускает одну фиксированную рабочую нагрузку при возрастающем уровне параллелизма и фиксирует результаты на каждом шаге. vLLM поставляет клиент для этой задачи, который использует OpenAI API, поэтому он также работает с Ollama и любыми другими решениями, совместимыми с OpenAI.
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts 160 \
--max-concurrency 16 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99--max-concurrency ограничивает количество запросов в обработке, и это переменная, которую вы изменяете в ходе проверки. --num-prompts — это общее количество отправленных запросов, поэтому держите это значение примерно в десять раз больше уровня параллелизма для получения стабильного среднего показателя. Сводка выводит Output token throughput (tok/s): и Total token throughput (tok/s):, а затем Mean TTFT (ms):, Median TTFT (ms): и P99 TTFT (ms): в разделе Time to First Token.
В этом выводе нет частоты запросов на поток, но её легко вычислить делением. Mean TPOT (ms): — это среднее время на один выходной токен после первого, поэтому 25 мс на токен означает 40 токенов в секунду на поток. Деление общей пропускной способности на уровень параллелизма дает тот же результат.
Затем выполните цикл, сохраняя результаты каждого запуска.
for C in 1 4 16 32 64 128; do
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts $(( C * 10 )) \
--max-concurrency "$C" \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99 \
--save-result --result-filename "sweep-c$C.json"
doneЧтение сохраненных JSON-файлов
Каждый запуск записывает один файл, поэтому извлекайте нужные поля из всех файлов одновременно.
for f in sweep-c*.json; do
jq -r --arg f "$f" \
'[$f, .output_throughput, .total_token_throughput,
.median_ttft_ms, .p99_ttft_ms] | @tsv' "$f"
doneoutput_throughput — это количество выходных токенов в секунду. total_token_throughput добавляет входные токены, поэтому при соотношении 1:1 значение будет почти вдвое больше. p99_ttft_ms существует только потому, что --metric-percentiles включает 99; если запросить процентиль, который вы не запрашивали, jq выведет null.
Что на самом деле показывает тест на параллелизм?
The data behind this chart
[
{
"label": "1 stream",
"per_stream_tok_s": 92,
"total_tok_s": 92,
"ttft_p50_ms": 48,
"ttft_p99_ms": 61
},
{
"label": "4 streams",
"per_stream_tok_s": 88,
"total_tok_s": 352,
"ttft_p50_ms": 71,
"ttft_p99_ms": 96
},
{
"label": "16 streams",
"per_stream_tok_s": 71,
"total_tok_s": 1136,
"ttft_p50_ms": 152,
"ttft_p99_ms": 244
},
{
"label": "32 streams",
"per_stream_tok_s": 54,
"total_tok_s": 1728,
"ttft_p50_ms": 287,
"ttft_p99_ms": 498
},
{
"label": "64 streams",
"per_stream_tok_s": 34,
"total_tok_s": 2176,
"ttft_p50_ms": 611,
"ttft_p99_ms": 1240
},
{
"label": "128 streams",
"per_stream_tok_s": 18,
"total_tok_s": 2304,
"ttft_p50_ms": 1490,
"ttft_p99_ms": 3820
}
]Эти 6 строк иллюстрируют форму, которую принимает результат теста на небольшом арендуемом GPU-сервере, с правдоподобными порядками величин. Это не замеры вашего сервера и не показатели от поставщика. Запустите цикл выше и замените их своими данными.
Анализируйте форму, так как именно она является обобщаемой характеристикой. При одном потоке весь сервер выдает 92 токенов в секунду с p99 TTFT, равным 61 мс. При 128 streams общая производительность достигает 2304 токенов в секунду, что в двадцать пять раз выше, в то время как скорость каждого отдельного потока падает до 18 токенов в секунду, а p99 TTFT достигает 3820 мс. Общая пропускная способность растет, потому что пакетная обработка превращает ожидание памяти в полезную работу. Скорость на поток падает, так как одни и те же вычислительные ресурсы теперь распределяются между всеми.
Последнее удвоение — ключевой индикатор. Переход от 64 к 128 потокам добавляет менее шести процентов к общему результату, в то время как p99 TTFT увеличивается примерно в три раза. Это означает, что KV cache переполнен и запросы встают в очередь, вместо того чтобы выполняться. Полезная рабочая точка находится раньше: при 32 потоках сервер все еще выдает 1728 токенов в секунду, что составляет 75 процентов от пиковой нагрузки, при 54 токенов в секунду на поток и p99 TTFT, равном 498 мс. Указывайте эту точку как свою емкость. Пик кривой — это значение, при котором вы не сможете обслуживать пользователей.
Ollama и vLLM измеряют разные показатели
Запустите этот тест для стандартного сервера Ollama, и итоговый результат почти не изменится. Значение OLLAMA_NUM_PARALLEL по умолчанию равно 1, поэтому выполняется только один запрос, пока остальные ожидают. Именно очередь приводит к росту p99 TTFT, в то время как общая производительность остается неизменной. Увеличьте это значение перед проведением любых измерений.
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=8"Перезапустите сервер с параметром sudo systemctl restart ollama, затем убедитесь, что модель по-прежнему помещается в память. Каждый параллельный слот получает свою долю контекстного окна, поэтому в документации Ollama указано, что контекст 2K при 4 параллельных запросах потребует 8K. Если увеличить количество слотов слишком сильно, модель выйдет за пределы VRAM. Проверьте ollama ps: если в столбце PROCESSOR отображается значение вроде 48%/52% CPU/GPU, это означает, что часть модели выгружена в CPU. В этом случае пропускная способность будет падать при увеличении параллелизма, а не расти. После заполнения параллельных слотов запросы выстраиваются в очередь до OLLAMA_MAX_QUEUE (по умолчанию 512), после чего сервер начинает отвечать ошибкой 503.
vLLM использует непрерывную пакетную обработку (continuous batching), поэтому новые запросы добавляются в текущий пакет по мере освобождения слотов, а кривая производительности продолжает расти, пока не закончится кэш KV. Ollama оптимизирована для работы с одной моделью на одной машине с минимальными затратами на настройку. Таким образом, эти два движка дают разные ответы на один и тот же тест, что и является основной темой статьи Сравнение Ollama и vLLM в качестве движков для обслуживания моделей. Фиксируйте, какой движок и какая версия были использованы для получения каждого значения.
Пять способов измерить неверные показатели
- Клиент находится слишком далеко. Запуск тестов производительности с вашего ноутбука через интернет добавляет время кругового обхода (RTT) к каждому значению TTFT, поэтому вы измеряете качество вашего домашнего соединения. Запускайте клиент в том же регионе, где находится сервер.
- Модель была «холодной». Первый запрос оплачивает загрузку весов, а в vLLM — еще и захват графа. Отправьте пакет для прогрева и отбросьте результат.
- Кэширование префиксов сработало за вас. vLLM по умолчанию включает автоматическое кэширование префиксов, поэтому отправка одного и того же промпта снова и снова измеряет кэш, а не этап prefill, и значение TTFT падает до доли от реального показателя.
--dataset-name randomпозволяет избежать этого, так как каждый промпт уникален. Чтобы убедиться в этом, запустите сервер с флагом--no-enable-prefix-caching. - Ответы были слишком короткими. При ответах длиной 32 токена TTFT доминирует в каждом запросе, и ваш показатель токенов в секунду фактически описывает только prefill. Используйте
--ignore-eosс реалистичной длиной вывода. - Вы сообщили о конкурентности 1. Это самое «дружелюбное» число в таблице, но оно не имеет никакого отношения к стоимости.
Превращение полученных данных в решение
Возьмите значение пропускной способности при насыщении, полученное в ходе тестирования (а не скорость одного потока), и сравните его с ценой за токен. Точка безубыточности вычисляется одним делением:
break_even_tok_s = (price_per_hour / price_per_million_output_tokens) * 1000000 / 3600Рассмотрим расчет на примере цен DigitalOcean за июль 2026 года. Выделенный инференс-эндпоинт H200 стоил 4.47 доллара в час, а серверный эквивалент — 0.65 доллара за миллион токенов. Делим 4.47 на 0.65, получаем 6.88 миллиона токенов в час. Делим на 3,600 секунд и получаем около 1,910 выходных токенов в секунду. Цены — их, расчет — наш. При проверке 7 октября 2026 года эндпоинт H200 всё ещё стоил 4.47 доллара в час, но 18 августа 2026 года DigitalOcean прекратил поддержку серверного Llama 3.3 70B и перенаправил пользователей на Llama 4 Maverick по цене 0.87 доллара за миллион выходных токенов. Это другая модель, поэтому перед сравнением для неё требуется провести собственное тестирование.
Ключевое слово здесь — устойчивая нагрузка. Достижение 1,910 токенов в секунду при насыщении в течение двух часов в день не означает 1,910 токенов в секунду устойчиво, так как вы платите и за остальные двадцать два часа. Аренда карты в виде GPU Droplet не упрощает задачу: на 7 октября 2026 года цена DigitalOcean Droplet для H200 составляла 4.47 доллара в час по запросу, как и для эндпоинта. Самые дешевые NVIDIA Droplets — RTX 4000 за 0.76 доллара в час и L40S или RTX 6000 за 1.57 доллара в час — слишком малы для размещения модели 70B в формате FP8. Таким образом, согласно приведенным цифрам, H200, который достигает насыщения на 2,036 выходных токенах в секунду, должен выдавать в среднем около 94 процентов от этого пика каждый час, за который вы платите, чтобы сравняться с ценой 0.65 доллара за миллион токенов. Если нагрузка ниже, выгоднее платить за токен. Обычно именно часы простоя GPU, а не низкая скорость генерации токенов, делают self-hosting невыгодным.
Итак, ваше решение зависит от двух факторов. Тестирование дает вам верхний предел. Ваш профиль трафика определяет, какую часть этого предела вы реально используете. Перемножьте эти значения, затем перейдите к сравнению GPU VPS и API с оплатой за токен и найдите ответ для вашего объема данных.
FAQ
Какое значение токенов в секунду считается хорошим для self-hosted LLM?
Существует два ответа, так как эта метрика выполняет две разные задачи. Для одного пользователя, читающего вывод, любое значение выше примерно 20 токенов в секунду на поток уже превышает скорость чтения, поэтому дальнейшее увеличение не дает преимуществ. С точки зрения затрат важным показателем является общая пропускная способность при полной загрузке, и «хорошим» считается значение, которое позволяет выйти на окупаемость. При стоимости $0.65 за миллион токенов на сервере ценой $4.47 в час, этот порог составляет около 1,910 токенов в секунду при стабильной нагрузке по ценам июля 2026 года. Один поток на большой модели никогда не достигает такого уровня, именно поэтому существует пакетная обработка (batching).
Почему пропускная способность Ollama не растет при добавлении параллельных запросов?
OLLAMA_NUM_PARALLEL по умолчанию равно 1, поэтому сервер выполняет только один запрос за раз для каждой модели, а остальные ставит в очередь до достижения OLLAMA_MAX_QUEUE (по умолчанию 512), после чего возвращает 503. Общий объем вывода остается неизменным, в то время как p99 TTFT растет — это характерный признак очереди, а не загруженного GPU. Установите переменную в drop-in файле systemd и перезапустите сервис, затем проверьте ollama ps, так как каждый параллельный слот увеличивает объем выделенного контекста и может привести к вытеснению части модели в CPU.
Что лучше измерять: время до первого токена (TTFT) или токены в секунду?
И то, и другое, так как при росте нагрузки эти показатели меняются в противоположных направлениях. TTFT — это то, что ощущает пользователь, а общая пропускная способность при полной загрузке — это то, что отражается в вашем счете. Фиксируйте p50 и p99 TTFT на каждом этапе увеличения параллелизма, затем выберите максимальный уровень параллелизма, при котором p99 TTFT остается для вас приемлемым. Указывайте пропускную способность именно в этой точке как свою рабочую емкость, а не максимальное значение с пика кривой.
Означает ли более высокое число токенов в секунду всегда более низкую стоимость токена?
Нет. Стоимость токена — это почасовая цена, деленная на количество токенов, которые сервер фактически произвел за этот час, поэтому быстрый сервер, простаивающий большую часть дня, все равно имеет высокую стоимость токена. Ее определяет коэффициент использования, а не пиковая скорость. Также следите за единицами измерения: заявленная общая пропускная способность учитывает входные токены, поэтому при соотношении входных и выходных данных 1:1 она почти вдвое превышает скорость вывода, за которую вы платите.