位置编码与长序列:从 Sinusoidal 到 RoPE

这一篇专门讲位置编码:为什么 Self-Attention 需要位置、经典的正弦位置编码在做什么,以及后面 RoPE 这类方案的直觉。

Self-Attention 本身对输入顺序是不敏感的:

  • 如果你把句子里的 token 整体乱序,注意力层依然能算出一堆分数;
  • 但语言显然是有顺序的,“我爱你”和“你爱我”在含义上不同。

换句话说,仅靠 token embedding,模型知道“这是什么词”,但不知道“它在第几个位置”
所以需要额外给每个位置加上一段“位置向量”,让模型在注意力计算前就带上顺序信息。

最常见的做法是:

1输入向量 = token embedding + position embedding

即在进入第一层 Transformer Block 之前,把每个 token 对应的位置向量直接加到它的 embedding 上。

原始 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 长度),然后按实际序列长度切片使用。

另一个常见选择是直接学习一个位置 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。

在更现代的大模型里(包括不少 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);
  • 训练时的数据采样方式(多样的长度分布)。

位置编码只是其中一环,但它确实是决定“模型对顺序有没有感觉”的关键一环。