大模型的“记忆”——从上下文窗口到会话管理
前言在前面的文章中我们理解了RAG如何让大模型基于外部文档回答问题。但还有一个关键问题没有解决多轮对话。你肯定见过这样的场景——用户问Java线程池有哪些参数AI回答后用户追问第二个参数怎么设置。AI必须知道第二个参数指的是什么才能正确回答。这就是上下文记忆要解决的问题。在课程问答项目中我用ConversationBufferMemory实现了多轮对话。但面试官可能会追问“为什么选BufferMemory而不是SummaryMemory如果对话持续100轮怎么办” 本文就是帮你彻底搞懂大模型记忆的底层原理让你能回答这类追问。本文核心问题大模型本身有记忆吗为什么每次对话都需要提醒它之前说了什么上下文窗口是什么128K的窗口到底够不够用ConversationBufferMemory的底层原理是什么它有什么致命缺陷ConversationSummaryMemory是怎么做摘要的和BufferMemory比优劣在哪为什么你选择了BufferMemory而不是SummaryMemory上下文太长会出现什么问题大模型会遗忘对话开头的内容吗如何平衡记住更多和成本控制除了LangChain提供的方案还有什么更高级的记忆管理策略读完本文你将对大模型的上下文管理拥有从原理到实践的完整理解。一、大模型本身没有记忆疑问ChatGPT不是能记住刚才聊了什么吗为什么说大模型没有记忆回答大模型本身是无状态的——每次调用都是独立的。它之所以看起来记得是因为我们把之前的对话内容拼在了Prompt里。1.1 无状态的大模型第一次调用 Prompt: Java线程池有哪些参数 回答: 有corePoolSize、maximumPoolSize、keepAliveTime等... 第二次调用没有记忆 Prompt: 第二个参数怎么设置 回答: 请问您指的是哪个参数我不清楚您之前问了什么。 第二次调用有记忆 Prompt: 之前的问题Java线程池有哪些参数回答有corePoolSize、maximumPoolSize... 现在的问题第二个参数怎么设置 回答: maximumPoolSize的设置需要考虑CPU核数、任务类型、队列容量...本质大模型不是记住了而是我们把记忆手动拼在了Prompt里让它重新读了一遍。1.2 为什么大模型不自己记住大模型不存储对话状态的主要原因架构限制Transformer每次前向传播是无状态的没有状态缓存来记住上一轮的信息商业考量如果每个用户都占用GPU显存存储对话状态服务提供商将无法支撑海量用户实现成本无状态架构可以通过水平扩展支持更多用户而有状态则需要复杂的会话管理所以记忆这个功能是由调用方在外部实现的——这就是会话管理的本质。二、上下文窗口——大模型的工作台疑问上下文窗口是什么为什么说它是工作台而不是记忆库回答上下文窗口是大模型每次处理文本的容量上限。它更像一个工作台——每次调用时我们把所有相关资料放在台面上大模型在这个台面上工作。2.1 上下文窗口的本质上下文窗口 一次Prompt中能包含的Token总数输入输出 GPT-4128K Token ≈ 约10万汉字 GPT-3.54K ~ 16K Token 本地开源模型通常 2K ~ 8K TokenToken是模型处理的最小单位英文大约1个单词≈1-2个Token中文一个汉字≈1.5-2个Token。128K窗口理论能装下一本中篇小说但这个空间不是全部给记忆的——它还要装Prompt指令、检索到的文档、系统消息和即将生成的回答。2.2 上下文窗口不是越大越好窗口大小影响了多个维度窗口大小优势劣势大窗口128K能装更多上下文成本高按Token计费、回答变慢、注意力稀释小窗口4K快、成本低多轮对话容易被截断中等16K大多数场景的平衡点极端长文档和长对话场景仍不够2.3 注意力稀释效应大模型对上下文窗口中的信息关注度并不均匀。通常开头和结尾的信息最受关注中间的信息容易被忽略。这意味着即使128K窗口能装下所有对话历史把100轮的对话全塞进去模型对第30-70轮的信息也可能关注不足。窗口容量是必要条件但不是答案质量的充分条件。三、ConversationBufferMemory——全量记忆的利与弊疑问BufferMemory是怎么工作的你在课程问答项目中为什么选它回答BufferMemory的逻辑很简单——把每轮对话都保存起来下次提问时全部拼进Prompt。关键词全量、简单、但不可持续。3.1 底层原理// 第一轮用户:Java线程池有哪些参数AI:有corePoolSize、maximumPoolSize、keepAliveTime、workQueue...memory → 存储一条用户: Java线程池有哪些参数AI: 有corePoolSize...// 第二轮用户:第二个参数怎么设置// 实际发给大模型的Prompt 之前的对话 用户: Java线程池有哪些参数 AI: 有corePoolSize、maximumPoolSize、keepAliveTime、workQueue... 当前问题第二个参数怎么设置 AI→ 知道第二个指的是maximumPoolSizememory → 追加存储// 第三轮同理之前的对话全部拼接...3.2 为什么在课程问答项目中选它课程问答场景的特点 - 对话轮次较短通常是3-5轮 - 每轮回答比较简洁不超过300字这是Prompt里约束好的 - 总Token量好控制3-5轮对话在16K窗口内完全够用 - 需要精确理解上下文技术问答中第二个参数这类指代很常见 BufferMemory在这个场景下刚好够用 - 实现简单不需要额外的摘要模型 - 信息完整不会因为摘要丢失技术细节 - 成本可控短对话的Token量不会爆炸3.3 BufferMemory的致命缺陷对话轮次 → Token增长 → 问题和后果 第10轮约4000 Token → 仍然正常 第50轮约20000 Token → 开始变慢、成本上升 第100轮约40000 Token → 注意了稀释、回答质量下降 第200轮约80000 Token → 超出窗口被截断、丢失早期对话根本问题BufferMemory的Token消耗随对话轮次线性增长而早期对话的价值随时间衰减。到某一轮后大量的历史内容对当前问题的帮助微乎其微反而挤占了真正有用的上下文空间。四、ConversationSummaryMemory——以精度换空间疑问如果对话超过50轮BufferMemory撑不住了怎么办回答用SummaryMemory——把长对话历史压缩成摘要只保留要点。4.1 底层原理// 当对话超过一定轮次时触发摘要Stringsummary 用户询问了Java线程池的核心参数。AI解答了corePoolSize、 maximumPoolSize、keepAliveTime和workQueue的含义及配置方法。 用户追问了最大线程数与CPU核数的关系。AI建议IO密集型任务 设置为核心数的2倍CPU密集型任务设置为核心数1。 用户... ;// 新一轮提问时只把摘要最近的几轮对话拼进PromptStringpromptf 对话历史摘要{summary} 最近的对话 用户: 那么workQueue有哪几种选择 AI: LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue... 当前问题{question} ;4.2 优劣对比维度BufferMemorySummaryMemory信息完整性完整保留压缩可能丢失细节Token消耗持续线性增长控制在一个范围内长对话支持受限理论上可以支持无限轮实现复杂度极低需要额外的摘要模型调用延迟无额外耗时摘要生成需要时间回答精度引用精确可能因摘要偏差导致错误成本Token持续增加的API费用摘要调用一次的API费用 被压缩后仍持续的Token费用4.3 什么场景选SummaryMemory用户和AI的对话很长超过20轮对话内容多为闲聊、咨询而非精确的技术参数对回答的细节精度要求不是极致摘要丢失的细节不影响用户体验愿意接受额外的摘要生成耗时和费用4.4 课程问答项目中为什么没选它课程问答场景中每轮对话都涉及精确的技术细节——“第二个参数怎么设置这类问题完全依赖上一轮回答中的精确信息而非主题摘要。SummaryMemory会把maximumPoolSize的详细配置压缩成讨论了最大线程数的设置”丢失了第二个参数和maximumPoolSize之间的精确映射后续追问就答不准了。五、上下文太长会出现什么问题疑问BufferMemory的上下文越长除了成本高还有什么隐藏问题回答三个隐藏问题——“迷失中间”、注意力分散、成本叠加。5.1 迷失中间效应大模型对上下文开头和结尾的内容关注度较高对中间部分关注度较低。当对话历史长达几万Token时早期对话被压在中间区域模型可能读不进去。一个在第3轮确认过的信息到了第40轮可能被模型遗忘——不是模型本身的问题而是上下文结构决定的注意力分布。5.2 注意力被无关信息分散早期对话中的信息大部分与当前问题无关。但这堆历史持续占据着上下文空间模型在生成回答时需要穿过它们才能找到后来的关键信息。信息密度被稀释了——有用的信息被大量无用信息包围模型更容易遗漏或误读。5.3 成本问题GPT-4每百万Token输入约30美元。如果多轮对话每次输入8000Token100次提问就是80万Token——约24美元。而实际对当前问题有用的信息可能只占这8000Token中的10%。90%的费用花在了无用的记忆搬运上。六、记忆管理的平衡之道疑问所以到底该怎么管记忆有哪些更高级的策略回答核心原则——“不该记住的果断忘该记住的保证记住”。这不是一句口号而是可以工程化实现的设计决策。6.1 滑动窗口记忆只保留最近N轮对话——旧对话直接丢弃。简单粗暴但有效。// 只保留最近10轮对话intwindowSize10;ListMessagerecentMessagesconversationHistory.subList(Math.max(0,history.size()-windowSize),history.size());适用场景对话话题会转移旧信息对当前几乎无用的场景。6.2 混合记忆Buffer Summary近期对话全量保留远期对话生成摘要。// 最近5轮全量保留ListMessagerecenthistory.subList(history.size()-5,history.size());// 5轮之前摘要保存StringdistantsummaryModel.summarize(history.subList(0,history.size()-5));Stringpromptf 历史对话摘要{distant} 最近对话{recent} 当前问题{question} ;这是生产环境中最常用的方案。细节保留和成本控制之间找到了一个可操作的平衡——最近几轮保持完整性支撑追问远期只保留主题脉络释放空间。6.3 实体记忆只记住对话中提到的关键实体人名、参数名、配置值而不是整段对话。这需要额外的命名实体识别NER能力实现复杂度高于前两种方案。但在知识密集型的专业场景中如医疗问诊、法律咨询按实体来组织记忆可以让检索和引用更精准。七、课程问答项目的记忆架构疑问在课程问答项目中你的记忆是怎么设计的为什么这么设计回答方案选型基于场景特征——短对话、技术问答、需要精确引用。最终选择了BufferMemory 对话轮次上限的组合。ConversationBufferMemorymemorynewConversationBufferMemory();memory.setMaxTokens(4000);// 上限约2000字的中文对话历史ConversationalRetrievalChainchainnewConversationalRetrievalChain(llm,// 大模型retriever,// RAG检索器memory// 记忆);设置4000 Token上限的原因课程问答通常3-5轮结束——学生问一个概念追问一两个细节问题就解决了。4000 Token足够覆盖这种典型场景超过上限自动截断最早的对话——防止Token溢出被OpenAI拒绝或丢失更重要的近期对话配合Prompt中的回答不超过300字约束——限制每轮回答长度延长记忆有效轮次学生追问第二个参数怎么设置时完整链路用户: Java线程池有哪些参数 → Chain检索课程文档 → 拼接Prompt → LLM生成回答 → memory存储这轮对话 用户: 第二个参数怎么设置 → memory取出上一轮对话 → 拼接到当前Prompt → LLM看到上文理解第二个参数 maximumPoolSize → Chain检索maximumPoolSize的课程文档 → 生成回答总结大模型本身没有记忆每次调用都是独立的。我们通过Prompt拼接历史对话来模拟记忆本质是在输入端重建对话上下文上下文窗口是大模型的工作台——容量决定了工作台上能放多少材料。不是越大越好还要考虑成本、速度和注意力稀释ConversationBufferMemory适合短对话20轮、需要精确上下文的场景优势是信息完整缺陷是Token线性增长不可持续ConversationSummaryMemory适合长对话、非精确应用能控制成本但会丢失细节。第二个参数这类精确引用可能在摘要过程中丢失上下文过长带来的问题迷失中间模型忽略中间的信息、注意力稀释信息密度下降、成本叠加多少轮对话乘上每轮的Token费用记忆管理的核心是取舍——近期全量Buffer远期摘要Summary的混合策略是生产环境中最实用的方案课程问答项目选BufferMemory的原因短对话、技术问答、需要精确引用。未来如果扩展为多轮的长对话系统可以用混合记忆策略平滑升级下一篇预告AI理论学习七——大模型API调用从Token到流式输出。拆解Token是什么、为什么按Token计费、温度参数如何控制输出、SSE流式输出的底层原理。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2587558.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!