KV Cache › The KV Cache Solution

Michael BrenndoerferJanuary 6, 20268 min read
Language AI HandbookAll chapters
Part 42 of 67Inference Optimization
Chapter 313KV Cache › The KV Cache Solution
Reading section 3 of 14 · The KV Cache Solution · 8 minSection 3 of 14 · 8 min

Book contents

Section 3 of 14 · 8 min read

KV Cache › The KV Cache SolutionOne section · Sections in this chapter3 / 14 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The insight behind KV caching is straightforward once we recognize where the redundancy lies: since each token's key and value vectors depend only on that token's representation and the fixed projection weights, we can compute them once and store them for reuse in all subsequent generation steps. The query vectors, by contrast, must be recomputed because they are used differently at each step. A query asks "what information should I attend to?" and the answer depends on the token's position in the evolving sequence. The key insight is that keys and values are determined entirely by the token that produced them, while queries are only real when there is a full, growing context to attend to.

This asymmetry between queries and key-values is basic to understanding the cache. When token 5 generates a query, it needs to attend to tokens 1 through 4. When token 10 generates a query, it needs to attend to tokens 1 through 9. The keys and values for tokens 1 through 4 are the same in both cases since they haven't changed. But the query's role is different at each step: it always asks about everything that came before, and "everything that came before" grows with each step. Think of queries as questions that depend on who is asking and when, while keys and values are stable answers that remain constant once a token has been processed.

During generation with KV caching, the process changes fundamentally from the naive approach. The two phases, prefill and decode, serve very different computational roles:

Prefill phase (Step 1): Process all prompt tokens at once, computing queries, keys, and values for the entire prompt. Store the computed KK and VV matrices in the cache, as these will be reused throughout generation. Use the queries to compute attention within the prompt, establishing the initial hidden states. This phase is compute-intensive but happens only once.

Decode phase (Steps 2+): For each new token, the process is efficient:

  1. Compute QQ, KK, VV only for the single new token
  2. Append the new KK and VV vectors to the end of the cache
  3. Compute attention using the new QQ against all cached KK values
  4. Weight all cached VV values by the resulting attention scores
  5. Produce the output for this position

The important difference is that we only compute projections for one token per step (the newly generated one), while the attention computation can freely access all previously computed keys and values through the cache. This transforms the projection work from quadratic to linear in the total sequence length. The attention computation itself remains linear in the cache size at each step (since we compute qtKcacheTq_t K_{\text{cache}}^T for a single query), but that is unavoidable because the query must attend over all prior context.

Mathematical Formulation

Let's formalize this caching mechanism to understand exactly how it reaches the computational savings. The formalization reveals that caching is not an approximation but an algebraically equivalent reformulation of the attention computation. The outputs are bit-for-bit identical to non-cached attention; only the order and grouping of computations changes.

At generation step tt, let xtx_t denote the embedding of the newly generated token. This embedding captures all the information the model has about this token at the input to the attention layer. We project this single token's embedding to obtain its query, key, and value vectors:

qt=xtWQ,kt=xtWK,vt=xtWVq_t = x_t W_Q, \quad k_t = x_t W_K, \quad v_t = x_t W_V

where:

  • xtx_t: the input embedding for the current token at step tt, a vector of dimension dmodeld_{\text{model}}
  • WQ,WK,WVW_Q, W_K, W_V: the fixed projection matrices shared across all positions and all generation steps
  • qtq_t: the query vector representing what the current token is seeking to attend to
  • ktk_t: the key vector representing how this token can be matched by future queries
  • vtv_t: the value vector containing the information this token contributes when attended to

Note the important distinction from the non-cached case: each of these is a single vector of dimension dkd_k or dvd_v, not a full matrix covering all positions. We compute projections for exactly one token, not the entire sequence.

The cache is the model's memory of past tokens, maintaining the history of key and value vectors computed in all previous steps. At the beginning of step tt, before processing the new token, the cache contains concatenated matrices:

Kcache=[k1;k2;…;kt−1]∈R(t−1)×dkVcache=[v1;v2;…;vt−1]∈R(t−1)×dv\begin{aligned} K_{\text{cache}} &= [k_1; k_2; \ldots; k_{t-1}] \in \mathbb{R}^{(t-1) \times d_k} \\ V_{\text{cache}} &= [v_1; v_2; \ldots; v_{t-1}] \in \mathbb{R}^{(t-1) \times d_v} \end{aligned}

where:

  • Kcache,VcacheK_{\text{cache}}, V_{\text{cache}}: matrices storing the complete history of keys and values for all previous t−1t-1 tokens
  • ki,vik_i, v_i: the key and value vectors computed when token ii was first processed
  • [;][;]: the concatenation operation along the sequence dimension, stacking vectors as rows
  • dk,dvd_k, d_v: the dimensions of the key and value vectors, often equal in practice

This cache is all the "memory" the attention mechanism has of the sequence so far. Each row corresponds to a token's contribution to the attention computation.

After computing the new token's projections, we update the cache by appending the new key and value vectors:

Kcache←[Kcache;kt]Vcache←[Vcache;vt]\begin{aligned} K_{\text{cache}} &\leftarrow [K_{\text{cache}}; k_t] \\ V_{\text{cache}} &\leftarrow [V_{\text{cache}}; v_t] \end{aligned}

where:

  • Kcache,VcacheK_{\text{cache}}, V_{\text{cache}}: the cached key and value matrices being extended with new entries
  • ←\leftarrow: the assignment operator showing an in-place update to the stored cache state
  • kt,vtk_t, v_t: the newly computed key and value vectors being appended to preserve the sequence history

After this update, the cache contains keys and values for all tt tokens, ready for the attention computation.

With the cache updated, the model computes attention for the new token by having its query vector interact with the entire history:

at=softmax(qtKcacheTdk)∈R1×tot=atVcache∈R1×dv\begin{aligned} a_t &= \text{softmax}\left(\frac{q_t K_{\text{cache}}^T}{\sqrt{d_k}}\right) \in \mathbb{R}^{1 \times t} \\ o_t &= a_t V_{\text{cache}} \in \mathbb{R}^{1 \times d_v} \end{aligned}

where:

  • ata_t: the attention weights for the current step, a vector of tt probabilities representing how much the new token should attend to each previous position
  • qtq_t: the query vector for the current token, seeking needed information from the context
  • KcacheTK_{\text{cache}}^T: the transpose of the cached key matrix, shaped for efficient dot product computation with the query
  • VcacheV_{\text{cache}}: the cached value matrix containing the actual content to be retrieved and aggregated
  • dkd_k: the dimension of key vectors, giving the scaling factor for numerical stability
  • dvd_v: the dimension of value vectors, determining the output size
  • oto_t: the final attention output for step tt, a weighted combination of all cached values

The computation qtKcacheTq_t K_{\text{cache}}^T produces a vector of tt scores, one for each cached position. These scores measure how needed each previous token is to the current query. After scaling and applying softmax, these become proper attention weights that sum to 1. Finally, multiplying by VcacheV_{\text{cache}} aggregates the cached values according to these weights, creating the output vector that captures all the needed information from the sequence history.

This formulation reaches efficiency because the matrix multiplications involve a single query vector against the cached matrices, requiring O(t⋅d)O(t \cdot d) operations rather than O(t2⋅d)O(t^2 \cdot d) that would be needed to recompute attention from scratch at every step.

Out[4]:
Visualization
Bar chart showing cached token count per generation step. The bar labeled Prefill starts at 8 tokens, then 12 decode-step bars grow from 9 to 20 tokens.
Cache growth during autoregressive generation. Starting with an 8-token prompt processed during prefill, the cache grows by one token at each decode step. The prefill phase processes all prompt tokens simultaneously, while each subsequent step adds exactly one new token's keys and values to the cache. The orange dashed line marks the initial prompt length boundary.