Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RoPE: 位置编码如何进入 Attention?

这一节单独把 RoPE 拿出来讲,因为它虽然属于“位置表示”这个主题,但它实际是直接作用在 attention 计算过程里的,而不是一个简单加在输入 embedding 上的小补丁。

这一节准备按下面三个层次展开:

  1. 核心思路:RoPE 为了解决什么问题提出?
  2. 数学定义:RoPE 在公式上到底是什么?
  3. 细节逻辑:它和 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 的核心可以拆成两步:

  1. 用 \( Q \) 和 \( K \) 计算相关性
  2. 用这个相关性权重去加权 \( V \)

也就是说:

  • \( Q/K \) 决定“当前应该关注谁”
  • \( V \) 提供“被取回来的内容”

RoPE 想解决的是“位置信息如何进入匹配关系”这个问题,所以它自然作用在 \( Q/K \) 上。
如果把同样的旋转硬套到 \( V \) 上,反而会把“位置匹配”与“内容表示”混在一起。

所以更准确的说法不是“RoPE 恰好只做在 \( Q/K \) 上”,而是:

  • 它本来就是为 \( Q/K \) 的匹配过程设计的

这一节之后最重要的收获是什么?

  1. RoPE 的提出,是为了让位置信息直接进入 attention score,而不是只停留在输入层。
  2. 它的数学定义是:对每个位置的 \( Q/K \) 做位置相关的二维分块旋转,再参与 attention 打分。
  3. 它和 absolute positional embedding 的关键区别,在于位置信息进入模型的位置不同。
  4. 它只作用在 \( Q/K \) 上,因为它首先要解决的是“如何匹配”,而不是“内容本身是什么”。