MemGPT - Towards LLMs as Operating Systems
[!MemGPT 的 OS 类比词典]
- main context: LLM 的 context window, 类比物理 RAM
- external context: 向量库 / 关系库 / 日志, 类比 disk
- function call: 模型主动触发的 I/O, 类比 system call
- queue manager: FIFO 驱逐器, 类比 page replacement policy
- heartbeat: 定时唤醒, 类比 timer interrupt
Context Window 的硬上限
现有商用 LLM 每次 forward 能看到的 token 数是有限的, 比如 GPT-4 turbo 是 128K, Llama-2-chat 是 4K; 一旦 input + output 超出这个窗口, 后续内容要么被截断, 要么只能靠外部检索 (classic RAG) 在推理前硬塞进去;
这带来两个根本问题:
- 长对话: session 时间够长后, 早期轮次会被挤出窗口, 模型彻底"忘"掉用户之前说过的事实
- 长文档分析: 单份文档已经接近或超过 context 上限, 跨章节的 key-value 关联几乎做不了
两个问题本质都是同一个: context 是昂贵的短期存储, 不能无限扩张, 需要一种外部存储 + 调度机制;
另外注意, 自注意力的计算量是 , 直接把窗口拉到无限大在工程上也不现实, 因此"扩窗口"这条路至少短期内是有上限的;
OS Memory Hierarchy 做类比
传统 OS 已经用几十年解决了完全同构的问题, MemGPT 的直觉就是照搬这套层级:
| 层级 | 容量 | 延迟 | OS 中的角色 | MemGPT 中的角色 |
|---|---|---|---|---|
| Register / Cache | 极小 | 极低 | CPU 直接取用 | attention 直接读取的 prompt token |
| RAM | 有限 | 低 | 活跃程序的状态 | main context — 本轮 forward 能看到 |
| Disk / SSD | 很大 | 高 | 非活跃程序 / 文件系统 | external context — 向量库 / 对话历史 / KV store |
OS 通过 MMU, page table, page fault handler 让程序看起来有一个几乎无限的虚拟地址空间; MemGPT 想复刻这一套, 让 LLM 看起来有一个几乎无限的上下文;
区别是: OS 里 page fault 由 MMU 硬件触发, 而 MemGPT 里 page fault 由模型自己通过 function call 主动触发, 所以模型本身扮演了一部分 kernel 的角色;
论文定位
MemGPT 不是一个新模型, 也不是一个新的 attention 变种, 而是一个围绕现有 function-calling LLM 搭建的 memory management system; 它的核心主张是:
- 与其等 context length 继续增长, 不如让模型主动管理自己的 context
- 让模型通过 function call 发起 “装入 / 驱逐 / 归档” 操作
- 把 LLM 本身当成 kernel 的一部分, 决定什么该留在 RAM, 什么该换出
论文解决的核心问题
现有方案的不足:
- 暴力拉大 context length: 吃 的 attention 成本, 而且窗口再大也仍然存在超出的风险, 并不是根治
- classic RAG: 在一次 forward 之前做一次向量检索, 然后把结果塞进 prompt 头部; 问题是 retrieval 时机固定, 只能被动响应当前 query, 无法在对话中途决定"我现在需要回忆起三周前那句话"
- sliding window / attention sink: 只是延缓遗忘, 没有持久化, 也没有"回忆"能力
MemGPT 试图回答:
- 能不能让 LLM 自己决定何时去 external storage 拉数据?
- 能不能让 LLM 自己决定把哪些 context 驱逐 / 归档?
- 能不能把上面两件事用统一的 function-call 接口暴露, 保持可观测和可扩展?
insight
Main context 的三段式分层
MemGPT 把 context window 不视作一块扁平的 prompt buffer, 而是切成三个区域:
| 分区 | 可写性 | 生命周期 | 类比 |
|---|---|---|---|
| System instructions | read-only | 整个 session 常驻 | OS kernel code |
| Working context | read-write, 模型显式编辑 | 跨 turn 持续 | 进程 heap / 长期状态 |
| Conversational context | append-only, FIFO 驱逐 | 短期, 会被挤出 | CPU 的 working set |
其中 working context 是最关键的设计: 它是一个模型自己能写的小笔记本, 用于持久化那些"我希望一直记得的事实", 比如用户的名字, 偏好, 角色设定; 在 OS 类比里 working context 不是 buffer cache, 而是"进程自己持有的数据结构", 模型把它当作第一人称状态来编辑;
External context 的两种后端
超出 main context 的所有内容会流向 external context, 分两类:
- Recall storage: 保存所有历史 event / message 的原始流水, 支持文本或向量检索; FIFO 驱逐下来的消息并不真的被删除, 而是进入 recall storage, 模型之后可以通过
conversation_search拉回来 - Archival storage: 模型主动写入的持久化知识库, 形式上是一组可检索的 document chunks; 比 recall 更结构化, 适合放笔记 / 摘要 / 总结
两者都只有通过 function call 才能被访问, 不会未经请求就被塞进 prompt; 这一点很重要, 它让 external context 的流量是受控的, 不会像 RAG 那样一上来就挤占 prompt 开头;
Function calls as system calls
MemGPT 依赖 function-calling LLM (原论文用的是 GPT-4), 给模型暴露一组统一接口, 每次 forward 可以输出若干 function call:
send_message: 正常回复用户conversation_search,archival_memory_search: 从 external storage 拉取core_memory_append,core_memory_replace: 编辑 working contextarchival_memory_insert: 把新知识沉淀到 archivalpause_heartbeats: 暂停后台中断
每个 function call 对应一次"系统调用", 返回结果会作为下一次 forward 的 observation 注入; 这种"模型输出 kernel 执行 observation 回注"的循环, 和 OS 里 user space kernel space 返回 user space 的切换几乎一一对应;
Queue manager 与 FIFO 驱逐
Conversational context 的容量有限, 当 token 使用率超过阈值 (论文里用 main context 容量的某个比例) 就触发 queue manager:
- 最早一批消息从 conversational context 驱逐 (evict)
- 同一份内容同时被归档到 recall storage, 保留可检索性
- 模型可以通过
conversation_search再把这些内容召回 (recall) 回 main context
这就是一个完整的 page-out page-in 周期; 与 OS 的 LRU/LFU 不同, 这里的驱逐策略显式采用 FIFO, 因为时间顺序本身就是对话语义的一部分, LRU 的"最近访问"在对话里反而不自然;
- 驱逐不等于删除: 消息仍在 recall storage 里
- 召回不等于重复: 召回后消息会被重新插入 main context, 但原位归档仍然保留
Heartbeat: 模型的自我中断
对话类场景有一个特殊问题: 模型只在用户有输入的时候才被调用; 但很多"记忆维护"动作其实希望在空闲时间做, 比如对一段长对话先做摘要再写入 archival;
MemGPT 的方案是 heartbeat events:
- 系统会在某些条件下主动给模型一条空消息, 等价于一次硬件 timer interrupt
- 模型也可以在本次 function call 中把
request_heartbeat=true, 表示"我还有后续工作要做, 请再给我一次机会" - 这让模型能做多步连续动作, 而不被"每轮只能输出一次"的默认 chat 协议卡住
这和 OS 中 中断 + syscall return 的组合极其相似: function call 返回后如果请求了 heartbeat, 等价于 schedule 一个后续 tick;
MemGPT vs Classic RAG
| 维度 | Classic RAG | MemGPT |
|---|---|---|
| 检索时机 | forward 之前, 固定一次 | 模型决定何时, 可以任意多次 |
| 检索对象 | 静态知识库 | 对话历史 + 模型自建 archival |
| 写入能力 | 只读 | 模型可写 working / archival |
| 驱逐策略 | 无 (每次都重构 prompt) | FIFO, 保留原始归档 |
| 工作方式 | reactive — 针对当前 query 拉文档 | agentic — 主动管理自己的记忆 |
| 失败模式 | retrieval miss 直接体现在答案里 | 模型可以察觉 miss 并重新检索 |
结论: RAG 是一次性 context 装填, MemGPT 是持续的 context 调度;
评估: 两个场景都是 context 上限放大器
Deep memory retrieval (多 session 对话)
设定: 给定先前 session 中提到过的事实, 在新 session 里正确引用;
baseline (固定 context) 在 session 数超过窗口容量后准确率断崖式下跌; MemGPT 依靠 conversation_search 主动查询早期 session, 能把准确率维持在高位;
关键启示: 让模型意识到自己应该去查, 比让模型天然记住更可扩展;
Nested key-value retrieval (长文档)
设定: 一个文档内部存在多层引用 (A B C …), 要求模型最终给出末端值;
固定 context 装不下整份文档, 每次跳转都失败; MemGPT 通过 archival_memory_search 做多次跳转式检索, 能显著延长可成功跳转的层数;
关键启示: 长文档不是一次性全部装入的问题, 而是如何做受控随机访问的问题;
四个工程化细节值得留意
- Prompt 里的 kernel 协议: 模型要能可靠调用 function, 必须在 system prompt 里用自然语言告诉模型自己是 MemGPT 的一部分, 把 OS 类比直接交代清楚; 这其实是一次 prompt-level 的 OS ABI 定义
- Function schema 严格校验: 非 function-calling 模型 (比如早期开源 7B) 很容易出现格式漂移, 论文里用的是 GPT-4, 下游实现需要考虑格式矫正层
- Recall 的检索不是向量必选: 论文强调支持文本检索 (BM25) 和向量检索的混合, 因为对话历史里大量是精确字面引用, 向量反而会 miss
- Archival 的写入是幂等式的: 同一段摘要可能被多次追加, 工程上要做 dedup / merge, 否则 archival 会退化成垃圾场
具体的开源落地在 Letta (原 MemGPT 项目) 里有进一步实现, 工程上的 schema / retry / 格式矫正这里先挖个坑, 后面单独开一篇笔记讨论;
如果按论文主线浓缩, MemGPT 的主流观点可以总结为:
- Context length 的物理上限短期内不会被彻底抹平, 因此必须引入分级存储思想
- LLM 本身已经足够强, 可以扮演 memory management 的决策者, 而不是被动的 context 消费者
- Function call 是一种天然的"用户态 内核态"边界, 把它复用为 memory API 是最小侵入的做法
- 驱逐策略应当保留语义顺序 (FIFO), 而不是 OS 里常用的 LRU, 因为对话本身是时间敏感的
- Heartbeat 机制让 LLM 从"一次回复 = 一次决策"进化到"可以连续做多步内部任务", 为 agentic 行为打下协议基础
- MemGPT 不是 RAG 的替代品, 而是把 RAG 从一次性 retrieval 泛化为可调度的 memory subsystem
