news 2026/10/12 1:43:13

kubeswap 模型权重存储选型:Kubernetes 集群中共享模型缓存 PVC 的 RWX、RWO 与 emptyDir 方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
kubeswap 模型权重存储选型:Kubernetes 集群中共享模型缓存 PVC 的 RWX、RWO 与 emptyDir 方案
  • 后端
  • API网关
  • LLM 网关
  • 人工智能
  • 大模型
  • 本地部署

【免费下载链接】llama-swap

Reliable model swapping for any local OpenAI/Anthropic compatible server - llama.cpp, vllm, etc

项目地址:https://gitcode.com/gh_mirrors/ll/llama-swap
点击查看免费下载

导读

在 llama-swap 的 kubeswap 架构里,模型是按需加载、随 TTL 或交换事件被驱逐的:一个模型 Pod 今天被删除,下一次请求时才被重建,且可能落在另一个节点上。这决定了存放模型权重的共享缓存卷必须能被所有后端可能落脚的节点挂载——在实践中就是ReadWriteMany(RWX)。本文围绕 storage-options-kubernetes.md 展开,讲清 RWX 的驱动选择、单节点环境下用 RWO 加节点固定(node-selector)的逃生方案、以及完全不使用 PVC 的 emptyDir 按需拉取方案,并结合 kubeswap 源码说明它创建、采纳和校验 PVC 的完整行为。

为什么共享模型缓存必须是 ReadWriteMany

llama-swap 通过kubeswap serve为每个模型维护一个 Kubernetes Deployment。模型的生命周期由路由器驱动:

  • 按需加载:收到请求、且该模型当前没有运行中的 Pod 时,kubeswap 才创建 Deployment,Pod 拉起后端服务(llama-server、sd-server、whisper-server、audiocpp_server 等);
  • TTL 或 swap-in 驱逐:空闲超过ttl、或同组内另一个模型需要抢占资源时,kubeswap 通过cmdStop(即kubeswap delete ...)删除 Pod;
  • 下一次请求重建:被驱逐的模型在下次请求时重新调度,调度器可以把它放到任何满足条件的节点上。

关键在于"可能换节点"这一环:如果共享模型缓存 PVC 是ReadWriteOnce(RWO),它只能被一个节点挂载。被驱逐的模型重新调度到另一个节点时,新 Pod 无法挂载该卷,会一直卡在ContainerCreating状态并报卷错误,而路由侧只能干等healthCheckTimeout超时——这既拖慢了请求,也让健康检查逻辑白白空转。

因此,只要你的模型可能在不同节点之间漂移,共享缓存就必须是ReadWriteMany:让每个后端可能落脚的节点都能挂载同一份权重。

提供 RWX 的存储驱动

文档给出的 RWX 驱动选型表如下:

驱动说明
Longhorn示例中的默认选项;通过其 NFS/replica 模式提供 RWX,适合家庭实验室(homelab)
CephFS(Rook)原生 RWX;集群已经跑 Rook 时的自然选择
Azure Files原生 RWX;AKS 上的托管方案
AWS EFS原生 RWX;EKS 上的托管方案
NFS Server通过 NFS CSI 驱动或静态 PV 提供 RWX

与之相对,云厂商的块存储(AWS EBS、GCP Persistent Disk、Azure Managed Disks)只支持ReadWriteOnce——不要为共享缓存选择它们。块存储适合单个节点独占的场景,无法满足"权重卷要随模型在节点间漂移"的需求。

存储规则与 GPU 无关:CPU-only 集群同样适用

这套选型规则并不依赖 GPU:

  • 路由器头端(head-end)本身不需要 GPU;
  • 不带--gpu的模型会得到一个纯 CPU 的 Pod,可以调度到任意节点——kubeswap-kubernetes.md 示例中的 whisper 与 TTS 模型正是这样运行的(distil-whisper-lgv3只挂--volume pvc:llama-swap-models:/models:ro,qwen3-tts-06b以--backend cpu运行);
  • 一个 CPU 模型被驱逐后同样可能在任何 CPU 节点重建,所以多 CPU 节点共享缓存同样需要 RWX;
  • 只有单节点的 CPU 环境才可以用 RWO 或本地卷,配合下面介绍的节点固定(pin)手段。

RWO 逃生方案:把后端 Pod 固定到单一节点

如果你的集群只有一个 GPU 节点(家庭实验室的常见形态),RWO 块存储完全可用——前提是每个后端都必须落在那个节点上。用--node-selector把 Pod 钉死即可:

cmd: >- kubeswap serve --listen 127.0.0.1:${PORT} --model lfm25-230m --namespace llama-swap --image ghcr.io/mostlygeek/llama-swap:unified-vulkan --gpu amd.com/gpu=1 --node-selector kubernetes.io/hostname=gpu-node # every backend, same node --volume pvc:llama-swap-models:/models:ro -- --model /models/model.gguf --port 8080

固定之后,RWO PVC(EBS、PD、Longhorn RWO)在 Pod 运行的任何位置都可以挂载,因为 Pod 永远只会被调度到gpu-node这一个节点。一旦去掉固定,Pod 可能漂移,你又回到了必须使用 RWX 的场景。

从源码看,--node-selector是key=value形式、可重复传入的 flag(serve.go),渲染时被直接写入 Deployment 的pod.spec.nodeSelector;objects_test.go 中TestKubeswap_DeploymentSpecMatchesNodeSelector还验证了 node-selector 的漂移会被严格模式检测到并触发替换。

零存储逃生方案:emptyDir + 按需拉取

小模型可以完全跳过 PVC:每个 Pod 在首次加载时把权重下载进自己的emptyDir。Helm chart 的默认演示配置就是这么做的——values.yaml 中模型只挂了emptydir:model-cache:/models和emptydir:slots:/slots,并用-hf QuantFactory/SmolLM2-135M-Instruct-GGUF:Q4_0从 Hugging Face 拉取约 135MB 的权重:

--volume emptydir:model-cache:/models -- --model /models/SmolLM2-135M-Instruct-Q4_0.gguf -hf QuantFactory/SmolLM2-135M-Instruct-GGUF:Q4_0

代价是每次重新加载都要重新下载:对 135MB 的演示模型可以接受,但对 16GB 级别的图像模型来说非常痛苦。emptyDir 的生命周期与 Pod 绑定,Pod 一旦被驱逐(TTL、swap-in、节点回收),缓存随之消失。

顺带一提,kubeswap 的--volume语法支持三种卷类型:pvc:name:path[:ro]、emptydir:name:path[:ro]和hostpath:nodePath:mountPath[:ro](可重复传入,serve.go)。其中hostpath会绕过 namespace 边界直接读写节点文件系统,kubeswap 在 serve.go 中对此给出明确警告,共享缓存场景下应优先使用 PVC。

kubeswap 对 PVC 的处理行为

结合 serve.go 与 objects.go 的实现,kubeswap 对 PVC 的处理遵循三条规则:

1. 缺失的 PVC 由kubeswap serve自动创建

当--volume pvc:<name>引用的 PVC 不存在时,kubeswap 调用renderPVC创建它,三个参数共同决定卷的形态:

参数默认值说明
--pvc-size1Gi创建的 PVC 的容量(见 serve.go)
--pvc-class集群默认 StorageClass创建 PVC 时使用的存储类,空值即不指定,交给集群默认(见 serve.go)
--pvc-access-moderwo取rwo或rwx,决定 PVC 的 accessModes(见 serve.go)

在 objects.go 中,rwo映射为ReadWriteOnce,rwx映射为ReadWriteMany。当你预期模型缓存要在节点间漂移时,务必传--pvc-access-mode rwx——默认的rwo创建的卷无法被第二个节点挂载。

2. 已存在的 PVC 原样采纳,绝不重新标记

如果 PVC 已经存在(比如操作员预置的共享缓存),kubeswap 直接采纳而不做任何 relabel,也不会替换它——lifecycle_test.go 的TestKubeswap_EnsureResourcesAdoptsExistingPVC验证了这一点。因此,正确的做法是把缓存 PVC 声明在 Helm chart 的extraResources下,让 helm 负责跟踪(详见下节)。

3. 读写挂载与访问模式不匹配时,启动即报错

采纳既有 PVC 时,kubeswap 会校验其 access modes 能否支撑当前挂载方式(objects.go 的pvcAllowsReadWrite:ReadWriteOnce、ReadWriteOncePod、ReadWriteMany均可,ReadOnlyMany不可以)。如果模型以读写方式挂载(--volume pvc:name:/path,不带:ro)了一个只读的 PVC,kubeswap serve会直接失败,错误信息中指明 claim 名称和修复办法(加:ro或改用读写 PVC),而不是让 Pod 卡在ContainerCreating里。这一行为在 lifecycle_test.go 的TestKubeswap_EnsureResourcesRejectsReadOnlyPVCForWritableMount中覆盖了完整的分支:ROX PVC + 读写挂载报错、ROX PVC +:ro挂载可用、RWO/RWOP/RWX PVC + 读写挂载全部可用。

:ro挂载对任何 access mode 都成立——把共享缓存以只读方式挂载,把 KV slot 等可变状态放到 emptyDir 上(即--volume emptydir:slots:/slots+--slot-save-path /slots,如 kubeswap-kubernetes.md 所示),这是文档推荐的多节点部署形态。

用 chart 的 extraResources 声明缓存 PVC

既然 kubeswap 采纳已有 PVC 而非自行管理,让 helm 跟踪它才是正统做法——PVC 作为extraResources原样渲染并纳入 helm 生命周期管理。文档给出的完整示例:

# extraResources in the chart values — the idiomatic home for the cache PVC extraResources: - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: llama-swap-models spec: accessModes: [ReadWriteMany] storageClassName: longhorn resources: requests: storage: 35Gi

配合多引擎示例配置(kubeswap-kubernetes.md),llama-swap-models这个 RWX PVC 被所有模型以--volume pvc:llama-swap-models:/models:ro只读挂载,35Gi 的容量足以容纳 LLM、图像模型、whisper 与 TTS 的一整套权重(该示例中权重布局包括 LFM2.5-230M、krea-2-turbo、Qwen3-VL、wan/ideogram VAE、distil-large-v3、qwen3-tts 等文件)。

决策速览:你的集群该选哪种方案

  • 多节点、模型可能漂移(GPU 或 CPU 集群)→ RWX 共享缓存:Longhorn / CephFS / Azure Files / AWS EFS / NFS,PVC 挂:ro,slot 状态放 emptyDir;
  • 单 GPU 节点(homelab)→ 可以用 RWO 块存储,但必须用--node-selector把所有后端固定到同一节点;去掉固定即回到 RWX 需求;
  • 小模型、可容忍重复下载→ 完全不建 PVC,--volume emptydir:model-cache:/models+-hf repo:file按需拉取,135MB 级别的演示模型可接受,16GB 级别不推荐;
  • 任何形态都注意:期望缓存跨节点移动时给创建型 PVC 传--pvc-access-mode rwx;已有 PVC 放extraResources里让 helm 跟踪;读写挂载与 PVC 访问模式不匹配会在启动阶段直接报错,不要指望 Pod 卡住后还能自愈。

延伸阅读

  • kubeswap 多引擎完整配置示例:RWX 共享缓存 + 四类后端引擎的可直接复制的 config.yaml;
  • kubeswap 命令参考与设计说明:--volume、--pvc-*等全部 flag 的取值与默认值;
  • kubeswap Helm chart values:默认 emptyDir 演示配置与extraResources、结构化配置的完整注释;
  • PVC 处理与卷渲染源码:renderPVC、pvcAllowsReadWrite与renderVolumes的实现;
  • PVC 采纳与拒绝行为测试:既有 PVC 采纳、ROX 拒绝、RWO/RWOP/RWX 放行等全部用例。
  • 后端
  • API网关
  • LLM 网关
  • 人工智能
  • 大模型
  • 本地部署

【免费下载链接】llama-swap

Reliable model swapping for any local OpenAI/Anthropic compatible server - llama.cpp, vllm, etc

项目地址:https://gitcode.com/gh_mirrors/ll/llama-swap
点击查看免费下载

相关推荐

上一篇:BusyBox跨平台编译指南:从x86到ARM的完整流程
下一篇:HTML压缩终极指南:7种配置策略对比分析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

ESP8285+MQTTX:电机控制器物联网接入实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 1:39:16

AnyPS5实战:用SQLite构建本地游戏库管理与统计工具

1. 游戏库从第三十款开始失控&#xff1a;我为什么要写AnyPS5说实话&#xff0c;我的PS5游戏库大概从第三十款开始就彻底失控了。当时我对着主机里的游戏列表想找某款回合制RPG&#xff0c;想了半天没想明白它到底是实体盘还是数字版、当时多少钱入的、还差几个奖杯能白金。群里…

作者头像 李华
网站建设 2026/10/12 1:39:09

SpringBoot+Vue+MySQL旅游网站管理平台:全栈毕设项目详解

如果你正在为毕业设计或课程设计发愁&#xff0c;想找一个“既能体现工作量、又不会把自己绕晕”的题目&#xff0c;“SpringBoot Vue 安康旅游网站管理平台”是非常值得认真考虑的方向。这不是客套话&#xff1a;旅游网站管理平台这套业务&#xff0c;天然包含了 Java 后端常…

作者头像 李华
网站建设 2026/10/12 1:37:20

Idea2Paper如何选对写作Pattern?三维评分与Story生成链路详解

【免费下载链接】Idea2Paper Idea2Paper Offical Demo 项目地址&#xff1a; https://gitcode.com/gh_mirrors/id/Idea2Paper 点击查看 免费下载 Idea2Paper 是一个将研究 Idea 自动转化为完整论文故事&#xff08;Story&#xff09;的端到端科研 Agent 框架。它的核心难点之一…

作者头像 李华