공장 상태를 기록하는 장부
GPU 한 장을 공장이라고 생각해 보자. 공장에는 생산 라인이 여러 개 있고, 자재들은 창고에 둔다. 작업 지시가 내려오면 라인 몇 개가 돌고, 자재가 창고와 라인 사이를 오간다. 공장의 상태는 관리실만 안다. 관리실은 그것을 장부에 실시간으로 적는다. 공장 상태가 궁금한 본사 직원들은 모두 이 장부를 확인한다.

NVML 사용법
GPU driver를 설치하면 libnvidia-ml.so라는 라이브러리가 포함되어 있다. 이것이 NVML의 정체다. 이 라이브러리에 포함된 함수를 호출하면 그 순간의 GPU 상태를 나타내는 값을 반환해준다. 예를 들어, 메모리는 nvmlDeviceGetMemoryInfo, 사용률은 nvmlDeviceGetUtilizationRates를 호출하면 된다.
다양한 종류의 클라이언트를 통해 NVML을 조작할 수 있다. Python은 nvidia-ml-py 패키지를 설치하고 import pynvml를 통해 NVML을 사용한다. GPU driver가 설치된 환경에서는 nvidia-smi 명령어를 통해 NVML을 사용할 수 있다. 두 방식은 같은 곳을 바라보지만, 값의 단위는 조금씩 다르다. 메모리는 bytes, 전력은 mW다. 단위가 nvidia-smi와 다르다. nvidia-smi는 같은 값을 MiB와 W로 보여준다.
nvidia-ml-py 예시
# pip install nvidia-ml-py
import pynvml as nv
nv.nvmlInit()
try:
for i in range(nv.nvmlDeviceGetCount()):
h = nv.nvmlDeviceGetHandleByIndex(i)
mem = nv.nvmlDeviceGetMemoryInfo(h)
util = nv.nvmlDeviceGetUtilizationRates(h)
try:
power_w = nv.nvmlDeviceGetPowerUsage(h) / 1000
except nv.NVMLError as e:
power_w = f"N/A ({e})"
print(i, mem.used // 1024**2, "MiB", util.gpu, "%", power_w, "W")
finally:
nv.nvmlShutdown()nvidia-smi 명령어는 다양한 옵션을 통해 출력 형식이나 대상 등을 의도에 맞게 조정할 수 있다. 주로 많이 쓰이는 것들은 다음과 같다.
| 의도 | 옵션 |
|---|---|
| 카드 측정 항목 이름 조회 | --help-query-gpu |
| 특정 카드 측정 항목만 출력 | --query-gpu=이름1,이름2 |
| 프로세스 측정 항목 이름 조회 | --help-query-compute-apps |
| 특정 프로세스 측정 항목만 출력 | --query-compute-apps=pid,used_gpu_memory |
| 헤더와 단위 빼고 숫자만 출력 | --format=noheader,nounits |
| 반복 호출 | -l 1 (초), -lms 500 (밀리초) |
| 특정 카드만 출력 | -i 0 또는 -i <UUID> |
일정 기간 기록을 쌓아두고 추이를 분석하거나 집계하는 것은 호출하는 쪽 클라이언트의 책임이다. 대표적으로 DCGM은 NVML을 활용해 GPU 지표를 주기적으로 수집하고 보관하여 통계와 상태 판단에 활용한다.
주요 측정 항목

| 구분 | 이름 |
|---|---|
| 메모리 사용량 | memory.used, memory.reserved, memory.free, memory.total |
| GPU 사용률 | utilization.gpu, utilization.memory |
| 전력, 온도, 클록 | power.draw, temperature.gpu, clocks.sm, clocks_event_reasons.active |
| Xid, ECC 오류 | ecc.errors.*, remapped_rows.* |
| CPU와 통신 | pcie.link.gen.current, pcie.link.width.current |
| GPU 사이 통신 | nvidia-smi nvlink -s, nvidia-smi topo -m (--query-gpu로 확인 불가) |
메모리 사용량
memory.used는 프로세스들이 가져간 GPU 메모리 양이다. 공장으로 치면 창고에서 작업 팀들이 자리를 맡아 둔 넓이다. GPU가 쉬고 있으면 memory.used가 거의 0에 수렴한다. GPU 사용 여부와 상관없이 driver가 자기 몫으로 떼어 둔 메모리는 따로 잡혀 있다. memory.reserved 값으로 확인할 수 있다. 여기에 남은 양 memory.free까지 더하면 카드의 전체 메모리 용량 memory.total이 된다.
$ nvidia-smi --query-gpu=memory.used,memory.reserved,memory.free,memory.total -i 0,1
memory.used [MiB], memory.reserved [MiB], memory.free [MiB], memory.total [MiB]
0 MiB, 616 MiB, 143156 MiB, 143771 MiB
114726 MiB, 616 MiB, 28430 MiB, 143771 MiBmemory.used는 프로그램이 실제로 쓰고 있는 양이 아니라 맡아 둔 양이다. memory.used가 가득 차 있어도 GPU가 놀고 있는 경우도 있다. 예를 들어, CUDA를 쓰는 프로세스는 아무것도 올리지 않아도 실행 환경(CUDA context)만으로 메모리를 차지한다. PyTorch는 다 쓴 메모리를 바로 돌려주지 않고 다음에 다시 쓰려고 caching allocator로 붙잡아 두며, JAX는 시작하자마자 메모리의 대부분을 미리 잡는다.
어느 프로세스가 얼마나 맡아 두었는지는 nvidia-smi --query-compute-apps=pid,used_gpu_memory 명령어로 확인할 수 있다. 컨테이너 안에서 호출하면 다른 프로세스가 보이지 않아 빈 목록이 나오기도 한다.
$ nvidia-smi --query-compute-apps=pid,used_gpu_memory
pid, used_gpu_memory [MiB]
2779540, 490 MiB
3651615, 490 MiB
3671411, 490 MiBGPU 사용률
utilization.gpu은 최근 단위 시간 동안 커널이 돌고 있던 시간의 비율이다. 단위 시간은 GPU에 따라 1초에서 1/6초 사이로 기준을 잡는다. 이 값에서 SM을 몇 개 돌렸는지는 고려하지 않는다. 실제로 커널 수행을 위해 1개의 SM만 연산에 참여했다고 하더라도, "어쨌든 사용했다"라고 퉁쳐버린다.

SM을 얼마나 썼는지를 확인하려면 GPU 안의 하드웨어 카운터를 직접 읽어야 한다. 일부 카드들은 GPM이라는 기능으로 이 값을 제공한다. nvidia-smi dmon --gpm-metrics 2 명령어로 확인할 수 있다.
$ nvidia-smi dmon --gpm-metrics 2
# gpu pwr gtemp mtemp sm mem enc dec jpg ofa mclk pclk smutil
# Idx W C C % % % % % % MHz MHz GPM:%
0 21 33 - 0 0 0 0 0 0 405 210 -
0 21 33 - 0 0 0 0 0 0 405 210 -
0 21 33 - 0 0 0 0 0 0 405 210 -이름이 가장 헷갈리는 항목은 utilization.memory다. 메모리를 얼마나 채웠는지를 의미하는 것이 아니다. 최근 단위 시간 동안 메모리를 읽거나 쓴 시간의 비율이다. 공장으로 치면 창고 문이 열린 상태로 자재를 운반한 시간이다. 창고가 꽉 차 있더라도 아무도 자재를 꺼내지 않으면 0%다.
$ nvidia-smi --query-gpu=utilization.gpu,utilization.memory -i 2,3,4
utilization.gpu [%], utilization.memory [%]
0 %, 0 %
100 %, 27 %
74 %, 10 %전력, 온도, 클록
GPU의 물리적인 상태도 전력, 온도, 클록 등의 값을 조회하여 확인할 수 있다. 관련 값들 중에서 눈여겨볼 것은 clocks_event_reasons.active다. 클록이 최대치보다 낮아졌을 때 원인을 비트로 알려 준다. 비트는 여러 개가 동시에 켜질 수 있다. 예를 들어, 0x1은 할 일 없이 쉬는 중이라는 뜻, 0x4는 전력 상한에 걸렸다는 뜻, 0x20과 0x40은 온도 때문에 감속 중이라는 뜻이다. 0x20은 온도가 운용 상한을 넘어 드라이버가 클록을 조금씩 낮춘 것이고, 0x40은 온도가 더 위험한 수준이라 하드웨어가 직접 클록을 절반 이하로 끌어내린 것이다.
$ nvidia-smi --query-gpu=power.draw,temperature.gpu,clocks.sm,clocks_event_reasons.active -i 1,9,10
power.draw [W], temperature.gpu, clocks.current.sm [MHz], clocks_event_reasons.active
117.94 W, 35, 1980 MHz, 0x0000000000000000
21.52 W, 27, 300 MHz, 0x0000000000000004
4.75 W, 30, 300 MHz, 0x0000000000000001Xid, ECC 오류
Xid는 GPU에 문제가 생길 때 드라이버가 남기는 오류 번호이다. Xid 자체는 측정 항목이 아니다. 커널 로그에서 확인하거나 NVML 이벤트로 받아야 한다. 커널 로그로 확인하는 경우 dmesg는 재부팅하면 비워지므로, 지난 부팅 기록은 디스크에 남은 journal에서 찾아야 한다. -b -1 은 직전 부팅 기록을 확인할 수 있는 옵션이다. 커널 로그에는 GPU PCI 주소, Xid 번호, 오류를 낸 프로세스 정보가 출력된다. Xid 43은 GPU가 해당 프로그램의 작업을 멈췄다는 뜻이고, 31은 MMU Fault, 즉, 프로그램이 GPU 메모리의 잘못된 주소를 읽으려고 시도했다는 뜻이다. 둘 다 카드 고장이 아니라 프로세스 내부에서 발생한 오류이다.
$ sudo journalctl -k -b -1 -o short-iso | grep -i xid
2026-07-14T21:15:06+00:00 <node> kernel: NVRM: Xid (PCI:0000:00:10): 43, pid=3268740, name=ray::train_tria, channel 0x0000000b
2026-07-21T03:23:10+00:00 <node> kernel: NVRM: Xid (PCI:0000:00:10): 31, pid=1412025, name=python3, channel 0x0000000f, intr 00000000. MMU Fault: ENGINE GRAPHICS GPC5 GPCCLIENT_GCC faulted @ 0x74f4_f3214000. Fault is of type FAULT_PDE ACCESS_TYPE_VIRT_READ코드를 통해 NVML 이벤트를 리스닝하면 프로세스 내부에서 Xid를 직접 받을 수 있다.
NVML 이벤트 예시
import pynvml as nv
nv.nvmlInit()
try:
events = nv.nvmlEventSetCreate()
for i in range(nv.nvmlDeviceGetCount()):
h = nv.nvmlDeviceGetHandleByIndex(i)
nv.nvmlDeviceRegisterEvents(h, nv.nvmlEventTypeXidCriticalError, events)
while True:
try:
e = nv.nvmlEventSetWait_v2(events, 3000) # 밀리초
except nv.NVMLError_Timeout:
continue
print("GPU", nv.nvmlDeviceGetIndex(e.device), "Xid", e.eventData)
finally:
nv.nvmlShutdown()ECC는 오류 정정 코드를 뜻한다. 메모리에 데이터를 쓸 때 검사용 비트를 함께 저장해 두고, 읽을 때 데이터가 바뀌었는지 확인한다. ECC 관련 값들은 데이터센터용 카드 등 일부 GPU에서만 확인할 수 있다. 그 외 GeForce 계열 카드같은 것들은 N/A로 나온다.
ECC 오류는 고칠 수 있었느냐에 따라 둘로 나뉜다. corrected는 비트 하나가 뒤집힌 것을 ECC가 찾아내 바로잡은 경우이다. 프로세스가 올바른 값을 읽을 수 있기 때문에 아무 일도 일어나지 않는다. 다만, 이 오류가 꾸준히 늘어난다면 메모리가 약해지고 있다는 신호다. uncorrected는 여러 비트가 뒤집혀 ECC가 오류를 찾기는 했지만 고치지 못한 경우이다. 데이터가 깨졌으므로 driver는 해당 메모리를 사용하던 프로세스를 중단시킨다. 학습 작업이 이유 없이 죽었다면 가장 먼저 확인해 봐야하는 값이다.
corrected와 uncorrected는 값을 측정한 시점에 따라 다시 크게 두 가지로 나뉜다. volatile은 드라이버가 올라온 뒤로 센 값이고, aggregate는 카드가 생긴 뒤로 센 값이다.
$ nvidia-smi --query-gpu=ecc.mode.current,ecc.errors.corrected.volatile.total,ecc.errors.uncorrected.volatile.total,ecc.errors.uncorrected.aggregate.total -i 0,1,9,10
ecc.mode.current, ecc.errors.corrected.volatile.total, ecc.errors.uncorrected.volatile.total, ecc.errors.uncorrected.aggregate.total
Enabled, 0, 0, 0
Enabled, 0, 0, 0
[N/A], [N/A], [N/A], [N/A]
[N/A], [N/A], [N/A], [N/A]remapped_rows는 메모리의 고장 난 줄을 예비 줄로 바꿔 끼운 기록을 의미한다. remapped_rows.pending이 Yes인 상태에서는 GPU를 리셋해야 교체가 적용된다. remapped_rows.failure가 Yes인 경우 더 바꿔 끼울 예비 줄이 없다는 뜻이다. 카드 교체를 검토해야 하는 신호다.
$ nvidia-smi --query-remapped-rows=gpu_bus_id,remapped_rows.correctable,remapped_rows.uncorrectable,remapped_rows.pending,remapped_rows.failure -i 0,1,9,10
gpu_bus_id, remapped_rows.correctable, remapped_rows.uncorrectable, remapped_rows.pending, remapped_rows.failure
00000000:1B:00.0, 0, 0, No, No
00000000:43:00.0, 0, 0, No, No
00000000:00:10.0, [N/A], [N/A], [N/A], [N/A]
00000000:00:11.0, [N/A], [N/A], [N/A], [N/A]-q -d 옵션을 통해 조금 더 가독성 좋은 형식으로 확인할 수 있다.
ECC 오류 확인 예시
$ nvidia-smi -q -d ECC -i 0
==============NVSMI LOG==============
Timestamp : Thu Oct 29 03:19:56 2026
Driver Version : 580.126.20
CUDA Version : Not Found
Attached GPUs : 8
GPU 00000000:1B:00.0
ECC Mode
Current : Enabled
Pending : Enabled
ECC Errors
Volatile
SRAM Correctable : 0
SRAM Uncorrectable Parity : 0
SRAM Uncorrectable SEC-DED : 0
DRAM Correctable : 0
DRAM Uncorrectable : 0
Aggregate
SRAM Correctable : 0
SRAM Uncorrectable Parity : 0
SRAM Uncorrectable SEC-DED : 0
DRAM Correctable : 0
DRAM Uncorrectable : 0
SRAM Threshold Exceeded : No
Aggregate Uncorrectable SRAM Sources
SRAM L2 : 0
SRAM SM : 0
SRAM Microcontroller : 0
SRAM PCIE : 0
SRAM Other : 0
Channel Repair Pending : No
TPC Repair Pending : No$ nvidia-smi -q -d ECC -i 0
==============NVSMI LOG==============
Timestamp : Tue Nov 3 03:57:51 2026
Driver Version : 580.82.07
CUDA Version : Not Found
Attached GPUs : 2
GPU 00000000:00:10.0
ECC Mode
Current : N/A
Pending : N/A
ECC Errors
Volatile
SRAM Correctable : N/A
SRAM Uncorrectable : N/A
DRAM Correctable : N/A
DRAM Uncorrectable : N/A
Aggregate
SRAM Correctable : N/A
SRAM Uncorrectable : N/A
DRAM Correctable : N/A
DRAM Uncorrectable : N/ARemapped rows 확인 예시
$ nvidia-smi -q -d ROW_REMAPPER -i 0
==============NVSMI LOG==============
Timestamp : Thu Oct 29 03:20:26 2026
Driver Version : 580.126.20
CUDA Version : Not Found
Attached GPUs : 8
GPU 00000000:1B:00.0
Remapped Rows
Correctable Error : 0
Uncorrectable Error : 0
Pending : No
Remapping Failure Occurred : No
Bank Remap Availability Histogram
Max : 3072 bank(s)
High : 0 bank(s)
Partial : 0 bank(s)
Low : 0 bank(s)
None : 0 bank(s)$ nvidia-smi -q -d ROW_REMAPPER -i 0
==============NVSMI LOG==============
Timestamp : Tue Nov 3 03:57:28 2026
Driver Version : 580.82.07
CUDA Version : Not Found
Attached GPUs : 2
GPU 00000000:00:10.0
Remapped Rows : N/ACPU와 통신
GPU는 CPU와 PCIe로 통신한다. 학습 데이터를 GPU 메모리로 올리고 결과를 내려받는 길이다. 이 길의 성능은 gen과 width로 표현한다. gen은 길 하나의 속도, width는 길이 몇 갈래인지를 의미한다. 유휴 상태에서는 전력을 아끼려고 gen을 낮춰 두기 때문에, current와 max 값을 모두 확인해야 한다.
$ nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.gen.max,pcie.link.width.current,pcie.link.width.max -i 0,9
pcie.link.gen.current, pcie.link.gen.max, pcie.link.width.current, pcie.link.width.max
5, 5, 16, 16
1, 3, 16, 16실시간 전송량도 dmon을 통해 확인할 수 있다. 방향은 GPU 기준이다. RX는 GPU가 받는 쪽이라 학습 데이터를 올릴 때 커지고, TX는 결과를 다시 CPU로 내려받을 때 커진다. 값은 20ms 동안 센 바이트를 초당으로 환산한 것이다. 순간값이라 튀기 쉽다. 한 번 조회된 값으로 판단하지 말고 전체적인 흐름 또는 집계된 값을 봐야 한다.
$ nvidia-smi dmon -s t -i 2
# gpu rxpci txpci
# Idx MB/s MB/s
2 54 21
2 62 36
2 59 21Python 코드를 통해 프로세스 내부에서도 실시간 전송량을 확인할 수 있다.
PCIe 처리량 코드 예시
import pynvml
pynvml.nvmlInit()
h = pynvml.nvmlDeviceGetHandleByIndex(0)
rx = pynvml.nvmlDeviceGetPcieThroughput(h, pynvml.NVML_PCIE_UTIL_RX_BYTES) # CPU -> GPU (KB/s)
tx = pynvml.nvmlDeviceGetPcieThroughput(h, pynvml.NVML_PCIE_UTIL_TX_BYTES) # GPU -> CPU (KB/s)
print(rx, tx)GPU 사이 통신
GPU들은 NVLink를 통해 CPU를 거치지 않고 서로 직접 통신할 수 있다. 여러 GPU로 학습할 때 서로의 계산 결과를 주고받는 용도로 사용된다. NVLink는 여러 개의 링크로 이루어진다. NVML를 통해 각 링크의 상태를 확인할 수 있다. NVLink가 설정되어 있지 않다면 링크가 조회되지 않는다.
nvlink 예시
$ nvidia-smi nvlink -s -i 0
GPU 0: NVIDIA H200 (UUID: GPU-84b6a60d-...)
Link 0: 26.562 GB/s
Link 1: 26.562 GB/s
Link 2: 26.562 GB/s
Link 3: 26.562 GB/s
Link 4: 26.562 GB/s
Link 5: 26.562 GB/s
Link 6: 26.562 GB/s
Link 7: 26.562 GB/s
Link 8: 26.562 GB/s
Link 9: 26.562 GB/s
Link 10: 26.562 GB/s
Link 11: 26.562 GB/s
Link 12: 26.562 GB/s
Link 13: 26.562 GB/s
Link 14: 26.562 GB/s
Link 15: 26.562 GB/s
Link 16: 26.562 GB/s
Link 17: 26.562 GB/s$ nvidia-smi nvlink -s -i 9
GPU 9: NVIDIA GeForce RTX 2080 Ti (UUID: GPU-ecbce321...)
NVML: Unable to retrieve NVLink information as all links are inActiveTopology 기능을 사용하면 각 GPU들의 연결 구성을 한눈에 확인할 수 있다.
topo 예시
$ nvidia-smi topo -m
GPU0 GPU1 GPU2 GPU3 GPU4 ...
GPU0 X NV18 NV18 NV18 NV18 ...
GPU1 NV18 X NV18 NV18 NV18 ...
GPU2 NV18 NV18 X NV18 NV18 ...
GPU3 NV18 NV18 NV18 X NV18 ...
GPU4 NV18 NV18 NV18 NV18 X ...
...
Legend:
X = Self
SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
PIX = Connection traversing at most a single PCIe bridge
NV# = Connection traversing a bonded set of # NVLinks
NIC Legend:
NIC0: mlx5_0
NIC1: mlx5_1
NIC2: mlx5_2
NIC3: mlx5_3
NIC4: mlx5_4
NIC5: mlx5_5
NIC6: mlx5_6
NIC7: mlx5_7
NIC8: mlx5_8
NIC9: mlx5_9
NIC10: mlx5_10
NIC11: mlx5_11