上一篇:[[Flash Attention]] 下一篇:[[GQA]]
RoPE(Rotary Positional Embedding)vs 绝对位置编码**
绝对位置编码(Absolute Positional Encoding)
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(Rotary Positional Embedding)
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 编码
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)矩阵的结果。下面是你需要的详细步骤:
🧮 RoPE 应用步骤
设嵌入维度 $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} x_{2i}’ \ x_{2i+1}' \end{bmatrix}
\begin{bmatrix} \cos(\theta) & -\sin(\theta) \ \sin(\theta) & \cos(\theta) \end{bmatrix} \begin{bmatrix} x_{2i} \ x_{2i+1} \end{bmatrix} $$
🧾 假设的 K 矩阵(4 个位置,每个 token 向量长度 4):
$$ 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 编码结果,所有三角函数值保持精确。
📊 RoPE 编码后的 K 值表格
我们将每一行作为一组 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] |
✅ 简要说明每一行计算过程:
行 1(位置 0):
$$ \theta_0 = 0,\ \theta_1 = 0 \Rightarrow \text{cos} = 1,\ \text{sin} = 0 $$ 旋转矩阵为单位矩阵 ⇒ 输出和原始一样。
行 2(位置 1):
- $\theta_0 = 1$,$\cos(1) ≈ 0.5403,\ \sin(1) ≈ 0.8415$
- 输入为 $[0, 1]$,旋转得:
$$ x_0’ = 0.5403×0 - 0.8415×1 = -0.8415,\quad x_1’ = 0.5403×1 + 0.8415×0 = 0.5403 $$
- $\theta_1 = 0.0001$,$\cos ≈ 0.999999995,\ \sin ≈ 0.0001$
$$ x_2’ = 0×0.999999995 - 1.0×0.0001 = -0.0001,\quad x_3’ = 0×0.0001 + 1.0×0.999999995 = 1.0 $$
行 3(位置 2):
- $\theta_0 = 2$,$\cos(2) ≈ -0.4161,\ \sin(2) ≈ 0.9093$
输入 $[1, 1]$:
$$ x_0’ = -0.4161×1 - 0.9093×1 = -1.3254,\quad x_1’ = 0.9093×1 + (-0.4161)×1 = 0.4932 $$
- $\theta_1 = 0.0002$,$\cos ≈ 0.99999998,\ \sin ≈ 0.0002$
输入 $[1, 1]$:
$$ x_2’ = 0.99999998×1 - 0.0002×1 = 0.99979998,\quad x_3’ = 0.0002×1 + 0.99999998×1 = 1.00019998 $$
行 4(位置 3):
- $\theta_0 = 3$,$\cos(3) ≈ -0.9899,\ \sin(3) ≈ 0.1411$
输入 $[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 $$
- $\theta_1 = 0.0003$,$\cos ≈ 0.999999955,\ \sin ≈ 0.0003$
输入 $[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 作用的差异:
✅ 传统 Transformer(如原始 Attention Is All You Need)
Embedding 流程:
- 对于输入 token,先查表得到 token embedding(记作 $E$)
- 然后加上位置编码:
$$ X = E + \text{PosEncoding}(p) $$
Q/K/V 来自线性变换: $$ Q = XW^Q,\quad K = XW^K,\quad V = XW^V $$ 所以位置编码间接地传递到了 Q/K/V
📌 注意:位置编码只在 embedding 层前加一次,影响 Q/K,但不直接作用在 Q/K 上。
✅ RoPE(旋转位置编码)有什么不同?
RoPE 的创新点是:
- 不再把位置加到 embedding 上
- 而是在Q 和 K 向量生成之后,直接旋转 Q 和 K
- 即先计算: $$ Q = EW^Q,\quad K = EW^K $$ 然后对 Q/K 做位置旋转: $$ Q_p’ = \text{RoPE}(Q, p),\quad K_p’ = \text{RoPE}(K, p) $$
💡 所以 RoPE 是应用在 Q/K 上的位置编码,而非嵌入层。
✅ 总结对比
- RoPE 的旋转操作让位置影响变成角度差;
- 同一个 token 对其它 token 的注意力权重随位置差异而变化;
- 和传统的 位置向量加法不同,RoPE 保持模长、变换方向(相对编码);
- 支持无限外推(理论上),不像固定位置编码。
总结表
| 项目 | 绝对位置编码 | 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]]