RoPE 旋转位置编码:用相对位置旋转注入位置信息

FreeGuideOnline 最新 2026-06-22

什么是位置编码

Transformer 模型的自注意力机制本身不具备序列顺序的感知能力。为了让模型理解 token 的先后顺序,必须在输入向量中加入位置信息。一种常见做法是向词嵌入向量中叠加一个位置编码向量。早期的绝对位置编码(如正弦位置编码、可学习位置编码)将每个位置的编码独立定义,但它们在处理相对位置关系时比较生硬,且难以外推到训练时未见过的序列长度。

旋转位置编码(Rotary Position Embedding,RoPE)采用了一种完全不同的思路:通过旋转来注入位置信息。它不增加额外的可学习参数,而是利用复数或二维向量的旋转变换,让注意力计算自然地具备相对位置感知能力。这使得 RoPE 兼具高效性、理论上的优雅性,已广泛应用于 LLaMA、GLM、ChatGLM 等主流大语言模型。

核心思想:用旋紧相对位置的信息

RoPE 的精髓可以凝练成一句话:通过绝对位置编码的方式,实现相对位置编码的效果

具体来说,对于每个 token 的查询向量 ( \mathbf{q} ) 和键向量 ( \mathbf{k} ),RoPE 会根据它们各自的位置 ( m ) 和 ( n ),分别施加一个旋转变换。经过旋转后的向量进行内积时,内积结果仅依赖于位置差 ( m - n ),而与具体的绝对位置无关。这样,注意力分数就天然包含了相对位置关系,而模型架构本身无需改动,计算过程依然高效。

数学原理:从二维旋转到高维扩展

RoPE 的实现可以分解为以下几个关键步骤。

基础:二维向量的旋转

在二维空间中,将一个向量 ( \mathbf{x} = (x_1, x_2) ) 逆时针旋转 ( \theta ) 角度,得到的新向量为:

[ \begin{pmatrix} x_1' \ x_2' \end{pmatrix} = \begin{pmatrix} \cos\theta & -\sin\theta \ \sin\theta & \cos\theta \end{pmatrix} \begin{pmatrix} x_1 \ x_2 \end{pmatrix} ]

如果我们把位置信息融入旋转角度,比如让位置 ( m ) 对应的旋转角度为 ( m\theta ),那么旋转矩阵就变为:

[ R_m = \begin{pmatrix} \cos(m\theta) & -\sin(m\theta) \ \sin(m\theta) & \cos(m\theta) \end{pmatrix} ]

对于另一个位置 ( n ) 上的向量,用 ( R_n ) 旋转。当计算旋转后的两个向量的内积时,根据三角恒等式可得:

[ (R_m \mathbf{u})^\top (R_n \mathbf{v}) = \mathbf{u}^\top R_{m-n} \mathbf{v} ]

与位置相关的部分只由差值 ( m-n ) 决定。这正是 RoPE 赋予注意力机制相对位置感知的数学基础。

扩展到高维:分块处理

实际使用的隐藏维度远大于 2,RoPE 会将 ( d )- 维的向量两两分组,形成 ( d/2 ) 个二维子空间。每个子空间使用不同的旋转频率。具体来说,对于维度索引 ( i \in {0, 1, \dots, d/2 - 1} ),定义对应的旋转角速度:

[ \theta_i = 10000^{-2i/d} ]

这与 Transformer 原始正弦位置编码的频率设定一致,确保了不同维度有不同的变化周期。

给定位置 ( m ),对向量 ( \mathbf{x} \in \mathbb{R}^d ) 应用 RoPE 的公式为:

[ \text{RoPE}(\mathbf{x}, m) = \begin{pmatrix} x_0 \cos(m\theta_0) - x_1 \sin(m\theta_0) \ x_1 \cos(m\theta_0) + x_0 \sin(m\theta_0) \ x_2 \cos(m\theta_1) - x_3 \sin(m\theta_1) \ x_3 \cos(m\theta_1) + x_2 \sin(m\theta_1) \ \vdots \ x_{d-2} \cos(m\theta_{d/2-1}) - x_{d-1} \sin(m\theta_{d/2-1}) \ x_{d-1} \cos(m\theta_{d/2-1}) + x_{d-2} \sin(m\theta_{d/2-1}) \end{pmatrix} ]

实际操作中,我们会预先计算好每个位置对应的 ( \cos ) 和 ( \sin ) 值,然后将向量分段相乘、相加即可。整个运算是对元素成对施加的,极其高效。

融入注意力计算

在 Transformer 的每一层,对计算出的查询 ( Q ) 和键 ( K ) 分别按各自位置施加 RoPE。值向量 ( V ) 通常不施加位置编码(仅需相对位置体现在注意力权重中)。注意力得分计算如下:

[ \text{Attention}(Q, K, V) = \text{softmax}\left( \frac{ \text{RoPE}(Q) \cdot \text{RoPE}(K)^\top }{\sqrt{d_k}} \right) V ]

因为 RoPE 保持了内积的相对位置性质,注意力矩阵天然包含距离信息。具体地,位置 ( m ) 对位置 ( n ) 的注意力得分会隐式地依赖 ( m-n ),模型能够轻易学到“邻近 token 更相关”等局部性先验。

为什么 RoPE 优于传统方案

与最常用的正弦位置编码和可学习位置编码相比,RoPE 展现出多项优势。

天然捕捉相对位置

绝对位置编码是将位置向量直接加到词向量上,这样两个位置的相互作用通过训练才能模糊捕获相对距离,且效果不够稳定。RoPE 将相对位置直接写入内积计算中,使模型对序列中 token 之间的先后顺序、距离远近有了更结构化的感知。

长度外推能力

可学习位置编码受限于最大训练长度,超出后必须插值或重新训练。RoPE 由于频率设定与正弦编码类似,天然具有向更长度差泛化的潜力。通过调整频率基值或使用位置插值(如 NTK-aware 缩放),可以将上下文窗口轻松扩展数倍,而无需完全重新训练。

计算效率与实现简洁

RoPE 不需要引入额外参数,也不改变矩阵乘法的核心形状。大部分框架(如 PyTorch、JAX)仅需约 10 行核心代码即可实现。因为它在注意力计算前直接修改 ( Q ) 和 ( K ),可以无缝接入已有的 Transformer 代码库。

代码实现示例(PyTorch)

下面给出一个简洁且功能齐全的 RoPE 实现。

import torch
import torch.nn as nn

class RotaryPositionalEmbedding(nn.Module):
    def __init__(self, dim, max_seq_len=2048, base=10000.0):
        super().__init__()
        self.dim = dim
        self.base = base
        # 预先计算频率 theta_i
        inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
        self.register_buffer("inv_freq", inv_freq)
        # 可选:预缓存 cos, sin 表以加速
        self.max_seq_len = max_seq_len
        self._build_cache(max_seq_len)

    def _build_cache(self, max_len):
        seq = torch.arange(max_len, device=self.inv_freq.device)
        freqs = torch.einsum("i,j->ij", seq, self.inv_freq)  # (max_len, dim/2)
        emb = torch.cat((freqs, freqs), dim=-1)               # (max_len, dim)
        self.register_buffer("cos_cached", emb.cos())
        self.register_buffer("sin_cached", emb.sin())

    def forward(self, x, seq_len=None):
        # x: tensor of shape (batch, n_heads, seq_len, head_dim)
        seq_len = x.shape[2] if seq_len is None else seq_len
        cos = self.cos_cached[:seq_len, :].unsqueeze(0).unsqueeze(0)  # (1,1,seq_len,dim)
        sin = self.sin_cached[:seq_len, :].unsqueeze(0).unsqueeze(0)
        # 将 x 分成两半以施加旋转
        x_rot = x[..., : self.dim // 2]
        x_pass = x[..., self.dim // 2 :]
        # 传统 RoPE 是成对旋转,这里采用“交替”模式简化:
        x1, x2 = x[..., 0::2], x[..., 1::2]
        rotated = torch.cat((-x2, x1), dim=-1)
        # 最终输出
        return (x * cos) + (rotated * sin)

注意:实际高性能实现多采用直接两两分组运算,以上代码为思路示意。更高效的写法可以参见 HuggingFace Transformers 或 FlashAttention 中的融合算子。

常见变体与技巧

随着大模型规模的扩大,RoPE 也在实践中衍生出一些改进策略。

  • 位置插值(Position Interpolation):简单地将位置索引缩放,例如将原长度 ( L ) 的模型扩展到 ( 2L ),只需将位置索引除以 2。这几乎不损失性能,即可实现上下文窗口倍增。
  • NTK-aware 插值:调整 RoPE 的基频 base,使得高频细节更少被压缩,保留下游任务的长距离依赖。
  • YaRN(Yet another RoPE extensioN):结合温度缩放和频率域调整,进一步改善外推时的困惑度。
  • 线性注意力中的 RoPE:对于线性注意力机制,RoPE 也可被改造为无 softmax 的形式,保持相对位置编码的特性。

总结

RoPE 旋转位置编码以简洁的数学变换,实现了高性能的相对位置编码。它的核心在于“用绝对位置施加旋转,注意力内积自然依赖相对位置”。由于其出色的性能、外推潜力和实现便利性,RoPE 已经成为当代大语言模型位置编码的事实标准。理解并掌握 RoPE,是深入 Transformer 模型设计与训练的重要一步。