vLLMとは?LLMサーバーを高速化する仕組み

多数のrequestのKV Cacheをblock単位で配置しGPU memoryを効率利用するvLLMとPagedAttentionの全体像

vLLMは、LLM(大規模言語モデル)を効率良く実行し、APIとして提供するためのinference and serving engine(推論・配信エンジン)です。モデルそのものを賢くする技術ではありません。複数のrequestが同時に届く環境で、GPU memory、とくにKV Cacheを無駄なく使い、server全体のthroughput(単位時間当たりの処理量)を高めます。

中心技術のPagedAttentionは、requestごとに増減するKV Cacheを固定長のblockへ分け、物理memory上の空いている場所へ配置します。OSのvirtual memoryに着想を得ていますが、OSのpagingをそのままGPUへ移したものではありません。本記事では、block化が必要な理由をmemory容量の具体計算から追い、continuous batchingやprefix cachingとの違い、導入時の測定方法まで整理します。

多数のrequestのKV Cacheをblock単位で配置しGPU memoryを効率利用するvLLMとPagedAttentionの全体像

3文要約

  1. vLLMはLLM modelではなく、複数requestを効率良く処理する推論・配信engineです。
  2. PagedAttentionはKV Cacheをlogical block(requestから見た論理block)とphysical block(GPU memory上の物理block)に分け、block tableで対応付けることで、大きな連続領域の事前予約とexternal fragmentationを抑えます。
  3. server全体の性能はPagedAttentionだけで決まらず、continuous batching、Attention kernel、promptとoutputの長さ、concurrencyを含めてTTFT、ITL、throughput、GPU memoryを実測する必要があります。

vLLMは「モデル」ではなく「サーバー側の実行基盤」

LLMを使う仕組みは、大きく3層に分けると理解しやすくなります。

主な役割
model 入力tokenから次tokenの確率を計算する Transformer、model weights
inference engine tensor計算、KV Cache、batch、schedulerを管理する vLLM
API/application 認証、rate limit、会話履歴、業務logicを扱う OpenAI互換API、chat application

vLLMは中央のinference engineを中心に、OpenAI互換HTTP serverも提供します。同じmodel weightsを使っても、requestをどの順に実行し、KV Cacheをどのように配置するかで、1台のGPUが単位時間に処理できるtoken数は変わります。

ここで「速い」には少なくとも2つの意味があります。

  • latency: 1つのrequestが応答を受け取るまでの時間
  • throughput: server全体が1秒間に処理できるrequest数やtoken数

PagedAttentionはmodelの演算量を直接減らすというより、KV Cacheのmemory効率を上げ、同時に載せられるrequestやtokenを増やす経路でthroughputへ効きます。1 requestだけを低concurrencyで実行したとき、必ず1 tokenの計算が大幅に短くなるという意味ではありません。

model、inference engine、API applicationの3層とvLLMが担当する範囲

LLM serverで増え続けるKV Cache

LLMの生成は、promptをまとめて処理するprefillと、tokenを1つずつ追加するdecodeに分かれます。decode時に過去tokenのKeyとValueを毎回計算し直さないため、各layerの結果をKV Cacheへ保存します。詳しい役割はKV Cacheとは?LLMの生成を高速化する仕組みで解説しました。

model weightsはrequest間で共有できます。一方、KV Cacheは原則としてrequestごとに異なり、sequence lengthとともに増えます。

たとえば次のrequestが同時に走る状況を考えます。

request prompt長 現在のoutput長 今後のoutput長
A 1,000 20 不明
B 80 150 不明
C 4,000 2 不明

requestごとに現在長も終了時刻も違います。生成はEOS tokenで早く終わることもあれば、設定した最大token数まで続くこともあります。つまりKV Cacheは「大きい」だけでなく、「requestごとに予測しにくい速度で伸び、別々の時刻に解放される」memoryです。

連続領域で管理すると何が無駄になるのか

単純な方法は、requestごとにKV Cache用の連続した領域を確保することです。しかし、可変長requestでは3種類の無駄が生じます。

将来token用の予約

最大sequence lengthまで先に確保すれば、途中で領域が足りなくなる心配は減ります。しかし、実際に100 tokenで終了するrequestへ2,048 token分を予約すると、大半は使われません。その予約中は他のrequestが利用できないため、batchへ載せられるrequest数が減ります。

internal fragmentation

allocatorの単位がrequestの実使用量より大きいと、確保した領域の内側に未使用部分が残ります。これをinternal fragmentation(確保領域内部の断片化)と呼びます。

external fragmentation

requestの終了と伸長を繰り返すと、空き領域が複数箇所へ分かれます。合計容量が足りていても、新しいrequestが必要とする大きな連続領域を置けない状態がexternal fragmentation(空き領域間の断片化)です。

最大長の連続予約で未使用領域と断片化が生じる方式と、必要なblockだけ割り当てる方式の比較

PagedAttentionはKV Cacheをblockで管理する

PagedAttentionの原論文は、OSのvirtual memoryとpagingから着想を得て、KV Cacheを固定数tokenごとのblockへ分けました。重要なのは、requestから見える順序と、GPU memory上の配置を分離することです。

  • logical block: requestのtoken順に並ぶblock
  • physical block: GPU memory上に確保された固定長block
  • block table: logical blockがどのphysical blockにあるかを示す対応表

たとえばrequest Aのlogical block A0, A1, A2を、physical block 7, 1, 9へ配置できます。GPU memory上では非連続ですが、block tableはA0→7A1→1A2→9という順序を保持します。Attention kernelはこの表を参照して、必要なK/Vを読みます。

request BのB0, B1はphysical block 3, 8へ置けます。physical block 24が離れて空いていても、空きを1つの連続領域へまとめる必要はありません。固定長blockとして個別に利用できます。

requestのlogical blockがblock tableを介して非連続なphysical blockへ対応する例

これはOS pagingと同じ発想を利用した比喩ですが、同一の実装ではありません。対象はLLM servingのKV Cacheであり、vLLM自身のblock managerとAttention実装が対応関係を扱います。「SSDへ追い出すから速い」「OSがGPU memoryを自動管理する」という説明ではありません。

KV Cacheの容量をtokenから計算する

block化の効果を理解するには、まず1 tokenのKV Cacheがどれくらい大きいかを計算します。layer数を\(L\)、KV head数を\(H_{kv}\)、1 headの次元を\(d_h\)、1要素のbyte数を\(b\)とします。KとVの2つを保存するため、raw KV payload(K/V tensor本体)のtoken当たり容量は次です。

\[m_{token}=2LH_{kv}d_hb\]

block sizeを\(B\) tokenとすると、1 blockのraw payloadは次になります。

\[m_{block}=Bm_{token}\]

現在のsequence lengthが\(N\) tokenなら、必要block数\(n_{blocks}\)、割当payload \(M_{alloc}\)、末尾blockの未使用payload \(M_{waste}\)は次です。

\[n_{blocks}=\left\lceil\frac{N}{B}\right\rceil\]
\[M_{alloc}=\left\lceil\frac{N}{B}\right\rceil Bm_{token}\]
\[M_{waste}=\left(\left\lceil\frac{N}{B}\right\rceil B-N\right)m_{token}\]

100 tokenを保存する具体例

説明用に、\(L=32\)、\(H_{kv}=8\)、\(d_h=128\)、BF16の\(b=2\) byteとします。

\[m_{token}=2\times32\times8\times128\times2 =131{,}072\ \text{byte}=128\ \text{KiB/token}\]

\(B=16\) tokenなら、1 blockは\(16\times128=2{,}048\) KiB、つまり2 MiBです。\(N=100\) tokenには\(lceil100/16\rceil=7\) blockが必要です。

項目 計算 結果
実token分 \(100\times128\) KiB 200 MiB
割当capacity \(7\times16=112\) token 224 MiB
末尾の未使用 \((112-100)\times128\) KiB 24 MiB

block化しても、最後のblockには最大で\(B-1\) token分のinternal fragmentationが残ります。それでもrequest最大長まで予約する方法に比べ、未使用量をblock単位へ制限できます。

32 layer、8 KV head、BF16の例で1 tokenから1 block、100 tokenの割当容量まで計算する図

この224 MiBはK/V tensorのraw payloadだけです。model weights、allocator metadata、alignment、workspace、CUDA graph、temporary tensorなどは含みません。また、KV head数はmodelにより異なります。GQA/MQAとは?KV Cacheを減らすAttention改良で扱ったように、\(H_{kv}\)を減らしたmodelならtoken当たり容量も減ります。

blockはrequestの成長に合わせて割り当てる

block pool(再利用可能なphysical blockの集合)を使うrequest lifecycleは、概念的に次の流れです。

  1. prefillするtoken数に応じて必要blockをpoolから割り当てる。
  2. decodeで現在blockが埋まったら、次のfree blockを追加する。
  3. requestが終了したら、そのrequestだけが使うblockをpoolへ戻す。
  4. 後続requestが解放済みblockを再利用する。

大きな連続領域を探すのではなく、必要な数の固定長blockを確保すればよいため、空きmemoryを使いやすくなります。

prefillでblockを確保しdecodeで追加しrequest終了時にpoolへ戻すlifecycle

ただし、physical blockが足りなくなれば何も起きないわけではありません。schedulerは実行するsequenceを減らしたり、後で再開するためのpreemption(実行の一時退避・再計算)を選んだりします。preemptionが頻発すれば追加costが増えるため、memory効率が高いこととmemory無制限は別です。

continuous batchingは別の問題を解く

PagedAttentionと一緒に語られるcontinuous batching(継続的batching)は、memory配置ではなくschedulingの技術です。

static batchでは、同じbatchに入ったrequestの一部が早く終了しても、長いrequestが終わるまで次のrequestを入れにくい構成があります。生成長がばらつくほど、終了済みの枠が遊びます。

continuous batchingでは、decode iterationごとに実行対象を見直します。終了したrequestを外し、budgetとmemoryに収まる新しいrequestを入れます。PagedAttentionがblockを素早く割り当て・解放できるため、schedulerは可変長requestを詰め替えやすくなります。

技術 管理対象 主な役割
PagedAttention KV Cache memory 非連続なphysical blockへ効率良く配置する
continuous batching 実行sequence iterationごとに終了requestと新requestを入れ替える
Attention kernel tensor計算とmemory access 実際のAttention計算を効率化する

static batchでは終了済みの枠が待つ一方、continuous batchingではiterationごとに新requestを入れる比較

FlashAttentionとは?Attentionを高速化する技術はkernel内部のHBM accessを減らす技術です。PagedAttentionはrequest間のKV Cache配置、continuous batchingは実行枠の管理を扱います。どれか1つが他を置き換えるのではなく、異なるbottleneckへ作用します。

block共有とAutomatic Prefix Caching

physical blockにはreference count(何個のsequenceが参照しているか)を持たせ、同じ内容を複数sequenceで共有できます。共有blockの内容を書き換える必要が生じたときだけcopy-on-writeで複製すれば、parallel samplingや共通prefixでK/Vを重複保持せずに済みます。

vLLMのAutomatic Prefix Caching(APC)は、同じprefixに対応するKV Cache blockを再利用します。長いsystem prompt、同じdocumentへの複数質問、共通の会話履歴などでは、同じprefixのprefillを繰り返さずに済みます。

ただしAPCは、新しいoutput tokenをdecodeする計算を省きません。

workload APCの期待効果
長い共通promptを何度も使う prefill再計算を減らしやすい
prefixがrequestごとに異なる cache hitが少なく効果は限定的
promptは短くoutputが長い 全体時間に占めるdecodeが大きく、効果は限定的

PagedAttentionはKV Cacheをblockとして管理する土台、APCは同一内容のblockを再利用する追加機能です。この2つを同義にしないことが重要です。

vLLM全体のthroughputは複数要因で決まる

vLLM公式サイトは、PagedAttention、continuous batching、最適化されたkernel、OpenAI互換APIなどを主要機能として挙げています。server全体の性能は、それらの組合せで決まります。

PagedAttention、continuous batching、prefix caching、Attention kernelが異なるbottleneckを担当する地図

原論文は、評価した条件でFasterTransformerやOrcaに比べ、同程度のlatencyで2〜4倍のthroughputを報告しました。この数値はPagedAttentionとvLLMの設計が有効だった実例ですが、2023年当時の対象model、hardware、workload、baselineによる結果です。現在の任意のmodelやGPUで2〜4倍になる保証ではありません。

とくに次の条件で結果は変わります。

  • prompt長とoutput長の分布
  • 同時request数と到着pattern
  • model size、KV head数、dtype
  • GPUの種類、枚数、memory容量
  • sampling設定と最大sequence length
  • prefix共有率
  • baseline engineとsoftware version

OpenAI互換serverとして試す最小例

現行のvLLM Quickstartでは、vllm serveでmodelを読み込み、OpenAI互換serverを起動できます。次はQwen2.5 1.5B Instructを使う例です。

vllm serve Qwen/Qwen2.5-1.5B-Instruct \
  --generation-config vllm \
  --api-key local-dev-key

別terminalからChat Completions endpointへrequestします。

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-dev-key" \
  -d '{
    "model": "Qwen/Qwen2.5-1.5B-Instruct",
    "messages": [{"role": "user", "content": "KV Cacheを一文で説明して"}],
    "temperature": 0
  }'

--generation-config vllmは、model repositoryのgeneration_config.jsonがserverのsampling defaultを上書きする影響を避け、比較条件を明示しやすくするために付けています。用途によってはmodel側の推奨設定を使う判断もあります。

commandや対応hardwareはversionで変わります。導入時は公式Quickstartを確認してください。また、--api-keyだけでinternet公開に必要なsecurityが完結するわけではありません。外部公開ではTLS、network access control、認証・認可、rate limit、logの機密情報対策を別途設計します。

調整parameterはtrade-offで選ぶ

vLLMにはKV Cacheやschedulerを調整するoptionがあります。名称や対応値はversion依存ですが、判断軸は次のように整理できます。

調整軸 増やす・変える利点 主なcost・注意点
block size 管理するblock数を減らせる場合がある 末尾の未使用slotが増え得る
KV Cache memory budget 多くのtokenを保持しやすい weightsやworkspaceの余裕が減る
max batched tokens 1 iterationの処理量を増やせる TTFTやmemory pressureへ影響し得る
max sequences concurrencyを増やせる request当たりlatencyやpreemptionへ影響し得る
cache dtype 低bit化で容量を減らせる場合がある accuracy、hardware対応、変換costを検証する
prefix caching 共通prefixのprefillを再利用できる hash管理のoverheadがあり、cache hitが必要

「GPU memory utilizationを上限近くへ設定すれば最速」とは限りません。runtimeが必要とする一時領域やtrafficの揺れを含め、OOMを起こさないmarginが必要です。公式engine argumentsと使用versionの仕様を確認し、小さく変更して測ります。

導入前後で測る項目

benchmarkでは、baselineとvLLMで同じmodel、dtype、prompt集合、sampling、最大output長を使います。平均値だけでなく、p50、p95、p99のtail latencyも確認します。

分類 指標 分かること
初動 TTFT(Time To First Token) requestから最初のtokenまでの待ち時間
生成 ITL/TPOT token間の待ち時間、decodeの体感速度
全体 end-to-end latency request完了までの時間
処理量 output tokens/s、requests/s server全体のcapacity
memory GPU使用量、KV cache utilization block budgetと余裕
scheduler queue time、preemption 混雑とmemory不足の影響
品質・安定性 error率、出力条件の一致 OOMや設定差による比較崩れ
APC prefix cache hit、prefill削減 共通prefix再利用の寄与

TTFT、ITL、throughput、GPU memory、preemption、error率を同じworkloadで測るbenchmark dashboard

trafficは一定間隔だけでなく、実運用に近いburstも用意します。短いchatだけでなく、長いprompt、長いoutput、長短混在も分けます。throughputを上げた結果、TTFTやp99 latencyが許容範囲を超えていないかを同時に見ます。

vLLMが向くケース・別の構成も検討するケース

vLLMは、同一modelへ複数requestが届き、長さがばらつくonline servingで特に検討価値があります。OpenAI互換APIを使って既存clientから接続したい場合にも便利です。

一方、次のケースでは効果と運用costを比較します。

  • 1 requestだけを低頻度で実行し、throughputより起動時間を重視する
  • edge deviceや対応外hardwareで動かす
  • 独自kernelや特殊なmodel architectureへの対応が必要
  • server管理よりmanaged APIの運用簡素化を優先する
  • strictなsingle-request latencyだけが目標で、batchingの待ちを許容しない

選択肢は「vLLMなら常に正解」ではありません。対象modelが現行versionで対応しているか、必要precisionとhardwareで安定するか、実trafficでSLO(service level objective、応答性能の目標)を満たすかを確認します。

第17〜21回の高速化技術との違い

フェーズ3で扱った技術は、すべてLLM推論を速く・軽くするものですが、削るcostが異なります。

技術 主に変える場所 主な効果 vLLMとの関係
KV Cache decodeのstate 過去K/Vの再計算を省く PagedAttentionが効率良く管理する対象
量子化 weight・activation・cacheのbit幅 memoryと計算costを減らす 対応backend・精度を確認して併用する
FlashAttention Attention kernel HBM accessを減らす kernel側の最適化として補完する
GQA/MQA model architecture KV headとcache容量を減らす 1 token当たりblock容量を小さくする
Speculative Decoding decode algorithm target call当たり複数tokenを確定する 対応方式ではserving engine上で併用できる
PagedAttention serving runtime KV Cacheの割当無駄を減らす vLLMのmemory管理の中核

速度最適化は足し算ではありません。たとえばGQAでKV Cacheを小さくし、PagedAttentionで効率良く配置しても、compute-bound(演算律速)ならmemory削減がそのままlatency短縮にはなりません。bottleneckを測ってから組み合わせます。

よくある質問

PagedAttentionはmodel weightsもpageに分けますか?

本記事で扱うPagedAttentionの中心は、requestごとに変化するKV Cacheのblock管理です。model weightsのoffloadやstorage pagingとは分けて考えてください。

block化すればmemory wasteは完全にゼロですか?

いいえ。末尾blockの未使用slot、metadata、alignment、runtimeの一時memoryなどは残ります。大きな連続領域の事前予約とexternal fragmentationを抑え、無駄の範囲を小さなblock単位へ限定しやすくする設計です。

PagedAttentionとFlashAttentionは同じですか?

違います。PagedAttentionは複数requestのKV Cacheをどう配置して参照するかを扱います。FlashAttentionはAttention計算中のmemory accessを工夫し、中間行列をHBMへ何度も読み書きするcostを減らします。

vLLMを使えば1 requestも必ず速くなりますか?

保証できません。vLLMの大きな利点は、memory効率とschedulingによりserver throughputを高めやすいことです。single-request latencyはmodel、prompt/output長、kernel、hardware、設定で変わるため実測します。

prefix cachingを有効にすればdecodeも速くなりますか?

共有prefixのprefill再計算を省きますが、新しく生成するoutput tokenのdecode計算は省きません。長い共通prefixが繰り返されるworkloadほど効果を期待できます。

まとめ

vLLMは、LLMをAPIとして高効率に提供するinference and serving engineです。PagedAttentionは、動的に伸びるKV Cacheをlogical blockとphysical blockへ分け、block tableで結びます。これにより、requestごとの最大長を大きな連続領域として予約せず、離れたfree blockも利用できます。

ただし、block化だけがperformanceを決めるわけではありません。continuous batchingは実行枠をiterationごとに入れ替え、prefix cachingは共有promptのprefillを再利用し、kernelはAttention計算を効率化します。それぞれが別のbottleneckを担当します。

導入判断ではpaperの倍率をそのまま期待せず、自分のprompt/output分布とconcurrencyを再現してください。TTFT、ITL、throughput、GPU memory、preemption、error率を同時に測り、必要なlatencyとcapacityを満たす設定を選ぶことが重要です。

参考資料

コメント

タイトルとURLをコピーしました