לדלג לתוכן

9.1 פרופיילינג Nsight Systems ו Nsight Compute הרצאה

לאורך פרקים 7 ו-8 בנינו את כל התיאוריה של הביצועים: מודל ה-Roofline וההבחנה בין compute-bound, memory-bound וoverhead-bound (שיעורים 7.1-7.4), ואת המדדים המיקרו-ארכיטקטוניים - occupancy, ניצולת ה-SM וה-pipe, warp divergence, scoreboard stalls, coalescing ו-bank conflicts (שיעורים 8.1-8.5). אבל כל אלה נשארו עד עכשיו מספרים שחישבנו ביד או שיערנו על הנייר. השיעור הזה נותן את הכלים שהופכים את כל התיאוריה הזו למדידה אמיתית על ה-GPU שלכם: שני ה-profilers של NVIDIA - Nsight Systems (nsys) לתמונה מערכתית של ציר הזמן, ו-Nsight Compute (ncu) לצלילה עמוקה לתוך kernel בודד. שניהם, יחד עם ה-PyTorch Profiler, בנויים על שכבת תשתית אחת בשם CUPTI (interface כלי-הפרופיילינג של CUDA), שנכיר גם אותה. הרעיון המרכזי לזכור לאורך כל השיעור הוא חלוקת העבודה: nsys עונה על השאלה "איפה אנחנו מפסידים זמן, ובאיזה kernel", ואילו ncu עונה על "למה ה-kernel הזה איטי". שני הכלים, שני מפלסים, לולאת אופטימיזציה אחת.

שכבת התשתית - CUPTI

לפני שנוגעים בכלים עצמם, חשוב להבין שהם אינם מדברים עם החומרה ישירות. מתחת ל-nsys, ל-ncu ול-PyTorch Profiler יושבת ספרייה אחת: CUPTI - interface כלי-הפרופיילינג של CUDA (CUDA Profiling Tools Interface). בלשון הגלוסרי, CUPTI "מספק אוסף של APIs לפרופיילינג של ביצוע קוד CUDA C++, PTX ו-SASS על GPUs". כלומר הוא יודע לנטר את שלושת מפלסי הקוד שהכרנו בפרק 4: את המקור (CUDA C++), את שפת הביניים (PTX, שיעור 4.1) ואת האסמבלי של החומרה (SASS, שיעור 4.2).

המשימה החתומה של CUPTI, זו שהגלוסרי בוחר להדגיש מכל היכולות שלו, היא סנכרון חותמות-זמן (timestamps) בין הhost (CPU) לdevice (GPU). זו בדיוק הבעיה הקשה: ל-CPU יש שעון משלו, ל-GPU יש שעון משלו, והם רצים במקביל ובאופן אסינכרוני. כדי להציג ציר זמן מאוחד - שבו קריאת CUDA API בצד ה-CPU, ה-kernel שהיא משגרת, וה-memcpy שרץ אחריה, כולם מופיעים על אותו קו זמן במיקום הנכון - צריך מישהו שיתרגם בין שני השעונים ויקבע מתי בדיוק, בזמן ה-CPU, התחיל וסיים כל אירוע ב-GPU. זהו התפקיד של CUPTI, וזו הסיבה שכל profiler מערכתי נשען עליו.

        profiling applications (high level)
   +----------------+----------------+------------------+
   | Nsight Systems |  Nsight Compute|  PyTorch Profiler|
   +----------------+----------------+------------------+
                 |  all consume       |
                 v                    v
        +--------------------------------------+
        |            CUPTI (infrastructure)     |
        |  synchronized CPU<->GPU timestamps    |
        |  profiles CUDA C++ / PTX / SASS       |
        +--------------------------------------+
                          |
                          v
                 GPU driver + hardware

שימו לב להבחנה עדינה: Nsight Systems וה-PyTorch Profiler הם צרכנים ישירים של CUPTI. Nsight Compute משתמש בתשתית פרופיילינג נמוכה-מפלס דומה כדי לקרוא את מוני החומרה (hardware performance counters), אבל התמונה המנטלית נכונה: הכלים שבהם נשתמש הם interfaces-משתמש מעל שכבת ניטור אחת שמדברת עם החומרה. כשמשהו לא עובד בפרופיילינג (הרשאות, מוני חומרה חסומים), הבעיה כמעט תמיד בשכבת התשתית הזו ולא בכלי עצמו.

התמונה המערכתית - Nsight Systems

Nsight Systems הוא, בלשון הגלוסרי, "כלי לניפוי-באגים של ביצועים לתוכניות CUDA C++, שמשלב פרופיילינג, tracing וניתוח מבוסס-מומחה ב-GUI". המילה החשובה כאן היא מערכתי (system-wide): nsys לא מסתכל על kernel בודד אלא על כל המערכת לאורך זמן - threads ה-CPU, קריאות ה-CUDA API, dispatches ה-kernels, העתקות הזיכרון, ה-streams וה-events (שיעור 5.3). הוא מצייר ציר זמן אחד שבו רואים בבת אחת מה ה-CPU עושה ומה ה-GPU עושה, מסונכרנים דרך CUPTI.

הפקודה הבסיסית פשוטה. עוטפים בה את האפליקציה כרגיל:

# create a profile report (.nsys-rep file) from running the application
nsys profile -o report_myapp ./myapp

# view a text summary without opening the GUI
nsys stats report_myapp.nsys-rep

ה-nsys profile מריץ את ./myapp מתחילתה ועד סופה, אוסף את כל האירועים דרך CUPTI, וכותב קובץ report_myapp.nsys-rep. אפשר לפתוח אותו ב-GUI של Nsight Systems (על מכונה עם תצוגה גרפית) או לקבל סיכום מיידי בטרמינל עם nsys stats. הסיכום הטקסטואלי לבדו כבר עונה על שאלות רבות, וזה מה שנשתמש בו בתרגול על שרת ללא GUI.

מה מחפשים בציר הזמן של nsys? ארבעה דפוסי תקלה עיקריים, וכל אחד מהם מתאר צוואר בקבוק מסוג אחר:

  • פערים (gaps) בציר ה-GPU - רצועות שבהן ה-GPU לא עושה כלום. אם ה-GPU בטל בזמן שה-CPU עסוק, אתם overhead-bound (overhead-bound, שיעור 7.4): ה-GPU מחכה לקבל עבודה.
  • הdispatch overhead (launch overhead) - הרבה קריאות cudaLaunchKernel קטנות בצד ה-CPU, כל אחת ~10 מיקרו-שנייה (שיעור 7.4), שבמצטבר גדולות מזמן ה-GPU עצמו. זהו התסמין הקלאסי של הרבה kernels זעירים - המקום שבו CUDA Graphs (שיעור 5.5) נכנס.
  • חוסר חפיפה (missing overlap) בין copy ל-compute - העתקות HtoD/DtoH שרצות בטור עם ה-kernels במקום במקביל להם. עם streams נכונים ו-pinned memory (שיעור 5.3), ההעתקה של אצווה אחת אמורה לחפוף לחישוב של אצווה אחרת. אם רואים אותן זו-אחר-זו על אותו קו, איבדנו את החפיפה.
  • סריאליזציה בין streams - שני streams שהיו אמורים לרוץ במקביל אבל בפועל רצים בטור, בגלל תלות סמויה (למשל שימוש בזרם ברירת-המחדל, ה-default stream, שמסנכרן הכל).

הנה סקיצה של איך נראית בעיה בציר הזמן של nsys, ומה מספרת החפיפה החסרה:

time ->
CPU:  [launch A][launch B][launch C] ...... [   idle   ] .....
GPU:  ............[ kernel A ]  gap  [ kernel B ]  gap  [ kernel C ]
copy: [ HtoD A ] ............................. [ HtoD B ] ......
                 ^^^                ^^^
              gaps in GPU: GPU idle between kernels -> overhead-bound
              HtoD doesn't overlap compute -> missing overlap

לעומת מצב בריא, שבו ההעתקות חופפות לחישוב וה-GPU רץ ברצף:

time ->
GPU:   [ kernel A ][ kernel B ][ kernel C ]   <- dense sequence, no gaps
copy:  [ HtoD B ]  [ HtoD C ]                 <- overlaps compute of A/B

השורה התחתונה של nsys: הוא הכלי שמוצא איפה הבעיה - באיזה kernel זורם רוב הזמן, האם בכלל מדובר ב-kernel או בoverhead CPU/העתקות, והאם יש חפיפה. הוא לא אומר לכם למה ה-kernel עצמו איטי. לשם כך עוברים ל-ncu.

הצלילה לתוך kernel - Nsight Compute

Nsight Compute (ncu) הוא ההפך המשלים של nsys: במקום תמונה רחבה של המערכת לאורך זמן, הוא ניתוח מיקרו-ארכיטקטוני עמוק של kernel בודד. הוא מריץ את ה-kernel (לרוב כמה פעמים, כדי לאסוף קבוצות שונות של מוני חומרה), ומוציא דוח מפורט עם כל המדדים שלמדנו בפרק 8. בגלל שהוא אוסף עשרות מוני חומרה, ncu הרבה יותר איטי מ-nsys - הוא יכול להאט kernel בעשרות עד מאות מונים - ולכן מפעילים אותו על kernel אחד ממוקד, לא על אפליקציה שלמה.

הפקודות המרכזיות:

# a full collection of all sections - the complete deep dive
ncu --set full -o report_kernel ./myapp

# focus on a kernel by name (regex), and run it only once
ncu -k rowsum_good --launch-count 1 ./myapp

# direct text output to the terminal, roofline section only
ncu --section SpeedOfLight_RooflineChart ./myapp
  • --set full מפעיל את כל קבוצות המדדים - roofline, occupancy, memory workload, stalls, source. זו האפשרות ה"תראה לי הכל", ובהתאם גם האיטית ביותר.
  • -k <regex> מסנן לפי שם ה-kernel, כדי לא לבזבז זמן על kernels אחרים באפליקציה.
  • --launch-count N מגביל לכמה dispatches ראשונים של ה-kernel שנבחר. שימושי כשאותו kernel רץ אלפי פעמים ומספיק לנו דגימה אחת.

ncu מארגן את הפלט לקטעים (sections), כל אחד מדד או משפחת מדדים. נעבור על החשובים - וכל אחד מהם מחבר ישירות לשיעור מפרק 8.

הקטע הראשון - Roofline ו-Speed of Light

הקטע החשוב ביותר ב-ncu נקרא Speed of Light, והוא כולל את מודל ה-Roofline (שיעור 7.1) שמצויר אוטומטית עבור ה-kernel שפרופלנו. הגלוסרי מציין זאת במפורש: "כלי ה-Nsight Compute מבצע אוטומטית ניתוח roofline עבור kernels שפורפלו". ncu מודד את הביצועים בפועל (FLOP/s) ואת ה-arithmetic intensity (FLOP/byte, שיעור 7.2), ומצייר נקודה ביחס לשני הגגות - גג החישוב (האופקי) וגג הזיכרון (האלכסוני).

הקריאה של הקטע הזה עונה מיד על השאלה הגדולה: האם ה-kernel compute-bound או memory-bound. אם הנקודה יושבת מתחת לגג האלכסוני - memory-bound, יש לעבוד על coalescing ועל arithmetic intensity. אם היא מתחת לגג האופקי - compute-bound. אם היא רחוקה משני הגגות - כנראה overhead-bound או latency שלא הוסתר, וכאן ה-roofline לבדו לא יעזור (שיעור 7.1: latency לא מופיע על ה-roofline).

חשוב לזכור את הדקויות מפרק 7. ה-roofline נגזר פעם אחת לכל תת-מערכת, ו-ncu מציג roofline נפרד ל-CUDA Cores ול-Tensor Cores, כי ל-Tensor Cores יש הרבה יותר FLOPS. חובה לקרוא את ה-roofline שמתאים לנתיב החישוב שבו ה-kernel באמת משתמש. הנה מספרי ה-H100 שאיתם נעבוד, ישירות מטבלת נקודות-הרכס של שיעור 7.2:

מדד H100 SXM ערך
רוחב פס אריתמטי (BF16 Tensor Core) 989 TFLOP/s
רוחב פס זיכרון (HBM3) 3.35 TB/s
נקודת רכס (ridge point), BF16 295 FLOP/byte
שיא FP32 (לא-Tensor, CUDA Cores) ~66.9 TFLOP/s

דוגמה מספרית: kernel של SAXPY (y = a*x + y) עם arithmetic intensity של 1/6 FLOP/byte (שיעור 7.2). נקודת הרכס של ה-H100 היא 295. מכיוון ש-1/6 קטן בהרבה מ-295, הנקודה נמצאת עמוק משמאל לרכס, כלומר memory-bound - וכך אכן ncu יצייר אותה, על הגג האלכסוני. אין טעם לחפש כאן Tensor Cores; הבעיה כולה ברוחב הפס של הזיכרון. ה-roofline של ncu מאשר במדידה אחת את מה שחישבנו ביד בפרק 7.

occupancy, memory workload ו-pipe utilization

מתחת ל-roofline, ncu מפרט את המדדים המיקרו-ארכיטקטוניים שהם בדיוק תוכן פרק 8:

Occupancy (תפוסה) - הקטע מציג את ה-occupancy התיאורטי (המקסימלי שמותר ע"י המשאבים) מול ה-occupancy המושג בפועל (שיעור 8.1). ה-H100 מחזיק עד 64 warps / 2048 threads פר SM; ncu מראה איזה חלק מהם באמת resident. פער גדול בין תיאורטי למושג מרמז על bottleneck bdispatch (מעט blocks, tail effect). occupancy נמוך פירושו פחות warps להסתרת latency (חוק Little, שיעור 7.4), ו-ncu אף מקשר occupancy נמוך לגורם המגביל - registers, shared memory, או גודל ה-block. אם ה-kernel מוגבל-registers, זה מתחבר ישירות ל-register pressure (שיעור 8.5).

Memory Workload - הקטע הזה מפרט את היררכיית הזיכרון: L1/shared, L2, ו-HBM3, עם ה-hit rates ורוחבי הפס המושגים בכל מפלס. כאן מופיעים המדדים שהכרנו בשיעור 8.4: sectors per request ל-coalescing (4 = מאוחד מושלם, עד 32 = מפוזר לגמרי), ומונה ה-bank conflicts ל-shared memory. הקטע הזה הוא איך מאשרים בעיית זיכרון קונקרטית.

Pipe Utilization (ניצולת ה-pipe) - שיעור 8.2. ncu מראה איזה חלק מהשיא מושג בכל אחד מארבעת pipelines הביצוע של ה-SM, דרך משפחת המדדים:

sm__inst_executed_pipe_fma.avg.pct_of_peak_sustained_active     (CUDA Cores)
sm__inst_executed_pipe_tensor.avg.pct_of_peak_sustained_active  (Tensor Cores)
sm__inst_executed_pipe_lsu.avg.pct_of_peak_sustained_active     (Load/Store)
sm__inst_executed_pipe_adu.avg.pct_of_peak_sustained_active     (Address)

זכרו את המלכודת מ-8.2: הסיומת .avg.pct_of_peak_sustained_active מודדת אחוז מהשיא רק במחזורים שבהם ה-pipe פעיל. לכן ניצולת pipe גבוהה לא אומרת שה-pipe עבד הרבה - רק שכשהוא עבד, הוא רץ קרוב לשיא. קוראים אותה תמיד יחד עם ה-occupancy וניצולת ה-SM. הצירוף "ניצולת SM גבוהה עם ניצולת pipe נמוכה" מסמן kernel שרץ על הרבה SMs אך לא מנצל את המשאבים בתוך כל אחד - רחב אבל רדוד.

סיבות עצירה - stall reasons ולוח-התוצאות

הקטע שאולי הכי מלמד ב-ncu הוא ניתוח סיבות העצירה (stall reasons) - למה ה-warp schedulers לא הנפיקו הוראה בכל מחזור. זהו הצד ההפוך של issue efficiency (שיעור 8.2): כשיעילות ההנפקה נופלת מ-100%, ncu מפרק את הסיבה למחזורים האבודים לפי קטגוריות. הקטגוריה הדומיננטית ברוב ה-kernels היא scoreboard stall (עצירת לוח-התוצאות) משיעור 8.3.

נזכיר: ל-warp יש 6 scoreboards שהcompiler משתמש בהם למעקב אחר תלויות בין הוראות. ncu מפריד בין שני סוגי עצירה:

  • long scoreboard stall - המתנה לפעולת זיכרון שעוזבת את ה-SM (LDG/STG ל-global). זו הסיבה ששולטת בקוד memory-bound. אם ncu מראה שרוב המחזורים האבודים הם long scoreboard, אתם memory-bound, וזה מתחבר לגג האלכסוני ב-roofline ולמדד ה-sectors-per-request.
  • short scoreboard stall - המתנה להוראה בעלת latency משתנה שנשארת על ה-SM: MUFU (SFU), MMA (Tensor Core), LDS/STS (shared memory).

ncu מציג את סיווג ה-stalls האלה, וכאן חשוב לזכור את המלכודת החדה משיעור 8.3: סיווג ה-stall של ncu עלול להטעות. מכיוון שיש רק 6 scoreboards, הcompiler ממחזר אותם, ו-stall שמדווח כ-short עלול להיות באמת long (זיכרון) ולהיפך, אם שניהם השתמשו באותו scoreboard. אל תסמכו על התווית short/long בעיניים עצומות - הצליבו אותה עם ה-memory workload ועם ה-roofline. אפשר גם לראות את סימון ה-scoreboards בעצמם ב-SASS, בדיוק כפי שעשינו בשיעור 8.3:

[B------:R-:W2:-:S04]  /*00f0*/  LDG.E.SYS R0, [R2] ;   // W2: writes scoreboard 2
[B--2---:R-:W-:Y:S08]  /*0150*/  IMAD R0, R0, c[..], R5; // B--2---: blocked until 2 clears

ה-IMAD תלוי בערך שנטען ב-LDG, ולכן הוא עוצר על scoreboard 2 עד שהזיכרון חוזר - וזה ה-long scoreboard stall שיופיע בדוח ה-ncu.

מהוראה בקוד לשורה ב-SASS - Source Correlation

היכולת האחרונה, ואולי המרשימה ביותר, של ncu היא קורלציית מקור-SASS (source correlation). אחרי שזיהינו שה-kernel עוצר, השאלה הבאה היא על איזו הוראה בדיוק, ומאיזו שורה במקור היא נובעת. ncu שומר את המיפוי הזה (בזכות אותה יכולת שראינו ב-nvdisasm בשיעור 4.4 - מיפוי הוראות SASS לשורות במקור) ומאפשר לראות זו-לצד-זו את שלושת המפלסים: CUDA C++, PTX ו-SASS, כשלכל שורה מוצמדים מונים - כמה הוראות בוצעו, כמה מחזורי stall נגרמו, כמה עסקאות זיכרון.

התנאי לכך הוא לקמפל עם מידע ניפוי-שורות למכשיר:

# -lineinfo embeds a SASS<->source mapping without slowing down the code (unlike -G)
nvcc -arch=sm_90 -O3 -lineinfo mykernel.cu -o mykernel
ncu --set full --import-source yes -o rep ./mykernel

הדגל -lineinfo (להבדיל מ--G שמכבה אופטימיזציות ומאט הכל) מטמיע רק את מיפוי השורות, בלי לפגוע בביצועים. עכשיו בתצוגת ה-Source של ncu אפשר למיין את השורות לפי מספר ה-stalls, לקפוץ לשורה החמה ביותר (זו שגורמת הכי הרבה מחזורים אבודים), ולראות איזו הוראת SASS מקורה - כמעט תמיד LDG על גישה לא מאוחדת, או LDS על גישה עם bank conflict. זו הדרך לסגור את הלולאה: מהתסמין המערכתי, דרך ה-kernel האשם, אל השורה המדויקת בקוד ואל הסיבה החומרתית שלה.

Source (CUDA C++)          | SASS                    | Sampling (stalls)
---------------------------+-------------------------+-------------------
 acc += A[row*N + col];    | LDG.E R0, [R2]          | ############  73%
 s[row] = acc;             | STG.E [R4], R0          | #             2%
                                                       ^ hottest line: LDG
                                                         memory-bound on
                                                         long scoreboard

חלוקת העבודה ולולאת האופטימיזציה מקצה-לקצה

כל השיעור מתנקז לעיקרון אחד: nsys מוצא איפה, ncu מוצא למה. אלה שני כלים לשני מפלסים, ומשתמשים בהם לפי הסדר. אם תתחילו מ-ncu, תבזבזו שעות על אופטימיזציה של kernel שבכלל לא צוואר הבקבוק - אולי כל הזמן הולך לoverhead CPU או להעתקות שלא חופפות, וזאת בעיה ש-ncu, שמסתכל על kernel בודד, לעולם לא יראה.

הנה טבלת ההשוואה שכדאי לזכור:

Nsight Systems (nsys) Nsight Compute (ncu)
מפלס מערכתי - כל ציר הזמן kernel בודד
שאלה איפה מפסידים זמן? למה ה-kernel איטי?
רואה CPU, API, kernels, copies, streams roofline, occupancy, זיכרון, stalls, SASS
מהירות קל, קרוב ל-real-time כבד, האטה של פי עשרות
מוצא פערים, overhead, חוסר overlap הcompute/memory-bound, גישה לא מאוחדת, bank conflicts
מבוסס על CUPTI ישירות מוני חומרה

ולולאת העבודה המלאה, מקצה לקצה:

1. nsys profile ./app        -> timeline
2. read the timeline:
     gaps / overhead?  -> overhead-bound  -> CUDA Graphs, fusion, batching (5.5, 7.4)
     missing overlap?  -> streams + pinned memory (5.3)
     one kernel dominates? -> move on to ncu on it
3. ncu --set full -k <kernel> ./app  -> dive into the kernel
4. read the sections:
     roofline  -> compute-bound or memory-bound? (7.1, 7.2)
     occupancy -> enough warps? what's limiting? (8.1, 8.5)
     memory    -> sectors/request? bank conflicts? (8.4)
     stalls    -> long scoreboard = memory; short = SM (8.3)
     source    -> which line is hot? (4.4)
5. fix the bottleneck, go back to step 1 (the new bottleneck - 7.1)

הצעד האחרון - "חזור לצעד 1" - הוא מתודולוגיית Theory of Constraints משיעור 7.1: מצא את הצוואר, הרם אותו, וחזור על הצוואר החדש. שני ה-profilers הם הכלים שהופכים את הלולאה הזו ממדידה-על-הנייר למדידה אמיתית. נשתמש בהם בדיוק כך בפרויקטים 9.3 ו-9.4, שם נבנה kernel שלב-אחר-שלב ונמדוד כל שיפור עם nsys ו-ncu.

סיכום

  • CUPTI (interface כלי-הפרופיילינג של CUDA) היא שכבת התשתית שמתחת ל-nsys, ל-ncu ול-PyTorch Profiler; היא יודעת לפרופל CUDA C++, PTX ו-SASS, ומשימתה החתומה היא סנכרון חותמות-זמן בין ה-CPU ל-GPU כדי ליצור ציר זמן מאוחד.
  • Nsight Systems (nsys) הוא הכלי המערכתי: הוא מצייר את כל ציר הזמן - CPU, קריאות API, kernels, העתקות ו-streams - ומשמש למצוא פערים, dispatch overhead, חוסר חפיפה בין copy ל-compute, וסריאליזציה בין streams. הוא עונה על "איפה מפסידים זמן".
  • Nsight Compute (ncu) הוא הכלי העמוק ל-kernel בודד: roofline, occupancy, memory workload, pipe utilization, stall reasons וקורלציית מקור-SASS. הוא עונה על "למה ה-kernel איטי", ובגלל מוני החומרה הרבים הוא איטי בהרבה מ-nsys.
  • קטע ה-roofline של ncu מצויר אוטומטית ומכריע מיד בין compute-bound לmemory-bound; יש roofline נפרד ל-CUDA Cores ול-Tensor Cores, ועל H100 נקודת הרכס (BF16) היא 295 FLOP/byte - כל דבר עם arithmetic intensity נמוך ממנה (כמו SAXPY ב-1/6) הוא memory-bound.
  • קטעי ה-occupancy, memory ו-pipe מחברים ישירות לפרק 8: occupancy מול המקסימום (64 warps ל-SM ב-H100), sectors-per-request ל-coalescing ומונה bank conflicts ל-shared, וניצולת pipe שנמדדת רק כשה-pipe פעיל - ולכן נקראת יחד עם ה-occupancy.
  • קטע ה-stall reasons מפרק את יעילות ההנפקה: long scoreboard stall (המתנה ל-LDG/STG שעוזבים את ה-SM) שולט בקוד memory-bound, short scoreboard stall (MUFU/MMA/LDS) נשאר על ה-SM; זכרו שמיחזור 6 ה-scoreboards עלול לבלבל בין הסיווגים.
  • קורלציית מקור-SASS (עם -lineinfo, לא -G) מובילה מהתסמין אל השורה המדויקת בקוד ואל הוראת ה-SASS האשמה, על בסיס אותו מיפוי הוראות-לשורות של nvdisasm משיעור 4.4.
  • חלוקת העבודה היא הכלל המרכזי: nsys כדי לדעת איזה kernel ואם זו בכלל overhead, ואז ncu כדי לדעת למה ה-kernel איטי; הלולאה מקצה-לקצה חוזרת על הצוואר החדש, בדיוק כמתודולוגיית ה-Theory of Constraints משיעור 7.1, ומשמשת בפרויקטים 9.3-9.4.