主题
位置编码与长上下文
一句话:注意力本身对顺序无感,位置编码把“第几个 token”注入模型;RoPE 通过旋转 Q/K 让注意力分数只依赖相对距离。长上下文难,不只是窗口写多大,而是训练长度、位置外推、平方级算力、KV 显存和“真的用得上远处信息”五件事同时成立。
为什么需要
把一句话的 token 打乱,注意力算出来的集合完全一样:每个 token 的 Query 还是和所有 Key 比较,只是顺序变了。但“狗咬人”和“人咬狗”含义相反,模型必须知道谁在前、谁在后、隔多远。
位置编码要回答三个问题:
- 绝对位置:这是第几个 token;
- 相对距离:两个 token 隔了多远;
- 外推:训练时只见过 4K 长度,推理时能不能处理 32K。
不同方案在这三件事上各有取舍。
核心机制
一、正弦位置编码
原始 Transformer 用固定公式给每个位置生成一个向量,再和词向量相加:
text
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))不同维度对应不同波长,波长从 2π 到 10000 · 2π 几何增长。短波负责区分邻近位置,长波负责区分远处位置。它不需要训练,理论上可以算到任意长度;但模型只见过训练长度内的位置组合,超出后效果仍然会退化。
二、可学习的绝对位置
BERT、GPT-2 直接给每个位置一个可训练向量,查表加到词向量上。实现简单,效果稳定,但最大长度在训练时就固定了:位置表只有 max_position_embeddings 行,超出就没有对应的向量。这类模型不能靠“算公式”外推。
三、相对位置与 RoPE
相对位置方案不再问“你在第几位”,而是问“我们隔多远”。T5 把距离分桶,给每个桶一个可学习的偏置,加在注意力分数上;ALiBi 用与距离成正比的线性惩罚,越远分数越低。
现在主流大模型用 RoPE(Rotary Position Embedding)。它不往词向量上加位置,而是把 Q 和 K 的每一对维度看成二维平面上的向量,按位置旋转一个角度:
text
q_m = R(m·θ) · q k_n = R(n·θ) · k
q_m · k_n = q · R((n−m)·θ) · k旋转后的点积只依赖相对位置 n−m,绝对位置被消掉了。这就是 RoPE 能同时表达“绝对位置”和“相对距离”的原因:每个 token 有自己的旋转角度,但两个 token 的匹配分数只由角度差决定。
不同维度用不同频率 θ_i = base^(-2i/d)。base 越大,长波越长,能区分的距离越远。原始 RoPE 和 Llama 2 用 base = 10000,Llama 3 把 base 提到 500000 以支持更长上下文。调大 base、位置插值(把 32K 的位置压回训练过的 4K 范围)、NTK-aware 缩放,都是在这个频率上做文章。
四、长上下文的五道关
“支持 128K”只是第一道关,实际能不能用要看五件事:
| 关卡 | 问题 | 表现 |
|---|---|---|
| 训练长度 | 预训练见过多长 | 超出训练分布,位置编码先失效 |
| 位置外推 | 位置方案能否推到更长 | 绝对位置表直接没有对应行 |
| 注意力算力 | O(n²) 的分数矩阵 | 长度翻倍,计算约翻四倍 |
| KV 显存 | 每 token 的 K/V 线性增长 | 并发被显存卡死,见 06 |
| 有效利用 | 模型是否真的用上远处信息 | “中间丢失”,关键信息被稀释 |
第五关最容易被忽略。检索测试(把一句关键信息藏在长文里,问模型能不能找到)只能证明“能找到”,不能证明模型能在长文上做多跳推理、聚合统计或跨段对照。公开研究里常见的现象是:关键信息放在开头或结尾时表现好,放在中间时明显下降;这就是所谓的“lost in the middle”。标称窗口是产品参数,有效窗口要在自己的任务上测。
五、注意力稀释
softmax 的权重和恒为 1。上下文从 4K 涨到 128K,参与竞争的位置多了 32 倍,平均每个位置分到的权重被摊薄;如果关键信息只有一两句,它就要和大量无关 token 争夺同一份注意力预算。这不是 softmax 的 bug,而是固定预算下的必然结果,也是“模型看见了,但没用上”的机制解释。
稀释与 lost in the middle 是两层:稀释是机制——权重被摊薄、相关信号的信噪比下降;中间丢失是现象——关键信息落在中间时更容易被忽略。两者相关但不重合:开头和结尾同样参与竞争,只是训练分布和位置先验让它们更占优势。缓解手段是检索/重排、把关键约束放两头、分块处理、显式引用;扩长训练能改善,但不能消除预算约束。
图中把 RoPE 的作用拆成两步:先按位置旋转,再在点积里抵消绝对位置、留下相对距离。频率设计决定了模型能分辨多远的距离;调大 base 只是把低频波长拉长,并不会自动让模型学会使用更远的上下文,后者仍然依赖训练数据和任务分布。
最小上手
用 numpy 验证 RoPE 的核心性质:相同的相对距离,在不同绝对位置上得到相同的注意力分数。
python
import numpy as np
def rope(x, pos, base=10000.0):
d = x.shape[-1]
half = d // 2
theta = base ** (-np.arange(half) / half)
ang = pos * theta
cos, sin = np.cos(ang), np.sin(ang)
x1, x2 = x[..., :half], x[..., half:]
return np.concatenate([x1 * cos - x2 * sin, x1 * sin + x2 * cos], axis=-1)
rng = np.random.default_rng(0)
q, k = rng.normal(size=8), rng.normal(size=8)
def score(m, n, base=10000.0):
return rope(q, m, base) @ rope(k, n, base)
print(round(score(10, 4), 6), round(score(110, 104), 6)) # 相对距离都是 6
print(round(score(10, 9), 6), round(score(110, 109), 6)) # 相对距离都是 1
print(round(score(10, 4), 6), round(score(10, 9), 6)) # 相对距离不同
print(round(score(10, 4, 500000.0), 6)) # 换 base输出:
text
-1.063905 -1.063905
-2.514775 -2.514775
-1.063905 -2.514775
-1.22029前两行证明相对位置不变性:位置整体平移 100,分数不变。第三行说明距离不同分数不同;第四行说明换 base 会改变同一对位置的分数,这就是长上下文扩展会改变模型行为的原因——它不是无损的“把窗口拉长”。
生产动作
- 按任务测有效窗口。至少覆盖:关键信息在开头/中间/结尾、多跳检索、跨段聚合、代码仓库级引用;只做“针在草堆里”会高估。
- 关键约束放两头。系统提示、输出格式、禁止事项放在开头和结尾,长材料放中间;不要指望模型对中间段落给予同等注意力。
- 长上下文按需开。长度增加带来平方级算力和线性 KV 显存,能靠检索、摘要、分块解决的,不要整段塞进去。
- 超出训练长度要验证。用位置插值、调 base 或改用支持长上下文的模型,都要在目标任务上回归;不要只看厂商标称值。
- 序列打包要重置位置。把多条短样本拼进一个训练序列时,position id 和 attention mask 必须按样本重置,否则第二条样本会“看见”第一条,训练和推理行为不一致。
边界
- 标称窗口不等于有效窗口。128K 是能输入的长度,不是能同等利用的长度;中间信息丢失、长程推理下降都很常见。
- 更长不一定更好。无关内容会稀释注意力、增加延迟和成本;对很多任务,检索比长窗口更便宜也更准。
- RoPE 外推不是免费的。调 base、插值、NTK 缩放都会改变频率分布,可能损失短距离分辨率,需要重新校准。
- 位置编码不产生推理能力。它只提供顺序和距离信号;多跳、聚合、因果判断仍然依赖训练数据和任务结构。
- 不同模型的位置方案不可直接比较。绝对位置、RoPE、ALiBi 的行为不同,token 数与窗口的关系也不同。
讲清楚
注意力对顺序无感,位置编码负责注入“谁在前、隔多远”。正弦编码固定可算但受训练长度限制,可学习绝对位置简单但不能外推,相对位置和 RoPE 把重点放在距离上;RoPE 通过旋转 Q/K,让点积只依赖相对位置,调大 base 或做位置插值可以扩展窗口,但会改变频率分布。
长上下文是五件事同时成立:训练见过、位置能外推、算力付得起、KV 显存放得下、模型真的用得上。前四件是工程账,第五件只能在业务任务上测;标称 128K 不等于有效 128K。
适用:采用 RoPE/ALiBi/绝对位置的主流模型;窗口与 base 以模型配置为准 | 初稿 2026-10