Shrinking the KV cache, MQA, GQA, and MLA
FlashAttention removed the matrices. It left alone the other memory cost of attention, the one that grows with every token a model generates. To produce token , every layer needs the keys and values of all earlier tokens, so a serving system keeps them in the KV cache (§6.5). For Llama 2 7B in bf16 that is half a megabyte per token. Run it on 32K tokens and one sequence would hold 16 GiB of cache, more than the model's own weights, all of which every decoding step must read from HBM.
And generation keeps getting longer. Reasoning models write thousands of tokens of chain of thought before they answer (§18.5), and reinforcement learning from verifiable rewards spends much of its time generating such rollouts (§18.4), so the cost of decoding, and with it the cache, sits on the critical path of training as well as serving.
So the question for this section is how to make the cache smaller without making the model worse. The answers form a family. Share one key and value head among all query heads (multi-query attention), share them among groups of query heads (grouped-query attention), or compress keys and values together into a small latent vector that every head decompresses for itself (multi-head latent attention). We derive each, implement each, and put them side by side on real models, with the quality each one costs.
The cache, recalled, and why it sets the speed of decoding
§6.5 counted the cache. With layers, key-value heads of width (take ), a context of tokens, a batch of sequences, and bytes per number (§6.5 writes ), it holds
one key and one value vector per key-value head per layer for every token, and §6.5 showed that at long context and large batch it outgrows the weights. For ordinary multi-head attention , the number of query heads, and . Everything in this section changes one part of that product, the numbers that each token leaves behind in each layer.
The cache is not only a capacity problem. It sets the speed of decoding. At each step a query head does FLOPs per cached token for its score and for its weighted value, in all, and the cache supplies numbers per token per key-value head. With query heads sharing key-value heads, the arithmetic intensity of decode attention is
which in bf16 is just , the number of query heads that share each key-value head. For multi-head attention that is 1 FLOP per byte, more than a hundred times below an A100's ridge point of about 153 (§12.1). Decode attention is about as memory-bound as a computation can be, and its time per token is the size of the cache divided by the HBM bandwidth. Shrinking the cache speeds it up in direct proportion. Shazeer (2019) made this argument when he introduced the first fix, with the ratio of memory access to arithmetic for incremental multi-head attention growing like in our notation, the term coming from the cache.
Notice what is missing from that formula, the batch size. For the weight matrices, batching is the cure for low intensity, because one weight read from HBM serves every sequence in the batch, so the intensity of a linear layer is about FLOPs per byte (§11.2). The cache gets no such reuse. Each sequence attends to its own keys and values, so a batch of sequences reads times the bytes for times the FLOPs, and the intensity of decode attention stays at however large the batch. Larger batches make the weights cheaper per token and leave attention exactly as memory-bound as before, which is why at long context attention, not the weights, ends up dominating decode time. The only levers on that intensity are architectural, how many query heads share each cached vector, which is what the rest of this section is about.
The spread is a factor of about 67 between GPT-3 and DeepSeek-V3, even though DeepSeek-V3 has almost four times as many parameters. GPT-3's 4.5 MiB per token means a single 128K-token sequence would need more than half a terabyte of cache. Nearly every large model released since 2023 uses one of the methods below, and the rest of the section explains them.
KV cache bytes for MHA, GQA, MQA, and MLA
Implement kv_cache_bytes(cfg, T, batch=1, bytes_per=2) for a config dictionary with keys "layers", "heads", "head_dim", and "attn", where "attn" is one of "mha", "gqa" (with a key "kv_heads"), "mqa", or "mla" (with keys "d_c" and "d_rope", the latent width and the width of the shared rotary key). Return the bytes to cache batch sequences of T tokens. MLA caches numbers per token per layer, not two vectors.
Multi-query attention, one key head for everyone
Shazeer's idea was the most drastic one available. Keep query heads, each with its own projection, but give all of them a single key head and a single value head,
with and the mask of §4.4. In plain words, every head still asks its own question and gets its own pattern of weights, but all heads look up the same keys and read the same values. The cache shrinks by a factor of , and the decode intensity rises to FLOPs per byte, because one cached key now serves dot products.
What does it cost? Shazeer trained Transformer translation models on WMT14 English to German, widening the feed-forward layer of the multi-query model (from 4,096 to 5,440) so both had the same number of parameters. The table collects his numbers (Tables 1 and 2 of the paper, times in TPUv2 microseconds per output token, encoder plus decoder).
| dev BLEU | test BLEU, beam 4 | training step | greedy decoding per token | |
|---|---|---|---|---|
| multi-head | 26.7 | 28.4 | 13.2 | 1.7 + 46 |
| multi-query | 26.5 | 28.5 | 13.0 | 1.5 + 3.8 |
Decoding became about 12 times faster for the decoder, at a cost of 0.2 dev BLEU and no loss in test BLEU. On a billion-word language-modeling benchmark, perplexity went from 29.9 to 30.2. PaLM (Chowdhery et al. 2022) adopted multi-query attention for all its sizes, reporting a neutral effect on quality and training speed and a large saving at inference, and so did Falcon 7B.
The catch showed up later and at scale. Ainslie et al. (2023) found multi-query models less stable. T5-Large models trained with it from scratch had frequent loss spikes and diverged immediately when fine-tuned on long-input tasks. Its quality also trails multi-head attention, by a little on their tasks and by more on the hard benchmarks of DeepSeek's later ablation (below). A single key head is a real bottleneck. All heads must find what they need in the same -dimensional keys.
Grouped-query attention, the middle ground
The obvious compromise is to have some key-value heads, but fewer than query heads. Split the query heads into groups of consecutive heads, and give each group one key-value head. Query head (counting from 0) uses key-value head
This is grouped-query attention (GQA, Ainslie et al. 2023). With it is multi-head attention, and with it is multi-query attention, so it interpolates between the two. The cache shrinks by , and the decode intensity is FLOPs per byte in bf16. The mapping is contiguous (heads share key-value head 0), the convention of Llama's repeat_kv and of PyTorch's enable_gqa, which both repeat each key-value head times in a row. A checkpoint trained with one mapping gives nonsense under another.
Ainslie et al. also showed how to get a GQA model without pretraining from scratch. Take a multi-head checkpoint, build each group's key and value projections by mean-pooling the projections of the heads in the group, and continue pretraining (uptraining) for a small fraction of the original steps, . Mean-pooling worked better than keeping the first head of each group, which worked better than random initialization, in the order of how much information each preserves. On T5-XXL, with GQA applied to the decoder's self- and cross-attention, they reported these results (Table 1, average over summarization, translation, and question-answering tasks).
| model | inference time per sample (s) | average score |
|---|---|---|
| MHA, T5-Large | 0.37 | 46.0 |
| MHA, T5-XXL | 1.51 | 47.2 |
| MQA, T5-XXL | 0.24 | 46.6 |
| GQA-8, T5-XXL | 0.28 | 47.1 |
GQA with 8 groups was almost as good as multi-head attention and almost as fast as multi-query attention. The authors chose 8 groups as "a favorable middle ground", and the number stuck. Llama 2 70B, every Llama 3 model, Mistral 7B, and many others use 8 key-value heads, whatever their number of query heads.
Implementing it without copying
The simplest implementation repeats each key-value head times and runs ordinary multi-head attention. That works, and wastes the memory and bandwidth the method was meant to save. The better way is a reshape. View the queries of shape (B, h, T, d_k) as (B, n_kv, g, T, d_k) and the keys as (B, n_kv, 1, T, d_k), and let broadcasting pair each group of queries with its own key-value head.
Notice what does and does not change. The cache and the key and value projections shrink by . The attention core does not, since every query head still computes scores against keys. GQA saves memory and bandwidth, not FLOPs, which is exactly what decoding needs. Decode kernels use the same reshape on the GPU. At one new token per sequence, the query heads of a group become rows of a single query tile against one key tile, so each key loaded from HBM serves dot products, and a small matrix product on tensor cores replaces matrix-vector products. That is where the intensity of the previous section actually comes from in a kernel.
Sharing meets tensor parallelism
Tensor parallelism splits a layer's heads across GPUs (§10.4), and the key-value heads have to split with them. With GPUs, each holds query heads and, when divides , key-value heads, so its share of the cache shrinks by as well. When exceeds there are not enough key-value heads to go around. Each must be stored on GPUs, and the cache per GPU stops shrinking at one key-value head. Llama 2 70B's 8 key-value heads split evenly over the 8 GPUs of a typical node, while a 16-way split would store every one of them twice. Multi-query attention is the extreme case, its single head copied to every GPU. MLA's latent, below, is shared by all heads in the same way, which is why serving systems such as SGLang offer data-parallel attention for MLA models, giving each GPU different sequences rather than different heads.
Grouped-query attention with a key-value head mapping
Implement gqa(Q, K, V, causal=False) with Q of shape (B, h, T_q, d_k) and K, V of shape (B, n_kv, T_k, d_k), where is a multiple of and query head uses key-value head . With causal=True, use the bottom-right alignment of §4.4, so query (counting from 0) may see keys up to , which is what decoding with a cache needs. Use no Python loops over heads or positions, and do not materialize repeated copies of or .
Convert a multi-head checkpoint to grouped-query attention
Implement mha_to_gqa(W_K, W_V, h, n_kv). The inputs are multi-head key and value projections of shape (d, h * d_k), where columns i * d_k:(i + 1) * d_k belong to head . Return projections of shape (d, n_kv * d_k) whose key-value head is the mean of the consecutive heads , as in the uptraining recipe of Ainslie et al. (2023).
Multi-head latent attention, compress instead of share
Sharing throws information away. Every head in a group must use the same keys. DeepSeek-V2 (DeepSeek-AI 2024) asked a different question. Is there a small vector per token from which every head's key and value can be reconstructed? If the keys and values of all heads together have low rank, the answer is yes, and that vector is all the cache needs to hold.
Low-rank joint compression
For token with hidden state , multi-head latent attention (MLA) computes a latent vector and decompresses it into per-head keys and values,
with the down-projection , up-projections , and . (DeepSeek writes vectors as columns, so their matrices act from the left. We follow them in this subsection.) Only is cached. Queries are compressed the same way, and , which saves activation memory in training but does nothing for the cache. DeepSeek-V2 uses heads of width , so , and compresses keys and values into numbers, with for queries.
Decompressing for every cached token at every step would cost FLOPs per cached token per step, far more than the of the attention itself. The trick that makes MLA practical is that you never have to. Look at head 's score between the query at and the key at . Write for the rows of that produce head 's key, so . Then
In plain words, instead of decompressing every cached latent into a key, move the decompression matrix onto the query. The query is computed once per step, the keys are many, so this is far cheaper, and the score is a dot product between a -dimensional "absorbed" query and the cached latent itself. DeepSeek-V2 says "can be absorbed into" the query projection.
The values work the same way from the other side. Head 's output is , a weighted sum of latents, decompressed once, and since the output projection multiplies it next, can be folded into . So at inference MLA is multi-query attention, with one shared "key" and one shared "value" per token, both equal to the latent of width 512 (the rotary part of the next subsection adds 64 more numbers to the key), and with per-head queries that live in the latent space. That is why its decode intensity is high. Every one of the 128 heads reads the same cached vector.
Why RoPE gets in the way, and the decoupled fix
Rotary embeddings (§5.5) rotate queries and keys by position-dependent matrices and . Apply them to the decompressed key and the score becomes
A matrix that depends on the pair of positions now sits between the query and . The absorbed query would have to be recomputed for every cached position, because matrix products do not commute and cannot be moved out of the way. The paper's verdict is that keys for all prefix tokens would have to be recomputed during inference, which defeats the purpose.
DeepSeek's fix is to decouple position from content. Each query gets a few extra dimensions that carry RoPE, and each token gets one extra rotary key shared by all heads, MQA-style. The full query and key of head are concatenations, and the score is a sum of a content part and a position part,
The content part has no rotation, so it can be absorbed. The rotary part is a small ordinary dot product with a cached, already rotated key. The cache per token per layer is and , numbers. DeepSeek-V2 uses and , so the cache is numbers per token per layer, which the paper notes is the same as GQA with only 2.25 groups.
The cell checks the whole construction. It builds a small MLA layer, computes one decoding step the explicit way (decompress every cached key and value) and the absorbed way (attend in the latent space), and compares.
The two agree to machine precision. In a real model the absorbed matrices are precomputed once, and the absorbed path never forms a per-head key at all.
One MLA decoding step in the latent space
The setup defines a small MLA layer (weights in the dictionary W, rows-as-tokens convention), the rope function, and mla_reference(x, pos, cache_c, cache_kr), which decompresses every cached key and value. Implement mla_step(x, pos, cache_c, cache_kr) that returns the same output vector of shape (d,) without decompressing the cache. Absorb W["UK"] into the query and W["UV"] into W["O"], and attend directly over cache_c of shape (T, d_c) and the shared rotary keys cache_kr of shape (T, d_r). Your function must not multiply anything of length by W["UK"] or W["UV"] (the tests run it on a cache of 100,000 tokens and measure its peak memory).
How good is it?
DeepSeek-V2 reports two ablations that, together, are the best public evidence on this trade-off (Appendix D). With dense 7B models trained on 1.33T tokens, and parameter counts matched by adjusting depth, multi-head attention beat GQA with 8 groups, which beat MQA, on every benchmark they list.
| 7B dense, 1.33T tokens | MQA | GQA-8 | MHA |
|---|---|---|---|
| BBH (3-shot) | 33.2 | 35.6 | 37.0 |
| MMLU (5-shot) | 37.9 | 41.2 | 45.2 |
| C-Eval (5-shot) | 30.0 | 37.7 | 42.9 |
| CMMLU (5-shot) | 34.6 | 38.4 | 43.5 |
Then, comparing MLA with MHA in mixture-of-experts models (§13.5), MLA used 14% of MHA's cache in a 16B-parameter model (15.6K against 110.6K elements per token) and 4% in a 250B model, and scored higher on most benchmarks, for example MMLU 50.0 against 48.7 in the small model and 59.0 against 57.5 in the large one, though not all (C-Eval in the small model was 50.9 against 51.6). These are results from one lab's training setup, not a law. They do suggest that compressing keys and values loses less than sharing them.
The headline number needs care. DeepSeek-V2 reports a 93.3% smaller KV cache than DeepSeek 67B. That figure includes quantizing the cache to about 6 bits per element on average. By element count, MLA alone shrinks it by about 82%, as the cell computes from the two configurations.
The last lines show the other half of the story. Decode attention with GQA-8 runs at 4 FLOPs per byte, and with MLA at over 200, close to an H100's ridge point. MLA turns decoding from a pure bandwidth problem into something close to a balanced one, which is why it pairs so well with large batches. Its costs are complexity (absorbed weights, the decoupled rotary part, a kernel specialized for a 576-wide key and 512-wide value) and more FLOPs per cached token, which matters for prefill. For that reason serving implementations typically use the absorbed form only for decoding. In prefill, which is compute-bound anyway, they decompress the keys and values and run ordinary multi-head attention, with query-key width and value width 128 per head. DeepSeek-V3 and Kimi K2 kept MLA with the same and .
How long one decoding step spends in attention
Implement decode_attention_time(cfg, T, batch, peak, bw, bytes_per=2) returning (seconds, bound), the roofline time of the attention core in one decoding step over all layers, and "memory" or "compute" for whichever term is larger. Each of the batch sequences has one new query per query head and cached tokens. The config is a dictionary with "layers", "heads", "head_dim", and "attn", as in Exercise 12.5a ("mha", "gqa" with "kv_heads", "mqa", or "mla" with "d_c" and "d_rope"). Count only reading the cache (bytes) and the score and weighted-sum FLOPs. Per cached token per layer, a query head does FLOPs, except in absorbed MLA, where it does . Ignore the weights and the projections.
The trade-offs, side by side
| method | numbers cached per token per layer | decode intensity (bf16) | quality, as reported | used by |
|---|---|---|---|---|
| MHA | 1 | the baseline | GPT-3, Llama 2 7B and 13B | |
| GQA | close to MHA with 8 groups (T5-XXL), below it in DeepSeek's 7B ablation | Llama 2 70B, Llama 3, Mistral, Qwen2.5, Gemma 2 and 3 | ||
| MQA | small drop on translation, larger on hard benchmarks, less stable | PaLM, Falcon 7B | ||
| MLA | about , 242 for DeepSeek-V2 | at or above MHA in DeepSeek's MoE ablation | DeepSeek-V2 and V3, Kimi K2 |
Two other levers multiply with these, and are covered elsewhere. Fewer tokens in the cache, by attending only to a window of recent tokens plus a few sinks, is the subject of §12.6. Fewer bits per number, storing the cache in 8 or 4 bits, and paging it into fixed-size blocks so that sequences of different lengths share memory without fragmentation (the PagedAttention of §12.4), are what serving systems built around paged attention and quantized KV caches do. Sharing keys and values across adjacent layers (cross-layer attention, Brandon et al. 2024) is a further option some models use.
In PyTorch
A grouped-query attention layer is a few lines with enable_gqa=True, which passes the smaller key and value tensors straight to the kernel. The module below caches keys and values for decoding and reports how many bytes its cache holds.
Read more
- Shazeer (2019). Fast Transformer Decoding: One Write-Head is All You Need. Multi-query attention, with the memory-bandwidth analysis of incremental decoding that motivates everything here.
- Ainslie et al. (2023). GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. Grouped-query attention, uptraining, and the comparison of conversion methods.
- DeepSeek-AI (2024). DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model. Section 2.1 derives MLA and decoupled RoPE, and Appendix D has the MHA, GQA, MQA, and MLA ablations.
- Chowdhery et al. (2022). PaLM: Scaling Language Modeling with Pathways. The largest model to use multi-query attention, with notes on why.
- Brandon et al. (2024). Reducing Transformer Key-Value Cache Size with Cross-Layer Attention. Sharing keys and values between adjacent layers, a complement to sharing them between heads.
- Pope et al. (2022). Efficiently Scaling Transformer Inference. How the KV cache, multi-query attention, and partitioning interact when serving very large models.