一、一个看起来只需要调大的数字

每个模型都标着一个数字:上下文窗口,也就是它一次最多能处理多少个 token。几千、几万、十几万、上百万——这个数字变大,似乎就意味着能一次读完一本书、一整个代码仓库。

那为什么不直接把它调到无穷大?

第 9 讲说过,长上下文"是工程难题,不是调个参数就能解决的"。此后第 2、7、8、22 讲又陆续埋下了几条伏笔。这一讲把它们收拢起来:长上下文同时撞上四堵独立的墙,每一堵的来源不同,解法也不同。

在开始之前,先重复第 7 讲的一个提醒:上下文窗口按 token 计,不按字计。同样是"10 万个 token 的窗口",能装下多少字,取决于这门语言在分词器里被切得多碎。

二、打个比方:摊在桌上的文件

你在开会,要根据桌上摊开的文件回答问题。文件从 10 份增加到 1000 份,会发生四件事:

  1. 交叉对照的工作量暴涨。每份文件都可能和其他任何一份相关,要两两对照,10 份是几十对,1000 份就是几十万对。
  2. 桌子放不下。
  3. 你从没在这么多文件面前工作过,平时练习时,最多也就摊开几十份。
  4. 就算都放得下,你也会看漏。开头几份和最后几份记得清楚,中间那一大摞,扫一眼就过去了。

这四件事,对应接下来的四堵墙。

三、第一堵墙:预填充时的平方级计算

打分表有多大

第 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 讲的旧账:全部保留(精确,但代价随长度增长)和压缩进固定状态(代价恒定,但有损)之间是一个真实的权衡。新的架构都在这个权衡里选位置;检索增强是绕开它的另一条路。

思考题

  1. 用第三节的估算,如果模型维度 d 从 4096 变成 8192,注意力开始主导的分界点是多少个 token?这说明更大的模型,在同样长度的上下文上,注意力的相对负担是更重还是更轻?
  2. 用第四节的数字:如果把 K/V 头数减少到四分之一,12.8 万个 token 的缓存是多少?这时每一步读缓存和读 14 GB 权重相比,哪个更多?
  3. 第五节的位置插值把 16,384 压回 4096,用的是"除以 4"。如果想扩展到 65,536,要除以几?相邻 token 的位置差会变成多少?你觉得扩展倍数越大,需要的额外长文本训练会更多还是更少?
  4. 用第六节的公式,取 Δ = 8,算一算 n = 10,000 时相关位置分到的权重。和 Δ = 5 相比提高了多少?这说明了什么?
  5. 第七节说"全部保留"和"压缩进固定状态"是一个真实的权衡。请各举一个任务:一个更适合前者(必须精确回看很久以前的某个细节),一个更适合后者(只需要大致记住前面讲了什么)。