大模型的上下文为什么能越扩越长?RoPE 外推与位置插值,一次讲明白(2026入门)

上一篇文章写位置编码的时候,我在结尾挖了个坑:为什么 Llama 2 只见过 4K 的位置,Llama 3.1 却敢直接标 128K?这篇就是把坑填上的。

先说结论,大模型把上下文从 4K 扩到 128K,靠的不是把训练拉长几十倍这种大力出奇迹的路子,而是在位置编码上动了手脚。常见的两种思路,一种叫位置插值(Position Interpolation,简称 PI),另一种靠调 RoPE 里的一个底数,俗称 NTK 方案。这篇文章把这两件事讲明白。

先复习:RoPE 给每个位置发一把「角度尺」

位置编码那篇里说过,RoPE(旋转位置编码)不另外加向量,它把位置信息直接拧进注意力计算里:把每个词的 query 和 key 向量,按它在文中的位置旋转一个角度。第 0 个词转 0 度,第 1 个词多一点,第 999 个词就转到一个由位置决定的角度。两个词做注意力时,点积只跟它俩的距离有关。

转多快,由一组频率决定,而频率由一个叫 base(底数) 的参数算出来。这个数你在模型的 config.json 里能直接看到,字段名就叫 rope_theta。

问题也出在这。模型训练时只见过 0 到 4095 这样的位置编号(假设窗口 4K)。你让它处理第 10000 个词,等于让它去转一个从没转过的角度。模型没学过这种角度下注意力该怎么表现,输出就开始失控。

硬外推为什么不行:没见过的角度会翻车

最直接的想法是硬来:窗口之外的位置,接着往外编就行了。(要是想先补 Q、K、V 这些概念,可以先看我写注意力机制的那篇,文章在这里。)2023 年之前不少人试过,基本都不行。

原因论文里写得很清楚。Meta 那篇提出位置插值的文章(Extending Context Window of Large Language Models via Positional Interpolation,2023 年 6 月)有一句原话:在训练好的范围之外做外推,可能产生灾难性的高注意力分数,彻底毁掉自注意力机制。他们的理论分析还给了个量级:插值的注意力分数上界,比外推至少小约 600 倍。

翻译成人话:外推是让模型猜没见过的角度,而且注意力分数会爆到离谱;插值是让模型只处理见过的角度,稳。

方案一:位置插值,把新位置压回旧范围

PI 的思路一句话就能说完:窗口要从 2048 扩到 32768,那好,把 0 到 32767 这串新位置编号,除以 16,全部压回 0 到 2048 的老范围里。模型从头到尾只见到训练时见过的位置区间,只是现在每个「位置单位」代表的实际距离被拉长了 16 倍。

代价是模型得重新找找感觉:原来相邻两个位置差一格,现在实际差 16 格。所以 PI 需要一点微调,论文里的设定是用 1000 步就能把 LLaMA 7B 到 65B 的窗口扩到 32768。跟从头预训练比,这点算力几乎可以忽略。

论文原文说得很克制:窗口从最初的 2048 扩到最高 32768,而且在原窗口内的任务上质量「保持得相对较好」。「相对较好」这个措辞你可以品一品,长距离的细节分辨能力多少会有损失,这是这类方法共同的代价。

方案二:调低底数,让高频转慢一点

另一条路不压缩位置编号,改 RoPE 本身,把 base 调大。base 越大,整套旋转的节奏越慢,尤其是那些转得飞快的高频维度会明显慢下来。慢下来之后,同样的窗口长度,模型需要处理的角度范围就小了,外推自然容易一些。这条路线一般叫 NTK 方案,出自 2023 年 Reddit 上的一篇讨论帖,后来被 CodeLlama 这些模型用在了实战里。

别以为这只是一拍脑袋的经验 trick,它后来有了一套理论。2023 年 10 月的论文《Scaling Laws of RoPE-based Extrapolation》从周期的角度解释了这件事:外推能力跟 base 值和微调长度之间有规律可循,还提出了「外推临界维度」的概念来解释外推为什么会失效。结论也实用:他们用 16K 的训练长度,就在 LLaMA2 7B 和 13B 上把外推做到了 100 万 token 的上下文。

所以 CodeLlama 标 100K 窗口不是靠堆训练数据。它那篇论文的摘要写明所有模型按 16k 序列训练、在最长 100k 输入上有提升,背后干的就是把 rope_theta 从 10000 调到 1000000 这件事。

你的 config.json 里全是这些痕迹

这套东西不是纸上谈兵,我拉了几个现役模型的 config,你可以对照着看:

  • CodeLlama-7B-Instruct:rope_theta = 1000000,max_position_embeddings = 16384。一百万的底数,就是上面说的 NTK 路线在产品里的样子。
  • Qwen3-8B:rope_theta = 1000000,窗口 40960。同样用大底数把旋转节奏放慢。
  • Llama-3.1-8B-Instruct:rope_theta = 500000,窗口 131072,另外多了一个 rope_scaling 字段,llama3 类型、factor 8.0。这一项是在 PI 思路上进一步改进的方案(原窗口 8K,高频少动、低频多压),Llama 3 的论文里有完整描述。

三份配置对应三种选择:纯大底数、大底数加适度窗口、大底数加缩放策略。你看,2023 年那两篇论文的思路,到今天还是所有主流模型扩窗口的底层数法。

为什么你本地跑模型得自己动手

API 用户可以跳过这节,服务端已经替你处理好了。本地跑的朋友会遇到一个反直觉的现象:Ollama 官方 FAQ 写着,默认上下文窗口是 4096 token,就算模型卡片上印着 128K,也得你自己调。改法两条路,启动前设环境变量 OLLAMA_CONTEXT_LENGTH,或者运行时用 /set parameter num_ctx 8192 这种方式设。

为什么默认这么小?因为窗口越长,KV 缓存占的显存越大,注意力计算也越慢。之前写 KV 缓存那篇算过一笔账:一个 8B 模型每处理一个 token,KV 缓存就要吃掉大约 144KB 的显存,32K 窗口就是 4.5GB 起。模型能力上够得着 128K,你的显存未必够得着,所以工具链宁可直接给你砍到 4K。

所以这不是 bug,是权衡。想用满长窗口,先看显存,再调参数。

写在最后

串一下这几篇文章的线索:注意力机制决定模型「该看哪里」,位置编码告诉模型「谁前谁后」,今天这篇是窗口的「人有多大胆」问题,KV 缓存则是「地有多大产」的显存账。RAG、长上下文、微调怎么选那篇里说的长上下文路线,底层就是今天这些机制在撑。

下回你在模型介绍页看到「支持 128K 上下文」,应该知道这句话背后是 rope_theta 从 10000 到 1000000 的改动,是把位置压回训练区间的缩放,也可能还有一笔不小的微调账单。宣传页不会写这些,config.json 会。

发表评论