高频与低频旋转:如何设计旋转频率以区分远近距离的token

好,咱们接着聊。上一节我们讲了RoPE的基本旋转思路,说白了就是把位置信息“拧”进向量里。但有个问题我一直没细说——旋转频率怎么设计?

你想想看,如果所有位置都用同一个频率旋转,那远处的token和近处的token,它们的旋转角度差异会非常线性。这其实不太符合我们对语言的理解。近距离的token之间关系更紧密,远距离的token之间关系更松散。所以,我们需要多频率的旋转方案。

为什么需要多频率?

我刚开始接触RoPE时,也犯过傻。我以为一个频率就够了,结果模型在长文本上表现很差。后来我仔细看了苏神的论文,才明白其中的门道。

核心原因有两点:

  • 近距离token需要高分辨率:两个相邻的词,它们的相对位置差1。如果旋转频率太低,角度变化太小,模型很难区分“我”和“你”谁在前谁在后。
  • 远距离token需要大范围覆盖:相隔1000个位置的token,如果频率太高,旋转角度可能已经转了好几圈,出现“混叠”现象。模型分不清到底是第1000位还是第1001位。

说白了,我们需要一组频率,从低到高排列。低频负责捕捉长距离依赖,高频负责捕捉短距离细节。

核心思想:RoPE使用一组等比数列的频率,让不同维度以不同速度旋转。这样,模型可以同时感知“近”和“远”的位置关系。

频率设计公式

好,我们直接看公式。在RoPE中,第i对维度的旋转频率是这样定义的:

θ_i = base^(-2i/d)

其中:

  • d 是隐藏层维度
  • i 从0到d/2-1
  • base 是一个超参数,通常取10000

嗯,这里要注意。这个公式其实借鉴了Transformer里Sinusoidal位置编码的设计。但RoPE把它用在了旋转矩阵上,而不是直接加到embedding里。

举个例子,假设d=4,那么我们有2对维度(i=0和i=1):

维度对 i 频率 θ_i 物理含义
0 10000^(0) = 1 低频,旋转最慢,适合长距离
1 10000^(-2/4) ≈ 0.01 高频,旋转较快,适合短距离

你看,i=0时频率是1,意味着每移动一个位置,旋转角度增加1弧度。i=1时频率是0.01,每移动一个位置只转0.01弧度。这样,不同维度对位置变化的敏感度就不同了。

我的个人习惯:在实际项目中,我通常把base设为10000。但如果处理超长文本(比如16K以上),我会适当增大base,比如100000。这样低频维度旋转更慢,能覆盖更长的距离。

频率如何区分远近?

我们画一张图来理解。假设有两个token,位置分别是m和n,它们的相对距离是Δ = m - n。

对于第i对维度,旋转角度差是:

Δθ_i = Δ * θ_i

当Δ很小时(比如1或2),高频维度(i较大)的Δθ_i变化明显,模型能清晰感知“谁在前谁在后”。当Δ很大时(比如1000),低频维度(i较小)的Δθ_i变化明显,而高频维度可能已经转了好几圈,出现模糊。

这就是RoPE的精妙之处——不同频率的维度分工协作,共同编码位置信息。

我曾经在项目中遇到过一个问题:模型在短文本上表现很好,但一旦文本长度超过2048,性能就急剧下降。排查后发现,是base设置太小,导致低频维度旋转太快,无法区分远距离位置。把base从10000调到50000后,问题就解决了。

可视化:频率与距离的关系

下面我用SVG画一张图,展示不同频率下,旋转角度随距离的变化趋势。这样你会有更直观的感受。

不同频率下旋转角度 vs 相对距离 相对距离 Δ 0 50 100 150 200 0 π/2 π 3π/2 低频 (i=0) 中频 (i=1) 高频 (i=2) 观察要点 • 低频曲线波动快,能区分远距离 • 高频曲线平缓,适合近距离 • 多频率组合覆盖全距离范围

从图中可以清楚看到:低频(红色)曲线波动剧烈,每增加一点距离,角度就大幅变化。这意味着它对远距离敏感。高频(绿色)曲线几乎是一条直线,角度变化很小,所以它对近距离敏感

实际应用中的调参经验

好,理论讲完了,咱们聊聊实战。我在多个对话系统中用过RoPE,总结出几个调参经验:

避坑指南:我曾经把base设成1000,结果模型在长文本上完全崩溃。原因是频率太高,所有维度都转得太快,远距离位置信息全部混叠。记住,base不能太小,至少10000起步。

具体建议如下:

  1. 短文本场景(512以内):base=10000,d=512或768,直接用默认配置就行。
  2. 中等长度(2048以内):base=10000,d=1024。如果发现长距离依赖不好,可以试试base=20000。
  3. 超长文本(8192以上):我建议base=100000,甚至更大。同时可以考虑把d增大到2048以上,让更多维度参与低频旋转。

你可能会问:为什么增大base就能处理更长文本?

原因很简单。base越大,θ_i = base^(-2i/d) 这个值就越小。所有维度的旋转速度都变慢了,这样在相同距离内,角度变化更小,不会出现“转圈圈”的混叠问题。模型就能区分更远的位置。

代码实现片段

最后,我贴一段我常用的频率初始化代码。你直接拿去用就行:

import torch
import math

def init_rope_frequencies(dim, max_seq_len, base=10000.0):
    """
    初始化RoPE旋转频率
    dim: 隐藏层维度(必须是偶数)
    max_seq_len: 最大序列长度
    base: 基础频率,默认10000
    """
    # 计算每个维度对的频率
    freqs = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
    
    # 生成位置索引
    positions = torch.arange(max_seq_len).float()
    
    # 计算每个位置每个维度的角度
    angles = torch.outer(positions, freqs)
    
    # 返回cos和sin
    cos = torch.cos(angles)
    sin = torch.sin(angles)
    
    return cos, sin

# 使用示例
dim = 768
max_len = 8192
cos, sin = init_rope_frequencies(dim, max_len, base=100000.0)
print(f"频率形状: {cos.shape}")  # [8192, 384]

这段代码里,我特意把base设成了100000,适合处理8K以上的长文本。如果你处理的是短文本,改回10000就行。

一个小技巧:在实际部署时,我会把cos和sin预计算好,存成张量。推理时直接查表,不用每次都算,能省不少时间。

好了,关于高频和低频旋转的设计,我就讲这么多。核心就一句话:用一组等比数列的频率,让不同维度各司其职,共同覆盖从近到远的所有距离。下一节我们会聊如何把RoPE应用到注意力计算中,到时候你会看到这些频率是怎么真正发挥作用的。


专注资料整理