AI Knowledge Tree

Positional encodings: sinusoidal, RoPE, ALiBi

Show why attention is position-agnostic, add sinusoidal absolute encodings, and upgrade to rotary (RoPE) and length-extrapolating (ALiBi) schemes.

⏱ 32 minPrerequisites:The transformer block

Attention is permutation-equivariant: if you shuffle the input tokens, the output is shuffled by the same permutation. The model has no intrinsic notion of "this token comes before that one." Yet order is fundamental to language -- "dog bites man" and "man bites dog" must have different meanings.

Positional encodings inject order information into the representations.

Sinusoidal absolute encodings (Vaswani et al., 2017)

Add a fixed, deterministic vector pt\mathbf{p}_t to each token embedding at position tt[1]:

PE(t,2i)=sin⁡ ⁣(t100002i/d),PE(t,2i+1)=cos⁡ ⁣(t100002i/d),PE(t, 2i) = \sin\!\left(\frac{t}{10000^{2i/d}}\right), \quad PE(t, 2i+1) = \cos\!\left(\frac{t}{10000^{2i/d}}\right),

for feature index i=0,1,…,d/2−1i = 0, 1, \dots, d/2 - 1.

Why this form?

  • Different frequencies mean positions are distinguishable.
  • Linearity: pt+k\mathbf{p}_{t+k} can be written as a linear function of pt\mathbf{p}_t for any offset kk (rotation in 2D planes). The model can potentially learn to attend based on relative offset, the idea made explicit by encoding relative positions directly in the attention scores[2].
  • Unbounded: can generate encodings for any sequence length.

Limitation: position information is added to the embedding, not the attention scores. As sequences grow beyond the training length, the embeddings become out-of-distribution.

python · numpy
import numpy as np
import matplotlib
matplotlib.use('Agg')

def sinusoidal_pe(T, d):
    """Returns (T, d) sinusoidal positional encoding matrix. d must be even."""
    assert d % 2 == 0, "d must be even for sinusoidal PE"
    pe = np.zeros((T, d))
    pos = np.arange(T)[:, None]          # (T, 1)
    dim = np.arange(0, d, 2)[None, :]   # (1, d/2)
    angles = pos / 10000 ** (dim / d)   # (T, d/2)
    pe[:, 0::2] = np.sin(angles)        # (T, d/2) -> even columns
    pe[:, 1::2] = np.cos(angles)        # (T, d/2) -> odd columns
    return pe

pe = sinusoidal_pe(T=64, d=128)
print("PE shape:", pe.shape)
print("PE[0, :4]:", pe[0, :4].round(4))
print("PE[1, :4]:", pe[1, :4].round(4))
# Verify: columns 0,1 are sin/cos of position / 1.0 = position
# columns d-2,d-1 are nearly constant (very long wavelength)

Rotary Position Encoding (RoPE)

RoPE (Su et al., 2022; used by LLaMA, Mistral, Qwen) encodes position in the attention score rather than the embedding[3]. The key insight: rotate the query and key vectors by an angle proportional to their position, so that the attention score between positions tt and ss depends only on the relative offset t−st - s.

For a 2D slice of query q\mathbf{q} at position tt:

qt(i)←(q2icos⁡(tθi)−q2i+1sin⁡(tθi)q2isin⁡(tθi)+q2i+1cos⁡(tθi)),θi=10000−2i/d.\mathbf{q}_t^{(i)} \leftarrow \begin{pmatrix} q_{2i} \cos(t \theta_i) - q_{2i+1} \sin(t \theta_i) \\ q_{2i} \sin(t \theta_i) + q_{2i+1} \cos(t \theta_i) \end{pmatrix}, \quad \theta_i = 10000^{-2i/d}.

Apply the same rotation to keys with angle sθis \theta_i. The dot product qt⋅ks\mathbf{q}_t \cdot \mathbf{k}_s then equals the dot product of the unrotated vectors premultiplied by a rotation of angle (t−s)θi(t - s)\theta_i -- a function of the relative offset only.

Key properties:

  • Relative position: attention scores depend on t−st - s, not on tt and ss independently.
  • Extrapolation: works well at lengths somewhat beyond training (unlike sinusoidal absolute).
  • Zero overhead: applied to Q and K before attention, not to the residual stream.
python · numpy
import numpy as np

def apply_rope(x, positions):
    """x: (T, d), positions: (T,). Returns RoPE-rotated x."""
    T, d = x.shape
    assert d % 2 == 0
    half = d // 2
    thetas = 10000 ** (-2 * np.arange(half) / d)      # (half,)
    angles = positions[:, None] * thetas[None, :]     # (T, half)
    cos_a, sin_a = np.cos(angles), np.sin(angles)

    x_even = x[:, 0::2]   # (T, half)
    x_odd  = x[:, 1::2]

    x_rotated = np.empty_like(x)
    x_rotated[:, 0::2] = x_even * cos_a - x_odd * sin_a
    x_rotated[:, 1::2] = x_even * sin_a + x_odd * cos_a
    return x_rotated

T, d = 8, 16
Q = np.random.randn(T, d)
Q_rope = apply_rope(Q, positions=np.arange(T))
print("RoPE output shape:", Q_rope.shape)

The relative-offset claim is checkable on numbers. Fix one query vector and one key vector (d=8d = 8) and rotate them to various absolute positions, then read off qt⋅ks\mathbf{q}_t \cdot \mathbf{k}_s:

  • Offset t−s=2t - s = 2, three placements. At (t,s)=(2,0)(t, s) = (2, 0), (5,3)(5, 3), and (10,8)(10, 8) the score is −1.7631-1.7631 in all three cases. The absolute positions slide by 88; the score does not move, because only t−st - s enters.
  • Offset 00 (a token against itself). At t=s=0t = s = 0, 55, 1010 the score is −1.4680-1.4680 every time, and it equals the dot product of the unrotated q\mathbf{q} and k\mathbf{k}: rotating both vectors by the same angle leaves their dot product unchanged.

That is the named distinction RoPE is built on: sinusoidal encoding gives each absolute position a fingerprint, while RoPE makes the attention score a function of the gap between positions, so (2,0)(2, 0) and (10,8)(10, 8) are indistinguishable to it.

ALiBi: Attention with Linear Biases

ALiBi (Press et al., 2022; used by MPT, BLOOM) adds a fixed linear bias to the pre-softmax attention scores, proportional to the distance between positions:

scoretj←scoretj−mi⋅∣t−j∣,\text{score}_{tj} \leftarrow \text{score}_{tj} - m_i \cdot |t - j|,

where mim_i is a head-specific slope (a geometric sequence across heads, not learned).

Key properties:

  • No modification to embeddings or projections.
  • Distance penalty grows linearly, so distant tokens are explicitly penalized.
  • Excellent length extrapolation: models trained on length 2048 often maintain performance at 4096+ without any fine-tuning. The linear penalty is monotonically stronger for larger offsets, which is a natural inductive bias.
python · numpy
import numpy as np

def alibi_bias(T, n_heads):
    """Returns (n_heads, T, T) ALiBi bias matrix."""
    # Slopes: geometric sequence 2^{-8/h}, 2^{-16/h}, ...
    slopes = 2 ** (-8 * np.arange(1, n_heads + 1) / n_heads)   # (h,)
    # Relative distances: |t - j| for each (t, j) pair
    distances = np.abs(np.arange(T)[:, None] - np.arange(T)[None, :])  # (T, T)
    # bias: (h, T, T)
    return -slopes[:, None, None] * distances[None, :, :]

bias = alibi_bias(T=8, n_heads=4)
print("ALiBi bias[0]:\n", bias[0].round(3))  # head 0: steepest penalty
Sinusoidal positional encodings give each position a unique fingerprint: every dimension is a sine/cosine of a different frequency, so position is encoded smoothly and relative offsets become linear shifts.
Sinusoidal positional encodings give each position a unique fingerprint: every dimension is a sine/cosine of a different frequency, so position is encoded smoothly and relative offsets become linear shifts.

Comparison

MethodPosition inRelative?Extrapolates?Used by
SinusoidalEmbeddingsNoPoorlyOriginal Transformer
Learned absoluteEmbeddingsNoNoBERT, GPT-2
RoPEAttention scoresYesSomewhatLLaMA, Mistral, Qwen
ALiBiAttention scoresYesWellBLOOM, MPT

Modern LLMs (LLaMA 3, Qwen, Mistral) universally use RoPE. ALiBi is preferred when extrapolation is a hard requirement without fine-tuning.

Exercises

Exercise 1. Write the sinusoidal encoding vector p0\mathbf{p}_0 at position t=0t = 0 for any dimension dd. What does it equal, and why does that make t=0t = 0 a natural reference point?

Solution

Step 1. At t=0t = 0 every angle argument is 0/100002i/d=00/10000^{2i/d} = 0, regardless of the dimension index ii.

Step 2. The even entries are sin⁡(0)=0\sin(0) = 0 and the odd entries are cos⁡(0)=1\cos(0) = 1, so

p0=(0,1,0,1,…,0,1).\mathbf{p}_0 = (0, 1, 0, 1, \dots, 0, 1).

Step 3. Every position tt is an alternating (sin⁡,cos⁡)(\sin, \cos) pair of growing angle, and position 00 is the zero-angle case, the same vector in every model dimension. It anchors the rotation: each later position is a rotation of this fixed starting phase, so t=0t = 0 serves as the reference the relative offsets are measured against.

Exercise 2. RoPE rotates each 2D slice of q\mathbf{q} and k\mathbf{k} by an angle proportional to position. Prove that rotating both vectors by the same angle leaves their dot product unchanged, then explain why this makes the attention score depend only on the offset t−st - s.

Solution

Step 1. A 2D rotation by angle α\alpha is the orthogonal matrix RαR_\alpha with Rα⊤Rα=IR_\alpha^\top R_\alpha = I. For two vectors u,vu, v,

(Rαu)⋅(Rαv)=u⊤Rα⊤Rαv=u⊤v=u⋅v.(R_\alpha u) \cdot (R_\alpha v) = u^\top R_\alpha^\top R_\alpha v = u^\top v = u \cdot v.

Rotating both by the same angle is an orthogonal transformation, which preserves inner products.

Step 2. RoPE rotates the query slice by tθit\theta_i and the key slice by sθis\theta_i. Their dot product is

(Rtθiq)⋅(Rsθik)=q⊤Rtθi⊤Rsθik=q⊤R(s−t)θik,(R_{t\theta_i} q) \cdot (R_{s\theta_i} k) = q^\top R_{t\theta_i}^\top R_{s\theta_i} k = q^\top R_{(s - t)\theta_i} k,

using Ra⊤=R−aR_{a}^\top = R_{-a} and R−aRb=Rb−aR_{-a} R_{b} = R_{b - a}. The result depends on the two positions only through their difference s−ts - t (equivalently t−st - s), not on either absolute position.

Step 3. When t=st = s the residual rotation is R0=IR_0 = I, recovering Step 1: a token against itself scores the unrotated q⋅kq \cdot k. So (t,s)=(2,0)(t, s) = (2, 0) and (10,8)(10, 8), both offset 22, give identical scores.

Exercise 3 (code). Implement RoPE and verify both claims numerically: equal offsets give equal scores at different absolute placements, and a zero offset reproduces the unrotated dot product.

Solution
python · numpy
import numpy as np

def apply_rope(x, positions):
    T, d = x.shape
    assert d % 2 == 0
    half = d // 2
    thetas = 10000 ** (-2 * np.arange(half) / d)
    angles = positions[:, None] * thetas[None, :]
    cos_a, sin_a = np.cos(angles), np.sin(angles)
    x_even, x_odd = x[:, 0::2], x[:, 1::2]
    out = np.empty_like(x)
    out[:, 0::2] = x_even * cos_a - x_odd * sin_a
    out[:, 1::2] = x_even * sin_a + x_odd * cos_a
    return out

rng = np.random.default_rng(0)
q = rng.normal(size=(1, 8))
k = rng.normal(size=(1, 8))

def score(t, s):
    qt = apply_rope(q, np.array([t]))
    ks = apply_rope(k, np.array([s]))
    return (qt @ ks.T)[0, 0]

# same offset (= 2) at three placements
s_far = [score(2, 0), score(5, 3), score(10, 8)]
assert np.allclose(s_far, s_far[0])              # offset fixes the score

# zero offset equals the unrotated dot product
assert np.isclose(score(0, 0), (q @ k.T)[0, 0])
print("offset-2 score", round(s_far[0], 4), "| zero offset == raw dot")

The three offset-22 placements return the same number because only t−st - s survives the rotation, and the zero-offset case collapses to the plain dot product, matching the proof.


References

  1. [1]Vaswani, A., et al. (2017). "Attention is all you need". NeurIPS. arXiv:1706.03762↩
  2. [2]Shaw, P., Uszkoreit, J., & Vaswani, A. (2018). "Self-attention with relative position representations". NAACL. arXiv:1803.02155↩
  3. [3]Su, J., et al. (2021). "RoFormer: enhanced transformer with rotary position embedding (RoPE)". arXiv:2104.09864↩