Blogue

O Crash por Falta de Memória Escondido numa Etiqueta de Progresso

Numa noite de finais de setembro, o agente de IA num espaço de trabalho de um utilizador caiu com um erro de falta de memória a meio de um turno. Os suspeitos óbvios — demasiados subagentes a correr ao mesmo tempo, ou um contexto que tinha crescido demasiado — estavam ambos errados. A causa real era uma etiqueta de estado: enquanto o modelo transmitia um comando de shell grande, poucos caracteres de cada vez, reenviávamos o comando inteiro até ao momento como etiqueta de progresso a cada pedaço. Um comando de 124 KB transformou-se em cerca de 41.000 eventos e em aproximadamente 690 MB de strings em fila.

Como o agente transmite o seu trabalho

Quando constróis no Mythex, o agente corre dentro da sandbox na cloud privada do teu projeto. À medida que o modelo escreve, a saída é transmitida de volta para o teu browser: o texto aparece palavra a palavra e, quando o agente usa uma ferramenta — por exemplo, um comando de shell que escreve um ficheiro — vês uma etiqueta para ela que se atualiza ao vivo, para saberes o que está a fazer.

O modelo transmite em pequenos pedaços. Com o modelo que estávamos a usar, esses pedaços tinham muitas vezes apenas três caracteres.

As primeiras hipóteses estavam erradas

O crash parecia um problema de escala, por isso começámos por aí.

  • Subagentes em paralelo? O turno tinha usado apenas dois, cada um com cerca de 1,5 MB de estado. Não era suficiente.
  • Traços retidos? O nosso rastreio mantinha uma árvore de execução em memória, mas não estava nem perto do tamanho da heap no momento do crash.

Ambas as histórias eram plausíveis, e ambas eram baratas de acreditar. Nenhuma reproduziu o crash. A lição que continuamos a reaprender: uma hipótese não é uma causa até reproduzir a falha.

Reproduzir a sério

Construímos uma sandbox descartável com os mesmos limites de memória da produção e um modelo falso que transmitia um ficheiro grande em pedaços realistas de três caracteres. Também tornámos lento o leitor do outro lado do socket, como uma rede real.

Funcionou. A heap subiu e o processo morreu com o mesmo erro.

O mecanismo era simples depois de o podermos observar. Enquanto o modelo escrevia um comando grande — neste caso um heredoc que escrevia um ficheiro de 124 KB —, o nosso código de streaming atualizava a etiqueta da ferramenta a cada pedaço. A etiqueta não era os três caracteres novos; era o comando inteiro até ao momento. Assim, os eventos cresciam: 3 caracteres, depois 6, depois 9, … até 124 KB, cerca de 41.000 vezes. O socket não conseguia enviá-los tão depressa como eram produzidos, por isso acumulavam-se no seu buffer de escrita como strings — cerca de 690 MB delas.

A correção

  • Limitar a etiqueta. Uma etiqueta de estado só precisa de dizer o que está a acontecer. Agora está limitada a 200 caracteres, e limitamos a frequência com que voltamos a analisar o comando parcial.
  • Acabar com a concatenação quadrática. Juntar pedaços transmitidos com concatenação repetida de strings é O(n²) para saídas grandes. Passámos para uma abordagem baseada em blocos que acrescenta em tempo linear.

Depois procurámos qualquer outra coisa que pudesse crescer sem limite com uma enxurrada de saída, e limitámo-la:

  • A saída de comandos em direto mostrada no chat é agrupada e mantém apenas os últimos 8.000 caracteres.
  • Cada linha de evento individual está limitada a 4 MB.
  • O buffer de repetição que um browser usa para recuperar após reconectar está limitado a 8 MB por projeto.
  • Os checkpoints antigos são eliminados à medida que um turno avança, mantendo o estado mais recente e as âncoras que turnos anteriores precisam.

Testar enxurradas com honestidade

Duas coisas tornaram isto mais difícil de testar do que deveria ser:

  1. Os modelos reais não colaboram. Se pedires a um modelo para imprimir um ficheiro gigantesco, normalmente ele recusa, resume ou escreve-o de forma mais sensata. Para testar enxurradas precisas de um modelo com um guião que faça exatamente o que lhe dizes, a correr pelo código real do agente.
  2. Os guiões têm de se comportar como modelos. O nosso primeiro modelo com guião reutilizava o mesmo ID de resposta em todas as respostas, por isso a framework do agente tratava cada resposta como substituição da anterior e terminava os turnos antes do tempo. Parecia um bug de atualização da interface. Não era — era o harness de teste a mentir-nos. Tivemos também de impor no guião o limite real de contexto do modelo, ou os números de memória não eram realistas.

O que tirámos disto

  • As interfaces de streaming multiplicam pequenos erros. Qualquer coisa que envies "por pedaço" é enviada dezenas de milhares de vezes. Envia deltas, ou limita a carga.
  • Limita todo o buffer que atravessa uma fronteira lenta. Sockets, registos de repetição e checkpoints precisam todos de um teto.
  • Reproduz antes de corrigir. As nossas duas primeiras explicações soavam bem e estavam erradas.

Se viste recentemente um ficheiro muito longo aparecer no chat do Mythex sem que o agente parasse, é em parte por isto.

Mythex · Pricing · Crie qualquer coisa com IA · Modelos de sites e apps · Mythex comparado · Glossário de criação de apps com IA · Ferramentas grátis para criar apps com IA · 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) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)