
RoPE位置编码的数学原理为什么旋转编码比绝对位置更泛化一、位置编码的核心矛盾如何让模型知道位置但不记忆位置Transformer 的自注意力机制是置换不变的——打乱输入顺序不会改变注意力分数的集合仅改变排列顺序。位置编码的作用是打破这种置换不变性让模型感知 token 的顺序。绝对位置编码如原始Transformer的sinusoidal编码和BERT的learned position embeddings的做法是给每个位置分配一个独特的向量加到 token embedding 上。这种做法的问题是位置 i 和位置 j 的表示被独立学习或固定模型无法自然地从位置 5和位置 6的关系推断出位置 105和位置 106的关系。这导致训练时的位置范围限制了推理时的序列长度——如果训练时最多见过512个位置推理时的第513个位置就是完全未知的。RoPERotary Position Embedding通过在高维空间中旋转 token 的表示来编码位置信息实现了一个关键性质两个 token 之间的注意力分数只依赖于它们的相对位置差而非绝对位置。flowchart LR subgraph Abs[绝对位置编码] A1[token_i pos_i] -- A2[token_j pos_j] A2 -- A3[注意力: f(token_ipos_i, token_jpos_j)] A3 -- A4[依赖绝对位置 i, j] end subgraph RoPE[旋转位置编码] R1[rotate(token_i, θ×i)] -- R2[rotate(token_j, θ×j)] R2 -- R3[注意力: g(token_i·token_j, i-j)] R3 -- R4[仅依赖相对位置 i-j] end subgraph Property[关键性质] P1[外推: 训练512, 推理2048 ✓] P2[平移不变性: 移位序列, 注意力不变 ✓] P3[远程衰减: 距离越远, 注意力越低 ✓] end R4 -- Property二、RoPE的数学构造二维旋转 高频/低频分量RoPE的核心数学思想是将 d 维的 token 表示拆分为 d/2 组二维向量每组在自己的二维平面上旋转不同的角度。第 k 组k 0, 1, ..., d/2-1的旋转频率为 θ_k 10000^(-2k/d)低维度小 k旋转速度快高频→ 对近距离位置变化敏感高维度大 k旋转速度慢低频→ 对远距离位置变化敏感这种多频率设计使得 RoPE 在多个尺度上同时编码位置信息类似于傅里叶变换使用不同频率的正弦波表示信号。数学上对于位置 m第 k 组二维向量的旋转矩阵为R(θ_k × m) [cos(θ_k×m), -sin(θ_k×m); sin(θ_k×m), cos(θ_k×m)]两个位置的旋转后向量的内积具有以下性质R(m)×x, R(n)×y x^T × R(n-m) × y即注意力分数只依赖于 (n-m)——相对位置差。import torch import torch.nn as nn import math from typing import Tuple class RotaryPositionalEmbedding(nn.Module): RoPE 的完整 PyTorch 实现。 关键参数设计 - base10000: 控制频率范围。这个值从原始 Transformer sinusoidal 编码继承经过大量实验验证其有效性。 改变 base 会改变模型的位置分辨率。 - 旋转维度对 (d/2 组): 每组独立旋转 高频组编码精细位置低频组编码粗略位置。 def __init__( self, head_dim: int, max_seq_len: int 2048, base: int 10000, device: str cpu ): super().__init__() self.head_dim head_dim self.max_seq_len max_seq_len self.base base # 预计算频率θ_k base^(-2k/d) for k 0, 1, ..., d/2-1 theta 1.0 / ( base ** (torch.arange(0, head_dim, 2, devicedevice).float() / head_dim) ) # 预计算所有位置的位置索引与频率的乘积 # 形状: [max_seq_len, head_dim/2] seq_idx torch.arange(max_seq_len, devicedevice).float() # [max_seq_len, 1] × [1, head_dim/2] → [max_seq_len, head_dim/2] freqs torch.outer(seq_idx, theta) # 每个频率需要 cos 和 sin # 拼接为 [max_seq_len, head_dim]cos 和 sin 交替 self.register_buffer(cos_cached, freqs.cos().repeat_interleave(2, dim-1)) self.register_buffer(sin_cached, freqs.sin().repeat_interleave(2, dim-1)) def forward( self, x: torch.Tensor, seq_len: int, position_ids: torch.Tensor None ) - Tuple[torch.Tensor, torch.Tensor]: 返回当前序列位置对应的 cos 和 sin 值。 为什么返回 cos/sin 而非直接旋转 x 分离旋转参数和旋转操作使调用者可以在 不同的注意力实现中复用相同的旋转参数。 if position_ids is not None: cos self.cos_cached[position_ids] # [B, seq_len, head_dim] sin self.sin_cached[position_ids] else: cos self.cos_cached[:seq_len] # [seq_len, head_dim] sin self.sin_cached[:seq_len] return cos, sin def apply_rotary_embedding( x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor ) - torch.Tensor: 对输入张量应用旋转位置编码。 旋转操作 将 x 的相邻两维视为复数的实部和虚部 乘以旋转因子 e^(iθ) cos(θ) i·sin(θ)。 即: x[0] x[0]*cos - x[1]*sin x[1] x[0]*sin x[1]*cos 这等价于在每对相邻维度上施加二维旋转。 # 将 x 拆分为奇偶维度对 x_even x[..., 0::2] # 偶数位置实部 x_odd x[..., 1::2] # 奇数位置虚部 cos_even cos[..., 0::2] sin_even sin[..., 0::2] # 复数旋转 rotated_even x_even * cos_even - x_odd * sin_even rotated_odd x_even * sin_even x_odd * cos_even # 交替拼接复原 rotated torch.stack([rotated_even, rotated_odd], dim-1) rotated rotated.flatten(-2) return rotated三、RoPE的远程衰减性质RoPE 的一个自然属性是随着相对距离 |m-n| 增加两个token的内积期望值在随机初始化下呈衰减趋势。这为模型提供了一个归纳偏好——更近的 token 自然获得更高的注意力权重。这个衰减不是硬编码的不同于 ALiBi 的显式偏置而是频率混叠的自然结果高频分量在远距离上几乎正交内积 ≈ 0低频分量在远距离上仍有关联。模型可以通过学习 Q 和 K 的投影来利用这个性质——想关注远距离时依赖低频维度想关注近距离时依赖高频维度。四、RoPE的局限与扩展最大序列长度的硬限制RoPE 在预训练时定义的max_seq_len决定了cos_cached和sin_cached的大小。对于超出训练长度的序列需要位置插值如 PI、NTK-aware scaling、YaRN来扩展有效长度。二维旋转的表示限制每对相邻维度的独立旋转假设维度对之间不交互。这在低维情况下限制了位置编码的表达能力。xPosSun et al., 2023通过对不同维度对施加不同的衰减因子来改善这一点。batch 推理中的效率对于变长序列的 batch不同样本的长度不同cos/sin 的索引也不同——需要 batch 级别的 gather 操作。五、总结RoPE 通过在高维空间中旋转 token 表示来实现相对位置编码其核心优势在于数学优雅性注意力分数自然地仅依赖相对位置差。外推能力比绝对位置编码更好地泛化到训练中未见过的序列长度。远程衰减为模型提供了距离越远关注度越低的归纳偏好利于学习长距离依赖。扩展方案位置插值使 RoPE 能够突破训练时的长度限制。