ステータス: ドラフト(2026-10-17)、一部実測済み。 基準バージョン: production-stack vllm-stack-0.1.13(コミット e8cb4959ebfa)、vLLM v0.31.0(コミット db9527a46873)、LMCache v0.5.6(コミット 3e11e10812b4)、llm-d v0.10.0(コミット f22bbaf3173a)、llm-d-router v0.11.0(コミット a5cbe600ebad)。小規模なクラスターにはvLLMが常時起動していないため、本日は新規Podの起動は行わず、実測値は2026-10-16にGPUノード1台(8GBクラスのノートPC用GPU)上で使い捨てPodとして測定した生データを、2026-10-17に再集計して引用しています。 作成ルール: 実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表記します。
目次
- 1. 1行まとめ: 厨房は1つ、ホールマネージャーと冷蔵庫は取り替え可能
- 2. 構成図
- 3. コードマップ
- 4. コアパスを辿る
- 5. ユーザーコントリビューションとのつながり
- 6. 実測
- 7. どこから読むべきか
- 8. 初めてのコントリビューションにおすすめの場所
- 9. 制限事項
- 参考
1. 1行まとめ: 厨房は1つ、ホールマネージャーと冷蔵庫は取り替え可能
vLLMは、1枚(または複数枚)のGPU上でモデルを高速に実行する 厨房です。厨房を複数稼働させる場合、注文をどの厨房に割り振るかを決める ホールマネージャー, 厨房ごとにバラバラに管理されている下準備(KVキャッシュ)を共有する 冷蔵庫, そして厨房がどれほど忙しいかを通知する 注文ボード(メトリクス) が必要です。これら3つのプロジェクトは、厨房(vLLM)自体はそのままで、その周辺をそれぞれ異なる方式で満たします。
| コンポーネント | 役割 | 実行環境 |
|---|---|---|
production-stack ルーター(vllm_router) |
OpenAI互換のリクエストを受け付け、エンジンPodを1つ選んでそのまま転送する | Deployment 1つ、Python・FastAPIプロセス |
| production-stack Helmチャート | エンジンDeployment・ルーター・ServiceMonitor・Grafanaダッシュボードを一括インストール | helm install 1回 |
vLLM KVコネクタ(KVConnectorBase_V1) |
エンジンスケジューラー・ワーカーが外部KVストレージを呼び出すインターフェース | エンジンプロセス内 |
| LMCache | コネクタの実装。KVを256トークンのチャンクに分割し、CPU・ディスク・リモートに保存・取得する | エンジンプロセス内、または別サーバー(MPモード) |
| llm-d Router(プロキシ + EPP) | プロキシは接続のみを担当し、EPPがPodを選択してアドレスを返却する | Envoy系ゲートウェイ + EPP Deployment(Go) |
vLLM /metrics |
キュー長・KV使用率・プレフィックスキャッシュヒット率などをPrometheus形式で出力する | エンジンAPIサーバー |
KVキャッシュがなぜ必要で、LMCacheが何を節約するのかについては、 LLM KVキャッシュ解剖 シリーズで解説しています。本記事では原理の解説を省き、 どのファイルのどの関数がその処理を行っているのかに焦点を当てます。
2. 構成図
図の読み方です。
- フロントエンドは 2つのうちいずれかです。左側の production-stack ルーターはリクエストボディを直接受け取ってエンジンにプロキシします。右側の llm-d では、Envoy などのプロキシがボディを保持し、EPP は ext-proc コールバックを通じて「このPodへ送信せよ」というアドレスのみを返却します。
- 両方のフロントエンドとも、エンジンの
/metricsを定期的に読み取ってPodの負荷状態を把握します。llm-d EPP はそれに加え、エンジンが ZMQ で送信する KV イベント(点線)を購読することで、「どのPodがどのブロックを保持しているか」をリアルタイムに把握します。 - エンジン内部では、GPU の 16 トークンブロックが1次キャッシュとなります。不足した場合は KV コネクタが LMCache を呼び出し、LMCache は上位層から順に検索を行います。
- LMCache をエンジン外の別サーバーとして起動する MP モードでは、一番下のボックス全体が別のプロセスになります。
3. コードマップ
まず、「どのリポジトリにコードが存在するか」を整理します。 llm-d/llm-d リポジトリにはサービスコードは存在しません。 v0.10.0 タグのファイル 1,315 個のうち、YAML が 834 個、Markdown が 249 個であり、Go ファイルは 0 個、Python は CI・ツール・サンプルスクリプトが 33 個のみです。デプロイガイド・Helm 値・設計文書を集めた場所であり、実際のコンポーネントは COMPONENTS.md の Roster テーブルが指し示す個別のリポジトリに存在します。EPP は llm-d-router です。以前の名前 llm-d-inference-scheduler のアドレスは現在 llm-d-router に転送されます(2026-10-17 HTTP 301 確認)。llm-d v0.10.0 の guides/env.sh が EPP イメージ v0.11.0 と Gateway API Inference Extension (GAIE) v1.5.0 CRD を固定しているため、本記事では llm-d-router v0.11.0 を別途取得して読み込みました。InferencePool などの API タイプは GAIE モジュールから取得します。
| パス | 内容 | 最初に読むファイル |
|---|---|---|
production-stack src/vllm_router/ |
ルーター本体。リクエスト受信・サービスディスカバリー・ルーティング・統計 | app.py の initialize_all |
production-stack helm/ |
チャート。エンジン Deployment、ルーター、ServiceMonitor、ダッシュボード ConfigMap | templates/deployment-vllm-multi.yaml |
production-stack helm/dashboards/ |
Grafanaダッシュボード JSON 3種類(vLLM・モデル別・LMCache) | vllm-dashboard.json |
vLLM vllm/v1/metrics/ |
Prometheus指標の定義と記録 | loggers.py の PrometheusStatLogger |
vLLM vllm/distributed/kv_transfer/kv_connector/v1/ |
KVコネクタインターフェースと各種実装(LMCache・NIXL・Mooncakeなど) | base.py |
LMCache lmcache/integration/vllm/ |
vLLMコネクタの実装 | vllm_v1_adapter.py |
LMCache lmcache/v1/ |
キャッシュエンジン・トークンチャンク・ストレージバックエンド・MPサーバー | cache_engine.py, storage_backend/__init__.py |
llm-d guides/, docs/architecture/ |
デプロイレシピと設計文書。コードなし | docs/architecture/core/router/README.md |
llm-d-router pkg/epp/ |
EPP: ext-procサーバー、スケジューラー、フィルター・スコアラー・ピッカープラグイン | scheduling/scheduler.go |
llm-d-router pkg/kvevents/, pkg/kvcache/, pkg/sidecar/ |
KVイベント購読・ブロックインデックス、P/Dルーティングサイドカ | kvevents/README.md |
4. コアパスを辿る
4.1 production-stack: リクエストがエンジンに到達するまで
- ルーターは起動時に
initialize_allにおいて、サービスディスカバリー、エンジン統計コレクター、リクエスト統計、ルーティングロジックを順番にシングルトンとして生成します。ルーティングロジックが前者の3つが生成したテーブルを参照するため、この順序になっています。 - エンジンの一覧は
K8sPodIPServiceDiscovery._watch_enginesが作成します。ラベルスリーターでPodを watch し、準備完了状態のPodごとに_get_model_namesを通じて/v1/modelsを問い合せ、「このIPはどのモデルを提供するのか」を記録します。ServiceではなくPod IPに直接接続する理由は、ルーターがPodを選択する必要がある一方、Serviceはその選択を kube-proxy に委譲してしまうためです。 - リクエストは
route_chat_completionを経由してroute_general_requestに入ります。ここでボディ内のmodelにより候補エンジンを絞り込み、ルーティングロジックのroute_requestを呼び出してURLを1つ取得した後、process_requestを通じてレスポンスをストリーミングします。失敗した場合はそのURLを除外して再選択するリトライループもこの関数内に含まれています。 - ルーティングロジックは
RoutingLogic列挙型の8種類です。基本軸はRoundRobinRouter, セッションキーをハッシュリングに登録するSessionRouter, プロンプト文字列を128文字のチャンクにハッシュ化してトライ木に格納するPrefixAwareRouter(HashTrie)、そしてLMCacheコントローラーに「このトークンを最も長く保持しているインスタンス」を問い合わせるKvawareRouterです。
2つのプレフィックスルーターの違いは、本プロジェクトの設計上の試行錯誤をよく表しています。 PrefixAwareRouter はルーターが 自身が送信した履歴のみに基づいて推測します。クラスのコメントに「プレフィックスキャッシュの退避はないと仮定する」と明記されている通り、エンジン側がブロックを削除してもルーター側はそれを把握できません。その代わり、外部依存がありません。 KvawareRouter は LMCache コントローラーに対して 実際に保存されている場所を問い合わせるため正確ですが、ルーターのコンテナイメージに LMCache パッケージを含める必要があります。デフォルトのルーターイメージは pyproject.toml のオプショナル依存 lmcache==0.3.11 を組み込んでおり、エンジン側とはバージョンが異なっていますが、両者のコントローラーメッセージの互換性が保たれているかは確認できていません(未確認)。
Helmチャートは、このルーターの引数をそのまま展開します。 deployment-router.yaml が routerSpec.routingLogic を --routing-logic に渡し、 deployment-vllm-multi.yaml は modelSpec ごとに vllm serve コマンドを組み立てます。LMCache を有効にすると、 --kv-transfer-config に LMCacheConnectorV1 を、サイズは LMCACHE_* 環境変数として渡します。エンジンの引数が文字列として組み立てられるため、vLLM がフラグを削除すると、チャートやチュートリアルは古い状態のまま放置され、実行時にエラーとなります。第5節でのコントリビューションはまさにこの点を対象としています。
4.2 vLLM ↔ LMCache: コネクタというインターフェース
vLLM v1 は外部 KV ストレージを KVConnectorBase_V1 として抽象化します。このクラスは、同一のオブジェクトが 2つの役割でインスタンス化されるという点が核心です。
- スケジューラー側:
get_num_new_matched_tokensが「GPUには存在しないが外部から取得可能なトークン数」を返答します。スケジューラーはschedule内の(949行目)この値を確認してその分の計算をスキップすることを決定した後、update_state_after_allocとbuild_connector_metaを用いて「何をロードし、何を保存するか」のメタデータを生成します。 - ワーカー側: そのメタデータを受け取り、
start_load_kvでロードを開始し、レイヤーごとにwait_for_layer_load·save_kv_layerを呼び出し、最後にwait_for_saveで保存を完了させます。
なぜこれら2つに分割されているのでしょうか。スケジューラーはGPUテンソルを操作せずバッチ作成のみを行い、ワーカーはGPUテンソルを直接扱います。両者は異なるプロセスで実行されている可能性もあります。「どれだけロードできるか」はバッチを作成する前に把握する必要があり、実際のコピー処理は順伝播と並行して実行する必要があるため、判断と実行をメタデータの塊で結合した構造になっています。レイヤー単位の関数は、あるレイヤーを計算している間に次のレイヤーの KV を読み込んでコピー時間を隠蔽するためです。
vLLM ツリー内の LMCacheConnectorV1 は薄いラッパーです。
use_native = vllm_config.kv_transfer_config.get_from_extra_config(
"use_native", False
)
if use_native:
from vllm.distributed.kv_transfer.kv_connector.v1 import lmcache_integration
_adapter = lmcache_integration.vllm_v1_adapter
cls = _adapter.LMCacheConnectorV1Impl
else:
from lmcache.integration.vllm.vllm_v1_adapter import (
LMCacheConnectorV1Impl as LMCacheConnectorLatestImpl,
)
cls = LMCacheConnectorLatestImpl
self._lmcache_engine = cls(vllm_config, role, self)
出典: vllm v0.31.0 vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py(88–108行目、ログ行省略)、Apache-2.0
デフォルト値は インストールされた LMCache パッケージ側の実装です。実装を vLLM に組み込んでしまうと、LMCache が修正されるたびに vLLM のリリースを待つ必要があるため、インターフェースは vLLM が、実装は LMCache が持ちます。MP コネクターも lmcache_mp_connector.py で LMCache 側の実装を先に探し、ない場合のみツリー内のコピーを使用します。
LMCache 側の実際の実装は LMCacheConnectorV1Impl です。検索は get_num_new_matched_tokens から始まり、lookup クライアントを経由し、トークンは ChunkedTokenDatabase が _chunk_tokens に分割し、 _prefix_hash で前のチャンクのハッシュをつなぎながらキーを作成します。チャンクのデフォルト値は config.py の 256 トークンで、vLLM GPU ブロック(実測 16 トークン)の 16 倍です。リモートストレージのオブジェクト数とラウンドトリップを減らすためにより大きなチャンクが必要だからであり、その代償として 256 トークンより短い一致のプレフィックスは LMCache でヒットしません。
ストレージ階層は CreateStorageBackends が OrderedDict に投入する 順序がそのまま検索順序になります(PD、LocalCPU、P2P、NIXL、LocalDisk、GDS、Maru、リモートのうち有効なもの)。 StorageManager.batched_get は手前から探し、リモートで見つかったものは LocalCPU に書き戻します。リモート側は storage_backend/connector/ の 17 個のアダプター(Redis、Valkey、S3、ファイルシステム、Mooncake など)に分かれています。
MP モードは、この階層全体をエンジンの外部に取り出したものです。 MPCacheServer が複数のエンジンモジュールを組み立て、 MPServerConfig のデフォルト転送は ZMQ(ポート 5555)、チャンクは同じく 256、ハッシュは blake3 です。エンジンが再起動してもキャッシュが残り、複数のエンジンで共有できるようにするためです。
4.3 llm-d: EPP がポッドを選択する方法
llm-d Router はプロキシと EPP に分かれています。プロキシがリクエストを保留し、ext-proc ストリームで EPP に問い合わせると、EPP の StreamingServer.Process が受け取ってスケジューラを実行し、 generateHeaders が x-gateway-destination-endpoint ヘッダーに選択したポッドのアドレスを載せて返送します。ボディの転送、TLS、リトライを検証済みのプロキシに任せ、LLM 特化の判断のみを分離した構造であり、両方を 1 つの Python プロセスで行う production-stack ルーターとは正反対です。
スケジューラ Scheduler.Schedule は「プロファイルハンドラーが選択したプロファイルを実行し、結果に基づいてさらに選択する」ループです。1 つのプロファイルは SchedulerProfile.Run 内でフィルター → 重み付きスコアラー(runScorerPlugins) → ピッカーを経過します。プロファイル複数回実行できるようにした理由が P/D 分離です。 disagg ハンドラーの Pick はデコードプロファイルで D ポッドを先に選択し、必要に応じてプリフィルプロファイルで P ポッドを選択します。実際の P→D フローはデコードポッドの横にあるルーティングサイドカー(llm-d-router pkg/sidecar/proxy/)が担当し、P に max_tokens=1 で先に送信し、受け取った KV 転送情報を付与して D に渡します。P/D 分離自体の原理は NVIDIA Dynamo 編にあります。
スコアラーが読み取る数値は 2 つのルートから入ってきます。1 つ目はエンジン /metrics をスクレイピングする datalayer ソースで、 parseMetrics が Prometheus のテキストをパースし、エンジン種別ごとのラベルがどの名前をどの属性にマッピングするかを決定します。
var defaultEngineConfigs = []engineConfigParams{
{
Name: "vllm",
QueuedRequestsSpec: "vllm:num_requests_waiting",
RunningRequestsSpec: "vllm:num_requests_running",
KVUsageSpec: "vllm:kv_cache_usage_perc",
LoRASpec: "vllm:lora_requests_info",
CacheInfoSpec: "vllm:cache_config_info",
},
出所: llm-d-router v0.11.0 pkg/epp/framework/plugins/datalayer/extractor/metrics/factories.go(91〜99行目)、Apache-2.0
同じテーブルに SGLang、TensorRT-LLM、Triton の名前が並んでおり、ポッドラベル llm-d.ai/engine-type でテーブルを選択します。エンジンが名前を変更しても、このテーブルの 1 箇所だけを修正すればよいように設計されています。この値を受け取って KVCacheUtilizationScorer.Score は 1 − KV 사용률 をスコアとして付与します。
もう 1 つは KV イベントです。vLLM はブロックの保存やエビクション時に BlockStored のようなイベントを ZmqEventPublisher で発行し、EPP の precise-prefix-cache プラグインは ensureSubscriber でポッドごとにサブスクライブを開きます。受け取ったメッセージは VLLMAdapter.ParseMessage がデコードしてブロックインデックスに追加、または削除します。production-stack PrefixAwareRouter の「送信履歴による推測」をエンジンが通知する事実ベースに置き換えたものであり、その代償としてエンジンで --kv-events-config を有効にし、ZMQ ポートを開く必要があります。
4.4 メトリクスパス: 定義の 1 行が 3 箇所のクエリになる
図の PS は production-stack です。出発点は PrometheusStatLogger コンストラクターで、 vllm: 指標が 1 行ずつ定義されています。KV 使用率は vllm:kv_cache_usage_perc、プレフィックスキャッシュは vllm:prefix_cache_queries·vllm:prefix_cache_hits、トークン間レイテンシは vllm:inter_token_latency_seconds です。ソースのカウンター名には _total がありませんが、 prometheus_client がエクスポートする際に _total と _created を付与するため、クエリを使用する側は vllm:prefix_cache_hits_total として読み取る必要があります。この違いが名前の混乱の原因の 1 つです。このレジストリは attach_router が /metrics パスにアタッチします。
vLLM はプレフィックスキャッシュを「ヒット率ゲージ」ではなく「検索・ヒットの 2 つのカウンター」として出力します。ユーザーが rate(hits[5m]) / rate(queries[5m]) のように任意の期間で比率を算出できるようにするためです(docs/design/metrics.md)。指標を削除する際は、X.Y の非推奨予告、X.Y+1 での非表示(--show-hidden-metrics-for-version で復活)、X.Y+2 での削除ルール(docs/usage/metrics.md)に従います。エンジンのルールは明確ですが、 この名前を読み取る側が別のリポジトリにあることが問題です。
最初の消費者である production-stack ルーターのスクレイパー EngineStatsScraper は、デフォルトで 30 秒ごとにエンジンごとに /metrics を GET し、 from_vllm_scrape で名前を正確に一致させて取得します。
for family in text_string_to_metric_families(vllm_scrape):
for sample in family.samples:
if sample.name == "vllm:num_requests_running":
num_running_reqs = sample.value
elif sample.name == "vllm:num_requests_waiting":
num_queuing_reqs = sample.value
elif sample.name == "vllm:gpu_prefix_cache_hit_rate":
gpu_prefix_cache_hit_rate = sample.value
elif sample.name == "vllm:gpu_prefix_cache_hits_total":
gpu_prefix_cache_hits_total = sample.value
elif sample.name == "vllm:gpu_prefix_cache_queries_total":
gpu_prefix_cache_queries_total = sample.value
elif sample.name == "vllm:gpu_cache_usage_perc":
gpu_cache_usage_perc = sample.value
出所: production-stack vllm-stack-0.1.13 src/vllm_router/stats/engine_stats.py(63〜76行目)、Apache-2.0
一致する名前がない場合、例外もログも出ずに 0 が残ります。ルーターはこの値を metrics_router.py の /metrics として のような vllm: プレフィックスを付けたゲージ(server ラベル)で再度出力します。そのため、同じ名前がエンジンとルーターの 2 つのソースからラベル違いで混在し、デフォルトダッシュボードのプレフィックスキャッシュパネルは endpoint="service-port" でフィルタリングします。 service-port は チャートがエンジン Service ポートに付与する名前であり、ルーター Service ポート名は router-sport であるため、エンジンからの時系列のみを残します。
2番目の消費者は Prometheus と Grafana です。チャートの ServiceMonitor がエンジンとルーターをスクレイピングし、ダッシュボード JSON の PromQL が名前を文字列として保持しています。3番目の消費者は 4.3 節の EPP ラベルです。3つすべてにおいて、名前が間違っているとエラーにならずに静かに 0 や「No data」になるだけです。
5. ユーザーコントリビューションとの繋がり
著者の 4 件の PR はすべて「vLLM が削除した名前を他のリポジトリがまだ呼び出している」という同じパターンです(状態は 2026-10-17 時点)。この記事では v0.31.0 タグを再びチェックアウトし、「現在は存在せず、代わりにこれが存在する」ことを確認しました。
| PR | 修正箇所 | 対応する vLLM v0.31.0 の行 | 状態 |
|---|---|---|---|
| vllm-project/production-stack#1147 | helm/dashboards/vllm-dashboard.json:826 「GPU KV Cache Hit Rate」パネルの vllm:gpu_prefix_cache_* |
loggers.py:600 vllm:prefix_cache_queries, loggers.py:611 vllm:prefix_cache_hits |
レビュー待ち(自動レビューは「修正点なし」) |
| vllm-project/production-stack#1148 | tutorials/23-whisper-api-transcription.md:24 の --task transcription |
--task は存在せず、その位置を arg_utils.py:930 の --runner·--convert が引き継ぐ |
レビュー待ち |
| LMCache/LMCache#5814 | ドキュメント・ベンチマーク・サンプル 12 ファイル(v0.5.6 タグの 20 箇所)の --disable-log-requests |
arg_utils.py:3141 には --enable-log-requests のみが存在し、薬の BooleanOptionalAction であるため、対義語は --no-enable-log-requests |
レビュー待ち(base dev) |
| llm-d/llm-d#2835 | ドキュメントの vllm:time_per_output_token_seconds, vllm:gpu_prefix_cache_hit_rate |
loggers.py:832 vllm:inter_token_latency_seconds、ヒット率は 600・611 行目の 2 つのカウンターの比率 |
レビュー待ち |
#1147 はダッシュボードのみを修正しており、ルーターパーサー(engine_stats.py:69–76)の同様の問題は PR 本文に記載した通り #1140・#1146 で個別に扱われます。llm-d#2835 は main 基準であるため、v0.10.0 タグでは同じ文が docs/architecture/advanced/autoscaling/wva.md:144·210줄 と DigitalOcean ダッシュボード 1544줄 付近にあり、3番目に修正したガイド(turn-priority-fairness)は v0.10.0 にはまだ存在しません。パスが異なる理由は確認していません(未確認)。
6. 実測
6.1 何をどのように測定したか
2026-10-16 に小規模クラスターの GPU ノード 1 つ(8GB 級ノートPC向け GPU)で使い捨てポッドを起動して測定しました。測定スクリプト(pod.sh)はノードを固定し、既存のイメージのみを使用し(imagePullPolicy: Never)、Hugging Face へのアクセスをブロックし、メモリ制限 3.5Gi・実行時間 90 分の制限を設けます。モデルは設定ファイルのみを作成した ランダム重み Qwen2 構造(2 層、hidden 256、 --load-format dummy)です。イメージは vllm/vllm-openai v0.31.0(PyTorch 2.13、CUDA 13.0)であり、最初の 160 トークンが同じリクエスト 5 件を送信した後に /metrics を取得しました。ルーター、LMCache、EPP は起動していないため、6.3 節は各タグのソースから抽出した名前をこの出力と照合した 静的突合です。
6.2 /metrics 出力の規模
| 項目 | 値 |
|---|---|
| 総行数 | 587 |
# TYPE ファミリー |
88 (vllm 68, http 10, process 6, python 4) |
vllm: ファミリー中の _created サブゲージ |
31 |
vllm: 実際の指標 |
37(カウンター 15、ヒストグラム 16、ゲージ 6) |
vllm:prefix_cache_queries_total / _hits_total |
805 / 640 → ヒット率 0.795 |
vllm:inter_token_latency_seconds_count |
75 |
vllm:cache_config_info |
block_size="16", num_gpu_blocks="144272", gpu_memory_utilization="0.3" |
旧名(gpu_prefix_cache*, gpu_cache_usage_perc, time_per_output_token_seconds) |
0 個 |
数値は一致しています。リクエスト 5 件 × 161 トークン = 805 検索であり、最初のリクエストはすべてミスし、残りの 4 件が 160 トークン(10 ブロック)ずつヒットして 640 になります。16 トークンずつ作成されたリクエスト 5 件のトークン間の間隔は 15 × 5 = 75 です。ブロック数 144,272 × 16 = 2,308,352 も同じ行の kv_cache_size_tokens と一致します(モデルが非常に小さいためブロック数が多くなっています)。
6.3 消費者が探している名前が常に出力に含まれているか
| 消費者(タグソース) | 検索する vllm: 名 |
出力に含まれているか | 含まれていない理由 |
|---|---|---|---|
production-stack ルーターパーサー engine_stats.py |
6 | 2 | 4: gpu_prefix_cache_hit_rate·_hits_total·_queries_total·gpu_cache_usage_perc、v0.31.0 ソースに定義なし |
production-stack vllm-dashboard.json |
13 | 9 | 4: current_qps·healthy_pods_total はルーター自身の指標(正常)、クエリ内の gpu_prefix_cache_* 2 つは削除された名前(#1147) |
production-stack vllm-model-metrics-dashboard.json |
21 | 21 | 0 |
| llm-d-router EPP デフォルト vLLM ラベル | 5 | 4 | 1: lora_requests_info、LoRA を有効にした場合のみ出現する条件付き指標(loggers.py:993) |
| llm-d v0.10.0 ドキュメント・ガイド全体 | 71 | 40 | 31: 27 個はオフロードや NIXL コネクターを有効にした場合のみ出現する条件付き指標、4 個は v0.31.0 ソースに定義なし |
llm-d ドキュメントの「定義なし」4 個は time_per_output_token_seconds 系 3 個(#2835 が修正した項目)と vllm:kv_offload_tiering_block_hits_total 1 個です。最後の項目は 8 節のコントリビューション候補として残しました。
6.4 削除されたフラグ
| コマンド | 結果 |
|---|---|
vllm serve <작은 모델> --disable-log-requests |
unrecognized arguments、終了コード 2 |
vllm serve <작은 모델> --swap-space 0 |
unrecognized arguments、終了コード 2 |
vllm serve 全体のヘルプから抽出したフラグ一覧(約 400 個) |
--task·--disable-log-requests·--swap-space なし、クエリ内の --enable-log-requests·--no-enable-log-requests·--runner·--kv-transfer-config·--kv-events-config·--show-hidden-metrics-for-version あり |
--task なしでランダム重み Whisper 構造で vllm serve |
ログ Supported tasks: ['transcription'], /v1/audio/transcriptions HTTP 200 |
同日に確認したチャートのデフォルトエンジンイメージ(lmcache/vllm-openai:latest)は vLLM 0.29.0・LMCache 0.5.5 であり、その中の loggers.py にも gpu_prefix_cache は存在しませんでした。クエリ内の チャートのデフォルト値のままであっても ルーターパーサーとプレフィックスキャッシュパネルには 0 または No data が表示されます。実際にチャートをインストールして Grafana を確認したわけではありません(未実測)。
7. どこから読むべきか
vllm/v1/metrics/loggers.pyのPrometheusStatLoggerコンストラクターを上から下にざっと確認します。エンジンが何をカウントしているかが、他のすべての入力になります。vllm_router/app.pyのinitialize_allから始めて、クエリ内のrequest.pyのroute_general_requestで 1 つのリクエストを最後まで追いかけます。routing_logic.pyでRoutingInterfaceと実装をいくつか比較します。テスト(クエリ内のsrc/tests/test_*_router.py)を合わせて確認すると、期待される動作がすぐに把握できます。kv_connector/v1/base.py一番上のコメント(スケジューラー側・ワーカー側の関数一覧)を読んだ後、LMCache のvllm_v1_adapter.pyの同名関数にジャンプします。- llm-d は
docs/architecture/core/router/README.mdで用語を把握し、コードは llm-d-router のscheduler.goとクエリ内のfactories.goを確認します。
8. 初めての貢献におすすめの箇所
ソースを読んでいて見つけたものです。すでに開いているPRやissueがないかは確認していないため、投稿する前にまず検索する必要があります。
- production-stackのクラウド別ダッシュボードのコピー。 ヘルムチャートのダッシュボードは #989 と #1147 で修正を進めているところですが、
tutorials/terraform/{coreweave,eks,nebius}/config/のvllm-dashboard*.jsonの4つにはvllm:gpu_cache_usage_percとvllm:gpu_prefix_cache_*がどちらも10か所残っています(例:eks/config/vllm-dashboard.json:732). - production-stackベンチマークチュートリアルの
--swap-space 0.07-benchmark-multi-round-qa-single-gpu.md:51など、07・08番のチュートリアル4つがextraArgs: ["--swap-space", 0]を与えます。v0.31.0はこのフラグで終了コード 2 で停止します(6.4節)。 - llm-d P2Pガイドの指標名。
guides/p2p-kv-cache-sharing/benchmarking/README.md:129はvllm:kv_offload_tiering_block_hits_totalを使っていますが、v0.31.0にはvllm:kv_offload_tiering_chunk_hitsのみが定義されています。ガイドが依存しているvLLMのバージョン(vllm#48798以降)で名前が変更されたのかどうかをまず確認する必要があります。 KvawareRouterのチャットリクエスト。route_requestはrequest_json.get("prompt", "")のみをトークン化し、TODO: Handle chat completionsを残しました。/v1/chat/completionsリクエストは空のプロンプトとして参照され、セッション・QPSルーティングに落ちると予想されます。同じファイルPrefixAwareRouterのmessagesの処理を参考にすることができます。- デフォルトダッシュボードの混ざった時系列(未確認)。 「Number of Running Requests」パネルはラベルなしで
vllm:num_requests_runningを描画します。ルーターも同じ名前を出力するため(4.4節)、2つの時系列が一緒に描画される可能性があります。実際の画面をまず確認する必要があります。
プロジェクト案内: production-stackは大きな変更なら proposals/ に提案書から、コミットにはDCO Signed-off-by を求めます。LMCacheはデフォルトブランチが devで、DCOが必須であり、入門用issueのまとめ(LMCache#627)があり、 AGENTS.md とコーディング基準に「AIが生成したコードのシグナル」というレビュー項目を設けています。llm-dはAIを使った貢献も同様の基準で受け付けるが、提出者が内容を直接説明できなければならないと記載しています。筆者のPRはすべて本文の最後にAI支援の事実を明らかにしています。
9. 制限事項
- ルーター・LMCache・EPPを実際に起動してリクエストを流した測定はありません(未実測)。6.3節の対照は、タグソースの文字列とエンジン出力の一つを合わせた静的な対照です。
- 実測エンジンは、ランダム重みの非常に小さなモデルが1つ、GPUが1つです。LoRA・スペックデコーディング・KVコネクターを有効にしないと出現しない指標や、マルチAPIサーバーのマルチプロセス指標モードは確認していません。
- production-stackのGoオペレーター、セマンティックキャッシュ・PIIなどの実験的機能、LMCacheのCacheBlend・コントローラー内部、llm-dのオートスケーラー(WVA)とバッチゲートウェイは対象外としています。
- 古い名前やフラグがどのバージョンで非表示になり削除されたかについては、v0.31.0以前のタグを開かずにPR本文の調査を転載しています。
- 性能数値(TTFT・スループット)はこの記事の範囲外です。LMCacheの実測は LMCache編, vLLM負荷実験は vLLM負荷テストにあります。
一言。 厨房は同じなのにメニューの名前だけ変わっても、ホールマネージャーは「そんな料理はありません」の代わりに空のお皿を出してしまいます。だからこそ、名前を1つ修正するPRが思った以上に大きな穴を防いでくれるのです!
参考
閲覧日はすべて2026-10-17です。
- production-stack vllm-stack-0.1.13:
src/vllm_router/,helm/,CONTRIBUTING.md,pyproject.toml - vLLM v0.31.0:
vllm/v1/metrics/loggers.py,kv_connector/v1/base.py,kv_connector/v1/lmcache_connector.py,kv_events.py,engine/arg_utils.py,docs/design/metrics.md,docs/usage/metrics.md - LMCache v0.5.6:
lmcache/integration/vllm/vllm_v1_adapter.py,lmcache/v1/token_database.py,lmcache/v1/storage_backend/,lmcache/v1/multiprocess/,CONTRIBUTING.md,AGENTS.md - llm-d v0.10.0:
COMPONENTS.md,docs/architecture/,docs/architecture/advanced/disaggregation/README.md,guides/env.sh,CONTRIBUTING.md - llm-d-router v0.11.0:
pkg/epp/,pkg/kvevents/,pkg/sidecar/ - 同サイトの関連記事: LLM KVキャッシュ解剖 第2回 サービングエンジン内のKVキャッシュ, 第3回 LMCache, 第4回 NVIDIA Dynamo
引用したコード: vLLM・production-stack・llm-d-routerはいずれもApache License 2.0です。