Continuous Batchingとは?長さの違うLLMリクエストをGPUで効率よく処理する
同時に届いたLLMリクエストをまとめてGPUへ渡せば、1件ずつ処理するより計算資源を使いやすい。ところが、100 tokenで終わる回答と2,000 token続く回答を同じbatchへ固定すると、短い回答が終わった後も長い回答の完了までbatchの枠が空いたままになる。
Continuous Batchingは、生成の途中でも完了したrequestをbatchから外し、空いた枠へ待機中requestを入れる。長さが揃わないLLM推論で、GPUのthroughputと利用者の待ち時間を両立するためのschedulerである。
固定batchでは一番長い生成に引きずられる
通常のbatch処理では、複数requestを一つのまとまりとしてmodelへ渡す。画像分類のように1回のforward passで結果が出る処理では、batchをまとめて一度に実行しやすい。
LLMの生成は異なる。promptを一度に処理するprefillの後、decodeでは次のtokenを1つ生成し、そのtokenを入力へ加えてまた次のtokenを生成する。requestごとにprompt長もoutput長も異なり、停止条件へ到達する時刻も違う。
固定batchでrequest A、B、Cを組み、Aが先に完了しても、batch全体を固定したままならAの枠をすぐ再利用できない。Cが長文を生成している間、待機中のDはGPUへ入れない。paddingで長さを揃えれば、実際には存在しないtokenに計算とmemoryを使う。
Continuous Batchingはiterationごとにbatchを組み直す
Continuous Batchingは、iteration-level batchingやinflight batchingとも呼ばれる。NVIDIA Tritonの公式資料は、各生成stepの終わりに完了requestを解放し、処理中requestを再度scheduleし、新しいrequestと次のbatchを組む方法として説明している。
流れは次のようになる。
- A、B、Cのprefillを処理する
- A、B、Cの次tokenを生成する
- Aが停止条件へ到達したらbatchから外す
- 空いたmemoryとslotへ待機中のDを入れる
- Dのprefill後、B、C、Dのdecodeを続ける
batchの境界をrequest完了まで固定せず、生成stepごとに更新するのが要点である。Hugging FaceのText Generation Inferenceも、total throughputを高める主要機能としてContinuous Batchingを提供している。
Dynamic Batchingとの違い
Dynamic BatchingとContinuous Batchingは似ているが、batchを作り直す時点が違う。
| 方式 | batchを作る時点 | 主な対象 |
|---|---|---|
| 固定batch | 処理前に固定 | offline処理、長さが揃った入力 |
| Dynamic Batching | model実行前に到着requestをまとめる | statelessな分類・回帰model |
| Continuous Batching | 生成iterationごとに完了・新規requestを入れ替える | tokenを逐次生成するLLM |
NVIDIA TritonのDynamic Batcherは、schedulerで待っているrequestをmodel instanceが空いた時点でまとめる。batchを大きくするための短い遅延も設定できる。ただし、通常のDynamic Batchingは一度modelへ渡したbatchへ途中からrequestを足す仕組みではない。
Continuous Batchingでは、すでにdecode中のrequestと新しいrequestを次のiterationで混ぜられる。LLMのように完了stepが大きくばらつく処理では、この違いがGPU利用効率に影響する。
prefillとdecodeは同じ仕事ではない
新しいrequestをbatchへ入れるには、まずprompt全体のprefillが必要である。prefillは多くのtokenを並列に処理し、計算量が大きい。一方、decodeは各requestについて基本的に1回1tokenを進め、KV Cacheの読み書きが効く。
そのため、待機requestを頻繁に入れすぎると、進行中requestのdecodeがprefillに何度も割り込まれ、tokenの配信間隔が伸びる。反対にprefillを長く後回しにすると、新しいrequestのTime to First Tokenが悪化する。
Hugging Face TGIのlauncherには、待機requestと実行中requestの比率、batchへ新規requestを入れるまでの最大待機token数、prefill token上限などの設定がある。公式の引数説明も、値が小さすぎれば新規prefillが計算を奪い、値が大きすぎれば短いrequestでも長時間待つ可能性を示している。
throughputを上げるだけならbatchを大きくしたくなる。しかし、利用者が見るのは次の三つである。
- requestを送ってから最初のtokenが届くまでのTTFT
- tokenが流れ始めてから次tokenが届く間隔のITL
- 最後まで生成し終えるまでのE2E latency
prefillの入れ方はTTFTとITLの間にtrade-offを作る。
batch sizeよりtoken量を見る
LLMでは「同時に8 request」という件数だけではmemory量を判断できない。短いpromptを8件と、context上限に近いpromptを8件では、必要なKV Cacheが大きく違う。生成可能な残りtokenもrequestごとに異なる。
TGIはmax_batch_total_tokensをhardware利用率を左右する重要な設定として説明している。paddingを使わない場合、batch内requestが持ちうるtotal tokenの合計を制御する。100 tokenのrequestなら10件入っても、1,000 tokenのrequestなら1件しか入らない、という考え方である。
安全な上限はmodel weightをGPUへ載せた後の空きmemory、KV Cacheの形式、量子化、attention実装、runtime overheadで変わる。request件数だけで上限を決めると、長文が重なったときにOOMになるか、短文中心の時間帯にGPUを余らせる。
throughputを上げると1件の応答が速くなるとは限らない
Continuous Batchingは、一定時間に処理できるtoken数を増やしやすい。一方、同じGPU上で多くのrequestを進めれば、1件が受け取る計算時間は分かれる。throughputが改善しても、TTFTやITLのp95、p99が悪化する場合がある。
運用では目標を分ける。
- offline要約では、1件の待ち時間よりtotal tokens/sを優先する
- 対話AIでは、TTFTとtokenの滑らかな配信を優先する
- agentのtool callでは、短い構造化出力を長文生成の後ろで待たせない
- 複数tenantでは、大量の長文requestが他利用者を占有しないようにする
一つのqueueと同じscheduler設定ですべてを扱うと、batch効率は高くても重要requestのSLOを守れない。対話、batch、優先requestでqueueやserving poolを分けることも選択肢になる。
長文requestと公平性
長いcontextと長いoutputを許すrequestは、KV Cacheを長時間保持する。slotが空かず、短いrequestが待ち続けるhead-of-line blockingが起こりうる。Continuous Batchingは固定batchの空き枠を減らすが、公平性を自動で保証するわけではない。
次の制御を組み合わせる。
- input、output、total tokenの上限
- tenantまたはworkload classごとの同時実行数
- queueの最大長と待機deadline
- 長文batchと対話requestのpool分離
- cancelされたrequestの即時除去
- 過大なpromptを受け付ける前のtoken数検査
streaming接続が切れた後も生成を続ければ、不要なrequestがbatch slotとKV Cacheを占有する。client disconnectとdeadlineをschedulerまで伝播させる必要がある。
監視するmetric
GPU utilizationだけでは、schedulerが良い状態か分からない。GPUが高稼働でも、利用者が長くqueueで待っているかもしれない。TGIのmetricsは、現在のbatch sizeと最大token数、prefill・decode別の推論回数と所要時間などを公開している。
少なくとも次をworkload classとmodel別に測る。
- waiting request数と最古requestのqueue age
- running request数
- batch内request数とtotal token数
- prefillとdecodeの実行時間・割合
- TTFT、ITL、E2E latencyのp50・p95・p99
- input tokens/s、output tokens/s、total tokens/s
- KV Cache使用率とeviction、preemption、OOM
- cancel後も処理したtoken数
- prompt長とoutput長の分布
平均値だけでは、長文requestが重なる時間帯のtail latencyを見落とす。
負荷試験は長さと到着時刻を変える
同じ長さのpromptを一定間隔で送る試験は、Continuous Batchingに有利すぎる。本番に近い次の条件を混ぜる。
- 短い対話と長文要約を同時に送る
- output上限へ到達するrequestと早くEOSになるrequestを混ぜる
- 平常流量の途中にburstを加える
- 長文生成中に短い優先requestを投入する
- clientを途中切断し、slotがすぐ解放されるか確認する
- context上限付近のrequestを重ね、KV Cache不足時の挙動を確認する
- batch設定を変え、tokens/sだけでなくTTFTとITLを比較する
同じGPU、model、量子化、runtime versionで比較しなければ、scheduler設定の効果と別要因を分離できない。
Continuous Batchingは待ち行列の運用である
Continuous Batchingは、完了したrequestの枠を生成途中で再利用し、長さの違うLLM requestを効率よく同時処理する。固定batchよりGPUを遊ばせにくく、total throughputを高められる。
しかし、batchを大きくすれば自動的に良いserviceになるわけではない。新規prefillを入れる頻度、batchのtoken上限、KV Cache、長文requestの公平性がTTFTとITLを変える。tokens/s、待ち時間、memory、cancelを同時に測り、用途ごとのSLOに合わせてschedulerを調整することが重要である。