上篇玩转了 GAN 生成,但再好的模型训完也得上线,体积、速度与部署才是工程的最后一公里。
实验室里 ResNet-50 跑出 76% 精度很爽,但一算:模型 98MB、推理要一张高端 GPU、延迟 50ms------根本塞不进手机、也扛不住线上 QPS。从"训得出来"到"用得起来",中间隔着模型压缩与部署这道最后一公里。本篇把量化、剪枝、ONNX 导出串成一条可落地的流水线。
一、压缩动机与体积对比
| 精度 | 单参数字节 | ResNet-50 体积 | 相对 FP32 |
|---|---|---|---|
| FP32 | 4 | ~98 MB | 1× |
| FP16 | 2 | ~49 MB | 0.5× |
| INT8 | 1 | ~25 MB | 0.25× |
光体积就差 4 倍,更关键的是低精度算子在大多数硬件上有数倍加速(整数运算比浮点便宜)。这就是压缩的商业意义:同样的卡,能扛更多流量;同样的手机,能跑更重的模型。
二、量化:PTQ 与 QAT
2.1 PTQ(训练后量化,Post-Training Quantization)
训完 FP32 模型,直接用少量校准数据(几百~几千张)统计激活分布,把权重和激活映射到 INT8。几乎零成本,是首选。
python
import torch
from torch.ao.quantization import get_default_qconfig, prepare, convert
model.eval()
model.qconfig = get_default_qconfig("fbgemm") # x86 后端
prepared = prepare(model) # 插入观察者
# 用校准集跑一遍,统计分布
calibrate(prepared, calib_loader)
quantized = convert(prepared) # 真正量化
坑 :激活里的离群值(outlier) 会撑大量化范围,导致大量正常值被压缩到同一个整数------精度骤降。所以校准集要能代表真实分布,且对敏感层可做 per-channel 量化或保持 FP16。
2.2 QAT(量化感知训练,Quantization-Aware Training)
在训练时就模拟量化误差(插入"伪量化"节点),让网络"提前适应"低精度。精度损失更小,但要重训,成本高。
经验法则:先用 PTQ,掉点多再用 QAT。绝大多数业务 PTQ 就能满足。
三、结构化与非结构化剪枝
| 类型 | 做法 | 硬件加速 | 难度 |
|---|---|---|---|
| 非结构化 | 把接近 0 的单个权重置零(稀疏) | 需专用稀疏算子才加速 | 易,但加速难兑现 |
| 结构化 | 整通道/整层移除 | 直接减算力,通用加速 | 需重训恢复精度 |
结构化剪枝更"工程友好",示例(torch.prune 通道级需自定义,这里给非结构化示意):
python
import torch.nn.utils.prune as prune
# 对某一层权重做 L1 非结构化剪枝,剪掉 30%
prune.l1_unstructured(model.conv1, name="weight", amount=0.3)
prune.remove(model.conv1, "weight") # 固化掩码
剪完通常需要短轮微调把精度补回来。结构化的做法是按通道 L1 范数排序,砍掉最不重要的通道,再微调。
四、ONNX 导出与推理加速
ONNX 是"模型界的 USB 接口"------把 PyTorch/TF 模型转成统一格式,再用各平台的高性能引擎推理。
python
# PyTorch -> ONNX
dummy = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model, dummy, "model.onnx",
input_names=["input"], output_names=["output"],
dynamic_axes={"input": {0: "batch"}}, # 支持动态 batch
)
# ONNX Runtime 推理(CPU/GPU 通用)
import onnxruntime as ort
sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
out = sess.run(["output"], {"input": input_numpy})
部署链路对比(典型区间,非本机实测):
| 阶段 | 体积 | 相对延迟 | 说明 |
|---|---|---|---|
| PyTorch FP32 原始 | 1× | 1× | 灵活但慢 |
| + INT8 量化 | 0.25× | ~0.4× | ONNX Runtime |
| + 结构化剪枝 | ~0.3× | ~0.5× | 配合微调 |
| + TensorRT (GPU) | 0.25× | ~0.2× | 极致加速 |
ResNet-50 量级在公开基准上,量化+剪枝组合常能把体积降约 70%、时延降约 50%,精度仅掉 1 个点左右------这正是压缩的性价比甜区。
五、ResNet-50 实测思路(开放复现)
上面数字来自社区公开复现的典型区间,建议你在自己机器按这个脚本验证(需 pip install onnx onnxruntime):
python
from torchvision import models
import time, numpy as np
m = models.resnet50(weights="DEFAULT").eval()
x = torch.randn(1, 3, 224, 224)
# 1) FP32 基线延迟
t0 = time.perf_counter()
for _ in range(50): m(x)
fp32_ms = (time.perf_counter() - t0) / 50 * 1000
# 2) 导出 ONNX + ORT 推理
torch.onnx.export(m, x, "resnet50.onnx", dynamo=False)
import onnxruntime as ort
sess = ort.InferenceSession("resnet50.onnx")
xa = x.numpy()
t1 = time.perf_counter()
for _ in range(50): sess.run(None, {"input": xa})
ort_ms = (time.perf_counter() - t1) / 50 * 1000
print(f"FP32 PyTorch: {fp32_ms:.1f} ms | ONNX Runtime: {ort_ms:.1f} ms")
跑完你会直观看到"导出 ONNX 本身就是一次提速",再叠加量化效果更明显。
六、部署 Checklist
-
先量化(PTQ),校准集要代表真实数据。
-
再剪枝(结构化优先),剪完必微调。
-
导出 ONNX ,用
onnx.checker验证图合法。 -
选推理引擎:CPU 用 ONNX Runtime,NVIDIA GPU 用 TensorRT,移动端用 NCNN/MNN/TFLite。
-
端到端压测:用真实 batch 和并发测延迟与吞吐,别只测单张。
-
精度回灌:部署后用真实请求抽样比对 FP32 输出,监控精度漂移。
总结
模型落地不是把 .pt 文件丢上服务器就完事。量化解决"体积+速度",剪枝解决"冗余",ONNX 解决"跨平台",三者组成一条标准流水线。记住优先级:PTQ 优先、结构化剪枝配微调、ONNX 做统一接口------这套组合拳能把 ResNet-50 这样的模型体积砍七成、时延腰斩,而精度几乎不掉。
参考资料
-
Jacob, B., et al. (2018). Quantization and Training of Neural Networks for Efficient Integer-Arithmetic Inference.
-
Han, S., et al. (2015). Deep Compression: Compressing DNNs with Pruning, Trained Quantization and Huffman Coding.
-
ONNX 官方文档:ONNX | Home
-
ONNX Runtime 部署指南:ONNX Runtime | Home
往期回顾
下一篇进入多模态------让模型同时"看懂图、读懂字"。
📚 本篇出自专栏《深学之路:从感知机到智能体》 ------ 从感知机到大模型智能体的完整深度学习连载(基础+工程化+扩散模型 / RAG / Agent 等)。每篇附可运行代码与原创实验,收藏专栏不迷路。