Grouped Query Attention › Mathematical Formulation
Language AI HandbookAll chapters Grouped Query Attention › Mathematical FormulationOne section · Sections in this chapter4 / 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
Let's formalize GQA mathematically. The core idea is straightforward: instead of each query head having its own key-value projections (as in MHA) or all query heads sharing one key-value projection (as in MQA), GQA divides query heads into groups where each group shares a single key-value projection.
Given an input sequence , GQA is parameterized by:
- : number of query heads (determines query diversity)
- : number of key-value groups, where divides evenly (controls memory vs. capacity trade-off)
- : dimension per head (same as standard multi-head attention)
The ratio determines how many query heads share each key-value head. For example, with query heads and KV groups, each group of 4 query heads shares one key-value representation.
Query Projections
Query projections work exactly as in standard multi-head attention. Each query head has its own learned projection matrix that turns the input into a query representation:
where:
- : the query matrix for head , containing a -dimensional query vector for each of the positions
- : the input sequence with tokens, each represented as a -dimensional vector
- : the learned query projection matrix for head , transforming from model dimension to head dimension
Having separate query projections preserves the model's ability to ask different "questions" of the input. Each query head can still specialize in detecting different patterns, maintaining expressive power.
Key-Value Projections (The Key Difference)
Here is where GQA diverges from MHA. Instead of key-value projections, we have only projections that are shared among groups of query heads:
where:
- : the key matrix for group , containing key vectors for all positions
- : the value matrix for group , containing value vectors for all positions
- : the learned key projection matrix for group
- : the learned value projection matrix for group
With only key-value projections instead of , the KV cache during inference stores key-value pairs rather than , reducing memory by a factor of .
Grouping Function
To connect query heads to their shared key-value representations, we define a grouping function that maps each query head index to its corresponding key-value group:
where:
- : the query head index, ranging from 1 to
- : the total number of key-value groups
- : the total number of query heads
- : the floor function (rounds down to nearest integer)
This function partitions the query heads into groups of size each. For example, with query heads and groups:
- Query heads 1, 2, 3, 4 map to group 1 (using , )
- Query heads 5, 6, 7, 8 map to group 2 (using , )
Attention Computation
Each query head computes attention using its own query but the shared key-value from its group:
where:
- : the output of attention head
- : the query matrix for head (unique to this head)
- : the key matrix shared by all heads in group
- : the value matrix shared by all heads in group
- : the attention score matrix
- : scaling factor to prevent dot products from growing too large
The key insight is that different query heads in the same group can still learn different attention patterns. Even though heads 1-4 all use and , their different query projections produce different attention weights over the same key-value pairs.
Output Combination
Finally, all head outputs are concatenated and projected back to the model dimension:
where:
- : concatenation of all head outputs along the feature dimension
- : the output projection matrix
- : the final output, matching the input dimension
Since , the concatenated heads have dimension , and projects back to dimension , preserving the input shape for residual connections.
import torch
import torch.nn as nn
import torch.nn.functional as F
class GroupedQueryAttention(nn.Module):
"""Grouped Query Attention implementation."""
def __init__(
self,
d_model: int,
num_heads: int,
num_kv_heads: int,
dropout: float = 0.0,
):
super().__init__()
assert d_model % num_heads == 0, (
"d_model must be divisible by num_heads"
)
assert num_heads % num_kv_heads == 0, (
"num_heads must be divisible by num_kv_heads"
)
self.d_model = d_model
self.num_heads = num_heads
self.num_kv_heads = num_kv_heads
self.num_queries_per_kv = num_heads // num_kv_heads
self.head_dim = d_model // num_heads
# Query projection: full size for all query heads
self.w_q = nn.Linear(d_model, num_heads * self.head_dim, bias=False)
# Key and value projections: reduced size for KV heads only
self.w_k = nn.Linear(d_model, num_kv_heads * self.head_dim, bias=False)
self.w_v = nn.Linear(d_model, num_kv_heads * self.head_dim, bias=False)
# Output projection
self.w_o = nn.Linear(d_model, d_model, bias=False)
self.dropout = nn.Dropout(dropout)
self.scale = self.head_dim**-0.5
def forward(
self,
x: torch.Tensor,
mask: torch.Tensor = None,
kv_cache: tuple = None,
) -> tuple:
batch_size, seq_len, _ = x.shape
# Compute queries: (batch, seq, num_heads, head_dim)
q = self.w_q(x).view(batch_size, seq_len, self.num_heads, self.head_dim)
# Compute keys and values: (batch, seq, num_kv_heads, head_dim)
k = self.w_k(x).view(
batch_size, seq_len, self.num_kv_heads, self.head_dim
)
v = self.w_v(x).view(
batch_size, seq_len, self.num_kv_heads, self.head_dim
)
# Handle KV cache for incremental decoding
if kv_cache is not None:
k_cache, v_cache = kv_cache
k = torch.cat([k_cache, k], dim=1)
v = torch.cat([v_cache, v], dim=1)
new_kv_cache = (k, v)
kv_seq_len = k.size(1)
# Reshape for attention computation
# q: (batch, num_heads, seq, head_dim)
q = q.transpose(1, 2)
# k, v: (batch, num_kv_heads, kv_seq, head_dim)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# Expand k, v to match query heads by repeating
# (batch, num_kv_heads, kv_seq, head_dim) -> (batch, num_heads, kv_seq, head_dim)
k = k.repeat_interleave(self.num_queries_per_kv, dim=1)
v = v.repeat_interleave(self.num_queries_per_kv, dim=1)
# Compute attention scores: (batch, num_heads, seq, kv_seq)
attn_scores = torch.matmul(q, k.transpose(-2, -1)) * self.scale
if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, float("-inf"))
attn_weights = F.softmax(attn_scores, dim=-1)
attn_weights = self.dropout(attn_weights)
# Apply attention to values: (batch, num_heads, seq, head_dim)
attn_output = torch.matmul(attn_weights, v)
# Reshape and project output
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, seq_len, self.d_model)
return self.w_o(attn_output), new_kv_cacheGrouped Query Attention Test ======================================================= Configuration: d_model: 512 Query heads: 8 KV heads: 2 Queries per KV group: 4 Shapes: Input: (2, 16, 512) Output: (2, 16, 512) KV cache K: (2, 16, 2, 64) KV cache V: (2, 16, 2, 64) Parameter counts: GQA: 655,360 MHA (equivalent): 1,048,576 MQA (equivalent): 589,824

The key implementation detail is the repeat_interleave operation that expands the key-value tensors to match the number of query heads. This creates the grouping structure: consecutive query heads share the same key-value entries. The KV cache stores only the reduced number of key-value heads. This provides the memory savings.




The attention heatmaps above reveal a important property of GQA: even though all four query heads share the same key and value tensors (they're all in Group 1), each head produces distinct attention patterns. Head Q1 might focus heavily on recent tokens, Q2 on the first token, Q3 on positions 2-3, and Q4 shows yet another pattern. This diversity emerges because each query head has its own learned projection. The queries are different even when the keys are identical.
The attention heatmaps reveal a important property of GQA: even though all four query heads share the same key and value tensors (they're all in Group 1), each head produces distinct attention patterns. Head Q1 might focus heavily on recent tokens, Q2 on the first token, Q3 on positions 2-3, and Q4 shows yet another pattern. This diversity emerges because each query head has its own learned projection. The queries are different even when the keys are identical.