AI Infra实战05:用Helm在K8s中部署vLLM,从安装到压测全流程

AI Infra实战05:用Helm在K8s中部署vLLM,从安装到压测全流程

本篇配套的全部脚本、YAML、Helm Chart、预检工具和 Terraform 配置已开源在 GitHub:https://github.com/Jich1123/gpu-k8s-lab 。clone 下来按 00-lab/execution-checklist.md 的顺序执行即可复现全部实验(仓库不含任何真实 IP、密钥或账号信息)。

本篇目标

用自己编写的 Helm Chart 在 K8s 中部署 vLLM 推理服务,跑通 OpenAI 兼容 API,用 2500 次请求压测拿到真实性能数据,并在 Grafana 上看到 GPU 从空闲到满载的完整曲线。

学完本篇你将掌握:

  • 如何把 vLLM 封装成 Helm Chart(Deployment、Service、PVC、ServiceMonitor)
  • 单 GPU 环境下的更新策略选择(Recreate vs RollingUpdate)
  • T4 上 vLLM 的真实性能表现(吞吐、延迟分布、显存分配)
  • 压测方法和结果解读
  • T4 的架构限制(V0 引擎回退、XFormers 替代 FlashAttention)

证据等级:B级实验复现。本篇所有命令、输出和性能数据来自一次真实执行(AWS g4dn.xlarge / T4),压测 2500 请求零失败。


实操环境

配置
平台 AWS EC2 g4dn.xlarge
GPU Tesla T4(16GB显存)
系统 Ubuntu 22.04.5 LTS
k3s v1.34.10+k3s1
GPU Operator 已部署(实验01)
监控 kube-prometheus-stack 已部署(实验02)
vLLM v0.10.1.1
模型 Qwen/Qwen2-1.5B-Instruct
费用 0.526/小时,本篇实操约 0.5(含模型下载和压测)

一、为什么用 Helm 部署,而不是直接 kubectl apply

前面第03篇(vLLM部署实战)是在裸机上直接跑 vllm serve,适合快速验证。但在 K8s 环境中,你需要:

  • Deployment(管理 Pod 生命周期、重启策略)
  • Service(提供稳定的集群内访问入口)
  • PVC(模型缓存持久化,避免每次重启都重新下载几个 GB 的模型)
  • ServiceMonitor(让 Prometheus 自动发现 vLLM 暴露的 /metrics)
  • 可配置的 values.yaml(不同环境改参数,不改模板)

这些打包成一个 Helm Chart,一条 helm install 全部部署,一条 helm uninstall 全部清理。


二、Chart 核心设计

完整 Chart 在配套资料的 05-vllm-helm/vllm-chart/ 中,这里说几个关键设计决策。

更新策略:Recreate

yaml 复制代码
strategy:
  type: Recreate

单 GPU 环境必须用 Recreate。因为 nvidia.com/gpu: 1 是独占资源,如果用 RollingUpdate,新 Pod 和旧 Pod 同时申请同一张 GPU,新 Pod 会一直 Pending。Recreate 先杀旧再起新,代价是升级时有短暂中断,但避免了 GPU 争抢死锁。

模型缓存 PVC

yaml 复制代码
volumes:
  - name: model-cache
    persistentVolumeClaim:
      claimName: vllm-vllm

20Gi PVC 挂载到 /root/.cache/huggingface,模型权重下载一次就缓存住。Pod 被删重建后,PVC 还在,不用重新下载。

GPU 资源申请

yaml 复制代码
resources:
  requests:
    nvidia.com/gpu: "1"
  limits:
    nvidia.com/gpu: "1"

requests 和 limits 都写 1,确保 Pod 独占这张 T4。

启动命令与参数

Deployment 模板里,容器的启动命令就是 vllm serve,后面跟一串通过 values 注入的参数:

yaml 复制代码
command: [vllm, serve, "Qwen/Qwen2-1.5B-Instruct"]
args:
  - --host
  - 0.0.0.0
  - --port
  - "8000"
  - --dtype
  - float16
  - --max-model-len
  - "4096"
  - --gpu-memory-utilization
  - "0.90"
  - --max-num-seqs
  - "64"
  - --tensor-parallel-size
  - "1"

几个和 T4 相关的关键参数:

  • --dtype float16:T4 不支持 bfloat16 的高效计算,用 float16。vLLM 启动时会自动把模型的 bfloat16 权重转成 float16。
  • --gpu-memory-utilization 0.90:让 vLLM 用 90% 的显存(剩 10% 给系统和碎片)。这个值直接决定 KV Cache 能拿多少显存,进而决定最大并发。
  • --max-model-len 4096:单请求最大 token 数,越大越占显存。
  • --tensor-parallel-size 1:单卡,不做张量并行。多卡才需要调大。

三个探针

模板给容器配了 startup / readiness / liveness 三个探针,都打 /health:

yaml 复制代码
startupProbe:   { httpGet: { path: /health, port: http }, failureThreshold: 180 }
readinessProbe: { httpGet: { path: /health, port: http } }
livenessProbe:  { httpGet: { path: /health, port: http } }

startupProbe 的 failureThreshold 给得很大(180)是刻意的:vLLM 首次启动要下载模型 + 加载权重 + 初始化引擎,可能好几分钟。startup 探针没过之前,readiness 和 liveness 不生效,避免容器还在加载模型就被 liveness 判定失败重启,陷入"启动-被杀-重启"的死循环。这是部署大模型推理服务的一个通用技巧。


三、部署

bash 复制代码
bash install.sh

关键输出:

复制代码
Release "vllm" does not exist. Installing it now.
STATUS: deployed
NAMESPACE: ai-inference

Pod 启动过程

安装后 Pod 经历 Pending → ContainerCreating → Running 三个阶段:

  • ContainerCreating(约4分钟):拉取 vLLM 镜像(vllm/vllm-openai:v0.10.1.1,较大)
  • Running 但 0/1(约1-2分钟):容器启动了但 readiness 探针还没过,vLLM 在下载模型和初始化引擎
  • Running 1/1:服务就绪

vLLM 启动日志中的关键信息

模型加载成功后,日志里有一段非常有价值的显存分配信息:

复制代码
total_gpu_memory (14.56GiB) x gpu_memory_utilization (0.90) = 13.11GiB
model weights take 2.89GiB
non_torch_memory takes 0.05GiB
PyTorch activation peak memory takes 0.48GiB
the rest of the memory reserved for KV Cache is 9.70GiB
Maximum concurrency for 4096 tokens per request: 88.64x

这段话翻译一下:

  • T4 总显存 14.56 GiB,vLLM 用了 90% = 13.11 GiB
  • 其中模型权重 2.89 GiB(Qwen2-1.5B 很小)
  • KV Cache 拿到了 9.70 GiB,这是 vLLM 高并发的关键:它用 PagedAttention 动态分配 KV Cache,这 9.7G 能同时服务 88 个并发请求(4096 tokens/请求)

T4 上的特殊行为:T4 是 Turing 架构(Compute Capability 7.5),vLLM 的 V1 引擎不支持,自动回退到 V0 引擎;同时 FlashAttention-2 不支持 Turing,改用 XFormers 后端。这不影响正确性,但性能不如 A100/H100 上的 V1 + FlashAttention-2。这是 T4 上的固有行为,文章里应该说清楚,避免读者用 T4 复现时困惑。


四、验证:API 真的能推理

bash 复制代码
bash scripts/verify.sh

查询模型

json 复制代码
{"data":[{"id":"qwen2-1.5b-instruct","object":"model","max_model_len":4096}]}

真实对话

问:什么是 Kubernetes?

json 复制代码
{
  "choices":[{
    "message":{
      "content":"Kubernetes是一个开源的容器编排和自动化管理系统,用于管理和部署容器化应用程序。"
    },
    "finish_reason":"stop"
  }],
  "usage":{"prompt_tokens":26,"completion_tokens":20,"total_tokens":46}
}

推理成功,finish_reason 是 stop(自然结束,不是截断)。

Helm Test

复制代码
TEST SUITE: vllm-vllm-test-connection
Phase: Succeeded

五、压测:2500 请求的真实性能数据

这是本篇最有价值的部分。用配套的 benchmark.sh,发 2500 次相同请求(单轮对话,max_tokens 50),并发 10:

bash 复制代码
CONCURRENCY=10 REQUESTS=2500 bash scripts/benchmark.sh

脚本的压测逻辑不复杂,核心是用后台进程控制并发:每发一个请求就放到后台,当在跑的请求数达到 CONCURRENCY 上限时就等一个完成再发下一个,始终维持固定并发。每个请求用 curl -w '%{http_code} %{time_total}' 记录状态码和耗时,最后用 Python 算出 P50/P95/P99。它不依赖任何压测框架,一个 bash 脚本就能拿到可信的延迟分布,方便在任何机器上复现。

压测结果

复制代码
总请求: 2500
成功: 2500
失败: 0
总耗时: 156730 ms
吞吐量: 15.95 req/s
平均延迟: 0.576 s
P50延迟: 0.574 s
P95延迟: 0.593 s
P99延迟: 0.609 s

怎么解读这些数据

零失败:2500 个请求全部 HTTP 200,vLLM 在持续负载下没有崩溃、超时或拒绝。

P50 和 P99 只差 35ms(0.574 vs 0.609):延迟分布极其集中,几乎没有长尾。这正是 vLLM Continuous Batching 的价值:它把多个请求合并到一个 GPU batch 里一起算,每个请求的延迟不会因为"排在别人后面"而差很多。

吞吐 15.95 req/s:对 T4 + 1.5B 模型来说合理。如果换成更大的模型(7B/14B),吞吐会下降,延迟会上升。

压测期间的 GPU 状态

压测过程中用 nvidia-smi 抓到的实时数据:

时间点 GPU-Util 功耗 温度 显存
压测前 0% 14W 32C 0 用
压测中(峰值) 92% 70W(满额) 50C 13409 MiB
压测后 0% 27W 40C 13409 MiB(常驻)

注意:压测结束后 GPU-Util 回到 0%,但显存不释放(13409 MiB),因为 vLLM 的模型权重和 KV Cache 预留是常驻的。这是正常行为,不是内存泄漏。

Grafana 对比(配合实验02)

在实验02的 Grafana 面板上,压测前后的对比截图清晰记录了 GPU 从空闲到满载再回落的完整过程。利用率从 0% 跳到 92%,功耗从 14W 顶到 71W(T4 TDP 上限),温度升了 19 度。详细对比图见实验02。


六、升级与回滚

配套脚本包含了 Recreate 策略下的升级回滚验证:

bash 复制代码
bash scripts/upgrade-rollback.sh

因为策略是 Recreate,升级时会:

  1. 杀掉旧 Pod
  2. 启动新 Pod(重新加载模型)
  3. 中间有约1-2分钟的中断

这是单 GPU 环境的固有代价。生产环境如果有多张 GPU,可以改用 RollingUpdate,新旧 Pod 各占一张卡,实现无中断升级。


七、踩过的坑小结

现象 处理
T4 回退 V0 引擎 启动日志 WARNING: Compute Capability < 8.0 T4 是 Turing(7.5),V1 不支持,自动回退 V0,正常
FlashAttention-2 不可用 Cannot use FlashAttention-2 for Turing 改用 XFormers 后端,不影响正确性
ContainerCreating 很久 Pod 卡在 ContainerCreating 4分钟 镜像大,正常;如果更久检查网络和镜像源
显存压测后不释放 压测结束显存仍 13.4GB vLLM 模型+KV Cache 常驻,正常行为

小结

这一篇完成了从"裸机跑 vLLM"到"K8s 里用 Helm 管理 vLLM"的跨越。Helm Chart 让部署可重复、可配置、可回滚;压测数据让你对 T4 上的 vLLM 性能有了真实的数字感;监控联动让你能看到推理负载下 GPU 的真实状态。

到这里,GPU 基础(01)、监控(02)、推理服务(05)三篇都跑通了。剩下的实验07(KServe)会在下次 GPU 实例上完成,届时你将看到 Serverless 推理、自动扩缩容和灰度发布的真实验证。


参考链接

相关推荐
张--小涛涛43 分钟前
0004 - 机器学习-K近邻算法
人工智能·机器学习·近邻算法
ellenwan202644 分钟前
量化实现的难点,常在规则和流程
人工智能·python
Lethehong1 小时前
ToDesk远程终端怎么用:手机免进桌面排查 Windows,主机名/账号/ipconfig/进程一篇讲透
人工智能
天辛大师1 小时前
天心大师谈重新学会传统文化,AI时代的心经奥义
大数据·人工智能·随机森林·重构·启发式算法
新知图书1 小时前
第 12 章 案例实战:多模态客服智能体
人工智能·语音识别
吴佳浩1 小时前
企业如何把通用大模型训练成行业模型:Continued Pre-Training实战指南
人工智能·神经网络·llm
luckystar513~1 小时前
Hermes 实战 :多渠道接入——日报推送到飞书/Telegram
人工智能·agent·智能体开发·hermes实战·智能体网关
Asum1ta1 小时前
生产环境 Kubernetes 高可用集群部署实战:外部 etcd + Keepalived + HAProxy + 多 Master 架构
架构·kubernetes·etcd
clorinda1 小时前
从零理解 PyTorch:用神经网络完成 MNIST 手写数字识别
人工智能·pytorch·神经网络