ALiBi:让大语言模型"免训练"外推到更长序列的位置编码方法
一、引言:为什么我们需要 ALiBi?
1.1 位置编码的困境
Transformer 的自注意力机制本身是排列不变 的------它不知道 token 的顺序。因此我们需要某种方式告诉模型"谁在前、谁在后",这就是位置编码(Positional Encoding)。
主流的位置编码方案有:
| 方案 | 代表模型 | 核心思路 |
|---|---|---|
| 正弦位置编码 | 原始 Transformer | 用不同频率的 sin/cos 函数 |
| 可学习位置编码 | GPT-2, BERT | 每个位置学一个向量 |
| 旋转位置编码 (RoPE) | LLaMA, ChatGLM | 在 Q、K 上施加旋转 |
| ALiBi | BLOOM, MPT | 直接在注意力分数上加偏置 |
前三种方案都面临一个共同问题:长度外推(Length Extrapolation)。
1.2 什么是长度外推问题?
假设模型训练时最长序列是 1024 个 token。推理时如果输入 4096 个 token 会怎样?
- 可学习位置编码:位置 1025~4096 的嵌入向量根本不存在,直接崩溃
- 正弦位置编码:虽然可以生成任意位置的编码,但效果严重下降
- RoPE:未经适配时,超出训练长度后性能也会大幅衰减
核心矛盾:训练时见过的最大长度有限,但推理时我们希望处理更长的文本。
ALiBi 就是为了解决这个问题而生的。
二、ALiBi 的核心思想
2.1 论文信息
Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation
Ofir Press, Noah A. Smith, Mike Lewis (2022, ICLR)
论文标题直接点明了核心卖点:训练短序列,测试长序列。
2.2 一句话总结
ALiBi 不对输入添加任何位置嵌入,而是在计算注意力分数时,根据 Q 和 K 之间的距离,直接减去一个线性惩罚项。距离越远,惩罚越大。
2.3 数学公式
标准注意力
Attention(Q,K,V)=softmax(QKTdk)V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V Attention(Q,K,V)=softmax(dk QKT)V
ALiBi 注意力
Attention(Q,K,V)=softmax(QKTdk+m⋅Bias)V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + m \cdot \text{Bias}\right) V Attention(Q,K,V)=softmax(dk QKT+m⋅Bias)V
其中:
Bias[i][j]=−∣i−j∣ \text{Bias}[i][j] = -|i - j| Bias[i][j]=−∣i−j∣
mmm 是每个注意力头各自的斜率(slope),是一个预设的常数。
2.4 偏置矩阵长什么样?
以序列长度 5 为例,Bias\text{Bias}Bias 矩阵(对于因果注意力,只看下三角):
Bias=(0−10−2−10−3−2−10−4−3−2−10) \text{Bias} = \begin{pmatrix} 0 & & & & \\ -1 & 0 & & & \\ -2 & -1 & 0 & & \\ -3 & -2 & -1 & 0 & \\ -4 & -3 & -2 & -1 & 0 \end{pmatrix} Bias= 0−1−2−3−40−1−2−30−1−20−10
乘以某个头的斜率 mmm 后:
m⋅Bias=(0−m0−2m−m0−3m−2m−m0−4m−3m−2m−m0) m \cdot \text{Bias} = \begin{pmatrix} 0 & & & & \\ -m & 0 & & & \\ -2m & -m & 0 & & \\ -3m & -2m & -m & 0 & \\ -4m & -3m & -2m & -m & 0 \end{pmatrix} m⋅Bias= 0−m−2m−3m−4m0−m−2m−3m0−m−2m0−m0
直觉理解 :每个 token 计算注意力时,越远的 token 被惩罚越多 → 自然倾向于关注近处的内容。
三、斜率 mmm 的设定
3.1 多头注意力中的斜率分配
ALiBi 最巧妙的设计之一:不同的头使用不同的斜率 ,而这些斜率是预设的,不需要学习。
对于 nnn 个注意力头,斜率为几何序列:
mi=128n⋅i,i=1,2,...,n m_i = \frac{1}{2^{\frac{8}{n} \cdot i}}, \quad i = 1, 2, \ldots, n mi=2n8⋅i1,i=1,2,...,n
3.2 具体例子
8 个头的情况:
8n=88=1 \frac{8}{n} = \frac{8}{8} = 1 n8=88=1
m1=121=12,m2=122=14,m3=123=18,...,m8=128=1256 m_1 = \frac{1}{2^1} = \frac{1}{2}, \quad m_2 = \frac{1}{2^2} = \frac{1}{4}, \quad m_3 = \frac{1}{2^3} = \frac{1}{8}, \quad \ldots, \quad m_8 = \frac{1}{2^8} = \frac{1}{256} m1=211=21,m2=221=41,m3=231=81,...,m8=281=2561
头1: m = 1/2 → 衰减最快,强烈偏好近距离
头2: m = 1/4 → 衰减较快
头3: m = 1/8 → 衰减中等
...
头8: m = 1/256 → 衰减最慢,能"看到"很远的位置
3.3 为什么这样设计?
注意力权重
↑
1.0 │ ██
│ ████
│ ██████ 头8 (m=1/256): 缓慢衰减,关注长距离
│ ████████████████████████───────────
│ ██████████████████──────
│ ██████████────
│ ████────
│ ── 头1 (m=1/2): 快速衰减,只关注近距离
└──────────────────────────────→ 距离
不同头承担不同的"感受野"角色:
- 大斜率头:像局部卷积,捕捉相邻 token 的关系(语法、短语结构)
- 小斜率头:像全局注意力,捕捉长距离依赖(篇章结构、指代关系)
这种设计使模型天然具备多尺度的位置感知能力。
四、ALiBi 为什么能外推?
4.1 与其他方案的关键区别
=== 正弦/可学习/RoPE 位置编码 ===
输入: [token₁, token₂, ..., tokenₙ]
+ PE₁ + PE₂ + PEₙ ← 位置信息注入到输入中
问题: 位置编码和内容编码混合后,模型很难泛化到未见过的位置编号
=== ALiBi ===
输入: [token₁, token₂, ..., tokenₙ] ← 不加任何位置编码!
位置信息只在注意力分数中体现
Q·K^T + m·[-|i-j|] ← 位置信息作为偏置后加
4.2 外推能力的直觉解释
ALiBi 编码的是"相对距离",而不是"绝对位置"。
训练时模型见过的情况:
- 距离 0 → 偏置 0
- 距离 1 → 偏置 −m-m−m
- 距离 100 → 偏置 −100m-100m−100m
- 距离 1024 → 偏置 −1024m-1024m−1024m(训练中的最大距离)
推理时遇到更长序列:
- 距离 2000 → 偏置 −2000m-2000m−2000m
- 距离 4096 → 偏置 −4096m-4096m−4096m
关键洞察 :偏置是线性函数 f(d)=−mdf(d) = -mdf(d)=−md,线性函数天然具有外推能力!
偏置
0 ┤───╮
│ ╲
│ ╲ 训练范围
-1024m ┤ ·╲·················
│ ╲
│ ╲ 外推范围(自然延伸)
-4096m ┤ ╲
└────────────────→ 距离
1024 4096
模型在训练时学到了"距离越远,关注越少"的模式。这个模式在更长的距离上自然延续,不会出现任何突变或未定义的行为。
4.3 与 RoPE 外推的对比
RoPE 在训练长度外:
- 旋转角度 mθ 超出训练范围
- 高频分量出现从未见过的相位
- 注意力分数剧烈波动
- 需要额外技术(NTK-aware 插值等)来修复
ALiBi 在训练长度外:
- 偏置 -md 只是线性函数的自然延伸
- 远距离 token 的注意力权重平滑地趋近于零
- 不需要任何修改,天然可外推
五、代码实现
5.1 计算斜率
python
import torch
import math
def get_alibi_slopes(num_heads):
"""
计算每个注意力头的斜率 m
对于 8 个头: [1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256]
"""
# 找到最接近的 2 的幂次
closest_power_of_2 = 2 ** math.floor(math.log2(num_heads))
# 基础比率
base = 2 ** (8 / closest_power_of_2) # 对于 8 头: base = 2^1 = 2
# 生成斜率: 1/base^1, 1/base^2, ..., 1/base^n
slopes = [1.0 / (base ** i) for i in range(1, closest_power_of_2 + 1)]
# 如果 num_heads 不是 2 的幂次,需要额外处理
if closest_power_of_2 != num_heads:
extra_base = 2 ** (8 / (2 * closest_power_of_2))
extra_slopes = [1.0 / (extra_base ** i)
for i in range(1, 2 * (num_heads - closest_power_of_2) + 1, 2)]
slopes = extra_slopes + slopes
return torch.tensor(slopes)
# 示例
slopes = get_alibi_slopes(8)
print("8 heads slopes:", slopes)
# tensor([0.5000, 0.2500, 0.1250, 0.0625, 0.0312, 0.0156, 0.0078, 0.0039])
5.2 构建偏置矩阵
python
def build_alibi_bias(seq_len, num_heads):
"""
构建 ALiBi 偏置矩阵
返回 shape: (num_heads, seq_len, seq_len)
"""
# Step 1: 计算距离矩阵
# positions[i][j] = -|i - j|
positions = torch.arange(seq_len)
# 对于因果注意力(decoder),通常用 i - j(只看过去)
relative_positions = positions.unsqueeze(0) - positions.unsqueeze(1)
# 取负的绝对值
distance_matrix = -torch.abs(relative_positions) # shape: (seq_len, seq_len)
# Step 2: 获取每个头的斜率
slopes = get_alibi_slopes(num_heads) # shape: (num_heads,)
# Step 3: 斜率 × 距离 → 偏置
# slopes: (num_heads, 1, 1) × distance: (seq_len, seq_len)
alibi_bias = slopes.unsqueeze(1).unsqueeze(1) * distance_matrix.unsqueeze(0)
return alibi_bias # shape: (num_heads, seq_len, seq_len)
# 示例: 4 个头,序列长度 5
bias = build_alibi_bias(seq_len=5, num_heads=4)
print(f"Shape: {bias.shape}")
print(f"\n头 0 (slope={get_alibi_slopes(4)[0]:.4f}):")
print(bias[0])
输出:
Shape: torch.Size([4, 5, 5])
头 0 (slope=0.0625):
tensor([[ 0.0000, -0.0625, -0.1250, -0.1875, -0.2500],
[-0.0625, 0.0000, -0.0625, -0.1250, -0.1875],
[-0.1250, -0.0625, 0.0000, -0.0625, -0.1250],
[-0.1875, -0.1250, -0.0625, 0.0000, -0.0625],
[-0.2500, -0.1875, -0.1250, -0.0625, 0.0000]])
5.3 因果注意力的偏置矩阵
在 decoder-only 模型(如 GPT)中,token 只能看到自己和之前的 token:
python
def build_alibi_bias_causal(seq_len, num_heads):
"""
因果(单向)注意力的 ALiBi 偏置
"""
# 因果注意力:位置 i 只看位置 j ≤ i
# 距离 = i - j ≥ 0
positions = torch.arange(seq_len)
# relative_positions[i][j] = j - i (负数或零)
relative_positions = positions.unsqueeze(1) - positions.unsqueeze(0)
# 只保留下三角(j ≤ i 的部分),上三角设为 -inf
causal_mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()
slopes = get_alibi_slopes(num_heads)
# 偏置 = slope × (j - i),其中 j ≤ i,所以 j - i ≤ 0
alibi_bias = slopes.unsqueeze(1).unsqueeze(1) * relative_positions.unsqueeze(0)
alibi_bias.masked_fill_(causal_mask.unsqueeze(0), float('-inf'))
return alibi_bias
可视化因果偏置矩阵:
头 1 (slope = 1/2):
token: t₀ t₁ t₂ t₃ t₄
t₀ [ 0.00, -inf, -inf, -inf, -inf ]
t₁ [ -0.50, 0.00, -inf, -inf, -inf ]
t₂ [ -1.00, -0.50, 0.00, -inf, -inf ]
t₃ [ -1.50, -1.00, -0.50, 0.00, -inf ]
t₄ [ -2.00, -1.50, -1.00, -0.50, 0.00 ]
↑ 距离远,惩罚大 ↑ 距离近,惩罚小
5.4 集成到注意力计算中
python
class ALiBiAttention(torch.nn.Module):
def __init__(self, hidden_dim, num_heads):
super().__init__()
self.num_heads = num_heads
self.head_dim = hidden_dim // num_heads
self.W_q = torch.nn.Linear(hidden_dim, hidden_dim, bias=False)
self.W_k = torch.nn.Linear(hidden_dim, hidden_dim, bias=False)
self.W_v = torch.nn.Linear(hidden_dim, hidden_dim, bias=False)
self.W_o = torch.nn.Linear(hidden_dim, hidden_dim, bias=False)
# 注册斜率为 buffer(不参与训练)
self.register_buffer('slopes', get_alibi_slopes(num_heads))
def forward(self, x, causal_mask=None):
"""
x: (batch_size, seq_len, hidden_dim)
"""
B, L, D = x.shape
# === 1. 线性投影(注意:不加任何位置编码!)===
Q = self.W_q(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
K = self.W_k(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
V = self.W_v(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
# Q, K, V shape: (B, num_heads, L, head_dim)
# === 2. 计算注意力分数 ===
scale = self.head_dim ** -0.5
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) * scale
# attn_scores shape: (B, num_heads, L, L)
# === 3. 加上 ALiBi 偏置(核心!)===
alibi_bias = self._build_alibi_bias(L) # (num_heads, L, L)
attn_scores = attn_scores + alibi_bias.unsqueeze(0) # 广播到 batch 维度
# === 4. 因果掩码 ===
if causal_mask is not None:
attn_scores = attn_scores.masked_fill(causal_mask, float('-inf'))
# === 5. Softmax + 加权求和 ===
attn_weights = torch.softmax(attn_scores, dim=-1)
output = torch.matmul(attn_weights, V)
# === 6. 合并多头 + 输出投影 ===
output = output.transpose(1, 2).contiguous().view(B, L, D)
output = self.W_o(output)
return output
def _build_alibi_bias(self, seq_len):
"""动态构建偏置矩阵(支持任意长度!)"""
positions = torch.arange(seq_len, device=self.slopes.device)
relative_positions = positions.unsqueeze(1) - positions.unsqueeze(0)
# relative_positions[i][j] = j - i
alibi_bias = self.slopes.unsqueeze(1).unsqueeze(1) * relative_positions.unsqueeze(0)
return alibi_bias
六、ALiBi 的完整工作流程
┌─────────────────────────────────────────────────────┐
│ 输入序列 │
│ "我 喜欢 自然 语言 处理" │
│ [t₁, t₂, t₃, t₄, t₅] │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Token Embedding │
│ │
│ 注意:不加任何位置编码! │
│ x = Embedding(tokens) │
│ │
│ 对比其他方案: │
│ ✗ x = Embedding(tokens) + PosEmbedding(positions) │
│ ✗ x = Embedding(tokens) + SinCos(positions) │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Q, K, V = Linear(x) │
│ (纯内容信息,无位置信息) │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 注意力分数 = Q·K^T / √d │
│ │
│ ┌ ┐ │
│ │ q₁k₁ q₁k₂ q₁k₃ q₁k₄ q₁k₅ │ 纯内容相似度 │
│ │ q₂k₁ q₂k₂ q₂k₃ q₂k₄ q₂k₅ │ │
│ │ q₃k₁ q₃k₂ q₃k₃ q₃k₄ q₃k₅ │ │
│ │ q₄k₁ q₄k₂ q₄k₃ q₄k₄ q₄k₅ │ │
│ │ q₅k₁ q₅k₂ q₅k₃ q₅k₄ q₅k₅ │ │
│ └ ┘ │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ │
│ + m × 距离偏置 ← ALiBi 的全部操作! │
│ │
│ ┌ ┐ │
│ │ 0 -m -2m -3m -4m │ │
│ │ -m 0 -m -2m -3m │ │
│ │ -2m -m 0 -m -2m │ │
│ │ -3m -2m -m 0 -m │ │
│ │ -4m -3m -2m -m 0 │ │
│ └ ┘ │
│ │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Softmax → 注意力权重 → 加权求和 V │
└─────────────────────────────────────────────────────┘
七、实验结果
7.1 外推能力对比
论文中的核心实验:在 1024 长度上训练,测试不同长度的困惑度(PPL):
测试长度 → 1024 2048 3072 4096 6144 8192
正弦编码 25.0 >1e3 >1e3 >1e3 >1e3 >1e3
旋转编码 25.0 43.0 >1e3 >1e3 >1e3 >1e3
T5 Bias 25.0 28.0 45.0 >1e3 >1e3 >1e3
ALiBi 25.0 25.5 25.8 26.0 26.5 27.0
✓ ✓✓ ✓✓✓ ✓✓✓✓ ✓✓✓✓✓ ✓✓✓✓✓✓
ALiBi 在 8 倍外推时仍保持合理的性能! 其他方案在 2-3 倍时就崩溃了。
7.2 训练速度
由于 ALiBi 不需要计算位置嵌入向量,计算量略有减少:
方法 相对训练速度
正弦编码 1.00x
可学习编码 1.00x
RoPE 0.98x(需要额外的旋转操作)
ALiBi 1.01x(只需一次加法)
八、ALiBi 的优缺点
8.1 优点
| 优点 | 说明 |
|---|---|
| 强外推能力 | 训练短、测试长,不需要任何额外技术 |
| 实现极简 | 只需在注意力分数上加一个矩阵,几行代码 |
| 零额外参数 | 斜率是预设的,不需要学习 |
| 计算高效 | 不需要像 RoPE 那样修改 Q/K,只需一次加法 |
| 与 KV Cache 天然兼容 | 偏置只依赖相对距离,增量推理不受影响 |
8.2 缺点
| 缺点 | 说明 |
|---|---|
| 位置建模能力较弱 | 线性衰减是一种很强的归纳偏置,可能不够灵活 |
| 在训练长度内的性能 | 同等训练条件下,ALiBi 在训练长度内的 PPL 有时略逊于 RoPE |
| 对注意力模式的约束 | 强制近距离偏好,可能限制某些需要远距离跳跃注意力的任务 |
| 生态不如 RoPE | 当前主流开源模型(LLaMA、Qwen 等)多用 RoPE |
九、ALiBi vs RoPE:深度对比
| 维度 | ALiBi | RoPE |
|---|---|---|
| 位置信息注入点 | 注意力分数(加偏置) | Q/K 向量(乘旋转矩阵) |
| 编码类型 | 相对位置 | 相对位置 |
| 需要额外参数? | 否 | 否 |
| 实现复杂度 | 极简(一次加法) | 中等(旋转操作) |
| 训练长度内性能 | 好 | 略好 |
| 原生外推能力 | 强(线性外推) | 弱(需要插值技术) |
| 修改输入嵌入? | 不修改 | 不修改 |
| 代表模型 | BLOOM, MPT | LLaMA, Qwen, Mistral |
| 当前主流程度 | 较少使用 | 主流 |
为什么 RoPE 最终更流行?
虽然 ALiBi 的外推能力更强,但 RoPE 通过一系列扩展技术(YaRN、NTK-aware 插值、动态 NTK 等)弥补了外推的不足,同时在训练长度内的建模能力更强。加上 LLaMA 系列模型的巨大影响力,RoPE 成为了当前大模型的主流选择。
十、使用 ALiBi 的知名模型
| 模型 | 发布方 | 参数量 | 说明 |
|---|---|---|---|
| BLOOM | BigScience | 176B | 最大的开源多语言模型之一 |
| MPT-7B/30B | MosaicML | 7B/30B | 号称可外推到 84k token |
| BTLM-3B | Cerebras | 3B | 高效小模型 |
十一、总结
ALiBi 用一个优雅至极的方法解决了位置编码的长度外推问题:
不在输入中编码位置,而在注意力分数中直接加线性距离惩罚。
python
# ALiBi 的核心,就这一行:
attn_scores = Q @ K.T / sqrt(d) + slope * distance_bias
它的设计哲学值得深思:
- 简单就是力量:线性函数的外推能力优于复杂的周期函数
- 正确的归纳偏置:自然语言中"距离近 = 更相关"几乎总是成立
- 不需要学的就别学:预设斜率的几何序列就足够好了
虽然在当前大模型生态中 RoPE 占据主导地位,但 ALiBi 的思想------将位置信息与内容信息解耦,用最简单的数学形式编码位置------依然深刻地影响着位置编码的研究方向。
ALiBi 外推能力的深层解释
你的疑问非常好------"线性函数天然具有外推能力"这句话确实太笼统了。让我真正讲清楚为什么 ALiBi 能外推而其他方案不能。
一、先搞清楚"外推失败"到底是什么现象
1.1 用一个极简例子说明
训练时最长序列 = 4 个 token,推理时来了 8 个 token。
模型需要算的核心东西 :token iii 对 token jjj 的注意力分数。
训练时 (seq_len=4):
token 0 对 token 0: 距离 0 ✓ 见过
token 3 对 token 0: 距离 3 ✓ 见过(最远距离)
推理时 (seq_len=8):
token 7 对 token 0: 距离 7 ✗ 没见过!
外推的本质问题:模型遇到了训练时从未出现过的"距离=7",它该怎么处理?
二、逐个分析各方案为什么失败
2.1 可学习位置编码(GPT-2 风格)
python
# 训练时
pos_embedding = nn.Embedding(max_len=4, dim=D)
# 学到了 4 个向量: PE[0], PE[1], PE[2], PE[3]
# 推理时,输入第 5 个 token
pos_embedding(4) # → 索引越界!直接报错
pos_embedding(7) # → 不存在!
失败原因:位置 4~7 的嵌入向量根本不存在,这是最硬性的失败。
2.2 正弦位置编码
python
# 位置 m 的编码:PE(m) = [sin(mθ₀), cos(mθ₀), sin(mθ₁), cos(mθ₁), ...]
虽然可以算出任意位置的值,但问题在于:
训练时:
输入 = 内容嵌入 + PE(位置)
模型学到的是:当输入向量"长这样"时,应该如何注意
推理时位置 7:
PE(7) 这个向量,模型在训练时从未作为输入的一部分见过
它和内容嵌入相加后,产生的向量落在了训练数据分布之外
失败原因 :位置编码加到了输入里,改变了输入的分布。新位置的 PE 向量让输入变成了分布外(Out-of-Distribution) 的数据。
模型见过的输入分布
┌─────────────┐
│ ●●●●● │ ← 训练时的输入(内容+PE(0~3))
│ ●●●● │
│ │
└─────────────┘
★ ← 推理时的输入(内容+PE(7))
落在分布外!
2.3 RoPE
RoPE 不加到输入里,而是旋转 Q 和 K:
score(m,n)=(Rmq)T(Rnk)=qTRm−nTk \text{score}(m, n) = (R_m q)^T (R_n k) = q^T R_{m-n}^T k score(m,n)=(Rmq)T(Rnk)=qTRm−nTk
注意力分数只依赖相对距离 m−nm-nm−n,这比正弦编码好。但问题是:
R(Δ) 是一个旋转矩阵,旋转角度 = Δ × θᵢ
训练时见过的角度范围:
θ₀ = 1.0: Δ·θ₀ ∈ [0, 3.0] (Δ最大=3)
θ₁ = 0.01: Δ·θ₁ ∈ [0, 0.03]
推理时 Δ=7:
θ₀ = 1.0: 7·θ₀ = 7.0 ← 超出 [0, 3.0] 的范围!
θ₁ = 0.01: 7·θ₁ = 0.07 ← 这个还好
高频分量 (大 θi\theta_iθi)的旋转角度超出训练范围后:
cos/sin 是周期函数,角度从 3.0 跳到 7.0 时:
cos(3.0) = -0.99 训练时的最远距离
cos(7.0) = +0.75 推理时的距离7
这两个值之间没有平滑过渡!
模型从未学过 cos=0.75 对应"很远的距离"
注意力分数
↑
│ ╱╲ ╱╲ ← RoPE:周期性波动
│ ╱ ╲ ╱ ╲
│ ╱ ╲ ╱ ╲
│─╱──────╳──────╲── ← 不该出现的"复活":远处token获得高分
│╱ 训练范围 │ 外推范围
└──────────────────→ 距离
3 7
失败原因 :cos(mθ)\cos(m\theta)cos(mθ) 和 sin(mθ)\sin(m\theta)sin(mθ) 是周期函数 。超出训练范围后,远距离的注意力分数不会单调衰减,反而会出现周期性的"复活"------一个距离很远的 token 可能突然获得很高的注意力分数。
三、ALiBi 为什么能成功
现在回到 ALiBi:
score(i,j)=qiTkjd+m⋅(−(∣i−j∣)) \text{score}(i, j) = \frac{q_i^T k_j}{\sqrt{d}} + m \cdot (-(|i-j|)) score(i,j)=d qiTkj+m⋅(−(∣i−j∣))
3.1 关键洞察:ALiBi 中"什么是模型需要泛化的"
让我们精确地分析模型在训练和推理时分别遇到什么:
训练时 (seq_len=4),模型经历过的所有注意力分数:
score(i,j) = [内容相似度] + [位置偏置]
距离0: content_score + m×0 = content_score + 0
距离1: content_score + m×(-1) = content_score - m
距离2: content_score + m×(-2) = content_score - 2m
距离3: content_score + m×(-3) = content_score - 3m
训练后 Softmax 学到了什么?
Softmax 接收的输入是一组分数,输出归一化的注意力权重。它学到的核心模式:
"偏置项从 0 开始,每远一步就减少 mmm,越远的 token 分数越低,注意力权重越小。"
3.2 推理时发生了什么
推理时 (seq_len=8),对于 token 7:
距离0: content_score - 0m ← 见过
距离1: content_score - 1m ← 见过
距离2: content_score - 2m ← 见过
距离3: content_score - 3m ← 见过(训练时的最远距离)
──────── 以下是外推 ────────
距离4: content_score - 4m ← 没直接见过
距离5: content_score - 5m ← 没直接见过
距离6: content_score - 6m ← 没直接见过
距离7: content_score - 7m ← 没直接见过
但是! 我们来看 Softmax 的输入分布:
进入 Softmax 的值:
[content₀ - 7m, content₁ - 6m, content₂ - 5m, content₃ - 4m,
content₄ - 3m, content₅ - 2m, content₆ - 1m, content₇ - 0m]
对于 token 7 来说,它附近的 token(距离 0~3)获得的偏置和训练时完全一样!
远处的 token(距离 4~7)获得了更大的负偏置 −4m,−5m,−6m,−7m-4m, -5m, -6m, -7m−4m,−5m,−6m,−7m。
3.3 核心问题:Softmax 能处理这些更大的负值吗?
能!这是 ALiBi 成功的真正原因。
Softmax 的性质:
softmax(z)_i = exp(z_i) / Σ exp(z_j)
当某个 z_i 变得很负时:
exp(-7m) 是一个很小的正数
这个 token 的注意力权重 ≈ 0
这完全符合模型已经学到的模式:
"偏置越负 → 权重越小"
让我们用具体数字看(假设 m=0.5m = 0.5m=0.5,忽略内容分数):
训练时 Softmax 见过的输入:
[0, -0.5, -1.0, -1.5]
→ exp: [1.00, 0.61, 0.37, 0.22]
→ softmax: [0.45, 0.28, 0.17, 0.10]
推理时 Softmax 的输入:
[0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0, -3.5]
→ exp: [1.00, 0.61, 0.37, 0.22, 0.14, 0.08, 0.05, 0.03]
→ softmax: [0.40, 0.24, 0.15, 0.09, 0.05, 0.03, 0.02, 0.01]
观察:
-
近距离 token 的权重分布几乎没变(0.45→0.40,0.28→0.24)
-
远距离 token 自然获得接近 0 的权重
-
没有任何突变、跳跃或异常
注意力权重
↑
0.5 │
│ █
0.4 │ █
│ █ █
0.3 │ █ █
│ █ █ █
0.2 │ █ █ █ █
│ █ █ █ █ █
0.1 │ █ █ █ █ █ █
│ █ █ █ █ █ █ █ █
0.0 │─█─█─█─█─█─█─█─█───→
距离 0 1 2 3 4 5 6 7
←训练→ ←──外推──→平滑衰减,没有任何异常!
3.4 对比:为什么 RoPE 的 Softmax 输入会出问题?
RoPE 推理时 Softmax 的输入(示意):
距离: 0 1 2 3 │ 4 5 6 7
分数: [+2.0, +1.5, +0.8, +0.1, +1.8, -0.5, +1.6, +0.3]
│ ↑ ↑
│ 周期性"复活"!
│ 远距离token突然获得高分
→ softmax 后,距离4和距离6的token获得异常高的权重
→ 模型从未学过如何处理这种"远处突然很重要"的模式
→ 生成质量崩溃
四、精确总结
外推成功的三个必要条件
ALiBi 同时满足了这三个条件:
条件 1:位置信息不污染输入分布
┌─────────────────────────────────────────┐
│ 正弦/可学习编码:PE 加到输入 → 新位置改变输入分布 │ ✗
│ RoPE:旋转 Q/K → 不改变输入,但改变注意力计算 │ △
│ ALiBi:输入完全不含位置信息 │ ✓
└─────────────────────────────────────────┘
条件 2:注意力分数随距离单调变化
┌─────────────────────────────────────────┐
│ RoPE:cos/sin 是周期函数 → 远距离分数会震荡 │ ✗
│ ALiBi:-m|d| 是单调递减函数 → 越远分数越低 │ ✓
└─────────────────────────────────────────┘
条件 3:外推区域的值在 Softmax 的"舒适区"内
┌─────────────────────────────────────────┐
│ RoPE:外推区域的分数可能突然变大 → Softmax 异常 │ ✗
│ ALiBi:外推区域的分数更负 → Softmax 自然给更低权重 │ ✓
│ exp(更负的数) = 更小的正数,完全在正常范围内 │
└─────────────────────────────────────────┘
一句话解释
ALiBi 外推时,远距离 token 只是获得了更大的负偏置,Softmax 自然地将它们的权重压到接近零。模型不需要"理解"新的距离,它只需要做和训练时一样的事:给负偏置大的 token 低权重。整个过程没有任何训练时未见过的新模式出现。
后记
2026年5月15日于上海,在claude opus 4.6辅助下完成。