บล็อก

ข้อผิดพลาดหน่วยความจำหมดที่ซ่อนอยู่ในป้ายสถานะความคืบหน้า

คืนหนึ่งในปลายเดือนกันยายน Agent AI ในพื้นที่ทำงานของผู้ใช้รายหนึ่งล่มด้วยข้อผิดพลาด out-of-memory ระหว่างเทิร์น ผู้ต้องสงสัยที่เห็นชัดๆ คือ sub-agent จำนวนมากที่รันพร้อมกัน หรือ context ที่โตเกินไป แต่ทั้งสองอย่างผิด สาเหตุจริงคือป้ายสถานะ: ขณะที่โมเดลสตรีมคำสั่ง shell ขนาดใหญ่ทีละไม่กี่ตัวอักษร เราส่ง คำสั่งทั้งหมดจนถึงตอนนั้น ซ้ำเป็นป้ายความคืบหน้าทุกครั้งที่ได้ชิ้นใหม่ คำสั่งขนาด 124 KB หนึ่งอันกลายเป็นประมาณ 41,000 เหตุการณ์ และสตริงที่รอคิวประมาณ 690 MB

Agent สตรีมงานอย่างไร

เมื่อคุณสร้างบน Mythex Agent จะทำงานภายใน sandbox ส่วนตัวบน cloud ของโปรเจกต์ ขณะที่โมเดลเขียน ผลลัพธ์จะถูกสตรีมกลับมายังเบราว์เซอร์ของคุณ ข้อความปรากฏทีละคำ และเมื่อมันรันเครื่องมือ เช่น คำสั่ง shell ที่เขียนไฟล์ คุณจะเห็นป้ายของมันอัปเดตสดๆ เพื่อให้คุณรู้ว่ามันกำลังทำอะไร

โมเดลสตรีมเป็นชิ้นเล็กๆ กับโมเดลที่เราใช้ ชิ้นเหล่านั้นมักยาวเพียง สามตัวอักษร

สมมติฐานแรกผิด

การล่มดูเหมือนปัญหาเรื่องการขยายตัว ดังนั้นเราจึงเริ่มจากตรงนั้น

  • Sub-agent ที่ทำงานพร้อมกัน? เทิร์นนั้นใช้แค่สองตัว แต่ละตัวถือสถานะราว 1.5 MB ไม่พอจะเป็นสาเหตุ
  • Trace ที่เก็บไว้? การ trace ของเราเก็บ run tree ไว้ในหน่วยความจำ แต่ไม่ได้ใกล้เคียงกับขนาด heap ตอนล่ม

ทั้งสองเรื่องฟังดูเป็นไปได้ และเชื่อได้ง่ายโดยไม่มีต้นทุน แต่ไม่มีอันไหนทำให้การล่มเกิดซ้ำได้ บทเรียนที่เราต้องเรียนรู้ซ้ำอยู่เสมอคือ สมมติฐานยังไม่ใช่สาเหตุจนกว่าจะทำให้ความล้มเหลวเกิดซ้ำได้

ทำให้เกิดซ้ำจริง

เราสร้าง sandbox ชั่วคราวที่มีขีดจำกัดหน่วยความจำเหมือน production และใช้โมเดลปลอมที่สตรีมไฟล์ขนาดใหญ่เป็นชิ้นสามตัวอักษรตามจริง เรายังทำให้ตัวอ่านฝั่งปลายทางของ socket ช้าเหมือนเครือข่ายจริงด้วย

สิ่งนั้นทำให้เกิดผล heap ไต่ขึ้นและโปรเซสตายด้วยข้อผิดพลาดเดียวกัน

กลไกเรียบง่ายเมื่อเราได้เห็นมัน ขณะที่โมเดลกำลังเขียนคำสั่งขนาดใหญ่ ในกรณีนี้คือ heredoc ที่เขียนไฟล์ 124 KB โค้ดสตรีมของเราอัปเดตป้ายของเครื่องมือทุกครั้งที่ได้ชิ้นใหม่ ป้ายไม่ใช่สามตัวอักษรใหม่ แต่เป็น คำสั่งทั้งหมดจนถึงตอนนั้น ดังนั้นเหตุการณ์จึงโตขึ้น: สามตัวอักษร แล้วหกตัว แล้วเก้าตัว ไปจนถึง 124 KB รวมประมาณ 41,000 ครั้ง socket ส่งไม่ทันเท่าที่มันถูกผลิต จึงกองค้างอยู่ใน write buffer เป็นสตริงประมาณ 690 MB

วิธีแก้

  • จำกัดป้าย ป้ายสถานะต้องบอกแค่สิ่งที่กำลังเกิดขึ้น ตอนนี้มันถูกจำกัดที่ 200 ตัวอักษร และเราหน่วงความถี่ที่สแกนคำสั่งบางส่วนใหม่
  • หยุดการต่อสตริงแบบกำลังสอง การต่อชิ้นที่สตรีมด้วยการต่อสตริงซ้ำๆ มีค่า O(n²) สำหรับผลลัพธ์ขนาดใหญ่ เราย้ายไปใช้วิธีแบบ chunk ที่ผนวกในเวลาเชิงเส้น

จากนั้นเราไล่หาสิ่งอื่นที่อาจโตโดยไม่มีขอบเขตเมื่อมีผลลัพธ์ท่วม และจำกัดมัน:

  • เอาต์พุตคำสั่งสดที่แสดงในแชทถูกรวมเป็นชุดและเก็บเฉพาะ 8,000 ตัวอักษรล่าสุด
  • บรรทัดเหตุการณ์แต่ละบรรทัดถูกจำกัดที่ 4 MB
  • บัฟเฟอร์สำหรับเล่นซ้ำที่เบราว์เซอร์ใช้ตามทันหลังเชื่อมต่อใหม่ถูกจำกัดที่ 8 MB ต่อโปรเจกต์
  • checkpoint เก่าจะถูกตัดทิ้งเมื่อเทิร์นดำเนินไป โดยเก็บสถานะล่าสุดและจุดยึดที่เทิร์นก่อนหน้าต้องใช้

การทดสอบน้ำท่วมอย่างซื่อสัตย์

มีสองเรื่องที่ทำให้การทดสอบยากกว่าที่ควร:

  1. โมเดลจริงไม่ทำตามที่ต้องการ ถ้าขอให้โมเดลพิมพ์ไฟล์ยักษ์ มันมักจะปฏิเสธ สรุป หรือเขียนมันอย่างสมเหตุสมผลกว่า การทดสอบน้ำท่วมต้องใช้โมเดลแบบสคริปต์ที่ทำตามที่คุณสั่งทุกอย่าง โดยรันผ่านโค้ด agent จริง
  2. สคริปต์ต้องทำตัวเหมือนโมเดล โมเดลแบบสคริปต์ตัวแรกของเราใช้ response ID เดิมในทุกการตอบกลับ เฟรมเวิร์ก agent จึงมองว่าแต่ละการตอบกลับแทนที่ตัวก่อนหน้า และจบเทิร์นก่อนเวลา ดูเหมือนบั๊กการรีเฟรช UI แต่มันไม่ใช่ สิ่งที่โกหกเราคือ test harness เราต้องบังคับใช้ขีดจำกัด context จริงของโมเดลในสคริปต์ด้วย ไม่อย่างนั้นตัวเลขหน่วยความจำจะไม่สมจริง

สิ่งที่เราเรียนรู้

  • UI ที่สตรีมขยายความผิดพลาดเล็กๆ อะไรก็ตามที่ส่ง "ต่อชิ้น" จะถูกส่งหลายหมื่นครั้ง ส่งเฉพาะส่วนที่เปลี่ยน (delta) หรือจำกัด payload
  • จำกัดทุกบัฟเฟอร์ที่ข้ามขอบเขตที่ช้า socket, replay log และ checkpoint ต้องมีเพดาน
  • ทำให้เกิดซ้ำก่อนแก้ คำอธิบายสองข้อแรกของเราฟังดูถูกต้องแต่ผิด

หากคุณเคยเห็นไฟล์ยาวมากปรากฏในแชท Mythex เมื่อไม่นานมานี้โดยที่ Agent ไม่สะดุด นี่คือส่วนหนึ่งของเหตุผล

Mythex · Pricing · สร้างอะไรก็ได้ด้วย AI · เทมเพลตเว็บไซต์และแอป · เปรียบเทียบ Mythex · อภิธานศัพท์การสร้างแอปด้วย AI · เครื่องมือฟรีสำหรับสร้างแอปด้วย 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) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)