Kthena 源码解析:云原生 LLM 推理平台的架构与实现
Kthena 源码解析:云原生 LLM 推理平台的架构与实现
项目地址:https://github.com/volcano-sh/kthena | 官网:https://kthena.volcano.sh
本文基于 kthena 仓库 main 分支源码进行系统分析,覆盖整体架构、控制面 CRD 与控制器、数据面路由与调度器、PD 分离、弹性扩缩容与运行时 Agent 等核心模块。
一、Kthena 是什么
Kthena 是 Volcano 社区推出的 Kubernetes 原生 LLM 推理平台,目标是把大模型推理这件事在 K8s 上变得简单、可扩展、低成本。它不替代 vLLM / SGLang 这类推理引擎,而是作为推理引擎之上的「流量枢纽 + 调度中心」,用声明式 CRD 管理模型全生命周期,用智能路由分发推理流量。
定位上有三个关键词值得记住:
- Lightweight(轻量):整个平台就是两个自包含的 Go 二进制,依赖面小,安装便宜、升级简单。
- Modular(可组合):控制面(workload)和数据面(networking)是两个独立的 Helm 子 chart,各自有独立的 CRD group 和发布周期,可以单独部署。
- Enterprise-Grade(企业级):支持多推理引擎(vLLM、SGLang、Triton)、Prefill-Decode 分离、成本驱动扩缩容、异构算力。
为什么需要它?生产级 LLM Serving 有几个老大难:KV Cache 动态占用显存导致传统 Round-Robin 负载均衡利用率低;Prefill(计算密集)和 Decode(访存密集)混部无法针对性优化;多租户多模型公平调度复杂;现有方案要么脱离 K8s 生态要么过于笨重。Kthena 正是冲着这些痛点来的。
二、双平面架构:控制面与数据面彻底解耦
Kthena 最核心的设计哲学是双平面(Two-Plane)架构,把「你声明什么」和「请求怎么流」彻底分开。
1 | ┌─────────────────────────── 控制面 (Control Plane) ───────────────────────────┐ |
这一点在源码里体现得非常干净。两个二进制入口分别是 cmd/kthena-controller-manager/main.go 和 cmd/kthena-router/main.go,它们各自启动自己的 webhook、controller、server,互不感知。部署形态因此有三种合法组合:
| 你想要…… | 安装 | 说明 |
|---|---|---|
| 只管模型工作负载 | workload 子 chart |
用 ModelServing/AutoscalingPolicy,流量走你自己的网关或 Service |
| 只做推理路由 | networking 子 chart |
ModelServer 通过 workloadSelector 标签发现任意后端 Pod |
| 全平台 | 两个子 chart | 才能用一站式 ModelBooster API,它会级联创建两组 CRD |
1 | # 只装控制面 |
「每个组件只跟 K8s API 说话,从不互相调用」——这是 Kthena 模块化的根基,也是它能被分块采用的前提。
三、控制面:分层 CRD 与三个控制器
控制面用一套分层 CRD 模型表达意图:一个高层资源级联展开成一组细粒度原语,你可以选择在任意抽象层操作。
1 | ModelBooster (一站式部署 API) |
CRD 分属两个 group:workload.serving.volcano.sh 管 ModelServing/ModelBooster/AutoscalingPolicy;networking.serving.volcano.sh 管 ModelRoute/ModelServer/ExternalModelProvider。源码定义在 pkg/apis/workload/v1alpha1/ 和 pkg/apis/networking/v1alpha1/。
3.1 ModelServing:三级工作负载模型
ModelServing 是工作负载的核心抽象,采用 ModelServing → ServingGroup → Role 三级结构。看 model_serving_types.go 的 Spec:
1 | type ModelServingSpec struct { |
几个值得注意的设计:
- RecoveryPolicy 有三档:
ServingGroupRecreate(组内任一 Pod 重建则整组重建)、RoleRecreate(按角色整组重建,默认)、None(跟随默认 Pod/Deployment 行为)。这是为分布式推理服务的——张量并行下各 worker 必须同时启动,局部重建会破坏一致性。 - RolloutStrategy 支持
ServingGroupRollingUpdate(按组滚动)和RoleRollingUpdate(跨组按角色同时滚动,建议单组场景用)。 - Plugins 用
PluginSpec(name + type + opaque JSON config + scope)声明插件实例,scope 可以按 roles 和 target(Entry/Worker/All) 收窄。这给了工作负载很强的可定制性,而不必改控制器代码。
每个 replica(ServingGroup)内部按角色组织,角色对应 LLM 推理两种本质不同的负载:
| 角色 | 负载特征 | 扩缩策略 |
|---|---|---|
| Prefill | 计算密集的 prompt 初始化与上下文编码 | 按吞吐扩——多 prompt 批处理 |
| Decode | 访存密集、延迟敏感的逐 token 生成 | 按延迟扩——最小化每 Pod 队列深度 |
角色副本数独立设置(如 2 prefill : 4 decode),让硬件匹配负载特征,避免任一阶段过供。
3.2 ModelBooster:开箱即用的模型上架
ModelBooster 是高层 opinionated API,一个资源把模型 spec 写清楚,它就级联创建/更新/删除所有下游资源。看 model_booster_types.go,核心是 ModelBackend:
1 | type ModelBackend struct { |
ModelURI 支持 HuggingFace、ModelScope、S3、OBS、PVC 多种模型源;Workers 的 Type 可以是 server/prefill/decode/controller/coordinator,覆盖原生部署、PD 分离、大规模 EP 等多种形态。一个 ModelBooster YAML 声明清楚 backend + workers + 资源 + engine config,剩下的路由/服务/扩缩全由控制器自动生成。
3.3 三个控制器
| 控制器 | 职责 |
|---|---|
| Model Booster Controller | reconcile ModelBooster → 下游原语,传播更新,编排级联生命周期 |
| Model Serving Controller | 管理 ServingGroup 与角色副本,拓扑/ gang 感知放置、故障恢复、滚动升级、entry/worker 模板 |
| Autoscaler Controller | 按运行时指标对照 AutoScalingPolicy 计算期望副本数,经 Binding 在异构硬件间调整实例组合 |
Model Serving Controller 是最重的一个,看 model_serving_controller.go 的方法列表就能感受到它的复杂度:syncServingGroupReplicas、syncRoleReplicas、scaleUpRoles/scaleDownRoles、manageRollingUpdate、handleReadyPod/handleErrorPod/handlePodAfterGraceTime……它把 ServingGroup 的扩缩、角色的滚动、Pod 的就绪/故障/优雅删除都管了起来。它还通过 podgroupmanager 与 Volcano scheduler 集成,实现拓扑感知与 gang 调度——gang 调度保证分布式推理组(如 xPyD)原子调度,避免部分部署造成的资源浪费。
四、数据面:请求级智能路由
数据面是 Kthena 的运行时路径。每个推理请求都流经 Kthena Router,在分发到最优推理 Pod 之前,依次过安全、公平、模型感知调度六道关。
4.1 六阶段请求管线
| 阶段 | 做什么 |
|---|---|
| 1. 鉴权认证 | 校验身份与权限(JWT,见 filters/auth) |
| 2. 限流 | 按 model/tenant 的吞吐限制,基于 token 而非仅请求数 |
| 3. 公平调度 | 按 model 的公平排队,防止一个模型的流量尖峰饿死共享集群的其他模型 |
| 4. 调度 | 核心智能层。可插拔调度器跑 filter → score → select,选出候选 Pod 集 |
| 5. 负载均衡 | 最终路由到候选后端实例,支持重试 |
| 6. 代理 | 分发请求到选中的推理 Pod,流式回传响应 |
Router 的入口在 pkg/kthena-router/router/router.go,基于 gin 构建。Router 结构体聚合了 scheduler、authenticator、store、loadRateLimiter、accessLogger、metrics、tokenizer 以及 KV connector factory:
1 | type Router struct { |
两个互斥的调度策略由环境变量开关:ENABLE_FAIRNESS_SCHEDULING(按用户历史 token 用量排序的公平队列)和 ENABLE_SESSIONBoost(会话感知提升 prefix cache 复用),同时开启会直接 klog.Fatalf。限流是 token 级的——TokenRateLimiter 不仅按请求限,还按输入/输出 token 限,这比传统网关更贴合 LLM 的成本模型。
4.2 可插拔调度器:filter/score 框架
调度器是数据面的心脏。它镜像了 K8s 调度器的 filter/score 模式,但作用在请求级,运行在微秒级而非毫秒级。框架接口在 scheduler/framework/interface.go:
1 | type FilterPlugin interface { |
调度上下文 Context 携带 Model、Prompt、SessionID、Hashes,以及 PD 分组信息(DecodePods/PrefillPods 或 BestPods)。调度主循环在 scheduler_impl.go 的 Schedule():
1 | func (s *SchedulerImpl) Schedule(ctx *framework.Context, pods []*datastore.PodInfo) error { |
注意几个细节:topN=5(取前五而不是最优一个,给负载均衡留余量);least-request 启用时会先从 Redis 同步跨 router 的在途计数(syncOnFlight),让多副本 router 共享全局视图。
Filter 插件(淘汰不合格 Pod)
| 插件 | 逻辑 |
|---|---|
| Least Requests | 丢弃超过可配 active-request 阈值的 Pod |
| LoRA Affinity | 排除没加载所需 LoRA adapter 的 Pod |
Score 插件(给剩余 Pod 加权打分)
| 插件 | 优化目标 |
|---|---|
| Least Requests | 偏好在途请求最少的 Pod,最小化排队延迟 |
| Least Latency | 最小化 TTFT(首 token 时间)和 TPOT(每 token 时间) |
| KV Cache Aware | 偏向 KV cache 命中潜力最大的 Pod |
| Prefix Cache | 把请求 prompt 前缀与各 Pod 缓存前缀匹配,最大化缓存命中 |
| GPU Cache | 考虑 GPU 显存利用率,避免请求被抢占 |
4.3 深入两个核心 Score 插件
这两个插件是 Kthena 路由智能的精华,值得单独看。
KV Cache Aware(kvcache_aware.go):用 token 级 block 匹配 + Redis 分布式协调实现 KV cache 命中感知。流程是:
- 把 prompt 经 tokenizer(vLLM/SGLang 引擎的 tokenizer 端点)切成 token 序列;
- 按
blockSizeToHash(默认 16 token)分块,每块用 SHA-256 算 63 位正整数 hash; - 用 Redis pipeline 批量查询
matrix:kv:block:{model}@{hash}这个 hash 结构,看哪些 Pod 缓存了该 block(field 是pod.namespace,value 是写入时间戳); - 只缓存了第一个 block 的 Pod 才有资格得分,然后做前缀逐块交集匹配——
podScores[pod] == i表示该 pod 在第 i 块仍匹配,巧妙地用分数本身充当活跃集合,无需额外交集 map; - 得分 =
匹配块数 / 总块数 * 100。
它还有一套 GC:后台 gcStaleFields 定时 SCAN + 删除超过 24h 的 stale field;freshOwners 会丢弃「写入时间早于 Pod 容器启动时间」的归属(Pod 重启后旧 cache 已失效),避免路由到一个已经不存在的 cache。这套设计让 KV cache 感知在多 router 副本、Pod 频繁重建的生产环境下仍然准确。
Prefix Cache(prefix_cache.go):与 KV Cache Aware 思路类似但更轻量,用滚动 hash 链做前缀匹配。每个 block 的 hash 由「上一块 hash + 当前块内容」组合生成,形成依赖链——这样匹配时从后往前,一旦某个 hash 命中就保证之前所有块都命中,无需逐块检查。用 LRU cache 记录 hash,淘汰时同步从三级 map(model → hash → pod)删除。它还实现了 PostScheduleHook,调度成功后把本次 prompt 的 hash 写回缓存。
两者的区别:KV Cache Aware 走 Redis 分布式、token 级、跨 router 共享;Prefix Cache 走本地 LRU、字符级(默认 64 字节 block)、单机。可以按场景混搭。
4.4 PD 分离路由:prefill 与 decode 配对
当请求目标是分离式 PD group 时,调度器先 score decode pod,再为每个候选 decode 在同一 PD group内配一个兼容的 prefill,保证 KV cache 局部性、最小化数据传输。这是 Schedule() 里 ctx.PDGroup != nil 分支的逻辑。
实际代理在 proxyToPDDisaggregated(router.go:1394):
1 | for i := 0; i < maxRetry; i++ { |
它会尝试多对 prefill/decode(最多 min(len(decode), len(prefill)) 对),某对失败就换下一对,全部失败才返回 500。KV cache 的实际传输由 KV Connector 抽象承担,Kthena 支持三种后端(见 connectors/):
| Connector | 取舍 |
|---|---|
| LMCache | 内存级,最低延迟,同节点或 RDMA;走共享 Redis backend |
| MoonCake | 分布式,跨节点,容错 |
| NIXL | 轻量,基于 NCCL,GPU-direct;in-band kv_transfer_params 握手,无需共享后端 |
选择对应用透明——Runtime Agent 和 KV Connector 自动处理传输。
4.5 外部模型 Provider
Router 不只能路由到 Kthena 自己管理的 Pod。通过 ExternalModelProvider CRD 和 providers/(含 OpenAI、Anthropic adapter),它可以把请求路由到外部托管模型,ModelServer 用 workloadSelector 标签发现任意后端——Deployment、StatefulSet、别的 operator 管的 Pod、甚至外部 OpenAI 都行。这正是「数据面可单独部署」的底气。
五、弹性扩缩容:从同构到异构成本优化
Autoscaler 在 pkg/autoscaler/ 实现,分同构(Homogeneous)和异构(Heterogeneous)两种模式。
同构模式(scaler.go):经典的单目标扩缩。MetricCollector 从 Pod metrics endpoint 采集 CPU/GPU/内存/自定义指标,scaleOneTarget 算法按 metric target、tolerance、min/max 副本、未就绪实例数计算推荐副本数,再经 behavior(stabilization window 防 flapping、panic window 应对流量尖峰)校正。支持 stable 和 panic 两种模式。
异构模式 / Optimizer(optimizer.go):这是 Kthena 的亮点——跨异构硬件的成本感知扩缩。比如同时有 H100 和 A100 两种实例,怎么分配副本最省钱又满足 SLO?看核心结构:
1 | type ReplicaBlock struct { |
NewOptimizerMeta 把每个后端的 [MinReplicas, MaxReplicas] 区间按 CostExpansionRatePercent 切成多个 ReplicaBlock(成本扩展率控制打包粒度),然后按 cost 升序排序,形成扩容顺序。RestoreReplicasOfEachBackend 按这个顺序贪心填充:先满足所有后端的 minReplicas,再把剩余配额按成本从低到高分配。这就是「成本-能力贪心放置」——优先扩便宜的实例,贵的留到最后。
Optimizer.Optimize 会聚合各后端 ready/unready 实例的指标、外部样本,按聚合后的指标算总需求,再映射回各后端的副本数。两种模式都支持 stable/panic 双窗口。
六、推理 Pod 架构与运行时 Agent
每个 replica 部署可能包含这些组件:
| 组件 | 职责 |
|---|---|
| Entry Pod | 角色请求的入口端点 |
| Worker Pod(s) | 执行模型推理计算(大模型跨 worker 张量并行) |
| Downloader | 通用模型/制品拉取器——支持 HF、ModelScope、S3/OBS、PVC,并发下载 + 文件锁 |
| Runtime Agent | sidecar,代理并标准化引擎指标、暴露 LoRA 生命周期 API、处理 PD 的 KV cache 事件、提供模型下载端点 |
| LLM Engine | 真正的推理后端——vLLM / SGLang 等 |
Downloader 和 Runtime Agent 是 Python 实现,在 python/kthena/ 下,分成 downloader/ 和 runtime/ 两个子包:
downloader/:huggingface.py、modelscope_downloader.py、s3.py、pvc.py+lock.py(文件锁防并发冲突)+base.py抽象基类。多源统一接口,PVC 共享缓存。runtime/:app.py是 sidecar 入口,metric.py/collect.py标准化 vLLM/SGLang 指标,kv_cache_manager.py处理 KV cache 事件,label.py打 Pod 标签,zmq_subscriber.py/sglang_zmq_subscriber.py订阅引擎事件,redis_client.py与 Redis 交互(KV cache 归属写入就靠它)。standard.py把不同引擎的指标/事件归一化成统一格式。
这套 runtime agent 让 Kthena 能用统一抽象管理多种引擎,而不必为每个引擎写一套控制逻辑。
七、工程化与可观测性
Kthena 的工程化水准是生产级的:
- 代码生成:
make generate一键再生 CRD、deepcopy、client-go、文档;make gen-check在 CI 里卡「生成产物是否 dirty」。CRD 字段改动必须连带提交生成文件。 - 多层测试:单元测试(table-driven,并发敏感的加
-race)、Kind-based E2E(分 controller-manager / router / gateway-api / gateway-inference-extension 四类矩阵,见 test/e2e/)、Python 测试(ruff lint)。 - 可观测性:Router 内建 Prometheus metrics(
metrics-port9090),调度器每个插件每阶段的执行耗时都被MetricsRecorder记录(RecordSchedulerPluginDuration、RecordKVCacheTokenizeDuration、RecordKVCacheMatchRatio等),还有 access log(text/json 可配)。debug server(debug-port15000,仅 localhost)可 dump 内部缓存。 - CLI:cli/kthena/ 提供独立
kthena命令和 kubectl 插件,支持 create/get/describe,内置主流模型部署模板(templates.go),降低上手门槛。 - webhook:两个二进制各自跑 validating/mutating webhook,证书自动生成存 Secret(pkg/webhook/cert)。CRD/API 变更要保持向后兼容。
八、性能数据与场景价值
根据官方博客的基准测试,在长系统 prompt(如 4096 token)场景下,KV Cache-aware + Least Request 相比随机基线:
- 吞吐提升约 2.73×
- TTFT 降低约 73.5%
- 端到端延迟降低 60%+
| 插件配置 | 吞吐(req/s) | TTFT(s) | 端到端延迟(s) |
|---|---|---|---|
| Least Request + KVCacheAware | 32.22 | 9.22 | 0.57 |
| Least Request + Prefix Cache | 23.87 | 12.47 | 0.83 |
| Random | 11.81 | 25.23 | 2.15 |
短 prompt 场景差距会随 prompt 长度收敛,但在多轮对话、模板化生成、高相似前缀的业务场景里,KV Cache-aware 路由收益显著。这正是「混搭、按场景选插件」的价值——调度器是可插拔的,按业务特征组合 filter/score 插件即可。
九、总结:Kthena 的设计取舍
通读源码后,Kthena 的几个关键设计取舍清晰可见:
-
双平面彻底解耦 是最大的架构赌注。它牺牲了一点「一站式」的便利(全平台才能用 ModelBooster),换来了极强的可分块采用性——你今天只需要一个模型感知网关,明天再加控制面,平滑演进。两个平面只跟 K8s API 说话,没有运行时耦合。
-
请求级 filter/score 调度框架 借鉴 K8s 调度器但作用域不同。微秒级的请求调度 + 可插拔插件,让 KV cache 感知、prefix cache、LoRA 亲和等 LLM 特有智能能以插件形式组合,而不是硬编码进路由。
-
PD 分离一等公民。从 CRD(
ModelBackendType.vLLMDisaggregated、worker 的 prefill/decode 角色)到调度器(PDGroup 配对)到代理(多对重试 + KV connector)到扩缩(disaggregated ratio),全链路把 PD 分离当一等场景支持,而不是事后补丁。 -
异构成本感知扩缩 把「省钱」做成调度问题。ReplicaBlock 按 cost 排序的贪心填充,让 H100+A100 混部时优先扩便宜实例,贵的留到最后——这是云原生推理平台少见的成本视角。
-
运行时 Agent 统一多引擎抽象。用 Python sidecar 把 vLLM/SGLang 的指标、事件、LoRA 生命周期、KV cache 事件归一化,控制面就能用统一逻辑管理多种引擎,避免每引擎一套控制代码。
Kthena 仍在活跃迭代(router 明确标注为 reference implementation,因为 Gateway Inference Extension 原生不支持 PD 分离),但它的架构骨架——双平面解耦、请求级可插拔调度、PD 分离全链路、成本驱动扩缩——已经为云原生 LLM 推理提供了一个相当完整且工程化成熟的范本。对于想做大规模 LLM 推理平台的人来说,这份源码值得逐模块精读。
