Tech カテゴリのイメージ

KV Cache量子化とは?FP8でLLMの同時実行数を増やす前に測ること

LLMのmodel weightがGPUへ収まっても、長いcontextと同時requestが増えるとKV Cacheが先に限界へ達することがある。KV Cache量子化は、保存するKeyとValueのprecisionを下げ、同じGPU memoryへより多くのtokenを保持する方法である。

たとえばFP16やBF16のKVをFP8へ変えれば、要素当たりの保存量は16 bitから8 bitになる。理論上のKV領域は約半分になるが、同時実行数や速度が必ず2倍になるわけではない。scale、metadata、kernel、modelの感度、request長の分布まで含めて測る必要がある。

weight量子化とは別の場所を小さくする

weight量子化はmodel parameterをINT8、INT4、FP8などへ変換する。KV Cache量子化は、requestのPrefillとDecodeで生成されるKeyとValueを低precisionで保存する。

weightはmodel読込後にほぼ一定だが、KV Cacheは次の条件で増減する。

  • layer数
  • KV head数とhead dimension
  • requestごとの保持token数
  • 同時に実行するsequence数
  • KeyとValueの二つ
  • Cacheのdata type

概算は次の形で考えられる。

[ M_{KV} \approx 2 \times L \times H_{KV} \times D \times T \times B \times S ]

Lはlayer数、H_KVはKV head数、Dはhead dimension、Tは保持token数、Bは同時sequence数、Sは1要素のbyte数である。先頭の2はKeyとValueを表す。実際にはblock単位の余り、alignment、scaleなどのmetadataが加わる。

Grouped Query AttentionやMulti Query AttentionではKV head数が少ないため、同じcontext長でもCache量が小さい。model名とparameter数だけで削減効果を推測しない。

FP8は値とscaleを組み合わせる

低precisionへ変換すると表現できる値の範囲と細かさが減る。そのため元の値をscaleで調整して量子化し、Attention計算時にそのscaleを使う。

vLLMのQuantized KV Cacheは、FP8についてper-tensorとper-attention-headのscale方式を説明している。

  • per-tensorはQ、K、Vごとに一つのscaleを使う
  • per-attention-headはheadごとにscaleを持つ

粒度を細かくすれば分布の違いへ合わせやすいが、scaleの保存量と処理が増える。KIVI論文はKV分布を分析し、Keyはchannel単位、Valueはtoken単位で量子化する非対称な2 bit方式を提案した。KeyとValueを同じ粒度で扱うことが常に最適とは限らない。

default scaleのまま本番へ出さない

量子化scaleの決め方には、固定値、実行時のdynamic計算、calibration datasetからの事前推定などがある。

vLLMではFP8 KV Cacheをscale 1.0で始める方法もあるが、公式資料はdatasetを使ったcalibrationを推奨している。scaleが実際の分布へ合わなければ、値が表現範囲を超えてclippingされるか、使えるbitを十分に活用できない。

calibration dataは一般的な会話だけで選ばない。本番にある長文、コード、日本語、表、RAGの引用、tool結果、system promptを含める。最大値だけを追うと少数のoutlierがscaleを広げ、通常値の解像度が粗くなる場合もある。

model、revision、KV dtype、scale、attention backendを一組のartifactとして管理する。modelだけ更新して古いscaleを流用しない。

memory削減と高速化は同じではない

KV CacheをFP8へすれば、同じmemory予算で保持できるtoken数は増える。これによりwaiting requestをrunningへ移し、batchを大きくできればthroughputが上がる可能性がある。

一方、1 requestだけを処理する場合は速くならないこともある。保存時のquantize、読込時のdequantize、scaleの取得に処理が必要だからである。低precisionのままAttentionを実行できる対応kernelならdata movementを減らせるが、毎stepで高precisionへ戻す実装では変換costが残る。

確認するのは「FP8を選べるか」だけではない。

  • 使用するGPUとattention backendがその形式を高速に処理できるか
  • quantizeとdequantizeが融合されたkernelか
  • Prefix CachingやPagedAttentionと同じblock layoutを使えるか
  • CUDA graphやcompile済みshapeが無効にならないか
  • tensor parallel間でscaleとlayoutが一致するか

memory使用率が下がっても、token間latencyが悪化すれば対話用途では逆効果になりうる。

品質はperplexityだけで判断しない

KV Cache量子化の誤差は、各Decode stepでAttentionが参照する過去情報へ入る。短い回答の平均accuracyが維持されても、長いcontextの特定位置を取り出す能力が落ちる可能性がある。

少なくとも次を比較する。

  1. 短文QAと通常会話
  2. 長文内の一箇所を探すretrieval
  3. 複数資料をまたぐ質問
  4. 長いコードの依存関係
  5. JSONやtool callの形式遵守
  6. RAGで引用した数値と固有名詞
  7. 長い生成での反復、脱線、途中の条件忘れ
  8. safetyと拒否挙動

評価は同じprompt、sampling parameter、seedを使い、native dtypeを基準にする。perplexity、task score、長文retrieval、形式違反率、人手評価を分ける。平均値だけでなくcontext長ごとの劣化を見る。

すべてのlayerを量子化しない選択肢

layerやattention typeによって誤差への感度は異なる。vLLMには特定layerやsliding-window attentionをKV Cache量子化から除外する設定がある。

品質問題が出たとき、すぐ全体をnative dtypeへ戻す必要はない。感度の高いlayerだけ高precisionに残すmixed構成を試せる。ただし、異なるpage sizeやlayoutが混ざるとcapacity計算とkernel選択が複雑になる。

除外layerを増やすたびに、実際の保存token数を測り直す。設定上FP8でも一部がBF16なら、単純な半減にはならない。

Prefix CacheとOffloadingへの影響

量子化はPrefix CachingやKV Cache Offloadingと併用できるが、Cache互換性を明示する必要がある。

同じtoken列でも、model revision、scale、quantization granularity、KV dtype、layoutが違えば同じblockとして再利用しない。rolling deploymentではnamespaceを分ける。

offloadでは転送byte数が減るため、CPUやSSDからGPUへ戻す時間も短くなる可能性がある。一方、保存先とGPU側で形式が違えば変換が必要になる。どの階層でquantizeし、どの階層でdequantizeするかを一つに決める。

監視するmetric

  • KV Cacheの総byte数と保持可能token数
  • running、waiting request数と実効concurrency
  • block使用率、eviction、preemption、recompute
  • Prefix Cacheのhit token数
  • quantize、dequantize、Attentionの時間
  • TTFT、ITL、E2E latency
  • input・output token throughput
  • context長別のtask scoreと形式違反率
  • clippingやscale更新を観測できる場合はその頻度
  • layer除外後の実効compression ratio

throughputの改善だけを見ず、同じ品質水準とlatency目標を満たすrequest数で比較する。

本番投入前の比較

native dtypeと量子化構成を同じtrafficで比較する。

  1. 短文中心、長文中心、混合の三つを用意する
  2. concurrencyを段階的に上げ、waitingが始まる点を測る
  3. Prefix Cacheをcoldとwarmに分ける
  4. context上限付近でretrieval品質を測る
  5. 同じGPU memory上限で最大安定concurrencyを比較する
  6. PagedAttentionのblock余りとpreemptionを確認する
  7. Offloading併用時の転送量とTTFTを測る
  8. model更新時にscale不一致を検知できるか確認する
  9. unsupported kernelへfallbackした場合の速度を測る
  10. canaryで品質指標とtail latencyを監視する

量子化後にOOMが減っても、長文回答の正確さやp99 ITLが悪化するなら、bit幅、scale粒度、除外layerを調整する。

KV Cache量子化はcapacityと品質の交換である

KV Cache量子化は、同じGPUへ多くのtokenを保持し、長いcontextや大きいbatchを扱う余地を作る。FP16やBF16からFP8への変更なら保存要素は半分になるが、system全体のmemory、throughput、latencyは単純に2倍改善しない。

重要なのは、本番dataに合うscaleを校正し、対応kernelを確認し、長文retrievalと形式遵守まで評価することである。weight量子化とは別のartifactとしてversion管理し、native dtypeへ戻せるcanary運用から始める。

参考資料