Grouped Query Attention › Memory Savings Analysis
Language AI HandbookAll chapters Grouped Query Attention › Memory Savings AnalysisOne section · Sections in this chapter5 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
GQA's memory benefits depend on the ratio of query heads to key-value heads. During autoregressive generation, the model caches key and value tensors from previous positions to avoid redundant computation. The size of this KV cache determines how many sequences can be processed in parallel and how long those sequences can be.
KV Cache Memory Formula
For a single transformer layer using GQA, the KV cache stores keys and values for all groups across all cached positions:
where:
- : accounts for storing both keys and values (two tensors per group)
- : number of key-value groups in GQA
- : dimension per head (typically 64 or 128 in modern models)
- : current sequence length (grows during generation)
- : batch size (number of sequences processed in parallel)
- : bytes per element (2 for float16/bfloat16, 4 for float32)
For a full model with layers, multiply by . The total cache size is .
Memory Reduction Factor
Comparing GQA to MHA, which stores key-value heads instead of , the memory reduction is simply the ratio of heads to groups:
where:
- : number of query heads (and number of KV heads in MHA)
- : number of KV groups in GQA
For GQA-8 with 64 query heads, the reduction factor is . This means the KV cache is 8 times smaller than it would be with full MHA, letting either 8 times longer sequences, 8 times larger batches, or some combination of both.
def memory_analysis(
num_query_heads: int,
head_dim: int,
num_layers: int,
seq_lengths: list,
batch_size: int = 1,
):
"""Analyze KV cache memory for different GQA configurations."""
bytes_per_element = 2 # fp16
kv_head_options = [num_query_heads, 8, 4, 2, 1]
results = {"seq_lengths": seq_lengths, "configs": []}
for num_kv_heads in kv_head_options:
config_data = {
"name": "MHA"
if num_kv_heads == num_query_heads
else "MQA"
if num_kv_heads == 1
else f"GQA-{num_kv_heads}",
"kv_heads": num_kv_heads,
"memory_gb": [],
}
for seq_len in seq_lengths:
cache_bytes = (
2
* num_layers
* num_kv_heads
* head_dim
* seq_len
* batch_size
* bytes_per_element
)
config_data["memory_gb"].append(cache_bytes / 1e9)
results["configs"].append(config_data)
return results
# Analyze for a 70B-scale model
memory_data = memory_analysis(
num_query_heads=64,
head_dim=128,
num_layers=80,
seq_lengths=[1024, 2048, 4096, 8192, 16384],
batch_size=1,
)
KV Cache Memory at 16K Sequence Length (70B model, batch=1) ======================================================= Variant KV Heads Memory (GB) vs MHA ------------------------------------------------------- MHA 64 42.95 1.0x smaller GQA-8 8 5.37 8.0x smaller GQA-4 4 2.68 16.0x smaller GQA-2 2 1.34 32.0x smaller MQA 1 0.67 64.0x smaller
The analysis reveals why GQA-8 has become the standard choice for large models. At 16K tokens, MHA would require 35 GB just for the KV cache, exceeding the memory of most GPUs. GQA-8 reduces this to 4.4 GB, fitting comfortably on modern hardware while retaining 8 independent key-value representations.