位置编码演进

绝对位置编码

原始 Transformer 使用可学习的绝对位置编码:$E = E_{\text{token}} + E_{\text{pos}}$,其中 $E_{\text{pos}}$ 是可学习参数。

问题:无法外推到训练时未见过的长度。

相对位置编码

T5/ALBERT 使用相对位置偏置:$\text{Attn}(Q_i, K_j) = \frac{Q_i K_j^T}{\sqrt{d}} + b_{i-j}$

问题:需要额外偏置项,且对窗口外的位置需要截断。

旋转位置编码(RoPE)

RoPE(Su et al., 2021)的核心思想:通过旋转矩阵将绝对位置编码转化为相对位置编码——对位置 $m$ 的 Query 和位置 $n$ 的 Key,注意力分数只依赖 $m - n$。

RoPE 数学原理

二维情形

对于二维向量 $q = (q_0, q_1)$,在位置 $m$ 处旋转角度 $m\theta$:

$$q_m = R_m q = \begin{pmatrix} \cos m\theta & -\sin m\theta \ \sin m\theta & \cos m\theta \end{pmatrix} \begin{pmatrix} q_0 \ q_1 \end{pmatrix}$$

注意力分数:

$$\langle q_m, k_n \rangle = q^T R_m^T R_n k = q^T R_{n-m} k$$

关键性质:内积只依赖相对位置 $n - m$,且 $R_{n-m}$ 是旋转矩阵。

高维推广

将 $d$ 维向量分为 $d/2$ 个二维对,每对使用不同频率 $\theta_i$:

$$\theta_i = 10000^{-2i/d}, \quad i = 0, 1, \ldots, d/2 - 1$$

旋转矩阵(块对角形式):

$$R_m = \text{diag}\left(R(m\theta_0), R(m\theta_1), \ldots, R(m\theta_{d/2-1})\right)$$

实现形式

实际实现不构造稀疏旋转矩阵,而是用元素级乘法:

def apply_rope(q, positions, theta_base=10000.0):
    """
    q: (batch, seq_len, num_heads, head_dim)
    positions: (batch, seq_len) 位置索引
    """
    d = q.shape[-1]
    half = d // 2
    
    # 计算频率
    freqs = 1.0 / (theta_base ** (torch.arange(0, half).float() / half))
    # (batch, seq_len, half)
    angles = positions.unsqueeze(-1) * freqs  # (batch, seq_len, half)
    
    cos = angles.cos()  # (batch, seq_len, half)
    sin = angles.sin()  # (batch, seq_len, half)
    
    # 扩展到 full dim: [cos_0, cos_1, ..., cos_{d/2-1}, cos_0, ...]
    cos = torch.cat([cos, cos], dim=-1)  # (batch, seq_len, d)
    sin = torch.cat([sin, sin], dim=-1)
    
    # 旋转: [q0, q1, ..., q_{d/2}, q_{d/2+1}, ...]
    # → [q0*cos0 - q1*sin0, q1*cos0 + q0*sin0, ...]
    q_rot = q * cos + rotate_half(q) * sin
    return q_rot

def rotate_half(x):
    """将后半部分取负并交换: [a, b] → [-b, a]"""
    half = x.shape[-1] // 2
    return torch.cat([-x[..., half:], x[..., :half]], dim=-1)

为什么 RoPE 有效?

  1. 相对位置编码:$q_m^T k_n$ 只依赖 $m - n$
  2. 远程衰减:高频分量 $\theta_0$ 使得相近位置区分度高,低频分量 $\theta_{d/2-1}$ 捕捉长程关系
  3. 可外推:理论上对任意位置 $m$ 都可计算旋转,不限于训练范围

长度外推问题

问题定义

模型在长度 $L_{\text{train}}$ 上训练,推理时使用 $L_{\text{test}} > L_{\text{train}}$。直接外推会导致性能急剧下降。

原因分析

训练时模型只见过 $m\theta_i \in [0, L_{\text{train}} \cdot \theta_i]$。测试时 $m > L_{\text{train}}$,某些 $\cos(m\theta_i)$ 的值超出训练分布。

特别是高频分量($\theta_i$ 大)在长序列中周期变化快,外推时产生训练中未见的组合。

位置插值(PI)

最简单的方法:将测试位置线性缩放到训练范围:

$$m’ = m \cdot \frac{L_{\text{train}}}{L_{\text{test}}}$$

$$q’m = R{m’} q$$

优点:简单,少量微调即可工作。缺点:均匀缩放降低了短距离的分辨率。

NTK-aware 缩放

核心思想

不同频率需要不同的缩放策略:

  • 高频分量:不需要缩放(短距离信息保持精确)
  • 低频分量:需要缩放(扩展长距离范围)

修改 base 频率:

$$\theta_i’ = \left(\text{base} \cdot s^{d/(d-2i)}\right)^{-2i/d}$$

其中 $s = L_{\text{test}} / L_{\text{train}}$ 为缩放因子。

def ntk_aware_freqs(d, base=10000.0, scale=4.0):
    """NTK-aware 频率缩放"""
    half = d // 2
    # 原始频率
    freqs = 1.0 / (base ** (torch.arange(0, half).float() / half))
    # NTK 缩放: base -> base * scale^(d/(d-2))
    scaled_base = base * (scale ** (d / (d - 2)))
    freqs_scaled = 1.0 / (scaled_base ** (torch.arange(0, half).float() / half))
    return freqs_scaled

NTK 数学推导

RoPE 的有效波长为 $\lambda_i = 2\pi / \theta_i = 2\pi \cdot \text{base}^{2i/d}$。

NTK 的策略是让最低频分量(最长波长)扩展 $s$ 倍,最高频不变:

$$\lambda_i’ = \lambda_i \cdot s^{1 - 2i/d}$$

YaRN

YaRN(Peng et al., 2023)进一步改进 NTK,引入分段插值

  1. 将频率分为三个区间:高频(不插值)、中频(线性插值)、低频(NTK 缩放)
  2. 添加注意力温度缩放因子 $t$
def yarn_freqs(d, base=10000.0, original_len=4096, target_len=32768):
    """
    YaRN 频率计算
    """
    half = d // 2
    s = target_len / original_len
    
    # 计算每个频率的波长
    freqs = 1.0 / (base ** (torch.arange(0, half).float() / half))
    wavelengths = 2 * math.pi / freqs
    
    # 定义区间边界
    low_freq_wavelen = original_len
    high_freq_wavelen = original_len / (s ** 0.1)  # 经验值
    
    freqs_scaled = freqs.clone()
    for i in range(half):
        wl = wavelengths[i]
        if wl < high_freq_wavelen:
            # 高频: 不缩放
            pass
        elif wl > low_freq_wavelen:
            # 低频: 缩放 s 倍
            freqs_scaled[i] = freqs[i] / s
        else:
            # 中频: 线性插值
            alpha = (wl - high_freq_wavelen) / (low_freq_wavelen - high_freq_wavelen)
            freqs_scaled[i] = freqs[i] * (1 - alpha * (1 - 1/s))
    
    return freqs_scaled

def yarn_attention_scale(target_len, original_len):
    """YaRN 注意力温度缩放"""
    s = target_len / original_len
    if s <= 1:
        return 1.0
    return math.sqrt(1 + math.log(s) / math.log(original_len))

Longrope

Longrope(Microsoft, 2024)的关键创新:为不同维度搜索不同的缩放因子,而非使用统一公式。

def longrope_search(d, original_len, target_len, eval_fn):
    """
    Longrope: 搜索最优的每组频率缩放因子
    """
    half = d // 2
    freqs = 1.0 / (10000 ** (torch.arange(0, half).float() / half))
    
    # 搜索空间: 每个维度独立缩放
    best_lambdas = torch.ones(half)
    best_score = eval_fn(freqs)
    
    for i in range(half):
        for lam in np.linspace(0.5, 8.0, 50):
            candidate = best_lambdas.clone()
            candidate[i] = lam
            score = eval_fn(freqs * candidate)
            if score > best_score:
                best_score = score
                best_lambdas = candidate
    
    return freqs * best_lambdas

外推方法全面对比

方法缩放策略需微调外推 8×外推 32×复杂度
直接外推❌ 严重降质-
位置插值 (PI)均匀线性需~1000步✅ 良好⚠️ 下降
NTK-aware频率自适应否(少步更佳)✅ 良好⚠️ 下降
YaRN分段+温度需~400步✅ 优秀✅ 良好
Longrope逐维搜索✅ 优秀✅ 优秀

实测困惑度对比 (LLaMA-2 7B, 训练 4K)

外推到直接PINTKYaRNLongrope
8K5.425.315.285.255.23
16K8.715.485.425.335.28
32KOOM6.125.815.525.38
128KOOM9.437.256.115.72

工程实现注意

1. 位置索引

不同框架的位置索引可能不同(从 0 或 1 开始),影响外推效果:

# LLaMA 系列: 从 0 开始
positions = torch.arange(0, seq_len)

# 某些实现: 第一个 token 位置为 0 但有 padding 偏移
positions = torch.arange(0, seq_len) + padding_offset

2. 与 Flash Attention 兼容

RoPE 需要在 Flash Attention kernel 内部应用,而非外部预处理:

# ✅ 正确: 在 Flash Attention kernel 内部应用 RoPE
from flash_attn import flash_attn_func
q_rot = apply_rope(q, positions)
k_rot = apply_rope(k, positions)
output = flash_attn_func(q_rot, k_rot, v, causal=True)

# ❌ 错误: 先做标准 attention 再应用 RoPE

3. 动态 NTK

推理时根据当前序列长度动态调整缩放因子:

def dynamic_ntk_scaling(seq_len, max_train_len=4096, base=10000.0):
    if seq_len <= max_train_len:
        return base
    scale = seq_len / max_train_len
    return base * (scale ** (d / (d - 2)))

总结

RoPE 通过旋转矩阵优雅地将绝对位置编码转化为相对位置编码,成为当前 LLM 的标准位置编码方案。长度外推方面,YaRN 和 Longrope 代表了 SOTA 方案,使 4K 训练的模型能够有效处理 128K+ 的上下文。选择建议:

  • 快速部署:NTK-aware(零微调)
  • 生产质量:YaRN(少量微调)
  • 极致外推:Longrope(搜索开销大但效果最好)

本文由硅基 AGI 技术团队撰写,转载请注明出处。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。