YaRN:Yet another RoPE extensioN——把上下文窗口扩展十倍的工程方案

注意力对长度没有意见,位置编码有意见

有一个事实可能和你的直觉恰好相反:Transformer 的注意力机制本身,对输入长度并没有硬性限制。只要显存够,几十万 token 照样能算。真正让长上下文崩溃的,是位置编码——更具体地说,是位置编码没见过那么大的数字。

AI technology illustration

我最早不是这么理解的。我一度以为上下文窗口是模型的“工作台”:窗口越大,能铺开的文字越多;窗口不够,前面说的话就“被挤出了记忆”。所以我把一个 4k 上下文的模型硬塞给两万字文档时,预期它只是“记不住前面”。直到读懂 RoFormer 论文 里 RoPE 的实现,我才发现它根本不是记不住——它的数学已经崩了。

在模型的世界里,每个 token 都需要一个“坐标”。训练时它只见过 0 到 4096 的坐标,你把文档第 10000 个词放在坐标 10000 上,模型不知道自己站在哪。位置感一旦错乱,注意力的分配自然就乱了。这就是上下文窗口扩展问题的根源,也是 YaRN 想解决的。

RoPE 在做什么:一个用旋转角度写的位置身份证

RoPE(Rotary Position Embedding)是 2021 年在 RoFormer 论文里提出的位置编码方案。LLaMA、Mistral、Falcon 全都用它,它基本就是开源模型的默认位置编码。

它的做法,是把“位置 m”编码成一组旋转角度。词向量的每一对相邻维度组成一个二维旋转块,位置 m 对应的 query 和 key 在参与注意力计算之前,先绕着自己的旋转角度转一下。第 i 个旋转块的角速度是:

θ_i = b^(-2i/d),其中 b 默认取 10000,i = 0,1,...,d/2-1

i 越小,转得越快;i 越大,转得越慢。所以 RoPE 的维度天然分成两类:高频维度(波长短)负责分辨相邻位置的细粒度信息,低频维度(波长长)负责感知全局的相对位置。

这个设计有个漂亮的数学性质:位置 m 的 query 和位置 m+n 的 key 做点积,结果恰好只和相对距离 n 有关。模型不需要知道“你是第 5 个词还是第 105 个词”,只需要知道“你俩隔了几个词”。

为什么直接外推会崩:秒针没事,时针不行

问题出在训练和推理的长度差异上。训练时模型见过的最长上下文是 L_train,任意两个位置的最大距离不会超过 L_train,所以每个维度见过的最大旋转角度是 L_train · θ_i。推理时你给了它远大于 L_train 的序列,位置差 n 一旦超过 L_train,旋转角度就进入训练时从未见过的区域。

这时,直觉会告诉我们:高频维度转得快,更容易“越界”,应该被插值回来。但 YaRN 论文 给出了相反的判断:

对于波长小于上下文窗口的高频维度,模型在训练中已经见过完整的旋转周期——所有相位都见过,继续外推不过是再转几圈,模型认识这些角度。而对于波长远大于上下文窗口的低频维度,模型从未见过它完成一个完整周期,训练时只展现了一小段弧。让它在更大的角度上运转,等于让模型回答一个它从未见过的坐标。

用钟表来理解最快:秒针 60 秒转一圈,训练时模型看了三个小时的钟表,秒针转过 180 圈,什么相位都见过了;你让它外推下一秒的位置,它毫不含糊。时针 12 小时才转一圈,三个小时的训练里它只走了 90 度;你问它 6 小时后指到哪,它完全不知道。

PI 和 NTK-aware 的一刀切错在哪

在 YaRN 之前,主流方案都没做这个区分。

位置插值(Position Interpolation) 的做法是:把测试时的位置索引整体除以缩放比 s,把超出的角度压缩回训练范围内。它的代价是:所有维度都被一视同仁地压缩。高频维度原本负责精细的“相邻位置”区分,压缩之后相邻位置的夹角差被抹平,短距离位置的分辨率下降。

NTK-aware 缩放 是 YaRN 作者 Bowen Peng 此前提出的改进:不改位置索引,而是把 RoPE 的 base 调大,让所有维度的角速度整体变慢。效果比 PI 好,但它仍然是全局一刀切——该外推的高频维度也被迫改了频率,训练时学到的模式反而被破坏。

方案 做法 根本问题
位置插值 PI 位置索引除以扩展比 s 高频维度被压缩,短距离分辨力下降
NTK-aware 调大 base,全局放慢旋转 全局一刀切,没有区分维度角色
YaRN 高频外推、低频插值,中间平滑过渡 ——

YaRN:拆开维度看,秒针外推、时针插值

YaRN 的完整方案由两部分组成。第一部分叫 NTK-by-parts,它根据每个维度的波长 λ_i 和训练长度 L_train 的关系,把维度分成三类处理:

  1. 高频维度(波长显著小于 L_train):保持原始频率,直接外推。
  2. 中频维度(波长与 L_train 同量级):用斜坡函数在插值和外推之间平滑过渡,避免两类策略切换时产生断裂。
  3. 低频维度(波长显著大于 L_train):使用插值,把旋转角度压缩到训练见过的范围。

核心思想一句话:不是所有维度都该用同一种缩放方式。RoPE 的不同通道在模型里分工不同,处理方式必须跟着分工走。

温度缩放:被忽略的第二块拼图

如果只做 NTK-by-parts,你会遇到一个隐蔽的问题:改了每个维度的旋转频率之后,注意力分数(query 和 key 的点积)的绝对幅值会整体偏移。因为各频率分量的相位关系不再是训练时的那一套,点积的结果自然偏离了原本分布。有研究专门分析过,RoPE 模型的注意力 logits 会随序列长度增长而变大,而 YaRN 的频率调整会进一步加剧这种偏移。

如果不修正,softmax 会顺着异常变大的 logits 让注意力分布变得过于尖锐——模型把注意力集中在少数几个离群的 token 上,输出质量急剧下降。YaRN 的解决方式是:给注意力分数乘一个温度系数,把缩放后的 logits 拉回训练时的尺度。这个系数不需要训练,通过统计训练时 logits 的期望幅值就能估计出来。

它解决了什么,没解决什么

论文在 LLaMA 7B/13B 和 Falcon 7B 上验证了这套方案:只用几千步微调,就把上下文从 2k~4k 扩展到 8k~128k,在困惑度和长文本检索任务上明显优于 PI 与 NTK-aware。更重要的是,扩展后的模型在短文本任务上没有出现明显回退——这一点在当时很多方案做不到。

落地也很快。Hugging Face Transformers 内置了 YaRN,你在 config 里写 rope_type: 'yarn',配合微调就能直接用。这个工作的背景也很有意思:它不是出自大厂实验室,而是 Nous Research 和 EleutherAI 社区几位开发者推动的。从 Reddit 上的一个 idea,到被主流框架官方支持,只用了几个月。

但边界要说清楚。YaRN 解决的是位置编码超出训练范围的问题,它保证了模型在 128k 长度上不“数学崩溃”。但这不等于模型能有效地读 128k 的文档。长文档里信息高度分散,注意力会被无关节摊薄——5 万字里的关键细节,夹在 12 万字的噪音里,模型找不找得到是另一个问题。这也是 LongBench 这类真实长文档评测里,扩展上下文的模型仍会犯错的原因。

我的判断是:YaRN 真正值得被记住的,不是“把上下文窗口翻了十倍”这个数字,而是它展示了一种修改模型的思路——在修改一个机制之前,先理解这个机制内部不同部分的角色差异。这句话听起来像常识,但绝大多数失败的上下文扩展方案,都是因为把一个复杂系统当作整体打了补丁。后来的 LongRoPE 把“分维度处理”的思路进一步精细化,但方法论上的开创,确实是 YaRN 做的。

想看看公式细节?

RoPE 的第 i 个频率通道角速度为 ω_i = b^(-2i/d),波长 λ_i = 2π / ω_i = 2π · b^(2i/d)。NTK-by-parts 计算每个维度的 λ_i 与训练长度 L 的比值,远小于 1 的高频通道缩放因子取 1(外推),远大于 1 的低频通道缩放因子取 1/s(插值),中间区域用线性斜坡从 1 过渡到 1/s。温度系数的估计方法在论文附录里有完整推导。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/728.html

(0)
上一篇 1小时前
下一篇 1小时前

相关推荐