3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

# ローカルGPU(RTX 4070 12GB)で並列 LLM 推論はどこまでスケールするか — vLLM × SGLang での並列処理実測検証

3
Posted at

はじめに

このシリーズは RTX 4070 12GB で vLLM と SGLang を同条件で比較した記録です。この記事 ③ は 並列リクエスト編です。単体でも読めますが、① 準備編② 単発比較 で「なぜこの比較が公平か」「単発ではどんな差があるか」を扱っているので、通しで読むと数字の文脈がつかみやすくなります。

シリーズ ②concurrency 1 の単発リクエストを比較した結果、SGLang が vLLM より +18〜28% 速いことがわかりました。ローカル LLM サーバを「単発リクエストでしか使わない」シーンは少なく、実用では複数リクエストを並列に投げます。

今回 ③ では concurrency を 1 / 2 / 4 / 8 と上げたときの挙動を見ます。RTX 4070 12GB の上で RedHatAI/Qwen3.5-4B-FP8-dynamic を回し、aggregate tok/s、per-request tok/s、p95 latency、peak VRAM を測りました。

結果だけ先取りすると、concurrency 8 でフレームワーク間の差はこうなります。

backend aggregate tok/s p95 latency
vLLM flashinfer 278.45 7,355 ms
SGLang flashinfer 468.01 4,375 ms

スループットで 1.68x、p95 で 1.68x 短縮。① でも ② でも 1.3x 前後だった差が、並列負荷で大きく広がります。

計測設計

5 構成 × concurrency 4 段 = 20 ケース。各ケースで warmup 1 バッチ + 計測 5 バッチ。バッチごとに concurrency 分のリクエストを同時投入し、最後のリクエストが返るまでの elapsed と aggregate 出力 token 数から aggregate tok/s を求めます。

ケース定義は次のとおりです。

case_id concurrency per-request prompt per-request max_tokens active token budget
c1_limit_8k 1 ~7,395 512 ~7,907
c2_limit_8k_each 2 ~7,395 × 2 512 ~15,814
c4_4k_each 4 ~4,323 × 4 512 ~19,340
c8_2k_each 8 ~2,189 × 8 256 ~19,560

concurrency が上がるごとに per-request prompt を短くしているのは、active token budget (同時にエンジン内に存在する KV を持つ token 数の合計) を 1〜2 万 tokens 帯に収めて、フェアな並列負荷を作るためです。c8 だけ max_tokens=256 にしているのは、c8 × 512 tokens 生成だと KV pool が 8K context 上限を超える可能性があるためで、その代わり concurrency を 8 まで上げて pipeline 飽和を見ています。

結果: aggregate tok/s

5 構成 × 4 concurrency の matrix です。

framework backend c1 c2 c4 c8
vLLM flash_attn 52.57 90.01 170.00 277.36
vLLM flashinfer 52.73 90.25 170.54 278.45
vLLM triton_attn 52.44 89.24 169.30 276.46
SGLang flashinfer 67.43 127.63 253.59 468.01
SGLang triton 66.74 126.31 251.23 466.83

vLLM 内の backend 差は引き続き誤差

② で見たのと同じで、vLLM 側の 3 backend は全 concurrency で 2 tok/s 以内に収まります。--attention-backend を変えても並列負荷でもほぼ動きません。

SGLang 内も backend 差は小さい

SGLang の flashinfer と triton の差も最大 1.3 tok/s (c2 で 127.63 vs 126.31)。フレームワーク内では backend の選択肢にあまり意味がなく、フレームワーク選定が支配的という ② の結論はそのまま強化されます。

vLLM vs SGLang: 差が広がる

両エンジンの最速 backend を取って並べると次のとおりです。

concurrency vLLM 最速 SGLang 最速 SGLang / vLLM
1 52.73 67.43 1.28x
2 90.25 127.63 1.41x
4 170.54 253.59 1.49x
8 278.45 468.01 1.68x

concurrency が上がるほど SGLang の優位が拡大します。c1 で 1.28x だった差が、c8 では 1.68x。スループット指向で複数リクエストを捌くサーバなら、フレームワーク選定だけで 1.6 倍以上の差が出てくる、という結果です。

スケーリングカーブ

各エンジンを 自分の c1 で正規化 した倍率と、vLLM c1 (52.73 tok/s) を共通基準 1.00x に置いて SGLang を重ねた倍率を、一つの表にまとめます。

concurrency vLLM flashinfer (vLLM c1 基準) SGLang flashinfer (SGLang c1 基準) SGLang flashinfer (vLLM c1 基準)
1 1.00 1.00 1.28
2 1.71 1.89 2.42
4 3.23 3.76 4.81
8 5.28 6.94 8.88

まず自分の c1 基準で見ると、理想的には c8 でスループット 8x ですが両者とも下回ります。とはいえ SGLang は c8 で 6.94x、vLLM は 5.28x。SGLang のスケーリング効率は vLLM の 1.31 倍です。

そのうえで vLLM c1 を共通基準にして並べ直すと、SGLang c8 は 8.88x に到達。これは理論最大の 8x を 超えています。仕組みとしては、c1 時点でのフレームワーク差 (1.28x) と SGLang 自身のスケーリング (6.94x) が乗算で効くため。「リクエスト 1 本あたりの単速」と「並列で詰める効率」の両方で勝てば、理論限界を上抜けする という現象が起きます。

逆方向に読むと、vLLM で c8 まで並列度を上げても、SGLang の c4 (4.81x) に届かない。「vLLM を 8 並列で回すよりも、SGLang を 4 並列で回す方が速い」というのが今回の構成での結論です。

per-request の tok/s も並べると、SGLang のスケジューラ品質の差がさらにわかりやすくなります。

concurrency vLLM flashinfer per-req tok/s SGLang flashinfer per-req tok/s
1 52.73 67.44
2 45.19 63.82
4 42.70 63.41
8 34.96 58.55

vLLM は c1 → c8 で per-request が 34% 落ちる (52.73 → 34.96)。SGLang は同じ条件で 13% 落ちるだけ (67.44 → 58.55)。「並列度を上げても 1 リクエストあたりの体感速度が落ちにくい」のが SGLang の強さです。

p95 リクエスト遅延

スループットだけだと運用判断には足りないので、p95 リクエスト遅延も並べます。各リクエストが投入から完了までに要した時間の 95 パーセンタイルです。

concurrency vLLM flashinfer (ms) SGLang flashinfer (ms) SGLang / vLLM
1 9,710 7,597 0.78
2 11,346 8,028 0.71
4 12,010 8,077 0.67
8 7,355 4,375 0.59

c8 で SGLang は 4.4 秒、vLLM は 7.4 秒max_tokens=256 生成での p95 で 3 秒差。同じハードウェア、同じモデル、同じプロンプト、同じ並列度で、エンジンを変えるだけでユーザー体感の応答時間がここまで動きます。

c4 で vLLM が 12,010 ms と最も長くなっているのは、max_tokens=512 で 4 リクエスト並列が KV pool と batching scheduler の両方を最も埋める領域だからと考えられます。SGLang は同じ条件で 8,077 ms。

peak VRAM

並列度を変えても peak VRAM はそれほど動かないというのが今回の観察です。各 backend の c1〜c8 中の最大値を載せます。

framework backend peak VRAM (MB) 12GB に対する余白
vLLM flash_attn 10,255 ~1,925 MB
vLLM flashinfer 10,655 ~1,525 MB
vLLM triton_attn 10,255 ~1,925 MB
SGLang flashinfer 11,285 ~895 MB
SGLang triton 10,821 ~1,359 MB

SGLang flashinfer が 11.3 GB と最も VRAM を使っています。スループットが 1.68x になっている代償と考えれば妥当で、「性能のために VRAM を積極的に使う」設計判断が透けて見えます。逆に言うと、メモリ余裕が欲しい場合は SGLang の --mem-fraction-static を 0.85 から 0.80 / 0.75 に下げる選択肢があります (起動オプションだけで触れる)。あるいは KV cache を BF16 → FP8 に切り替えれば KV pool を約 2x に伸ばせるため、長文要件がある場合はその選択肢が現実的です (別シリーズ FP8 KV Cache は実用投入できるか / 長文運用の限界 で同じハードウェアで検証済み)。

vLLM 側は c1〜c8 全体でほぼ peak が動きません。これは --gpu-memory-utilization 0.93 の制約下で起動時にほぼ上限まで埋まっており、リクエスト並列度を上げても新規確保はほぼないからです。

成功率

全 20 ケースで OOM ゼロ、リクエスト失敗ゼロ

framework backend total success fail
vLLM flash_attn 75 75 0
vLLM flashinfer 75 75 0
vLLM triton_attn 75 75 0
SGLang flashinfer 75 75 0
SGLang triton 75 75 0

ここはどちらも合格です。Phase 1 の長文 prefill で発生した「prompt + max_tokens 合計が context window を超える」現象を回避するため、Phase 2 では case_id ごとに prompt 長と max_tokens を調整してあります。

なぜ並列負荷で SGLang が伸びるのか

短く言える範囲だけ整理しておきます。

  1. 継続バッチング (continuous batching) と prefill chunking の差
    両エンジンとも実装しているが、SGLang は radix cache を起点としたスケジューリングで「次の step で何を実行するか」の判断が軽量で、GPU の forward 占有率が高い時間が伸びる傾向にあります。

  2. prefix sharing の効果は今回は影響が小さい
    今回のプロンプトは異なるテキストを切り出して使っているため、SGLang の代表機能である radix attention の prefix 共有メリットは影響が小さいと考えられます。にもかかわらず差が出ているということは、scheduling overhead と batching 戦略だけでもこれだけ違う、ということです。

  3. CUDA graph 無効下での Python オーバーヘッド
    両エンジンとも --enforce-eager / --disable-cuda-graph 指定です。CUDA graph が使えない環境では、step ごとの Python/CPU 側オーバーヘッドが相対的に大きくなり、ここでの実装差がそのまま tok/s に乗ります。

  4. per-request tok/s が落ちにくい設計
    per-request tok/s の劣化幅 (34% vs 13%) は、batch size が増えたときの KV-cache アクセスパターンとスケジューラの効率に直結します。SGLang の per-step latency 安定性が、p95 latency の差にも繋がっています。

これらの定量的内訳は別記事で追跡する価値があると思いますが、今回は 「現状の構成では SGLang を選ぶと並列負荷で大きく差がつく」 という観測事実だけ確定として残します。

結論

並列負荷比較で言える 3 点をまとめます。

  1. concurrency が上がるほど SGLang の優位が拡大。c1 で 1.28x だった差が、c8 では 1.68x に。スループット指向で複数リクエストを捌くなら、フレームワーク選定だけで 1.6 倍以上の差が出る
  2. per-request tok/s の劣化幅は vLLM −34% / SGLang −13%。SGLang は並列度を上げても 1 リクエストあたりの体感速度が落ちにくい
  3. p95 latency (c8) は SGLang 4.4s / vLLM 7.4s。3 秒差はそのままユーザー体感の差になる

並列負荷比較としての整理は以下のとおりです。

観点 結果
vLLM 内 attention backend 差 計測誤差 (2 tok/s 以内)
SGLang 内 attention backend 差 計測誤差 (1.3 tok/s 以内)
vLLM vs SGLang (c1) SGLang 1.28x
vLLM vs SGLang (c8) SGLang 1.68x
スケーリング (c1→c8) vLLM 5.28x / SGLang 6.94x
per-request 劣化 (c1→c8) vLLM −34% / SGLang −13%
p95 latency (c8) vLLM 7,355ms / SGLang 4,375ms
peak VRAM (12GB) vLLM 10.3〜10.7 GB / SGLang 10.8〜11.3 GB
全 75 ケース 失敗ゼロ

スループットと p95 latency だけで判断するなら、暫定的な棲み分けは次のとおりです。

用途 推奨 理由
自宅・低 concurrency チャット vLLM でも SGLang でも OK c1 差は +28%。vLLM なら speculative decoding (+20〜30%) も狙える
複数ユーザー・並列前提サーバ SGLang c8 で 1.68x、p95 latency 0.59 倍
RAG / 長文要約 SGLang context が長いほど差が広がる (② で +28%)
Speculative decoding 重視 vLLM MTP / D-Flash のエコシステムが充実。4B FP8 + D-Flash で 2.6x 確認済み

この棲み分けは 並列スループットと p95 latency という指標で見た場合の判断であり、本記事 ③ としてはこの結論で完結します。

まだ残る疑問

ただし、ここで使った指標には抜けがあります。実運用での「体感速度」を決める要素として、次の 2 つが未測定のままです。

  • TTFT (Time to First Token): ストリーミング応答の最初のトークンが届くまでの時間。チャット UI で「入力して送信してから最初の文字が見えるまで」の体感を直接決める
  • ITL (Inter-Token Latency): トークンとトークンの間隔。「タイピングしているように見えるか、引っかかって見えるか」を決める

aggregate tok/s や p95 latency は「完了するまでの時間」で、TTFT と ITL を内包しています。両者を分離しないと、どちらのフェーズ (prefill か decode か) が遅延の原因か が分かりません。

加えて、scheduler 系パラメータ (--max-running-requests--chunked-prefill-size など) を 1 軸ずつ振ると、改善できるものと逆効果になるものに分かれることが想定されます。

これらは次回 ④ で別途調査しました。本記事 ③ の並列負荷比較としてはここで完結します。


実験環境: RTX 4070 12GB / Ubuntu / vLLM nightly (v0.21.1rc1.dev243) / SGLang lmsysorg/sglang:latest / モデル: RedHatAI/Qwen3.5-4B-FP8-dynamic

関連記事 (同じハードウェア・同じモデルでの FP8 KV cache 検証):

シリーズ過去記事:

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?