Kserve的学习和使用(上)
从 vllm serve 进入模型服务时,最先看到的是推理引擎本身:PagedAttention、Continuous Batching、KV Cache、TTFT、TPOT,以及 GPU 上如何更高效地完成 Prefill 和 Decode。
但视角一旦从“启动一个推理进程”切换到“在 Kubernetes 中长期运行一批模型服务”,问题会立刻变多:
- 模型服务如何声明和部署
- 不同推理框架如何统一接入
- 模型文件如何下载到 Pod
- 服务如何暴露、扩缩容和灰度
- 多个模型之间如何编排
- 控制器如何感知服务状态并完成故障恢复
vllm serve 解决的是推理引擎和服务进程的问题。KServe 解决的则是模型服务在 Kubernetes 上的平台化管理问题。
这篇文章记录一次从本地安装 KServe 到理解核心抽象的过程,重点放在 Control Plane、Data Plane、InferenceService、ServingRuntime 和 InferenceGraph。LLM Runtime、vLLM 集成和 GPU 环境会放到后续文章继续展开。
KServe 是什么
KServe 是一个运行在 Kubernetes 上的模型推理平台,同时覆盖传统预测模型和生成式 AI 模型。
它不是另一个 vLLM,也不会亲自完成矩阵计算和 Token 生成。更准确地说,KServe 位于推理引擎的上层,负责模型服务的声明、编排和生命周期管理。
一个简化的调用链如下:
1 | 客户端请求 |
其中:
- KServe 决定模型服务应该如何运行
- vLLM、Triton 等 Runtime 真正执行推理
- Kubernetes 负责调度和维持底层工作负载
可以把几个概念先粗略类比为:
1 | KServe ≈ 面向模型服务的 Operator |
KServe 当前同时覆盖两类场景。
第一类是 Predictive AI,例如 scikit-learn、XGBoost、TensorFlow、PyTorch、ONNX。
第二类是 Generative AI,例如通过 Hugging Face Runtime、vLLM 和 llm-d 部署 Qwen、Llama 等大语言模型。
为什么不直接写 Deployment
如果只是部署一个固定的 vLLM 服务,直接写 Kubernetes 原生资源当然可以:
- Deployment
- Service
- 模型下载逻辑
- 启动参数
- 健康检查
- GPU 资源
- 扩缩容策略
- 版本发布和路由
- 监控指标
当模型数量很少时,这样做足够直接。
但一旦平台上存在多个团队、多个模型格式和多个推理框架,每个模型都手写一套 Deployment,很快会产生大量重复且不一致的配置。模型使用者需要理解太多运行时细节,平台团队也很难统一治理镜像、资源、路由和发布策略。
KServe 的思路是把“模型是什么”和“模型服务器怎么运行”分开。
用户只声明模型:
1 | apiVersion: serving.kserve.io/v1beta1 |
至于使用什么镜像、启动什么命令、监听哪个端口,则由 ServingRuntime 描述。
这样一来,平台团队可以统一维护 Runtime,模型使用者只需要关心模型本身。
安装方式怎么选
KServe 的安装文档里有几种模式:
- Standard Kubernetes Installation
- Knative Installation
- ModelMesh Installation
- Quick Installation
- Kubeflow Installation
第一次接触时容易把它们理解成不同安装工具。实际上,它们更像是在选择底层运行模式。
Standard 模式
Standard 模式直接使用原生 Kubernetes 资源:
1 | InferenceService |
它依赖少、排查直观,适合常驻模型服务,也适合作为第一次学习 KServe 的入口。
它默认不提供 Knative 带来的请求驱动 Scale-to-Zero 和基于 Revision 的流量切分能力。
Knative 模式
Knative 模式会在 KServe 与 Pod 之间加入 Knative Serving:
1 | InferenceService |
它可以提供请求驱动扩缩容、Scale-to-Zero、Revision 管理、流量切分和灰度发布。代价是控制链路和网络链路都会更复杂。
ModelMesh
ModelMesh 面向大量、小型、频繁变化的模型。
它并不是简单地给每个模型创建一个独立 Pod,而是让多个模型共享 Serving Runtime,并按需加载和卸载模型。这种设计适合大量传统机器学习模型,不一定适合每个模型都需要占用大量 GPU 显存的大语言模型。
本次安装选择
为了先理解 KServe 自身,本次采用:
- kind
- KServe Standard Mode
暂时不引入 Knative、Istio 和 ModelMesh。
本地环境如下:
- MacBook Apple Silicon
- Docker Desktop
- kind
- Kubernetes v1.35
- KServe v0.18
- Standard Deployment Mode
KServe 的快速安装脚本会安装:
- cert-manager
- KServe CRD
- KServe Controller
- Admission Webhook
- 默认 ClusterServingRuntime
安装命令如下:
1 | curl -fsSL \ |
安装完成后,可以通过下面的命令验证控制面:
1 | kubectl get pods -n cert-manager |
cert-manager 为什么是依赖
KServe 会安装 Admission Webhook,用于处理 CR 的默认值填充、校验和转换。
调用路径大致是:
1 | kubectl apply |
API Server 调用 Webhook 时要求 HTTPS,因此 KServe 需要一套 Webhook 证书。
cert-manager 负责:
- 创建证书
- 把证书保存到 Secret
- 给 Webhook 配置注入 CA
- 在证书到期前续期
所以 cert-manager 属于 KServe 控制面的证书依赖,并不参与模型推理。
本地安装中的两个现实问题
本地安装过程里有两个问题值得单独记录:代理链路和镜像架构。它们不是 KServe 的核心概念,但非常容易影响第一次体验。
kind 节点里的代理地址
当网络需要通过本机代理访问外部镜像仓库时,kind 节点不能直接使用宿主机视角下的 127.0.0.1:<proxy-port>。
kind 节点本身是 Docker 容器。在这个容器中,127.0.0.1 指向的是节点容器自己,而不是 Mac 宿主机。因此 containerd 拉镜像时可能出现:
1 | proxyconnect tcp: |
正确的网络关系应该是:
1 | kind node |
创建 kind 集群时可以传入:
1 | HTTP_PROXY=http://host.docker.internal:<proxy-port> \ |
同时需要保证本机代理允许来自 Docker 网络的连接。
这也能顺手区分几类代理配置:
- Shell 代理
- Docker Desktop 代理
- Docker CLI 拉镜像代理
- kind 节点 containerd 代理
- Pod 内部代理
它们不是同一条链路。宿主机 curl 成功,并不能证明 Kubernetes 节点一定能拉镜像。
ARM Mac 与 AMD64 镜像
按照官方示例部署 Hugging Face LLM 时,InferenceService 可以完成 Runtime 匹配,并创建模型 Deployment,但 Pod 拉取下面的镜像会失败:
1 | kserve/huggingfaceserver:v0.18.0 |
检查镜像平台:
1 | docker buildx imagetools inspect \ |
结果只有:
1 | linux/amd64 |
而本地 kind 节点是:
1 | linux/arm64 |
因此 containerd 会报错:
1 | no match for platform in manifest |
在 Apple Silicon 上模拟完整的 AMD64 kind 节点并不划算。跨架构模拟不只是运行一个业务容器,还要运行 containerd、kubelet、etcd、kube-apiserver、scheduler、controller-manager 等控制面和节点组件,兼容成本明显更高。
当前还处于实验阶段,目标是先用最简单的方式理解 KServe 的控制面和资源抽象,不需要为了跑通一个 LLM Runtime 引入过多兼容成本。更简单的方式是:
1 | Mac ARM kind |
Control Plane 与 Data Plane
理解 KServe 时,最重要的概念之一是 Control Plane 与 Data Plane。
一句话概括:
1 | Control Plane 决定服务应该如何运行 |
KServe Control Plane
KServe 的控制面主要包括:
- InferenceService CR
- KServe Controller
- Admission Webhook
- ServingRuntime 配置
- Status 回写
当创建一个 InferenceService 后,KServe Controller 会进行 Reconcile:
1 | 读取 InferenceService |
KServe Controller 不会接收用户的推理流量,也不会生成 Token。
KServe Data Plane
Data Plane 是实际处理请求的部分:
1 | 客户端 |
它更关注请求延迟、吞吐、Batch、CPU/GPU 使用率、KV Cache、TTFT 和 TPOT。
这两个平面可以独立扩展。Controller 不需要随着每秒推理请求数线性扩容,而模型服务 Pod 可以按照负载增加副本。
InferenceService:声明一个模型服务
InferenceService 是用户最常接触的 KServe CR。
它描述的是:
1 | 希望集群中存在一个怎样的模型服务 |
例如:
1 | apiVersion: serving.kserve.io/v1beta1 |
这里声明了:
- 服务名称:
sklearn-iris - 模型格式:
sklearn - 模型位置:
gs://...
但没有直接写容器镜像。
应用后,KServe 会生成类似资源:
1 | InferenceService |
当模型 Pod 就绪后,InferenceService 的状态中会出现:
1 | InferenceServiceReady |
这说明控制器已经把声明状态落实成了真实工作负载。
ServingRuntime:模型如何运行
ServingRuntime 是 KServe 非常关键的一层抽象。
InferenceService 只说明模型是什么,ServingRuntime 则说明:
1 | 这种模型应该使用什么镜像和参数运行 |
以 sklearn 为例,ClusterServingRuntime 大致会声明:
1 | spec: |
当 InferenceService 中出现:
1 | modelFormat: |
KServe 就会查找支持 sklearn 格式的 Runtime,例如 kserve-sklearnserver,再根据 Runtime 中的容器模板生成实际的 Deployment 和 Pod。
ServingRuntime 分为两种作用域:
ServingRuntime:只在当前 Namespace 生效ClusterServingRuntime:对整个集群生效
平台团队可以通过 ClusterServingRuntime 为所有租户提供统一、受控的推理镜像。业务团队也可以在自己的 Namespace 中定义专用 Runtime。
未来部署 LLM 时,也可以沿用同样的思路:InferenceService 声明模型格式,ServingRuntime 负责把它映射到 Hugging Face Runtime、vLLM 等具体模型服务器。
模型如何进入 Pod
KServe 并不要求模型一定被打包进容器镜像。
InferenceService 通过 storageUri 指定模型地址,KServe 会给 Pod 注入 Storage Initializer。
启动时,Storage Initializer 会先从对象存储或模型仓库下载文件,写入共享目录 /mnt/models,随后模型服务器容器再从该目录加载模型。
Pod 中通常会出现一个共享的 emptyDir:
1 | kserve-provision-location |
Storage Initializer 写入该目录,主容器以只读方式挂载它。
这种设计把“模型文件获取”和“模型服务器运行”拆成了两个职责。
InferenceGraph:多个模型之间如何路由
一个 InferenceService 通常代表一个模型服务。
当请求需要经过多个模型,或者需要在多个版本之间分流时,可以使用 InferenceGraph。
InferenceGraph 由 Node 和 Step 组成:
1 | InferenceGraph |
KServe 支持四类路由节点。
Sequence
按照顺序执行多个步骤:
1 | 请求 |
前一个 Step 的响应可以作为后一个 Step 的输入。
Switch
根据条件选择第一个匹配的分支:
1 | 请求 |
它类似程序中的 if / else if。
Ensemble
将同一个请求发送给多个模型,再聚合响应:
1 | ┌→ model-a |
KServe 可以组合各步骤的响应,但“多数投票”“平均值”等业务融合逻辑通常仍需要专门的后处理服务。
Splitter
按照权重把不同请求分配给不同目标:
1 | 请求 |
它适合灰度发布、A/B 测试和新模型小流量验证。
Splitter 与 Ensemble 的区别是:
- Splitter:一个请求只进入一个目标
- Ensemble:一个请求同时进入多个目标
一个最简单的 InferenceGraph 如下:
1 | apiVersion: serving.kserve.io/v1alpha1 |
InferenceGraph 创建后,KServe 会额外部署 Graph Router。InferenceGraph 本身是控制面中的声明,Graph Router 才是真正处理请求路由的 Data Plane 组件。
KServe 与 vLLM 的关系
KServe 与 vLLM 不是替代关系。KServe 负责模型服务生命周期、工作负载、路由、存储和扩缩容;vLLM 负责加载大语言模型、管理 KV Cache、执行 Continuous Batching,并完成 Prefill、Decode 和 Token 生成。
换句话说,KServe 更像模型服务的平台控制层,vLLM 是其中一种具体的 LLM 推理运行时。上篇先到这里,后续再继续看 KServe 如何和 vLLM、GPU 调度以及 LLM Runtime 结合。
参考资料
- KServe 官方文档
- KServe GitHub
- KServe Standard Kubernetes Installation
- Deploy Your First LLM with InferenceService
- KServe InferenceGraph
- vLLM 与 KServe 集成





