部署与 DevOps


文档摘要

部署与 DevOps 部署,是让你的模型从研究产物变成产品的过程。本文件覆盖面向 ML 的 Docker、模型服务、实验追踪、可复现性、生产监控、特征存储和管道编排——这些基础设施把一个训练好的模型从 notebook 送到百万用户手里。 一个只在你笔记本上能跑的模型是个原型。一个能大规模稳定运行、用毫秒级延迟给出预测、能从故障中恢复、还能在不停机的情况下更新的模型,才是一个产品。两者之间的鸿沟,就是部署与 DevOps(deployment and DevOps)。 大多数 ML 工程师花在部署、监控和调试生产问题上的时间,比花在训练模型上的还多。对任何构建真实 ML 系统的人来说,理解这套基础设施都不是可选项。

部署与 DevOps

部署,是让你的模型从研究产物变成产品的过程。本文件覆盖面向 ML 的 Docker、模型服务、实验追踪、可复现性、生产监控、特征存储和管道编排——这些基础设施把一个训练好的模型从 notebook 送到百万用户手里。

  • 一个只在你笔记本上能跑的模型是个原型。一个能大规模稳定运行、用毫秒级延迟给出预测、能从故障中恢复、还能在不停机的情况下更新的模型,才是一个产品。两者之间的鸿沟,就是部署与 DevOps(deployment and DevOps)

  • 大多数 ML 工程师花在部署、监控和调试生产问题上的时间,比花在训练模型上的还多。对任何构建真实 ML 系统的人来说,理解这套基础设施都不是可选项。

面向 ML 的 Docker

  • 我们在第 13 章(操作系统)里从概念上介绍过容器。这里我们关注实践:为 ML 负载写 Dockerfile。

  • Dockerfile 是构建容器镜像(container image)的配方:

# 从官方 CUDA 基础镜像开始 FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04 # 系统依赖 RUN apt-get update && apt-get install -y \ python3.11 python3-pip git \ && rm -rf /var/lib/apt/lists/* # Python 依赖(单独安装以便利用缓存) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制源代码(频繁变化,所以这一层放在最后) COPY src/ /app/src/ COPY configs/ /app/configs/ WORKDIR /app # 入口 CMD ["python3", "src/scripts/serve.py", "--config", "configs/serve.yaml"]
  • 层缓存(layer caching):Docker 缓存每一层。如果 requirements.txt 没变,重新构建时 pip install 就会被跳过。把很少变化的层(系统包、pip install)放在频繁变化的层(源代码)之前。这能把一次 10 分钟的构建变成一次 10 秒的重建。

  • GPU 访问:用 nvidia/cuda 基础镜像,并用 docker run --gpus all 运行。nvidia-container-toolkit 提供从宿主机到容器的 GPU 直通(passthrough)。

  • **多阶段构建(multi-stage builds)**通过把构建环境和运行时分开来缩小镜像体积:

# 构建阶段:安装构建工具,编译依赖 FROM python:3.11 AS builder COPY requirements.txt . RUN pip install --user -r requirements.txt # 运行时阶段:只保留运行时依赖 FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04 COPY --from=builder /root/.local /root/.local COPY src/ /app/src/ ENV PATH=/root/.local/bin:$PATH
  • 最终镜像里只有运行时库,没有编译器、头文件或构建工具。一个 5 GB 的构建镜像可以变成一个 2 GB 的运行时镜像。

  • Docker Compose 运行多容器组合(模型服务器 + 负载均衡器 + 监控):

# docker-compose.yml services: model: build: . ports: - "8080:8080" deploy: resources: reservations: devices: - capabilities: [gpu] prometheus: image: prom/prometheus ports: - "9090:9090"

模型服务

  • **模型服务(model serving)**是把推理作为一项服务来运行:接收请求、运行模型、返回预测。

  • FastAPI(在第 03 节讲过)是中低吞吐场景下最简单的方案。对高吞吐和 GPU 优化的服务,要用专门的工具:

  • Triton Inference Server(NVIDIA 出品):以 TensorRT、ONNX、PyTorch 和 TensorFlow 格式服务模型。特性包括:

    • 动态批处理(dynamic batching):把单个请求收集起来打包,以提高 GPU 效率。一连串单个请求被打成 32 个一组的 batch,能极大提升吞吐。
    • 模型集成(model ensembles):在一次请求里把多个模型链起来(预处理器 → 模型 → 后处理器)。
    • 多模型服务(multi-model serving):在同一个 GPU 上服务多个模型,共享资源。
    • 并发模型执行(concurrent model execution):在同一个 GPU 上并行跑多个推理请求。
  • TorchServe(PyTorch 出品):用 REST/gRPC API 服务 PyTorch 模型。支持模型版本管理、A/B 测试和自定义 handler。

  • vLLM:专为 LLM 服务而生。实现了 PagedAttention(高效的 KV cache 管理)、连续批处理(continuous batching),以及跨 GPU 的张量并行(tensor parallelism)。对大语言模型,能比天真的服务方式高出 10-20 倍吞吐。

  • Cactus(项目地址 github.com/cactus-compute/cactus):一个面向移动和边缘端设备端服务(on-device serving)的低延迟 AI 引擎。Cactus 提供一个 OpenAI 兼容的 API(聊天补全、流式输出、工具调用、转录、嵌入、RAG、视觉),完全在设备端运行;当本地模型无法处理某个请求时,会自动回退到云(cloud fallback)。这种混合架构意味着你的应用代码无论推理是在本地跑还是在云端跑,用的都是同一套 API——引擎会根据模型置信度和设备能力来决定。SDK 覆盖 Python、Swift、Kotlin、Flutter、React Native 和 Rust,并在 HuggingFace 上提供预先转换好的模型权重。它支持多模态推理(LLM、视觉、语音),用自定义的 ARM SIMD kernel 在 ARM CPU 上做到最快推理,并用零拷贝内存映射把内存占用降低 10 倍(第 16 章、第 17 章)。

  • 模型格式优化

    • ONNX:用于互操作的开源格式。从 PyTorch/TensorFlow 导出,到处都能跑。
    • TensorRT:NVIDIA 的优化器。融合层、选择最优 kernel、量化权重。在 NVIDIA GPU 上通常比 PyTorch 快 2-5 倍。
    • GGUF/GGML:面向 CPU 高效推理的格式,在消费级硬件上跑 LLM 时很流行。

实验追踪

  • 没有实验追踪,ML 研究就会退化成:"我觉得上周二那个、我好像改了点什么的配置的那个模型是最好的,但我不记得我改了什么。"

  • Weights & Biases(W&B):最受欢迎的实验追踪器。从你的训练脚本里记录任何东西:

import wandb wandb.init(project="my-project", config={ "model": "transformer", "lr": 3e-4, "batch_size": 64, }) for epoch in range(num_epochs): train_loss = train_one_epoch() val_loss = validate() wandb.log({ "train/loss": train_loss, "val/loss": val_loss, "epoch": epoch, }) # 把模型作为 artifact 记录 if val_loss < best_loss: wandb.save("best_model.pt") wandb.finish()
  • W&B 提供:用来比较多次运行的看板、超参数 sweep 工具、模型注册表、数据集版本管理,以及团队协作。

  • MLflow:开源的替代品。可以在本地或服务器上运行:

import mlflow mlflow.set_experiment("my-experiment") with mlflow.start_run(): mlflow.log_params({"lr": 3e-4, "batch_size": 64}) mlflow.log_metric("val_loss", 0.042, step=epoch) mlflow.pytorch.log_model(model, "model")
  • 模型注册表(model registry):一个带版本管理、分级(dev → staging → production)和元数据的已训练模型中央仓库。W&B 和 MLflow 都提供注册表。注册表回答的是:"当前生产里跑的是哪个模型、是谁训练的、它的验证准确率是多少、是哪份代码/数据产出的?"

可复现性

  • 可复现性意味着:给定相同的代码、数据和配置,产出相同的模型。这在 ML 里出奇地难,原因是 GPU 运算、数据 shuffle 和浮点累加中的非确定性。

  • 可复现性清单

内容 方式
代码版本 Git 提交哈希
配置 / 超参数 配置文件(在 git 里版本化,或记录到 W&B)
随机种子 设置并记录所有种子(Python、NumPy、PyTorch、CUDA)
数据版本 DVC 哈希、数据集版本标签,或 S3 对象版本
依赖 pip freeze、Docker 镜像哈希,或 lockfile
硬件 GPU 型号、GPU 数量、CUDA 版本
非确定性 torch.backends.cudnn.deterministic = True(更慢但可复现)
  • 锁定一切pip install torch==2.2.1 而不是 torch>=2.0。一个小版本升级就可能改变数值行为、优化器实现或默认超参数。

  • 用 Docker 保证可复现性:Docker 镜像锁定了操作系统、系统库、Python 版本和 pip 包。镜像哈希是一份完整的环境指纹。如果你能复现这个 Docker 镜像,你就能复现这次训练。

生产监控

  • 部署一个模型不是终点——它是一系列新问题的起点。模型会随时间退化,因为真实世界在变(概念漂移 concept drift),也因为输入数据分布发生了偏移(数据漂移 data drift)。

  • 要监控什么

    • 延迟(latency):推理要花多久?追踪 p50(中位数)、p95 和 p99。p99 是 500ms 意味着每 100 个用户里有 1 个要等半秒,这可能无法接受。

    • 吞吐(throughput):每秒能处理多少请求?系统跟得上需求吗?

    • 错误率(error rate):有多大比例的请求失败(异常、超时、非法输入)?

    • 模型指标:在留出集上的准确率、精确率、召回率。如果生产中有标注数据(比如用户纠错),就追踪在线指标。

    • 数据漂移:流入数据的分布变了吗?一个用白天照片训练的模型可能在夜间照片上失效。统计检验(KS 检验、PSI)把训练分布和实时分布做比较。

    • 特征漂移:单个特征的分布变了吗?一个在训练时服从正态分布、现在却变成双峰的特征,往往意味着数据管道出了问题。

  • 工具

    • Prometheus + Grafana:基础设施监控的事实标准。Prometheus 采集指标,Grafana 在带告警的看板里把它们可视化。
    • Evidently AI:开源的 ML 监控。生成关于数据漂移、模型性能和数据质量的报告。
  • 告警:不要只做看板——要配自动告警。"如果 p99 延迟连续 5 分钟超过 200ms,就发一条 Slack 通知。""如果数据漂移分数超过阈值,就呼叫值班工程师。"

特征存储

  • **特征存储(feature store)**是一个集中式的预计算特征仓库,在训练和服务之间共享。它解决两个问题:

    • 训练-服务偏差(training-serving skew):训练时用的特征必须和服务时用的完全一致。如果训练用一种方式计算 user_age_at_signup,而服务时用另一种方式计算,模型的预测就会悄悄出错。

    • 特征复用:多个模型经常用相同的特征(用户画像、物品嵌入、聚合统计)。算一次再共享,能避免重复和不一致。

  • Feast 是最受欢迎的开源特征存储。它管理在线特征(低延迟,从 Redis 或 DynamoDB 服务)和离线特征(批量,存在数据仓库里用于训练)。

  • 特征存储对推荐系统、欺诈检测,以及任何特征要从原始数据管道计算出来的应用,都至关重要。

管道编排

  • 一个生产级 ML 系统不只是一个模型。它是一条管道(pipeline):数据摄入 → 预处理 → 特征计算 → 训练 → 评估 → 部署 → 监控。每一步都依赖上一步,都可能独立失败,还可能要按不同的时间表运行。

  • **编排器(orchestrator)**管理这些管道:

  • Apache Airflow:数据管道编排的事实标准。用有向无环图(DAG,Directed Acyclic Graph)定义任务依赖。每个任务独立运行、失败可重试,并通过一个 Web UI 监控。

# airflow DAG 示例(简化版) from airflow import DAG from airflow.operators.python import PythonOperator dag = DAG("training_pipeline", schedule="@daily") preprocess = PythonOperator(task_id="preprocess", python_callable=preprocess_data, dag=dag) train = PythonOperator(task_id="train", python_callable=train_model, dag=dag) evaluate = PythonOperator(task_id="evaluate", python_callable=evaluate_model, dag=dag) deploy = PythonOperator(task_id="deploy", python_callable=deploy_model, dag=dag) preprocess >> train >> evaluate >> deploy
  • Kubeflow Pipelines:在 Kubernetes 上的 ML 专用编排。每一步跑在一个容器里,GPU 资源按需分配,实验被自动追踪。

  • PrefectDagster:Airflow 的现代替代品,有更好的开发者体验、原生 Python API,以及内置的数据血缘(data lineage)。

  • 什么时候该编排:当你的管道超过 2-3 步、按时间表运行、涉及多个团队或服务,或者需要从故障中自动恢复时。一个单脚本的训练任务不需要编排器。而一条每天从 5 个数据源摄入数据、训练 3 个模型、做评估、再部署其中最好的一个的再训练管道,绝对需要。


发布者: 作者: HenryNdubuaku 转发
评论区 (0)
U