博客

藏在进度标签里的内存溢出崩溃

9 月下旬的一个晚上,一位用户工作区中的 AI 智能体在一轮对话进行中因内存溢出(out-of-memory)而崩溃。最明显的嫌疑对象——同时运行太多子智能体,或者上下文增长得太大——都不是原因。真正的原因是一个状态标签:模型每次只流式输出几个字符来生成一条很大的 shell 命令,而我们每发送一个片段,都会把到目前为止的完整命令重新作为进度标签发送一次。一条 124 KB 的命令,产生了大约 41,000 个事件,以及约 690 MB 排队中的字符串。

智能体如何流式输出工作

在 Mythex 上构建时,智能体运行在你项目专属的私有云沙盒中。模型生成内容时,输出会以流的形式回传到你的浏览器:文字逐词出现;当它运行某个工具——例如写入文件的 shell 命令——时,你会看到该工具的标签实时更新,让你知道它正在做什么。

模型以很小的片段进行流式输出。以我们当时使用的模型来说,这些片段往往只有三个字符。

最初的假设是错的

这次崩溃看起来像是规模问题,所以我们先从那里入手。

  • 并行的子智能体? 这一轮只用了两个,每个持有大约 1.5 MB 的状态。不足以导致崩溃。
  • 保留的追踪数据? 我们的追踪功能会在内存中保存一棵运行树,但它的大小远不足以解释崩溃时的堆大小。

这两种说法都很有道理,而且都很容易相信。但两者都无法复现这次崩溃。我们一再重新学到的教训是:如果无法复现故障,它就还不是原因。

真正地复现它

我们搭建了一个临时沙盒,内存限制与生产环境相同,并使用一个假模型,以真实的三字符片段流式输出一个很大的文件。我们还让另一端的读取者变慢,模拟真实的网络。

这样就成功复现了。堆内存不断攀升,进程以同样的错误退出。

一旦能够观察它,机制就很简单了。模型在写入一条很大的命令时——在这个例子中是一个写入 124 KB 文件的 heredoc——我们的流式代码会在每个片段上更新工具的标签。标签的内容不是新的三个字符,而是到目前为止的整条命令。因此事件不断增长:3 个字符,然后 6 个,然后 9 个……一直到 124 KB,共约 41,000 次。套接字发送的速度跟不上生成的速度,于是这些内容在它的写入缓冲区中以字符串的形式堆积起来,总计约 690 MB。

修复

  • 限制标签长度。 状态标签只需要说明正在发生什么。现在它被限制为 200 个字符,并且我们会限制重新扫描部分命令的频率。
  • 停止二次方级的字符串拼接。 用重复的字符串拼接来连接流式片段,对于大输出是 O(n²) 的。我们改用基于分块的方法,以线性时间追加。

然后我们检查了其他可能在大量输出洪流下无限增长的地方,并为它们设定了上限:

  • 聊天中显示的实时命令输出会被批量处理,并且只保留最后 8,000 个字符。
  • 单行事件的长度上限为 4 MB。
  • 浏览器在重新连接后用于追赶进度的回放缓冲区,每个项目的上限为 8 MB。
  • 旧的检查点会随着轮次推进被修剪,只保留最新的状态以及早前轮次所需的锚点。

诚实地测试洪流

有两件事让这次测试比应有的更难:

  1. 真实模型不会配合。 要求模型输出一个巨大的文件,它通常会拒绝、做摘要,或者更合理地写入。要测试洪流,你需要一个脚本化的模型,它会严格按照你的指示行事,同时运行在真实的智能体代码之中。
  2. 脚本必须表现得像模型。 我们最初的脚本化模型在每次回复中都复用了相同的响应 ID,因此智能体框架把每次回复都当作对上一次的替换,于是提前结束了轮次。这看起来像是界面刷新的 bug,实际上不是——是测试工具在误导我们。我们还必须在脚本中强制执行模型真实的上下文限制,否则内存数据就不真实。

我们的收获

  • 流式界面会放大小错误。 任何「按片段」发送的东西,都会被发送数万次。要么发送增量,要么限制载荷大小。
  • 为每一个跨越慢速边界的缓冲区设置上限。 套接字、回放日志和检查点都需要一个上限。
  • 先复现,再修复。 我们最初的两种解释听起来都很对,但都是错的。

如果你最近在 Mythex 聊天中看到一个很长的文件出现,而智能体没有卡住,这就是部分原因。

Mythex · Pricing · 用 AI 构建任何东西 · 网站和应用模板 · Mythex 对比 · AI 应用构建术语表 · 免费的 AI 应用构建工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 繁體中文(香港) 繁體中文(台灣)