状态:草案(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 节点(8GB 级别笔记本 GPU)上通过临时 Pod 测得的原始数据,并在 2026-10-17 重新清点后引用。 编写规则:以实测数据为基础,未能测得的标为“未实测”,无法确认的标为“未确认”。
目录
1. 一句话总结:只有一个厨房,但大堂经理和冰箱可以随时更换
vLLM 是在一张(或几张)GPU 上快速运行模型的 厨房。如果开设多个厨房,就需要决定将订单送到哪个厨房的 大堂经理、共享各厨房各自独立进行的预备工作(KV 缓存)的 冰箱, 以及通报厨房繁忙程度的 订单板(指标) 。这三个项目保持厨房(vLLM)不变,而以各自不同的方式填充这些周边。
| 组件 | 职责 | 运行位置 |
|---|---|---|
production-stack 路由器(vllm_router) |
接收兼容 OpenAI 的请求,挑选一个引擎 Pod 并原样转发 | 单个 Deployment,Python·FastAPI 进程 |
| production-stack Helm Chart | 一次性安装引擎 Deployment、路由器、ServiceMonitor 和 Grafana 仪表盘 | helm install 一次 |
vLLM KV 连接器(KVConnectorBase_V1) |
引擎调度器与工作进程调用外部 KV 存储的接口 | 引擎进程内部 |
| LMCache | 连接器实现。将 KV 切割为 256 token 的块,并在 CPU·磁盘·远程中存储与查询 | 引擎进程内部,或独立服务器(MP 模式) |
| llm-d Router(代理 + EPP) | 代理仅负责连接,由 EPP 选择 Pod 并返回地址 | Envoy 系列网关 + EPP Deployment(Go) |
vLLM /metrics |
以 Prometheus 格式输出队列长度、KV 使用率、前缀缓存命中等 | 引擎 API 服务器 |
为什么需要 KV 缓存以及 LMCache 节省了什么,请参见 LLM KV 缓存剖析 系列。本篇不讲原理,专注于 哪个文件的哪个函数在执行该任务。
2. 架构图
这是读图指南。
- 前端是 两者选一。左侧的 production-stack 路由器直接接收请求体并将其代理给引擎。右侧的 llm-d 则由 Envoy 等代理持有请求体,而 EPP 通过 ext-proc 回调仅返回“发送到该 Pod”的地址。
- 两个前端都会定期读取引擎的
/metrics以了解 Pod 的繁忙程度。此外,llm-d EPP 还订阅了引擎通过 ZMQ 发送的 KV 事件(虚线),以实时了解“哪个 Pod 拥有哪个块”。 - 在引擎内部,GPU 上的 16 token 块是一级缓存。如果不够,KV 连接器会调用 LMCache,而 LMCache 会依次从各层中查找。
- 在将 LMCache 作为引擎外部独立服务器运行的 MP 模式下,最下方的方框整体将成为一个单独的进程。
3. 代码导览
首先整理“代码在哪个仓库中”。 llm-d/llm-d 仓库中没有服务代码。 在 v0.10.0 标签的 1,315 个文件中,834 个是 YAML,249 个是 Markdown,没有 Go 文件,Python 文件也只有 33 个 CI、工具和示例脚本。它是一个收集了部署指南、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/ |
Chart。引擎 Deployment、路由器、ServiceMonitor、仪表盘 ConfigMap | templates/deployment-vllm-multi.yaml |
production-stack helm/dashboards/ |
三套 Grafana 仪表盘 JSON(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/ |
缓存引擎·token 块·存储后端·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中的服务发现、引擎统计收集器、请求统计和路由逻辑创建为单例。由于路由逻辑需要读取前三者创建的表格,因此必须按此顺序。 - 引擎列表由
K8sPodIPServiceDiscovery._watch_engines创建。它通过标签选择器 watch Pod,并向每个就绪的 Pod 查询_get_model_names以获取/v1/models并记录“此 IP 提供哪个模型”。之所以直接通过 Pod IP 而非 Service 进行连接,是因为路由器必须选择具体的 Pod,而 Service 会将该选择交由 kube-proxy 处理。 - 请求通过
route_chat_completion进入route_general_request。在此处,利用正文中的model筛选出候选引擎,调用路由逻辑的route_request获取一个 URL,随后通过process_request流式传输响应。如果失败,该函数内还包含一个重试循环,用于剔除该 URL 并重新选择。 - 路由逻辑有
RoutingLogic枚举定义的八种。核心轴包括RoundRobinRouter、将会话键放入一致性哈希环的SessionRouter、将提示词字符串按 128 字符块哈希并放入字典树的PrefixAwareRouter(HashTrie), 以及向 LMCache 控制器询问“拥有最长匹配 token 的实例”的KvawareRouter。
两种前缀路由器的差异很好地展示了该项目的设计考量。 PrefixAwareRouter 是指路由器仅根据 自己发送的记录进行推测。正如类注释中自行表明的“假设前缀缓存不会被逐出(Eviction)”一样,即使引擎删除了块,路由器也无从得知。但它的优点是没有外部依赖。而 KvawareRouter 则向 LMCache 控制器询问 实际存储的位置,因此非常准确,但代价是路由器镜像中必须包含 LMCache 软件包。默认的路由器镜像安装了 pyproject.toml 的可选依赖 lmcache==0.3.11 ,这与引擎侧的版本有所不同,不过双方的控制器消息是否匹配尚未得到确认(未确认)。
Helm Chart 将这些路由器参数原样展开。 deployment-router.yaml 将 routerSpec.routingLogic 传递给 --routing-logic ,而 deployment-vllm-multi.yaml 则为每个 modelSpec 组装 vllm serve 命令。如果启用了 LMCache,则将 --kv-transfer-config 传给 LMCacheConnectorV1 ,并将大小作为 LMCACHE_* 环境变量传入。由于引擎参数是以字符串形式拼接的,如果 vLLM 删除了某个标志,Chart 和教程就会在静默中陈旧,并在执行时崩溃。第 5 节的贡献正是针对这一痛点。
4.2 vLLM ↔ LMCache:名为连接器的洞
vLLM v1 将外部 KV 存储抽象为一个 KVConnectorBase_V1 。该类的核心在于同一个对象是由 两个角色构成的。
- 调度器侧:
get_num_new_matched_tokens回答“GPU 上没有但可以从外部加载的 token 数量”。调度器在schedule内部(第 949 行)根据该值决定跳过相应部分的计算,并通过update_state_after_alloc和build_connector_meta制定“加载什么、保存什么”的元数据。 - 工作进程侧:接收该元数据,通过
start_load_kv开始加载,逐层调用wait_for_layer_load·save_kv_layer,并在结束时通过wait_for_save完成保存。
为什么要分为两部分?调度器不触碰 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 客户端,而 Token 则由 ChunkedTokenDatabase 切分为 _chunk_tokens ,并通过 _prefix_hash 连续拼接前一个分块的哈希来生成键。分块的默认值是 config.py 的 256 个 Token,是 vLLM GPU 块(实测 16 个 Token)的 16 倍。这是因为为了减少远程存储的对象数量和往返开销,分块必须足够大;代价是比 256 个 Token 更短的重叠前缀无法在 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 如何选择 Pod
llm-d Router 分为代理(Proxy)和 EPP。当代理拦截请求并通过 ext-proc 流向 EPP 询问时,EPP 的 StreamingServer.Process 接收并运行调度器,随后 generateHeaders 将选中的 Pod 地址写入 x-gateway-destination-endpoint 请求头中并返回。这种将请求体转发、TLS、重试交由经过验证的代理,而把 LLM 专属判定剥离出来的架构,与在一个 Python 进程中搞定一切的 production-stack 路由器正好相反。
调度器 Scheduler.Schedule 是一个“运行由配置文件处理器选定的配置文件,并根据结果再次进行选择”的循环。单个配置文件在 SchedulerProfile.Run 内部依次通过:过滤器 → 加权打分器(runScorerPlugins) → 挑选器。之所以允许多次运行配置文件,是因为采用了 P/D(Prefill/Decode)分离架构。 disagg 处理器的 Pick 通过解码配置文件首先选择 D Pod,并在需要时通过预填充配置文件选择 P Pod。实际的 P→D 流程由解码 Pod 旁边的路由边车(llm-d-router pkg/sidecar/proxy/)负责,它先将请求发送至 P max_tokens=1 ,并附带收到的 KV 传输信息传递给 D。P/D 分离本身的原理参见 NVIDIA Dynamo 篇。
打分器读取的数值通过两条途径传入。一条是抓取引擎 /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 的名称,并通过 Pod 标签 llm-d.ai/engine-type 来选择对应的表格。这种设计使得即使引擎更改了名称,也只需要修改这一个表格即可。接收到该值后, KVCacheUtilizationScorer.Score 将 1 − KV 사용률 赋予相应的分数。
另一条途径是 KV 事件。当 vLLM 存储或逐出块时,会通过 BlockStored 发布诸如 ZmqEventPublisher 之类的事件,而 EPP 的 precise-prefix-cache 插件则通过 ensureSubscriber 为每个 Pod 打开订阅。接收到的消息由 VLLMAdapter.ParseMessage 解析并添加或删除块索引。这用引擎告知的事实取代了 production-stack PrefixAwareRouter 的“通过发送记录进行推测”,代价是必须在引擎中开启 --kv-events-config 并打开 ZMQ 端口。
4.4 指标路径:定义中的一行化作三处的查询
图中的 PS 即 production-stack。起点是 PrometheusStatLogger 构造函数,其中 vllm: 指标是一行行定义的。KV 使用率为 vllm:kv_cache_usage_perc,前缀缓存为 vllm:prefix_cache_queries·vllm:prefix_cache_hits,Token 间延迟为 vllm:inter_token_latency_seconds 。源码中的计数器名称中虽然没有 _total ,但由于 prometheus_client 在导出时会附加 _total 和 _created ,因此编写查询的一方必须以 vllm:prefix_cache_hits_total 来读取。这种差异正是导致名称混乱的原因之一。该注册表由 attach_router 挂载到 /metrics 路径上。
vLLM 不输出“命中率仪表盘(Gauge)”,而是输出“查询与命中两个计数器(Counter)”。这是为了让用户能够像 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 标签)。因此,相同的名称在引擎和路由器两个来源中仅凭标签不同而混杂在一起,而默认仪表盘的前缀缓存面板则通过 endpoint="service-port" 进行过滤。 service-port 是 图表附加到引擎 Service 端口上的名称,而路由器 Service 端口的名称则是 router-sport ,因此只保留来自引擎的时间序列。
第二个消费者是 Prometheus 和 Grafana。图表的 ServiceMonitor 负责抓取引擎和路由器,且仪表盘 JSON 中的 PromQL 直接硬编码了字符串形式的名称。第三个消费者则是 4.3 节中的 EPP 标签。这三者如果名称写错,都只会默默显示为 0 或“No data”,而不会导致失败。
5. 与用户贡献的关联
作者的四笔 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 行两个计数器的比例 |
待审 |
#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줄 附近,而第三个修复的指南(turn-priority-fairness)在 v0.10.0 中尚不存在。路径不同的原因未作确认(未确认)。
6. 实测
6.1 测量内容与方法
于 2026-10-16 在小型集群的一个 GPU 节点(8GB 级别笔记本 GPU)上运行一次性 Pod 进行了测量。测量脚本(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 个 token 相同的 5 个请求后,获取了 /metrics 。由于未启动路由器、LMCache 和 EPP,因此 6.3 节是将从各标签源码中提取的名称与此输出进行匹配的 静态对比。
6.2 /metrics 输出规模
| 项目 | 值 |
|---|---|
| 总行数 | 587 |
# TYPE 系列 |
88 (vllm 68, http 10, process 6, python 4) |
vllm: 系列中的 _created 辅助 Gauge |
31 |
vllm: 实际指标 |
37(Counter 15,Histogram 16,Gauge 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 个 token = 805 次查询,第一个请求全部未命中,其余四个请求各命中 160 个 token(10 个块),共计 640。由 16 个 token 组成的 5 个请求,其 token 间的间隔为 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 个是启用卸载(Offload)或 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 在没有 vllm serve |
的情况下,以随机权重的 Whisper 结构运行 Supported tasks: ['transcription'], /v1/audio/transcriptions 日志 |
HTTP 200lmcache/vllm-openai:latest同一天读取的 Chart 默认引擎镜像( loggers.py )是 vLLM 0.29.0 和 LMCache 0.5.5,其中 gpu_prefix_cache 中也没有 。即使保持 Chart 默认值不变 ,路由解析器和前缀缓存面板也会显示 0 或 No data。并非实际安装 Chart 并查看了 Grafana(未实测)。
7. 从何处开始阅读
vllm/v1/metrics/loggers.py的PrometheusStatLogger构造函数自上而下浏览。引擎统计的内容是其余所有内容输入。vllm_router/app.py的initialize_all开始,沿着request.py的route_general_request追踪单个请求到底。routing_logic.py中对比RoutingInterface和两三个实现。结合测试(src/tests/test_*_router.py)一起看能更快理解预期行为。kv_connector/v1/base.py读完最上方的注释(调度器侧和工作进程侧的函数列表)后,跳转到 LMCachevllm_v1_adapter.py中同名函数。- llm-d 使用
docs/architecture/core/router/README.md统一术语,代码则查看 llm-d-routerscheduler.go和factories.go。
8. 适合进行首次贡献的地方
这是阅读源码时发现的。由于未确认是否已有开启的 PR 或 Issue,提交前必须先进行搜索。
- production-stack 各云厂商仪表盘副本。 Helm Chart 仪表盘正在通过 #989 和 #1147 进行修复,但
tutorials/terraform/{coreweave,eks,nebius}/config/的vllm-dashboard*.json四个版本中,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 号教程的四个地方会产生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 节),因此这两个时间序列可能会被绘制在一起。必须先查看实际屏幕。
项目指南:对于重大更改,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 节的对比是将标签源码中的字符串与引擎输出进行的一对一静态对比。
- 实测引擎是一个使用随机权重的极小模型和一个 GPU。未考察开启 LoRA、推测解码(Speculative Decoding)、KV 连接器时才会出现的指标,以及多 API 服务器的多进程指标模式。
- 未涉及 production-stack 的 Go Operator、语义缓存和 PII 等实验性功能,LMCache 的 CacheBlend 和控制器内部,以及 llm-d 的自动缩放器(WVA)和批处理网关。
- 旧名称和标志在哪个版本中被隐藏和删除,并未通过打开 v0.31.0 之前的标签来验证,而是直接引用了 PR 正文中的调查结果。
- 性能数值(TTFT、吞吐量)不在本文的范围内。LMCache 实测请参考 LMCache 篇,vLLM 负载实验请参考 vLLM 负载测试。
Teety 的话。 厨房明明没变,仅仅是菜单名字变了,大堂经理就会端出空盘子,而不是说「没有这道菜」。因此,修复一个名称的 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。