完整学习LLM(六):上下文窗口是什么,为什么模型会忘东西

完整学习LLM(六):上下文窗口是什么,为什么模型会忘东西

好家伙,

上一篇我们聊了 Embedding.

当时说到一个很关键的点:

text 复制代码
Embedding 负责把文本变成向量.
RAG 负责根据语义找到相关资料.
大模型负责基于资料组织答案.

但这里马上就会遇到一个新问题:

text 复制代码
资料找到了以后,到底怎么交给大模型?

比如我问:

text 复制代码
请根据这份部署文档,告诉我 battle monitor 怎么上线.

RAG 检索到了 5 段资料.

历史对话里还有我前面问过的问题.

系统提示词里还写着回答规则.

这些东西最后都要放到哪里?

答案就是:

text 复制代码
放进上下文窗口.

所以今天这篇就专门聊一个很基础,但很容易误解的概念:

text 复制代码
上下文窗口是什么?
为什么模型会忘东西?
为什么不是窗口越大就一定越好?

0.背景:为什么要学上下文窗口

我最开始用大模型时,对它的感觉是:

text 复制代码
它好像能记住我刚才说过什么.

比如我先说:

text 复制代码
我现在在写一篇关于 RAG 的文章.

然后我再问:

text 复制代码
帮我把刚才那个主题换一个标题.

模型通常能知道"刚才那个主题"指的是 RAG.

这就很像记忆.

但用久了以后会发现:

text 复制代码
聊太长,它会忘.
资料太多,它会漏.
前面明明说过,后面它却像没看见.

这个时候就不能只说一句:

text 复制代码
模型记性不好.

更准确的说法是:

text 复制代码
这次请求里,模型能看到的内容是有限的.

这个有限范围,就是上下文窗口.

1.先给一个直觉:上下文窗口就是这次请求的可见范围

我现在对上下文窗口的理解是:

text 复制代码
上下文窗口 = 模型在一次请求里能够看到和处理的 token 范围.

注意这里有两个关键词:

text 复制代码
一次请求
token 范围

也就是说,模型不是把我们所有聊天内容都永久放在脑子里.

每次调用模型时,系统会把一批内容打包给它.

这批内容可能包括:

text 复制代码
系统提示词
开发者提示词
历史对话
用户当前问题
RAG 检索出来的资料
工具调用结果
预留给模型输出的空间

这些内容加起来,不能超过模型允许处理的上下文范围.

可以先看这张图:

这张图想表达的是:

text 复制代码
对话和资料是一条很长的流.
上下文窗口只覆盖其中一段.
新问题和新资料进来时,太早的内容可能会离开窗口.

所以模型"忘了",很多时候不是它真的产生了人类意义上的遗忘.

而是:

text 复制代码
那段内容没有被放进这一次请求里.

2.上下文窗口里到底装了什么

很多人会把上下文窗口理解成:

text 复制代码
聊天记录长度.

这不算错,但不完整.

上下文窗口不只是聊天记录.

它更像一次请求的总预算.

里面装的东西可能很多:

text 复制代码
系统规则
  -> 你要怎么回答,哪些不能做

历史对话
  -> 用户前面问过什么,模型前面答过什么

当前问题
  -> 用户这一次真正要问什么

检索资料
  -> RAG 找回来的文档片段

工具结果
  -> 数据库查询结果,接口返回内容,代码执行结果

输出空间
  -> 模型接下来要生成答案的位置

这些东西都要占 token.

所以一个很现实的问题是:

text 复制代码
窗口不是只给输入用的.
输出也要占空间.

比如一个模型的上下文窗口最多能处理一批 token.

如果输入已经塞得很满,那留给输出的空间就会变少.

反过来,如果希望模型输出很长,输入就不能无限塞.

这就是为什么我说上下文窗口更像预算:

这张图里最重要的是第二条:

text 复制代码
RAG 资料塞太多时,模型不一定更聪明.

因为塞进去的内容越多,模型越需要在里面找重点.

如果资料里混进了很多无关内容,模型反而可能被干扰.

这也是为什么 RAG 系统不能只做一件事:

text 复制代码
把所有搜索结果都扔给模型.

它通常还需要:

text 复制代码
召回
重排
过滤低分片段
压缩摘要
控制 TopK

说白了:

text 复制代码
上下文窗口有限,所以要把最有价值的内容放进去.

3.为什么模型会忘东西

这里可以拆成三种情况.

第一种:内容根本没进这次请求

这是最常见的.

比如我前面很早说过:

text 复制代码
我的项目叫 Lighter.

但后面聊了很长.

如果系统为了控制上下文长度,把早期对话裁掉了.

那模型这次请求里就看不到这句话.

它自然可能不知道 Lighter 是什么.

这不是参数忘了.

而是:

text 复制代码
这次输入里没有这条信息.

第二种:内容进来了,但太靠前或太分散

有些时候,内容确实在上下文里.

但上下文太长.

关键信息埋在中间.

模型可能没有稳定抓住它.

这就涉及一个很有名的问题:

text 复制代码
Lost in the Middle

简单说就是:

text 复制代码
长上下文里,模型不一定能同等有效地利用每个位置的信息.

有些信息放在开头或结尾更容易被用到.

埋在中间时,模型可能更容易漏掉.

所以长上下文不是万能药.

窗口变大以后,还要考虑:

text 复制代码
哪些内容放前面
哪些内容放后面
哪些内容要压缩
哪些内容要重复强调

第三种:用户以为它是长期记忆

还有一种误解是:

text 复制代码
我告诉过模型一次,它以后就应该一直记得.

但大多数普通调用里,模型并不会因为一次聊天就改写自己的参数.

它能利用的通常是:

text 复制代码
这次请求里的上下文

如果系统有记忆功能,那也是另外一层工程能力.

比如:

text 复制代码
把用户偏好保存到数据库
下次聊天前再取出来
重新塞进上下文

这和模型参数本身记住不是一回事.

可以用这张图区分:

这张图里我想强调一句话:

text 复制代码
上下文窗口是临时工作台.
外部资料是资料库.
模型参数是训练时形成的能力.

这三件事不要混成一个东西.

4.用一个例子理解:为什么它前面知道,后面又不知道

假设我们在写一个后端部署文档问答助手.

一开始用户说:

text 复制代码
我的项目是 battle monitor.
它由 Go API、PostgreSQL、Nginx 组成.

然后用户问了很多问题:

text 复制代码
数据库怎么配置?
Nginx 怎么反代?
Docker Compose 怎么写?
日志怎么看?
健康检查接口是什么?

聊到后面,用户突然问:

text 复制代码
那这个项目上线前还要检查什么?

如果系统把所有历史都完整塞进上下文,模型大概率知道"这个项目"指 battle monitor.

但如果历史太长,系统只保留最近几轮:

text 复制代码
用户: 日志怎么看?
助手: ...
用户: 健康检查接口是什么?
助手: ...
用户: 那这个项目上线前还要检查什么?

这时候模型可能只知道:

text 复制代码
有日志
有健康检查
可能是某个项目

但它不一定知道:

text 复制代码
这是 battle monitor
用了 Go API、PostgreSQL、Nginx

所以它可能会给一个泛泛的上线检查清单.

这就解释了为什么有时候我们会觉得:

text 复制代码
它前面明明知道,后面又忘了.

实际上更准确的流程是:

text 复制代码
早期信息
  -> 曾经在上下文里
  -> 后来窗口滑动
  -> 早期信息被裁掉
  -> 新请求里看不到
  -> 回答变泛

5.上下文窗口和 RAG 是什么关系

上一篇讲 Embedding 时,我们说 RAG 大概是:

text 复制代码
用户提问
  -> 检索相关资料
  -> 把资料交给模型
  -> 模型基于资料回答

现在可以把中间那步说得更准确一点:

text 复制代码
把资料交给模型 = 把资料塞进上下文窗口.

所以 RAG 的效果不只取决于:

text 复制代码
能不能搜到资料.

还取决于:

text 复制代码
搜到的资料有没有放进上下文
放进去的位置是否合理
放进去的资料是否太多
有没有和用户问题放在一起
有没有保留足够输出空间

这也是为什么一个 RAG 系统可能会出现这种情况:

text 复制代码
向量数据库确实搜到了正确文档.
但模型最后回答还是不准.

原因可能不是 Embedding 错了.

而是:

text 复制代码
正确文档被别的片段挤掉了
正确文档埋在太长上下文中间
提示词没有要求模型优先依据来源
输出空间不够

所以工程上经常会做这些事:

text 复制代码
只取 TopK
对召回结果 rerank
把长文档压缩成短摘要
把关键来源放到问题附近
让模型按引用回答

这不是形式主义.

而是在管理上下文窗口.

6.上下文窗口越大越好吗

我的答案是:

text 复制代码
大窗口很有用,但不是越大就自动越好.

大窗口能解决一些问题.

比如:

text 复制代码
能放更长的文档
能保留更多历史对话
能一次处理更多工具返回结果

这当然有价值.

但它也带来新的问题:

text 复制代码
输入成本更高
响应可能更慢
无关内容更容易混进去
模型不一定能稳定利用所有位置的信息
调试更难

所以我现在更愿意把上下文窗口当成一个资源.

不是说:

text 复制代码
有多少塞多少.

而是问:

text 复制代码
这次回答真正需要哪些信息?
哪些是噪音?
哪些可以摘要?
哪些必须原文保留?

这和写代码有点像.

内存大当然好.

但你不能因为内存大,就把所有东西都塞进一个函数.

上下文窗口也是一样.

大窗口给了更大的空间.

但工程上还是要管理.

7.它和 Token、Embedding 的关系

到这里,前面几篇文章可以串起来了.

先是 Token:

text 复制代码
文本进入模型之前,要先被切成 token.

然后是 Embedding:

text 复制代码
文本可以被表示成向量,用来做语义相似度比较.

现在是上下文窗口:

text 复制代码
模型一次请求最多能处理多少 token.

这三件事放在一起:

text 复制代码
Token 决定文本怎么被切开.
Embedding 帮我们找到相关资料.
上下文窗口决定这些内容最多能带进去多少.

如果放到 RAG 里,流程就是:

text 复制代码
用户问题
  -> 切成 token
  -> 生成向量
  -> 检索相关资料
  -> 选择一部分资料
  -> 塞进上下文窗口
  -> 模型生成答案

所以这篇文章其实是在回答上一篇留下的问题:

text 复制代码
Embedding 找到资料以后,资料怎么进入模型?

答案就是:

text 复制代码
进入上下文窗口.

8.容易踩的坑

第一个坑:

text 复制代码
以为上下文窗口等于模型记忆.

不是.

它更像一次请求的临时工作台.

第二个坑:

text 复制代码
以为窗口越大,回答一定越好.

也不是.

窗口大只是能放更多内容.

但内容是否相关、位置是否合理、提示词是否清楚,仍然很重要.

第三个坑:

text 复制代码
RAG 把资料全塞进去.

这很容易让模型在一堆材料里迷路.

应该先筛选,再重排,必要时压缩.

第四个坑:

text 复制代码
忽略输出空间.

如果输入塞满了,模型后面生成答案的空间也会受影响.

第五个坑:

text 复制代码
长对话不做总结.

长对话里,可以定期把历史压缩成摘要.

再把摘要放进后续上下文.

这样比机械保留所有历史更稳.

9.这些引用到底说明了什么

这里还是按之前的习惯,把引用说人话一点.

OpenAI 的 Conversation State 文档

OpenAI 的文档里会讲到,多轮对话本质上需要把历史消息作为上下文提供给模型.

这说明一件事:

text 复制代码
模型能接上前文,不是因为它自动永久记住了所有对话.
而是因为系统把相关历史作为输入的一部分传给了它.

所以这篇文章里说的"上下文窗口是一次请求的可见范围",可以从这里理解.

Anthropic 的 Context Windows 文档

Anthropic 的文档会直接把 context window 解释为模型一次能处理的文本范围.

这对初学者最有帮助的一点是:

text 复制代码
上下文窗口有上限.
输入、输出、历史、资料都要在这个上限里安排.

也就是说,窗口不是一个抽象概念.

它会直接影响你怎么设计长文档问答、Agent、RAG 和多轮对话.

Lost in the Middle 论文

这篇论文讨论的是长上下文里一个很现实的问题:

text 复制代码
模型不一定总能稳定利用上下文中间的信息.

它提醒我们:

text 复制代码
窗口变大,不代表所有位置的信息都一样容易被模型用到.

所以在写 RAG 或长上下文应用时,不能只追求塞更多.

还要关心信息的位置、排序和压缩.

Attention Is All You Need

这篇论文是 Transformer 的起点之一.

本文引用它不是为了提前讲 Transformer 细节.

而是为了引出下一篇:

text 复制代码
模型为什么能在一段上下文里看不同 token 之间的关系?

这个问题就会走到 Transformer 和 Attention.

10.总结

这篇文章先记住一句话:

text 复制代码
上下文窗口不是长期记忆.
它是模型一次请求里能看到和处理的 token 范围.

所以模型会"忘东西",常见原因是:

text 复制代码
那段内容没有进这次请求
内容太长导致关键信息被淹没
用户把上下文当成了长期记忆

放到工程里,我现在会这样理解:

text 复制代码
Embedding 负责找资料.
上下文窗口负责装资料.
Prompt 负责告诉模型怎么用资料.
模型负责组织答案.

上下文窗口越大越有价值.

但它不是免死金牌.

真正要做的是:

text 复制代码
把最该看的内容,放到模型这次最容易看见的位置.

下一篇我们继续往下走:

text 复制代码
Transformer 为什么重要?
为什么现在主流 LLM 基本都绕不开它?

参考资料

主要看它对多轮对话状态的说明:模型能接上前文,是因为历史消息被作为上下文提供给模型.

主要看它对 context window 的直观解释:模型一次能处理的内容有范围,输入和输出都要在这个范围里安排.

主要看它说明长上下文里的信息位置问题:上下文变长以后,模型不一定能同等稳定地使用每个位置的信息.

这篇论文提出 Transformer 架构,下一篇聊 Transformer 和 Attention 时会正式展开.

相关推荐
装不满的克莱因瓶5 分钟前
了解 LangChain 中的 LLM 与 ChatModel 的差异
人工智能·python·ai·langchain·llm·agent·chatmodel
颜酱22 分钟前
LangChain 工具调用:从原理、入门到落地
langchain·llm
swipe22 分钟前
做多轮对话 Agent,为什么我建议把短期记忆放到 Redis
后端·面试·llm
swipe1 小时前
别再把关系库和向量库拆开了:PostgreSQL 搭建 AI 长期记忆层实战
面试·langchain·llm
元Y亨H3 小时前
大数据转大模型(LLM)进阶学习路线图
大数据·llm
小lan猫8 小时前
用 AI Agent 让购物更便捷:LumiGlow 电商网站实践
前端框架·llm·agent
meilindehuzi_a8 小时前
全栈进阶:告别 Node 繁琐配置,用下一代运行时 Bun 丝滑构建 AI Agent 客户端
人工智能·llm
sg_knight8 小时前
Claude Code、Cursor、Copilot、openCode,到底怎么选
llm·copilot·agent·claude·code·codex·claude-code
程序员三明治9 小时前
RAG 元数据的作用与管理:让知识库回答可追溯、可过滤、可维护
人工智能·llm·知识库·元数据·rag·java后端
冬奇Lab20 小时前
每日一个开源项目(第126篇):turbovec - 向量索引的内存杀手,1千万文档从31GB压到4GB
人工智能·开源·llm