KV Cache
KV Cacheは生成済みトークンの中間結果を保持する領域で、同時実行数とコンテキスト長に正比例して増えます。大規模構成では重みより大きくなることも珍しくなく、「同時に何人さばけるか」を実質的に決めるのはこの領域です。
30秒で分かる
- KV Cacheは、生成済みトークンの中間結果(KeyとValue)を保存しておく領域です
- これがないと、1トークン生成するたびに文章全体を計算し直すことになります
- サイズは 同時実行数 × コンテキスト長 に正比例して増えます
- 大規模な構成では、重みよりKV Cacheのほうが大きくなります
- GQA・PagedAttention・KV量子化が、この消費を抑える3つの手段です
仕組み
なぜ必要か
Transformerは、次のトークンを予測するとき、それまでのすべてのトークンを参照します。素朴に実装すると、1トークン生成するたびに全トークンの計算をやり直すことになります。
キャッシュなし
1トークン目を生成:1トークン分を計算
2トークン目を生成:2トークン分を計算 ← 1回目の計算をやり直している
3トークン目を生成:3トークン分を計算
...
キャッシュあり
1トークン目:1トークン分を計算 → KとVを保存
2トークン目:新しい1トークン分だけ計算 → 保存済みのKVを再利用
3トークン目:同上
計算量が劇的に減る代わりに、保存したKVがメモリを占有し続けます。これがKV Cacheです。
計算式
KV Cache / token = 2 × layers × kv_heads × head_dim × dtype_bytes
↑
Key と Value の2本
| 項目 | 取得元 |
|---|---|
| layers | config.json の num_hidden_layers |
| kv_heads | config.json の num_key_value_heads |
| head_dim | hidden_size ÷ num_attention_heads |
| dtype_bytes | 推論エンジンのKVキャッシュ設定(FP16なら2) |
これに、リクエストごとのトークン数と同時実行数を掛けます。
合計 = (KV Cache / token) × コンテキスト長 × 同時実行数
KV Cacheは「消えない」
生成中のリクエストが保持するKV Cacheは、そのリクエストが終わるまで解放されません。長い回答を生成している利用者が10人いれば、その10人分が同時に居座り続けます。
平均ではなくピーク時の同時実行数で見積もる必要があるのは、このためです。
GQAでKVヘッドを減らす
Attentionヘッドすべてに個別のKVを持たせると、KV Cacheは膨大になります。そこで、複数のAttentionヘッドでKVを共有する方式が広く使われています。
| 方式 | KVヘッド数 | KV Cache量 | 品質への影響 |
|---|---|---|---|
| MHA | Attentionヘッドと同数 | 最大 | 基準 |
| GQA | グループ単位で共有 | 中程度 | ほぼ影響なし |
| MQA | 1つを全ヘッドで共有 | 最小 | わずかに劣化 |
num_attention_heads が64で num_key_value_heads が8なら、KV CacheはMHAの8分の1です。同じパラメータ数のモデルでも、この値次第で収容できる同時実行数が桁違いになります。
PagedAttention
従来の実装は、リクエストごとに「最大コンテキスト長」分のメモリを連続領域で先に確保していました。実際には最大長まで使わないリクエストがほとんどのため、大量の無駄が生じます。
素朴な確保
[████░░░░░░░░░░░░] ← 確保したが使わない領域が無駄
PagedAttention
[████][████][████] ← 小さなブロック単位で必要な分だけ割り当て
OSの仮想メモリと同じ考え方で、KV Cacheを固定サイズのブロックに分割して管理します。断片化が減り、同じメモリでより多くのリクエストを収容できます。実装の詳細は推論エンジン側の話であり、Level 3で扱います。
Prefix Caching
複数のリクエストが同じ先頭部分(システムプロンプトなど)を共有する場合、その分のKV Cacheを使い回せます。
RAGでは「同じ文書を参照する質問が続く」ことがあるため、条件次第で効果があります。ただしプロンプトの先頭が完全に一致する必要があるため、リクエストごとに冒頭へ日時やユーザー名を差し込む設計にすると、まったく効かなくなります。
設計に効く数字と落とし穴
計算例
layers 64・kv_heads 8・head_dim 128・FP16の場合:
1トークン = 2 × 64 × 8 × 128 × 2 = 262,144 bytes ≈ 262 KB
4Kコンテキスト ≈ 1.07 GB / リクエスト
16並列 ≈ 17.2 GB
50並列 ≈ 53.7 GB ← 30B FP8の重み(30GB)より大きい
16Kで50並列 ≈ 215 GB ← 単体GPUでは収まらない
実際に数字を動かして確認するには、Level 2のサイジング計算機を使ってください。
消費を抑える3つの手段
| 手段 | 効果 | 代償 |
|---|---|---|
| GQA採用モデルを選ぶ | 数分の1〜8分の1 | モデル選定の制約になる |
| KV Cacheを量子化する | FP16→FP8で半減 | 長文で品質劣化が出ることがある |
| コンテキスト長を制限する | 線形に削減 | RAGで詰められる文書量が減る |
重みの量子化ではKV Cacheは減らない
よくある誤解です。重みをINT4に落としても、KV Cacheのサイズは1バイトも変わりません。両者は独立した設定です。
同時実行数で詰まっている場合、重みの量子化はほとんど効きません。KV側(GQA・KV量子化・コンテキスト制限)に手を入れる必要があります。
落とし穴
- 平均で見積もらない:ピーク同時実行数で計算します
- 出力長も含める:コンテキスト長は入力+出力です。長い回答を許すほどKV Cacheが伸びます
- RAG導入で前提が変わる:検索結果をプロンプトへ詰めるため、入力長が数倍になります。RAG導入前のサイジングは、導入後には通用しません
- 推論エンジンの事前確保設定に注意:起動時にメモリの大半を先に確保する設定が既定になっていることがあり、実測時の「使用量」が実需要を反映しない場合があります