长度外推问题,就是要考虑编码的维度,又要考虑能表示的范围
这是一个在机器学习领域常见问题,包括从线性回归开始就有类似的问题,也就是如何让机器理解和表达训练数据数值范围之外的数据。
1 进制表示到直接外推
进制表示: 假设我们有一个1000以内(不包含1000)的整数N要作为条件输入到模型中,那么要以哪种方式比较好?
- 一维浮点向量输入,然而0~999这涉及到近千的跨度,对基于梯度的优化器来说并不容易优化,缩放到0~1之间也不大好,因为此时相邻的差距从1变成了0.001,模型和优化器都不容易分辨相邻的数字
- 10进制表示直接输入,将整数N以一个三维向量[a,b,c]来输入,a,b,c分别是n的百位、十位、个位。至于如果想要进一步缩小数字的跨度,还可以进一步缩小进制的基数,如使用8进制、6进制甚至2进制,代价是进一步增加输入的维度
直接外推: 如果用三维10进制表示训练了模型,又需要将n上限增加到2000以内,那么此时的输入就是一个四维向量了。然而,原本的模型是针对三维向量设计和训练的,所以新增一个维度后,模型就无法处理了。所以解决方案就是提前预留多几维,训练阶段设为0,推理阶段设为其他数字,这就是外推Extrapolation。但是推理阶段改为其他数字,因为模型对没被训练过的情况不一定具有适应能力,所以直接进行外推通常会导致模型的性能严重下降

2 线性内插到进制转换
线性内插: 外推改为内插Interpolation,就是将2000以内压缩到1000以内
- 比如通过除以2,1749就变成了874.5,然后转为三维向量[8,7,4.5]输入到原来的模型中,从绝对数值来看,新的[7,4,9]实际上对应的是1498,是原本对应的2倍,映射方式不一致;从相对数值来看,原本相邻数字的差距为1,现在是0.5,最后一个维度更加拥挤
- 内插后需要微调训练,以便模型重新适应拥挤的映射关系
- 当处理范围进一步增大时,相邻差异则更小,且集中在个位数,剩下的百位、十位,还是保留了相邻差异为1,内插方法使得不同维度的分布情况不一样,每个维度不对等,模型进一步学习难度也更大

进制转换: 不用新增维度,又能保持相邻差距
- 三个数字的10进制编码可以表示0~999,16进制可以表示0~4095,三维向量就可以覆盖目标范围,代价是每个维度的数字从0~9变为0~15
- 原来训练好的模型已经学会了 875>874,而在16进制下同样有875>874,比较规则是一样的
- 每个维度超过9之后(10~15)模型,由于一般模型也有一定的泛化能力,所以每个维度稍微往外推一些是没问题,可以正常比较,转换进制可能不微调原来模型也有效

3 Positional Interpolation (位置内插)
语言模型通常是用固定的上下文长度进行预训练的,如何通过在相对较少的数据量上进行微调来扩展上下文长度,位置插值将上下文长度扩展到预训练极限之外
RoPE的问题:直接外推会出现比较大的Attention Score
RoPE(相对位置编码) 使用正弦和余弦函数将位置信息嵌入到词汇向量的旋转矩阵中。然而,由于以下原因,RoPE直接外推会导致Attention Score显著增加:
- 正弦和余弦函数的周期性:
- 正弦和余弦函数是周期性的,周期为 2\pi。在训练数据中,位置通常在一个相对较小的范围内(例如,0到512或0到2048),这些位置的编码值会保持在周期的某一部分。
- 当位置超出这个范围时(例如,位置变为3000或3500),编码值会进入正弦和余弦函数的另一个周期。由于这些函数的周期性,这些位置的编码值可能与训练数据中的编码值非常不同,导致模型在计算注意力分数时出现剧烈变化。
- 高频成分的影响:
- 在RoPE编码中,较高维度的编码(即频率较高的正弦和余弦成分)会对较大的位置变化更加敏感。这意味着,随着位置数值的增加,这些高频成分会迅速变化。
- 对于较大的位置值,正弦和余弦函数的值可能会经历快速变化,这种快速变化会导致Attention机制中query和key的点积(即Attention Score)出现显著波动。
关键思想:
- 不进行外推,而是直接将位置索引减小,使得最大位置索引与目标长度大小,即预训练阶段的先前上下文窗口限制相匹配。可以在相邻的整数位置上插值位置编码,毕竟位置编码可以应用在非整数的位置上(而非在训练位置之外进行外推)。
- 下图所示,如果直接使用位置(2048,4096]进行推理,那么因为模型没有见过这一部分的位置,效果会出现灾难性的下降。就可以把[0,4096]这个区间压缩到[0,2048],原先的1就变成了0.5,4096就变成了2048,这就是位置内插法,即将没见过的位置映射到见过的位置。

内插公式:
对于绝对位置 m,缩放变成 \frac{L'}{L} m,L 为原先支持的长度(如2048),L' 为需要扩展的长度(如4096)。计算 query 和 key 的时候,就有 f_{w'}(x_m, m, \theta_d) = f_w(x_m, g(m), \theta_d),定义缩放比例 s = \frac{L}{L'}, g(m) = \frac{m}{s}。
PI之后是否微调:
- PI之后,在没有微调的情况下(在步骤0),模型可以展示出一定的语言建模能力,如扩展到8192上下文窗口的困惑度<20所示(相比之下,直接外推方法导致困惑度>1000)。
- PI之后,经过微调,困惑度(perplexity)迅速改善。在200步时,模型超过了2048上下文窗口大小的原始模型困惑度,表明模型能够有效地使用比预训练设置更长的序列进行语言建模。在1000步时,我们可以看到模型稳步改善,并取得了显著更好的困惑度。

困惑度(Perplexity)是自然语言处理中常用的一个评价指标,用于衡量语言模型的好坏。语言模型Model在测试集数据 T=\{w_1, w_2, …, w_N\} 上的困惑度计算如下:
Perplexity(Model) = \exp\left(-\frac{1}{N} \sum_{i=1}^{N} \log P(w_i | w_1, …, w_{i-1})\right)困惑度越低,说明模型对下一个单词的预测越准确,模型性能越好。
PI的问题:
- 三角函数 \sin(\omega x) 的周期是 T = \frac{2\pi}{\omega}。对应到RoPE里的每个维度 (\sin m\theta_j, \cos m\theta_j),其中 \theta_j = 10000^{-2(j-1)/d}, j \in [1, 2, …, d/2](m 为位置,j 为维度)。
- 计算得到周期为:\frac{m2\pi}{b^{d/2(j-1)}},其中,用 b 表示base,即10000。从周期计算的公式可以知道,针对不同的维度编码 j,每个维度对应的三角函数周期是越来越大的(即对应到低频、高频)。
- 如果插值是针对绝对位置 m,那么对每个维度 j 都同等地生效;但是周期小(高频)维度,插值之后会变得很密集(本来一个周期包含10个值,内插之后能包含20个值),这样高频的维度就变得很拥挤。
4 NTK-aware 插值到 Dynamic NTK插值
4.1 NTK-aware 插值
核心思想
- 高频外推,低频内插。
- 不像PI针对所有维度平均缩放,而是减少对高频区域的缩放和增加对低频区域的缩放,从而将插值压力分散到多个维度。
- 上述公式最后面的是最低频 \frac{m}{\beta_{d/2-1}} ,引入参数 \lambda 变为 \lambda\frac{m}{\beta_{d/2-1}} ,让这一项与内插一致做缩放:
- 上述公式最前面的是最高频 \frac{m}{\beta^0},引入参数 \lambda 变为 \frac{m}{(\lambda\beta)^0}。由于 d_{model} 一般比较大,所以 \lambda 很接近1,即不做缩放等价于外推,从而NTK-aware插值把外推和内插结合起来。
缺点
- 一些维度被轻微外推到超出边界的值,因此使用NTK-aware插值进行微调的结果有可能不如PI。
- 此外,由于存在“越界”值,理论尺度因子s并不能准确描述真实的上下文扩展尺度。在实践中,对于给定的上下文长度扩展,尺度值s必须设置得高于预期尺度。
4.2 NTK-by-parts 插值
NTK-by-parts插值考虑了波长与上下文长度的关系。
波长: 维度 j 上嵌入的RoPE执行完整旋转 2\pi 所需要的token长度 \lambda_j 定义如下:
\lambda_j = \frac{\theta_j}{2\pi} = 2\pi b^{d/2(j-1)}
其中,\theta_j 是维度j上的角度参数,b 是基础,d 是模型维度。PI和NTK-aware插值不关心波长的维数。
存在的问题:
- 一些维度的波长长于预训练期间看到的最大上下文长度 \lambda > L,这表明一些维度的嵌入可能在旋转域中不均匀分布。
- 当波长很长时,这些维度上的嵌入几乎不变,可以认为它们保持了绝对位置信息;当波长较短时,嵌入会在较短的距离内完成多次旋转,这使得这些维度上的嵌入反映的是相对位置信息。
- 使用比例s去对所有维度进行缩放的时候,损害了LLM理解其内部嵌入之间的小型和局部关系的能力,导致模型在邻近标记的位置顺序上被混淆。
解决方法:
- 如果波长 \lambda 比上下文长度 L 小得多,此时不插值;
- 如果波长 \lambda 等于或大于上下文长度 L,此时只做插值,不做任何外推;
- 两者之间的维数可以兼备。
引入比率 r(j) 和边界参数 \alpha, \beta:
r(j) = \frac{\lambda_j}{L} = \frac{2\pi b^{d/2(j-1)}}{L}
定义斜坡函数 \gamma(r):
最终,NTK-by-parts方法的公式如下:
g(m) = m
h(\theta_j) = (1 - \gamma(r(j))) s\theta_j + \gamma(r(j))\theta_j
该方法通过调整不同波长维度的处理方式,以更精细地控制模型对不同尺度位置信息的理解。
4.3 Dynamic NTK 插值
Dynamic NTK插值的核心在于动态调整缩放因子 s,以适应不同长度的序列输入。
存在的问题:
在许多用例中,模型需要对从1到最大上下文大小不等的序列长度进行多次前向传递。一个典型的例子是自回归生成,其中序列长度在每一步之后递增1。之前的方法使用固定的缩放因子,这可能导致以下问题:
- 当输入序列长度小于预训练的最大上下文长度 L 时,模型可能出现性能折扣。
- 当序列长度超过原定的上下文长度 L' (> L) 时,可能会出现突然的性能退化。
动态插值方法:
为了克服这些问题,Dynamic NTK插值采用了一个基于当前序列长度动态调整的缩放因子 s:
其中,
- l' 是当前序列的长度,
- L 是预训练期间使用的最大上下文长度。
这种方法允许模型根据实际输入序列的长度灵活调整其内部表示,从而在处理不同长度的序列时保持更稳定的性能表现。通过这种方式,Dynamic NTK插值能够有效缓解由于固定缩放因子导致的性能问题,特别是在处理较短或超出预训练范围的长序列时。
5 YaRN (Yet another RoPE extensioN method)
温度调节与YaRN方法:
无论数据样本和扩展上下文窗口上的token位置如何,在对logits进行softmax操作之前引入温度 t 可以统一地影响困惑度(perplexity)。因此,可以将注意力权重的计算修改为:
其中,
- t 是温度参数,
- d_k 是key向量的维度,
- q_m 和 k_n 分别是query和key向量。
将RoPE嵌入按相同比例缩放,使得query和key都以 \sqrt{\frac{1}{t}} 进行缩放,然后再结合NTK-by-parts方法得到YaRN,推理和训练阶段没有额外开销,因为RoPE嵌入是提前生成的,而且可以重复使用
对于LLaMA1,2推荐: \sqrt{\frac{1}{t}} = 0.1\ln(s)+1