01 初试模型的部署
目录
- 一、什么是“模型部署”?一句话 & 五种场景
- 二、为什么选 ONNX?核心概念 5 件事
- 三、PyTorch → ONNX 最稳姿势(含动态维度、常见坑)
- 四、TensorFlow/Keras → ONNX 两种方式(CLI & 代码)
- 五、导出后如何“验模”?等价性校验与可视化
- 六、进一步优化:Simplify、融合、量化、FP16
- 七、用 ONNX Runtime 推理(CPU/GPU/TensorRT)
- 八、上线服务:FastAPI + Uvicorn 模板(可直接用)
- 九、容器化与部署(含 GPU Dockerfile)
- 十、性能调优清单:吞吐、延迟、内存
- 十一、排坑大全:典型报错 & 解决
- 十二、上线工程要点:版本、监控、安全
- 十三、写作彩蛋:一键复用的文章骨架 & SEO 提示
一、什么是“模型部署”?一句话 & 五种场景
一句话:把“训练好的模型”变成“可被真实业务低延迟高可用地调用的系统”让你的代码走出舒适区,走出学术的象牙塔,到车间去,到户外去,到生产的一线去!
二、为什么选 ONNX?核心概念 5 件事
- 跨框架中间表示:一次导出(导一次刚刚好,煮啵说的是模型),多处运行(CPU/GPU/NPU),便于迁移。
- Opset:算子集版本号。不同导出器/后端支持范围不同。建议优先选较新的稳定 opset(如 17+),遇到不支持算子再降级或替换实现。
- 图结构:
Graph = Node + Tensor + Attribute。便于做常量折叠、算子融合。 - 静态 vs 动态形状:通过
dynamic_axes指定批大小/可变尺寸,提升泛化但可能弱化部分优化;静态形状则更易做极致优化。 - 生态:ONNX Runtime(ORT)、TensorRT、OpenVINO、CoreML、Web(ONNX Runtime Web)。
小工具:可视化用 Netron,简化用 onnx-simplifier(onnxsim)。传送门:Netron
三、PyTorch → ONNX 最稳姿势(含动态维度、常见坑)
3.1 基础导出(以 ResNet18 为例)
# pip install torch torchvision onnx onnxruntime-gpu onnxsim
import torch
import torchvision.models as modelsmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
model.eval()dummy = torch.randn(1, 3, 224, 224) # 代表输入样例torch.onnx.export(model,dummy,"resnet18.onnx",input_names=["input"],output_names=["logits"],opset_version=17, # 建议 17 起步;遇到不支持再调整do_constant_folding=True, # 常量折叠dynamic_axes={ # 动态维度(批大小可变)"input": {0: "batch"},"logits": {0: "batch"}}
)
print("Exported to resnet18.onnx")
3.2 导出技巧
- 一定要
model.eval():避免 Dropout/BN 训练态差异。 - 确保输入前后处理一致:导出时把归一化、Resize、Padding 等逻辑“定型”,上线端严格复刻。
- 控制算子:
interpolate、groupnorm等在早期 opset/后端可能不完美;必要时改写为等价算子或固定尺寸。 - 自定义算子:优先改写为通用算子组合;否则考虑自定义 kernel(TensorRT Plugin/ORT Custom Op)。
- 多输入多输出:合理命名并在
dynamic_axes中逐一声明可变维度。
3.3 导出检测/分割类模型的要点
- NMS、后处理(阈值、解码)通常不要放进 ONNX(跨后端兼容差),更稳的做法是导出纯网络主干,后处理在应用层实现。
- 需要放进 ONNX 时,优先选 ORT/TensorRT 支持的实现(如 TRT 的
BatchedNMSPlugin)。
四、TensorFlow/Keras → ONNX 两种方式(CLI & 代码)
4.1 使用 tf2onnx(CLI 最省心)
# pip install tf2onnx onnx onnxruntime-gpu
python -m tf2onnx.convert \--saved-model ./saved_model_dir \--opset 17 \--output model.onnx
支持 --from_keras、--concrete_function、--inputs-as-nchw 等参数,遇到通道顺序不一致问题时很有用。
4.2 Python 代码方式
import tensorflow as tf
import tf2onnxmodel = tf.keras.applications.MobileNetV2(weights="imagenet")
# Keras -> ConcreteFunction
spec = tf.TensorSpec((1, 224, 224, 3), tf.float32, name="input")
model_func = tf.function(model).get_concrete_function(spec)onnx_model, _ = tf2onnx.convert.from_function(model_func,input_signature=[spec],opset=17,output_path="mobilenetv2.onnx"
)
五、导出后如何“验模”?等价性校验与可视化
5.1 结构合法性检查
import onnx
onnx_model = onnx.load("model.onnx")
onnx.checker.check_model(onnx_model)
print("Checker passed!")
5.2 ONNX Runtime 推理对比(数值容差)
import numpy as np
import onnxruntime as ort# 原框架输出 (以 PyTorch 为例)
import torch
x = torch.randn(2, 3, 224, 224)
with torch.no_grad():torch_out = model(x).cpu().numpy()# ORT 输出
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
sess = ort.InferenceSession("model.onnx", providers=providers)
onnx_out = sess.run([sess.get_outputs()[0].name], {"input": x.numpy()})[0]# 比较
np.testing.assert_allclose(torch_out, onnx_out, rtol=1e-03, atol=1e-05)
print("ORT output is close to PyTorch output!")
5.3 可视化
- 使用 Netron 打开
.onnx,检查输入输出名称、维度、算子连接是否符合预期。
六、进一步优化:Simplify、融合、量化、FP16
6.1 图简化
python -m onnxsim model.onnx model-sim.onnx
好处:消灭冗余节点、常量折叠,更容易被后端吃满。
6.2 ONNX Runtime 图优化
import onnxruntime as ort
opt = ort.SessionOptions()
opt.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess = ort.InferenceSession("model-sim.onnx", sess_options=opt,providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
6.3 量化(Dynamic / Static)
动态量化(简单、无需标注数据)
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic(model_input="model-sim.onnx",model_output="model-int8.onnx",weight_type=QuantType.QInt8,optimize_model=True
)
静态量化(效果更好,需要校准数据):
核心思路是实现 CalibrationDataReader,将若干代表性样本喂给校准器。
from onnxruntime.quantization import CalibrationDataReader, quantize_static, QuantFormat, QuantType
import numpy as npclass NumpyFolderReader(CalibrationDataReader):def __init__(self, samples): # samples: List[np.ndarray]self.samples = iter(samples)def get_next(self):try:arr = next(self.samples)return {"input": arr}except StopIteration:return None# 假设已准备好若干 (1,3,224,224) 的样本数组
reader = NumpyFolderReader(samples)
quantize_static(model_input="model-sim.onnx",model_output="model-int8s.onnx",calibration_data_reader=reader,quant_format=QuantFormat.QDQ, # 兼容更好activation_type=QuantType.QInt8,weight_type=QuantType.QInt8
)
6.4 FP16(GPU 友好)
import onnx
from onnxconverter_common import float16
m = onnx.load("model-sim.onnx")
m_fp16 = float16.convert_float_to_float16(m, keep_io_types=True)
onnx.save(m_fp16, "model-fp16.onnx")
A100 场景:优先尝试 TensorRT 或 ORT 的 TensorRT Execution Provider,再考虑 FP16/INT8。
七、用 ONNX Runtime 推理(CPU/GPU/TensorRT)
7.1 选择后端
- CPU:
CPUExecutionProvider(可设置线程数、亲和性)。 - GPU:
CUDAExecutionProvider(支持 FP16、BFloat16 等)。 - TensorRT:
TensorrtExecutionProvider(极致延迟/吞吐,支持 engine 缓存)。
7.2 Python 最简调用
import onnxruntime as ort, numpy as np
opt = ort.SessionOptions()
opt.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALLproviders = [("TensorrtExecutionProvider", {"trt_fp16_enable": True}),"CUDAExecutionProvider","CPUExecutionProvider"
]sess = ort.InferenceSession("model-fp16.onnx", sess_options=opt, providers=providers)x = np.random.randn(4, 3, 224, 224).astype(np.float32)
out = sess.run([sess.get_outputs()[0].name], {"input": x})[0]
print(out.shape)
7.3 进阶:Session 环境变量与 Provider 参数
intra_op_num_threads、inter_op_num_threads- TensorRT:
trt_max_workspace_size,trt_engine_cache_enable,trt_timing_cache_enable - CUDA:
cuda_mem_limit,cudnn_conv_algo_search等
八、上线服务:FastAPI + Uvicorn 模板
目录结构:
app/├── server.py├── preprocess.py└── postprocess.py
server.py
from fastapi import FastAPI
from pydantic import BaseModel
import onnxruntime as ort
import numpy as npapp = FastAPI(title="ONNX Inference Service")# 1) 加载模型(进程启动即加载与预热)
opt = ort.SessionOptions()
opt.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
providers = [("TensorrtExecutionProvider", {"trt_fp16_enable": True}),"CUDAExecutionProvider","CPUExecutionProvider"
]
sess = ort.InferenceSession("model.onnx", sess_options=opt, providers=providers)
output_name = sess.get_outputs()[0].nameclass Item(BaseModel):data: list # 简易示例:传二维/四维数组@app.on_event("startup")
async def warmup():dummy = np.zeros((1,3,224,224), dtype=np.float32)sess.run([output_name], {"input": dummy})@app.post("/predict")
def predict(item: Item):x = np.array(item.data, dtype=np.float32)if x.ndim == 3:x = x[None, ...]y = sess.run([output_name], {"input": x})[0]return {"shape": list(y.shape), "result": y.tolist()[:10]} # demo:返回前 10 个数
启动:
uvicorn app.server:app --host 0.0.0.0 --port 8000 --workers 1
压测(示例):
# 安装 locust 或 wrk 任选其一
# wrk 示例
wrk -t4 -c64 -d30s --timeout 2s \-s post.lua http://127.0.0.1:8000/predict
post.lua(简单脚本):
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"local data = {}
for i=1,3 do data[i] = {}for j=1,224*224 do data[i][j] = 0.0 end
end
local body = '{"data":[' .. table.concat(data, ",") .. ']}'request = function()return wrk.format("POST", "/predict", nil, body)
end
九、容器化与部署
Dockerfile(GPU)
FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y python3-pip && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir fastapi uvicorn[standard] onnx onnxruntime-gpu onnxsim numpy pillow
WORKDIR /app
COPY app /app
EXPOSE 8000
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]
构建 & 运行:
docker build -t onnx-service:gpu .
# 需要 NVIDIA Container Toolkit
docker run --rm -it -p 8000:8000 --gpus all onnx-service:gpu
Kubernetes 小提示:
- 设定
livenessProbe/readinessProbe; - 使用
nvidia.com/gpu: 1资源请求; - 通过 ConfigMap/Secret 注入模型路径与密钥;
- 滚动更新 + 金丝雀发布。
十、性能调优清单:吞吐、延迟、内存
- Batch:在线低延迟场景小批或零批,离线/流式可用微批聚合。
- Pinned Memory:数据拷贝更快;Python 端使用
numpy的 page-locked 需要配合 IOBinding/自定义缓冲。 - IOBinding:ORT 把输入/输出绑定到 GPU,减少 H2D/D2H;极端延迟优化利器。
- 图静态化:能固定就固定(尺寸、步幅、Pad),换取更强优化。
- Provider 顺序:优先 TensorRT,其次 CUDA,最后 CPU。
- 混合精度:FP16/INT8;校验精度,记录 A/B 实验数据。
- 多进程:Python GIL 影响下,多进程优于多线程;或用 C++/Rust 封装服务。
十一、排坑大全:典型报错 & 解决
| 报错/现象 | 可能原因 | 解决思路 |
|---|---|---|
No Op kernel registered for ... | 后端不支持该算子/数据类型 | 提升/降低 opset;替换算子;切换 Provider;自定义插件 |
Non-zero status code while running ... | 维度不一致/越界 | 检查预处理、动态轴,打印真实输入形状 |
| 导出后精度突降 | 训练/推理前后处理不一致;BN/Dropout | eval();统一归一化/Resize;比对中间层 |
| TensorRT 构建失败或极慢 | 动态维度过多;不支持算子 | 固定关键维度;开启 Engine Cache;替换不支持算子 |
| 显存炸裂 | 批太大;中间激活过多 | 降低 batch;FP16/INT8;图简化;分块推理 |
十二、上线工程要点:版本、监控、安全
- 版本管理:模型文件加
model_name/version/opset;用 MLflow/DVC/S3 做注册与溯源。 - 可观测性:请求计数、P50/P95/P99、GPU 利用率;Prometheus + Grafana。
- 灰度/回滚:按流量/用户分群灰度;事故一键回滚到上一稳定版本。
- 安全:输入校验、超时/限流、CORS 与鉴权;容器只读文件系统、非 root 运行。
十三、写作彩蛋:一键复用的文章骨架 & SEO 提示
文章开头三板斧:
- 痛点:训练模型很顺,上线反而卡;
- 承诺:这篇文章给你从导出到部署的可复制路径;
- 预告:PyTorch/TensorFlow 都有示例,GPU/CPU 都能跑。
SEO 关键词(标题/小节/Alt 文案中穿插):“模型部署”、“ONNX 转换”、“ONNX Runtime 推理”、“TensorRT 加速”、“FastAPI 部署”。
结尾 CTA:给出 Demo 仓库链接/压测数据图(可后续补充)。
附:可直接复用的最小 Demo
A. PyTorch 导出 + ORT 对比
# pip install torch torchvision onnx onnxruntime onnxsim
import torch, torchvision as tv, onnx, onnxruntime as ort, numpy as np
m = tv.models.resnet18(weights=tv.models.ResNet18_Weights.DEFAULT).eval()
dummy = torch.randn(1,3,224,224)
torch.onnx.export(m, dummy, "res18.onnx", input_names=["input"], output_names=["logits"], opset_version=17,dynamic_axes={"input":{0:"batch"},"logits":{0:"batch"}})
# ORT 推理
sess = ort.InferenceSession("res18.onnx", providers=["CPUExecutionProvider"]) # CPU 也能跑
x = np.random.randn(1,3,224,224).astype(np.float32)
y = sess.run([sess.get_outputs()[0].name], {"input": x})[0]
print(y.shape)
B. FastAPI 服务最小化
from fastapi import FastAPI
import onnxruntime as ort, numpy as np
app = FastAPI()
sess = ort.InferenceSession("res18.onnx", providers=["CPUExecutionProvider"])
name = sess.get_outputs()[0].name
@app.post("/predict")
def predict(data: list):arr = np.array(data, dtype=np.float32)if arr.ndim==3: arr=arr[None,...]out = sess.run([name], {"input": arr})[0]return out.tolist()
到这里,你已经具备:会把模型转成 ONNX、会验证和优化、会用 ORT 推理、会把服务跑起来并容器化。把具备生产力的业务前后处理接上去,就是真实生产的雏形!
