系列文章:AI大模型知识体系 | 第六篇

引言:一句话引发的"惨案"

"我喜欢你"和"你喜欢我",六个字一模一样,只是顺序不同,意思却天差地别——一个是在表白,一个是在发好人卡。

但你可能不知道,Transformer架构本身是完全分不清这两个句子的。在它的"眼里",这两句话不过是同一堆词的某种"摆法"而已——就像把四个字扔进袋子摇一摇再倒出来,Transformer觉得什么都没变。

为了让模型感知顺序,研究者发明了位置编码(Positional Encoding)——给每个位置上的词打上一个独特的"位置标记"。本文带你从零理解位置编码的演进,讲清楚每个方案"为什么"和"怎么想的"。


一、为什么Transformer天生不懂顺序?

要理解这个问题,得先看注意力机制在干什么。

注意力计算的核心是:拿每个词的Query去和所有词的Key做点积,算出"谁该关注谁"。这个过程本质上是一个集合运算——只关心"谁和谁关系大",完全不关心"谁在前谁在后"。

打个比方:想象你在聚会上,每个人举着名片(Query)看别人的名片(Key)。你决定和谁聊天,完全取决于名片内容,跟这人站在门口还是角落里毫无关系。

这就是所谓的排列不变性:你把输入序列的顺序随便打乱,注意力输出的结果也只是跟着打乱,信息本身没有任何变化。模型对"位置"这件事,完全是"盲人"状态。

所以,我们必须主动把位置信息"塞"进去,告诉模型"你在第几个位置"。这就是位置编码的使命。


二、最早的方案:给每个位置编一个"指纹"

2017年,Transformer的原始论文(Attention Is All You Need)提出了Sinusoidal位置编码,思路非常直觉:给每个位置生成一个独特的向量,当作这个位置的"指纹",然后加到词的向量上去。

具体做法是用正弦(sin)和余弦(cos)函数,以不同的频率来生成指纹向量。你可以这样理解:

想象手腕上戴着一排手环,每个手环以不同的速度闪烁。第一个1秒闪一次,第二个2秒闪一次,第三个4秒闪一次……任意时刻,所有手环的亮灭组合就是独一无二的——足以识别"现在是第几秒"。

位置编码同理。不同维度用不同频率的正弦波,低频变化慢(捕捉大范围位置),高频变化快(捕捉精细位置),拼在一起就得到了每个位置的独特向量。

这个方案有一个优雅的性质:任意两个位置之间的"位置差"可以通过一个固定的线性变换表达,模型有机会学到"往前走3个词"这样的相对位置概念。但Sinusoidal编码是写死的数学函数,模型不能自己调整,实际效果并非最优。


三、可学习的位置编码:让模型自己学

BERT用了一种更"暴力"的方案:不设计什么函数了,直接初始化一个大的查找表,让模型自己去学。

具体来说,创建一个形状为 (最大长度, 向量维度) 的矩阵,位置0对应第0行,位置1对应第1行,每一行都是可训练参数,随模型一起通过反向传播更新。

这就像给模型一本"位置字典",但字典里每个位置该写什么,由模型在训练过程中自己摸索出来。

优点:简单粗暴,表达自由度高,模型想学成什么样就学成什么样。

缺点:致命问题是长度泛化性极差。训练时如果最大长度设为512,那模型只见过位置0到511的"字典条目"。推理时你给它一篇2000字的文章,位置512之后完全没有位置信息,模型直接"迷路"。这也是BERT只能处理短文本(512个token)的重要原因之一。

GPT-2也用了类似的可学习位置编码方案,同样受到长度限制的困扰。


四、RoPE:当前主流——旋转位置编码

从2023年开始,几乎所有主流大模型(LLaMA、Qwen、Mistral……)都转向了RoPE(Rotary Position Embedding,旋转位置编码)。这是由国内研究者苏剑林在2021年提出的,设计之精妙令人拍案叫绝。

核心思想:把位置"旋转"进去

之前的方案都是把位置编码"加"到词向量上。RoPE换了思路:不改变词向量的大小,而是把它"旋转"一个角度,旋转角度由位置决定。

想象一面墙上挂满了时钟,每个时钟代表一个词。第1个位置的词,时钟指针指向1点钟方向;第2个位置的词,指向2点钟方向……以此类推。每个词的意思(指针的长度)没变,但指向的方向(角度)携带了位置信息。

当模型计算两个词之间的注意力时,它实际上是在比较两个"时钟指针"的夹角。第3个位置的词和第7个位置的词,指针夹角就是4个"小时"——模型天然就知道了它们的相对距离是4。

这就是RoPE最核心的优点:不需要额外计算,两个词的内积结果自动包含了它们的相对位置信息。 这比Sinusoidal和可学习编码都优雅——它不是把位置信息"贴"在词向量上,而是把位置关系"编织"进了注意力计算本身。

为什么RoPE胜出?

  1. 天然编码相对位置:旋转后做内积,结果只和相对距离有关——"主语和谓语隔了3个词"比"主语在第17个位置"更有意义。

  2. 长度外推有潜力:旋转操作对位置值没有上限,理论上可推广到任意长度。

  3. 与注意力深度耦合:位置信息直接嵌入注意力分数,而非简单叠加在词向量上。

  4. 计算几乎免费:只需逐元素乘法和加法,不引入额外参数。

极简代码实现

以下是RoPE核心逻辑的极简PyTorch实现:

import torch

def apply_rope(q, k, freqs_cis):
    """对query和key施加旋转位置编码"""
    # 将q和k的最后一维拆成两半,模拟二维旋转
    q1, q2 = q.chunk(2, dim=-1)
    k1, k2 = k.chunk(2, dim=-1)
    # freqs_cis包含每个位置的cos和sin(预计算好的复数形式)
    cos, sin = freqs_cis.cos(), freqs_cis.sin()
    # 旋转公式:(q1*cos - q2*sin, q1*sin + q2*cos)
    q_rot = torch.cat([q1*cos - q2*sin, q1*sin + q2*cos], dim=-1)
    k_rot = torch.cat([k1*cos - k2*sin, k1*sin + k2*cos], dim=-1)
    return q_rot, k_rot

核心就这么几行:把向量对半拆,用sin和cos做二维旋转,再拼回来。实际工程中会用更高效的实现(比如LLaMA中的rotate_half写法),但数学本质完全一样。


五、长文本扩展:训练2048,推理10000怎么办?

RoPE理论上支持任意长度,但实际中模型只在训练时见过特定长度(如2048)。推理时输入10000个token,高频维度的旋转角度会进入模型从未见过的区域,导致注意力分数"发疯",性能急剧下降。

这就像一个人只在400米跑道上训练过,突然让他在10公里的马路上跑,他可能会迷路。研究者提出了几个解决方案:

  • 位置插值(PI):把超出长度的位置"压缩"映射回训练范围,简单粗暴但会损失精度。

  • NTK-aware Scaling:对不同频率维度做不同程度的缩放——低频拉伸覆盖更大范围,高频保持不变保留精度。

  • YaRN:目前效果最好的方案之一,综合温度缩放、NTK插值等策略,可将上下文窗口从4K扩展到128K。

这些方案的核心思路都是:在"扩展长度范围"和"保持位置精度"之间找平衡。


六、一张表对比各种位置编码

方案

类型

需要训练?

长度外推能力

代表模型

Sinusoidal

固定绝对编码

有限

原始Transformer

可学习编码

可学习绝对编码

差(严格受限)

BERT, GPT-2

ALiBi

固定相对偏置

较好

BLOOM

RoPE

旋转相对编码

中(可扩展)

LLaMA, Qwen, Mistral

RoPE + YaRN

旋转+综合扩展

少量微调

很好

Qwen2.5等长文本模型

可以清晰地看到一条演进路线:从"写死的绝对编码"到"可学习的绝对编码",再到"天然的相对编码",最终RoPE及其扩展方案成为当下大模型的事实标准。


总结

位置编码虽然是Transformer中的"小模块",但解决的是模型理解序列的根本性问题。回顾演进过程:

  • Sinusoidal用数学函数给每个位置生成"指纹",优雅但表达力有限;

  • 可学习编码让模型自己学位置,灵活但受限于训练长度;

  • RoPE通过旋转设计让位置信息天然融入注意力计算,成为当前最优解;

  • 长文本扩展(NTK Scaling、YaRN等)解决了RoPE的外推短板。

对开发者来说,RoPE是当前大模型位置编码的基础方案,理解它的旋转思想,有助于你更好地理解模型架构、微调策略和推理优化。

下一篇我们聊聊大模型的微调技术——如何用很少的资源让大模型学会新技能,敬请期待。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐