4.1.7 · D2Transformer Architecture

Visual walkthrough — Rotary positional embeddings (RoPE)

2,731 words12 min readBack to topic

This is a companion to Rotary positional embeddings (RoPE). We assume you know what a query and key are from Attention Mechanism and Multi-Head Attention — nothing else.


Step 1 — An arrow on a grid: what a 2D vector even is

WHAT. Before any rotation, we need to agree what we are rotating. A vector here is just an arrow drawn from the origin (the point ) to some point on a flat grid. Two numbers pin it down: how far right, how far up.

WHY. RoPE works on pairs of embedding dimensions. So the whole story lives in a flat 2D world first — one pair of numbers, one arrow. Master this picture and the full 64-dimensional case is just this same picture stacked many times.

PICTURE. Look at the two chalk arrows below.

Figure — Rotary positional embeddings (RoPE)

Step 2 — The dot product measures alignment, not position

WHAT. Attention scores one token against another with a dot product. Here is the query arrow (the token doing the looking) and is the key arrow (the token being looked at) — both are ordinary 2D vectors exactly like Step 1, with ( = its rightward amount, = its upward amount). For these two arrows the dot product is:

WHY THIS TOOL and not another? We need one number that says "how much do these two arrows point the same way?" Multiplying matching parts and summing is the simplest such measure — and it has a beautiful geometric identity: where (Greek letter phi, just a name for the angle between the two arrows). The (cosine — the horizontal reach of a unit arrow tilted by ) is the star: it is when the arrows agree, when perpendicular, when opposite.

PICTURE. The angle between the query arrow and key arrow is the whole game.

Figure — Rotary positional embeddings (RoPE)

Hold that last bullet. If we can control , we control the score.


Step 3 — Rotation: spinning an arrow without stretching it

WHAT. A rotation by angle takes an arrow and turns it counter-clockwise by , keeping its length fixed. In numbers, the new arrow is where

WHY THIS TOOL? We want to stamp a position onto an arrow without changing what it "is" (its length, its content). Rotation is the unique move that changes direction only. Adding a vector (the old way) changes both direction and length — smearing content and position together. Rotation keeps them clean.

PICTURE. Watch one arrow before and after a turn — same length, new heading.

Figure — Rotary positional embeddings (RoPE)

Step 4 — Two rotations meet: the angles subtract

WHAT. This is the heart. Rotate by , rotate by , then dot them: The two absolute angles collapse into a single difference .

WHY. From Step 2, the dot product only cares about the angle between the two arrows. Turning by and by changes the gap between them by exactly . The individual turns cancel; only their difference survives. (Algebraically: , because and stacked rotations add.)

PICTURE. Same two arrows, both spun; the shaded gap between them is all the dot product feels.

Figure — Rotary positional embeddings (RoPE)

Step 5 — Make the angle be the position

WHAT. Now we choose the angles. For a token at position , rotate its arrow by ; for position , rotate by . Here (theta) is a fixed base frequency — radians of turn per step of position.

WHY. By Step 4 the score depends on . That is the relative distance between the two tokens. So the attention score now automatically reads "how far apart are these tokens?" — never their absolute slots. This is the promise the parent note made, now visible.

PICTURE. Positions and ; each step advances the arrow by the same , and the gap is .

Figure — Rotary positional embeddings (RoPE)

This is exactly the identity in the parent's Step 3 — now you saw why it holds.


Step 6 — Many frequencies: stacking flat worlds into dimensions

WHAT. Real heads have dimension (say 64). Split the vector into pairs. Pair (counting ) lives in its own flat world and spins at its own frequency:

HOW the pairs weave into one matrix. Each pair gets its own little rotation , and we stack them down the diagonal of one big matrix — a block-diagonal matrix, meaning every off-diagonal block is all zeros so the pairs never mix:

  • Each ::: the spin from Step 3, but with angle (position times that pair's speed).
  • the zeros ::: pair 's two coordinates are rotated only among themselves; dimension 1–2 never leak into dimension 3–4.

In code you never build this big matrix. You reshape into pairs, and for pair apply the same two lines from Step 3 with angle : Do the same to with its position , then take the ordinary dot product . Because the matrix is block-diagonal, that dot product is just the sum over pairs of each pair's rotated dot product — and Step 4 fires inside every pair, so each contributes a term depending only on . That is exactly how the schedule gets woven into .

WHY (where the exponent comes from). We want the clocks to span a huge range of speeds, from "one radian per step" down to "barely turning at all", spaced evenly on a log scale (so each clock is a fixed ratio slower than the last — the natural way to cover many scales with few clocks). Demand two things:

  • The fastest clock () should turn at . Check: . ✓
  • The slowest clock (, essentially the largest ) should turn about times slower, i.e. .

The only smooth rule that hits at and at the top, evenly in log-space, is an exponential in : . The exponent just re-labels the pair index (which runs ) onto the interval so the base is raised to a fraction from down to almost . The number is a chosen constant fixing the ratio between the fastest and slowest clock. This is the same schedule used by Relative Position Bias (T5)-era sinusoidal encodings — RoPE reuses it as rotation speeds rather than additive waves.

WHY (what the range buys us). Fast clocks ( large, small ) resolve nearby tokens sharply; slow clocks ( small, large ) stay distinct over long ranges. Together they read distance at every scale — the mechanism behind the length-extrapolation gains discussed in Length Extrapolation in LLMs.

PICTURE. Three clocks side by side, spinning at different rates for the same position.

Figure — Rotary positional embeddings (RoPE)

Step 7 — Edge and degenerate cases (the parts people skip)

WHAT / WHY / PICTURE, four short cases:

(a) Same position, . Then the relative distance is , so and (the identity — do nothing). The score is the plain, unrotated dot product . Good: a token compared with itself carries no positional twist.

(b) The wrap-around. An angle only lives mod (a full circle brings an arrow back to where it started). Wrap-around happens when the total relative rotation satisfies — note this is about , the whole accumulated gap, not the per-step angle . Whenever two different relative distances land on angles apart, they produce the same arrow and hence the same score. This is why extrapolation works for a single clock: an absolute position never seen (say 2000) still produces a relative angle the model already met during training. (Multiple clocks at different wrap at different distances, so together they stay unambiguous over the trained range.)

(c) Negative distance, . Then : rotation goes clockwise instead of counter-clockwise. is the inverse rotation — a reverse spin by that exactly undoes (so ), not a reflection. Because the spin direction flips sign, "B before A" and "A before B" are cleanly distinguished by the sign of the angle — order is preserved.

(d) Values are never rotated. Attention computes , where is the value matrix — the stack of content vectors that actually get mixed together to form each token's output (the only decide how much of each to take). RoPE spins only and ; the value vectors are left completely untouched. Position belongs in (which tokens attend to which) — twisting would corrupt the content being aggregated. This is a point worth stressing because the name "Rotary Embedding" tempts people to rotate everything, including the values; only the query–key pair carries the rotation.

PICTURE. The four cases on one board.

Figure — Rotary positional embeddings (RoPE)

Worked check — parent Example 1, redrawn

, , , positions , .

  • (position 0 ⇒ angle 0 ⇒ no turn).
  • .
  • Dot: .

The one-picture summary

Figure — Rotary positional embeddings (RoPE)

The whole derivation on one board: two arrows → spin each by its position-angle → the dot product forgets both absolute angles and keeps only the gap → stack many such flat worlds at frequencies to read distance at every scale.

Recall Feynman retelling — say it back in plain words

Imagine every token is a little arrow. Instead of scribbling its position number next to it (the old "adding" way, which smears the arrow's meaning), we just spin the arrow — a bit for position 1, twice as much for position 2, and so on. Spinning never stretches the arrow, so its meaning is untouched; only its heading changes.

When we compare two tokens, we take a dot product, and a dot product is blind to where each arrow points on the page — it only feels the gap between them. Spin the first arrow by steps and the second by steps, and that gap becomes steps: pure relative distance. Absolute positions cancel out on their own.

To read distance at every scale, we don't use one arrow but many arrow-pairs, each spinning at its own speed — fast little clocks for nearby words, slow big clocks for far-apart words. Each pair sits in its own block of a big block-diagonal matrix, spinning independently and never mixing with the others. Because a full turn wraps back to the start, a position we never trained on still lands on a familiar relative angle, so the model keeps working on longer texts than it ever saw. And we only spin the queries and keys — the values, which carry the actual content we mix together, are left in peace.

Recall Quick self-test

Why does the attention score depend only on ? ::: Because a dot product only sees the angle between two arrows, and spinning by and by makes that between-angle equal ; the absolute turns cancel. Why lower dimensions get higher frequencies? ::: Fast clocks resolve nearby tokens but wrap too soon for long range; slow clocks stay distinct over long distances — so we assign fast to local (low ), slow to global (high ). When does wrap-around occur? ::: When the total relative rotation is a whole multiple of — i.e. — not when the per-step angle is zero. Why are values not rotated? ::: (the value/content vectors) is the stuff to aggregate; only decides which tokens to attend to, so position belongs there and not in .