רוחב פס זיכרון ב-GPU מוסבר - הצוואר בקבוק שאף אחד לא מדבר עליו מספיק¶
כשאנשים חושבים על ביצועים של GPU, הם כמעט תמיד חושבים על כוח חישוב - כמה פעולות צף השנייה הכרטיס מסוגל לבצע. זה מספר מרשים, ומופיע בכל דף שיווקי של כל כרטיס. אבל האמת המפתיעה היא שברוב עבודות ה-GPU בעולם האמיתי, כוח החישוב הזה כלל לא המגבלה. המגבלה היא כמה מהר אפשר להזרים נתונים אל תוך הליבות ומחוצה להן. וזה בדיוק הנושא של הפוסט הזה - רוחב פס זיכרון.
מה זה בעצם רוחב פס - bandwidth¶
רוחב פס זיכרון מתאר כמות מידע שאפשר להעביר בין הזיכרון לליבות החישוב ביחידת זמן. תחשבו על זה כמו על צנרת מים - לא משנה כמה חזק המשאבה שלכם אם הצינור עצמו צר, כי הוא זה שקובע כמה מים בפועל יכולים לזרום בכל רגע נתון.
ב-GPU, אלפי ליבות עובדות במקביל, וכולן צריכות כל הזמן נתונים חדשים כדי להמשיך לעבוד. אם הזיכרון לא מצליח "להזין" את הליבות מספיק מהר, הליבות פשוט יושבות ומחכות, גם אם הן טכנית מסוגלות לבצע פי כמה יותר חישובים בשנייה. זו התופעה שנקראת memory-bound - החישוב מוגבל לא על ידי כוח המחשוב, אלא על ידי קצב אספקת הנתונים.
רוחב פס זה לא latency - וההבדל קריטי¶
חשוב מאוד להפריד בין שני מושגים שנשמעים דומים אבל הם שונים לגמרי. latency הוא הזמן שלוקח לבקשה בודדת לזיכרון לחזור עם תשובה - כמה זמן עובר מהרגע שביקשתם בייט מסוים עד שהוא הגיע. bandwidth, לעומת זאת, הוא כמה מידע כולל אפשר להעביר ביחידת זמן, כשיש הרבה בקשות שרצות במקביל.
ההבדל הזה משנה הכל בגישה לאופטימיזציה. אם הבעיה שלכם היא latency, הפתרון הוא לצמצם את מספר הפעמים שאתם מחכים לתשובה בודדת. אם הבעיה היא bandwidth, הפתרון הוא לצמצם את כמות הנתונים הכוללת שאתם צריכים להעביר, או לארגן אותה בצורה יעילה יותר. GPU בנוי בעיקרו כדי להתמודד עם latency על ידי הרצת המון threads במקביל - כשthread אחד מחכה לנתונים, אחר כבר עובד. אבל שום כמות של threads במקביל לא תפתור מגבלת bandwidth, כי היא מגבלה על כמות המידע הכוללת שיכולה לזרום, לא על זמן ההמתנה של בקשה בודדת.
עוצמת החישוב - arithmetic intensity¶
כדי לדעת אם עבודה מסוימת מוגבלת על ידי החישוב או על ידי הזיכרון, יש מושג מרכזי שכדאי להכיר - עוצמת חישוב, arithmetic intensity. המושג הזה שואל שאלה פשוטה - על כל בייט אחד שאתם מעבירים מהזיכרון, כמה פעולות חישוב אתם בפועל מבצעים עליו?
אם התשובה גבוהה, כלומר אתם מבצעים הרבה חישוב על כל בייט שהעברתם, סביר שהעבודה שלכם תהיה compute-bound - הכרטיס יבלה את רוב הזמן שלו בחישוב בפועל, וזה בדיוק המצב הרצוי. אבל אם התשובה נמוכה, כלומר אתם מעבירים המון נתונים אבל עושים עליהם מעט חישוב יחסית, סביר שהעבודה שלכם תהיה memory-bound - הכרטיס יבלה את רוב הזמן שלו מחכה לנתונים, לא מחשב אותם.
הרבה מהעבודות הנפוצות בעולם ה-AI, כמו פעולות מסוימות בהיסק של מודלים, סובלות בדיוק מהבעיה הזו - עוצמת החישוב נמוכה, כלומר יחסית למידע שצריך להזיז, נעשה עליו מעט חישוב, וכתוצאה מכך הצוואר בקבוק הוא הזיכרון ולא הליבות.
למה מהנדסי ביצועים תמיד בודקים את זה קודם¶
הסיבה שהמושג הזה כל כך מרכזי היא שהוא קובע לחלוטין לאיזה כיוון כדאי להשקיע מאמץ באופטימיזציה. אם אתם memory-bound ומנסים לשפר ביצועים על ידי כתיבת חישוב "חכם" יותר, לא תראו שיפור משמעותי, כי הצוואר בקבוק לא שם בכלל. במקום זה, הפתרון הוא לצמצם תעבורת זיכרון - לשמור נתונים קרוב יותר לליבות, לצמצם קריאות חוזרות לאותו מידע, או לשנות את סדר הגישה כך שהיא תהיה יעילה יותר.
ולהפך, אם אתם compute-bound, לצמצם תעבורת זיכרון לא יעזור, כי הליבות ממילא עסוקות בחישוב כל הזמן. שם ההשקעה צריכה ללכת לצמצום פעולות חישוב מיותרות, או לניצול יעיל יותר של יחידות החישוב הזמינות.
זו הסיבה שהצעד הראשון של כל מהנדס ביצועים רציני, לפני שהוא בכלל נוגע בקוד, הוא לבדוק לאיזו קטגוריה העבודה שלו שייכת. בלי התשובה לשאלה הזו, אופטימיזציה היא בעצם ניחוש. אנחנו מלמדים בדיוק את הכלים לזהות את זה בקורס תכנות GPU, כי זו אחת המיומנויות שמפרידות בין מי שכותב קוד GPU לבין מי שבאמת יודע לייעל אותו.
לסיכום¶
רוחב פס זיכרון הוא לרוב הצוואר בקבוק האמיתי בעבודות GPU, לא כוח החישוב הגולמי שמככב בעלוני השיווק. ההבדל בין latency ל-bandwidth קובע איזו אסטרטגיית אופטימיזציה רלוונטית, ומושג עוצמת החישוב עוזר לכם לדעת מראש אם אתם צפויים להיות memory-bound או compute-bound. מי שמפנים את זה, חוסך המון זמן שאחרת היה מתבזבז על אופטימיזציה בכיוון הלא נכון.
בואו תלמדו לזהות את זה נכון מהיום הראשון.