
第8部 推論を速くする第15章
KV cache / batching / paged attention
最初の一語までは待たされるのに、続きはスラスラ出てくる。その裏にはKVキャッシュという「メモ帳」と、それを大勢で上手に使い回すための仕組みがあります。
このページの内容(10項目)
ChatGPTに長い資料を貼ると、最初の一語まで少し待たされる。でも、そのあとはスラスラ出てくる。なぜでしょう?
LLMの文章生成は、実は性格のまったく違う2つの段階でできています。そしてスラスラ出てくる段階を支えているのが、KVキャッシュ(KV cache)と呼ばれる「メモ帳」です。
このメモ帳は便利な反面、メモリをどんどん食べます。この章では、メモ帳の正体と、それを何十人・何百人のリクエストで上手に分け合うための工夫——バッチング、PagedAttention、プレフィックスキャッシュ——を順に見ていきます。
生成は2つのフェーズでできている
ひとことで言うと
入力をまとめて読むprefill(プリフィル)と、1トークンずつ書くdecode(デコード)。前者は計算が重く、後者はメモリの読み出しが重い、という正反対の性質を持っています。
- prefill: プロンプト全体を一度に読み込み、最初の1トークンを出すまで。数千トークンを並列に処理できるので、GPUの計算能力をフルに使えます。ここにかかる時間が TTFT(Time To First Token、最初の一語までの時間)です。
- decode: 1トークンずつ続きを書く段階。毎回1トークン分の計算しかしないのに、モデルの重み全体をメモリから読み直す必要があるので、計算器はほとんど暇をしています。1トークンごとの間隔は TPOT や ITL(inter-token latency)と呼ばれます。
もう一歩深く: decodeの速さの上限を見積もる
16bitの8Bモデルなら、1トークン書くたびに約16GBの重みを読みます。仮に1秒間に1TBを読み出せるGPUがあっても、1秒に読めるのは約60回分。つまり1人分の生成は毎秒60トークン前後が上限で、計算能力がいくら余っていても、それ以上は出ません。第10章の量子化が生成を速くするのも、第16章の投機的デコーディングが効くのも、この「読み出しの壁」があるからです。
この章の工夫の多くは「decodeの暇な時間をどう埋めるか」と「prefillとdecodeをどう混ぜるか」の2つに集約されます。
KVキャッシュ:毎回読み直さないためのメモ帳
Transformerの注意機構(attention)では、新しいトークンを書くたびに、それまでの全トークンを振り返ります。そのとき各トークンから作られるのがKey(K)とValue(V)というベクトルです。Kは「このトークンは何についての情報か」という見出し、Vは「中身」にあたります。
KVキャッシュは「会議の議事録」です。新しく発言するたびに会議を最初から録音で聞き直していたら日が暮れます。それまでの発言を議事録に書き留めておけば、新しい発言の分だけ書き足して、議事録を見返すだけで済みます。
過去のトークンのKとVは、あとから変わることがありません。だから一度計算したら保存しておき、次のトークンでは新しい1トークン分だけを計算して追記します。これがKVキャッシュで、ほぼすべての推論エンジンが使っています。
flowchart TD A["プロンプト"] --> B["prefill
全トークンのKとVを計算"] B --> C["KVキャッシュに保存"] C --> D["decode
新しい1トークンだけ計算"] D --> E["KとVを追記"] E --> C D --> F["次のトークンを出力"]
KVキャッシュはどれくらいメモリを食う?
KVキャッシュの大きさは、次の掛け算で決まります。
2(KとV)× 層数 × KVヘッド数 × ヘッド次元 × トークン数 × 同時リクエスト数 × 1要素のバイト数
Llama 3.1 8B(32層、KVヘッド8個、ヘッド次元128)を16bitで持つと、1トークンあたり 2 × 32 × 8 × 128 × 2バイト = 128KiB。たいしたことはなさそうですが、トークン数と人数を掛けると一気に膨らみます。
ウィジェットの初期状態(Llama 3.1 8B、16Kトークン、1リクエスト、FP16)では約2GiBと表示されます。文脈のスライダーを128Kまで動かすと約16GiB。8Bモデルの重み(16bitで約16GB)と同じくらいの大きさが、たった1人の会話のために必要になるわけです。
次に、同時リクエスト数を8に増やしてみてください。16Kトークンのままでも約16GiBになります。プリセットを Llama 3.1 70B(80層)に切り替えると、1トークンあたり320KiBになり、128K文脈1人分で約40GiBです。サーバーで「何人を同時に捌けるか」は、重みではなくKVキャッシュで決まることが多いのです。キャッシュの精度を「FP8 / INT8」にすると、どの場合もちょうど半分になります。これが第10章で触れたKVキャッシュの量子化です。
KVを小さくするモデル側の工夫:MQA・GQA・MLA
KVキャッシュを減らすいちばん根本的な方法は、モデルの設計そのものを変えることです。
| 方式 | やり方 | 例 |
|---|---|---|
| MHA(Multi-Head Attention) | 質問役(Query)のヘッドごとにKとVを持つ | 初期のTransformer |
| MQA(Multi-Query Attention) | 全ヘッドでKとVを1組だけ共有 | 2019年の提案 |
| GQA(Grouped-Query Attention) | ヘッドをグループに分け、グループごとに共有 | Llama 3.1(Query 32個に対しKV 8個) |
| MLA(Multi-head Latent Attention) | KとVを小さな潜在ベクトルに圧縮して保存 | DeepSeek-V2 / V3 |
ウィジェットのプリセット「比較用: GQAなし」を選ぶと、同じ形でKVヘッドが32個の場合が計算できます。1トークンあたり512KiBで、GQAの4倍です。GQAの論文は、MQAほど品質を落とさずに、MQAに近い速さが得られると報告しています。
MLAはさらに大胆です。DeepSeek-V2 の論文では、従来の DeepSeek 67B と比べてKVキャッシュを93.3%削減したと述べています。DeepSeek-V3(総パラメータ671B)の設定値(61層、潜在次元512、位置情報用64次元)から計算すると、16bitで1トークンあたり約69KiB。8Bの Llama 3.1 の半分ほどで済む計算です。MoEの仕組みについては第26章で扱います。
バッチング:暇なGPUにまとめて仕事を渡す
decodeのGPUは暇をしている、という話を思い出してください。重みを1回読み出すなら、そのついでに何人分ものトークンを同時に計算したほうがお得です。これがバッチング(batching)です。
素朴なバッチング(static batching)では、何件かのリクエストをまとめて開始し、全員が書き終わるまで次を入れません。すると、短い返事で終わった人の席が空いたまま、長文を書く人を待つことになります。
static batching は「全員そろって出発し、最後の一人が戻るまで次の便を出さないバス」。continuous batching は「降りる人がいたら、次の停留所ですぐ新しい人を乗せる路線バス」です。
continuous batching(連続バッチング、iteration-level scheduling とも)は、1トークン生成するたびに「終わった人を降ろし、待っている人を乗せる」方式です。2022年のOrca(OSDI '22)が提案し、今では vLLM、SGLang、TensorRT-LLM などの標準機能になっています。llama.cpp のサーバーでも既定で有効です。
PagedAttention:メモリを「ページ」で貸し出す
continuous batching で人の出入りが激しくなると、今度はKVキャッシュの置き場所が問題になります。以前のシステムは、リクエストごとに「最大の長さ分」の連続した領域を先に確保していました。実際には短く終わる会話が多いので、大半が使われないまま無駄になります。
vLLMの論文(2023年)は、既存システムではKVキャッシュ用メモリのうち実際のトークンの保存に使われていたのは20.4〜38.2%にすぎないと測定しました。そこで提案されたのがPagedAttentionです。
OSの仮想メモリと同じ発想です。ホテルで「家族旅行かもしれないから」と1人客にスイートを丸ごと押さえるのをやめ、必要になった分だけ小部屋を1つずつ貸す。部屋がとびとびでも、台帳(ブロックテーブル)を見ればどこに誰がいるか分かります。
KVキャッシュを一定のトークン数ごとのブロックに分け、必要になるたびに空いているブロックを割り当てます。ブロックは物理的に連続していなくてよく、注意機構の計算はブロックテーブルを見ながら行います。論文の測定では、vLLMはメモリの96.3%を実際のトークンの保存に使えていました。
ブロック単位なので、同じプロンプトから複数の回答を作るときは共通部分のブロックを共有し、書き換えが必要になった時点でだけコピーする(copy-on-write)こともできます。こうした工夫で、vLLMは同程度の遅延のまま、従来システムより2〜4倍のスループットを出したと報告されています。vLLMそのものは第9章で紹介しています。
プレフィックスキャッシュとchunked prefill
プレフィックスキャッシュ(prefix caching)は、プロンプトの先頭が同じリクエスト同士でKVキャッシュを使い回す仕組みです。長いシステムプロンプト、毎回同じ資料、チャットの過去のやり取りなどは、前回作ったKVキャッシュをそのまま使えばprefillを省けます。vLLMは Automatic Prefix Caching としてブロックのハッシュで一致を探し、SGLang は RadixAttention という木構造で管理します。商用APIの「プロンプトキャッシュ」割引も、考え方は同じです。
chunked prefillは、長いプロンプトのprefillを小さな塊(チャンク)に分け、他の人のdecodeと同じバッチに混ぜて処理する方法です。巨大なprefillが割り込むと、生成中の人の文字の流れが止まってしまいます。分割して少しずつ混ぜれば、計算が重いprefillとメモリが重いdecodeがちょうどよく補い合います。Sarathi-Serve(2024年)がこの方式を提案し、vLLMのV1エンジンでは可能な場合に既定で有効になっています。
ためしてみよう
llama.cpp のサーバーでは -np(--parallel)で同時に受け付けるスロット数を、-ctk / -ctv でKVキャッシュの型(例: q8_0)を指定できます。スロットを増やすとKVキャッシュ用メモリが増えることを、上のウィジェットの「同時リクエスト数」と見比べてみてください。
FlashAttention:注意計算そのものを速く
最後に、計算の中身の話です。注意機構の素直な実装は、トークン数の2乗の大きさの中間結果をGPUの大きなメモリ(HBM)に書いては読み直すため、メモリの往復で時間を食います。FlashAttention(2022年)は、計算を小さなタイルに分けてGPUチップ上の高速なSRAMの中で済ませ、HBMとの往復を減らしました。近似ではなく、結果は通常の注意計算と同じです。
| 版 | 年 | 主な対象と特徴 |
|---|---|---|
| FlashAttention | 2022 | IOを意識したタイル化。メモリ使用量が系列長に対して線形に |
| FlashAttention-2 | 2023 | 仕事の分け方を改善し、初代の約2倍速 |
| FlashAttention-3 | 2024 | Hopper(H100)向け。非同期処理とFP8に対応 |
| FlashAttention-4 | 2026(論文) | Blackwell(B200)など向け。CuTe-DSLで記述 |
FlashAttentionはKVキャッシュの量を減らすものではなく、同じ計算をより速く行う技術です。PagedAttentionやGQAとは役割が違い、組み合わせて使われます。
よくある誤解
モデルが動くだけのGPUメモリがあれば、長い文脈も大人数も大丈夫
重みとは別に、文脈の長さ × 同時リクエスト数に比例してKVキャッシュが必要です。128K文脈では重みより大きくなることもあります
PagedAttentionとFlashAttentionは同じ種類の技術
PagedAttentionはKVキャッシュの置き場所の管理、FlashAttentionは注意計算のカーネルの高速化です
バッチを大きくすれば、一人ひとりの応答も速くなる
大きくなるのは全体のスループット。一人あたりのトークン間隔は、むしろ少し遅くなるのが普通です
確認クイズ
Q1. prefillとdecode、GPUの計算能力をより使い切っているのはどちら?
prefillです。多くのトークンを並列に処理できるからです。decodeは1トークンずつで、重みの読み出しが律速になります。
この章のまとめ
- 生成は計算が重いprefillと、メモリ読み出しが重いdecodeの2段階
- KVキャッシュは過去トークンのKとVを保存する「議事録」。文脈長 × 同時人数に比例して大きくなる
- GQA・MLAはモデル設計で、量子化は精度を下げてKVキャッシュを小さくする
- continuous batching、PagedAttention、prefix caching、chunked prefill がサーバーのスループットを支え、FlashAttentionが注意計算を速くする
さらに深く学ぶ
この章の内容をもう一歩掘り下げるための原典です。
- Efficient Memory Management for Large Language Model Serving with PagedAttentionvLLMの論文。KVキャッシュをOSのページのように小さなブロックで管理し、メモリの無駄をほぼなくしてスループットを大きく上げました。
- FlashAttention: Fast and Memory-Efficient Exact Attention with IO-AwarenessGPUのメモリ階層を意識して計算順序を組み替え、近似なしでアテンションを高速・省メモリにした論文。
- GQA: Training Generalized Multi-Query Transformer Models from Multi-Head CheckpointsKVヘッドをグループで共有するGQAを提案。品質とKVキャッシュ削減のバランスがよく、多くのLLMが採用しています。
- DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language ModelKeyとValueを小さな潜在ベクトルに圧縮して保存するMLAを導入し、KVキャッシュを大幅に減らしたモデルの報告。
出典・参考
- Kwon ほか「Efficient Memory Management for Large Language Model Serving with PagedAttention」(arXiv:2309.06180) — メモリ使用率20.4〜38.2%と96.3%、2〜4倍のスループット
- Yu ほか「Orca: A Distributed Serving System for Transformer-Based Generative Models」(OSDI '22)
- Shazeer「Fast Transformer Decoding: One Write-Head is All You Need」(MQA, arXiv:1911.02150)、Ainslie ほか「GQA」(arXiv:2305.13245)
- 「DeepSeek-V2」(arXiv:2405.04434) — MLAとKVキャッシュ93.3%削減。DeepSeek-V3 config.json(kv_lora_rank 512、qk_rope_head_dim 64、61層)
- Llama 3.1 8B Instruct の config.json(unsloth によるミラー) — 32層、KVヘッド8、hidden 4096 / 32ヘッド
- vLLM ドキュメント「Automatic Prefix Caching」、「Optimization and Tuning」(chunked prefill)(2026年10月時点)
- Agrawal ほか「Sarathi-Serve」(arXiv:2403.02310)、Zheng ほか「SGLang」(RadixAttention, arXiv:2312.07104)
- FlashAttention(arXiv:2205.14135)、FlashAttention-2(arXiv:2307.08691)、FlashAttention-3(arXiv:2407.08608)、FlashAttention-4(arXiv:2603.05451)、Dao-AILab/flash-attention(GitHub)
- llama.cpp server README —
--parallel、--cont-batching(既定で有効)、--cache-type-k