- Key idea
- KV cache size, FP8 and integer KV caches, per-channel keys and per-token values (KIVI), keeping recent tokens in full precision
- Real world
- Taking notes in a day-long meeting. You can’t write down every word, so older discussion gets summarised in shorthand, while the last few minutes stay in full sentences because you’re still working with them.
- In an LLM
- Llama 3 8B needs 128 KiB of KV cache per token in 16-bit: 16 GiB for one 128k-token conversation, over three times the size of its 4-bit weights. Store keys and values in FP8 and that halves; in 4 bits it quarters.
The other big memory user
During generation, each layer’s attention looks back at the keys and values of every earlier token. Recomputing them for every new token would be far too slow, so they are stored: the KV cache (introduced in how much memory a model needs). Its size is
The 2 is for keys and values. It grows linearly with context length and with the number of conversations served at once, while the weights stay fixed.
Try it
Two levers shrink it: architecture (grouped-query attention, which shares each key/value head among several query heads, cut Llama 3’s cache 4× compared with an equivalent model without it) and quantization.
How the KV cache is quantized
- FP8 KV cache. The simplest and now common in serving: keys and values stored as FP8 (E4M3 or E5M2), usually with per-tensor or per-head scales. Half the memory, usually no measurable loss. In vLLM:
kv_cache_dtype="fp8". - INT8 / INT4 blocks. llama.cpp can store the cache in its own block formats:
--cache-type-k q8_0 --cache-type-v q8_0(quantizing V requires flash attention,-fa). Q8_0 is close to lossless; Q4_0 saves more but costs some quality, more for keys than for values. - 2-bit research methods such as KIVI and KVQuant push further by exploiting how keys and values are shaped.
The two groupings on a matrix of tokens × channels: per token gives each row its own scale, per channel gives each column its own.
- one scale covers
- one token’s activations (4096 values)
- used by
- Dynamic activation quantization in W8A8 and FP8 serving; values in a KV cache.
Keys and values are shaped differently
Look at a key cache as a table: one row per token, one column per channel. Keys have outlier channels: a few columns are large in every token (partly because of how rotary position embeddings act on certain dimensions). Values have no such column pattern, but some tokens are larger overall than others. The right grouping follows the shape:
- Keys: per channel. Each column gets its own scale, so the outlier columns don’t ruin the others.
- Values: per token. Each row gets its own scale. The attention output is a weighted mix of value rows, so errors should be controlled row by row.
That’s exactly what KIVI does at 2 bits. Per-channel grouping has a practical snag, though: a column’s scale needs many tokens’ worth of that column, but new tokens arrive one at a time. KIVI keeps the most recent tokens (a window of a few dozen to a hundred or so) in full precision, and quantizes them as a group once the window fills. Recent tokens, which usually matter most for the next prediction, are therefore never quantized.
At 9 am you write full sentences. By 4 pm the morning is a page of shorthand: “budget ↑10%, A. owns hiring”. That’s enough to follow the discussion, while the last ten minutes are still in full so you can respond precisely. Older context, compressed; recent context, exact: KIVI’s full-precision window.
Serving Llama 3 8B with a 4-bit weight file (about 4.6 GiB) to 8 users with 32k-token contexts on a 24 GB GPU: a 16-bit KV cache would need GiB, and doesn’t fit. FP8 KV needs 16 GiB, which fits alongside the weights with a little room to spare. A 4-bit KV cache would need 8 GiB and leave room for longer contexts or more users.
Where this shows up
- The size formula and why decoding is limited by memory reads: how much memory a model needs.
- What else to quantize besides weights: weights, activations and the KV cache.
- Hadamard rotations make 4-bit KV caches work too: rotations.
Per-channel and per-token grouping
For the key cache (tokens × channels) and value cache , KIVI uses asymmetric -bit quantization with groups along different axes:
with and , applied to groups of a fixed number of tokens. The newest tokens are kept in full precision.
Why values per token
The attention output for a query is with attention weights . Quantization error in a value row is multiplied by that token’s weight , so the error in is a weighted sum of per-row errors. Controlling each row’s error relative to its own size keeps every term small. Keys enter through dot products , where an outlier channel affects every token’s score, so it’s the channel that needs its own scale.
In papers
- KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache (Liu et al., 2024).
- KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization (Hooper et al., 2024): quantizes keys before rotary embeddings are applied, uses non-uniform levels, and keeps a few outliers in a sparse side matrix.
- QuaRot and QServe (W4A8KV4) quantize the cache to 4 bits as part of a full low-precision serving stack.