团团虾声明:本文由 codex 基于 llm-d 代码仓库深度探索并撰写。
llm-d KV Cache 感知与流量编排技术报告
0. 阅读版本与说明
本文基于本地 llm-d 仓库 main 分支阅读整理。
- 阅读版本:
4ece9cdd3a83f65b4bf42d3cafcc76c7408e76ef - commit 时间:2026-05-05 13:15:17 -0700
- commit 标题(原文):
one build approval per set of chagnes in a PR (#1413) - release tag:当前 HEAD 未绑定 tag
- 整理时间:2026-05-06(Asia/Shanghai)
- 主要阅读范围:
docs/architecture/core/router/epp/、docs/architecture/advanced/kv-management/、docs/architecture/advanced/disaggregation/、guides/precise-prefix-cache-aware/、guides/tiered-prefix-cache/、guides/pd-disaggregation/,以及相关 proposal / monitoring 文档。
从当前文档形态看,llm-d 仍处在积极开发和相对早期的阶段:架构文档、proposal、well-lit path、guide 和监控配置同时存在,部分能力还带有 experimental、working branch 或实现约束说明。本文因此不把当前代码视为长期稳定接口来解读,重点放在调度思想和系统分工上:EPP 如何把 prompt prefix、KV cache index、endpoint 负载、P/D role、latency prediction 和 offloading tier 合并成请求级路由决策。
1. 摘要
llm-d 的 KV cache 能力分成两层:
- 模型引擎层:vLLM、SGLang 等 model server 负责生成、保存、复用和淘汰 KV cache。
- 编排层:llm-d Router 的 EPP 负责感知各个 model server 上的 KV cache 状态,并把这些状态纳入请求调度。
llm-d 不接管模型计算,也不直接替代 vLLM 的 cache 管理。它的关键价值是把单机 KV cache 变成集群级可见的调度信号:某个请求的 prompt prefix 已经在哪些 pod 上命中、命中多少、命中在哪个存储层、当前 pod 是否拥塞、继续粘到该 pod 是否会伤害 SLO。EPP 再把这些信号和队列长度、running requests、KV cache utilization、token load、latency prediction、P/D role 等信息合并,做请求级流量编排。
2. 架构位置
llm-d 的请求路径可以简化为:
graph LR
Client["Client / OpenAI API"] --> Gateway["Gateway / Proxy"]
Gateway --> EPP["llm-d Router / EPP"]
EPP --> Gateway
Gateway --> MS["Model Server Pod<br/>vLLM / SGLang"]
Kube["Kubernetes API<br/>InferencePool / Pods"] --> EPP
Metrics["Prometheus Metrics<br/>queue / running / KV usage"] --> EPP
KVEvents["KVEvents<br/>BlockStored / BlockRemoved / AllBlocksCleared"] --> EPP
Tokenizer["Tokenizer<br/>UDS sidecar or local"] --> EPP
各组件分工:
| 组件 | 职责 | 和 KV cache 的关系 |
|---|---|---|
| Model server | 执行 prefill/decode,管理本机 KV cache | 产生 KV block,执行 prefix reuse,暴露 cache metrics,发出 KVEvents |
| InferencePool | 定义一组可服务同一模型的 pods | 给 EPP 提供候选 endpoint 集合 |
| Gateway / Proxy | 接入请求,执行 L7 转发 | 调用 EPP 获取目标 pod,不理解 KV 细节 |
| EPP | 请求解析、准入、排队、调度 | 维护 cache index,执行 prefix scorer,结合负载和 SLO 选 endpoint |
| KV-Cache Indexer | EPP 内部的数据层组件 | 维护 block key -> pods/tier 的近实时索引 |
| Offloading connector | 把 KV 扩展到 CPU、SSD、共享存储 | 扩大有效 cache 容量,提供 tier-aware reuse 的基础 |
3. KV cache 状态从哪里来
3.1 Prometheus metrics
model server 暴露指标,EPP 通过 metrics 获取每个 endpoint 的运行状态。和 KV cache 直接相关的核心指标包括:
KVCacheUtilization:当前 KV cache 使用率,例如 vLLM 的vllm:kv_cache_usage_perc。BlockSize:KV block 对应的 token 数,用于 prefix block 切分和打分。NumGPUBlocks:GPU HBM 上可用 KV block 总数。
这些指标适合回答”这个 pod 还有多少 KV 空间""继续打过去是否可能造成碎片或淘汰”。它们不能精确回答”某个 prompt prefix 的 block 是否在这个 pod 上”。
3.2 近似 prefix index
轻量部署中,EPP 使用 approx-prefix-cache-producer 和 prefix-cache-scorer 感知 prefix locality。
流程:
- EPP 解析请求中的 prompt。
- 由于 EPP 默认没有 tokenizer,按字符到 token 的比例估算 token block。
- producer 对 prompt prefix 做 rolling hash,生成 block hash chain。
- EPP 在本地 LRU index 中查询这些 hash 最近被路由到哪些 pods。
- scorer 按命中比例给候选 pod 打分。
- EPP 做出路由决定后,假设该 pod 将拥有这些 prefix block,并更新本地 index。
这个模式部署成本低,不需要 model server 发 KVEvents,也不依赖 ZMQ。但它是基于调度历史的推断,pod 内部真正淘汰了哪些 block,EPP 并不知道。
3.3 精确 KVEvents
生产级精确模式中,model server 在 KV cache 变化时发布 KVEvents,EPP 内部的 KV-Cache Indexer 订阅事件并维护全局索引。
核心事件:
| 事件 | 含义 | EPP 处理 |
|---|---|---|
BlockStored | 某个 block 已写入某个 pod 的某个 cache tier | 把 block key 加入 block key -> pod/tier 映射 |
BlockRemoved | 某个 block 被淘汰或从某个 tier 移除 | 从索引里删除对应 pod/tier 记录 |
AllBlocksCleared | 某个 pod 清空了全部 cache | 删除该 pod 在索引中的所有记录 |
事件传输有两种模式:
- Centralized:所有 model server pod 向单个 EPP endpoint 发布事件,适合单 EPP 副本。
- Pod discovery:每个 model server pod 暴露 ZMQ socket,每个 EPP 副本订阅所有 pods,适合多 EPP active-active。
精确模式的索引来源是 model server 的真实 cache 事件,比近似模式更能处理 eviction、offload、多模态、LoRA 和复杂 cache 策略。
3.4 Tokenizer / Data Producer
精确匹配需要 token ID,而不是字符片段。EPP 的 tokenizer data producer 在调度前完成这些工作:
- 渲染 chat template。
- 把 prompt 转成 exact token IDs。
- 提取多模态内容 hash。
- 把结果挂到内部
InferenceRequest,供 scorer 复用。
生产部署推荐 tokenizer 作为 UDS sidecar。这样可以避免 EPP 进程内反复加载 tokenizer,也减少网络开销。
4. llm-d 感知哪些 KV cache 维度
4.1 endpoint 维度
EPP 首先要知道”有哪些候选 endpoint”。这些信息来自 InferencePool 和 Kubernetes pod watch:
- pod name、namespace、IP、port。
- readiness 状态。
- model server 类型,例如 vLLM、SGLang、TRT-LLM。
- endpoint role,例如
prefill、decode、prefill-decode。 - pod labels,用于 profile/filter 过滤。
4.2 容量与压力维度
这些信号用于判断”这个 pod 还能不能接请求”:
- KV cache utilization。
- queue depth。
- running requests。
- token load。
- ready pod 数。
- pool 平均 KV cache utilization。
- per-pod queue size。
调度上通常会把低 KV utilization、短队列、少 running requests 的 pod 排在前面,降低过载、碎片和 cache eviction 风险。
4.3 prefix locality 维度
这是 llm-d KV 感知的核心。EPP 关心的是:当前请求的 prompt prefix 在哪个 pod 上可复用,能复用多长。
精确模式中,scorer 会把请求 token 切成 block key chain,并查索引:
Prompt blocks: B0 B1 B2 B3 B4
Pod A: B0 B1 B2 B3 -- => hit length = 4
Pod B: B0 B1 -- -- -- => hit length = 2
Pod C: -- -- B2 B3 B4 => hit length = 0
KV block 有因果依赖。即使 Pod C 有 B2/B3/B4,没有 B0/B1 也不能复用这段 suffix,所以 longest consecutive prefix 才是有效命中长度。
4.4 tier 维度
KV block 可能位于不同层级:
- GPU HBM:命中价值最高,读取最快。
- CPU DRAM:容量更大,延迟高于 HBM。
- local SSD / NVMe:容量更大,延迟继续升高。
- shared filesystem / remote storage:可跨 pod、跨节点共享,延迟和吞吐取决于存储系统。
EPP 的精确 scorer 可以按 tier 加权。示例权重是 GPU 1.0、CPU 0.8。同一个 block 如果存在多个 tier,取最高权重。这样调度不会只看”有没有”,还会看”在哪里”。
4.5 cache 身份维度
同一段文本不一定对应同一份可复用 KV。索引需要把以下身份折入 block key:
- base model。
- LoRA adapter name 或 ID。
- token block。
- parent block hash,也就是前缀链。
- multimodal extra keys,例如图片或音频内容 hash。
- attention group 或 cache group,尤其是 hybrid-attention 模型。
这些维度避免错误复用。比如同一段文字配不同图片,或者同一 token 序列挂不同 LoRA adapter,KV 语义都不同。
4.6 时间与一致性维度
KVEvents 到达 EPP 有延迟。EPP 刚把请求路由到某个 pod 时,新的 BlockStored 事件可能还没发出来。为避免连续同 prefix 请求被打散,scorer 支持 speculative indexing:
- 路由成功后,EPP 立即向 index 写入短 TTL 的预测项。
- 后续请求可以先命中预测项。
- 真正的
BlockStored到达后转为 confirmed state。 - TTL 到期仍未确认则删除预测项。
这解决了路由决策和事件传播之间的短暂空窗。
5. 流量编排流程
5.1 聚合 serving 模式
聚合模式中,每个 pod 既能 prefill 也能 decode。EPP 只需要选一个目标 endpoint。
sequenceDiagram
participant Client
participant Proxy
participant EPP
participant Index as KV Index
participant MS as Model Server
Client->>Proxy: OpenAI-compatible request
Proxy->>EPP: ext-proc request metadata/body
EPP->>EPP: parse request and run data producers
EPP->>Index: lookup prefix block hits
Index-->>EPP: hit length and tier per candidate pod
EPP->>EPP: filter, score, pick endpoint
EPP-->>Proxy: selected pod IP/port
Proxy->>MS: forward original request
MS-->>Proxy: streaming response
MS-->>Index: KVEvents for stored/removed blocks
调度步骤:
- Parser 把请求转换成内部
InferenceRequest。 - Data Producer 做 tokenization、prefix lookup、latency prediction 等预处理。
- Flow control 判断是否排队、放行或拒绝。
- Scheduler 从 InferencePool 拿候选 endpoints。
- Filters 去掉不满足条件的 endpoints,例如 role 不匹配、SLO headroom 不足。
- Scorers 分别计算 prefix hit、KV utilization、queue depth、running requests、token load、latency headroom 等分数。
- Weighted scoring 合并分数。
- Picker 选择目标 endpoint,例如 max-score 或 weighted-random。
- Proxy 把原请求转发给目标 pod。
5.2 Prefix-aware routing 的权衡
单纯追求 prefix hit 可能导致热点 pod 过载。llm-d 的编排方式是把 cache 命中和负载一起算:
- prefix 命中高:倾向继续路由到该 pod,减少 prefill recompute。
- queue depth 高:降低该 pod 得分,避免尾延迟变差。
- KV utilization 高:降低得分,避免进一步挤压 cache。
- predicted latency 超 SLO:通过 latency scorer 或 SLO filter 限制选择。
- cold request 无命中:可用 no-hit LRU,把 prefill 压力均匀摊到不同 pod。
因此,KV cache locality 是重要信号,但不是唯一信号。EPP 最终目标是吞吐、TTFT、TPOT 和 SLO 的整体平衡。
5.3 P/D disaggregation 模式
P/D disagg 把 prefill 和 decode 分到不同角色的 pods。EPP 的 disagg-profile-handler 会运行两个 profile:
- decode profile:选择 decode endpoint,关注 decode 负载、KV 命中、running requests、SLO。
- prefill profile:选择 prefill endpoint,关注 prompt 处理能力、queue/token load、prefix affinity。
流程:
sequenceDiagram
participant Client
participant Proxy
participant EPP
participant DSidecar as Decode Sidecar
participant D as Decode Worker
participant P as Prefill Worker
Client->>Proxy: Request
Proxy->>EPP: ext-proc request
EPP->>EPP: select decode endpoint
EPP->>EPP: estimate uncached suffix on decode endpoint
alt enough cache on decode
EPP-->>Proxy: decode endpoint only
Proxy->>DSidecar: request
DSidecar->>D: decoder-only request
else large uncached suffix
EPP->>EPP: select prefill endpoint
EPP-->>Proxy: decode endpoint + prefill header
Proxy->>DSidecar: request with x-prefiller-host-port
DSidecar->>P: prefill request
P-->>DSidecar: KV transfer params
DSidecar->>D: decode request with KV transfer params
D->>P: pull KV cache by NIXL/RDMA
end
D-->>DSidecar: response
DSidecar-->>Proxy: response
关键点:
- prefill 和 decode endpoints 可以同属一个 InferencePool,用
llm-d.ai/role过滤。 - EPP 先选 decode,再根据 decode 上的 cache 命中和未命中 token 数决定是否触发 P/D。
- decode endpoint 是主转发目标。
- prefill endpoint 通过 header 注入,例如
x-prefiller-host-port。 - sidecar 负责把 OpenAI 请求改写成 vLLM/SGLang 的 KV transfer 协议。
- 真正的 KV 数据传输由 NIXL 等底层传输完成,RDMA 场景下可直接在 KV cache memory 间搬运。
6. KV offload 与调度的组合
offload 解决的是”cache 放得下多久、能不能跨节点复用”的问题;EPP 解决的是”知道 cache 在哪里后,请求该打到哪里”的问题。
6.1 CPU offload
vLLM OffloadingConnector 可以把被挤出 HBM 的 KV block 放到 CPU DRAM。EPP 通过 tier-aware index 知道某些 block 虽然不在 GPU,但仍在该 pod 的 CPU tier。调度上,这类命中通常弱于 GPU 命中,但强于完全 miss。
适用场景:
- 多轮会话间隔较短。
- agent workflow 反复带长 system prompt。
- GPU HBM 不足但节点 CPU 内存充足。
- preemption 后希望少 recompute。
6.2 Storage offload
llm-d FS backend 把 KV block 存到共享文件系统。多个 vLLM 实例可以访问同一路径,从而实现跨 pod、跨节点复用。
适用场景:
- cache working set 超过单节点 CPU 内存。
- 新扩容 pod 需要复用已有 cache。
- pod restart 后希望保留 KV。
- 集群里存在大量重复长 prefix。
风险点:
- storage latency 会影响命中收益。
- 共享文件系统吞吐不足时,cache load 会拖慢 TTFT。
- FS backend 本身不负责容量清理,需要底层存储或外部 controller 管理 eviction。
6.3 外部 KV cache engine
LMCache、Mooncake、KVBM 等外部引擎通常通过 model server 的 KV connector API 接入。llm-d 侧的共同契约是 KVEvents:不管底层 cache engine 如何管理内存、磁盘或远端存储,只要能把 cache mutation 暴露给 EPP,EPP 就能把它纳入 prefix-aware routing。
7. 典型调度策略
7.1 长 prompt 多轮会话
目标:复用上一轮 prompt prefix,降低 TTFT。
策略:
- 开启 prefix scorer 或 precise-prefix-cache-scorer。
- 会话请求优先打到持有最长连续 prefix 的 pod。
- 同时启用 queue-depth / running-requests scorer,避免单 pod 过热。
- 有 CPU offload 时,对 CPU tier 命中给较低但非零权重。
7.2 高并发冷请求
目标:避免大量冷 prefill 打爆少数 pods。
策略:
- cold request 的 prefix hit 为 0。
- no-hit-lru-scorer 把冷请求分散到不同 pods。
- token-load-scorer 避免长 prompt 全落到同一个 endpoint。
- KV utilization scorer 降低高 cache 压力 pod 的得分。
7.3 长上下文 P/D disagg
目标:隔离长 prefill 对 decode 的干扰。
策略:
- decode profile 先选 decode pod。
- 根据 decode pod 上已缓存 prefix 长度判断是否需要远端 prefill。
- 大量未命中 token 触发 prefill profile。
- sidecar 编排 prefill 请求和 KV transfer。
- RDMA/NIXL 负责跨 pod 传 KV,避免 decoder 重新 prefill。
7.4 多 LoRA 或多模态请求
目标:避免错误命中,尽量复用同 adapter/同多模态输入的 KV。
策略:
- block key 包含 LoRA name/ID。
- multimodal extra keys 折入 hash chain。
- lora-affinity-scorer 偏向已有目标 adapter 的 pods。
- precise index 比 approximate index 更适合这类场景。
8. 可观测性
EPP 和 model server 提供的关键观测项:
| 指标 | 用途 |
|---|---|
inference_pool_average_kv_cache_utilization | 观察 pool 级 KV 压力 |
inference_pool_per_pod_queue_size | 观察单 pod 排队 |
inference_pool_average_running_requests | 观察 pool 运行中请求数 |
inference_extension_scheduler_attempts_total | 观察调度成功、失败和目标分布 |
inference_extension_scheduler_e2e_duration_seconds | 观察 EPP 调度耗时 |
inference_extension_plugin_duration_seconds | 观察具体 filter/scorer/picker 开销 |
inference_extension_prefix_indexer_size | 观察 prefix index 规模 |
inference_extension_prefix_indexer_hit_ratio | 观察 prefix lookup 命中率 |
inference_extension_prefix_indexer_hit_bytes | 观察命中的 prefix 字节量 |
vllm:kv_cache_usage_perc | 观察 vLLM KV cache 使用率 |
vllm:kv_offload_* | 观察 offload 传输、大小和耗时 |
排查时建议按三层看:
- model server 层:KV utilization、queue、running requests、offload latency。
- EPP 数据层:KVEvents 是否正常、index size 是否增长、hit ratio 是否符合预期。
- EPP 调度层:scheduler attempts、plugin duration、目标 pod 分布是否倾斜。
9. 工程边界与风险
9.1 近似模式会漂移
approx index 基于”请求曾经被发到哪个 pod”的历史推断。pod 内 cache 被淘汰、进程重启、负载挤压后,EPP 可能仍认为该 pod 有对应 prefix。高价值生产场景应优先评估 precise 模式。
9.2 精确模式依赖事件链路
precise 模式需要 tokenizer、KVEvents、ZMQ 订阅、index backend 都正常。事件丢失、延迟过高或 EPP 多副本订阅不完整,会直接影响调度准确性。
9.3 cache 命中不等于一定更快
GPU 命中通常收益最高;CPU 或 storage tier 命中要看传输延迟。共享存储吞吐不足时,从远端拉 KV 可能比 recompute 更慢。实际策略要结合 TTFT、TPOT 和 storage metrics 调权。
9.4 P/D disagg 强依赖网络
P/D disagg 的收益建立在高性能 KV transfer 上。没有 RDMA 时,NIXL 可能退到 TCP,只适合测试或开发。生产环境需要确认 IB/RoCE/EFA、GPUDirect RDMA、NIXL backend 和 pod 网络拓扑。
9.5 身份隔离必须严格
KV block key 必须包含模型、LoRA、多模态 extra keys、prefix chain 等身份信息。错误复用 KV 属于正确性问题,不是单纯性能问题。
10. 结论
llm-d 感知 KV cache 的核心链路是:model server 暴露 metrics 和 KVEvents,EPP data producer 把请求转成可匹配的 token/block 表示,KV-Cache Indexer 维护集群级 cache 位置索引,scheduler 用 prefix scorer、KV utilization scorer、queue/load scorer、latency scorer 等插件合并决策。
流量编排的本质是把”请求内容”和”集群实时状态”放在同一个调度循环里。聚合 serving 里,EPP 选择一个最合适的 model server pod;P/D disagg 里,EPP 同时选择 decode 和必要的 prefill endpoint,并由 sidecar 和 NIXL/RDMA 完成 KV 传递。KV offload 则把 cache 保留时间和共享范围扩大,让 EPP 的 cache-aware routing 有更大的命中空间。
在长上下文、多轮 agent、多租户、高 QPS、MoE 和 P/D disagg 场景下,这套机制能减少重复 prefill、降低 TTFT、平衡 decode 压力,并减少热点 pod 引发的尾延迟。