MODULE 11  Β·  Tokenizers & Data Formats

Attention Variants: MHA, MQA, GQA β€” Speed vs Quality at Scale

The original Multi-Head Attention has a dirty secret: it's a memory hog. MQA and GQA fix it β€” with tradeoffs.

πŸ“… Mar 2026
⏱ 10 min read
🎯 Episode 74 of 98
AttentionMHAGQAArchitecture
In this episode

The attention mechanism is the heart of every transformer. It's the reason GPT can write poetry, Llama can code, and Gemini can understand images. But the original design β€” Multi-Head Attention (MHA) β€” has a dirty secret.

It's a memory hog.

For every token the model generates, MHA stores a key-value pair for every head in every layer. At 128K context, that's gigabytes of memory just for the cache. Not the model weights β€” just the conversation history.

So researchers asked: do we really need all those key-value pairs? The answer created two variants that every modern model now uses: MQA and GQA.

Quick Refresher: How Attention Works

If you need the full deep dive, revisit Episode 2. Here's the essential mechanic:

For each token, attention computes three vectors:

snippet
code
Query (Q): "What am I looking for?"
Key (K): "What do I contain?"
Value (V): "What information do I carry?"

Attention score = how well a Query matches each Key. The output = weighted sum of Values based on those scores.

snippet
code
Attention(Q, K, V) = softmax(QK^T / √d) Γ— V

Multi-Head Attention (MHA)

Instead of one Q/K/V per token, MHA uses multiple heads β€” each looking at different aspects of the relationship:

Head 1: Q₁, K₁, V₁ β†’ focuses on syntax ("subject-verb agreement")

Head 2: Qβ‚‚, Kβ‚‚, Vβ‚‚ β†’ focuses on semantics ("related concepts")

Head 3: Q₃, K₃, V₃ β†’ focuses on position ("nearby words")

...

Head 32: Q₃₂, K₃₂, V₃₂ β†’ focuses on something else

Each head has its own Q, K, and V projection matrices. The outputs are concatenated and projected back.

For a model with h = 32 heads and d = 128 dimensions per head:

Each token generates:

⚑

The KV Cache Problem

During inference, the model generates tokens one at a time. Each new token needs to attend to all previous tokens. To avoid recomputing K and V for every previous token, we cache them β€” the KV cache.

snippet
code
Token 1: compute K₁, V₁ β†’ store in cache
Token 2: compute Kβ‚‚, Vβ‚‚ β†’ store in cache, attend to [K₁,Kβ‚‚], [V₁,Vβ‚‚]
Token 3: compute K₃, V₃ β†’ store in cache, attend to [K₁,Kβ‚‚,K₃], [V₁,Vβ‚‚,V₃]

...

Token 128000: attend to ALL 128K previous K,V pairs

How Big Is This Cache?

For Llama 3 70B (MHA, if it used it):

KV cache per token = 2 (K+V) Γ— num_layers Γ— num_heads Γ— head_dim Γ— bytes_per_param

example
code
= 2 Γ— 80 Γ— 64 Γ— 128 Γ— 2 (FP16)
                   = 2,621,440 bytes per token
                   = 2.5 MB per token

At 128K context:

= 2.5 MB Γ— 128,000

snippet
code
= 320 GB just for KV cache!
That's 320 GB β€” more than the model weights themselves. For a single conversation. You literally can't serve it.

This is the problem MQA and GQA solve.

πŸ”§

Multi-Query Attention (MQA)

Paper: "Fast Transformer Decoding" (Noam Shazeer, 2019) Used by: Falcon, PaLM, StarCoder

The idea is brutal in its simplicity: share one K and V across all query heads.

MHA (original):

Head 1: Q₁, K₁, V₁ ← each head has its own K, V

Head 2: Qβ‚‚, Kβ‚‚, Vβ‚‚

Head 3: Q₃, K₃, V₃

...

Head 32: Q₃₂, K₃₂, V₃₂

Total KV: 32 K's + 32 V's = 64 vectors per token

MQA:

Head 1: Q₁, K_shared, V_shared ← ALL heads share ONE K, V

Head 2: Qβ‚‚, K_shared, V_shared

Head 3: Q₃, K_shared, V_shared

...

Head 32: Q₃₂, K_shared, V_shared

Total KV: 1 K + 1 V = 2 vectors per token

The Impact

MetricMHA (32 heads)MQA (1 KV group)Reduction
KV vectors per token64232x smaller
KV cache at 128K320 GB10 GB32x smaller
Inference speedBaseline~2-3x fasterMemory bandwidth freed
QualityBestSlightly worseSmall drop

MQA gives you a 32x reduction in KV cache memory. That's the difference between "impossible to serve" and "fits on one GPU."

But there's a cost. Having only one K/V pair means all heads see the same keys and values β€” they lose the ability to attend to different aspects of the input. Quality drops, especially on complex reasoning tasks.

πŸ“Š

Grouped-Query Attention (GQA)

Paper: "GQA: Training Generalized Multi-Query Attention" (Ainslie et al., 2023, Google) Used by: Llama 3, Mistral, Gemma 2, Phi-3, Qwen 2, DeepSeek V3

GQA is the Goldilocks solution: group the heads and share K/V within each group.

MHA (32 heads, 32 KV groups):

Group 1: Q₁, K₁, V₁

Group 2: Qβ‚‚, Kβ‚‚, Vβ‚‚

...

Group 32: Q₃₂, K₃₂, V₃₂

MQA (32 heads, 1 KV group):

Group 1: Q₁-Q₃₂, K_shared, V_shared

GQA (32 heads, 8 KV groups):

Group 1: Q₁-Qβ‚„, K₁, V₁ ← 4 query heads share 1 KV

Group 2: Qβ‚…-Qβ‚ˆ, Kβ‚‚, Vβ‚‚ ← 4 query heads share 1 KV

Group 3: Q₉-Q₁₂, K₃, V₃

...

Group 8: Q₂₉-Q₃₂, Kβ‚ˆ, Vβ‚ˆ

Total KV: 8 K's + 8 V's = 16 vectors per token

GQA: The Numbers

MetricMHA (32 KV)GQA-8 (8 KV)MQA (1 KV)
KV vectors/token64162
KV cache at 128K320 GB80 GB10 GB
Cache reduction1x4x32x
QualityBestNear-MHASlightly worse
Inference speedSlowestFastFastest

GQA-8 (8 groups for 32 query heads) gives you 4x memory reduction with virtually no quality loss. That's the sweet spot β€” and it's why every major model released in 2024-2025 uses GQA.

Visual Comparison

MHA (Multi-Head Attention) β€” Every head has its own K,V

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Q₁→K₁,V₁ Qβ‚‚β†’Kβ‚‚,Vβ‚‚ Q₃→K₃,V₃ Qβ‚„β†’Kβ‚„,Vβ‚„ β”‚

β”‚ Qβ‚…β†’Kβ‚…,Vβ‚… Q₆→K₆,V₆ Q₇→K₇,V₇ Qβ‚ˆβ†’Kβ‚ˆ,Vβ‚ˆ β”‚

β”‚ ... (32 KV pairs total) ... β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

GQA (Grouped-Query) β€” Groups of heads share K,V

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Q₁ Qβ‚‚ Q₃ Qβ‚„ β†’ K₁,V₁ (Group 1) β”‚

β”‚ Qβ‚… Q₆ Q₇ Qβ‚ˆ β†’ Kβ‚‚,Vβ‚‚ (Group 2) β”‚

β”‚ ... (8 KV pairs total) ... β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MQA (Multi-Query) β€” ALL heads share one K,V

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Q₁ Qβ‚‚ Q₃ Qβ‚„ Qβ‚… Q₆ ... Q₃₂ β†’ K₁,V₁ β”‚

β”‚ (1 KV pair total) β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘

Real-World Model Configurations

Model Attention Query Heads KV Groups KV Heads Ratio

GPT-3 MHA 96 96 96 1:1

Falcon-40BMQA641164:1
Llama 2 7BMHA3232321:1
Llama 2 70BGQA64888:1
Llama 3 8BGQA32884:1
Llama 3 70BGQA64888:1
Mistral 7BGQA32884:1
Gemma 2 27BGQA3216162:1
DeepSeek V3MLA128β€”β€”Custom

Notice: Llama 2's small model (7B) could afford MHA. The 70B model needed GQA. By Llama 3, even the 8B model uses GQA β€” because context lengths got longer (8K β†’ 128K), making KV cache the bottleneck even for small models.

πŸ”¬

The Math: Why GQA Barely Hurts Quality

Intuitively, it seems like sharing K/V would lose information. But the research shows otherwise:

Many heads learn redundant patterns β€” when you analyze what different heads attend to, there's massive overlap

K/V contains less unique info than Q β€” the query is "what am I looking for" (unique per head), while K/V is "what's in this position" (more shared)

Training adapts β€” when you train a model from scratch with GQA, the remaining KV heads learn to be more general

The quality difference between MHA and GQA on benchmarks:

MMLU (knowledge): MHA: 79.5% GQA-8: 79.3% (βˆ’0.2%)

snippet
code
HumanEval (code):   MHA: 67.1%  GQA-8: 66.8%  (βˆ’0.3%)

GSM8K (math): MHA: 84.2% GQA-8: 83.9% (βˆ’0.3%)

Less than 0.5% drop for 4x memory savings. That's a no-brainer.

πŸ›‘οΈ

Try It Yourself

python

import torch

import torch.nn as nn

class GroupedQueryAttention(nn.Module):

example
code
"""GQA implementation β€” the attention used in modern LLMs."""
example
code
def __init__(self, d_model=512, n_heads=8, n_kv_heads=2):
        super().__init__()
        self.n_heads = n_heads
        self.n_kv_heads = n_kv_heads
        self.head_dim = d_model // n_heads
        self.n_rep = n_heads // n_kv_heads  # How many Q heads per KV group
example
code
self.wq = nn.Linear(d_model, n_heads * self.head_dim, bias=False)
        self.wk = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=False)
        self.wv = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=False)
        self.wo = nn.Linear(n_heads * self.head_dim, d_model, bias=False)
example
code
def forward(self, x):
        B, T, _ = x.shape
example
code
# Project Q, K, V
        q = self.wq(x).view(B, T, self.n_heads, self.head_dim)
        k = self.wk(x).view(B, T, self.n_kv_heads, self.head_dim)
        v = self.wv(x).view(B, T, self.n_kv_heads, self.head_dim)
example
code
# Repeat K, V to match number of Q heads
        # This is the GQA trick β€” expand KV to match Q heads
        k = k.repeat_interleave(self.n_rep, dim=2)  # (B, T, n_heads, head_dim)
        v = v.repeat_interleave(self.n_rep, dim=2)
example
code
# Standard attention
        q, k, v = [x.transpose(1, 2) for x in (q, k, v)]  # (B, n_heads, T, head_dim)
        scores = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5)
        attn = torch.softmax(scores, dim=-1)
        out = torch.matmul(attn, v)
example
code
out = out.transpose(1, 2).contiguous().view(B, T, -1)
        return self.wo(out)

Compare parameter counts

snippet
code
mha = GroupedQueryAttention(d_model=512, n_heads=8, n_kv_heads=8)  # MHA
gqa = GroupedQueryAttention(d_model=512, n_heads=8, n_kv_heads=2)  # GQA
mqa = GroupedQueryAttention(d_model=512, n_heads=8, n_kv_heads=1)  # MQA

for name, model in [("MHA", mha), ("GQA", gqa), ("MQA", mqa)]:

example
code
kv_params = sum(p.numel() for n, p in model.named_parameters() if 'wk' in n or 'wv' in n)
    print(f"{name}: KV parameters = {kv_params:,}")

MHA: KV parameters = 524,288

GQA: KV parameters = 131,072 (4x smaller)

MQA: KV parameters = 65,536 (8x smaller)

πŸ“¦

Practical Takeaways

MHA gives every head its own KV β€” highest quality, highest memory, doesn't scale to long contexts

MQA shares one KV across all heads β€” 32x smaller cache, but measurable quality drop

GQA groups heads to share KV β€” the sweet spot, ~4x smaller cache, negligible quality loss

KV cache is the bottleneck at long contexts β€” at 128K tokens, it dwarfs the model weights

Every major 2024-2025 model uses GQA β€” Llama 3, Mistral, Gemma 2, Qwen 2, Phi-3

The ratio matters β€” 4:1 (Llama 3 8B) to 8:1 (Llama 3 70B) is the current sweet spot

πŸš€

What's Next?

Episode 75: FlashAttention β€” Even with GQA reducing KV cache size, the attention computation itself is a bottleneck. FlashAttention rewrites the algorithm to be IO-aware β€” understanding the GPU's memory hierarchy to run 2-4x faster while using 5-20x less memory. It's the single most impactful optimization in modern LLM inference.

← Previous

Ep 73: Model Formats

Next β†’

Ep 75: FlashAttention

Next: Episode 75 β€” FlashAttention

Standard attention wastes 90% of its time waiting for data to move between memory chips. Not computing. Waiting.

This is part of a 98-episode series covering AI engineering from tokens to production deployment.

← Previous Ep 73: Model Formats: GGUF, SafeTensors, ONNX β€” The File Behin…