大模型的位置编码干什么用的?sin/cos、RoPE、ALiBi 有什么区别?
Transformer 很强但它有一个天然短板Self-Attention 只擅长计算 token 之间的相关性并不自带“第几个词在前、第几个词在后”的概念。位置编码就是为了解决这个问题。1. Transformer 为什么需要位置编码如果不给模型位置信号Transformer 看到的更像是一袋词而不是一句有顺序的话。例如“我爱北京”和“北京爱我”三个 token 基本相同但语义完全不同。人一眼就能看出顺序差异是因为人知道“谁在前、谁在后”。Self-Attention 只计算 token 之间的关系如果没有额外位置输入它很难稳定获得这种顺序信息。提示位置编码不是为了让模型背第几个 token而是让模型在计算 Attention 时知道 token 之间的先后和距离。2. sin/cos 绝对位置编码给每个位置发“身份证”原始 Transformer 使用的是 sin/cos 位置编码。它的做法很直接用多组不同频率的正弦和余弦函数为每个位置生成一个固定向量然后把这个位置向量加到 token embedding 上。可以把它理解成位置身份证第 0 个位置有一张身份证第 1 个位置也有一张身份证。模型输入时拿到的是“词向量 位置身份证”然后自己学习如何利用这些位置信息。它的优点是简单、零参数、容易实现缺点是绝对位置味道比较重。模型想要知道两个词相隔多远需要自己从两个“位置身份证”里推断出来。短上下文场景还能接受到了长上下文就容易暴露问题。importtorchimportmath class SinusoidalPositionEmbedding(torch.nn.Module): def __init__(self, max_len: int, dim: int): super().__init__()positiontorch.arange(max_len).unsqueeze(1)div_termtorch.exp(torch.arange(0, dim,2)*(-math.log(10000.0)/ dim))petorch.zeros(max_len, dim)pe[:,0::2]torch.sin(position * div_term)pe[:,1::2]torch.cos(position * div_term)self.register_buffer(pe, pe)def forward(self, x):# x: [batch, seq_len, dim]seq_lenx.size(1)returnx self.pe[:seq_len]3. 绝对位置和相对位置差别在哪里绝对位置像座位号第 1 个、第 2 个、第 3 个。相对位置像距离关系这个词在那个词前面 2 个位置这个词离那个词很近。语言理解更关心相对关系。比如主语和动词之间隔了几个词、修饰语离中心词有多近这些通常比“它在整句话第几个位置”更有价值。后来的很多位置编码改进本质都是想把“相对距离”更直接地放进 Attention 计算里。RoPE 和 ALiBi 就是两个代表。4. RoPE把位置变成 Q/K 的旋转角度RoPE 的思路非常巧不再把位置向量加到 token embedding 上而是在 Attention 里对 Q 和 K 做旋转。每个位置对应一个旋转角度。位置越靠后旋转得越多。这样两个 token 的 Q/K 做点积时点积结果会自然包含它们之间的相对距离。RoPE 受欢迎的原因有几个不增加参数不改变向量长度和 KV Cache、FlashAttention、MHA/MQA/GQA 等推理优化兼容对长上下文也更友好。def rotate_half(x): x1x[..., ::2]x2x[...,1::2]returntorch.stack((-x2, x1),dim-1).flatten(-2)def apply_rope(q, k, cos, sin):# q, k: [batch, heads, seq_len, head_dim]# cos, sin: [seq_len, head_dim]coscos[None, None, :, :]sinsin[None, None, :, :]q_embed(q * cos)(rotate_half(q)* sin)k_embed(k * cos)(rotate_half(k)* sin)returnq_embed, k_embed5. ALiBi直接给远距离注意力扣分ALiBi 的做法更简单不改 embedding也不旋转 Q/K而是在 Attention 分数进入 softmax 之前加一个距离惩罚。两个 token 离得越远分数被扣得越多。它像是在告诉模型近处信息默认更重要远处信息要有足够理由才值得关注。不同 attention head 可以使用不同斜率有的关注近邻有的保留更远的视野。def build_alibi_bias(seq_len: int, slope: float,deviceNone): positionstorch.arange(seq_len,devicedevice)distancepositions[None, :]- positions[:, None]distancedistance.abs()# 距离越远偏置越负bias-slope* distancereturnbias# attention_scores: [batch, heads, seq_len, seq_len]# attention_scores attention_scores alibi_bias6. 长上下文为什么离不开位置编码现在的大模型动不动就是 32K、128K 甚至更长上下文。这里真正难的不是把输入长度配置调大而是模型能不能在很长文本里保持稳定的位置感。如果训练时只见过 2K推理时突然塞进 32K绝对位置编码很容易遇到训练分布之外的位置。RoPE 虽然更稳但直接外推也不是万能的所以才有 NTK Scaling、Position Interpolation、YaRN 等扩展技巧。它们的核心都和调整 RoPE 频率、压缩或重映射位置有关。提示长上下文上线不能只看最大长度参数还要评测长文检索、跨段引用、多跳推理、开头信息召回和结尾信息召回。7. 几种方案放在一起怎么选如果是现代 Decoder-only 大模型RoPE 基本是默认优先项如果是实验型长上下文或强调极简实现可以考虑 ALiBi如果是传统 Encoder 模型或分类任务学习式绝对位置、sin/cos、相对位置编码仍然有价值。8. 面试和工程里最容易被追问的点8.1 为什么不用“拼接”位置向量拼接会让维度变大后续矩阵计算也要跟着变重。原始 Transformer 选择“相加”是因为位置向量和词向量维度相同工程上更省模型也有能力从叠加后的向量里学习到两类信息。8.2 RoPE 是绝对位置还是相对位置RoPE 计算时使用的是绝对位置角度但两个旋转后的 Q/K 做点积时会自然表现出相对位置关系。所以更准确的说法是RoPE 用绝对位置构造旋转但在 Attention 里体现相对距离。8.3 ALiBi 为什么没有成为绝对主流ALiBi 外推简单但它的距离惩罚比较强会天然偏向近处 token。对于需要复杂长距离依赖的任务表达力不如 RoPE 灵活。8.4 长上下文扩展是不是改一个 rope_scaling 就行不是。配置只是第一步真正决定能不能上线的是评测。尤其是 RAG、法律合同、代码仓库、长文总结这类任务要看模型是否能稳定找到远处证据而不是只看能不能塞进去。9. 总结位置编码解决的是 Transformer 的顺序感问题。sin/cos 给每个位置一张固定身份证简单但长上下文外推有限相对位置编码把距离关系直接放进计算思路更接近语言理解RoPE 通过旋转 Q/K把相对距离自然写进点积兼顾表达力、外推和工程兼容性ALiBi 用距离惩罚实现简单外推但表达力相对弱。今天做大模型尤其是 Decoder-only 模型RoPE 已经是最常见选择。但无论用哪种方案只要涉及长上下文最终都要回到真实任务评测。