LLM推論バッチング(エルエルエムスイロンバッチング)
LLM推論バッチングは、複数の推論リクエストを束ねて同時に処理するスケジューリング技術。Static Batching(静的バッチング)・Dynamic Batching(動的バッチング)・Continuous Batching(連続バッチング)の3段階で進化し、GPUのTensor Core稼働率とメモリ帯域利用率を最大化する。連続バッチングの導入によりスループットは静的バッチング比で2〜5倍向上し、現代の推論エンジンの標準技術となっている。
LLM推論バッチングとは
LLM推論バッチングは、複数のユーザーリクエストを1つのGPU演算バッチにまとめて処理する技術である。LLMの推論はメモリ帯域律速(Memory Bandwidth Bound)であり、単一リクエストではGPUの演算能力の10〜20%しか活用できない。バッチングにより複数リクエストの行列演算をまとめることで、Tensor Coreの稼働率を80〜95%に引き上げ、リクエストあたりのコストを大幅に削減する。
バッチングの効果はLLMのAutoregressive(自己回帰)生成プロセスに起因する。1トークン生成のたびにモデル全体(数十GB)の重みをGPUメモリから読み出す必要があり、この「重み読み出しコスト」は1リクエストでも100リクエストでもほぼ同じである。したがって、バッチサイズを増やしても追加コストはKVキャッシュ分の僅かなメモリ増加のみで、リクエストあたりの演算コストは劇的に低下する(Arithmetic Intensity向上)。
バッチング手法の進化
Static Batching(静的バッチング)
最も原始的なバッチング手法。一定数のリクエストが溜まるか、タイムアウト期間が経過した時点でバッチを確定し、全リクエストが生成完了するまで新リクエストを受け付けない。
問題点:
- Padding Wastage: バッチ内のリクエストは入力長・出力長が異なるため、最長シーケンスに合わせてパディングが必要。パディング部分のGPU演算は完全に無駄。
- Tail Latency: バッチ内の1リクエストが長い出力を生成すると、短い出力で完了した他リクエストのレスポンスも遅延する。
- GPU Idle: バッチ完了から次バッチ開始までのアイドル時間が不可避。
| バッチ内リクエスト | 入力長 | 出力長 | 完了タイミング | アイドル時間 |
|---|---|---|---|---|
| リクエストA | 128 | 32 | 0.8秒 | 4.2秒待ち |
| リクエストB | 256 | 64 | 1.5秒 | 3.5秒待ち |
| リクエストC | 512 | 256 | 5.0秒 | 基準(最遅) |
| リクエストD | 64 | 16 | 0.4秒 | 4.6秒待ち |
上記例では、バッチ全体の完了は最遅のリクエストC(5.0秒)に律速され、A/B/Dは3.5〜4.6秒のアイドル時間が発生。バッチ平均GPU利用率は約35%にとどまる。
Dynamic Batching(動的バッチング)
Static Batchingのバッチ構成を動的に最適化する手法。入力長が近いリクエストを優先的にグループ化し、パディング量を最小化する。Triton Inference Serverのdynamic_batching設定が典型例で、preferred_batch_sizeとmax_queue_delay_microsecondsでバッチサイズと待機時間のトレードオフを制御する。
パディング削減により、Static Batching比で30〜50%のスループット向上が見込めるが、出力長のばらつきによるTail Latency問題は依然として残る。
Continuous Batching(連続バッチング / Iteration-Level Scheduling)
2023年にOrcaシステム(OSDI 2022論文)で提唱され、vLLMで広く普及した革新的スケジューリング手法。トークン生成の各イテレーション(1ステップ)単位でバッチ構成を動的に変更する。
動作原理:
- イテレーション開始時に、現在のバッチ内リクエストの状態を確認
- EOSトークンを生成したリクエストをバッチから除去
- 待機キューから新リクエストを空きスロットに投入
- 更新されたバッチでAttention + FFN演算を実行
- 1に戻る
これにより、GPUは常に最大バッチサイズに近い状態で稼働し、アイドル時間がほぼゼロになる。
Prefill/Decode分離アーキテクチャ
LLM推論には2つのフェーズがある。
Prefillフェーズ
入力プロンプト全体を一括処理してKVキャッシュを構築するフェーズ。入力トークン数に対して演算量がO(n²)で増加し、Compute Bound(演算律速)となる。バッチ内の全リクエストのPrefillを並列実行することで、GPU演算能力を最大活用できる。
Decodeフェーズ
1トークンずつ自己回帰生成するフェーズ。KVキャッシュの読み出しが支配的で、Memory Bandwidth Bound(メモリ帯域律速)となる。バッチサイズを大きくしてもメモリ帯域がボトルネックとなり、スループット向上に限界がある。
Chunked Prefill
PrefillとDecodeを同一バッチ内で混在させる技術。長い入力のPrefillをチャンク(例: 512トークン単位)に分割し、Decodeリクエストと交互に実行する。これにより、Prefillの長い入力がDecodeリクエストのレイテンシを阻害する「Prefill Starvation」問題を解消する。vLLM 0.6以降、SGLang 0.4以降で標準実装。
| フェーズ | 演算特性 | ボトルネック | バッチング効果 |
|---|---|---|---|
| Prefill | O(n²)演算 | Compute Bound | 高(演算並列化) |
| Decode | KVキャッシュ読み出し | Memory BW Bound | 中(帯域上限) |
| Chunked Prefill | Prefill+Decode混在 | 両方のバランス | 高(レイテンシ平準化) |
スループットvsレイテンシのトレードオフ
バッチサイズの増大はスループット向上とレイテンシ増加のトレードオフを伴う。
スループット最大化(バッチ推論・オフライン処理)
バッチサイズを可能な限り大きくし、GPUメモリの限界まで同時リクエストを詰め込む。オフラインのバッチ評価(ベンチマーク・データセット処理)で使用。レイテンシは犠牲にするが、1トークンあたりのコストを最小化。
レイテンシ最小化(リアルタイムチャット)
バッチサイズを制限し、個々のリクエストのTTFT(Time to First Token)とITL(Inter-Token Latency)を最小化する。チャットアプリケーションでは、TTFT < 200ms、ITL < 50msが快適性の目安。vLLMの--max-num-seqsやSGLangの--max-running-requestsでバッチサイズ上限を制御する。
SLO(Service Level Objective)ベースの制御
本番環境では、P99レイテンシ目標(例: TTFT P99 < 500ms)を満たす範囲でスループットを最大化する。vLLMの--scheduling-policyやカスタムスケジューラで、SLO違反リスクが高まった時点でバッチサイズを動的に縮小する。
よくある質問(FAQ)
Q1: バッチサイズはいくつに設定すべきですか?
用途とハードウェアに依存する。リアルタイムチャットなら8〜32、APIサービングなら32〜128、オフラインバッチ処理なら256〜1024が目安。GPUメモリに余裕があればバッチサイズを大きくするほどスループットが向上するが、KVキャッシュのメモリ消費が増加するため、--gpu-memory-utilization 0.9(vLLM)のようにメモリ使用率の上限を設定して安全マージンを確保する。
Q2: Continuous BatchingとDynamic Batchingの違いは何ですか?
Dynamic Batchingはバッチ全体の開始・終了を単位とするのに対し、Continuous Batchingはトークン生成の1イテレーション単位でバッチ構成を変更する。Dynamic Batchingでは生成完了したリクエストがバッチ終了まで待機するが、Continuous Batchingでは即座に退出・新リクエスト投入が行われる。スループットはContinuous Batchingが2〜5倍優れる。
Q3: Prefill/Decode分離はどんな場合に有効ですか?
入力プロンプトが長い(4K〜128Kトークン)場合に特に有効。長いPrefillがDecodeリクエストのレイテンシを数秒単位で阻害する「Prefill Starvation」が発生するため、Chunked PrefillまたはPrefill/Decode専用GPUの分離構成で解消する。RAG(検索拡張生成)やドキュメント要約など、入力長が出力長を大幅に上回るワークロードで必須の技術。