上一篇:[[Flash Attention]] 下一篇:[[GQA]]
Transformer 中原始的位置编码是将固定的正弦和余弦向量加到输入嵌入上:
$\text{PE}{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right) \ \text{PE}{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)$
然后将位置编码加到输入 $x$ 上:
$x’ = x + \text{PE}(pos)$
这种编码方式在原始 transformer 中是不可迁移的,不能泛化到更长的序列。
RoPE 的核心思想是将位置信息通过复数空间中的旋转矩阵注入到 Query 和 Key 中,而不是加到输入上:
对每一对维度 $(x_{2i}, x_{2i+1})$,我们应用一个旋转:
$\begin{bmatrix} x_{2i}’ \ x_{2i+1}’ \end{bmatrix} = \begin{bmatrix} \cos(\theta_{pos}) & -\sin(\theta_{pos}) \ \sin(\theta_{pos}) & \cos(\theta_{pos}) \end{bmatrix} \begin{bmatrix} x_{2i} \ x_{2i+1} \end{bmatrix}$
其中:
$\theta_{pos} = \frac{pos}{10000^{2i/d}}$
RoPE 的优势:
保持 dot product 的平移等变性(relative distance encoding)
可推广到更长序列(无固定长度限制)
更适合 GPT 系列的解码式模型
二、绝对位置编码 vs RoPE(4 个 token)
我们设定嵌入维度为 4,每个 token 的初始嵌入为:
| Token 位置 | Token 向量(简化) |
|---|---|
| pos = 0 | $[1.0,\ 0.0,\ 1.0,\ 0.0]$ |
| pos = 1 | $[0.0,\ 1.0,\ 0.0,\ 1.0]$ |
| pos = 2 | $[1.0,\ 1.0,\ 1.0,\ 1.0]$ |
| pos = 3 | $[0.5,\ 0.5,\ 0.5,\ 0.5]$ |
位置编码是按维度加上 sin/cos 值,例如(使用 $d=4$):
$$ \text{PE}{(pos, 0)} = \sin(pos / 10000^{0/4}) = \sin(pos) $$ $$ \text{PE}{(pos, 1)} = \cos(pos) $$ $$ \text{PE}_{(pos, 2)} = \sin(pos / 10000^{2/4}) = \sin(pos / 100) $$ $$
\text{PE}_{(pos, 3)} = \cos(pos / 100) $$
我们用近似值展示:
| pos | PE |
|---|---|
| 0 | [0.0, 1.0, 0.0, 1.0] |
| 1 | [0.84, 0.54, 0.0099, 0.9999] |
| 2 | [0.91, -0.41, 0.0199, 0.9998] |
| 3 | [0.14, -0.99, 0.0298, 0.9995] |
最终输入向量:
$$ x_{\text{input}} = x + \text{PE}(pos) $$
这是一种“加法注入位置信息”,缺点是它和词义耦合、不能外推到更长序列。
RoPE 的想法是:在每对维度上做一个与位置相关的旋转,而不是直接加。
对于维度 $d = 4$,我们将 token 嵌入视作两个复数向量:
$$ [x_0, x_1], [x_2, x_3] \Rightarrow z_1 = x_0 + ix_1,\ z_2 = x_2 + ix_3 $$
然后乘以位置对应的旋转因子(也表示为复数):
$$ R_{pos} = e^{i\theta_{pos}} = \cos(\theta_{pos}) + i\sin(\theta_{pos}) $$
假设 $\theta_{pos} = 0.1, 0.2, 0.3, 0.4$ 对应 pos = 0,1,2,3
取 pos = 2 为例:
Token = $[1.0, 1.0, 1.0, 1.0] \Rightarrow z_1 = 1+i, z_2 = 1+i$
$$ z_1’ = z_1 \cdot e^{i\cdot0.3} = (1+i)(\cos(0.3) + i\sin(0.3)) \ \approx (1+i)(0.955 + 0.296i) = \underbrace{0.659 + 1.251i}_{\text{旋转后}} $$
对应新的向量:$$ [0.659,\ 1.251,\ \ldots] $$
我在上面的例子中简化了 RoPE 的旋转角度 $\theta_{pos}$,写成了比如 θ = 0.3 这样的近似数值,但没有解释它是怎么来的。现在我们来严谨说明一下:
我们严格地、不做近似地计算出 RoPE 应用于 Key(K)矩阵的结果。下面是你需要的详细步骤:
设嵌入维度 $d = 4$,我们按 2 维一组,做复数旋转:
每组维度 (0,1) 和 (2,3) 分别旋转。我们使用以下公式:
$$ \theta_{pos, i} = \frac{pos}{10000^{2i/d}} = \frac{pos}{10000^{i/(d/2)}} $$
对向量中每一对分量 $(x_{2i}, x_{2i+1})$,应用旋转:
\begin{bmatrix} \cos(\theta) & -\sin(\theta) \ \sin(\theta) & \cos(\theta) \end{bmatrix} \begin{bmatrix} x_{2i} \ x_{2i+1} \end{bmatrix} $$
$$ K = \begin{bmatrix} 1.0 & 0.0 & 1.0 & 0.0 \ 0.0 & 1.0 & 0.0 & 1.0 \ 1.0 & 1.0 & 1.0 & 1.0 \ 0.5 & 0.5 & 0.5 & 0.5 \ \end{bmatrix} $$
我们现在为每一行计算 RoPE 编码结果,所有三角函数值保持精确。
我们将每一行作为一组 token,每一列表示一次旋转计算步骤:
| 位置 $pos$ | $\theta_0 = \frac{pos}{10000^0} = pos$ | $\theta_1 = \frac{pos}{10000^1} = \frac{pos}{10000}$ | RoPE(0,1): $[x_0’, x_1’]$ | RoPE(2,3): $[x_2’, x_3’]$ |
|---|---|---|---|---|
| 0 | 0 | 0 | [1.0, 0.0] | [1.0, 0.0] |
| 1 | 1 | 0.0001 | [$\cos 1$, $\sin 1$] | [$\cos 0.0001$, $\sin 0.0001$] |
| [0.5403, 0.8415] | [1.0, 0.0001] | |||
| 2 | 2 | 0.0002 | [$\cos 2 - \sin 2,\ \sin 2 + \cos 2$] | [$\cos(0.0002) - \sin(0.0002),\ \sin(0.0002) + \cos(0.0002)$] |
| [-0.4161, 0.9093] | [0.99999998, 0.0002] | |||
| 3 | 3 | 0.0003 | [$\cos 3 - \sin 3,\ \sin 3 + \cos 3$] | [$\cos(0.0003) - \sin(0.0003),\ \sin(0.0003) + \cos(0.0003)$] |
| [-0.9899, 0.1411] | [0.999999955, 0.0003] |
$$ \theta_0 = 0,\ \theta_1 = 0 \Rightarrow \text{cos} = 1,\ \text{sin} = 0 $$ 旋转矩阵为单位矩阵 ⇒ 输出和原始一样。
$$ x_0’ = 0.5403×0 - 0.8415×1 = -0.8415,\quad x_1’ = 0.5403×1 + 0.8415×0 = 0.5403 $$
$$ x_2’ = 0×0.999999995 - 1.0×0.0001 = -0.0001,\quad x_3’ = 0×0.0001 + 1.0×0.999999995 = 1.0 $$
输入 $[1, 1]$:
$$ x_0’ = -0.4161×1 - 0.9093×1 = -1.3254,\quad x_1’ = 0.9093×1 + (-0.4161)×1 = 0.4932 $$
输入 $[1, 1]$:
$$ x_2’ = 0.99999998×1 - 0.0002×1 = 0.99979998,\quad x_3’ = 0.0002×1 + 0.99999998×1 = 1.00019998 $$
输入 $[0.5, 0.5]$:
$$ x_0’ = -0.9899×0.5 - 0.1411×0.5 = -0.5655,\quad x_1’ = 0.1411×0.5 + (-0.9899)×0.5 = -0.4244 $$
输入 $[0.5, 0.5]$:
$$ x_2’ = 0.999999955×0.5 - 0.0003×0.5 = 0.49984998,\quad x_3’ = 0.0003×0.5 + 0.999999955×0.5 = 0.50014998 $$
RoPE 对所有位置都这样做,只作用在 Q/K 上,不影响 Value 或嵌入向量本身,因此模型可以自动学习更强的相对位置感知能力。 下面我来澄清 transformer 中的位置编码 和 RoPE 作用的差异:
Embedding 流程:
Q/K/V 来自线性变换: $$ Q = XW^Q,\quad K = XW^K,\quad V = XW^V $$ 所以位置编码间接地传递到了 Q/K/V
📌 注意:位置编码只在 embedding 层前加一次,影响 Q/K,但不直接作用在 Q/K 上。
RoPE 的创新点是:
💡 所以 RoPE 是应用在 Q/K 上的位置编码,而非嵌入层。
| 项目 | 绝对位置编码 | RoPE |
|---|---|---|
| 形式 | 加法(嵌入 + PE) | Q/K 上复数旋转 |
| 优点 | 简单直观 | 支持长文本、保持相对顺序 |
| 缺点 | 不可外推 | 实现略复杂 |
LLaMA 3 支持 更长上下文窗口,如 8K、32K,甚至更长。
配合 RoPE 的扩展技术(如 Linear RoPE Scalers)进行训练,支持高效泛化到长文本。 oPE 在长序列时有问题:
它对 token 位置敏感,如果位置远超训练时的最大长度,旋转角度会失控,attention 崩溃。
为了解决这个问题,很多扩展方法被提出,比如:
Linear RoPE Scaling:将位置 i 缩放为 i/s,其中 s > 1,让模型能处理更长位置;
NTK-aware RoPE scaling(如 LLaMA 2)进一步改进了位置缩放策略。
这些方法可以让模型在不重新训练的前提下处理更长的输入。
上一篇:[[Flash Attention]] 下一篇:[[GQA]]