Tech カテゴリのイメージ

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を組む方法として説明している。

流れは次のようになる。

  1. A、B、Cのprefillを処理する
  2. A、B、Cの次tokenを生成する
  3. Aが停止条件へ到達したらbatchから外す
  4. 空いたmemoryとslotへ待機中のDを入れる
  5. 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 Batchingmodel実行前に到着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に有利すぎる。本番に近い次の条件を混ぜる。

  1. 短い対話と長文要約を同時に送る
  2. output上限へ到達するrequestと早くEOSになるrequestを混ぜる
  3. 平常流量の途中にburstを加える
  4. 長文生成中に短い優先requestを投入する
  5. clientを途中切断し、slotがすぐ解放されるか確認する
  6. context上限付近のrequestを重ね、KV Cache不足時の挙動を確認する
  7. 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を調整することが重要である。

参考資料