Abstract

Autoregressive decoding is cheap only because it does not recompute the past: every generated token reuses the keys and values of every token before it. Those tensors are the KV cache, and its size follows from six numbers with no slack in the derivation. This article works out that formula, shows why the cache — not the weights — sets the batch-size ceiling at long context, and derives the arithmetic intensity that makes decode memory-bound. It then examines the architectural response: multi-query and grouped-query attention are two points on the HkvH_{kv} axis, and multi-head latent attention is a different move that leaves the head count alone and compresses what is stored instead. Finally it measures what those variants actually buy, and finds that an eight-times reduction in bytes returns about 2.4× in latency. Two candidate explanations are tested and rejected — access granularity and parallelism — leaving the work done per byte as the cause. It assumes the decoding loop from LLM Inference Introduction.

1. Why the Cache Exists

Decoding generates one token at a time. Producing token t+1t+1 requires attention over all previous positions, which needs their keys and values — and those depend only on tokens already fixed, so they never change once computed.

Without a cache, generating the token at step tt re-runs attention over all tt previous positions, doing O(t2d)O(t^2 d) attention work and re-reading every weight matrix each step. Over a run of TT tokens that sums to O(T3d)O(T^3 d) — cubic, not merely quadratic. With a cache, each step computes one new key and value, appends them, and attends over stored state, which brings the run down to O(T2d)O(T^2 d). The cache does not shave a constant factor off a recomputation; it changes the complexity class, trading a linear amount of storage for it.

That is the trade the rest of this article is about: the storage is not small.

2. Sizing the Cache

Each layer stores one key vector and one value vector per position per KV head. For a model with LL layers, HkvH_{kv} key-value heads, head dimension dhd_h, and bb bytes per element, a sequence of TT tokens costs

KV bytes  =  2⏟K and V⋅L⋅Hkv⋅dh⋅T⋅b. \text{KV bytes} \;=\; \underbrace{2}_{K \text{ and } V} \cdot L \cdot H_{kv} \cdot d_h \cdot T \cdot b .

Everything except TT is fixed by the architecture, so the useful form is bytes per token:

bytes/token  =  2⋅L⋅Hkv⋅dh⋅b, \text{bytes/token} \;=\; 2 \cdot L \cdot H_{kv} \cdot d_h \cdot b ,

and total cost is that constant times the number of live tokens, summed over every sequence in the batch.

Worked examples. In BF16 (b=2b = 2):

ModelLLHkv×dhH_{kv} \times d_hBytes/token8K context×32 sequences
7B, multi-head3232×12832 \times 128512 KB4.3 GB137 GB
70B, multi-head (hypothetical)8064×12864 \times 1282.5 MB21.5 GB687 GB
70B, grouped-query (8 groups)808×1288 \times 128320 KB2.7 GB86 GB

The middle row is the reason grouped-query attention exists. A 70B model in BF16 has 140 GB of weights, which already needs two 80 GB GPUs; with multi-head attention, one 8K-token sequence would add 21.5 GB, and a batch of 32 would need 687 GB of cache — nearly five times the weights. The bottom row is the same model as actually shipped: the cache falls to an eighth, so that batch of 32 needs 86 GB, which one additional GPU covers.

Three properties of the formula are worth stating plainly.

It is linear in context length. Doubling the context doubles the cache. There is no reuse to exploit; every position must be retained.

It is linear in batch size, with no sharing. Two sequences share weights but never share cache — except for a common prefix, which is exactly the case PagedAttention makes exploitable.

It does not depend on the number of query heads. Only HkvH_{kv} appears. That is the loophole the next section exploits.

The 512 KB figure in the first row is not just arithmetic. Allocating one layer’s cache for one token as a (2, 1, 32, 128) BF16 tensor gives 16,384 bytes, and multiplying by 32 layers gives 524,288 bytes — exactly 512 KB. Every capacity number later in this series is built from that constant, so it is worth confirming once that the formula and the tensor layout agree.

3. Why Decode Is Memory-Bound

The cache is not just a capacity problem. It is read in its entirety on every decode step, and that read is what sets the speed.

Consider one layer of one decode step. Attention over TT cached positions performs HqTdhH_q T d_h multiply-accumulates for the scores and the same again for the value-weighted sum, so

FLOPs=4 Hq T dh,bytes read=2 Hkv T dh b. \text{FLOPs} = 4\,H_q\,T\,d_h , \qquad \text{bytes read} = 2\,H_{kv}\,T\,d_h\,b .

Their ratio is the arithmetic intensity of attention at decode time:

Idecode=4HqTdh2HkvTdhb=2b⋅HqHkv. I_{\text{decode}} = \frac{4 H_q T d_h}{2 H_{kv} T d_h b} = \frac{2}{b}\cdot\frac{H_q}{H_{kv}} .

The context length cancels. What survives is the ratio of query heads to KV heads — the group size — divided by the element width. In BF16 with multi-head attention, where Hq=HkvH_q = H_{kv}, the intensity is exactly 1 FLOP per byte, against a ridge point of roughly 295 on an H100 (2,000 TFLOP/s BF16 over 3.35 TB/s HBM). Attention at decode time runs at about a three-hundredth of the machine’s arithmetic capability, and no amount of kernel tuning changes that; the operator is reading memory, not computing. The ratio 2g/b2g/b is hardware-independent; only the ridge point it is compared against changes from one GPU to the next.

That ratio is measurable rather than merely derivable. On a B300 (2,250 TFLOP/s BF16, 8 TB/s HBM, so a ridge point of 281 FLOP/byte), a paged decode kernel with Hq=Hkv=8H_q = H_{kv} = 8 and dh=128d_h = 128 computes at an arithmetic intensity of 1.0 FLOP/byte at every batch size and context length tested, and reaches at most 75% of HBM peak — while prefill at the same head dimension reaches 78% of compute peak.

OperationShapeTFLOP/sGB/sFLOP/byte% of peak
prefillL=16,3841,7534284,09677.9% (compute)
decodeB=1, kv=4,0960.54641.05.8%
decodeB=32, kv=4,0965.45,4001.067.5%
decodeB=32, kv=16,3846.06,0131.075.2%
Roofline plot on log axes: prefill points climb the compute roof toward 78 percent of peak, while all decode points sit on a vertical line at an arithmetic intensity of 1 FLOP per byte, far left of the ridge point.
Figure 1. Measured placement of prefill and decode on a B300 roofline. Decode's intensity is pinned at 1 FLOP/byte regardless of batch or context, landing two orders of magnitude left of the ridge point. The vertical spread within that column is the entire headroom available to a serving engine — and it can only be closed by moving more bytes per second, never by doing the arithmetic faster.

One consequence is easy to get wrong. Increasing the batch size does not change the intensity. Batching amortizes weight reads across sequences, which is why it works so well for the feed-forward layers, but every sequence has its own KV cache, so the attention read scales with the batch exactly as the arithmetic does. Attention stays at the same intensity no matter how many requests are in flight.

What batching does change is whether the memory system is actually saturated. Those are different statements, and the distinction matters. Measured on the same kernel, at a context length of 16,384:

BatchAchieved read bandwidth% of HBM peakPer-request cost
11,838 GB/s23%583 µs
85,621 GB/s70%77 µs
326,013 GB/s75%11 µs

Intensity never leaves 1.0 FLOP/byte, yet throughput improves 51× per request. The intensity fixes the ceiling — you can never escape the memory roof — while the batch size decides how close to that ceiling you get. A single sequence leaves the HBM channels mostly idle; thirty-two concurrent sequences keep them busy. This is why serving throughput is a scheduling problem, not just a kernel problem.

Log-log plot of achieved read bandwidth against batch size for two context lengths, rising from about a quarter of peak at batch one towards three quarters at batch thirty-two, with the HBM peak drawn as a horizontal reference line.
Figure 2. Achieved read bandwidth against batch size, with the HBM peak drawn for reference. The same kernel, at the same arithmetic intensity, moves three times more of the machine's bandwidth at batch 32 than at batch 1. Intensity sets the roof; occupancy of the memory system decides how close you run to it.

4. The HkvH_{kv} axis: MHA, MQA, GQA

The three variants in this section are one idea at three strengths: reduce how many key-value heads are stored. Nothing else in the formula is available — LL and dhd_h are the model, TT is the request, bb is the storage format — so HkvH_{kv} is the only quantity left to move. A fourth variant that moves something else follows in the next section.

Eight query heads mapped to eight key-value heads under multi-head attention, to two under grouped-query attention, and to one under multi-query attention.
Figure 3. The same eight query heads under three schemes. Multi-head attention gives each query head private keys and values; multi-query attention gives all of them one shared pair; grouped-query attention partitions them into groups that share within the group. Only the boxes in the lower row are stored per token, so the cache shrinks in proportion to how many there are.

4.1 Multi-head attention (MHA)

Every query head gets its own key and value. Eight query heads, eight KV heads, eight caches per token:

  q0   q1   q2   q3   q4   q5   q6   q7      query heads (H_q = 8)
   |    |    |    |    |    |    |    |
  k0   k1   k2   k3   k4   k5   k6   k7      KV heads  (H_kv = 8)
   |    |    |    |    |    |    |    |
  c0   c1   c2   c3   c4   c5   c6   c7      stored per token

  group size g = 1        KV heads stored = 8        intensity = 2/b

The original: Hkv=HqH_{kv} = H_q, every head with its own projection. Maximum expressiveness, maximum cache. For the 70B model above it is 2.5 MB per token, which is unaffordable at any serious batch size or context length.

4.2 Multi-query attention (MQA)

Keep all eight query heads, share a single key and value among them:

  q0   q1   q2   q3   q4   q5   q6   q7      query heads (H_q = 8)
   |    |    |    |    |    |    |    |
   +----+----+----+----+----+----+----+---->
                        |
                       k0                    KV heads  (H_kv = 1)
                        |
                       c0                    stored per token

  group size g = 8        KV heads stored = 1        intensity = 16/b

Proposed in 2019 for exactly this reason: keep all HqH_q query heads but project a single shared key and value. The cache falls by a factor of HqH_q — 64× for the model above — and decode intensity rises by the same factor.

The cost is quality. Collapsing to one KV head is a real reduction in what attention can represent, and models trained this way show measurable degradation and, in some reports, training instability. MQA is the right answer when memory is the binding constraint and the quality loss is acceptable.

4.3 Grouped-query attention (GQA)

The interpolation, and now the default. Partition the query heads into groups; every head within a group shares one key-value pair. With two groups:

  q0   q1   q2   q3   q4   q5   q6   q7      query heads (H_q = 8)
   |    |    |    |    |    |    |    |
   +----+----+    |    |    +----+----+
        |         |    |         |
       k0         |    |        k1           KV heads  (H_kv = 2)
        |         |    |         |
       c0                     c1             stored per token

  group size g = 4        KV heads stored = 2        intensity = 8/b

  g = 1  <-----------------+-----------------> g = H_q
     MHA                  GQA                   MQA
                        (g = 8 typical)

At g=8g = 8, the cache is one eighth of MHA and the intensity is eight times better, with quality close to MHA.

Two practical properties explain its adoption. First, the natural choice is HkvH_{kv} equal to the tensor-parallel degree, so each GPU holds exactly one KV head and no cache is replicated across devices. Second, an existing MHA checkpoint can be converted rather than retrained: mean-pool the key and value projections within each group and continue training for a small fraction of the original budget. Llama 2’s 70B model, Mistral, and most models released since use GQA.

4.4 The variants side by side

VariantCached per token per layerRelative cacheDecode intensityChoose when
MHA2Hqdhb2 H_q d_h b112/b2/bcontexts are short and quality dominates
GQA, gg groups2(Hq/g)dhb2 (H_q/g) d_h b1/g1/g2g/b2g/bthe default; pick gg = tensor-parallel degree
MQA2dhb2 d_h b1/Hq1/H_q2Hq/b2H_q/bmemory is the binding constraint and you can retrain

MLA is deliberately absent from that table; it is not on this axis and gets its own section next.

The formulas are worth applying to one concrete model rather than three separate ones. The 70B shape from §2 (L=80L = 80, Hq=64H_q = 64, dh=128d_h = 128, BF16) at 8K context:

VariantHkvH_{kv}Cache/tokenOne 8K sequence×32 sequencesIntensity
MHA642.5 MB21.5 GB687 GB1.0
GQA, g=8g{=}88320 KB2.7 GB86 GB8.0
MQA140 KB335 MB10.7 GB64.0

The last column is the point of the whole article. Going from MHA to GQA makes the cache eight times smaller and the intensity eight times higher, so the same kernel reads an eighth as many bytes while the ratio of arithmetic to bytes rises by the same factor. The bytes are what cost time, so capacity and speed improve together.

But only up to a point, and the limit is worth stating here rather than leaving to the measurement section: an eight-times reduction in bytes does not buy an eight-times reduction in latency. Measured, it buys about 2.4×.

Bars comparing kilobytes of KV cache per token for multi-head, eight-group grouped-query, multi-query, and latent attention on an eighty-layer model.
Figure 4. Cache per token for an 80-layer, 64-head model in BF16, at $d_h = 128$. The vertical axis of the problem is capacity and bandwidth at once: each bar is simultaneously what a token costs to store and what it costs to re-read on every subsequent decode step.

4.5 What the variants do to the weights

Everything so far has been about the cache. The variants also change the model’s parameter shapes, and they do so asymmetrically — which is the part that is easy to get wrong. Only the key and value projections shrink. The query and output projections keep their full width in every variant.

With D=HqdhD = H_q d_h the model width:

ProjectionMHAGQA, gg groupsMQA
WQW_QD×DD \times DD×DD \times DD×DD \times D
WKW_KD×DD \times DD×DgD \times \frac{D}{g}D×dhD \times d_h
WVW_VD×DD \times DD×DgD \times \frac{D}{g}D×dhD \times d_h
WOW_OD×DD \times DD×DD \times DD×DD \times D

The reason is structural rather than incidental. There are always HqH_q query heads, so WQW_Q must emit Hqdh=DH_q d_h = D columns, and WOW_O must consume DD and return DD. Only the K and V projections emit one column block per KV head, and there are fewer of those.

It is worth being precise here because the phrasing “each head has its own WQW_Q, WKW_K, WVW_V” invites a wrong mental picture. In code there is one matrix, and the heads are slices of its output:

# MHA: Q, K, V all project to D columns
self.W_Q = nn.Linear(D, D)
self.W_K = nn.Linear(D, D)
Q = self.W_Q(X).view(B, L, H_q, d_h).transpose(1, 2)   # heads = column blocks

# GQA: only K and V get narrower
self.W_K = nn.Linear(D, H_kv * d_h)          # D/g columns worth
K = self.W_K(X).view(B, L, H_kv, d_h).transpose(1, 2)

Splitting one (D,D)(D, D) weight into HqH_q column blocks is exactly equivalent to having HqH_q separate (D,dh)(D, d_h) matrices, because a linear layer’s output columns are independent. It is also why production stacks fuse the three projections into one qkv_proj: one GEMM beats three.

Applied to the 70B shape, the asymmetry shows up as a real parameter difference:

VariantWKW_K shapeAttention params/layer× 80 layers
MHA (hypothetical)8192×81928192 \times 8192268 M21.5 B
GQA, g=8g{=}8 (as shipped)8192×10248192 \times 1024151 M12.1 B
MQA8192×1288192 \times 128136 M10.9 B

GQA saves 9.4 billion parameters against multi-head — roughly 13% of a 70B model. That is a larger effect than it first appears, because attention is being compared against itself; the FFN in the same layer is 704 M parameters, so GQA attention (151 M) is about a fifth of the block.

One last consequence is practical, and it explains why the checkpoint-conversion trick in §4.3 works at all. Moving down this table is a reshape: going from MHA to GQA discards columns within each group and averages what remains, which is exactly the mean-pooling operation used to initialise from an existing checkpoint. Moving up, or going to MQA, is not a reshape — 87% of WKW_K’s columns have no counterpart — so those models are trained from scratch. Shape compatibility runs one way only.

5. Compressing instead of dropping heads: MLA

The three variants above sit on a single axis: how many KV heads get stored. Multi-head latent attention is not on that axis. It leaves HkvH_{kv} alone and shrinks what is stored per head, so the parameter-shape argument above does not apply to it and neither does the group-size machinery.

Instead of reducing the number of KV heads, MLA compresses keys and values jointly into a low-rank latent vector and caches that, reconstructing per-head keys and values on the fly. The cached object is one vector of dimension dcd_c per token per layer, with dcd_c far smaller than HkvdhH_{kv} d_h, plus a small decoupled component drd_r carrying rotary position information that cannot be compressed away.

DeepSeek-V2 makes those dimensions concrete: dc=512d_c = 512 compressed plus dr=64d_r = 64 decoupled RoPE. That is 576 elements per token per layer, against 2Hkvdh=2⋅128⋅128=32,7682 H_{kv} d_h = 2 \cdot 128 \cdot 128 = 32{,}768 for an equivalent multi-head layer — about 1.8% of the naive per-layer cache. The paper reports roughly a 93% end-to-end reduction; the two figures measure different baselines, since the end-to-end number compares against a sibling model with its own layer count and head configuration rather than against a like-for-like MHA layer. The trade is arithmetic: the reconstruction is extra work on every step, and the kernel is more complex than either MHA or GQA.

  stored per token, per layer:

     MHA                     MLA
  +---------+            +---------+
  |   k0    |            |         |
  |   k1    |            |  latent |  d_c = 512   compressed K and V
  |   ...   |   ---->    |         |
  |  k_{63} |            +---------+
  |   v0    |            |  rope   |  d_r = 64    cannot be compressed
  |   ...   |            +---------+
  |  v_{63} |
  +---------+            576 elements
  32,768 elements

  Heads stay at 64. The bytes are compressed, not the head count.

The diagram marks the difference that matters for everything after this section: MHA, GQA, and MQA are all points on one axis — how many KV heads get stored — whereas MLA shrinks the representation instead, keeping the head count where it was. It is the only variant here that does not change HkvH_{kv}.

That difference extends to the weights, and in the opposite direction from the previous section. GQA removes parameters by narrowing WKW_K and WVW_V. MLA adds them: a down-projection WDKVW_{DKV} from DD to the latent width dcd_c, plus up-projections WUKW_{UK} and WUVW_{UV} back out to per-head keys and values. Fewer bytes are stored but more matrices exist. In DeepSeek-V2 those up-projections are folded algebraically into WQW_Q and WOW_O at inference time, so the extra cost is mostly a training-time and kernel-complexity cost rather than a serving one — which is why MLA can be worth it despite having more parameters than the GQA model it replaces.

6. Measuring the Variants

The formulas above predict that cutting bytes per token cuts decode time in proportion. That prediction is testable, and testing it turns out to be more informative than confirming it.

Sweep Hkv∈{8,4,2,1}H_{kv} \in \{8, 4, 2, 1\} with Hq=8H_q = 8 fixed, so the group size takes the values a real model would choose: g=1g = 1 is MHA, g=8g = 8 is MQA. Same kernel, same page size, same dtype; only the head axis of the cache changes. At a 16K context and batch 32:

HkvH_{kv}ggbytes read per steplatencyspeedupachieved GB/s% of HBM peak
8 (MHA)12048 MB0.360 ms1.00×5,96674.6%
421024 MB0.214 ms1.68×5,00862.6%
24512 MB0.177 ms2.04×3,03738.0%
1 (MQA)8256 MB0.152 ms2.38×1,77222.1%

Bytes fall by exactly the factor the formula predicts: eight. Latency falls by 2.38×. The prediction is wrong by a factor of 3.4, and it is not wrong because the machine was idle — repeating the sweep at batch 64 and 64K context, where the baseline already sustains 88.9% of peak, still yields only 2.12×.

Log-log plot: the dashed prediction line rises linearly with group size to eight times, while the measured curves flatten at roughly two to two and a half times for every context length and batch size.
Figure 5. Measured speedup against group size, with the prediction from the byte formula drawn as a straight line. The two agree at $g=1$ by construction and diverge immediately: the measured curve flattens near 2.4× where the prediction reaches 8×. Every batch size and context length tested produces the same flattening.

The reason is visible in the bandwidth column. Achieved bandwidth falls as the group size rises: from 74.6% of HBM peak at g=1g=1 to 22.1% at g=8g=8. The tensor does shrink exactly as promised; what does not shrink proportionally is the memory system’s ability to stream it.

Plot of achieved bandwidth against group size, falling steeply from about three quarters of peak at group size one to under a quarter at group size eight.
Figure 6. The mechanism. As the group size grows, the bytes a decode step must read fall eightfold, but the fraction of HBM peak the kernel can sustain falls with them — from 75% to 22%. The cache gets smaller faster than the memory system can exploit the reduction.

The obvious explanation — that narrower reads coalesce worse — turns out to be wrong, and it is worth showing the falsification rather than asserting the answer. In the standard NHD layout the contiguous run for one token is HkvdhbH_{kv} d_h b bytes, so the two candidates are access granularity and parallelism. Both can be varied independently.

Hold the head count at one and widen the contiguous run by raising dhd_h, and bandwidth does not move at all:

dhd_hcontiguous runachieved GB/s% of peak
64128 B1,64720.6%
128256 B1,77922.2%
256512 B1,74721.8%
5121024 B1,73921.7%

An eight-times wider slice changed bandwidth by one percentage point, while latency tracked bytes exactly. Access granularity is not the cause. Nor is parallelism: at fixed Hkv=1H_{kv}=1, going from 16 to 512 concurrent requests — 32× more independent streams — moved bandwidth only from 19.7% to 24.0%.

Log-log plot of latency against bytes read per decode step, showing bytes falling eightfold while latency falls only about twofold.
Figure 7. Bytes against latency, both on logarithmic axes. If the byte formula predicted latency the points would lie on a line of slope one; the measured slope is visibly shallower. The horizontal distance between the first and last point is the eight-fold byte saving, and the vertical distance is the 2.4× latency saving actually obtained.

What does track bandwidth is the work per byte. For each KV element a decode kernel reads, it performs gg dot products and gg exponentiations, so raising gg raises the arithmetic per byte in exact proportion to the byte saving. At g=8g=8 the kernel is doing eight times the work per byte that it does at g=1g=1, and the exponentiation path — not HBM — sets the pace. That is the same effect the later schedule article describes, where exponential throughput becomes limiting on hardware whose matrix units outgrew it, here showing up in a serving kernel rather than a prefill kernel.

Three consequences follow, and they are more useful than the prediction was.

This is why production GQA stops at g=8g = 8. Beyond that point the per-byte arithmetic dominates and further sharing stops paying: the last doubling of gg from 4 to 8 bought 17% of latency while costing more than half the bandwidth efficiency. That is a stronger argument for GQA-as-default than “quality is close to MHA”, and one that no model-quality study would have surfaced.

It suggests where MLA sits, though this article does not measure it. MLA reduces bytes by compression rather than by dropping heads, so its per-token record is a single 576-element latent — and with HkvH_{kv} left alone, its work per byte should be much closer to MHA than to MQA. If that holds, MLA is the one variant that takes the byte saving without the arithmetic penalty, at the cost of the reconstruction the previous section describes. Whether it does is a question for the serving article.

It generalises past attention, and it is testable. Reducing bytes while keeping the work per byte constant — quantization, layer sharing, windowing — should not pay this penalty. Reducing bytes by increasing work per byte, as group size does, should. That distinction predicts the shape of the curve for any future variant, and it is a better guide than “fewer bytes is faster”.

7. The Other Levers

Changing the architecture is not the only way to shrink the cache, and the alternatives compose with it.

Quantize it. The cache is a tensor like any other, and quantizing it to INT8 or INT4 halves or quarters it directly. The effect on both formulas is exact and worth stating: bb appears in the numerator of bytes/token and in the denominator of intensity, so moving BF16 (b=2b=2) to FP8 (b=1b=1) halves every capacity number in this article and doubles every intensity. The 512 KB constant becomes 256 KB, the 137 GB batch becomes 68 GB, and MHA’s intensity goes from 1.0 to 2.0 — still two orders of magnitude below the ridge point, which is why quantizing the cache helps but does not change the regime.

It has its own asymmetry worth knowing: keys carry channel-wise outliers and want per-channel scales, while values are well behaved under per-token scaling. At long context this is often the single largest win available, because it is orthogonal to everything else in this article.

Bound what is kept. Sliding-window attention caps the cache at the window size instead of the context length, making it constant in TT rather than linear. Attention-sink methods keep the first few positions permanently alongside a recent window, recovering most of the quality that a naive window loses. Eviction policies go further and drop positions judged unimportant.

Share across layers. Adjacent layers can be made to share one set of keys and values, dividing the cache by the sharing factor along LL — the same idea as GQA applied to the layer axis rather than the head axis.

Stop wasting what you allocate. PagedAttention does not make the cache smaller; it removes the fragmentation and over-reservation that made the allocated cache several times the used cache, and lets sequences with a common prefix share physical blocks.

8. What This Means for a Serving System

Three consequences follow from the formula and the intensity result.

The cache sets the batch-size ceiling at long context. Weights are a fixed cost paid once; cache is a per-sequence cost that grows with the context. Past a few thousand tokens the cache is the term that decides how many requests fit, which is why throughput at 32K context looks nothing like throughput at 512.

Decode speed tracks cache bytes — but sub-linearly, once work per byte rises. Since attention at decode is memory-bound with intensity 2g/b2g/b, anything that reduces bytes read per token helps: a larger group size, a compressed latent, a quantized cache, a shorter window. Anything that only reduces arithmetic does not. But as §6 measures, an eight-times reduction in bytes obtained by raising the group size yields about 2.4× in latency, not eight, because the same change multiplies the arithmetic per byte and the kernel stops saturating memory. Reducing bytes without raising work per byte is the stronger move.

Prefill and decode want different things. Prefill processes many tokens at once and is compute-bound; decode processes one and is bandwidth-bound. The KV cache is written in the first phase and read in the second, which is why serving systems increasingly schedule the two separately, and sometimes on different hardware.

9. Summary

  • KV cache size is 2LHkvdhTb2 L H_{kv} d_h T b bytes, linear in context and batch, and independent of the number of query heads.
  • Decode attention has arithmetic intensity 2g/b2g/b where g=Hq/Hkvg = H_q/H_{kv} — one FLOP per byte for multi-head attention in BF16, roughly 280× below the hardware’s ridge point. Batching does not change the intensity, though it does decide how much of the memory system’s bandwidth is actually used.
  • MQA, GQA, and MLA all reduce what has to be stored per token. The first two shrink HkvH_{kv}; MLA keeps the head count and compresses the cached representation. GQA at 8 groups is the current default because it recovers most of the memory at a small and measurable quality cost, and converts cleanly from an existing MHA checkpoint.
  • Cutting bytes per token helps decode less than proportionally when the saving comes from raising the group size. Measured over g∈{1,2,4,8}g \in \{1,2,4,8\}, an eight-times reduction in bytes returned 2.4× in latency, because the same change multiplies the arithmetic per byte and achieved bandwidth falls from 75% of peak to 22%. Tests that varied access granularity and parallelism independently ruled both out as the cause.
  • Quantization, windowing, cross-layer sharing, and paged allocation are orthogonal levers that compose with the architectural choice — and they reduce bytes without narrowing the read, so they do not pay the same penalty.

References

  1. Noam Shazeer. Fast Transformer Decoding: One Write-Head is All You Need. 2019.
  2. Joshua Ainslie et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. EMNLP 2023.
  3. DeepSeek-AI. DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model. 2024.
  4. Reiner Pope et al. Efficiently Scaling Transformer Inference. MLSys 2023.
  5. Woosuk Kwon et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP 2023.
  6. Guangxuan Xiao et al. Efficient Streaming Language Models with Attention Sinks. ICLR 2024.
  7. Zirui Liu et al. KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache. ICML 2024.