はじめに
前回の記事 では、--kv-cache-dtype fp8 を有効にしても、decode 速度・TTFT・出力品質のいずれも BF16 と区別がつかないことを確認しました。「壊れない」ことが分かった。
ではその上で、FP8 KV cache の積極的な恩恵は何か。一言でいえば、
同じ VRAM 予算で、より長い文章を扱えるようになる
です。FP8 にすると K/V が 1 トークンあたり 2 bytes → 1 bytes になり、同じ VRAM 予算で KV プールが約 2 倍に伸びます。これが「コンテキスト長の上限」「同時収容できるリクエスト数」「起動できる設定の境界」を直接押し上げます。
今回は RTX 4070 12GB + Qwen3.5-4B FP8 の上で、max_model_len を 2K → 8K → 32K → 64K → 96K と段階的に伸ばして、
- BF16 KV と FP8 KV それぞれで どこまで起動できるか
- 長文プロンプト (8K / 32K tokens) を どの構成が受理するか
- 出力品質は構成を問わず 遜色ないか (32K BF16 / 32K FP8 / 96K FP8 を並べて確認)
を実測しました。
先に結論を書きます。
| max_model_len | BF16 KV | FP8 KV |
|---|---|---|
| 2,048 〜 65,536 | 起動 OK | 起動 OK |
| 98,304 (96K) | 起動失敗 | 起動成功 (177K tokens プール) |
64K まではどちらも起動でき、出力品質も BF16 / FP8 で違いが見えません。96K の起動可否が FP8 / BF16 の分かれ目で、ここに「同じ GPU でより長い文脈を扱う」FP8 KV の本領が出ます。逆方向に読めば、「同じ文脈長を BF16 比で半分の KV メモリで扱える」ので、VRAM 節約 → 並列収容増 の文脈でも使える設定です。
なぜ FP8 KV で KV プールが伸びるのか
復習を兼ねて、KV cache のサイズ式と vLLM の起動可否ロジックを整理しておきます。
KV cache のメモリは概ね次の式です。
KV cache (bytes) ≈ batch × seq_len × num_layers × num_kv_heads × head_dim × 2 × dtype_bytes
^^^^^^^^^^^^^^^^
K と V dtype に依存
dtype_bytes は BF16 で 2、FP8 で 1。1 トークンあたりの KV メモリが半分になります。
ただし VRAM を消費しているのは KV cache だけではありません。Qwen3.5-4B の場合は内訳に次のような固定コストが入ります。
- モデル重み (FP8) ~4 GiB
- Vision Encoder ~3 GiB
- Mamba SSM state ~0.9 GiB (
max_model_lenに無関係に固定) - activation buffer 他
これらを引いた「残り VRAM」が KV ブロックに割り当てられます。なので
-
max_model_lenが小さい (2K) ときの FP8/BF16 比は 1.40x 程度: 固定コストが支配的で、KV 部分の節約効果が薄い -
max_model_lenを伸ばすほど比率は 2x に漸近する: KV ブロックの占有比率が上がり、FP8 化の効果が顕在化する
これが「max_model_len を伸ばすほど FP8 KV のメリットが大きくなる」根拠です。
そしてもう一つ重要なのが、vLLM の起動可否判定です。vLLM はサーバ起動時に「宣言した max_model_len が、確保できた KV プールに収まるか」を検証します。たとえば max_model_len=98304 を指定したのに、実際に確保できた KV プールが 89,600 トークンしかなければ、
ValueError: The model's max seq len (98304) is larger than the maximum number
of tokens that can be stored in KV cache (89,600). Try increasing
`gpu_memory_utilization` or decreasing `max_model_len` when initializing
the engine.
として 起動を拒否されます。「max_model_len 宣言値 ≤ KV プール容量」が起動可能の必要条件です。
FP8 KV で KV プールが 2 倍になるということは、この条件を満たせる max_model_len の上限も約 2 倍になる、ということです。
実験設計
ハードウェアと共通条件は 前回 と同じです。
| 項目 | 内容 |
|---|---|
| GPU | RTX 4070 12 GB |
| モデル |
RedHatAI/Qwen3.5-4B-FP8-dynamic (重みは FP8 化済み) |
| vLLM | nightly v0.21.1rc1.dev243
|
| attention backend |
flashinfer (FP8 KV を使うため必須) |
| 共通フラグ |
--enforce-eager, --max-num-batched-tokens 4096, concurrency = 1 |
gpu_memory_utilization |
0.937 (RTX 4070 12GB) |
構成マトリクス: max_model_len を 5 段階に振る
| 構成 ID | kv_cache_dtype | max_model_len |
|---|---|---|
| base_fi_2k | auto (BF16) | 2,048 |
| base_fi_8k | auto (BF16) | 8,192 |
| base_fi_32k | auto (BF16) | 32,768 |
| base_fi_64k | auto (BF16) | 65,536 |
| base_fi_96k | auto (BF16) | 98,304 |
| fp8kv_2k | fp8 | 2,048 |
| fp8kv_8k | fp8 | 8,192 |
| fp8kv_32k | fp8 | 32,768 |
| fp8kv_64k | fp8 | 65,536 |
| fp8kv_96k | fp8 | 98,304 |
ワークロード
長文 prefill を段階的に重くします。
| Workload | 入力 tokens | 出力 max_tokens | 性格 |
|---|---|---|---|
| medium | ~50 | 256 | 短入力・標準ケース |
| long | ~50 | 512 | decode 速度感度 |
| ctx_long | ~1,000 | 256 | 中程度の prefill |
| ctx_8k | 1,793 | 256 | 長文 prefill 入門 |
| ctx_32k | 7,937 | 256 | 重い長文 prefill |
結果 1: KV プールサイズ — FP8 で実際何倍に増えたか
vLLM 起動ログから GPU KV cache size: N tokens を抽出して比較しました。
| max_model_len | BF16 KV tokens | FP8 KV tokens | FP8 / BF16 |
|---|---|---|---|
| 2,048 | 51,200 | 71,680 | 1.40x |
| 8,192 | 75,452 | 130,327 | 1.73x |
| 32,768 | 86,884 | 163,840 | 1.89x |
| 65,536 | 89,600 | 173,769 | 1.94x |
| 98,304 | — (起動失敗) | 177,352 | — |
予測通り、max_model_len を伸ばすほど FP8/BF16 比は 2x に漸近します。2K では 1.40x にとどまっていたのが、65K で 1.94x。固定コスト (モデル重み・SSM state など) が一定なので、KV ブロックの占有比率が上がるほど dtype 半減の効果が透けて見えるようになります。
base_fi_64k が KV tokens = 89,600 で頭打ちになっているのは「max_model_len=65536 宣言したものの、実際に確保できた KV プールは 89,600 トークンまで」というケース。max_model_len < KV プール なので起動は通っています。
結果 2: 96K の境界線 — BF16 起動失敗、FP8 起動成功
ここが今回もっとも示唆に富んだ結果です。
base_fi_96k (BF16, max_model_len=98304) を起動すると、vLLM は次のエラーで停止します。
ValueError: The model's max seq len (98304) is larger than the maximum number
of tokens that can be stored in KV cache (89,600). Try increasing
`gpu_memory_utilization` or decreasing `max_model_len` when initializing
the engine.
BF16 で 12GB GPU 上に確保できる KV プールは 89,600 トークンが限界で、これは宣言値 98,304 を下回るため起動を拒否される、という挙動です。gpu_memory_utilization はすでに 0.937 (現実的上限) なので、これ以上は上げられません。
一方 fp8kv_96k は同じ max_model_len=98304 で 177,352 トークンの KV プールを確保して起動に成功します。
| 項目 | base_fi_96k (BF16) | fp8kv_96k (FP8) |
|---|---|---|
| 起動結果 | ValueError: model_limit |
起動成功 |
| 確保 KV tokens | 89,600 (98,304 を下回るため不可) | 177,352 |
| medium decode | — | 59.97 tok/s |
| long decode | — | 60.13 tok/s |
| ctx_long decode | — | 58.54 tok/s |
| ctx_8k decode | — | 43.29 tok/s |
| ctx_32k decode | — | 23.57 tok/s |
つまり「FP8 KV を使うかどうか」は速度ではなく、まず そもそも起動できるかどうか を決める設定です。RTX 4070 12GB クラスのコンシューマー GPU で 96K コンテキストを宣言した Qwen3.5-4B を立てたければ、FP8 KV はオプションではなく前提です。
結果 3: 長文プロンプト受理マトリクス
max_model_len ごとに、長文プロンプトをどこから受け付けるかも変わります。ctx_8k (1,793 tokens) と ctx_32k (7,937 tokens) を投げて API の振る舞いを並べました。
| 構成 | max_model_len | ctx_8k 受理 | ctx_32k 受理 |
|---|---|---|---|
| base_fi_2k | 2,048 | ✗ | ✗ |
| base_fi_8k | 8,192 | ○ (TTFT 773 ms) | ✗ |
| base_fi_32k | 32,768 | ○ (TTFT 773 ms) | ○ (TTFT 2,762 ms) |
| base_fi_64k | 65,536 | ○ | ○ |
| base_fi_96k | 98,304 | — (起動失敗) | — (起動失敗) |
| fp8kv_2k | 2,048 | ✗ | ✗ |
| fp8kv_8k | 8,192 | ○ (TTFT 782 ms) | ✗ |
| fp8kv_32k | 32,768 | ○ | ○ (TTFT 2,896 ms) |
| fp8kv_64k | 65,536 | ○ | ○ |
| fp8kv_96k | 98,304 | ○ | ○ |
凡例: ○ = 正常応答 / ✗ = 400 BadRequest "input tokens exceed max_model_len" / — = サーバ起動失敗
ここから読み取れることは 2 つあります。
-
8K プロンプトを受け付けるには
max_model_len >= 8192、32K プロンプトにはmax_model_len >= 32768が必要。これは API レベルの厳格チェックで、KV プールが余っていても max_model_len を超える入力は拒否されます。 -
「96K 宣言サーバで長文を捌く」シナリオは FP8 KV でしか成立しない。BF16 では
base_fi_96kが起動しないので、宣言可能な context window は 64K 止まりです。
結果 4: スループットと TTFT — FP8 KV のオーバーヘッドは見えない
理論上、FP8 KV は K/V 書き込み時に BF16 → FP8、読み出し時に FP8 → BF16 の変換が走るためペナルティが乗るはずです。実測ではどうか。
decode tok/s (medium, 256 tokens 出力)
| max_model_len | BF16 | FP8 | 差 |
|---|---|---|---|
| 2,048 | 60.26 | 60.16 | -0.17% |
| 8,192 | 60.16 | 60.09 | -0.12% |
| 32,768 | 60.05 | 59.98 | -0.12% |
| 65,536 | 60.04 | 59.98 | -0.10% |
| 98,304 | — | 59.97 | — |
decode tok/s (long, 512 tokens 出力)
| max_model_len | BF16 | FP8 | 差 |
|---|---|---|---|
| 2,048 | 60.39 | 60.33 | -0.10% |
| 8,192 | 60.28 | 60.26 | -0.03% |
| 32,768 | 60.20 | 60.13 | -0.12% |
| 65,536 | 60.15 | 60.14 | -0.02% |
| 98,304 | — | 60.13 | — |
差は 0.2% 以内に収まり、誤差範囲。Qwen3.5-4B クラスでは forward 計算自体が支配的で、KV の dtype 変換は隠れています。
長文 prefill の TTFT
| 構成 | ctx_8k TTFT | ctx_32k TTFT |
|---|---|---|
| base_fi_32k | 773 ms | 2,762 ms |
| fp8kv_32k | 786 ms | 2,896 ms |
| fp8kv_96k | 781 ms | 2,894 ms |
ctx_32k で FP8 側が +130 ms ほど遅い傾向はありますが、これは prefill の attention カーネルが flashinfer の FP8 KV パスを通ることのオーバーヘッドと考えられます。スループットは下がっていないので、長文 prefill のレイテンシ感度が高いユースケースでない限り無視できる差です。
注目すべきは、fp8kv_96k の TTFT が fp8kv_32k とほぼ同じであることです。max_model_len を 32K → 96K に 3 倍宣言しても、実プロンプトが同じ 7.9K tokens であれば prefill のコストは変わりません。「context window を大きく宣言しても、短いプロンプトは短いまま処理される」という当然の性質ですが、起動できないと使えない側面でもあります。
結果 5: 出力品質 — 32K / 96K で文章は崩れないか
「FP8 KV にしたら品質が落ちる」「max_model_len を大きく取ると品質が落ちる」はどちらもよく聞く懸念です。今回は同一プロンプト・同一 seed (temperature=0) で BF16 KV と FP8 KV、さらに 96K 宣言を含む 3 構成の出力を並べて確認しました。
代表として ctx_32k (7,937 tokens のテクニカル文書を 3 文要約させるタスク) を取り上げます。
BF16 KV / max_model_len=32K (base_fi_32k)
The document explains that Key-Value (KV) caching is essential for optimizing Large Language Model inference by reusing attention states to reduce computational complexity from O(T²) to O(T). It highlights that FP8 quantization of the KV cache significantly reduces memory footprint, enabling longer context lengths on consumer hardware like the RTX 4070 compared to BF16. Furthermore, the text details how vLLM's PagedAttention mechanism manages this memory efficiently while noting the importance of distinguishing between model weight quantization and KV cache quantization as complementary optimization strategies.
FP8 KV / max_model_len=32K (fp8kv_32k)
The document explains that Key-Value (KV) caching is essential for optimizing Large Language Model inference by reusing attention states to reduce computational complexity from O(T²) to O(T). It highlights that FP8 quantization of the KV cache significantly reduces memory footprint, enabling longer context lengths on consumer hardware like the RTX 4070 compared to BF16. Furthermore, the text details how vLLM's PagedAttention mechanism manages this memory efficiently while noting the importance of distinguishing between model weight quantization and KV cache quantization as complementary optimization strategies.
FP8 KV / max_model_len=96K (fp8kv_96k)
The document explains that Key-Value (KV) caching is essential for optimizing Large Language Model inference by reusing attention states to reduce computational complexity from O(T²) to O(T). It highlights that FP8 quantization of the KV cache significantly reduces memory footprint, enabling longer context lengths on consumer hardware like the RTX 4070 compared to BF16. Furthermore, the text details how vLLM's PagedAttention mechanism manages this memory efficiently while noting the importance of distinguishing between model weight quantization and KV cache quantization as complementary optimization strategies.
3 つとも完全一致でした。
確認できたこと:
- KV cache dtype を BF16 → FP8 に変えても出力は変わらない (32K 比較)
-
max_model_lenを 32K → 96K に大きく宣言しても出力は変わらない (FP8 同士比較) - つまり「大きな context window を確保しても品質は損なわれない」「FP8 KV cache も品質を損なわない」
ctx_8k (1,793 tokens 入力) でも同様の一致が得られており、5 構成 × 5 ワークロード × 5 ラン (約 125 応答) を機械的にスキャンしてループ・繰り返し・破綻パターンを検出する review.md では「No obvious repetition loops detected」となりました。
検証範囲の caveat
ただし、今回の ctx_32k プロンプトは 7,937 tokens であり、96K 構成の context window 全体に対しては余裕がある状態での出力です。「真に 96K いっぱいまで詰めた長文プロンプト」での品質は今回のデータには含まれません。
vLLM 公式ブログ (2026-04-22) の Qwen3.5-27B 評価では、1M tokens 級の長文ベンチマーク (mrcr) で AUC 回復率 100% が報告されており、超長文ベンチでも FP8 KV は破綻しないことが大規模モデルでは確認されています。本記事の 4B 規模では、その範囲までは未確認 — 32K プロンプトまでで「壊れていない」ことを確認した状態です。
実務でこの結果をどう活かすか
1. 「速くする」ではなく「収容を増やす」設定として使う
FP8 KV cache は decode 速度を上げません (前回記事の通り)。代わりに、
-
同じ VRAM 予算で
max_model_lenを約 2 倍まで宣言できる (RTX 4070 12GB / 4B FP8 で 64K → 96K) -
同じ
max_model_len宣言で KV プールが約 2 倍になり、より多くの同時リクエストを収容できる (本記事は concurrency=1 で未検証)
という効果が出ます。スループット (req/s) のスケーリングは「KV プール容量 × 並列度」で決まるため、長文サービングや RAG のような KV 重いワークロードでは特に有効です。
2. 「VRAM 節約」の方向にも使える
同じ max_model_len で考えれば、FP8 にすると KV プールに必要な VRAM が半分で済みます。これは:
- 同じモデルをより小さい GPU に乗せる
- 余った VRAM を他のモデルやサービスに回す
-
--mem-fraction-staticを下げて起動時の VRAM 余裕を作る
といった運用に転用できます。「長くする」と「節約する」は同じ設定の表裏です。
3. max_model_len は「使う最長プロンプト + 出力余白」に合わせて宣言する
vLLM は API レベルで input_tokens > max_model_len を 400 で拒否します。長文を扱う可能性があるなら、実プロンプト最長 + 出力 max_tokens をカバーする max_model_len 宣言が必須です。「とりあえず大きく宣言しておく」のは VRAM 浪費なので、ワークロードから逆算して値を決めましょう。
4. 起動できない構成は FP8 KV で救える可能性が高い
ValueError: max seq len ... is larger than KV cache は典型的な「max_model_len 宣言過大」エラーですが、FP8 KV にするだけで KV プールが約 2 倍になり、宣言を維持したまま起動できるケースがあります。gpu_memory_utilization を上げる前に試す価値があります。
5. attention backend の制約に注意
| 機能 | flash_attn | flashinfer |
|---|---|---|
| FP8 KV cache | ✗ | ○ |
| D-Flash speculative decoding | ○ | ✗ |
FP8 KV と D-Flash は同時には組めません。「decode 速度を投機的デコーディングで上げる」 vs 「コンテキスト長と収容数を稼ぐ」のどちらを取るかは、ワークロード次第です。チャット系で短いやり取りが主なら D-Flash や MTP、長文 RAG や大量同時接続なら FP8 KV、と切り分けるのが現状の運用解です。
制約と注意点
- シングルリクエスト計測: 今回は concurrency=1 です。FP8 KV の本当の恩恵 (KV プール 2 倍) は同時実行で利く設定で、複数リクエストでのスループットスケーリングは次回検証対象です。
- 真の長文ベンチは未検証: 96K 構成の context window 全体を埋めるような超長文プロンプトでの品質は今回のデータでは確認していません。32K プロンプトまでで「壊れていない」状態の確認に留まります。大規模モデル (27B) では公式ブログが 1M tokens 級の品質維持を報告しています。
-
gpu_memory_utilization=0.937上限: RTX 4070 12GB で今回の評価ではこれが現実的な上限です。0.94以上で OOM になります。 - flashinfer のバージョン依存: vLLM nightly の flashinfer ビルドに密結合した結果なので、リリース版の挙動とは差が出る可能性があります。
まとめ
- FP8 KV cache は
max_model_lenの上限を約 2 倍まで押し上げる設定。RTX 4070 12GB / Qwen3.5-4B FP8 では BF16 上限 64K に対して FP8 で 96K が起動できた - BF16 KV では起動できなかった
max_model_len=98304の構成が、FP8 KV では 177,352 トークンの KV プールで起動成功 - decode tok/s と TTFT は 32K プロンプトまでで実用上同等。FP8 変換オーバーヘッドは隠れている
- 出力品質は BF16 KV / 32K FP8 / 96K FP8 の 3 者で完全一致。
max_model_lenを大きく宣言しても品質は損なわれない - 同じ設定は「VRAM 節約 → 並列度向上」の方向にも転用できる
- 長文サービング・高並列 RAG では選択肢ではなく標準装備にすべき設定
民生 GPU で 4B クラス LLM の 96K コンテキストサービングが現実的になっています。これがデータセンター GPU 専有の機能ではなく、5 万円台のコンシューマー GPU の上で成立している、というのが今回の記事でいちばん伝えたい結論です。
計測環境: RTX 4070 12GB / Ubuntu / vLLM nightly v0.21.1rc1.dev243 / RedHatAI/Qwen3.5-4B-FP8-dynamic / --enforce-eager / --max-num-batched-tokens 4096 / concurrency = 1 / warmup 1 + TTFT 1 + 本計測 5 ラン (中央値)
参考
過去シリーズ (Qwen3.5 + 投機的デコーディング / KV cache)
- VRAM 12GB 環境で FP8 KV Cache は実用投入できるか — テキスト性能評価 (前回記事)
- Qwen3.5-2B で Multi-Token Prediction を試す ① 投機的デコーディングと MTP の基礎
- Qwen3.5-2B で Multi-Token Prediction を試す ② 量子化と MTP の組み合わせ比較
- Qwen3.5-4B + D-Flash を 12GB GPU で動かし更に高速化を試す(日本語環境での実測)
- 日本語だと D-Flash が伸びない?Qwen3.5 + vLLM で日英ベンチを比較した