7.3 רוחבי פס ו peak rate תרגול
תרגול - רוחבי פס ו-peak rate¶
בתרגול הזה תיתנו לגגות של ה-Roofline מספרים אמיתיים, ואז תמדדו כמה אתם מתקרבים אליהם בפועל. תחשבו את קצב השיא של הכרטיס שלכם ביד מהמפרט (ותיזהרו מהמלכודת של FMA=2), תכתבו kernel שמעתיק זיכרון device-to-device ותמדדו את רוחב הפס המושג כאחוז מהשיא של ה-HBM, תחשבו נקודות מפנה לשני דיוקים ותקשרו לטבלה של שיעור 7.2, ולבסוף תסבירו - ותוכיחו עם profiler - למה הביצוע בפועל תמיד נמוך מהשיא. עבדו לפי הסדר; כל תרגיל בונה על הקודם. אפשר לעבוד על כל כרטיס NVIDIA מקומי או בחינם ב-Google Colab. אם אתם ב-Colab על T4, החליפו -arch=sm_90 ב--arch=sm_75, הוסיפו ! לפני כל פקודת shell, והשתמשו במפרט של ה-T4 (40 SM, 64 ליבות FP32 ל-SM, ~1.59 GHz, רוחב פס GDDR6 320 GB/s) במקום של ה-H100 - העיקרון זהה, המספרים שונים.
הכנה - קובץ המדידה¶
לאורך התרגול נשתמש בקובץ מדידה אחד, נרחיב אותו תרגיל אחרי תרגיל. צרו אותו עכשיו עם מאקרו בדיקת השגיאות:
// bwpeak.cu
#include <cstdio>
#include <cstdlib>
#include <cuda_runtime.h>
#define CUDA_CHECK(call) \
do { \
cudaError_t err_ = (call); \
if (err_ != cudaSuccess) { \
fprintf(stderr, "CUDA error %s at %s:%d\n", \
cudaGetErrorString(err_), __FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
int main() {
// filled in in exercises 2 and 5
return 0;
}
קמפלו תמיד עם ציון הארכיטקטורה ואופטימיזציה, למשל nvcc -arch=sm_90 -O3 bwpeak.cu -o bwpeak.
תרגיל 1 - חישוב קצב השיא מהמפרט¶
לפני שמודדים, מחשבים את התקרה. בתרגיל הזה אין קוד לחישוב עצמו - נייר, עיפרון, ומחשבון - אבל שלפו את המפרט מהכרטיס.
- הריצו תוכנית קצרה שקוראת ל-
cudaGetDevicePropertiesומדפיסה אתmultiProcessorCountואתclockRate(או שלפו את התדר מ-nvidia-smi -q -d CLOCK). לכל SM ב-H100 יש 128 ליבות FP32 (ידיעה סטנדרטית; לא כל השדות ב-cudaDevicePropחושפים זאת ישירות). - חשבו ביד את קצב השיא של FP32 (non-Tensor) לפי הנוסחה:
SM x (ליבות FP32 ל-SM) x (FLOP לליבה למחזור) x תדר. השתמשו ב-132 SM, 128 ליבות, ותדר 1.98 GHz. שימו לב: כמה FLOP יש ב-FMA אחת? - חשבו כעת את קצב השיא של BF16 על ה-Tensor Cores. NVIDIA מפרסמת ל-H100 SXM את המספר 989 TFLOP/s ב-BF16. עבדו הפוך: מכמה FLOP למחזור זה נובע, כמה FLOP ל-SM למחזור, וכמה FMA ל-SM למחזור זה (זכרו לחלק ב-2). השוו את מספר ה-FMA ל-SM למחזור בין ה-FP32 CUDA Core לבין ה-BF16 Tensor Core - פי כמה?
- חשבו: אם שכחתם ש-FMA = 2 FLOP, איזה מספר הייתם מקבלים ל-FP32, ובכמה אחוז הייתם טועים?
רמז: FMA (Fused Multiply-Add) מבצעת כפל וחיבור בהוראה אחת, ולכן נספרת כ-2 פעולות ממשיות. קצב שיא FP32 = 132 x 128 x 2 x 1.98e9. לחישוב ההפוך של BF16: התחילו מ-989e12, חלקו בתדר לקבל FLOP/מחזור, חלקו ב-132 לקבל FLOP/SM/מחזור, וחלקו ב-2 לקבל FMA/SM/מחזור.
תרגיל 2 - מדידת רוחב פס מושג של העתקה device-to-device¶
עכשיו נמדוד את גג הזיכרון בפועל. נכתוב kernel memory-bound מובהק - העתקה טהורה - ונראה כמה אחוז מ-3.35 TB/s אנחנו גורפים.
- הוסיפו ל-
bwpeak.cukernelcopyKernelשמעתיקout[i] = in[i]עם grid-stride loop על מערך שלN = 256Mfloat (1 GiB לכל מערך). - הקצו שני מערכים ב-device עם
cudaMalloc, אתחלו את הקלט, והריצו חימום (warmup) אחד לפני המדידה. - מדדו את הזמן עם
cudaEventעל פני לפחות 50 איטרציות, וחשבו רוחב פס: כמה בייטים זזו בכל העתקה? (רמז מובנה: כל אלמנט נקרא פעם ונכתב פעם.) - הדפיסו GB/s ואת האחוז מ-3350 GB/s. איזה אחוז קיבלתם? האם הוא בטווח 85%-92%?
- שנו את גודל ה-block וה-grid (למשל block של 128 מול 512, grid קטן מול גדול) והריצו שוב. כמה רגיש רוחב הפס לתצורת ההפעלה?
רמז: הבייטים שזזו הם 2 x N x sizeof(float) - קריאה אחת וכתיבה אחת לכל אלמנט. שכחת ה-2 תיתן חצי מהמספר. חלקו את הבייטים בזמן בשניות וב-1e9 ל-GB/s. תמיד חממו לפני המדידה כדי לא לספור overhead הפעלה ראשונה, ומדדו על פני הרבה איטרציות.
תרגיל 3 - נקודת המפנה לשני דיוקים¶
נחבר את שני רוחבי הפס לנקודת מפנה, ונקשר לטבלה של שיעור 7.2.
- חשבו את נקודת המפנה של H100 BF16 TC:
989 TFLOP/s / 3.35 TB/s. שמרו על היחידות (FLOP/s חלקי בייט/s = FLOP/בייט). - חשבו את נקודת המפנה של H100 FP8 TC:
1979 TFLOP/s / 3.35 TB/s. השוו לערך בטבלה של 7.2. - הסבירו בפסקה: מדוע נקודת המפנה של FP8 גבוהה בערך פי 2 מזו של BF16, למרות שרוחב פס הזיכרון זהה? מה זה אומר על כמה עבודה לכל בית צריך kernel כדי להישאר compute-bound ב-FP8?
- kernel של הכפלת וקטור בסקלר (
y[i] = a * x[i]) קורא 4 בייט וכותב 4 בייט לכל פעולה ממשית אחת - arithmetic intensity של ~0.125 FLOP/בייט. השוו ל-295 ול-591: משמאל או מימין לנקודת המפנה? memory-bound או compute-bound? - בונוס-חישוב: חשבו את נקודת המפנה גם ל-A100 BF16 (312/2) ול-B200 FP4 (9000/8). סדרו את כל ארבע (A100 BF16, H100 BF16, H100 FP8, B200 FP4) בסדר עולה והסבירו את המגמה.
רמז: נקודת מפנה = רוחב פס אריתמטי / רוחב פס זיכרון. 989/3.35 ~= 295; 1979/3.35 ~= 591. ה-FP8 מכפיל את רוחב הפס האריתמטי בלי לשנות את רוחב פס הזיכרון, ולכן מכפיל את נקודת המפנה. הarithmetic intensity של 0.125 קטנה בהרבה מ-295 - עמוק בצד ה-memory-bound.
תרגיל 4 - למה הביצוע בפועל תמיד מתחת לשיא¶
זהו תרגיל הסבר עם אימות ב-profiler.
- כתבו בפסקה, במילים שלכם, שלוש סיבות מדוע kernel אמיתי לא מגיע לקצב השיא. קשרו לשלושת החשודים שהגלוסרי מונה: לחץ אוגרים, מגבלת רוחב פס זיכרון, ומחסומי סנכרון (barriers).
- עבור ה-
copyKernelמתרגיל 2: האם הוא compute-bound או memory-bound? באיזה משני הגגות הוא "נוגע", ולמה גג החישוב שלו יהיה כמעט אפס? - הריצו את ה-
copyKernelתחתncuעם המדדיםgpu__dram_throughput.avg.pct_of_peak_sustained_elapsedו-sm__throughput.avg.pct_of_peak_sustained_elapsed. איזה מהשניים גבוה ואיזה נמוך? כיצד זה מאשר את התשובה שלכם מסעיף 2? - הסבירו: מדוע גם ה-
copyKernelשהוא "אופטימלי" מגיע רק ל-~90% ולא ל-100% מרוחב הפס? מה גוזל את ה-10% האחרונים?
רמז: kernel memory-bound "נוגע" בגג הזיכרון (ה-DRAM throughput גבוה) אבל בקושי בגג החישוב (ה-SM throughput נמוך) - וההפך ל-kernel compute-bound. ה-10% האחרונים אובדים לרענון DRAM, מעברי שורות, ו-overhead התחלה/סיום. השיא הוא תיאורטי בהנחת אפס עצירות.
תרגיל 5 - מדידת FLOP/s מושג ואחוז מהשיא¶
מדדנו את גג הזיכרון; עכשיו נמדוד את גג החישוב. נכתוב kernel עתיר-FMA טהור ונשווה ל-66.9 TFLOPS.
- הוסיפו ל-
bwpeak.cukernel שכל thread מריץ בו לולאה ארוכה של פעולות FMA תלויות-חלקית על ערכים באוגרים בלבד (בלי גישות זיכרון בלולאה הפנימית), למשל מספר משתני מצטבר שכל אחד מקבלacc = acc * x + y;שוב ושוב. השתמשו במספיק threads כדי להרוות את הכרטיס (למשלgrid = 132 * 4,block = 256). - ספרו כמה FLOP ה-kernel כולו ביצע:
(מספר threads) x (איטרציות ל-thread) x (FMA לאיטרציה) x 2. - מדדו את הזמן עם
cudaEvent, חשבו FLOP/s, וחלקו ב-66.9e12. איזה אחוז מהשיא קיבלתם? - הריצו את אותו kernel תחת
ncuעםsm__throughput.avg.pct_of_peak_sustained_elapsedוהשוו לאחוז שחישבתם ידנית. האם ה-DRAM throughput כאן נמוך (כצפוי מ-kernel compute-bound)?
רמז: כדי שהcompiler לא ימחק את הלולאה, כל איטרציה חייבת להיות תלויה בקודמת, והתוצאה הסופית חייבת להיכתב ל-global memory. השתמשו בכמה שרשראות FMA מקבילות (למשל 4-8 משתני acc) כדי לתת לחומרה מספיק עבודה בלתי-תלויה להסתיר latency ולהתקרב לשיא. הכפילו את מספר ה-FMA ב-2 ל-FLOP.
תרגיל 6 (בונוס) - מיפוי ה-Roofline של שני ה-kernels¶
עכשיו נשים את שני ה-kernels על ה-Roofline באותו גרף.
- עבור ה-
copyKernel(תרגיל 2) ועבור ה-kernel עתיר-FMA (תרגיל 5), רשמו זוג מספרים: (arithmetic intensity, ביצוע מושג ב-FLOP/s). ל-copyKernelהarithmetic intensity היא בערך 0 (הוא לא מחשב); ל-kernel החישובי היא גבוהה מאוד (בקושי נוגע בזיכרון). - ציירו את ה-Roofline של H100 (גג חישוב 66.9 TFLOPS ל-FP32 CUDA Core, שיפוע 3.35 TB/s) וסמנו עליו את שתי הנקודות. איזו נקודה יושבת על הגג המשופע ואיזו על הגג האופקי?
- הריצו את שניהם תחת
ncuעם שני מדדי ה-pct_of_peak_sustained_elapsed(DRAM ו-SM) ובנו טבלה של 2x2: kernel מול (DRAM%, SM%). הראו שהטבלה מזהה בדיוק איזה kernel memory-bound ואיזה compute-bound, בלי לצייר בכלל.
רמז: kernel שנוגע בגג המשופע יראה DRAM% גבוה ו-SM% נמוך; kernel שנוגע בגג האופקי יראה את ההפך. זו בדיוק אבחנת ה-Roofline שנשתמש בה בכל פרויקט מכאן והלאה כדי להחליט מה לייעל.