לדלג לתוכן

5.4 ניטור וניהול NVML ו nvidia smi הרצאה

לאורך פרק 5 בנינו את מחסנית התוכנה שרצה בצד הhost (host, ה-CPU): ראינו את ה-CUDA Runtime API ואת libcudart.so שמממש אותו, את ה-CUDA Driver API ואת libcuda.so, ואת ה-NVIDIA GPU Driver שמדבר עם החומרה בפועל. כל אלה נמצאים על מסלול החישוב - הם משיקים kernels ומזיזים זיכרון. בשיעור הזה נעבור למסלול השני, המקביל, שנמצא באותה שכבת host software אבל אחראי על משהו אחר לגמרי: הניטור והניהול (monitoring and management) של מצב ה-GPU. כמה חשמל הכרטיס מושך ברגע זה? מה הטמפרטורה שלו? כמה מהזיכרון מוקצה? האם הוא מווסת (throttling) בגלל תקרת הספק או חום? בשיעור נכיר את NVML (NVIDIA Management Library) - הספרייה עם ה-C API היציב שכל הכלים נשענים עליו - ואת nvidia-smi, כלי שורת-הפקודה המפורסם שיושב מעליה. נלמד לתשאל את הכרטיס, לצפות בטלמטריה חיה תוך כדי ריצת kernel, לשנות הגדרות כמו תקרת הספק ומצב מתמיד, ולמה אסור לגרד (scrape) את הפלט הטקסטואלי של nvidia-smi בכלים אמיתיים. הרעיון של החומרה כמערכת עם תקציב הספק וחום, שנגענו בו בשיעור 0.2, יחזור כאן ככוח שיכול לחסום ביצועים - נושא שנעמיק בו בפרק 7.1.

ספריית הניהול - NVML

הדבר הראשון שחשוב להפריד: עד עכשיו כל ה-.so שראינו היו על מסלול החישוב. libcudart.so = ה-Runtime, libcuda.so = ה-Driver. עכשיו נכנס שחקן שלישי בשכבת ה-host software, ולו תפקיד שונה לחלוטין: NVML - NVIDIA Management Library, ספריית הניהול של NVIDIA. NVML היא, בלשון הגלוסרי, "ספרייה לניטור וניהול של מצב ה-GPU של NVIDIA". היא אינה משיקה kernels ואינה מקצה זיכרון גלובלי - היא קוראת ומשנה טלמטריה והגדרות של הכרטיס.

מבחינת מיקום במחסנית, NVML יושבת בין ה-NVIDIA GPU Driver מתחת לבין הכלים והinterfaces מעל: היא מדברת עם ה-driver כדי לקרוא ולנהל את מצב ה-GPU, וחושפת את היכולות האלה כלפי מעלה. על לינוקס היא ממומשת על ידי הקובץ libnvml.so (בדיוק כמו ש-libcudart.so מממש את ה-Runtime API), וזוהי ספריית C סגורה (closed source).

האטריביוטים המרכזיים ש-NVML חושפת - אלה שנשתמש בהם שוב ושוב:

אטריביוט מה זה מודד דוגמה על H100 SXM
power draw הספק רגעי שהכרטיס מושך, בוואטים 78 W בסרק, עד ~700 W בעומס
temperature טמפרטורת ה-GPU, במעלות צלזיוס 34 מעלות בסרק, 60-75 בעומס
allocated memory כמה מה-VRAM מוקצה כרגע 512 MiB מתוך 80 GiB
power limit תקרת ההספק הנוכחית (ניתנת לשינוי) 700 W (ברירת מחדל)
power limiting state האם הכרטיס מווסת כרגע, ולמה Active: SW Power Cap
clocks תדרי ה-SM והזיכרון 1980 MHz (SM), 2619 MHz (mem)

נקודת המפתח של השיעור כולו: NVML היא ה-API היציב לבנות עליו כלים. כל כלי ניטור אמיתי - dashboard, exporter ל-Prometheus, מתזמן שמחליט על אילו GPU להשיק job - צריך לפנות ל-C API של NVML (או לאחת מהמעטפות שלה), ולא לפרסר טקסט. נחזור לזה בהרחבה בסעיף על יציבות.

מי צורך את NVML? שלושה ערוצים: (1) nvidia-smi, כלי שורת-הפקודה, שהוא נקודת הגישה האנושית העיקרית; (2) ישירות דרך ה-C API של libnvml.so, לתוכניות C/C++; (3) דרך מעטפות שפה - pynvml ל-Python ו-nvml_wrapper ל-Rust. נראה את שלושתם.

כלי שורת-הפקודה - nvidia-smi

מעל NVML יושב nvidia-smi - NVIDIA System Management Interface, interface ניהול המערכת של NVIDIA. בלשון הגלוסרי הוא "כלי שורת-הפקודה לתשאול וניהול מצב ה-GPU שנחשף על ידי ספריות הניהול NVML". במילים אחרות: nvidia-smi הוא עטיפת CLI דקה מעל NVML. כל מספר שהוא מציג, וכל הגדרה שהוא משנה, עוברים דרך libnvml.so. הפלט שלו כה מוכר למשתמשי GPU עד שהגלוסרי מציין בחיוך שהוא נהפך ל"מם".

הרצה יבשה של nvidia-smi מדפיסה את הטבלה הקלאסית:

$ nvidia-smi
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15              Driver Version: 550.54.15      CUDA Version: 12.4      |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA H100 80GB HBM3          On  | 00000000:1B:00.0 Off   |                    0 |
| N/A   34C    P0             78W / 700W  |     512MiB / 81559MiB  |      0%      Default |
|                                         |                        |             Disabled |
+-----------------------------------------+------------------------+----------------------+

בואו נפרק את השדות החשובים, כי כל אחד מהם מגיע היישר מ-NVML:

  • Persistence-M - מצב מתמיד (persistence mode). כשהוא Off, ה-driver נטען ונפרק בכל פעם שאין תהליך CUDA פעיל, מה שמוסיף השהיית אתחול של מאות מילישניות לlaunch הראשונה. On משאיר את ה-driver טעון תמיד.
  • Temp - טמפרטורת ה-GPU. N/A תחת Fan כי כרטיסי SXM בשרת מקוררים על ידי המארז, אין להם מאוורר עצמאי.
  • Perf - מצב הביצועים (performance state), מ-P0 (הכי גבוה, בוסט מלא) עד P12 (חיסכון עמוק). תחת עומס נצפה P0.
  • Pwr:Usage/Cap - שני מספרים: ההספק הרגעי (78W) מול תקרת ההספק (700W). היחס ביניהם הוא הסיפור המרכזי של השיעור: כשהעומס דוחף את ה-Usage אל ה-Cap, הכרטיס מתחיל לווסת תדרים.
  • Memory-Usage - הזיכרון המוקצה מול הכולל (512MiB מתוך ~80GiB).
  • GPU-Util - אחוז הזמן שבו רץ kernel כלשהו על הכרטיס. שימו לב למלכוד: זהו אחוז זמן העסוק, לא אחוז ניצול יחידות החישוב. 100% פירושו "משהו רץ כל הזמן", לא "כל ה-CUDA Cores עובדים". להערכת ניצול אמיתי צריך profiler (פרק 9.1).
  • Compute M. - מצב החישוב (compute mode): Default (ריבוי תהליכים), Exclusive_Process, או Prohibited.
  • MIG M. - האם ה-Multi-Instance GPU פעיל (נגיע לזה בסעיף נפרד).

תשאול - query, dmon ו-pmon

הטבלה יפה לעין אדם, אבל לסקריפטים היא חסרת ערך. לתשאול ממוקד יש שלושה מנגנונים.

המנגנון הראשון - --query-gpu עם פורמט CSV. אנחנו מבקשים בדיוק את השדות שאנחנו רוצים, ומקבלים שורות מכונה:

nvidia-smi --query-gpu=name,power.draw,power.limit,temperature.gpu,utilization.gpu,memory.used,memory.total,clocks.sm,clocks.mem,pstate \
           --format=csv
name, power.draw [W], power.limit [W], temperature.gpu, utilization.gpu [%], memory.used [MiB], memory.total [MiB], clocks.current.sm [MHz], clocks.current.memory [MHz], pstate
NVIDIA H100 80GB HBM3, 78.11, 700.00, 34, 0, 512, 81559, 345, 2619, P0

הדגל --format=csv,noheader,nounits נותן ערכים נקיים בלי כותרת ובלי יחידות - נוח לפרסור מהיר בסקריפט. עדיין, כפי שנראה, זה לא הכלי הנכון לכלי production; זה נוח ל-one-liner אינטראקטיבי.

המנגנון השני - -q (query מלא), שמדפיס דוח מפורט של כל מה ש-NVML יודעת על הכרטיס. אפשר לצמצם לתחום מסוים עם -d. במיוחד שימושי הוא תחום הביצועים, שחושף את סיבות הוויסות (throttle reasons):

nvidia-smi -q -d PERFORMANCE
    Clocks Event Reasons
        Idle                              : Not Active
        Applications Clocks Setting       : Not Active
        SW Power Cap                      : Active
        HW Slowdown                       : Not Active
            HW Thermal Slowdown           : Not Active
            HW Power Brake Slowdown       : Not Active
        Sync Boost                        : Not Active
        SW Thermal Slowdown               : Not Active

השורה SW Power Cap : Active היא בדיוק ה-power limiting state מ-NVML: הכרטיס מוריד תדרים כרגע כי הוא נגע בתקרת ההספק. זהו האות הישיר לכך שה-workload power-bound ולא compute-bound טהור.

המנגנון השלישי - טלמטריה חיה בקצב של שנייה לשורה. nvidia-smi dmon נותן ניטור device-level (לכל הכרטיס), ו-pmon נותן ניטור per-process (לכל תהליך):

nvidia-smi dmon -s puct -d 1

הדגל -s puct בוחר קבוצות: p=power/temp, u=utilization, c=clocks, t=PCIe throughput. -d 1 = דגימה כל שנייה. הפלט תוך כדי ריצת kernel כבד:

# gpu   pwr gtemp mtemp    sm   mem   enc   dec   mclk   pclk
# Idx     W     C     C     %     %     %     %    MHz    MHz
    0    79    35     -     0     0     0     0   2619    345
    0   690    58     -    99    43     0     0   2619   1980
    0   700    64     -    99    45     0     0   2619   1830
    0   700    69     -    99    45     0     0   2619   1755

ראו את הסיפור מתגלגל: בשורה הראשונה סרק (79W, תדר SM 345 MHz). ברגע שה-kernel עולה, ההספק קופץ ל-690W ותדר ה-SM לבוסט מלא 1980 MHz. אבל בשורות הבאות ההספק נוגע ב-700W, ותדר ה-SM יורד ל-1830 ואז 1755 - זהו הוויסות בזמן אמת. ה-pmon, במקביל, מראה איזה תהליך אחראי:

nvidia-smi pmon -s um -d 1
# gpu        pid  type    sm   mem   enc   dec   command
# Idx          #   C/G     %     %     %     %   name
    0      48213     C    99    43     -     -   burn

ניהול - שינוי מצב הכרטיס

עד כה רק קראנו. אבל nvidia-smi יכול גם לשנות מצב, וכל שינוי כזה עובר דרך פונקציית set של NVML. רוב הפעולות דורשות הרשאות root ומצב מתמיד פעיל. הנה הפעולות המרכזיות:

פעולה דגל מה היא עושה
מצב מתמיד -pm 1 משאיר את ה-driver טעון, מבטל השהיית אתחול
מצב חישוב -c 2 קובע compute mode (0=Default, 2=Prohibited, 3=Exclusive_Process)
תקרת הספק -pl 500 מגדיר את תקרת ההספק ל-500W
שעונים קבועים -lgc 1500,1500 נועל את תדר ה-GPU (lock GPU clocks)
שעוני זיכרון -lmc 2619 נועל את תדר הזיכרון (lock memory clocks)
שעוני אפליקציה -ac 2619,1980 קובע application clocks (mem,graphics)
איפוס -r מבצע reset מלא לכרטיס

לדוגמה, זרימת ההכנה הטיפוסית לפני benchmark - קיבוע תנאים כדי שהמדידות יהיו יציבות:

sudo nvidia-smi -pm 1                 # persistence mode On
sudo nvidia-smi -lgc 1980,1980        # lock SM clock to 1980 MHz (no variable boost)
# ... run the benchmark with repeated measurements ...
sudo nvidia-smi -rgc                  # release the clock lock (reset GPU clocks)

למה נועלים תדרים לפני benchmark? כי אחרת התדר "צף": הכרטיס מתחיל בבוסט 1980 MHz, מתחמם, נוגע בתקרת ההספק, ומוריד תדר - ואותו kernel בדיוק ייתן GFLOP/s שונה בהרצה הראשונה מול העשירית. נעילת התדר מסירה את המשתנה הזה. נעמיק במתודולוגיית מדידה נכונה בפרק 9.1.

גוטצ'ה של תקרת ההספק. אי אפשר לקבוע תקרה שרירותית. לכל כרטיס יש טווח מותר (constraints); ל-H100 SXM זה בערך 200W עד 700W. nvidia-smi -q -d POWER מראה את Min Power Limit ו-Max Power Limit. ניסיון לקבוע מחוץ לטווח יידחה. בנוסף, על כרטיסי ענן/data-center משותפים ההרשאה לשנות תקרה לרוב חסומה - הפקודה תיכשל ב-Insufficient Permissions, וזה תקין.

פלט XML ולמה לא לגרד טקסט

הגענו לנקודה המושגית החשובה ביותר של השיעור, וזו נקודה שהגלוסרי מדגיש במפורש: פורמט הפלט הטקסטואלי של nvidia-smi אינו מובטח כיציב. הטבלה היפה שראינו בתחילת השיעור עלולה להשתנות בין גרסאות driver - סדר שדות, רוחב עמודות, מחרוזות. הציטוט המדויק מהמקור: "אף שפורמט הפלט הטקסטואלי של nvidia-smi אינו מובטח כיציב, ספריית ה-C של NVML מציעה API יציב לפיתוח כלים".

מכאן מסקנה מעשית חד-משמעית עם שלוש רמות של איכות:

   Worst  ─►  nvidia-smi | grep | awk     (parses free-form text - fragile, breaks on driver update)
   OK     ─►  nvidia-smi -x  (structured XML)  or  --query-gpu=...,--format=csv
   Best   ─►  C API of NVML  /  pynvml  /  nvml_wrapper   (the stable API, directly)

הפלט ב-XML (-x) הוא ביניים סביר - הוא לפחות מובנה, עם תגיות, אז פרסור שלו לא ישבר בגלל רוחב עמודה:

nvidia-smi -x -q | head -40
<?xml version="1.0" ?>
<nvidia_smi_log>
  <gpu id="00000000:1B:00.0">
    <product_name>NVIDIA H100 80GB HBM3</product_name>
    <gpu_power_readings>
      <power_draw>78.11 W</power_draw>
      <current_power_limit>700.00 W</current_power_limit>
    </gpu_power_readings>
    <temperature>
      <gpu_temp>34 C</gpu_temp>
    </temperature>
  </gpu>
</nvidia_smi_log>

אבל התשובה האמיתית לכלי production היא לדלג על nvidia-smi לגמרי ולפנות ל-C API של NVML. nvidia-smi הוא כלי אינטראקטיבי לבני אדם; הכלי שלכם צריך לדבר עם הספרייה שהוא עצמו מדבר איתה. זו בדיוק הסיבה שקיימות המעטפות pynvml ו-nvml_wrapper.

מעטפות שפה - pynvml ו-nvml_wrapper

ל-Python יש pynvml (חבילת PyPI בשם nvidia-ml-py), שהיא binding דק וישיר מעל ה-C API של NVML - כל פונקציה nvmlDeviceGet... נחשפת כפונקציית Python. ל-Rust יש nvml_wrapper. שתיהן נותנות את היציבות של NVML בלי לפרסר טקסט.

הנה סקריפט pynvml מלא שקורא הספק, טמפרטורה, ניצול וזיכרון - בדיוק חמשת האטריביוטים מהטבלה בתחילת השיעור:

# gpu_stats.py    install:  pip install nvidia-ml-py
import pynvml as nv

nv.nvmlInit()                                   # loads libnvml.so
try:
    h = nv.nvmlDeviceGetHandleByIndex(0)        # handle to card 0

    name = nv.nvmlDeviceGetName(h)
    if isinstance(name, bytes):                 # older versions return bytes
        name = name.decode()

    power_w  = nv.nvmlDeviceGetPowerUsage(h) / 1000.0        # comes in mW
    limit_w  = nv.nvmlDeviceGetPowerManagementLimit(h) / 1000.0
    temp_c   = nv.nvmlDeviceGetTemperature(h, nv.NVML_TEMPERATURE_GPU)
    util     = nv.nvmlDeviceGetUtilizationRates(h)           # .gpu, .memory
    mem      = nv.nvmlDeviceGetMemoryInfo(h)                 # .used, .total (in bytes)
    sm_clock = nv.nvmlDeviceGetClockInfo(h, nv.NVML_CLOCK_SM)

    print(f"Card:        {name}")
    print(f"Power:       {power_w:6.1f} W  / limit {limit_w:.0f} W")
    print(f"Temperature: {temp_c} C")
    print(f"GPU util:    {util.gpu}%   mem util: {util.memory}%")
    print(f"Memory:      {mem.used>>20} MiB / {mem.total>>20} MiB")
    print(f"SM clock:    {sm_clock} MHz")
finally:
    nv.nvmlShutdown()                           # always shut down

שימו לב לפרטים החשובים: nvmlInit() טוענת את libnvml.so, כל nvmlDeviceGet... היא הפונקציה היציבה שגם nvidia-smi קורא לה, ההספק מגיע ב-מיליוואט (חילקנו ב-1000), הזיכרון ב-בתים (הזזנו 20 סיביות ל-MiB), ובסוף nvmlShutdown() בבלוק finally כדי לשחרר את הספרייה תמיד. אם נרצה לדגום לאורך זמן תוך כדי ריצת workload - בדיוק תרגיל 2 - נעטוף את גוף הקריאות בלולאה עם time.sleep(0.1) ונצבור מינימום/מקסימום/ממוצע.

לקביעת הגדרות מ-Python (למשל תקרת הספק) יש את פונקציות ה-set המקבילות, שדורשות root:

lo, hi = nv.nvmlDeviceGetPowerManagementLimitConstraints(h)   # allowed range, in mW
nv.nvmlDeviceSetPowerManagementLimit(h, 500_000)              # 500W, in mW, requires root

MIG וצוואר-בקבוק של הספק, תדר וחום

שני נושאים לסיום, ששניהם מתחברים לביצועים.

ריבוי מופעי GPU - MIG (Multi-Instance GPU). על כרטיסים מדור Ampere ומעלה (A100, H100) אפשר לחתוך GPU יחיד לכמה מופעים (instances) מבודדים חומרתית, שכל אחד מקבל פרוסה קבועה של SMs, זיכרון ו-cache. זה מאפשר להריץ כמה workloads נפרדים על אותו כרטיס פיזי בלי שיפריעו זה לזה - שימושי בענן ובשירותי inference. את MIG מפעילים דרך nvidia-smi:

sudo nvidia-smi -mig 1                    # enables MIG mode (requires a reset)
sudo nvidia-smi mig -cgi 9 -C             # creates an instance and a compute profile
nvidia-smi -L                             # shows the instances and their UUIDs

שדה ה-MIG M. בטבלה הראשית מראה אם המצב פעיל, ו-NVML חושפת את מצב ה-MIG ואת המופעים דרך ה-API שלה. זהו כלי ניהול קלאסי - הוא לא משנה איך kernel רץ, אלא כיצד הכרטיס מחולק.

הספק, תדר וחום כצוואר-בקבוק. ראינו ב-dmon איך התדר יורד כשההספק נוגע בתקרה. זה לא ניואנס - זה יכול להיות החוסם המרכזי של הביצועים. מודל ה-Roofline (שנכיר בפרק 7) מניח תפוקת שיא קבועה, אבל בפועל תפוקת השיא עצמה דינמית: היא נקבעת על ידי כמה חשמל אפשר להכניס לשבב (power ingress) וכמה חום אפשר להוציא ממנו (heat egress). kernel שמריץ הרבה FMA צפוף יגיע במהירות לתקרת ה-700W, התדר יירד מ-1980 ל-1755 MHz, וה-TFLOPS בפועל יהיה נמוך מהשיא התיאורטי - לא כי החישוב לא יעיל, אלא כי אין מספיק תקציב הספק. בפרק 7.1 נראה איך power ingress ו-heat egress קובעים תקרה פיזית שאף אופטימיזציה של קוד לא תעקוף. NVML ו-nvidia-smi הם בדיוק העיניים שמאפשרות לזהות שזה מה שקורה: אם SW Power Cap : Active וה-pclk יורד תחת עומס, אתם power-bound, לא algorithm-bound.

סיכום

  • NVML (NVIDIA Management Library) היא ספריית הניהול והניטור של NVIDIA, ממומשת על לינוקס ב-libnvml.so, ויושבת בשכבת ה-host software בין ה-GPU driver לכלים - היא קוראת ומשנה טלמטריה, לא משיקה kernels.
  • האטריביוטים המרכזיים ש-NVML חושפת: power draw, temperature, allocated memory, power limit, ו-power limiting state (האם מווסת ולמה), וכן תדרי SM וזיכרון.
  • nvidia-smi הוא עטיפת CLI דקה מעל NVML: כל מספר שהוא מציג וכל הגדרה שהוא משנה עוברים דרך libnvml.so.
  • לתשאול: --query-gpu=...--format=csv לשדות ממוקדים, -q -d PERFORMANCE לסיבות הוויסות, ו-dmon/pmon לטלמטריה חיה של שנייה-לשורה ברמת device ורמת תהליך.
  • לניהול: -pm (מצב מתמיד), -c (מצב חישוב), -pl (תקרת הספק), -ac/-lgc/-lmc (נעילת תדרים), ו--r (איפוס); רוב הפעולות דורשות root ומצב מתמיד.
  • אסור לגרד את הפלט הטקסטואלי של nvidia-smi - הוא אינו מובטח כיציב; לכלים אמיתיים משתמשים ב-C API היציב של NVML (או ב-XML -x כפשרה מובנית).
  • מעטפות השפה pynvml (Python) ו-nvml_wrapper (Rust) נותנות גישה ישירה ל-NVML; שימו לב שהספק מגיע ב-mW וזיכרון בבתים, ותמיד קוראים nvmlShutdown().
  • MIG (Multi-Instance GPU) חותך GPU יחיד לכמה מופעים מבודדים חומרתית, מנוהל דרך nvidia-smi -mig, ומדווח בשדה MIG M. וב-NVML.
  • הספק, תדר וחום הם צוואר-בקבוק אמיתי: כשההספק נוגע בתקרה התדר יורד, תפוקת השיא בפועל נמוכה מהתיאורטית, ו-SW Power Cap : Active הוא הסימן לכך - קשר שנעמיק בו בפרק 7.1.