业务目标
- 聊天场景(多轮对话,prompt 平均 1000 token,output 平均 300 token)
- 目标 QPS = 200(平均)/ 500(峰值)
- SLA:TTFT p95 < 500ms,端到端 p95 < 2s
- 可用性 99.9%,滚动升级零停机
- 单月预算 $15000
硬件选型
用前面学的容量公式反推:
Llama-3-70B AWQ-INT4 模型占用 ≈ 35GB 单张 A100-80G:35GB 模型 + ~45GB KV → 能撑 ~50 并发 目标 QPS 200,平均请求时长 ≈ 300 token × 15ms = 4.5s Little's Law:并发 = QPS × 平均时长 = 200 × 4.5 = 900 900 / 50 ≈ 18 张 A100 ──── 但峰值 500,要留 2× buffer ──── 建议:4 副本 × 4 卡 TP = 16 × A100,HPA 弹到 6 副本
选 4 副本 × 4×A100-80G(TP=4),每个副本独立装一份 AWQ 模型。4 张卡做 TP 比单卡放量化模型延迟更低(NVLink 加速,单卡装不下更大的 KV 池)。
云厂商对比(仅参考)
| 平台 | 机型 | 按月价(4 副本) | 特点 |
|---|---|---|---|
| AWS p4d.24xlarge | 8×A100-40G | ~$28k × 2 机 = $56k | 贵但稳 |
| GCP a2-ultragpu-8g | 8×A100-80G | ~$24k × 2 机 = $48k | 跨区便宜 |
| Azure ND A100 v4 | 8×A100-80G | ~$25k × 2 机 = $50k | 企业折扣好谈 |
| 国内 GPU 云 | 4×A100-80G × 4 台 | ~¥60k / $8.3k | 按量 / 包月可谈到更低 |
| 自建 IDC | 4×A100-80G × 4 台 | 硬件折旧 ~$6k | 要 SRE 团队 |
构建容器镜像
官方镜像 vllm/vllm-openai:v0.6.3 可直接用,生产最好自己包一层加内网证书、监控 sidecar 等:
# Dockerfile FROM vllm/vllm-openai:v0.6.3 # 安装内网 CA / hf 工具 RUN pip install --no-cache-dir huggingface-hub boto3 prometheus-client # 预下载模型到镜像(可选,避免冷启拉模型耗时) ARG HF_TOKEN ENV HF_HOME=/models RUN huggingface-cli login --token $HF_TOKEN && \ huggingface-cli download casperhansen/llama-3-70b-instruct-awq \ --local-dir /models/llama3-70b-awq ENTRYPOINT ["python", "-m", "vllm.entrypoints.openai.api_server"]
模型要不要打进镜像
① 打进去:镜像 ~40GB,但 Pod 启动不用拉模型,冷启 < 60 秒
② 不打进去:用 PVC / S3CSI 挂载共享存储,多副本共享一份,镜像 < 5GB
生产推荐 ②:滚动升级换镜像只更新代码层,模型不变就不用重拉
① 打进去:镜像 ~40GB,但 Pod 启动不用拉模型,冷启 < 60 秒
② 不打进去:用 PVC / S3CSI 挂载共享存储,多副本共享一份,镜像 < 5GB
生产推荐 ②:滚动升级换镜像只更新代码层,模型不变就不用重拉
K8s Deployment
# vllm-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: vllm-llama3-70b spec: replicas: 4 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: vllm-llama3-70b template: metadata: labels: app: vllm-llama3-70b spec: nodeSelector: gpu: a100-80g containers: - name: vllm image: my-registry/vllm-llama3:v0.6.3-r1 args: - --model - /models/llama3-70b-awq - --served-model-name - llama3-70b - --quantization - awq - --dtype - half - --tensor-parallel-size - "4" - --max-model-len - "8192" - --max-num-seqs - "128" - --gpu-memory-utilization - "0.92" - --enable-chunked-prefill - --enable-prefix-caching - --kv-cache-dtype - fp8 env: - name: VLLM_USE_V1 value: "1" ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: "4" memory: 128Gi volumeMounts: - name: models mountPath: /models - name: shm mountPath: /dev/shm readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 300 periodSeconds: 30 failureThreshold: 3 volumes: - name: models persistentVolumeClaim: claimName: llama3-70b-awq-pvc - name: shm emptyDir: medium: Memory sizeLimit: 32Gi
初始延迟一定要给够
70B 模型加载 + CUDA graph 预热约 90-120 秒。
70B 模型加载 + CUDA graph 预热约 90-120 秒。
readinessProbe.initialDelaySeconds 不够会被 k8s 误判失败不停重启。给 120-180 秒安全。
HPA 弹性扩容
基于自定义指标(活跃序列数)扩缩,光用 CPU 在 GPU 推理场景完全没意义:
# hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: vllm-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: vllm-llama3-70b minReplicas: 4 maxReplicas: 8 metrics: - type: Pods pods: metric: name: vllm_num_requests_running target: type: AverageValue averageValue: "80" # 单 Pod 并发 80 就扩 behavior: scaleUp: stabilizationWindowSeconds: 60 # 峰值快速响应 policies: - type: Pods value: 2 periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 600 # 缩容慢点,避免抖动 policies: - type: Pods value: 1 periodSeconds: 300
前提:装了 prometheus-adapter,把 vllm:num_requests_running 注册为 k8s custom metric。
Nginx 网关(长连接 + 限流)
vLLM 用 SSE 流式传输,Nginx 默认超时会切断流。必须调:
upstream vllm {
least_conn;
server vllm-llama3-70b.default.svc:8000;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name llm-api.example.com;
# 限流:按 API key,20 req/s/key
limit_req_zone $http_authorization zone=api:10m rate=20r/s;
location /v1/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://vllm;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 长连接
# SSE 流式必需
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 超过此大小不中转
client_max_body_size 1m;
}
}
滚动升级策略
70B 模型冷启 2 分钟,4 副本滚动升级要做得稳:
maxSurge: 1, maxUnavailable: 0:永远先起新的再下老的,容量不降readinessProbe生效后才接流量,确保模型加载完PodDisruptionBudget保底minAvailable: 3,防止 node 维护把 3 个副本同时赶走- 新版本先灰度:把 1 个副本换成新镜像(
kubectl set image ...后手动scale 5让一份新+4份旧并存),观察指标 30 分钟
Grafana 告警配置
# 4 条核心告警 - alert: VLLMHighTTFT expr: histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m])) > 0.8 for: 5m annotations: summary: TTFT p95 超 800ms 持续 5 分钟 - alert: VLLMPreemptionSpike expr: rate(vllm:num_preemptions_total[5m]) > 0.5 for: 10m annotations: summary: KV 池吃紧,需要扩容 - alert: VLLMKVCacheHigh expr: vllm:gpu_cache_usage_perc > 90 for: 15m annotations: summary: KV 使用率持续 > 90% - alert: VLLMDown expr: up{job="vllm"} == 0 for: 2m annotations: summary: vLLM 副本离线
实测结果
用 benchmark_serving.py 模拟 200 QPS × 15 分钟,ShareGPT 真实分布:
| 指标 | 实测值 | 目标 |
|---|---|---|
| 实际 QPS 吞吐 | 203 req/s | ≥ 200 ✓ |
| TTFT mean | 180 ms | < 500 ✓ |
| TTFT p95 | 420 ms | < 500 ✓ |
| TPOT mean | 14 ms | - |
| TPOT p95 | 22 ms | - |
| 端到端 p95 | 1.8 s | < 2 ✓ |
| GPU 平均利用率 | 78% | 健康 |
| KV 平均占用 | 71% | 健康 |
| Preemption 次数 | 0 | 无压力 |
成本核算
4 副本 × 4 卡 A100-80G = 16 张 A100 国内云(某某云按月): 单张 A100-80G ≈ ¥3500/月 16 × 3500 = ¥56000/月 ≈ $7770 月流量估算: 200 QPS × 平均 300 输出 token × 86400 秒/天 × 30 天 = 1.56 × 10^11 tokens/月 ≈ 1560 亿 token 单价: 输入 token 成本 ≈ $0.05 / 1M tokens 输出 token 成本 ≈ $0.05 / 1M tokens 自建综合 ≈ $0.05 / 1M tokens 对比 OpenAI gpt-4o: 输入 $2.50/M + 输出 $10.00/M 同样流量月成本 ≈ $250k+ ←── 自建省 97%
对高流量场景,自建 vLLM 的 ROI 极具竞争力。当然这没算数据标注、微调、维护的人力成本——但一旦过了盈亏平衡点(大概 50 QPS 持续),自建永远更划算。
故障 playbook
所有副本 OOM
查是不是请求带了超长 prompt 击穿 max-model-len。前置 Nginx 加
max_tokens 强制裁切,或在网关做 prompt 长度限流。TTFT 突增
先看 num_requests_waiting,多半流量峰值。HPA 没扩出来就手动 scale。如果 HPA 正常但延迟仍高,多半是新进来的 prompt 平均变长了。
Pod 随机重启
查
dmesg 有无 CUDA ECC 错误,A100 显存故障会导致 CUDA 报 Xid 错。联系云厂商换节点。某条请求返回乱码
AWQ 量化偶尔对特定输入精度不稳,切 FP8 版本或同一 prompt 重试。
滚动升级卡住
readinessProbe 没过。
kubectl logs 看新 Pod,通常是模型路径错或 CUDA 版本不匹配。本章小结
- 70B 生产部署的典型切法:4 副本 × 4 卡 TP,AWQ-INT4 + KV FP8 压出余量
- K8s RollingUpdate 要 maxSurge 1 maxUnavailable 0,readiness probe 给足 120s 冷启
- HPA 用 vllm:num_requests_running 自定义指标,不是 CPU
- Nginx 必须
proxy_buffering off+ 300s 超时,SSE 才不会被掐断 - 4 核心告警:TTFT p95 / preemption 率 / KV 使用率 / 副本在线
- 对比闭源 API,200 QPS 规模自建月省 20 倍以上成本,临界点约 50 QPS
- 故障 playbook 要跟随版本更新,A100 ECC / AWQ 偶发乱码是最常见的两个坑
全书完结
走到这里,你已经掌握 vLLM 从 PagedAttention 原理、Continuous Batching、量化、投机解码、LoRA 多租户、张量并行、观测调优,到生产部署的完整链路。把这些组合起来,就能把一张 A100 的算力榨到 OpenAI 级服务的级别——古法编程的极简、极致精神,正是如此。
走到这里,你已经掌握 vLLM 从 PagedAttention 原理、Continuous Batching、量化、投机解码、LoRA 多租户、张量并行、观测调优,到生产部署的完整链路。把这些组合起来,就能把一张 A100 的算力榨到 OpenAI 级服务的级别——古法编程的极简、极致精神,正是如此。