Skip to content

ROPE

介绍

可以。RoPE(Rotary Positional Embedding,旋转位置编码)是现在很多 LLM(LLaMA、Qwen、GLM 等)里最常用的相对位置编码方案之一。它的核心想法特别干净:

不给 token “加”位置向量,而是对 Q/K 做一个“随位置旋转”的变换,让注意力天然带上相对位置信息。


1) 为什么要位置编码?

自注意力里,打分是:

\text{score}(i,j)= q_i^\top k_j

如果没有位置编码,模型只看到了“词是什么”,看不到“第几个词”。


2) RoPE 做了什么?

RoPE 不把位置向量加到输入上,而是直接把 Query / Key 向量按位置做旋转

q_i' = R(i), q_i,\quad k_j' = R(j), k_j

然后注意力分数用旋转后的:

(q_i')^\top (k_j') = (R(i)q_i)^\top (R(j)k_j)

关键性质来了(旋转矩阵正交):

(R(i)q_i)^\top (R(j)k_j) = q_i^\top R(i)^\top R(j), k_j = q_i^\top R(j-i), k_j

只依赖相对位置 (j-i)
这就是 RoPE 的精髓:注意力天然变成相对位置感知


3) “旋转”到底怎么实现?(2D 一组一组转)

RoPE 会把向量维度两两配对:

(x_0,x_1),\ (x_2,x_3),\ \dots

每一对当成二维平面中的一个点,然后按角度 (\theta) 旋转:

\begin{pmatrix} x'_{2t}\ \\ x'_{2t+1} \end{pmatrix} = \begin{pmatrix} \cos\theta_{t,i} & -\sin\theta_{t,i}\ \\ \sin\theta_{t,i} & \cos\theta_{t,i} \end{pmatrix} \begin{pmatrix} x_{2t}\ \\ x_{2t+1} \end{pmatrix}

其中角度和位置有关:

\theta_{t,i} = i \cdot \omega_t

(\omega_t) 是不同频率(类似正余弦位置编码的频率尺度):

\omega_t = 10000^{-2t/d}

所以:

  • 低维旋转快(捕捉短距离)

  • 高维旋转慢(捕捉长距离)


4) 这和传统正余弦位置编码有什么关系?

传统做法(绝对位置编码)是把位置向量加进去:

x_i + p_i

RoPE 更像是把正余弦编码“搬进了注意力打分公式里”,并且变成了相对位置形式

更通俗一点:

  • Sinusoidal:位置当输入特征

  • RoPE:位置当几何变换(旋转)直接作用在 Q/K 上


5) RoPE 为什么这么受欢迎?

✅ (1) 天然相对位置

上面推导说明了注意力里出现 (R(j-i)),所以模型更容易学到“前后关系”。

✅ (2) 对长上下文更友好(可外推)

RoPE 结构上允许你在推理时用更长位置(虽然仍可能需要 scaling 技巧,比如 NTK/YaRN 等,但 RoPE 本身就比绝对编码强很多)。

✅ (3) 计算开销小、实现简单

就是对 Q/K 做一个 element-wise 的 cos/sin 组合,不需要额外的注意力 bias 表。


6) 一句话理解 RoPE

每个位置 i 给 Q/K 加一个“相位”,注意力比较时只看相位差,所以感知到相对距离。

ROPE中两两组合旋转

接下来我问GPT老师;为什么是两两配对呢,同一个token每一对维度旋转的角度是相同的吗,但是和别的token相乘的时候,没有两两配对相乘啊?实际回答我理解为,虽然相乘的时候没有专门分成两两相乘,但是最后确实是各个维度各自相乘再求和,其实加法怎么组合都行,两两组合就可以对应上旋转

你这三个问题都戳在 RoPE 最“容易让人卡住”的点上了。我按你提问顺序把逻辑捋顺:


1) 为什么一定要“两两配对”?

因为 RoPE 做的是 旋转(rotation),而“旋转”天然定义在 2D 平面里。

  • 1D 里你没法“旋转”,只能乘一个数(缩放/翻转)

  • 2D 里才有经典旋转矩阵:

\begin{pmatrix} \cos\theta & -\sin\theta\ \\ \sin\theta & \cos\theta \end{pmatrix}

所以把向量维度 两两分组,每一组当成一个二维向量去转。

本质:把高维向量拆成很多个 2D 子空间,每个子空间做一个旋转。

这也解释了为什么 RoPE 的维度一般要求是偶数。


2) 同一个 token 里,每一对维度旋转角度相同吗?

不相同。
同一个 token 在不同“维度对”上旋转角度不同:

\theta_{t,i} = i \cdot \omega_t
  • (i) 是 token 位置

  • (t) 是第几对维度(第几个 2D 子空间)

  • (\omega_t) 是不同频率(随 (t) 变化)

所以对固定 token 位置 (i):

  • 第 0 对:角度 (i\omega_0)(通常变化最快)

  • 第 1 对:角度 (i\omega_1)

  • 后面某些对:角度变化很慢(负责长距离)

同一个 token:不同维度对 = 不同频率的“相位”,角度各不一样。

这个设计跟传统 sinusoidal positional encoding 一样:不同维度用不同频率,才能同时表达短程/长程位置信息。


3) 你最关键的疑问:

“注意力是做 (q^\top k),不是两两配对相乘啊?那两两配对旋转怎么还对 dot-product 有用?”

这就是 RoPE 最妙的一点:
虽然最终是整体 dot-product,但它可以拆成每个 2D 子空间的 dot-product 相加。

因为:

q^\top k = \sum_{m=0}^{d-1} q_m k_m

如果你把维度按 pair 分组:

q^\top k = \sum_{t} \Big(q_{2t}k_{2t} + q_{2t+1}k_{2t+1}\Big)

也就是:

整体内积 = 各个二维小块内积的总和。

而 RoPE 的旋转是“在每个二维小块里转”,所以它直接改变每个小块的内积,从而改变总内积。


4) 更直观:2D 子空间里发生了什么?

把某一对维度看成二维向量:

  • (q_t = (q_{2t}, q_{2t+1}))

  • (k_t = (k_{2t}, k_{2t+1}))

旋转后:

q_t' = R(i) q_t,\quad k_t' = R(j) k_t

这一对维度对注意力贡献是:

(q_t')^\top (k_t') = (R(i)q_t)^\top (R(j)k_t) = q_t^\top R(i)^\top R(j) k_t = q_t^\top R(j-i) k_t

注意最后只剩下 相对位置 (j-i)

然后把所有 t 加起来,整个 head 的注意力就带上了相对位置信息。


5) “但是 token 间相乘并没有两两配对呀?”——其实配对一直在

你看到的是:

  • 最后算的是 (q^\top k)(整体)

但本质上它确实是:

  • 各个 pair 的 2D dot-product 一块一块算完再加起来

这就是为什么 RoPE 只需要在 pair 内旋转就能影响整体 attention score。


6) 超短总结(给你脑子里定个画面)

  • 两两配对:因为旋转必须在 2D 中定义

  • 不同 pair 角度不同:因为每对维度有不同频率 (\omega_t),同一 token 在不同子空间转不同角度

  • 虽然内积不是“显式两两配对相乘”:但内积可以拆成“每个 pair 的 2D 内积之和”,RoPE 正好逐块改变这些内积 → 总分数就变了

一定要2D吗

对,你这个判断很到位:“不一定非要二维对”——但要分清两件事:

  1. “注入相对位置信息”不一定要 2D

  2. “用旋转(rotation)这种形式注入”基本就离不开 2D(或复数等价的 2D)

我给你一个清晰的结论:

RoPE 之所以用 2D 配对,是因为它想用“正交旋转”这种结构,让 dot-product 只依赖相对位置。这个性质在 2D 旋转(或其高维正交块)里最自然、最便宜。


1) 如果你坚持“旋转”这个想法:2D 是最小单位(不可省)

在 1D 里你能做的“正交变换”只有两种:

  • 乘以 (+1)(不变)

  • 乘以 (-1)(翻转)

也就是说 1D 的“旋转”根本不存在,只有符号翻转,没法产生连续角度。

RoPE 需要的是一个随位置变化的连续相位 (\theta=i\omega),所以最小实现单位就是 2D:

R(\theta)= \begin{pmatrix} \cos\theta & -\sin\theta\ \sin\theta & \cos\theta \end{pmatrix}

所以:如果你要保持 RoPE 的结构(正交旋转 + 相位差),最小就必须 2D。


2) 但你可以用“更高维旋转”,不必局限在“2维一对”

你完全可以在 4D / 8D 的子空间里做正交变换:

q' = R_i q,\quad k' = R_j k

只要满足:

  • (R_i) 是正交矩阵(保持长度)

  • (R_i^\top R_j = R_{j-i})(相对位置的群性质)

那你依然能得到:

(q_i')^\top (k_j') = q_i^\top R_{j-i} k_j

那为什么大家都用 2D block?

因为任意高维正交旋转矩阵,都能分解成很多个 2D 平面旋转的直和(线性代数里叫 block diagonal / Givens rotations 这类思想)。

也就是说:

你以为你在做 4D 旋转,本质上它还是“若干个 2D 旋转拼起来”。

所以 2D 配对不是“唯一可能”,但它是最原子、最省参数、最省计算的实现。


3) 如果你不要求“旋转”,那当然不需要 2D

RoPE 的目标是“让注意力带位置信息”,旋转只是其中一种优雅方式。

其他常见做法完全不需要 2D 配对,例如:

✅ ALiBi(Attention with Linear Bias)

直接在注意力 logits 上加偏置:

\text{score}(i,j)= q_i^\top k_j - m\cdot|i-j|

它是相对位置,但不是旋转。

✅ 相对位置 bias(T5-style)

用一个表/函数给每个相对距离一个 bias。

✅ 绝对位置编码(sinusoidal / learned)

直接加到输入 embedding 上。

这些都能注入位置信息,只是性质和 RoPE 不同。


4) 所以真正答案是:

  • 如果你要“连续相位差 + 正交变换 + 内积只依赖相对距离”
    👉 最自然、最小单位就是 2D(或等价复数)

  • 如果你只要“给注意力加位置感”
    👉 完全不必 2D,有很多别的路


5) 一句话点破你这个问题的核心

RoPE 的“两两配对”不是因为“维度必须这样才能相乘”,而是因为 旋转这种几何操作最小需要二维,而且它能保证一个超关键性质:相对位置只体现在“相位差”上。

复数表示法

好,我们用复数表示法把 RoPE 讲一遍,你会发现它一下子变得特别“干净”,而且你前面所有疑问(为什么两两配对、为什么相对位置)都会自动解释清楚。


1) 两两配对 = 把向量当成复数

把每一对维度 (x_{2t}, x_{2t+1})组成一个复数:

z_t = x_{2t} + i,x_{2t+1}

于是一个 d-维向量(假设 d 是偶数)就变成了 d/2 个复数:

x \quad \leftrightarrow \quad (z_0, z_1, \dots, z_{d/2-1})

这就是“必须两两配对”的根本原因:
复数就是 2D(实部+虚部)的最自然表示。


2) 旋转在复数里是什么?——乘以单位复数

在 2D 平面中旋转角度 \theta,用复数写就是:

z' = z \cdot e^{i\theta}

因为:

e^{i\theta} = \cos\theta + i\sin\theta

而复数乘法本质就是在 2D 里做旋转+缩放。
这里 e^{i\theta}的模长为 1,所以纯旋转不缩放


3) RoPE 的定义(复数版)

对 token 位置 (p),对第 (t) 个频率子空间,角度为:

\theta_{t,p} = p \cdot \omega_t

那么对 Query / Key 的复数表示:

\tilde q_{t,p} = q_{t,p}\cdot e^{i\theta_{t,p}},\quad \tilde k_{t,p} = k_{t,p}\cdot e^{i\theta_{t,p}}

注意:

  • q_{t,p}, k_{t,p}本身就是复数(由两个维度组成)

  • RoPE 就是乘上一个位置相关的相位 e^{i\theta}


4) 关键推导:为什么出现“相对位置”?

注意力打分是内积 q^\top k
用复数表示时,每个频率子空间(每对维度)的贡献可以写成:

pair\_dot(\tilde q_{t,i}, \tilde k_{t,j}) = \Re\Big(\tilde q_{t,i}\cdot \overline{\tilde k_{t,j}}\Big)

这里:

  • \overline{k}是复共轭

  • \Re(\cdot) 取实部
    (它等价于二维向量的点积)

代入 RoPE 旋转:

\tilde q_{t,i}\cdot \overline{\tilde k_{t,j}} = (q_{t,i}e^{i\theta_{t,i}})\cdot \overline{k_{t,j}e^{i\theta_{t,j}}} =(q_{t,i}e^{i\theta_{t,i}})\cdot (\overline{k_{t,j}}e^{-i\theta_{t,j}})

合并相位:

= q_{t,i}\overline{k_{t,j}} \cdot e^{i(\theta_{t,i}-\theta_{t,j})}

再把 (\theta_{t,p}=p\omega_t) 代入:

e^{i(\theta_{t,i}-\theta_{t,j})} = e^{i(i-j)\omega_t}

💥 你看到了:只剩下 (i-j)

所以每个子空间的贡献变成:

\Re\Big(q_{t,i}\overline{k_{t,j}} \cdot e^{i(i-j)\omega_t}\Big)

整头注意力就是所有子空间相加:

\text{score}(i,j)=\sum_t \Re\Big(q_{t,i}\overline{k_{t,j}} \cdot e^{i(i-j)\omega_t}\Big)

✅ 这就是“RoPE = 相对位置编码”的本质:
注意力分数只依赖相对距离 (i-j)(通过相位差体现)。


5) 这也解释了你之前的困惑:为什么 token 间相乘不需要显式两两配对?

因为内积本来就是各维度乘积求和;
在复数视角里,它等价于:

  • 每一对维度当一个复数

  • 复数乘共轭取实部,就是这个二维点积

  • 最终所有频率通道加起来就是原来的 (q^\top k)

所以“两两配对”不是为了“方便相乘”,而是为了:

在每个 2D 子空间里植入一个“相位”,让 dot-product 自动带上相位差(相对位置)。


6) 复数视角的直观理解(超级好记)

把每个 token 的 Q/K 想成一堆“不同频率的转盘指针”:

  • token 在位置p

  • t个转盘转到角度 (p\omega_t)

  • 两个 token 做注意力时,比的是“指针相对角度差”

所以相对距离i-j 就像:

\Delta \theta = (i-j)\omega_t

而不同频率 \omega_t提供不同尺度的“测距能力”。


7) 你会发现 RoPE 其实就是“复数相位编码”

传统 sinusoidal 是把 \sin,\cos加到输入里;
RoPE 是把它当成相位e^{ip\omega} 直接乘到 Q/K 上:

  • 加法编码:x + p

  • 乘法编码(相位):x \cdot e^{i\theta}

乘法的好处就是:相位差会自动在内积里出现


如果你想继续深入,我建议你看下一步会很爽:

为什么只对 Q/K 做 RoPE,不对 V 做?

RoPE 的目的不是“让表示本身带位置”,而是让“注意力权重的计算”带位置(相对位置信息)。
所以它只需要作用在 产生权重的那一侧:Q 和 K
V 只是被加权求和的内容载体,旋转它不会帮你算出更好的权重,反而可能伤害内容表达。

下面我用一套非常“算式 + 直觉”组合拳讲透。


1) Attention 的核心结构:QK 决定权重,V 决定内容

自注意力一层可以写成:

A = \text{softmax}\left(\frac{QK^\top}{\sqrt{d}}\right) $$ $$ \text{out} = A V
  • QK^\top:决定“我该看谁”(权重)

  • AV:决定“我拿到什么信息”(加权内容)

所以如果你想注入“位置关系”,最直接、最有效的地方就是 权重 (A)


2) RoPE 在数学上就是让 QK^\top 依赖相对位置

RoPE 做的是:

Q' = R(p)Q,\quad K' = R(p)K

于是 score 变成(对某个 i,j):

(q_i')^\top (k_j') = q_i^\top R(j-i) k_j

你看,它通过 Q 和 K 的相位差,让分数天然带上 j-i

✅ 所以 RoPE “工作的战场”就在 score 这一步


3) 那如果你也对 V 做 RoPE,会发生什么?

假设你也旋转 V:

V' = R(p) V

输出变成:

\text{out}_i = \sum_j A_{ij} v_j' = \sum_j A_{ij} R(j), v_j

注意:权重 A_{ij} 已经通过 Q/K 含有相对位置了。
现在你又把每个 value 额外乘了一个位置旋转 R(j)

这会引发一个很关键的问题:

你输出里混进了 绝对位置依赖(j 的绝对角度),而不是你想要的相对位置。

也就是说:

  • Q/K 的 RoPE:把“相对位置”正确注入到 谁影响谁

  • V 的 RoPE:把“绝对位置相位”强行注入到 内容本身

这通常不是你想要的,因为注意力输出是多个 token 的内容混合,如果每个内容都带一个不同绝对相位,混起来会像“每个向量被随机旋转再相加”,很容易变得难学。


4) 从直觉讲:V 应该是“可加的内容”,别乱旋转

注意力输出是加权求和:

\text{out}_i = \sum_j A_{ij} v_j

这要求 v_j处在一个“共同坐标系”里,才能稳定相加。

如果你对每个位置的 v_j 做不同旋转 R(j),等于把每个 v_j放到不同坐标系里再求和:

  • 你想加 “语义信息”

  • 但你先把每个语义向量转了不同角度

  • 加起来语义方向对不齐,模型更难利用

所以对 V 做 RoPE 往往是破坏性更强的操作。


5) 更本质的视角:位置应该影响“选择”,不应该扭曲“被选中的内容”

你可以把注意力理解为:

  1. 用 Q/K 决定去哪儿拿信息(路由/寻址)

  2. 用 V 提供被拿走的信息(payload)

RoPE 是一种“位置感知寻址方式”,所以放在 Q/K 上非常合理。

而 V 就是 payload,你一般不希望 payload 被位置变换扭曲,否则同一个 token 的内容会因为它在不同位置而变得“表达方式不同”,不利于泛化。


6) 你猜的“V 不需要保存位置信息”——再精炼一下

更准确的说法是:

  • 位置关系需要体现在“权重 A”里,因为 A 决定“相互作用模式”

  • V 不需要额外加位置,因为位置已经通过 A 影响了输出组合方式
    (你可以理解为:位置已经决定了“从哪里取”,取到的内容本身不必再被位置编码污染)


7) 一个小补充:那 V 完全不含位置信息吗?

不完全是。

即使你不对 V 显式 RoPE:

  • token 的隐藏表示本身(经过多层)已经会包含上下文结构信息

  • 而且注意力权重 A 已经按位置组织信息流

所以最终 out 里当然包含位置相关的结构,只是它来自“路由方式”,而不是来自“把 payload 也旋转”。


总结一句话

只对 Q/K 做 RoPE 是因为 RoPE 需要让 attention score(谁看谁) 带相对位置;
V 是内容(看到了拿什么),旋转它会把绝对位置相位混进内容求和里,通常弊大于利。

RoPE scaling(长上下文外推)和常见扩展(NTK scaling、RoPE scaling、YaRN、LongRoPE)

好,我们来把 RoPE 长上下文外推这条线一次讲清楚:为什么会崩、怎么修、每种方法在改什么、各自代价是什么。我会按你点名的四个:RoPE scaling / Position Interpolation、NTK scaling、YaRN、LongRoPE 讲,并给你一个“怎么选”的实践指南。


0) 为什么 RoPE 一过训练长度就容易坏?

RoPE 的本质是给每个维度对一个相位:

\theta_{t,p} = p\cdot \omega_t

注意力里关键是相位差

\Delta\theta = (i-j)\omega_t

当你把位置 (p) 用到比训练时更大(比如训练到 4K,你推理到 32K),会发生两类 OOD(分布外)问题:

(A) 旋转太快 → “高频混乱”

高频维度((\omega_t) 大)会让相位差 (\Delta\theta) 变化特别剧烈,远距离 token 的相对关系变得像“乱抖”,注意力很难稳定。YaRN 论文就明确讨论了“高频更敏感”的问题。 (arXiv)

(B) 模型没见过这么大的相位 → 泛化失败

模型训练时只在 (p \le L_{\text{train}}) 的相位范围内学过模式,超过这个范围就等于在一个没见过的“角度分布”里工作。相关分析可参考 RoPE 外推 scaling laws 的工作。 (arXiv)

所以:RoPE 的长上下文扩展,本质是在“怎么让相位在更长范围内仍然像训练时一样可用”。


1) RoPE Scaling / Position Interpolation(PI):最简单粗暴,但很有效

这是最经典的第一招:把位置缩小,让模型“以为”还在训练长度内。

设你想把上下文从 (L) 扩到 (L' = sL),那就把位置 index 压缩:

p' = p / s \quad\Rightarrow\quad \theta_{t,p} = (p/s)\omega_t

也就是说:把所有频率的角速度都统一变慢 (s) 倍

✅ 优点

  • 0 训练/0 微调就能把可用长度拉长(很多开源推理直接这么做)

  • 实现极其简单(只改 RoPE 的 position ids / 或等效改角度)

❌ 缺点(关键!)

  • 你把所有频率都压慢了,等于“局部位置分辨率”也被压扁
    → 近距离(例如 1~20 token)的精细结构会变差

  • 当 scaling factor 很大时(比如 8x、16x),效果明显下降(YaRN 里也提到 naive scaling 会早早退化)。 (ICLR 会议录)

直觉:PI 像把整张“位置尺子”拉伸,远距离能看见了,但近距离刻度变粗了。


2) NTK-aware scaling:关键改进——别一刀切,按频率分配“压缩压力”

社区发现 PI 的主要问题来自高频维度太敏感,所以就有了 NTK-aware 思路:

低频可以多压缩(它负责长程),高频少压缩(它负责短程细节)。

YaRN 论文把这类方法作为重要前置工作讨论:不是把所有维度统一缩放,而是“spread out interpolation pressure across multiple dimensions”。 (ICLR 会议录)

你可以把它理解成一个频率相关的缩放函数:

\theta_{t,p} = p \cdot \omega_t' \quad \text{其中 }\omega_t' \text{是按} \omega_t \text{重新映射的}

✅ 优点

  • 仍可做到 0 微调扩展(经常被用作“开箱即用长上下文”)

  • 相比 PI,短程能力掉得少、长程更稳

❌ 缺点

  • 它不是严格“无损”的:只是在 trade-off 上更聪明

  • 不同模型/不同目标长度需要调参(实现细节也有多个版本)

参考:这条线最早在社区里很流行(例如 bloc97 的 NTK-aware scaled RoPE 讨论),YaRN 也系统总结了这类思路。 (ICLR 会议录)


3) YaRN:把“频率分治”做成系统方案,并提供高效微调策略

YaRN(ICLR 2024) 是第一批把这些经验“论文级整理+给出有效 recipe”的方法之一。它的主张很明确:

  • RoPE 外推时,不同频率应该用不同策略

  • 高频更影响局部结构,不要被压得太狠

  • 低频承载长程外推,可以承担更多“插值压力”

  • 并且它强调:用更少 tokens/更少 steps 的微调就能把窗口扩得很大(相比以前方法训练成本显著更低)。 (arXiv)

✅ 优点

  • 效果通常比纯 PI/纯 NTK-aware 更好、更稳

  • 论文给了比较完整的工程路径(怎么扩、怎么训、怎么保短文本能力)

  • 训练成本比很多 earlier 方法低(paper 强调 10x less tokens、2.5x less steps)。 (arXiv)

❌ 缺点

  • 最佳效果一般还是需要微调(尽管比别人省)

直觉:YaRN 的核心是“别用一把尺子量所有距离”,而是让不同频率各司其职。


4) LongRoPE:把窗口推到 256K~2M 的“工程级”方案(少量微调)

LongRoPE 的定位更像“我要非常长:256K / 2M”,因此它必须更激进、也更工程化。

它的亮点是:

  • 用相对少的微调步数(文中提到最多约 1k steps、训练长度到 256K)

  • 把预训练 LLM 的窗口扩到非常夸张的量级(论文写到 2048K tokens)

  • 同时尽量保持原本短上下文能力。 (arXiv)
    并且有官方开源实现仓库。 (GitHub)

✅ 优点

  • 目前公开论文里属于“能把 RoPE 推到超长”的代表工作之一

  • 训练成本相对“2M 目标”来说已经很夸张地省

❌ 缺点

  • 你要付出至少一点微调成本

  • 工程复杂度更高(数据构造、训练 schedule、稳定性)

直觉:LongRoPE 不只是改个公式,它是在“系统地让模型适应超长相位分布”。


6) 一个很关键但经常被忽略的点:长上下文不是只改 RoPE

哪怕 RoPE 完美外推了,你仍可能遇到:

  • attention 数值稳定性(softmax、精度)

  • KV cache / memory

  • 训练数据分布(模型没学过“长文档组织结构”)

所以 RoPE scaling 是必要条件,但不总是充分条件。