跳到主内容
快讯直播
AI智模界
教程

vLLM + llm-d:多模型路由与缓存感知调度实战

先把成果说清楚:搭完之后,Kubernetes 里会跑起多个 vLLM 模型副本,外部通过一个统一 Gateway 访问;请求按路径或域名路由到不同模型;llm-d 的 Endpoint Picker Provider(EPP)在选副本时会参考前缀缓存命中、等待队列长度和 KV 缓存利用率;HPA 根据 vLLM 暴露的指标自动扩缩容。下面从单机 vLLM 出发,一步步升级到这层推理控制平面。

前置条件清单

  • 一个可用的 Kubernetes 集群,GPU 节点已安装 NVIDIA 驱动与 device plugin,能分配 nvidia.com/gpu。
  • 本地有 kubectl、helm,能拉取容器镜像。
  • 已经能在单 Pod 或单机跑通 vLLM 的 OpenAI 兼容服务,确认模型权重可访问。
  • 按官方文档安装 Gateway API CRDs 与 Gateway API AI 词典:Inference">Inference Extension CRDs。版本以官方文档当前版本为准。
  • 安装 Prometheus 与 Prometheus Adapter,后面 HPA 要用自定义指标。
  • 模型权重放在 PVC、对象存储或镜像中,尽量缩短冷启动时间。
  • 网络策略允许 Gateway 到 EPP、EPP 到 vLLM 的流量。
  • 下文镜像名、CRD 版本、Helm chart 名称均以官方文档当前版本为准,不要直接照搬旧 tag。

步骤 1:把单机 vLLM 变成 Kubernetes 可调度后端

先部署两个模型后端,这里以 qwen 为例。重点是开启前缀缓存、暴露 metrics、设置启动探针。镜像用占位符,替换成实际可用的 vLLM 镜像。

```yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: vllm-qwen

namespace: llm

spec:

replicas: 2

selector:

matchLabels:

app: vllm-qwen

template:

metadata:

labels:

app: vllm-qwen

annotations:

prometheus.io/scrape: "true"

prometheus.io/port: "8000"

spec:

containers:

  • name: vllm

image: <vllm-image> # 以官方文档当前版本为准

command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]

args:

  • "--model"
  • "<model-path>"
  • "--served-model-name"
  • "qwen"
  • "--host"
  • "0.0.0.0"
  • "--port"
  • "8000"
  • "--enable-prefix-caching"
  • "--max-model-len"
  • "8192"
  • "--gpu-memory-utilization"
  • "0.90"
  • "--disable-log-requests"

ports:

  • containerPort: 8000

resources:

limits:

nvidia.com/gpu: "1"

readinessProbe:

httpGet:

path: /health

port: 8000

initialDelaySeconds: 30

periodSeconds: 10

startupProbe:

httpGet:

path: /health

port: 8000

failureThreshold: 120

periodSeconds: 10

---

apiVersion: v1

kind: Service

metadata:

name: vllm-qwen

namespace: llm

spec:

selector:

app: vllm-qwen

ports:

  • name: http

port: 8000

targetPort: 8000

```

再复制一份,改成 vllm-llama、--served-model-name llama、对应模型路径。两个 Deployment 的标签要区分开,后面 InferencePool 靠标签选 Pod。

```bash

kubectl apply -f vllm-qwen.yaml

kubectl apply -f vllm-llama.yaml

kubectl -n llm get pods -w

```

确认每个 Pod 的 /health 返回正常,再继续下一步。

步骤 2:安装 Gateway API 与 llm-d 控制组件

这一步把 CRD、Gateway 控制器、EPP 装进集群。命令里的文件与仓库地址按官方文档获取。

```bash

kubectl apply -f gateway-api-crds.yaml

kubectl apply -f inference-extension-crds.yaml

helm repo add llm-d <官方 Helm 仓库地址> # 以官方文档当前版本为准

helm repo update

helm install llm-d-infra llm-d/llm-d-infra \

--namespace llm-d-system \

--create-namespace \

--set gateway.enabled=true \

--set epp.enabled=true

```

如果不用 Helm,就按官方 YAML 清单部署。核心是集群里出现 GatewayClass、Gateway、EPP 的 Deployment/Service,以及 InferencePool CRD。安装后检查:

```bash

kubectl get gatewayclass

kubectl -n llm-d-system get pods

kubectl get crd | grep inference

```

步骤 3:为每个模型建 InferencePool

InferencePool 的作用是告诉控制平面“这组 Pod 是某个模型的后端”,并绑定 EPP 做端点选择。apiVersion 以实际安装的 CRD 为准。

```yaml

apiVersion: inference.networking.x-k8s.io/v1alpha2

kind: InferencePool

metadata:

name: qwen-pool

namespace: llm

spec:

selector:

matchLabels:

app: vllm-qwen

targetPortNumber: 8000

extensionRef:

name: llm-d-epp

---

apiVersion: inference.networking.x-k8s.io/v1alpha2

kind: InferencePool

metadata:

name: llama-pool

namespace: llm

spec:

selector:

matchLabels:

app: vllm-llama

targetPortNumber: 8000

extensionRef:

name: llm-d-epp

```

extensionRef 指向实际部署的 EPP 服务,名称和命名空间按安装结果调整。接着配置 EPP 调度策略,启用前缀缓存感知、队列长度和 KV 缓存利用率打分。不同版本可能用 ConfigMap 或自定义 CRD,下面只是结构示意,字段名以官方文档当前版本为准。

```yaml

apiVersion: v1

kind: ConfigMap

metadata:

name: llm-d-epp-config

namespace: llm-d-system

data:

config.yaml: |

plugins:

  • name: prefix-cache-scorer

weight: 3

  • name: queue-scorer

weight: 2

  • name: kv-cache-utilization-scorer

weight: 1

schedulingProfile: default

```

改完配置后滚动重启 EPP:

```bash

kubectl -n llm-d-system rollout restart deploy/llm-d-epp

kubectl -n llm-d-system logs deploy/llm-d-epp -f

```

步骤 4:用 Gateway + HTTPRoute 做多模型路由

OpenAI 兼容接口的 model 字段在 JSON body 里,普通 L7 路由看不到,所以用路径前缀区分模型。客户端把 base_url 改成对应前缀即可。

```yaml

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

name: llm-multi-model

namespace: llm

spec:

parentRefs:

  • name: inference-gateway

namespace: llm-d-system

rules:

  • matches:
  • path:

type: PathPrefix

value: /qwen

backendRefs:

  • group: inference.networking.x-k8s.io

kind: InferencePool

name: qwen-pool

port: 8000

  • matches:
  • path:

type: PathPrefix

value: /llama

backendRefs:

  • group: inference.networking.x-k8s.io

kind: InferencePool

name: llama-pool

port: 8000

```

调用时这样写:

```bash

curl http://<gateway-ip>/qwen/v1/chat/completions \

-H "Content-Type: application/json" \

-d '{"model":"qwen","messages":[{"role":"user","content":"你好"}]}'

curl http://<gateway-ip>/llama/v1/chat/completions \

-H "Content-Type: application/json" \

-d '{"model":"llama","messages":[{"role":"user","content":"你好"}]}'

```

如果 Gateway 是 LoadBalancer,用 kubectl -n llm-d-system get gateway 查地址。没有外部 IP 时,可以用端口转发临时验证。

步骤 5:验证缓存感知调度

先确认 vLLM 的 /metrics 里有前缀缓存相关指标。然后发相同长前缀的请求,观察 EPP 日志选了哪个副本。

```bash

for i in $(seq 1 20); do

curl -s http://<gateway-ip>/qwen/v1/chat/completions \

-H "Content-Type: application/json" \

-d '{"model":"qwen","messages":[{"role":"user","content":"<长前缀> 问题'"$i"'"}]}' > /dev/null

done

```

查看 EPP 日志中的 endpoint pick 记录和打分。如果请求均匀分散到所有副本,说明前缀缓存打分没生效:检查 EPP 能否访问 vLLM 的 metrics 端口,InferencePool selector 是否匹配到 Pod,以及 EPP 配置是否真正加载。

步骤 6:配置自动扩缩容

用 Prometheus 抓取 vLLM 指标,把等待队列长度或 KV 缓存利用率通过 Prometheus Adapter 注册成 Pods 指标。下面以等待队列为例,指标名以 Adapter 实际注册名为准,通常由 vllm:num_requests_waiting 转换而来。

```yaml

apiVersion: autoscaling/v2

kind: HorizontalPodAutoscaler

metadata:

name: vllm-qwen-hpa

namespace: llm

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: vllm-qwen

minReplicas: 1

maxReplicas: 8

metrics:

  • type: Pods

pods:

metric:

name: vllm_num_requests_waiting

target:

type: AverageValue

averageValue: "4"

behavior:

scaleUp:

stabilizationWindowSeconds: 30

scaleDown:

stabilizationWindowSeconds: 300

```

模型加载需要时间,扩容后 Pod 就绪前不能接流量;缩容太快会导致冷启动。生产环境建议结合 GPU 配额、PDB 和优雅退出一起配。

步骤 7:观测与灰度

给 vLLM、EPP、Gateway 都接 Grafana 面板,关注 TTFT、ITL、队列长度、KV 缓存使用率、前缀缓存命中率。新模型先建小流量 HTTPRoute,按权重切少量流量,再逐步放大。用 PodDisruptionBudget 保护推理副本,避免滚动更新打断长请求。

常见坑与排错

1. GPU 不可见:检查 nvidia.com/gpu 是否可分配,device plugin 是否 Running。

2. vLLM 启动失败:显存不足、模型路径错误、tensor parallel 与 GPU 数不匹配。

3. Gateway 无地址:LoadBalancer 未就绪,或 GatewayClass 没有被控制器接管。

4. InferencePool 不生效:CRD 版本与控制器不匹配,selector 没匹配到 Pod。

5. EPP 选端点失败:EPP 服务端口、命名空间、RBAC 权限、网络策略要逐项检查。

6. 前缀缓存收益不明显:请求前缀不一致,或 EPP 读不到指标;短请求收益本来就低。

7. HPA 不触发:Prometheus 没抓到指标,Adapter 规则名称不对,或指标值一直为 0。

8. 滚动更新 5xx:readinessProbe 太早通过,模型还没加载完;用 startupProbe 兜底。

9. 长请求被缩容打断:设置较长缩容稳定窗口,配合 PDB 和优雅退出。

10. 多模型路由 404:路径前缀没在 HTTPRoute 中匹配,或客户端 base_url 没带前缀。

下一步建议

  • 接入 PD 分离,把 prefill 和 decode 拆成不同池,让 EPP 分别调度。
  • 用 LoRA 适配器在一个基座模型上挂多个模型名,减少副本数。
  • 引入 Workload Variant Autoscaler 或 KEDA,按队列和缓存指标做更细粒度扩缩。
  • 多集群:把 InferencePool 扩展到多集群网关,做故障转移和就近推理。
  • 给 EPP 加自定义 scorer,比如按租户、成本、延迟 SLO 选择副本。
  • 所有版本、字段与安装方式,以 vLLM、llm-d、Gateway API Inference Extension 官方文档当前版本为准。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。