一、一个看起来只需要调大的数字
每个模型都标着一个数字:上下文窗口,也就是它一次最多能处理多少个 token。几千、几万、十几万、上百万——这个数字变大,似乎就意味着能一次读完一本书、一整个代码仓库。
那为什么不直接把它调到无穷大?
第 9 讲说过,长上下文"是工程难题,不是调个参数就能解决的"。此后第 2、7、8、22 讲又陆续埋下了几条伏笔。这一讲把它们收拢起来:长上下文同时撞上四堵独立的墙,每一堵的来源不同,解法也不同。
在开始之前,先重复第 7 讲的一个提醒:上下文窗口按 token 计,不按字计。同样是"10 万个 token 的窗口",能装下多少字,取决于这门语言在分词器里被切得多碎。
二、打个比方:摊在桌上的文件
你在开会,要根据桌上摊开的文件回答问题。文件从 10 份增加到 1000 份,会发生四件事:
- 交叉对照的工作量暴涨。每份文件都可能和其他任何一份相关,要两两对照,10 份是几十对,1000 份就是几十万对。
- 桌子放不下。
- 你从没在这么多文件面前工作过,平时练习时,最多也就摊开几十份。
- 就算都放得下,你也会看漏。开头几份和最后几份记得清楚,中间那一大摞,扫一眼就过去了。
这四件事,对应接下来的四堵墙。
三、第一堵墙:预填充时的平方级计算
打分表有多大
第 9 讲讲过,自注意力要为每一对位置打一个分,n 个 token 就是一张 n × n 的打分表,每一层、每一个头都有一张:
| 上下文长度 | 打分表的格子数 | 相对 1000 个 token |
|---|---|---|
| 1,000 | 100 万 | 1 倍 |
| 4,000 | 1600 万 | 16 倍 |
| 32,000 | 10 亿 | 1024 倍 |
| 128,000 | 164 亿 | 16384 倍 |
长度翻 128 倍,打分表大 16384 倍。这就是第 2、9 讲反复说的"平方级增长"。
平方级什么时候才开始主导
但第 22 讲说过,模型里还有投影矩阵和前馈层,它们的计算量随长度线性增长。平方级的注意力,在长度多少以后才会压过它们?
粗略估算一下每一层里、每一个 token 分摊到的计算。设模型维度是 d:
投影和前馈层: Q、K、V、输出四个投影约 4d²,前馈层(中间宽 4 倍,第 10 讲)约 8d²
合计约 12d²,与上下文长度无关
注意力: Q 要和 n 个 K 打分,约 n·d;再对 n 个 V 加权求和,约 n·d
合计约 2n·d,随上下文长度增长
两者相等时,2n·d = 12d²,也就是 n = 6d。取第 22 讲的示意数字 d = 4096,分界点大约在 24,576 个 token。
⭐ 这个估算说明:几千个 token 的日常对话里,注意力的平方项根本不是主要开销,大头是投影和前馈层;超过几万个 token 之后,平方项开始主导,并且越来越快地压过其他一切。长上下文的计算问题,是在某个长度之后才突然变严重的。
存下这张表:一个可以绕开的问题
如果把 128,000 个 token 的打分表完整存下来,每个数字 2 字节,单是一层的一个头就要约 30 GB。这显然不可行。
幸运的是,这一半问题可以绕开。注意力的计算不需要把整张表同时摆在显存里:可以把 Q、K、V 切成小块,一块一块地算打分、边算边累积 softmax 的归一化结果,最后得到和完整计算完全相同的输出,而任何时刻都只需要存一小块。
⚠️ 关于时效性:这类"分块、不落地整张打分表"的注意力实现在 2022 年前后被系统地提出,此后不断改进,已经成为标配。具体实现会继续演进,但要分清它解决了什么:它把存储从平方级降到了线性,没有减少计算。
n²次打分一次都没少,只是不再需要同时存下它们。
四、第二堵墙:解码时的 KV Cache
第 22 讲给过一个示意数字:每个 token 的 KV Cache 约 0.5 MB。放到长上下文里:
一段 128,000 个 token 的对话: 0.5 MB × 128,000 ≈ 62.5 GB
⭐ 这个数字有两层含义。
第一,放不放得下。 62.5 GB 只是一个用户的缓存,已经远远超过模型权重本身(十几 GB)。第 22 讲说过,KV Cache 的大小决定一台机器能同时服务多少人——一个超长对话,可能就占掉了几十个普通对话的位置。
第二,读不读得完。 第 22 讲的核心结论是:解码阶段受内存带宽限制,每生成一个 token,都要把全部权重读一遍。但还不止——新 token 的注意力要和缓存里所有的 K 打分、对所有的 V 加权求和,所以整个缓存每一步也要读一遍。上下文短时,缓存比权重小得多,可以忽略;到了 128,000 个 token,每一步要读 62.5 GB 的缓存,是读 14 GB 权重的四倍多。长上下文的每一个新 token,都生成得更慢。
第 22 讲讲过的"多个查询头共用一组 K、V"“量化 KV Cache”,都能把这个数字压下来几倍——但它依然随长度线性增长。
五、第三堵墙:没见过的位置
第 8 讲埋过一个伏笔:位置编码方案的选择,决定了模型能不能处理训练时没见过的长度。
- 可学习的位置嵌入:训练时最长见过 4096 个位置,第 4097 号位置的向量从来没被训练过,模型对它一无所知。
- 用公式构造的位置编码(第 8 讲的正弦编码,以及此后更常用的、直接作用在 Q、K 上的旋转式编码):任何位置都能算出一个向量,但模型从没在那么大的位置数值、那么远的相对距离上被训练过,表现依然会明显下降。
第 8 讲的结论是:“位置编码解决的是给不给得出一个位置向量,不完全解决模型有没有学会怎么用它。”
一种办法:把新的长度压回见过的范围
一个直接的思路叫位置插值:模型只在 4096 以内训练过,现在想处理 16,384 个 token,那就把所有位置编号统一除以 4——
原来:位置 0, 1, 2, …, 16383
插值后:位置 0, 0.25, 0.5, …, 4095.75
最大的位置变回了 4095.75,落在模型见过的范围里。代价是:相邻两个 token 的位置差从 1 变成了 0.25,位置变得更"密"了,模型需要适应这种它没见过的精细程度。所以插值之后,通常还要在长文本上再训练一小段。
⭐ 这也对应第 14 讲讲过的多阶段训练:绝大部分预训练在较短的序列上进行(便宜,而且大部分训练文本本来就不长),到后期再专门安排一个长上下文阶段,配合调整过的位置编码,把窗口扩展上去。
六、第四堵墙:放得进去,不等于用得好
前三堵墙都是"能不能处理"的问题,工程手段可以一步步把它们推远。第四堵墙不同:就算算得动、放得下、位置也认得,模型能不能真正用好这么长的上下文?
被公开报告反复观察到的现象
- 中间被遗忘:公开研究中报告过,把关键信息放在一段长上下文的开头或结尾,模型找得到;放在中间,正确率明显下降。这和第二节比方里的第 4 条如出一辙。
- “找得到"不等于"用得好”:常见的长上下文测试,是在大段无关文本里藏一句特定的话,看模型能不能找出来。很多模型能在这类测试上表现很好,但当任务需要综合分散在上下文各处的多条信息、再进行推理时,表现可能差得多。
一个可以手算的原因:注意力被稀释
有一个原因可以直接从第 9 讲的公式里算出来。
softmax 输出的权重总和固定为 1。假设在某一步里,只有一个位置是真正相关的,它的打分比其他每个无关位置都高出 Δ;其余 n − 1 个无关位置打分相同。那么相关位置分到的权重是:
权重 = e^Δ / (e^Δ + (n − 1))
取 Δ = 5(相关位置的打分高出一大截):
上下文长度 n |
相关位置分到的权重 |
|---|---|
| 100 | 60.0% |
| 1,000 | 12.9% |
| 10,000 | 1.5% |
| 100,000 | 0.15% |
⭐ 打分优势一点没变,但上下文每长 10 倍,关键信息分到的注意力就被稀释到约十分之一——因为它要和更多的无关位置分享那个固定为 1 的总量。10 万个 token 时,它只拿到 0.15%,剩下 99.85% 被无关内容分走,加权求和的结果里几乎看不到它。
反过来算:要让相关位置在不同长度下都保住 60% 的权重,Δ 需要多大?
n = 100: Δ ≈ 5.0
n = 1,000: Δ ≈ 7.3
n = 10,000: Δ ≈ 9.6
n = 100,000: Δ ≈ 11.9
上下文每长 10 倍,打分优势就要多出约 2.3。模型必须学会在越来越嘈杂的背景里,把关键信息的打分抬得越来越高——而这正是它在短文本上训练时很少被要求做的事。
另一个原因:训练数据里很少有真正的长距离依赖
第 14 讲说过,模型的能力来自训练数据。绝大多数文本是短的;即便是长文档,大部分句子只和附近几段有关,真正需要把第 1 页和第 300 页联系起来才能理解的情况,本来就少。模型很少在这种任务上得到训练信号,自然也就不擅长。
七、回到第 2 讲的旧账
第 2 讲留下过一句话:RNN 每一步的计算和存储都是常数,处理任意长度的开销只线性增长;Transformer 在"能不能并行"上赢了 RNN,但"这个被甩开的旧账,其实一直没有被真正还清"。
现在可以把账算清了。处理长上下文,有两种根本不同的思路:
全部保留:每个 token 的 K、V 都留在缓存里,新 token 可以精确地回看任何一个旧 token。这就是标准的 Transformer。代价是前面四节讲的全部问题。
压缩进一个固定大小的状态:不管读了多少 token,都只维护一个大小不变的"摘要",每来一个新 token 就更新一次。每一步的计算和存储都是常数,读 100 万个 token 也不会更慢。这就是第 2 讲的 RNN 的思路。
第 2 讲讲过后者的死穴:固定大小的容器,装不下任意长的历史,早期细节必然被挤掉。第 2 讲那张便利贴,大小从来就不够写下一整本书。
⭐ 所以这是一个真实的权衡,不存在免费的答案:想精确回看任意旧 token,就要为每个 token 付存储和计算;想让代价不随长度增长,就要接受信息被有损压缩。
⚠️ 关于时效性:这正是近几年一个活跃的研究方向。有的方案让每个 token 只注意最近的一段窗口(代价按窗口大小计算,但窗口外的内容只能靠层层传递间接获得);有的方案设计了新一代的固定状态模型,通过特殊的数学结构让训练也能并行,从而避开第 2 讲说的 RNN 训练慢的问题;还有的把少数几层完整注意力和大量廉价的层混合使用。具体哪一种会胜出还没有定论,但它们都在上面那个权衡里选位置。
还有一种思路完全绕开了这堵墙:不把所有内容都塞进上下文,只把和当前问题有关的那几段找出来再放进去。这就是第 25 讲要讲的检索增强。
八、四堵墙,一张表
| 来源 | 发生在哪个阶段 | 能不能靠工程推远 | |
|---|---|---|---|
| 平方级计算 | 第 9 讲的 n × n 打分表 |
预填充 | 存储可以降到线性,计算的 n² 省不掉 |
| KV Cache | 第 22 讲的缓存公式 | 解码 | 共用 K/V、量化能压几倍,仍随长度线性增长 |
| 没见过的位置 | 第 8 讲的位置编码 | 训练与推理 | 插值加长文本训练,可以扩展 |
| 用不好 | softmax 稀释 + 训练数据 | 模型能力本身 | 最难,不是工程问题 |
⭐ 这张表就是"不只是调个参数"的完整回答:标称的窗口大小,通常只说明前三堵墙被推到了哪里;第四堵墙决定这个窗口里的内容,有多少真正被用上了。
九、和后面课程的关系
- 第 24 讲(测试时计算的代价):第 19 讲的推理模型要写几千个 token 的推理过程,这些 token 同样占上下文、占 KV Cache、让后续每一步更慢。
- 第 25 讲(RAG):第七节最后提到的思路——只把相关内容放进上下文——是应对长上下文的另一条路,它把"找到相关信息"这件事从注意力里拿出来,交给一个专门的检索系统。
- 第 28 讲(多步推理与规划):Agent 执行一个多步任务时,每一步的工具调用结果都会累积进上下文,很快就会撞上这一讲的四堵墙。
- 第 30 讲(Prompt injection):上下文越长,模型读进来的外部内容越多,其中混进恶意指令的机会也越多。
十、本讲小结
- 上下文窗口按 token 计,调大它同时撞上四堵独立的墙。
- 第一堵墙,平方级计算:打分表
n × n,长度 128 倍、表 16384 倍。粗略估算,注意力在n ≈ 6d(示意数字下约 2.5 万个 token)之后才开始主导。分块计算把存储降到了线性,但n²次打分一次都没少。 - 第二堵墙,KV Cache:示意数字下 12.8 万个 token 约 62.5 GB,超过模型本身;解码时每一步都要把它读一遍,长上下文里读缓存比读权重还慢。
- 第三堵墙,没见过的位置:位置插值把新长度压回见过的范围,配合第 14 讲的多阶段训练,在后期专门做长上下文训练。
- ⭐ 第四堵墙,用不好:中间被遗忘、找得到不等于用得好。手算显示,打分优势不变时,上下文每长 10 倍,关键信息的注意力权重就被稀释约 10 倍;要保住同样的权重,打分优势每 10 倍长度要多约 2.3。训练数据里真正的长距离依赖本来就少。
- ⭐ 第 2 讲的旧账:全部保留(精确,但代价随长度增长)和压缩进固定状态(代价恒定,但有损)之间是一个真实的权衡。新的架构都在这个权衡里选位置;检索增强是绕开它的另一条路。
思考题
- 用第三节的估算,如果模型维度
d从 4096 变成 8192,注意力开始主导的分界点是多少个 token?这说明更大的模型,在同样长度的上下文上,注意力的相对负担是更重还是更轻? - 用第四节的数字:如果把 K/V 头数减少到四分之一,12.8 万个 token 的缓存是多少?这时每一步读缓存和读 14 GB 权重相比,哪个更多?
- 第五节的位置插值把 16,384 压回 4096,用的是"除以 4"。如果想扩展到 65,536,要除以几?相邻 token 的位置差会变成多少?你觉得扩展倍数越大,需要的额外长文本训练会更多还是更少?
- 用第六节的公式,取
Δ = 8,算一算n = 10,000时相关位置分到的权重。和Δ = 5相比提高了多少?这说明了什么? - 第七节说"全部保留"和"压缩进固定状态"是一个真实的权衡。请各举一个任务:一个更适合前者(必须精确回看很久以前的某个细节),一个更适合后者(只需要大致记住前面讲了什么)。