直接把位置编号告诉模型,行不行?
本课一问:上一课证明了位置必须从外部放进去。最直接的办法——把「第 0 个、第 1 个、第 2 个」这个编号告诉模型——行不行?
这一课先不碰:用公式算位置(第 3 课)、RoPE(第 4 课)、超长上下文的扩展方法(第 5 课)。
从已有出发
到目前为止,「小明打了小王」进模型时是 3 个 token 的向量,每个向量只说明「我是谁」。第 1 课的结论是:只要这 3 个向量里没有任何随位置变化的数,注意力就分不清先后。
所以任务很清楚:让每个 token 的向量带上一点「我在第几个」的信息。最先想到的,是把编号本身放进去。
先自己设计:把编号直接加到每一维上
token 向量是一串数,位置编号是一个数。最省事的做法:第 p 个位置的 token,每一维都加上 p,即 x = e + p·𝟙(𝟙 是全 1 向量)。对话样本里的例子正是「我 + 位置 37」。拖动下面的位置,看内容还剩多少:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
每个位置 t 不再加一个数,而是加一个和词向量同样长的向量 P[t]。P 是一张表,一行对应一个位置,和 Embedding 表一样随机初始化、在训练中学出来。它的数值尺度和词向量相当,不会淹没内容;每个位置一个独立的向量,也不会像 p 那样无限增长。下一节看它的形状和代价。
失败有两层,都是算出来的,不是经验判断:
- 尺度:内容 ÷ 位置 = RMS(e) ÷ p,与维度无关。位置越靠后,内容占比越小。Qwen3.8 原生支持 262,144 个位置,到最后一个位置时,这个比例只有 1/262,143。
- 分不清词:每一维都加同一个 p,相当于所有 token 都朝同一个方向 [1, 1, …, 1] 被推远。p 越大,「小明」和「小王」越像同一个向量;再过一次 RMSNorm,两者都接近 [1, 1, 1, 1]。
试三件事:① 点预设的 37,读出「内容 / 位置」和余弦相似度;② 切到「std = 0.02(项目初始化)」:我们自己的模型刚初始化时,embedding 就是这么小,在位置 1 内容就只剩 2%;③ 把 p 拖到最右(262,143),看 RMSNorm 之后的向量。
🔍 把编号除以句长,压到 0–1 之间行不行?
这是第二个常见的朴素方案:位置 t 编成 t/(T−1),T 是句长。尺度问题解决了(永远在 0–1 之间),但出现两个新问题,都能直接算出来。① 同一个数在不同句子里意思不同:在 3 个 token 的「小明打了小王」里,0.5 表示第 2 个 token;在 1,024 个 token 的文档里,0.5 表示第 512 个左右。② 「相邻」不再是固定的距离:3 个 token 的句子里相邻两个位置差 1/2 = 0.5;1,024 个 token 的文档里差 1/1,023 ≈ 0.000978。模型没法学到一个稳定的「前一个词」信号。
改进:可学习的绝对位置向量
每个位置配一个可学习的向量,和词向量相加:xt = E[idt] + P[t]。GPT-2 就是这么做的。同一句「小明打了小王」在 GPT-2 的字节级 BPE 下切成 11 个 token(C1 讲过中文为什么会被拆成字节),形状如下:
| 维度 | 含义 | 真实规模 | 小规模 |
|---|---|---|---|
| V | 词表大小(vocab_size) | 50,257 | 12 |
| L | 位置表行数(n_positions)= 能处理的最长序列 | 1024 | 8 |
| d | 向量维度(n_embd) | 768 | 4 |
| T | 「小明打了小王」在 GPT-2 里的 token 数 | 11 | 3 |
位置表 P 一共 1,024 × 768 = 786,432 个参数。在训练长度之内它确实管用:原始 Transformer 论文里,把公式版位置编码换成可学习的位置向量,翻译质量几乎不变(dev PPL 4.92 对 4.92,BLEU 25.8 对 25.7) 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条英德翻译、base 模型、newstest2013 开发集;只比较了训练长度之内。 出处Vaswani 2017 表 3 第 (E) 行 ↗ 。
它在哪失败:表外的位置
问题出在「表」这个字上。位置表有 L 行,模型就只认得位置 0 到 L−1。
- 查无此行:GPT-2 的第 1,024 个位置(从 0 数)在 P 里没有对应的行,前向计算直接越界。我们项目的 minillm 在这里会主动报错:「learned 位置编码只学过 0..block_size−1」。
- 表再大也没用:就算把表开到 100 万行,训练时序列只有 128 长,第 128 行以后一次梯度都拿不到,永远停在随机初始值(项目里是 std = 0.02 的高斯噪声)。模型从没见过这些位置。
- 放在锚点模型上:如果 Qwen3.8 用可学习位置覆盖它的 262,144 个位置,光这张表就是 262,144 × 5,120 = 1,342,177,280 个参数,占全模型 27,781,427,952 的 4.8%;而且只有真的训练过那么长的序列,后面的行才会被更新。Qwen3.8 实际用的 RoPE 是 0 个参数(第 4 课)。
我们自己的模型上实测:同一数据、同一步数、同一参数量,训练长度 128,再拿 256、512 长的验证集去测。
learned 那两格是「✗」:模型根本跑不起来,这就是表外位置的真实样子。实验里还强行让它跑了一次——位置表后面的位置一律复用第 127 行。把 512 长的序列按每 16 个位置一段拆开看,问题出在哪一段一目了然:
越过训练长度那条竖线,强行延长的 learned 立刻跳上去:从那以后每个位置拿到的都是「位置 127」的向量,模型看到的是一串自称站在同一个位置上的 token。
常见误解:「位置向量和词向量一样,是模型的一部分,所以任意长度都能用。」
和词向量一样的地方,恰恰是问题所在:词表里没有的词查不到,位置表里没有的位置也查不到。训练只会更新出现过的行——没出现过的词,向量是随机的;没出现过的位置,向量也是随机的。词表外的词可以用 BPE 拆成子词(C1),位置表外的位置却没有「拆」的办法。
位置编码的演进,你在这里
- 解决了注意力对所有 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 带上「我在第几个」,而且尺度不失控 |
| ② 数学上为什么可能有效? | 没有特别的数学性质:每个位置一个独立参数,位置 5 和位置 6 的向量之间没有任何先验关系,「相邻」要靠训练自己学 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条这是「它没有什么性质」的陈述:表的每一行互相独立,是按构造成立的事实;它让模型最灵活,也让表外位置无从谈起。 出处待补 |
| ③ 实验上为什么被选过? | 训练长度之内,它和公式版一样好(Vaswani 表 3);GPT-2 用了它;项目实测它在训练长度之外的表现见上图 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条只在训练长度之内比较过;外推是另一回事。 出处Vaswani 2017 表 3 ↗ |
一句话
位置不能当一个数加进去(会淹没内容);做成可学习的位置向量能用,但位置表有多少行,模型就只认多少个位置。
先停在这里:记住「表外位置无从谈起」即可,进下一课。
再深一层:展开上面「把编号除以句长」的 Dig,把第二个朴素方案也排除掉。
下一个缺口
训练时没见过的位置怎么办?能不能不「背」位置,而是「算」出来? → 第 3 课:没训练过的位置,能不能用公式算出来?
回扣先记下:上图里 RoPE 那条线在训练长度之外的表现,第 5 课会专门拆开讲。
你可能会问
- 「BERT、GPT-2 为什么还用可学习位置?」 它们的最大长度是固定的(GPT-2 为 1,024),在这个长度之内,可学习位置和公式版一样好,实现最简单。问题只在「超出」时出现。
- 「位置向量为什么是和词向量相加,不是拼在后面?」 原论文只说两者维度相同所以可以相加;拼接会把每一层的宽度都撑大。相加的后果是内容和位置混在同一组数里——第 4 课会看到,这正是注意力打分里「内容 × 位置」交叉项的来源。
- 「把编号除以句长行不行?」 见上面的 Dig:同一个数在不同长度的句子里意思不同,「相邻」也不再是固定距离。