Падот од недостиг на меморија скриен во една статусна лабела
Една вечер во доцниот септември, AI агентот во работниот простор на еден корисник падна со грешка out-of-memory среде тек. Очигледните осомничени — премногу под-агенти што работат истовремено или контекст што порасна предолго — беа погрешни и двете. Вистинската причина беше статусна лабела: додека моделот пренесуваше голема shell команда по неколку знаци, ние ја повторно испраќавме целата команда до тој момент како лабела за напредок при секој дел. Една команда од 124 KB стана околу 41.000 настани и околу 690 MB наредени стрингови.
Како агентот го пренесува својата работа
Кога градите на Mythex, агентот работи во приватната облачна sandbox средина на вашиот проект. Додека моделот пишува, излезот се пренесува назад во вашиот прелистувач: текстот се појавува збор по збор, а кога агентот извршува алатка — да речеме shell команда што пишува датотека — ја гледате нејзината лабела како се ажурира во живо, па знаете што прави.
Моделот пренесува во мали делови. Со моделот што го користевме, тие делови често беа долги само три знаци.
Првите хипотези беа погрешни
Падот изгледаше како проблем со скалирање, па почнавме таму.
- Паралелни под-агенти? Текот користел само два, секој со околу 1,5 MB состојба. Тоа не е доволно.
- Задржани трагови? Нашето следење чуваше дрво на извршувања во меморија, но тоа не беше ни блиску до големината на heap-от при падот.
И двете приказни беа веродостојни, и двете звучеа убедливо и беа лесни за прифаќање. Ниту една не го репродуцираше падот. Лекцијата што постојано ја учиме: хипотезата не е причина додека не го репродуцира дефектот.
Репродуцирање вистински
Изградивме привремена sandbox средина со истите ограничувања за меморија како продукцијата и лажен модел што пренесуваше голема датотека во реалистични делови од по три знаци. Го направивме и читачот на другиот крај од сокетот бавен, како вистинска мрежа.
Тоа го направи. Heap-от растеше и процесот умре со истата грешка.
Механизмот беше едноставен откако можевме да го гледаме. Додека моделот пишуваше голема команда — во овој случај heredoc што запишуваше датотека од 124 KB — нашиот код за пренесување ја ажурираше лабелата на алатката при секој дел. Лабелата не беше новите три знаци; беше целата команда до тој момент. Настаните растеа: 3 знаци, па 6, па 9, … до 124 KB, околу 41.000 пати. Сокетот не можеше да ги испраќа толку брзо колку што се произведуваа, па се натрупуваа во неговиот бафер за пишување како стрингови — околу 690 MB од нив.
Поправката
- Ограничете ја лабелата. Статусната лабела треба само да каже што се случува. Сега е ограничена на 200 знаци, а повторното скенирање на делумната команда го ограничуваме по фреквенција.
- Запрете ја квадратната конкатенација. Спојувањето делови со повторено надоврзување на стрингови е O(n²) за големи излези. Преминавме на пристап базиран на парчиња, кој додава во линеарно време.
Потоа побаравме сè друго што може да расте без граница при поплава од излез, и го ограничивме:
- Живиот излез од команди прикажан во разговорот се групира и се задржуваат последните 8.000 знаци.
- Поединечните линии на настани се ограничени на 4 MB.
- Баферот за повторно пуштање што прелистувачот го користи за да се стигне по повторно поврзување е ограничен на 8 MB по проект.
- Стари checkpoint-и се чистат додека тек напредува, задржувајќи ја најновата состојба и котвите што ги бараат претходните текови.
Тестирање на поплави чесно
Две работи го направија ова потешко за тестирање отколку што требаше да биде:
- Вистинските модели не соработуваат. Ако побарате модел да испечати огромна датотека, обично ќе одбие, ќе резимира или ќе ја напише попаметно. За да тестирате поплави ви треба скриптиран модел што точно го прави она што му кажете, работејќи низ вистинскиот код на агентот.
- Скриптите мора да се однесуваат како модели. Нашиот прв скриптиран модел го користеше истиот ID на одговор при секој одговор, па рамката на агентот го третираше секој одговор како замена за претходниот и тековите завршуваа рано. Изгледаше како грешка во освежувањето на UI-то. Не беше — беше тест-средината што нè лажеше. Исто така, морававме да го спроведеме реалниот лимит на контекст на моделот во скриптата, инаку бројките за меморија беа нереалистични.
Што научивме
- UI-јата за стриминг ги множат малите грешки. Сè што испраќате „по парче“ се испраќа десетици илјади пати. Испраќајте делта или ограничете ја содржината.
- Ограничете секој бафер што минува низ бавна граница. Сокетите, дневниците за повторно пуштање и checkpoint-ите сите имаат потреба од таван.
- Репродуцирајте пред да поправите. Првите две објаснувања звучеа точно и беа погрешни.
Ако неодамна сте видели многу долга датотека како се појавува во разговорот на Mythex без агентот да застане, ова е дел од причината.