最近在把一个训练好的图像分类模型(ResNet-50)从PyTorch导出到ONNX,再用ONNX Runtime做推理。结果发现精度掉得很明显,Top-1从原来的92.3%掉到了88%左右。我试了用torch.onnx.export,设置opset_version=11,也试了用onnx-simplifier简化模型,但效果不大。有没有老哥遇到过类似的问题?是ONNX对某些算子(比如BatchNorm、AdaptiveAvgPool)的转换有精度损失,还是我导出时没开正确的优化选项?另外,如果后续要部署到移动端,这个精度掉得能接受吗?还是说应该直接上TFLite?求指点。
PyTorch转ONNX后推理精度下降,是量化问题还是算子不支持?
全部回复
共 169 条精度掉这么多大概率不是算子问题,试试把BN层fold进conv再导出,顺便检查下预处理有没有对齐。
我上次转YOLOv5也遇到过类似情况,后来发现是导出时模型默认进入了训练模式,BatchNorm的running_mean和running_var没被正确冻结,你试试在export前明确调用model.eval(),然后检查一下ONNX里BatchNorm节点的输入是不是两个参数都带上了。另外AdaptiveAvgPool在某些opset下会展开成动态shape的Gather,精度影响很小但性能会差,建议手动换成固定kernel的AvgPool试试。移动端的话,4个点的Top-1差距其实挺大的,如果是生产环境建议直接上TFLite量化,INT8能压到1-2%以内,但需要校准数据集,别省这一步。
我之前也踩过类似的坑,ResNet-50导出ONNX后精度掉4个点确实不太正常,先别急着甩锅给量化。建议你把导出时的dynamic_axes参数检查下,尤其是BatchNorm在推理模式下应该已经折叠了,如果没折叠可能会引入数值误差。另外AdaptiveAvgPool在opset 11里转换有时会变成多个slice+reduce操作,你可以试着把opset升到13或17,ONNX Runtime对高版本算子支持更稳。至于Mobile端,如果精度掉到88%对你业务影响大,不如直接试TFLite的量化感知训练,那个对精度控制比事后转换强不少。
先别急着赖算子,ResNet-50转ONNX精度掉这么多大概率是export时training和eval模式没切对,把BN层跑成训练逻辑了。
移动端这精度确实有点悬,TFLite也不一定更好,建议先对比下ONNX Runtime和PyTorch的输入预处理是否完全一致。
我上次转YOLOv5也遇到过类似情况,后来发现是模型里有个自定义的采样层在ONNX里被拆成了多个op,精度就崩了。你试试把导出时的dynamic_axes参数关掉,固定输入尺寸,有时候能规避一些图优化的问题。另外AdaptiveAvgPool在opset 11里确实有已知的精度坑,建议升级到opset 13以上再试。移动端部署的话4个点的掉精度得看业务场景,如果对误判容忍度低,TFLite的量化感知训练反而更稳一些,建议两条路都跑个对比测试。
之前跑YOLOv5也踩过类似的坑,后来发现多半不是算子转换问题,而是模型里的BN层在导出时被折叠了,但onnxruntime的优化和pytorch的推理逻辑有细微差异导致的。你可以先关掉runtime的图优化(比如graph_optimization_level设成ORT_DISABLE_ALL)对比一下,或者用onnxruntime的per-session选项试试。另外Top1掉4个点确实偏大,如果只是deploy到移动端,我建议先查一下输入预处理(比如归一化参数)是不是和训练时完全一致,这个经常被忽略。TFLite也不一定更稳,关键还是看你的精度损失是确定性的还是随机性的。
之前做分割模型转onnx也遇到过类似情况,后来发现是AdaptiveAvgPool在某些opset下会被展开成多个小算子,浮点累加顺序变了导致精度漂移。建议你试试opset12以上,或者手动把模型里的AdaptiveAvgPool替换成固定尺寸的AvgPool再导出。另外,92.3到88这个幅度不太像单纯量化误差,更像预处理或者后处理环节有差异,比如pytorch里默认的RGB顺序和onnxruntime读入的格式对不上。移动端部署如果精度这么敏感,可以先考虑fp16,实在不行再上int8量化。
我也遇过,当时查了半天发现是torch.on
建议先查下模型里有没有动态shape,固定输入尺寸再导出试试,另外检查下preprocessing和归一化参数是否一致。
这精度掉的幅度确实有点大,不太像是单纯量化或算子转换的正常损耗。我之前也踩过类似的坑,最后发现是torch.onnx.export里没把training参数设为False,导致BatchNorm层带着训练时的统计量一起导出,你可以先检查下这个。另外AdaptiveAvgPool在某些opset版本下会展开成多个slice+reduce,数值上会有微小误差,但一般不至于掉4个点。建议你导出后先用onnxruntime的CPU和GPU分别跑一下,对比下是不是后端实现的问题。移动端部署如果精度要求高,确实TFLite的量化感知训练会更稳一些,但ONNX转TensorRT或NCNN也不差,关键还是得先定位到具体层。
这情况大概率不是量化的锅,opset 11下ResNet-50的算子覆盖挺全的。我怀疑你是不是用的PyTorch版本比较老,导出时对BatchNorm的fold处理有bug,试着更新一下torch或者手动把BN层先融合进卷积。另外onnx-simplifier有时候会过度化简,反而把某些图结构改坏,建议用onnxruntime的优化选项替代它。精度掉这么多,跑一下每层输出的余弦相似度,基本能锁定是哪个算子出的问题。移动端的话,如果模型不是特别大,直接上TFLite可能更省心,毕竟量化工具链更成熟。
4个
这精度掉得有点狠,不太像纯量化误差,ResNet-50在opset 11下转ONNX一般不会掉这么多。你检查下导出时是不是把training=True带进去了,或者模型里有没有动态shape的输入,这俩最容易导致BatchNorm和AdaptiveAvgPool的图优化失效。另外可以试试opset 13+,对算子的支持更完整,simplifier有时候反而会把某些fuse搞坏。移动端部署的话,这个精度差距肯定不能接受,但先别急着换TFLite,把ONNX这边的坑填完再对比也不迟。
遇到过类似的坑,ResNet这种带BN和AdaptiveAvgPool的模型转ONNX确实容易出问题,但你这掉的幅度有点大,不太像单纯的算子精度损失。建议先查一下导出时模型有没有切成eval模式,还有输入数据的预处理(比如归一化参数)在ONNX Runtime里是不是和PyTorch完全一致,这两点最容易忽略。另外opset 11对某些算子的融合确实不友好,可以试试opset 12或13,配合dynamic_axes处理一下输入维度。如果移动端部署,这个精度肯定不达标,TFLite也不一定更稳,主要看量化方式和校准集,建议先用FP32的ONNX排查问题,再考虑量化。
八成是AdaptiveAvgPool转ONNX时被拆成动态shape了,试试固定输入尺寸加onnx opset 13看看。
4个点左右的掉精度确实有点狠,不太像纯量化问题,更像是某个算子转换时数值行为变了。你可以先跑一下onnxruntime的CPU和CUDA对比,再检查下模型里有没有用torch的某些fused操作,有时候是导出的图里插了额外的reshape或transpose导致数值扰动。移动端部署的话,这精度我觉着不太能接受,TFLite的量化调参空间反而更可控,但建议先把onnx精度问题定位清楚再考虑换框架。
大概率不是量化的问题,opset11下BatchNorm和AdaptiveAvgPool转换挺稳的,你先检查下预处理和输入尺寸是否一致。
这精度掉得确实有点狠,4个多点不太像纯量化误差。你试过导出前把模型设为eval模式吗,BatchNorm在train和eval下行为不一样,这个最容易踩坑。另外AdaptiveAvgPool在opset11里有时会展开成多个slice+reduce,数值上可能有微小差异,建议用opset13+试试。移动端部署的话,这精度损失我觉得不太行,TFLite加int8量化调好了能控制在1个点以内,但前提是你得用代表性数据集做校准。
我之前也踩过这个坑,ResNet-50导出ONNX后Top-1掉4个点确实有点多,但大概率不是量化的锅——你用的是FP32导出吧?ONNX Runtime对BatchNorm和AdaptiveAvgPool的转换其实挺成熟的,精度损失通常来自模型里的某些自定义逻辑或者预处理不一致,比如PyTorch里的normalize参数有没有在推理时和ONNX里保持一致。建议你先检查下输入数据的预处理(mean/std、resize方式),再对比一下导出前后每个层的输出差异,用onnxruntime的profiling或者torch.onnx.export的dynamic_axes参数看看是不是动态shape导致的。另外opset_version=11对ResNet-50来说够用,但如果你用了F.interpolate或者某些较新的算子,建议直接试opset 13+,有些细节优化是隐含的。至于移动端部署,如果精度掉到88%而你的业务对误判容忍度不高,那TFLite + 量化感知训练可能是更稳的路线,因为TFLite的量化工具链对常见CNN优化更彻底。不过也别急着换框架,先跑个纯ONNX Runtime的CPU推理对比一下PyTorch的CPU推理,如果两者精度一致,那问题就出在导出环节的某个算子上,可以用onnxruntime的算子级调试工具定位到具体层。我之前遇到过AdaptiveAvgPool在导出时被拆成多个Slice+ReduceMean,虽然数学等价但数值误差会累积,换opset 12后就没这问题了。
4个点大概率的差异不在算子本身,而是导出时BN层被fold进conv后数值精度变了,你试试把torch.onnx.export里的training参数设成False,同时检查下输入图像的归一化方式在ONNX Runtime里是不是没对齐。另外ResNet的AdaptiveAvgPool在opset 11会展开成两个pooling,某些情况下确实会有微小误差,但4个点有点太多了,我怀疑你推理时的预处理或后处理跟训练时不一致。移动端这个精度掉得肯定不能接受,建议先排查数据流,不行再考虑TFLite,毕竟量化后还会再掉一点。
我之前也踩过这个坑,ResNet50转ONNX掉点大概率不是量化的事,你opset11默认就是fp32全精度导出。先查一下模型里有没有用F.interpolate或者自定义的grid_sample,这两个算子经常在ONNX里被拆成奇怪的子图导致数值漂移,建议把onnxruntime的execution_mode设成ORT_ENABLE_ALL看看日志里有没有fallback警告。另外AdaptiveAvgPool在opset11下确实会转成动态shape的ReduceMean,如果输入尺寸固定的话直接改成AvgPool或者把opset拉到13试试。移动端部署的话,4个点的精度损失对ResNet这种强模型来说有点肉疼,建议先试一下onnxruntime的graph优化和动态量化,实在不行再考虑TFLite,毕竟TFLite对BN融合更激进一些。
之前也踩过这个坑,92%掉到88%确实有点狠了,但大概率不是量化的问题,因为纯导出ONNX默认是fp32,精度损失主要来自算子映射。ResNet-50里AdaptiveAvgPool这个算子,PyTorch实现和ONNX的转换有时候会引入数值误差,特别是当输入尺寸不是固定的时候,建议你直接改用固定尺寸输入导出,或者把模型里的AdaptiveAvgPool换成GlobalAveragePool,很多情况这么一改精度就回来了。BatchNorm的话,如果模型在eval模式下导出,一般是融合进卷积的,不太会掉精度,但如果你是在训练模式下导出,那问题就大了,赶紧检查一下。另外onnx-simplifier虽然能简化图结构,但有时候会把一些融合操作改得太过激进,反而引入误差,你可以试着手动检查一下导出的图里有没有奇怪的Reshape或者Cast节点。至于移动端部署,88%的Top-1如果任务本身不太敏感还能凑合,但如果是医疗或金融场景肯定不能接受,TFLite也不是银弹,它主要强在量化优化上,但fp32精度也不一定比ONNX Runtime强。建议你先用onnxruntime的精度分析工具跑一下每个节点的输出对比,定位到具体哪个op开始偏差变大的,再决定是修模型还是换框架。
之前跑YOLOv5也踩过这个坑,后来发现多半不是算子的锅,而是导出时模型默认进了训练模式,BN层的均值和方差没被冻结。你试试在export前显式调用model.eval(),再把torch.no_grad()包上,精度应该能回来不少。另外AdaptiveAvgPool在opset 11下确实会展开成多个算子,数值误差会累积,直接升到opset 17或者用onnxruntime的CUDA EP试试。至于移动端,4个点的掉精度对分类任务影响不大,但如果是检测或分割就得慎重,TFLite的量化校准也不一定比ONNX强,关键还是看你的部署框架对算子的支持度。
我之前也踩过这个坑,ResNet-50转ONNX掉点大概率不是算子不支持,而是BatchNorm和AdaptiveAvgPool在导出时被融合或替换成了近似实现,尤其是opset版本低的时候。建议你试试opset_version=12以上,同时用onnxruntime的graph优化(比如enable_all_optimizations),另外检查下输入图像的预处理(mean/std)有没有被重复归一化,这个经常导致精度悄悄掉。如果移动端部署对精度要求高,TFLite的量化校准其实也不省心,建议先对比下ONNX Runtime的FP32和TFLite的FP32推理差异,别急着上量化。