4 · 模型转换进阶
动态Batch、动态分辨率、精度校准(AIPP)还有算子融合…… 这些词你肯定不陌生。说实话,模型转换这一步,卡住过不少人。我刚开始接触昇腾的时候,也在这上面栽过跟头。今天咱们就把这几个硬骨头一块儿啃下来。
4.1 动态Batch与动态分辨率
很多场景下,输入图片的尺寸或Batch大小不是固定的。比如线上服务,有时候来一张图,有时候来一批。如果模型只支持固定尺寸,那就尴尬了。
我个人习惯在转换时直接设置动态维度。昇腾的 atc 工具提供了 --input_shape 和 --dynamic_batch / --dynamic_image_size 参数。举个例子:
# 动态Batch,最大支持8
atc --model=model.onnx --framework=5 \
--output=dynamic_model \
--input_shape="input:[-1,3,224,224]" \
--dynamic_batch="1,2,4,8"
注意,-1 表示动态维度。你想想看,如果模型里有Reshape或者全连接层,动态Batch可能会引起shape不匹配。我在项目中遇到过,一个分类模型转动态Batch后推理报错,最后发现是中间有个Flatten写死了维度。嗯,这里要提醒你:动态分辨率同理,--dynamic_image_size="224,224;256,256;320,320" 可以指定多组。
4.2 模型精度校准(AIPP)
AIPP(AI Preprocessing)是昇腾专门做数据预处理用的。说白了,就是把归一化、减均值、除方差、色域转换这些操作,从CPU/GPU搬到硬件上,几乎零开销。
我曾经在移植一个YOLOv5模型时,发现输出框全偏了。查了半天,原来是训练时用的BGR顺序,而AIPP默认是RGB。加上一句 input_format : YUV420SP_U8 或者 csc_switch : true 就解决了。所以AIPP配置一定要和训练对齐。
一个典型的AIPP配置(aipp.cfg)长这样:
aipp_op {
aipp_mode : static
input_format : RGB888_U8
mean_chn_0 : 123.675
mean_chn_1 : 116.28
mean_chn_2 : 103.53
var_reci_chn_0 : 0.017124
var_reci_chn_1 : 0.017507
var_reci_chn_2 : 0.017429
}
⚠️ 关键点: AIPP 有两种模式:static(转换时固化)和 dynamic(推理时动态传入)。我建议能用static就用static,性能更好。如果模型需要频繁切换预处理参数,再考虑dynamic。
另外,AIPP还支持色域转换(CSC)、填充(padding)、裁剪等。比如你的模型输入是640x640,但实际图片是1280x720,可以用AIPP做letterbox填充,省得自己写resize。
4.3 算子融合与优化
算子融合,就是把多个小算子合并成一个,减少访存和kernel launch开销。昇腾的 atc 在转换时会自动做一部分融合,比如Conv+BN+ReLU。但有些融合需要手动开启。
我记得有一次,一个BERT模型在昇腾上跑得比预期慢。打开 profiling 一看,很多小算子(比如Add、LayerNorm)都是单独执行的。后来加了 --enable_small_channel=1 和 --fusion_switch_file,吞吐直接提升了30%。
常用的优化开关:
| 参数 | 作用 | 建议 |
|---|---|---|
--buffer_optimize | 缓冲区优化,减少显存碎片 | 设为 l2_optimize |
--enable_small_channel | 小通道卷积融合 | 对轻量模型有效 |
--fusion_switch_file | 自定义融合规则(json) | 高级调优使用 |
--op_select_implmode | 算子实现模式选择 | 高精度选 high_precision |
另外,算子二进制编译也值得关注。对于频繁调用的算子,可以用 --op_debug_level=0 关闭调试信息,编译出更高效的二进制。
📌 整体建议: 模型转换不是一次性的活儿。我通常的做法是:先用动态Batch + AIPP静态配置跑通,然后打开profiling看瓶颈,再针对性做算子融合。每一步都做精度对比,确保输出误差在1%以内。