64 query heads share 8 KV heads (d_h=128) — full attention quality with a 8× smaller KV cache.
Standard causal self-attention with grouped queries: the 64 query heads
are split into 8 groups, and each group shares one K/V head. Per token
the KV cache stores 8 × 128 keys and values instead of
64 × 128 — at inference, KV-cache size (not FLOPs) is what limits
batch size and context, so this is nearly free quality-wise and hugely valuable.
The spectrum: MHA (kvHeads = heads, original), GQA (1 < kvHeads < heads),
MQA (kvHeads = 1, maximum sharing, some quality cost).
In this model:
- 64 query heads, 8 KV heads, d_h=128
- RoPE base θ=500000 with llama3 ×8 context extension
- no Q/K normalization
- no projection biases
- no attention sinks
class GQA(nn.Module):
def __init__(self, d_model=8192, n_heads=64,
n_kv_heads=8, head_dim=128):
super().__init__()
self.q = nn.Linear(d_model, n_heads * head_dim, bias=False)
self.k = nn.Linear(d_model, n_kv_heads * head_dim, bias=False)
self.v = nn.Linear(d_model, n_kv_heads * head_dim, bias=False)
self.o = nn.Linear(n_heads * head_dim, d_model, bias=False)
self.groups = n_heads // n_kv_heads
def forward(self, x, pos):
B, T, _ = x.shape
q = split_heads(self.q(x), 64) # [B, 64, T, 128]
k = split_heads(self.k(x), 8) # [B, 8, T, 128]
v = split_heads(self.v(x), 8)
q, k = apply_rope(q, pos), apply_rope(k, pos)
k, v = repeat_kv(k, self.groups), repeat_kv(v, self.groups)
out = F.scaled_dot_product_attention(q, k, v, is_causal=True)
return self.o(merge_heads(out)) # [B, T, 8192]