ローカルLLMのtokens per secondを正しく測る方法
GPUレンタルと従量課金の損益分岐点を判断するため、tokens per secondを測定します。単一リクエストではなく同時実行数を変え、実測値から必要なスループットを計算する方法を解説します。
GPU が採算に合うかどうかを決める tokens per second
Tokens per second は、サーバーが出力テキストを生成する速度です。また、GPU を借りるほうが API にトークン単位で料金を支払うより安くなるかどうかを決める数値でもあります。GPU サーバーは、稼働中かアイドル状態かにかかわらず時間単位で課金されます。一方、ホスト型 API はトークン単位で課金されます。そのため、GPU が有利になるのは、料金を支払う時間の大部分で十分に高い出力速度を維持できる場合だけです。
つまり、どこかで読んだ数値ではなく、実測値が必要です。このページでは、記録する価値のある4つの数値を定義し、それらを取得するコマンドと、数値を判断に変換する計算方法を示します。
公開された tokens per second の値が自分の環境の値にならない理由
DigitalOcean は 2026 年 7 月、vLLM 上で NVIDIA H200 1 台に llama3.3-70b-instruct を FP8(8-bit floating point)で実行した場合のスループット値を公開しました。これらの値は参考になりますが、そのまま自分の環境の値にはなりません。
このグラフのデータ
[
{
"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"
}
]上の各行はすべてそのページから引用したものです。このうち 2 つは、ページに示された範囲の下限値なので、最低値として読み取ってください。この表の値は、私たちが測定したものではありません。
まず最後の 2 行を見ます。見出しの値は 4,071.6 tok/s ですが、出力だけのレートは 2,036 tok/s です。見出しの値は、入力トークンと出力トークンを合算しています。このテストでは、入力 1,024 トークンに対して出力 1,024 トークンを生成しているため、見出しの値のほぼ半分が出力分です。この内訳は重要です。出力側の処理が遅いからです。Prefill(プロンプトの読み込み)では、すべての入力トークンを 1 回で処理します。Decode(回答の生成)では、1 回に 1 トークンずつ生成します。合計スループットの見出し値は、処理コストの低い処理と高い処理を平均した値です。
次に最初の行を見ます。同じ H200 で一度に 1 件だけリクエストを処理すると、47 tok/s になります。つまり、同じハードウェアでも、飽和状態の値は 40 倍を超えます。この差が生じるのは、単一の Decode ステップでは処理時間の大半で GPU がメモリ待ちになる一方、同時実行するリクエストがそのアイドル時間を埋めるためです。2 行目の 236 tok/s は、同じモデルを NVIDIA H100 1 台で実行した値です。KV cache(key and value cache。処理中の会話ごとにカード上で保持されるメモリ)の制約を受けます。80 GB のカードでは、70B モデルで同時に処理できるリクエスト数が少ないため、より低い値で飽和します。
モデルを変更するか、入力と出力の比率を変更すると、上の数値はすべて変わります。公開された値は期待値を設定するためのものであり、予算を決めるためのものではありません。これは、ディスクとネットワークについて VPS を正しくベンチマークする 場合にも当てはまる原則です。
重要な4つの指標
- 初回トークンまでの時間、TTFT。 リクエストを送信してから、最初の出力トークンが到着するまでの遅延です。prefill 時間とキュー待ち時間の合計です。ユーザーが直接体感する指標です。
- ストリームあたりの出力トークン数/秒。 生成が始まってから、1つの回答が書き出される速さです。およそ20 tok/sを超えると、ほとんどの人の読速をすでに上回るため、ここで速度をさらに上げても効果は限定的です。
- 負荷飽和時の総出力スループット。 サーバーに最大負荷をかけた状態で、同時実行中のすべてのストリームの合計値です。処理能力を示す指標であり、GPU の費用に直結します。
- 同時実行時の p50 および p99 TTFT。 p50 はリクエストの中央値です。p99 は、100件中99件がこの値未満に収まる値です。キュー待ちは、まず p99 に現れます。
最初の2つは、サーバーが空いているときに改善します。3つ目は、サーバーが混雑しているときに改善します。これらは互いに相反するため、推論サーバーの性能を単一の数値で表すことはできません。
測定前に入力長と出力長を固定する
スループットはトラフィックの形状に左右されます。4,000 トークンのプロンプトに 50 トークンの回答を返す処理は、prefill が中心です。200 トークンのプロンプトに 2,000 トークンの回答を返す処理は、decode が中心です。同じサーバーでも、この 2 つでは 1 秒あたりのトークン数が大きく異なります。そのため、比率を 1 つ選び、記録するすべての数値の横に明記してください。異なる比率の数値は比較しないでください。入力 1,024、出力 1,024 は、複数のベンダーがこの比率で数値を公開しているため、妥当なデフォルトです。実際のトラフィックが分かっている場合は、実際のトラフィックを使用してください。
出力長も固定してください。モデルが 60 トークンで stop token に到達すると、完全な長さの実行とは比較できない短い実行になります。TTFT の占める割合が大きくなるためです。その結果、各リクエストの完了は早くても、ベンチマーク上の出力スループットは低下します。vLLM benchmark client の --ignore-eos flag を使うと、すべてのリクエストで指定した数のトークンを正確に生成できます。これにより、2 つの実行結果を比較できます。これらの数値には、どの flag よりもモデルの選択が大きく影響します。メモリ面の選択については、Qwen 3 model を単一の VPS GPU に収める方法で説明しています。
最初に1ストリームを測定する
最も単純なケースから始めます。これは妥当性確認であり、上限値でもあります。Ollama は独自の処理時間を出力します。
ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."確認する行は eval rate です。これは1秒あたりの出力トークン数です。prompt eval rate は prefill の速度で、load duration はモデルを VRAM に読み込むのにかかった時間です。コールドスタート後の最初の呼び出しでは load duration が大きいため、total duration は誤解を招きます。コマンドを2回実行し、2回目の結果を読み取ってください。Ollama はデフォルトで、アイドル状態のモデルを5分後にアンロードします。そのため、実行間隔が長いと再びコールドスタートになります。
同じフィールドは 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 で割ると秒になります。この割り算は、tokens per second について Ollama の API ドキュメントが指定している方法そのものです。サーバーがまだ起動していない場合は、VPS 上で Ollama を使用して LLM をセルフホストするでインストールと systemd unit の設定を確認できます。
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 は、レスポンスボディの最初のバイトが到着した時点です。ストリーミング chat completion では、そのバイトは最初の server-sent event に属します。このイベントは、最初のコンテンツトークンか、その直前に送られる role-only delta のいずれかです。そのため、この値は1イベント分の誤差を含む TTFT として扱ってください。同じサーバーで2回実行した結果を比較するには十分な精度です。
1ストリームの数値は、サーバーの性能を2重に実際より良く見せます。自分より前にキューへ入っているリクエストがないため、TTFT は実現可能な最良値です。カード全体で1つのリクエストだけを処理するため、ストリームごとの速度も実現可能な最良値です。どちらの値も、そのサーバーが実際に処理できる負荷を示してはいません。
同時実行数のスイープはどのように実行しますか?
スイープでは、同じワークロードを同時実行数だけ増やしながら実行し、各段階の結果を記録します。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 は送信する総リクエスト数なので、安定した平均値を得るには同時実行数の約 10 倍にします。サマリーには Output token throughput (tok/s): と Total token throughput (tok/s): が表示され、続いて Time to First Token という見出しの下に Mean TTFT (ms):、Median TTFT (ms):、P99 TTFT (ms): が表示されます。
この出力にはストリームごとのレートはありませんが、1 回の割り算で求められます。Mean TPOT (ms): は、最初のトークンを除く出力トークン 1 個あたりの平均時間です。したがって、1 トークンあたり 25 ms なら、ストリームあたり毎秒 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 ファイルを読み取る
各実行で 1 つのファイルが書き込まれるため、必要なフィールドをすべてのファイルからまとめて取り出します。
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 は 1 秒あたりの出力トークン数です。total_token_throughput は入力トークンも加算するため、入力と出力の比率が 1:1 なら、値はほぼ 2 倍になります。--metric-percentiles がなくても p99_ttft_ms は表示されます。このフラグのデフォルト値が 99 だからです。コマンド中の 50 によって median_ttft_ms の隣に p50_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 サーバーでスイープを実行した場合の形状を示す例です。お使いのサーバーで測定した値ではなく、ベンダーの公表値でもありません。上のループを実行し、値を実測結果に置き換えてください。
重要なのは形状です。形状から一般化できる傾向を読み取ります。ストリームが 1 本の場合、サーバー全体のスループットは毎秒 92 トークンで、p99 TTFT は 61 ms です。128 streams では、全体のスループットが毎秒 2304 トークンに達し、25 倍に増加します。一方、各ストリームの速度は毎秒 18 トークンに低下し、p99 TTFT は 3820 ms に達します。バッチ処理によって、アイドル状態だったメモリ待ち時間が有効な処理に変わるため、全体のスループットは向上します。同じ計算資源を共有するため、ストリームごとの速度は低下します。
最後の倍増が判断材料になります。ストリーム数を 64 から 128 に増やしても、全体のスループットは 6% 未満しか増えません。一方、p99 TTFT はおよそ 3 倍になります。これは、KV cache が満杯になったか、vLLM の max_num_seqs のようなスケジューリング上限に達したため、リクエストが処理されずキューに滞留していることを示します。実用的な運用点は、その手前です。ストリーム数が 32 の場合、サーバー全体で毎秒 1728 トークンを返します。これはピークの 75% です。ストリームごとの速度は毎秒 54 トークンで、p99 TTFT は 498 ms です。この点を容量として報告してください。曲線のピーク値を、そのままユーザー向けの提供能力として使うことはできません。
Ollama と vLLM は同じ指標を測定しない
デフォルト設定の Ollama サーバーにその負荷掃引を実行しても、合計値はほとんど変わりません。OLLAMA_NUM_PARALLEL のデフォルト値は 1 なので、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 は連続バッチ処理を使用します。そのため、スロットが空くと実行中のバッチに新しいリクエストを追加します。通常は KV cache が枯渇するまで、スループットの曲線は上昇し続けます。ただし、max_num_seqs のような設定上限や CPU スケジューリングのオーバーヘッドによって、より早く頭打ちになることがあります。Ollama は、1 つのモデルを 1 台のマシンで運用し、導入コストを抑えることに最適化されています。そのため、同じ負荷掃引でも、2 つのエンジンは異なる結果を示します。これが Ollama と vLLM をサービングエンジンとして比較する ことの本質です。すべての数値について、どのエンジンのどのバージョンが生成したものかを記録してください。
誤った対象を測定する5つの例
- クライアントが遠い。 インターネット経由でラップトップからベンチマークすると、すべての TTFT に自宅からのラウンドトリップ時間が加わるため、自宅の接続を測定することになります。クライアントはサーバーと同じリージョンで実行します。
- モデルがコールド状態だった。 最初のリクエストでは重みのロードに時間がかかります。vLLM ではグラフキャプチャの時間も加わる場合があります。ウォームアップ用のバッチを送信し、その結果は破棄します。
- プレフィックスキャッシュが応答した。 vLLM では自動プレフィックスキャッシュがデフォルトで有効です。そのため、同じプロンプトを繰り返し送信すると、prefill ではなくキャッシュを測定することになり、TTFT は実際の値のごく一部まで短縮されます。
--dataset-name randomでは毎回異なるプロンプトを使うため、この問題を回避できます。確実に無効化するには、--no-enable-prefix-cachingを指定してサーバーを起動します。 - 出力が短かった。 32 トークンの回答では、各リクエストで TTFT の影響が大きくなり、tokens per second は実際には prefill を示す値になります。現実的な出力長を指定して
--ignore-eosを使用します。 - 同時実行数 1 だけを報告した。 これは表の中で最も有利な TTFT になります。また、実際のリクエストが重ならない場合に限り、正しいコスト計算の入力になります。リクエストが重なる場合、コストは実際のトラフィックによって発生する同時実行数で維持できるスループットに依存します。そのため、その条件でも測定します。
測定値を判断に結び付ける
スイープによって得られた出力スループットを、実際のトラフィックが到達する同時実行数で確認し、トークン単価と比較します。十分な数のリクエストが重なってバッチが常に満たされる場合に限り、その値を飽和時の値として扱えます。1 回に 1 つのリクエストだけを送るワークロードでは、シングルストリームのレートを使う必要があります。各出力トークンに伴う入力トークンの価格を計算すれば、損益分岐点は 1 回の割り算で求められます。
cost_per_million_output = price_out + price_in * (input_tokens / output_tokens)
break_even_tok_s = (price_per_hour / cost_per_million_output) * 1000000 / 3600DigitalOcean の 2026 年 7 月の価格で計算します。同社の H200 専用推論エンドポイントは 1 時間あたり $4.47 で、サーバーレスの Llama 3.3 70B は入力が 100 万トークンあたり $0.65、出力も 100 万トークンあたり $0.65 でした。上記で使用した 1:1 の比率では、出力トークン 100 万個ごとに入力トークンも 100 万個発生するため、出力トークン 100 万個あたりの実効価格は $1.30 です。したがって、4.47 を 1.30 で割ると、1 時間あたり約 344 万個の出力トークンになります。これを 3,600 秒で割ると、毎秒約 955 個の出力トークン、入力と出力を合わせると毎秒約 1,910 トークンです。価格は同社のものです。割り算は私たちが行ったものです。2026 年 10 月 25 日に確認した時点でも H200 エンドポイントは 1 時間あたり $4.47 でしたが、DigitalOcean は 2026 年 8 月 18 日にサーバーレスの Llama 3.3 70B を非推奨とし、Llama 4 Maverick を案内しています。Llama 4 Maverick の価格は入力が 100 万トークンあたり $0.25、出力が 100 万トークンあたり $0.87 です。これは別のモデルなので、比較を有効にするには独自のスイープが必要です。
この判断を左右する言葉は「継続的」です。飽和状態で 1 日 2 時間、毎秒 955 個の出力トークンを処理できても、継続的に毎秒 955 個の出力トークンを処理できることにはなりません。残りの 22 時間分も支払う必要があるためです。GPU Droplet としてカードを借りても、基準は下がりません。2026 年 10 月 25 日時点の DigitalOcean の Droplet 価格では、H200 のオンデマンド価格は 1 時間あたり $4.47 で、エンドポイントと同じでした。また、最も安価な NVIDIA Droplet である 1 時間あたり $0.76 の RTX 4000 と、1 時間あたり $1.57 の L40S または RTX 6000 は、FP8 の 70B モデルを収容するには小さすぎます。したがって上記の数値では、2,036 出力トークン/秒で飽和する H200 が、入出力合わせて 100 万トークンあたり $0.65 という価格に対抗するには、支払い対象となるすべての時間でピーク値の約 47% を平均して処理する必要があります。それを下回る場合は、トークン単価のほうが有利です。通常、セルフホスティングの採算を崩すのは遅いトークンではなく、アイドル状態の GPU 時間です。
したがって、判断には 2 つの入力があります。スイープからは上限値が得られます。実際のトラフィックパターンからは、その上限値のうち実際に利用できる割合が分かります。両者を掛け合わせ、その結果を GPU VPS とトークン単価 API の損益分岐点 に当てはめれば、利用量に応じた答えを読み取れます。
FAQ
自己ホスト型 LLM では、tokens per second はどの程度が適切ですか?
この指標には 2 つの用途があるため、答えは 2 つあります。1 人で出力を読む場合、ストリームあたりの出力が約 20 tokens per second を超えていれば、読む速度をすでに上回っています。そのため、それ以上速くしても効果はありません。コストの観点では、実際の負荷で維持できる総出力スループットが重要です。損益分岐点を超えられる値であれば十分です。入力と出力の双方が 1 million tokens あたり $0.65、サーバー費用が 1 時間あたり $4.47 の場合、2026 年 7 月の価格では、入力と出力の比率が 1:1 なら、損益分岐点は継続的な出力約 955 tokens per second です。大規模モデルを 1 ストリームで実行しても、この値には達しません。これが batching を使う理由です。
同時リクエストを増やしても Ollama のスループットが一定なのはなぜですか?
OLLAMA_NUM_PARALLEL のデフォルト値は 1 です。そのため、サーバーはモデルごとに 1 リクエストずつ処理し、残りをキューに入れます。OLLAMA_MAX_QUEUE(デフォルトは 512)を超えると 503 を返します。p99 TTFT が上昇する一方で総出力が一定なら、GPU が高負荷なのではなく、キューが詰まっている状態です。systemd の drop-in でこの変数を設定して再起動します。その後、ollama ps を確認してください。並列スロットごとに割り当てられるコンテキストが増えるため、モデルの一部が CPU に移る可能性があります。
time to first token と tokens per second のどちらを測定すべきですか?
両方を測定してください。負荷が上がると、この 2 つは反対方向に変化するためです。TTFT はユーザーが体感する待ち時間であり、飽和時の出力スループットは請求額に反映されます。並行数を変えるたびに p50 と p99 の TTFT を記録し、p99 TTFT が許容範囲に収まる最大の並行数を選びます。その時点のスループットを容量として報告します。曲線の最上部で得られる最大値を使わないでください。
tokens per second が高いほど、常に token あたりのコストは下がりますか?
いいえ。token あたりのコストは、1 時間あたりの料金を、その時間にサーバーが実際に生成した tokens 数で割って求めます。そのため、高速なサーバーでも、1 日の大半がアイドル状態なら token あたりのコストは高くなります。ピーク速度ではなく、利用率がコストを決めます。単位にも注意してください。表示された総 token スループットに入力 tokens が含まれる場合、入力と出力の比率が 1:1 なら、出力レートのほぼ 2 倍になります。比較対象の API が入力 tokens と出力 tokens の両方に課金する場合は、比較前に両方の料金を計算してください。