昇腾平台小模型推理精度问题分析方法论 ​作者​昇腾实战派​知识地图​https://blog.csdn.net/Lumos_Lovegood/article/details/161601003背景概述随着AI应用场景的多样化发展开发者常需将已在GPU或CPU上训练好的小模型迁移至昇腾NPU上进行推理部署。然而由于不同硬件平台在算子实现、数据类型支持等方面的差异模型迁移后可能出现精度下降的问题。本文系统性地介绍了一套从整体输出比对到逐算子排查的精度定位方法论并提供了常见精度问题的解决方案帮助开发者高效完成模型迁移与精度调优。1. 定位方法论小模型精度的定位流程如下图所示。首先在相同输入下对比GPU/CPU与NPU的输出tensor若余弦相似度≥0.999且最大绝对误差≤0.001则认为精度达标否则进入精度排查步骤。排查时使用msprobe工具一键dump并比对ONNX模型与OM模型中各算子的输出tensor定位到第一个精度异常的算子最后对该算子进行深入分析。1.1. 模型输出tensor对比以下脚本实现了Pth模型CPU、ONNX模型CPU和OM模型NPU的输出精度比对。若需使用GPU作为标杆只需将device和providers改为cuda相关配置即可。importargparseimportnumpyasnpimporttorchimportonnxruntimefromais_bench.infer.interfaceimportInferSessiondefpreprocess_image(img):ifimgisNone:imgnp.random.randint(0,255,size(112,112,3),dtypenp.uint8)else:imgcv2.imread(img)imgcv2.resize(img,(112,112))imgcv2.cvtColor(img,cv2.COLOR_BGR2RGB)imgnp.transpose(img,(2,0,1))imgtorch.from_numpy(img).unsqueeze(0).float()img.div_(255).sub_(0.5).div_(0.5)returnimgdefonnx_infer(onnx_model_path,img):sessiononnxruntime.InferenceSession(onnx_model_path,providers[CPUExecutionProvider])input_namesession.get_inputs()[0].name model_outputsession.run(None,{input_name:img.numpy()})[0]returnmodel_outputdefom_infer(model,img,device_id):sessionInferSession(device_id,model_pathmodel)model_outputsession.infer(feeds[img.numpy()])[0]returnmodel_outputtorch.no_grad()defpytorch_infer(weight,name,img):devicetorch.device(cpu)modelModelClass(...)model.load_state_dict(torch.load(model_weight.pth),map_locationdevice)model.eval()featmodel(img).numpy()returnfeatdefprecision_compare(output1,output2,label1Output1,label2Output2):output1_flatoutput1.flatten().astype(np.float64)output2_flatoutput2.flatten().astype(np.float64)cosine_similaritynp.dot(output1_flat,output2_flat)/(np.linalg.norm(output1_flat)*np.linalg.norm(output2_flat))absolute_errorsnp.abs(output2_flat-output1_flat)relative_errorsabsolute_errors/(np.abs(output1_flat)1e-10)*100print(f{label1}vs{label2}:)print(f 余弦相似度:{cosine_similarity:.10f})print(f 最大绝对误差:{absolute_errors.max():.10e})print(f 最大相对误差(%):{relative_errors.max():.10e})if__name____main__:parserargparse.ArgumentParser(descriptionArcFace Model Precision Compare)parser.add_argument(--network,typestr,defaultr50,helpbackbone network)parser.add_argument(--weight,typestr,default,helpPyTorch权重路径)parser.add_argument(--onnx,typestr,defaultNone,helpONNX模型路径)parser.add_argument(--img,typestr,defaultNone,help图片路径)parser.add_argument(--model,typestr,defaultNone,helpOM模型路径)parser.add_argument(--device,typeint,default0,helpNPU device ID)argsparser.parse_args()imgpreprocess_image(args.img)outputs{}ifargs.weight:outputs[PyTorch]pytorch_infer(args.weight,args.network,img)ifargs.onnx:outputs[ONNX]onnx_infer(args.onnx,img)print(fONNX output shape:{outputs[ONNX].shape})ifargs.model:outputs[OM]om_infer(args.model,img,args.device)print(fOM output shape:{outputs[OM].shape})ifPyTorchinoutputsandONNXinoutputs:precision_compare(outputs[PyTorch],outputs[ONNX],PyTorch,ONNX)ifONNXinoutputsandOMinoutputs:precision_compare(outputs[ONNX],outputs[OM],ONNX,OM)ifPyTorchinoutputsandOMinoutputs:precision_compare(outputs[PyTorch],outputs[OM],PyTorch,OM)使用步骤数据预处理可使用numpy生成固定随机值或对同一张图片进行预处理得到tensor。分别加载Pth、ONNX和OM模型进行推理保存输出结果。两两对比输出tensor输出余弦相似度、最大绝对误差和最大相对误差如下图所示若余弦相似度≥0.999则认为精度达标否则进入后续排查步骤。1.2. 精度数据dump和对比使用msprobe工具可一键dump并对比ONNX模型与OM模型中各算子的输出精度工具下载地址。命令如下msprobe compare-moffline_model-gp/golden_path/golden_model.onnx-tp/target_path/target_path.om-o./compare_output--input_shapeinput_name:1,3,112,112参数说明-m比对模式需指定为offline_model。-gp标杆模型文件.onnx路径。-tp对比的昇腾AI处理器离线模型.om路径。-o结果输出路径默认为当前路径的output文件夹。-input_shape模型输入为静态shape时使用例如input_name1:1,224,224,3;input_name2:3,300节点间用英文分号隔开。更多用法详见链接。1.3. 比对结果说明及分析输出结果文件目录结构如下{output_path}/{timestamp}/{input_name-input_shape} # {input_name-input_shape}用来区分动态shape时不同的模型实际输入静态shape时没有该层 ├-- dump_data │ ├-- npu # npu dump数据目录 │ │ ├-- {timestamp} # 模型所有npu dump的算子输出dump为False情况下没有该目录 │ │ │ └-- 0 # Rank设备ID号 │ │ │ └-- {om_model_name} # 模型名称 │ │ │ └-- 1 # 模型ID号 │ │ │ ├-- 0 # 针对每个Task ID执行的次数维护一个序号从0开始计数该Task每dump一次数据序号递增1 │ │ │ │ ├-- Add.8.5.1682067845380164 │ │ │ │ ├-- ... │ │ │ │ └-- Transpose.4.1682148295048447 │ │ │ └-- 1 │ │ │ ├-- Add.11.4.1682148323212422 │ │ │ ├-- ... │ │ │ └-- Transpose.4.1682148327390978 │ │ ├-- {timestamp} │ │ │ ├-- output_0.bin │ │ │ └-- output_0.npy │ │ └-- {timestamp}_summary.json │ └-- {onnx} # 原模型dump数据存放路径onnx对应ONNX模型 │ ├-- Add_100.0.1682148256368588.npy │ ├-- input_Add_100.0.1682148256368588.npy # 如果是ONNX模型则会dump输入数据并增加对应的input前缀 │ ├-- ... │ └-- Where_22.0.1682148253575249.npy ├-- input │ └-- input_0.bin # 随机输入数据若指定了输入数据则该文件不存在 ├-- model │ ├-- {om_model_name}.json # 离线模型OM模型(.om)通过atc工具转换后的json文件 │ └-- new_{onnx_model_name}.onnx # 把每个算子作为输出节点后新生成的ONNX模型 └-- result_{timestamp}.csv # 比对结果文件比对结果位于result_{timestamp}.csv中示例如下需关注以下字段DateTypeNPU侧与CPU/GPU侧的数据类型若不同可能存在精度损失。IsNpuOps是否为NPU独有节点无需关注。IsPrecisionError是否为精度异常节点需重点关注第一个精度异常的节点。CosineSimilarity…MeanRelativeError各类误差比对结果若某项超过阈值则需重点关注。各算法说明见链接。精度指标参考标准如下误差对比算法正常参考标准CosineSimilarity 0.99RelativeEuclideanDistance 0.05KullbackLeiblerDivergence 0.005RootMeanSquareError 1.0MeanRelativeError 1.0根据比对结果找到第一个精度异常的算子进行进一步分析。对于小模型精度问题通常由数据类型强制转换导致的数值溢出引起因为ATC在转换模型时可能强制转换某些算子的数据类型。2. 常见精度问题解决方法2.1. 设置精度模式对于Atlas 800I A2或Atlas 800I A3产品因其支持fp32数据格式可通过ATC转换命令中的--precision_mode参数设置精度模式避免数值溢出。例如must_keep_origin_dtype表示保持原图精度转换原则如下若AI Core支持原图中算子的精度则采用该算子。若原图算子为float16AI Core不支持float16但支持float32和bfloat16则自动采用高精度float32。若原图算子为float16AI Core仅支持bfloat16则使用float16的AI CPU算子若AI CPU也不支持则报错。若原图算子为float32AI Core不支持float32仅支持float16则使用float32的AI CPU算子若AI CPU也不支持则报错。其他参数取值介绍见链接。用法示例--precision_modemust_keep_origin_dtype不同精度模式的精度与性能排序如下精度高低force_fp32 must_keep_origin_dtype allow_fp32_to_fp16 allow_mix_precision force_fp16性能优劣force_fp16 allow_mix_precision allow_fp32_to_fp16 must_keep_origin_dtype force_fp322.2. 算子修改对于部分产品许多算子的输入输出仅支持fp16而原始ONNX算子通常为fp32转换过程中存在fp32到fp16的转换容易导致数值溢出。针对此类算子精度问题需在算子侧进行修改具体方法因算子而异。以conv2d算子为例若其输入输出仅支持fp16而原始模型基于fp32训练卷积计算所得数值可能超出fp16范围。可通过缩放方式解决将卷积权重或输入缩小x倍由于卷积为线性操作输出结果相应缩小x倍从而将结果限制在fp16范围内。卷积计算完成后将输出cast回fp32再添加一个Mul算子将结果放大x倍。x的具体数值需根据模型实际情况确定。