5.4 ניטור וניהול NVML ו nvidia smi פתרון
פתרון - ניטור וניהול - NVML ו-nvidia-smi¶
הערה: כל הפלטים למטה הם ממכונת H100 SXM עם driver 550.x ו-CUDA 12.4. המספרים המדויקים - הספק, טמפרטורה, תדרים, תקרות - ישתנו אצלכם לפי הכרטיס, הקירור והעומס הרגעי (על T4 התקרה היא ~70W והתדרים נמוכים בהרבה). מה שלא ישתנה הוא המבנה והמגמה: תחת עומס ההספק עולה, נוגע בתקרה, והתדר יורד; וסיבת הוויסות SW Power Cap נדלקת. אם אין לכם הרשאת root, תרגילי הניהול (4) ייכשלו ב-Insufficient Permissions - זה תקין, והסבר הצפוי מופיע בפתרון.
פתרון תרגיל 1 - תשאול בסיסי עם query-gpu¶
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
עם noheader,nounits:
הטווח המותר של תקרת ההספק:
Current Power Limit : 700.00 W
Requested Power Limit : 700.00 W
Default Power Limit : 700.00 W
Min Power Limit : 200.00 W
Max Power Limit : 700.00 W
למה זה עבד: --query-gpu מבקש מ-NVML שדות מדויקים ומחזיר אותם כ-CSV במקום כטבלה. הגרסה noheader,nounits מסירה את הכותרת ואת היחידות ומשאירה ערכים נקיים - הרבה יותר נוח ל-awk או לפרסור בסקריפט. תקרת ברירת המחדל של H100 SXM היא 700W, והטווח המותר הוא 200-700W; ניסיון לקבוע מחוץ לטווח יידחה.
איך להכליל: --query-gpu הוא הדרך הנכונה ל-one-liner אינטראקטיבי או לניטור מהיר - הרבה יותר יציב מפרסור הטבלה. אבל לכלי production אמיתי עדיין עדיף NVML ישירות (ראו תרגיל 5). את רשימת כל השדות הזמינים מקבלים עם nvidia-smi --help-query-gpu.
פתרון תרגיל 2 - צפייה חיה בהספק ותדר עם dmon¶
טרמינל 1:
טרמינל 2:
הפלט הצפוי (מקוצר):
# gpu pwr gtemp mtemp sm mem enc dec mclk pclk rxpci txpci
# Idx W C C % % % % MHz MHz MB/s MB/s
0 79 35 - 0 0 0 0 2619 345 0 0
0 688 56 - 99 41 0 0 2619 1980 0 0
0 700 62 - 99 44 0 0 2619 1875 0 0
0 700 67 - 99 45 0 0 2619 1785 0 0
0 700 70 - 99 45 0 0 2619 1755 0 0
טרמינל 3:
וסיבות הוויסות בשיא:
למה זה עבד: ה-burn הוא kernel compute-bound (לולאת FMA צפופה, בלי גישות זיכרון בלולאה), ולכן הוא דוחף את יחידות ה-FP32 עד הסוף. בשנייה הראשונה התדר בבוסט המלא (1980 MHz, 688W), אבל תוך שניות ההספק נוגע בתקרת 700W, SW Power Cap נדלק, והכרטיס מוריד תדר ל-1755 MHz כדי להישאר בתקציב. זהו ויסות (throttling) בזמן אמת: הכרטיס לא יכול להריץ 1980 MHz בתוך 700W עבור העומס הזה, אז הוא מוריד תדר. pmon הראה שה-PID של burn הוא זה שאחראי ל-99% ניצול ה-SM.
איך להכליל: dmon היא העין הראשונה לכל חקירת ביצועים ברמת מערכת - היא מראה מיד אם אתם power-bound (התדר יורד כשההספק בתקרה) או thermal-bound (gtemp מטפס גבוה ו-HW Thermal Slowdown נדלק). pmon מייחס את הצריכה לתהליך ספציפי, קריטי כשכמה jobs חולקים כרטיס. תמיד הצליבו את dmon עם -q -d PERFORMANCE כדי לדעת למה התדר יורד.
פתרון תרגיל 3 - סקריפט pynvml שדוגם workload¶
# sampler.py
import time
import pynvml as nv
def summarize(values):
return min(values), sum(values) / len(values), max(values)
nv.nvmlInit()
try:
h = nv.nvmlDeviceGetHandleByIndex(0)
limit_w = nv.nvmlDeviceGetPowerManagementLimit(h) / 1000.0
powers, temps, utils, clocks = [], [], [], []
duration_s, dt = 20.0, 0.1
t_end = time.time() + duration_s
while time.time() < t_end:
powers.append(nv.nvmlDeviceGetPowerUsage(h) / 1000.0) # mW -> W
temps.append(nv.nvmlDeviceGetTemperature(h, nv.NVML_TEMPERATURE_GPU))
utils.append(nv.nvmlDeviceGetUtilizationRates(h).gpu)
clocks.append(nv.nvmlDeviceGetClockInfo(h, nv.NVML_CLOCK_SM))
time.sleep(dt)
p_min, p_avg, p_max = summarize(powers)
t_min, t_avg, t_max = summarize(temps)
u_min, u_avg, u_max = summarize(utils)
c_min, c_avg, c_max = summarize(clocks)
print(f"Samples: {len(powers)}")
print(f"Power W: avg {p_avg:6.1f} max {p_max:6.1f} (limit {limit_w:.0f})")
print(f"Temp C: avg {t_avg:6.1f} max {t_max:6.1f}")
print(f"Util %: avg {u_avg:6.1f}")
print(f"SM clock MHz: min {c_min} max {c_max}")
print(f"Power/limit ratio: {p_avg / limit_w:.2%}")
finally:
nv.nvmlShutdown()
הרצה במקביל ל-./burn 20:
הפלט הצפוי:
Samples: 200
Power W: avg 695.4 max 700.1 (limit 700)
Temp C: avg 64.8 max 71.0
Util %: avg 98.7
SM clock MHz: min 1755 max 1980
Power/limit ratio: 99.34%
למה זה עבד: הסקריפט דוגם את NVML ישירות דרך pynvml כל 100ms וצובר את הערכים. שמנו לב ליחידות: nvmlDeviceGetPowerUsage מחזיר מיליוואט (חילקנו ב-1000), nvmlDeviceGetClockInfo מחזיר MHz, ו-nvmlDeviceGetUtilizationRates מחזיר אובייקט עם .gpu. הפער בין תדר ה-SM המקסימלי (1980, בהתחלה כשהכרטיס קר) למינימלי (1755, אחרי חימום וגעילה בתקרה) הוא בדיוק הוויסות שראינו ב-dmon, עכשיו נמדד תוכנתית. יחס ההספק/תקרה של ~99% מאשר שה-workload דוחף כמעט צמוד לתקרה - כלומר power-bound.
איך להכליל: זהו השלד של כל collector של טלמטריה: nvmlInit -> ידית -> לולאת דגימה שצוברת -> nvmlShutdown ב-finally. מכאן קצרה הדרך ל-exporter אמיתי (תרגיל 6). כלל ברזל: תמיד סוגרים עם nvmlShutdown(), ותמיד זוכרים את היחידות (mW להספק, בתים לזיכרון) - טעות יחידות היא הבאג הנפוץ ביותר בקוד NVML.
פתרון תרגיל 4 - תקרת הספק והשפעתה על התפוקה¶
התקרה הנוכחית:
מדידה בתקרה מלאה, ואז בתקרה מופחתת (עם root):
time ./burn 5 # 700W limit
sudo nvidia-smi -pm 1 # persistence mode
sudo nvidia-smi -pl 350 # lower the limit by half
time ./burn 5 # again, now at 350W
nvidia-smi dmon -s pc -c 5 # 5 samples of power and clock
הפלט הצפוי:
# at 700W limit:
real 0m5.12s
# dmon: pwr ~700W, pclk ~1785 MHz
# after -pl 350:
Power limit for GPU 00000000:1B:00.0 was set to 350.00 W from 700.00 W.
real 0m9.40s
# gpu pwr pclk
# Idx W MHz
0 349 990
0 350 975
0 350 975
0 350 960
0 350 960
והחזרת התקרה:
אם אין לכם root, במקום המדידה תקבלו:
למה זה עבד: תקרת ההספק היא תקציב חשמלי קשיח. ב-700W הכרטיס מריץ את ה-burn בתדר ~1785 MHz וגומר בערך תוך 5 שניות. כשהורדנו את התקרה ל-350W, הכרטיס חייב להישאר מתחת ל-350W, ולכן הוריד את התדר ל-~975 MHz (בערך חצי), וה-burn לקח כמעט כפול זמן (9.4 שניות). זהו הקשר הישיר: פחות ואטים -> פחות מחזורי שעון לשנייה -> פחות FLOP לשנייה. בענן משותף ההרשאה חסומה בכוונה, ואז מקבלים Insufficient Permissions - וזה תקין, פשוט לא ניתן לשנות תקרה במכונה שלא שלכם.
איך להכליל: תקרת ההספק היא הכפתור הישיר שמחבר בין תקציב חשמלי לביצועים. במרכזי נתונים מגבילים תקרות כדי להישאר בתוך תקציב חשמל/קירור של המתלה - ומשלמים בביצועים. בדיוק בגלל זה, לפני benchmark מהימן נועלים תדר (-lgc) כדי להסיר את המשתנה הזה, כפי שנראה בפרק 9.1. ותמיד מחזירים את התקרה בסוף - הגדרה שנשכחת נשארת עד reset או reboot.
פתרון תרגיל 5 - למה NVML ולא טקסט של nvidia-smi¶
הסקריפט השביר (מפרסר טקסט):
הסקריפט היציב (pynvml):
# power_good.py
import pynvml as nv
nv.nvmlInit()
try:
h = nv.nvmlDeviceGetHandleByIndex(0)
print(int(nv.nvmlDeviceGetPowerUsage(h) / 1000)) # mW -> W
finally:
nv.nvmlShutdown()
שניהם מסכימים על 78W. אבל ההבדל מהותי:
למה זה עבד: שני הסקריפטים מגיעים לאותו מספר כי בסוף שניהם קוראים את אותו ערך מ-NVML - nvidia-smi הוא בסך הכל עטיפה מעל libnvml.so. אבל הסקריפט הטקסטואלי מסתמך על מבנה הפלט המדויק: המחרוזת W /, המיקום, רוחב העמודה. הגלוסרי אומר במפורש שפורמט הטקסט של nvidia-smi אינו מובטח כיציב - עדכון driver יכול לשנות את הטבלה (סדר שדות, רווחים, מחרוזות) ולשבור את ה-grep שלכם בלי שנגעתם בשורת קוד אחת. הסקריפט ה-pynvml מדבר עם ה-API היציב ישירות, ולכן חסין לשינויי פורמט.
איך להכליל: שלוש רמות איכות. גרוע: nvidia-smi | grep | awk (שביר, ישבר בעדכון driver, ומריץ תת-תהליך בכל דגימה). ביניים מקובל: nvidia-smi -x (XML מובנה) או --query-gpu ... --format=csv - עדיין דרך nvidia-smi, אבל מובנה. הטוב ביותר: C API של NVML, או pynvml/nvml_wrapper - ה-API שהמקור מבטיח את יציבותו, זול לקריאה, ובלי תת-תהליכים. כל כלי ניטור אמיתי (dashboard, exporter, מתזמן) חייב לפנות ל-NVML.
פתרון תרגיל 6 (בונוס) - exporter קטן וזיהוי throttling¶
# exporter.py
import time
import pynvml as nv
nv.nvmlInit()
try:
h = nv.nvmlDeviceGetHandleByIndex(0)
print("timestamp,power_w,temp_c,sm_clock,throttle")
while True:
power = nv.nvmlDeviceGetPowerUsage(h) / 1000.0
temp = nv.nvmlDeviceGetTemperature(h, nv.NVML_TEMPERATURE_GPU)
clk = nv.nvmlDeviceGetClockInfo(h, nv.NVML_CLOCK_SM)
reasons = nv.nvmlDeviceGetCurrentClocksThrottleReasons(h)
tags = []
if reasons & nv.nvmlClocksThrottleReasonSwPowerCap:
tags.append("SwPowerCap")
if reasons & nv.nvmlClocksThrottleReasonHwThermalSlowdown:
tags.append("HwThermal")
if reasons & nv.nvmlClocksThrottleReasonSwThermalSlowdown:
tags.append("SwThermal")
throttle = "|".join(tags) if tags else "none"
print(f"{time.time():.0f},{power:.1f},{temp},{clk},{throttle}")
time.sleep(1.0)
finally:
nv.nvmlShutdown()
הרצה במקביל ל-./burn 15:
timestamp,power_w,temp_c,sm_clock,throttle
1720598400,79.0,35,345,none
1720598401,688.3,56,1980,none
1720598402,700.1,62,1875,SwPowerCap
1720598403,700.0,67,1785,SwPowerCap
1720598404,700.0,70,1755,SwPowerCap
1720598405,120.5,58,1410,none
למה זה עבד: nvmlDeviceGetCurrentClocksThrottleReasons מחזיר מסכת סיביות, וכל סיבה נבדקת עם AND מול הקבוע שלה. בשנייה הראשונה של העומס אין ויסות (none, התדר בבוסט 1980), אבל ברגע שההספק נוגע ב-700W הביט SwPowerCap נדלק והתדר יורד ל-1755 - זיהוי תוכנתי מדויק של אותו סיפור שראינו ב-dmon ו--q -d PERFORMANCE. כשה-burn נגמר, ההספק צונח והוויסות נעלם (none).
איך להכליל: זהו גרעין של exporter לפרודקשן (למשל DCGM-exporter ל-Prometheus בנוי בדיוק על העיקרון הזה, מעל NVML/DCGM). היתרון של NVML כאן כפול: ה-API יציב לאורך גרסאות driver, וקריאותיו זולות ובתוך התהליך - בלי להריץ nvidia-smi כתת-תהליך בכל שנייה. והקשר לפרק 7.1: אם SwPowerCap דולק תחת עומס, ה-workload power-bound, ואז אופטימיזציה של הקוד לא תשבור את תקרת ה-700W - התדר יישאר מוגבל. מה שיעזור זה תקציב הספק/קירור גדול יותר (power ingress ו-heat egress), או הפחתת כמות העבודה. תפוקת השיא בפועל של הכרטיס אינה קבועה - היא נקבעת על ידי כמה חשמל אפשר להכניס וכמה חום אפשר להוציא, בדיוק הנושא שנעמיק בו בפרק 7.1.