網誌

隱藏在進度標籤中的記憶體不足崩潰

九月底的一個晚上,一位用戶工作區中的 AI 代理,在一個回合進行到一半時因記憶體不足而崩潰。我們最先懷疑的兩個原因——同時運行的子代理太多,或上下文增長得太大——都不對。真正的原因是一個狀態標籤:模型以每次幾個字元的方式串流一個大型 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,因此代理框架把每個回覆都視為取代上一個,並提早結束回合。這看起來像是介面刷新的錯誤,但並不是——是測試框架在誤導我們。我們還必須在腳本中強制執行模型真實的上下文限制,否則記憶體數字就不切實際。

我們的心得

  • 串流介面會放大細微錯誤。 任何「每個片段」都要傳送的東西,都會被傳送數以萬計次。傳送增量,或限制負載大小。
  • 為每個跨越慢速邊界的緩衝區設定上限。 套接字、重播紀錄和檢查點都需要一個天花板。
  • 先重現,再修復。 我們最初兩個解釋聽起來都對,但都錯了。

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