从零开始理解大模型(八):上下文窗口——大模型的"工作记忆"到底有多大?

欢迎阅读「从零开始理解大模型」系列 —— 十篇文章,从"下一个词预测"到完整的大模型心智模型。每篇配可运行代码。
第一篇:一切从"猜下一个词"开始
第八篇:上下文窗口——大模型的"工作记忆" (本文)
第九篇:Scaling Law——为什么"大力出奇迹"有效
第十篇:从大模型到 Agent——下一个词预测如何长出手脚
* 本系列配套运行代码,可在公众号后台回复“大模型”完整获取。
作者:十一
你有没有过这种经历——和 AI 聊得正起劲,它突然对前面说过的事一脸茫然。或者你把一份 3 万字的行业报告丢进去,它礼貌地回你:"抱歉,内容太长,我处理不了。"
它不是失忆了。它只是"工作台"满了。
今天我们来扒一扒大模型的"工作记忆"——上下文窗口到底有多大、为什么不能无限大,以及为什么 128K 听起来很多,用起来却很容易见底。
一、先说结论
| 模型 | 真相 |
| 模型能"记住"所有对话历史 | 模型只能看到窗口内的内容,窗口外的等于不存在 |
| 28K 很大,肯定够用了 | 128K token ≈ 10 万字中文。处理长文档或多轮 Agent 调用时,瞬间见底 |
| 窗口大小可以随意设置 | 受限于计算量(n²)和显存(KV Cache)的物理极限 |
| 只要能"装下"就能"读懂" | 存在 Lost in the Middle 现象,中间的信息最容易被漏掉 |
核心定义:上下文窗口 = 输入 token 数 + 输出 token 数 ≤ 窗口上限
它就像模型的一次性"工作台"——台子越大,能同时摆放的参考资料越多,但台子不是无限大的。
窗口大小一览
以下为各模型发布时的代表性规格,部分模型后续版本已扩展:
| 模型 | 上下文窗口 | 大约能装多少中文 |
| GPT-2(2019 | 1,024 | 约 500 字 |
| LLaMA 2(2023 | 4,096 | 约 2,000 字 |
| GPT-4o(2024) | 128,000 | 约 10 万字 |
| Claude Opus 4.7 | 1,000,000 | 约 70 万字 |
| Gemini 3.1 Pro(2026) | 1,000,000 | 约 70 万字 |
从 GPT-2 的 1K 到 2026 年旗舰模型的 1M,窗口大了 1000 倍。进步巨大,但代价也巨大。
注意:输入 + 输出一起算。你塞进去的内容越多,留给模型回答的空间就越少。别以为 128K 窗口就能随便聊一整天——聊着聊着它就"断片"了。
二、为什么不能无限大?(三个致命瓶颈)
2.1 Attention 的 n² 问题
第四篇讲过,Attention 让每个词和**所有词**算相关度。5 个词之间需要 25 条连线,10,000 个词之间就需要 1 亿条。
Attention 计算量 ∝ n² 1K tokens: 1K × 1K = 1M 次计算 4K tokens: 4K × 4K = 16M 次计算 ← 长度×4,计算量×16 128K tokens: 128K × 128K = 16,384M 次计算 ← 长度×128,计算量×16384
长度翻 4 倍,计算量翻 16 倍。 这是上下文窗口最硬的天花板。
2.2 KV Cache 把显存撑爆
第七篇算过的账——为了推理快,模型会把之前算过的 K 和 V 缓存起来。问题是缓存大小和上下文长度成正比:
KV Cache = n × 层数 × 头数 × 每头维度 × 2(K和V) × 2字节 LLaMA 7B, 128K tokens: 128,000 × 32 × 32 × 128 × 2 × 2 = 64 GB
模型参数本身只有 14 GB,KV Cache 却要 64 GB——比模型本身还大。一张 80 GB 的 A100 很快就装不下了。
2.3 位置编码的硬上限
第三篇提过,Embedding 不包含位置信息——"狗咬人"和"人咬狗"的向量是一样的。模型靠**位置编码**来区分先后顺序。
GPT-2 的做法最简单:准备一张只有 1024 行的表,每个位置存一个向量。
python position_embedding = model.transformer.wpe.weight print(position_embedding.shape) # [1024, 768] ← 只有 1024 个位置
超过 1024?表里没有对应的向量,模型直接傻眼。上下文窗口被位置编码硬性锁死。
三、RoPE——让位置编码"旋转"起来
为了打破位置编码的页数限制,新一代模型(LLaMA、Qwen、DeepSeek)引入了 RoPE(Rotary Position Embedding,旋转位置编码)。
3.1 核心思路
GPT-2 是"给每个位置发一张身份证"——身份证只印了 1024 张,用完就没了。
RoPE 完全不同:不发身份证,而是让每个 token 根据自己的位置旋转一个角度。
位置 0: 不旋转 位置 1: 旋转一个小角度 θ 位置 2: 旋转 2θ ... 位置 n: 旋转 nθ
两个 token 相遇时,只看旋转角度差多少,就知道"你在我前面几个词"。就像时钟的指针——第 3 秒和第 5 秒的距离永远是 2 秒,不管现在是几点。
公式:RoPE(x, pos) = x · cos(pos · θ) + rotate(x) · sin(pos · θ)
不同维度用不同的旋转频率(θ),就像秒针、分针、时针——转速不同,组合起来能编码极其精细的位置信息。
3.2 为什么 RoPE 能支持更长的上下文
可无限外推:RoPE 是公式,不是表格。位置 1024 和位置 100,000 用同一个公式算,不需要提前"学过"。
只关心相对距离:第 3 个词和第 5 个词距离 2,第 100003 个词和第 100005 个词也距离 2——在 RoPE 看来完全一样。
一键拉伸:通过调整旋转频率参数(NTK-aware scaling),可以像拉皮筋一样把 4K 窗口直接拉到 128K——不需要重新训练。
RoPE 出现后,上下文窗口终于不再被位置编码卡死。真正的瓶颈只剩计算量和显存了。
完整的位置编码对比实验见附件 [context_window.py](./context_window.py)。
四、长上下文的真实雷区:不只是"能不能装下"
就算模型标称 128K 甚至 1M,也不代表里面的内容都能被好好利用。
4.1 "Lost in the Middle"——中间的内容直接被忽略
研究发现:上下文越长,模型对**开头和结尾**关注度最高,**中间**关注度最低,形成经典 U 形曲线:
关注度分布(示意): 开头 ████████████████████ ... ███████ 中间 ████ ← 这里直接"失忆" ... ██████ 结尾 ████████████████████████
你把关键信息埋在第 50,000 个 token 的位置,模型可能直接当没看见。
避坑指南:如果你有重要的关键指令(比如"请基于以上内容给出结论"),一定要放在开头或结尾**,千万别埋在中间。
4.2 窗口标称值 ≠ 有效推理长度
处理 1,000 字时的逻辑能力,通常远高于处理 100 万字时。
就像你同时处理 3 件事和 300 件事——脑子够大全装得下,但注意力被彻底稀释了,推理质量直线下降。
五、扩展上下文的常见方案
上下文不够用?几种常见的解决思路:
5.1 上下文压缩
让 LLM 定期对长历史做摘要,把几千 token 压缩成几百 token。牺牲细节,但能装更多轮对话。
Agent 系列第六篇讲过——Agent 执行很多步之后,上下文塞满了历史消息和工具返回结果,用一次摘要把 10,000 字压成 500 字精华。
5.2 滑动窗口注意力
每个 token 不和所有历史 token 做 Attention,只和最近的 W 个 token 做:
标准 Attention: 每个 token 看所有 token → O(n²) 滑动窗口: 每个 token 只看最近 W 个 → O(n × W)
W 通常设为 4096 或 8192。远距离信息看不到,但大部分语言理解其实只需要局部上下文。
5.3 稀疏注意力
每隔几十个 token 设一个"锚点",锚点之间全连接,普通 token 只看附近。远近兼顾。
六、代码实验:亲手感受"记忆"的边界
配套代码 [context_window.py](./context_window.py) 包含三组实验:
实验一:位置编码的"距离感"
python
pos_embed = model.transformer.wpe.weight.detach()
for dist in [1, 10, 100, 1023]:
sim = cosine_similarity(pos_embed[0], pos_embed[dist])
print(f" 位置 0 vs 位置 {dist:>4}: 相似度 {sim:.3f}")
```
```
位置 0 vs 位置 1: 相似度 0.862 ← 紧挨着,很相似
位置 0 vs 位置 10: 相似度 0.651
位置 0 vs 位置 100: 相似度 0.312
位置 0 vs 位置 1023: 相似度 0.095 ← 最远,差异最大
实验二:越界崩溃
python
too_long = tokenizer.encode("hello " * 1200, return_tensors="pt")
model(too_long) # → IndexError! 位置编码表只有 1024 行,直接炸了
实验三:RoPE 模拟
验证"相对位置"特性——同样距离 1 的两个 token,不管绝对位置是 0-1 还是 100000-100001,RoPE 算出的相关度几乎相同。
完整代码见附件 [context_window.py](./context_window.py)。
七、上下文窗口和你的实际使用
为什么和 ChatGPT 聊久了它会"忘事"?不是忘了,是窗口满了。最早的消息被截掉——模型根本看不见了。
几个实用建议:
算好 token 预算。中文大约 1 个汉字 ≈ 1-2 个 token(第二篇讲过)。128K 看着大,一篇万字长文就吃掉 15K-20K token。
重要信息放两头。避开 U 形曲线的底部——开头和结尾关注度最高。
主动压缩。 在构建长对话系统或 Agent 时,学会让模型定期做摘要,把冗长的历史压缩成精华。
Agent 场景最危险。 Agent 每调一次工具,上下文就暴涨一截——工具返回的 JSON 日志 token 密度极高(第二篇提到过),几轮循环下来窗口很容易爆满。
八、结语
上下文窗口是大模型最硬的"硬指标"之一。它决定了你和模型能一起处理多大的任务。
RoPE 解放了位置编码,但 Attention 的 n² 计算量和 KV Cache 的线性显存增长仍然是铁律。窗口不是想多大就多大——你得学会挑最重要的资料放上工作台。
上下文窗口是模型与现实世界连接的"咽喉"。了解它的边界,你才能在这片有限的战场上,指挥它打出最漂亮的仗。
下一篇,我们讲更宏观的问题:参数量、数据量、算力到底是什么关系?为什么模型越大越强?"大力出奇迹"有没有尽头?

扫码回复“大模型”
获取本系列文章完整配套代码
「从零开始理解大模型」是「从零开始理解 Agent」的姊妹系列。Agent 系列讲"四肢",本系列讲"大脑"。建议对照阅读 专栏入口。
- 点赞
- 收藏
- 关注作者
评论(0)