小白也能看懂的位置编码:大模型怎么知道字的顺序
系列文章: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胜出?
-
天然编码相对位置:旋转后做内积,结果只和相对距离有关——"主语和谓语隔了3个词"比"主语在第17个位置"更有意义。
-
长度外推有潜力:旋转操作对位置值没有上限,理论上可推广到任意长度。
-
与注意力深度耦合:位置信息直接嵌入注意力分数,而非简单叠加在词向量上。
-
计算几乎免费:只需逐元素乘法和加法,不引入额外参数。
极简代码实现
以下是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是当前大模型位置编码的基础方案,理解它的旋转思想,有助于你更好地理解模型架构、微调策略和推理优化。
下一篇我们聊聊大模型的微调技术——如何用很少的资源让大模型学会新技能,敬请期待。
更多推荐


所有评论(0)