
nvidia-smi로 회수 후보 GPU를 Python으로 검출하기
요약
nvidia-smi를 활용하여 유휴 GPU를 정확하게 판별하는 Python 스크립트 작성법을 다룹니다. 단순 이용률뿐만 아니라 VRAM 사용량과 compute process 존재 여부를 동시에 확인하여 오검출을 방지하는 방법을 제시합니다.
핵심 포인트
- 이용률 0%만으로는 실제 유휴 상태를 보장할 수 없음
- VRAM 사용량과 compute process(PID) 유무를 병행 확인해야 함
- nvidia-smi CSV 출력의 공백 처리가 UUID 비교의 핵심
- 표준 라이브러리만 사용하여 환경 독립적인 검증 로직 구현
GPU Utilization 0%
인 GPU를 그대로 유휴 상태로 간주하여 모니터링하는 것은 위험하다. 추론(Inference) 프로세스가 대기 중인 것뿐이라면 이용률은 0%가 되며, VRAM이 비어 있어도 PID가 남아 있는 경우가 있다. 회수해도 좋은 후보는 이용률, VRAM 사용량, compute process 세 가지를 동시에 확인하는 것이 안전하다.
7월 30일에 Hugging Face가 공개한 "GPU Management: Why Idle GPUs Are the New Grounded Aircraft"를 읽고, 수동 확인 절차를 작은 스크립트로 만들었다. GPU를 자동으로 리셋하는 것이 아니다. 운영 담당자가 "아무도 사용하고 있지 않으니, 작업을 올릴 수 있겠다"라고 확인하기 위한 후보 목록이다.
오늘 아침, 모의한 3장의 GPU에서 실행했을 때 이용률과 VRAM 모두 0인데 PID가 남아 있는 GPU가 한 장 있었다. 이용률만 보고 있었다면 그 GPU도 유휴 상태로 통지했을 것이다. 이런 사소한 오검출이 가장 곤란하다.
이용률은 순간값이다. 배치(Batch) 사이의 간격, 모델을 로드한 채로 입력을 기다리는 API, CUDA 컨텍스트(Context)만 남긴 프로세스는 0%를 반환할 수 있다. 반면 VRAM만 보는 방법도 불충분하여, CPU 측의 대기나 작은 VRAM 예약 등을 놓치기 쉽다.
따라서 다음 조건을 모두 만족하는 GPU만을 회수 후보로 한다.
| 관측값 | 조건 | 의도 |
|---|---|---|
utilization.gpu | 0 | 실행 중인 계산이 보이지 않음 |
memory.used | 0 MiB | VRAM에 남은 모델이나 캐시가 없음 |
--query-compute-apps | 해당 UUID가 없음 | compute process가 점유하고 있지 않음 |
판정의 입구는 GPU 단위의 CSV와 프로세스 단위의 CSV를 UUID로 결합하는 부분에 있다.
표준 라이브러리만으로 작성했다. --fixture는 기사 중의 검증용이며, 실전에서는 제외하고 실행한다. GPU가 없는 Mac이나 CI에서도 판정 로직을 확인할 수 있도록 해두었다.
#!/usr/bin/env python3
from __future__ import annotations
import argparse
...
GPU-cccc는 이용률 0%, VRAM 0 MiB임에도 불구하고 FIXTURE_APPS에 PID 9876이 있다. 후보에서 제외되는 것은 이 UUID 대조 때문이다. 프로세스가 아무것도 없는 상태에서는 nvidia-smi --query-compute-apps가 No running compute processes found라고 출력되는 환경도 있다. 이 문자열은 UUID가 아니므로, 모든 GPU가 프로세스 없음으로 취급된다.
기사의 코드를 gpu_idle_check.py로 저장하고, Python 3.9.6에서 다음을 실행했다.
python3 gpu_idle_check.py --fixture
출력은 이것뿐이었다.
GPU 0: GPU-aaaa (이용률 0%, VRAM 0/81920 MiB)
GPU 1은 18%·4096 MiB이므로 당연히 남는다. GPU 2는 앞서 언급한 PID가 있기 때문에 남는다. 3장 중 1장만을 후보로 만들 수 있었다. nvidia-smi의 CSV는 공백을 포함하므로, csv.reader 다음에 strip()을 수행하고 있다. 이 부분을 생략하면 UUID 비교가 조용히 실패하여, 실행 중인 GPU까지 후보에 섞이게 된다.
--fixture로 확인한 것은 CSV 분해, UUID 대조, 세 가지 조건의 AND 판정이다. 드라이버나 컨테이너에서 보이는 PID의 범위까지는 검증하지 않았다. 실기기에 적용하기 전에, 해당 노드에서 nvidia-smi를 한 번 단독으로 실행하여 CSV가 5열과 4열로 출력되는지 확인해 두는 것이 좋다. 이 부분은 환경 차이를 코드로 흡수하려 하지 않는 편이 좋다.
반드시 추적할 수 있는 것은 아니다. --query-compute-apps에 나오는 PID는 호스트 측의 PID이며, Kubernetes의 Pod 이름이나 Docker의 컨테이너 ID가 아니다. 통지에 PID만 기재하면, 조사하는 사람은 결국 ps나 런타임 정보를 교차 확인해야 한다.
처음부터 그렇게까지 자동화할 필요는 없다. 후보에서 제외된 GPU가 많다면 UUID와 PID를 함께 로그에 남겨서, 나중에 crictl ps나 잡 스케줄러(Job Scheduler)의 할당 내용과 대조한다. 반대로 후보에 나온 GPU만을 대상으로 한다면, 이 스크립트의 출력은 GPU index와 UUID만으로 충분하다. 조사를 위한 정보와 회수 판단을 위한 정보를 섞지 않는 것이 운영을 안정적으로 만든다.
같은 이유로 프로세스 이름(process name)을 조건에 넣지 않았다. python, python3, 추론 서버의 실행 파일 이름을 나열하기 시작하면, 이름이 바뀐 작업(job)만 놓치게 된다. GPU UUID가 프로세스 목록(process list)에 있는지만을 판정 기준으로 삼으면, 구현 언어나 프레임워크에 의존하지 않는다.
실제 기기에서는 다음과 같이 사용한다.
python3 gpu_idle_check.py
출력을 Slack으로 보내거나, Kubernetes 노드에 라벨(label)을 붙이는 등의 후속 처리는 별도로 분리해 두는 것이 좋다. 회수 후보와 즉시 회수를 하나의 스크립트로 연결하면, 짧은 유휴 시간(idle time) 때문에 작업을 중단시킬 위험이 있다. 나는 5분마다 기록하고, 연속 3회 후보가 된 UUID만 사람이 확인하는 방식으로 운영하고 있다.
단 한 번의 판정이라면, API 서버의 요청 간격이나 데이터 로더(data loader)의 대기 시간까지 유휴 상태로 보일 수 있다. 5분 간격으로 3회라면, 적어도 10분을 넘어서 동일한 상태였던 GPU만 골라낼 수 있다. 여기서 필요한 것은 엄격한 '유휴 시간'이 아니라, 다음 작업을 올리기 전에 확인하는 목록이다.
모니터링 기반에 시계열 DB(time-series DB)가 있다면, 스크립트를 상주시키지 않고 DCGM_FI_DEV_GPU_UTIL과 VRAM 메트릭(metrics)으로 동일한 윈도우(window)를 만들어도 된다. 그 경우에도 프로세스 목록과의 대조는 별도로 필요하다. 메트릭은 GPU가 조용하다는 것을 나타내고, 프로세스 목록은 누군가가 GPU를 점유하고 있다는 것을 나타낸다. 역할이 다르다.
회수 후보가 0장인 날이 계속되어도 임계값(threshold)을 바로 완화하지 않는다. 0%인 GPU를 늘리기 위해 기준을 'VRAM 1 GiB 미만'으로 바꾸면, 모델을 로드한 채 일시 정지해 있는 서버까지 후보에 포함된다. 가용 용량을 늘리는 대책은 이용자의 명시적인 해제(release)나 스케줄러(scheduler)의 예약 기한과 결합하는 것이 본질이라고 생각한다.
알림 본문만 남기면 왜 후보가 되었는지에 대한 근거가 사라진다. 적어도 관측 시각, GPU UUID, 이용률, 사용 VRAM, 프로세스 목록에 동일한 UUID가 있었는지 여부를 한 줄로 남겨야 한다. 후보가 아니었던 GPU도 집계값만은 남겨두는 것이 좋다. 다음 날에 "GPU 2가 후보가 되지 않은 것이 버그인가, 아니면 PID가 있었기 때문인가"를 판별할 수 있기 때문이다.
이 스크립트는 CSV의 열(column) 수가 예상과 다르면 예외(exception)로 인해 멈춘다. 억지로 0으로 취급하지 않기 위해서다. 드라이버 업데이트나 래퍼(wrapper) 변경으로 출력 형식이 바뀌었을 때, 유휴 GPU를 대량으로 알리는 것보다 모니터링이 고장 났다는 것을 아는 편이 나았다. 알림 모니터링에서는 이 종료 실패 자체도 포착하도록 하고 있다.
운영 로그를 살펴보다 보면 후보 판정과는 별개의 편향(bias)도 보인다. 특정 GPU만 매번 비어 있다면, GPU 고장을 의심하기 전에 스케줄러의 리소스 요청(resource request)이나 어피니티(Affinity)를 확인한다. 반대로 모든 GPU가 동시에 후보가 되는 시간이 있다면, 야간 배치(batch)의 종료 시각에 맞춰 다음 작업을 채워 넣을 수 있다. 회수 후보 목록은 유휴 상태의 검출뿐만 아니라 배치(placement)의 습성을 읽는 재료가 된다.
좋지 않다. 이것은 'GPU에 아무것도 올라와 있지 않음'을 일반적인 연산 프로세스(compute process)에 한해서만 보는 검사다. MIG 구성, 컨테이너 런타임(container runtime)의 가시성, 그래픽 프로세스(graphics process), GPU 외부에 상태를 가지는 분산 작업(distributed job)은 별도의 확인이 필요하다. 특히 공유 노드에서의 nvidia-smi --gpu-reset을 이 결과만 보고 실행해서는 안 된다.
판정을 엄격하게 하면 회수할 수 있는 GPU는 적게 보인다. 그래도 괜찮다. 유휴 GPU 알림은 속도보다 오보(false positive)가 적은 것이 중요하며, 한 장을 놓치는 것보다 실행 중인 추론을 멈추는 비용이 더 크기 때문이다.
GPU 운영에서 봐야 할 것은 이용률의 낮음이 아니라, 실제로 누가 잡고 있는가였다. nvidia-smi의 GPU 목록과 프로세스 목록을 UUID로 대조하면, 0%라는 찰나의 값을 알림 근거로 삼지 않아도 된다.
우선은 읽기 전용의 후보 추출 도구로 도입하여 실제 기기의 출력을 며칠간 남긴다. 후보가 정말로 사용되지 않았는지 확인한 후에 알림 대상이나 자동화를 추가한다. 이 순서라면 GPU를 방치함으로써 발생하는 손해와, 회수를 너무 서두르다 발생하는 사고 양쪽을 모두 줄일 수 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기