Képekkel teli ügynöki körök a memórián belül
Egy éles ügynöki kör 952 MB-nál kifutott a memóriából, miközben hat katalógusszkennelésből vágott ki termékeket, és visszanézte a kivágásokat. Nem egyetlen szivárgás volt, hanem három. A modellhívások mindegyike a kör képeit teljes méretben küldte újra; a modellhívó könyvtár a kör végéig életben tartotta minden kérést; az ügynökkeretrendszer nyomkövetése pedig a kör egész ideje alatt megtartotta minden hívás bemenetét, benne a képekkel. Mindhármat javítottuk: a képeket küldés előtt kicsinyítjük, és minden hívás képeit elengedjük, amint a hívás véget ér.
Hogyan látja az ügynök a képeket
A Mythexen az ügynök a projekted felhős sandboxában dolgozik. Képeket nézhet meg: egy képernyőképet, amit csatolsz, egy makettet, egy fájlt a projektedben, vagy egy képet, amelyet épp maga készített. Ezt úgy teszi, hogy a képeket a beszélgetéssel együtt elküldi a modellnek.
Egy kör mindaz, amit az ügynök egy üzenetedre válaszul csinál. Egy körön belül az ügynök sokszor hív modellt: gondolkodik, lefuttat egy eszközt, megnézi az eredményt, újra gondolkodik. Minden ilyen hívás elküldi az eddigi beszélgetést, benne a kör képeivel, szöveges formában kódolva (base64, ami kb. egyharmadával nagyobbá teszi őket).
Az összeomlott kör ésszerűen viselkedett. Hat katalógusszkennelést vett, egyenként kb. 2,4 MB-osat, ezekből egyes termékeket vágott ki, és újra meg újra visszaolvasta a kivágásokat ellenőrzés céljából. Ez sok modellhívás, mindegyik több nagy képpel.
1. ok: minden hívás teljes méretben küldte újra a képeket
Minden modellhívás újraépítette a kérés törzsét, benne a kör képeivel teljes méretben — hívásonként kb. 20 MB base64. A törzs egy szövegként épül fel az ügynök memóriájában, így minden hívás újabb ~20 MB-ot foglalt.
A teljes méretű küldés semmit nem hozott. A modell maga kb. 1300 pixelre kicsinyíti a képeket a hosszabb oldalon, így a plusz pixelek csak memóriát és feltöltési időt kóstáltak.
A javítás: egyszer kicsinyíts, kicsit küldj
- A 400 KB-nál nagyobb képeket a küldés előtt legfeljebb 1568 pixeles hosszú oldalú JPEG-gé alakítjuk. A kisebbek változatlanul mennek.
- Az átalakítás az ImageMagickkel történik, amely minden Mythex-sandboxban megtalálható, és fájlonként egyszer fut le. Az a kör, amely minden hívásban ugyanazokat a szkenneléket mutatja, a kicsinyített másolatot újrahasználja. A gyorsítótárat a fájl elérési útja, mérete és módosítási ideje azonosítja, így a szerkesztett fájl újra átalakul, és a gyorsítótárnak saját méretkorlátja van, hogy ne a memóriát töltse meg.
- Egy hívás képeire vonatkozó keretet 16 MB-ról 6 MB-ra csökkentettük.
Ugyanez a hat szkennelés most hívásonként 4,4 MB-ba kerül a korábbi 19,7 MB helyett.
2. ok: a modellkliens sosem engedte el a kérést
A kérések mérete csak egy része volt a problémának. A memória hívásról hívásra is nőtt. A staging környezetünkon ugyanilyen kör 369 MB élő memóriát tartott akkor is, miután egy teljes szemétgyűjtés lefutott — ez az a pont, ahol minden elérhetetlen objektum fel van szabadítva. A heap-mintavétel az összes növekedést a modellkliens-könyvtárnak arra a részére helyezte, amely a kérés törzsét kódolja.
Az ok egy megszakítás-figyelő volt. Ha megnyomod a Leállítás gombot, a kör megszakítási jele tüzel, és minden figyelő kérés megszakad. Mi ugyanazt a körszintű jelet adtuk át minden kérésnek. A kliens könyvtár „abort” figyelőt tesz arra a jelre, amit kap — és sosem távolítja el.
Vagyis a kör minden kérése elérhető maradt a körjelről, a kör egész ideje alatt: a figyelő tartotta a vezérlőt, a vezérlő a kérést, a kérés pedig az egész kérés-törzset a képekkel együtt.
A javítás: egy jel kérésenként
Minden kérés most saját jelet kap. Ez csak addig kapcsolódik a kör jeléhez, amíg az adott kérés fut — streamelt válasz esetén az utolsó darab beolvasásáig —, utána azonnal leválasztjuk. A Leállítás továbbra is eléri a folyamatban lévő kérést, de a körjel egyszerre csak egy figyelőt tart.
A tesztek mindhárom részt lefedik:
- 12 hívás után nem marad figyelő a körjelen. A javítás előtt 12 volt.
- 8 MB-os, 15 kérés esetén a memória egyenes marad. Előtte 128 MB-tal nőtt.
- A Leállítás továbbra is megszakít egy kérést a streamje közepén.
3. ok: a nyomkövetés megtartotta minden hívás bemenetét
Egy heap-pillanatkép harmadik tartót mutatott: hívásonként kb. 6 MB-ot. Ez az ügynökkeretrendszer eseményfolyamából származott. Minden modellhívást a körön belül gyermekként rögzít, a bemenetével együtt, egészen a kör végéig. Minden képet, amely bármelyik hívásnak csatolva volt, mellette tartott a memóriában.
A javítás: a képek ürítése, amint a hívás visszatér
A képek csak az adott modellhíváshoz készített üzenetekhez kerülnek hozzá. Sosem kerülnek a beszélgetés mentett állapotába. Ezért miután a hívás visszatért, biztonságosan kiüríthetjük az üzenetek képadatait. A nyomkövetés megtartja a hívás szöveges nyilvántartását; a pixelek eltűnnek.
Egy részlet fontos volt: a képeket helyben kell kiüríteni. A nyomkövető ugyanahhoz a képadathoz több hivatkozáson keresztül is hozzáférhet egy üzeneten belül, ezért egy új, üres lista behelyezése az eredetit — pixelekkel együtt — elérhetően hagyná. A mentett beszélgetési állapotot sosem módosítjuk.
Egy teszt lefuttatja az ügynök gráfját ugyanezzel az eseményfolyam-követéssel, minden hívásnál 4 MB-os képpel. A javítás nélkül a memória 16 hívás alatt 77 MB-tal nőtt. A javítással egyenes marad.
Mit vittünk ebből magunkkal
- A memóriaproblémák rétegekben jöhetnek. Három külön ok tartotta túl sokáig a képeket, és mindháromnak el kellett tűnnie, hogy a kör a memórián belül maradjon.
- Bármit átadsz egy könyvtárnak, megtarthatja. Egy jel és egy nyomkövetés is túlélte a hívásokat, amelyeknek átadtuk őket. Ha a memória hívásonként nő, nézd meg, milyen hosszú életű objektumokat ér el a hívás.
- Ne küldj olyat, amit a másik fél eldob. A pixelek, amelyeket a modell úgyis eldobna, hívásonként memóriát kóstáltak.
- Teszteld közvetlenül a memóriát. Minden javításhoz tartozik egy teszt, amely a heap-növekedést vagy a figyelőszámot méri, így egy visszaesés szám formájában jelentkezik, nem összeomlásként.
Ez rövid időn belül a második memóriatörténet az ügynök kimenetéből: az első egy állapotjelző címke, amely 41 000-szer lett újra elküldve. Ha többet szeretnél tudni arról, mi az ügynök és hogyan működik, nézd meg az AI-ügynökök útmutatónkat és a chat dokumentációt.