在 K8s 上部署 TGI(Text Generation Inference)服务时,持久化模型存储和“动态加载”是两件容易被混为一谈的事。TGI 进程启动时会把模型权重加载到显存或内存,运行期间不会自动重新读取磁盘上的模型文件,因此“动态加载”在 TGI 场景通常理解为:在不需要重建集群、不中断在线请求的前提下,完成模型文件的更新与服务的重新拉起。实现方式取决于你希望做到多“动态”,以及模型文件由谁管理。
部署 TGI 建议先区分“静态挂载”和“模型更新”:TGI 不支持运行时热换模型,模型变更必须重启服务实例。持久化模型存储用 PVC 挂载即可,但要保证多副本只读共享;动态加载更适合用滚动更新 Deployment、切换 model_id 路径、或预先在 PVC 里放多个模型版本,再通过 Service 指向不同副本组来实现。验证时重点看启动日志、模型加载时间和 metrics。
1. 部署 TGI 的基本形态
TGI 在 K8s 里的最小单元是 Deployment + Service。镜像选择 `ghcr.io/huggingface/text-generation-inference`,具体 tag 需结合部署机器的 GPU 驱动和 CUDA 版本确认。Deployment 里最关键的是环境变量,TGI 通过环境变量读取模型名称、路径、量化参数等。
apiVersion: apps/v1
kind: Deployment
metadata:
name: tgi-svc
spec:
replicas: 1
selector:
matchLabels:
app: tgi
template:
metadata:
labels:
app: tgi
spec:
containers:
- name: tgi
image: ghcr.io/huggingface/text-generation-inference:latest
resources:
limits:
nvidia.com/gpu: 1
env:
- name: MODEL_ID
value: /models/llama-2-7b-chat
- name: PORT
value: "8080"
ports:
- containerPort: 8080
volumeMounts:
- name: model-storage
mountPath: /models
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: model-pvc这个配置把所有模型文件放到 PVC 的 `/models` 目录下,`MODEL_ID` 指向具体子目录。如果有多个 GPU 副本,PVC 需要支持多节点读写(ReadWriteMany),否则只能把 replicas 设为 1 或使用本地盘。
2. 持久化模型存储的配置要点
模型存储的持久化有两种常见方式:一是把模型文件直接放入 PVC,二是把模型打进镜像。前者适合模型频繁更新、想省去重新构建镜像的场景;后者适合模型固定不变、想通过镜像版本做回滚的场景。对于“动态加载”诉求,建议用 PVC。
PVC 的 YAML 不复杂,难点在于底层存储选择:NFS、CephFS、云厂商的共享盘都支持多读,具体需要结合环境确认。如果只有一个节点,可以使用 hostPath,但注意 hostPath 不保证跨节点调度,且节点头文件系统异常会影响服务。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: nfs-csi模型文件放到 PVC 后,需要提前把目录结构安排清楚。建议按 `模型名/版本/` 组织,比如 `/models/gpt2/20250601/`,这样后续切换模型时只需要修改 `MODEL_ID` 环境变量,不需要搬移文件。模型下载可以放在单独的一次性 Job 里执行,也可以先在测试机上把模型传到共享存储,再挂载到 K8s。
3. “动态加载”模型更新的通用套路
TGI 本身不提供热加载模型接口,所以动态效果要由 K8s 编排层实现。下面两种方式比较实用,按需选用。
方式一:滚动更新 Deployment,修改 environment 后重新创建 Pod
把模型文件按版本放在 PVC 的不同子目录后,修改 Deployment 里的 `MODEL_ID` 指向新版本,然后执行 kubectl rollout restart deployment/tgi-svc。滚动更新会先起新 Pod,再杀掉旧 Pod,配合 Service 可做到平滑切换。适合单模型多版本切换、回滚场景。
kubectl -n your-ns set env deployment/tgi-svc MODEL_ID=/models/llama-2-70b-chat-0401
kubectl -n your-ns rollout status deployment/tgi-svc注意:如果新模型文件还未完全写入 PVC,直接重启 Pod 会导致启动失败。最好先执行 kubectl exec 到 PVC 的挂载目录确认文件完整,再触发滚动更新。
方式二:多个 Deployment 对应不同模型,用 Service 分流
如果业务要求不同模型同时在线,或者你不想每次更新都等待滚动发布,可以为每个模型创建一个独立的 Deployment,例如 `tgi-llama2-7b`、`tgi-llama2-70b`,再用 Service 或 Ingress 按路径转发。这种方式的“动态”体现在不需要动已有实例,新模型直接以新 Service 发布,流量切换通过路由规则实现。
apiVersion: v1
kind: Service
metadata:
name: tgi-router
spec:
selector:
app: tgi
ports:
- port: 8080如果使用 Ingress,可以按路径把 `/model-a` 转发到对应的 Service。这种方式最适合需要多模型共存、互不影响的场景。
4. 验证与边界确认
完成部署后,不能只看 Pod 状态。TGI 启动时需要下载或加载模型,Pod 进入 Running 不代表模型已经可用。建议用下面的顺序验证:
- 查看日志:
kubectl logs -f deployment/tgi-svc,确认出现模型加载完成、HTTP server 启动等关键词。 - 请求 /health 或 /info 接口,确认返回状态正常。TGI 的接口路径在不同版本中有差异,需要结合镜像版本确认。
- 用一次真实推理调用验证模型是否可响应,例如
curl -X POST http://service:8080/generate -H 'Content-Type: application/json' -d '{"inputs":"hello","parameters":{}}'。 - 如果是多副本,确认 PVC 支持多读,且没有文件锁问题。
需要清醒的是:TGI 启动时加载模型到显存,这个过程可能要几十秒到几分钟,期间请求会超时。如果模型文件变化频繁,建议每次发布前做好版本目录的原子切换,比如先写入新目录,再通过软链接指向“当前目录”,最后改环境变量触发重启。这样能减少启动失败窗口。
常见问题
问:可以用 emptyDir 做模型缓存吗?
emptyDir 的生命周期与 Pod 一致,Pod 重建后模型文件就丢失,适合临时测试,不适合持久化。如果只是想把启动时下载的模型缓存到本地加速重启,可考虑 hostPath 或单独的本地 PV,但要注意调度约束。
问:多个副本同时挂载同一个 PVC 会不会冲突?
如果 PVC 只用于读模型文件,且底层存储支持 ReadWriteMany(如 NFS),一般不会冲突。若底层只支持 ReadWriteOnce,则只能调度到同一节点,副本数再高也没有意义。