Blog

Der Speicherüberlauf, der sich in einer Fortschrittsanzeige versteckte

An einem Abend Ende September stürzte der KI-Agent im Arbeitsbereich eines Nutzers mitten in einem Durchlauf mit einem Speicherfehler (Out of Memory) ab. Die naheliegenden Verdächtigen, zu viele gleichzeitig laufende Subagenten oder ein zu groß gewordener Kontext, waren beide falsch. Die eigentliche Ursache war eine Statusanzeige: Während das Modell einen großen Shell-Befehl Stück für Stück streamte, sendeten wir bei jedem Teil den gesamten bisherigen Befehl erneut als Fortschrittsanzeige. Aus einem 124 KB großen Befehl wurden rund 41.000 Ereignisse und etwa 690 MB in die Warteschlange gestellte Zeichenketten.

Wie der Agent seine Arbeit streamt

Wenn Sie auf Mythex bauen, läuft der Agent in der privaten Cloud-Sandbox Ihres Projekts. Während das Modell schreibt, wird seine Ausgabe in Ihren Browser gestreamt: Text erscheint Wort für Wort, und wenn er ein Werkzeug ausführt, etwa einen Shell-Befehl, der eine Datei schreibt, sehen Sie eine Anzeige dafür live aktualisiert, damit Sie wissen, was er tut.

Das Modell streamt in kleinen Stücken. Bei dem Modell, das wir einsetzten, waren diese Stücke oft nur drei Zeichen lang.

Die ersten Hypothesen waren falsch

Der Absturz sah nach einem Skalierungsproblem aus, also haben wir dort angefangen.

  • Parallele Subagenten? Der Durchlauf hatte nur zwei verwendet, und jeder hielt rund 1,5 MB Zustand. Zu wenig.
  • Aufbewahrte Traces? Unser Tracing hielt einen Lauf-Baum im Speicher, aber er war bei Weitem nicht so groß wie der Heap beim Absturz.

Beide Geschichten waren plausibel, und beide waren leicht zu glauben. Keine reproduzierte den Absturz. Die Lehre, die wir immer wieder neu lernen: Eine Hypothese ist keine Ursache, solange sie den Fehler nicht reproduziert.

Der echte Reproduktionsversuch

Wir haben eine Wegwerf-Sandbox mit denselben Speichergrenzen wie in der Produktion gebaut, dazu ein Fake-Modell, das eine große Datei in realistischen Dreizeichen-Stücken streamte. Außerdem machten wir den Leser am anderen Ende des Sockets langsam, wie ein echtes Netzwerk.

Das hat es gezeigt. Der Heap wuchs, und der Prozess starb mit demselben Fehler.

Der Mechanismus war einfach, sobald wir ihn beobachten konnten. Während das Modell einen großen Befehl schrieb, in diesem Fall ein Heredoc, das eine 124 KB große Datei schrieb, aktualisierte unser Streaming-Code die Anzeige des Werkzeugs bei jedem Stück. Die Anzeige enthielt nicht die drei neuen Zeichen, sondern den gesamten bisherigen Befehl. So wuchsen die Ereignisse: 3 Zeichen, dann 6, dann 9 und so weiter bis 124 KB, rund 41.000-mal. Der Socket konnte sie nicht so schnell senden, wie sie entstanden, also stauten sie sich in seinem Schreibpuffer als Zeichenketten, etwa 690 MB davon.

Die Korrektur

  • Die Anzeige begrenzen. Eine Statusanzeige muss nur sagen, was gerade passiert. Sie ist jetzt auf 200 Zeichen begrenzt, und wir drosseln, wie oft der Teilbefehl erneut ausgewertet wird.
  • Quadratische Verkettung beenden. Das Zusammenfügen gestreamter Teile durch wiederholtes Verketten von Zeichenketten hat bei großen Ausgaben den Aufwand O(n²). Wir sind auf einen blockbasierten Ansatz umgestiegen, der linear anhängt.

Dann haben wir nach allem gesucht, was bei einer Flut von Ausgaben ohne Grenze wachsen könnte, und es begrenzt:

  • Live-Befehlsausgabe im Chat wird gebündelt und auf die letzten 8.000 Zeichen beschränkt.
  • Einzelne Ereigniszeilen sind auf 4 MB begrenzt.
  • Der Wiederholungspuffer, mit dem ein Browser nach einer Wiederverbindung aufholt, ist auf 8 MB pro Projekt begrenzt.
  • Alte Checkpoints werden während eines Durchlaufs bereinigt, wobei der neueste Zustand und die Anker erhalten bleiben, die frühere Durchläufe brauchen.

Floods ehrlich testen

Zwei Dinge machten das Testen schwieriger, als es sein sollte:

  1. Echte Modelle spielen nicht mit. Bitten Sie ein Modell, eine riesige Datei auszugeben, und es weigert sich meist, fasst zusammen oder schreibt es vernünftiger. Um Floods zu testen, brauchen Sie ein skriptgesteuertes Modell, das genau das tut, was Sie ihm sagen, und das durch den echten Agenten-Code läuft.
  2. Skripte müssen sich wie Modelle verhalten. Unser erstes skriptgesteuertes Modell verwendete bei jeder Antwort dieselbe Antwort-ID. Das Agenten-Framework behandelte deshalb jede Antwort als Ersatz der letzten und beendete Durchläufe vorzeitig. Es sah aus wie ein Fehler bei der Oberflächen-Aktualisierung. War es aber nicht, sondern der Testaufbau hat uns belogen. Außerdem mussten wir die echte Kontextgrenze des Modells im Skript erzwingen, sonst waren die Speicherwerte unrealistisch.

Was wir daraus mitnehmen

  • Streaming-Oberflächen vervielfachen kleine Fehler. Alles, was „pro Stück“ gesendet wird, geht zehntausendfach hinaus. Senden Sie Deltas oder begrenzen Sie die Nutzlast.
  • Begrenzen Sie jeden Puffer, der eine langsame Grenze überquert. Sockets, Wiederholungsprotokolle und Checkpoints brauchen alle eine Obergrenze.
  • Reproduzieren Sie, bevor Sie korrigieren. Unsere ersten beiden Erklärungen klangen richtig und waren falsch.

Wenn Sie in letzter Zeit eine sehr lange Datei im Mythex-Chat haben erscheinen sehen, ohne dass der Agent hängen blieb, ist das ein Teil des Grundes.

Mythex · Pricing · Alles mit KI bauen · Vorlagen für Websites und Apps · Mythex im Vergleich · Glossar zum App-Bau mit KI · Kostenlose Tools für den App-Bau mit KI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Ελληνικά 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) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)