Learn: MQA → GQA → MLA (Shrinking the KV Cache)

What you're learning

At inference, the KV cache — not FLOPs — is usually what caps how many sequences you can serve and how long a context you can hold. This lesson walks the arc of KV-cache reduction: full multi-head attention (MHA) → MQA (one KV head) → GQA (a few KV groups, the current production default) → MLA (a low-rank latent cache, DeepSeek’s frontier trick). By the end you should be able to write each scheme’s per-token cache formula from memory, explain why MLA beats GQA on both memory and quality at once, and derive why MLA needs a separate “decoupled RoPE” key.

This lesson assumes lessons 01–04: scaled dot-product attention (01-scaled-dot-product-attention), the multi-head block (02-multi-head-attention-and-the-block), positional encodings including RoPE (03-positional-information), and the quadratic bottleneck / IO-aware view (04-quadratic-bottleneck-and-io-aware). It is the attention-side companion to the serving-systems view of the KV cache in 12-llm-inference-systems. Vocabulary follows the attention-glossary.


1. Learning map

graph TD
    A["Inference bottleneck:<br/>KV cache grows with<br/>tokens × heads × layers"] --> B["Lever: reduce the number<br/>of DISTINCT K/V heads stored"]
    B --> C["MHA<br/>n_kv = n_heads<br/>(baseline, biggest cache)"]
    C --> D["MQA<br/>n_kv = 1<br/>÷h cache, quality drops"]
    C --> E["GQA<br/>1 < g < h groups<br/>the memory/quality sweet spot"]
    D --> E
    E --> F["MLA<br/>cache a low-rank latent c_t<br/>reconstruct K,V per head"]
    F --> G["Weight absorption:<br/>fold up-projs into W^Q / W^O<br/>→ never materialize K,V"]
    F --> H["Decoupled RoPE:<br/>separate rope-carrying key<br/>(RoPE ⊥ absorption)"]
    G --> I["Smaller cache than GQA<br/>AND quality ≥ MHA"]
    H --> I

    style A fill:#a44,color:#fff
    style E fill:#44a,color:#fff
    style F fill:#4a4,color:#fff
    style I fill:#a83,color:#fff

2. Intuition: stop storing a full set of keys and values per head

Recall the serving picture from 12-llm-inference-systems: autoregressive decode generates one token at a time, and to attend to the past without recomputing it times we cache every past token’s keys and values. That KV cache is

bytes, where is layers, the number of key/value heads, the head dimension, the sequence length, the batch, and bytes per element. It grows with every concurrent sequence and every generated token — and at serving batch sizes it rivals or exceeds the model weights in HBM. That is the wall.

Stare at that formula and ask which factor you can actually move. , , , are fixed by the model and the workload (well — you can shrink with KV quantization, a separate lever). The one architectural knob is : how many distinct key/value heads you store per layer.

Here is the key observation from lesson 02: in MHA every one of the query heads has its own K head and V head. But do the value/key subspaces really need to be that different across heads? Empirically, no — you can make many query heads share the same keys and values and lose surprisingly little. That single idea — decouple the number of query heads from the number of KV heads — generates the whole progression:

  • MHA: . Every query head gets a private K/V head. Maximum expressivity, maximum cache.
  • MQA: . All query heads share a single K/V head. Cache shrinks by a factor of .
  • GQA: . Query heads are partitioned into groups; each group shares one K/V head. A dial between the two extremes.
  • MLA: don’t store per-head K/V at all — store a small shared latent per token and reconstruct each head’s K and V on the fly. Breaks out of the box entirely.

The one-sentence version

MQA/GQA shrink the cache by cutting the number of K/V heads (); MLA shrinks it by cutting the width of what’s cached — storing a low-rank latent instead of full-width K,V — which lets it go smaller than GQA and keep MHA-level quality.

This is a decode-memory optimization, not a FLOPs optimization

Sharing KV heads barely changes prefill FLOPs (the score and matmuls still sum over the full model dimension ; see §5). What it slashes is KV-cache bytes and therefore the memory-bandwidth traffic of decode — which is exactly the binding constraint at serving time (lesson 12). Don’t expect MQA/GQA to speed up a compute-bound prefill much; expect it to let you fit more sequences and longer contexts, and to make each memory-bound decode step read fewer KV bytes.


3. The math

Throughout: = number of query heads, = head dimension, = model dimension (assume // head dims equal for clarity), = layers.

3.1 MQA — one write-head is all you need

Multi-Query Attention1 keeps all query heads but uses a single key head and a single value head shared across every query head. Concretely, still produces query heads, but each produce only one -wide head:

Head ‘s attention is — every head reads the same cached . The cache stores one K and one V per token per layer:

The win is huge (for a 32-head model, less KV). The cost is quality: collapsing key/value subspaces into one removes representational capacity, and MQA-trained models show measurable degradation and can be training-unstable on large models.2 That instability motivated the middle ground.

3.2 GQA — grouped query attention, the sweet spot

Grouped-Query Attention2 interpolates: partition the query heads into groups (, with ), and give each group its own shared K/V head. So :

recovers MHA; recovers MQA. The GQA paper’s two findings are the reason it is everywhere:

  1. Quality/memory sweet spot. A moderate (e.g. ) recovers essentially all of MHA’s quality while keeping most of MQA’s memory savings — the quality-vs- curve is steep near and flat by a handful of groups.2
  2. Uptraining, not retraining. You can convert an existing MHA checkpoint to GQA by mean-pooling the K/V projections of each group and fine-tuning for a small fraction (~5%) of the original compute — no pretrain from scratch.2

This is why Llama-2-70B / Llama-3-class models and most production LLMs use GQA with : near-MHA quality at less KV.2

Why grouping is stable where MQA is not

MQA forces all heads through a single 1-dimensional-per-token K/V bottleneck; the gradient signal to that lone head is an average over conflicting demands, which destabilizes large-model training. GQA gives each group its own K/V, so the shared head only has to serve a handful of similar query heads — enough capacity to stay stable and expressive while still cutting the cache several-fold.

3.3 MLA — Multi-head Latent Attention

GQA still stores full-width K and V, just for fewer heads. Multi-head Latent Attention (MLA, DeepSeek-V2)3 asks: what if we cache neither — and instead store a single low-rank latent per token, reconstructing per-head K and V only at the moment of use?

Down-projection (the only thing cached). From the token’s hidden state , compute a compressed KV latent

This -wide vector is what goes in the cache — one per token per layer, shared across all heads.

Up-projection (reconstruct at use time). Per-head keys and values are recovered by up-projecting the latent:

Queries get their own (optional) low-rank compression , which shrinks activation memory and parameters during training but doesn’t affect the cache.

Naively this looks worse — you’d reconstruct full K,V for every head every step, materializing exactly what MHA stored. The magic is that you never have to.

Weight absorption — never materialize K, V

The compressed part of the attention score for query head is

Because and are fixed weights, their product can be precomputed and folded into the query path. At inference you compute an effective query and dot it directly against the cached latent — you never reconstruct . Symmetrically, absorbs into the output projection , so you never materialize either: the value read is a projection of the same cached latent. Net effect: you cache only (width ), yet compute exactly MHA-style multi-head attention.

Decoupled RoPE — why a separate key part is mandatory

There is a catch, and it is the subtle heart of MLA. The absorption above works only because is a single constant matrix. RoPE breaks that. RoPE (03-positional-information) multiplies each key by a position-dependent rotation (and each query by ), so a RoPE’d score term would be

Now a position-dependent factor sits between and . It does not commute with them, so you can no longer collapse them into one constant matrix — the “absorbed” weight would depend on the token pair , defeating the whole scheme (you’d be back to materializing per-head, per-position keys). RoPE and weight absorption are fundamentally incompatible.

DeepSeek’s fix is decoupled RoPE: carry positional information on a separate, small key/query part that lives outside the compressed path.

  • A shared rope-carrying key , generated straight from (one head, MQA-style — shared across all heads), and cached alongside the latent.
  • A per-head rope query .

The final query and key for each head are the concatenation of the compressed (position-free, “NoPE”) part and the decoupled RoPE part, so the score splits into two additive terms:

The compressed term stays fully absorbable (no RoPE inside it); the RoPE term is small and shared. So the total cache per token per layer is elements — the latent plus the shared rope key.

(Often quoted as since .)

The numbers. DeepSeek-V23 uses , , and a decoupled RoPE width , so it caches elements/token/layer. Compare full MHA at those dims ( heads): elements — MLA is ~57× smaller. DeepSeek report this equals the cache of GQA with just groups (since ), while matching or exceeding full MHA quality.3 That is the punchline: MLA lands below the GQA cache curve and above the GQA quality curve — it dominates GQA on both axes. DeepSeek-V3 carries MLA forward at larger scale.4

MLA is not "just low-rank KV"

Two things make it work and are easy to miss. (1) Weight absorption — without folding the up-projections into /, you’d reconstruct full-width per-head K,V at every step and gain nothing (you’d cache small but compute like MHA with extra up-proj FLOPs). (2) Decoupled RoPE — you cannot just RoPE the reconstructed keys, because the position rotation sits between the two absorbable matrices and kills the absorption. The separate rope key is not a detail; it is what makes MLA + RoPE compatible at all.


4. Illustration: what each scheme stores per token

graph TB
    subgraph MHA["MHA — n_kv = h (baseline)"]
        direction LR
        m1["K₁ V₁"]; m2["K₂ V₂"]; m3["K₃ V₃"]; mdots["…"]; mh["K_h V_h"]
    end
    subgraph GQA["GQA — n_kv = g groups"]
        direction LR
        g1["K₁ V₁<br/>(heads 1..h/g)"]; g2["K₂ V₂<br/>(next group)"]; gdots["…"]; gg["K_g V_g"]
    end
    subgraph MQA["MQA — n_kv = 1"]
        direction LR
        q1["K V<br/>(shared by ALL heads)"]
    end
    subgraph MLA["MLA — cache latent c_t + shared k^R"]
        direction LR
        l1["c_t (d_c)"]; l2["k^R (d_h^R, shared)"]
        l1 -. "up-proj W^UK,W^UV<br/>(absorbed into W^Q,W^O)" .-> l3["per-head K,V<br/>materialized only transiently"]
    end

    MHA --> GQA --> MQA
    MHA -. "low-rank, not fewer heads" .-> MLA

    style MHA fill:#a44,color:#fff
    style GQA fill:#44a,color:#fff
    style MQA fill:#864,color:#fff
    style MLA fill:#4a4,color:#fff

Reading the figure: MHA→GQA→MQA all store full-width K,V, just for fewer heads (boxes get fewer, each box unchanged). MLA is different in kind — it stores a narrow latent (plus a tiny shared rope key ) and reconstructs the per-head K,V transiently, which is why it can undercut even MQA’s width while keeping every head distinct.

Cache per token per layer, at a glance:

SchemeDistinct K/V heads Cached elements / token / layervs MHA
MHA
GQA
MQA
MLA— (latent)

5. FLOPs & memory

KV cache (bytes) — the number that actually binds serving. With bytes/element:

Note the general pattern for the head-sharing family vs MLA’s (the factor of 2 disappears because K and V are both reconstructed from the one cached latent).

FLOPs. The head-sharing schemes barely change attention compute. The score and matmuls sum over the full model width regardless of (the shared K/V head is broadcast to all query heads), so prefill FLOPs are ~unchanged; only the tiny K/V projection GEMMs shrink (from to each). MLA adds up-projection work, but weight absorption folds it into the existing / GEMMs, so its inference FLOPs stay comparable while its bytes moved during decode drop sharply — and decode is memory-bound (lesson 12), so fewer KV bytes is the win. The upshot across all four: the lever is memory/bandwidth, not FLOPs.

Play with it

The Attention FLOPs & KV-cache calculator has an MHA / GQA / MQA / MLA comparison built in (the “KV cache: MHA vs GQA vs MQA vs MLA” table, with an MLA latent d_c field). Set a model’s , , , , pick for GQA and for MLA, and watch the four cache sizes and their ratios-to-MHA update live. Try , , , and confirm MLA lands below GQA.


6. Practice


Read the three primary sources in order of the arc: Shazeer (MQA), “Fast Transformer Decoding: One Write-Head is All You Need”1; Ainslie et al. (GQA)2 — §2–3 for the uptraining recipe and the quality/ ablation; DeepSeek-V23 — §2.1 for MLA, weight absorption, and decoupled RoPE (the crux). DeepSeek-V34 shows MLA at larger scale ([verify] exact config details against the report). For how these cache reductions cash out in a serving stack (PagedAttention, batch sizing, KV quantization), see 12-llm-inference-systems.

Ask your teacher

Fuzzy on the weight-absorption algebra, the non-commutativity of RoPE with absorption, or how to reason about the cache/quality frontier for a specific model? Ask — I’m your teacher for this course and can rederive any step, sketch the absorbed-matrix shapes explicitly, or build you a harder cache-sizing problem.

What’s next

  • 06-sparse-and-sliding-window — the other axis of attention scaling: instead of shrinking the per-token cache, shrink how many tokens each query attends to (sliding-window, sparse, and hybrid patterns).
  • Back to 04-quadratic-bottleneck-and-io-aware — the quadratic-compute and IO-aware view that motivates why we care about the memory side at all.

References

Reference: attention-glossary | Topic: attention-mechanisms | Filed: 2026-09-10

Footnotes

  1. Shazeer, “Fast Transformer Decoding: One Write-Head is All You Need” — arXiv:1911.02150 (2019). Introduces multi-query attention (): all query heads share a single K/V head; ~ smaller KV cache, with a quality/stability cost on large models. [established] ↩ ↩2

  2. Ainslie et al., “GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints” — arXiv:2305.13245 (2023). Grouped-query attention (), the quality/memory sweet spot; the mean-pool + ~5%-compute uptraining recipe to convert MHA checkpoints; the quality-vs- ablation. The standard KV reduction in Llama-2/3-class models. [established] ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  3. DeepSeek-AI, “DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model” — arXiv:2405.04434 (2024). Introduces Multi-head Latent Attention (MLA): low-rank joint KV compression to a cached latent , weight absorption of up-projections, and decoupled RoPE. Reports cache equal to GQA-2.25 while matching/exceeding MHA quality (, , ). [recent] ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  4. DeepSeek-AI, “DeepSeek-V3 Technical Report” — arXiv:2412.19437 (2024). Carries MLA forward at larger scale (alongside DeepSeekMoE). [verify] exact MLA dimensions against the report before citing specific numbers. ↩ ↩2