部落格

藏在進度標籤裡的記憶體不足當機

九月底的一個晚上,某位使用者工作區中的 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 次。Socket 無法像產生的速度那樣快速送出,於是它們在寫入緩衝區中堆積成字串,約 690 MB。

修正方式

  • 限制標籤長度。 狀態標籤只需要說明正在發生什麼。現在它被限制在 200 個字元,並且我們會節流重新掃描部分命令的頻率。
  • 停止二次方的字串串接。 以重複字串串接來合併串流片段,對大型輸出而言是 O(n²)。我們改用以區塊為基礎的做法,以線性時間附加。

接著我們檢查了在大量輸出湧入時,是否還有其他可能無限制增長的地方,並為它們加上上限:

  • 聊天中顯示的即時命令輸出會被批次處理,並只保留最後 8,000 個字元。
  • 單行事件上限為 4 MB。
  • 瀏覽器在重新連線後用來追趕進度的重播緩衝區,每個專案上限為 8 MB。
  • 舊的檢查點會隨著回合進行而修剪,保留最新的狀態,以及早期回合需要的錨點。

如實測試大量輸出

有兩件事讓這件事比應有的更難測試:

  1. 真實模型不會配合。 要求模型印出一個巨大的檔案,它通常會拒絕、摘要,或以更合理的方式寫出。要測試大量輸出,你需要一個依你指示精確行動的腳本模型,並透過真實的代理人程式碼執行。
  2. 腳本必須表現得像模型。 我們第一個腳本模型在每次回覆時都重用同一個回應 ID,因此代理人框架把每次回覆都當成是上一次的替代,並提早結束回合。看起來像是 UI 重新整理的 bug,其實不是——是測試框架在騙我們。我們還必須在腳本中強制執行模型真實的上下文上限,否則記憶體數字就不真實。

我們從中學到什麼

  • 串流 UI 會放大小錯誤。 任何「每個區塊」都要送出的東西,都會被送出上萬次。要嘛送出增量,要嘛限制負載大小。
  • 為每個跨越慢速邊界的緩衝區設定上限。 Socket、重播紀錄與檢查點都需要一個天花板。
  • 先重現,再修正。 我們最初的兩個解釋聽起來很合理,但都是錯的。

如果你最近在 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 简体中文 繁體中文(香港)