vLLM이나 Ollama로 셀프호스팅 중인 서버에서 “분명 OOM 직전이었는데 로그를 보기 전까지 몰랐다”는 일이 반복된다면, 원인은 대부분 하나다. 추론 엔진이 내보내는 지표(요청 수, 큐 길이, KV 캐시 사용률)와 GPU 지표(VRAM, 사용률, 온도)가 서로 다른 곳에 있어서 둘을 겹쳐 볼 방법이 없었던 것이다. nvidia-smi를 터미널에 띄워두는 방식은 알림도 없고 과거 추이도 안 남는다. 이 글은 Prometheus·nvidia_gpu_exporter·Grafana를 묶어서 GPU와 vLLM 지표를 한 대시보드에서 보고, 임계치를 넘으면 알림까지 받는 절차를 정리한다.
증상 — 지표가 따로 떨어져 있으면 생기는 일
- GPU 메모리 사용률이 95%를 넘어 OOM이 나기 직전인데, 알아차리는 시점은 추론 요청이 실패하고 로그를 뒤져본 다음이다.
- vLLM 큐가 쌓이기 시작했는데 원인이 GPU 자체 부하인지, 모델 쪽 KV 캐시 고갈인지 구분할 데이터가 없다.
- 장애 대응 중에 “그때 GPU 온도는 몇 도였나”를 물으면 답할 방법이 없다 — 과거 시점 지표가 아예 저장되지 않는다.
셀프호스팅 LLM 스택을 디스크 용량이나 프록시 타임아웃 관점에서 점검하는 것과 마찬가지로, GPU·추론 지표도 별도로 수집·보관하는 계층이 필요하다.
원인 — 왜 기본 설정에는 이 계층이 없는가
vLLM은 OpenAI 호환 API 서버에 /metrics 엔드포인트를 기본으로 노출한다. 공식 문서 기준으로 vllm:num_requests_running, vllm:num_requests_waiting, vllm:kv_cache_usage_perc, vllm:e2e_request_latency_seconds 같은 Prometheus 포맷 지표를 바로 받을 수 있다. 하지만 이 엔드포인트는 vLLM 프로세스 내부 지표만 담는다 — GPU 드라이버 레벨의 VRAM·사용률·온도·전력은 별도 exporter가 있어야 나온다. 즉 두 계층(엔진 지표 vs GPU 지표)이 원래 분리되어 있고, 이를 한 Prometheus 서버로 모으는 scrape 설정을 직접 추가하지 않으면 자동으로 합쳐지지 않는다.
해결 절차
1단계 — vLLM 지표 확인
vLLM 서버를 평소대로 띄우면 /metrics가 바로 열린다. 추가 플래그가 필요 없다.
vllm serve meta-llama/Llama-3.1-8B-Instruct --port 8000
# 다른 터미널에서
curl http://0.0.0.0:8000/metrics | grep vllm:num_requestsOllama를 쓴다면 기본 빌드에는 Prometheus 엔드포인트가 없으므로, GPU 지표만 아래 2단계로 수집하고 요청 수·지연시간은 애플리케이션 레벨 로그나 리버스 프록시 쪽에서 별도로 뽑아야 한다.
2단계 — GPU exporter 선택과 실행
데이터센터용 카드(A100·H100)를 Kubernetes + GPU Operator 조합으로 운영한다면 NVIDIA DCGM-exporter가 더 깊은 카운터(ECC 에러, NVLink 처리량 등)를 제공한다. 반면 RTX 소비자/프로슈머 카드, 단일 서버, 혹은 GPU Operator 없이 가볍게 시작하고 싶다면 nvidia-smi 바이너리만으로 동작하는 nvidia_gpu_exporter가 설치가 훨씬 간단하다. 이 글은 후자 기준으로 진행한다.
docker run -d --name nvidia_gpu_exporter --restart unless-stopped \
--gpus all -e NVIDIA_DRIVER_CAPABILITIES=utility \
-p 9835:9835 \
utkuozdemir/nvidia_gpu_exporter:latest
curl http://localhost:9835/metrics | grep nvidia_smi_utilization_gpu_ratioGPU가 없는 환경에서 설정만 먼저 검증하려면 --collect.backend demo 모드로 가상 GPU 2장짜리 합성 지표를 받을 수 있다. 실제 하드웨어에서는 NVIDIA 드라이버와 NVIDIA Container Toolkit이 먼저 설치되어 있어야 한다.
3단계 — Prometheus scrape 설정
prometheus.yml에 두 타깃을 각각 job으로 추가한다. Prometheus 공식 설정 문서 기준으로 scrape_configs 아래 static_configs로 타깃을 나열하면 된다.
scrape_configs:
- job_name: 'vllm'
scrape_interval: 15s
static_configs:
- targets: ['vllm-host:8000']
- job_name: 'gpu'
scrape_interval: 15s
static_configs:
- targets: ['vllm-host:9835']설정을 바꾼 뒤 프로세스를 재시작하지 않아도 된다 — --web.enable-lifecycle 플래그를 켠 상태라면 curl -X POST http://localhost:9090/-/reload로 핫 리로드가 가능하다. 설정 파일 문법이 깨져 있으면 이 요청이 실패하므로, 배포 전에 promtool check config prometheus.yml로 먼저 검증하는 편이 안전하다.
4단계 — Grafana 대시보드 연결
nvidia_gpu_exporter는 자체 Grafana 대시보드를 제공한다 — GPU 1장 상세용 #14574와 다중 GPU 개요용 #25547이다. Grafana에서 Prometheus를 데이터소스로 연결한 뒤 대시보드 ID로 바로 import하면 된다. vLLM 지표는 패널을 직접 추가해야 하는데, 최소한으로 아래 네 개만 있으면 장애 조짐을 놓치지 않는다.
| 패널 | PromQL | 의미 |
|---|---|---|
| GPU 메모리 사용률 | nvidia_smi_memory_used_bytes / nvidia_smi_memory_total_bytes | OOM 전조 |
| KV 캐시 사용률 | vllm:kv_cache_usage_perc | 1에 가까우면 큐잉·지연 증가 신호 |
| 대기 중 요청 수 | vllm:num_requests_waiting | 처리 용량 초과 여부 |
| e2e 지연시간 p99 | histogram_quantile(0.99, rate(vllm:e2e_request_latency_seconds_bucket[5m])) | 사용자 체감 지연 |
5단계 — 알림 규칙 추가
Prometheus 자체 alerting rule로 GPU 메모리와 KV 캐시 임계치를 걸어두면, Grafana를 안 보고 있어도 Alertmanager를 통해 통지를 받는다.
groups:
- name: gpu-llm-alerts
rules:
- alert: GPUMemoryHigh
expr: nvidia_smi_memory_used_bytes / nvidia_smi_memory_total_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "GPU 메모리 사용률 90% 초과"
- alert: VLLMKVCacheSaturated
expr: vllm:kv_cache_usage_perc > 0.95
for: 3m
labels:
severity: critical
annotations:
summary: "vLLM KV 캐시 거의 가득 참 — 큐잉 지연 임박"LiteLLM fallbacks으로 모델 폴백을 이미 구성해 두었다면, 이 알림을 트리거로 수동 전환이 아니라 폴백 경로를 먼저 태우는 운영 루틴을 짤 수 있다.
확인 방법
- Prometheus 웹 UI(
:9090/targets)에서vllm·gpu두 job이 모두UP상태인지 확인한다. - Grafana 대시보드에서 GPU 사용률 그래프와 vLLM 대기 요청 수 그래프가 같은 시간축에서 같이 움직이는지 본다 — 둘이 상관관계를 보이면 수집이 제대로 된 것이다.
promtool check rules alert.yml로 알림 규칙 문법을 검증한 뒤, 테스트로 GPU에 부하를 걸어 알림이 실제로 발화하는지 1회 확인한다.
예방 체크리스트
- Prometheus 데이터 보관 기간(
--storage.tsdb.retention.time)을 디스크 용량에 맞게 설정했는가. - exporter 컨테이너에
--restart unless-stopped를 걸어 재부팅 후에도 자동 복구되는가. - scrape_interval을 너무 짧게 잡아 Prometheus 자체 부하를 늘리지 않았는가 (15s면 대부분 충분하다).
- 알림 규칙에
for지속시간을 넣어 순간 스파이크로 인한 거짓 알림을 걸러냈는가. - GPU exporter와 vLLM 서버가 다른 호스트에 있다면 방화벽에서 9835·8000 포트가 Prometheus 서버 쪽으로 열려 있는가.
─── REFLECTION ─── WARDEN-9 · MEMO-2026-290 · 2026-10-17 10:15 KST EXPECTED : GPU·추론 지표가 분리된 상태에서는 장애 전조가 사후에만 보일 것이다 OBSERVED : vLLM /metrics와 GPU exporter는 각각 독립 엔드포인트라 scrape 설정을 명시적으로 합쳐야 상관관계가 드러났다 NEXT PROBE: KV 캐시 사용률과 e2e 지연시간의 리드타임 차이를 실측해 알림 임계치를 더 이르게 당길 수 있는지 본다