上一章的
这个性质有个术语叫"置换不变性"(permutation invariance):顺序随便打乱,每个词拿到的输出内容都不变。
互动 · 打乱输入顺序,看注意力输出变不变
原顺序
打乱顺序
想一下你看到的那两排格子:打乱之后,每个词拿到的输出向量完全没变——
模型根本没有"第几个词"这个概念。
而语言里词序几乎决定一切:"我不喜欢" ≠ "喜欢我不"。
所以位置信息必须从外面注入。
| 方案 | 怎么注入 | 一句话特点 |
|---|---|---|
| 正弦编码 2017 |
把不同波长的 sin/cos 加到词向量上(波长 = 隔几个词重复一次) | 不用学参数;位置和语义挤在同一串数里 |
| 可学习编码 BERT/GPT-2 |
每个位置一个可训练向量,直接加上去 | 简单、效果好;长度写死在训练范围里 |
| RoPE 2021 → 主流 |
按位置旋转 Q、K | 不碰词向量,但得改注意力内部的 Q、K;点积自动只看位置差(第 3 节) |
| ALiBi 2021 |
不给 |
只表达"越远越不看"这一种偏好,实现极简 |
| NoPE 2023 |
什么都不加,靠因果掩码自己隐含的一点顺序 | 因果 LM(从左到右预测下一个词)上不差;双向(能同时看前后文的那类模型,如 BERT)任务明显更弱 |
示意 · 四种把位置塞进去的写法(NoPE 什么都不加,所以没画)
大多数现代大模型都用 RoPE——LLaMA、Qwen、DeepSeek、Mistral 这些都在用(BLOOM 用的是 ALiBi,是少数派)。 它被选中是因为三件事:点积只依赖相对位置、不碰词向量、 需要更长的上下文时有成熟的扩展方法(第 4 节讲)。
RoPE 的想法非常几何。把词向量的每两维看成平面上的一个点, 然后按位置把它旋转一个角度——位置越大,转得越多。
互动 · 真实的二维旋转
像钟表上的指针。位置 = 时刻,向量 = 指针的初始方向。
第 3 个词是"3 点钟方向",第 7 个词是"7 点钟方向"。
关键性质是:两个指针的夹角只取决于它们的时刻差(7 − 3 = 4),
而和现在几点无关。这正是"注意力只需要知道相对距离"所需要的性质。
设两个向量各旋转 θm 和 θn,然后做点积。
二维旋转的点积结果里,只出现角度差 (θm − θn)——绝对值被消掉了。
而 θ 和位置成正比,所以角度差 ∝ 位置差。
于是:注意力分数自动只依赖两个词的相对距离,这正是我们想要的。
模型在 4096 个词上训练,你想让它读 128K 的文档——这叫外推(extrapolation):把模型用到比训练时更长的句子上。 先记住一个词:困惑度(perplexity)——模型预测下一个词时平均在几个候选之间犹豫,越小越好。 长度一超,困惑度就会崩。为什么?
互动 · 外推失败的直观图(示意,不是实测值)
互动 · 为什么一到训练长度就撞墙
| 方法 | 怎么做 | 代价 |
|---|---|---|
| 直接外推 | 什么都不做 | 超出训练长度后立刻崩坏,困惑度暴涨 |
| 位置插值 PI | 把 128K 的位置压缩回 0~4K 的范围 | 短文本性能下降(位置被挤密了) |
| NTK 感知插值 | 不整体压缩,而是按频率分别处理:转得快的那几对几乎不动,转得慢的那几对多压缩 | 需要调一个缩放因子 |
| YaRN | NTK 插值 + 注意力温度缩放(把注意力分数整体调尖或调平),分频段处理 | 目前长上下文扩展的主流方案,128K 很稳 |
| 滑动窗口注意力 | 只让每个位置看最近 N 个词(N 是固定的窗口大小) | 单层只能看最近 N 个词(远距离靠叠层间接传),降低推理成本(只看最近一段,算得少、存得少) |
| 继续训练 | 用长文本继续训练一段 | 最有效,但要数据和算力 |
"支持 128K
所以工程上有个经验:最重要的内容放开头或结尾,别放中间。
钟表类比说完了。下面把它变成一个能拖的东西—— 拖完你会看见一句话:两个词挪到多后面都不重要,只要它们的距离没变,注意力分数就一模一样。
互动 · 两根箭头,一个点积,一条平线
公式卡 · 上面那句话写成数学
三个时刻,夹角一样 → 点积一样
θ 是每一行的转速:低维快、高维慢
到这里收个口:每个词的方向由它的绝对位置决定——位置越大,q、k 转得越多。 但两个词做点积时,两边的绝对位置被约掉,只剩下位置差。「你离我多远」才是真正进了注意力的那个量。
互动 · 为什么旋转不会把意思弄脏
把两个位置滑块一起往前挪 5 格(或连点五次「两个一起 +1」)。
左边两根箭头明显转到了别的地方,右边那条橙色平线却一动没动——
公式里没有 m,也没有 n,只有 (m − n),这就是全部内容。
| 隐患 | 说明 | 什么时候真的会痛 → 换什么 |
|---|---|---|
| ① 外推是"借来的"能力 | 位置编码和训练长度绑死,超出就成了分布外(训练时没见过的样子)。 大多数扩展方法都是在事后打补丁,不是结构上的保证。 | 要用的长度超过训练长度 → 用 YaRN 这类插值再少量继续训练(第 4 节) |
| ② 长上下文的成本是平方的 | 128K 的注意力矩阵是 4K 的 1024 倍,显存和算力都按这个倍数涨。 | 用不到那么长就别开那么长;要长就用省显存的注意力实现(FlashAttention 一类,阶段 6) |
| ③ 长度开了,模型不一定用得上 | 《Lost in the Middle》(2023)发现:信息放在上下文中间时,模型的表现明显下降。 | 长文里关键信息在中间 → 调排布(放到开头或结尾),别指望它自己找到 |
上一章(《Transformer 自注意力》)在“它牺牲了什么”那一行写过一句: “位置信息要自己加”。 这一章就是那张账单的逐条明细。
回到第 1 节,点一次「🔀 再打乱一次」——同一个词的输出一模一样、只是位置换了,这就是欠款本身; 再回到第 3 节把「Q 的位置」从 0 拖到 32、K 不动,Q·K 只跟着差值变,这就是还款方式:位置不是加进向量的,是旋进去的。
| 暗线 | 这一章的回答 |
|---|---|
| 信息流动 | 数据形状:这一章最特别的地方就是形状一点没变。
可学习[L, d] 的参数表
(L 个位置、每个位置 d 维,一个位置一行);
而 RoPE 是原地旋转 Q、K——它不增维、不改形状、不加参数,
只是把每两维当成一个平面转一个角度。三种注入方式落在三个不同的地方: 加法(正弦、可学习)把位置写进向量里; 注意力里减距离(ALiBi)把位置写进分数矩阵; 旋转(RoPE)把位置写进向量之间的关系。 |
| 什么被牺牲了 | 牺牲了“位置是免费先验”的幻觉和长度的自由度。
换回来的是一个很难替代的性质:注意力分数自动只依赖相对距离,
而且不需要一张随长度增长的参数表——这正是 128K 乃至 1M 上下文能在同一个模型里做到的前提。 但得诚实:外推能力是“借来的”,不是结构保证, 它是事后用 NTK 插值 / YaRN 打补丁换的 |
| 参数账本 | 作为对照,可学习编码是 [L, d] 一张表:
GPT-2 是 1024 × 768 = 78.6 万参数(占 1.24 亿总参数的 0.6%)。真正的大账在它解锁的那一头:n = 128K 时,注意力分数矩阵有 (128×1024)² ≈ 1.7 × 10¹⁰ 个元素(fp16 半精度、每个数 2 字节,存下来约 34 GB)。 也就是说,那个 0 参数的旋转决定你能不能吃下这份 34 GB 的账(阶段 6 的 FlashAttention / KV cache 就是为它而生) |
| 它假设了什么 | 三条,一条比一条具体: ① “只有相对距离重要,绝对位置不重要”——RoPE 的点积里 m−n 出现、m 和 n 自己不出现。对语言大体成立, 但遇到“请你数到第 100 个字”这类需求就弱了。 ② “位置和语义可以分离”——旋转不改变向量长度, 所以它在注入位置时不扰动“这个词有多强”这个量;加法做不到这一点。 这就是暗线「它假设了什么」在代码级别的一个实例。 ③ ALiBi / 滑窗额外假设“越远越不重要”——这是一条很强的语言假设, 而开头的例子、需回溯的人名恰恰常常不成立 |
| 违背了哪个直觉 | “打乱输入顺序,输出居然不变”——这是全章最反直觉的起点。
一个看起来在“理解语言”的模块,其实对顺序完全不敏感。 第二个更反直觉:什么都不加(NoPE)在一些实验里外推也不差—— 原因是因果掩码本身就隐含了一点顺序信息,多做的那一步反而带来了新的分布外。 “加更多信息”不等于“更好”。 |
它接住了上一章的什么:《Transformer 自注意力》 用“任意两个位置都能直接对视”换掉了循环的串行性,代价是丢掉了顺序感。 这一章把丢掉的顺序用一个 0 参数的操作找了回来。
它给下一章留了什么:现在“模型长什么样”这一块已经齐了—— 注意力、位置、前馈、残差。接下来的问题是: 这套结构一口气读完整个互联网之后,到底学会了什么?能不能被一个统一范式描述? 这就是《预训练语言模型》要回答的问题。
注意力是置换不变的——它天生不知道谁先谁后(第 1 节那两排格子)。
RoPE 用"按位置旋转"这个几何操作,让点积自动只依赖相对距离,是今天的默认答案。
"支持 128K" ≠ "用好 128K":外推仍然靠事后补丁,
而且信息放在中间时模型容易忽略——这是工程上必须知道的坑。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。