Split View: DCGM Exporter — GPU 이용률은 당신이 생각하는 그것이 아니다
DCGM Exporter — GPU 이용률은 당신이 생각하는 그것이 아니다
- 들어가며 — DCGM과 exporter의 관계
- 기본으로 켜져 있는 메트릭
- GPU 이용률이라는 말의 함정
- 그럼 무엇을 같이 봐야 하나
- 커스텀 메트릭 설정하기
- 파드와 컨테이너 라벨 붙이기
- 마치며 — 이름을 믿지 말고 정의를 읽는다
- 직접 해보기
- 시리즈
- 참고 자료
들어가며 — DCGM과 exporter의 관계
GPU 대시보드를 만들라는 요청을 받으면 대부분 같은 곳에서 시작합니다. DCGM Exporter를 띄우고, 기본 대시보드를 가져오고, 큰 게이지에 GPU 이용률을 꽂습니다. 그 게이지가 95퍼센트를 가리키면 모두가 만족합니다. 문제는 그 숫자가 사람들이 기대하는 것을 재지 않는다는 점입니다.
구조부터 정리하면 이렇습니다. GPU에서 값을 실제로 읽는 것은 DCGM이고, exporter는 그 값을 프로메테우스 노출 형식으로 옮기는 얇은 층입니다. GPU Operator의 차트 기본값에서 dcgm.enabled가 거짓인 이유도 여기 있습니다. 주석이 그대로 말하듯 exporter에 내장된 nv-hostengine을 쓰기 때문에 별도 DCGM 데몬셋이 필요 없습니다.
메트릭 이름과 설정은 2026-08-12에 공식 문서·저장소에서 확인했습니다. 버전에 따라 다를 수 있으니 사용 중인 버전에서 다시 확인하세요.
기본으로 켜져 있는 메트릭
무엇이 노출되는지는 저장소의 etc/default-counters.csv가 결정합니다. 이 주제의 유일한 권위 있는 출처이며, 형식은 DCGM 필드 이름, 프로메테우스 메트릭 타입, 도움말 문자열의 세 칸입니다. 먼저 실제 출력을 봅니다.
kubectl -n gpu-operator port-forward svc/nvidia-dcgm-exporter 9400:9400
curl -s localhost:9400/metrics | grep -E '^DCGM_FI_(DEV|PROF)' | head -20
저장소 README가 보여 주는 출력 형태는 이렇습니다.
DCGM_FI_DEV_SM_CLOCK{gpu="0", UUID="GPU-604ac76c-d9cf-fef3-62e9-d92044ab6e52",container="",namespace="",pod=""} 139
DCGM_FI_DEV_MEM_CLOCK{gpu="0", UUID="GPU-604ac76c-d9cf-fef3-62e9-d92044ab6e52",container="",namespace="",pod=""} 405
기본 CSV에서 주석 처리되지 않은, 즉 실제로 켜져 있는 필드를 성격별로 묶으면 다음과 같습니다. 괄호 안은 CSV의 도움말 문자열입니다.
- 클럭:
DCGM_FI_DEV_SM_CLOCK(SM clock frequency (in MHz)),DCGM_FI_DEV_MEM_CLOCK - 온도와 전력:
DCGM_FI_DEV_GPU_TEMP,DCGM_FI_DEV_MEMORY_TEMP,DCGM_FI_DEV_POWER_USAGE(Power draw (in W)),DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION(counter, since boot, in mJ) - 메모리:
DCGM_FI_DEV_FB_USED,DCGM_FI_DEV_FB_FREE,DCGM_FI_DEV_FB_RESERVED— 모두 MiB 단위 - 이용률:
DCGM_FI_DEV_GPU_UTIL,DCGM_FI_DEV_MEM_COPY_UTIL,DCGM_FI_DEV_ENC_UTIL,DCGM_FI_DEV_DEC_UTIL - 오류:
DCGM_FI_DEV_XID_ERRORS(Value of the last XID error encountered),DCGM_FI_DEV_PCIE_REPLAY_COUNTER - 행 리맵:
DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS,DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS,DCGM_FI_DEV_ROW_REMAP_FAILURE - 프로파일링:
DCGM_FI_PROF_GR_ENGINE_ACTIVE,DCGM_FI_PROF_PIPE_TENSOR_ACTIVE,DCGM_FI_PROF_DRAM_ACTIVE,DCGM_FI_PROF_PCIE_TX_BYTES,DCGM_FI_PROF_PCIE_RX_BYTES
여기서 이미 중요한 사실이 하나 나옵니다. 이 목록에 DCGM_FI_PROF_SM_ACTIVE와 DCGM_FI_PROF_SM_OCCUPANCY가 없습니다. 두 필드는 CSV에 존재하지만 주석 처리되어 있습니다. 즉 기본 설정만으로는 지금 이야기할 진짜 지표들이 아예 수집되지 않습니다.
GPU 이용률이라는 말의 함정
DCGM_FI_DEV_GPU_UTIL의 CSV 도움말은 한 줄입니다. GPU utilization (in %). 이 이름과 이 설명만 보면 GPU 성능의 몇 퍼센트를 쓰고 있다는 뜻으로 읽힙니다. 그런데 CSV의 섹션 주석은 조용히 다른 힌트를 줍니다. 표본 구간이 제품마다 다르다고 적혀 있습니다.
같은 이름의 값을 NVIDIA의 NVML API 문서는 훨씬 노골적으로 정의합니다. nvmlUtilization_t의 gpu 필드는 지난 표본 구간 동안 GPU에서 하나 이상의 커널이 실행되고 있던 시간의 비율입니다. 문장을 그대로 뜯어보면 무엇을 재지 않는지가 보입니다. 커널이 몇 개인지, SM을 몇 개나 쓰는지, 텐서 코어를 건드리는지, 메모리 대역폭을 얼마나 쓰는지는 전혀 들어 있지 않습니다. 재는 것은 오직 시간입니다. 커널이 하나라도 얹혀 있었느냐 아니냐입니다.
그래서 이런 일이 벌어집니다. 블록 하나짜리 커널을 끊임없이 돌리는 파드가 있다고 합시다. SM이 108개인 카드에서 그 커널은 SM 하나만 씁니다. 나머지 107개는 놀고 있습니다. 그런데 커널은 계속 실행 중이므로 이용률 게이지는 100퍼센트를 가리킵니다. 대시보드는 초록색이고, 카드는 사실상 1퍼센트도 안 쓰고 있습니다.
DCGM 문서가 이 간극을 메우는 지표로 제시하는 것이 DCGM_FI_PROF_SM_ACTIVE입니다. 필드 아이디는 1002이고, 정의는 "적어도 하나의 워프가 멀티프로세서에서 활성이었던 시간의 비율을 전체 멀티프로세서에 대해 평균한 값"입니다. 같은 문서가 붙여 놓은 계산 예시가 이 지표의 성격을 정확히 드러냅니다. GPU에 SM이 N개일 때, N개의 블록을 쓰는 커널이 구간 전체에 걸쳐 돌면 활동률은 1이 됩니다. N/5개의 블록을 쓰는 커널이 구간 전체에 걸쳐 돌면 0.2입니다. 그리고 N개의 블록을 쓰지만 구간의 5분의 1 동안만 돌고 나머지는 SM이 놀았다면 그 값도 0.2입니다.
앞의 예로 돌아가면 답이 나옵니다. 이용률은 100퍼센트인데 DCGM_FI_PROF_SM_ACTIVE는 0.01 근처입니다. 두 숫자가 같은 상황을 설명하고 있고, 진실을 말하는 쪽은 후자입니다.
그럼 무엇을 같이 봐야 하나
DCGM 문서는 DCGM_FI_PROF_SM_ACTIVE에 해석 기준까지 붙여 두었습니다. 0.8 이상은 GPU를 효과적으로 쓰기 위한 필요조건이지 충분조건은 아니며, 0.5 미만이면 비효율적인 사용일 가능성이 높다는 것입니다. 필요조건이라는 표현이 핵심입니다. SM이 바쁘다는 것과 유용한 일을 하고 있다는 것은 다릅니다.
그래서 최소 네 개를 함께 봐야 합니다.
DCGM_FI_PROF_SM_ACTIVE (1002)로 얼마나 넓게 퍼져 있는지를 봅니다. DCGM_FI_PROF_SM_OCCUPANCY (1003)는 멀티프로세서에 상주하는 워프 수를 그 멀티프로세서가 지원하는 최대 동시 워프 수로 나눈 비율로, SM 안이 얼마나 채워져 있는지를 말합니다. 다만 문서가 직접 경고하듯 점유율이 높다고 반드시 GPU를 더 잘 쓰는 것은 아닙니다. 메모리 대역폭 제한 워크로드에서는 그렇지만 연산 제한 워크로드에서는 그렇지 않습니다.
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE (1004)는 텐서 파이프가 활성이었던 사이클의 비율로 텐서 코어가 실제로 도는지를, DCGM_FI_PROF_DRAM_ACTIVE (1005)는 장치 메모리로 데이터가 오가던 사이클의 비율로 메모리가 병목인지를 말합니다. LLM 추론 서버에서 앞이 바닥이면 행렬 연산이 텐서 코어로 가지 않는다는 뜻이고, 디코드 위주 구간에서 뒤가 높고 앞이 낮은 것은 정상적인 모습입니다.
쿼리로 쓰면 이런 모양이 됩니다. 프로메테우스 표현식은 라벨 셀렉터에 중괄호를 쓰기 때문에 반드시 코드 블록 안에 둡니다.
# 노드별 평균 SM 활동률
avg by (hostname) (DCGM_FI_PROF_SM_ACTIVE)
# 이용률은 높은데 SM은 비어 있는 GPU 찾기
DCGM_FI_DEV_GPU_UTIL > 90 and on(gpu, UUID) DCGM_FI_PROF_SM_ACTIVE < 0.3
# 프레임버퍼 사용 비율
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
# 그라파나 변수는 달러 기호를 쓰므로 반드시 코드 블록 안에서만
avg by (gpu) (rate(DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION{hostname=~"$node"}[$__rate_interval]))
한 가지 더 있습니다. 프로파일링 메트릭은 하드웨어 제약으로 특정 그룹만 함께 읽을 수 있어 DCGM이 통계적 표본 추출로 자동 다중화합니다. 그래서 문서는 수집 주기를 지나치게 짧게 잡으면 메트릭을 묶는 과정에서 0이 반환된다고 경고합니다. 대시보드에 이유 없는 0이 찍힌다면 이 이야기를 떠올릴 필요가 있습니다.
커스텀 메트릭 설정하기
기본 CSV를 바꾸는 방법은 세 가지입니다.
첫째, CSV 파일을 직접 지정합니다. -f 또는 --collectors 플래그이고, 환경 변수는 DCGM_EXPORTER_COLLECTORS, 기본값은 /etc/dcgm-exporter/default-counters.csv입니다.
둘째, YAML 설정 파일을 씁니다. --config-file 또는 DCGM_EXPORTER_CONFIG_FILE입니다. README가 보여 주는 구조는 이렇습니다.
version: 1
metrics:
file: /etc/dcgm-exporter/default-counters.csv
collection:
interval: 30s
인라인으로 필드를 나열할 수도 있으며 이때 각 항목의 키는 name, prometheusType, help입니다. 쿠버네티스에서는 커스텀 메트릭 ConfigMap을 파일로 마운트하고 metrics.file을 그 경로로 지정하라고 README가 안내합니다. YAML은 시작 시점에만 읽히므로 수정하면 재시작이 필요합니다.
셋째, GPU Operator를 쓴다면 차트 값으로 지정합니다. dcgmExporter.config 블록에 name, create, data가 있고, 차트 주석이 중요한 제약을 명시합니다. 기존 ConfigMap을 가리키는 경우 그 ConfigMap은 릴리스와 같은 네임스페이스에 있어야 하며, 메트릭 목록은 dcgm-metrics.csv라는 키 아래에 있어야 합니다.
# helm values 예시
dcgmExporter:
config:
name: custom-dcgm-exporter-metrics
create: true
data: |-
DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %).
DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB).
DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned.
DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM.
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active.
DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data.
수집 주기는 -c 또는 --collect-interval, 환경 변수는 DCGM_EXPORTER_INTERVAL, 단위는 밀리초로 기본값 30000입니다. 리스닝 주소는 -a 또는 DCGM_EXPORTER_LISTEN이고 기본값은 9400 포트입니다. 차트는 ServiceMonitor를 기본으로 켜며 스크레이프 간격이 15초인데, 수집 주기 30초와 어긋나 같은 값을 두 번 긁는 구간이 생깁니다.
파드와 컨테이너 라벨 붙이기
기본 상태의 시계열에는 GPU 식별 라벨만 붙습니다. 소스에서 확인한 라벨은 gpu, UUID, pci_bus_id, device, modelName, hostname이고, MIG 인스턴스에서는 GPU_I_PROFILE과 GPU_I_ID가 추가됩니다. 이것만으로는 어느 팀이 카드를 먹고 있는지 알 수 없습니다.
쿠버네티스 매핑을 켜는 플래그가 --kubernetes 또는 -k이고 환경 변수는 DCGM_EXPORTER_KUBERNETES, 기본값은 거짓입니다. 켜면 pod, namespace, container 라벨이 붙습니다. 파드 UID를 원하면 --kubernetes-enable-pod-uid를 켜서 pod_uid 라벨을 얻고, 파드 라벨까지 원하면 --kubernetes-enable-pod-labels를 켭니다. 이때 라벨 키에는 pod_label_ 접두어가 붙습니다.
파드 라벨을 통째로 넣으면 카디널리티가 폭발하므로 --kubernetes-pod-label-allowlist-regex로 걸러야 합니다. 환경 변수는 DCGM_EXPORTER_KUBERNETES_POD_LABEL_ALLOWLIST_REGEX이고 비워 두면 모든 라벨이 들어갑니다. 차트에서는 이렇게 노출됩니다.
dcgmExporter:
enablePodLabels: true
enablePodUID: true
podLabelAllowlistRegex:
- '^app$'
- '^team$'
차트 주석이 경고를 덧붙입니다. 둘 중 하나라도 켜면 오퍼레이터가 파드에 대한 get, list, watch 권한을 가진 클러스터 범위 ClusterRole과 바인딩을 만듭니다. 권한이 넓어지는 변경이니 알고 켜야 합니다.
덧붙일 것이 셋 있습니다. GPU와 파드를 매칭하는 식별자는 --kubernetes-gpu-id-type으로 고르며 값은 uid와 device-name, 기본은 uid입니다. time-slicing이나 MPS에서 나오는 가상 GPU 메트릭을 잡으려면 --kubernetes-virtual-gpus가 필요하고 이때 vgpu 라벨이 붙습니다. 그리고 -o 또는 DCGM_EXPORTER_USE_OLD_NAMESPACE를 켜면 라벨 이름이 옛 방식인 pod_name, pod_namespace, container_name으로 바뀝니다. 오래된 대시보드에서 쿼리가 안 맞는 흔한 이유입니다.
파드 라벨이 붙으면 이런 질문에 답할 수 있게 됩니다.
# 네임스페이스별 프레임버퍼 사용량 합계
sum by (namespace) (DCGM_FI_DEV_FB_USED)
# 파드별 SM 활동률 (SM_ACTIVE를 CSV에 추가한 뒤에만 동작)
avg by (namespace, pod) (DCGM_FI_PROF_SM_ACTIVE)
# GPU를 잡고 있으면서 SM은 거의 안 쓰는 파드
avg by (namespace, pod) (DCGM_FI_PROF_SM_ACTIVE) < 0.2
and on(namespace, pod) (avg by (namespace, pod) (DCGM_FI_DEV_FB_USED) > 1024)
마치며 — 이름을 믿지 말고 정의를 읽는다
실질적으로 지켜야 할 것은 두 가지입니다. 첫째, 대시보드에 이용률 게이지 하나만 두지 않는 것입니다. 그 숫자는 커널이 얹혀 있었는지만 말하고 카드가 얼마나 쓰이는지는 말하지 않습니다. 최소한 SM 활동률과 프레임버퍼 사용량을 옆에 붙여야 그림이 완성됩니다.
둘째, 필요한 필드를 CSV에 직접 넣는 것입니다. 기본 CSV에서 DCGM_FI_PROF_SM_ACTIVE와 DCGM_FI_PROF_SM_OCCUPANCY가 주석 처리되어 있다는 사실을 모르면, 쿼리를 아무리 잘 써도 데이터가 없어 빈 그래프만 봅니다.
다음 글에서는 층을 하나 올라가 애플리케이션 쪽 메트릭을 봅니다. vLLM이 노출하는 시계열은 지금 몇 개가 기다리고 있고 첫 토큰까지 얼마나 걸리는지 같은, GPU 메트릭이 답하지 못하는 질문에 답합니다.
직접 해보기
- SLO 에러 버짓 계산기 — 지금 보고 있는 지표로 목표를 세우면 여유가 얼마나 되는지 계산해 보세요.
- LLM GPU 메모리(VRAM) 계산기 — 프레임버퍼 사용량이 예상과 맞는지 대조해 보세요.
- kubectl 명령어 찾기 — 포트 포워딩과 로그 확인 명령을 상황별로 찾아보세요.
시리즈
참고 자료
- dcgm-exporter 기본 카운터 CSV: https://github.com/NVIDIA/dcgm-exporter/blob/main/etc/default-counters.csv
- dcgm-exporter README: https://github.com/NVIDIA/dcgm-exporter/blob/main/README.md
- DCGM Feature Overview (프로파일링 메트릭 정의): https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html
- NVML nvmlUtilization_t: https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html
- gpu-operator 저장소 values.yaml: https://github.com/NVIDIA/gpu-operator/blob/main/deployments/gpu-operator/values.yaml
DCGM Exporter — GPU Utilization Is Not What You Think It Is
- Introduction — How DCGM and the Exporter Relate
- What Is Enabled by Default
- The Trap in the Word Utilization
- What to Look at Alongside It
- Configuring Custom Metrics
- Attaching Pod and Container Labels
- Closing — Do Not Trust the Name, Read the Definition
- Try It Yourself
- Series
- References
Introduction — How DCGM and the Exporter Relate
Ask most people to build a GPU dashboard and they start in the same place. Deploy DCGM Exporter, import the default dashboard, put GPU utilization on the big gauge. When the gauge reads 95 percent, everyone is satisfied. The problem is that the number does not measure what people expect it to.
Start with the structure: DCGM is what actually reads values off the GPU, and the exporter is a thin layer translating those values into Prometheus exposition format. That is also why dcgm.enabled is false in the GPU Operator chart defaults. As the comment says outright, the exporter uses an embedded nv-hostengine, so no separate DCGM DaemonSet is needed.
Metric names and configuration were verified against the official documentation and repositories on 2026-08-12. They can differ between releases, so check again against the version you are running.
What Is Enabled by Default
What gets exposed is decided by etc/default-counters.csv in the repository, the only authoritative source on this subject, whose format is three columns: the DCGM field name, the Prometheus metric type, and a help string. Look at real output first.
kubectl -n gpu-operator port-forward svc/nvidia-dcgm-exporter 9400:9400
curl -s localhost:9400/metrics | grep -E '^DCGM_FI_(DEV|PROF)' | head -20
The output shape shown in the repository README looks like this.
DCGM_FI_DEV_SM_CLOCK{gpu="0", UUID="GPU-604ac76c-d9cf-fef3-62e9-d92044ab6e52",container="",namespace="",pod=""} 139
DCGM_FI_DEV_MEM_CLOCK{gpu="0", UUID="GPU-604ac76c-d9cf-fef3-62e9-d92044ab6e52",container="",namespace="",pod=""} 405
Grouping the fields that are not commented out in the default CSV, meaning the ones actually enabled, by character gives this. Parentheses hold the CSV help string.
- Clocks:
DCGM_FI_DEV_SM_CLOCK(SM clock frequency (in MHz)),DCGM_FI_DEV_MEM_CLOCK - Temperature and power:
DCGM_FI_DEV_GPU_TEMP,DCGM_FI_DEV_MEMORY_TEMP,DCGM_FI_DEV_POWER_USAGE(Power draw (in W)),DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION(counter, since boot, in mJ) - Memory:
DCGM_FI_DEV_FB_USED,DCGM_FI_DEV_FB_FREE,DCGM_FI_DEV_FB_RESERVED— all in MiB - Utilization:
DCGM_FI_DEV_GPU_UTIL,DCGM_FI_DEV_MEM_COPY_UTIL,DCGM_FI_DEV_ENC_UTIL,DCGM_FI_DEV_DEC_UTIL - Errors:
DCGM_FI_DEV_XID_ERRORS(Value of the last XID error encountered),DCGM_FI_DEV_PCIE_REPLAY_COUNTER - Row remapping:
DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS,DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS,DCGM_FI_DEV_ROW_REMAP_FAILURE - Profiling:
DCGM_FI_PROF_GR_ENGINE_ACTIVE,DCGM_FI_PROF_PIPE_TENSOR_ACTIVE,DCGM_FI_PROF_DRAM_ACTIVE,DCGM_FI_PROF_PCIE_TX_BYTES,DCGM_FI_PROF_PCIE_RX_BYTES
One important fact already falls out. DCGM_FI_PROF_SM_ACTIVE and DCGM_FI_PROF_SM_OCCUPANCY are not on that list. Both exist in the CSV but are commented out. Which means with the default configuration, the metrics this post is about to argue for are not collected at all.
The Trap in the Word Utilization
The CSV help for DCGM_FI_DEV_GPU_UTIL is one line: GPU utilization (in %). From the name and that description you would read it as some percentage of the GPU's capability being consumed. Yet the section comment in the CSV quietly hints otherwise, noting that the sample period varies depending on the product.
NVIDIA's NVML API documentation defines the same-named quantity far more bluntly. The gpu field of nvmlUtilization_t is the percent of time over the past sample period during which one or more kernels was executing on the GPU. Take that sentence apart and what it omits becomes visible. How many kernels, how many SMs they occupy, whether tensor cores are touched, how much memory bandwidth is moved — none of it is in there. What is measured is time alone: was any kernel resident or not.
So this happens. Suppose a pod runs a single-block kernel continuously. On a card with 108 SMs, that kernel uses exactly one. The other 107 sit idle. But a kernel is always executing, so the utilization gauge reads 100 percent. The dashboard is green and the card is barely one percent used.
The metric the DCGM documentation offers to close that gap is DCGM_FI_PROF_SM_ACTIVE, field ID 1002, defined as the fraction of time at least one warp was active on a multiprocessor, averaged over all multiprocessors. The worked example the same document attaches nails the character of the metric. If a GPU has N SMs, a kernel using N blocks that runs over the entire interval yields an activity of 1. A kernel using N/5 blocks over the entire interval yields 0.2. And a kernel using N blocks that runs over one fifth of the interval with the SMs otherwise idle also yields 0.2.
Return to the earlier example and the answer appears. Utilization is 100 percent while DCGM_FI_PROF_SM_ACTIVE sits near 0.01. Both numbers describe the same situation, and the second one is telling the truth.
What to Look at Alongside It
The DCGM documentation even attaches interpretation guidance to DCGM_FI_PROF_SM_ACTIVE: a value of 0.8 or greater is necessary but not sufficient for effective use of the GPU, and a value less than 0.5 likely indicates ineffective usage. The word necessary is the key. SMs being busy and SMs doing useful work are different claims.
So look at four things together, at minimum.
Use DCGM_FI_PROF_SM_ACTIVE (1002) to see how widely the work spreads. DCGM_FI_PROF_SM_OCCUPANCY (1003), defined as the fraction of resident warps on a multiprocessor relative to the maximum number of concurrent warps it supports, tells you how full each SM is. Interpret it carefully: as the documentation warns directly, higher occupancy does not necessarily indicate better GPU usage. It does for memory-bandwidth-limited workloads, but not necessarily for compute-limited ones.
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE (1004), the fraction of cycles the tensor pipe was active, tells you whether the tensor cores are actually turning, and DCGM_FI_PROF_DRAM_ACTIVE (1005), the fraction of cycles where data was sent to or received from device memory, tells you whether memory is the bottleneck. On an LLM inference server, the former near the floor means the matrix math is not reaching tensor cores, while a high latter with a low former during decode-heavy stretches is the normal picture.
As queries, they look like this. Prometheus expressions use braces in label selectors, so they must live inside code blocks.
# Mean SM activity per node
avg by (hostname) (DCGM_FI_PROF_SM_ACTIVE)
# Find GPUs with high utilization but empty SMs
DCGM_FI_DEV_GPU_UTIL > 90 and on(gpu, UUID) DCGM_FI_PROF_SM_ACTIVE < 0.3
# Framebuffer usage ratio
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
# Grafana variables use the dollar sign, so keep them inside code blocks only
avg by (gpu) (rate(DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION{hostname=~"$node"}[$__rate_interval]))
One more thing. Hardware constraints mean only certain groups of profiling metrics can be read together, so DCGM applies automatic multiplexing by statistically sampling the requested metrics. The documentation therefore warns that collection at higher frequencies will result in zeroes returned as DCGM attempts to group metrics together. If unexplained zeroes appear on your dashboard, this is worth remembering.
Configuring Custom Metrics
There are three ways to change the default CSV.
First, point at a CSV file directly. The flag is -f or --collectors, the environment variable is DCGM_EXPORTER_COLLECTORS, and the default is /etc/dcgm-exporter/default-counters.csv.
Second, use a YAML configuration file, via --config-file or DCGM_EXPORTER_CONFIG_FILE. The structure the README shows is this.
version: 1
metrics:
file: /etc/dcgm-exporter/default-counters.csv
collection:
interval: 30s
You can also list fields inline, in which case each entry uses the keys name, prometheusType, and help. For Kubernetes the README instructs you to mount custom metric ConfigMaps as files and set metrics.file to the mounted path. YAML is read only at startup, so edits require a restart.
Third, if you run the GPU Operator, specify it through chart values. The dcgmExporter.config block takes name, create, and data, and the chart comments state an important constraint: when pointing at an existing ConfigMap it must exist in the same namespace as the release, and the metrics are expected to be listed under a key called dcgm-metrics.csv.
# helm values example
dcgmExporter:
config:
name: custom-dcgm-exporter-metrics
create: true
data: |-
DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %).
DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB).
DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned.
DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM.
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active.
DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data.
The collection interval is -c or --collect-interval, environment variable DCGM_EXPORTER_INTERVAL, in milliseconds, defaulting to 30000. The listen address is -a or DCGM_EXPORTER_LISTEN, defaulting to port 9400. The chart enables a ServiceMonitor by default at a 15 second scrape interval, which sits out of step with the 30 second collection interval, so you will scrape the same value twice in places.
Attaching Pod and Container Labels
Out of the box, the series carry only GPU identity labels. The labels confirmed in the source are gpu, UUID, pci_bus_id, device, modelName, and hostname, with GPU_I_PROFILE and GPU_I_ID added for MIG instances. None of that tells you which team is eating the card.
The flag that turns on Kubernetes mapping is --kubernetes or -k, environment variable DCGM_EXPORTER_KUBERNETES, default false. Enabling it adds the pod, namespace, and container labels. For the pod UID, enable --kubernetes-enable-pod-uid to get a pod_uid label; for pod labels, enable --kubernetes-enable-pod-labels, which prefixes the label keys with pod_label_.
Ingesting pod labels wholesale explodes cardinality, so filter with --kubernetes-pod-label-allowlist-regex, environment variable DCGM_EXPORTER_KUBERNETES_POD_LABEL_ALLOWLIST_REGEX. Leave it empty and every label is included. In the chart it surfaces like this.
dcgmExporter:
enablePodLabels: true
enablePodUID: true
podLabelAllowlistRegex:
- '^app$'
- '^team$'
The chart comments append a warning. Turning on either one makes the operator create a cluster-scoped ClusterRole and binding granting get, list, and watch on pods. That is a permission widening, so flip the switch knowingly.
Three additions. The identifier used to match GPUs to pods is chosen with --kubernetes-gpu-id-type, whose values are uid and device-name, defaulting to uid. Capturing virtual GPU metrics from time-slicing or MPS needs --kubernetes-virtual-gpus, which adds a vgpu label. And enabling -o or DCGM_EXPORTER_USE_OLD_NAMESPACE switches the label names to the older form: pod_name, pod_namespace, container_name. That is a common reason queries in an inherited dashboard do not match.
Once pod labels are attached, questions like these become answerable.
# Framebuffer usage summed per namespace
sum by (namespace) (DCGM_FI_DEV_FB_USED)
# SM activity per pod (works only after adding SM_ACTIVE to the CSV)
avg by (namespace, pod) (DCGM_FI_PROF_SM_ACTIVE)
# Pods holding a GPU while barely using the SMs
avg by (namespace, pod) (DCGM_FI_PROF_SM_ACTIVE) < 0.2
and on(namespace, pod) (avg by (namespace, pod) (DCGM_FI_DEV_FB_USED) > 1024)
Closing — Do Not Trust the Name, Read the Definition
Two things are worth actually enforcing. First, never put a lone utilization gauge on a dashboard. That number says whether a kernel was resident, not how much of the card is being used. It takes SM activity and framebuffer usage beside it before the picture is complete.
Second, put the fields you need into the CSV yourself. Not knowing that DCGM_FI_PROF_SM_ACTIVE and DCGM_FI_PROF_SM_OCCUPANCY are commented out in the default CSV means that however well you write the query, you stare at an empty graph because the data was never collected.
The next post climbs one layer up, to application metrics. The series vLLM exposes answer questions GPU metrics cannot: how many requests are waiting right now, and how long until the first token.
Try It Yourself
- SLO & Error Budget Calculator — turn the metrics you are watching into a target and see how much headroom you have.
- GPU VRAM Calculator for LLMs — cross-check framebuffer usage against what you expected.
- kubectl Command Finder — look up port-forward and log commands by situation.
Series
References
- dcgm-exporter default counters CSV: https://github.com/NVIDIA/dcgm-exporter/blob/main/etc/default-counters.csv
- dcgm-exporter README: https://github.com/NVIDIA/dcgm-exporter/blob/main/README.md
- DCGM Feature Overview (profiling metric definitions): https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html
- NVML nvmlUtilization_t: https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html
- gpu-operator repository values.yaml: https://github.com/NVIDIA/gpu-operator/blob/main/deployments/gpu-operator/values.yaml