Implement Grouped-Query Attention (GQA)
DatadogMachine Learning Engineer · Technical ScreenLast updated
Implement the Grouped-Query Attention (GQA) mechanism commonly used in modern LLMs to balance serving memory bandwidth with generation quality. You will handle tensor reshaping/expansion across query head groups, scaled dot-product attention, and causal masking.
Problem Statement
Grouped-Query Attention (GQA) interpolates between Multi-Head Attention (MHA, where queries, keys, and values have the same number of heads) and Multi-Query Attention (MQA, where keys and values share a single head across all query heads). In GQA, query heads are divided into num_heads_kv groups, and each group of query heads shares a single key head and value head.
Implement the forward pass of GQA using PyTorch (or standard tensor operations). Your function should support an optional causal mask.
Function Signature
import torch
def grouped_query_attention(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
is_causal: bool = False,
scale: float | None = None,
) -> torch.Tensor:
"""
Computes Grouped-Query Attention.
Args:
q: Query tensor of shape (batch_size, seq_len_q, num_heads_q, head_dim)
k: Key tensor of shape (batch_size, seq_len_kv, num_heads_kv, head_dim)
v: Value tensor of shape (batch_size, seq_len_kv, num_heads_kv, head_dim)
is_causal: If True, applies an autoregressive causal mask where position i
can only attend to positions j <= i.
scale: Scaling factor applied before softmax. If None, defaults to 1.0 / sqrt(head_dim).
Returns:
torch.Tensor of shape (batch_size, seq_len_q, num_heads_q, head_dim)
"""
pass
Requirements & Constraints
num_heads_qmust be divisible bynum_heads_kv. Letgroup_size = num_heads_q // num_heads_kv.- Head replication: Expand or repeat each KV head
group_sizetimes so that keys and values align with the query heads, preferably using zero-copy view/broadcasting orrepeat_interleave. - Attention formula:
softmax(Q @ K.T / sqrt(head_dim) + mask) @ V. - When
is_causal=True, attention scores wherequery_pos < key_posmust be masked with-inf(or a very large negative value) before computing the softmax. - Assume
q,k,vare float tensors on the same device. - Sequence lengths
seq_len_qandseq_len_kvmay differ (e.g., during autoregressive decoding with a KV cache whereseq_len_q = 1andseq_len_kv = T). In that case, causal masking aligns queries to the most recent positions: query indexicorresponds to key positionseq_len_kv - seq_len_q + i.
Example
batch_size = 2
seq_len = 4
num_heads_q = 8
num_heads_kv = 2
head_dim = 64
q = torch.randn(batch_size, seq_len, num_heads_q, head_dim)
k = torch.randn(batch_size, seq_len, num_heads_kv, head_dim)
v = torch.randn(batch_size, seq_len, num_heads_kv, head_dim)
out = grouped_query_attention(q, k, v, is_causal=True)
assert out.shape == (batch_size, seq_len, num_heads_q, head_dim)
Hints
Hint 1
Verify that `num_heads_q % num_heads_kv == 0`, and repeat each KV head `num_heads_q // num_heads_kv` times along the head dimension using either `torch.repeat_interleave` or an explicit reshape and expand.
Hint 2
Transpose the tensors so head dimension precedes sequence length (e.g., shape `(batch, num_heads, seq_len, head_dim)`) to perform batched matrix multiplication efficiently via `torch.matmul`.
Hint 3
For causal masking when `seq_len_q != seq_len_kv`, compute `mask = torch.arange(seq_len_q)[:, None] + (seq_len_kv - seq_len_q) >= torch.arange(seq_len_kv)[None, :]`.
Follow-up questions
- How does GQA impact the memory footprint and memory bandwidth requirements of the KV cache during autoregressive generation compared to MHA?
- Instead of materializing repeated KV heads with `repeat_interleave`, how would you rewrite the attention calculation using einops/broadcasting to avoid allocating additional memory for duplicate KV tensors?
- How does FlashAttention support GQA under the hood, and what kernel optimizations are enabled by having fewer KV heads?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.