Positional Encoding
Self-attention cannot tell "dog bites man" from "man bites dog". Positional encoding is the fix — a fingerprint added to every word that says where it sits.
Overview
Quick Context
Self-attention computes every position from every other position by dot products and weighted sums. Nowhere in that computation does an index appear. Permute the input and the outputs permute with it, unchanged — the mechanism is permutation-equivariant.
For a bag of words that would be a feature. For language it is fatal: "dog bites man" and "man bites dog" contain exactly the same words, and a model that cannot tell them apart cannot do anything useful. An RNN got order for free by reading left to right. Throwing out recurrence threw that away too, and it has to be put back by hand.
Encoding
"dog bites man" → "man bites dog"
The Encoding Matrix
24 × 32Row = position, column = dimension. Left columns oscillate fast, right columns slowly.
Does It Encode Distance?
Dot product of the inspected position's encoding with every other position's.
Word Order Test
This Position
Positional Encoding: A Practical Guide
Why a transformer has to be told what order the words came in, and the odd-looking function that tells it.
The obvious ideas, and why they fail
- Append the integer position. Position 512 then contributes a value five hundred times larger than position 1, swamping the embedding, and the model has never seen position 3000 during training.
- Append position / length. Now it is bounded, but the same value means different things in different sentences — 0.5 is word 5 of 10 and word 50 of 100. Nothing consistent can be learned from it.
Both are available in the Scheme control above. What is wanted is something bounded, unique per position, consistent across sentence lengths, and — ideally — carrying information about relative distance, since "the adjective three words back" is a far more useful notion than "the word at index 17".
The sinusoidal answer
The original transformer used a fixed function of position, with a different frequency in every pair of dimensions:
PE(pos, 2i) = sin( pos / 100002i/d )
PE(pos, 2i+1) = cos( pos / 100002i/d )
The wavelengths form a geometric series from about 2 up to about 10000·2π. Early dimensions flip every couple of positions; late dimensions barely move across the whole sequence. Together they behave like the digits of a binary counter — the fast bits distinguish neighbours, the slow bits distinguish regions — which is why the matrix above has that striped, fanning appearance.
Every value stays inside −1 to 1 no matter how long the sequence, and the pattern for position 500 is defined whether or not the model ever trained on a sequence that long.
Attention cannot see order
Self-attention computes a weighted mixture over all positions. Shuffle the input tokens and the same set of outputs comes back, permuted — the mechanism treats the sequence as a bag.
For language that is fatal. "The dog bit the man" and "The man bit the dog" contain identical tokens, and a model without positional information cannot tell them apart.
So order has to be injected separately. That is the entire job of positional encoding, and there are three families of answer.
| Approach | How | Used by |
|---|---|---|
| Sinusoidal | Fixed sine/cosine patterns added to embeddings | Original transformer |
| Learned absolute | A trainable vector per position | BERT, GPT-2 |
| Rotary (RoPE) | Rotate Q and K by an angle set by position | Llama, most current models |
| ALiBi | Add a distance-based penalty to attention scores | Some long-context models |
Sinusoidal encodings
The original scheme adds a fixed pattern to each token's embedding, using sines and cosines at geometrically spaced frequencies:
PE(pos, 2i) = sin(pos / 100002i/d) PE(pos, 2i+1) = cos(pos / 100002i/d)
Each dimension oscillates at a different rate. Low dimensions change quickly with position — useful for distinguishing neighbours — and high dimensions change slowly, encoding coarse location. Together they give every position a unique signature, rather like a binary counter in continuous form.
Two properties made this attractive. It requires no parameters, and it extends to positions never seen during training, because the formula is defined for any pos. There is also a neat mathematical fact: the encoding for position pos + k is a linear function of the encoding for pos, so relative offsets are representable.
Its weakness is that it encodes absolute position, and what usually matters in language is relative distance — "the adjective two words before this noun" rather than "the word at index 47".
Rotary embeddings, and why they won
RoPE takes a different route. Instead of adding something to the embedding, it rotates the query and key vectors by an angle proportional to their position, in each two-dimensional slice of the vector.
The consequence falls out of trigonometry: when a rotated query is dotted with a rotated key, the result depends on the difference between their positions, not on their absolute values. Relative position is built into the attention score itself, without any extra terms.
That gives three practical advantages, which is why essentially every current large model uses it:
- Relative by construction, matching what language actually needs.
- Extends further beyond the training length, and can be stretched deliberately with interpolation tricks (NTK scaling, YaRN) to extend context after training.
- No parameters, and it applies inside attention rather than at the input, so it affects every layer consistently.
ALiBi is the other approach worth knowing: it adds a penalty to attention scores proportional to distance, so tokens attend less to things far away. Simple, parameter-free, and it extrapolates well — at the cost of building in a bias towards locality.
Giving attention a sense of order
Attention is permutation-invariant -- this proves it, then builds the sinusoidal encoding that fixes it and checks the property that makes it work.
Experiments to try
- Break word order. Set the Scheme to None and tick Swap Two Words. Input Difference reads 0.000 and the two sentences are literally identical to the model. Switch the Scheme back to Sinusoidal and the difference becomes non-zero — the encoding is what makes them different inputs.
- See the frequency fan. Look at the matrix with the Model Dimension slider at 64. The leftmost columns stripe rapidly down the page while the rightmost are almost flat. That spread of wavelengths is the whole design.
- Check it encodes distance. Set the Inspect Position slider to 12 and look at the lower panel. Similarity peaks at position 12 and falls away smoothly on both sides, so nearby positions have similar encodings. Move the position and the peak travels with it, keeping its shape — the pattern depends on the gap, not on where you are.
- Try the naive schemes. Set the Scheme to Raw integer position, then drag the Inspect Position slider up and watch Largest Value climb with it, without bound. Compare against Sinusoidal, where it never leaves 1. Then try Position / length and lengthen the sequence: every encoding shifts, because the same position now means a different fraction.
- Push past training length. Set the Sequence Length slider to its maximum with Sinusoidal selected. The values stay inside −1 to 1 and every row is still distinct. Nothing had to be learned for those positions to exist.
Added, not concatenated
The encoding is added to the word embedding, not stapled onto the end of it. That surprises people — surely adding corrupts the meaning?
In practice it does not, and the usual explanation is that in a space of several hundred dimensions there is room for the model to keep the two kinds of information in largely separate subspaces, and the projections that follow can pull them apart again. Concatenating would be cleaner conceptually but would spend dimensions that are more useful elsewhere.
What is used now
- Learned absolute — a plain embedding table indexed by position, used by BERT and GPT-2. Simple and works well, but has a hard maximum length and learns nothing about positions beyond it.
- Relative — bias the attention scores by the distance between the two positions rather than encoding absolutes at all. Closer to what attention actually wants to know.
- RoPE (rotary) — rotate the query and key vectors by an angle proportional to position, so the dot product between them depends on their difference by construction. Now the default in most open-weight models.
Sinusoidal encoding is no longer the state of the art, but it is the clearest illustration of what the problem is, and RoPE is easier to understand once you have seen it.
Where this goes wrong
- Leaving it out. The model still trains and the loss still falls, so the bug is silent — it just performs like a bag-of-words model and nobody knows why.
- Exceeding the trained length. With learned encodings, positions past the table simply do not exist. Most context-extension work is about making positional information survive past where it was trained.
- Adding it at every layer. The original adds it once, at the input. Re-adding it at each layer is a common misreading of the diagram.
- Assuming the model reads it as a number. It is a pattern, not a counter. What the model learns is which patterns co-occur, not that dimension 7 means "position 12".
Where that leaves you
Self-attention has no notion of order, so position has to be supplied explicitly, and the naive options fail: raw indices grow without bound and normalised ones mean different things in different-length sentences. Sinusoidal encoding solves it with a bank of sine and cosine waves at geometrically spaced frequencies, giving every position a bounded, unique fingerprint that is defined for any length and whose similarity to other positions depends on the distance between them. It is added to the embedding rather than concatenated, and modern models mostly use learned or rotary variants — but all of them exist to answer the same question this one does.
Learned absolute encodings, and their limit
The simplest option is a trainable vector per position — effectively an embedding table indexed by position instead of by token. BERT and GPT-2 both did this.
It works well within the trained range and has one hard limitation: position 512 has no vector if the model was trained to 512. There is nothing to look up, so the model cannot process longer input at all without adding and training new rows.
That single constraint is a large part of why the field moved to rotary and ALiBi schemes. Extending a context window from 4,000 to 128,000 tokens is a plausible fine-tuning project with RoPE and effectively impossible with learned absolute positions.
Extending context after training
Because RoPE is a formula rather than a table, the angles can be reinterpreted — and that is how long-context versions of existing models are produced.
Position interpolation scales positions down so that a longer sequence maps into the range the model was trained on. Cheap, and it compresses the model's sense of fine-grained distance.
NTK-aware scaling adjusts the frequency base rather than the positions, distorting high frequencies less and preserving local resolution better.
YaRN combines interpolation with attention-temperature adjustment and is the current standard for large extensions.
All three usually involve a short fine-tune on long sequences to let the model adapt. The reason any of it works is that RoPE's rotations are continuous in position, so intermediate and stretched values remain meaningful.
Common mistakes
- Assuming attention handles order. It does not, at all. This is the most common misconception about transformers.
- Feeding input longer than the positional scheme supports, which either errors or silently degrades.
- Mixing schemes — loading weights trained with learned positions into an architecture using RoPE produces fluent nonsense.
- Forgetting positions shift with padding. Left-padding a batch moves every real token's index; most implementations handle it, and custom code frequently does not.
- Expecting extrapolation for free. Even RoPE degrades well beyond its training length without an interpolation fix.
Questions people ask
Why add positional information rather than concatenate it? Addition keeps the dimension unchanged and works empirically. Concatenation would spend model width on position, and was tried and abandoned.
Does adding to the embedding not corrupt the meaning? Somewhat, and the model learns to separate the two — there is enough room in a few hundred dimensions for both signals to coexist.
Do vision transformers need positional encodings? Yes — image patches are also an unordered set without them. Learned 2-D encodings are common there.
Which should I use? RoPE, if you are choosing. In practice you use whatever the pretrained model uses.
Why 10,000 in the sinusoidal formula? It sets the range of frequencies, chosen so the wavelengths span from a couple of positions to tens of thousands. It is a tuned constant, not a derived one.
Can a model learn position without any encoding? A causal decoder can partly infer it, because masking makes position observable through how many tokens are visible. It is a weak signal, and explicit encoding works far better.
Recap in one screen
- Self-attention is permutation-invariant, so order must be supplied separately.
- Sinusoidal encodings are fixed patterns at many frequencies, parameter-free and extendable.
- Learned absolute encodings work well up to the trained length and stop dead beyond it.
- RoPE rotates queries and keys by position, so attention scores depend on relative distance — and it is what current models use.
- Long-context versions of models come from reinterpreting RoPE's angles, plus a short fine-tune.