دو جعبه در کلاس DGX Spark، یک مدل: یک کپی روی هر کدام به جای تقسیم آن
عملیات
هفت هفته مدل زبان محلی ما به صورت تقسیمشده روی دو ماشین NVIDIA GB10 با موازیسازی تانسوری اجرا میشد. در 22 اکتبر آن را با یک کپی کامل از مدل روی هر ماشین و یک پروکسی کمترین اتصال در جلوی آنها جایگزین کردیم. نرخ پردازش از حدود 180 به 210 توکن در ثانیه رسید و اکنون هر ماشین میتواند بدون از دست رفتن مدل، ریاستارت شود.
چرا آن را تقسیم کرده بودیم
هر ماشین 128 گیگابایت حافظه دارد که CPU و GPU آن را به اشتراک میگذارند. در سپتامبر مدل روی یک ماشین جا میشد، اما کش آن به سختی جا میشد: نسخه vLLM که استفاده میکردیم نمیتوانست کش KV این مدل را به صورت FP8 نگه دارد، بنابراین کش به صورت BF16 بود و یک ماشین فضای 499,110 توکن از آن را داشت. این مقدار کمتر از دو درخواست در کانتکست کامل 262,144 توکنی مدل است. با تقسیم روی هر دو ماشین و موازیسازی تانسوری از طریق لینک شبکه مستقیم آنها، کش به بیش از سه میلیون توکن رسید. ما مدل را برای جا شدن تقسیم کرده بودیم، نه برای سرعت.
چه چیزی تغییر کرد
یک نسخه جدیدتر 4 بیتی از همان مدل (NVFP4، ساختهشده با تقطیر آگاه از کمسازی) حدود 71 گیبایت حافظه میگیرد و یک نسخه vLLM جامعه برای GB10 (eugr/spark-vllm-b12x) کش آن را به صورت FP8 اجرا میکند. اکنون یک ماشین کل مدل و کشی با 0.76 تا 1.08 میلیون توکن را نگه میدارد؛ فضایی برای 2.9 تا 4.1 درخواست در کانتکست کامل، بسته به اینکه ماشین چقدر حافظه به آن اختصاص دهد. هر دو کپی از رمزگشایی حدسی خود مدل (MTP، چهار توکن پیشنویس) استفاده میکنند. قبل از جابهجایی، تست فراخوانی ابزار خود را روی نسخه جدید اجرا کردیم: امتیاز 86 گرفت، در حالی که نسخه جایگزینشده 75 گرفته بود.
آنچه اندازه گرفتیم
| تقسیم روی دو ماشین | یک کپی روی هر ماشین | |
|---|---|---|
| کش KV | بیش از 3 میلیون توکن | 0.76 و 1.08 میلیون توکن |
| درخواستها در کانتکست کامل | حدود 12 | 2.9 و 4.1 |
| نرخ پردازش تحت بار ما | حدود 180 توکن/ثانیه | 210 توکن/ثانیه (111 + 99) |
| راهاندازی | حدود 20 دقیقه | 5 تا 13 دقیقه برای هر کپی |
| ریاستارت یک ماشین | مدل از دسترس خارج میشود | کپی دیگر سرویس میدهد |
یک نسخه بهتنهایی، بر اساس تعداد درخواستهایی که همزمان پاسخ میدهد: با پرامپتهای کوتاه، 1 درخواست 36 توکن در ثانیه، 4 درخواست 82، 8 درخواست 109 و 16 درخواست 150 توکن در ثانیه تولید کرد. با پرامپتهای بلندتر عامل ایمیل ما، 4 درخواست 81، 8 درخواست 113 و 16 درخواست تنها 87 توکن در ثانیه داد: پس از 8 درخواست از این نوع، افزودن درخواستهای بیشتر سرعت را کاهش میدهد.
برای یک بعدازظهر، یک نسخه حداکثر 12 درخواست را همزمان و نسخه دیگر 8 درخواست را پردازش کرد. نسخه اول 91 تا 93 توکن در ثانیه تولید کرد و 26 تا 27 درخواست در دقیقه را تمام کرد؛ نسخه دوم 81 تا 83 توکن در ثانیه و 22 درخواست در دقیقه. اکنون هر دو 12 درخواست را همزمان پردازش میکنند.
آنچه از دست دادیم
فضا. مدل تفکیکشده یک کش با بیش از سه میلیون توکن داشت؛ نسخهها دو کش کوچکتر دارند و یک درخواست تنها میتواند از کش نسخهای استفاده کند که آن را پاسخ میدهد. عوامل ما درخواستهای کوتاه و متوسط زیادی ارسال میکنند و برای آنها توان عملیاتی اهمیت بیشتری داشت. اگر چند کانتکست بسیار بلند را همزمان سرو میکنید، تفکیک ممکن است همچنان انتخاب بهتری باشد. ما تنظیمات تفکیکشده را نگه داشتیم و بازگشت به آن تنها یک اسکریپت است.
آنچه در طول جابهجایی اشتباه پیش رفت
- یازده دقیقه بدون مدل، سپس 30 ثانیه خطای 502. مدل تفکیکشده باید قبل از بارگذاری نسخه روی همان ماشینها متوقف میشد، بنابراین عوامل ما حدود یازده دقیقه منتظر ماندند. وقتی نسخه اول بالا آمد، پروکسی حدود 30 ثانیه دیگر به پاسخ دادن «502 no live upstreams» ادامه داد: بکاندها را در زمان قطعی بهعنوان ناموفق علامتگذاری کرده بود (max_fails=3, fail_timeout=30s) و تا پایان آن زمان دوباره آنها را امتحان نکرد.
- کلاینتی که پروکسی را دور زد. یکی از کارهای دستهای ما آدرس ماشین اول را در پیکربندی خود داشت، بنابراین تمام بار آن به یک نسخه رفت در حالی که نسخه دیگر ظرفیت خالی داشت. اکنون همه کلاینتها از طریق پروکسی میروند؛ آدرسهای خود ماشینها تنها برای بررسی سلامت استفاده میشوند.
- معیارهایی که جهش داشتند. پایشگر توان عملیاتی ما از طریق پروکسی /metrics را از vLLM میخواند. با دو بکاند، خوانشهای متوالی از نسخههای مختلف انجام میشد، شمارندهها بهصورت رفتوبرگشتی جهش میکردند و پایشگر مدل را گرسنه (starved) گزارش میکرد. اکنون هر نسخه را مستقیماً میخواند و آنها را جمع میزند.
- واچداگ قدیمی که نسخه جدید را متوقف میکرد. در تنظیمات تقسیمشده، یک واچداگ وجود داشت که سرویس خود را هنگامی که مدل پاسخ نمیداد، راهاندازی مجدد میکرد. سرویس جدید با استفاده از Conflicts= با سرویس قدیمی تعارض دارد، بنابراین آن راهاندازی مجدد، نسخه جدید را متوقف میکرد. باید با دستور
systemctl disable --nowغیرفعال میشد، نه فقط متوقف، زیرا لایه عملیات ما واحدهایی را که فعال اما در حال اجرا نیستند، راهاندازی مجدد میکند. - حافظه روی ماشینی که کارهای دیگری انجام میدهد. یک ماشین همچنین پایگاههای داده و ساختوسایتهای ما را اجرا میکند. با مقدار 0.78 برای gpu-memory-utilization در vLLM، تنها 5 گیگابایت برای سایر موارد باقی میماند، بنابراین آن را به 0.74 کاهش دادیم و فقط همان نسخه را راهاندازی مجدد کردیم در حالی که نسخه دیگر بار را تحمل میکرد. در این ماشینها، سهمی که به vLLM میدهید از همان حافظهای کسر میشود که سایر برنامهها استفاده میکنند.
اگر دو مورد از این ماشینها را دارید
- اگر مدل و کش مورد نیاز شما روی یک ماشین جا میشود، یک نسخه روی هر ماشین پشت یک پروکسی با الگوریتم کمترین اتصال (least-connection) اجرا کنید. مدل را فقط زمانی تقسیم کنید که یک ماشین نتواند کش مورد نیاز شما را نگه دارد.
- همه کلاینتها را به پروکسی متصل کنید، از جمله کارهای دستهای (batch jobs) و پیکربندیهای قدیمی.
- متریکها را مستقیماً از هر نسخه بخوانید.
- به هر ماشین سهم حافظه مخصوص به خود را بدهید، متناسب با سایر برنامههایی که روی آن اجرا میشوند.
- تعداد درخواستهایی که هر نسخه همزمان سرویس میدهد را بر اساس ترافیک خودتان تنظیم کنید: برای ترکیب ترافیک ما، 12 بهتر از 8 بود.
- انتظار داشته باشید که جابهجایی، مدل را به مدت زمانی که راهاندازی یک نسخه طول میکشد، از دسترس خارج کند و برای آن برنامهریزی کنید.
Factory Floor پروکسی و پیکربندیهای راهاندازی برای یک یا دو ماشین، بررسی سلامت (health check) و تست استفاده از ابزار (tool-use) که قبل از جابهجایی مدلها اجرا میکنیم، به همراه واچداگها و محافظهای حافظه برای سایر بخشهای ماشین را ارائه میدهد.