Blog

Le crash de mémoire caché dans une étiquette de progression

Un soir de fin septembre, l'agent IA d'un espace de travail d'utilisateur a planté avec une erreur de mémoire insuffisante au milieu d'un tour. Les coupables évidents — trop de sous-agents actifs en même temps, ou un contexte devenu trop grand — étaient tous deux faux. La vraie cause était une étiquette de statut : pendant que le modèle diffusait une longue commande shell quelques caractères à la fois, nous renvoyions la commande entière jusque-là comme étiquette de progression à chaque morceau. Une commande de 124 Ko a produit environ 41 000 événements et près de 690 Mo de chaînes de caractères en file d'attente.

Comment l'agent diffuse son travail

Quand vous construisez sur Mythex, l'agent s'exécute dans le bac à sable cloud privé de votre projet. Au fur et à mesure que le modèle écrit, sa sortie est diffusée vers votre navigateur : le texte apparaît mot par mot, et quand il utilise un outil — par exemple une commande shell qui écrit un fichier —, vous voyez une étiquette pour cet outil se mettre à jour en direct, pour savoir ce qu'il fait.

Le modèle diffuse par petits morceaux. Avec le modèle que nous utilisions, ces morceaux faisaient souvent trois caractères seulement.

Les premières hypothèses étaient fausses

Le plantage ressemblait à un problème de passage à l'échelle, nous avons donc commencé par là.

  • Des sous-agents en parallèle ? Le tour n'en avait utilisé que deux, chacun portant environ 1,5 Mo d'état. Ce n'était pas suffisant.
  • Des traces conservées ? Notre traçage gardait un arbre d'exécution en mémoire, mais il était loin de la taille du tas au moment du plantage.

Les deux explications étaient plausibles, et toutes deux faciles à croire. Aucune ne reproduisait le plantage. La leçon que nous réapprenons sans cesse : une hypothèse n'est pas une cause tant qu'elle ne reproduit pas la panne.

Reproduire pour de vrai

Nous avons construit un bac à sable jetable avec les mêmes limites mémoire que la production, et un faux modèle qui diffusait un gros fichier par morceaux réalistes de trois caractères. Nous avons aussi rendu lent le lecteur à l'autre bout de la socket, comme un vrai réseau.

Cela a suffi. Le tas a grimpé et le processus a planté avec la même erreur.

Le mécanisme était simple une fois observable. Pendant que le modèle écrivait une grosse commande — ici un heredoc qui écrivait un fichier de 124 Ko —, notre code de diffusion mettait à jour l'étiquette de l'outil à chaque morceau. L'étiquette ne contenait pas les trois nouveaux caractères ; elle contenait la commande entière jusque-là. Les événements grossissaient donc : 3 caractères, puis 6, puis 9, … jusqu'à 124 Ko, environ 41 000 fois. La socket ne pouvait pas les envoyer aussi vite qu'ils étaient produits, et ils s'accumulaient dans son tampon d'écriture sous forme de chaînes — environ 690 Mo.

La correction

  • Plafonner l'étiquette. Une étiquette de statut doit seulement dire ce qui se passe. Elle est désormais limitée à 200 caractères, et nous limitons la fréquence de réanalyse de la commande partielle.
  • Supprimer la concaténation quadratique. Assembler les morceaux diffusés par concaténations répétées de chaînes coûte O(n²) pour les grandes sorties. Nous sommes passés à une approche par blocs qui ajoute les données en temps linéaire.

Nous avons ensuite cherché tout autre élément susceptible de grossir sans limite face à un flot de sortie, et nous l'avons borné :

  • La sortie en direct des commandes affichée dans le chat est regroupée et limitée aux 8 000 derniers caractères.
  • Chaque ligne d'événement est plafonnée à 4 Mo.
  • Le tampon de rejeu qu'un navigateur utilise pour rattraper son retard après une reconnexion est limité à 8 Mo par projet.
  • Les anciens points de contrôle sont élagués au fil du tour, en gardant l'état le plus récent et les ancres dont les tours précédents ont besoin.

Tester les inondations honnêtement

Deux éléments ont rendu ce test plus difficile qu'il ne devait l'être :

  1. Les vrais modèles ne coopèrent pas. Demandez à un modèle d'afficher un fichier gigantesque : il refusera, résumera, ou l'écrira plus raisonnablement. Pour tester les inondations, il faut un modèle scripté qui fait exactement ce que vous lui dites, en passant par le vrai code de l'agent.
  2. Les scripts doivent se comporter comme des modèles. Notre premier modèle scripté réutilisait le même identifiant de réponse à chaque réponse, si bien que le framework de l'agent traitait chaque réponse comme un remplacement de la précédente et terminait les tours trop tôt. Cela ressemblait à un bogue d'actualisation de l'interface. Ce n'en était pas un : c'était le harnais de test qui nous trompait. Nous avons aussi dû imposer dans le script la vraie limite de contexte du modèle, sinon les chiffres de mémoire étaient irréalistes.

Ce que nous en avons retenu

  • Les interfaces de streaming multiplient les petites erreurs. Tout ce qui est envoyé « par morceau » est envoyé des dizaines de milliers de fois. Envoyez des deltas, ou plafonnez la charge utile.
  • Bornez chaque tampon qui traverse une frontière lente. Sockets, journaux de rejeu et points de contrôle ont tous besoin d'un plafond.
  • Reproduisez avant de corriger. Nos deux premières explications semblaient justes et étaient fausses.

Si vous avez vu récemment un très long fichier apparaître dans le chat Mythex sans que l'agent ne cale, c'est en partie pour cette raison.

Mythex · Pricing · Créez tout avec l'IA · Modèles de sites et d'applications · Mythex comparé · Glossaire de la création d'applications avec l'IA · Outils gratuits pour créer des applications avec l'IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi 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 简体中文 繁體中文(香港) 繁體中文(台灣)