チャプター 18 · 推論 · 8 min
なぜ2番目のトークンは1番目より速いのか
KVキャッシュと自己回帰生成。Prefill vs decode、TTFT、そしてキャッシュがすべてを変える理由。
応答時間の幻想
ChatGPT に質問をする。1秒ほどの間を置いてから答え始める。それから単語が、速読の速さで、ほぼ瞬時に出てくる。
この非対称性はインタフェースの装飾ではない。これがなければ LLM でテキストを生成するコストが100倍になる、根本的な最適化の痕跡だ:KV cache。
Transformer はどうやってトークンを生成するか
各生成ステップで、Transformer は新しいトークンを1つ生成しなければならない。そのために、最後のトークンのそれ以前のすべてのトークンに対するアテンションを計算する。これが文脈全体を考慮できる仕組みだ。
しかしアテンションには、コンテキスト内の各トークンに対して、2つのベクトルが必要だ:key (K) と value (V)。最適化なしでは、新しいトークンが生成されるたびに、モデルは列全体の K と V を再計算する——前のステップですでに処理されたトークンも含めて。これは列の長さに対して O(n²) の作業だ:トークン数を倍にすれば、コストは4倍になる。
これは無駄だ:これらのベクトルは変わっていない。トークン3 は前のステップと同じ K₃ を持っている。
KV cache:すでに持っているものを決して再計算しない
アイデアは些細で決定的だ。すでに処理されたすべてのトークンの K と V を(GPU の)メモリに保存する。新しい生成ステップごとに、新しいトークンの K と V だけを計算し、キャッシュに追加する。
アテンションはそのとき完全なキャッシュを見るが、このステップで行う計算はサイズで O(1) になる——O(n) ではなく。
キャッシュがなければ、新しいトークンごとにプレフィックス全体に対するアテンションを再計算する——コストは O(n²)。キャッシュがあれば、新しい行だけを計算すればよい。これが最初のトークン(遅い prefill)と次のトークン(速い decode)を分ける。
左側、キャッシュなし:各ステップですべての行を再描画する。右側、キャッシュあり:行を1つ追加するだけ。数トークン後には、累積演算の差は相当なものになる。
prefill vs decode:明確に異なる2つのフェーズ
LLM での生成は、推論エンジニアが慎重に区別する2つのフェーズに分かれる。
Prefill。 モデルは完全なプロンプトを受け取り、そのすべてのトークンに対して並列に K と V を計算する。スループットの観点から速い——GPU は飽和している——が、プロンプトが長ければ時間がかかる。これが最初の単語が現れるまでの遅延、TTFT (Time To First Token) を決める。
Decode。 モデルは残りを、キャッシュを再利用しながら、1つずつトークンを生成する。各ステップは個別には速いが、逐次的だ:未来のトークンを並列化することはできない、各トークンが前のトークンに依存するからだ。これが ITL (Inter-Token Latency) を決める。
これら2つのフェーズは完全に異なるプロファイルを持つ:
| Prefill | Decode | |
|---|---|---|
| 並列化可能? | はい(すべてのトークンを一度に) | いいえ(逐次的) |
| ハードウェアのボトルネック | 計算(FLOPs) | メモリ(キャッシュの読み取り) |
| 長さの影響 | N に対して線形 | トークンごとに N に対して線形 |
| 主要メトリック | TTFT | ITL |
長いプロンプトでは、prefill が数秒かかることがある。長い出力では、decode が支配的になる——そしてそれは GPU の HBM メモリから KV cache を読み取れる速度によって制限される。
なぜプロバイダは「input tokens」を異なる値段で課金するのか
OpenAI、Anthropic、Google の価格を見れば、input tokens は output tokens よりも体系的に安い——しばしば4倍から5倍。これは恣意的ではない。input tokens を処理する prefill は大規模に並列で、GPU を効率的に使う。decode は output tokens を1つずつ生成し、ハードウェアを十分に活用しない。
より微妙な点:Anthropic、OpenAI などは現在 prefix caching を提供している。複数のリクエストが同じ system prompt で始まる場合、その接頭辞の KV cache を一度だけ計算し、再利用する。これがエージェントとマルチターンのチャットボットを経済的に成立させているものだ:prefix caching なしでは、各ターンが会話全体の再処理コストになる。
隠れたコスト:GPU メモリ
KV cache はメモリの面で無料ではない。次の領域を占める:
メモリ = 2 × n_layers × n_heads × d_head × seq_len × batch_size × 2バイト (FP16)
128,000 トークンのコンテキストとバッチサイズ 1 の 700億パラメータのモデルでは、数十 GB に相当する。これは多くの場合、モデル自身が列について推論する能力よりも、実用的なコンテキスト長を制限している。
さらに先へ進むため、いくつかの技法がある:
- Cache quantization:FP16 ではなく INT8 や INT4 で K と V を保存し、メモリを2分の1か4分の1にする。
- MQA / GQA(Multi-Query / Grouped-Query Attention):複数のヘッド間で K/V を共有する。Llama 2 70B と Llama 3 は GQA を使い、キャッシュサイズを劇的に縮小する。
- Sliding window attention:キャッシュの最近のウィンドウだけを保持する(Mistral、Gemma)。
- PagedAttention(vLLM):動的バッチングをよりよく管理するため、キャッシュを仮想メモリのページとして扱う。
Quantization、ふた言で
この言葉はこのパートのあちこちに出てくる:4ビットのQLoRA(第 14 章)、すぐ上のcache quantization、Hugging Faceからダウンロードするモデル GGUF。そろそろこれが何を意味するのか説明する頃合いだ。
Quantize するとは、モデルの各パラメータをより少ないビット数で表現することだ。32ビット浮動小数点数(FP32)は4バイトを取る。FP16では2バイト。INT8では1バイト。INT4では半バイト——重みが占めるメモリは元のFP32と比較して8分の1になる。
| 精度 | バイト / パラメータ | 70Bモデルの占有量 |
|---|---|---|
| FP32 | 4 | 280 GB |
| FP16 / BF16 | 2 | 140 GB |
| INT8 | 1 | 70 GB |
| INT4 | 0.5 | 35 GB |
| INT2(極端) | 0.25 | 17.5 GB |
仕掛けはこうだ:0.237 という重みは INT4 では正確に 0.237 にはならず(INT4 は 16 個の値しか取れない)、離散的なグリッド上で最も近い値になる。品質の損失はモデルと手法に依存するが、典型的には INT8 までは小さく、INT4 ではそこそこ(ベンチマークで数 % の劣化)、それ以下では顕著になる。
現代の技法(GPTQ、AWQ、GGUF)はすべての重みを均等に量子化するわけではない——感度の高い層では精度を保ち、それ以外はより積極的に圧縮する。そして上で触れた KV cache の quantization は、生成中にメモリに保存される活性値にまったく同じ考えを適用したものだ。
これが Llama 70B を 64 GB の RAM の MacBook で動かせるようにする技法だ。FP16 版ならクラスタが必要になるところを。
教訓
KV cache なしでは、本番環境の LLM は実用不可能だろう。長い会話、考えるエージェント、5メッセージ前に言ったことを覚えているチャットボット——どれも経済的に成立しないだろう。
しかしこのキャッシュこそがコンテキスト長を制約するものでもある。「100万トークンのウィンドウ」と言うとき、それは大部分がキャッシュメモリの問題であり、アテンション計算の問題ではない。
KV cache は他の最適化の中の1つではない。アテンションを理論的なメカニズムから本番環境のインフラへと変える、その何かだ。
更新日