Blog

Blocajul de memorie ascuns într-o etichetă de progres

Într-o seară de la sfârșitul lui septembrie, agentul AI dintr-un spațiu de lucru al unui utilizator s-a oprit cu o eroare de tip out-of-memory în mijlocul unui tur. Bănuielile evidente — prea mulți sub-agenți rulând în același timp sau un context care crescuse prea mult — erau ambele greșite. Cauza reală era o etichetă de stare: în timp ce modelul transmitea o comandă de shell mare câteva caractere pe rând, noi retrimiteam întreaga comandă de până atunci drept etichetă de progres la fiecare bucată. O comandă de 124 KB a devenit aproximativ 41.000 de evenimente și circa 690 MB de șiruri așteptând în coadă.

Cum transmite agentul munca

Când construiești pe Mythex, agentul rulează în interiorul sandbox-ului cloud privat al proiectului tău. Pe măsură ce modelul scrie, rezultatul lui este transmis înapoi în browserul tău: textul apare cuvânt cu cuvânt, iar când rulează o unealtă — de pildă o comandă de shell care scrie un fișier — vezi o etichetă pentru ea care se actualizează live, ca să știi ce face.

Modelul transmite în bucăți mici. Cu modelul pe care îl rulam, bucățile erau adesea doar de trei caractere.

Primele ipoteze erau greșite

Blocajul arăta ca o problemă de scalare, așa că am început de acolo.

  • Sub-agenți paraleli? Turul folosise doar doi, fiecare ținând în jur de 1,5 MB de stare. Nu era suficient.
  • Urme păstrate în memorie? Trasarea noastră ținea un arbore de rulare în memorie, dar nu era nici pe departe de dimensiunea heap-ului la momentul blocajului.

Ambele povești erau plauzibile și ambele erau ieftin de crezut. Niciuna nu a reprodus blocajul. Lecția pe care tot o reînvățăm: o ipoteză nu este o cauză până nu reproduce defectul.

Reproducerea reală

Am construit un sandbox de unică folosință, cu aceleași limite de memorie ca în producție, și un model fals care transmitea un fișier mare în bucăți realiste de trei caractere. De asemenea, am făcut cititorul de la celălalt capăt al socket-ului lent, ca o rețea reală.

Asta a funcționat. Heap-ul a crescut și procesul a murit cu aceeași eroare.

Mecanismul a fost simplu odată ce l-am putut urmări. În timp ce modelul scria o comandă mare — în cazul acesta un heredoc care scria un fișier de 124 KB — codul nostru de streaming actualiza eticheta uneltei la fiecare bucată. Eticheta nu conținea cele trei caractere noi; conținea întreaga comandă de până atunci. Deci evenimentele creșteau: 3 caractere, apoi 6, apoi 9, … până la 124 KB, de aproximativ 41.000 de ori. Socket-ul nu putea trimite la fel de repede cât erau produse, așa că se adunau în bufferul lui de scriere, ca șiruri — circa 690 MB.

Remedierea

  • Limitează eticheta. O etichetă de stare trebuie doar să spună ce se întâmplă. Acum este limitată la 200 de caractere, iar re-scanarea comenzii parțiale este limitată în frecvență.
  • Oprește concatenarea pătratică. Unirea bucăților transmise prin concatenare repetată de șiruri este O(n²) pentru rezultate mari. Am trecut la o abordare pe bucăți, care adaugă în timp liniar.

Apoi am căutat orice altceva care ar putea crește nemărginit la un potop de ieșire și am limitat tot:

  • Ieșirea live a comenzilor afișată în chat este grupată și păstrată doar până la ultimele 8.000 de caractere.
  • Liniile individuale de evenimente sunt limitate la 4 MB.
  • Bufferul de reluare pe care îl folosește un browser ca să recupereze după reconectare este limitat la 8 MB pe proiect.
  • Checkpoint-urile vechi sunt eliminate pe măsură ce avansează turul, păstrând cea mai nouă stare și punctele de ancorare de care au nevoie tururile anterioare.

Testarea potopurilor, onest

Două lucruri au făcut testarea mai grea decât ar fi trebuit:

  1. Modelele reale nu cooperează. Dacă ceri unui model să afișeze un fișier gigantic, de obicei refuză, rezumă sau îl scrie mai rezonabil. Ca să testezi potopuri ai nevoie de un model scriptat, care face exact ce îi spui, rulând prin codul real al agentului.
  2. Scripturile trebuie să se comporte ca modelele. Primul nostru model scriptat reutiliza același ID de răspuns la fiecare replică, așa că framework-ul agentului considera fiecare răspuns o înlocuire a celui anterior și încheia tururile devreme. Arăta ca un bug de reîmprospătare a interfeței. Nu era — era harnessul de test care ne mințea. De asemenea, a trebuit să impunem în script limita reală de context a modelului, altfel cifrele de memorie erau nerealiste.

Ce am învățat din asta

  • Interfețele de streaming înmulțesc greșelile mici. Orice trimiți „pe bucată” este trimis de zeci de mii de ori. Trimite diferențe sau limitează încărcătura.
  • Limitează fiecare buffer care trece printr-o frontieră lentă. Socket-urile, jurnalele de reluare și checkpoint-urile au nevoie toate de un plafon.
  • Reproduce înainte să repari. Primele noastre două explicații sunau bine și erau greșite.

Dacă ai văzut recent un fișier foarte lung apărând în chatul Mythex fără ca agentul să se blocheze, aceasta este parte din motiv.

Mythex · Pricing · Construiește orice cu AI · Șabloane pentru site-uri și aplicații · Mythex comparat · Glosar pentru crearea de aplicații cu AI · Instrumente gratuite pentru crearea de aplicații cu 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) Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)