MiniMind 学习笔记 07:RoPE 旋转位置编码,从代码到三角公式

Attention 本身只看 token 内容,不天然知道 token 的位置。

如果两个 token 的内容向量相同,普通 attention 的点积也可能相同。模型需要某种方式知道:

这个 token 在第几个位置;
两个 token 相隔多远;
哪个 token 在前,哪个 token 在后。

RoPE,也就是 Rotary Position Embedding,是当前 LLM 中非常常见的位置编码方法。

MiniMind 也使用 RoPE。

1. 为什么需要位置编码

Transformer 的 self-attention 本质是集合式计算。

如果没有位置编码,下面两句话在某些层面会很难区分:

猫追狗
狗追猫

词一样,但顺序不同,语义完全不同。

所以模型必须引入位置信息。

早期 Transformer 使用绝对位置编码:

token embedding + position embedding

RoPE 的思路不同:它不直接把位置向量加到 token embedding 上,而是在 Attention 里的 Q/K 上做旋转。

2. RoPE 做在哪里

MiniMind 的 Attention 里,Q/K 经过投影和 norm 后,会进入:

xq, xk = apply_rotary_pos_emb(xq, xk, position_embeddings)

也就是说 RoPE 作用在:

Q 和 K

不是作用在 V,也不是直接作用在 hidden state。

原因是 Attention score 来自:

Q @ K^T

位置关系最需要影响的是“匹配关系”,也就是谁应该关注谁。

V 更像内容本身,不一定需要被位置旋转。

3. 二维旋转的基本公式

先看一个二维向量:

[a, b]

旋转角度 θ 后变成:

[a cosθ - b sinθ,
 a sinθ + b cosθ]

矩阵写法是:

[ cosθ  -sinθ ] [a]
[ sinθ   cosθ ] [b]

RoPE 就是把高维向量两两分组,在每个二维平面里做旋转。

例如 head_dim=96,会分成:

(0, 1)
(2, 3)
(4, 5)
...

每一组用不同频率。

4. rotate_half 在做什么

代码里常见:

x * cos + rotate_half(x) * sin

其中 rotate_half 做的是二维旋转里的:

[a, b] -> [-b, a]

于是:

x * cos        = [a cos, b cos]
rotate_half*x = [-b sin, a sin]

相加后:

[a cos - b sin,
 b cos + a sin]

也就是标准二维旋转:

[a cos - b sin,
 a sin + b cos]

注意:rotate_half 作用在 hidden/head_dim 维度上,不作用在 token 长度维度上。

如果 Q 的形状是:

[batch, seq_len, heads, head_dim]

比如:

[1, 6, 8, 96]

那么 rotate_half 操作的是最后的 96,不是 6

它不会改变 token 顺序。

5. 为什么 RoPE 天然支持相对位置

这是 RoPE 最关键的数学点。

假设 query 在位置 m,key 在位置 n

RoPE 分别把它们旋转:

q_m = R(mθ) q
k_n = R(nθ) k

Attention score 是:

score(m, n) = (R(mθ)q)^T (R(nθ)k)

二维展开后会出现:

cos((m - n)θ)
sin((m - n)θ)

核心三角恒等式是:

cosA cosB + sinA sinB = cos(A - B)
sinA cosB - cosA sinB = sin(A - B)

令:

A = mθ
B = nθ

就得到:

A - B = (m - n)θ

也就是说,两个绝对位置分别旋转后做点积,结果里自然出现了相对距离 m - n

这就是 RoPE 强大的地方。

6. 一个简单例子

假设:

q = [1, 0]
k = [1, 0]

不用 RoPE:

q^T k = 1

不管两个 token 相隔多远,score 都是 1。

使用 RoPE 后:

R(mθ)q = [cos(mθ), sin(mθ)]
R(nθ)k = [cos(nθ), sin(nθ)]

点积:

cos(mθ)cos(nθ) + sin(mθ)sin(nθ)
= cos((m - n)θ)

如果 m 和 n 很近,某些频率下相关性可能高。

如果距离变大,相关性会随相位变化而改变。

于是 Attention score 不再只是内容相似度,还包含了相对位置关系。

7. 为什么有很多频率

真实模型里 head_dim 不止 2 维。

RoPE 会给不同二维分组不同频率:

低频维度:变化慢,适合长距离关系。
高频维度:变化快,适合短距离关系。

这有点像 Fourier 特征。

不同维度用不同频率编码位置变化,模型可以组合出丰富的位置感知能力。

8. 为什么长文本还需要 YaRN / NTK scaling

原始 RoPE 对训练长度附近的上下文表现很好。

但如果训练时只见过:

4k context

推理时直接拉到:

32k / 128k

旋转角度可能进入训练时没见过的范围,相位变化会变得不稳定。

所以后来出现了:

NTK-aware scaling
YaRN
LongRoPE

这些方法本质上都是调整 RoPE 的频率或缩放方式,让长上下文外推更稳定。

MiniMind 也支持推理时的 RoPE scaling。

9. 和代码形状对应

调试时你看到:

position_embeddings: [6, 96]

这里:

6  = 输入 token 长度
96 = head_dim

因为 RoPE 是对每个 token 位置、每个 head_dim 维度准备 cos/sin。

如果输入长度是 6,每个 head 维度是 96,就需要:

每个位置一组 96 维的旋转参数。

这和 Q/K 的最后一维对齐。

10. 小结

RoPE 可以用一句话概括:

在 Q/K 空间做位置相关旋转,让 Attention score 天然携带相对位置信息。

它的核心不是“把 sin/cos 加到 embedding 上”,而是:

把位置变成旋转角度;
让 Q/K 点积通过三角恒等式出现 m - n。

理解了这一点,再看 apply_rotary_pos_embrotate_halfposition_embeddings 的形状,就会清楚很多。

发表评论