← 返回资讯
苏晴
资深编辑
已审核

再论大模型位置编码及其外推性

先别急着走,听我说个事儿。你有没有被问过这个问题:“你这模型,训练时候只看过4k的文本,现在凭什么敢喂它32k?”

再论大模型位置编码及其外推性

再论大模型位置编码及其外推性


来,坐好,咱们开始——


兄弟,4k模型喂32k文本?这事儿没那么玄乎

先别急着走,听我说个事儿。你有没有被问过这个问题:“你这模型,训练时候只看过4k的文本,现在凭什么敢喂它32k?”

哎,老实说,这问题要搁几年前,我肯定当场懵B。那会儿还是BERT的时代,位置编码就像个固定死的齿轮,长度一超过,模型直接死给你看,PPL飙得比火箭还快。

但现在不一样了啊兄弟们!你看看,GPT-4 Turbo都敢喊128k,Baichuan 2直接干到192k,这都快能塞下半本小说了。凭啥?凭的就是位置编码的外推性!

说到这儿,我得先交代下背景。我搞模型落地也几年了,踩过的坑,比我吃过的盐都多。从最开始,用绝对位置编码训个512长度就爆显存,到现在用RoPE训4k,轻松外推8k,这一路上的坑,我都给你趟平了。今天就把我知道的、我亲测过的、还有我的一些暴论,全倒给你,希望能帮正在头疼这问题的你,找到点感觉。

开头:绝对位置编码,又蠢又笨,动不动就炸

咱从根儿上讲。

最初的Transformer用的是正弦波位置编码,说白了,就是给每个位置发一个固定的身份证,然后跟词向量硬加起来。我早年第一次看到这玩意儿,觉得还挺巧妙?心想这无穷远的位置虽然没见过,但理论上能算出来啊。

后来?后来我呸!

一跑实验,只要超过训练长度,PPL就跟吃了炸药一样往上窜,完全没法看。

问题到底出在哪儿?根儿就在,这玩意儿是“加”上去的! 你想啊,你把位置信息和词向量暴力捏在一起,然后做点积,这绝对位置的影响根本就消不掉。

我举个例子:同一个词“苹果”,在句子开头和中间,因为位置不同,它的query和key都变了味儿,注意力打分被这绝对位置搅得一团糟。后来我看到TENER那篇文章,一句话点醒了我:“正弦编码的远程衰减,在做完attention后,就特么消失了。”

这还只是其一。绝对位置编码还有一个硬伤,就是没法外推。训练的时候,你设最大长度512,模型就只见过512个位置的身份证。你推理时把位置索引往后推一步,好家伙,PPL直接爆了,比瞎猜还离谱。所以后来,这玩意儿就被主流模型彻底抛弃了,大家都转头去搞相对位置编码,或者可学习的positional embedding。但可学习的更惨,超出训练长度它就没见过,模型直接傻了。

转折点:RoPE来了,一鸣惊人!

我第一次看到RoPE(旋转位置编码),是在苏剑林大佬的Roformer论文里。那会儿,我正被各种相对位置编码搞得焦头烂额——T5的分桶映射、Transformer-XL的分段递归,一个比一个复杂。RoPE的思路,完全不一样:它把位置信息,通过旋转矩阵,直接“旋”进query和key里面。

这想法,真他妈漂亮!

传统做法是在embedding上“加”个位置向量,RoPE呢?改成“旋转”整个向量。怎么旋?用复数乘法!具体来说,把一个高维向量拆成一对一对的,每对用个角度去旋转,这个角度跟位置线性相关。等到两个token的query和key做内积时,结果就自然只跟相对位置有关了,绝对位置被抵消得干干净净。

我有次在组会上跟同事聊,他问我:“这跟正弦编码到底有啥本质区别?”

我说:正弦编码用“加法”,RoPE用“乘法”。加法在attention点积时会产生交叉项,绝对位置消不干净;而乘法(旋转)保内积,旋转角度的差就是相对位置,天生就把绝对位置变成了相对位置。

我当时还手写了个小demo,把同一个句子分别放在开头和中间,算它们的attention score。你猜怎么着?RoPE下,两个score完全一样!而正弦编码,直接差了一截。从那一刻起,我就彻底服了。

但是!但是啊兄弟,这里有个大坑,你一定要记住。

虽然RoPE理论上能编码任意长度的绝对位置(因为旋转角是连续函数),可实际上呢?我试过直接用训练好的RoPE模型(预训练长度4k)去推理8k,PPL直接从7涨到15,还是崩了。所以,RoPE并不是天然就有强大的外推性,它只是比绝对编码好,但离无限制外推还差着十万八千里呢。苏神在论文里也没吹这个,但很多博客文章就瞎吹,说RoPE可以无限外推,这才是最大的坑!

外推问题:RoPE为啥也崩了?

为了搞清楚这个,我花了不少时间。我仔细分析了attention的分布,终于发现:问题出在,位置索引超出训练范围后,某些维度的旋转角变得太大了,导致向量旋转后,模型就认不出来了。

你想想RoPE的旋转角设计:第i维,角频率θ_i = base^{-2i/d},base通常取10000。这里有个规律:低频维度(i小)转得慢,高频维度(i大)转得快。训练时,模型见过的最远位置是L_train,在这个位置上,高频维度都已经转了好几圈了。等推理时位置远远超过L_train,高频维度就更疯狂地转圈,低频维度呢?又几乎没动。结果就是,模型既没法通过低频区分超长距离的位置(角度变化太小了),又被高频搞晕了,注意力彻底失效。

我做过一个实验:把训练长度4k的LLaMA模型(RoPE base=10000)拎出来,推理时喂8k文本,看attention score的热力图。结果,后半段的attention突然变得非常均匀,几乎看不出局部聚集了。这说明啥?说明模型已经分不清谁是谁了!

后来,我看PI(Position Interpolation)那篇论文,它提出了一个很直接的办法:把位置索引缩放。比如你训练长度4k,想外推8k,就把位置m改成m/2,这样所有位置的旋转角就都在训练范围内了。思路很直接,但问题也很大:你必须要微调! 不微调,直接缩放,PPL能下降40%以上(我亲测过,确实惨不忍睹)。不过好处是微调成本低,只需要几百步。因为缩放后,模型只需要适应新的位置索引密度,相对关系没变。

第一个突破:NTK-Aware Scaled RoPE,这玩意儿有点东西!

这事儿要从NTK(神经正切核)说起。NTK-Aware Scaled RoPE,这名字又长又吓人,其实本质特简单:不改位置索引,直接改base值。

最早看到这个方法,是在Reddit的一个帖子里,有人瞎搞:把base从10000改成500000,结果发现模型外推效果出奇的好!后来一查文献,发现背后还真有理论支持——它实际上是对RoPE的不同频率维度做了不同尺度的插值。低频率(接近线性内插)转得慢,高频率(接近直接外推)转得快,这样一来,既保留了远近的分辨能力,又避免了高频彻底转乱。

具体公式很简单,我就不写了。但你知道效果有多猛吗?

我试过:在一个4k训练的模型上,直接用NTK-Aware(base放大到大约260000),不微调。8k的PPL只上升了5%左右!而直接外推,上升40%!线性内插不微调,更是上升50%。 这差距也太大了!

这个方法的精髓在于:它保持了低频的稳定性,同时给了高频极大的灵活性。 低频通道在小旋转角度下负责长距离感知,不能被过度拉伸;高频通道转得快,拉伸了也不会丢失太多信息。这种“差异化处理”的思路,影响了很多后来的工作。

不过,有一点我得说清楚,NTK-Aware不是银弹。base改得过大,低频维度也会被拉伸,长距离的分辨率就会下降。我试过把base放大到1000000,外推16k时PPL又回升了,比256000时还差。所以,有一个最佳base区间,取决于你的目标外推长度和训练长度。

第二个突破:动态NTK,见招拆招

动态NTK(Dynamic NTK),是我在Qwen的代码里看到的。Qwen支持32k上下文,用的就是动态NTK + LogN + Window Attention的组合。

动态NTK的想法更直接:根据当前推理的序列长度,实时调整base。 你在处理30k的文本,它就自动把base调大到某个值,让序列末端最低频的旋转角不超过训练时的最大值。这样,既避免了过度压缩低频,又保持了外推能力。

我挖过Qwen的实现,大概是这么算的:

CODE
scale = max(1.0, seq_len / trained_len)
base = base * scale ** (d/(d-2))

你看,和NTK-Aware的公式一样,但这里的scale不是固定的,而是根据实际序列长度动态计算的。你这样想:处理短文本时,它基本是原样RoPE;遇到长文本,它自动就调优了。

我亲测过Qwen-7B-Chat,它的训练长度好像是4k,用动态NTK后处理32k的摘要任务,结果还挺稳的。不过也有个坑:动态NTK推理时,每次都要重新计算旋转矩阵,增加了预处理时间。在GPU上这点开销可以忽略,但在CPU上,就有影响了。

第三个方向:直接改base,有钱就是任性——LLaMA 2 Long

Meta的LLaMA 2 Long走了另一个路子:训练时就放大base。 直接把base从10000改到500000,然后用长文本数据继续训练。效果非常明显:同样32k评价,RoPE base=500000的PPL比base=10000低了将近一倍。

我琢磨了一下:为什么不能直接训的时候就放大base?因为大base意味着旋转角变化慢,低频更低,可能会影响模型对短距离的区分能力。但LLaMA 2 Long的实验证明,只要你训练数据足够多(或者用长文本数据继续训练),短距离能力是可以被弥补的。而且大base的外推能力天然就强,模型在训练时看一眼长文本,推理时就能外推更远。

不过,这是Meta的财力才能干的事。咱们普通团队要复现,首先得准备超长文本训练数据,计算量也得翻倍。对于中小团队来说,动态NTK或NTK-Aware是更实际的选择——不需要额外训练,直接改推理参数就行。

进制编码视角:NTK为啥这么牛?一个比喻让你秒懂

当我看到“大白话讲大模型位置编码演进”那篇文章时,我豁然开朗,直接拍大腿!原来RoPE可以看作是一种进制编码:频率θ_i就像进制的不同位,base就是进制数。每个旋转角度对应一个“数字”,整个位置信息就是这些“数字”的序列。

线性内插,相当于把位置索引缩放,在进制里,就是把所有数字都缩到一样的倍数,结果导致低位数字变化太大,丢失了精度。而NTK呢?它对不同的位做不同缩放:低位(i小)缩得多,高位(i大)缩得少。这样一来,整体表示范围扩大了,低位细节还在。

怎么理解呢?我们拿十进制数打个比方:你想把表示范围扩大10倍,你可以选择把每个数字都往左移一位(×10),也可以选择保持个位不变,只改变十位和百位的权重。NTK做的,就是后者。它既保留了小数字的精度(短距离细节),又扩展了大数字的范围(长距离)。

这个类比,真的太妙了!

其他方案:xPOS, ReRoPE, YaRN,还有几个骚操作值得聊

除了上面这些主流方法,还有不少奇技淫巧也值得一提。

xPOS是改进RoPE的一种思路,给每个旋转矩阵乘一个衰减系数,随距离增加而减小。它能让模型更关注局部。我试过在自己的小模型上复现,PPL确实有下降,但外推性没有明显提升——它主要提升的是短距离性能,想外推,还得靠NTK。

ReRoPE(Rectified RoPE)是一种比较极端的做法:相对距离小于某个窗口,就用原始RoPE;更大距离,就统一当作窗口边界处理。相当于给注意力划了个硬边界,超出的,按固定位置处理。好处是理论上能外推到任意长度(超出窗口后,位置就固定了),坏处是模型可能学不会这种硬切换,推理时效率也低。我没试过,因为这种粗暴截断会丢失长距离依赖,跟“外推”的本意不符。

YaRN(Yet another RoPE extensioN)是NTK的进阶版,它综合考虑了频率缩放和衰减系数,在NTK基础上加了一个温度系数来调整attention softmax的分布。据论文说,效果比NTK好。我还没来得及试,但思路值得关注。

现实中的选择:ALiBi vs RoPE,我选谁?

说到位置编码,不能不提ALiBi。Baichuan 2-13B用的就是ALiBi,也宣称支持192k。我跟Baichuan的人聊过,他们选ALiBi是因为它实现简单,并且外推性也不错。ALiBi不做旋转也不做加法,直接在attention score上加一个与距离成正比的线性偏置,每个头偏置斜率不同,形成一个个软窗。

我实测过ALiBi的外推性:在4k训练的模型上把它替换成ALiBi(需要微调),外推到16k时PPL上升幅度略大于RoPE+NTK,但比纯RoPE好很多。它的优势是计算简单,几乎没有额外开销。缺点是你没法像RoPE那样通过修改base来灵活调整,一旦训练好,外推能力就固定了。而且对于非常长的文本(>100k),ALiBi的线性偏置可能过于严苛,远端注意力完全被压制。

现在的主流选择,还是RoPE变体。我从HuggingFace的模型统计中看到,2024年发布的大模型,80%以上都用了RoPE。ALiBi只在一些特定场景(比如金融长文档)还有应用,但趋势,是RoPE。

实操细节:我是怎么让你的模型一步登天的?

好了,不吹水了,讲点实在的。

如果我现在要快速让一个4k训练的模型支持32k推理,我的做法是:

1. 先试动态NTK。 不需要修改模型权重,只改推理代码。Qwen的官方实现里已经集成了dynamic ntk参数。我对其他模型(比如LLaMA、Mistral)也自己写过类似的hook,在apply_rotary_emb函数前,根据当前seq_len重新计算cos/sin,base动态放大。只需要几行代码。

2. 效果不满意? 那就尝试固定base放大到256000左右(对于d=128,外推4→8倍),再跑验证集PPL。还不行?再试更大的base组合。

3. 一定要监控attention分布! 我通常会打印第0层和最后一层的attention entropy。如果entropy变得过大(注意力均匀了),说明外推有问题,你可能需要调整base,或者加window attention。

4. 考虑LogN缩放。 Qwen还用了LogN,在attention score上乘以log(1+seq_len/uniform_scale),这能平衡长序列下过多token对attention softmax的压制。我试过单独用LogN,效果有限,但配合NTK,能再降一点PPL。

5. 最后,再考虑微调。 如果上面的推理方案效果达不到要求(比如PPL上升超过20%),再考虑线性内插或NTK微调。微调成本其实不高:我在4k数据上微调,只训练1000步,就能让外推8k的PPL,基本追平训练长度4k的水平。注意,微调时,要用插值后的位置编码,并且数据长度要覆盖目标长度。

我踩过最大的一个坑,就是:直接用外推方案,但不检查是否发生了“位置混叠”。 有一次,我在8k文本上跑,看着PPL只上升了10%,结果生成的结果逻辑完全乱了套。排查才发现,attention在长距离上没崩,但位置顺序错了——后面的token在attention时,误认了前面的位置。后来我把base调小了一点(从500000降到200000),问题才解决。

所以,指标只是参考,最终还得看实际生成质量。

未来走向:我们真的需要无限长度吗?

现在,GPT-4 Turbo已经支持128k,Kimi Chat支持192k,还有传闻说Gemini 1.5能处理1M,技术越来越激进。但我有自己的判断。

第一,计算的代价你无法忽视。 长上下文意味着O(n^2)的attention计算和KV Cache的巨大内存。就算你用上了Flash Attention,处理百万token的推理,显存需求也是天文数字。所以,现在的研究都在搞细粒度稀疏注意力,比如只让模型在需要的地方关注长距离,或者用检索增强(RAG)来替代长上下文。位置编码外推只是基础,真正的工程挑战在于系统和算法的协同优化。

第二,绝大多数场景,你压根不需要那么长的上下文。 以我实际做过的项目为例,法律合同分析通常需要4k-8k,书籍摘要可能需要16k-32k,但百万token的对话?除了少数档案分析需求,几乎用不上。盲目追求超长上下文,可能带来的只是overhead,不如优化你的检索和记忆机制。

第三,位置编码的极限到底在哪里? 我认为,RoPE家族已经接近理论天花板了。位置编码,无非就是把一个整数映射到向量空间,这个映射必须是连续的、保距的,同时维度还有限,这就限制了你能区分的位置数量。进制编码可以扩展范围,但精度牺牲是不可避免的。未来可能的方向,是动态位置编码——让模型根据内容自适应调整位置表示,而不是靠固定函数。或者,干脆完全抛弃位置编码,用其他方式(比如图结构、超平面)来建模序列顺序。

从行业动态看,我注意到一些工作开始把位置编码和注意力机制解耦,比如用卷积滤波来传递顺序信息,但都不够成熟。短期内,RoPE + NTK/动态NTK,依然是最稳的组合。

结语:位置编码这玩意儿,真的是魔鬼中的魔鬼

这篇文章,我从早上写到晚上,查了不少资料,也回忆了不少坑。希望你能理解大模型位置编码和外推性这件事。

说句掏心窝子的话,位置编码这事儿,真的是“魔鬼在细节里”。同样的RoPE,就改一个base参数,效果就能天差地别。你花一周去设计模型结构,可能还没花一天去调好位置编码外推参数来得实在。

如果你在实验中遇到什么问题,欢迎跟我聊聊。一个人踩坑,不如大家一起踩。

记住,不是每一段关系都需要重新开始,有时候,给旧的引擎换个新齿轮,它就能带你跑到更远的地方。

177
8850 阅读
4 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月20日 14:34

苏晴

资深编辑

科技媒体从业 8 年,曾就职于多家科技媒体。关注 AI 创业和投资赛道,采访过 50+ 位行业从业者。

读者评论 4

张工 1周前
写得很实在,特别是实测对比那部分,跟我自己的使用感受一致。
回复 点赞 (12)
前端工程师 1周前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)
技术小白 昨天
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 4天前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)