部落格

讓大量圖片的代理回合留在記憶體之內

正式環境中的一個代理回合,在從六份商品目錄掃描檔裁切商品並檢視裁切結果時,耗盡了 952 MB 的記憶體。問題不是一個洩漏,而是三個。每次呼叫模型時,都會以完整尺寸重新傳送該回合的圖片;我們用來呼叫模型的函式庫會把每個請求保留到回合結束;而代理框架的追蹤功能則在整個回合中保留每次呼叫的輸入,包括圖片。我們修正了這三處:圖片在傳送前會先縮小,而且每次呼叫的圖片在呼叫完成後會立即釋放。

代理如何看見圖片

在 Mythex 上建置時,代理會在你專案的雲端沙箱中工作。它可以查看圖片,例如你附加的截圖、設計稿、專案中的檔案,或它自己剛產生的圖片,做法是把這些圖片連同對話一起送給模型。

回合是代理為了回應你的一則訊息所做的所有事情。在一個回合中,代理會多次呼叫模型:思考、執行工具、查看結果、再思考。每次呼叫都會送出目前為止的對話,其中包括該回合的圖片,並以文字(base64 編碼,會大約增加三分之一)的形式傳送。

當機的那個回合做的是合理的事情。它拿了六份大約各 2.4 MB 的商品目錄掃描檔,從中裁切出個別商品,並反覆讀取裁切結果來檢查。這代表多次模型呼叫,而且每次都帶著幾張大型圖片。

第一個成因:每次呼叫都重新傳送完整尺寸的圖片

每次模型呼叫都會以完整尺寸的圖片重新建立請求內容:每次約 20 MB 的 base64。請求內容是在代理的記憶體中以一個字串建立的,所以每次呼叫都會再配置約 20 MB。

以完整尺寸傳送其實毫無好處。模型自己會把圖片縮小到長邊約 1,300 像素,因此多出來的像素只會耗費記憶體與上傳時間。

修正方式:先縮小一次,之後傳送小尺寸

  • 超過 400 KB 的圖片,在傳送前會被轉換為長邊最多 1,568 像素的 JPEG。較小的圖片則維持原樣。
  • 轉換使用 ImageMagick,它內建於每個 Mythex 沙箱,而且每個檔案只執行一次。在每次呼叫都顯示相同掃描檔的回合中,會重複使用縮小後的副本。該快取以檔案的路徑、大小與修改時間為鍵,因此被編輯過的檔案會重新轉換,而且快取本身有大小上限,不會變成吃掉記憶體的元凶。
  • 單次模型呼叫中圖片的預算,從 16 MB 降到 6 MB。

同樣的六份掃描檔,現在每次呼叫的成本是 4.4 MB,而不是 19.7 MB。

第二個成因:模型用戶端從不釋放請求

單一請求的大小只是其中一部分。記憶體也會隨著一次又一次的呼叫而增長。在我們的預備環境中,同樣類型的回合即使在完整的垃圾回收後(也就是任何無法存取的物件都已被釋放的時間點),仍然持有 369 MB 的存活記憶體。堆積取樣顯示,所有的成長都發生在模型用戶端函式庫中負責編碼請求內容的部分。

原因是一個中止監聽器。當你按下 Stop,該回合的 abort signal 會被觸發,所有監聽它的請求都會取消。我們把這個整個回合共用的訊號傳給每一個請求。用戶端函式庫會在你給它的訊號上加入一個 "abort" 監聽器,而且從不移除。

於是,每個請求都會一直被該回合的訊號所參照,只要回合持續進行:監聽器持有請求的控制器,控制器持有請求,請求持有整個請求內容,包括圖片。

修正方式:每個請求一個訊號

現在每個請求都有自己的訊號。它只在該請求執行期間才連結到回合的訊號,對於串流回應,則是直到最後一個區塊被讀取為止,然後立即解除連結。按下 Stop 仍然能中止任何正在進行的請求,但回合的訊號同一時間只會持有一個監聽器。

測試涵蓋了這三個部分:

  • 經過 12 次呼叫後,回合的訊號上沒有任何監聽器殘留。修正前則有 12 個。
  • 每個 8 MB 的 15 次請求,記憶體維持持平。修正前則增加了 128 MB。
  • 按下 Stop 仍能在串流中途中止請求。

第三個成因:追蹤保留了每次呼叫的輸入

堆積快照顯示了第三個持有者:每次模型呼叫大約保留 6 MB。這來自代理框架的事件串流。它會把回合中的每一次模型呼叫記錄為該回合的子項目,包括輸入,直到回合結束為止。附加在每次呼叫上的每張圖片都會跟著留在記憶體中。

修正方式:呼叫返回後清空圖片

圖片只會附加在為特定模型呼叫而建立的訊息上,從不會儲存在對話的保存狀態中。因此,呼叫返回後,清空那些訊息中的圖片資料是安全的。追蹤記錄仍會保留該呼叫的文字紀錄;像素則被釋放。

有一個細節很重要:圖片必須就地清空。追蹤器可能透過訊息上的多個參照存取同一份圖片資料,因此如果換成一個新的空清單,原來那份(連同像素)仍然可以被存取。已保存的對話狀態從不會被修改。

一項測試在相同的事件串流下執行代理的圖形,每次呼叫都附加一張 4 MB 的圖片。沒有修正時,16 次呼叫後記憶體增加了 77 MB。有修正後,記憶體維持持平。

我們的收穫

  • 記憶體問題可能分層出現。 三個各自的成因都讓圖片保留過久,三者都必須解決,回合才能維持在記憶體限制之內。
  • 交給函式庫的任何東西都可能被保留。 訊號與追蹤都比它們所服務的呼叫存活得更久。當記憶體隨每次呼叫增長時,請檢查這次呼叫碰到了哪些長壽命的物件。
  • 不要傳送對方會丟棄的東西。 模型本來就會捨棄的像素,卻在每次呼叫時消耗我們的記憶體。
  • 直接測試記憶體。 每項修正都附帶測量堆積成長或監聽器數量的測試,因此回歸會以數字呈現,而不是以當機呈現。

這是近期第二個與代理有關的記憶體故事;第一個是一個進度標籤重複傳送了 41,000 次。如果你想了解代理是什麼、如何運作,請參閱我們的AI 代理指南與對話文件。

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 简体中文 繁體中文(香港)