- 들어가며 — 엔드포인트를 한 번 긁어 보기
- 스케줄러 상태 — 지금 몇 개가 돌고 몇 개가 기다리나
- KV 캐시와 프리픽스 캐시
- 지연 히스토그램 — TTFT와 그 이후
- 이름이 버전마다 다르다는 문제
- 대시보드에 올릴 것과 알람으로 걸 것
- 마치며 — 애플리케이션 메트릭이 먼저다
- 직접 해보기
- 시리즈
- 참고 자료
들어가며 — 엔드포인트를 한 번 긁어 보기
앞 글에서 GPU 메트릭이 답하는 질문과 답하지 못하는 질문을 나눴습니다. DCGM은 카드가 뜨겁고 메모리가 얼마나 찼는지는 알려 주지만, 지금 요청이 몇 개 밀려 있고 사용자가 첫 글자를 얼마나 기다렸는지는 모릅니다. 그 질문은 애플리케이션만 답할 수 있습니다.
vLLM은 그 답을 프로메테우스 형식으로 내놓습니다. 엔드포인트 경로는 /metrics입니다.
kubectl -n serving port-forward svc/vllm 8000:8000
curl -s localhost:8000/metrics | grep '^vllm:' | head -30
메트릭 이름과 설정은 2026-08-12에 공식 문서·저장소에서 확인했습니다. 버전에 따라 다를 수 있으니 사용 중인 버전에서 다시 확인하세요.
스케줄러 상태 — 지금 몇 개가 돌고 몇 개가 기다리나
가장 먼저 봐야 할 두 게이지가 있습니다. vllm:num_requests_running은 문서 설명 그대로 모델 실행 배치에 들어 있는 요청 수이고, vllm:num_requests_waiting은 처리되기를 기다리는 요청 수입니다. 이 둘의 비율이 서버의 상태를 거의 다 말해 줍니다. 실행 수가 어떤 값에 붙어 더 안 올라가는데 대기 수가 계속 늘어난다면 포화입니다.
대기 이유를 세분해 주는 게이지도 있습니다. vllm:num_requests_waiting_by_reason이고 reason 라벨을 가집니다. 문서가 명시하는 값은 두 가지입니다. 스케줄링 용량을 기다리는 경우는 capacity, LoRA 예산이나 KV 전송 같은 일시적 제약으로 미뤄진 경우는 deferred입니다. 이 구분은 실무에서 유용합니다. 앞쪽이면 용량 문제이고 뒤쪽이면 설정 문제인 경우가 많습니다.
여기에 반드시 함께 봐야 할 카운터가 하나 있습니다. vllm:num_preemptions이고 설명은 엔진에서 발생한 누적 선점 횟수입니다. 선점은 KV 캐시가 모자라 실행 중인 요청을 되돌리는 동작이므로, 이 카운터가 올라가고 있다면 용량 부족이 이미 지연 시간에 영향을 주고 있다는 뜻입니다.
# 실행 중과 대기 중
sum by (model_name) (vllm:num_requests_running)
sum by (model_name) (vllm:num_requests_waiting)
# 대기 이유별
sum by (model_name, reason) (vllm:num_requests_waiting_by_reason)
# 선점 발생률
sum by (model_name) (rate(vllm:num_preemptions_total[5m]))
KV 캐시와 프리픽스 캐시
vllm:kv_cache_usage_perc는 KV 캐시 사용률이며 문서 설명은 1이 100퍼센트를 뜻한다고 못박습니다. 이 값이 천장에 붙어 있으면 대기열이 자라고 선점이 시작됩니다. 앞서 본 세 지표와 묶어서 보면 인과가 한 화면에 들어옵니다. 캐시가 차고, 선점이 생기고, 대기가 늘고, 지연이 나빠지는 순서입니다.
프리픽스 캐시는 질의 수와 적중 수를 따로 셉니다. vllm:prefix_cache_queries와 vllm:prefix_cache_hits이며, 문서 설명은 각각 질의된 토큰 수와 캐시된 토큰 수 기준이라고 밝힙니다. 즉 요청 개수가 아니라 토큰 개수입니다. 이 사실을 모르고 적중률을 계산하면 숫자가 이상해집니다. KV 커넥터를 통한 인스턴스 간 캐시 공유를 쓴다면 vllm:external_prefix_cache_queries와 vllm:external_prefix_cache_hits가 별도로 있습니다.
토큰 카운터는 vllm:prompt_tokens가 프리필 토큰 수, vllm:generation_tokens가 생성 토큰 수입니다. 캐시된 프롬프트 토큰은 vllm:prompt_tokens_cached로 따로 셉니다.
# KV 캐시 사용률
max by (model_name) (vllm:kv_cache_usage_perc)
# 프리픽스 캐시 적중률 (토큰 기준)
sum by (model_name) (rate(vllm:prefix_cache_hits_total[10m]))
/ sum by (model_name) (rate(vllm:prefix_cache_queries_total[10m]))
# 초당 생성 토큰
sum by (model_name) (rate(vllm:generation_tokens_total[5m]))
지연 히스토그램 — TTFT와 그 이후
지연 계열은 전부 히스토그램입니다. 요청 하나의 생애를 시간 순서대로 따라가면 이렇게 대응됩니다.
vllm:request_queue_time_seconds는 대기 단계에서 보낸 시간입니다. vllm:time_to_first_token_seconds는 첫 토큰까지의 시간입니다. vllm:request_prefill_time_seconds와 vllm:request_decode_time_seconds는 각각 프리필 단계와 디코드 단계에서 보낸 시간이고, vllm:request_inference_time_seconds는 실행 단계 전체입니다. vllm:inter_token_latency_seconds는 토큰 간 지연, vllm:request_time_per_output_token_seconds는 요청당 출력 토큰 하나에 걸린 시간이며, 전체는 vllm:e2e_request_latency_seconds입니다.
버킷 경계를 알아 두면 분위수를 해석할 때 유용합니다. 소스에서 확인한 vllm:time_to_first_token_seconds의 버킷은 0.001, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0, 20.0, 40.0, 80.0, 160.0, 640.0, 2560.0입니다. 1초와 2.5초 사이에 버킷이 없다는 점에 주의해야 합니다. 이 구간에서 나온 분위수 값은 보간 결과이지 실측이 아닙니다.
# TTFT 95분위
histogram_quantile(0.95,
sum by (le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m])))
# 토큰 간 지연 99분위
histogram_quantile(0.99,
sum by (le, model_name) (rate(vllm:inter_token_latency_seconds_bucket[5m])))
# 대기 시간이 전체 지연에서 차지하는 비중
sum by (model_name) (rate(vllm:request_queue_time_seconds_sum[5m]))
/ sum by (model_name) (rate(vllm:e2e_request_latency_seconds_sum[5m]))
요청 종료는 vllm:request_success 카운터로 셉니다. finished_reason 라벨이 붙어 있어 정상 종료와 길이 제한 종료를 나눠 볼 수 있습니다. 그리고 모든 시계열에는 model_name과 engine 라벨이 기본으로 붙습니다.
이름이 버전마다 다르다는 문제
여기서 반드시 짚어야 할 함정이 있습니다. 문서 두 곳이 같은 메트릭을 다르게 적습니다.
사용 안내 문서의 목록에는 vllm:prompt_tokens와 vllm:generation_tokens가 Counter로 적혀 있습니다. 반면 설계 문서 쪽에는 vllm:prompt_tokens_total과 vllm:generation_tokens_total이 나옵니다. 소스에서 확인해 보면 등록되는 이름에는 접미사가 없고, 프로메테우스 노출 형식에서 _total이 붙습니다. 그래서 쿼리에는 _total이 붙은 이름을 써야 하고, 문서 목록을 그대로 복사해 붙이면 아무 데이터도 안 나옵니다. 위의 예시 쿼리에서 카운터에만 _total을 붙인 이유가 이것입니다.
버전 차이는 이것만이 아닙니다. 설계 문서는 몇몇 메트릭을 폐기 대상으로 명시합니다. vllm:num_requests_swapped와 vllm:cpu_cache_usage_perc는 V1에서 더 이상 의미가 없다고 적혀 있고, vllm:time_in_queue_requests는 vllm:request_queue_time_seconds와 중복되는 폐기 대상입니다. 인터넷에 남아 있는 오래된 대시보드가 이 이름들을 그대로 쓰고 있는 경우가 많습니다.
반대로 조건부로만 나오는 것도 있습니다. KV 블록 수명 계열인 vllm:kv_block_lifetime_seconds, vllm:kv_block_idle_before_evict_seconds, vllm:kv_block_reuse_gap_seconds는 소스에서 관측 설정의 KV 캐시 메트릭 옵션이 켜져 있을 때만 등록됩니다. 투기적 디코딩을 쓸 때만 나오는 vllm:spec_decode_num_accepted_tokens_per_pos, KV 커넥터를 쓸 때만 나오는 NIXL 계열도 마찬가지입니다.
그러니 대시보드를 만들기 전에 반드시 자기 인스턴스의 엔드포인트를 한 번 긁어서 실제 이름 목록을 확보하는 편이 좋습니다. 확인되지 않은 이름은 대시보드에서 조용히 빈 패널이 되고, 알람 규칙에서는 영원히 발동하지 않는 규칙이 됩니다.
대시보드에 올릴 것과 알람으로 걸 것
두 가지를 구분해야 합니다. 대시보드는 원인을 찾는 곳이고, 알람은 사용자가 아프다는 것을 알리는 곳입니다. 섞으면 알람이 소음이 됩니다.
대시보드에 올릴 것은 인과 사슬 전체입니다. 위에서부터 KV 캐시 사용률, 선점률, 대기 요청 수와 이유별 분해, 실행 요청 수, 프리픽스 캐시 적중률, 초당 생성 토큰, 그리고 지연 히스토그램의 분위수들입니다. 여기에 앞 글의 GPU 지표인 SM 활동률과 프레임버퍼 사용량을 같은 시간축에 붙이면 원인 추적이 한 화면에서 끝납니다.
알람으로 걸 것은 훨씬 적습니다. 사용자가 체감하는 것 두 개면 대체로 충분합니다. TTFT 분위수가 목표를 넘는 경우와, 요청 실패율이 올라가는 경우입니다.
groups:
- name: vllm-serving
rules:
- alert: VLLMHighTTFT
expr: |
histogram_quantile(0.95,
sum by (le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m]))) > 2
for: 10m
labels:
severity: warning
annotations:
summary: 'TTFT p95 above 2s for {{ $labels.model_name }}'
- alert: VLLMQueueGrowing
expr: |
sum by (model_name) (vllm:num_requests_waiting) > 50
and sum by (model_name) (rate(vllm:num_preemptions_total[5m])) > 0
for: 15m
labels:
severity: warning
annotations:
summary: 'Queue growing with preemptions for {{ $labels.model_name }}'
두 규칙 모두 for 절이 길다는 점에 주목할 만합니다. 추론 서버는 요청 하나의 길이가 크게 다르기 때문에 짧은 창에서는 정상 상태에서도 분위수가 크게 흔들립니다. 알람 설계의 세부는 다음 글에서 이어서 다룹니다.
마치며 — 애플리케이션 메트릭이 먼저다
GPU 대시보드를 아무리 잘 만들어도 사용자가 느린지는 알 수 없습니다. GPU가 바쁜 것과 사용자가 기다리는 것은 서로 다른 사실이고, 심지어 반대 방향으로 움직일 때도 있습니다. 카드가 한가한데 대기열이 긴 경우는 대개 KV 캐시나 배치 설정 문제입니다.
그래서 순서는 이렇습니다. 먼저 애플리케이션 메트릭으로 사용자가 아픈지를 판단하고, 그다음 GPU 메트릭으로 왜 아픈지를 찾습니다. 반대 순서로 하면 초록색 대시보드를 보면서 사용자 불만을 이해하지 못하는 상태에 빠집니다.
다음 글에서는 이 두 층의 지표로 실제 약속을 만드는 방법, 즉 SLO와 알람 설계를 다룹니다.
직접 해보기
- SLO 에러 버짓 계산기 — TTFT 목표를 정하고 한 달치 여유가 얼마나 되는지 계산해 보세요.
- LLM GPU 메모리(VRAM) 계산기 — KV 캐시가 얼마나 필요한지 먼저 추정해 보세요.
- kubectl 명령어 찾기 — 포트 포워딩과 로그 확인 명령을 상황별로 찾아보세요.
시리즈
- 이전 글: DCGM Exporter — GPU 이용률의 함정
- 다음 글: GPU 서빙 SLO와 알람 설계
참고 자료
- vLLM Metrics 사용 문서: https://docs.vllm.ai/en/latest/usage/metrics.html
- vLLM Metrics 설계 문서: https://docs.vllm.ai/en/latest/design/metrics.html
- vLLM 메트릭 로거 소스: https://github.com/vllm-project/vllm/blob/main/vllm/v1/metrics/loggers.py
- Prometheus histogram_quantile: https://prometheus.io/docs/prometheus/latest/querying/functions/
현재 단락 (1/71)
앞 글에서 GPU 메트릭이 답하는 질문과 답하지 못하는 질문을 나눴습니다. DCGM은 카드가 뜨겁고 메모리가 얼마나 찼는지는 알려 주지만, 지금 요청이 몇 개 밀려 있고 사용자가 ...