news 2026/10/23 6:47:51

K8s 上大模型推理优化:KV Cache 感知路由与 Prefill/Decode 分离

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s 上大模型推理优化:KV Cache 感知路由与 Prefill/Decode 分离

不少团队在把大模型推理服务放到 Kubernetes 上之后,很快就会遇到一个尴尬场景:请求的上下文越长,显存越吃紧,GPU 利用率越低,响应耗时越不稳定。尤其是长上下文或者多轮对话场景,前一个请求的 KV Cache 还没释放,后一个请求又涌进来,导致 P99 延迟持续抬高,甚至触发 OOM。

为了解决这类问题,业界逐渐出现了两种关键手段:KV Cache 感知路由和Prefill / Decode 分离。前者让请求被调度到拥有合适缓存状态的实例上,后者把两阶段计算拆开,让不同算力特征的节点各司其职。本文将围绕这两个技术方向,说明它们的基本原理、Kubernetes 上的落地方式,以及可以运行的实战示例。

如果你正在做大模型推理服务化、Kubernetes 上部署 LLM 应用,或者负责推理平台的资源调度,这篇文章应该能帮你把思路理清楚。

1. KV Cache 与 LLM 推理的基本背景

1.1 LLM 推理为什么有“两阶段”

目前主流的大模型推理服务,例如 vLLM、TGI、SGLang 等,在生成回答时都会经历两个阶段:

  • 预填充阶段(Prefill):用户输入 Prompt 后,模型需要并行处理这整段文本,产生中间状态,包括生成第一个输出 token。这个阶段是典型的计算密集型任务,需要做大量矩阵运算,GPU 利用率高。
  • 解码阶段(Decode):模型逐个 token 生成后面的内容。每生成一个 token,都需要读取此前所有的 Key 和 Value 信息,所以这个阶段是典型的访存密集型任务,GPU 算力利用率反而不高,瓶颈更多在显存带宽和 KV Cache 读取。

从数学上看,两阶段的耗时特征差异是非常明显的:

  • Prefill 阶段的耗时接近T_prefill ≈ O(P),其中 P 是 Prompt 长度,GPU 算力越强、并行度越高,处理越快。
  • Decode 阶段每生成一个 token 都需要访问全部已计算的 KV Cache,耗时接近T_decode ≈ O(C),其中 C 是上下文长度,访问的数据量越大,生成越慢。

1.2 KV Cache 是什么

KV Cache 是 Transformer 架构推理时的一种缓存机制。模型在解码阶段计算注意力时,需要用到历史上所有 token 对应的 Key 和 Value 向量。为了不每生成一个 token 就重新计算前面所有 token 的 Key 和 Value,推理引擎会把它们缓存下来,这个缓存就是 KV Cache。

KV Cache 的大小可以按下面的近似公式估算:

KV Cache 内存 ≈ 2 × 层数 × Batch Size × 序列长度 × 注意力头数 × Head 维度 × 每个元素字节数

举个例子,一个 7B 模型如果配置 32 层、32 个注意力头、Head 维度 128,用 FP16 存储,batch_size=16、seq_len=2048时,KV Cache 大约占用:

2 × 32 × 16 × 2048 × 32 × 128 × 2 ≈ 8.6 GB

这只是一个 batch 状态下的占用。如果并发请求多、上下文长,KV Cache 很容易成为显存中的大头。

1.3 传统 Kubernetes 路由为什么不够用

Kubernetes Service 默认的负载均衡方式,要么是轮询,要么是随机连接,要么是基于连接数的简单均衡。它根本不知道 Pod 内部当前的 KV Cache 状态。

如果请求被随机分发到多个推理实例,就会遇到几个问题:

  1. 同一个用户的多轮对话,被分发到不同 Pod,前文的 KV Cache 无法复用。
  2. 某个 Pod 的显存已接近上限,但负载均衡器仍然把新请求打进去,OOM 风险升高。
  3. Pod 的缓存命中率低,推理服务被迫重复执行 Prefill,浪费 GPU 算力。

所以,单纯依赖 Kubernetes Service 做流量转发,在大模型推理场景下是不够的。我们需要在路由层面感知每个实例的 KV Cache 状态,从而做到缓存感知路由。

2. 核心概念拆解

2.1 KV Cache 感知路由

所谓 KV Cache 感知路由,就是请求进入网关或负载均衡层时,调度组件根据后端实例上报的缓存状态,决定该把请求转发给谁。

这里的关键信息包括:

  • 每个实例当前缓存的 token 前缀(例如prompt_hash或prefix_id)。
  • 每个实例当前的 KV Cache 已用显存。
  • 每个实例的剩余可用 KV Cache 空间。
  • 每个实例当前排队的请求数量。

举例说明,如果有三个推理 Pod:

Pod缓存前缀 ID空闲 KV Cache当前队列深度
Pod Aprefix-0078 GB2
Pod Bprefix-0072 GB6
Pod Cprefix-00910 GB1

当新请求的前缀是prefix-007时,路由层应该优先把请求转发给 Pod A,因为 Pod A 既有对应前缀的缓存,又有充足的 KV Cache 空间,队列深度也小。

这种路由策略带来的收益是:

  • 提高 KV Cache 前缀命中率。
  • 减少重复 Prefill 计算。
  • 均衡实例间的显存压力。
  • 降低响应延迟的抖动幅度。

2.2 Prefill / Decode 分离

把 Prefill 和 Decode 两个阶段拆分到不同的计算节点上执行,就是预填充/解码分离架构,国外一般称为 P/D Disaggregation。

传统做法是一个推理实例同时处理 Prefill 和 Decode。但两种阶段对资源的需求完全不同:

维度PrefillDecode
计算特征高计算密度,适合大批量并行访存瓶颈大,单 token 延迟敏感
GPU 利用率高较低
显存压力主要保存中间计算状态主要存储 KV Cache
目标尽快完成 Prompt 处理稳定低延迟逐 token 输出

如果混部在同一个 Pod 里,你会很难同时做好两件事:GPU 强算力的机器被 Decode 阶段拖慢,显存充足的机器又可能因为 Prefill 瞬时高负载而抖动。

P/D 分离之后,架构变成:

客户端请求 ↓ 网关/路由层(KV Cache 感知路由) ↓ Prefill 实例池(负责处理 Prompt,产生 KV Cache) ↓ KV Cache 传输 ↓ Decode 实例池(负责逐 token 生成) ↓ 流式返回响应

Prefill 实例将生成的 KV Cache 通过高速网络传递给 Decode 实例,Decode 实例续接缓存并继续生成,最终把结果流式返回给客户端。

2.3 两者的关系

KV Cache 感知路由和 P/D 分离并不是互斥方案,而是可以协同工作:

  • 在 P/D 分离架构下,Prefill 节点需要把新生成的 KV Cache 路由到合适的 Decode 节点,这里同样依赖 KV Cache 状态感知。
  • 多轮对话命中缓存时,可以绕过 Prefill 节点,直接由 Decode 节点接管,这需要路由层识别请求前缀。
  • 一个请求的 KV Cache 从 Prefill 节点迁移到 Decode 节点,实际上是一次有状态的调度,必须感知目标节点的缓存容量。

因此,KV Cache 感知路由可以视为 P/D 分离架构中的核心调度组件。

3. 系统架构设计

3.1 整体架构

在 Kubernetes 上实现 KV 缓存感知路由和 P/D 分离,整体架构可以按下面的层次拆分:

客户端 ↓ Ingress / 网关层(路由决策入口) ↓ 路由服务(KV Cache 感知调度器) ↓ Kubernetes API(读取 Pod 状态、元数据) ↓ Prefill Deployment → Prefill Pod 池 ↓ KV Cache 共享/传输 Decode Deployment → Decode Pod 池 ↓ 监控与指标采集

路由服务需要感知两类信息:

  1. 静态信息:Pod 的标签、注解、调度分组。
  2. 动态信息:每个 Pod 当前缓存的前缀 ID、KV Cache 剩余容量、请求队列长度。

静态信息可以通过 Kubernetes API 直接获取,动态信息则需要推理引擎暴露出来。

3.2 路由决策流程

路由决策可以分为几个步骤:

  1. 请求进入网关。
  2. 网关解析 Prompt,计算前缀 Hash 或从请求头提取会话 ID。
  3. 路由服务查询实例状态表,筛选出符合前缀缓存的节点。
  4. 在候选节点中,结合 KV Cache 剩余空间、队列深度、GPU 负载进行打分。
  5. 得分最高的节点作为目标节点。
  6. 如果所有节点的前缀缓存均未命中,则选择 KV Cache 空间最充足的 Prefill 节点。

这个流程可以用伪代码表示:

def select_instance(request, instance_table): """ request: 包含 prompt 前缀 hash、上下文长度等信息 instance_table: 所有推理实例的动态状态 """ prefix_hash = request.prefix_hash # 第一步:过滤出命中前缀缓存的实例 hit_instances = [ ins for ins in instance_table if prefix_hash in ins.cached_prefixes ] if hit_instances: # 第二步:在命中缓存的实例中,按资源余量和负载打分 hit_instances.sort(key=lambda ins: ( ins.kv_cache_free_bytes, -ins.queue_depth ), reverse=True) return hit_instances[0] # 第三步:没有命中缓存,去 Prefill 池找空间最充足的实例 prefill_instances = [ ins for ins in instance_table if ins.role == "prefill" ] prefill_instances.sort(key=lambda ins: ins.kv_cache_free_bytes, reverse=True) return prefill_instances[0]

这是简化版本,核心逻辑就是“先命中缓存,再考虑资源容量和负载”。

3.3 需要采集的指标

要让路由组件感知 KV Cache 状态,推理服务在 Pod 内需要暴露至少以下指标:

kv_cache_prefix_hits_total # 请求命中缓存的次数 kv_cache_prefix_miss_total # 请求未命中缓存的次数 kv_cache_used_bytes # 已使用的 KV Cache 显存 kv_cache_total_capacity_bytes # 可分配的 KV Cache 总容量 kv_cache_prefix_ids # 当前缓存的 token 前缀 ID 集合 gpu_queue_depth # 当前 Pod 队列中的请求数 gpu_running_requests # 正在执行推理的请求数

这些指标可以通过 Prometheus 格式暴露,也可以以 JSON 接口的形式由路由组件主动拉取。

4. 基于 Kubernetes 的实现方案

4.1 环境准备与版本说明

下面给出一个可运行的示例,基于以下环境:

  • Kubernetes 1.26 及以上版本。
  • Python 3.9 以上,用于编写路由示例代码。
  • FastAPI 用于实现指标采集接口。
  • vLLM 或任意支持 KV Cache 前缀缓存的推理引擎,作为 Pod 内的推理服务。

版本需要根据你的实际环境调整,本文重点演示配置思想,代码在常见环境均可运行。

4.2 创建命名空间和实例池

首先创建命名空间和两组 Deployment,一组对应 Prefill 实例,一组对应 Decode 实例。

apiVersion: v1 kind: Namespace metadata: name: llm-inference

Prefill Deployment 示例:

apiVersion: apps/v1 kind: Deployment metadata: name: prefill-pool namespace: llm-inference spec: replicas: 2 selector: matchLabels: app: llm-worker role: prefill template: metadata: labels: app: llm-worker role: prefill spec: containers: - name: prefill-engine image: vllm/vllm-openai:latest args: - --model - /models/llama-7b - --gpu-memory-utilization - "0.90" - --enforce-eager ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: "1"

Decode Deployment 示例:

apiVersion: apps/v1 kind: Deployment metadata: name: decode-pool namespace: llm-inference spec: replicas: 3 selector: matchLabels: app: llm-worker role: decode template: metadata: labels: app: llm-worker role: decode spec: containers: - name: decode-engine image: vllm/vllm-openai:latest args: - --model - /models/llama-7b - --gpu-memory-utilization - "0.90" ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: "1"

这里的关键是给不同角色的 Pod 打上不同的role标签,路由组件可以利用 Kubernetes 标签选择器筛选对应 Pod。

4.3 Pod 内指标采集服务

为了让路由组件感知实例状态,需要在每个推理 Pod 中额外运行一个指标暴露服务。下面用 FastAPI 做一个简单的示例。

# 文件路径:worker_metrics/main.py """ 每个推理 Pod 内运行的指标服务。 实际项目中建议由推理引擎直接暴露这些指标,这里为了演示单独实现。 """ import os import random import time from fastapi import FastAPI app = FastAPI() @app.get("/metrics") def get_metrics(): """ 模拟返回 KV Cache 状态指标。 实际场景中,这里应该调用推理引擎的统计接口,例如 vLLM 的 /metrics。 """ prefix_ids = ["prefix-007", "prefix-009"] if random.random() > 0.5: prefix_ids.append("prefix-012") return { "pod_name": os.getenv("HOSTNAME", "unknown"), "role": os.getenv("ROLE", "unknown"), "kv_cache_used_bytes": random.randint(4_000_000_000, 10_000_000_000), "kv_cache_total_capacity_bytes": 20_000_000_000, "queue_depth": random.randint(0, 6), "cached_prefix_ids": prefix_ids, "timestamp": time.time(), }

暴露cached_prefix_ids时需要注意,真实场景中前缀 ID 可以用 Prompt 的 Hash 值来标识。相同前缀的请求可以复用同一个 KV Cache 段。

4.4 路由服务实现

路由服务可以使用 Python 实现,它需要定期采集各 Pod 的指标,形成一个实例状态表。

# 文件路径:router_service/route.py """ KV Cache 感知路由服务的核心逻辑。 简化版,只展示路由决策部分,实际生产环境需要结合 gRPC 或 HTTP 转发。 """ class InferenceInstance: def __init__(self, pod_name, role, cached_prefix_ids, kv_cache_used_bytes, kv_cache_total_capacity_bytes, queue_depth): self.pod_name = pod_name self.role = role self.cached_prefix_ids = cached_prefix_ids self.kv_cache_free_bytes = kv_cache_total_capacity_bytes - kv_cache_used_bytes self.queue_depth = queue_depth class RouteDecision: def __init__(self, instances): self.instances = instances def select(self, prefix_hash): # 优先选择命中前缀缓存的实例 hit_instances = [ ins for ins in self.instances if prefix_hash in ins.cached_prefix_ids ] if hit_instances: hit_instances.sort( key=lambda ins: ins.kv_cache_free_bytes, reverse=True, ) return hit_instances[0] # 缓存未命中,选择 Prefill 池中剩余空间最大的实例 prefill_instances = [ ins for ins in self.instances if ins.role == "prefill" ] prefill_instances.sort( key=lambda ins: ins.kv_cache_free_bytes, reverse=True, ) return prefill_instances[0] def demo(): instances = [ InferenceInstance( pod_name="prefill-1", role="prefill", cached_prefix_ids=["prefix-007"], kv_cache_used_bytes=6_000_000_000, kv_cache_total_capacity_bytes=20_000_000_000, queue_depth=2, ), InferenceInstance( pod_name="decode-1", role="decode", cached_prefix_ids=["prefix-009", "prefix-007"], kv_cache_used_bytes=8_000_000_000, kv_cache_total_capacity_bytes=20_000_000_000, queue_depth=1, ), ] router = RouteDecision(instances) chosen = router.select("prefix-007") print(f"请求 prefix-007 被路由到: {chosen.pod_name}") if __name__ == "__main__": demo()

输出预期:

请求 prefix-007 被路由到: decode-1

因为decode-1命中了prefix-007,且其 KV Cache 剩余空间为 12GB,大于prefill-1,所以被选中。

这是路由策略的核心思想。实际生产环境中,还需要把这个决策结果和负载均衡器、网关联动,变成真正的流量转发动作。

4.5 定义 CRD 扩展调度信息

在实际项目中,Pod 之间的 KV Cache 分布、缓存前缀信息会不断变化,单纯依靠 Kubernetes 原生字段很难承载这类动态数据。常用的方式是定义 CRD(Custom Resource Definition)来描述推理实例状态。

下面是一个简化示例:

apiVersion: inference.example.com/v1 kind: WorkerStatus metadata: name: decode-1-status namespace: llm-inference spec: workerRef: name: decode-1 kind: Pod cachedPrefixes: - prefix-007 - prefix-009 kvCacheUsedBytes: 8000000000 kvCacheTotalBytes: 20000000000 queueDepth: 1

通过 CRD,路由服务可以以 Kubernetes 原生的方式读取、监听推理实例状态变化,而不是额外维护一套外部存储。这个方案适合平台化、规模化的推理服务集群。

4.6 调度联动策略

KV Cache 感知路由要真正生效,还需要与 Kubernetes 调度联动。比较常见的做法有:

  1. 在网关层拦截请求,直接路由到指定 Pod,不经过 Service。
  2. 为每个 Pod 创建独立 Service Endpoint,路由服务控制转发目标。
  3. 使用自定义调度器扩展,在 Pod 创建时决定把 Prefill / Decode 实例调度到哪台节点。
  4. 结合topology.kubernetes.io/zone等标签,尽量让 KV Cache 在物理网络上短距离传输。

如果是 P/D 分离架构,还需要考虑 KV Cache 的传输线路。Prefill 节点完成计算后,需要把 KV Cache 序列化并传给 Decode 节点。这个传输过程比较占用网络带宽,所以在 Kubernetes 节点分组时,最好让 Prefill 和 decode 节点所在的主机处于同一个高带宽网络区域内。

5. 实战案例:vLLM 前缀缓存 + 路由联动

5.1 场景设计

假设我们要部署一个在线问答服务,模型是 Llama-3-8B。用户会围绕同一个长文档进行多轮提问。

因为我们希望:

  • 用户第一轮提问时,请求被路由到 Prefill 实例。
  • 用户后续追问时,请求能命中 KV Cache,且被直接路由到持有缓存前缀的 Decode 实例。

所以必须让路由层看到prompt hash。常见做法是请求进入网关时,网关层解析 Prompt 的前缀,或者直接由客户端把prompt_hash写入 HTTP Header。

curl -X POST http://llm-gateway:8080/v1/completions \ -H "Content-Type: application/json" \ -H "X-Prompt-Hash: prefix-007" \ -d '{"prompt": "请总结文档第3章内容"}'

网关根据X-Prompt-Hash查询路由表,转发到对应 Pod。

5.2 网关代码示例

下面是一个基于 FastAPI 的轻量级网关,它会在收到请求后调用路由决策模块,选择目标 Pod 并将请求转发过去。

# 文件路径:gateway/main.py """ KV Cache 感知网关示例。 简化版:实际项目中应使用异步 HTTP 客户端,并处理超时、重试、限流等问题。 """ import httpx from fastapi import FastAPI, Request from route import RouteDecision, InferenceInstance app = FastAPI() # 这里模拟实时拉取到的实例列表 # 实际项目中,应该从 Kubernetes API 或 CRD 中动态获取 instances = [ InferenceInstance( pod_name="prefill-0", role="prefill", cached_prefix_ids=["prefix-007"], kv_cache_used_bytes=5_000_000_000, kv_cache_total_capacity_bytes=20_000_000_000, queue_depth=1, ), InferenceInstance( pod_name="decode-0", role="decode", cached_prefix_ids=["prefix-007", "prefix-009"], kv_cache_used_bytes=9_000_000_000, kv_cache_total_capacity_bytes=20_000_000_000, queue_depth=2, ), ] router = RouteDecision(instances) @app.post("/v1/completions") async def completions(request: Request): body = await request.json() prompt_hash = request.headers.get("X-Prompt-Hash", "unknown") chosen = router.select(prompt_hash) target_url = f"http://{chosen.pod_name}.llm-inference.svc.cluster.local:8000/v1/completions" async with httpx.AsyncClient(timeout=300) as client: resp = await client.post(target_url, json=body) return resp.json()

这个网关的存在价值,是把路由决策和流量转发聚合在一个入口,对客户端保持统一 API 形态。

5.3 验证完整流程

部署完成后,可以按以下步骤进行验证。

第一步,查看实例状态:

kubectl get pods -n llm-inference -o wide

预期能看到prefill-pool和decode-pool下的 Pod 都在运行。

第二步,发送第一个请求,并携带新的前缀 Hash:

curl -X POST http://llm-gateway:8080/v1/completions \ -H "Content-Type: application/json" \ -H "X-Prompt-Hash: prefix-020" \ -d '{"prompt": "请解释 Kubernetes 调度器原理"}'

这个请求的前缀prefix-020不在任何实例的缓存列表中,因此会被路由到 Prefill 实例。

第三步,再次发送相同前缀的请求:

curl -X POST http://llm-gateway:8080/v1/completions \ -H "Content-Type: application/json" \ -H "X-Prompt-Hash: prefix-020" \ -d '{"prompt": "请继续解释 Pod 生命周期"}'

如果路由决策模块监测到prefix-020已经在某个实例上缓存,后续请求会被转发到该实例,从而复用 KV Cache,避免重复 Prefill。

5.4 结果说明

在实际负载下,命中缓存的请求 P99 延迟通常会有明显下降。因为解码阶段不需要重复计算前文,直接从已缓存的 KV Cache 开始生成即可。

量化地说,如果一个长文档有 4000 token,用户 10 轮追问都基于同一文档,那么在非缓存路由架构下,每一轮都需要重复处理 4000 token 的 Prompt,总计 40000 token 的 Prefill 计算。而在 KV Cache 感知路由架构下,第一轮 Prefill 处理 4000 token,后面 9 轮全部命中缓存,Preill 计算量减少约 90%。

6. P/D 分离中的 KV Cache 传输

6.1 为什么需要传输

P/D 分离架构中,KV Cache 在 Prefill 节点生成后需要传给 Decode 节点。这就涉及一个之前单体推理服务中不存在的问题:KV Cache 如何高效传输。

常见方式有:

  1. 分布式缓存共享:使用 Redis、Memcached 或专门的 KV Cache 存储层,把 KV Cache 持久化或暂存。
  2. RDMA / 高速网络直传:Prefill 节点和 Decode 节点之间通过 RDMA 或 InfiniBand 直接传输张量。
  3. Kubernetes 内网传输:在普通 TCP 网络环境下,把 KV Cache 序列化之后通过 Pod 网络传过去。

普通 TCP 传输在规模较小时可行,但在高并发场景下会成为瓶颈。一个 8B 模型在上下文 4096 token 时的 KV Cache 可能有几百 MB 到数 GB,如果是高 QPS 服务,网络负载会非常大。

6.2 在 Kubernetes 中降低传输损耗

从调度层面可以做一些优化:

  • 把 Prefill Pod 和 Decode Pod 尽量调度到同一台节点,或者同一机架,减少跨交换机传输。
  • 使用节点亲和性,把相同模型的 Prefill / Decode 实例绑定在同一拓扑域。
  • 给调度器传入自定义评分逻辑,优先选择与 KV Cache 源节点网络距离短的节点。

示例调度配置:

apiVersion: apps/v1 kind: Deployment metadata: name: decode-pool namespace: llm-inference spec: template: spec: affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - llm-worker - key: role operator: In values: - prefill topologyKey: topology.kubernetes.io/zone

这段配置表示,在调度 Decode Pod 时,尽量让它和 Prefill Pod 处于同一个可用区,降低 KV Cache 传输延迟。

6.3 传输后的一致性保障

传输 KV Cache 时需要注意,不同批次、不同序列长度的请求生成的 Cache 结构不同。传输协议需要包含以下信息:

  • 模型版本和配置,确保接收方模型结构一致。
  • 原始 Prompt 的分词 ID 序列。
  • 各层的 Key 张量和 Value 张量。
  • Cache 对应的前缀长度。

在典型实现中,传输层会把这些信息封装成一个“状态包”。接收方拿到后,先校验模型 ID,再将张量加载进显存。

7. 常见问题与排查思路

7.1 路由不生效,请求仍打到了无缓存实例

问题现象常见原因解决思路
请求总是命中同一个 Pod,且没有走缓存路由网关按轮询策略转发,没有读取路由决策检查网关代码是否真的使用了路由服务返回的目标地址
多个副本状态一致,但路由结果混乱实例状态表长时间未刷新检查指标采集器的同步周期,确保状态表更新频率高于请求频率
前缀 Hash 计算不一致客户端和网关对 Prefix 的 Hash 算法不同统一 Prefix Hash 计算逻辑,建议在网关内计算,不依赖客户端

排查顺序建议是:

  1. 先确认目标 Pod 上是否有对应前缀的 KV Cache。
  2. 确认路由服务读到的实例状态是否最新。
  3. 打印路由决策日志,确认决策过程。
  4. 确认流量转发链路用的是路由结果,而不是 Service VIP。

7.2 P/D 分离后延迟反而更高

P/D 分离并不是在所有场景下都有收益。如果你的 Prompt 很短,缓存命中率低,而且 KV Cache 传输耗时超过了重复 Prefill 的耗时,那么分离架构反而会带来额外开销。

这种情况下,需要评估收益边界:

场景Prefill 计算量Cache 传输量是否适合 P/D 分离
短 Prompt,高并发小相对小不一定
长文档多轮问答大大但命中率高适合
低并发,单请求长输出小大不适合
高并发,长上下文大大适合

建议先通过监控数据评估当前系统的 Prefill 耗时占比和缓存命中率,再决定是否做架构拆分。

7.3 Pod 经常 OOM,显存不足

KV Cache 容量是推理服务显存管理的核心。出现 OOM 时,需要检查:

  1. 实例的最大并发数是多大,是否超出了 KV Cache 容量上限。
  2. 是否合理设置gpu-memory-utilization。
  3. 路由层是否在 KV Cache 剩余空间不足时,仍然把请求发到了该实例。

在路由决策中,加入一个“最低剩余空间阈值”是一个常见的保护措施:

MIN_KV_CACHE_FREE_BYTES = 2_000_000_000 # 2GB 阈值 def select(self, prefix_hash): hit_instances = [ ins for ins in self.instances if prefix_hash in ins.cached_prefix_ids and ins.kv_cache_free_bytes >= MIN_KV_CACHE_FREE_BYTES ] if hit_instances: hit_instances.sort( key=lambda ins: ins.kv_cache_free_bytes, reverse=True, ) return hit_instances[0] prefill_instances = [ ins for ins in self.instances if ins.role == "prefill" and ins.kv_cache_free_bytes >= MIN_KV_CACHE_FREE_BYTES ] prefill_instances.sort( key=lambda ins: ins.kv_cache_free_bytes, reverse=True, ) if prefill_instances: return prefill_instances[0] # 所有实例空间都不足,应该触发限流或扩容 raise RuntimeError("no available instance, need scale up or reject")

这样可以在 KV Cache 空间不足时,从“继续转发”变成“拒绝或扩容”,降低 OOM 概率。

7.4 缓存命中率低

缓存命中率低通常有以下原因:

  • 请求前缀变化太大。如果每个用户的 Prompt 前缀都不相同,命中率自然低。
  • 缓存淘汰策略激进,缓存被频繁清除。
  • 路由策略没有基于前缀 Hash 做选择。

如果要提升命中率,可以考虑:

  • 使用结构化 Prompt 模板,让相同意图的请求共享前缀。
  • 在推理引擎中开启 Prefix Caching。
  • 适当扩大每实例的 KV Cache 容量。
  • 增加缓存预热机制,提前把高频文档的 Prefix 加载到实例中。

8. 最佳实践与工程建议

8.1 先把吞吐瓶颈量化清楚

在实际投入重构之前,先回答几个问题:

  • 当前 Prefill 耗时占比是多少?
  • 当前 KV Cache 平均占用率是多少?
  • 缓存命中率是多少?
  • P99 延迟主要贡献来自 Prefill 还是 Decode?

把这些指标监控起来之后,再决定是否需要做 P/D 分离,以及采用多大比例的 Prefill / Decode 实例池。

8.2 路由层要保留降级策略

KV Cache 感知路由依赖实例状态数据的实时上报。如果上报链路故障、实例状态缺失,路由层不能直接报错,应该降级为普通轮询路由,保证服务可用。这里推荐的做法是:

  • 路由组件内存中维护一个长期状态表。
  • 每次指标采集失败时,标记实例状态为“未知”,但保留历史 KV Cache 信息。
  • 当实例状态完全不可用时,降级为随机路由。

8.3 安全与权限边界

路由服务需要读取 Kubernetes Pod 信息,如果使用 CRD 管理实例状态,还需要操作 CRD 的权限。应按最小权限原则配置 RBAC,不要给路由服务赋予整个集群的超级权限。

示例 RBAC:

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: router-reader namespace: llm-inference rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] - apiGroups: ["inference.example.com"] resources: ["workerstatuses"] verbs: ["get", "list", "watch"]

同时,实际生产环境中的变更操作,比如更新 CRD、更新 Deployment 副本数、调整缓存配置,都应该先在小范围实例上验证,再滚动发布。

8.4 监控与告警

需要重点监控的指标包括:

  • 请求级别的缓存命中率。
  • KV Cache 平均占用率。
  • 每个实例的 KV Cache 剩余空间。
  • Prefill 耗时 P99 和 Decode 耗时 P99。
  • KV Cache 传输耗时和传输失败率。
  • 路由实例状态表的数据新鲜度。

其中,KV Cache 剩余空间是最容易引发 P0 事故的指标。建议设定两级告警:

  • 当实例 KV Cache 剩余空间低于 20% 时,触发告警。
  • 当低于 10% 时,触发扩容流程或拒绝新请求。

8.5 版本管理注意事项

LLM 推理引擎的迭代速度很快,KV Cache 的存储格式、前缀缓存算法、显存管理方式都可能随版本变化。升级推理引擎时,要重点验证:

  • KV Cache 的序列化格式是否兼容。
  • 前缀缓存的 Hash 规则是否变化。
  • 指标接口返回字段是否有调整。

如果上游版本有破坏性变更,建议在一个独立命名空间里先跑新版本,对照旧版本观察至少几天,再决定全量迁移。

9. 总结与学习路线

本文围绕 Kubernetes 上的大模型推理优化,讲了 KV Cache 感知路由和 Prefill / Decode 分离两条主线。核心结论如下:

  • KV Cache 是 LLM 推理中显存消耗的大头,也是多轮对话优化的重要抓手。
  • 传统 Kubernetes Service 负载均衡无法感知 KV Cache 状态,需要额外的路由组件参与流量调度。
  • KV Cache 感知路由的核心是:优先命中缓存、按剩余空间排序、空间不足时拒绝或扩容。
  • Prefill / Decode 分离可以让两种计算特征的阶段各得其所,但需要评估收益边界,不能盲目拆分。
  • 在 Kubernetes 上落地时,可以通过标签区分实例池、CRD 描述实例状态、自定义网关完成端到端转发。

接下来如果你想继续深入,建议按这个顺序学习:

  1. 先把 Prefill 和 Decode 两个阶段用 Profiler 量化分析清楚。
  2. 掌握 vLLM / SGLang 的 Prefix Caching 配置方法。
  3. 实现一个最小可运行的 KV Cache 状态采集模块。
  4. 再扩展路由策略、调度联动和 P/D 分离。

大模型推理系统的优化空间很大,KV Cache 感知路由只是其中一个方向。对日常开发而言,先把缓存命中率和实例状态监控建设起来,往往就能解决大部分长上下文场景下的性能抖动问题。如果你正在推进类似的架构改造,建议从最小可行版本开始,逐步沉淀出一套适合自己业务的路由和调度体系。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 6:47:01

工业数据采集站搭建指南:从Modbus到边缘网关的落地实践

我们车间里的老设备,从来不会自己开口说话。温度、压力、转速、电流,全都闷在那些铁壳子里,靠老师傅拿手电筒照着表头抄数,一天抄四次,抄完再填Excel。前几年做产线升级,领导说要上数字化系统,我…

作者头像 李华
网站建设 2026/10/23 6:46:33

能源管理系统建设方案:从数据采集到优化闭环的完整指南

简介:面向企业能源管理、信息化建设及可持续发展相关从业者,这份《能源管理系统建设方案》文档围绕能耗监控、效率分析与节能优化,给出了从物理设备层到业务应用层的完整建设路径。方案系统阐述了电力、水力、燃气、热力等多种能源数据实时采…

作者头像 李华
网站建设 2026/10/23 6:45:59

HackRF One软硬协同实战指南:驱动配置、USB通信与GNURadio调试

简介:本资源是HackRF One软件无线电平台的官方用户手册PDF,面向无线电爱好者、嵌入式开发者及通信专业学生,解决SDR入门配置、跨平台开发环境搭建与基础信号收发实践等核心问题。手册覆盖硬件架构解析、Windows下VS2010/2012运行库与USB驱动安…

作者头像 李华
网站建设 2026/10/23 6:45:41

SCORM课件测试环境搭建与数据回传排查实战

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教育从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

作者头像 李华
网站建设 2026/10/23 6:45:29

PyTorch卫星图像分割实战:U-Net语义分割全流程与工程避坑指南

简介:这份资源面向遥感图像处理与计算机视觉方向的开发者、学生及研究人员,聚焦卫星图像分割这一核心任务,帮助读者理解从图像预处理、特征提取到分割算法选择与后处理的完整流程。包内共3个文件,均为Python脚本,压缩包…

作者头像 李华
网站建设 2026/10/23 6:44:47

labelme标注工具全流程:安装、标注、JSON转数据集与避坑指南

简介:这份资源是面向计算机视觉、机器学习初学者及数据标注从业者的 Labelme 安装与使用教程文档,帮助读者快速搭建图像分割与标注环境,解决从零配置工具链时容易遇到的版本与环境问题。资源包共 1 个 docx 文件,约 14KB&#xff…

作者头像 李华