RoPE: positions as rotations

Almost every modern LLM marks word order by rotating queries and keys. Each pair of numbers is turned by an angle that grows with the token’s position, so the attention score between two tokens depends only on how far apart they are. Stretching those angles is how models are extended to longer texts.

Key idea
Split q and k into pairs, rotate pair i by position × θᵢ; the score q·k then depends only on the offset; extend context by slowing the slow pairs (PI, NTK, YaRN)
Real world
Two people on a merry-go-round. Wherever the ride has turned to, the angle between them stays the same. You can tell how far apart they sit without knowing where the ride started.
In an LLM
In Llama 3, “it” at position 812 and “cat” at position 805 get the same position effect on their attention score as the same two words at positions 12 and 5: an offset of 7 either way.

The problem RoPE solves

Attention on its own has no idea of order: it compares every query with every key by a dot product, and shuffling the tokens just shuffles the scores. GPT-2 fixed this the way the positions lesson describes: it learned one vector per position (1,024 of them) and added it to the token’s embedding.

That has two weaknesses. The model can’t read past position 1,024, because there is no vector for position 1,025. And what usually matters for language is relative position (“the word just before me”, “the noun three words back”), but an added absolute vector makes the model work that out for itself.

Rotary position embedding, or (Su et al., 2021), builds the relative part into the arithmetic. Since 2023 it has been the default: Llama, Mistral, Qwen, DeepSeek, Gemma and gpt-oss all use it.

Rotate, don’t add

RoPE leaves the embeddings alone. Instead, after a head has made its query qq and key kk (see queries, keys and values), it:

  1. Splits each vector into pairs of numbers: (q1,q2),(q3,q4),…(q_1, q_2), (q_3, q_4), \dots A head of 128 numbers has 64 pairs.
  2. Treats each pair as an arrow in a flat plane.
  3. Turns the arrow by an angle equal to the token’s position × that pair’s frequency θi\theta_i.

Turning an arrow by angle α\alpha is multiplication by a rotation matrix:

(x′y′)=(cos⁡α−sin⁡αsin⁡αcos⁡α)(xy)\begin{pmatrix} x' \\ y' \end{pmatrix} = \begin{pmatrix} \cos \alpha & -\sin \alpha \\ \sin \alpha & \cos \alpha \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix}

Values are not rotated. Only queries and keys are, because only they meet in the score.

Why only the offset survives

The dot product of two arrows is their lengths times the cosine of the angle between them. If the query sits at position mm and the key at position nn, the query was turned by mθm\theta and the key by nθn\theta. The angle between them changed by (m−n)θ(m - n)\theta, and nothing else changed. So the score depends on the content of the two tokens and on the offset m−nm - n, never on mm or nn alone.

Try it. Set any two positions, then shift both tokens along together:

shift both tokens:
offset (query − key)+3
angle from k to q105°
score q·k−0.245
The query at position 5 is turned by 5 × 0.3 = 1.5 rad, the key at position 2 by 0.6 rad. What matters for the score is the difference, 3 × 0.3 = 0.9 rad. Every dot on the curve is one offset; the big dot is yours. Now press “1 →” to move both tokens.
One pair of numbers from a query and one from a key, drawn as arrows. Each arrow is turned by its token’s position times the pair’s frequency. Change the positions, or shift both tokens together and watch the score.

The curve on the right is the score for every offset. Shifting both tokens moves you along nothing: you stay on the same dot. Changing only one position slides you along the curve.

example · the merry-go-round

Two friends sit on a merry-go-round, one on a horse, one in a teacup. As it turns, both move, but the angle between them, seen from the centre, never changes. A photo taken at any moment tells you how far apart they sit, not how long the ride has been going. RoPE gives every token a seat on many merry-go-rounds at once, and the attention score only ever sees the gaps.

example · “it” and “cat” anywhere in a document

An attention head that links a pronoun to a noun a few tokens back should work the same on page 1 and page 40. With RoPE it does by construction: “cat” at position 805 and “it” at position 812 have the same offset, 7, as “cat” at 5 and “it” at 12, so the position part of their score is identical. With GPT-2’s learned vectors, the model has to learn that rule separately for every pair of positions it ever sees.

Many pairs, many speeds

If every pair turned at the same speed, the score would repeat every full turn and the model could not tell an offset of 3 from an offset of 3 + one full turn. So each pair gets its own frequency, spread over a huge range:

θi=b−2i/d,i=0,1,…,d2−1\theta_i = b^{-2i/d}, \qquad i = 0, 1, \dots, \tfrac{d}{2} - 1

where dd is the head size and bb is the base, 10,000 in the original paper. The first pair turns 1 radian per token (a full turn every 6.3 tokens); the last turns about 1/b1/b radians per token and needs tens of thousands of tokens to go round.

position 12
each clock is one pair; the number under it is how many tokens one full turn takes
pair 16.3
pair 220
pair 363
pair 4199
pair 5628
pair 62k
pair 76k
pair 820k
cos(position × frequency) for every pair (rows) and positions 0 to 127 (columns)
At position 12, pair 1 has turned 12 rad (1.9 full turns), while pair 8 has turned only 0.2°. Fast pairs repeat every few tokens, so they tell neighbours apart, but on their own they confuse positions about one turn apart (pair 1 is back almost where it started after 6 tokens). Slow pairs barely move between neighbours but keep counting over long distances. Together they work like the hands of a clock.
A 16-number query split into 8 pairs. Pair 1 turns fastest, pair 8 slowest. Press play to move the token along the sequence, or drag the slider.

This is the same trick as the hands of a clock: the second hand is precise over short times, the hour hand tells you roughly where you are in the day. Fast pairs separate neighbours; slow pairs measure long distances. Models with long contexts raise the base so the slowest hands turn more slowly still: Llama 3 uses 500,000, Qwen3 1,000,000.

Reading past the training length

A model trained on 4,096-token texts has only ever seen the angles that positions 0 to 4,095 produce. Fast pairs went round hundreds of times, so every angle is familiar. The slowest pairs only covered part of a turn. Feed the model 16,000 tokens and those pairs point at angles it has never seen, and the output falls apart.

Three fixes, each a way of squeezing angles back into the familiar range:

  • Position interpolation (Chen et al., 2023): divide every position by the stretch factor. Everything fits, but neighbours now look closer together than in training, which blurs the fast pairs.
  • NTK-aware scaling (an online post by “bloc97”, 2023): raise the base instead. The fastest pair keeps its speed and the slowest pair is fully interpolated, with a smooth slide between.
  • YaRN (Peng et al., 2023): decide per pair. Pairs that turned many times in training are left alone, pairs that never finished a turn are interpolated, those in between are blended. It also sharpens the softmax slightly, because attention spreads thinner over a longer text.
pair 1326 turns seenstep kept 100%fine
pair 2103 turns seenstep kept 100%fine
pair 333 turns seenstep kept 100%fine
pair 410 turns seenstep kept 100%fine
pair 53.3 turns seenstep kept 100%fine
pair 61.0 turns seenstep kept 100%fine
pair 7117° seenstep kept 100%angles never seen
pair 837° seenstep kept 100%angles never seen
pairs pushed into unseen angles2
fast pairs blurred0
Pairs 1 to 6 already went round at least once during training, so any angle is familiar. Pairs 7 and 8 never completed a turn: at position 8,192 they point at angles the model has never seen (red), and quality collapses. 2 pairs are out of range.
A toy head with 8 pairs (base 10,000), trained on 2,048 tokens, now asked to read 8,192. Shaded arc: the angles a pair saw in training. Hand: the angle it needs at the new last position. Pick a method.

All three need little or no fine-tuning, which is why context extension became cheap. Real recipes: DeepSeek-V3 was pre-trained at 4K tokens and stretched to 128K with YaRN; Qwen3 trains at 32K and offers YaRN for 128K; Llama 3.1 went from 8K to 128K with its own per-frequency rule in the same spirit.

What it costs

Nothing in parameters: the angles are fixed, not learned. The rotation is a few multiply-adds per number, fused into the attention kernel. RoPE is applied after the query and key are made and before the score, so it also works with a KV cache: each key is rotated once, by its own position, and stored that way.

going deeper

The formula for a whole vector

For a head of size dd, RoPE multiplies qq at position mm by a block-diagonal matrix of d/2d/2 rotations:

Rm=diag⁡(R(mθ0), R(mθ1), …, R(mθd/2−1)),R(α)=(cos⁡α−sin⁡αsin⁡αcos⁡α)R_m = \operatorname{diag}\big(R(m\theta_0),\, R(m\theta_1),\, \dots,\, R(m\theta_{d/2-1})\big), \qquad R(\alpha) = \begin{pmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{pmatrix}

Because rotations satisfy Rm⊤Rn=Rn−mR_m^\top R_n = R_{n-m}:

(Rmq)⊤(Rnk)=q⊤Rm⊤Rn k=q⊤Rn−m k(R_m q)^\top (R_n k) = q^\top R_m^\top R_n\, k = q^\top R_{n-m}\, k

which depends on mm and nn only through n−mn - m.

The context-extension rules

With stretch factor ss, original length LL and per-pair frequency θi\theta_i:

  • Position interpolation: θi′=θi/s\theta_i' = \theta_i / s.
  • NTK-aware: b′=b⋅sd/(d−2)b' = b \cdot s^{d/(d-2)}, which leaves θ0\theta_0 unchanged and makes the last pair exactly θ/s\theta/s.
  • YaRN: let ri=Lθi/2πr_i = L\theta_i / 2\pi be the number of turns pair ii made in training, and γi\gamma_i ramp from 0 at ri≤αr_i \le \alpha to 1 at ri≥βr_i \ge \beta (the paper uses α=1\alpha = 1, β=32\beta = 32 for Llama). Then θi′=(1−γi) θi/s+γi θi\theta_i' = (1-\gamma_i)\,\theta_i/s + \gamma_i\,\theta_i, and queries and keys are each multiplied by 1/t=0.1ln⁡s+1\sqrt{1/t} = 0.1 \ln s + 1.

The widget uses a toy head of 16 numbers (8 pairs), base 10,000 and L=2,048L = 2{,}048.

In code

import torch
 
def rope(x, pos, base=10000.0):          # x: (T, d), pos: (T,)
    d = x.shape[-1]
    theta = base ** (-torch.arange(0, d, 2) / d)       # (d/2,)
    ang = pos[:, None] * theta[None, :]                # (T, d/2)
    cos, sin = ang.cos(), ang.sin()
    x1, x2 = x[..., 0::2], x[..., 1::2]                # the pairs
    return torch.stack([x1 * cos - x2 * sin,
                        x1 * sin + x2 * cos], dim=-1).flatten(-2)

Many libraries pair dimension ii with i+d/2i + d/2 instead of 2i2i with 2i+12i+1. It is the same idea with the numbers stored in a different order.

Real settings

ModelBase bbTrained lengthExtended to
Llama 3 8B500,0008,192131,072 (Llama 3.1, factor 8)
Qwen31,000,00032,768131,072 (YaRN)
DeepSeek-V3 (64 rotated numbers per head)10,0004,096128K (YaRN, factor 40)
gpt-oss-120b150,0004,096131,072 (YaRN, factor 32)

Numbers from each model’s config.json and technical report.

In papers

Related

  • positions - Attention on its own can’t tell word order: shuffle the tokens and every token’s output stays the same, only moved. So models add information about position. GPT-2 learns one vector per position; the original transformer used sine waves of different speeds; most modern models rotate queries and keys instead (RoPE).
  • qkv - Attention is three matrix multiplications to make queries, keys and values, one more to compare every query with every key, a softmax to turn scores into weights, and a last multiplication to mix the values. Here is every number.
  • efficient-attention - Attention’s two costs are the KV cache, which grows with every token, and the score matrix, which grows with the square of the length. Modern models share keys and values between heads (GQA, MLA), let most layers look only at recent tokens, compute attention in tiles that never store the full matrix (FlashAttention), and pick a few keys to read per query (sparse attention).
  • modern-recipe - A 2026 open model is still GPT-2’s skeleton: embed, a stack of attention and feed-forward blocks on a residual stream, then predict the next token. Almost every part inside has been swapped: RMSNorm, SwiGLU, RoPE, shared or compressed keys and values, mixtures of experts, no biases, QK-norm, bigger vocabularies and contexts a thousand times longer. Here is each change, model by model.