1. 部署前的生产就绪检查清单

“可部署”不等于“已部署”——前者是通过所有自动化校验的制品状态,后者是在真实流量下持续稳定运行的服务实例。二者之间横亘着模型一致性、代码鲁棒性、依赖确定性与配置安全性的四重鸿沟。跳过任一环节,都可能在凌晨三点收到 P99 延迟飙升的告警。

✅ 模型验证:PyTorch → ONNX 推理一致性比对

模型转换后必须验证数值等价性。以下为完整校验流程(含断言):

import torch
import onnx
import onnxruntime as ort
from torch.testing import assert_close

# 1. 构建示例模型与输入
model = torch.hub.load('pytorch/vision:v0.15.0', 'resnet18', pretrained=True).eval()
x = torch.randn(1, 3, 224, 224)

# 2. 导出 ONNX(关键:指定 dynamic_axes 支持变长 batch)
onnx_path = "resnet18.onnx"
torch.onnx.export(
    model, x,
    onnx_path,
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}},
    opset_version=17
)

# 3. 加载并推理 ONNX
ort_session = ort.InferenceSession(onnx_path, providers=['CPUExecutionProvider'])
ort_out = ort_session.run(None, {"input": x.numpy()})[0]

# 4. PyTorch 原生推理
with torch.no_grad():
    pt_out = model(x).numpy()

# 5. 断言严格一致性(容忍 1e-5 数值误差)
assert_close(
    torch.from_numpy(ort_out),
    torch.from_numpy(pt_out),
    atol=1e-5, rtol=1e-5,
    msg="ONNX output deviates from PyTorch beyond tolerance!"
)

⚠️ 常见问题torch.load("model.pt") 在 CPU 环境加载 GPU 训练模型会报 RuntimeError: Attempting to deserialize object on a CUDA device。修复方案:显式指定 map_location

model = torch.load("model.pt", map_location=torch.device("cpu"))  # 生产环境默认 CPU 加载
# 或更健壮写法:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = torch.load("model.pt", map_location=device)

✅ 代码加固与依赖锁定

  • 日志结构化:用 structlog 替代 print(),支持 JSON 输出与 level 分级:
    import structlog
    structlog.configure(
        processors=[
            structlog.stdlib.filter_by_level,
            structlog.stdlib.add_logger_name,
            structlog.stdlib.add_log_level,
            structlog.stdlib.PositionalArgumentsFormatter(),
            structlog.processors.TimeStamper(fmt="iso"),
            structlog.processors.JSONRenderer()
        ],
        logger_factory=structlog.stdlib.LoggerFactory()
    )
    log = structlog.get_logger()
    log.info("model_loaded", version="v1.4.2", device="cuda:0")
    
  • 依赖锁定
    pip-compile requirements.in --output-file=requirements.txt --upgrade
    pip check  # 必须返回空,否则存在版本冲突
    

模型验证与依赖检查流程

2. 容器化部署:从本地模型到 Docker 镜像

容器是生产环境的“最小可信单元”。我们拒绝 FROM ubuntu:22.04 && apt install python3-pip 这类高风险基础镜像。

🐳 多阶段构建 Dockerfile(GPU 版本)

# 构建阶段:编译依赖、打包模型
FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 AS builder
RUN apt-get update && apt-get install -y python3-pip && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt

# 运行阶段:极简镜像
FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
COPY model-store/ /home/model-server/model-store/
COPY config.properties /home/model-server/config.properties
EXPOSE 8080 8081
CMD ["torchserve", "--start", "--model-store", "/home/model-server/model-store", "--ts-config", "/home/model-server/config.properties"]

📦 TorchServe 模型服务封装要点

  • 创建模型归档包(.mar):
    torch-model-archiver \
      --model-name resnet18 \
      --version 1.0 \
      --model-file model.py \
      --serialized-file model.pt \
      --handler image_classifier \
      --extra-files index_to_name.json \
      --export-path model-store
    
  • config.properties 关键参数:
    inference_address=http://0.0.0.0:8080
    management_address=http://0.0.0.0:8081
    number_of_netty_threads=8
    enable_envvars_config=true  # 允许通过 ENV 覆盖配置
    

⚠️ CUDA 兼容性速查:宿主机 nvidia-smi 显示驱动版本 ≥ 容器内 nvidia-container-toolkit 所需最低驱动。例如 nvidia/cuda:12.1.1-runtime 要求驱动 ≥ 530.30.02。执行 docker run --rm --gpus all nvidia/cuda:12.1.1-runtime-ubuntu22.04 nvidia-smi -q | grep "Driver Version" 可验证。

3. 生产环境 API 网关与流量治理

直接暴露 TorchServe 端口是反模式。网关是流量的“交通警察”。

🌐 Nginx 限流 + 熔断配置

http {
  limit_req_zone $binary_remote_addr zone=api:10m rate=50r/s;

  upstream torchserve {
    server 127.0.0.1:8080;
  }

  server {
    location /predictions/ {
      limit_req zone=api burst=100 nodelay;
      proxy_pass http://torchserve;
      proxy_next_upstream error timeout http_500;
      client_max_body_size 100M;  # 同步调整!解决 413 错误
    }
  }
}

🎛️ Kong 网关注册服务(Admin API)

# 注册服务
curl -X POST http://kong:8001/services \
  --data name=resnet-service \
  --data url="http://torchserve:8080"

# 绑定路由
curl -X POST http://kong:8001/services/resnet-service/routes \
  --data paths[]="/v1/predict"

# 启用插件链
curl -X POST http://kong:8001/plugins \
  --data name=rate-limiting \
  --data config.minute=3000 \
  --data service.id=$(curl -s http://kong:8001/services/resnet-service | jq -r .id)

curl -X POST http://kong:8001/plugins \
  --data name=request-transformer \
  --data config.add.headers="X-Trace-ID:${uuid()}" \
  --data service.id=$(curl -s http://kong:8001/services/resnet-service | jq -r .id)

4. 性能调优:从 P99 延迟到吞吐量压测

优化必须基于数据。我们用 locust 模拟真实请求流:

# locustfile.py
from locust import HttpUser, task, between
import json

class TorchServeUser(HttpUser):
    wait_time = between(0.1, 0.5)
    
    @task
    def predict(self):
        with open("test.jpg", "rb") as f:
            self.client.post(
                "/predictions/resnet18",
                files={"data": f},
                timeout=30
            )

⚡ 关键调优项实测效果

组件参数效果
TorchServebatch_size=4, max_batch_delay=5000P99 ↓ 37%,吞吐 ↑ 2.1x
PyTorchtorch.backends.cudnn.benchmark = True输入尺寸固定时延迟 ↓ 22%
ONNX Runtimeproviders=['CUDAExecutionProvider']GPU 利用率从 45% → 89%

⚠️ 注意cudnn.benchmark=True 仅适用于输入 shape 高度稳定的场景(如固定分辨率图像)。若输入尺寸动态变化(如多尺度检测),反而因反复搜寻最优 kernel 导致性能下降。

TorchServe 性能监控仪表盘(Prometheus + Grafana)

5. AI 协作闭环:监控告警 + 模型漂移检测 + 自动重训

MLOps 不是独立系统,而是 DevOps 的 AI 扩展。

📉 Evidently 漂移检测(定时任务)

from evidently.report import Report
from evidently.metrics import DataDriftMetrics, ClassificationPerformanceMetrics
from datetime import datetime

report = Report(metrics=[
    DataDriftMetrics(),
    ClassificationPerformanceMetrics()
])
report.run(reference_data=ref_df, current_data=prod_df)
report.save_html(f"drift_report_{datetime.now().strftime('%Y%m%d')}.html")

🔄 GitHub Actions 自动重训流水线(节选)

- name: Check drift score
  id: drift
  run: |
    SCORE=$(python -c "import json; print(json.load(open('drift.json'))['metrics'][0]['result']['dataset_drift'])")
    echo "DRIFT_SCORE=$SCORE" >> $GITHUB_ENV
    echo "score=${SCORE}" >> $GITHUB_OUTPUT

- name: Trigger retrain if drift > 0.3
  if: ${{ env.DRIFT_SCORE > 0.3 }}
  run: |
    python train.py
    docker build -t acme/model:latest .
    docker push acme/model:latest
    kubectl rollout restart deployment/torchserve

⚠️ 关键细节:Evidently 的 ColumnMapping 必须显式声明特征列,避免使用 pandas.describe() 的隐式推断——二者统计口径不一致会导致漂移误报。

6. 故障复盘与 SRE 实践:一份真实的线上事故报告

📜 事故时间线(2023-10-17)

  • 22:15:发布 acme/model:v1.5.0(基于 PyTorch 2.1 + CUDA 12.1)
  • 22:18:P99 延迟从 200ms 飙升至 2800ms,GPU 利用率跌至 5%
  • 22:20:nvidia-smi dmon -s u 显示 gpu_util 持续 < 10%,证实 kernel 未被调度

🔍 根因定位

torch.nn.functional.interpolate 在 CUDA 12.1 中触发了已知 kernel regression(PyTorch Issue #102891)。降级至 CUDA 11.8 后恢复。

💡 经验总结

  • 硬件栈一致性:Staging 环境必须复现 Prod 的 GPU 型号、驱动版本、CUDA minor 版本;
  • 版本矩阵文档化:建立 model-framework-driver-compat.md 表格,每次升级前交叉验证;
  • 禁止动态安装:CI 流程中禁用 pip install nvidia-cublas-cu12 等未锁定 CUDA 扩展包。

SRE 事故复盘会议纪要模板

行动清单

  • nvidia-smi -q 兼容性检查加入 CI
  • 所有 .mar 包嵌入 model-versioncuda-version 标签
  • 在 Prometheus 中新增 ts_model_cuda_version 指标,实现版本漂移告警

部署不是终点,而是可观测、可治理、可进化的起点。当你的模型能自动感知数据漂移、触发重训、灰度发布并回滚——你才真正拥有了生产级 AI 能力。