Roadmap / Attention & Transformers

✓

Rotary Position Embeddings (RoPE)

mediumattentiontransformerpositional-encoding

The problem

Attention on its own ignores the order of the tokens. RoPE (rotary position embedding) adds it by rotating the queries and keys: the vector of the token at position mm is turned by an angle proportional to mm. Then the score of a query and a key depends on how far apart the two tokens are (the theory page shows why). You will write the rotation.

Pairs of numbers, turned like points in the plane

A vector of dd numbers is read as d/2d/2 pairs of neighbours: (x0,x1)(x_0, x_1), (x2,x3)(x_2, x_3), and so on. Each pair is a point in the plane, and pair ii turns at its own speed θi\theta_i:

θi=base−2i/d,i=0,…,d/2−1\theta_i = \text{base}^{-2i/d}, \qquad i = 0, \dots, d/2 - 1

At position mm, pair ii is rotated by the angle mθim\theta_i:

x2i′=x2icos⁡mθi−x2i+1sin⁡mθix2i+1′=x2isin⁡mθi+x2i+1cos⁡mθi\begin{aligned} x'_{2i} &= x_{2i} \cos m\theta_i - x_{2i+1} \sin m\theta_i \\ x'_{2i+1} &= x_{2i} \sin m\theta_i + x_{2i+1} \cos m\theta_i \end{aligned}

where:

  • x2ix_{2i} and x2i+1x_{2i+1} are the two numbers of pair ii, and x′x' is the rotated vector;
  • mm is the position of the token: 0,1,2,…0, 1, 2, \dots;
  • base is usually 1000010000. The exponent makes θ0=1\theta_0 = 1 (the first pair turns 1 radian per position) and the later pairs turn more and more slowly.

These two lines are the standard rotation of a point around the origin: its distance from the origin does not change, only its direction.

Example. d=4d = 4, base 1000010000: two pairs, with speeds θ0=100000=1\theta_0 = 10000^0 = 1 and θ1=10000−2/4=0.01\theta_1 = 10000^{-2/4} = 0.01. The same vector (1,0,1,0)(1, 0, 1, 0) at positions 0, 1 and 2:

  • Position 0: both angles are 0, nothing turns: (1,0,1,0)(1, 0, 1, 0).
  • Position 1: pair 0 turns by 1 radian, pair 1 by 0.010.01: (0.540,0.841,1.000,0.010)(0.540, 0.841, 1.000, 0.010).
  • Position 2: pair 0 turns by 2 radians, pair 1 by 0.020.02: (−0.416,0.909,1.000,0.020)(-0.416, 0.909, 1.000, 0.020).

For pair 0 at m=1m = 1: (1⋅cos⁡1−0⋅sin⁡1, 1⋅sin⁡1+0⋅cos⁡1)=(0.540,0.841)(1 \cdot \cos 1 - 0 \cdot \sin 1,\ 1 \cdot \sin 1 + 0 \cdot \cos 1) = (0.540, 0.841). Pair 0 turns fast and tells nearby positions apart; pair 1 has barely moved, and changes noticeably only over hundreds of positions.

What to implement

apply_rope(x, positions, base=10000.0): x has shape (…,T,d)(\dots, T, d) and positions is an integer array of shape (T,)(T,), the position of each row. Return the rotated array, same shape.

Requirements

  • Pairs are adjacent dimensions (0,1),(2,3),…(0, 1), (2, 3), \dots, as in the RoFormer paper. (Some libraries pair dimension ii with i+d/2i + d/2 instead; that is a different, equally valid layout, but not the one asked for.)
  • Raise ValueError if dd is odd.
  • No Python loops.

Hints

  • Build the (T,d/2)(T, d/2) table of angles at once: positions as a column times the speeds as a row (broadcasting).
  • x[..., 0::2] is the first number of every pair and x[..., 1::2] the second. Compute both rotated halves, then write them back into an output array with the same slices.
solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.