1. 深入理解旋转位置编码(RoPE)
随着像 GPT-4、Llama、Mistral 等模型的涌现,我们对它们的能力叹为观止。但一个长久以来的痛点始终存在:它们能处理的文本长度是有限的。这个限制,我们称之为“上下文窗口”(Context Window)。上下文窗口决定了模型在一次推理中能“记住”的最大文本长度。
扩展上下文窗口,意味着模型可以“读”得更长,“记”得更多,从而能够处理更长的文档、进行更复杂的对话、编写更完整的代码。而 YaRN (Yet another RoPE extensioN method) 就是一个非常亮眼的解决方案,它能够在很少的微调(甚至零微调)下,将 Llama 等模型的上下文窗口扩展数倍,同时保持很高的性能。
要理解 YaRN,我们必须先深入了解它所改进的核心技术:**旋转位置编码(Rotary Position Embedding, RoPE)
1.1 深入理解旋转位置编码(RoPE)
之前有文章简单介绍过旋转位置编码(RoPE)《transformer(三)旋转位置编码RoPE详解》
由于YaRN是基于RoPE的,必须先讲RoPE的原理,才能理解YaRN的扩展上下文窗口的原理。
RoPE 的简单引入
相对位置编码它的核心思想是,在计算注意力时,只关心 query token 和 key token 之间的相对距离。而 RoPE 就是其中最优雅和高效的实现之一。
而RoPE 的思想非常巧妙。它没有直接将位置信息“加”到词嵌入上,而是通过旋转 query (q) 和 key (k) 向量来注入位置信息。
定义 qm,km,vmq_m,k_m,v_mqm,km,vm 分别为 query, key, value 向量在位置 mmm 的表示
用 RmR_mRm 表示位置 mmm 处的旋转矩阵,讲旋转矩阵嵌入到 q,kq,kq,k 中,计算注意力分数时
(Rmq)T(Rnk)=qTRn−mk(R_m q)^T(R_n k)=q^T R_{n-m} k(Rmq)T(Rnk)=qTRn−mk
内积的结果中,query qmq_mqm 被一个只依赖于相对位置 n−mn-mn−m 的旋转矩阵 Rn−mR_{n-m}Rn−m 变换后,再与 key knk_nkn 进行内积。这就完美地将相对位置信息融入了注意力计算中。
1.2 完整的旋转矩阵
之前写的RmR_mRm 表示位置 mmm 处的旋转矩阵,其实里面还有个θ\thetaθ,表示旋转的角度。这个角度是根据位置 mmm 计算得到的,这个角度是不需要学习的,根据具体公式计算即可:
θk=b−2k/d\theta_k = b^{-2k/d}θk=b−2k/d
其中 b=10000b=10000b=10000,ddd 是模型的维度(如1024维),kkk 是维度的索引。
这个角度的计算,确保了不同位置之间的旋转角度是不同的,从而能够注入不同的位置信息。
完整的第m个位置(m是上下文的位置索引,上下文长度可能是32k)上的RoPE矩阵应该为
Rθ,m=[cos(mθ1)−sin(mθ1)00...00sin(mθ1)cos(mθ1)00...0000cos(mθ2)−sin(mθ2)...0000sin(mθ2)cos(mθ2)...00.....................0000...cos(mθd/2)−sin(mθd/2)0000...sin(mθd/2)cos(mθd/2)] R_{\theta, m}=\begin{bmatrix} \cos(m\theta_1) & -\sin(m\theta_1) & 0 & 0 & ... & 0 & 0 \\ \sin(m\theta_1) & \cos(m\theta_1) & 0 & 0 & ... & 0 & 0 \\ 0 & 0 & \cos(m\theta_2) & -\sin(m\theta_2) & ... & 0 & 0 \\ 0 & 0 & \sin(m\theta_2) & \cos(m\theta_2) & ... & 0 & 0 \\ ... & ... & ... & ... & ... & ... & ... \\ 0 & 0 & 0 & 0 & ... & \cos(m\theta_{d/2}) & -\sin(m\theta_{d/2}) \\ 0 & 0 & 0 & 0 & ... & \sin(m\theta_{d/2}) & \cos(m\theta_{d/2}) \\ \end{bmatrix} Rθ,m=cos(mθ1)sin(mθ1)00...00−sin(mθ1)cos(mθ1)00...0000cos(mθ2)sin(mθ2)...0000−sin(mθ2)cos(mθ2)...00.....................0000...cos(mθd/2)sin(mθd/2)0000...−sin(mθd/2)cos(mθd/2)
1.3 RoPE的分析
关键是这个角度 θk\theta_kθk 的设计非常精妙,它使得 RoPE 能够在不同维度上编码不同尺度的位置信息:
-
高频信息(感知近距离): 当维度索引 kkk 很小(即在向量的靠前部分)时, θk\theta_kθk 的值很大。这意味着旋转的频率很高。位置 mmm 的微小变化都会导致旋转角度 mθkm\theta_kmθk 的显著改变。这部分维度负责编码精细的、近距离的相对位置信息。模型可以利用这些高频信号来区分紧邻的词。
-
低频信息(感知远距离): 当维度索引 kkk 很大(即在向量的靠后部分)时,θk\theta_kθk 的值很小。这意味着旋转的频率很低。即使位置 mmm 变化很大,旋转角度 mθkm\theta_kmθk 的变化也相对平缓。这部分维度负责编码粗粒度的、远距离的相对位置信息。模型可以利用这些低频信号来判断两个词相距很远。
对于sin(xθ)sin(x\theta)sin(xθ),它的周期(或者叫波长)是2πθ\frac{2\pi}{\theta}θ2π,一个周期就会回到原始角度。
举例:
- 例如中间向量维度是1024,上下文的长度为32k=32768,批量输入transformer的形状就是[batch_size, 32k, 1024]
- 讲旋转矩阵应用于向量上时,对于这个公式是:θk=b−2k/d\theta_k = b^{-2k/d}θk=b−2k/d
- 这个ddd 是模型的维度,即向量的维度就是1024,kkk 是维度的索引,因为每两个值共享一个角度,所以从0开始,到511结束。
- 考虑向量的前两个维度,计算出的角度分别是:θ0=b−2(0)/1024=100000=1\theta_0 = b^{-2(0)/1024} = 10000^0=1θ0=b−2(0)/1024=100000=1,它的周期是2π1=2π\frac{2\pi}{1}=2\pi12π=2π,也就是相对位置的距离超过6个位置,就会回到原始角度。
- 考虑向量的后两个维度,计算出的角度分别是:θ511=b−2(511)/1024=10000−0.9980≈10−4\theta_{511} = b^{-2(511)/1024} = 10000^{-0.9980}\approx 10^{-4}θ511=b−2(511)/1024=10000−0.9980≈10−4,它的周期是2π10−4=2π×104\frac{2\pi}{10^{-4}}=2\pi\times 10^{4}10−42π=2π×104,也就是相对位置的距离超过62.8k个位置,就会回到原始角度。(即使相对距离达到预定义的上下文长度32k,也不会回到原始角度,因为周期太长)
过于高频的信号会导致模型对远距离位置的感知能力下降,周期太短可能过几个位置又回到了和原始角度相近的角度,所以需要非常低频信号来维护距离信息,周期特别长,根本无法走完整个周期,角度不会重复,从而维护了远位置的相对距离信息
通过在不同维度位置上组合使用不同频率的旋转,RoPE 就像是为模型提供了一把“多尺度”的尺子。它既能用高频信息精确测量邻近词语的距离,又能用低频信息感知到远方词语的大致顺序,从而对整个序列的位置关系有一个完整而丰富的表征。这正是 RoPE 优雅而强大的地方。
这样学习的一个效果就是导致q,kq,kq,k 向量在不同维度上的旋转角度不同,从而能够注入不同的位置信息。向量维度较低的位置负责编码精细的、近距离的相对位置信息,而维度较高的位置负责编码粗粒度的、远距离的相对位置信息。
Part 2: 揭秘 YaRN 的扩展魔法
2.1 基本思想
其实扩展上下文窗口最直观的解决方法是:“把长的文本假装成短的”。
这就是位置内插(Position Interpolation, PI的思想。
假设我们要把窗口从 Ltrain=4096L_{train}=4096Ltrain=4096 扩展到 Ltest=32768L_{test}=32768Ltest=32768,扩展倍数 s=327684096=8s = \frac{32768}{4096} = 8s=409632768=8。
我们可以把新的位置索引 mmm 除以 sss,变成 m′=m/sm' = m/sm′=m/s。
这样,原本 0∼327680 \sim 327680∼32768 的位置范围,就被压缩回了 0∼40960 \sim 40960∼4096。
这样做的优点是:所有的位置都在模型“见过”的范围内。
但是缺点是:分辨率下降,特别是在高频部分,因为高频信号的周期很短,强行拉长会导致模型对位置的感知能力下降。
回到 RoPE 的视角,把位置 mmm 变成 m/sm/sm/s,等价于把所有的频率 θk\theta_kθk 都缩小了 sss 倍(波长拉长了 sss 倍)。
即:
θk′=θks=b−2k/ds\theta'_k = \frac{\theta_k}{s} = \frac{b^{-2k/d}}{s}θk′=sθk=sb−2k/d
还记得 RoPE 的高频部分负责近距离识别吗?
如果我们把高频信号也强行拉长,相邻 Token 之间的旋转角度变化就变得非常小,模型可能分不清“谁在前,谁在后”了。这严重损害了模型的微观感知能力。
2.2 NTK 感知内插 (NTK-aware Interpolation)
这时候,NTK (Neural Tangent Kernel) 理论给出了一个更聪明的方向:
不要对所有维度一视同仁!
- 高频维度(低维索引):包含丰富的信息,对分辨率要求极高。它们旋转得很快,即便外推到更远的距离,随机性也比较强,模型反而对外推比较鲁棒。不要去压缩它们!
- 低频维度(高维索引):波长很长,模型没见过完整的周期。这些才是真正需要“内插/压缩”的部分。
NTK-aware 的做法不是直接除以 sss,而是修改 RoPE 的基数 bbb(原为 10000)。通过增大 bbb,可以非线性地改变不同维度的频率:让高频部分保持原样,只把低频部分拉长。
计算公式为:
b′=b⋅sdd−2b' = b \cdot s^{\frac{d}{d-2}}b′=b⋅sd−2d
这样,新的频率 θk′=(b′)−2k/d\theta'_k = (b')^{-2k/d}θk′=(b′)−2k/d 在高频部分变化很小,在低频部分变化较大,完美符合我们的需求。
按照之前的想法,所有的角度缩放为原来的 1/s=18=0.1251/s = \frac{1}{8} = 0.1251/s=81=0.125,所有的角度值都乘以 0.1250.1250.125 从而得到新的角度值。但是现在这个相当于这个比例值是按照位置进行指数下降的,高频位置缩放比例没有那么大,而低频位置缩放比例则是接近 0.1250.1250.125。

2.3 YaRN:集大成者 (Yet another RoPE extensioN)
YaRN 在 NTK-aware 的基础上,做得更加极致和精细。它的核心思想可以概括为:分而治之。
为了精确地实现这个思想,YaRN 引入了两个关键的阈值参数 α\alphaα 和 β\betaβ,用来界定什么是“高频”,什么是“低频”。
我们首先定义每个维度的波长 λ\lambdaλ。回顾一下 RoPE,第 kkk 个维度的旋转频率是 θk\theta_kθk,那么它的波长就是完成一次 2π2\pi2π 旋转所需的长度(即转一圈需要的 Token 数量):
λk=2πθk \lambda_k = \frac{2\pi}{\theta_k} λk=θk2π
我们将波长 λk\lambda_kλk 与模型原本的训练长度 LtrainL_{train}Ltrain 进行比较,看看这个维度在训练时“转了几圈”,以此来判断它属于哪个区域:
-
高频区(High Frequency):如果不插值,模型也能懂
- 判断标准:波长很短,小于一定比例的训练长度。具体来说,λkLtrain<α\frac{\lambda_k}{L_{train}} < \alphaLtrainλk<α。
- 含义:在训练长度 LtrainL_{train}Ltrain 内,这个维度的向量已经旋转了很多圈。模型已经充分见过各种相对位置关系,对它的外推能力很强。
- 策略:不进行插值 (γ=0\gamma = 0γ=0)。保持原汁原味,保留局部的高分辨率。
-
低频区(Low Frequency):必须插值,否则模型没见过
- 判断标准:波长很长,大于一定比例的训练长度。具体来说,λkLtrain>β\frac{\lambda_k}{L_{train}} > \betaLtrainλk>β。
- 含义:这个维度的旋转极慢,在整个训练长度 LtrainL_{train}Ltrain 内,可能只转了一个很小的角度。如果我们把长度扩展到 LtestL_{test}Ltest,旋转角度会超出模型见过的范围(Out-of-Distribution)。
- 策略:完全线性插值 (γ=1\gamma = 1γ=1)。把它“压缩”回模型熟悉的范围内。
-
过渡区(Transition):平滑过渡
- 判断标准:介于两者之间。α≤λkLtrain≤β\alpha \le \frac{\lambda_k}{L_{train}} \le \betaα≤Ltrainλk≤β。
- 策略:混合处理。γ\gammaγ 从 0 平滑过渡到 1。
公式化描述:
我们定义一个斜坡函数 γk\gamma_kγk 来表示插值的强度:
γk={0if λkLtrain<α(高频)1if λkLtrain>β(低频)λkLtrain−αβ−αotherwise(过渡) \gamma_k = \begin{cases} 0 & \text{if } \frac{\lambda_k}{L_{train}} < \alpha \quad (\text{高频}) \\ 1 & \text{if } \frac{\lambda_k}{L_{train}} > \beta \quad (\text{低频}) \\ \frac{\frac{\lambda_k}{L_{train}} - \alpha}{\beta - \alpha} & \text{otherwise} \quad (\text{过渡}) \end{cases} γk=⎩⎨⎧01β−αLtrainλk−αif Ltrainλk<α(高频)if Ltrainλk>β(低频)otherwise(过渡)
最终的频率 θkYaRN\theta_k^{YaRN}θkYaRN 是原始频率 θk\theta_kθk 和插值频率 θk/s\theta_k/sθk/s 的加权混合:
θkYaRN=(1−γk)θk+γkθks \theta_k^{YaRN} = (1 - \gamma_k) \theta_k + \gamma_k \frac{\theta_k}{s} θkYaRN=(1−γk)θk+γksθk
为什么需要过渡区?
如果直接一刀切(只有0和1),在临界点的频率会发生突变,导致模型的性能不稳定。过渡区确保了频率变化是平滑连续的。
通常的参数设置:
在 Llama 2 等模型中,通常设置 α=1\alpha = 1α=1, β=32\beta = 32β=32。这意味着:
- 波长小于训练长度的,完全不插值。
- 波长大于 32 倍训练长度的,完全插值。
- 中间的进行平滑过渡。
2.4 温度缩放
除了上述的频率分离,YaRN 还引入了一个关键细节:温度缩放(Temperature Scaling)。
当上下文变长时,Attention 机制中的 Softmax 分母(通常是 d\sqrt{d}d)会让注意力分布变得“过于平滑”或“过于尖锐”(熵变了)。
YaRN 建议修改 Softmax 的温度参数:
softmax(q⋅kTtd)\text{softmax}(\frac{q \cdot k^T}{t \sqrt{d}})softmax(tdq⋅kT)
其中 ttt 是一个根据扩展倍数计算的系数(通常 t≈0.1ln(s)+1\sqrt{t} \approx 0.1 \ln(s) + 1t≈0.1ln(s)+1)。
这一步简单但至关重要,它修正了长文本下的注意力分布,使得模型不需要微调就能保持原来的“注意力习惯”。
760

被折叠的 条评论
为什么被折叠?



