Inference engineeringLesson 03

Attention, MLPs and Two Shapes

A serving-level tour of the transformer: what each block does, why attention needs a cache, and how the same weights produce two completely different performance regimes.

Foundation22 min readUpdated November 2026

Attention, MLPs and Two Shapes

You do not need to derive backpropagation to serve a transformer. You need to know which tensors exist, which ones grow, and which ones get read on every single token.

One block at a time

A decoder-only transformer is a stack of identical blocks. Each block has two main parts:

  1. Self-attention. Project the hidden state into queries, keys and values. Compare each query with the keys of current and previous positions. Use the resulting weights to mix the values. Project the result back to the model dimension.
  2. MLP. A position-wise feed-forward network, usually two linear layers with a nonlinearity between them. For a model with hidden size dd and intermediate size 4d4d, the MLP holds the majority of the parameters.

Each part has a residual connection and a normalization layer. At the end of the stack, a language-model head maps the final hidden state to logits over the vocabulary.

For serving, the important tensors are:

TensorShape (per layer)Grows withRead every decode step?
Q projectiond×dd \times dmodel sizeyes
K projectiond×dkvd \times d_{kv}model sizeyes
V projectiond×dkvd \times d_{kv}model sizeyes
Output projectiond×dd \times dmodel sizeyes
MLP up/gated×4dd \times 4dmodel sizeyes
MLP down4d×d4d \times dmodel sizeyes
KV cachelayers × 2 × kv heads × head dimcontext × concurrencyyes

The weights are fixed. The KV cache is the only tensor that grows while the request runs.

The attention computation

For a single attention head, with query matrix QQ, key matrix KK and value matrix VV:

Attention(Q,K,V)=softmax ⁣(QK⊤dk)V.\mathrm{Attention}(Q,K,V) = \mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V.

The scaling by dk\sqrt{d_k} keeps the dot products from growing with head dimension. A causal mask prevents each position from attending to future positions.

In prefill, QQ, KK and VV all contain TT prompt tokens. The attention matrix is T×TT \times T. Computing it costs roughly O(T2d)O(T^2 d) and, if materialized, O(T2)O(T^2) memory. For a 32K-token prompt, that is a billion entries per head if you write the full matrix to HBM. FlashAttention avoids materializing it by tiling the computation and keeping blocks in SRAM.

In decode, QQ has one token, while KK and VV contain the entire prefix. The attention matrix is 1×T1 \times T. The math per step is linear in context length, but the KV cache must be read from HBM. That read is a bandwidth cost, and it recurs for every token.

Why the cache exists

Without a cache, generating token tt would require recomputing keys and values for all t−1t-1 previous tokens. The total work for NN output tokens would be O(N2)O(N^2) in the number of forward passes, not just attention FLOPs. The KV cache stores the keys and values after they are computed, so each decode step only needs to compute the new token’s KK and VV and append them.

The price is memory. For a model with LL layers, HkvH_{kv} KV heads, head dimension dhd_h, and bb bytes per element, one token costs

Mkv=L×2×Hkv×dh×b bytes.M_{\text{kv}} = L \times 2 \times H_{kv} \times d_h \times b\ \text{bytes}.

For Llama 3.1 8B in BF16 — 32 layers, 8 KV heads, head dimension 128 — that is

32×2×8×128×2=131,072 bytes≈128 KiB per token.32 \times 2 \times 8 \times 128 \times 2 = 131{,}072\ \text{bytes} \approx 128\ \text{KiB per token}.

At 8K context, one request needs about 1 GiB of KV cache. At 32K, about 4 GiB. Multiply by concurrency and the cache becomes a first-class capacity constraint. This is the subject of the next lesson.

Figure 3.1Transformer serving view
The transformer from a serving perspectiveTokens go through embedding layers, repeated attention and MLP blocks, then a language-model head. Attention writes keys and values to the KV cache during prefill and reads them during decode.The only parts serving cares aboutTOKENS → EMBED → N × (ATTENTION + MLP) → LM HEAD → LOGITSINPUTtoken IDsB × TEMBEDvectorsB × T × HN TRANSFORMER LAYERSattention + MLPresidual + norm each blockweights dominate memoryLM HEADvocab logitsH → VSAMPLE / VERIFYnext tokenthen loop backINSIDE ONE ATTENTIONQ · K · VQ = current tokenK, V = cached prefixKV cache lives herePREFILL VS DECODESame weights, different shapeprefill: T prompt tokens in paralleldecode: 1 token, read all weights + KV→ compute-bound vs bandwidth-boundA 70B model has roughly 70 billion weights. Every decode step reads them again.
The decoder stack from a serving perspective. The KV cache is written during prefill and read during every decode step.

Grouped-query attention changes the budget

Standard multi-head attention (MHA) has one key/value head per query head. Multi-query attention (MQA) shares a single key/value head across all query heads. Grouped-query attention (GQA) sits between them: query heads are divided into groups, and each group shares one key/value head.

GQA reduces the KV cache by the ratio of query heads to KV heads. Llama 3.1 8B has 32 query heads and 8 KV heads, so its cache is one quarter the size of an MHA equivalent. That is why modern long-context models can serve reasonable concurrency at all.

The tradeoff is quality and implementation complexity. Fewer KV heads mean less representational capacity in the cache. MQA is the most aggressive; GQA is the common compromise. Some architectures go further: multi-head latent attention (MLA) compresses keys and values into a latent vector, shrinking the cache substantially at the cost of more involved kernels. DeepSeek-V2 and V3 use this approach.

For a serving engineer, the practical rule is simple: the KV cache formula is part of the model card you should care about. Parameter count alone does not tell you how many concurrent users fit.

Counting FLOPs, roughly

For a dense decoder with NN parameters:

  • Prefill over TT tokens costs roughly 2NT2NT FLOPs for the linear layers, plus attention terms that grow with T2T^2.
  • Decode for one token costs roughly 2N2N FLOPs for the linear layers, plus attention work proportional to the cached context.

The factor of 2 is multiply-add. These are order-of-magnitude estimates; they ignore embeddings, normalization, softmax and masking. For MoE models, replace NN with the number of active parameters per token, not the total parameter count.

At batch size BB, decode linear-layer FLOPs become roughly 2NB2NB, while the weight bytes read remain roughly 2N2N in BF16. That is the arithmetic-intensity argument from the previous lesson, expressed in transformer terms.

Two shapes, one set of weights

It is tempting to treat prefill and decode as separate systems. They are not. They share weights, kernels and often the same GPU. But their shapes differ enough that optimizations rarely transfer:

PropertyPrefillDecode
Tokens per forward passTT (prompt)1 per request
Dominant costcomputememory bandwidth
Attention matrixT×TT \times T1×T1 \times T
Weight reusehighlow at small batch
Latency metricTTFTTPOT / ITL
Typical optimizationfused GEMMs, FlashAttentionbatching, quantization, KV compression

This is why a serving engine schedules them differently, and why disaggregated serving — running prefill and decode on separate pools — is an active design. The two phases want different hardware ratios and different parallelism.

What this means for a real deployment

If your workload is long prompts and short outputs (RAG, classification, code review), prefill dominates. Invest in compute, prompt caching, and avoiding duplicate work. If your workload is short prompts and long outputs (chat, creative writing, chain-of-thought), decode dominates. Invest in bandwidth, batching, KV-cache efficiency, and possibly speculative decoding.

If you have both, measure the mix. A single “average latency” number hides the fact that your RAG requests and your chat requests are stressing different parts of the machine.

Next: the KV cache in detail — how it grows, how it fragments, and why managing it is most of what a modern serving engine does.

Sources and further reading

One question before you go

retrieval check
What is the key computational difference between prefill and decode?
Why: Prefill sees the whole prompt and is usually compute-bound. Decode is autoregressive and usually memory-bound, reusing the KV cache.
Type at least two characters. Search is indexed after the production build.
Lessons · labs · glossary↑↓ to browse · esc to close