4 · 模型转换进阶

动态Batch、动态分辨率、精度校准(AIPP)还有算子融合…… 这些词你肯定不陌生。说实话,模型转换这一步,卡住过不少人。我刚开始接触昇腾的时候,也在这上面栽过跟头。今天咱们就把这几个硬骨头一块儿啃下来。

模型转换进阶 动态Batch / 动态分辨率 输入shape灵活可变 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" 可以指定多组。

💡 我的经验: 动态Batch不要设太多档位,2~4个常用值就够了。档位越多,编译时间越长,而且可能浪费显存。

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
🚨 避坑指南: 我曾经为了极致性能,把融合开关全打开,结果模型输出全是NaN。后来发现某些融合对量化模型不友好。建议逐个开启,每开一个就验证一下精度。

另外,算子二进制编译也值得关注。对于频繁调用的算子,可以用 --op_debug_level=0 关闭调试信息,编译出更高效的二进制。

📌 整体建议: 模型转换不是一次性的活儿。我通常的做法是:先用动态Batch + AIPP静态配置跑通,然后打开profiling看瓶颈,再针对性做算子融合。每一步都做精度对比,确保输出误差在1%以内。