6975 文字
35 分
【調べた】LLMサーバーの負荷検証、AIに書かせた自作スクリプトでは他プロジェクトと数字が比べられない──測定ツールは層ごとにチームで1つに統一する(アプリ層は Locust、vLLM 層は vllm bench と GuideLLM)
この記事について

Claude(Anthropic)との共同編集により作成されました。

要約
  • 負荷検証スクリプトは AI に頼めば数分で書ける。問題は書けることではなく、別々に書いたスクリプトの数字が同じ尺度に乗らないことにある
  • ずれる場所は決まっている。ストリーミングを切ると TTFT / ITL が測れない、reasoning モデルでは TTFT の測定点がツールごとに違う、同じプロンプトを繰り返すと prefix cache でスループットが水増しされる、負荷発生器側が先に飽和する、負荷の掛け方(一斉送信 / 定常レート / 同時数固定)で分布が変わる
  • 汎用の負荷試験(同時接続に耐えるか)と LLM の性能ベンチマーク(TTFT / ITL / tok/s)は別の問いで、NVIDIA も vLLM もそう整理している。「全部 Locust で測る」は E2E 秒数と tok/s を混同しやすい
  • Python 主軸の汎用負荷検証は Locust が第一候補。ゲートウェイの高 RPS と CI ゲートには k6(スクリプトは JS、ライセンスは AGPL)を限定併用する
  • vLLM の本番評価は公式が GuideLLM を推奨している。vllm bench serve は回帰とデータセット比較、vllm bench sweep はパラメータ探索、AIPerf は NVIDIA スタックと goodput 向け
  • 自分の結論は、測定ツールを層ごとにチームで 1 つに統一すること。アプリ層は Locust、vLLM 層は公式の vllm bench と GuideLLM。測定条件(ISL / OSL、負荷プロファイル、レート)はレポートに書くのが当然だが、要件ごとに違うので車輪を揃える手段にはならない。同じ TTFT という名前の数字が車輪ごとにずれるのは困るので、ツールを固定する。AI に書かせるのは測定器ではなく、決めたツールを回すシナリオとラッパー

はじめに#

LLM 推論サーバーを運用していると、負荷検証は避けて通れない。「同時 50 ユーザーで p95 がどこまで伸びるか」「max_num_seqs を上げたら tok/s がどう変わるか」といった問いは、GPU を買う前にも、サーバー設定を変えるたびにも出てくる。

ここで最近の困りごとは、負荷検証ツールを作ることが簡単になりすぎたことにある。「OpenAI 互換の /v1/chat/completions に同時 N 本投げて、レイテンシとトークン数を集計するスクリプト」は、Claude Code や Cursor に頼めば数分で出てくる。動く。数字も出る。

問題はその先で、複数人が複数プロジェクトでそれぞれ AI にスクリプトを書かせると、出てきた数字が同じ尺度に乗らない。A プロジェクトは「p95 が 2.1 秒」と言い、B プロジェクトは「35 tok/s」と言い、C プロジェクトは「TTFT 400ms」と言う。どれも正しく測れているように見えて、ストリーミングの有無、プロンプト長、負荷の掛け方、prefix cache の状態が違うので、横に並べても比べられない。同じサーバーの同じ設定を測っても、スクリプトが違えば違う数字が出る。

これは車輪の再発明が非効率だという話ではない。車輪を再発明するコスト自体は、AI がほぼゼロにした。再発明された車輪同士で寸法が合わないことが問題である。

そこで、Python 主軸の汎用負荷検証ツールと、vLLM など LLM 推論サーバー向けの専用ツールを、公式ドキュメントで整理し直した。ツール比較そのものより、「どこで尺度がずれるか」と「チームで何を決めておけば比べられるようになるか」を主眼に書く。

自作スクリプトの数字がずれる場所#

先に、自作スクリプト同士で数字が食い違う原因を挙げておく。ここに挙げるものは、どれも専用ツールの公式ドキュメントがわざわざ注意書きしている項目で、逆に言えば自作だと踏みやすい。

ストリーミングを切ると TTFT / ITL が測れない#

LLM の体感速度は、リクエストを投げてから最初のトークンが返るまでの TTFT(Time To First Token)と、その後のトークン間隔 ITL(Inter-Token Latency)で決まる。非ストリーミングでリクエストすると、クライアントは生成完了まで待つので、この 2 つは原理的に取れない。AIPerf は TTFT / ITL を出すには --streaming が必要だと明記している1

自作スクリプトで stream: false のまま「レスポンス時間」を測ると、出てくるのは生成完了までの E2E であって、体感速度の代理指標にはならない。汎用 HTTP ツールの「レスポンス時間」も同じで、ストリーミングにしても HTTP 完了までの時間しか記録しない。

メトリクスの名前が同じでも定義が違う#

vLLM の vllm bench serve は、TTFT をリクエスト送信から最初のストリーム出力まで、TPOT を (E2E - TTFT) / (出力トークン数 - 1) としてリクエスト単位で集計し、ITL を連続するストリーム出力の間隔と定義している2。投機デコードを使うと 1 回のストリームイベントで複数トークンが返るため、ITL と TPOT が乖離しうる2

reasoning モデルではさらにずれる。AIPerf の TTFT は reasoning を含む最初のトークンまでで、reasoning でない最初の出力トークンまでは TTFO という別の指標になっている。旧 GenAI-Perf の TTFT は後者に相当する3。つまり同じ TTFT という名前でも、ツールとバージョンで測定点が違う。vLLM 公式も、名前ではなく測定点と式で比較せよ、と書いている2

自作スクリプトの場合、この定義は書いた人の頭の中にしかない。「TTFT 400ms」の 400ms が最初のバイトまでなのか、最初の非 reasoning トークンまでなのか、レポートを読んでも分からない。

prefix cache でスループットが水増しされる#

vLLM は prefix caching を持つので、同じサーバーに同じプロンプトを繰り返し投げると、prefill が省略されてスループットが実態より良く出る。公式はこれを Warning として書いていて、意図しないなら --seed を変えるか、サーバーを再起動するか、キャッシュをリセットする vllm bench sweep serve を使え、としている2

自作スクリプトは「Hello」を 100 回投げがちで、これは prefix cache のベンチマークをしているのに近い。

負荷発生器側が先に飽和する#

Locust のデフォルト HttpUserrequests ベースで、高 RPS では負荷を掛ける側の CPU が先に飽和する。公式は FastHttpUser を用意していて、同一ハードウェアで最大 RPS が 5〜6 倍になることがあると説明している4。目安として 1 プロセスあたり FastHttpUser で約 16,000 RPS、HttpUser で約 4,000 RPS とあるが、これは被試験システムの性能ではなく、負荷発生器側の上限である4。Python はプロセスあたり 1 コアしか使い切れないので、分散時はコアあたり 1 worker が推奨されている5

asyncio + httpx で書いた自作スクリプトが 1 プロセスで頭打ちになり、それが「サーバーの限界」として報告されている可能性は普通にある。

負荷の掛け方で分布が変わる#

同じ 100 リクエストでも、一斉送信するのか、毎秒 5 本の定常レートで送るのか、Poisson 到着にするのか、同時数を 16 に固定するのかで、レイテンシ分布はまったく違う。vllm bench serve--request-rate はデフォルトが inf(一斉送信)で、--burstiness で Poisson / Gamma、--max-concurrency で同時数上限を指定する2。GuideLLM のプロファイルは synchronous / concurrent / throughput / constant / poisson / sweep に分かれている6。vLLM の sweep 文書は、request_rate より max_concurrency のほうがエンジン負荷を直接制御できて安定しやすい、と書いている7

自作スクリプトは asyncio.gather で一斉送信していることが多い。これは throughput プロファイルの一種であって、定常レートの結果とは比べられない。

入出力トークン長がレポートに残らない#

ISL(入力トークン長)と OSL(出力トークン長)を変えれば、prefill と decode の比率が変わり、TTFT も tok/s も変わる。専用ツールはこれを prompt_tokens=512,output_tokens=128 のように明示するが、自作スクリプトでは固定文字列とデフォルトの max_tokens になりがちで、レポートに残らない。


こうして並べると分かるとおり、ずれる場所のうち ISL / OSL や負荷プロファイルは要件次第で変わってよいものだが、メトリクスの定義、ストリーミングの扱い、キャッシュへの対処、負荷発生器の上限はツールに固有である。同じツールを使っていれば、条件の違いは引数の違いとして読めるが、ツールが違えば、同じ名前の数字が違うものを指す。この観点を持ったうえで、ツールを見ていく。

先に分ける:負荷試験と性能ベンチマークは別の問い#

ツールを並べる前に、測りたい対象を分ける。NVIDIA の NIM ベンチマークガイドは、Locust / k6 を負荷試験(同時接続・オートスケール・ゲートウェイ)、AIPerf を性能ベンチマーク(TTFT / ITL / トークンスループット)と分けて整理している8。vLLM 公式も、本番サーバーの評価には GuideLLM を推奨し、付属の vllm bench serve は主に機能評価と回帰用途だと位置づけている2

測りたいこと典型メトリクス向いている系統
API / ゲートウェイが同時接続に耐えるかRPS、E2E レイテンシ、エラー率、CPULocust, k6, JMeter
ユーザー行動(ログイン → 複数 API)を再現するかタスク成功率、シナリオ遅延Locust, Gatling
LLM の体感速度と容量上限TTFT, ITL / TPOT, tok/s, goodputGuideLLM, vllm bench, AIPerf
サーバー設定(max_num_seqs など)のトレードオフレイテンシ vs スループット曲線vllm bench sweep, GuideLLM sweep

自作スクリプトがずれる根本はここで、上の表の 1 行目と 3 行目を 1 本のスクリプトで兼ねようとする。「同時接続に耐えるか」を測っているのか「体感速度」を測っているのかが混ざると、E2E 秒数と tok/s が同じレポートに並んで、どちらとも比べられなくなる。

Python 主軸の汎用負荷検証ツール#

比較表#

ツール言語 / ランタイムライセンス強み弱みPython チームでの位置づけ
LocustPython(gevent)MITシナリオが普通の Python。分散実行が OSS 標準。Web UI。任意プロトコルに拡張可能単一ノードの生 RPS は k6 より低い。pass/fail は後処理が必要になりやすい第一候補
MolotovPython(asyncio / aiohttp)コルーチンで短いシナリオを書ける。autosizingエコシステム・UI・分散が Locust より薄いLocust が重いときの軽量代替
TaurusPython(YAML オーケストレーション)Apache-2.0JMeter / Locust 等を YAML で束ねる負荷生成エンジンそのものではない既存ツールのラッパー
k6スクリプトは JS(エンジンは Go / Sobek)AGPL-3.0単一バイナリ。高 RPS。native thresholds で CI ゲートPython ではない。npm も使えない。AGPLゲートウェイ層の高負荷・CI 用
GatlingJava / Kotlin / ScalaApache-2.0JVM チーム向け。HTML レポートと assertionsPython ではないJVM サービスと揃える場合のみ
JMeterJava(GUI + XML)Apache-2.0JDBC / JMS など HTTP 以外のプロトコル.jmx はレビューしにくい。VU あたり重いDB / キュー直叩きが必要なとき
VegetaGo CLIMIT定数レートの HTTP ブラストHTTP のみ。分散なし。シナリオ向きでない単発のレート確認
ohaRust CLIMITwrk 系の高速 HTTP ベンチ。メンテが続いているシナリオ・分散なし単一エンドポイントの上限確認
wrkC + LuaJITCustom単一マシンの生スループット2023 年末以降コミットが止まっているという指摘がある(一次確認はしていない)新規採用は見送り

Locust — Python チームの標準候補#

Locust は HTTP を中心とした OSS の負荷試験ツールで、テストは普通の Python として書く9。各仮想ユーザーは greenlet 上で動くため、async コールバックではなく同期風のコードでシナリオを書ける9。公式が挙げる特徴は、シナリオを Python で書くこと、master / worker による分散実行、リアルタイム Web UI(ヘッドレス実行も可能)、HTTP 以外もクライアントを書けば対象にできること、である9

スループット側の注意は前述のとおりで、高 RPS が要るなら FastHttpUser を使い4、単一マシンなら locust --processes -1 で論理コア数ぶんの worker を立てる5。master 自体は User を動かさない。

LLM 向けには、実験的な locust.contrib.oai.OpenAIUser がある。OpenAI SDK を使い、response_length に出力トークン数を入れる設計で、ペイロードのバイト数より有用という位置づけである10。ただしTTFT / ITL の標準メトリクスは出ないし、公式も experimental と注記している10。Locust 2.40 以降は pytest 風の test_* 関数と session fixture でもシナリオを書けるが、こちらも experimental である10

Locust が向くのは、Python チームがシナリオとアプリコードを同じ言語でメンテしたい場合、認証・複数ステップ・条件分岐のあるユーザー行動を再現したい場合、社内 SDK や既存クライアントを再利用したい場合、Web UI で同時ユーザー数を探索的に変えたい場合、分散負荷を OSS のまま複数マシンに広げたい場合である。向かないのは、単一ノードで数十万 RPS を出したい場合と、LLM のトークン単位 SLO を厳密に測りたい場合である。

k6 — Python ではないが、CI ゲートとしては強い#

Grafana k6 は Go 製の負荷試験ツールで、テストスクリプトは埋め込み JS エンジン(Sobek)上の JavaScript である1112。Node.js ではないため npm パッケージは使えない。単一静的バイナリ、高 RPS、そして CI 向きの thresholds(満たさなければ非ゼロ終了)が強みである13

export const options = {
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<200'],
},
};

「p95 が閾値を超えたら fail」を宣言的に書けるのは、Locust だと後処理が要るところなので、CI ゲートとしては k6 のほうが素直である。

OSS の k6 は AGPL-3.0 で12、自システムへの負荷試験ツールとして社内で走らせる分と、製品に埋め込んでホストする分では法務上の扱いが違う。Python 互換を謳う xk6-python は Grafana Hackathon の PoC で、Starlark 方言であり CPython / pip は使えない。リポジトリは archived である14

Python メインなら、k6 は「ゲートウェイの高 RPS CI」に限定して併用するのが現実的で、メインのシナリオ言語にはしない。

その他#

  • Molotov@scenario 付きの asyncio コルーチン。aiohttp セッションを受け取る。gRPC 対応(2.7 以降)。--sizing で失敗率が閾値を超えるまで worker を増やす15。Locust より単純だが、UI・分散・エコシステムは薄い
  • JMeter — HTTP 以外(JDBC, JMS, LDAP 等)が必要なときの OSS 選択肢。テスト計画は XML(.jmx
  • Gatling — JVM DSL。assertions と HTML レポートが強い。Python チームには学習コストが大きい
  • Vegeta / oha — URL を定数レートまたは高並行で叩く CLI。シナリオ維持には向かない
  • Taurus — 複数エンジンの YAML ラッパー。エンジン選定を代替するものではない

LLM 推論サーバー向けの専用ツール#

vLLM は OpenAI 互換 HTTP を出すので Locust でも叩けるが、本番サーバーの性能評価として公式が推しているのは GuideLLM である2。汎用ツールとは別レイヤーの専用ツールが、公式の役割分担つきで揃っている。

比較表#

ツール提供元言語TTFT / ITL公式の位置づけ向く用途
GuideLLMvLLM projectPython 3.10–3.13あり(分布つき)本番 vLLM サーバーの推奨ベンチマーク容量計画、SLO、sweep
vllm bench servevLLM 本体Pythonあり(TTFT / TPOT / ITL)機能評価・回帰が主データセット比較、PR 回帰
vllm bench sweepvLLM 本体Python上記を複数条件で設定・負荷のスイープmax_num_seqs 等のチューニング
AIPerfNVIDIA (ai-dynamo)Pythonあり(reasoning は TTFO も)NIM / 生成 AI 推論の推奨goodput、GPU テレメトリ
Locust OpenAIUserLocust contribPythonなし(出力トークン数は記録)experimentalアプリ層の同時ユーザー
llmperfRay projectPython + Rayあり(ITL / throughput)古い簡易ロードテスト参考・レガシー
inference-perfkubernetes-sigsPython部分的GuideLLM 側の比較表に掲載k8s 推論負荷
genai-benchSGLang projectPython部分的同上SGLang 周辺

GuideLLM の README にも他ツールとの比較表があるが6、プロジェクト自身の表なので割り引いて読む。たとえば vllm bench は TTFT / ITL を出すので、そこでの「フルメトリクスなし」は GuideLLM 基準の自己評価である。

GuideLLM — vLLM 本番評価の第一候補#

GuideLLM は vLLM プロジェクト配下の Python パッケージで、OpenAI 互換および vLLM native サーバーに対して本番に近い負荷をかけ、TTFT / ITL / E2E の分布を取る6。vLLM のベンチマーク文書は、本番サーバー評価に GuideLLM を勧め、vllm bench serve よりデータセット読み込み・リクエスト形式・負荷パターンが柔軟だと書いている2

Terminal window
pip install guidellm[recommended]

最小の sweep 例(合成データ、30 秒)6

Terminal window
guidellm run \
--backend kind=openai_http,target=http://localhost:8000 \
--profile kind=sweep \
--constraint kind=max_duration,seconds=30 \
--data kind=synthetic_text,prompt_tokens=256,output_tokens=128

負荷プロファイルは synchronous / concurrent / throughput / constant / poisson / sweep。データは合成テキスト、HuggingFace、ローカルファイル、マルチモーダル合成、Mooncake trace の replay など6。出力は JSON / CSV / HTML。warmup / cooldown、エラー上限、過飽和(over-saturation)検出もある。Python 3.10–3.13、Linux / macOS、Apache-2.06

注意点が 2 つ。sweep の最初のステージは同期(逐次)実行になるため、--max-requests を大きくすると極端に長くなり得るという Issue がある16。時間制約(max_duration)を先に置くか、同期ステージの件数を小さくする。もう 1 つ、CLI は kind=key,value 形式へ移行済みで、古いブログにある guidellm benchmark --rate-type=sweep はそのまま使えないことがある。AI にコマンドを書かせると古い形式が出てくることがあるので、README の現行形式で確認する。

vllm bench serve / sweep — vLLM 付属#

サーバーを立ててからクライアント側で測るオンラインベンチマークである2

Terminal window
vllm serve NousResearch/Hermes-3-Llama-3.1-8B
vllm bench serve \
--backend vllm \
--model NousResearch/Hermes-3-Llama-3.1-8B \
--endpoint /v1/completions \
--dataset-name sharegpt \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--num-prompts 10

出力にはリクエスト / 出力トークン / 総トークンのスループットと、TTFT、TPOT、ITL が含まれる2。定義は前述のとおり。負荷パターンは --request-rate--burstiness--max-concurrency で、データセットは ShareGPT、random、custom JSONL、画像・音声など広い2

vllm bench sweep は複数設定を回して可視化するスイートで7、次のサブコマンドを持つ。

  • servevllm serve のパラメータ組み合わせ(例: max_num_seqs × max_num_batched_tokens
  • serve_workload — 負荷レベルを探索してレイテンシ vs スループット。公式は GuideLLM の --profile sweep 相当と明記。デフォルトは GuideLLM 寄せで request_rate だが、--workload-var max_concurrency のほうが安定しやすい7
  • serve_sla — 例: {"p99_e2el_ms": "<=500"} を満たす最大レート / 同時数を探索
  • plot / plot_pareto — 曲線と tokens/s/user vs tokens/s/GPU

機能の回帰や「この PR で ShareGPT が落ちていないか」には vllm bench serve。本番の容量計画には GuideLLM。設定スイープはどちらでもできるが、vLLM 付属 sweep はサーバー起動からキャッシュリセットまで一体で回せる。

AIPerf — NVIDIA の生成 AI ベンチマーク(旧 GenAI-Perf)#

AIPerf は NVIDIA の OSS(pip install aiperf)で、OpenAI 互換エンドポイントのストリーミング性能を測る17。NIM 向けガイドは Locust / k6 を負荷試験、AIPerf を性能ベンチマークと分け、後者を推奨している8。GenAI-Perf の後継で、多くのオプションは drop-in である3

Terminal window
aiperf profile \
--model your-model \
--url localhost:8000 \
--endpoint-type chat \
--streaming \
--concurrency 10 \
--request-count 100

--streaming を付けると TTFT / ITL が有効になる1。時間ベースなら --benchmark-duration。goodput(SLO を満たしたリクエストだけのスループット)や、disaggregated serving 向けの --prefill-concurrency もある。vLLM を OpenAI 互換で叩けるので、NVIDIA スタックや goodput / GPU テレメトリが要るときに使う。

Locust で vLLM を叩く意味#

意味はあるが、GuideLLM の代替ではない。向くのは、LiteLLM や自前ゲートウェイを経由して vLLM に届く経路全体、API キー・レート制限・リトライ・マルチターンといったユーザー行動、「同時 N ユーザーがチャットする」運用試験(オートスケール、接続枯渇、タイムアウト)といったアプリ層である。

from locust import HttpUser, task, between
import json
class ChatUser(HttpUser):
wait_time = between(1, 3)
@task
def chat(self):
self.client.post(
"/v1/chat/completions",
json={
"model": "your-model",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True,
"max_tokens": 128,
},
headers={"Authorization": "Bearer dummy"},
name="/v1/chat/completions",
)

この書き方で Locust が記録するのは、HTTP 完了までの時間である。ストリーミングの最初のチャンク時刻(TTFT)を取るなら、自前で chunk を読んでイベントを fire する必要がある。ここまで来ると専用ツールを使うほうが早い。実験的な OpenAIUser は Responses API の例が中心で、vLLM の主経路は Chat Completions なので、アプリ試験は HttpUser のほうが現状は分かりやすい10

使い分けは 2 本立て#

サーバーのトークン性能は専用ツール、アプリ全体の同時ユーザーは Locust、という 2 本立てが基本になる。

やりたいこと使うツール使わない理由
vLLM の容量計画、SLO(TTFT p99 など)GuideLLMLocust はトークン指標が標準でない
PR 回帰、データセット比較vllm bench serveGuideLLM よりセットアップが重い場合がある
max_num_seqs 等のパラメータ探索vllm bench sweep または GuideLLM sweep手で concurrency を変えると再現しにくい
NIM / goodput / GPU メトリクスAIPerfvLLM 単体の評価なら公式推奨は GuideLLM
ゲートウェイ+認証+同時ユーザーLocustGuideLLM はユーザー行動の記述が主目的ではない
単一 HTTP の生 RPSoha / k6LLM 指標は出ない
「全部 Locust で測る」しないE2E 秒数と tok/s を混同しやすい

実務的な順序は次のようになる。

  1. vllm serve を固定設定で起動する
  2. GuideLLM の sweep で、合成プロンプト(本番に近い ISL / OSL)の安全動作点を探す
  3. 必要なら vllm bench sweep でエンジンパラメータを変えて Pareto を見る
  4. 前段に LiteLLM 等があるなら、Locust で同時ユーザーとエラー率を見る
  5. CI では vllm bench serve を短時間(固定 seed / キャッシュ方針を明記)で回帰する

チームで決めること:層ごとに測定ツールを 1 つにする#

ここからは自分の結論である。

測定条件(ISL / OSL、負荷プロファイル、レート、seed とキャッシュ方針)をレポートに書くのは当然で、書かない数字は比べようがない。ただ、条件を書くことと、車輪を揃えることは別の話である。測定条件は要件で決まる。チャットボットのプロジェクトなら ISL は短く OSL は長め、RAG なら ISL が長く、バッチ要約なら定常レートより一斉送信のほうが実態に近い。プロジェクトごとに条件が違うのは当たり前で、そこを揃えようとしても揃わないし、揃える必要もない。

車輪を揃えるために固定するのは、条件ではなくツールである。理由は単純で、同じ TTFT という名前の数字が、車輪ごとに違うものを指していると困るからだ。上で見たとおり、TTFT の測定点、ITL と TPOT の式、ストリーミングの扱い、prefix cache への対処、負荷発生器の上限は、どれもツールに固有の話である。ツールが同じなら、この部分は自動的に揃う。残るのは ISL / OSL や負荷プロファイルといった条件の差で、これは同じツールの引数の差として読めるので、レポートを横に並べたときに「どこが違うか」がすぐ分かる。

ただし、ツールを全社で 1 つに統一することはできない。ゲートウェイの同時ユーザーを測るツールと、vLLM のトークン性能を測るツールは違う。なので、層ごとに 1 つにする。

測るものチームで決めるツール
アプリ層(ゲートウェイ、認証、同時ユーザー)RPS、E2E レイテンシ、エラー率Locust
vLLM 層(推論サーバー単体)TTFT / ITL / TPOT、tok/svllm bench serve と GuideLLM

アプリ層を Locust にするのは、Python チームがシナリオをアプリコードと同じ言語で持てて、分散と Web UI が OSS 標準だからである。vLLM 層を公式の vllm bench と GuideLLM にするのは、どちらも vLLM プロジェクト配下で、メトリクスの定義が公式ドキュメントに書かれていて、prefix cache やストリーミングの注意書きが揃っているからである。回帰は vllm bench serve、容量計画と SLO は GuideLLM、という使い分けは公式の位置づけに従う。AIPerf は NVIDIA スタックに寄せるチームなら候補になるが、vLLM 中心ならまず入れない。

この 2 段の切り分けができていれば、自作スクリプトは測定器から外れる。AI に書かせるのは、決めたツールを回す側になる。Locust の locustfile.py、GuideLLM の起動コマンドと出力先を固定したラッパー、vllm bench serve を CI で短時間回すスクリプト。プロジェクトごとに変えるのは、シナリオの中身とターゲットと ISL / OSL といった要件由来の部分だけで、測定器そのものは変えない。

ここまで決めておけば、車輪の再発明は起きても、寸法は揃う。A プロジェクトの TTFT と B プロジェクトの TTFT が同じ GuideLLM の出力なら、条件の差を確認したうえで比べられる。別のツールの数字は、測定点と式を確認するまで比べない。reasoning モデルの TTFT のように、同じ名前で違うものを指す指標があるからである3

最小コマンド例(vLLM)#

サーバー:

Terminal window
vllm serve Qwen/Qwen3-0.6B --port 8000

GuideLLM(本番寄りの評価)6

Terminal window
guidellm run \
--backend kind=openai_http,target=http://localhost:8000 \
--profile kind=constant,rate=5 \
--constraint kind=max_duration,seconds=60 \
--data kind=synthetic_text,prompt_tokens=512,output_tokens=128

vLLM 付属(回帰)2

Terminal window
vllm bench serve \
--backend openai-chat \
--model Qwen/Qwen3-0.6B \
--endpoint /v1/chat/completions \
--dataset-name random \
--num-prompts 100 \
--random-input-len 512 \
--random-output-len 128 \
--request-rate 5 \
--max-concurrency 16

AIPerf1

Terminal window
aiperf profile \
--model Qwen/Qwen3-0.6B \
--url localhost:8000 \
--endpoint-type chat \
--streaming \
--concurrency 8 \
--benchmark-duration 60

GuideLLM と vllm bench serve の例は ISL 512 / OSL 128、ストリーミングあり、定常レートまたは同時数固定、に揃えてある。一方 AIPerf の例は公式ドキュメントの形をそのまま載せているので、合成トークン長を指定しておらず、このままでは上の 2 本と比べられない。揃えるなら CLI リファレンス1で合成トークン長のオプションを確認して、同じ 512 / 128 にしてから当てる。

チームで決めたツールを導入する前に、この 3 本の条件を揃えて同じサーバーに順に当て、TTFT の中央値がどれくらいずれるかを一度見ておくと、「ツールをまたいだ比較にどれくらい幅を見込むべきか」の肌感が持てる。ツールを 1 つに決める理由が、数字で見える。

おわりに#

負荷検証ツールを作ること自体は、AI で簡単になった。簡単になったぶん、プロジェクトごとに違う測定器が増えて、数字が比べられなくなった。

調べてみると、専用ツールの公式ドキュメントは、自作でずれる場所をほぼ全部、注意書きとして書いていた。ストリーミングを切ると TTFT が取れない、reasoning モデルでは TTFT の定義が違う、同じプロンプトの繰り返しは prefix cache で水増しされる、負荷発生器が先に飽和する、負荷の掛け方で分布が変わる。専用ツールを使うというのは、この注意書きを一式引き受けてもらうことに近い。

測定条件はレポートに書く。ただしそれは要件で変わるので、車輪を揃える手段にはならない。揃えるのはツールで、層ごとにチームで 1 つに決める。アプリ層は Locust、vLLM 層は公式の vllm bench と GuideLLM。同じツールの数字なら、条件の差は引数の差として読める。AI に書かせるのは測定器ではなく、決めたツールを回すシナリオとラッパーにする。

車輪の再発明は止められないし、止める必要もない。寸法だけ揃えればいい。

参考文献#

  1. NVIDIA AIPerf — Command Line Options https://docs.nvidia.com/aiperf/reference/command-line-options
  2. vLLM — Benchmark CLI https://docs.vllm.ai/en/latest/benchmarking/cli/
  3. NVIDIA AIPerf — Migrating from GenAI-Perf https://docs.nvidia.com/aiperf/getting-started/migrating-from-gen-ai-perf
  4. Locust — Increase performance with FastHttpUser https://docs.locust.io/en/stable/increase-performance.html
  5. Locust — Distributed load generation https://docs.locust.io/en/stable/running-distributed.html
  6. vllm-project/guidellm README https://github.com/vllm-project/guidellm
  7. vLLM — Parameter Sweeps https://docs.vllm.ai/en/stable/benchmarking/sweeps/
  8. NVIDIA NIM LLMs Benchmarking — Overview https://docs.nvidia.com/nim/benchmarking/llm/2.0.0/overview.html
  9. Locust — What is Locust? https://docs.locust.io/en/stable/what-is-locust.html
  10. Locust — Testing other systems (OpenAI / pytest) https://docs.locust.io/en/stable/testing-other-systems.html
  11. Grafana k6 documentation https://k6.io/docs/
  12. grafana/k6 GitHub (AGPL-3.0) https://github.com/grafana/k6
  13. Grafana k6 — Thresholds https://grafana.com/docs/k6/latest/using-k6/thresholds/
  14. grafana-cold-storage/xk6-python https://github.com/grafana-cold-storage/xk6-python
  15. Molotov documentation https://molotov.readthedocs.io/en/latest/index.html
  16. GuideLLM Issue #588 (sweep × max-requests) https://github.com/vllm-project/guidellm/issues/588
  17. ai-dynamo/aiperf https://github.com/ai-dynamo/aiperf
【調べた】LLMサーバーの負荷検証、AIに書かせた自作スクリプトでは他プロジェクトと数字が比べられない──測定ツールは層ごとにチームで1つに統一する(アプリ層は Locust、vLLM 層は vllm bench と GuideLLM)
https://yurudeep.com/posts/deeplearning/2026/20260918/
作者
ひらノルム
公開日
2026-09-18
ライセンス
CC BY-NC-SA 4.0