27届大模型岗面试准备(三):位置编码全景------从绝对编码到 RoPE/ALiBi 的演进与手推
位置编码是大模型面试里一道"看似基础、实则筛人"的题。面试官问"你家模型用的是哪种位置编码,为什么",能答出 RoPE 名字的候选人很多,但能把"为什么旋转等价于相对位置""RoPE 和 ALiBi 在长文本外推上谁更稳"讲清楚的不到两成。这篇文章把主流位置编码从绝对到相对、从 RoPE 到 ALiBi 的完整演进脉络梳一遍,配上对比表和可直接跑的 RoPE 手推代码,目标是让你在面试里能把这道题答成加分项。
一、为什么 Transformer 需要位置编码
Self-Attention 本身是 permutation-invariant(置换不变)的。换句话说,把输入序列打乱顺序送进注意力层,输出的每个 token 向量集合完全不变(只是位置换了),模型根本分不清"猫追狗"和"狗追猫"。这对语言这种强依赖词序的任务是致命的,所以必须额外注入位置信息。
位置编码的核心矛盾在于:既要让模型知道 token 在序列里的绝对位置(第几个词),又要让它感知 token 之间的相对距离(隔了几个词),同时还得在推理时支持比训练更长的序列(外推性)。这三条诉求的权衡,直接决定了每种位置编码的设计取向和适用边界。
下面这张表先给个全景,后面逐个展开。
| 位置编码方案 | 类型 | 是否显式编码相对位置 | 外推能力 | 长序列衰减 | 代表模型 | 主要缺点 | |---|---|---|---|---|---|---| | 正弦绝对编码 | 绝对 | 间接(通过线性组合) | 弱 | 无 | 原始 Transformer | 外推差、依赖线性组合才能近似相对位置 | | 可学习绝对编码 | 绝对 | 否 | 无 | 无 | BERT、GPT-2 | 无法外推、参数量随长度增长 | | 相对位置编码(Shaw) | 相对 | 是 | 中 | 无 | 早期相对位置Transformer | 计算开销大、对长序列仍需截断 | | T5 Relative Bias | 相对 | 是 | 中 | 无 | T5 | bucket 化牺牲精度 | | RoPE(旋转位置编码) | 绝对形式+相对效果 | 是(隐式) | 较强(配合 NTK/YaRN) | 有(高频衰减) | LLaMA、Qwen、Mistral | 长度外推仍需插值扩展 | | ALiBi(线性偏置) | 相对 | 是 | 强 | 有(线性衰减) | BLOOM、MPT | 绝对位置感知弱、短序列略弱于RoPE |
二、绝对位置编码:正弦与可学习
2.1 正弦/余弦编码
原始 Transformer 用的是固定的正弦余弦编码:
PE_{(pos,2i)} = \\sin(pos / 10000\^{2i/d})$$ $$PE_{(pos,2i+1)} = \\cos(pos / 10000\^{2i/d})
其中 pos 是位置,i 是维度索引。这种编码有几个性质:每个位置的编码向量是唯一的;任意固定偏移 k,PE(pos+k) 可以表示为 PE(pos) 的线性函数(这正是它能间接编码相对位置的根源);不同维度对应不同频率,从 2\\pi 到 10000 \\cdot 2\\pi。
但它的外推能力弱。训练时只见过 512 个位置,推理时来了 2048,虽然公式能算出编码,但模型没学过那些频率组合下的注意力模式,效果直接崩。这也是为什么后来的 BERT 和 GPT-2 都转向了可学习编码。
2.2 可学习编码
BERT 和 GPT-2 的做法粗暴直接:初始化一个 [max_len, d_model] 的可训练矩阵,每个位置一个学出来的向量。简单、有效,但有两个硬伤:一是无法外推,超出 max_len 就没有编码可用;二是这个矩阵占参数,序列越长参数越多。BERT 的 512 位置限制很大程度上就是被这个设计锁死的。
面试官常追问:"可学习编码为什么不做插值外推?"答案是:正弦编码有数学结构可以做线性插值或频率缩放,可学习编码是离散的学出来的向量,没有可插值的连续结构,硬插值没有数学保证。
三、相对位置编码:从 Shaw 到 T5
绝对编码的局限催生了相对位置编码。核心思想是:注意力分数 q \\cdot k 里,真正有意义的不是 q 和 k 各自的绝对位置,而是它们的相对距离。
3.1 Shaw 相对位置编码
Shaw 等人在 2018 年提出,把注意力分数计算从 q_i \\cdot k_j 改成引入一个可学习的相对位置向量 a_{ij}\^K:
e_{ij} = \\frac{(q_i)(k_j + a_{ij}\^K)}{\\sqrt{d}}
其中 a_{ij}\^K 只依赖相对距离 i - j,通过查一个可学习的相对位置 embedding 表得到。这样模型直接对"两个 token 隔了多远"建模。问题是计算开销变大,而且对长序列需要截断相对距离(比如只保留 ±128 范围)。
3.2 T5 的 Relative Bias
T5 把相对位置信息以 bias 的形式加到注意力分数上:e_{ij} = q_i \\cdot k_j + b_{i-j}。其中 b_{i-j} 通过 bucket 化(把相对距离分桶)映射到一个可学习的偏置标量。bucket 化的好处是参数少、能覆盖很长的距离,坏处是粒度粗。
相对位置编码解决了"外推时模型没见过绝对位置"的问题,但并没有彻底解决外推------bucket 表的长度还是有限的。真正的外推突破要等 RoPE 和 ALiBi。
四、RoPE:旋转位置编码(重头戏)
RoPE(Rotary Position Embedding)是苏剑林在 2021 年提出的,目前是大模型位置编码的事实标准。LLaMA、Qwen、Mistral、DeepSeek 等几乎清一色用 RoPE。面试官最爱考的就是它。
4.1 RoPE 的核心思想
RoPE 的出发点是:我们希望找到一种位置编码,使得内积 q_m \\cdot k_n 的结果只依赖相对位置 m - n。苏剑林用复数域给出了一个优雅的解:对位置 m 的向量 q_m,乘以一个旋转矩阵 R_m,使得:
\\langle R_m q, R_n k \\rangle = q\^T R_m\^T R_n k = q\^T R_{n-m} k
也就是说,q 旋转 m、k 旋转 n 后做内积,等价于 q 不动、k 旋转 (n-m)。旋转矩阵的正交性保证了 R_m\^T R_n = R_{n-m},这就是 RoPE"用绝对位置的形式实现了相对位置的效果"的数学根源。
4.2 RoPE 的实现形式
实际实现里,RoPE 把 d 维向量两两分组(共 d/2 组),每组的两个维度构成一个二维平面,在第 i 组上旋转角度 \\theta_i = m \\cdot 10000\^{-2i/d}。不同组用不同频率,高频组对应局部细节,低频组对应长程位置。
这就引出一个关键性质:高频分量旋转快,对位置变化敏感,随着相对距离增大,高频分量的内积快速振荡衰减;低频分量旋转慢,变化平缓。综合下来,RoPE 天然带有"近处注意力强、远处注意力弱"的衰减特性,这与自然语言中"近距离 token 更相关"的先验吻合。
4.3 RoPE 的外推问题与扩展
原始 RoPE 训练长度 2K,推理到 4K 时效果明显下降。原因是高频分量在未见过的位置上旋转到"陌生"的角度组合。业界发展出一系列扩展方案:
- Position Interpolation(PI):把推理位置线性压缩到训练范围内(位置 m → m × train_len/target_len),简单粗暴,但高频信息被压缩损失大。
- NTK-aware Scaling:只缩放低频分量、保留高频不变,外推更平滑,被 YaRN 等继承。
- YaRN:在 NTK 基础上对注意力温度做插值调整,目前是 LLaMA 系列做长上下文扩展的主流方案之一。
面试常考追问:"为什么 NTK 比 PI 好?"答:PI 无差别压缩所有频率,高频信号本就变化快,压缩后相邻位置区分度下降严重;NTK 只调低频(低频本就变化慢,缩放后仍能区分大尺度位置),高频保留原样,短距离建模能力不退化。
4.4 手推 RoPE 代码
下面这段代码用 NumPy 实现 RoPE,可以直接跑,建议在面试前手敲一遍理解每一行。
python
import numpy as np
def precompute_freqs_cis(dim: int, max_seq_len: int, theta: float = 10000.0):
"""预计算每个位置、每个频率组的旋转角度 cos/sin。
Args:
dim: 头维度(必须为偶数)
max_seq_len: 最大序列长度
theta: 基础频率基数,默认 10000
Returns:
freqs_cis: shape [max_seq_len, dim//2],复数形式 cos+isin
"""
assert dim % 2 == 0, "RoPE 要求头维度为偶数"
# 频率向量:theta^(-2i/d), i=0,1,...,dim/2-1
freqs = 1.0 / (theta ** (np.arange(0, dim, 2).astype(np.float64) / dim))
# 位置向量 [0,1,...,max_seq_len-1]
t = np.arange(max_seq_len).astype(np.float64)
# 外积得到 [max_seq_len, dim//2] 的角度矩阵
angles = np.outer(t, freqs)
# 转成复数 cos + i*sin,方便后续旋转
freqs_cis = np.cos(angles) + 1j * np.sin(angles)
return freqs_cis
def apply_rope(x: np.ndarray, freqs_cis: np.ndarray):
"""对输入张量施加 RoPE 旋转。
Args:
x: shape [batch, seq_len, dim] 或 [seq_len, dim]
freqs_cis: shape [seq_len, dim//2],对应位置的复数
Returns:
旋转后的张量,shape 同输入
"""
orig_ndim = x.ndim
if orig_ndim == 2:
x = x[None, ...] # 加 batch 维
bsz, seq_len, dim = x.shape
assert dim % 2 == 0
# 把相邻两个维度看作复数的实部和虚部: [b, s, d] -> [b, s, d/2]
x_pairs = x.astype(np.float64).reshape(bsz, seq_len, dim // 2, 2)
x_complex = x_pairs[..., 0] + 1j * x_pairs[..., 1] # [b, s, d/2]
# 取对应位置的旋转因子 [s, d/2] -> broadcast 到 [1, s, d/2]
rot = freqs_cis[:seq_len][None, ...]
# 复数乘法 = 旋转
x_rotated = x_complex * rot
# 复数拆回实部虚部 [b, s, d/2, 2] -> [b, s, d]
out = np.stack([x_rotated.real, x_rotated.imag], axis=-1).reshape(bsz, seq_len, dim)
return out[0] if orig_ndim == 2 else out
# ---- 验证:RoPE 注入相对位置 ----
if __name__ == "__main__":
dim, seq_len = 8, 16
freqs_cis = precompute_freqs_cis(dim, seq_len)
# 两个随机向量 q(在位置2) 和 k(在位置7),相对距离=5
q = np.random.randn(seq_len, dim)
k = np.random.randn(seq_len, dim)
q_rot = apply_rope(q, freqs_cis)
k_rot = apply_rope(k, freqs_cis)
# 注意力分数:q_2 · k_7 (相对距离5)
score = np.dot(q_rot[2], k_rot[7])
# 对比:q 在位置0、k 在位置5(相对距离同为5),分数应接近
score2 = np.dot(q_rot[0], k_rot[5])
print(f"相对距离5 (pos 2->7): {score:.4f}")
print(f"相对距离5 (pos 0->5): {score2:.4f}")
print(f"差值: {abs(score - score2):.6f} (应很小,说明RoPE只依赖相对位置)")
# 衰减性:固定 q_0,看与 k 的分数随距离的变化
print("\n固定 q_0,注意力分数随距离衰减:")
for dist in range(0, seq_len, 2):
print(f" 距离 {dist:2d}: {np.dot(q_rot[0], k_rot[dist]):+.4f}")
跑完你会看到两个现象:相对距离相同的两组分数非常接近(验证"RoPE 只依赖相对位置");固定一个 q,与不同距离 k 的分数总体呈振荡衰减趋势(验证"高频衰减特性")。面试时把这两点说出来,比背公式有说服力得多。
五、ALiBi:线性偏置的外推利器
ALiBi(Attention with Linear Biases)是 Press 等人在 2021 年提出的,思路比 RoPE 更直接:不改 q、k 向量,而是在注意力分数上直接加一个与相对距离成正比的负偏置。
e_{ij} = q_i \\cdot k_j - m \\cdot \|i - j\|
其中 m 是每个注意力头独有的斜率,按几何级数预设(如 1/2, 1/4, 1/8, ...)。距离越远,减得越多,注意力越弱。不同头用不同斜率,有的头关注近处(大斜率),有的头看更远(小斜率)。
ALiBi 的最大优势是外推能力极强。训练长度 1024,推理到 2048、4096 效果几乎不降,因为线性偏置对任意距离都有定义且行为可预测。BLOOM、MPT 等模型用它。
它的劣势在于:没有显式的绝对位置感知(只编码相对距离),对"序列开头""序列末尾"这类需要绝对位置感知的任务略弱;短序列上精度通常略低于 RoPE。所以目前主流开源模型还是 RoPE 占多。
六、RoPE vs ALiBi 深度对比
这是面试高频对比题,给你一张更细的表:
| 对比维度 | RoPE | ALiBi | |---|---|---| | 编码形式 | 旋转 q/k 向量(绝对形式) | 注意力分数加线性偏置(纯相对) | | 数学本质 | 复数旋转,R_m\^T R_n = R_{n-m} | e_{ij} = q_i k_j - m\|i-j\| | | 相对位置 | 隐式实现 | 显式实现 | | 长序列衰减 | 高频振荡衰减(非单调) | 严格线性单调衰减 | | 外推能力 | 较强,需配合 PI/NTK/YaRN | 极强,原生支持 | | 绝对位置感知 | 有(旋转角度含绝对位置) | 无 | | 参数量 | 零额外参数 | 每头一个斜率(可预设,不训练) | | 短序列精度 | 略优 | 略弱 | | 工程实现复杂度 | 中(需预计算 cos/sin) | 低(加一个 bias) | | 主流采用 | LLaMA/Qwen/Mistral/DeepSeek | BLOOM/MPT |
面试答题框架建议:先说共同点(都是相对位置编码思路、都零额外参数或极少参数、都支持一定外推),再说差异(RoPE 形式是绝对、效果是相对且保留绝对感知;ALiBi 纯相对、外推更强但绝对感知弱),最后给选型结论(短中序列选 RoPE 精度略好,超长上下文且需强外推选 ALiBi 或 RoPE+YaRN)。
七、面试高频追问与答题模板
追问1:RoPE 为什么能等价于相对位置编码? 答:因为旋转矩阵正交,R_m\^T R_n = R_{n-m},内积 \\langle R_m q, R_n k \\rangle = q\^T R_{n-m} k 只依赖相对位置 n-m。用绝对位置的旋转操作,得到只含相对位置的内积结果。
追问2:RoPE 外推到训练长度2倍时为什么会崩? 答:高频分量在训练范围内已旋转若干圈,外推后角度组合超出模型见过的分布,注意力分数剧烈变化。低频分量变化慢问题不大。所以 NTK-aware 只缩放低频、不动高频。
追问3:ALiBi 的斜率为什么要用几何级数而非统一值? 答:不同头负责不同尺度------大斜率头强衰减只看近处,小斜率头弱衰减能看远处。几何级数让头的"视野"覆盖多个尺度,类似多分辨率分析。统一斜率会让所有头视野重合,浪费头数。
追问4:你的项目里如果要做 32K 上下文,位置编码怎么选? 答:优先 RoPE + YaRN 扩展。理由:短序列精度好、生态成熟(vLLM/SGLang 都原生支持)、YaRN 外推到 4-8 倍训练长度实测稳定。若场景对超长外推(如 128K)和成本极敏感,可评估 ALiBi,但需接受短序列略掉点。
八、位置编码选择的工程决策清单
最后给一份工程选型清单,面试和实战都管用:
- 序列长度是否固定且较短(≤2K):可学习绝对编码或 RoPE 都行,前者更简单。
- 需要外推到训练长度的 2-4 倍:RoPE + PI/NTK,或直接 ALiBi。
- 需要超长上下文(32K+):RoPE + YaRN 是目前最稳的方案,配合 vLLM/SGLang。
- 对绝对位置敏感的任务(如代码补全的缩进):优先 RoPE,它保留绝对位置感知。
- 资源受限需极致简化:ALiBi,实现最简,无预计算表。
- 多语言/多模态:RoPE 兼容性好,VLM 里视觉 token 也能统一编号。
位置编码看似一个小模块,但它直接决定了模型能处理多长的上下文、外推稳不稳。理解了这条演进线,面试时被追问就不会只停留在"我们用的 RoPE"这一句话。下一篇我们继续深挖注意力变体(MHA/MQA/GQA/FlashAttention),那是另一道面试必考题。