RoPE Applies at d_k, Not d_model
# WRONG — rotate before head split
x = apply_rope(x, freqs) # (B, T, d_model)
Q, K, V = split_heads(project(x)) # breaks relative-position property
# RIGHT — split heads first, rotate Q and K per-head
Q, K, V = split_heads(project(x)) # (B, H, T, d_k)
Q = apply_rope(Q, freqs)
K = apply_rope(K, freqs)
Why it matters: RoPE encodes relative position through 2D rotations in the per-head subspace. Apply at d_model and heads mix rotations, losing the ⟨q_m, k_n⟩ = f(m−n) property. Every modern LLM rotates at d_k.