- はじめに — エンドポイントを一度叩いてみる
- スケジューラ状態 — 今いくつ回っていくつ待っているか
- KVキャッシュとプレフィックスキャッシュ
- 遅延ヒストグラム — TTFTとその後
- 名前がバージョンごとに違うという問題
- ダッシュボードに載せるものとアラートにするもの
- おわりに — アプリケーションメトリクスが先
- 試してみる
- シリーズ
- 参考資料
はじめに — エンドポイントを一度叩いてみる
前回はGPUメトリクスが答える問いと答えられない問いを分けました。DCGMはカードが熱いこととメモリがどれだけ埋まっているかは教えますが、今リクエストが何件詰まっていて利用者が最初の一文字をどれだけ待ったかは知りません。その問いはアプリケーションだけが答えられます。
vLLMはその答えをPrometheus形式で出します。エンドポイントのパスは/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のp95
histogram_quantile(0.95,
sum by (le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m])))
# トークン間遅延のp99
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が出てきます。ソースで確認すると登録される名前に接尾辞は無く、Prometheusの公開形式で_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サービングの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はカードが熱いこととメモリがどれだけ埋まっているかは教えますが、今リクエストが何件詰まっていて利用者が最初の一文字をどれだけ待...