同样三个 token 换个顺序,模型分得出来吗?
本课一问:「小明打了小王」和「小王打了小明」切出来是同样三个 token,只是顺序不同,意思完全相反。一个只有 Embedding 和注意力的模型,分得出这两句话吗?
这一课先不碰:位置信息具体怎么编码(第 2–4 课)、因果掩码(C3 讲,本课只在末尾预告一句)、多层堆叠(C4)。
从已有出发
C1 结束时,一句话已经变成了一个矩阵:切分 → Token ID → 查 Embedding 表,每个 token 一行 5,120 维向量。贯穿本章的两句话,用锚点模型 Qwen3.8 的真 tokenizer 切出来是这样:
| 句子 | 切出的 token | Token ID(Qwen3.8 真 tokenizer) |
|---|---|---|
| 小明打了小王 | 小明 · 打了 · 小王 | 115942 · 111327 · 126835 |
| 小王打了小明 | 小王 · 打了 · 小明 | 126835 · 111327 · 115942 |
两句话用的是同一组 ID,只是排列不同。Embedding 查表只认 ID:ID 115942 不管出现在句首还是句尾,查到的都是表里同一行。所以进入模型的是同样三个向量,只换了排列顺序。
流水线里「位置」这一环就是本章要补的。补之前,先证明它非补不可:假设模型里只有 Embedding 和注意力,看它能不能自己分清先后。
先自己设计:什么都不加,直接让 token 互相看
注意力要到 C3 才细讲,这一课只需要它的一个事实:第 i 个位置的输出,是所有 token 的 v 向量按权重加起来;权重来自 qi 和每个 kj 的点积,再过 softmax。而 q、k、v 都是各自 token 的向量乘一个矩阵得来的。把公式逐项拆开看,顺序在哪一项丢掉的:
公式看完,亲手跑一次。下面每个数都在你的浏览器里现算:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
看输入:每个 token 的 4 维向量与 WQ/WK/WV(教学构造)
看输入:每个 token 的 4 维向量与 WQ/WK/WV(教学构造)
每个位置加一个随位置变化的向量之后,同一个 token 在不同位置的输入就不同了,差值不再是 0。这个「位置信号」该怎么设计,就是第 2–4 课的全部内容;这里用的是第 3 课的公式,先当作「某种位置信号」即可。
试三件事:① 先按「会变 / 不会变」做预测,再对照三个差值;② 看两句话的权重矩阵:句子 B 的那张,就是句子 A 的那张把行和列按同一顺序换了位置——数值一个没变;③ 连点几次「换一组随机向量」,差值始终是 0。这说明它不是这组数字碰巧,而是运算结构决定的。
为什么差值恰好是 0:置换等变
把上面的现象写成一行代数。用 P 表示「把行重新排列」的矩阵(句子 A → 句子 B 就是一个这样的 P):
- 输入换了行:X′ = PX。q、k、v 都是逐行乘矩阵,所以 Q′ = PQ、K′ = PK、V′ = PV。
- 分数矩阵 Q′K′ᵀ = P(QKᵀ)Pᵀ:行和列按同一个顺序重排,里面的数一个不少、一个不变。
- softmax 按行算,重排行列不影响每行里的数值;乘上 V′ 之后,输出 O′ = P·O。
结论:置换输入,只会同样地置换输出 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条推导见本节三步,只用到「逐行乘矩阵」和「按行 softmax」,对任意 W 成立。原文 §3.5 据此说明:模型既没有循环也没有卷积,要利用顺序就必须额外注入位置信息。 出处Vaswani 2017 §3.5(本地 PDF) ↗ 。数学上这叫置换等变。每个 token 得到的输出只取决于「句子里有哪些 token」,和它们的排列无关。
页面上的差值是精确的 0,还要多一个条件。浮点加法不满足结合律,(a+b)+c 和 (c+b)+a 可能差最后一位。为此,组件里对 j 的求和一律按 token ID 的固定顺序相加。换成 numpy 直接相加,差值是 2.2×10−16 级的舍入误差,数学上仍然恰为 0。
常见误解:「注意力训练久了,会自己学到顺序。」
不会。上面的推导对任何 WQ、WK、WV 都成立,而训练只能改这些矩阵里的数值,改不了「逐行乘矩阵 + 对集合求和」这个结构。没有位置信息进来,训练多少步,「小明打了小王」和「小王打了小明」里的每个 token 都得到同一个输出。要让模型分清先后,只能从外部把位置放进去:放进输入(第 2、3 课)、放进注意力的打分(第 4 课),或者靠掩码这类结构间接透露(第 5 课)。
有一个常见说法:「没有位置编码时,Transformer 看到的是一袋词。」它只能当入口用,要配上精确映射和失效点:
- 映射:「一袋」对应多重集——只记录有哪些 token、各几个,不记录排列。本课的等变性质说的正是这一点:输出只依赖这个多重集。
- 失效点:词袋模型给整句一个固定表示,注意力却给每个 token 一个随上下文变化的输出。句子里多一个或少一个 token,「小明」的输出就会变;这里丢掉的只是「顺序」,不是「上下文」。所以更准确的说法是「等变」,不是「看不见上下文」。
🔍 打开因果掩码会怎样?(第 5 课兑现)
上面的演示里勾选「加因果掩码」:每个位置只能看自己和前面的 token。句子 A 里「小明」排第 0,只能看到自己;句子 B 里它排第 2,能看到全部三个。能看到谁随位置变了,所以输出也变了,差值不再是 0。也就是说,掩码本身就透露了一点位置线索。GPT 这类模型都带掩码,那它们是不是不需要位置编码?第 5 课会拿真实模型的选择来回答:「不加位置编码(NoPE)」并不等于「完全不知道位置」,可是 2026 年的主流模型大多仍然保留了 RoPE。
位置编码的演进,你在这里
- 解决了注意力对所有 token 一视同仁,一次矩阵乘并行算完。卡在交换「小明打了小王」为「小王打了小明」,每个 token 的输出向量逐位相同,差值 = 0(第 1 课在浏览器里实算)。
- 解决了每个位置一个不同的数,简单直接。卡在编号没有上限:在位置 37,内容只剩位置信号的 2.7%(内容 RMS = 1 时;按项目初始化尺度只剩 0.054%)。
- 解决了位置也查表,和词向量一样训练出来。卡在表有多少行就只能处理多少个位置:GPT-2 的位置表 1,024 行 × 768 维 = 786,432 个参数,位置 1,024 没有对应的行。
- 解决了不背位置,用多组 sin/cos 现算,任意位置都算得出来;两个位置编码的点积只依赖距离。卡在「算得出」不等于「会用」:原文表 3 里它和可学习位置几乎一样(dev PPL 4.92 vs 4.92,BLEU 25.8 vs 25.7);而且加在输入上,整句平移后注意力分数会变。
- 解决了直接告诉注意力「相隔多远」,语言规律在文章各处一样。卡在要改注意力的计算,为每个距离单独加一项,距离还被截断在 [rmin, rmax] 之内(RoFormer §2.3 的综述)。
- 解决了按位置旋转 Q、K,转完再点积,绝对位置被约掉,只剩 n − m;不加参数。卡在能算到任意长,却只在训练见过的角度内可靠:Qwen3 原生 32,768,64 个频率对里有 24 对一圈都没转满;Qwen3.8 原生 262,144,32 对里有 10 对。
- 解决了把新位置折算回见过的范围:Qwen3 32,768 → YaRN ×4 = 131,072;Qwen3.8 262,144 → ×4 ≈ 1M。卡在窗口 ≠ 有效长度:Qwen3.8-Flash-Next 技术报告自报,8 针 MRCR 在 128K 为 97.14,到 1M 只剩 20.71(全注意力版本)。
- 解决了Qwen3.8 只转 25% 的维度;Kimi K3、GLM-5.3-Flash 的全局注意力层不用任何位置编码,顺序交给线性注意力层。卡在Qwen 报告:全注意力层去掉 RoPE,预训练看不出差别,后训练后「停不下来」的比例明显升高——所以它保留了 RoPE。两条路线都做到了百万级上下文,没有唯一正解。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 这一课本身是「问题」:证明位置信息必须从外部注入,否则同一组 token 的任何排列对模型都一样 |
| ② 数学上为什么? | 置换等变:Attn(PX) = P·Attn(X),对任意参数成立 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条三步推导;不依赖数据和规模。 出处Vaswani 2017 §3.5 ↗ |
| ③ 实验上呢? | 不需要实验来支持:它是恒等式。页面上换任意随机向量都得到 0,只是在演示这个恒等式 |
一句话
注意力只看「有哪些 token」,不看「谁在前」:不注入位置信息,换个顺序只会把输出换个顺序。
先停在这里:如果你只想知道「为什么需要位置编码」,读到这里就够了,直接去下一课。
再深一层:展开上面的「打开因果掩码会怎样」,再在演示里勾选掩码,看差值怎么变。
下一个缺口
那就把位置告诉它——最直接的办法是什么? → 第 2 课:直接把位置编号告诉模型,行不行?
两处回扣先记下:公式里 q、k、v 从哪来、为什么用点积打分,C3 第 1–2 课兑现;掩码透露位置线索这件事,本章第 5 课兑现。
你可能会问
- 「那 GPT 这类只能看前文的模型呢?掩码不是也分了先后吗?」 分了一部分,见上面的「打开因果掩码会怎样」;完整回答在第 5 课的 NoPE 一节。
- 「RNN 为什么不需要位置编码?」 RNN 逐个读入 token,第 t 步的状态由第 t−1 步算出,「先后」就写在计算顺序里。代价是必须一步一步算、没法并行,这正是 Transformer 放弃它的原因(C0 序章第 2 课)。
- 「把 token 按顺序排好再送进去,顺序不就在里面了吗?」 矩阵的行号不参与任何计算:矩阵乘和按行 softmax 对「哪一行排第几」一视同仁,推导里的 P 就是在说这件事。顺序要起作用,必须有某个数值随行号变化——这个数值就叫位置编码。