Gemma 3 Attention Block
Gemma 3
Hard
Full Gemma 3 attention with routing and QK-Norm
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
Gemma 3 attention is grouped-query attention with a per-layer choice of local or global span, RMSNorm on (Q) and (K), and RoPE whose base frequency depends on that choice. The technical report lists those pieces in §2: GQA, pre- and post-RMSNorm around the block, QK-norm in place of Gemma 2’s logit soft-cap, and a 5:1 interleaving of sliding-window layers with global layers. The report does not write the forward equations. The official Attention module in gemma/gm/nn/_modules.py is the concrete graph.
The design is aimed at 128K context without a KV cache that grows in every layer. Local layers attend inside a short window (1024 tokens on 4B–27B; 512 on 270M/1B). Global layers see the full sequence, use a RoPE base of (10^6), and on the long-context sizes apply a RoPE scale of 8 after the 32K→128K extension. QK-norm keeps the logits stable once the residual stream is no longer soft-capped.
How it works
Input (x) has shape [B, T, D]. Positions segment_pos are [B, T]. The incoming attn_mask is [B, T, S] (causal and padding; (S=T) without cache).
Projections. If num_kv_heads == num_heads, a fused qkv_einsum produces (Q,K,V) each [B, T, H, d_h]. Otherwise a q_einsum yields (Q) as [B, T, H, d_h] and a kv_einsum yields (K,V) as [B, T, H_{kv}, d_h]. Released Gemma 3 sizes are GQA except where (H_{kv}=1) (270M, 1B), which is multi-query.
QK-norm, then RoPE, then scale. When use_qk_norm is set (true for all Gemma 3 configs),
Q \leftarrow \mathrm{RMSNorm}_Q(Q),\qquad K \leftarrow \mathrm{RMSNorm}_K(K)
with last-axis RMS and ((1+\gamma)) (see the QK-norm note). RoPE is applied to (Q) and (K) using the layer’s rope_base_frequency and rope_scale_factor. Queries are then scaled:
Q \leftarrow Q\cdot \alpha,\qquad \alpha = \begin{cases} d_h^{-1/2} & \text{1B--12B}\\ (D/H)^{-1/2} & \text{27B}. \end{cases}
The 27B exception is QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_EMBED_DIM_DIV_NUM_HEADS in _config.py: (D=5376), (H=32), so (\alpha=168^{-1/2}), not (128^{-1/2}). Soft-cap is None; the tanh branch in Attention is unused.
Cache. If a cache is present, (K) and (V) (and positions) are written into a left-aligned buffer of length (S) with dynamic_update_slice. Sliding-window layers need those stored positions later.
Scores. Full attention is
\mathrm{logits}_{b,t,h,s} = \sum_{c=1}^{d_h} Q_{b,t,h,c}\,K_{b,s,h,c}.
GQA reshapes (Q) to [B, T, H_{kv}, G, d_h] with (G=H/H_{kv}), einsums against (K), then flattens heads back. Local layers multiply attn_mask by the sliding mask
M_{t,s}=\mathbf{1}[s>t-W]\,\mathbf{1}[s<t+W]
(create_sliding_mask). Global layers leave attn_mask as the causal mask. Blocked entries are filled with K_MASK = -2.3819763e38. Softmax over (s) produces weights; they mix (V) and the output einsum attn_vec_einsum maps [B, T, H, d_h] back to [B, T, D].
Routing. Transformer.setup stamps each layer with attn_type from GEMMA3_ATTENTION_PATTERN tiled to depth: five LOCAL_SLIDING then one GLOBAL. That type selects both the mask and the RoPE pair ((\theta,\mathrm{scale})). Local: (\theta=10^4), scale 1. Global: (\theta=10^6), scale 8 on 4B/12B/27B.
The paper specifies GQA, QK-norm instead of soft-cap, 5:1 local/global, window 1024, and the two RoPE bases. Projection einsums, the 27B query scale, K_MASK, cache-position masking, and the 512-token small-model window are from the official module.
Official code
gemma/gm/nn/_modules.py—Attention,AttentionType,create_sliding_mask.gemma/gm/nn/_transformer.py— per-layer RoPE base/scale fromattn_type.gemma/gm/nn/_gemma.py—GEMMA3_ATTENTION_PATTERNand size-specifichead_dim,num_kv_heads,sliding_window_size,use_qk_norm.gemma/gm/nn/_config.py—query_pre_attn_scalar().
Watch-outs
- QK-norm before RoPE; query scale after RoPE. Swapping either pair changes both the rotation geometry and the logit magnitude.
- 27B does not use (1/\sqrt{d_h}). Copying the 12B scalar onto 27B (or the reverse) mis-scales every head.
- GQA grouping must use (H_{kv}), not (H). Repeating (K) incorrectly, or einsuming (Q) against an ungrouped (K) of width (H_{kv}), is a shape error that silent broadcasts can hide.
- Local vs global is a mask and a RoPE config. A global layer with (W=1024), or a local layer with (\theta=10^6), is not Gemma 3 attention.
Sources
- Paper: Gemma 3 Technical Report, §2 and §5.2–5.3
- Code: google-deepmind/gemma
gemma/gm/nn/_modules.py,gemma/gm/nn/_transformer.py,gemma/gm/nn/_gemma.py,gemma/gm/nn/_config.py