Агентски ходове с много изображения, които се побират в паметта
Един агентски ход в продукция изчерпа паметта при 952 MB, докато изрязваше продукти от шест сканирания на каталог и разглеждаше изрязаните части. Нямаше едно изтичане — имаше три. Всяко извикване към модела изпращаше наново изображенията на хода в пълен размер; библиотеката, която използваме за извикване на модела, пазеше всяка заявка жива до края на хода; а трасирането на агентската рамка пазеше входовете на всяко извикване, включително изображенията, за целия ход. Поправихме и трите: изображенията се смаляват, преди да бъдат изпратени, а изображенията на всяко извикване се освобождават веднага щом то приключи.
Как агентът вижда изображенията
Когато създавате с Mythex, агентът работи в облачната sandbox среда на проекта ви. Той може да разглежда изображения — скрийншот, който прикачите, макет, файл в проекта ви или изображение, което сам току-що е създал — като ги изпраща към модела заедно с разговора.
Ход е всичко, което агентът прави в отговор на едно ваше съобщение. В рамките на хода агентът извиква модела много пъти: мисли, пуска инструмент, гледа резултата, мисли отново. Всяко от тези извиквания изпраща досегашния разговор, а това включва изображенията на хода, кодирани като текст (base64, което ги прави около една трета по-големи).
Ходът, който се срина, вършеше нещо разумно. Взе шест сканирания на каталог, около 2,4 MB всяко, изряза отделни продукти от тях и продължаваше да чете изрязаните части обратно, за да ги провери. Това са много извиквания към модела, всяко от които носи няколко големи изображения.
Причина 1: всяко извикване изпращаше наново изображенията в пълен размер
Всяко извикване към модела изграждаше отново тялото на заявката с изображенията на хода в пълен размер — около 20 MB base64 при всяко извикване. Тялото се изгражда като един низ в паметта на агента, така че всяко извикване заделяше още около 20 MB.
Изпращането в пълен размер дори не носеше полза. Моделът сам смалява изображенията до около 1300 пиксела по по-дългата страна, така че допълнителните пиксели само харчат памет и време за качване.
Решението: смаляване веднъж, изпращане малко
- Изображенията над 400 KB се превръщат в JPEG с най-много 1568 пиксела по по-дългата страна, преди да бъдат изпратени. По-малките изображения се изпращат както са.
- Превръщането използва ImageMagick, който е в всяка Mythex sandbox среда, и се изпълнява веднъж за всеки файл. Ход, който показва едни и същи сканирания при всяко извикване, използва смаленото копие повторно. Кешът е ключиран по пътя на файла, размера и времето на последна промяна, така че редактиран файл се превръща отново, а кешът има собствен таван на размера, за да не стане сам той причината паметта да се запълни.
- Бюджетът за изображения в едно извикване към модела пада от 16 MB на 6 MB.
Същите шест сканирания сега струват 4,4 MB на извикване вместо 19,7 MB.
Причина 2: клиентът към модела никога не освобождаваше заявка
Размерът на всяка заявка беше само част от проблема. Паметта също растеше от извикване към извикване. В нашата staging среда същият вид ход държеше 369 MB жива памет дори след пълно събиране на боклука — момента, в който всичко недостижимо е освободено. Снимането на heap показа, че целият растеж е в частта от клиентската библиотека към модела, която кодира телата на заявките.
Причината беше слушател за прекъсване. Когато натиснете Stop, сигналът за прекъсване на хода се задейства и всяка заявка, която го слуша, се отменя. Ние подавахме този един сигнал за целия ход на всяка заявка. Клиентската библиотека добавя слушател „abort“ към всеки сигнал, който получи — и никога не го премахва.
Така че всяка заявка от хода оставаше достижима от сигнала на хода, докато ходът продължаваше: слушателят, който държеше контролера на заявката, който държеше заявката, която държеше цялото тяло на заявката с изображенията му.
Решението: един сигнал за всяка заявка
Всяка заявка вече получава собствен сигнал. Свързва се със сигнала на хода само докато тази заявка работи — при поточен отговор до прочитането на последния фрагмент — и се разкачва веднага след това. Stop все още стига до заявката, която е в ход, но сигналът на хода държи най-много един слушател едновременно.
Тестовете покриват и трите части:
- След 12 извиквания няма слушатели да останат върху сигнала на хода. Преди поправката имаше 12.
- При 15 заявки по 8 MB всяка паметта остава права. Преди растеше с 128 MB.
- Натискането на Stop все още прекъсва заявка по средата на нейния поток.
Причина 3: трасирането пазеше входовете на всяко извикване
Снимка на heap показа трети държател: около 6 MB, пазени на всяко извикване към модела. Това идваше от поточното предаване на събития на агентската рамка. Тя записва всяко извикване към модела в хода като дете на хода, включително входовете, докато ходът не завърши. Всяко изображение, прикачено към всяко извикване, оставаше в паметта заедно с него.
Решението: изчистване на изображенията веднага след връщане на извикването
Изображенията се прикрепят само към съобщенията, построени за конкретно извикване към модела. Никога не се съхраняват в запазеното състояние на разговора. Затова след като извикването върне резултат, е безопасно да се изтрие данните на изображенията в тези съобщения. Трасирането запазва текстов запис на извикването; пикселите се махат.
Една подробност беше решаваща: изображенията трябва да се изчистват на място. Трасьорът може да достигне същите данни на изображението през повече от една референция в съобщението, така че подмяната със нов, празен списък би оставила оригинала — с пикселите — все още достижим. Запазеното състояние на разговора никога не се променя.
Тест пуска графа на агента под същото поточно предаване на събития с изображение от 4 MB при всяко извикване. Без поправката паметта растеше с 77 MB за 16 извиквания. С нея паметта остава права.
Какво взехме от това
- Проблемите с паметта могат да идват на слоеве. Три отделни причини държаха изображения твърде дълго и трите трябваше да се махнат, за да остане ходът в рамките на паметта.
- Всичко, което подадете на библиотека, може да бъде запазено. И сигналът, и трасирането надживяваха извикванията, на които бяха подадени. Когато паметта расте на извикване, гледайте кои дълготрайни обекти извикването докосва.
- Не изпращайте това, което другата страна така или иначе изхвърля. Пикселите, които моделът би отхвърлил, ни струваха памет при всяко извикване.
- Тествайте паметта директно. Всяка поправка дойде с тест, който измерва растеж на heap или броя на слушателите, така че регресията се вижда като число, а не като срив.
Това е втората история за паметта, свързана с агента, за кратко време; първата беше етикет за прогрес, изпратен 41 000 пъти. Ако искате да научите повече за това какво е агентът и как работи, вижте нашето ръководство за AI агенти и документацията за чата.