从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 25 分钟 页内实验 ×2
C2 位置:模型怎么知道顺序 第 2 课

直接把位置编号告诉模型,行不行?

直接塞编号
把 0、1、2… 直接加进向量,位置 37 时内容只剩 2.7%;改成可学习的位置向量能用,但位置表有多少行,模型就只认多少个位置。
❓

本课一问:上一课证明了位置必须从外部放进去。最直接的办法——把「第 0 个、第 1 个、第 2 个」这个编号告诉模型——行不行?

这一课先不碰:用公式算位置(第 3 课)、RoPE(第 4 课)、超长上下文的扩展方法(第 5 课)。

从已有出发

到目前为止,「小明打了小王」进模型时是 3 个 token 的向量,每个向量只说明「我是谁」。第 1 课的结论是:只要这 3 个向量里没有任何随位置变化的数,注意力就分不清先后。

从已有出发 · 累计数据流 点已学环节看形状与参数
解码层

所以任务很清楚:让每个 token 的向量带上一点「我在第几个」的信息。最先想到的,是把编号本身放进去。

先自己设计:把编号直接加到每一维上

token 向量是一串数,位置编号是一个数。最省事的做法:第 p 个位置的 token,每一维都加上 p,即 x = e + p·𝟙(𝟙 是全 1 向量)。对话样本里的例子正是「我 + 位置 37」。拖动下面的位置,看内容还剩多少:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案把位置编号 p 直接加到每一维
内容向量的尺度
位置编号 p 0

失败有两层,都是算出来的,不是经验判断:

🎯

试三件事:① 点预设的 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。模型没法学到一个稳定的「前一个词」信号。HF 博客《You could have designed state of the art positional encoding》按同样的顺序排除了这个方案。

改进:可学习的绝对位置向量

每个位置配一个可学习的向量,和词向量相加:xt = E[idt] + P[t]。GPT-2 就是这么做的。同一句「小明打了小王」在 GPT-2 的字节级 BPE 下切成 11 个 token(C1 讲过中文为什么会被拆成字节),形状如下:

GPT-2 的输入层:词向量 + 可学习的位置向量(config 真实维度)
X = E[ids] + P[0 … T−1]
参数(可学习)输出中间结果
边长按对数比例缩放,块边上的数字是真实值;同一维度符号的边在全图等长。
维度含义 真实规模 小规模
V 词表大小(vocab_size) 50,257 12
L 位置表行数(n_positions)= 能处理的最长序列 1024 8
d 向量维度(n_embd) 768 4
T 「小明打了小王」在 GPT-2 里的 token 数 11 3
维度取自 GPT-2 官方 config(frontier-models.json);T 由 GPT-2 真 tokenizer 切出(c1-tokenize.json)。

位置表 P 一共 1,024 × 768 = 786,432 个参数。在训练长度之内它确实管用:原始 Transformer 论文里,把公式版位置编码换成可学习的位置向量,翻译质量几乎不变(dev PPL 4.92 对 4.92,BLEU 25.8 对 25.7) 。

它在哪失败:表外的位置

问题出在「表」这个字上。位置表有 L 行,模型就只认得位置 0 到 L−1。

我们自己的模型上实测:同一数据、同一步数、同一参数量,训练长度 128,再拿 256、512 长的验证集去测。

项目实测 · 可学习位置 vs RoPE:训练长度之外

learned 那两格是「✗」:模型根本跑不起来,这就是表外位置的真实样子。实验里还强行让它跑了一次——位置表后面的位置一律复用第 127 行。把 512 长的序列按每 16 个位置一段拆开看,问题出在哪一段一目了然:

项目实测 · 强行延长的 learned:loss 在哪个位置开始变坏

越过训练长度那条竖线,强行延长的 learned 立刻跳上去:从那以后每个位置拿到的都是「位置 127」的向量,模型看到的是一串自称站在同一个位置上的 token。

⚠

常见误解:「位置向量和词向量一样,是模型的一部分,所以任意长度都能用。」

和词向量一样的地方,恰恰是问题所在:词表里没有的词查不到,位置表里没有的位置也查不到。训练只会更新出现过的行——没出现过的词,向量是随机的;没出现过的位置,向量也是随机的。词表外的词可以用 BPE 拆成子词(C1),位置表外的位置却没有「拆」的办法。

位置编码的演进,你在这里

位置编码的演进:每一代都被上一代的某个真数字逼出来
  1. 起点 不加任何位置信息 Vaswani 2017 §3.5 的前提
    解决了注意力对所有 token 一视同仁,一次矩阵乘并行算完。
    卡在交换「小明打了小王」为「小王打了小明」,每个 token 的输出向量逐位相同,差值 = 0(第 1 课在浏览器里实算)。
  2. 朴素 ① 把编号直接加进向量 最容易想到的做法
    解决了每个位置一个不同的数,简单直接。
    卡在编号没有上限:在位置 37,内容只剩位置信号的 2.7%(内容 RMS = 1 时;按项目初始化尺度只剩 0.054%)。
  3. 第一代 可学习的绝对位置向量 GPT-2 等 你在这里
    解决了位置也查表,和词向量一样训练出来。
    卡在表有多少行就只能处理多少个位置:GPT-2 的位置表 1,024 行 × 768 维 = 786,432 个参数,位置 1,024 没有对应的行。
  4. 第一代 · 公式版 Sinusoidal 正弦位置编码 Vaswani 2017
    解决了不背位置,用多组 sin/cos 现算,任意位置都算得出来;两个位置编码的点积只依赖距离。
    卡在「算得出」不等于「会用」:原文表 3 里它和可学习位置几乎一样(dev PPL 4.92 vs 4.92,BLEU 25.8 vs 25.7);而且加在输入上,整句平移后注意力分数会变。
  5. 第二代 相对位置(在注意力里按距离加项) Shaw 2018、T5 等
    解决了直接告诉注意力「相隔多远」,语言规律在文章各处一样。
    卡在要改注意力的计算,为每个距离单独加一项,距离还被截断在 [rmin, rmax] 之内(RoFormer §2.3 的综述)。
  6. 第三代 RoPE 旋转位置编码 Su et al. 2021
    解决了按位置旋转 Q、K,转完再点积,绝对位置被约掉,只剩 n − m;不加参数。
    卡在能算到任意长,却只在训练见过的角度内可靠:Qwen3 原生 32,768,64 个频率对里有 24 对一圈都没转满;Qwen3.8 原生 262,144,32 对里有 10 对。
  7. 解决了把新位置折算回见过的范围: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(全注意力版本)。
  8. 2026 分歧 部分 RoPE,还是干脆不要(NoPE) 2025–2026
    解决了Qwen3.8 只转 25% 的维度;Kimi K3、GLM-5.3-Flash 的全局注意力层不用任何位置编码,顺序交给线性注意力层。
    卡在Qwen 报告:全注意力层去掉 RoPE,预训练看不出差别,后训练后「停不下来」的比例明显升高——所以它保留了 RoPE。两条路线都做到了百万级上下文,没有唯一正解。
数字口径:GPT-2、Qwen3、Qwen3.8 的数字由 frontier-models.json / anchor-model.json 的官方 config 在构建时现算;表 3 数字出自 Vaswani 2017(本地 PDF);MRCR 数字出自 Qwen3.8-Flash-Next 技术报告表 3(官方自报,未经第三方复现);Qwen 的 NoPE 结论同一报告,只有定性描述。

证据三问

问本课的回答
① 它解决什么问题?让每个 token 带上「我在第几个」,而且尺度不失控
② 数学上为什么可能有效?没有特别的数学性质:每个位置一个独立参数,位置 5 和位置 6 的向量之间没有任何先验关系,「相邻」要靠训练自己学
③ 实验上为什么被选过?训练长度之内,它和公式版一样好(Vaswani 表 3);GPT-2 用了它;项目实测它在训练长度之外的表现见上图

一句话

📌

位置不能当一个数加进去(会淹没内容);做成可学习的位置向量能用,但位置表有多少行,模型就只认多少个位置。

🚦

先停在这里:记住「表外位置无从谈起」即可,进下一课。
再深一层:展开上面「把编号除以句长」的 Dig,把第二个朴素方案也排除掉。

下一个缺口

训练时没见过的位置怎么办?能不能不「背」位置,而是「算」出来? → 第 3 课:没训练过的位置,能不能用公式算出来?

回扣先记下:上图里 RoPE 那条线在训练长度之外的表现,第 5 课会专门拆开讲。

你可能会问