SSD Nodes Learn 🎉 VPS от $4.99/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-07

Как измерить токены в секунду для локальной LLM

Аренда GPU выгодна только при высокой нагрузке. Проведите замер пропускной способности с помощью vLLM и определите порог окупаемости, сравнив стоимость аренды и API за токен.

Почему количество токенов в секунду определяет экономическую эффективность GPU

Количество токенов в секунду — это скорость генерации текстового вывода вашим сервером. Именно этот показатель определяет, выгоднее ли арендовать GPU, чем оплачивать API за каждый токен. Аренда GPU оплачивается по часам независимо от того, простаивает сервер или выполняет задачи. Использование API оплачивается по количеству токенов. Таким образом, GPU становится выгоднее только в том случае, если вы поддерживаете достаточно высокую скорость вывода в течение большей части оплачиваемого времени.

Это означает, что вам нужны собственные измерения, а не теоретические цифры из документации. На этой странице определены четыре показателя, которые необходимо фиксировать, приведены команды для их получения и формулы, позволяющие принять решение.

Почему опубликованный показатель токенов в секунду — это не ваша цифра

В июле 2026 года DigitalOcean опубликовала показатели пропускной способности для одного NVIDIA H200, работающего с llama3.3-70b-instruct в формате FP8 (8-битное число с плавающей запятой) под управлением vLLM. Эти цифры полезны, но они не относятся к вашим задачам.

ChartPublished DigitalOcean H200 figures, llama3.3-70b-instruct FP8, July 2026
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. Если сервер еще не запущен, в руководстве self-hosting an LLM with Ollama on a VPS описаны установка и настройка systemd-юнита.

Для измерения TTFT требуется потоковый запрос, и 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"
done

output_throughput — это количество выходных токенов в секунду. total_token_throughput добавляет входные токены, поэтому при соотношении 1:1 значение будет почти вдвое больше. p99_ttft_ms существует только потому, что --metric-percentiles включает 99; если запросить перцентиль, который вы не запрашивали, jq выведет null.

Что на самом деле показывает проверка параллелизма?

ChartIllustrative sweep shape: 8B model, one 24 GB GPU, 1024 in / 1024 out
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 cache. 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 выходных токенов в секунду. Цены — их, деление — наше.

Ключевое слово здесь — устойчивая нагрузка. Достижение 1,910 токенов в секунду при полной загрузке в течение двух часов в день не означает 1,910 токенов в секунду устойчиво, так как вы платите и за остальные двадцать два часа. Собственная точка перехода DigitalOcean для более дешевого GPU Droplet стоимостью 3.44 доллара в час достигается при 72.2 процентах устойчивой средней утилизации; ниже этого уровня выгоднее платить за токен. Обычно самохостинг становится невыгодным не из-за медленной генерации токенов, а из-за простоя GPU.

Таким образом, ваше решение зависит от двух входных данных. Тестирование дает вам верхний предел. Ваш профиль трафика определяет долю этого предела, которую вы реально используете. Перемножьте их, затем перейдите к сравнению GPU VPS и API с оплатой за токен и найдите ответ для вашего объема нагрузки.

FAQ

Какое значение tokens per second считается хорошим для self-hosted LLM?

Существует два ответа, так как эта метрика выполняет две разные задачи. Для одного пользователя, читающего вывод, любое значение выше примерно 20 tokens per second на поток уже превышает скорость чтения, поэтому дальнейшее увеличение не дает преимуществ. С точки зрения затрат важным показателем является совокупная пропускная способность при полной загрузке, и «хорошим» считается значение, позволяющее выйти на окупаемость. При стоимости $0.65 за миллион токенов и цене аренды сервера $4.47 в час, порог окупаемости составляет около 1,910 выходных токенов в секунду (по ценам на июль 2026 года). Один поток на большой модели никогда не достигает этого значения, поэтому и используется пакетная обработка (batching).

Почему пропускная способность Ollama не растет при добавлении параллельных запросов?

OLLAMA_NUM_PARALLEL по умолчанию равно 1, поэтому сервер обрабатывает запросы по очереди для каждой модели, помещая остальные в очередь до достижения OLLAMA_MAX_QUEUE (по умолчанию 512), после чего возвращает 503. Общая скорость вывода остается неизменной, а p99 TTFT растет — это характерный признак очереди, а не перегрузки GPU. Установите переменную в systemd drop-in файле и перезапустите сервис, затем проверьте ollama ps, так как каждый параллельный слот увеличивает объем используемого контекста и может привести к вытеснению части модели в CPU.

Что лучше измерять: time to first token или tokens per second?

Оба показателя, так как при росте нагрузки они меняются в противоположных направлениях. TTFT определяет пользовательский опыт, а совокупная пропускная способность при полной загрузке — ваши расходы. Фиксируйте p50 и p99 TTFT на каждом уровне параллелизма, затем выберите максимальный уровень параллелизма, при котором p99 TTFT остается приемлемым для вас. Указывайте пропускную способность в этой точке как вашу рабочую емкость, а не пиковое значение с вершины графика.

Означает ли более высокое значение tokens per second всегда более низкую стоимость токена?

Нет. Стоимость токена — это почасовая цена, деленная на количество токенов, фактически произведенных сервером за этот час. Поэтому быстрый сервер, который простаивает большую часть дня, все равно имеет высокую стоимость токена. Решающим фактором является утилизация, а не пиковая скорость. Также следите за единицами измерения: заявленная общая пропускная способность учитывает входные токены, поэтому при соотношении входных и выходных данных 1:1 она почти вдвое превышает скорость вывода, за которую вы платите.

#benchmarking#tokens-per-second#vllm#ollama#gpu-vps