Progress Label میں چھپا ہوا Out-of-Memory کریش
ستمبر کی ایک شام، ایک صارف کے workspace میں AI agent ایک turn کے بیچ میں out-of-memory error کے ساتھ کریش ہو گیا۔ واضح شبہات، یعنی بیک وقت بہت سے sub-agents یا بہت بڑا context، دونوں غلط نکلے۔ اصل وجہ ایک status label تھی: جب model ایک بڑی shell command کو چند حروف کے ٹکڑوں میں stream کر رہا تھا، تو ہم ہر ٹکڑے کے ساتھ اب تک کی پوری command progress label کے طور پر دوبارہ بھیج رہے تھے۔ ایک 124 KB کی command تقریباً 41,000 events بن گئی اور تقریباً 690 MB قطار میں لگے strings۔
Agent اپنا کام کیسے stream کرتا ہے
Mythex پر build کرتے وقت agent آپ کے project کے نجی cloud sandbox کے اندر چلتا ہے۔ جیسے جیسے model لکھتا ہے، اس کا آؤٹ پٹ آپ کے براؤزر تک stream ہوتا ہے: متن لفظ بہ لفظ ظاہر ہوتا ہے، اور جب وہ کوئی tool چلاتا ہے، مثلاً فائل لکھنے والی shell command، تو آپ کو اس کا label براہِ راست اپ ڈیٹ ہوتا نظر آتا ہے، تاکہ آپ جان سکیں کہ وہ کیا کر رہا ہے۔
Model چھوٹے ٹکڑوں میں stream کرتا ہے۔ جس model پر ہم چل رہے تھے، ان میں سے کئی ٹکڑے اکثر صرف تین حروف لمبے ہوتے تھے۔
پہلے مفروضے غلط تھے
کریش ایک scaling مسئلے جیسا لگا، تو ہم وہیں سے شروع ہوئے۔
- متوازی sub-agents؟ Turn میں صرف دو استعمال ہوئے تھے، ہر ایک تقریباً 1.5 MB state رکھتا تھا۔ یہ کافی نہیں تھا۔
- Retained traces؟ ہماری tracing ایک run tree memory میں رکھتی تھی، مگر وہ کریش کے وقت heap کے سائز کے قریب بھی نہیں تھی۔
دونوں کہانیاں قابلِ یقین تھیں، اور دونوں پر یقین کرنا سستا تھا۔ نہ کوئی کریش دوبارہ پیدا ہوا۔ جو سبق ہم بار بار دوبارہ سیکھتے ہیں: کوئی مفروضہ اس وقت تک وجہ نہیں جب تک وہ خرابی کو دوبارہ پیدا نہ کرے۔
اسے حقیقی طور پر دوبارہ پیدا کرنا
ہم نے ایک عارضی sandbox بنایا جس کی memory حدیں production جیسی تھیں، اور ایک جعلی model جو حقیقت سے قریب تین حروف کے ٹکڑوں میں ایک بڑی فائل stream کرتا تھا۔ ہم نے socket کے دوسرے سرے کے reader کو بھی سست کیا، جیسے اصل نیٹ ورک۔
یہی کام کر گیا۔ Heap بڑھتا گیا اور process اسی error کے ساتھ مر گیا۔
اس کا طریقہ کار، جب ہم اسے دیکھ سکے، سادہ تھا۔ جب model ایک بڑی command لکھ رہا تھا، یہاں ایک heredoc جو 124 KB کی فائل لکھتا تھا، تو ہمارا streaming کوڈ ہر ٹکڑے پر tool کا label اپ ڈیٹ کرتا تھا۔ Label نئے تین حروف نہیں تھا؛ وہ اب تک کی پوری command تھا۔ تو events بڑھتے گئے: 3 حروف، پھر 6، پھر 9، یہاں تک کہ 124 KB تک، تقریباً 41,000 بار۔ Socket انہیں اتنی تیزی سے نہیں بھیج سکتا تھا جتنی تیزی سے یہ بنتے تھے، اس لیے یہ اس کے write buffer میں strings کی شکل میں جمع ہوتے گئے، تقریباً 690 MB۔
حل
- Label کی حد لگائیں۔ Status label کو صرف بتانا ہوتا ہے کہ کیا ہو رہا ہے۔ اب یہ 200 حروف تک محدود ہے، اور ہم جزوی command کو دوبارہ scan کرنے کی تعداد پر throttle لگاتے ہیں۔
- Quadratic concatenation بند کریں۔ Stream شدہ ٹکڑوں کو بار بار string concatenation سے جوڑنا بڑے آؤٹ پٹ کے لیے O(n²) ہے۔ ہم ایک chunk پر مبنی طریقے پر چلے گئے جو خطی وقت میں جوڑتا ہے۔
اس کے بعد ہم نے ہر اس چیز کو تلاش کیا جو آؤٹ پٹ کے سیلاب میں بے حد بڑھ سکتی تھی، اور اسے حد میں باندھا:
- چیٹ میں دکھائی جانے والی live command آؤٹ پٹ batch کی جاتی ہے اور آخری 8,000 حروف تک رکھی جاتی ہے۔
- انفرادی event lines 4 MB تک محدود ہیں۔
- Replay buffer، جسے browser دوبارہ connect ہونے کے بعد پیچھے رہ جانے والی چیزیں پکڑنے کے لیے استعمال کرتا ہے، فی project 8 MB تک محدود ہے۔
- پرانے checkpoints turn کے ساتھ prune ہوتے ہیں، نیا state اور پچھلے turns کو درکار anchors رکھتے ہوئے۔
سیلاب کی جانچ ایمانداری سے
دو چیزوں نے اس کی جانچ کو جتنا ہونا چاہیے تھا اس سے مشکل بنایا:
- حقیقی models تعاون نہیں کرتے۔ کسی model سے بہت بڑی فائل چھاپنے کو کہیں تو وہ عام طور پر انکار کرتا ہے، خلاصہ کرتا ہے، یا اسے زیادہ معقول طریقے سے لکھتا ہے۔ سیلاب کی جانچ کے لیے آپ کو ایک scripted model چاہیے جو وہی کرے جو آپ کہیں، اصل agent کوڈ کے ذریعے چلتے ہوئے۔
- Scripts کو models کی طرح برتاؤ کرنا ہوگا۔ ہمارے پہلے scripted model نے ہر جواب پر ایک ہی response ID دوبارہ استعمال کیا، تو agent framework نے ہر جواب کو پچھلے کا متبادل سمجھا اور turns پہلے ختم کر دیے۔ یہ UI refresh کا bug لگا۔ وہ نہیں تھا: وہ test harness ہمیں جھوٹ بتا رہا تھا۔ ہمیں script میں model کی اصل context حد بھی لاگو کرنی پڑی، ورنہ memory کے اعداد غیر حقیقی تھے۔
ہم نے اس سے کیا سیکھا
- Streaming UIs چھوٹی غلطیوں کو ضرب دیتے ہیں۔ کوئی بھی چیز جو “ہر chunk کے ساتھ” بھیجی جائے، دس ہزار بار بھیجی جاتی ہے۔ Deltas بھیجیں، یا payload کی حد لگائیں۔
- ہر buffer کو حد میں باندھیں جو سست سرحد عبور کرتا ہو۔ Sockets، replay logs اور checkpoints سب کو ایک چھت کی ضرورت ہے۔
- ٹھیک کرنے سے پہلے دوبارہ پیدا کریں۔ ہماری پہلی دو وضاحتیں درست لگیں اور غلط نکلیں۔
اگر آپ نے حال ہی میں Mythex chat میں کوئی بہت لمبی فائل بغیر agent کے رکے ظاہر ہوتے دیکھی ہے، تو اس کی ایک وجہ یہی ہے۔