模型架构 / 位置编码 原理笔记进阶 4 分钟阅读 查看源文件 ↗

RoPE:旋转位置编码与相对位置#

本页内容5 个部分
查看概览图
POSITION ENCODING

按绝对位置旋转 q / k,
点乘后只剩相对位置

RoPE 不把位置向量加到 embedding 上,而是把 head 内每两维当成一个平面,按 m·θᵢ 旋转 q 和 k。

INPUT ht token hidden state
PROJECT Q / K / V [B, S, n_h, d_h]
PAIR (q2i, q2i+1) 每两维一个平面
ROTATE R(m·θi) 按绝对位置旋转
SCORE q · k 普通点乘
旋转发生在每个 head 内部——不是加到 token embedding 上
01二维旋转

RoPE 的最小单位是一个平面

q · mθ k · nθ (n − m)θ
  • R(a) = [[cos a, −sin a], [sin a, cos a]]
    旋转矩阵
  • R(a)[x, y] = [x cos a − y sin a, x sin a + y cos a]
  • rotate_half([x, y]) = [−y, x]
    垂直方向
展开 R(mθ)ᵀ R(nθ)
= R((n−m)θ)
02相对位置

点乘后绝对位置自己消掉

QUERY @ mrope(qm, m)·KEY @ nrope(kn, n)

= qm⊤ R((n − m)θ) kn —— 只剩位置差 n − m

核心直觉RoPE 先按绝对位置旋转 q/k,但 q/k 点乘后只剩相对位置差。
核心实现q_rot = q * cos + rotate_half(q) * sink_rot = k * cos + rotate_half(k) * sin
head_dim = 8一个 head 里有多把尺子一对维度共用一个频率
(q0, q1) → θ₀最快
(q2, q3) → θ₁慢一点
(q4, q5) → θ₂更慢
(q6, q7) → θ₃最慢
长尺度θ 越小转得越慢,
覆盖越长的位置跨度
常见误解
RoPE 不是把 360° 切成 token_length × head_dim 份

角度单位是弧度,`angle_i = m · θ_i` 可以超过 2π —— cos / sin 本来就是周期函数,转过一圈继续算即可。每个二维对有自己的旋转速度,这才是「多尺度」的来源。

作用在每个 head 内部一对维度 = 一个旋转平面score 只依赖 n − m

RoPE,全称 Rotary Position Embedding,中文常叫旋转位置编码。

它的作用是:让 Transformer 的 attention 在计算 q · k 时感知 token 之间的相对位置。

一句话版本:

RoPE 不是把位置向量加到 token embedding 上,而是按位置旋转每个 attention head 里的 q 和 k,让它们点乘时自然带上相对距离信息。


1. 位置编码的问题与计算对象#

1.1 为什么需要位置编码#

Transformer 的 self-attention 本身主要做这件事:

score = q · k

也就是一个 token 的 query 和另一个 token 的 key 做点乘,得到 attention 分数。

如果没有位置信息,模型很难区分:

我 爱 你
你 爱 我

因为 attention 本身对顺序不敏感。于是我们需要告诉模型:

这个 token 在第几个位置
这个 token 和另一个 token 隔了多远

RoPE 的做法不是直接贴一个“第几个位置”的标签,而是把位置变成旋转角度,作用到 q 和 k 上。


1.2 Multi-Head Attention 里的 q 是什么#

假设模型有:

hidden_size = 32
num_heads = 4
head_dim = 8

对某个 token,它的 hidden state 是:

h: 32 维

经过线性层得到:

q = h Wq
k = h Wk
v = h Wv

这里的 q 总体还是 32 维,但在 multi-head attention 里会被拆成 4 个 head:

q = [
  head0 的 8 维,
  head1 的 8 维,
  head2 的 8 维,
  head3 的 8 维
]

真实张量形状通常类似:

Q shape = [batch, seq_len, num_heads, head_dim]

比如:

Q shape = [2, 5, 4, 8]

含义是:

2 个样本
每个样本 5 个 token
每个 token 有 4 个 head
每个 head 的 q 是 8 维

RoPE 操作的是最后一维:

head_dim = 8

也就是说,对于每个:

batch b
token position m
head h

拿出:

Q[b, m, h, :] = [q0, q1, q2, q3, q4, q5, q6, q7]

然后在这个 head 自己的 8 维空间里做旋转。


1.3 为什么是每个 head 自己做 RoPE#

因为 multi-head attention 本来就是每个 head 独立算 attention。

不是这样算:

score = q_32dim · k_32dim

而是这样:

head0_score = q_head0 · k_head0
head1_score = q_head1 · k_head1
head2_score = q_head2 · k_head2
head3_score = q_head3 · k_head3

每个 head 都得到自己的一张 attention map。

所以 RoPE 也跟着 attention 的结构走:

head0_score = rope(q_head0, m) · rope(k_head0, n)
head1_score = rope(q_head1, m) · rope(k_head1, n)
head2_score = rope(q_head2, m) · rope(k_head2, n)
head3_score = rope(q_head3, m) · rope(k_head3, n)

这里:

m = query token 的位置
n = key token 的位置

RoPE 的本质是在 attention score 里加入相对位置旋转;实现上是先旋转每个 head 内部的 q/k,再用普通点乘计算 attention。


2. 二维旋转与实现布局#

2.1 二维旋转公式#

RoPE 的最小单位是二维旋转。

给一个二维向量:

[x, y]

旋转角度 a 后得到:

[
  x cos(a) - y sin(a),
  x sin(a) + y cos(a)
]

这就是标准二维旋转矩阵:

R(a) = [
  [cos(a), -sin(a)],
  [sin(a),  cos(a)]
]

所以:

R(a) [x, y]
= [
  x cos(a) - y sin(a),
  x sin(a) + y cos(a)
]

2.2 q 里的 x 和 y 是哪部分#

假设一个 head 的 query 是 8 维:

q_head = [q0, q1, q2, q3, q4, q5, q6, q7]

最直观的配对方式是:

(q0, q1)
(q2, q3)
(q4, q5)
(q6, q7)

每一对是一个二维向量。

对第 i 对来说:

x = q_{2i}
y = q_{2i+1}

比如:

第 0 对: [q0, q1]  => x = q0, y = q1
第 1 对: [q2, q3]  => x = q2, y = q3
第 2 对: [q4, q5]  => x = q4, y = q5
第 3 对: [q6, q7]  => x = q6, y = q7

注意:有些实现不是相邻配对,而是前半部分和后半部分配对,例如:

[q0, q4]
[q1, q5]
[q2, q6]
[q3, q7]

这两种布局都把维度两两配对后旋转,但要相应置换 Q/K 投影权重和频率排布才等价。加载既有 checkpoint 时不能直接替换配对方式。


2.3 rope(q_head, m) 怎么算#

假设:

head_dim = 8
q_head = [q0, q1, q2, q3, q4, q5, q6, q7]

RoPE 会给每个二维对一个频率:

theta_i = 10000^(-2i / head_dim)

其中:

i = 0, 1, 2, 3

因为 head_dim = 8,所以有 4 个二维对。

于是:

theta_0 = 10000^(-0/8) = 1
theta_1 = 10000^(-2/8)
theta_2 = 10000^(-4/8)
theta_3 = 10000^(-6/8)

如果 token 的位置是 m,第 i 对的旋转角度是:

angle_i = m * theta_i

所以:

(q0, q1) 用 angle_0 = m * theta_0 旋转
(q2, q3) 用 angle_1 = m * theta_1 旋转
(q4, q5) 用 angle_2 = m * theta_2 旋转
(q6, q7) 用 angle_3 = m * theta_3 旋转

完整展开就是:

rope(q_head, m)
= [
  q0 cos(m theta_0) - q1 sin(m theta_0),
  q0 sin(m theta_0) + q1 cos(m theta_0),

  q2 cos(m theta_1) - q3 sin(m theta_1),
  q2 sin(m theta_1) + q3 cos(m theta_1),

  q4 cos(m theta_2) - q5 sin(m theta_2),
  q4 sin(m theta_2) + q5 cos(m theta_2),

  q6 cos(m theta_3) - q7 sin(m theta_3),
  q6 sin(m theta_3) + q7 cos(m theta_3)
]

伪代码:

from math import cos, sin

def rope(q, m):
    if len(q) % 2:
        raise ValueError("RoPE head dimension must be even")
    # q shape: [head_dim]
    # example: head_dim = 8

    out = []
    head_dim = len(q)

    for i in range(head_dim // 2):
        x = q[2 * i]
        y = q[2 * i + 1]

        theta = 10000 ** (-2 * i / head_dim)
        angle = m * theta

        x_rot = x * cos(angle) - y * sin(angle)
        y_rot = x * sin(angle) + y * cos(angle)

        out.extend([x_rot, y_rot])

    return out

2.4 rotate_half 为什么等价于旋转#

很多代码会写:

q_rot = q * cos + rotate_half(q) * sin
k_rot = k * cos + rotate_half(k) * sin

对单个二维向量:

q_pair = [x, y]

定义:

rotate_half([x, y]) = [-y, x]

那么:

q * cos(a) + rotate_half(q) * sin(a)

就是:

[x, y] * cos(a) + [-y, x] * sin(a)

展开:

[
  x cos(a),
  y cos(a)
]
+
[
  -y sin(a),
   x sin(a)
]

得到:

[
  x cos(a) - y sin(a),
  y cos(a) + x sin(a)
]

也就是:

[
  x cos(a) - y sin(a),
  x sin(a) + y cos(a)
]

这正是二维旋转公式。

所以:

rotate_half(q)

不是单独完成旋转,它只是提供旋转公式里的垂直方向。

真正的旋转是:

原方向 * cos + 垂直方向 * sin

3. 相对位置与多频率机制#

3.1 为什么 RoPE 能表示相对位置#

普通 attention:

score = q · k

RoPE attention:

score_{m,n} = rope(q_m, m) · rope(k_n, n)

其中:

m = query 的位置
n = key 的位置

写成旋转矩阵:

rope(q_m, m) = R(m theta) q_m
rope(k_n, n) = R(n theta) k_n

所以:

score_{m,n}
= (R(m theta) q_m)^T (R(n theta) k_n)

因为旋转矩阵有性质:

R(a)^T = R(-a)
R(a) R(b) = R(a + b)

所以:

(R(m theta) q_m)^T (R(n theta) k_n)
= q_m^T R(m theta)^T R(n theta) k_n
= q_m^T R(-m theta) R(n theta) k_n
= q_m^T R((n - m) theta) k_n

注意最后只剩:

n - m

也就是相对位置。

这就是 RoPE 最关键的数学点:

分别按绝对位置旋转 q/k

等价于:

在 q · k 的时候加入相对位置差 n - m

3.2 RoPE 不是把 360 度切成很多份#

一个容易误解的点是:

是不是把 360 度切成 token_length * head_dim 份?

不是。

RoPE 不是切圆,而是每个二维对都有自己的旋转速度:

angle_i = m * theta_i

角度单位通常是弧度,不是度。

角度可以超过 2π,也就是可以超过 360 度。因为:

cos(angle)
sin(angle)

本来就是周期函数,超过一圈也可以继续算。

如果 head_dim = 8,频率大概可以直觉理解为:

theta_0 = 1
theta_1 = 0.1
theta_2 = 0.01
theta_3 = 0.001

那么位置 m = 100 时:

第 0 对角度: 100 * 1     = 100 rad
第 1 对角度: 100 * 0.1   = 10 rad
第 2 对角度: 100 * 0.01  = 1 rad
第 3 对角度: 100 * 0.001 = 0.1 rad

这些二维对像不同速度的指针:

第 0 对转得快,感知短距离
第 1 对慢一点
第 2 对更慢
第 3 对最慢,感知长距离

每个频率对应一个周期:

period_i = 2π / theta_i

例如:

theta_0 = 1       => 周期约 6.28 tokens
theta_1 = 0.1     => 周期约 62.8 tokens
theta_2 = 0.01    => 周期约 628 tokens
theta_3 = 0.001   => 周期约 6280 tokens

所以 RoPE 是:

不同维度对用不同速度旋转

不是:

把 360 度均匀切成 token_length * head_dim 份

3.3 每个维度对对应不同位置尺度#

你可以把一个 head 内部理解成有多把位置尺子。

如果:

q_head = [q0, q1, q2, q3, q4, q5, q6, q7]

那么:

(q0, q1) -> theta_0,最快,短尺度
(q2, q3) -> theta_1,慢一点
(q4, q5) -> theta_2,更慢
(q6, q7) -> theta_3,最慢,长尺度

所以是:

q0/q1 这一对是一个 scale
q2/q3 这一对是另一个 scale
q4/q5 这一对是另一个 scale
q6/q7 这一对是另一个 scale

不是单个维度一个 scale,而是一对维度共同组成一个二维旋转平面,共用同一个频率。

通常每个 head 都使用同一组 RoPE 频率:

theta_0, theta_1, theta_2, ...

但每个 head 的 q/k 内容不同,因为它们来自不同的投影参数。因此不同 head 可以学习用这些尺度关注不同模式。


4. 完整例子与回顾#

4.1 一个完整小例子#

假设:

head_dim = 4
q_head = [2, 3, 10, 20]
m = 1

有两对:

第 0 对: [2, 3]
第 1 对: [10, 20]

频率:

theta_0 = 10000^(-0/4) = 1
theta_1 = 10000^(-2/4) = 0.01

角度:

angle_0 = m * theta_0 = 1
angle_1 = m * theta_1 = 0.01

所以:

第 0 对 [2, 3] 旋转 1 rad
第 1 对 [10, 20] 旋转 0.01 rad

展开:

第 0 对:
[
  2 cos(1) - 3 sin(1),
  2 sin(1) + 3 cos(1)
]

第 1 对:
[
  10 cos(0.01) - 20 sin(0.01),
  10 sin(0.01) + 20 cos(0.01)
]

最后拼回:

rope(q_head, 1)
= [
  2 cos(1) - 3 sin(1),
  2 sin(1) + 3 cos(1),
  10 cos(0.01) - 20 sin(0.01),
  10 sin(0.01) + 20 cos(0.01)
]

4.2 最终总结#

RoPE 的计算流程:

输入 token hidden state
  ↓
线性层生成 Q/K/V
  ↓
Q/K reshape 成 [batch, seq_len, num_heads, head_dim]
  ↓
对每个 head 内部的 Q/K 做 RoPE
  ↓
每两个维度组成一个二维平面
  ↓
第 i 个二维平面使用 theta_i
  ↓
位置 m 的旋转角度是 m * theta_i
  ↓
旋转后的 Q/K 做普通 attention 点乘
  ↓
点乘结果自然包含相对位置 n - m

最核心的公式:

rope(q_m, m) · rope(k_n, n)
= q_m^T R((n - m) theta) k_n

最核心的直觉:

RoPE 先按绝对位置旋转 q/k,
但 q/k 点乘后只剩相对位置差。

最核心的实现:

q_rot = q * cos + rotate_half(q) * sin
k_rot = k * cos + rotate_half(k) * sin

其中:

rotate_half([x, y]) = [-y, x]

它对应二维旋转公式里的垂直方向。

原始资料:RoFormer。RoPE 使点积中的显式位置因子依赖相对位置;完整 attention score 仍取决于 Q/K 的内容。

延伸阅读#

相关阅读路线

理解 Transformer 与架构变体 ↗