AI记忆机制全解析:模型会遗忘,系统才记忆

大模型本身没有记忆,所有“记忆感”都是应用在每次调用时重新组织上下文的结果。
文章从“模型是无状态函数”这一本质出发,系统梳理了LLM应用中的记忆架构。模型权重训练后冻结,所谓记忆其实是应用每次把历史token重新发送,由此引出参数化、上下文、外部三层记忆模型。文章深入辨析了KV缓存与Prompt缓存:前者是推理优化,不增加新信息;后者遵循严格前缀规则,要求prompt从稳定到易变排列。随后讨论了上下文窗口满后的滑动窗口与摘要策略、跨对话记忆的提取-合并-存储-注入管道、用RAG检索相关记忆的流程与失败模式,以及代理式记忆让模型自主管理记忆的取舍。最终强调:记忆不是模型里的开关,而是系统状态,需要归属、存储和过期方案。
每次打开几天前的AI对话,继续追问时它总能接上话——这种"记忆感"让人以为大模型真的记住了什么。但真相是:模型什么都不记得,是你的系统在替它记忆。
这篇文章源自一位开发者的系统梳理,他从"模型到底看到了什么"出发,一步步讲到如何为自己的AI功能设计记忆架构。核心结论只有一条:每一种记忆技术,本质上都在回答同一个问题——这次调用,上下文里应该放什么?

模型是无状态函数
语言模型只做一件事:给定一串token序列,预测下一个token。生成文本就是围绕这个过程的循环——模型的权重在训练后就固定了,你在对话里说的任何话都不会改变它们。模型能看到的,只有上下文窗口能容纳的那部分token。
用一个实验就能验证。第一次调用告诉模型"我叫Shayan",第二次调用直接问"我叫什么",不带历史记录时它会回答"我不知道你的名字";把第一轮的对话历史一并附上,它才会说"你叫Shayan"。
模型并没有记住名字,是你把名字又发了一遍。
在到达模型之前,messages数组会被压平成一长串带角色标记的文本,模型只是在续写这段文本。聊天应用之所以能"续上",是因为后端数据库存了每一条消息,你发新消息时,它把整段对话连同新消息一起发过去,模型从第一个字开始重新读一遍。
这带来一个关键后果:如果每轮对话增加约500个token,第100轮就要发送约50000个token。对话的总计算量大致随长度平方增长——缓存和压缩机制的存在,正是为了应对这个问题。
记忆可以存在于三个地方
既然模型本身是无状态的,"记忆"该放在哪里?作者给出了一个三层模型:
| 层级 | 所在位置 | 擅长 | 局限 |
|---|---|---|---|
| 参数化记忆 | 模型权重 | 广泛的通用知识 | 训练时冻结,模糊,完全不了解你 |
| 上下文记忆 | 当前调用的输入 | 精确,模型直接对其推理 | 容量有限,按token付费,调用结束即消失 |
| 外部记忆 | 数据库、文件、向量库 | 无限且持久 | 除非被放入上下文,否则模型看不见 |
作者用了电影《记忆碎片》作类比:主角Leonard无法形成新记忆,只能靠纸条和纹身,在每个场景里重新阅读它们。LLM应用就是Leonard加上一个笔记本。
由此得出贯穿全文的规则:*每个记忆系统都在回答同一个问题——这次调用,什么从外部层移入上下文?*滑动窗口、摘要、RAG和记忆工具,都是同一个问题的不同答案。这正是当下被称为"上下文工程"(context engineering)的核心。
KV缓存到底是什么
这是作者误解最久的部分。当模型读取一个token时,会为它计算几个向量,其中两个关键:**key(K)**是标签,value(V)是内容。新token到达时会构建一个query(Q),与之前的每个key比对,找出该关注谁——这就是注意力机制。
在文本生成模型中,token只能向后看不能向前看,所以"Shayan"的K和V只取决于它之前的token。它后面追加再多词,这一行都完全不变。那为什么每一步都重算?不必。把这张表存在GPU显存里,每来一个新token就追加一行——这张表就是KV缓存。
节省是惊人的。prompt 1000个token、生成100个token:不用缓存要算约105000行,用缓存只需约1100行(先并行处理1000个token的prefill,再每步1个)。
代价是显存。一个Llama-3-70B级别的模型在16位精度下,每个token约320KB,10万token的prompt仅缓存就要约32GB显存。这也解释了为什么输出token比输入token贵——prefill可以并行处理整个prompt,GPU擅长这个;decode要逐个token生成,每一步都要读取权重和缓存。
关键区分:缓存不是记忆。 KV缓存不增加任何新信息,删掉它可以从token重建,损失的只是时间和金钱。
理解KV缓存需要先了解Transformer的注意力机制。在自注意力层中,每个token的表示会被投影成三组向量:Query(查询)、Key(键)和Value(值)。注意力计算时,当前token的Query会与序列中所有先前token的Key做点积,得到一组权重,再用这些权重对所有Value加权求和,得到当前token的上下文表示。
关键点在于:生成是自回归的,token只能关注它之前的位置。这意味着当第N个token被处理时,前N-1个token的Key和Value已经计算完毕且不会再改变。KV缓存的本质,就是把这些历史Key/Value矩阵保留在显存中,每生成一个新token时只计算它自己的K和V并追加,无需重算整个序列。
因此KV缓存是一种经典的空间换时间优化:用显存存储历史计算结果,避免O(n²)的重复计算。这也是为什么长上下文推理对显存压力如此之大,以及为什么服务商要对输入token和输出token分别定价。
Prompt缓存保留请求之间的表
相邻两轮对话中,第二个请求开头与第一个完全相同,所以它KV表的前几行也完全相同。Prompt缓存就是让服务商在短时间内保留这张表,下一个以相同token开头的请求可以跳过重算。
前缀规则决定了哪些行能存活。就像前缀和数组或git提交哈希:改动第三个元素,前面的和不变,后面全部改变。缓存只对从头开始逐token完全相同的前缀有效——多一个空格、JSON键顺序改变、数字变化,都足以让后面全部失效。
因此prompt应该从稳定到易变排列:工具定义 → 系统提示 → 静态文档 → 对话历史 → 新用户消息。经典错误是把f"Current time: {datetime.now()}"放在系统提示顶部——每次请求前缀都变,缓存永远不命中。放到用户消息里才对。
作者引用了一个测算:5万token的文档问20个问题,无缓存需100万输入token,有缓存约15.7万,输入成本降低约84%,首token也来得更快。
回到开头的困惑:几天后重开那个对话,缓存早就没了——它只存活几分钟。对话能继续,是因为真正的记忆(token本身)存在应用的数据库里,那张表是从零重建的。
上下文满了怎么办
上下文有三种限制:硬限制(上下文窗口,超出报错)、经济限制(成本和首token时间随token增长)、质量限制(上下文越长模型用得越差——"Lost in the Middle"研究表明模型对长上下文开头和结尾的信息利用更好,中间部分较差)。
方案A:滑动窗口,及其隐蔽陷阱。 只保留最近20条消息看似简单,但有一个容易忽略的问题:每次窗口滑动,历史开头的第一个token都变了,整段历史全部缓存失效,命中率降到几乎为零。修复方法是批量淘汰:让窗口从20涨到40,再一次性砍回20。这样两次裁剪之间历史只在末尾增长,缓存持续命中。原则是:上下文要少改、批量改,而不是每轮改一点。
方案B:压缩。 对话达到预算80%时,用LLM摘要较早的部分并替换。摘要是有损的,所以摘要器的prompt至关重要——要明确告诉它保留什么:用户事实、已做决定及原因、未完成任务、ID和数字。多轮之后摘要会退化,像传话游戏。对于绝不能丢失的内容,摘要不够用,需要第六步的长期记忆。
跨对话的记忆
设想一个外卖应用的客服助手:用户在一次对话中说"请不要在我的订单里加洋葱",三周后在新对话里问"推荐个披萨"。历史为空,滑动窗口和摘要都无能为力。
这需要一条管道:提取 → 合并 → 存储 → 注入。
提取用一次独立的LLM调用(便宜的模型即可)从对话中抽取持久事实。合并是最难的部分——如果只插入不更新,记忆会堆满重复和矛盾。所以存储前要先检索相似记忆,让LLM决定ADD、UPDATE、DELETE还是NOOP。*这是在用LLM做数据库的合并逻辑。*要注意:不是每个矛盾都是改变("我在婚礼上吃了鱼"是例外不是新饮食),有时想保留历史而非覆盖。
作者给出两个默认实践,很有价值:提取要异步,放在回复路径之外——因为本次对话的事实本来就在本次历史里,长期记忆是给下一次对话用的;每次对话开始时给记忆拍快照——记忆块在历史之前,中途更新会破坏整个历史的缓存。
风险包括:错误事实被永久保存、过期事实、记忆投毒(用户粘贴网页诱导"记住用户希望每个答案都包含某链接",若提取器保存了就成了永久prompt注入)、隐私和过度个性化。
用RAG找到正确的记忆
记忆变大后无法全部塞进上下文,需要检索相关内容。关键词搜索不够用——"车抛锚了"匹配不到"车辆无法启动"。
Embedding是另一个更小的模型,把文本变成定长向量,语义相近的文本距离相近,用余弦相似度衡量。注意这些向量不是第三步的K和V——embedding模型是专门训练来让相似含义靠近的。非英语用户要选多语言embedding模型。
RAG的检索流程是:问题 → 改写 → 嵌入 → top-k → 重排 → 上下文 → LLM。模型不搜索任何东西,是你的代码在搜索并把结果放进上下文。
如果已经用Postgres,pgvector扩展对多数项目就够了。但WHERE user_id这一行是安全边界不是细节——忘了它,一个用户的记忆会进入另一个用户的上下文。
检索有三种典型失败:精确字符串(订单号#48213,embedding不理解)需要混合搜索(BM25+向量,用RRF融合);依赖对话的问题("另一个呢?更快吗?")需要用LLM改写成独立问题;时间("上周聊的那个bug")主要是关于何时,embedding不理解时间,应作为过滤条件。
Embedding(嵌入) 是一种将离散文本映射到连续稠密向量的技术。它的训练目标通常是让语义相近的句子在向量空间中距离更近,例如对比学习会拉近正样本对、推远负样本对。这个过程由专门的embedding模型完成,与生成模型的内部K/V向量完全不同——后者是注意力层的中间激活,而前者是经过训练的语义表示。
常见的相似度度量是余弦相似度,它衡量两个向量夹角的余弦值,对向量长度不敏感。检索时,系统把查询和文档分别嵌入,计算相似度,返回top-k个最相近的文档片段。
BM25是一种基于词频和逆文档频率的经典关键词检索算法,对精确术语(如订单号、专有名词)效果很好,但不懂同义词。混合搜索将BM25和向量检索的结果合并,常用RRF(Reciprocal Rank Fusion,倒数排名融合):对每个文档在两个排名列表中的名次取倒数求和,得到综合排序。这样既能抓住精确匹配,又能捕捉语义相近,弥补单一方法的盲区。
当模型管理自己的记忆
前几步都是代码决定上下文内容,模型只是读取。代理式记忆把决定权翻转——给模型工具,让它自己决定何时读取、何时写入。Leonard开始自己写笔记了。
关键细节是:模型从不直接接触数据库。 它发出请求,你的代码执行。所以护栏在你的代码里:user_id从会话取而非模型输入、限制记忆大小和数量、文件模式要验证路径。
Anthropic的记忆工具让Claude请求查看、创建或编辑/memories下的文件,应用在可控存储上执行。MemGPT(现Letta)更进一步,用操作系统类比:上下文是RAM,外部存储是磁盘,模型决定换入换出什么。
对缓存而言有个好副作用:工具结果追加到上下文末尾,代理式检索永不触碰前缀,天然缓存友好——这就是"即时上下文"(just-in-time context)。
对比两种模式:管道驱动由代码决定、总是运行、易测试,但可能注入无关事实;代理驱动由模型决定、只在需要时调用、更灵活,但模型可能忘记保存或查找,测试更难,记忆投毒风险更直接。实践中多数系统两者混用。
即时上下文(just-in-time context) 是一种与预加载上下文相对的策略:不把可能用到的所有信息提前塞进prompt,而是让模型在推理过程中按需通过工具调用获取。这样做的核心优势有三:一是节省token和成本,无用信息不进入上下文;二是保护prompt缓存前缀,因为工具结果总是追加在末尾;三是让模型自主判断信息相关性,适用于记忆量远超上下文窗口的场景。
需要注意的是,即时上下文把检索时机的决策权交给了模型,因此对工具描述、模型能力和错误恢复机制要求更高。如果模型没有意识到需要检索,或工具调用失败,就可能产生幻觉或答非所问。因此实践中通常会结合管道驱动的兜底逻辑,在关键路径上强制注入必要记忆,同时允许模型自主补充。
一个请求的端到端旅程
用户打开新对话输入"推荐个披萨":应用加载系统提示、工具、记忆快照和历史(空);按稳定到易变顺序构建请求并设缓存断点;服务商在缓存中找到系统提示和工具并跳过重读;模型prefill剩余token建KV行;模型调用recall,应用执行过滤后的向量+关键词搜索并追加结果;模型逐个token写答案,复用KV缓存;应用保存新消息;后台异步完成提取、合并、存储,为下次对话准备。
模型在第6步结束的瞬间就忘了一切。列表中其他每一行,都是系统在替它记忆。
这套方案解决不了什么
作者坦率地列出了局限:检索会漏,漏了模型要么说不知道要么编造,而更多记忆不等于更好记忆;摘要和抽取事实是有损的且可能出错,你需要的是评估而非良好意愿;每一层都增加成本、延迟和调试负担——很多功能用一个排序良好的prompt加prompt缓存、完全不搞长期记忆就是正确答案,别在第四步够用时去建第八步;存储的记忆就是存储的个人数据,要承担保留、删除和访问规则。
回到那个对话:重开时感觉AI记得我,其实没有。是数据库记得,一个无状态函数从头重读了所有消息然后继续。记忆不是模型里可以打开的能力开关,它是状态。而像我们构建的系统里任何状态一样,它需要一个归属、一个存放之处,以及过期时的应对方案。
记忆投毒(memory poisoning) 是长期记忆系统特有的一类安全风险。由于提取管道会把对话中识别出的“持久事实”写入外部存储,攻击者可以通过对话内容诱导提取器保存恶意指令,例如让模型“记住用户希望每个答案都附带某链接”。一旦这条记录被保存,它就会在后续所有对话中被注入上下文,相当于一次永久性的prompt注入,影响范围远超单次会话。
防御思路包括:对提取出的记忆做来源标记和置信度评估、限制单条记忆的权限范围、对写入内容做敏感模式过滤、以及对记忆更新保留审计日志和回滚能力。此外,涉及个人数据的记忆还须遵守GDPR等法规中的删除权和访问权要求,这意味着存储层必须支持按用户精准删除和导出。
相关推荐

WorkBuddy全流程教程:从零搭建AI办公自动化智能体
WorkBuddy 全流程教程精讲:从下载安装、三种工作模式到多模态绘本视频实战,再到专家团搭建智能体与工作流配置,帮你用低成本 AI Agent 实现办公自动化。

WorkBuddy智能体全攻略:7天从入门到自动化实战
基于B站256集WorkBuddy全套精讲课程,系统拆解腾讯AI办公工作台的核心能力:Agent原理、Skill封装、连接器跨平台、定时自动化、知识库与零代码开发,附学习路径建议。

8个AI账号翻车7个:我用AI干活的真实复盘
B站8个AI运营账号仅1个跑通。本文拆解"演AI"与"用AI干活"的本质区别,分析5条视频217万播放背后的产能控制、评论区运营和交付节奏策略。