שני מכשירים ברמת DGX Spark, מודל אחד: עותק בכל מכשיר מנצח את הפיצול
תפעול
במשך שבעה שבועות מודל השפה המקומי שלנו רץ מפוצל בין שני מכשירי NVIDIA GB10 עם מקביליות טנזורית. ב־22 באוקטובר החלפנו זאת בעותק מלא של המודל בכל מכשיר ובפרוקסי של פחות-חיבורים מלפנים. התפוקה עלתה מכ־180 ל־210 טוקנים לשנייה, וכל מכשיר יכול כעת לאתחל מחדש בלי להוריד את המודל.
למה פיצלנו אותו
לכל מכשיר יש 128 GB זיכרון שה־CPU וה־GPU חולקים בו. בספטמבר המודל נכנס למכשיר אחד, אבל המטמון שלו בקושי נכנס: גרסת vLLM שהשתמשנו בה לא יכלה לשמור את מטמון ה־KV של המודל הזה ב־FP8, ולכן המטמון היה ב־BF16, ולמכשיר אחד היה מקום ל־499,110 טוקנים שלו. זה פחות משני בקשות בהקשר המלא של המודל, 262,144 טוקנים. כשפיצלנו בין שני המכשירים עם מקביליות טנזורית על גבי הקשר הרשת הישיר ביניהם, המטמון גדל ליותר משלושה מיליון טוקנים. פיצלנו את המודל למקום, לא למהירות.
מה השתנה
גרסה חדשה יותר ב־4־bit של אותו מודל (NVFP4, שנוצרה באמצעות אגד תודעת-קוונטיזציה) תופסת כ־71 GiB זיכרון, וגרסת 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: מעבר לשמונה בקשות כאלה, יותר בקשות האטו את המערכת.
במשך אחר הצהריים, עותק אחד קיבל לכל היותר 12 בקשות בו-זמנית והשני 8. הראשון ייצר 91 עד 93 טוקנים לשנייה וסיים 26 עד 27 בקשות בדקה; השני 81 עד 83 טוקנים לשנייה ו-22 בקשות בדקה. כעת שניהם קולטים 12.
מה ויתרנו עליו
מקום. למודל המפוצל היה מטמון אחד של יותר משלושה מיליון טוקנים; לעותקים יש שני מטמונים קטנים יותר, ובקשה יכולה להשתמש רק במטמון של העותק שמשרת אותה. הסוכנים שלנו שולחים בקשות רבות קצרות ובינוניות, ולכן תפוקה הייתה חשובה יותר. אם אתם משרתים כמה הקשרים ארוכים מאוד בו-זמנית, פיצול עשוי עדיין להיות הבחירה הטובה יותר. שמרנו על תצורת הפיצול, והחזרה אחורה היא סקריפט אחד.
מה השתבש במהלך ההחלפה
- אחת-עשרה דקות ללא מודל, ואז 30 שניות של שגיאות 502. המודל המפוצל נדרש לעצור לפני שעותק יוכל להיטען על אותם מכשירים, ולכן הסוכנים שלנו חיכו כעשר-עשרה דקות. כאשר העותק הראשון עלה, הפרוקסי המשיך להשיב "502 no live upstreams" במשך כ-30 שניות נוספות: הוא סימן את ה-backends ככושלים בזמן שהיו למטה (max_fails=3, fail_timeout=30s) ולא ניסה אותם שוב עד שפג הזמן הזה.
- לקוח שדילג על הפרוקסי. לאחד ממשימות האצווה שלנו היה כתובת המכשיר הראשון בתצורה, כך שכל העומס שלו הלך לעותק אחד בעוד שלשני היה קיבולת פנויה. כעת כל הלקוחות עוברים דרך הפרוקסי; הכתובות של המכשירים עצמם משמשות רק לבדיקות בריאות.
- מדדים שקפצו. מוניטור התפוקה שלנו קרא את /metrics של vLLM דרך הפרוקסי. עם שני backends, קריאות עוקבות הגיעו מעותקים שונים, המונים קפצו הלוך ושוב, והמוניטור דיווח שהמודל רעב. כעת הוא קורא כל עותק ישירות ומסכם אותם.
- שומר סף ישן שהיה עוצר את העותק החדש. בתצורה המפוצלת היה שומר סף שהפעיל מחדש את השירות שלו כאשר המודל הפסיק להגיב. השירות החדש מגדיר Conflicts= מול הישן, כך שהפעלה מחדש הייתה עוצרת את העותק החדש. היה צורך לכבות אותו עם
systemctl disable --now, ולא רק לעצור, כי שכבת התפעול שלנו מפעילה מחדש יחידות (units) שמוגדרות כ-enabled אך לא רצות. - זיכרון במכונה שמבצעת גם עבודות אחרות. מכונה אחת מפעילה גם את מסדי הנתונים ואת בניית האתרים שלנו. עם gpu-memory-utilization של vLLM מוגדר ל-0.78, נשארו רק 5 GiB לכל השאר, לכן הורדנו את הערך ל-0.74 והפעלנו מחדש רק את העותק הזה בזמן שהשני נשא את העומס. במכונות אלו, החלק שאתם מקצים ל-vLLM נלקח מאותו זיכרון כמו כל השאר.
אם יש לכם שתי מכונות כאלה
- אם המודל והמטמון (cache) שאתם צריכים נכנסים למכונה אחת, הריצו עותק בכל מכונה מאחורי פרוקסי עם חלוקת עומס לפי מספר החיבורים המינימלי (least-connection). פצלו את המודל רק כאשר מכונה אחת לא יכולה להכיל את המטמון שאתם צריכים.
- כוונו כל לקוח לפרוקסי, כולל משימות אצווה (batch jobs) ותצורות ישנות.
- קראו מדדים (metrics) מכל עותק ישירות.
- הקצו לכל מכונה חלק זיכרון משלה, בהתאם למה עוד רץ עליה.
- הגדירו כמה בקשות עותק משרת בו-זמנית לפי התעבורה שלכם: עבור התמהיל שלנו, 12 עדיף על 8.
- הצפו שהמעבר ייקח את המודל מהשירות למשך זמן ההפעלה של עותק, ותכננו בהתאם.
Factory Floor כוללת את הפרוקסי ואת תצורות ההפעלה למכונה אחת או שתיים, את בדיקת הבריאות ואת בדיקת שימוש בכלים (tool-use) שאנחנו מבצעים לפני החלפת מודלים, יחד עם שומרי הסף ומנגנוני ההגנה על הזיכרון עבור שאר המכונה.