令大量圖片嘅代理回合保持喺記憶體範圍之內
正式環境入面有一個代理回合,喺裁剪六張目錄掃描圖入面嘅產品,並檢查嗰啲裁剪圖嘅時候,耗盡咗 952 MB 記憶體。問題唔係一個洩漏,而係三個。每次呼叫模型,都會以全尺寸重新傳送該回合嘅圖片;我哋用嚟呼叫模型嘅函式庫,會一直保留每個請求,直到回合結束;而代理框架嘅追蹤功能,亦會保留每次呼叫嘅輸入,包括圖片,直到整個回合完結。我哋三個都修正咗:圖片喺傳送之前會縮細,而每次呼叫嘅圖片,亦會喺呼叫完成後即時釋放。
代理點樣睇圖片
喺 Mythex 建構嘅時候,代理會喺你項目嘅雲端沙盒入面工作。佢可以睇圖片,例如你附加嘅截圖、設計稿、項目入面嘅一個檔案,或者佢自己剛生成嘅圖片,方法係將圖片連同對話一齊傳送俾模型。
回合係指代理因為你嘅一則訊息而做嘅所有工作。喺一個回合入面,代理會多次呼叫模型:思考、執行工具、睇結果、再思考。每次呼叫都會傳送到目前為止嘅對話,而當中包括該回合嘅圖片,圖片會以文字形式編碼(base64,令佢大咗約三分之一)。
令我哋當機嗰個回合,做嘅嘢係合理嘅。佢取咗六張目錄掃描圖,每張約 2.4 MB,裁剪出單一產品,然後不斷讀返啲裁剪圖嚟檢查。咁樣就係好多次模型呼叫,每次都帶住幾張大圖。
原因一:每次呼叫都重新傳送全尺寸圖片
每次模型呼叫都會用全尺寸圖片重新組裝請求內容,圖片於回合入面每次都係全尺寸。每次呼叫大約係 20 MB 嘅 base64。請求內容係喺代理記憶體入面組成一個字串,所以每次呼叫都會再配置多約 20 MB。
而且全尺寸傳送根本冇帶嚟任何好處。模型自己會將圖片縮到長邊約 1,300 像素,所以額外嘅像素只係增加記憶體同上傳時間。
修正:一次縮細,之後都傳送細版
- 超過 400 KB 嘅圖片,傳送前會轉換成 JPEG,長邊最多 1,568 像素。細過咁嘅圖片則維持原樣。
- 轉換使用 ImageMagick,每個 Mythex 沙盒都有,而且每個檔案只會執行一次。一個喺每次呼叫都顯示同一批掃描圖嘅回合,會重用縮細後嘅副本。快取以檔案嘅路徑、大小同修改時間作為鍵,所以被編輯過嘅檔案會重新轉換;快取亦有自己嘅大小上限,唔會變成另一個吃光記憶體嘅來源。
- 單次模型呼叫中圖片嘅預算,由 16 MB 降到 6 MB。
同一六張掃描圖而家每次呼叫只需 4.4 MB,而唔係 19.7 MB。
原因二:模型客戶端從來唔釋放請求
每個請求本身嘅大小只係其中一部分。記憶體亦會隨著一次又一次嘅呼叫而增長。喺我哋嘅預備環境度,同樣類型嘅回合,即使做完完整嘅垃圾回收(即係所有無法到達嘅物件都已釋放嘅時間點),仍然佔住 369 MB 嘅活動記憶體。堆積取樣顯示,所有增長都發生喺模型客戶端函式庫編碼請求內容嘅部分。
原因係一個 abort 監聽器。當你撳 Stop,該回合嘅 abort signal 會被觸發,所有監聽緊佢嘅請求都會取消。我哋將該回合唯一嘅 signal 傳俾每個請求。模型客戶端函式庫會往俾佢嘅 signal 加一個「abort」監聽器,然後永遠唔移除。
所以,一個回合嘅每個請求,都會一直連繫住該回合嘅 signal,直至回合完結:監聽器持有請求嘅 controller,controller 持有請求,請求再持有整個請求內容,連同嗰啲圖片。
修正:每個請求一個 signal
而家每個請求都有自己嘅 signal。佢只會喺該請求運行期間連結到回合嘅 signal;如果係串流回應,就會一直到最後一個區塊被讀取為止,之後即時解除連結。Stop 仍然會觸達正在進行嘅請求,但回合嘅 signal 每次只會持有一個監聽器。
測試涵蓋咗上述三部分:
- 12 次呼叫之後,回合嘅 signal 上冇任何監聽器殘留。修正之前,有 12 個。
- 連續 15 個各 8 MB 嘅請求,記憶體保持平穩。修正之前,會增長 128 MB。
- 按 Stop 仍然可以喺串流中途中止請求。
原因三:追蹤保留咗每次呼叫嘅輸入
堆積快照顯示第三個持有者:每次模型呼叫大約保留 6 MB。呢個來自代理框架嘅事件串流。佢會將回合入面每次模型呼叫記錄為回合嘅子項,連輸入都包括,直到回合結束為止。每次呼叫附加嘅每張圖片,都跟住留喺記憶體入面。
修正:呼叫返回後清空圖片
圖片只會附加喺為某次特定模型呼叫而建立嘅訊息上面。佢哋從來唔會儲存喺對話嘅已儲存狀態入面。所以呼叫返回之後,清空嗰啲訊息入面嘅圖片數據係安全嘅。追蹤會保留該呼叫嘅文字記錄,像素就會被丟棄。
有一個細節好關鍵:圖片必須就地清空。追蹤器可以透過一則訊息上面嘅多個參照,接觸到同一份圖片數據,所以如果只係換成一個新嘅空清單,原本嗰份(連同像素)仍然可以被到達。已儲存嘅對話狀態則從來唔會被修改。
有一個測試會喺同一個事件串流下運行代理嘅圖,每次呼叫都附帶一張 4 MB 圖片。冇修正之前,16 次呼叫之後記憶體增長咗 77 MB。有修正之後,記憶體保持平穩。
我哋學到嘅嘢
- 記憶體問題可以分層出現。 三個獨立原因,每個都令圖片留喺記憶體太耐,三個都要修好,回合先可以保持喺記憶體範圍之內。
- 交俾函式庫嘅任何東西都可能被保留。 Signal 同追蹤都比佢哋被交俾嘅呼叫存活得更久。當記憶體隨每次呼叫而增長,就要留意呼叫觸及咗邊啲長壽嘅物件。
- 唔好傳送對方本來就會丟棄嘅嘢。 模型本身都會丟棄嘅像素,喺我哋每次呼叫時都佔用記憶體。
- 直接測試記憶體。 每個修正都附有一個測試,量度堆積增長或監聽器數目,所以出現退步時,會以數字顯示,而唔係變成崩潰。
呢個係近期第二個有關代理記憶體嘅故事。第一個係一個進度標籤被重新排隊咗 41,000 次。如果你想了解代理係乜嘢,以及佢點樣運作,可以睇吓我哋嘅 AI 代理指南,同對話文件。