Sinusoidal Position Encoding
The first answer to the question. Build a table of sines and cosines at geometrically spaced frequencies, add it to the token embeddings, and let the model work out what to do with it.
Standing
StaleLoad-bearing for understanding how the field arrived here, and replaced in practice by something on this list. Worth reading, not worth reaching for.
The original scheme, and the one every later design is a reaction to. Nothing trained since about 2021 uses it, but the argument it makes still shapes the field.
judged as of 2026-09 · what the labels mean
Theory
Self-attention is a sum over a set. Permute the tokens and every score, every
weight and every output permutes with them — the operation genuinely cannot tell
the cat sat from sat cat the. Position is not something attention loses; it
is something attention never had, and it has to be supplied from outside.
The original transformer supplies it by adding a fixed vector to each token embedding before the first layer.
Read it as a clock face with hands. The hand at turns once per token; the hand at turns once per tokens. Any position is the unique reading of all the hands at once, and nearby positions give nearby readings on the fast hands while agreeing on the slow ones.
The relative-position argument
The paper’s justification is that offsets are linear: for a fixed there is a matrix , independent of , with . It is a rotation by in each two-dimensional subspace, so the claim is exactly true.
What does not follow is that the model gets relative position for free. The encoding is added to the content vector, so a query at position is and the score expands into four terms — content with content, content with position, position with content, position with position. Only the last depends on the offset alone, and it is entangled with the other three in a single scalar. The linear structure is available to be learned; it is not imposed.
Relative position embeddings exist because of that expansion, and rotary embeddings exist because rotating and rather than adding to makes the offset dependence structural instead of learned.
Why it does not extrapolate
Nothing about the table is undefined past the training length — the sinusoids carry on. The problem is that the scores do not. Positions beyond anything seen in training produce combinations of phases the attention weights were never fit against, and perplexity degrades sharply within a few hundred tokens of the training window. The encoding is defined everywhere; the model is not.
Implementation
import math
import torch
from torch import Tensor
def sinusoidal(n: int, d: int, base: float = 10_000.0) -> Tensor:
"""[n, d] table. Even dims sine, odd dims cosine, shared frequency."""
p = torch.arange(n).unsqueeze(1) # [n, 1]
i = torch.arange(0, d, 2) # [d/2]
# exp/log rather than a direct power: base**(i/d) overflows fp32 for large d.
freq = torch.exp(-math.log(base) * i / d) # [d/2]
pe = torch.zeros(n, d)
pe[:, 0::2] = torch.sin(p * freq)
pe[:, 1::2] = torch.cos(p * freq)
return peTwo conventions are in circulation and they disagree. The paper interleaves sine and cosine on adjacent dimensions, as above; several widely copied implementations concatenate the sine block and the cosine block instead. Both train equally well, and neither is compatible with the other’s checkpoints — the same silent-mismatch hazard that later reappears in RoPE’s two pairing schemes.