模型怎么知道词序?
注意力机制天生看不见顺序。位置必须单独注入,而注入方式决定了上下文能拉多长。
关于自注意力,有一个事实很容易被忽略,一旦看见又再也无法忽视:它根本没有顺序的概念。
注意力用点积计算 token 两两之间的分数。把输入序列打乱,每一对算出的分数完全一样。对机制本身来说,dog bites man 和 man bites dog 是同一袋 token。
对语言来说这是致命的。所以位置必须单独提供。
为什么最直觉的做法行不通
最朴素的做法是把序号——1、2、3——直接拼到每个 embedding 上。
它有两个地方行不通。数值量级会无界增长,于是位置 5000 会在数值上压过每一个语义维度。而且模型没有理由去泛化:数字 5000 本身没有任何东西能告诉它,5000 和 4999 的关系就像 3 和 2 的关系。
真正需要的是一种相对距离可还原的表示,因为语言在乎的正是这个。「往回数两个词的那个形容词」很要紧;「绝对序号 847 处的那个形容词」几乎从来不要紧。
正弦编码
最早的 transformer 论文用的是一套固定的正弦、余弦模式,覆盖许多不同频率。每个位置得到一个唯一的向量,直接加到它的 embedding 上。
这个构造有一个讨喜的性质:由于正弦和余弦的复合方式,位置 p+k 的编码是位置 p 编码的线性变换,而且这个变换只取决于 k。相对偏移因此可以线性还原——正是当初需要的性质。
它还不需要任何参数,长度上也不设限。它管用,多数讲解也讲到这里为止,但它已经不是大多数模型在用的方案了。
学习式绝对位置
另一种做法:把每个位置当成拥有自己的可学习 embedding,像其他参数一样训练。
它很简单,在训练覆盖的范围内表现也好。致命弱点是完全无法外推。一个训练到位置 512 的模型,没有位置 513 的 embedding——这个参数根本不存在。上下文长度于是变成一道硬性的架构天花板。
旋转位置编码
大多数当前模型用的是 RoPE,即旋转位置编码(rotary position embedding),思路很优雅。
它不额外加一个位置向量,而是把每个 token 的 query 和 key 向量按与其位置成正比的角度做旋转。把维度两两配对,看作一个 2D 平面,旋转量由位置序号决定。
回报直接来自三角恒等式。位置 m 的 query 遇到位置 n 的 key 时,它们的点积只取决于 m − n,也就是相对距离。绝对位置被消掉了。位置信息烤进了注意力计算本身,而不是加在输入上,机制天然看到的就是相对距离。
这对长上下文影响极大。因为 RoPE 编码的是相对位置而非绝对位置,它的行为可以被拉伸。把旋转频率做缩放,就能让一个在某个上下文长度上训练的模型,只用少量额外调优就工作在更长的长度上——很多模型就是这样在没有从头重训的情况下发布了超大上下文窗口。
另外两种做法值得点名。ALiBi 完全不用位置向量,改为给注意力分数加一个与距离成正比的惩罚项,让每个头都偏向附近的 token;它外推表现好,而且非常便宜。相对位置编码(relative position embeddings)为每个相对偏移学习一个偏置,有效但更重。
为什么它决定上下文长度
位置编码是上下文能有多长的两个真正约束之一。另一个是注意力的平方级开销。
学习式绝对编码是一道硬墙。正弦编码和 RoPE 原则上没有墙,但一个主要在短序列上训练的模型,到了长序列上依然会退化——距离 10 万处的那些位置模式,压根没练过。
一个熟悉的抱怨背后就是这个机制:模型宣称有巨大的上下文窗口,却似乎对中间部分关注得很差。那些位置存在,也能被表示;模型有没有学会在那样的距离上好用它们,是另一个问题,取决于训练数据,而不是架构。
要点回顾
- 注意力天生看不见顺序,位置必须单独注入
- 朴素序号行不通,因为数值量级会增长,相对距离又还原不出来
- 正弦编码让相对偏移可以线性还原,而且不需要参数
- 学习式绝对编码无法外推到训练长度之外,是一道硬天花板
- RoPE 旋转 query 和 key,让注意力分数只取决于相对距离,这才让上下文扩展变得可行
- 宣称的长上下文不保证长距离上用得也好,那取决于训练
下一篇: 层、残差与深度