LLM Internals a learning center

Positional Encoding

Attention is order-blind by construction, so position has to be injected — as stacked sine waves, learned vectors, or rotations.

What positional encoding is

The problem it solves, and the one-line definition

A transformer processes every token in parallel — there is no left-to-right scan, no memory of “what came before,” nothing in the attention arithmetic that knows about sequence. The attention score between two tokens is a dot product of a query and a key, and a dot product does not care where either vector came from. Shuffle the input tokens and the outputs shuffle identically. To the raw mechanism, “the king is here” and “here is the king” are the same bag of tokens.

That is not a small defect; it deletes syntax. Word order is most of English grammar, and the model literally cannot see it. So position has to be injected into the input — turned into numbers and mixed into each token’s vector so that the same token at position 3 and position 17 arrives at attention as two different vectors.

Definition. A positional encoding is a position-dependent vector, the same width as the token embedding, that is combined with each token’s embedding before attention — classically by simple addition — so that token identity and token position both live in the input representation.

The vector comes from the same space the Embeddings topic describes — position is just added into the row, dimension by dimension. And the choice of how to encode position turns out to control how far past its training length a model can stretch, which is exactly the Context Window story.

The mechanic

Every position becomes a stack of sine waves at different frequencies

The original transformer’s scheme is sinusoidal: position pos maps to a deterministic vector whose entries are sines and cosines at geometrically spaced frequencies,

PE(pos, 2i)   = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))

Words undersell what that looks like. Look at it instead — the whole encoding matrix at once, then two positions pulled out and compared, then the modern alternatives side by side:

Positional encoding explorer interactive
the whole encoding at once — 64 positions × 32 dimensions, one row per position
pos ↓dimension 0 … 31 → (frequency drops left to right)08162432404856

blue = positive, amber = negative. Left columns cycle every ~6 positions; right columns barely move in 64. Every row is unique, and rows drift apart smoothly — that is the entire trick.

7
12
similarity0.736dot 11.78 · offset 5
offset 0offset 63

The grey curve is similarity vs. offset, recomputed from position A; the dot marks the current offset |A−B|. Drag A: the curve is rebuilt from a new starting position, yet its shape never changes. Similarity depends only on the gap between two positions — relative position falls out of the encoding for free.

what each scheme does at position p — drag past the training length and watch who survives
p = 20

The rose line marks the longest position seen in training (here 32). Everything to its right is extrapolation.

Sinusoidal
acts on: input embedding (added)
The formula produces a valid vector at any p — but the model never learned to read phase combinations past 32, so quality decays.
Learned
acts on: input embedding (added)
Row 20 of a trained 32-row lookup table. Flexible, but the table ends where training ended.
RoPE
acts on: Q and K (rotated)
fast pair
slow pair
Each dimension pair rotates by p·ω. Angles are defined at any p, and Q·K depends only on the angle difference — i.e. relative distance.
ALiBi
acts on: attention scores (bias)
bias = −0.25·distance, most recent key highlighted
farthest key: distance 20 → bias 5.00
No position vector at all: subtract slope·distance from every attention score. The penalty just keeps growing — any length works.

First panel: every position's vector, one row each — see the fast wave bands on the left and the near-frozen ones on the right. Second panel: drag positions A and B; their vectors overlay and the similarity readout updates. Notice the grey similarity-vs-offset curve keeps its shape no matter where A sits — only the offset |A−B| matters. The four cards at the end share one position slider — drag it past the rose training-length line and watch which schemes survive.

Three things the picture is showing you:

  • Each dimension pair is a clock running at its own speed. The leftmost pair cycles every ~6 positions (wavelength 2π); each pair to the right runs slower, out to a wavelength of thousands of positions. Fast clocks give fine position, slow clocks give coarse position — together the row pins down exactly one position, the same way hour, minute, and second hands pin down a time of day.
  • Nothing is learned in the encoding itself. The matrix is fixed before training starts. What the model learns is how to read it — which attention heads should use which frequency bands.
  • Relative position falls out for free. The dot product between two encodings depends only on the offset between them, not their absolute location — you verified this in the widget when the grey similarity curve refused to change shape as you dragged A around. Even better, PE(pos+k) is a fixed rotation of PE(pos) (each sin/cos pair turns by k·ω), so “attend 3 tokens back” is a linear operation the model can learn once and apply anywhere.

The four schemes in one breath. Sinusoidal: fixed sin/cos vector added to the embedding (original transformer, d=512 → 256 frequency pairs). Learned: a trainable table with one row per position, added the same way (GPT-2: 1,024 rows; GPT-3: 2,048; BERT: 512). RoPE: no addition at all — rotate each Q and K dimension pair by an angle proportional to position, so relative offset lands directly in the attention dot product (Llama, Qwen, Mistral, most current models). ALiBi: skip vectors entirely and subtract slope × distance from every attention score (BLOOM, MPT).

Where this happens in the stack matters: additive schemes (sinusoidal, learned) mix position into the embedding before the Q/K/V projections the Attention topic walks through. RoPE acts later, inside attention, on Q and K after projection. ALiBi acts later still, on the score matrix itself. Same goal, three different insertion points.

Worked example

d = 8, positions 0–2, every number real

Shrink the model width to d = 8 so the whole vector fits on a line. That gives four sin/cos pairs with divisors 10000^(2i/8) = 1, 10, 100, 1000 — wavelengths of 6.3, 63, 628, and 6,283 positions. The first three rows of the encoding matrix:

possin(p/1)cos(p/1)sin(p/10)cos(p/10)sin(p/100)cos(p/100)sin(p/1000)cos(p/1000)
00.00001.00000.00001.00000.00001.00000.00001.0000
10.84150.54030.09980.99500.01001.00000.00101.0000
20.9093−0.41610.19870.98010.02000.99980.00201.0000

Read a row left to right and you can see the clock speeds: between position 1 and 2 the first pair swings hard (0.8415 → 0.9093, 0.5403 → −0.4161), the second pair moves a little, and the last two pairs barely twitch. One step is a big deal to the fast clock and nothing to the slow ones.

The generator is a few lines:

import numpy as np

def pe(pos, d=8, base=10000):
    i = np.arange(d // 2)
    angles = pos / base ** (2 * i / d)   # divisors: 1, 10, 100, 1000
    out = np.empty(d)
    out[0::2] = np.sin(angles)
    out[1::2] = np.cos(angles)
    return out

np.round(pe(1), 4)
# array([0.8415, 0.5403, 0.0998, 0.995 , 0.01  , 1.    , 0.001 , 1.    ])

# similarity depends ONLY on the offset, not the location:
pe(7) @ pe(8),  pe(30) @ pe(31)   # (3.5353, 3.5353)   offset 1
pe(7) @ pe(9),  pe(30) @ pe(32)   # (2.5637, 2.5637)   offset 2
pe(7) @ pe(7)                     # 4.0  (= d/2, since sin² + cos² = 1)

That last block is the property the widget’s similarity curve plots: the dot product between PE(7) and PE(8) is 3.5353, and between PE(30) and PE(31) it is exactly the same 3.5353. The encoding hands attention a clean “how far apart are we?” signal at any absolute location. This is what the input to the model actually is: token embedding + this vector, summed elementwise, and only then projected to Q, K, and V.

What breaks

Order blindness, and three ways to die past the training length

  • No positional encoding: syntax is gone. Without the injected vector, attention is permutation-equivariant — “the king is here” and “here is the king” produce identical representations up to reordering. The model can still do bag-of-words topic matching; it cannot do grammar, negation scope, or “who did what to whom.”
  • Learned encodings hit a wall, hard. GPT-2 has exactly 1,024 position rows. Position 1,025 is not “poorly encoded” — the row does not exist. Feeding a longer sequence is an out-of-bounds lookup; the context length is a hard architectural ceiling. This is the worst extrapolation behavior of the four schemes.
  • Sinusoidal encodings extrapolate in theory, badly in practice. The formula produces a perfectly valid vector at position 50,000 — but the model only learned to interpret the phase combinations it saw during training. Past the training length the vectors are novel inputs, and quality degrades fast. Defined ≠ understood.
  • RoPE stretches — with tricks — and that is why it won. Because RoPE encodes only relative angles, positions can be rescaled after training: position interpolation, NTK-aware scaling, and YaRN squeeze longer sequences into the angle range the model already understands. That is the machinery behind stretching a model trained at 8K out to 128K — the long-context story in the Context Window topic. ALiBi extrapolates natively (the distance penalty just keeps growing) but gives up long-range precision to do it.
  • A subtle one: additive encodings share the embedding space. The position vector is summed into the same dimensions carrying token meaning (the Embeddings topic’s space) — the model must learn to keep “what” and “where” separable inside one vector. RoPE sidesteps this entirely by never touching the values, which is part of why it composes better at scale.

Interview pressure test

Answers hidden — use as flashcards

Why is attention order-blind in the first place? Point at the exact operation.

The attention score is q·k / √d — a dot product of two projected vectors. Nothing in that product references an index; if you permute the input tokens, the score matrix permutes with them and every output vector is unchanged, just reordered (permutation equivariance). Order blindness is not a bug of a particular model, it is a property of the operation — so position must be added to the representations, because it will never emerge from the mechanism.

Where exactly is position injected, for each of the four schemes?

Sinusoidal and learned encodings are added to the token embedding before the Q/K/V projections — position enters once, at the bottom, and rides through the whole stack. RoPE is applied inside each attention layer: Q and K are rotated after projection (V is untouched). ALiBi is latest of all: a −slope·distance bias added directly to the pre-softmax attention scores. Knowing the insertion point is the difference between memorizing names and understanding the design space: embedding → projections → scores.

Sinusoidal vs learned vs RoPE vs ALiBi — one line each, with the trade-off.

Sinusoidal: fixed sin/cos vector added to the embedding — free, deterministic, extrapolates poorly. Learned: trainable per-position rows — maximally flexible inside the trained range, undefined one token past it. RoPE: rotate Q/K by position-proportional angles so the dot product sees relative offset — best-in-class long-context behavior via rescaling tricks, now the default. ALiBi: no vectors, just a distance penalty on attention scores — extrapolates natively, but encodes nothing about position except “farther is fainter.”

Why does a learned positional embedding fail at position max+1 when a sinusoidal one doesn't?

A learned encoding is a lookup table with a fixed number of rows; position max+1 is an index that has no row — the failure is structural, not statistical. The sinusoidal formula is a function, defined for every real position, so it always produces a vector — but the model has never seen those phase combinations, so it misreads them. One scheme fails by absence, the other by inexperience. Neither actually works past the training length, which is why the question “how was this model’s context extended?” is really a question about its positional scheme.

A model was trained at 8K context and ships with 128K. What happened, mechanically?

Almost certainly RoPE plus a rescaling trick. RoPE encodes position as rotation angles of Q/K pairs, and attention only sees angle differences — so you can compress 128K of positions into the angle range the model learned at 8K (position interpolation), or rescale the frequency base so slow dimensions stretch while fast ones keep local precision (NTK-aware scaling / YaRN), usually followed by a short fine-tune. This works precisely because RoPE is relative; a learned absolute table has no equivalent move — there is nothing to rescale, only rows that don’t exist.

The same token appears at positions 3 and 17. Are its Q/K/V vectors the same or different, and why does that matter?

Different. The input to the projections is embedding(token) + PE(pos) (or, for RoPE, the projections get rotated by a position-dependent angle), so the same token at two positions yields different queries, keys, and values. That is the entire point: it lets attention distinguish “king at position 3” from “king at position 17,” which is what makes word order — and therefore syntax — visible to a mechanism that is otherwise a bag of vectors.

This connects to