第3章 模型打包与容器化

模型训练完只是第一步,真正让它跑起来、对外提供服务,还得靠容器化。我见过太多团队在模型交付时手忙脚乱——环境不一致、依赖冲突、镜像几个G大……说白了,容器化就是给模型穿上“标准工装”,到哪都能跑。

模型容器化 Docker 打包 模型服务化 镜像优化技巧 多阶段构建 · 依赖固化 Flask / FastAPI / Triton 层压缩 · 基础镜像选型

🐳 用 Docker 打包模型——别让环境坑了你

我刚开始做模型部署时,最头疼的就是“在我机器上能跑啊”。后来我学乖了,所有模型一律用 Docker 封装。你想想看,训练时用的 Python 3.8,生产环境却是 3.10,一个语法糖就能让你服务挂掉。

一个典型的模型 Dockerfile 长这样:

# 多阶段构建:先装依赖,再拷贝模型
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY model/ ./model/
COPY serve.py .
EXPOSE 8080
CMD ["python", "serve.py"]
💡 个人习惯:我一般把模型权重单独放在一个卷里,而不是打进镜像。这样更新模型不用重新 build 镜像,省时间。

⚡ 模型服务化:Flask / FastAPI / Triton 怎么选?

模型打包好之后,得给它配个“嘴巴”对外说话。这里我聊三个主流方案,每个我都踩过坑。

框架适用场景个人评价
Flask快速原型、简单推理轻量但性能一般,适合内部工具
FastAPI高并发、异步推理我最近的主力,自动文档太香了
Triton多模型、GPU 优化、生产级配置稍重,但吞吐量是真的猛

拿 FastAPI 举个例子,我经常这样写推理接口:

from fastapi import FastAPI
from pydantic import BaseModel
import joblib

app = FastAPI()
model = joblib.load("/app/model/model.pkl")

class InputData(BaseModel):
    features: list[float]

@app.post("/predict")
async def predict(data: InputData):
    pred = model.predict([data.features])
    return {"prediction": pred.tolist()}
⚠️ 我曾经踩过的坑:用 Flask 时忘了加 threading 锁,结果高并发下模型 predict 出了乱数据。换成 FastAPI 后,异步 + 进程池就稳多了。

🔧 镜像优化技巧——把镜像从 2G 瘦到 300M

镜像太大,拉取慢、启动慢、存储贵。我接手过一个项目,镜像 2.3GB,每次部署等 5 分钟。后来我做了三件事:

优化后的 Dockerfile 示例:

FROM python:3.9-slim AS base
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 && rm -rf /var/lib/apt/lists/*

FROM base AS builder
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM base
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY model/ ./model/
COPY serve.py .
CMD ["uvicorn", "serve:app", "--host", "0.0.0.0", "--port", "8080"]

📦 镜像层数不是越少越好——我习惯把变化频率不同的内容分开放。比如模型权重单独一层,依赖一层,代码一层。这样改代码时不用重新下载依赖。

🧪 Triton 容器化——生产级推理的正确姿势

如果你要部署多个模型,或者要用 GPU 做高性能推理,Triton 是绕不开的。我去年帮一个客户把三个模型塞进一个 Triton 容器,吞吐量翻了 4 倍。

它的容器化其实很标准,但要注意模型仓库的目录结构:

model_repository/
├── model_a/
│   ├── 1/
│   │   └── model.onnx
│   └── config.pbtxt
├── model_b/
│   ├── 1/
│   │   └── model.pt
│   └── config.pbtxt

然后一行命令启动:

docker run --gpus all -p 8000:8000 -p 8001:8001 \
  -v /path/to/model_repository:/models \
  nvcr.io/nvidia/tritonserver:23.12-py3 \
  tritonserver --model-repository=/models
🔁 我的避坑指南:记得给 Triton 容器设置 --shm-size=1g,否则并发高时共享内存会爆。我曾经因为这个排查了一整天……

📊 镜像优化前后对比

指标优化前优化后
镜像大小2.1 GB312 MB
构建时间4 分 20 秒1 分 05 秒
容器启动时间12 秒3 秒
首次推理延迟8.5 秒1.2 秒

你看,优化不是玄学,每一步都有实实在在的收益。我个人习惯把优化清单写进 CI 里,每次构建自动检查镜像大小,超过 500MB 就报警。

🎯 核心总结:模型打包与容器化,说白了就是三件事——标准化环境、选对服务框架、把镜像做小做快。别小看这一步,它决定了你后续流水线是丝滑还是卡顿。