Как измерить токены в секунду для локальной LLM
Узнайте, как правильно рассчитать пропускную способность GPU через замеры с разной степенью параллелизма. Определите порог рентабельности аренды сервера вместо оплаты API.
Почему количество токенов в секунду определяет рентабельность GPU
Количество токенов в секунду — это скорость, с которой ваш сервер генерирует выходной текст. Именно этот показатель определяет, выгоднее ли арендовать GPU, чем оплачивать API за каждый токен. Аренда GPU оплачивается по часам независимо от того, простаивает сервер или работает. API оплачивается по количеству токенов. Поэтому использование GPU выгодно только в том случае, если вы поддерживаете достаточно высокую скорость генерации на протяжении большей части оплачиваемого времени.
Это означает, что вам нужны собственные измерения, а не теоретические цифры из документации. На этой странице определены четыре показателя, которые стоит фиксировать, приведены команды для их получения и формулы, позволяющие принять решение.
Почему опубликованные показатели токенов в секунду не являются вашими цифрами
В июле 2026 года DigitalOcean опубликовала показатели пропускной способности для одного NVIDIA H200, работающего с llama3.3-70b-instruct в формате FP8 (8-битное число с плавающей запятой) под управлением vLLM. Эти цифры полезны, но они не относятся к вашим задачам.
Данные, на которых построен график
[
{
"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 на выходе является разумным стандартом, так как многие вендоры публикуют показатели именно для него. Если вы знаете характеристики своего реального трафика, используйте их.
Также принудительно задавайте длину выходных данных. Модель, которая достигает токена остановки (stop token) через 60 токенов, выдает короткий результат, который нельзя сравнивать с полным: TTFT (время до первого токена) начинает занимать большую долю в общем времени, что снижает выходную пропускную способность в рамках бенчмарка, даже если каждый запрос завершается быстрее. Флаг --ignore-eos в клиенте бенчмарка vLLM заставляет каждый запрос генерировать ровно указанное количество токенов, благодаря чему результаты двух запусков остаются сопоставимыми. Выбор модели влияет на эти показатели сильнее, чем любой флаг: в статье размещение модели Qwen 3 на одном GPU VPS рассматриваются вопросы выбора модели с точки зрения потребления памяти.
Измерение одного потока
Начните с самого простого случая. Это проверка работоспособности и определение верхнего предела производительности. 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 здесь максимально возможное, так как перед вами нет очереди запросов. Скорость для одного потока также является максимально возможной, так как вся видеокарта обслуживает только один запрос. Ни один из этих показателей не отражает реальную нагрузочную способность сервера.
Как выполнить замер производительности при разной конкурентности?
Замер выполняется путем запуска фиксированной рабочей нагрузки при возрастающей конкурентности с записью результатов на каждом шаге. 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. Число 50 в команде добавляет p50_ttft_ms рядом с median_ttft_ms, который получает каждая метрика, указанная в --percentile-metrics. Если запросить процентиль, который вы не запрашивали, например .p90_ttft_ms, то jq выведет null.
Что на самом деле показывает проверка параллелизма?
Данные, на которых построен график
[
{
"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, либо из-за достижения сервером лимита планировщика, такого как max_num_seqs в vLLM. Оптимальная рабочая точка находится раньше: при 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-кэш, хотя настроенное ограничение, такое как max_num_seqs, или накладные расходы на планировщик CPU могут привести к выравниванию графика раньше. Ollama оптимизирована для работы с одной моделью на одной машине с минимальными затратами на настройку. Таким образом, эти два движка дают разные результаты при одном и том же тесте, что и является основной темой статьи Сравнение Ollama и vLLM в качестве движков для обслуживания моделей. Фиксируйте, какой движок и какая версия были использованы для получения каждого числа.
Пять способов измерить неверные показатели
- Клиент находится слишком далеко. Запуск тестов производительности с вашего ноутбука через интернет добавляет время кругового обхода (RTT) к каждому значению TTFT, поэтому вы измеряете качество вашего домашнего соединения. Запускайте клиент в том же регионе, где находится сервер.
- Модель была «холодной». Первый запрос оплачивает загрузку весов, а в vLLM — еще и захват графа. Отправьте пакет для прогрева и отбросьте полученный результат.
- Кэширование префиксов сработало за вас. vLLM по умолчанию включает автоматическое кэширование префиксов, поэтому отправка одного и того же промпта снова и снова измеряет кэш, а не префилл, и TTFT падает до доли от реального значения.
--dataset-name randomпозволяет избежать этого, так как каждый промпт уникален. Чтобы убедиться в этом, запустите сервер с--no-enable-prefix-caching. - Выходные данные были слишком короткими. При ответах длиной 32 токена TTFT доминирует в каждом запросе, и ваш показатель токенов в секунду фактически описывает префилл. Используйте
--ignore-eosс реалистичной длиной вывода. - Вы предоставили отчет только для конкурентности 1. Это дает самый привлекательный TTFT в таблице, и это верный показатель стоимости только в том случае, если ваши реальные запросы никогда не перекрываются. Если они перекрываются, стоимость зависит от пропускной способности, которую вы поддерживаете при уровне конкурентности, создаваемом вашим реальным трафиком, поэтому проводите измерения и в этих условиях.
Превращение измеренных данных в решение
Возьмите пропускную способность, которую показывает ваш замер при уровне конкурентности, соответствующем вашему реальному трафику, и сравните её с ценой за токен. Это значение будет показателем насыщения только в том случае, если запросов достаточно для полной загрузки батча; рабочая нагрузка, отправляющая по одному запросу за раз, будет использовать скорость одного потока. Точка безубыточности находится путем деления, как только вы оцените стоимость входных токенов, поступающих вместе с каждым выходным токеном:
cost_per_million_output = price_out + price_in * (input_tokens / output_tokens)
break_even_tok_s = (price_per_hour / cost_per_million_output) * 1000000 / 3600Рассчитаем это на примере цен DigitalOcean за июль 2026 года. Их выделенный инференс-эндпоинт H200 стоил 4.47 доллара в час, а serverless Llama 3.3 70B — 0.65 доллара за миллион входных токенов и 0.65 доллара за миллион выходных. При соотношении 1:1, использованном выше, каждый миллион выходных токенов сопровождается миллионом входных, поэтому эффективная цена составляет 1.30 доллара за миллион выходных токенов. Таким образом, 4.47, деленное на 1.30, дает около 3.44 миллиона выходных токенов в час, а деление на 3,600 секунд дает около 955 выходных токенов в секунду, или около 1,910 токенов в секунду с учетом входных и выходных данных вместе. Цены — их. Деление — наше. Когда мы проверяли данные 25 октября 2026 года, эндпоинт H200 все еще стоил 4.47 доллара в час, но DigitalOcean прекратил поддержку serverless Llama 3.3 70B 18 августа 2026 года и перенаправляет пользователей на Llama 4 Maverick, цена на которую составляет 0.25 доллара за миллион входных токенов и 0.87 доллара за миллион выходных. Это другая модель, поэтому перед сравнением для нее требуется собственный замер.
Ключевое слово здесь — устойчивая нагрузка. Достижение 955 выходных токенов в секунду при насыщении в течение двух часов в день — это не 955 выходных токенов в секунду в устойчивом режиме, потому что вы платите и за остальные двадцать два часа. Аренда карты в виде GPU Droplet не меняет ситуацию: 25 октября 2026 года цены DigitalOcean Droplet указывали H200 по 4.47 доллара в час при оплате по требованию, столько же, сколько и эндпоинт, а их самые дешевые NVIDIA Droplets — RTX 4000 за 0.76 доллара в час и L40S или RTX 6000 за 1.57 доллара в час — слишком малы, чтобы вместить модель 70B в формате FP8. Исходя из приведенных цифр, H200, который достигает насыщения на 2,036 выходных токенах в секунду, должен в среднем выдавать около 47 процентов от этого пика в течение каждого оплачиваемого часа, чтобы просто соответствовать цене 0.65 доллара за миллион входных и выходных токенов; ниже этого уровня цена за токен оказывается выгоднее. Обычно именно часы простоя GPU, а не медленная генерация токенов, делают self-hosting нерентабельным.
Таким образом, ваше решение зависит от двух факторов. Замер дает вам потолок производительности. Ваш профиль трафика определяет долю этого потолка, которую вы реально используете. Перемножьте их, затем перейдите к сравнению GPU VPS и API с оплатой за токены и найдите ответ для вашего объема данных.
FAQ
Какое значение токенов в секунду считается хорошим для self-hosted LLM?
Существует два ответа, так как эта метрика выполняет две разные задачи. Для одного пользователя, читающего вывод, любое значение выше примерно 20 токенов в секунду на поток уже превышает скорость чтения, поэтому дальнейшее увеличение не дает преимуществ. С точки зрения затрат важна общая пропускная способность, которую вы можете поддерживать при реальной нагрузке; «хорошим» считается значение, позволяющее выйти на окупаемость. При цене 0.65 доллара за миллион токенов (как входных, так и выходных) на сервере стоимостью 4.47 доллара в час, порог окупаемости составляет около 955 выходных токенов в секунду при соотношении 1:1 и ценах июля 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 она почти вдвое выше скорости вывода. Если API, с которым вы сравниваете, тарифицирует как входные, так и выходные токены, рассчитайте стоимость обеих частей перед сравнением.