- はじめに — 何を約束するのか
- 利用者体験を代弁する指標を選ぶ
- 飽和シグナルの読み方
- アラート設計 — 症状で起こし原因はダッシュボードで
- よくある誤検知の四つ
- おわりに — 指標を選ぶ瞬間にほとんどが決まる
- 試してみる
- シリーズ
- 参考資料
はじめに — 何を約束するのか
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が壊れる前に見えるシグナルがあります。順序があり、その順序を知っていれば原因追跡が短くなります。
第一段はKVキャッシュです。 vllm:kv_cache_usage_percが天井に張り付きます。ただしこの値自体は正常運用でも高いことがあります。警告ではなく文脈です。
第二段はプリエンプションです。 vllm:num_preemptions_totalの増加率がゼロを離れます。この時点で既に遅延は悪化しています。巻き戻されたリクエストは再計算が必要で、その分が無駄になるからです。
第三段は待ち行列です。 vllm:num_requests_waitingが伸び、vllm:num_requests_waiting_by_reasonのcapacity側が膨らみます。逆にdeferred側が膨らんでいるなら、容量ではなく設定の問題である可能性が高いです。
第四段は利用者の遅延です。 ここで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分窓のp99はリクエスト数個に左右されます。トラフィックの少ない時間帯には、リクエストが二つ長かったというだけでアラートが鳴ります。回避法は窓を伸ばし、分位数の代わりに割合ベースのSLIを使い、最小リクエスト数の条件を付けることです。
第二に、GPU利用率のアラートです。 DCGM_FI_DEV_GPU_UTILはカーネルが載っていたかだけを測るため、サービング中のGPUではほぼ常に高い値になります。ここに閾値を掛けると正常状態で鳴り続け、逆に低く設定すると本当の問題を見逃します。この指標はアラートではなく文脈用です。
第三に、KVキャッシュ使用率のアラートです。 この値が高いのはたいてい意図された状態です。キャッシュを大きく取って使うのが正常運用だからです。アラートを掛けるべきは使用率ではなく、その結果であるプリエンプションと待ち行列です。
第四に、データ欠測を障害と読むことです。 これはGPU環境で特に頻発します。DCGMのドキュメントが警告するとおり、プロファイリングメトリクスはハードウェア制約により特定のグループのみ同時に読めるため自動多重化され、収集周期を短くしすぎるとゼロが返ります。このゼロをそのまま閾値と比べると、存在しない障害が作られます。
# 十分なトラフィックがあるときだけ評価する
(
sum by (model_name) (rate(vllm:time_to_first_token_seconds_count[30m])) > 0.1
)
and (
1 - vllm:ttft_good_ratio:rate1h > 0.05
)
# ゼロと欠測を区別する
absent(vllm:num_requests_running) or (vllm:num_requests_running >= 0)
おわりに — 指標を選ぶ瞬間にほとんどが決まる
SLO作業の難しさは閾値を決めることにはありません。指標を選ぶことにあります。可用率から始めると、どれだけ精緻に消費率を計算しても、利用者の不満と無関係な数字を管理することになります。
順序をまとめるとこうです。対話型なら最初のトークン遅延を目標にし、スループットは観測にとどめる。閾値はヒストグラムに実在するバケット境界から選ぶ。アラートは症状にのみ掛け、原因指標はすべてダッシュボードへ送る。 この三行を守れば、アラート数が大きく減り、残ったアラートの信頼度が上がります。
最終回は、アラートが鳴ったときに実際に何をどの順序で確認するかを扱います。
試してみる
- SLO エラーバジェット 計算ツール — 目標割合に応じた月間の許容失敗量と消費率を計算してみてください。
- Kubernetesプレイグラウンド — 負荷とリソースを変えながら飽和がどう現れるか確かめてください。
- 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/
현재 단락 (1/79)
SLOを決めようという会議で最も頻繁に出る最初の提案は可用率です。エンドポイントが200を返す割合を99.9パーセントにしよう、という具合です。推論サービスでこの提案はほとんど役に立ちません。サーバは...