TT Lab
Blog

한국어 English 日本語

Get started
Blog

Blog

Why the KV Cache is Needed: Remembering to Avoid Recomputation

Status: Draft (2026-10-13), partially measured. The toy model measurements (Section 3) were measured directly by the author on a laptop CPU. The GPU server measurements (Sections 4 and 6) re-reference values measured on a single 8GB-class GPU from the "Toss ML/AI Infra Preparation" series on the same site. Model configurations were verified from official configs and paper tables (viewed 2026-10-13), and KV sizes were calculated using those values. Writing rule: Measured data is the default; unmeasured items are marked as "Unmeasured", and unverified items are marked as "Unverified".

Table of Contents

1. Models That Generate One Token at a Time

A chatbot's response does not appear all at once. The model looks at the text so far (prompt + answers generated so far) to choose a single next token, appends that token to the end of the text, and chooses the next token again. This is called autoregressive decoding.

When choosing a single token, each layer of the transformer computes attention. It creates three vectors for each token:

The Q of the new token is compared one by one with the Ks of the preceding tokens to score them (how relevant they are), and the Vs are mixed using those scores. Expressed as a formula, it is softmax(q·Kᵀ/√d)·V .

There is an important property here. Language model attention is masked so that it cannot look ahead(causal mask). Because the K and V of the 3rd token are created by looking only at the 1st through 3rd tokens, even if 100 tokens are appended afterwards, they do not change . Therefore, there is no reason to discard the K and V once they are created and make them again. We can store them and retrieve them in the next step. This is the KV cache.

Q is not stored. This is because the Q of a previous token is never used again after it is used once to create that token. The only Q needed for a new step is that of the new token itself.

2. Without a Cache, Starting from Scratch at Every Step

토큰 5개를 만드는 동안 계산하는 K·V 캐시 없음 — 걸음마다 처음부터 다시 걸음 1 걸음 2 걸음 3 걸음 4 걸음 5 앞 토큰의 K·V 를 버리고 매번 전부 다시 만든다 계산 1+2+3+4+5 = 15칸 n 걸음이면 약 n²/2 캐시 있음 — 새 토큰만 계산, 지난 것은 읽기 걸음 1 걸음 2 걸음 3 걸음 4 걸음 5 새 토큰의 K·V 하나만 만들어 캐시에 붙이고, 어텐션은 전부 본다 계산 5칸, 읽기 10칸 n 걸음이면 계산 n칸 새로 계산한 K·V 캐시에서 읽은 K·V 한 칸 = 한 토큰의 K·V(모든 층). 어텐션 점수 계산은 두 방식 모두 지난 토큰 수만큼 든다.
Figure: Comparison of Decoding Without and With a Cache

Without a cache, at the t-th step, the K and V of all t tokens are re-created. Since the linear projections (Q, K, V, output) and MLPs performed per layer are proportional to the number of tokens, the cost of a single step is proportional to t, and summing over n steps gives 1+2+…+n ≈ n²/2 . With a cache, only a single new token is projected and fed into the MLP at each step, resulting in n for n steps.

Attention score calculation remains in both methods. Even with a cache, the Q of the new token must be compared against all preceding Ks, costing t per step (summing to n²/2 over n steps). Without a cache, even this costs t² per step, causing the sum to grow to n³/3. As long as the context is not very long, projections and MLPs make up the majority of computation, so the practically felt difference is "t versus 1 per step".

3. Trying It Yourself: numpy Toy Transformer

It is easy to describe but hard to intuitively grasp, so I built a small transformer in numpy with weights filled with random numbers to measure both methods. Since it is not a trained model, the output tokens have no meaning, but the computational shape (projection, attention, MLP, normalization) is identical to the real thing.

The core of the cached version is just a few lines. The K and V of the current token are written to the end of a pre-allocated array, and attention looks at everything accumulated so far (reshape separating by heads was simplified).

k = mm(h, W["k"])  # 새 K
v = mm(h, W["v"])  # 새 V
n = cache["n"]
cache["k"][:, n:n+T] = k  # 끝에 붙임
cache["v"][:, n:n+T] = v
K = cache["k"][:, :n+T]  # 지난 것 전부
V = cache["v"][:, :n+T]
a = attend(q, K, V)

Result 1. Outputs are identical, but workload and time differ significantly.

Workload (GFLOPs, counted by code):

Total Length No Cache With Cache Ratio
128 6.71 0.121 55x
256 31.0 0.264 118x
512 148.2 0.599 247x

Time (seconds, median of three runs):

Total Length No Cache With Cache Ratio
128 0.205 0.084 2.5x
256 1.16 0.131 8.9x
512 8.01 0.485 16.5x
한 토큰을 만드는 데 걸린 시간(ms) numpy 장난감 모델 · 노트북 CPU · 실측(64토큰 구간 중앙값) 0 10 20 30 40 0 128 256 384 512 지금까지의 문맥 길이(토큰) 캐시 없음 35ms 캐시 있음 0.4→0.6ms 캐시 없음(실선·점) 캐시 있음(점선) 512토큰 전체: 없음 8.0초, 있음 0.49초 · 연산량 247배 차이
Figure: Time per step by generation position

Result 2. Without a cache, each step becomes slower in proportion to the context. Looking at the median time per step across 64-token intervals in the 512-token run, the uncached version increased from 1.25 ms to 35.1 ms, while the cached version increased only slightly from 0.43 ms to 0.60 ms. The slight increase even with a cache is due to the attention score calculation mentioned in Section 2 (t per step).

Result 3. The size of the cache array matches the formula down to the byte. After prefilling 1,024 tokens, we added the nbytes .

Query heads are 8 for all three, and the formula value is 2 * layers (2) * KV heads * head dimension (16) * tokens (1,024) * 4 bytes.

Method KV Heads Actual Bytes Formula Value
MHA 8 2,097,152 2,097,152
GQA 2 524,288 524,288
MQA 1 262,144 262,144

For all three methods, the final logit using the cache was identical to the value calculated from scratch without a cache (tolerance 10⁻³).

4. The Cost of the Cache: Memory

토큰 하나가 남기는 KV 의 크기 2 × 층 × KV 헤드 × 헤드 차원 × 바이트 K 와 V 두 벌 · 이 값에 토큰 수를 곱하면 한 요청의 KV MHA GQA MQA MLA 헤드마다 KV 그룹마다 KV KV 하나 공유 압축해 저장 8칸 (기준) KV 헤드 = 쿼리 헤드 2칸 (1/4) 헤드 4개가 한 벌 공유 1칸 (1/8) 모든 헤드가 한 벌 잠재 벡터 하나 쓸 때 K·V 로 펼침 쿼리 헤드(저장 안 함) 캐시에 남는 K·V Llama 3.1 70B(80층·헤드 차원 128·bf16): GQA 8 → 320KiB/토큰 KV 헤드가 쿼리 헤드만큼(64) 있었다면 2.5MiB/토큰 (계산)
Figure: KV Memory Formula and MHA·GQA·MQA·MLA

The cache trades computation for memory. Since the KV left behind by a single token consists of one set of K and V per layer, it is as follows:

KV Bytes/Token = 2 * Number of Layers * Number of KV Heads * Head Dimension * Datatype Bytes

Whether this formula matches actual servers was verified in another series. For Qwen3-1.7B (28 layers, 8 KV heads, head dimension 128, bf16), the formula yields 114,688 bytes per token, and dividing the KV cache memory in the vLLM server logs (2.36 GiB) by the number of tokens (22,048) gave approximately 114,900 bytes, matching within 0.2% (8GB-class GPU, vLLM 0.31.0, measured). Storing KV in 8-bit (fp8) allowed 41,920 tokens to fit in the same memory, nearly doubling it precisely.

Plugging this into well-known public models yields the following. Layer and head counts were verified from official materials, and sizes were calculated assuming KV is stored in bf16 (2 bytes). The models below all have a head dimension of 128. "32K" is the KV for a single 32,768-token request.

Model Layers × KV Heads KV per Token 32K
Qwen2.5-1.5B 28 × 2 28 KiB 0.88 GiB
Qwen2.5-7B 28 × 4 56 KiB 1.75 GiB
Llama 3.1 8B 32 × 8 128 KiB 4 GiB
Mistral-7B 32 × 8 128 KiB 4 GiB
Qwen3-8B 36 × 8 144 KiB 4.5 GiB
Llama 3.1 70B 80 × 8 320 KiB 10 GiB
Llama 3.1 405B 126 × 8 504 KiB 15.75 GiB
If 70B Were MHA (Hypothetical) 80 × 64 2.5 MiB 80 GiB
DeepSeek-V2 (MLA) 60 layers, latent 576 67.5 KiB 2.11 GiB

The number of query heads is 12 for Qwen2.5-1.5B, 28 for Qwen2.5-7B, 32 for Llama 3.1 8B, Mistral-7B, and Qwen3-8B, 64 for 70B, and 128 for 405B. Having fewer KV heads than query heads is the GQA discussed in Section 5.

5. Methods to Reduce Size: MHA → GQA → MQA → MLA

What can be reduced in the formula are the number of KV heads (or whatever replaces them) and the bytes. The number of layers and head dimension are directly tied to the model's capabilities and cannot be arbitrarily reduced.

Why go to such lengths to reduce it? Section 6 holds the answer.

6. Prefill and Decode: Two Phases with Different Characteristics

A single request goes through two phases.

  1. Prefill(prefill): The entire prompt is fed at once to generate K and V for all tokens and write them to the cache. The first token is produced here. This determines the Time to First Token (TTFT).
  2. Decode(decode): Tokens are generated one by one thereafter. This determines the Inter-Token Latency (ITL) and Time Per Output Token (TPOT).

Both use the same model, but they manifest differently on hardware.

The difference was also visible in a toy model (with cache, median of three runs).

Prompt Time per prefill token Single decode step Ratio
64 0.018ms 0.445ms 24x
256 0.034ms 0.506ms 15x
1,024 0.144ms 0.852ms 6x

Total prefill times were 1.16ms for 64 tokens, 8.76ms for 256 tokens, and 146.9ms for 1,024 tokens.

Decoding a single token took 6 to 24 times longer than the per-token prefill time. Because it uses NumPy on CPU, Python call overhead is mixed in, and for the 1,024-token prefill, the quadratic attention term grows larger, increasing the per-token cost. Therefore, rather than the exact multiplier, you should look at the general trend that "batching computations together is far cheaper per token."

On GPUs, this difference is even more pronounced. In actual measurements from a different setup (8GB-class GPU, Qwen3-1.7B, vLLM), the input processing speed was about 8,200 tokens/sec, while decoding a single request yielded an inter-token latency of 15ms (approx. 66 tokens/sec). There is over a 120-fold difference per token.

If decoding is bound by memory, there is only one way to increase throughput: decoding multiple requests together whenever weights are read, which means increasing the batch size. In the same measurements, increasing concurrent requests from 1 to 8 raised throughput from 66 to 383 tokens/sec (5.8x), and to 831 tokens/sec at 32 concurrent requests. However, every request in the batch must have its own KV cache stored in the GPU memory. With an input of 512 + output of 128 tokens, a single request takes 640 tokens, and the GPU's KV capacity (22,048 tokens) can accommodate about 34 of them. When 64 concurrent requests were sent, throughput did not increase, and the time to first token spiked to 5.7 seconds. The upper limit of throughput is determined by the KV memory.

7. Summary: Questions This Series Will Follow

Fact Arising Question Coverage
KV does not change, so we store and reuse it. — Part 1
Decoding is cheaper with larger batches. How can we use KV memory without waste to increase batch size? Part 2 (PagedAttention, Continuous Batching)
KV for identical prefix parts is the same. How can we reuse KV from other requests? Part 2 (Prefix Caching), Part 3
GPU memory is small. What if we store KV on CPU, disk, or another server and fetch it? Part 3 (LMCache)
Prefill and decode have different characteristics. What if we split the two onto different GPUs? What if we route requests to where their KV already resides? Part 4 (Dynamo)

Titty's note. No referee in a table tennis match recounts the score from the very first serve every time. They write it down on the scoreboard and add one point at a time. The KV cache is precisely that scoreboard. However, if there are hundreds of arenas, you start running out of places to put the scoreboards.

References

Sign in to like

Comments

No comments yet.

Sign in to leave a comment