Chapter 10

实战:Llama-3 70B 生产部署

前九章的知识端到端串起来——用 Llama-3-70B-Instruct-AWQ 起一个能扛 200 QPS、p95 < 2s 的聊天服务。硬件、K8s、HPA、灰度升级、告警,一步一步走完。

业务目标

硬件选型

用前面学的容量公式反推:

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.24xlarge8×A100-40G~$28k × 2 机 = $56k贵但稳
GCP a2-ultragpu-8g8×A100-80G~$24k × 2 机 = $48k跨区便宜
Azure ND A100 v48×A100-80G~$25k × 2 机 = $50k企业折扣好谈
国内 GPU 云4×A100-80G × 4 台~¥60k / $8.3k按量 / 包月可谈到更低
自建 IDC4×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
生产推荐 ②:滚动升级换镜像只更新代码层,模型不变就不用重拉

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 秒。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 副本滚动升级要做得稳:

  1. maxSurge: 1, maxUnavailable: 0:永远先起新的再下老的,容量不降
  2. readinessProbe 生效后才接流量,确保模型加载完
  3. PodDisruptionBudget 保底 minAvailable: 3,防止 node 维护把 3 个副本同时赶走
  4. 新版本先灰度:把 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 mean180 ms< 500 ✓
TTFT p95420 ms< 500 ✓
TPOT mean14 ms-
TPOT p9522 ms-
端到端 p951.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 版本不匹配。

本章小结

全书完结
走到这里,你已经掌握 vLLM 从 PagedAttention 原理、Continuous Batching、量化、投机解码、LoRA 多租户、张量并行、观测调优,到生产部署的完整链路。把这些组合起来,就能把一张 A100 的算力榨到 OpenAI 级服务的级别——古法编程的极简、极致精神,正是如此