לדלג לתוכן

מה זה Occupancy ב-GPU - ולמה יותר לא תמיד אומר יותר טוב

אם התחלתם לחפור בכלי פרופיילינג כמו Nsight Compute, בטוח נתקלתם במספר אחד גדול שכולם מדברים עליו - occupancy. ויש בו הרבה בלבול, כי אינטואיטיבית נשמע שככל שהמספר הזה גבוה יותר, ככה טוב יותר. האמת מסובכת יותר, ומעניינת יותר.

אז מה זה בעצם occupancy

בואו נחזור רגע לרעיון שכבר דיברנו עליו - כל SM בתוך ה-GPU יכול להריץ במקביל כמה קבוצות של threads, שנקראות warp, כל אחת מהן 32 threads. אבל יש מגבלה - לכל SM יש כמות מוגבלת של רגיסטרים וזיכרון משותף, ולכן יש תקרה לכמה warp הוא יכול להחזיק פעיל בו זמנית.

occupancy הוא בדיוק היחס בין כמה warp פעילים אתם באמת משיגים, לבין המקסימום התיאורטי שה-SM יכול להחזיק. אם ה-SM שלכם יכול להחזיק עד 64 warp במקביל, והקוד שלכם מגיע ל-32, ה-occupancy שלכם הוא 50 אחוז.

למה זה בכלל חשוב

הסיבה ש-occupancy מעניין היא latency hiding, הסתרת עיכובים. גישה לזיכרון הראשי של ה-GPU איטית יחסית, לפעמים מאות מחזורי שעון. אם ל-SM יש רק warp אחד פעיל וה-warp הזה מחכה לנתונים מהזיכרון, ה-SM פשוט יושב בטל בזמן ההמתנה. אבל אם יש עוד עשרות warp פעילים, ה-warp scheduler פשוט מחליף למישהו אחר שמוכן לרוץ, ומסתיר את זמן ההמתנה לגמרי. ככל שיש יותר warp פעילים, יש יותר "מלאי" של עבודה שממתינה, כדי שה-SM לעולם לא ייתקע בלי מה לעשות.

זו הסיבה ש-occupancy גבוה בדרך כלל עוזר - הוא נותן ל-GPU יותר אפשרויות להסתיר עיכובים ולהישאר עמוס.

אז מה מגביל occupancy

שלושה משאבים עיקריים תחרותיים על אותו מרחב מוגבל ב-SM. הראשון הוא רגיסטרים - כל thread צריך רגיסטרים לעבודה שלו, וככל שה-kernel מסובך יותר, הוא צורך יותר רגיסטרים לכל thread, מה שמשאיר פחות מקום ל-threads נוספים במקביל. השני הוא זיכרון משותף - אם ה-kernel שלכם מקצה הרבה shared memory לכל block, פחות blocks יכולים לרוץ בו זמנית על אותו SM. השלישי הוא גודל ה-block עצמו - אם הבחירה שלכם לא מתחלקת יפה במספרי ה-warp המקסימליים של ה-SM, אתם משאירים משאבים על השולחן בלי לנצל אותם.

זו בדיוק הסיבה שכתיבת kernel יעיל היא לא רק עניין של לוגיקה נכונה, אלא גם ניהול תקציב משאבים מודע - כל רגיסטר נוסף שאתם משתמשים בו, וכל בית של זיכרון משותף, בא על חשבון כמה threads יכולים לרוץ ביחד.

אז למה occupancy גבוה לא תמיד אומר מהיר יותר

הנה החלק שמפתיע רבים. occupancy הוא לא מדד ביצועים בפני עצמו, הוא רק מדד שמאפשר הסתרת עיכובים. אם ה-kernel שלכם ממילא לא ממתין הרבה לזיכרון, כי הוא compute-bound ועסוק כל הזמן בחישוב, occupancy נמוך יותר לא בהכרח יפגע בו. יש אפילו מקרים שבהם הגדלת occupancy באמצעות הפחתת רגיסטרים לכל thread גורמת ל-kernel לבזבז יותר זמן על גישה חוזרת לזיכרון במקום לשמור ערכים ברגיסטרים, ובסופו של דבר הוא דווקא מאט.

זו בדיוק הסיבה שמהנדסי ביצועים לא רודפים אחרי occupancy של 100 אחוז כמטרה בפני עצמה, אלא בודקים אותו כאחד ממספר גורמים, לצד ניתוח memory-bound מול compute-bound שדיברנו עליו במודל Roofline.

איך לומדים לנווט את זה נכון

הבנת occupancy דורשת גם הבנה של ארכיטקטורת ה-SM וגם ניסיון מעשי בקריאת דוחות פרופיילינג. זה בדיוק אחד הנושאים שאנחנו מפרקים לגורמים בקורס תכנות GPU, עם דוגמאות אמיתיות של kernel שמראות איך שינוי קטן בשימוש ברגיסטרים או ב-block size משנה את התמונה כולה.

לסיכום

occupancy הוא מדד ליכולת של ה-GPU להסתיר עיכובי זיכרון בעזרת החלפה מהירה בין warp פעילים. הוא כלי חשוב, אבל לא המטרה הסופית - המטרה הסופית היא תמיד ביצועים בפועל, ולפעמים הדרך לשם עוברת דווקא דרך occupancy נמוך יותר עם ניצול חכם יותר של רגיסטרים.

בואו תלמדו לקרוא את המספרים האלה נכון, עם קהילה שכבר שם.

הצטרפו לקהילה בדיסקורד