RoPE: 位置编码如何进入 Attention?
这一节单独把 RoPE 拿出来讲,因为它虽然属于“位置表示”这个主题,但它实际是直接作用在 attention 计算过程里的,而不是一个简单加在输入 embedding 上的小补丁。
这一节准备按下面三个层次展开:
- 核心思路:RoPE 为了解决什么问题提出?
- 数学定义:RoPE 在公式上到底是什么?
- 细节逻辑:它和 absolute positional embedding 有什么不同?为什么只作用在 \( Q/K \) 上?而没有出现在\( V \)上面?
Q1: 核心思路: RoPE 为了解决什么问题提出?
先看最早、最直观的位置编码思路。
设
- \( x_m \in \mathbb{R}^{d_{\text{model}}} \) 表示第 \( m \) 个位置的 token embedding
- \( p_m \in \mathbb{R}^{d_{\text{model}}} \) 表示第 \( m \) 个位置的位置向量
那么最朴素的 absolute positional embedding 写法是:
\[ \tilde{x}_m = x_m + p_m \]
这个做法当然能告诉模型“当前位置是第几个位置”,但它有一个特点:
- 位置信息是在输入层注入的
后面 attention 要怎么使用这些位置信息,还得依赖模型自己在训练中学出来。
RoPE 想解决的正是这里的问题。
它不满足于“先把位置混进输入里,再让模型自己想办法用”,而是进一步追问:
- 能不能让位置信息直接进入 attention score 的计算?
因为 attention 的核心就是:
\[ QK^\top \]
如果位置信息不能更直接地进入这一步,那么位置和匹配关系之间始终隔着一层“模型自己去学”的间接过程。
所以 RoPE 的核心思路可以先概括成一句话:
- 不在输入层加位置向量,而是在 \( Q/K \) 上做位置相关变换,让位置直接进入 attention 的匹配过程
从这个角度看,RoPE 想解决的不是“序列有没有顺序”这么泛的问题,而是一个更具体的问题:
- 在 attention 机制里,位置信息应该如何进入打分过程
Q2: 数学定义: RoPE 在公式上到底是什么?
先只看单个 attention head。
设
- \( x_m \in \mathbb{R}^{d_{\text{head}}} \) 表示第 \( m \) 个位置的输入表示
- \( W_Q, W_K \in \mathbb{R}^{d_{\text{head}} \times d_{\text{head}}} \) 表示 Query / Key 的投影矩阵
- \( q_m = x_m W_Q \)
- \( k_m = x_m W_K \)
在普通 attention 里,第 \( m \) 个位置和第 \( n \) 个位置之间的打分通常写成:
\[ s_{m,n} = \frac{q_m^\top k_n}{\sqrt{d_{\text{head}}}} \]
RoPE 做的事情是:
在计算 attention score 之前,先对 \( q_m \) 和 \( k_n \) 做一个和位置相关的旋转变换。
如果记位置 \( m \) 对应的旋转算子为 \( R_m \),那么:
\[ \tilde{q}_m = R_m q_m,\qquad \tilde{k}_n = R_n k_n \]
于是打分变成:
RoPE 的关键不在“旋转”这个词,而在于这个旋转是按二维子空间成对做的。
如果 \( d_{\text{head}} \) 是偶数,可以把一个向量拆成若干个二维块。
例如把 \( q_m \) 写成:
\[ q_m = (q_m^{(1)}, q_m^{(2)}, \dots, q_m^{(d_{\text{head}}/2)}) \]
其中每个 \( q_m^{(i)} \in \mathbb{R}^2 \)。
对第 \( i \) 个二维块,RoPE 使用一个二维旋转矩阵:
\[ R(m\theta_i)= \begin{bmatrix} \cos(m\theta_i) & -\sin(m\theta_i) \\ \sin(m\theta_i) & \cos(m\theta_i) \end{bmatrix} \]
这里:
- \( m \) 表示位置索引
- \( \theta_i \) 表示第 \( i \) 个频率
于是整个 \( R_m \) 可以理解成由这些二维旋转块拼成的分块对角矩阵:
\[ R_m = \mathrm{diag}\big(R(m\theta_1), R(m\theta_2), \dots, R(m\theta_{d_{\text{head}}/2})\big) \]
如果把 batch 和多头维度也写上,真实代码里更常见的张量形状是:
\[ Q, K \in \mathbb{R}^{B \times h \times n \times d_{\text{head}}} \]
RoPE 作用后 shape 不变,仍然是:
\[ \tilde{Q}, \tilde{K} \in \mathbb{R}^{B \times h \times n \times d_{\text{head}}} \]
也就是说,RoPE 改变的不是张量形状,而是 \( Q/K \) 在不同位置上的取值方式。
Q3: 细节逻辑: 它和 absolute positional embedding 有什么不同? 为什么只作用在 \( Q/K \) 上?
先看它和 absolute positional embedding 的区别。
两者最根本的差异,不是“一个新一个旧”,而是位置信息进入模型的位置不同:
- absolute positional embedding:位置先进入输入 \( \tilde{x}_m = x_m + p_m \)
- RoPE:位置直接进入 \( Q/K \) 的构造和打分
这意味着:
- absolute positional embedding 是“把位置信息交给模型后续去利用”
- RoPE 是“让位置信息直接参与 attention 匹配”
RoPE 更关键的一点在于,它会让 attention score 自然依赖相对位置。
因为二维旋转矩阵满足:
\[ R_m^\top R_n = R_{n-m} \]
所以:
这个式子很重要。
它说明 RoPE 后的内积,不再只是“第 \( m \) 个绝对位置”和“第 \( n \) 个绝对位置”各自挂了一个标签,而是它们的相对位移 \( n-m \) 直接进入了匹配过程。
这也是为什么 RoPE 经常会被说成“更适合 attention”的位置编码方式。
因为它不是停留在输入层,而是把位置关系带进了 attention score 本身。
再看为什么它只作用在 \( Q/K \) 上,而不作用在 \( V \) 上。
attention 的核心可以拆成两步:
- 用 \( Q \) 和 \( K \) 计算相关性
- 用这个相关性权重去加权 \( V \)
也就是说:
- \( Q/K \) 决定“当前应该关注谁”
- \( V \) 提供“被取回来的内容”
RoPE 想解决的是“位置信息如何进入匹配关系”这个问题,所以它自然作用在 \( Q/K \) 上。
如果把同样的旋转硬套到 \( V \) 上,反而会把“位置匹配”与“内容表示”混在一起。
所以更准确的说法不是“RoPE 恰好只做在 \( Q/K \) 上”,而是:
- 它本来就是为 \( Q/K \) 的匹配过程设计的
这一节之后最重要的收获是什么?
- RoPE 的提出,是为了让位置信息直接进入 attention score,而不是只停留在输入层。
- 它的数学定义是:对每个位置的 \( Q/K \) 做位置相关的二维分块旋转,再参与 attention 打分。
- 它和 absolute positional embedding 的关键区别,在于位置信息进入模型的位置不同。
- 它只作用在 \( Q/K \) 上,因为它首先要解决的是“如何匹配”,而不是“内容本身是什么”。