What is KV Cache and how does it speed up inference?
Intermediate · 3 min read · LLM Fundamentals
Open in study mode to save this question and track progressShort answer: Key-Value (KV) Cache stores the precomputed Key and Value vector representations of previously processed tokens in memory during autoregressive decoding, preventing redundant matrix multiplications for past tokens at every new token generation step.
Detail
In autoregressive Large Language Models (LLMs), text generation happens token-by-token. To generate token , the self-attention mechanism needs to calculate attention scores between the query vector and the key/value vectors of all preceding tokens ( and ).
Without KV Caching:
- At step , the model computes and .
- At step , the model would recompute and from scratch.
- Cost per step grows with sequence length: re-projecting every prior token is and re-running attention over them is , so generating tokens costs attention FLOPs across the whole loop.
With KV Caching:
- The model computes and stores for past tokens in GPU memory.
- At step , the model computes ONLY for the new token, appends and to the cache, and computes attention against the stored keys and values.
- Time complexity per step drops from projection operations to new projections + memory fetch and attention dot product.
Memory Impact
While KV caching drastically speeds up generation, it shifts inference from compute-bound to memory-capacity bound. For a model with layers, key/value heads, head dimension , sequence length , batch size , and precision bytes:
The head count in that formula is the number of KV heads, not query heads — under MHA they are equal, but under GQA/MQA the cache is far smaller. Getting this wrong is the most common sizing mistake in interviews.
Worked example — Llama-3-70B in float16 (), 4K context, batch size 1. It has layers, (GQA), and :
Using its 64 query heads instead would overestimate this by 8x (~10.7 GB), which is exactly the error the formula above prevents.
Example
Below is a conceptual Python implementation demonstrating autoregressive decoding with and without KV caching:
import torch
import torch.nn as nn
class CausalSelfAttentionWithKVCache(nn.Module):
def __init__(self, d_model: int, n_heads: int):
super().__init__()
self.d_model = d_model
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.q_proj = nn.Linear(d_model, d_model)
self.k_proj = nn.Linear(d_model, d_model)
self.v_proj = nn.Linear(d_model, d_model)
self.out_proj = nn.Linear(d_model, d_model)
def forward(self, x: torch.Tensor, kv_cache=None):
# x shape: (batch_size, seq_len, d_model)
b, seq_len, _ = x.shape
# Project new token input
q = self.q_proj(x).view(b, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
k = self.k_proj(x).view(b, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
v = self.v_proj(x).view(b, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
if kv_cache is not None:
prev_k, prev_v = kv_cache
k = torch.cat([prev_k, k], dim=2)
v = torch.cat([prev_v, v], dim=2)
new_kv_cache = (k, v)
# Scaled dot-product attention
scores = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5)
attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, v)
output = output.transpose(1, 2).contiguous().view(b, seq_len, self.d_model)
return self.out_proj(output), new_kv_cacheInterview tips
- Always distinguish prefill phase (compute-bound batch processing of prompt tokens) from decoding phase (memory-bandwidth bound single token generation).
- Mention Grouped-Query Attention (GQA) and Multi-Query Attention (MQA) as key architectural optimizations designed specifically to shrink KV cache size.
- Be prepared to discuss PagedAttention (vLLM) as the standard production solution for memory fragmentation caused by dynamic KV cache allocation.