Monitoring inferencije sa Prometheusom i Grafanom
Benchmark iz prethodnog teksta ti kaže kako je bilo u trenutku merenja. Nadzor ti kaže kako je sada i kako je bilo prošle srede u tri ujutru — a to je jedini način da odgovoriš na pitanje da li je sporije nego pre.
Ovaj tekst postavlja tri komponente: prikupljanje metrika sa kartice i iz servisa, njihovo čuvanje i prikaz.
Šta se prati
| Sloj | Metrika | Zašto |
|---|---|---|
| Kartica | Memorija, potrošnja, temperatura | Kapacitet i usporavanje |
| Servis | Zahtevi u obradi i u redu, KV keš | Da li stižeš da obradiš |
| Proxy | Trajanje zahteva, kodovi odgovora | Šta korisnik zaista doživljava |
Sva tri sloja su potrebna. Kartica na devedeset posto ne znači da je servis zagušen; servis bez grešaka ne znači da korisnici ne čekaju.
Metrike sa kartice
Zvanični alat je DCGM exporter. Radi kroz isti nvidia-container-toolkit iz teksta o Dockeru:
$ docker run -d \
--name dcgm-exporter \
--gpus all \
--cap-add SYS_ADMIN \
-p 127.0.0.1:9400:9400 \
--restart unless-stopped \
nvcr.io/nvidia/k8s/dcgm-exporter:latest
$ curl -s localhost:9400/metrics | grep -E '^DCGM_FI_DEV_(GPU_UTIL|FB_USED|POWER)'
DCGM_FI_DEV_GPU_UTIL{gpu="0",modelName="NVIDIA GeForce RTX 3090"} 94
DCGM_FI_DEV_FB_USED{gpu="0",modelName="NVIDIA GeForce RTX 3090"} 18432
DCGM_FI_DEV_POWER_USAGE{gpu="0",modelName="NVIDIA GeForce RTX 3090"} 287.431
Najkorisnija polja:
| Metrika | Značenje |
|---|---|
DCGM_FI_DEV_FB_USED |
Zauzeta memorija u MiB |
DCGM_FI_DEV_FB_FREE |
Slobodna memorija |
DCGM_FI_DEV_POWER_USAGE |
Potrošnja — pravi pokazatelj rada |
DCGM_FI_DEV_GPU_TEMP |
Temperatura |
DCGM_FI_DEV_GPU_UTIL |
Iskorišćenost — zavarava |
Kao što stoji u tekstu o nvidia-smi, iskorišćenost meri samo da li je kartica imala posla. Potrošnja je pouzdaniji pokazatelj i na grafikonu se lepše čita.
Bez DCGM-a
Na potrošačkim karticama DCGM ume da zakaže. Jednostavan zamenski exporter:
#!/usr/bin/env python3
"""GPU metrike za Prometheus, preko nvidia-smi."""
import subprocess
from http.server import HTTPServer, BaseHTTPRequestHandler
POLJA = "index,memory.used,memory.free,utilization.gpu,power.draw,temperature.gpu"
class Rukovalac(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/metrics":
self.send_response(404); self.end_headers(); return
ispis = subprocess.run(
["nvidia-smi", f"--query-gpu={POLJA}",
"--format=csv,noheader,nounits"],
capture_output=True, text=True, timeout=10,
).stdout
redovi = []
for red in ispis.strip().split("\n"):
i, mem_u, mem_s, util, snaga, temp = [p.strip() for p in red.split(",")]
redovi += [
f'gpu_memory_used_mib{{gpu="{i}"}} {mem_u}',
f'gpu_memory_free_mib{{gpu="{i}"}} {mem_s}',
f'gpu_utilization_percent{{gpu="{i}"}} {util}',
f'gpu_power_watts{{gpu="{i}"}} {snaga}',
f'gpu_temperature_celsius{{gpu="{i}"}} {temp}',
]
telo = ("\n".join(redovi) + "\n").encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(telo)
def log_message(self, *a):
pass
HTTPServer(("127.0.0.1", 9401), Rukovalac).serve_forever()
Stavi ga u systemd jedinicu i imaš isto što i DCGM, u trideset redova.
Metrike iz servisa
vLLM
Uključene same od sebe:
$ curl -s localhost:8000/metrics | grep -E '^vllm:(num_requests|gpu_cache)' vllm:num_requests_running 7.0 vllm:num_requests_waiting 2.0 vllm:gpu_cache_usage_perc 0.63
Ova tri broja su srce nadzora. Zahtevi u redu koji ne opada znače premašen kapacitet; keš blizu jedinice znači da se zahtevi odbacuju i vraćaju u obradu.
Uz njih idu i histogrami trajanja:
vllm:time_to_first_token_seconds_bucket vllm:time_per_output_token_seconds_bucket vllm:e2e_request_latency_seconds_bucket
llama.cpp
Traži izričito uključivanje:
$ llama-server -m model.gguf --metrics ...
$ curl -s localhost:8080/metrics | grep llamacpp llamacpp:tokens_predicted_seconds_total 1247.3 llamacpp:requests_processing 2 llamacpp:requests_deferred 0
Ollama
Nema ih. Ako ti nadzor treba, ostaje ti da meriš spolja — kroz proxy, ili malom skriptom koja povremeno šalje upit i beleži trajanje:
#!/bin/bash
# Provera odziva, za textfile collector
POCETAK=$(date +%s.%N)
KOD=$(curl -s -o /dev/null -w '%{http_code}' --max-time 60 \
http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3:8b","messages":[{"role":"user","content":"test"}],"max_tokens":5}')
TRAJANJE=$(echo "$(date +%s.%N) - $POCETAK" | bc)
cat > /var/lib/node_exporter/textfile/ollama.prom.$$ <<EOF
ollama_up $([ "$KOD" = "200" ] && echo 1 || echo 0)
ollama_probe_seconds $TRAJANJE
EOF
mv /var/lib/node_exporter/textfile/ollama.prom.$$ /var/lib/node_exporter/textfile/ollama.prom
Upis u privremeni fajl pa premeštanje sprečava da Prometheus pročita polupisan fajl. Zakazivanje kroz cron na svakih minut.
Prometheus i Grafana
$ nano compose.yaml
services:
prometheus:
image: prom/prometheus
container_name: prometheus
restart: unless-stopped
network_mode: host
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./pravila.yml:/etc/prometheus/pravila.yml:ro
- prom-data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=90d
- --web.listen-address=127.0.0.1:9090
grafana:
image: grafana/grafana
container_name: grafana
restart: unless-stopped
ports:
- "127.0.0.1:3001:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=promeni-me
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
prom-data:
grafana-data:
$ nano prometheus.yml
global:
scrape_interval: 15s
rule_files:
- /etc/prometheus/pravila.yml
scrape_configs:
- job_name: gpu
static_configs:
- targets: ['127.0.0.1:9400']
- job_name: vllm
static_configs:
- targets: ['127.0.0.1:8000']
- job_name: node
static_configs:
- targets: ['127.0.0.1:9100']
$ docker compose up -d $ curl -s localhost:9090/api/v1/targets | jq -r '.data.activeTargets[] | "\(.labels.job) \(.health)"'
Port 3001 za Grafanu je namerno — 3000 verovatno već drži Open WebUI. Oba idu iza proxy-ja, sa istim pravilima.
Grafanu nikad ne izlaži bez izmene podrazumevane lozinke. Ona ima pristup svim tvojim metrikama, uključujući i one koje otkrivaju kako je server postavljen.
Upiti koje ćeš koristiti
Zauzeće memorije u procentima:
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) * 100
Prosečna brzina generisanja u poslednjih pet minuta:
rate(vllm:generation_tokens_total[5m])
Medijana vremena do prvog tokena:
histogram_quantile(0.5, rate(vllm:time_to_first_token_seconds_bucket[5m]))
Devedeset peti percentil trajanja zahteva:
histogram_quantile(0.95, rate(vllm:e2e_request_latency_seconds_bucket[5m]))
Percentil je ovde važniji od proseka. Prosek od dve sekunde uz p95 od trideset znači da svaki dvadeseti korisnik čeka pola minuta — što je ono na šta se ljudi zapravo žale.
Udeo grešaka iz proxy loga, ako koristiš nginx exporter:
sum(rate(nginx_http_requests_total{status=~"5.."}[5m]))
/ sum(rate(nginx_http_requests_total[5m]))
Grafikoni koji vrede
Manje je bolje. Šest panela dovoljno pokriva:
- Zauzeće VRAM-a — s pragom na devedeset posto
- Potrošnja i temperatura — na istoj osi, otkrivaju usporavanje
- Zahtevi u obradi i u redu — dve linije, jedna ispod druge
- Zauzeće KV keša — ranije upozorenje od svega ostalog
- TTFT, medijana i p95 — ono što korisnik oseća
- Tokena u sekundi — ukupna propusnost
Grafana ima gotove table koje se uvoze po broju; potraži one za DCGM i za vLLM pod Dashboards → Import. Brže je nego crtati od nule, a posle ih doteraš.
Obaveštenja
$ nano pravila.yml
groups:
- name: ai-servis
rules:
- alert: ServisNeRadi
expr: up{job="vllm"} == 0
for: 2m
annotations:
summary: "AI servis ne odgovara"
- alert: MemorijaPriKraju
expr: DCGM_FI_DEV_FB_FREE < 1024
for: 5m
annotations:
summary: "Manje od 1 GB slobodnog VRAM-a"
- alert: RedRaste
expr: vllm:num_requests_waiting > 5
for: 10m
annotations:
summary: "Zahtevi čekaju duže od deset minuta"
- alert: KesPun
expr: vllm:gpu_cache_usage_perc > 0.95
for: 5m
annotations:
summary: "KV keš pun, zahtevi se odbacuju"
- alert: TermalnoUsporavanje
expr: DCGM_FI_DEV_GPU_TEMP > 83
for: 10m
annotations:
summary: "Kartica na granici usporavanja"
- alert: OdzivLos
expr: histogram_quantile(0.95,
rate(vllm:e2e_request_latency_seconds_bucket[5m])) > 60
for: 10m
annotations:
summary: "p95 trajanja zahteva preko minuta"
$ docker compose restart prometheus $ curl -s localhost:9090/api/v1/rules | jq -r '.data.groups[].rules[].name'
Vrednost for je ono što deli koristan nadzor od zvona koje niko ne sluša. Kartica na sekund puna nije problem; kartica puna pet minuta jeste.
Iskorišćenost kartice namerno nije među pravilima. Devedeset posto tokom inferencije je normalno stanje, ne uzbuna.
Šta ovo otkriva
Nekoliko obrazaca koje ćeš prepoznati posle nedelju dana prikupljanja:
Postepen rast TTFT kroz nedelje — promptovi rastu, jer je neko proširio sistemski prompt ili je RAG počeo da vraća više delova.
Skokovi zauzeća memorije u isto vreme svakog dana — cron zadatak koji radi u paketu.
Potrošnja niska uz visoku iskorišćenost — kartica čeka memoriju, što je za inferenciju normalno i ne popravlja se.
Zauzeće koje raste i ne vraća se — model ostaje učitan zbog keep-alive postavke, ili se nakupljaju zaostali procesi.
Praktični scenariji
Scenario 1: DCGM ne radi na potrošačkoj kartici
Uobičajeno. Koristi zamenski exporter preko nvidia-smi iz ovog teksta.
Scenario 2: Prometheus ne vidi metu
$ curl -s localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health, lastError}'
Ako je exporter na domaćinu, a Prometheus u kontejneru bez network_mode: host, 127.0.0.1 pokazuje na sam kontejner.
Scenario 3: Ollama nema metrike
Nema ih i neće ih imati. Meri spolja, kroz proxy ili skriptom.
Scenario 4: obaveštenja stižu bez prestanka
Prag je prenizak ili for prekratak. Podigni oba, pa posmatraj nedelju dana.
Scenario 5: disk se puni od metrika
$ docker exec prometheus du -sh /prometheus
Skrati retention.time ili podigni scrape_interval na trideset sekundi.
Scenario 6: grafikoni prazni posle restarta
Volumen nije podešen, pa su podaci nestali sa kontejnerom. Ista greška kao kod modela u tekstu o Dockeru.
Kratka referenca
dcgm-exporter— GPU metrike, port 9400DCGM_FI_DEV_FB_USED,FB_FREE— memorijaDCGM_FI_DEV_POWER_USAGE— pouzdaniji od iskorišćenostivllm:num_requests_waiting— premašen kapacitetvllm:gpu_cache_usage_perc— najranije upozorenjellama-server --metrics— traži izričito uključivanje- Ollama nema metrike — meri spolja
histogram_quantile(0.95, ...)— ono što korisnik osećarate(...[5m])— brzina promene brojačafor: 5mu pravilima — bez toga nadzor postane buka--storage.tsdb.retention.time— koliko se čuvanetwork_mode: host— da Prometheus vidi exportere na domaćinu
Vežba
- Pokreni exporter za karticu i potvrdi da vraća metrike na
/metrics. - Podigni Prometheus i proveri da su sve mete u stanju
up. - Napravi grafikon zauzeća VRAM-a i pusti test opterećenja iz prethodnog teksta.
- Izračunaj p95 trajanja zahteva i uporedi ga sa prosekom.
- Postavi pravilo za nedostupan servis, zaustavi ga i sačekaj da se javi.
- Prikupljaj podatke nedelju dana, pa pronađi obrazac koji se ponavlja.
Sledeći tekst u serijalu: Deploy modela na Kubernetes
Povezano:
- AI na Linux serveru — pregled celog serijala
- Benchmark lokalnog modela — prethodni tekst
- nvidia-smi i nvtop — iste vrednosti, iz terminala
- vLLM — servis sa najbogatijim metrikama
- Docker Compose — podizanje ovog skupa servisa
Comments
Post a Comment