位置编码与长序列:从 Sinusoidal 到 RoPE
这一篇专门讲位置编码:为什么 Self-Attention 需要位置、经典的正弦位置编码在做什么,以及后面 RoPE 这类方案的直觉。
为什么 Self-Attention 需要位置?
Self-Attention 本身对输入顺序是不敏感的:
- 如果你把句子里的 token 整体乱序,注意力层依然能算出一堆分数;
- 但语言显然是有顺序的,“我爱你”和“你爱我”在含义上不同。
换句话说,仅靠 token embedding,模型知道“这是什么词”,但不知道“它在第几个位置”。
所以需要额外给每个位置加上一段“位置向量”,让模型在注意力计算前就带上顺序信息。
最常见的做法是:
1输入向量 = token embedding + position embedding
即在进入第一层 Transformer Block 之前,把每个 token 对应的位置向量直接加到它的 embedding 上。
正弦位置编码(Sinusoidal)在干什么?
原始 Transformer 论文里用的是一种固定的位置编码,不需要学习参数:
对于位置 $pos$,维度 $i$:
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) $$$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) $$直觉上看,有几个特点:
- 不同维度使用不同频率的正弦/余弦;
- 同一维度上,位置越靠后,波形周期变化越慢;
- 任何一个位置 $\text{pos}$ 的编码,都是一条在不同频率上采样得到的“波形”。
这些性质带来一些好处:
- 位置关系是“连续”的:
- 模型可以从位置编码里推断出相对位移,而不仅仅是“第几号”;
- 在没见过的更长序列上,也能自然地外推。
- 不需要额外参数:
- 对于超大模型,能少一个矩阵就少一个矩阵,尤其是在早期硬件不太富裕的时候。
在工程实现里,通常会预先生成一个足够长的 $PE$ 表(例如支持到 10k 或 100k 长度),然后按实际序列长度切片使用。
可学习的位置编码(Learned Positional Embedding)
另一个常见选择是直接学习一个位置 embedding 表:
- 假设最大序列长度为 $L_{\max}$,模型维度为 $d_{\text{model}}$;
- 定义一个矩阵 $P \in \mathbb{R}^{L_{\max} \times d_{\text{model}}}$ 作为可训练参数;
- 第 $pos$ 个位置的编码就是 $P[pos]$。
好处是:
- 灵活、简单,直接交给训练过程去学;
- 在复杂任务上,有时比固定正弦编码效果更好。
代价是:
- 不太擅长外推到比训练时更长的序列:
- 如果推理时序列长度超过 $L_{\max}$,需要想办法“复用”或外插这一段位置表;
- 否则就会出现“没有对应位置向量”的情况。
很多 BERT 系列模型就是用的 Learned Positional Embedding。
RoPE(Rotary Position Embedding)的直觉
在更现代的大模型里(包括不少 GPT 变体),比较常见的是 RoPE(Rotary Position Embedding)。
它的核心思路是:不再把位置向量简单相加,而是通过旋转的方式,把位置信息“嵌入”到向量的相位里。
非常粗略地说,RoPE 做的是:
- 把向量的每一对维度 $(x_{2i}, x_{2i+1})$ 看成一个复平面上的点;
- 对于位置 $pos$,用一个与 $pos$ 相关的角度 $\theta_{pos}$ 把这个点旋转一圈;
- 这样一来,“相对位置”就会自然地反映在两个向量之间的相位差上。
直觉上的好处:
- 相对位置信息更直接地编码在 Q/K 的内积里;
- 对长序列有更好的扩展性(可以通过控制频率分布来兼顾短/长依赖);
- 不再是“位置向量相加”,而是把位置当作一种变换作用在向量上。
具体公式会稍微复杂一些,但在使用层面,你只需要知道:
- 在 RoPE 情况下,位置编码不再是单独的向量表,而是一个对 Q/K 做旋转变换的操作;
- 这一变换在实现上通常被写成一个单独的函数,接受“原始向量 + 位置索引”,输出“带位置信息的向量”。
长序列时的位置问题
当我们把模型推到很长的上下文(几十万 token)时,位置编码会直接影响:
- 模型在远距离依赖上的表现;
- 数值稳定性;
- 是否能泛化到训练时没见过的长度。
一些常见的实践问题包括:
训练时的最大长度 vs 推理时实际使用长度:
- 如果训练时只看到过最多 2k token,推理时突然用到 32k,需要考虑位置编码如何外推;
- 固定正弦编码在这方面更自然一些,可学习位置表则需要特别处理。
频率分布的选择:
- 对于 RoPE 等方案,频率的设计会影响“近处 vs 远处”依赖的分辨率;
- 有些论文会专门调整频率分布,以更好地适配长上下文任务。
工程上,长序列支持往往不是只改位置编码这么简单,还会一起改:
- Attention 的稀疏模式(局部注意力、滑动窗口等);
- 缓存策略(分块 KV cache);
- 训练时的数据采样方式(多样的长度分布)。
位置编码只是其中一环,但它确实是决定“模型对顺序有没有感觉”的关键一环。