ステータス: ドラフト(2026-10-07更新)、一部実測値を反映。 公式ドキュメントで確認した記述をベースに執筆しており、直接測定した実測値は第4章の実験を引用した部分と、第5章の終わりの「実測:同じモデル、同じGPUでのvLLM対SGLang」(小さなモデル1つ、GPU1枚)のみです。大きなモデル、マルチGPU、異なるワークロードでの比較は未測定です。 記事の終わりの「確認したバージョンと日付」と「確認できなかったこと」を先にお読みください。 執筆ルール:実測データを基本とし、測定できなかったものは「未測定」と表記します。
目次
- 1. 設計哲学
- 2. 機能比較表
- 3. 選択基準:ワークロードの形状別
- 4. サーバーを立ち上げてOpenAI互換APIで呼び出す
- 5. ベンチマークを正しく行う方法
- 6. 実際に試してみる
- 7. 面接で問われそうな質問
- 8. よくある誤解
- 参考文献
- 確認したバージョンと日付、確認できなかったこと
2つのエンジンを選ぶことは好みではなく、ワークロードの形状と測定の問題です。この記事では、vLLMとSGLangがプリフィックスキャッシュをどのように異なる設計にしたか、現在はどこまで収束しているか、ワークロードごとに何を真っ先に疑うべきかを整理し、その判断を裏付けるベンチマークをどのように設計し、読み解くかを扱います。筆者はSGLangを直接運用または測定したことがありません。2つのエンジンの優劣に関する独自の実測値はなく、公式ドキュメント、ソースコード、論文、そして筆者の実験環境でvLLMを用いて測定した経験のみを根拠に執筆します。
この記事を読んだ後に面接で説明できるようになること
- ブロックハッシュチェーン(vLLM)とラディックスツリー(SGLang)の違い、および現在は機能が大きく重複している点を区別して説明できること。
- プリフィックスの再利用、構造化出力、モデルサポートなどのワークロード形状別の選択基準とその限界を説明できること。
- オープンループ負荷、ポアソン到着、プリフィックスキャッシュの罠、パーセンタイルを含むベンチマークの設計と、結果をGPU台数に換算する手順を説明できること。
1. 設計哲学
vLLMはPagedAttentionの論文(SOSP 2023)に端を発する汎用高スループットエンジンです。V1構造では、APIサーバープロセスがZMQを介してエンジンコアプロセス(スケジューラーとKVマネージャー)と通信し、エンジンコアはGPUごとに1つ存在するワーカーを管理します(公式アーキテクチャドキュメント)。v0.29.0以降、Model Runner V2がデフォルトです(リリースノート、一部のROCmモデルなどは例外)。
SGLangの論文(arXiv 2312.07104)は、エンジンではなくLLMプログラムを効率的に実行するシステムとして始まります。フロントエンド言語は生成(gen, select)と並列制御(fork, join)プリミティブを提供し、ランタイムには2つの手法があります。RadixAttentionは、リクエストが終了してもプロンプトと生成結果のKVをラディックスツリーにLRUとして保持し、一致するプリフィックスが長いリクエストを優先的に処理するキャッシュ認識型スケジューリングを使用します。圧縮有限状態機械は、制約によって決定される複数のトークンを1ステップでデコードします。論文では、スループットが最大6.4倍向上し、遅延が最大3.7倍削減されると主張されています。条件は主にA10G 24GB(一部A100)、Llama-7B/70B、Mixtral-8x7Bなどで、エージェント、JSONデコディング、RAG、マルチターンなどのプリフィックス再利用が大きいワークロードであり、比較対象となったvLLMはv0.2.5です。論文では、その時点の最新のvLLMにRadixAttentionが実験的機能として部分的に統合されていたため、以前のバージョンを使用したと脚注で述べています。現在のv0.30.0と比較した数値ではありません。
vLLM: 블록 해시 사슬 (블록 16 토큰) SGLang: 라딕스 트리 (page-size 기본 1)
요청 A [B0]-[B1]-[B2] root
요청 B [B0]-[B1]-[B3] └ "시스템 프롬프트 ..." (공통 노드 하나)
해시 = H(부모 해시, 블록 토큰, 추가 키) ├ "질문 A ..." -> 생성 결과
해시 맵 조회, 빈 블록 큐에서 LRU 방출 └ "질문 B ..."
리프부터 방출(기본 lru), 사용 중 노드는 잠김
SGLangのドキュメントの --page-size のデフォルト値が1であるため、プリフィックスの一致がトークン単位で可能であるというのはドキュメントのデフォルト値から推測したものであり、vLLMは埋まったブロック(16トークン)単位でのみヒットします(第4章)。最近のSGLangのリリースノートには、v0.5.19で統合ラディックスツリーがすべての構成のデフォルトになり、v0.5.21でプリフィックスキャッシュがRustコアのデフォルト(サポートしていない構成はPythonにフォールバック)になり、古い実装(SWARadixCache, MambaRadixCache, HiRadixCache)が削除されたと記載されています。同時期に、vLLMはプリフィックスキャッシュをデフォルトで有効にし、構造化出力(xgrammar、guidance)をサポートしています。したがって、哲学の違いがそのまま機能の有無の違いになるわけではありません。
2. 機能比較表
ドキュメントに項目が存在することを意味し、成熟度や性能の優劣を示すものではありません。基準はvLLM v0.30.0のドキュメントとSGLang v0.5.21のドキュメントです。
| 領域 | vLLM | SGLang |
|---|---|---|
| プリフィックスキャッシュ | ブロックハッシュ、LRU、デフォルトで有効。容量拡張は OffloadingConnector、LMCache |
ラディックスツリー、 --radix-eviction-policy lru、lfu、slru、priority、tlru。階層キャッシュ HiCache |
| リクエストの順序 | --scheduling-policy fcfs(デフォルト)、priority |
--schedule-policy fcfs(デフォルト)、lpm(最長プリフィックス優先)、dfs-weightなど |
| 推測デコーディング | EAGLE、MTP、ドラフトモデル、n-gram、suffixなど | EAGLE-2/3、MTP、独立ドラフトモデル、NGRAM |
| 量子化 | AWQ、GPTQ、FP8、INT8、bitsandbytes、ModelOpt、GGUF、KVキャッシュFP8 | --quantization awq、fp8、gptq、modelopt_fp4など、 --kv-cache-dtype fp8_e4m3、fp8_e5m2 |
| LoRA | --enable-lora --lora-modules |
--enable-lora、1つのバッチ内で複数のアダプター(S-LoRA、Punica手法) |
| マルチモーダル | 画像、動画、音声入力 | ビジョンAPIドキュメント、マルチモーダルモデル |
| 構造化出力 | xgrammar、guidance(structured_outputs) |
xgrammar(デフォルト)、outlines、llguidance。JSONスキーマ、正規表現、EBNF |
| MoE | --enable-expert-parallel (EP = TP × DP) |
--ep-size, --attn-dp-size |
| 分散 | TP、PP、DP。PD分離はドキュメントにexperimentalと表記 | TP、DP、EP。SGLang Model Gateway(ルーター)、PD分離ドキュメント |
| モデルフォールバック | Transformersモデリングバックエンド | --model-impl transformers |
vLLMのドキュメントでは、PD(プレフィル、デコード)の分離はスループットを向上させるものではないと記載されており、目的はTTFTとITLの個別調整とテールレイテンシの制御に限定されています。
3. 選択基準:ワークロードの形状別
| ワークロード | まず確認すべき点 |
|---|---|
| プリフィックスがほとんど重複しない単発のリクエスト | プリフィックスキャッシュのメリットがないため、エンジンの違いはカーネルとスケジューラーのオーバーヘッドのみです。実際に測定する必要があります。 |
| 長い共通システムプロンプト、RAGドキュメントの繰り返し | プリフィックスキャッシュが鍵となります。どちらのエンジンもサポートしており、容量が不足している場合はオフローディング(vLLM)またはHiCache(SGLang)を検討します。第4章の実験がこのケースに該当します。 |
| エージェント、マルチターン(ターンごとに履歴が増加する) | 直前のターンのKV再利用が鍵となります。SGLangが設計の中心に据えており、ドキュメントにはセッション認識型ラディックスキャッシュと agentic-trace のベンチマークデータセットがあります。vLLMでもプリフィックスキャッシュで可能ですが、同じワークロードでの優劣は測定していません。 |
| 構造化出力が大部分を占める | 両方のエンジンが同じxgrammarをサポートしているため、エンジンよりもスキーマの複雑さとバックエンドを確認します。 |
| 最新の大規模MoEモデルを素早く試したい | モデルごとのクックブックや検証済みの実行レシピがあるかを確認します。 |
| Kubernetes上での運用 | vLLMのドキュメントにはllm-d、production-stack、KServe、KubeRay、AIBrix、Dynamoの統合項目があり、SGLangにはModel GatewayとKubernetes PDデプロイのドキュメントがあります。 |
モデルのサポート速度は定量的に確認できていません。リリースノートから見えた事例は以下の通りです。Qwen3.8-Flash-NextとHy4-previewは、vLLM v0.29.0(2026-09-09)の方がSGLang v0.5.20(09-18)よりも先に追加され、GLM-5.3-Flash、K2-Horizon、Nanbeige4.2は、SGLang v0.5.20の方がvLLM v0.30.0(09-22)よりも先でした。リリースに含まれたタイミングに過ぎず、day-0サポートであるかどうかも不明なため、どちらが速いかの根拠にはなりません。両方のエンジンにTransformersベースのフォールバックがあるため、ネイティブ実装のないモデルでも一応起動することは可能です。
4. サーバーを立ち上げてOpenAI互換APIで呼び出す
vllm serve Qwen/Qwen2.5-1.5B-Instruct --max-model-len 8192 # 기본 포트 8000
sglang serve Qwen/Qwen3-0.6B --host 0.0.0.0 --port 30000 # 문서 quickstart (기본 host 127.0.0.1)
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen2.5-1.5B-Instruct","messages":[{"role":"user","content":"안녕"}],"max_tokens":32}'
運用前に知っておくべき内容です。vLLMの --api-key は /v1, /v2, /inference のパスのみを認証し、 /invocations などの他のパスは認証しないため、これだけでセキュリティを担保しないよう公式ドキュメントが警告しています。vLLMサーバーはデフォルトでモデルリポジトリの generation_config.json を適用するため、サンプリングのデフォルト値がモデルごとに異なる場合があります(--generation-config vllm で無効化)。SGLangは --enable-metrics を有効にしないとPrometheusのメトリクスを出力せず、 --enable-cache-report でレスポンスの prompt_tokens_details にキャッシュヒットトークン数を受け取ることができます。
5. ベンチマークを正しく行う方法
ワークロードの形状を決定することから始めます。 入力および出力長の分布、プリフィックスの共有率と人気の分布、到着パターン、遵守すべきSLOです。可能であれば実際のログから取得し、ない場合は仮定を記述して感度を確認します。
クローズドループとオープンループ。 クローズドループは同時ユーザー数を固定し、レスポンスを受け取った後に次のリクエストを送信します(--max-concurrency)。サーバーの処理が遅くなると負荷も同様に減少し、過負荷が表面化しません。オープンループはサーバーの状態に関係なく、定められた到着率でリクエストを送信します(--request-rate)。以下のモデル(説明用のシミュレーションであり実測値ではない)は、リクエストあたり0.25秒(容量4 req/s)の単一サーバーを対象としています。
열린 루프 3 req/s 평균 0.64s p99 2.26s
열린 루프 4.5 req/s 평균 57.43s p99 114.55s <- 용량 초과, 시간이 갈수록 대기열 증가
닫힌 루프 동시 4 평균 1.00s p99 1.00s <- 사용자 수만큼의 대기만 보임
両方のツールで --request-rate のデフォルト値が inf であるため、すべてのリクエストを時刻0に送信します(ソースコードで確認)。最大スループットはわかりますが、TTFTに待機時間が混ざるため、遅延の測定には使用できません。有限の値を指定するとポアソン過程で到着が生成され、vLLMの --burstiness は1.0がポアソン、小さくなるほどリクエストが一気に集中するガンマ分布になります。 --ramp-up-strategy でリクエストレートを徐々に上げることができます。
長さの分布。 vLLMの random データセットは --random-range-ratio のデフォルトが0.0であるため、長さが固定されます。実際の分布にはShareGPTや独自のログを使用します。モデルがEOSによって早期に終了すると出力長が変わるため、長さを固定するにはvLLMでは --ignore-eos を使用します(SGLangはデフォルトでEOSを無視し、 --disable-ignore-eos でオフにします)。 ウォームアップ。 vLLMは --num-warmups のデフォルトが0、SGLangは --warmup-requests のデフォルトが1です。最初のリクエストにはコンパイルとCUDAグラフのコストが混ざります。
最もよくある罠:プリフィックスキャッシュ。 vLLMのベンチマークドキュメントは、同じサーバーに vllm bench serve を繰り返すと、前回の実行のプロンプトがプレフィックスキャッシュに残ってスループットが水増しされる可能性があり、固定シードの合成データセットでも同様であると警告されています。第4回の実験はその規模を示しています。同じ文書を再度読み込んだ2回目のパスで、TTFTのp50が575.5msから48.6msに減少しました(LMCache、同時実行数1、1.5Bモデルの一度きりの測定値)。繰り返しが生み出す差は、エンジンの差よりも大きい場合があります。その対策は次のとおりです。
- 実行間にキャッシュをクリアします。vLLMの場合はキャッシュをクリアする
vllm bench sweep serveを使用するかサーバーを再起動し、SGLangのbench_servingには--flush-cacheがあるため、SGLangサーバーの/flush_cacheを、vLLMサーバーの/reset_prefix_cacheを呼び出します(vLLMはVLLM_SERVER_DEV_MODE=1でなければ開かないとSGLangのドキュメントとvLLMのenvs.pyのコメントで案内されています)。 - シードを変更するか、プレフィックス共有を意図的に組み込んだデータセット(vLLMの
prefix_repetition、SGLangのgenerated-shared-prefixと--gsp-group-distribution zipf)を使用して現実的なヒット率を作ります。 - 結果にプレフィックスヒット率(
prefix_cache_hits_total ÷ queries_total)を併記します。本番環境で有効にする機能は有効にした状態で比較します。
平均ではなく分位数。 デフォルトの出力として、vLLMは --metric-percentiles のデフォルト 99(平均と中央値を含む)、SGLangは平均、中央値、p99を出力します。p95が必要な場合は --metric-percentiles 50,95,99 のように指定します。サンプル数も確認します。リクエストが100件の場合、p99は上位1、2件の値で決まり、第4回の実験ではパスあたり10件だったためp50が最大値になっていました。TTFT、TPOT、ITLの定義はツールによって異なる場合があるとvLLMのドキュメントに記載されているため、同じクライアントで両方のエンジンを測定する方が公平です。SGLangの bench_serving は --backend vllm を使ってvLLMサーバーを呼び出すことができます。負荷ジェネレーターがボトルネックにならないよう、サーバーとは別のマシンで実行し、 --max-concurrency を使用する場合はクライアントの待ち時間(client_queue_time)を個別に確認します。vLLMのドキュメントでは、本番サービスのベンチマークにはGuideLLMが推奨されています。
ツール名が変更されました。 SGLang v0.5.21のソースでは、 sglang.bench_serving は非推奨の警告を出し、 sglang.benchmark.serving に移動しました(ドキュメントの例はまだ古い名前です)。vLLMは vllm bench serve です。
キャパシティプランニングにつなげます。 1)SLOを決定します(例:p99 TTFT 2秒、p99 TPOT 50ms)。2)リクエストレートを上げながら定常状態区間のp99を測定します。3)SLOを満たす最大リクエストレートが1インスタンスの容量(goodput基準)です。4)インスタンス数 = ピークリクエストレート ÷ (容量 × 目標使用率)に障害用の余裕を持たせます。説明用の数値としてピークが20 req/s、容量が4 req/s、目標使用率が0.7の場合、20 ÷ 2.8 = 7.1となり、8台に予備1台を追加して9台となります。曲線の形状はPagedAttention論文の記述と同じです。リクエストレートが上がると遅延が緩やかに増加し、容量を超えた瞬間にキューが延び続けて遅延が急激に大きくなります。
p99 TTFT
│ /
SLO ┼ - - - - - - - - - - - - - -/- - <- 이 교점의 요청률이 용량
│ _____/
│ ____________________/ (이 지점부터 급증)
└────────────────────────────── 요청률
実測:同一モデル、同一GPUでのvLLM対SGLang
同一モデル(Qwen3-1.7B、bf16)、同一GPU(8GBクラス1枚)、同一クライアント(リクエストごとに異なるランダムな入力、出力128トークン、ストリーミングによるTTFTとITLの測定)で両方のエンジンを測定しました。vLLM 0.31.0は --gpu-memory-utilization 0.85 --max-num-seqs 64、SGLang 0.5.21は --mem-fraction-static 0.8 --max-running-requests 64であり、コンテキスト長はどちらも4,096です。2つのオプションの意味が異なるため、 メモリ設定は同一ではありません。 以下の入力512の表は サーバーを3回再起動して測定し、値は3回の中央値です。初回測定値と3回繰り返した値は1%以内で同じでしたが、下のvLLM同時実行数64のみ例外でした。入力2,048の表は繰り返していない1回分の値です。
| 入力512、出力128 | 同時実行数1 | 同時実行数8 | 同時実行数32 | 同時実行数64 |
|---|---|---|---|---|
| スループット (tok/s) vLLM / SGLang | 66 / 66 | 384 / 386 | 831 / 809 | 842 / 836 |
| TTFT中央値 vLLM / SGLang | 73 / 89ms | 349 / 454ms | 669 / 1,093ms | 2.2 / 4.0s |
| ITL p99 vLLM / SGLang | 16 / 15ms | 19 / 19ms | 224 / 33ms | 231 / 101ms |
同時実行数64のvLLMの列は、サーバーを起動するたびに異なっていました。 3つのサーバーでのスループットは792、842、843 tok/s、TTFT中央値は5.7、2.1、2.2秒でした。最初に起動したサーバーのみが遅く、サーバーログのKVキャッシュは22,048トークンでした(残りの2つは26,560トークン)。同じオプションであるにもかかわらず、vLLMはメモリを測定してKVの領域を決定するため、サーバーを起動するたびに容量が異なり(22,048から27,504トークン)、同時64リクエストが640トークンずつキャッシュをほぼ満たすこの時点では、20%の容量の差によってTTFTが2.7倍に変化しました(第14回)。SGLangは3回すべて25,282トークンであり、値も1%以内で同じでした。上表のvLLM同時64は3回の中央値であり、この1つのセルは試行ごとの誤差が大きいです。
| 入力2,048、出力128 | 同時実行数4 | 同時実行数16 | 同時実行数32 |
|---|---|---|---|
| スループット (tok/s) vLLM / SGLang | 151 / 150 | 207 / 219 | 213 / 212 |
| TTFT中央値 vLLM / SGLang | 0.73 / 0.83s | 4.0 / 2.5s | 12.5 / 12.9s |
- スループットはほぼ同じでした(同時32でvLLMが3%高く、その他は1%以内)。このモデルはトークン生成がメモリ帯域幅に制約されているため(第21回)、どちらのエンジンでも同じハードウェアの限界に達するためであると読み取れます。エンジンを選ぶ理由が、こうした小さなモデルの純粋なスループットではないということです。大きなモデルでは異なっていました(後述の「大きなモデル」)。マルチGPUは測定していません(未実測)。
- KVキャッシュの1トークンあたりのサイズは同じであり、容量は設定によって異なっていました。 SGLangのログは25,282トークン(K 1.35 + V 1.35GB)であり、vLLMは22,048トークンでした。1トークンあたりのバイト数(約114.7KB)は計算式と一致しているため、容量の差はメモリ設定の差に起因します。GPUメモリ使用量はSGLangが7,373MiB、vLLMが6,168MiBであり、同じカードでSGLangの設定の方が多くのメモリを消費しました。
- TTFTとテールレイテンシの傾向は異なっていました。 同時32において、SGLangのTTFT中央値は1,093msとvLLM(669ms)よりも長く、ITL p99は33msとvLLM(224ms)よりも短くなりました。同時64では、TTFTはvLLMの方が低く(2.2秒対4.0秒、ただしvLLMはサーバーごとにばらつきあり)、ITL p99はSGLangの方が低くなりました(101ms対231ms)。初回測定値では、同時64のTTFTもSGLangの方が低くなりましたが、それは上記の遅い最初のサーバーが原因でした。新しいリクエストの入力処理をデコードとどのように混在させるかが異なるという手がかりですが、原因の特定までは行っていません(推測)。
- プレフィックスキャッシュの効果は両方で同等でした。 共通の3,000トークンのプレフィックスにおいて、最初のリクエストのTTFTはvLLMが394ms、SGLangが399msであり、以降は28msと36msになりました。無効にすると(
--no-enable-prefix-caching,--disable-radix-cache)、393msおよび399〜416msに戻りました。
大きなモデル(Qwen3-4B-FP8)での違い
1.7B bf16の約2.4倍大きなQwen3-4B-FP8(8GBに収まるようにfp8量子化された重み)を用いて、サーバーを2回起動して同じテストを繰り返しました。精度が異なるため、1.7Bとの直接的な比較ではなく「同一モデルにおける2つのエンジン」として読み取る必要があります。両方のエンジンともコンテキスト長は4,096、最大同時実行数は64であり、サーバーログのKVキャッシュは、vLLMが8,880トークンと13,232トークン、SGLangが2回とも13,036トークンでした。
| 入力512、出力128 | 同時実行数1 | 同時実行数8 | 同時実行数32 |
|---|---|---|---|
| スループット (tok/s) vLLM / SGLang | 49 / 49 | 251 / 278 | 323·365 / 450 |
| TTFT中央値 vLLM / SGLang | 166・168 / 113ms | 911 / 720ms | 7.2・3.4 / 3.6s |
| ITL p99 vLLM / SGLang | 21 / 20ms | 24〜27 / 25ms | 548・583 / 138ms |
- 小さなモデルでは同等だったスループットが、大きなモデルでは分かれました。 同時8でSGLangが11%、同時32で23〜39%高くなりました(同時32のvLLMの2つの値は、KVが8,880トークンだったサーバーと13,232トークンだったサーバーの値です。KVが同程度のもの同士で比較すると365対450となり23%の差です)。原因の特定までは行っていません。fp8の重みを読み込むカーネルの違いである可能性がありますが、確認はできていません(推測)。
- 割り込み時の最大ITLの傾向は1.7Bと同じでした。 vLLMが602ms(両方とも)、SGLangが770〜772msとvLLMの方が低くなりました(1.7Bは227ms対約460ms)。割り込む3,500トークンの入力自体のTTFTは、vLLMが1.22秒、SGLangが0.78〜0.80秒とSGLangの方が速くなりました(1.7Bはどちらも約470msで同等でした)。処理を早く終える代わりにデコードのアイドル時間が長かったと読み取れますが、原因の特定までは行っていません(推測)。
- vLLMの同時32でのTTFTは、サーバーごとに7.2秒と3.4秒で異なっていました。 KVが8,880トークンだったサーバーの方遅く、これは1.7Bで確認した現象(第14回)と同じです。1回のみサーバーを起動した値でエンジンを比較してはならない理由がここにあります。
構造化出力(JSONスキーマ制約)のコスト
SGLang 0.5.21のJSONスキーマ制約デコード(response_format の json_schema、文法バックエンドのデフォルトはxgrammar)を有効にしたリクエストと、プロンプトのみでJSONを要求したリクエストを同じサーバーで比較しました(Qwen3-1.7B、思考プロセス無効、出力約74トークン、リクエストごとに異なる入力)。
| 同時実行数 | 制約なしのスループット | 制約ありのスループット | 有効なJSON(スキーマを満たす) 制約なし / 制約あり |
|---|---|---|---|
| 1 | 69 tok/s | 67 tok/s (-3%) | 4/4 / 4/4 |
| 8 | 482 tok/s | 486 tok/s | 32/32 / 32/32 |
| 32 | 1,577 tok/s | 1,654 tok/s (+5%) | 128/128 / 128/128 |
- 制約付きデコードのコストはこの条件では測定できないほど小さかったと言えます(誤差の範囲内の-3%から+5%)。文法マスクの計算負荷が、GPUでのトークン生成処理に比べて小さかったことを意味します。
- 制約がなくてもすべて有効な出力が得られました。 このモデルは簡単なスキーマにおいてプロンプトのみでも128/128件すべて正解したため、制約によるメリット(形式の保証)はこのテストでは現れませんでした。フォーマットエラーが発生しやすい小さなモデルや複雑なスキーマでは結果が異なるはずですが、今回は検証していません(未実測)。1回ずつの測定値であるため、試行ごとの誤差は不明です。
- 測定中に発生した事象。 このテストをメモリ比率0.8で最初に実行した際、最初のリクエストでサーバーとの接続が切断されました。比率を0.7に下げて再実行したところ、サーバーログに
Triton kernel ... device-loaded after serving started (free device mem: 0.64 GiB). Pre-load it during engine init to avoid CUDA OOMという警告が出力されました。制約付きデコードはサービスの開始後にGPUカーネルを初めてロードするため、メモリに余裕(約0.6GiB以上)を残しておかないと、最初のリクエストでサーバーがクラッシュする可能性があることを意味しています(原因は警告文と状況証拠に基づくものであり、0.8での終了ログは保存できませんでした)。
重みをfp8に縮小した場合:両方のエンジンで同じ方向性
同じサーバー設定で SGLang に --quantization fp8 を追加しただけです(bf16 の重み 3.28GB がほぼ半分に)。
| 入力 512、出力 128 | 同時実行 1 | 同時実行 8 | 同時実行 32 | 同時実行 64 |
|---|---|---|---|---|
| SGLang bf16 スループット (tok/s) | 66 | 382 | 808 | 837 |
| SGLang fp8 スループット (tok/s) | 100 | 567 | 1,122 | 1,250 |
| 増加 | +52% | +48% | +39% | +49% |
| fp8 の ITL 中央値 (ms) | 9 | 11 | 19 | 27 |
同時実行 1 で 66 から 100 tok/s に増加したことは、1 つのトークンを生成する際に読み込む重みのバイト数が減少した効果であると解釈されます(第 21 回の帯域幅上限の式)。第 21 回で vLLM により測定された 1.7B fp8 の 105.7 tok/s と同等レベルであるため、この利得はエンジンの特性ではなく精度の特性です。品質(精度)はこのテストでは測定していません(未測定)。
長い入力が割り込む場合:デフォルトの動作が異なる
デコード中の 1 つのリクエストに対し、3,500 トークンの入力リクエスト 3 件を割り込ませ、割り込ませた区間の最大トークン間隔(ITL)を測定しました。
| サーバー設定 | 割り込み区間の最大 ITL | チャンクサイズのログ |
|---|---|---|
vLLM、 --max-num-batched-tokens 512 |
69ms | 512 |
| vLLM、デフォルト | 227ms | 確認できず |
| vLLM、8192 | 445ms | 8192 |
| SGLang、デフォルト(初回テスト 3 回:459、457、464ms、サーバーの再テスト 3 回:462、462、462ms) | 約 460ms | 2048(ログ) |
SGLang、 --chunked-prefill-size 512 |
472ms | 512 |
SGLang、 --enable-mixed-chunk |
228ms | 2048 |
SGLang、 --enable-mixed-chunk --chunked-prefill-size 512 |
69ms | 512 |
- SGLang はデフォルト設定でチャンクサイズを小さくしてもカクつきは減少しませんでした。 チャンクを 512 に減らしたにもかかわらず、最大 ITL は 472ms となり、3,500 トークンを一度に処理する時間(約 470ms)と同等でした。入力処理が終了するまでデコードが待機させられていたと解釈されます。
--enable-mixed-chunkを有効にすると、 チャンク 1 つ分の時間(2048 トークンの場合は約 230ms、512 トークンの場合は約 69ms)と同等になり、この値は vLLM の結果と一致しました。ドキュメントのオプション説明(チャンクとデコードを 1 つのバッチに混在させる)がテスト結果と一致しています。 - vLLM はデフォルトでチャンクとデコードを混在させる動作になっているようです。 デフォルト設定でのカクつき 227ms が 2,048 トークンチャンクの時間(約 250ms)と一致するためです。ただし、vLLM のデフォルトのチャンクサイズはログに出力されないため推測となります。
- SGLang の
--chunked-prefill-sizeの自動決定値を確認しました。 この GPU ではログに 2048 と記録され(ドキュメントの表では None)、max_prefill_tokens=16384も同時に記録されました。異なるメモリサイズの GPU では異なる可能性があります(未測定)。 - 混合チャンクと 512 を併用したサーバーで同時実行 32 のテストを 3 回行ったところ、 スループットは 835~837 tok/s(デフォルトの SGLang は 807~809)、TTFT 中央値は 321~323ms(デフォルトは 1,089~1,094ms)、ITL p99 は 66~67ms(デフォルトは 31~35ms)であり、3 回とも 1% 以内で同等でした。チャンクを縮小して混在させる設定により、TTFT が約 3.4 倍に短縮される代わりに、ITL のテールレイテンシが約 2 倍に増加するというトレードオフが繰り返されました。vLLM の
--max-num-batched-tokens 512は、同じ負荷においてスループット 834~835、TTFT 335~338ms、ITL p99 70ms(デフォルト 224ms)でした。なぜそのようになるのかの詳細な分析は行っていません(推測)。 - サーバーの準備時間は同等でした。 SGLang は初回起動 60 秒、再起動 30~36 秒、vLLM は初回起動 71 秒、再起動 35~55 秒でした(モデルファイルが存在する場合)。
このテストが示している最も実用的な点は、 エンジン名よりもデフォルト設定が異なるという点です。同じ名前のオプションでもデフォルト値が違うため、比較を行うにはチャンクサイズ、ミックスドチャンク、メモリ設定、プレフィックスキャッシュをログで確認し、一致させてから計測し直す必要があります。この記事の第5節の前半(正しくベンチマークを行う方法)で述べている「条件を合わせる」が、このテストにおいて具体的な数値として現れました。
6. 自分で試してみる
すべて読み取り専用またはローカル計算です。
curl -sL https://raw.githubusercontent.com/vllm-project/vllm/v0.30.0/vllm/benchmarks/serve.py \
| grep -n -E '"--(request-rate|burstiness|max-concurrency|num-warmups|goodput|metric-percentiles)"'
curl -sL https://raw.githubusercontent.com/sgl-project/sglang/v0.5.21/python/sglang/benchmark/serving.py \
| grep -n -E '"--(request-rate|max-concurrency|warmup-requests|flush-cache)"'
第5節のオープンループとクローズドループの比較は、以下のコードの出力を表しています(実測値ではなく説明用のシミュレーションであり、リクエスト数を増えると容量超過区間のレイテンシも一緒に増大します)。
import random, statistics as st
random.seed(1)
S, N = 0.25, 4000 # 요청 하나에 0.25초 = 용량 4 req/s, 요청 4000개
def open_loop(rate): # 포아송 도착: 서버가 느려도 요청은 제 시간에 온다
t = free_at = 0.0; lat = []
for _ in range(N):
t += random.expovariate(rate)
free_at = max(t, free_at) + S
lat.append(free_at - t)
return lat
def closed_loop(users): # 사용자 고정: 응답을 받아야 다음 요청을 보낸다
ready, free_at, lat = [0.0] * users, 0.0, []
for _ in range(N):
i = ready.index(min(ready)); t = ready[i]
free_at = max(t, free_at) + S
lat.append(free_at - t); ready[i] = free_at
return lat
def row(name, lat):
s = sorted(lat)
print(f"{name:<14} 평균 {st.mean(lat):6.2f}s p99 {s[int(.99 * (N - 1))]:6.2f}s")
for r in (3, 4.5, 5): row(f"열린 {r} req/s", open_loop(r))
for u in (4, 16): row(f"닫힌 동시 {u}", closed_loop(u))
第04話の実験をこの記事基準で評価し、弱点を探してみてください。同時実行数1、固定ドキュメント、同じドキュメントの再利用、ドキュメント10個という理由でp95が最大値になるという点がすべて引っかかります。改善課題は、同時実行数4(実験設定の --max-num-seqs=4)、ポアソン到着、長さ分布、プレフィックスヒット率の記録です。この実験は一時的なPodで実行され、この記事では再実行していません。リスクは同じノード上の他のワークロードとのメモリ競合およびGPU占有であり、Podは --rm によって削除され、残った場合は1時間後に終了します。
7. 面接で出そうな質問
- vLLMとSGLangのプレフィックスキャッシュはどう違いますか? vLLMはブロック16トークンごとに親ハッシュを繋げたハッシュチェインをマップで照会し、空きブロックキューからLRUで追い出します。SGLangはトークン列をラディックスツリーに保存し、リーフから追い出します。デフォルトのpage-sizeは1です。現在はvLLMもデフォルトで有効になるため、違いは機能の有無よりも粒度、スケジューリングポリシー、エビクションポリシーにあります。
- エージェントワークロードにおいてSGLangの方が優れているという主張をどうやって検証しますか? 同じハードウェア、モデル、精度において、同じクライアントで実際のターン構造のトレースをオープンループで送信します。プレフィックスヒット率を一緒に記録し、p99 TTFTとSLOを満たすスループットを比較します。論文の数値はvLLM v0.2.5と比較したものであるため、現在のバージョンにはそのまま適用できません。
- オープンループとクローズドループはそれぞれいつ使いますか? 容量とSLOを把握するためには、オープンループでリクエスト率を上げながら急増地点を探します。クローズドループは上位システムが同時実行数を制限する環境を模倣するときに使用し、過負荷時にレイテンシが増加しないように見える限界を把握します。
- ベンチマーク結果が異常に良好です。何を疑うべきですか? 同じプロンプトの繰り返しによりプレフィックスキャッシュがヒットしたか、ウォームアップを除外したか、出力がEOSで早期終了したか、リクエスト率が
infであるためTTFTに待機が混ざっていないかを確認します。ヒット率の指標と、入力・出力トークン分布をあわせて確認します。 - ベンチマーク結果をGPU台数にどう換算しますか? SLOを遵守する最大リクエスト率をインスタンス容量とみなし、ピークリクエスト率を容量と目標使用率の積で割った後、障害への備えを加えます。平均スループットではなくp99基準である必要があり、ワークロードの形状が変わった場合は再計測する必要があります。
8. よくある誤解
- "SGLangは常に速い(またはvLLMは常に速い)。" ワークロードに依存します。論文の6.4倍という数値は、プレフィックスの再利用が多いワークロードにおいてvLLM v0.2.5と比較した場合の上限であり、論文の第6.2節では、この改善がKVキャッシュの再利用だけでなく、プログラム内部の並列性とより高速な制約付きデコーディングからももたらされると説明されています。つまり、プレフィックスキャッシュだけの効果ではありません。逆に、出力が長いマルチターン対話ではほとんど向上がないと、同じ節に記載されています。
- "プレフィックスキャッシュはSGLangにしかない。" vLLMでもデフォルトで有効になります。
- "最大スループットが容量である。" SLOを満たすスループット(goodput)が容量です。
- "平均レイテンシで比較すればよい。" テールレイテンシがサービス品質を決定し、平均値はキューの急増を隠してしまいます。
- "1回実行した数値で決定すればよい。" 繰り返し実行して分散を確認し、キャッシュ状態とバージョンを固定して記録します。
参考文献
実際に中身を確認したものだけを記載しています。
- SGLang論文: https://arxiv.org/abs/2312.07104(本文はarxiv.org/htmlで確認)、PagedAttention論文: https://arxiv.org/abs/2309.06180
- SGLangドキュメント(https://docs.sglang.io): get-started/quickstart, advanced_features の server_arguments, hyperparameter_tuning, structured_outputs, radix_eviction_policy, lora, speculative_decoding, pd_disaggregation, sgl_model_gateway, developer_guide/bench_serving, supported-models/transformers_fallback, llms.txt
- SGLangソース: https://github.com/sgl-project/sglang/tree/v0.5.21 の
python/sglang/bench_serving.py,python/sglang/benchmark/serving.py - SGLangリリース: https://github.com/sgl-project/sglang/releases(v0.5.19, v0.5.20, v0.5.21)
- vLLMドキュメント(https://docs.vllm.ai/en/latest/): design/arch_overview, benchmarking/cli, serving/online_serving/openai_compatible_server, features の structured_outputs, speculative_decoding, quantization, lora, multimodal_inputs, disagg_prefill, kv_offloading_usage, serving/expert_parallel_deployment, serving/data_parallel_deployment, sitemap.xml
- vLLMソース: https://github.com/vllm-project/vllm/tree/v0.30.0 の
vllm/benchmarks/serve.py,vllm/benchmarks/datasets/datasets.py,vllm/envs.py。リリース: https://github.com/vllm-project/vllm/releases(v0.29.0, v0.30.0)
確認したバージョンと日付、確認できなかったこと
- 確認日は2026-10-05です。vLLMの最新安定版はv0.30.0(2026-09-22)、SGLangの最新版はv0.5.21(2026-10-02)です。
- SGLang階層キャッシュ(HiCache)はこの環境では起動できませんでした。 ホストメモリを使用する階層キャッシュを有効にすると、サーバーの起動中に
Not enough host memory available. Requesting 1.85 GB but only have -10.74 GB freeで終了しました。ソース(pool_host/base.py)を読み込んだところ、ホストメモリの空き容量から常に10GiBを予約分として差し引いて比較するチェック(HICACHE_HOST_MEMORY_RESERVE_BYTES)が行われており、このテストのノードはRAMが約15GB、空き容量が約10GBであったため、リクエストサイズに関わらず通過することができませんでした。そのため、vLLMのLMCache(第8節)のようなシナリオ比較を行うことはできませんでした。階層キャッシュを使用するには、ホストの空きメモリが10GiBにリクエスト量を足した値よりも大きくなければならないという運用上のガイドラインとして読み取ることができます(ソース確認済み、大容量ホストでのテストは行っていません)。 - 確認できなかったこと:第5節の実測外の条件における両エンジンの現在バージョンの性能比較(論文の数値はvLLM v0.2.5基準)、SGLang
--chunked-prefill-sizeの自動決定値の決定ルール(このテストの8GBクラスGPUでは2048に設定されましたがルールは確認できず)、大モデル・マルチGPU環境における両エンジンの比較、SGLangフロントエンド言語の現在の活用レベル(ドキュメントにチュートリアルがあることの確認のみ)、モデルサポートのday-0の有無、/reset_prefix_cacheがVLLM_SERVER_DEV_MODE=1でのみ開かれる点の直接実行(環境変数のコメントとSGLangのドキュメントのみで確認)、ベンチマーク説明用シミュレーションの現実適合性。オープンループの過負荷において遅延が時間とともに大きくなることはシミュレーションと一般的な待ち行列理論に基づくものであり、別途ソースを開いて確認したわけではありません。