第三篇的多头实验台里,三个头分清了「小明把书借给了小红」和「小红把书借给了小明」谁是借出的人、谁是借到的人。那里藏着一个前提:模型知道每个词排在第几个。可是只看前两篇讲的计算,这个前提并不成立。这一篇讲模型是怎么知道顺序的。

想先看个大概,可以先播放下面的动画讲解,两分钟把整条线过一遍,再往下读正文。

动画讲解位置编码,两分钟看一遍13 步 · 约 2 分钟 · 可以随时暂停、跳步
1 / 13

先看一个前提。第三篇里三个头分清了谁借给谁,靠的是模型知道每个词排在第几个。只看注意力本身的计算,它知道吗?

朗读用的是浏览器自带的语音,不同设备声音不一样;没有语音的浏览器只显示字幕。

一、注意力看不见顺序

注意力看不见顺序:两句话的词一样、顺序不同,「借」算出的分数和输出完全一样

这个性质有个名字,叫置换等变:把输入的词打乱顺序,每个词算出来的结果不变,只是跟着换了位置。对注意力来说,一句话就是一袋词,「小明把书借给了小红」和「小红把书借给了小明」是同一袋。第一篇说 Transformer 能把所有词一起算、不用一个一个来,代价就在这里:一起算,就没有了先后。

二、把座位号写进向量

既然注意力只看向量,办法就是在进注意力之前,把「第几个」写进每个词的向量里。做法是给每个座位准备一个位置向量 p,长度和词向量 x 一样,两个向量按维度逐个相加,x + p 再送进注意力。可以把 x + p 想成一张卡片上写了两行字,一行是词义,一行是座位号。

把座位号写进向量:词向量 x 和位置向量 p 按维度逐个相加,再送进注意力

整篇文章剩下的内容,都是在挑一个合格的 p。它要满足三条:

  1. 每个座位的 p 都不一样。
  2. p 里的数要小,和词向量同一个量级,否则会把词义盖住。
  3. p 只由座位决定,和句子多长无关;并且从第 3 个到第 4 个的「一步」,要和从第 503 个到第 504 个的「一步」长得一样,模型才能学到稳定的「往右一格」。

三、两个行不通的写法

还有一个后果:同一个「猫」,排在第 3 个和排在第 5000 个,向量差了一大截,模型会把它们当成两个不同的词。这个写法违反了第二条。

四、原论文的做法:一排钟

把座位号想成钟面上的时刻,三条就都能满足。时刻的读数永远不大;每个座位对应一个独一无二的时刻;往后一个座位就是走一秒,不管在第 3 个还是第 5000 个座位,「走一秒」都长一样。

所以要加上分针、时针,还有转得更慢的针。原论文把 512 维两两分组,共 256 组,每组是一个钟:第 i 个座位在第 k 个钟上的读数是 sin(i / 10000^(2k/512)) 和 cos(同一个角度),分别占一个维度。第 0 个钟走得最快,每个座位转一大格;越往后的钟走得越慢,最慢的一个要转六万多个座位才转一圈。快钟分得清相邻的座位,慢钟分得清远处的座位,合起来几万个座位里没有两个读数相同。

用一排走速不同的钟记座位号:第 5 个和第 13 个座位在快钟上读数相同,中钟和慢钟把它们分开

动手试一试拖动座位号,看每个钟转到哪
快钟8 格一圈(0.00, 1.00)
中钟32 格一圈(0.00, 1.00)
慢钟128 格一圈(0.00, 1.00)
最慢512 格一圈(0.00, 1.00)
这个座位的位置向量(前 8 个数)

原论文有 256 个钟,最快的 2π ≈ 6.3 格一圈,最慢的约 62832 格一圈;这里只画 4 个,圈数取整方便看。

五、查表:可学习的位置向量

还有一种更省事的做法:准备一张表,第 0 个座位一行 512 个数,第 1 个座位一行,一直到最大长度。这些数一开始随机,和模型一起训练,模型自己学出每个座位该长什么样。BERT 和 GPT-2 用的就是这种,叫可学习的位置向量。

两种做法各有取舍。查表贴合训练数据,但长度写死了;钟面能算出任意座位的读数,但模型训练时没见过很长的座位,真给它几万个词,效果也不好。原论文试过两种,结果几乎一样,最后选了钟面,想的就是它能推到更长的句子。

六、现在的做法:RoPE

前面两种都把位置向量加到词向量 x 上,再算 Q、K、V。2021 年提出的 RoPE(旋转位置编码)换了一个位置放:不碰 x,先照常算出 q 和 k,再按座位旋转它们。

先只看两个维度。一个 2 维的 q,比如 [1, 0],可以画成平面上从原点出发的一支箭头,指向正右方。旋转就是字面意思:把箭头绕原点转一个角度,长度不变,方向变,转 90° 以后它指向正上方,变成 [0, 1]。RoPE 的规则只有一条:第 i 个座位的 q 转 i × θ,第 j 个座位的 k 转 j × θ,θ 是固定角度,座位越靠后,转得越多。

RoPE:按座位旋转 q 和 k。座位 1 和 3、座位 5 和 7 都隔两格,两支箭头的夹角都是 60°,分数一样

动手试一试按座位转箭头,看夹角跟什么有关

q 转

k 转

夹角

分数 cos(夹角) =

θ 取 30° 方便看。真实模型里 512 维分成 256 个平面,每个平面各有自己的 θ,从快到慢。

512 维的做法和钟面一样:两两一组分成 256 个平面,每个平面是一支箭头,各自旋转,每个平面的 θ 不同,第一组转得快,后面越来越慢,近距离和远距离都分得清。用到的 sin 和 cos 就是第四节那排钟的读数,只是用法变了:正弦编码把读数写在卡片上,RoPE 拿读数去转箭头。

RoPE 的好处是相对位置直接进了注意力分数,长度也没有写死。LLaMA、Qwen、DeepSeek 这些大模型都在用它,后来把模型从几千个词扩展到几十万个词的各种办法,大多是在 RoPE 的 θ 上做文章。

检验一下

不加位置编码,把一句话的词序打乱,每个词的注意力输出会怎样?

直接把座位号 i 加到词向量的每一维上,主要问题是什么?

原论文用了很多个走速不同的钟,慢钟是用来做什么的?

RoPE 和「把位置向量加到词向量上」的区别是什么?

想深入可以看

← 全部文章