SSD Nodes Learn 🎉 VPS $4.99/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-07

ローカルLLMのtokens per secondを測る方法

GPUレンタルがトークン課金APIを上回る境界を判断できます。concurrency sweepで実測し、正しいtokens per secondと出力速度を比較する方法を解説します。

トークン毎秒がGPUの採算を左右する理由

トークン毎秒は、サーバーが出力テキストを生成する速度です。GPUを借りる費用が、トークン単位で課金されるAPIを利用する費用より安くなるかどうかを決める数値でもあります。GPUサーバーは、稼働中でもアイドル状態でも時間単位で課金されます。一方、ホスト型APIはトークン単位で課金されます。そのため、GPUが有利になるのは、料金を支払う時間の大半で十分に高い出力速度を維持できる場合だけです。

つまり、どこかで読んだ数値ではなく、実測値が必要です。このページでは、記録する価値のある4つの数値を定義し、それらを取得するコマンドと、数値を判断材料に変える計算方法を示します。

公開されている tokens per second の数値が自分の値にならない理由

DigitalOcean は 2026 年 7 月、vLLM 上で単一の NVIDIA H200 を llama3.3-70b-instruct の FP8(8-bit floating point)で動作させた場合のスループット値を公開しました。これらの値は参考になりますが、あなたの環境で得られる値ではありません。

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"
  }
]

上の各行はすべてそのページから引用した値です。そのうち 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 は、同じモデルを単一の H100 で処理した値です。この値は KV cache(key と value の cache。処理中の会話ごとにカード上で保持するメモリ)の制約を受けます。80 GB のカードでは、70B モデルで同時に処理できるリクエスト数が少ないため、飽和時のスループットも低くなります。

モデルを変更するか、入力と出力の比率を変更すると、上の数値はすべて変わります。公開されている値は期待値を設定するためのものであり、予算を決めるためのものではありません。これは、ディスクとネットワークについて VPS を正直にベンチマークする場合にも当てはまる原則です。

重要な4つの数値

  • 初回トークンまでの時間(TTFT)。 リクエストを送信してから、最初の出力トークンが届くまでの遅延です。Prefill 時間とキュー待ち時間を合わせたものです。ユーザーはこの値を直接体感します。
  • ストリームごとの出力トークン毎秒。 1つの応答が開始された後、どれだけ速く生成されるかを示します。おおむね 20 tok/s を超えると、すでに多くの人が読む速度を上回るため、ここをさらに高速化しても効果は限定的です。
  • 負荷飽和時の総出力スループット。 サーバーに十分な負荷をかけた状態で、同時実行中のすべてのストリームの出力速度を合計した値です。これは処理能力を示す数値であり、GPU の料金に直結します。
  • 同時実行時の p50 および p99 TTFT。 p50 は中央のリクエストの値です。p99 は 100 件中 99 件のリクエストがこの値未満に収まることを示します。キュー待ちは、常に最初に p99 に現れます。

最初の2つは、サーバーがアイドルに近いときに改善します。3つ目は、サーバーが高負荷のときに改善します。これらは互いに相反するため、サーバーの性能を1つの数値だけで表すことはできません。

測定前に入力長と出力長を固定する

スループットはトラフィックの構成に左右されます。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 よりもモデルの選択が大きく影響します。この選択におけるメモリ面については、単一の VPS GPU に Qwen 3 モデルを収める方法で説明しています。

まず 1 ストリームを測定します

最も単純なケースから始めます。これは妥当性確認であり、上限値でもあります。Ollama は独自の計測結果を出力します。

ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."

確認する行は eval rate です。これは 1 秒あたりの出力トークン数です。prompt eval rate はプリフィル速度、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 で割ると秒になります。この割り算は、トークン毎秒の計算方法として 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 のみの delta の場合もあります。そのため、この値は 1 event 程度の誤差を含む 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"
done

output_throughput は毎秒あたりの出力トークン数です。total_token_throughput は入力トークンも加算するため、入力と出力の比率が 1:1 なら、値はおよそ 2 倍になります。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 サーバーでスイープを実行した場合の形状を示す例です。これはお使いのサーバーの測定値ではなく、ベンダーの公表値でもありません。上記のループを実行し、実際の値に置き換えてください。

形状を確認してください。一般化できるのは数値ではなく形状だからです。ストリームが 1 本の場合、サーバー全体の生成速度は 1 秒あたり 92 トークンで、p99 TTFT は 61 ms です。128 streams では合計生成速度が 1 秒あたり 2304 トークンまで上がり、25 倍になります。一方、各ストリームの生成速度は 1 秒あたり 18 トークンまで低下し、p99 TTFT は 3820 ms に達します。バッチ処理によって、アイドル状態だったメモリ待ち時間が有効な処理に変わるため、合計スループットは向上します。同じ計算資源を共有するため、ストリームごとの速度は低下します。

最後の倍増が判断材料になります。ストリーム数を 64 から 128 に増やしても、合計スループットは 6 パーセント未満しか増えません。その一方で、p99 TTFT はおよそ 3 倍になります。これは KV キャッシュが満杯になり、リクエストが処理中ではなくキューで待機していることを示します。実用的な運用点はその手前です。ストリーム数が 32 の場合でも、サーバー全体の生成速度は 1 秒あたり 1728 トークンで、ピークの 75 パーセントに達します。ストリームごとの生成速度は 1 秒あたり 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 キャッシュが不足するまで性能曲線は上昇し続けます。Ollama は、1 つのモデルを 1 台のマシンで運用することと、セットアップコストの低さを重視しています。そのため、同じスイープでも 2 つのエンジンは異なる結果を返します。これが Ollama と vLLM をサービングエンジンとして比較する の主題です。すべての数値について、どのエンジンのどのバージョンが生成したものかを記録してください。

測定対象を間違える5つの原因

  • クライアントが遠い。 インターネット経由でノート PC からベンチマークを実行すると、すべての TTFT に自宅からの往復遅延が加わるため、自宅の回線を測定することになります。クライアントはサーバーと同じリージョンで実行してください。
  • モデルがコールド状態だった。 最初のリクエストでは重みの読み込みが発生し、vLLM ではグラフキャプチャの時間も加わる場合があります。ウォームアップ用のバッチを送信し、その結果は破棄してください。
  • プレフィックスキャッシュが応答していた。 vLLM はデフォルトで自動プレフィックスキャッシュを有効にしているため、同じプロンプトを繰り返し送信すると、プリフィルではなくキャッシュを測定することになります。その結果、TTFT は実際の値のごく一部まで低下します。--dataset-name random ではすべてのプロンプトが異なるため、この問題を回避できます。確実に無効化するには、--no-enable-prefix-caching を指定してサーバーを起動してください。
  • 出力が短かった。 32 トークンの回答では各リクエストで TTFT の影響が大きくなり、tokens per second は実質的にプリフィルを示す値になります。現実的な出力長を指定した --ignore-eos を使用してください。
  • 同時実行数を 1 として報告した。 シート上では最も都合のよい数値ですが、コストとは関係ありません。

測定値を意思決定に変える

スイープで得た飽和時の出力スループットを使用します。単一ストリームのレートは使用しません。これをトークン単価と比較します。損益分岐点は、次の割り算で求められます。

break_even_tok_s = (price_per_hour / price_per_million_output_tokens) * 1000000 / 3600

DigitalOcean の 2026年7月の料金で計算します。同社の H200 専用推論エンドポイントは 1 時間あたり $4.47、サーバーレス版は 100万トークンあたり $0.65 です。したがって、4.47 を 0.65 で割ると 1 時間あたり 6.88 million tokens になり、3,600 秒で割ると出力は約 1,910 tokens per second です。料金は DigitalOcean のものです。割り算は私たちが行ったものです。

この判断を左右する言葉は sustained です。飽和状態で 1 日 2 時間だけ 1,910 tokens per second に達しても、sustained な 1,910 tokens per second ではありません。残りの 22 時間分も支払うためです。DigitalOcean のより安価な $3.44 per hour の GPU Droplet について同社が示す損益分岐点は、sustained average utilisation 72.2 percent です。これを下回る場合は、トークン単価のほうが有利です。通常、セルフホスティングの採算を悪化させるのは、遅いトークンではなく、アイドル状態の GPU 時間です。

したがって、判断材料は 2 つあります。スイープからは上限が得られます。トラフィックパターンからは、その上限のうち実際に利用する割合が分かります。両方を掛け合わせ、その結果を GPU VPS とトークン単価 API の損益分岐点 と比較して、利用量に対する答えを確認します。

FAQ

自己ホスト型 LLM では、tokens per second はどの程度が適切ですか?

この指標には 2 つの用途があるため、答えも 2 つあります。1 人で出力を読む場合、1 ストリームあたりおよそ 20 output tokens per second を超えれば、読む速度をすでに上回っています。そのため、それ以上速くしても効果はありません。コストの評価では、飽和時の総出力スループットが重要です。適切な値は、損益分岐点を超える値です。1 時間あたり $4.47 のサーバーで、100 万 tokens あたり $0.65 の料金を基準にすると、2026 年 7 月時点の価格で、損益分岐点は持続的に約 1,910 output 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 のどちらを測定すべきですか?

両方を測定してください。負荷が上がると、これらは反対方向に変化するためです。TTFT はユーザーが体感する待ち時間で、飽和時の出力スループットは請求額に反映されます。並列度を変えるたびに p50 と p99 の TTFT を記録し、p99 TTFT が許容範囲に収まる最大の並列度を選びます。その時点のスループットを容量として報告してください。曲線の最上部で得られる最大値を使ってはいけません。

tokens per second の値が高ければ、必ず token あたりのコストは下がりますか?

いいえ。token あたりのコストは、1 時間あたりの料金を、その時間内にサーバーが実際に生成した tokens 数で割った値です。そのため、高速なサーバーでも 1 日の大半がアイドル状態なら、token あたりのコストは高くなります。ピーク速度ではなく、利用率がコストを決めます。単位にも注意してください。表示される総 token スループットには input tokens が含まれるため、input と output の比率が 1:1 の場合、請求対象となる output rate のほぼ 2 倍になります。

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