给本地大模型装记忆系统,真正要解决的不是模型智商,而是会话失忆。模型每次回答都只看当前请求,关了对话、重启服务,之前聊过的内容就全部消失。这个问题的标准解法,是在模型外面挂一层检索和缓存,把历史信息转成可查询的片段,在每次请求前重新拼回上下文。整套方案做下来,本地说不上便宜,至少 16G 内存起步,跑稍大一点的模型时 CPU 和风扇会先进入状态。这篇文章就把我在本地环境里从部署、嵌入、检索到记忆回填的完整流程拆开讲,适合已经能跑起 Ollama 或类似本地模型、但觉得每次对话都要重复交代背景的读者。
1. 先弄清楚:模型没有记忆,只有上下文窗口
1.1 大模型的"记忆"到底存在哪
很多人第一次玩本地大模型时会有一个错觉:它好像能记住我刚才说了什么。实际上,模型本身没有任何持久状态。你看到的“记得”,只是开发者在交互层把历史消息重新发送了一次,模型根据上下文窗口里的全部内容重新生成答案。一次请求结束,模型就回到“失忆”状态。
所以这里要先区分两个概念:
- 上下文窗口:模型单次请求能读取的最大 token 数量,比如 4k、8k、32k。
- 长期记忆:跨会话、跨重启仍然可以保留的用户信息、知识内容、对话摘要。
本地大模型的上下文窗口不是记忆,它只是工作台。工作台越大,能临时放下更多材料,但关了电脑、换个进程、重启服务,工作台就被清空。真正的记忆系统,是在模型外部做一套读写机制,把长期信息保存下来,在需要时重新放回上下文窗口。
这也是为什么“给模型装记忆”听起来像改造模型,实际做的是周边系统工程。
1.2 为什么风扇会先遭殃
“烧坏风扇”这个说法有点夸张,但它描述的现象很真实:本地跑大模型时,设备会长时间处在高负载状态。
如果用的是 CPU 推理,模型权重和中间计算会让 CPU 占用率长时间接近 100%,风扇自然满转速。如果用的是 GPU,显卡功耗升高、显存吃满,整机发热也会明显增加。再加上模型加载后会常驻内存或显存,即使你只是开着服务没对话,资源也不会自动释放。
这里要特别说明一个高频问题:5600G + 32G 内存能不能跑本地大模型?
我的判断是能跑,但要控制预期。5600G 没有独立显卡,只能靠 CPU 和核显,推理速度不会快。32G 内存跑 7B 甚至 14B 的量化模型,内存容量基本够,但持续多轮对话或批量任务时,CPU 负载会很快拉满,风扇转速升高属于正常现象。如果同时再跑向量库、嵌入模型和 Web 服务,内存也可能吃紧。
所以给本地大模型做记忆系统之前,先接受一个事实:这本来就是一个资源敏感型任务。不是功能上做不到,而是你愿不愿意让风扇一直保持高转速。
2. 记忆系统不是把历史全塞进上下文
2.1 三种常见记忆方案对比
给大模型加记忆,最容易想到的是把历史记录全部拼进提示词。这个方案最简单,但问题也最明显:对话一长,token 迅速膨胀,超出上下文限制后,最老的内容被截断;每次请求都要把所有历史重新计算一遍,成本和延迟越来越高。
更合理的方案是从信息里提取关键内容,而不是让模型记住每一句话。常见有三类:
| 方案 | 实现成本 | 记忆容量 | 延迟影响 | 适合场景 |
|---|---|---|---|---|
| 上下文拼接 | 最低 | 受上下文窗口限制 | 随历史变长而增大 | 短对话、临时演示 |
| 摘要记忆 | 中 | 较高,但会丢失细节 | 依赖摘要生成时间 | 流水账式对话、定期归档 |
| 向量检索记忆 | 中高 | 高,可扩展 | 检索速度快,相对稳定 | 知识库、个人资料、跨会话长期记忆 |
我在本地环境里更推荐向量检索记忆,因为它把“存储”和“语义理解”分开:向量库负责存,嵌入模型负责把文本变成语义向量,查询时按相似度把最相关片段捞回来,再拼给大模型。这样即使历史资料很多,每次进入上下文的只有几个高相关片段,不会撑爆窗口。
2.2 我建议的组合
我实际搭建时用的组合是:Ollama 负责模型推理,Ollama 自带的嵌入模型或 Hugging Face 上的小型嵌入模型负责生成向量,ChromaDB 负责向量存储和检索,Python 脚本负责组装记忆和调用模型。
为什么选这套组合?
- Ollama 安装简单,能直接拉取量化模型,内存占用比原版 PyTorch 更友好。
- ChromaDB 是嵌入式向量库,不需要额外起服务,本地单机场景非常方便。
- 嵌入模型选小体积的本地模型,不需要 GPU 也能跑,避免整个系统一启动就吃掉一大块显存。
如果不写代码,也可以用 Dify、AnythingLLM 这类平台把同等能力拼好。但自己写一遍脚本,你会更容易理解记忆系统的瓶颈到底在哪。
3. 动手搭一条能记住人的流水线
3.1 环境准备
先确认硬件底线。我这边建议至少有 16G 内存,内存 32G 会更舒服。有独立显卡,优先跑模型;没有显卡,也可以用 CPU 推理,但响应会慢一些,风扇转速也会高。系统方面,Windows、macOS、Linux 都可以装 Ollama,命令差别不大。
安装好 Ollama 后,先拉取主模型和嵌入模型。这里以命令方式演示,具体模型名以你本地能拉到的为准:
# 拉取主模型,这里以小尺寸模型示例 ollama pull qwen2.5:7b # 拉取嵌入模型,用来把文本转成向量 ollama pull nomic-embed-text # 查看模型信息和上下文长度 ollama show qwen2.5:7b用ollama show能看到模型默认的上下文长度、参数规模、量化方式等信息。如果你想临时增大上下文,可以在调用时配置num_ctx,比如让 Ollama 使用 8192 上下文:
ollama run qwen2.5:7b --num-ctx 8192需要注意,增大上下文会明显增加内存占用和计算量。如果本来内存就紧张,先把上下文调到 4096 或 2048,保证能跑再说。
3.2 初始化向量库并写入第一批记忆
接下来做三件事:初始化向量库、生成嵌入向量、写入第一批记忆片段。
我习惯先把“用户背景信息”“项目知识点”“历史结论”这类内容拆成小块,写入向量库。下面是一个最小示例,使用 ChromaDB 和 Ollama 的嵌入接口:
import chromadb import requests # 初始化 ChromaDB,指定持久化目录 client = chromadb.PersistentClient(path="./memory_db") collection = client.get_or_create_collection(name="memory") # 准备要写入的记忆片段 memory_items = [ "用户在做本地部署相关项目,环境是 Windows,内存 32G。", "用户的项目已经跑通了 Ollama,模型是 qwen2.5:7b。", "这次迭代的重点是让模型记住用户的项目背景和偏好。", ] # 逐条生成向量并写入集合 for i, text in enumerate(memory_items): resp = requests.post( "http://localhost:11434/api/embeddings", json={"model": "nomic-embed-text", "prompt": text} ) embedding = resp.json()["embedding"] collection.add( ids=[str(i)], documents=[text], embeddings=[embedding], metadatas=[{"source": "manual"}] )这里关键点是PersistentClient的路径。如果不指定路径,ChromaDB 默认用临时目录,重启进程后数据可能丢失。路径和集合名称一旦确定,后续读写都要保持一致。
3.3 每次对话前检索记忆并组装提示词
记忆库建好之后,真正的用法是:用户发来新消息时,先把这条消息转成查询向量,从向量库里检索最相关的几个片段,再把这些片段拼到系统提示词里,最后发给大模型。
实现思路:
# 1. 把用户当前输入转成查询向量 query_text = "我之前提到的项目是什么?" resp = requests.post( "http://localhost:11434/api/embeddings", json={"model": "nomic-embed-text", "prompt": query_text} ) query_embedding = resp.json()["embedding"] # 2. 从向量库里检索最相关的记忆片段 results = collection.query( query_embeddings=[query_embedding], n_results=3 ) # 3. 把检索结果拼成上下文 context_parts = [] for doc in results["documents"][0]: context_parts.append(f"[记忆片段] {doc}") memory_context = "\n".join(context_parts) system_prompt = f"""你是一个有长期记忆的助手。下面是和当前用户相关的历史记忆: {memory_context} 请基于这些记忆回答问题。如果记忆中没有相关信息,就说明不知道,不要编造。""" # 4. 调用 Ollama 生成回答 chat_resp = requests.post( "http://localhost:11434/api/chat", json={ "model": "qwen2.5:7b", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": query_text} ] } ) answer = chat_resp.json()["message"]["content"] print(answer)这套流程的关键在于:先检索,再生成。不要把整个向量库都塞进提示词,只取 top 3 或 top 5 的相关片段。这样既能保留关键记忆,又不会把上下文窗口占满。
3.4 把新对话内容写回记忆
光会读记忆还不够,还要会写。每次对话结束后,应该把有价值的结论、用户偏好、项目状态沉淀回向量库。
但这里不要每轮都写。如果每句话都入库,向量库会被大量重复内容污染,检索准确率也会下降。我一般用两种策略:
- 会话结束后,让模型把整段对话压缩成几条结构化记忆,再写入向量库。
- 只保存关键实体、结论和待办事项,不保存寒暄和重复解释。
压缩记忆的示例提示词可以这样写:
请把以下对话压缩成不超过 5 条记忆片段,每条一句话,保留用户偏好、项目背景、技术结论和待办事项。生成后,再走一次文本转向量、写入 ChromaDB 的流程。这样长期跑下来,向量库里沉淀的是“经过提炼的信息”,而不是聊天记录的堆叠。
4. 关键参数与效果判断
4.1 核心参数表
记忆系统跑通之后,真正决定好不好的是一堆参数。下面是我实测时比较关注的几个:
| 参数 | 作用 | 推荐起点 | 注意点 |
|---|---|---|---|
| top_k | 每次检索返回多少片段 | 3 到 5 | 太大容易把不相关内容混进来 |
| 相似度阈值 | 低于阈值的结果丢弃 | 0.3 到 0.5 | 不同嵌入模型阈值差异大 |
| chunk_size | 单条记忆片段长度 | 200 到 500 字 | 太长语义容易混合 |
| chunk_overlap | 分块之间重叠长度 | 50 字左右 | 防止关键句被切断 |
| num_ctx | 模型上下文长度 | 4096 起步 | 越大占用资源越多 |
| 温度 | 回答随机性 | 0.1 到 0.3 | 记忆系统建议偏低 |
这里最容易被忽略的是相似度阈值。不同嵌入模型产出的向量分布不一样,同一个项目里 0.3 可能是合理值,换一个嵌入模型后 0.5 才能过滤掉噪声。稳妥做法是先用小样本试,打印出检索分数,再决定阈值。
4.2 怎么判断记忆系统真的有效
记忆系统不像模型推理,跑通不等于有效。我一般用三个测试来判断:
第一,跨会话测试。新建一个会话,直接问:“我刚才提到的项目背景是什么?”如果回答里有准确的项目信息,说明记忆写入和检索链路正常。
第二,反幻觉测试。问一个记忆库里没有的问题,比如“我昨天说的电话号码是多少?”如果系统回答“没有找到相关信息”,说明提示词约束生效了;如果模型开始编造,就要检查是否把“不知道”写进了系统提示词。
第三,相关性测试。连续问几个和记忆库中不同主题相关的问题,看模型能否准确区分。如果所有问题都检索到同一个片段的答案,多半是重复内容太多或 chunk 过长。
4.3 默认参数适合入门,不一定适合生产
我见过很多人刚搭好系统就直接上大批量任务,结果不是检索混乱,就是内存溢出。
不要一上来就开最大并发。先跑单条对话,观察检索结果和回答质量。确认没有问题后,再逐步提高批次和并发。尤其是机器性能一般时,最稳的节奏是:单条 → 五条 → 二十条 → 再加并发。
如果你只是学习,默认参数就够了。但如果想把记忆系统当长期服务用,就必须把输出目录、日志、向量库备份都提前规划好。
5. 极限压榨时的风扇、内存和失败排查
5.1 风扇狂转和资源占用的排查顺序
机器转得厉害,先不要急着换散热,按这个顺序排查:
- 看任务管理器(Linux 可以用
htop,Windows 可以用任务管理器,NVIDIA 显卡用nvidia-smi)。确认是 CPU、GPU 还是内存占用异常。 - 看模型服务是否常驻。Ollama 加载模型后,理论上会保留一段时间,即使没有请求也会占内存或显存。可以在配置里关闭保活,或者手动卸载模型。
- 看是否有多余进程同时运行。ChromaDB、嵌入模型、Web API、浏览器页面,每个都在吃资源。
- 看是否由于上下文过长导致推理计算量飙升。把上下文从 8192 降到 4096,资源占用通常会明显下降。
如果长时间跑批量任务,风扇高转速并不一定是故障,但要注意设备温度。超过安全温度后,优先降低并发数,或者缩小模型体积。千万不要为了跑得快把风扇拆了或者屏蔽系统温控,这很容易真的烧坏硬件。
5.2 检索不到内容的常见原因
检索不到内容是记忆系统最常见的坑。通常不是因为算法不支持,而是下面几个原因:
- 记忆片段太长,导致语义被稀释。一条 2000 字的记忆混了多个主题,检索时相关片段无法精确命中。解决方法是缩小
chunk_size。 - 嵌入模型不匹配。有的嵌入模型擅长英文,对中文效果差;有的向量维度差异很大,入库和查询不能使用不同嵌入模型。
- 相似度阈值太高,相关结果被过滤掉。先调低阈值,看看能不能召回,再逐步提高。
- 向量库持久化路径丢失。换目录、换机器后,集合变成空库,检索结果当然为空。
- 写入成功后马上查询,但向量库还没有完成持久化。多数情况是异步写入导致,等几秒再查一次。
遇到检索结果不对,不要直接去改模型,先把记忆库里的内容打出来看看。如果库里根本没有相关内容,就写回;如果库里有但查不出来,才需要调参数。
5.3 内存不足或者 OOM 该怎么办
本地大模型最现实的问题是内存和显存告急。解决办法按优先级排序:
- 换更小的量化模型。7B 的 Q4 模型大约需要 4-6G 内存,13B 的 Q4 模型可能需要 8-10G,实际要看是否还有其他组件。不要为了追求效果强行上大模型。
- 缩短上下文长度。上下文从 8192 降到 4096,内存占用会明显减少,检索式记忆也可以弥补上下文缩短带来的信息缺失。
- 卸载常驻模型。Ollama 支持配置模型保活时间,如果不需要随时响应,可以设置更短的保活时间,让模型在空闲时释放内存。
- 减少同时常驻的模型数量。如果 Ollama 里加载了多个模型,内存会被同时占用。只保留当前要用的一个。
- 向量库设置单条最大长度和内存上限。ChromaDB 这类工具可以限制集合中的数据量,避免无限膨胀。
如果你用的是 32G 内存但跑 32B 甚至更大的模型仍然报内存不足,那就不是优化能解决的问题,应该换更小的模型或更激进的量化格式。原始材料里没有给出一套能适配所有硬件的配置,实际落地要以你的资源和口算为准。
6. 不想写代码,直接接平台也能做记忆
6.1 Dify 对接本地模型
如果不想自己维护 Python 脚本,可以用 Dify 这类平台把本地模型、嵌入模型和知识库拼起来。
大致的操作路径是:在 Dify 中配置 Ollama 模型作为对话模型和 Embedding 模型,创建一个知识库,导入文档或历史对话,再在应用设置里打开记忆开关,把检索到的知识片段拼入上下文。这样做的好处是界面化,不用写代码,可以直接看到检索命中的片段和模型输出。
需要说明的是,Dify 不同版本的功能入口和配置项会有差异,实际配置时要按你当前版本的界面为准。我一般建议先把本地模型跑通,再配置知识库,最后再开记忆功能。顺序反过来,出问题时会分不清是模型问题还是平台配置问题。
平台方案适合快速验证,但它的封装也会让你很难看到底层参数。如果只是学习,用平台没有问题;如果要调优到生产级,我还是会回到底层脚本,因为参数可控性更强。
6.2 接口化之后的批量场景
记忆系统验证完后,一个很自然的想法是把它封装成 API,给多个客户端用。
这个阶段要注意几点:
- 先保证单用户稳定,再上多用户并发。每个用户应该有自己的记忆命名空间,否则会互相污染。
- 增加请求队列和超时控制。不要让一个慢请求拖垮整个服务。
- 记录每次检索命中的内容和生成的耗时。这能帮你判断性能瓶颈是在向量检索,还是在大模型推理。
- 做好失败重试。模型可能临时不可用,嵌入接口可能超时,输出可能为空,都要有对应的重试流程。
我踩过的坑是:一开始只写了业务逻辑,完全没有日志。结果出了问题,既不知道检索到了什么,也不知道模型返回了什么,只能从头再排查一遍。后来我强制要求每一步都有日志:输入、检索结果、上下文片段、模型输出,全部落盘。
这个习惯在记忆系统里尤其重要,因为记忆系统的问题可能出在任何一个环节:数据写入、向量检索、上下文组装、模型生成。哪一环都有可能是“看起来正常但结果不对”。
回到开头那个问题:给本地大模型装记忆系统,不是让模型真的长生不老,而是把“信息从外部搬回上下文”这件事做得更快、更准、更省。我自己的建议是,先用小集合、小对话把链路跑通,再决定要不要上更多模型和更大向量库。很多问题不是工具能力不够,而是输入格式、资源上限和检索阈值没有调好。