7.1 צווארי בקבוק ומודל Roofline הרצאה
עד כאן בנינו את הידע מלמטה למעלה: הכרנו את ארכיטקטורת החומרה (פרק 1), את מודל התכנות של CUDA (פרק 2), את היררכיית הזיכרון (פרק 3), את שפות הביניים והcompiler (פרק 4), את מחסנית התוכנה בhost (פרק 5) ואת ספריות הביצועים (פרק 6). כל אלו לימדו אותנו איך לכתוב קוד GPU נכון. הפרק הזה פותח שאלה חדשה ושונה במהותה: איך יודעים אם הקוד מהיר, ולמה הוא לא מהיר יותר. בתכנות GPU, בניגוד לתכנות מסורתי, הביצועים אינם תוספת אופציונלית - הם המוצר עצמו. בשיעור הזה נבנה את מסגרת החשיבה המרכזית של כל פרקי הביצועים (7 עד 9): מתודולוגיית צוואר הבקבוק (bottleneck) של גולדראט, החלוקה של עבודת ה-GPU לחישוב, זיכרון וoverhead, ובעיקר מודל ה-Roofline - התרשים הוויזואלי שמכריע בהצצה אחת אם kernel compute-bound או memory-bound. נבנה את ה-Roofline של H100 ממספרי הספסיפיקציה, נגדיר את נקודת הרכס (ridge point), ונבין את המלכוד הקריטי: מה שהמודל לא מראה חשוב לא פחות ממה שהוא כן.
הביצועים הם המוצר - performance is the product¶
לפני שנצלול לכלים, צריך להבין מדוע פרק שלם מוקדש לביצועים. תכנות GPU שונה מהותית מפיתוח תוכנה קונבנציונלי. אפליקציה רגילה - בסיס נתונים, שרת ווב - מציבה את הנכונות (correctness) מעל הכל: תשובה שגויה היא באג, נקודה. קוד GPU, לעומת זאת, מגדיר נכונות לרוב רק "עד מספר מסוים של ביטים משמעותיים, או רק עבור תת-קבוצה לא-מוגדרת של קלטים 'מתנהגים יפה'". כלומר, נכונות מספרית ומקורבת היא לגיטימית ואף מצופה: אימון רשת נוירונים ב-BF16 מקבל שגיאות עיגול כחלק מהעסקה.
מכאן נובע העיקרון המנחה של כל התחום, המיוחס לתרבות ההנדסית של NVIDIA: "הביצועים הם המוצר" (performance is the product). ביצועים מעולים אינם אופטימיזציה שנעשית "אם נשאר זמן" - הם תנאי הכרחי להצלחה. והמדדים הרלוונטיים אינם מופשטים אלא כלכליים: ביצועים לשנייה, לדולר, או לוואט. kernel שרץ פי שניים מהר יורד פי שניים בעלות הענן, מכפיל את התפוקה של מרכז הנתונים, וחוסך חצי מהחשמל. לכן, אחרי שלמדנו לכתוב קוד נכון, אנחנו מקדישים שלושה פרקים ללמוד לכתוב אותו מהיר - וזה מתחיל בכך שנדע לאבחן למה הוא איטי.
מתודולוגיית צוואר הבקבוק - the bottleneck methodology¶
צוואר הבקבוק (performance bottleneck) הוא המשאב שמגביל את קצב השלמת המשימות. המטאפורה מילולית: צוואר הבקבוק הפיזי הוא שמגביל את קצב שפיכת הנוזל, לא גוף הבקבוק הרחב. בכל מערכת, המשאב הצר ביותר קובע את הקצב הכולל - וכל שיפור במשאב שאינו צוואר הבקבוק לא יזיז את הביצועים כלל.
מכאן נגזרת מתודולוגיה בת שלושה שלבים, שמקורה בתורת האילוצים (Theory of Constraints) של אליהו גולדראט, בהשראת שיטות הייצור של טויוטה:
1. Locate the bottleneck (what is the limiting resource?)
2. Elevate it (improve it until it no longer limits)
3. Repeat the process (now a new bottleneck becomes the limiter)
הלולאה הזו אינסופית מטבעה: ברגע שפתרתם את צוואר הבקבוק הנוכחי, משאב אחר הופך למגביל, ומתחילים מחדש. זו הסיבה שאופטימיזציה אף פעם לא "נגמרת" - היא מתכנסת אל צוואר הבקבוק הבא.
החלוקה של עבודת ה-GPU לשלוש קטגוריות. כדי לדעת היכן לחפש את צוואר הבקבוק, מחלקים את כל עבודתו של kernel לשלושה סוגים (חלוקה המיוחסת להרצאה של Horace He):
| קטגוריית עבודה | מה קורה בפועל | צוואר הבקבוק המתאים |
|---|---|---|
| חישוב - compute | הרצת פעולות נקודה-צפה על CUDA Cores או Tensor Cores | חסום-חישוב (compute-bound) |
| זיכרון - memory | הזזת נתונים דרך היררכיית הזיכרון | חסום-זיכרון (memory-bound) |
| תקורה - overhead | כל השאר | חסום-תקורה (overhead-bound) |
לכל קטגוריית עבודה מתאים מחלקה של צוואר בקבוק:
- גרעין compute-bound - kernel - מוגבל ברוחב הפס האריתמטי (arithmetic bandwidth) של יחידות החישוב. דוגמה: כפל מטריצה-מטריצה גדול (GEMM).
- גרעין memory-bound - kernel - מוגבל ברוחב הפס של תת-מערכת הזיכרון (memory bandwidth). דוגמה: כפל וקטור-וקטור גדול.
- גרעין overhead-bound - kernel - מוגבל ב-latency (השהיה): ה-GPU יושב בטל ומחכה לעבודה. דוגמה: פעולות על מערכים קטנטנים.
הסתייגות חשובה. בעיקרון כל משאב יכול להיות צוואר בקבוק (נחזור לזה בהמשך - הספק וחום הם דוגמה), אבל, כלשון ה-glossary: "חישוב וזיכרון הם המשאבים החשובים ביותר וצווארי הבקבוק הנפוצים ביותר". לשם נפנה את עיקר המאמץ. וכאן נכנס הכלי המרכזי לאבחון: מודל ה-Roofline, שמפריד בהצצה אחת בין compute-bound לmemory-bound.
מודל ה-Roofline - הצירים והגגות¶
מודל ה-Roofline הוא מודל ויזואלי ומפושט של ביצועים, שנועד לקבוע במהירות אם תוכנית bound ברוחב פס של זיכרון או ברוחב פס אריתמטי. שני צירים בונים אותו:
- ציר ה-X - עצימות אריתמטית (arithmetic intensity): פעולות לכל בית (operations per byte). זהו יחס העבודה החישובית לתנועת הנתונים ב-kernel.
- ציר ה-Y - ביצועים (performance): פעולות לשנייה (operations per second).
על גבי הצירים האלה מציירים שני "גגות" (roofs), שניהם נגזרים מהחומרה בלבד:
- גג החישוב - compute roof: קצב השיא (peak rate) של יחידות החישוב, כלומר רוחב הפס האריתמטי. הוא נצבע כקו אופקי בגובה שווה לרוחב הפס האריתמטי (ב-FLOP/s). לא משנה כמה עצימה התוכנית - היא לא תעבור את גובה גג החישוב.
- גג הזיכרון - memory roof: קצב השיא של תנועת הזיכרון, כלומר רוחב הפס של הזיכרון. הוא נצבע כקו אלכסוני שהשיפוע שלו שווה לרוחב הפס של הזיכרון. שימו לב ליחידות: ביצועים חלקי arithmetic intensity =
[FLOP/s] / [FLOP/byte] = byte/s, כלומר השיפוע הוא באמת רוחב פס (בתים לשנייה).
התקרה בת-ההשגה היא המינימום בין השניים. בכל arithmetic intensity נתונה, הביצועים המקסימליים האפשריים הם הנמוך מבין שני הגגות. גרפית, זהו קו שעולה אלכסונית מהראשית (גג הזיכרון), נפגש עם גג החישוב, ומשם ואילך משתטח לקו אופקי:
performance [FLOP/s]
(log scale)
^
| compute roof (arithmetic BW)
989T|........................__________________________ <- horizontal line
| /:
| memory roof / :
| (slope = / :
| mem BW) / : compute-bound region
| / : (below the horizontal roof)
| / :
| / region :
| / memory-bound :
| / (below the :
| / diagonal roof) :
| / :
| / :
+-/-----------------------+-------------------------> arithmetic intensity
ridge [FLOP/byte]
point (=295 for H100 BF16)
סיווג לפי המיקום מתחת לגג:
- נקודה מתחת לגג האופקי (מימין לנקודת הרכס) => compute-bound. הarithmetic intensity גבוהה, הרבה פעולות לכל בית.
- נקודה מתחת לגג האלכסוני (משמאל לנקודת הרכס) => memory-bound. הarithmetic intensity נמוכה, מעט פעולות לכל בית.
בדיקת מציאות. kernel-ים אמיתיים כמעט לעולם אינם נוגעים באף אחד מהגגות - הם יושבים מתחת לקו ה-Roofline בגלל אפקטים של overhead. הגג הוא התקרה התיאורטית; המרחק ממנו הוא בדיוק מה שיש לכם עוד לשפר.
נקודת הרכס - the ridge point¶
הנקודה שבה הגג האלכסוני והגג האופקי נפגשים נקראת נקודת הרכס (ridge point), והיא המושג המרכזי של המודל. שיעור ה-X שלה הוא הarithmetic intensity המינימלית הדרושה כדי להיחלץ מצוואר הבקבוק של הזיכרון, כלומר להפוך לcompute-bound.
גזירת נקודת הרכס. הנקודה מוגדרת בדיוק היכן ששני הגגות שווים. הגג האלכסוני נותן ביצועים של arithmetic_intensity * memory_BW, והגג האופקי נותן arithmetic_BW. משווים:
arithmetic_intensity * memory_BW = arithmetic_BW
=> arithmetic_intensity = arithmetic_BW / memory_BW
וזוהי הנוסחה שכדאי לחרוט בזיכרון:
נקודת הרכס אינה תלויה ב-kernel כלל - היא תכונה של החומרה בלבד, יחס בין שני רוחבי הפס שלה.
שמאל מול ימין. מערכת שנקודת הרכס שלה שמאלה יותר קלה יותר להשגת ביצועי שיא: דרושה arithmetic intensity נמוכה יחסית כדי להפוך לcompute-bound ולנצל את מלוא כוח החישוב. לרוע המזל, "הסקיילינג הגרוע יחסית של הזיכרון לעומת החישוב דחף את נקודות הרכס של מערכות ימינה עם השנים". במילים אחרות: ככל שהחומרה מתקדמת, נעשה קשה יותר להיות compute-bound, כי כוח החישוב גדל מהר יותר מרוחב הפס של הזיכרון. נראה זאת מיד במספרים של שלושה דורות.
בניית ה-Roofline של H100 - worked example¶
בואו נבנה Roofline קונקרטי מספרי הספסיפיקציה של H100 SXM, בשביל מסלול ה-Tensor Core ב-BF16 (קלט BF16, צבירה FP32) - מסלול החישוב הנפוץ באימון והסקה מודרניים:
compute roof (arithmetic BW), Tensor Core BF16 : 989 TFLOP/s
memory roof (memory BW), HBM3 : 3.35 TB/s
---------------------------------------------------------
ridge point = 989e12 / 3.35e12 = 295 FLOP/byte
כלומר: כדי ש-kernel על H100 ינצל את מלוא ליבות הטנזור ב-BF16, עליו לבצע לפחות 295 פעולות נקודה-צפה לכל בית שהוא מזיז מהזיכרון. פחות מזה - הוא memory-bound, וליבות הטנזור היקרות מורעבות.
נקודות הרכס נעות ימינה לאורך הדורות. נשווה שלושה דורות של ליבות טנזור ב-BF16:
| מערכת / דור | רוחב פס אריתמטי (BF16) | רוחב פס זיכרון | נקודת רכס [FLOP/byte] |
|---|---|---|---|
| A100 80GB SXM (Ampere, HBM2e) | 312 TFLOP/s | 2 TB/s | 156 |
| H100 SXM (Hopper, HBM3) | 989 TFLOP/s | 3.35 TB/s | 295 |
| B200 (Blackwell, HBM3e) | 2250 TFLOP/s | 8 TB/s | 281 |
מ-Ampere ל-Hopper נקודת הרכס כמעט הכפילה את עצמה (156 -> 295): כוח החישוב שולש בערך, בעוד רוחב הפס של הזיכרון גדל פחות. זהו "קיר הזיכרון" (memory wall) בפעולה, שנחזור אליו בהיסטוריה.
אותה חומרה, כמה Roofline-ים - per-subsystem rooflines. נקודה עדינה וקריטית: הגגות נגזרים פעם אחת לכל תת-מערכת, אך הם משתנים לפי תת-המערכת, לא רק לפי המערכת. אותו H100 פיזי, אבל מסלול חישוב שונה נותן גג חישוב שונה ולכן נקודת רכס שונה:
| מסלול חישוב ב-H100 SXM | רוחב פס אריתמטי | נקודת רכס |
|---|---|---|
| CUDA Cores, FP32 | ~66.9 TFLOP/s | ~20 |
| Tensor Core, BF16 | 989 TFLOP/s | 295 |
| Tensor Core, FP8 | 1979 TFLOP/s | 592 |
שלוש שורות, שלושה Roofline-ים שונים על אותו כרטיס. ליבות הטנזור מספקות הרבה יותר FLOPS מ-CUDA Cores, ולכן דורשות arithmetic intensity גבוהה בהרבה כדי להיות compute-bound. וירידה בדיוק הקלט (BF16 -> FP8) מכפילה את רוחב הפס האריתמטי אך משאירה את רוחב פס הזיכרון קבוע - ולכן דוחפת את נקודת הרכס עוד ימינה. המסקנה המעשית: כשאתם משרטטים Roofline, בחרו את הגג שמתאים למסלול החישוב שאתם באמת מריצים. Roofline של ליבות טנזור אינו רלוונטי ל-kernel שרץ על CUDA Cores.
עצימות אריתמטית - arithmetic intensity ומיקום kernel-ים¶
הarithmetic intensity היא ציר ה-X, והמספר היחיד שקובע באיזה צד של נקודת הרכס תיפול. היא מוגדרת כיחס הפעולות האריתמטיות לפעולות הזיכרון ב-kernel, ביחידות של FLOP לבית. הarithmetic intensity גבוהה אומרת הרבה פעולות לכל בית שנטען - וזה טוב, כי מודרני GPU-ים בעלי רוחב פס אריתמטי גבוה בהרבה מרוחב פס הזיכרון, כך שarithmetic intensity גבוהה מעסיקה את יחידות החישוב המהירות במקום להרעיב אותן על זיכרון איטי.
הטבלה הבאה, הליבה של הנושא, מראה איך הarithmetic intensity מתנהגת עבור שלושה kernel-ים קלאסיים כפונקציה של גודל הבעיה N:
| Kernel | FLOPs | בתים שהוזזו | הarithmetic intensity | סקיילינג ב-N |
|---|---|---|---|---|
| SAXPY (y = a·x + y) | 2N | 12N | 1/6 | O(1) - קבועה |
| FFT ממשי דיוק-יחיד | (5/2)·N·log(N) | 16N | (5/32)·log(N) | O(log N) |
| SGEMM (C = A·B + C) | 2N³ | 16N² | N/8 | O(N) - לינארי |
הפרשנות היא לב העניין. ה-SAXPY (פעולת וקטור) מחזיק arithmetic intensity קבועה ונמוכה מאוד (1/6 בערך 0.17 FLOP/byte) - כלומר הוא תמיד memory-bound, ללא תלות בגודל. ה-FFT גדל רק לוגריתמית. אבל ה-SGEMM (כפל מטריצות) הוא O(N) בarithmetic intensity: O(N³) פעולות מעל O(N²) בתים. הסקיילינג הנוח הזה הוא "קריטי להצלחת רשתות הנוירונים": מטריצות גדולות יותר => arithmetic intensity גבוהה יותר => קל יותר להיות compute-bound => ניצול יעיל של ליבות הטנזור.
חישוב עובד - מתי SGEMM הופך לcompute-bound על H100. נציב את arithmetic intensity ה-SGEMM, N/8, מול נקודת הרכס של H100 ב-BF16, 295:
כלומר, על H100 בליבות טנזור BF16, כפל מטריצות מרובעות הופך לcompute-bound רק סביב N ~ 2360 ומעלה. מתחת לזה - אתם memory-bound, וליבות הטנזור אינן רוויות. זו בדיוק הסיבה שאימון והסקה של רשתות ענק אוהבים מטריצות ענק ו-batch-ים גדולים.
מיקום נקודה מתחת לגג. אם kernel memory-bound (משמאל לרכס), הביצועים בפועל שהוא ישיג הם arithmetic_intensity * memory_BW. למשל SAXPY על H100:
כלומר, SAXPY על H100 יגיע ל-0.56 TFLOP/s בלבד, פחות מאלפית מ-989 TFLOP/s של גג החישוב. לא מפני שהחישוב איטי, אלא מפני שהarithmetic intensity נמוכה מדי; הכרטיס מבזבז את רוב זמנו בהמתנה לזיכרון. אין טעם לקנות GPU חזק יותר בחישוב עבור kernel כזה - צריך להעלות את הarithmetic intensity (לדוגמה, על ידי fusion של פעולות כדי לחלוק טעינות).
החלפת עבודת זיכרון בעבודת חישוב. כשאתם memory-bound, אפשר להזיז עבודה מתת-מערכת הזיכרון אל תת-מערכת החישוב - להעלות את הarithmetic intensity במכוון. שתי טכניקות קלאסיות: דחיסת נתונים (פחות בתים לתעבורה, במחיר חישוב פענוח) ו-gradient checkpointing (חישוב מחדש של ערכי ביניים במקום אחסונם בזיכרון גלובלי בזמן backpropagation, מה שמעלה את הarithmetic intensity). שתיהן סוחרות תעבורת זיכרון בחישוב - הגיוני בדיוק כשהזיכרון הוא צוואר הבקבוק.
מה ה-Roofline לא מראה - הגבול הקריטי¶
וכאן המלכוד החשוב ביותר בכל השיעור, וזו הסיבה שהoverhead היא קטגוריית צוואר בקבוק נפרדת ושלישית. מודל ה-Roofline הוא "פשוט באופן מטעה" (deceptively simple). כלשון ה-glossary:
"ה-latency של המערכת אינה מופיעה בשום מקום בתרשים - רק רוחבי פס ותפוקות."
חשבו על כך: שני הצירים הם FLOP/byte ו-FLOP/s, ושני הגגות הם רוחב פס אריתמטי ורוחב פס זיכרון. אין ציר, אין גג, ואין נקודה שמייצגים latency או overhead. לכן המודל לא מסוגל לאבחן מצב overhead-bound. הוא מפריד אך ורק בין compute-bound לmemory-bound.
מהי overhead בכלל. תקורה (overhead) היא "הזמן שבו לא מתבצעת עבודה שימושית". זו ההבחנה היסודית: בזמן צוואר בקבוק של חישוב או זיכרון, ה-GPU עובד הכי מהר שהוא יכול - הוא פשוט מוגבל. בזמן overhead, ה-GPU יושב בטל ומחכה לקבל עבודה או הוראות. הoverhead היא זמן-סרק, לא עבודה איטית. דוגמאות:
- הoverhead קריאת CUDA API: בערך 10 מיקרו-שניות לכל dispatch kernel. אם ה-kernel עצמו רץ 5 מיקרו-שניות, מחצית מהזמן בזבוז טהור.
- קבלת החלטות של framework (PyTorch/TensorFlow) שבוחר איזה kernel לdispatch: מיקרו-שניות רבות.
- הoverhead תקשורת בהעברת נתונים בין CPU-GPU או GPU-GPU.
למה זה מסביר את "מתחת לגג". זו בדיוק הסיבה ש-kernel-ים אמיתיים יושבים מתחת ל-Roofline: הoverhead גוזלת זמן שאינו מופיע במודל. ה-Roofline יגיד לכם "אתה יכול תיאורטית להגיע לגג הזה", אבל אם ה-kernel overhead-bound, תישארו הרחק מתחתיו, וה-Roofline לבדו לעולם לא יסביר למה. לשם צריך כלים אחרים: חוק ליטל (Little's Law) להסתרת latency, פרופיילינג של זמני-סרק, ו-CUDA Graphs לצמצום overhead הdispatch (שכבר ראינו בשיעור 5.5). דוגמה קלאסית ל-kernel overhead-bound: פעולה על מערך קטנטן - dispatch של 10 מיקרו-שניות עבור עבודה של מיקרו-שנייה. ב-Roofline הוא יופיע כנקודה נמוכה מאוד, בלי שום דרך להבחין אם הנמיכות נובעת מזיכרון או מoverhead. זה תמיד מזכיר לנו: ה-Roofline הוא כלי חד-ממדי לאבחון דו-דרכי (חישוב מול זיכרון); הoverhead היא הממד השלישי, ולה כלים משלה.
כל משאב יכול להיות צוואר בקבוק, וכלי האבחון - Nsight Compute¶
אמרנו שחישוב וזיכרון הם צווארי הבקבוק הנפוצים, אבל כל משאב יכול להיות צוואר בקבוק - וכדאי לדעת אילו עוד קיימים, כי לפעמים הם המפתיעים:
- כניסת הספק ופליטת חום יכולות לחסום GPU מתחת למקסימום התיאורטי שלו. אם אין מספיק חשמל להזין את כל הטרנזיסטורים בו-זמנית, או אם החום לא נפלט מהר מספיק, הכרטיס מוריד תדר (throttling) והביצועים צונחים - בלי קשר לחישוב או לזיכרון.
- חברת NVIDIA השיגה שיפור ביצועים של 4% מקצה-לקצה על ידי ניתוב הספק מcache ה-L2 אל ה-SMs. מספר קונקרטי ומאלף: אותו סיליקון בדיוק, אותו חישוב, אבל חלוקה שונה של תקציב ההספק נתנה 4% - הוכחה שהספק הוא משאב אמיתי שאפשר לחסום עליו.
- ביצועי כפל מטריצות משתנים לפי הנתונים עצמם: דפוסי ביט שונים גורמים לפעילות מיתוג שונה של טרנזיסטורים => צריכת הספק שונה => ביצועים שונים. אותו kernel על אותו כרטיס יכול לרוץ בקצב שונה תלוי בקלט.
זו הסיבה שהמתודולוגיה של גולדראט חשובה: לא מניחים מראש מהו צוואר הבקבוק, מודדים אותו. וכלי המדידה הסטנדרטי לרמת ה-kernel הוא Nsight Compute (הפקודה ncu), הפרופיילר של NVIDIA. הוא מבצע אוטומטית ניתוח Roofline עבור kernel-ים שהוא מפרופל - מציב את הנקודה שלכם ביחס לגגות, ומראה מיד באיזה צד של נקודת הרכס אתם ובכמה אתם רחוקים מהתקרה. בפרקים 8 ו-9 נעבוד עם ncu באופן שוטף; כאן די שנדע שה-Roofline אינו רק תרגיל עיפרון-ונייר אלא פלט אמיתי של כלי, ושהעיקרון הראשון שלנו הוא: מדוד, אל תנחש.
ההיסטוריה - למה נולד ה-Roofline¶
מודל ה-Roofline לא נפל מהשמיים; הוא נולד ב-2008 במאמר של Samuel Williams, Andrew Waterman ו-David Patterson, כתשובה לשלוש מגמות חומרה שהתבררו בעשור שקדם לו. הבנת המגמות האלה מסבירה למה כל התחום התכנס ל"arithmetic intensity מול רוחב פס".
1. קיר הזיכרון - the memory wall (Wulf ו-McKee, 1994). תת-מערכות החישוב (ליבות מעבד) שיפרו את ביצועיהן הרבה יותר מהר מתת-מערכות הזיכרון (caches ו-DRAM). הפער הזה, שגדל אקספוננציאלית, נקרא "קיר הזיכרון": ככל שהחישוב מזנק, הזיכרון נשאר מאחור, והוא הופך יותר ויותר לצוואר הבקבוק. ראינו את זה במספרים - נקודת הרכס נעה מ-156 ב-A100 ל-295 ב-H100 בדיוק בגלל הפער הזה.
2. ה-latency מפגר אחרי רוחב הפס - latency lags bandwidth (Patterson, 2004). תצפית של Patterson: היסטורית, "שיפור לינארי ב-latency לווה בשיפור ריבועי ברוחב הפס". כלומר, רוחב הפס משתפר הרבה יותר מהר מה-latency. ההשלכה: מערכות ייהפכו מוכוונות-תפוקה (throughput-oriented) - נתכנן אותן להזרים המון עבודה במקביל כדי להסתיר את ה-latency הגבוה. ה-GPU הוא ההתגלמות המושלמת של הרעיון הזה.
3. קץ סקיילינג דנארד - the end of Dennard scaling (תחילת שנות ה-2000). דנארד סקיילינג היה הכלל שאיפשר להעלות תדר שעון בהספק קבוע ככל שהטרנזיסטורים מתכווצים. הוא הסתיים בעיקר בגלל זרם הדליפה הקבוע של הטרנזיסטורים, שיצר בעיות של צריכת הספק ופיזור חום. הקץ הזה לא לווה בהאטה של חוק מור (עדיין קיבלנו יותר טרנזיסטורים), וזה דחף להתמחות חומרתית (hardware specialization): פירוק המחשב לרכיבים המתמחים כל אחד במשימה נפרדת. ליבות הטנזור הן בדיוק זה - חומרה ייעודית לכפל מטריצות.
הפאנץ' שהמגמות האלה ייצרו. מערכות העתיד יהיו מוכוונות-תפוקה, ומבין כל רוחבי הפס, רוחב הפס של הזיכרון יהיה צוואר הבקבוק המרכזי. לכן, אפליקציות שרוצות ביצועי שיא חייבות להחזיק arithmetic intensity גבוהה עבור הפעולות המתמחות של החומרה - במקרה של GPU, arithmetic intensity עבור ליבות הטנזור, כלומר כפל מטריצות גדול מאוד. כך שלושת המגמות של 2008 מסבירות ישירות למה, ב-2026, אנחנו מאמנים מודלים עם מטריצות ענק על ליבות טנזור - ולמה נקודת הרכס ממשיכה לנוע ימינה בכל דור.
סיכום¶
- בתכנות GPU הביצועים הם המוצר: הנכונות לרוב מקורבת (עד מספר ביטים), והמדדים הם ביצועים לשנייה, לדולר או לוואט; לכן אבחון וכיוונון ביצועים הם מיומנות ליבה, לא תוספת.
- מתודולוגיית צוואר הבקבוק של גולדראט (תורת האילוצים): אתר את צוואר הבקבוק, הרם אותו, וחזור על התהליך על צוואר הבקבוק החדש - לולאה שלעולם אינה נגמרת.
- עבודת ה-GPU מתחלקת לשלושה: חישוב (CUDA/Tensor Cores), זיכרון (הזזת נתונים) וoverhead (כל השאר), ובהתאמה שלוש מחלקות צוואר בקבוק: compute-bound, memory-bound וoverhead-bound.
- מודל ה-Roofline: ציר X הוא arithmetic intensity (FLOP/byte), ציר Y הוא ביצועים (FLOP/s); גג החישוב אופקי בגובה רוחב הפס האריתמטי, גג הזיכרון אלכסוני בשיפוע רוחב הפס של הזיכרון, והתקרה בת-ההשגה היא הנמוך מביניהם.
- נקודת הרכס (ridge point) היא היכן שהגגות נפגשים, ושיעור ה-X שלה = arithmetic BW / memory BW = הarithmetic intensity המינימלית להיות compute-bound; ל-H100 ב-BF16 היא 295 FLOP/byte.
- נקודות הרכס נעות ימינה עם הדורות (A100 156 -> H100 295) בגלל קיר הזיכרון, וכל תת-מערכת (CUDA Cores מול Tensor Cores, BF16 מול FP8) נותנת Roofline ונקודת רכס שונים על אותו כרטיס.
- כפל מטריצות (SGEMM) הוא O(N) בarithmetic intensity ולכן הופך לcompute-bound ככל שהמטריצה גדלה (על H100 BF16, סביב N=2360); פעולות וקטור כמו SAXPY הן O(1) בarithmetic intensity (1/6) ולכן תמיד memory-bound.
- המלכוד הקריטי: ה-latency והoverhead אינם מופיעים ב-Roofline (רק רוחבי פס ותפוקות), ולכן kernel overhead-bound אינו ניתן לאבחון ממנו - זו הסיבה ש-kernel-ים אמיתיים יושבים מתחת לגגות ושהoverhead היא קטגוריה שלישית עם כלים משלה.
- כל משאב יכול לחסום (הספק וחום - NVIDIA הרוויחה 4% מהעברת הספק מ-L2 ל-SMs), אך חישוב וזיכרון הם הנפוצים; לכן מודדים ולא מנחשים, ו-Nsight Compute (
ncu) מייצר Roofline אוטומטי. - מודל ה-Roofline נולד ב-2008 (Williams-Waterman-Patterson) בתגובה לשלוש מגמות: קיר הזיכרון (Wulf-McKee 1994), ה-latency שמפגר אחרי רוחב הפס (Patterson 2004) וקץ סקיילינג דנארד שדחף להתמחות חומרתית - שלושתן מובילות למסקנה שרוחב פס הזיכרון הוא צוואר הבקבוק המרכזי, ולכן צריך arithmetic intensity גבוהה.