为 GPU 成本优化而生:Hearth 开源,一个云原生、支持异构计算的 LLM 推理引擎,邀你共建
缘起:一台空转的 GPU
自今年以来我在公司内部搭过几次自建大模型推理服务。流程大同小异:拉一个 vLLM 镜像,写个 Deployment,挂上 nvidia.com/gpu: 1,配个 Service,再加个 HPA。能跑,但有两件事一直让我别扭。
第一件:模型在空转的时候,GPU 还在烧钱。 内部很多模型是「偶尔有人用」的——评测环境、给某个业务方留的测试通道、Demo 用的小模型。它们一天可能就被调用几十次,但 Pod 得 7×24 占着一整张卡。HPA 默认最小副本是 1,它不会帮你缩到 0;就算你把 minReplicas 设成 0,原生 HPA 也不支持从 0 启动——因为没有副本就没有指标,没有指标就永远不会扩容,这是个先有鸡还是先有蛋的死结。
第二件:这套东西是「NVIDIA 优先 + 英文优先」的。 现在国内自建推理,绕不开昇腾、寒武纪这些国产卡。vLLM 社区有 vllm-ascend、vllm-mlu 这些后端,但把它们编排进 K8s、做好健康检查、模型加载、缓存预热、指标采集——这部分活儿,每家都在自己那套 YAML 里重写一遍。换一种卡,整套部署模板推倒重来。
我翻了一圈现有方案:KServe、llm-d 这些是冲着数据中心级、大规模 serving 去的,重;vLLM 自己只管推理引擎,不管 K8s 编排;各家云厂商的托管方案又把你锁死在它们的平台上。没有一个东西,是专门为「少量 GPU、可以容忍冷启动、对成本敏感、还想要厂商中立」这个场景设计的。
于是我开源了Hearth(hearth-project/hearth: Declarative, scale-to-zero LLM serving on Kubernetes — vendor-neutral.)——你的模型,你的炉火,平时熄着,要用的时候点起来。
一句话:把「在我的集群上跑 Qwen」变成一个声明式 manifest
Hearth 是一个 Kubernetes Operator(基于 Kubebuilder / controller-runtime)。它的目标是把一坨手写 YAML,收敛成一个 LLMService:
apiVersion: serving.hearth.dev/v1alpha1
kind: LLMService
metadata:
name: qwen3-8b
namespace: ai
spec:
model:
source:
uri: modelscope://Qwen/Qwen3-8B-Instruct # hf:// | modelscope://
runtime:
selector: { vendor: [nvidia, ascend] } # 按偏好顺序自动选后端
resources:
accelerators: 1
scaling:
min: 0 # 缩容到零
max: 3
metric: queueDepth
target: 10
kubectl apply 之后,Operator 会把它展开成一整套子对象:vLLM 的 Deployment、Service、模型缓存 PVC、预热 Job、网关(Gateway)、以及一个 KEDA 的 ScaledObject。然后你会看到:
$ kubectl get llmservice -n ai
NAME PHASE RUNTIME REPLICAS AGE
qwen3-8b ScaledToZero vllm-nvidia 0 30s
REPLICAS 0——没有请求的时候,它一张卡都不占。同样这份 manifest,把可用后端换成 vllm-ascend,在昇腾集群上一行不用改就能跑。这种可移植性,是我做这个项目的核心目的。
下面我想认真讲讲,作为维护者,这里面我觉得最有意思的两个设计。
设计一:缩容到零,难点根本不在「缩」,在「从零启动」
「缩到 0」很容易,没流量了把副本砍到 0 就行。真正的难点是:当一个请求打进来,后端却是 0 副本时,这个请求该怎么办?
原生 HPA 在这里是死的。所以 Hearth 在后端前面放了一个轻量的 网关(Gateway)——一个 OpenAI 协议兼容的反向代理,每个 LLMService 一个。整条数据通路是这样的:
client ──OpenAI API──▶ Hearth Gateway ──就绪后转发──▶ vLLM pods (0..N)
▲ │
│ 轮询 /hearth/queue │ 加载权重
KEDA ──────扩缩 0..N────────────▶│
模型缓存
关键点在于网关暴露的一个指标端点 /hearth/queue,它返回当前积压(pending)的请求数。这个数,就是给 KEDA 看的「需求信号」。
一次冷启动的完整过程:
-
空闲:KEDA 把后端 Deployment 摁在 0 副本。
-
冷请求到达:网关收下请求(有界队列,满了就
429背压),把pending计数 +1,然后把这个连接 hold 住。在keepalive模式下,它会往这条 SSE 流里发心跳(: heartbeat),让客户端和网关层不要超时断开;在reject模式下,它直接返回503 + Retry-After让客户端稍后重试。 -
激活:KEDA 用
metrics-api这个 scaler 去轮询网关的/hearth/queue,一看pending > 0,就把 Deployment 从 0 拉到 1。Pod 起来后从本地缓存(已预热)加载权重,只有当模型真正加载完、就绪探针通过,Pod 才算 Ready。 -
服务:网关把那个一直 hold 着的请求转发给后端,开始往回 stream token。
-
扩容:如果队列持续高于阈值,KEDA 继续
1 → N(一个副本一整张卡)。 -
缩容:需求降下来,KEDA 往回缩到 0;Pod 终止前有一个
preStop排水钩子,让还在流式输出的请求先跑完再退出。
这里有几个我踩过、也想强调的工程细节:
-
Operator 永远不设置
.spec.replicas。 副本数完全归 KEDA 的 HPA 管(0..N)。Operator 只负责把「形状」reconcile 出来,谁来决定副本数,是另一回事。这个边界一旦混了,两个控制器会互相打架。 -
冷启动会有一个「需求残留」(demandLinger)。 一个返回很快的请求,可能在 KEDA 的两次轮询间隔之间就处理完了,导致
pending又变回 0,激活信号一闪而过、扩容没触发。所以网关会把信号多举一会儿,保证它能跨过一个轮询周期。 -
冷启动成本主要在「拉权重 + 加载」上,所以缓存才是让「缩容到零」真正可用的前提。v0 支持
HostPath和节点本地 PVC,外加一个预热 Job 在首次流量前把权重灌好。
设计二:厂商中立,靠的是「把差异当数据,而不是当代码」
要支持 N 种加速卡,最糟的做法是写 N 套 Operator,或者在代码里堆一堆 if vendor == "ascend"。Hearth 的做法是把后端描述成数据——一个集群级的 InferenceRuntime CRD:
apiVersion: serving.hearth.dev/v1alpha1
kind: InferenceRuntime
metadata:
name: vllm-nvidia
spec:
vendor: nvidia
image: ...
accelerator: nvidia.com/gpu # 换昇腾就是 huawei.com/Ascend910
args: [ ... ] # 带模板的启动参数
probes: { ... } # 模型加载感知的探针
metrics: { ... } # LLM 指标在哪
镜像、启动参数模板、设备插件资源名、探针、指标路径——这些厂商之间真正的差异,全都是数据。于是适配器(adapter)的代码可以非常薄:它只做「K8s 层的适配」——调度、健康检查、模型加载、指标采集,绝不去碰芯片算子,绝不重写 vLLM。
| 后端 | 引擎 | 加速卡 | v0 状态 |
| vllm-nvidia | NVIDIA-vLLM | 英伟达 | 已实现,真机 GPU 验证 |
| vllm-ascend | vLLM-Ascend | 昇腾 | 已搭好,golden 测试通过 |
| vllm-mlu | vLLM-MLU | 寒武纪 | 规划中 |
加一种新卡 = 加一个薄薄的 adapter 包,在 registry 里注册一下,而不是重写整个项目。这条边界(Hearth 只做 K8s 编排/生命周期层,推理引擎交给 vLLM,算子和设备插件交给厂商和 HAMi、Volcano)是我一直克制自己不要越界的地方——一个项目想做的事越少,它能做好的概率才越大。
一个我自己很在意的细节:没有 GPU 也能开发和测试
厂商中立有个现实问题:贡献者大概率没有一柜子各种卡。如果改个调度逻辑都得上真机,这个项目就别想有人参与了。
所以我专门做了一套无 GPU 测试链路:一个 CPU 版的 vllm-stub(假的 vLLM,能模拟启动延迟、流式输出、/metrics),配上给节点打的假扩展资源,在 kind 集群上不需要任何加速卡,就能把完整的 0 → 1 → N → 0 缩放循环、冷启动 keepalive、优雅排水全部跑通。make test-scale-e2e 一条命令,本地或 CI 都能验证缩容到零的核心行为。
这意味着:你想给 Hearth 贡献代码,一台普通笔记本就够了。
诚实地说说现状:这是 alpha,不是成品
我不想把它包装得比实际更好。截至 v0.1.0:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)