KV Cache感知路由与PD分离:让LLM推理在Kubernetes上吞吐翻倍 在 LLM 推理服务里很多人以为“加 GPU”就能提升吞吐但真正卡住生产环境的往往是显存里那一块看不见的 KV Cache。请求越多上下文越长KV Cache 就越大节点之间的负载就越难以预测。更麻烦的是传统 Kubernetes Service 只是做随机负载均衡它根本不关心某个节点上已经缓存了哪段前缀结果就是缓存命中率低、首 token 延迟高、GPU 利用率上不去。这篇文章从调度与路由视角出发讲清楚三个问题到底什么是 KV Cache 感知路由预填充/解码分离PD Disaggregation为什么能改善推理吞吐以及在 Kubernetes 上这套架构应该怎么落地、怎么验证、怎么排错。读完你会明白KV Cache 感知路由不是“锦上添花”而是让大模型服务走向高并发、长上下文、规模化部署的关键能力。先说一个明确判断PD 分离与 KV Cache 感知路由不是两个独立的技术方案而是一个整体。预填充Prefill负责处理长的输入提示计算密集解码Decode逐 token 生成访存密集。二者混在一起部署时会因为 GPU 资源争抢互相拖累。把两者拆开部署到不同节点池再通过一个“能看到 KV Cache 状态”的路由层将请求流转到正确节点才能真正把每一块显存用到位。本文会先从基础概念讲起再给出一组可在 Kubernetes 上运行的最小部署示例与路由代码最后整理在真实项目中容易踩的坑。1. 传统大模型推理服务在 Kubernetes 上卡在哪很多团队刚开始把 LLM 服务容器化时都是用一个 Deployment 管理推理实例通过 Service 暴露 8000 端口让网关做流量分发。这个模式在模型较小、请求量不高时没什么问题但一旦上下文变长、并发走高问题会集中爆发。首先是 Prefill 和 Decode 互相干扰。同一个 GPU 上一个请求可能正在计算很长的 Prompt 注意力矩阵另一个请求则在做逐 token 生成。两者对显存带宽和计算单元的需求完全不同Prefill 要尽快算出所有 token 的注意力结果Decode 则要把有限的 token 逐个送进模型。当它们混在一起调度GPU 会频繁切换任务类型吞吐和响应速度都会受影响。其次是 KV Cache 导致负载严重不均。KV Cache 是推理过程中临时产生的 Key-Value 张量占用显存且大小随序列长度变化。两个请求即使来自同一个用户、共享同一段系统提示词如果在随机负载均衡下落到不同节点每个节点都要重复计算并重新缓存这一段既浪费显存又增加排队。传统负载均衡器只看连接数或请求数看不到“这个节点上已经缓存的 token 前缀”所以它做的分配决定往往是最差的一种。最后是水平扩缩容的粒度太粗。在 Kubernetes 中增加副本很简单但一个推理实例的内存中可能保存了大量活跃请求的 KV Cache。滚动更新或弹性伸缩时旧实例的缓存会被直接清空所有依赖公共前缀的请求都会“缓存失效”让后续流量瞬间打满剩余节点。这也是为什么需要专门讨论“缓存感知路由”它本质上是把 KV Cache 当成一种有位置、有容量、会失效的分布式资源来管理。2. KV Cache 基础概念为什么路由必须感知它要理解“KV Cache 感知路由”得先拆开 KV Cache 到底是什么。现在主流的大模型使用 Transformer 架构生成下一个 token 时注意力层需要计算当前 query 与所有历史 key、value 的注意力。如果不做缓存每生成一个新 token 都要重新计算整段历史成本极高。因此推理引擎会在显存中保存历史 token 的 Key 和 Value 张量这就是 KV Cache。KV Cache 有两个特征直接影响路由设计。第一它是“请求私有”的同一个用户的多轮对话会持续产生并复用同一段缓存但它内部又可以按前缀共享。多个请求如果拥有相同前缀比如同一套 system prompt 或少代码场景里相同的文件上下文这部分 KV Cache 是可以被复用的。现代推理引擎通常用一棵前缀树来管理这些共享缓存只要公共前缀一致就可以跳过对应的 Prefill 计算。SGLang 的 RadixAttention、vLLM 的 Automatic Prefix Caching 都是类似思路。第二它的大小是动态的。显存中的 KV Cache 会随请求不断增长因此不能简单地用“节点剩余显存”来评估节点可承载能力。一个节点上可能显存还剩 20 GB但其中 15 GB 已经被某个超长请求的 KV Cache 占据另一个节点显存剩余不多但缓存命中率很高。路由层如果只看显存剩余会把长请求打进已经不健康的节点。所以“KV Cache 感知路由”的定义就很清楚了路由层在选择目标节点时不仅要考虑节点的算力、显存、健康状态还要考虑目标节点是否已经缓存了当前请求的公共前缀以及这个节点还能不能容纳新增长的 KV Cache。它跟传统负载均衡的最大区别是路由决策是“有状态的”而且状态变化非常快不能等几秒刷新一次。3. 预填充解码分离PD Disaggregation架构预填充解码分离的核心思想是把一次推理请求拆成两个阶段分别部署到不同的 GPU 节点。传统模型推理过程是一次请求从输入到输出完全在一个节点内完成。Preflfill 阶段读取用户的 Prompt可能是几千甚至上万 token计算出所有输入 token 的 attention 结果形成初始 KV CacheDecode 阶段基于这些 KV Cache 逐 token 生成输出。在混合部署的模式下GPU 要同时服务这两种不同性质的计算调度器很难找到最优切分。PD 分离后工作流变成了请求先进入 Prefill 节点池由 Prefill 实例接收完整 Prompt计算并生成该请求的 KV Cache随后路由层将这份“已填充好的缓存状态”连同 Prompt 一起传递给 Decode 节点池的某个实例由该实例继续逐 token 生成。正因为 KV Cache 只在 Prefill 与 Decode 之间迁移所以 KV Cache 感知路由在这里不是可选项而是必需品你要把预填充结果交给哪个 Decode 节点直接决定了下一次生成是否命中该节点已有的缓存。PD 分离带来的收益很明显隔离资源争抢。Prefill 和 Decode 各自运行在专属节点上调度策略可以分别调整避免相互干扰。首 token 延迟更可控。Prefill 不会被大量 Decode 请求拖垮长输入或高并发下更容易保持可预期的处理速度。吞吐更容易横向扩展。如果线上输入请求量大可以只扩展 Prefill 节点池如果输出 token 特别长就只扩展 Decode 节点池。但代价也不能回避。PD 分离要求 Prefill 和 Decode 之间传输显存中的 KV Cache节点间的数据传输会带来额外延迟尤其在跨机、跨机架时更明显同时一个请求的生命周期变长链路中任何一环失败都需要额外处理。这些问题放到 Kubernetes 上后就需要通过节点亲和性、网络优化、容错重试来缓解。从部署形态上看PD 分离对应三类角色Prefill 角色负责长 prompt 的输入处理和 KV Cache 生成通常消耗更多计算资源。Decode 角色负责逐 token 生成显存带宽需求高对 KV Cache 容量敏感。Router 角色负责根据请求前缀、KV Cache 状态和节点负载决定请求进入哪个 Prefill 或 Decode 实例。在 Kubernetes 中这三类角色可以用不同 NodePool、不同 Deployment/StatefulSet 以及独立的路由服务来表达。4. 在 Kubernetes 上部署 PD 分离与路由层既然架构上有三个角色Kubernetes 的部署模型也应当尽量按角色拆分。节点可以按 GPU 类型、显存容量、是否配备高速 RDMA 网络来划分而不是随意混部。下面的 YAML 示例聚焦一个可扩展的骨架不依赖某个特定推理引擎也没有写死版本号方便你根据自己的镜像和配置改造。4.1 Prefill 节点池示例先看 Prefill 角色。由于 Prefill 是计算密集型的建议通过nodeSelector指定到高算力节点池同时给足 CPU 和显存资源。# prefill-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: llm-prefill namespace: inference labels: role: prefill spec: serviceName: llm-prefill-headless replicas: 2 selector: matchLabels: role: prefill template: metadata: labels: role: prefill spec: nodeSelector: node-type: gpu-compute containers: - name: engine image: your-registry/llm-engine:prefill args: - --roleprefill - --max-prefill-tokens32768 ports: - name: http containerPort: 8000 resources: limits: nvidia.com/gpu: 1 memory: 64Gi requests: nvidia.com/gpu: 1 memory: 32Gi readinessProbe: httpGet: path: /health port: http initialDelaySeconds: 30这里的关键是给 Prefill 单独设置--max-prefill-tokens避免单个请求的输入长度无限挤压 GPU。生产环境还可以加上PriorityClass保证长输入重计算任务不会被普通低优先级请求抢占。4.2 Decode 节点池示例Decode 节点池更看重显存容量和带宽同样的 YAML 结构但角色参数不同。# decode-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: llm-decode namespace: inference labels: role: decode spec: serviceName: llm-decode-headless replicas: 4 selector: matchLabels: role: decode template: metadata: labels: role: decode spec: nodeSelector: node-type: gpu-memory containers: - name: engine image: your-registry/llm-engine:decode args: - --roledecode - --max-kv-cache-tokens1048576 ports: - name: http containerPort: 8000 resources: limits: nvidia.com/gpu: 1 memory: 96Gi requests: nvidia.com/gpu: 1 memory: 48Gi readinessProbe: httpGet: path: /health port: http initialDelaySeconds: 30Decode 节点的显存需求通常比 Prefill 更紧因为要容纳多个并发生成请求的 KV Cache。max-kv-cache-tokens这类参数要结合模型大小和 GPU 显存计算不要为了追求缓存容量把所有显存都占满至少要留出几 GB 给推理引擎的激活值和临时张量。4.3 Headless Service 与路由层网络由于路由层需要精确感知每个 Pod 的状态不能只用 ClusterIP Service 做随机负载均衡。这里需要 Headless Service让路由层可以拿到所有 Pod 的 IP 和状态# headless-services.yaml apiVersion: v1 kind: Service metadata: name: llm-prefill-headless namespace: inference spec: clusterIP: None selector: role: prefill ports: - port: 8000 targetPort: http --- apiVersion: v1 kind: Service metadata: name: llm-decode-headless namespace: inference spec: clusterIP: None selector: role: decode ports: - port: 8000 targetPort: http路由层可以是一个独立的 Deployment它通过 Kubernetes API 或内部服务发现机制获取llm-prefill-headless、llm-decode-headless下的 Endpoints再结合缓存状态表做决策。这样 Kubernetes Service 只负责“发现实例”真正决定流量去哪里的逻辑被收拢到路由服务中。5. KV 缓存感知路由的最小实现路由层要解决的问题是来了一个请求给哪个 PrefillPrefill 完成后把结果交给哪个 Decode先看一个典型流程客户端发起请求携带完整 Prompt。路由服务接收请求解析出 Prompt 的公共前缀或系统提示词部分。路由服务查询自身的 KV Cache 状态表查看每个实例上的缓存前缀、KV 容量和健康度。如果某个实例缓存了完整前缀且容量足够直接选择该实例作为目标。对于需要走 PD 分离的请求先选择一个 Prefill 实例处理再根据生成的 KV Cache 状态选择一个 Decode 实例。由于完整实现需要接入具体推理引擎的 API下面给出一个最小可运行的路由决策示例用 FastAPI 实现状态上报和路由选择你可以直接复用或改造成 gRPC 服务。5.1 缓存状态模型与上报接口推理实例需要周期性或事件驱动地向路由服务上报自己的缓存状态。这里用一段 Python 代码表示状态表# cache_state.py import time from dataclasses import dataclass, field from typing import Dict, Set dataclass class CacheState: node_id: str address: str prefixes: Set[str] kv_capacity_percent: float healthy: bool True updated_at: float field(default_factorytime.time) class CacheStateStore: def __init__(self): self.states: Dict[str, CacheState] {} def upsert(self, state: CacheState): state.updated_at time.time() self.states[state.node_id] state def get_by_prefix(self, prefix: str) - list[CacheState]: candidates [] for state in self.states.values(): if not state.healthy: continue if prefix in state.prefixes: candidates.append(state) return sorted( candidates, keylambda s: self._score(s, prefix), reverseTrue ) def _score(self, state: CacheState, prefix: str) - float: # 简单的打分缓存命中优先容量越大越优先越新越优先 hit_bonus 100.0 if prefix in state.prefixes else 0.0 cap max(0.0, 100.0 - state.kv_capacity_percent) freshness min(1.0, (time.time() - state.updated_at) / 60.0) return hit_bonus cap * 0.3 freshness * 10.0实际生产中prefixes存储的不会是字符串本身而是前缀哈希。因为完整的 prompt 前缀可能非常长直接比较字符串效率不高。推荐用固定长度哈希如 SHA-256 截断指纹作为缓存键路由时先计算请求前缀指纹再在状态表中查找。路由服务还需要提供一个 HTTP 接口接收上报# router_api.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from cache_state import CacheState, CacheStateStore app FastAPI() store CacheStateStore() class CacheReport(BaseModel): node_id: str address: str prefixes: list[str] kv_capacity_percent: float healthy: bool True app.post(/report) def report(report: CacheReport): state CacheState( node_idreport.node_id, addressreport.address, prefixesset(report.prefixes), kv_capacity_percentreport.kv_capacity_percent, healthyreport.healthy, ) store.upsert(state) return {ok: True} app.get(/route) def route(prefix: str): candidates store.get_by_prefix(prefix) if not candidates: # 没有命中缓存走默认 prefill 逻辑 raise HTTPException(status_code404, detailno cache candidate) target candidates[0] return {node_id: target.node_id, address: target.address}这个实现的重点是让路由决策可测试。你完全可以在本地用 Python 跑一套伪造的 CacheReport再调用/route接口验证选择逻辑。真实部署时还需要把路由服务和推理引擎的“实际前缀”打通而不是让实例手动上报。5.2 路由决策与 KV Cache 命中的结合上面的例子把“缓存命中”作为最高优先级但实际系统的路由决策不会这么简单。一个请求的 Prompt 可能只有一部分能命中缓存而不是全部命中。更合理的做法是把 Prompt 划分成多个缓存块例如 64 个 token 为一块路由服务计算出请求中可命中的缓存块数量再除以总块数得到“缓存命中率”。然后使用一个综合公式得分 缓存命中率权重 × 命中率 容量权重 × 剩余KV容量 健康度权重 × 节点健康度这样既能照顾已有的公共前缀缓存又不会把所有流量都打到同一个节点避免某个节点的 KV Cache 容量被打满。以下是一个可扩展的决策函数# routing_policy.py from dataclasses import dataclass from cache_state import CacheState dataclass class RoutingPolicy: hit_weight: float 0.5 capacity_weight: float 0.3 health_weight: float 0.2 def route_score(policy: RoutingPolicy, state: CacheState, hit_ratio: float) - float: capacity_score max(0.0, 100.0 - state.kv_capacity_percent) / 100.0 health_score 1.0 if state.healthy else 0.0 return ( policy.hit_weight * hit_ratio policy.capacity_weight * capacity_score policy.health_weight * health_score ) def choose_target(policy, states, hit_ratios): return max( states, keylambda s: route_score(policy, s, hit_ratios[s.node_id]) )这类策略的最大好处是可以在线调整权重。例如发现 KV Cache 命中率虽然高但某些节点长时间被打满导致请求排队就可以降低hit_weight、提高capacity_weight让路由层更积极地分散流量。5.3 PD 分离后的转发逻辑当请求经过 Prefill 后路由层要把 KV Cache 状态转发给 Decode 节点。这个逻辑在真实系统中往往通过后端专用协议完成而不是在 HTTP 层传递显存数据。但在最小实现中可以抽象为两步调用 Prefill 服务生成 KV Cache 引用。将 KV Cache 引用与待解码的 token 序列发送给 Decode 服务。下面的示例展示了一个伪实现重点在于表达“路由选择发生在每个阶段之前”# pd_dispatcher.py import httpx class PDDispatcher: def __init__(self, prefill_candidates, decode_candidates): self.prefill_candidates prefill_candidates self.decode_candidates decode_candidates async def infer(self, prompt: str): async with httpx.AsyncClient() as client: prefill_node self.prefill_candidates[0] pre_payload { prompt: prompt, generate: False } pre_resp await client.post( fhttp://{prefill_node}/v1/prefill, jsonpre_payload ) kv_ref pre_resp.json()[kv_cache_ref] decode_node self.decode_candidates[0] decode_payload { kv_cache_ref: kv_ref, max_tokens: 256 } final_resp await client.post( fhttp://{decode_node}/v1/decode, jsondecode_payload ) return final_resp.text这段代码为了可读性省略了异常重试、超时和真实的 KV Cache 传输但它表达了一个关键思想在 PD 分离架构里路由发生在 Prefill 决定服务哪台机器之前也发生在 Decode 决定服务哪台机器之前。路由不是把请求“随便转发到一个后端”而是把每个阶段的请求定向到“对该阶段最优的实例”。6. 运行验证与效果评估部署完成后不能只看 Pod 都 Running就认为 PD 分离和 KV 缓存感知路由已经生效。需要有一组明确的验证路径。6.1 启动路由服务与推理实例如果使用示例路由服务先在本机或集群中启动uvicorn router_api:app --host 0.0.0.0 --port 8001然后分别向/report上报两个节点的缓存状态curl -X POST http://127.0.0.1:8001/report \ -H Content-Type: application/json \ -d {node_id:decode-0,address:10.0.0.10:8000,prefixes:[sys_prompt_fingerprint],kv_capacity_percent:20.0,healthy:true} curl -X POST http://127.0.0.1:8001/report \ -H Content-Type: application/json \ -d {node_id:decode-1,address:10.0.0.11:8000,prefixes:[],kv_capacity_percent:60.0,healthy:true}随后请求路由curl http://127.0.0.1:8001/route?prefixsys_prompt_fingerprint预期输出会指向decode-0因为只有它上报了匹配前缀并且 KV 容量更充足。如果切换到一个未缓存的前缀路由服务应当返回 404 或选择一个默认 Prefill 节点。6.2 在 Kubernetes 中验证路由结果如果路由服务与推理引擎都是独立 Pod可以通过响应头或业务日志看到路由结果。例如在推理引擎中记录x-node-id响应头kubectl logs -l roledecode --tail10 -n inference同时观察请求的分布。如果你连续发送多轮带有相同 system prompt 的请求且路由层实现正确则所有首轮请求应被路由到同一组命中缓存的 Decode 节点而不是随机散到全部节点。此时缓存命中率会快速上升。6.3 关键指标与判断标准建议在路由层与推理实例都暴露 Prometheus 指标至少包含llm_router_cache_hit_total缓存命中的请求总数。llm_router_cache_hit_ratio缓存命中率成功率通常应保持在 50% 以上才有收益。llm_prefill_queue_secondsPrefill 排队延迟。llm_decode_kv_cache_usage_percentDecode 节点 KV Cache 使用率。llm_request_e2e_seconds完整请求端到端延迟。判断 PD 分离是否有效不能只看平均延迟而要看“长上下文首 token 延迟”和“吞吐每秒生成 token 数”是否同时改善。如果首 token 延迟下降了但 KV Cache 使用率长期超过 90%说明缓存容量已经打满需要扩容 Decode 节点池或优化缓存驱逐策略。7. 常见问题与排查思路KV 缓存感知路由和 PD 分离在生产环境经常遇到以下问题下面按“现象-可能原因-排查方式-解决方案”整理问题现象可能原因排查方式解决方案缓存命中率长期偏低前缀规范化不一致比如请求系统提示词每次都有微小差异检查路由层拆解的 prompt 前缀和实例上报的前缀是否有差异在网关层统一处理系统提示词对 prompt 做规范化和指纹化路由层感知的节点状态过期推理实例只在上报一次状态成功后就不再更新查看路由服务日志尽量让实例通过心跳定期上报状态缩短上报周期增加状态过期时间在过期后自动剔除节点所有流量都集中到某几个节点路由打分中缓存命中权重过高或容量权重设置过低查看路由决策日志确认命中率打分是否掩盖了容量问题动态调整权重增加随机抖动避免同分节点全部命中同一个目标Prefill 完成后 Decode 节点无法接收 KV Cache网络隔离或 Pod IP 变化检查 Kubernetes Headless Service 的 Endpoints 是否更新确认跨节点网络策略使用 Pod DNS 或 StatefulSet 稳定地址开放必要的端口Decode 节点显存不足导致 OOMmax-kv-cache-tokens设置过大或缓存驱逐策略未启用查看 GPU 显存监控和容器 OOMKilled 事件降低 KV Cache 容量上限配置严格的缓存驱逐策略长上下文请求延迟反而更高PD 分离引入跨节点 KV Cache 传递而网络带宽不够比较相同请求在混合部署和 PD 分离部署下的网络传输耗时将 Prefill 和 Decode 部署在相近节点池内优先使用内网高带宽网络滚动升级导致全部缓存失效升级时路由层不能意识到某个实例正在重启流量仍被路由过去查看滚动更新期间的请求失败率和缓存命中率用 PreStop 钩子将实例标记为不健康等待缓存耗尽后再更新这里最容易被忽略的是“前缀规范化”。很多人以为同一个 system prompt 在代码里是常量就一定能命中但实际上如果用户输入携带了偶然的空格、换行或 BOS token前缀指纹就会不同。解决思路是在路由层统一对 prompt 做规则化处理比如去掉尾部空白、统一换行符、固定系统提示词拼接顺序。8. 最佳实践与工程建议这套架构从“能跑”到“能稳定支撑生产流量”中间还差一层工程细节。8.1 路由状态不要直接依赖 Kubernetes ServiceKubernetes Service 的负载均衡是随机的它不知道 KV Cache 状态因此不适合作为“缓存感知路由”的直接载体。更稳妥的做法是路由服务通过 Headless Service 拿到所有 Pod IP再根据自身维护的缓存状态表做决策。如果缓存状态丢失Kubernetes 的 Endpoints 仍然可以作为兜底保证请求不中断。8.2 缓存状态上报与探测要分开路由层一方面需要节点上报“我缓存了哪些前缀”另一方面需要主动探测“节点是否健康、容量是否足够”。把这两类信息混在一个接口里容易在节点网络抖动时产生误判。建议分开健康探测用短超时的 TCP/HTTP 请求缓存状态上报用可容忍一定延迟的异步队列。8.3 给缓存设置过期时间业务在白天和晚上的 prompt 分布很可能不同。如果某个节点缓存了大量白天时段的高频前缀晚上这些缓存就失去价值。路由层需要给缓存前缀设置 TTL过期后主动清理状态表中的旧记录。否则时间久了路由层会为了“看似命中但实际已失效”的缓存前缀选择糟糕的目标节点。8.4 为 PD 分离设置资源隔离与配额Preflfill 和 Decode 虽然是同一模型的不同阶段但它们在 CPU、内存、网络带宽上的需求差异很大。建议在 Kubernetes 中为两类节点池设置不同的 ResourceQuota 和 LimitRange避免某类节点被误调度。比如 Prefill 节点专门处理长输入可以限制最大并发请求数Decode 节点专门生成长 output可以限制每请求最大生成 token 数。8.5 安全与权限边界路由层需要调用 Kubernetes API 获取 Endpoints或者访问内部服务发现组件。要最小化这个权限不要给路由服务授予整个集群的管理权限。只授予对pods和endpoints的get/list/watch权限即可。同时涉及缓存状态上报的端口不要直接暴露到公网建议放在独立的内网网段并通过 namespace 网络策略限制来源。8.6 可观测性建设不可跳过由于一个请求可能经过“客户端 → 路由 → Prefill → Decode → 客户端”多跳链路没有链路追踪很难定位性能瓶颈。推荐在请求头中注入 trace ID让路由服务和推理引擎都输出结构化日志并记录每个阶段的开始时间、结束时间、目标节点和缓存命中情况。这样才能知道延迟究竟是消耗在 Prefill、网络传输还是 Decode 上。8.7 先小规模验证再全量推广不要一上来就把所有推理流量切到 PD 分离。更稳妥的路径是先在一个测试 namespace 部署独立的 Prefill 和 Decode 节点池用压测脚本模拟“相同系统提示词、不同用户/不同问题”的请求分布对比混合部署与 PD 分离的首 token 延迟、吞吐和 GPU 利用率。只有当缓存命中率稳定且收益明显时才逐步将线上流量切到新的路由方案。9. 总结与后续学习方向KV Cache 感知路由与 PD 分离之所以值得认真研究是因为它们把推理优化从“单卡内部优化”推进到了“多卡调度与路由”这个维度。单卡层面的优化已经越来越接近显存和算力的上限而集群层面的资源编排还有很大空间。对一个多副本、长上下文、高并发的 LLM 服务来说KV Cache 就是长期存在的热点资源谁能感知它、调度它谁就能更充分地利用 GPU。如果你接下来要继续深入建议按三条线走第一研究具体推理引擎比如 vLLM、SGLang的 Prefix Caching 实现弄清它们如何生成缓存键、如何驱逐缓存第二结合 Kubernetes 自定义控制器把路由层的状态表做成可观测的 CRD 资源让 Pod 的调度和路由决策真正联动第三在 PD 分离基础上继续探索异步预填充、推测解码、模型并行与缓存路由的组合。关键是要先跑通一个最小链路让“KV Cache 状态影响路由决策”这句话变成可测试、可量化的代码再逐步把它变强。