GQA Attention
gpt-oss
Medium
Forward pass combining grouped-query attention, optional sliding window, and learned softmax sinks.
Independent study note. Written from the public paper and official code. This is not TensorTonic Plus and does not reproduce their exercises, starter code, or tests. For the official version, subscribe on TensorTonic.
Overview
Every gpt-oss layer uses grouped-query attention. Section 2.2 fixes 64 query heads of width 64 and 8 key-value heads. Eight query heads share each KV head, so the KV cache is (8/64=1/8) the size of full MHA while the residual width stays 2880. The same attention block also applies YaRN-RoPE to (Q) and (K), an optional 128-token sliding window on even layers, and a learned sink logit per query head.
The model card lists these pieces side by side. The composition lives in AttentionBlock plus sdpa in the reference PyTorch file. Pre-norm RMSNorm and the residual add wrap the block; they are part of the layer but not part of the attention scores themselves.
How it works
Let the residual stream be (x\in\mathbb{R}^{T\times d}) with (d=2880). After RMSNorm, a fused linear qkv produces a single tensor of width
d_{\mathrm{qkv}} = d_h\bigl(n_q + 2n_{\mathrm{kv}}\bigr) = 64\cdot(64+16)=5120.
Split that into (Q\in\mathbb{R}^{T\times n_q\times d_h}), (K,V\in\mathbb{R}^{T\times n_{\mathrm{kv}}\times d_h}). The official view then groups queries as
Q \in \mathbb{R}^{T \times n_{\mathrm{kv}} \times q_{\mathrm{mult}} \times d_h},\qquad q_{\mathrm{mult}}=n_q/n_{\mathrm{kv}}=8,
and leaves (K,V) as (\mathbb{R}^{T\times n_{\mathrm{kv}}\times d_h}). Rotary embeddings run on (Q) and (K) only. sdpa expands (K) and (V) along the (q_{\mathrm{mult}}) axis so each of the eight queries in a group dots with the same key and mixes the same value:
s_{t,g,m,j} = \frac{\langle q_{t,g,m},\,k_{j,g}\rangle}{\sqrt{d_h}} + M_{tj}.
(M) is (0) on allowed keys and (-\infty) elsewhere. Even layers add the sliding-window band (i-W<j\le i) with (W=128); odd layers use a full causal triangle. After masking, a per-query-head sink (\sigma_{g,m}) is concatenated as a (T+1)-st logit (see the sinks note). Softmax over keys-plus-sink, drop the sink weight, and mix (V):
o_{t,g,m} = \sum_{j} \alpha_{t,g,m,j}\, v_{j,g}.
Flatten the ((g,m)) axes back to 64 heads and project with out: (\mathbb{R}^{T\times 4096}\to\mathbb{R}^{T\times 2880}). Add the residual: (x\leftarrow x+\mathrm{out}(o)).
Scale is (1/\sqrt{64}=1/8), stored as sm_scale. There is no QK-norm and no attention dropout in the public graph. Both qkv and out are bfloat16 linears with bias (PyTorch nn.Linear default). The paper does not mention those biases; they are an official-code detail.
GQA sharing is only on (K) and (V). Sinks, output projection rows, and the eight queries inside a group are distinct. Repeating KV is an expand, not a copy of learned weights: there are 8 key projections, not 64.
Official code
gpt_oss/torch/model.py: AttentionBlock.forward does RMSNorm, the fused qkv split, the GQA reshape, RotaryEmbedding, then sdpa. sdpa expands KV, applies the causal / window mask, concatenates sinks, softmaxes, and mixes values. Config defaults in ModelConfig match the card: 64 / 8 / 64 and sliding_window=128.
Watch-outs
- Repeat KV after RoPE, or apply RoPE to the un-repeated 8 heads and then expand. Repeating first and then rotating 64 independent keys invents 56 extra rotary applications the checkpoint does not have.
- (q_{\mathrm{mult}}) must be (64/8=8). Treating the model as MHA ((n_{\mathrm{kv}}=64)) or MQA ((n_{\mathrm{kv}}=1)) will not load the fused
qkvwidth 5120. - Sinks are 64-wide, aligned with query heads, not 8-wide aligned with KV heads.
- Windowing is even-layer only. Putting (W=128) on every layer, or forgetting the causal
triu, either isolates later tokens from long-range context or leaks the future.
Sources
- Paper: gpt-oss-120b & gpt-oss-20b Model Card, §2.2
- GQA: Ainslie et al., “GQA: Training Generalized Multi-Query Transformer Models,” 2023
- Code: openai/gpt-oss
gpt_oss/torch/model.py(AttentionBlock,sdpa)