לדלג לתוכן

מה זה Warp Divergence - כשסעיף if הופך לבעיית ביצועים

בקוד רגיל, if-else הוא הדבר הכי טבעי בעולם. אף אחד לא חושב פעמיים לפני שכותב תנאי. אבל בעולם ה-GPU, אותו if תמים יכול לגרום לקוד שלכם לרוץ באיטיות מפתיעה, אם לא הבנתם איך threads בתוך GPU באמת מבצעים תנאים. זה בדיוק מה שנקרא warp divergence, ואחת התופעות הראשונות שכל מתכנת CUDA חייב להכיר.

מודל ה-SIMT שעומד מאחורי כל זה

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

זה עובד מצוין כל עוד כל ה-32 threads בתוך warp רוצים לבצע בדיוק את אותה הוראה בדיוק באותו זמן. הבעיה מתחילה כשהם לא רוצים.

מה קורה כשמגיעים לתנאי

תארו לעצמכם kernel עם תנאי - "אם האינדקס זוגי, בצע פעולה A, אחרת בצע פעולה B". בתוך warp אחד, יש threads עם אינדקס זוגי ויש עם אינדקס אי-זוגי. אבל כל ה-32 threads בתוך אותו warp חייבים לבצע את אותה הוראה בדיוק באותו רגע. אז מה החומרה עושה?

היא מריצה את שני הענפים ברצף, אחד אחרי השני. קודם היא מריצה את הוראת A, כשה-threads שהתנאי שלהם לא מתקיים פשוט "מושבתים" זמנית ולא כותבים תוצאה. ואז היא מריצה את הוראת B, הפעם עם threads אחרים מושבתים. בפועל, warp אחד שהיה יכול לרוץ בזמן של פעולה בודדת, רץ עכשיו בזמן של שתי פעולות, כי החומרה מבצעת את שני הענפים ברצף במקום במקביל.

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

עד כמה זה יכול להזיק

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

איך נמנעים מזה

הפתרון הכי טוב הוא לתכנן את חלוקת העבודה כך שהתנאי לא תלוי במיקום של thread בתוך warp, אלא מתחלק בצורה גסה יותר - למשל, כל block שלם מבצע ענף אחד, ולא threads בודדים בתוך אותו warp. במקרים רבים אפשר גם לנסח מחדש את הלוגיקה כדי להימנע מתנאי לגמרי, למשל דרך נוסחאות מתמטיות שמחליפות if-else, או דרך מיון מוקדם של הנתונים כך שערכים דומים יגיעו לאותו warp.

חשוב להבהיר שלא כל תנאי הוא בעיה. אם כל ה-threads בתוך warp מסכימים על אותו נתיב, אין שום divergence ואין שום עלות נוספת. הבעיה מתעוררת רק כשיש חוסר הסכמה בתוך אותה קבוצה של 32.

למה זה מלמד שיעור רחב יותר

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

זה בדיוק סוג ההבנה שאנחנו בונים לעומק בקורס תכנות GPU, עם דוגמאות אמיתיות שמראות איך divergence נראה בפועל בכלי פרופיילינג, ואיך מתקנים אותו.

לסיכום

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

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

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