RoPE 用一个正交旋转把绝对位置乘进 Q/K,使注意力内积自动退化为相对位置的函数——这是「以绝对形式输入、以相对形式生效」的唯一优雅解。
设计目标
RoFormer(arXiv:2104.09864)把问题写成一个函数方程:寻找变换 ,使得
即每个 token 只知道自己的绝对下标,但内积结果只依赖距离 。绝对位置编码的加法注入做不到这点——相加后的内积会展开出与 、 各自相关的交叉项;相对位置编码则要显式改写注意力打分函数。RoPE 的答案是乘法旋转:位置信息进入向量的相位而非模长。
复数视角的推导
先看 。把二维向量视作复数 ,定义
复内积取实部:
相位中的 与 相消,只剩 。这就是全部核心——位置差表现为两个复向量的相位差,而 保证语义向量模长不变。
高维分块旋转
把 head_dim 拆成 个二维子空间,第 块用自己的频率:
低频块( 大,)负责长程、慢变的粗粒度位置;高频块(,)负责邻近 token 的精细区分。整体变换是块对角正交矩阵:
相对位置的证明
正交,故 ;同一子空间内旋转可加,。于是
逐块验证即可:第 块得 。证明只依赖正交性与旋转的群结构,与 无关——这也是 RoPE 能平移到多维的根据(见 3D RoPE)。
两种分块方式
同一套数学有两种维度配对约定,务必分清:
| 方案 | 配对维度 | 代表实现 |
|---|---|---|
| interleaved | 相邻两维 | RoFormer 原文、部分 JAX 实现 |
| half-split | 前后半对应 | GPT-NeoX、LLaMA、HuggingFace |
两者只差一个固定的维度置换 ,而置换是正交的,因此 的谱与内积结果完全等价,数学上无差别。但权重不可互换:Q/K 投影矩阵的行序绑定了具体约定,混用会让高低频错位配对,输出直接崩坏。移植权重时必须核对上游用的是哪种。
实现
half-split 版本,即 HuggingFace 的 rotate_half 写法:
import torch
def build_cos_sin(seq_len, dim, base=10000.0, device="cpu", dtype=torch.float32):
"""预计算 cos/sin 缓存,形状 (seq_len, dim)。"""
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=device).float() / dim))
t = torch.arange(seq_len, device=device).float()
freqs = torch.outer(t, inv_freq) # (seq_len, dim/2)
emb = torch.cat((freqs, freqs), dim=-1) # 前后半共享频率,与 rotate_half 对齐
return emb.cos().to(dtype), emb.sin().to(dtype)
def rotate_half(x):
x1, x2 = x.chunk(2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
def apply_rope(q, k, cos, sin):
"""q, k: (batch, heads, seq, head_dim); cos, sin: (seq, head_dim)。"""
cos, sin = cos[None, None], sin[None, None]
return q * cos + rotate_half(q) * sin, k * cos + rotate_half(k) * sin
if __name__ == "__main__":
torch.manual_seed(0)
q = torch.randn(1, 2, 8, 64)
k = torch.randn(1, 2, 8, 64)
cos, sin = build_cos_sin(8, 64)
qr, kr = apply_rope(q, k, cos, sin)
# 平移不变性:位置整体后移,相对距离相同的内积不变
cos2, sin2 = build_cos_sin(16, 64)
qs, ks = apply_rope(q, k, cos2[5:13], sin2[5:13])
lhs = (qr[..., 2, :] * kr[..., 5, :]).sum(-1)
rhs = (qs[..., 2, :] * ks[..., 5, :]).sum(-1)
print(torch.allclose(lhs, rhs, atol=1e-5)) # Truerotate_half 把 拼接,正是二维旋转的 分量;一次逐元素乘加就完成了 个旋转,无需真的构造矩阵。RoPE 只作用于 Q、K,V 不旋转——否则输出会被旋进位置相位,破坏值空间语义。
与正弦编码对比
| 维度 | RoPE | Sinusoidal |
|---|---|---|
| 注入位置 | Q/K 投影后、打分前 | 输入 embedding |
| 注入方式 | 乘法旋转(正交) | 向量加法 |
| 相对位置 | 内积精确等于 | 依赖模型隐式学习 |
| 模长影响 | 严格保持 | 改变语义向量模长 |
| 每层生效 | 是,逐层重新旋转 | 否,随深度稀释 |
| 长序列 | 可插值/缩放外推 | 超训练长度即失效 |
RoPE 的注意力分数还带有随距离缓慢衰减的性质,与 ALiBi 的显式线性偏置形成对照:前者是相位相干性衰减的副产品,后者是硬编码的先验。
应用
LLaMA、Qwen、GLM、Mistral、DeepSeek 等主流开源 LLM 已把 RoPE 作为默认位置方案,理由很实际:无额外参数、不改注意力结构、每层可复用同一份 cos/sin 缓存、且能通过调整 base 或缩放位置索引来延展上下文——这条线索展开在 RoPE 长度外推与插值。把一维下标换成时空坐标即可支持视频与多模态输入,对应 3D RoPE 与 MRoPE 详细介绍。