- Published on
GPU 서빙 SLO와 알람 설계 — 무엇을 약속하고 무엇을 깨울 것인가
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 무엇을 약속할 것인가
- 사용자 경험을 대변하는 지표 고르기
- 포화 신호 읽는 법
- 알람 설계 — 증상에 걸고 원인은 대시보드로
- 흔한 오탐 네 가지
- 마치며 — 지표를 고르는 순간에 대부분이 결정된다
- 직접 해보기
- 시리즈
- 참고 자료
들어가며 — 무엇을 약속할 것인가
SLO를 정하자는 회의에서 가장 자주 나오는 첫 제안은 가용률입니다. 엔드포인트가 200을 돌려주는 비율을 99.9퍼센트로 하자는 식입니다. 추론 서비스에서 이 제안은 거의 쓸모가 없습니다. 서버는 200을 돌려주고 있는데 첫 글자가 30초 뒤에 나오면 사용자는 그것을 장애라고 부릅니다.
GPU 서빙에서 약속할 만한 것은 응답의 존재가 아니라 응답의 속도입니다. 그리고 속도를 재는 방법이 두 가지 있는데, 이 둘은 서로를 잡아먹습니다.
메트릭 이름과 설정은 2026-08-12에 공식 문서·저장소에서 확인했습니다. 버전에 따라 다를 수 있으니 사용 중인 버전에서 다시 확인하세요.
사용자 경험을 대변하는 지표 고르기
후보는 크게 셋입니다. 첫 토큰 지연, 토큰 간 지연, 그리고 처리량입니다.
대화형 서비스라면 첫 토큰 지연이 답입니다. 사람이 화면을 보며 기다리는 시간이기 때문입니다. vLLM에서는 vllm:time_to_first_token_seconds가 이 값을 잽니다. 그다음으로 중요한 것이 vllm:inter_token_latency_seconds입니다. 첫 글자가 빨리 나와도 그 뒤가 끊기면 체감은 나쁩니다.
배치나 오프라인 작업이라면 처리량이 답입니다. vllm:generation_tokens_total의 증가율이 그것이고, 이때 개별 요청의 지연은 크게 중요하지 않습니다. 사용자가 없기 때문입니다.
문제는 두 목표를 동시에 세울 때 생깁니다. 처리량을 올리는 방법은 배치를 키우는 것이고, 배치를 키우면 개별 요청이 배치에 실릴 때까지 기다리는 시간이 늘어납니다. 그래서 처리량 목표와 첫 토큰 지연 목표를 둘 다 공격적으로 잡으면 둘 다 못 지킵니다. 한쪽을 목표로 삼고 다른 쪽은 관측만 하는 것이 정직한 설계입니다.
지표를 골랐다면 SLI를 정의합니다. 여기서 실무적으로 중요한 선택이 하나 있습니다. 분위수를 SLI로 쓰지 말고, 버킷 경계를 넘긴 요청의 비율을 쓰는 편이 낫습니다. 이유는 앞 글에서 본 버킷 목록에 있습니다. vllm:time_to_first_token_seconds의 버킷 경계에는 1.0이 있고 그다음이 2.5입니다. 그 사이에는 경계가 없으므로, 분위수 함수가 1.4초라고 답한다면 그것은 보간 결과이지 실측이 아닙니다. 반면 1.0초 이하 요청의 비율은 히스토그램에서 직접 읽히는 값입니다.
# SLI: 첫 토큰이 1초 안에 나온 요청의 비율
sum by (model_name) (rate(vllm:time_to_first_token_seconds_bucket{le="1.0"}[5m]))
/ sum by (model_name) (rate(vllm:time_to_first_token_seconds_count[5m]))
# 참고: 분위수는 대시보드용으로만 쓰고 SLI로는 쓰지 않는다
histogram_quantile(0.95,
sum by (le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m])))
임계값을 정할 때는 반드시 실제 버킷 경계 중에서 고르십시오. 0.5, 0.75, 1.0, 2.5는 존재하는 경계이고, 1.5나 2.0은 존재하지 않습니다.
포화 신호 읽는 법
SLO가 깨지기 전에 미리 보이는 신호가 있습니다. 순서가 있고, 그 순서를 알면 원인 추적이 짧아집니다.
1단계는 KV 캐시입니다. vllm:kv_cache_usage_perc가 천장에 붙습니다. 다만 이 값 자체는 정상 운영 상태에서도 높을 수 있습니다. 경고가 아니라 맥락입니다.
2단계는 선점입니다. vllm:num_preemptions_total의 증가율이 0을 벗어납니다. 이 시점부터는 이미 지연이 나빠지고 있습니다. 되돌려진 요청은 다시 계산되어야 하므로 낭비가 발생합니다.
3단계는 대기열입니다. vllm:num_requests_waiting이 늘고, vllm:num_requests_waiting_by_reason의 capacity 쪽이 커집니다. 반면 deferred 쪽이 커지고 있다면 용량이 아니라 설정 문제일 가능성이 높습니다.
4단계는 사용자 지연입니다. 이제 SLI가 목표 아래로 떨어집니다.
GPU 쪽 지표는 이 사슬의 원인을 좁히는 데 씁니다. 대기열이 긴데 DCGM_FI_PROF_SM_ACTIVE가 낮다면 GPU가 놀고 있다는 뜻이고, 원인은 연산이 아니라 메모리나 스케줄링에 있습니다. DCGM_FI_DEV_FB_USED가 천장이라면 모델이나 캐시 설정을 줄여야 합니다.
# 포화 사슬을 한 화면에
max by (model_name) (vllm:kv_cache_usage_perc)
sum by (model_name) (rate(vllm:num_preemptions_total[5m]))
sum by (model_name, reason) (vllm:num_requests_waiting_by_reason)
# GPU는 놀고 있는데 대기열이 긴 상황
(sum by (pod) (vllm:num_requests_waiting) > 10)
and on(pod) (avg by (pod) (DCGM_FI_PROF_SM_ACTIVE) < 0.3)
알람 설계 — 증상에 걸고 원인은 대시보드로
원칙은 하나입니다. 사람을 깨우는 알람은 사용자가 아프다는 사실에만 걸고, 왜 아픈지는 대시보드에서 찾습니다. 원인 지표마다 알람을 걸면 한 번의 장애에 알람 여덟 개가 울리고, 그중 어느 것이 진짜인지 판단하는 데 시간이 갑니다.
에러 버짓 소진율을 쓰면 심각도를 자연스럽게 나눌 수 있습니다. 긴 창과 짧은 창을 함께 요구해서, 지속적인 장애만 잡고 순간적인 흔들림은 흘려보내는 방식입니다.
groups:
- name: vllm-slo
rules:
# SLI를 기록 규칙으로 먼저 고정한다
- record: vllm:ttft_good_ratio:rate5m
expr: |
sum by (model_name) (rate(vllm:time_to_first_token_seconds_bucket{le="1.0"}[5m]))
/ sum by (model_name) (rate(vllm:time_to_first_token_seconds_count[5m]))
- record: vllm:ttft_good_ratio:rate1h
expr: |
sum by (model_name) (rate(vllm:time_to_first_token_seconds_bucket{le="1.0"}[1h]))
/ sum by (model_name) (rate(vllm:time_to_first_token_seconds_count[1h]))
# 목표 99%, 소진율 14.4배 = 1시간 창과 5분 창을 모두 넘길 때만
- alert: VLLMTTFTBudgetBurnFast
expr: |
(1 - vllm:ttft_good_ratio:rate1h) > (14.4 * 0.01)
and (1 - vllm:ttft_good_ratio:rate5m) > (14.4 * 0.01)
for: 2m
labels:
severity: page
annotations:
summary: 'TTFT error budget burning fast for {{ $labels.model_name }}'
# 느린 소진은 깨우지 않고 티켓으로
- alert: VLLMTTFTBudgetBurnSlow
expr: (1 - vllm:ttft_good_ratio:rate1h) > (3 * 0.01)
for: 1h
labels:
severity: ticket
annotations:
summary: 'TTFT error budget burning slowly for {{ $labels.model_name }}'
여기서 세 가지가 의도적입니다. SLI를 기록 규칙으로 먼저 고정한 것은 알람과 대시보드가 같은 정의를 쓰게 하기 위함입니다. 빠른 소진 알람이 두 창을 모두 요구하는 것은 이미 회복된 장애로 깨우지 않기 위함입니다. 그리고 느린 소진은 심각도를 낮춰 티켓으로 보냅니다.
흔한 오탐 네 가지
GPU 서빙에서 반복적으로 나오는 오탐이 있습니다.
첫째, 짧은 창의 분위수입니다. 추론 요청은 길이 분포가 넓어서 5분 창의 99분위는 요청 몇 개에 좌우됩니다. 트래픽이 적은 시간대에는 요청 두 개가 길다는 이유만으로 알람이 울립니다. 회피법은 창을 늘리고, 분위수 대신 비율 기반 SLI를 쓰고, 최소 요청 수 조건을 붙이는 것입니다.
둘째, GPU 이용률 알람입니다. DCGM_FI_DEV_GPU_UTIL은 커널이 얹혀 있었는지만 재기 때문에, 서빙 중인 GPU에서는 거의 항상 높습니다. 여기에 임계값을 걸면 정상 상태에서 계속 울리거나, 반대로 낮게 잡으면 진짜 문제를 놓칩니다. 이 지표는 알람이 아니라 맥락용입니다.
셋째, KV 캐시 사용률 알람입니다. 이 값이 높은 것은 대체로 의도된 상태입니다. 캐시를 크게 잡아 두고 쓰는 것이 정상 운영이기 때문입니다. 알람을 걸어야 하는 것은 사용률이 아니라 그 결과인 선점과 대기열입니다.
넷째, 데이터 없음을 장애로 읽는 경우입니다. 이것은 GPU 환경에서 특히 자주 발생합니다. DCGM 문서가 경고하듯 프로파일링 메트릭은 하드웨어 제약으로 특정 그룹만 함께 읽을 수 있어 자동 다중화되며, 수집 주기를 너무 짧게 잡으면 0이 반환됩니다. 이 0을 그대로 임계값과 비교하면 없는 장애가 만들어집니다.
# 요청이 충분히 있을 때만 평가하도록 조건을 붙인다
(
sum by (model_name) (rate(vllm:time_to_first_token_seconds_count[30m])) > 0.1
)
and (
1 - vllm:ttft_good_ratio:rate1h > 0.05
)
# 0과 결측을 구분한다
absent(vllm:num_requests_running) or (vllm:num_requests_running >= 0)
마치며 — 지표를 고르는 순간에 대부분이 결정된다
SLO 작업의 어려움은 임계값을 정하는 데 있지 않습니다. 지표를 고르는 데 있습니다. 가용률로 시작하면 아무리 정교하게 소진율을 계산해도 사용자 불만과 무관한 숫자를 관리하게 됩니다.
정리하면 순서는 이렇습니다. 대화형이면 첫 토큰 지연을 목표로 삼고 처리량은 관측만 합니다. 임계값은 히스토그램에 실제로 존재하는 버킷 경계에서 고릅니다. 알람은 증상에만 걸고, 원인 지표는 전부 대시보드로 보냅니다. 이 세 줄을 지키면 알람 수가 크게 줄고 남은 알람의 신뢰도가 올라갑니다.
다음 글은 마지막으로, 알람이 울렸을 때 실제로 무엇을 어떤 순서로 확인할지를 다룹니다.
직접 해보기
- SLO 에러 버짓 계산기 — 목표 비율에 따른 월간 허용 실패량과 소진율을 계산해 보세요.
- 쿠버네티스 놀이터 — 부하와 리소스를 바꿔 가며 포화가 어떻게 나타나는지 확인해 보세요.
- LLM GPU 메모리(VRAM) 계산기 — 용량 계획의 출발점을 먼저 잡아 보세요.
시리즈
- 이전 글: vLLM 메트릭
- 다음 글: GPU 장애 진단 플레이북
참고 자료
- vLLM Metrics 사용 문서: https://docs.vllm.ai/en/latest/usage/metrics.html
- vLLM 메트릭 로거 소스: https://github.com/vllm-project/vllm/blob/main/vllm/v1/metrics/loggers.py
- DCGM Feature Overview: https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html
- Prometheus Alerting Rules: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Google SRE Workbook, Alerting on SLOs: https://sre.google/workbook/alerting-on-slos/