diff --git "a/Ascend-PyTorch\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274/PyTorch\347\246\273\347\272\277\346\216\250\347\220\206-\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274.md" "b/Ascend-PyTorch\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274/PyTorch\347\246\273\347\272\277\346\216\250\347\220\206-\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274.md" index 7fa534a20eb2674e1a2ab5e80ea0530150713e47..4f312dc6b2ac0dbf82def2698f14001bd94a4b8e 100644 --- "a/Ascend-PyTorch\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274/PyTorch\347\246\273\347\272\277\346\216\250\347\220\206-\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274.md" +++ "b/Ascend-PyTorch\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274/PyTorch\347\246\273\347\272\277\346\216\250\347\220\206-\347\246\273\347\272\277\346\216\250\347\220\206\346\214\207\345\257\274.md" @@ -1,4 +1,7 @@ + + # Ascend PyTorch模型离线推理指导 + - [1 概述](#1-概述) - [1.1 原理与方案](#11-原理与方案) - [1.2 环境搭建与使用说明](#12-环境搭建与使用说明) @@ -29,20 +32,30 @@ ### 1.1 原理与方案 -- Ascend PyTorch模型离线推理迁移 - - 基于开源PyTorch框架的开源模型代码加载npu 910训练的pth权重,在npu 310上进行离线推理,要求精度与pth权重精度一致,性能超越gpu t4 -![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/images/Ascend_PyTorch_offlineinfer_migrate.png) - +- Ascend PyTorch模型离线推理迁移**目标** + + 基于开源PyTorch框架的开源模型代码加载对应的pth权重,在Ascend 310芯片上进行离线推理满足: + 1. 精度与pth权重精度(开源仓精度)一致 + 2. 性能(推理速度)超越GPU标杆 + - Ascend PyTorch模型离线推理流程 - - 首先在github上找到开源PyTorch框架实现的引用多包含预训练的模型代码仓,参考开源模型加载预训练模型的代码加载开源pth权重文件在cpu上导出onnx模型文件,优先使用910训练好的pth权重文件,如果使用910权重需要把训练适配的影响精度的代码移植到开源模型代码。 - - 然后在装有310卡与CANN软件的服务器环境使用Ascend atc模型转换工具将onnx模型转换为om模型文件。 - - 参考开源模型代码数据测试集预处理方法进行预处理。 - - 在310服务器上使用Ascend benchmark工具执行om模型的离线推理。 - - 参考开源模型代码数据后处理部分对om模型输出进行后处理,统计出精度。 - - 最后Ascend benchmark工具也会测试出om模型推理性能,对性能不达标的om模型,使用Ascend profiling工具分析并进行调优。 - + 1. 获取预训练权重:基于基于Ascend 910训练好的pth权重文件或者开源代码仓提供的模型权重(优先) + + 2. 基于pth权重文件在**cpu**上导出onnx模型文件 + + 3. 转换om模型:需要在Ascend310环境上基于Ascend atc模型转换工具将onnx模型转换为om模型文件 + + 4. 数据预处理:参考开源模型代码数据测试集预处理方法进行预处理 + + 5. 离线推理:在Ascend 310服务器上使用Ascend benchmark工具执行om模型的离线推理 + + 6. 后处理(精度/性能统计):参考开源模型代码数据后处理部分对om模型输出结果进行后处理,统计精度;5.步骤中,Ascend benchmark工具也会测试出om模型推理性能,可以从日志中解析得到。 + + 补充步骤: + + - 对性能不达标的模型,需要借助Ascend profiling工具分析并进行调优。 + ![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/images/Ascend_PyTorch_offlineinfer_process.png) @@ -56,18 +69,15 @@ **开发者需要根据[机器申请与使用指南](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86-%E6%9C%BA%E5%99%A8%E7%94%B3%E8%AF%B7%E4%B8%8E%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md)的要求申请与使用机器** -深度学习框架与第三方库 +深度学习框架与第三方库(仅作参考) ``` python3.7.5 - pytorch >= 1.5.0 torchvision == 0.6.0 onnx == 1.7.0 - onnxruntime onnxoptimizer - numpy Pillow opencv-python @@ -103,14 +113,14 @@ opencv-python - 获取开源PyTorch模型代码与权重文件 基于开源PyTorch框架的EfficientNet开源模型代码与pth权重文件可以从[github网址](https://github.com/lukemelas/EfficientNet-PyTorch)获取 >![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/public_sys-resources/icon-note.gif) -**说明:** -> 1.如果Ascend 910训练提供了PyTorch模型代码与权重文件,那么优先使用910训练的代码与权重做离线推理,然后om模型精度对齐训练权重的精度 +**说明:** +> 1.如果Ascend 910训练提供了PyTorch模型代码与权重文件,那么优先使用910训练的代码与权重做离线推理,om模型精度对齐训练权重的精度 > 2.否则在github上找到pytorch实现的尽可能是模型作者的或引用量最多的与提供pth权重文件的开源模型代码仓 > 3.如果开源代码仓提供了多个pth权重文件,使用常用的基础的那个配置的权重文件即可,并且模型支持多任务时只需要针对一个基础的任务做推理 > 4.如果开源代码仓没有提供pth权重文件,需要暂时使用开源代码仓训练脚本简单训练一个权重,然后om模型精度对齐pth权重在线推理的精度 参考github网址说明安装efficientnet_pytorch -``` +```shell git clone https://github.com/lukemelas/EfficientNet-PyTorch cd EfficientNet-Pytorch pip3.7 install -e . @@ -121,220 +131,138 @@ pip3.7 install -e . > sys.path.append(r"./EfficientNet-PyTorch") > from efficientnet_pytorch import EfficientNet - [下载pth权重文件](https://github.com/lukemelas/EfficientNet-PyTorch/releases/download/1.0/efficientnet-b0-355c32eb.pth) -``` +```sh wget https://github.com/lukemelas/EfficientNet-PyTorch/releases/download/1.0/efficientnet-b0-355c32eb.pth ``` - 导出onnx模型 - - 一般模型代码仓提供导出onnx的脚本,如果没有提供则需要调用onnx的torch.onnx.export接口导出onnx。参考EfficientNet-PyTorch模型预训练加载与导出onnx的代码,写脚本导出onnx。 -``` + + 一般模型代码仓提供导出onnx的脚本,如果没有提供则需要调用onnx的`torch.onnx.export`接口导出onnx。以下以EfficientNet-PyTorch为例: +```python def pth2onnx(input_file, output_file): model = EfficientNet.from_pretrained('efficientnet-b0', weights_path=input_file) - model.eval() - input_names = ["image"] - output_names = ["class"] - dynamic_axes = {'image': {0: '-1'}, 'class': {0: '-1'}} + # 调整模型为eval mode + model.eval() + # 输入节点名 + input_names = ["image"] + # 输出节点名 + output_names = ["class"] + dynamic_axes = {'image': {0: '-1'}, 'class': {0: '-1'}} dummy_input = torch.randn(1, 3, 224, 224) - torch.onnx.export(model, dummy_input, output_file, input_names = input_names, dynamic_axes = dynamic_axes, output_names = output_names, opset_version=11, verbose=True) + # verbose=True,支持打印onnx节点和对应的PyTorch代码行 + torch.onnx.export(model, dummy_input, output_file, input_names = input_names, dynamic_axes = dynamic_axes, output_names = output_names, opset_version=11, verbose=True) ``` >![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/public_sys-resources/icon-note.gif) **说明:** -> 目前atc工具支持的onnx算子版本opset_version为11 +> > 如上导出的onnx模型使用netron查看其输入节点image的shape是(-1,3,224,224),-1代表onnx模型是动态batch的,当用tensorrt在t4上测onnx的性能时可以指定任意batch的输入(batch,3,224,224),dynamic_axes是动态batch参数,'image': {0: '-1'}表示输入image的第一维是-1即batch维为-1表示动态。 -> 当然像少数模型如shufflenetv1即使设置dynamic_axes实际上导出的onnx也是固定batch的,转换为om时指定的batch size要和onnx的固定batch一样才不会报错 +> 少数模型如shufflenetv1即使设置dynamic_axes实际上导出的onnx也是固定batch的,转换为om时需要各自固定不同的batch size > 导出onnx出现如下错误Exporting the operator eye to ONNX opset version 11 is not supported,可以参考[issue](https://github.com/pytorch/pytorch/pull/41357)进行修改 - 目前atc不支持efficientnet-b0模型中自定义pad算子,使用开源提供的模型可视化工具Netron可以看到这部分算子名称与连接关系,使用脚本删除自定义pad并使用卷积中的pad属性实现同样的功能: -``` -import onnx -model = onnx.load("./efficientnet-b0.onnx") -model.graph.node[15].input[0] = 'image' -model.graph.node[34].input[0] = '388' -model.graph.node[66].input[0] = '429' -model.graph.node[98].input[0] = '470' -model.graph.node[131].input[0] = '512' -model.graph.node[163].input[0] = '553' -model.graph.node[196].input[0] = '595' -model.graph.node[228].input[0] = '636' -model.graph.node[261].input[0] = '678' -model.graph.node[294].input[0] = '720' -model.graph.node[326].input[0] = '761' -model.graph.node[359].input[0] = '803' -model.graph.node[392].input[0] = '845' -model.graph.node[424].input[0] = '886' -model.graph.node[457].input[0] = '928' -model.graph.node[490].input[0] = '970' -model.graph.node[523].input[0] = '1012' -delete_id_range = [[0, 14], [19, 33], [51, 65], [83, 97], [116, 130], [148, 162], [181, 195], - [213, 227], [246, 260], [279, 293], [311, 325], [344, 358], [377, 391], - [409, 423], [442, 456], [475, 489], [508, 522]] -modify_ids = [15, 34, 66, 98, 131, 163, 196, 228, 261, 294, 326, 359, 392, 424, 457, 490, 523] -def indelrang(id): - for start, end in delete_id_range: - if id >= int(start) and id <= int(end): - return True -return False -max_idx = len(model.graph.node) -rm_cnt = 0 -for i in range(max_idx): - if indelrang(i): - n = model.graph.node[i - rm_cnt] - model.graph.node.remove(n) - print("remove {} total {}".format(n.name, len(model.graph.node))) - rm_cnt += 1 - else: - if i in modify_ids: - kh = model.graph.node[i - rm_cnt].attribute[2].ints[1] - if kh % 2 != 0: - pad = (kh - 1) // 2 - else: - pad = kh // 2 - model.graph.node[i - rm_cnt].attribute[3].ints[0] = pad - model.graph.node[i - rm_cnt].attribute[3].ints[1] = pad - model.graph.node[i - rm_cnt].attribute[3].ints[2] = pad - model.graph.node[i - rm_cnt].attribute[3].ints[3] = pad - print("adapt pad for", model.graph.node[i - rm_cnt].name) -onnx.checker.check_model(model) -onnx.save(model, "./efficientnet-b0_adaptpad.onnx") -``` -但是由于强行更改了pad没有重新训练会导致精度下降2%,使用如下脚本可以测试onnx模型推理的精度: -``` -import os, sys -from PIL import Image -import numpy as np -import torch -import onnx -import onnxruntime -class ONNXModel(): - def __init__(self, onnx_path): - self.onnx_session = onnxruntime.InferenceSession(onnx_path) - self.input_name = self.get_input_name(self.onnx_session) - self.output_name = self.get_output_name(self.onnx_session) - def get_output_name(self, onnx_session): - output_name = [] - for node in onnx_session.get_outputs(): - output_name.append(node.name) - return output_name - def get_input_name(self, onnx_session): - input_name = [] - for node in onnx_session.get_inputs(): - input_name.append(node.name) - return input_name - def get_input_feed(self, input_name, image_numpy): - input_feed = {} - for name in input_name: - input_feed[name] = image_numpy - return input_feed - def forward(self, image_numpy): - input_feed = self.get_input_feed(self.input_name, image_numpy) - segmap = self.onnx_session.run(self.output_name, input_feed=input_feed) - return segmap -def to_numpy(tensor): - return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy() -if __name__ == '__main__': - model_file = sys.argv[1]#'/home/adapt/efficientnet/efficientnet-b0.onnx' - prep_file_path = sys.argv[2]#'/home/adapt/efficientnet/prep_dataset' - output_path = sys.argv[3]#'/home/adapt/efficientnet/infer_onnx_res' - if not os.path.exists(output_path): - os.makedirs(output_path) - net = ONNXModel(model_file) - files = os.listdir(prep_file_path) - for file in files: - img = np.fromfile(os.path.join(prep_file_path, file), dtype='float32') - img = np.reshape(img, (3, 224, 224)) - img = torch.from_numpy(img) - img = img.unsqueeze(0) - output = net.forward(to_numpy(img))[0] - output = np.array(output) - np.savetxt(os.path.join(output_path, file.split('.')[0] + '_1.txt'), output, fmt='%.6f') -``` -实际的解决方法是使用onnxsim去除自定义pad并优化网络: +可以使用`onnxsim`去除自定义pad并优化网络: ``` pip3.7 install onnx-simplifier python3.7 -m onnxsim --input-shape="1,3,224,224" --dynamic-input-shape efficientnet-b0.onnx efficientnet-b0_sim.onnx ``` - - 这里也总结一些其它模型遇到的问题。导出onnx是需要可以在cpu上执行的脚本,因此可能需要将权重map到cpu。如果gpu训练时使用了DataParallel,map到cpu上时模型结构需要去掉DataParallel,同时删除权重节点名前缀module.: -``` -def proc_nodes_module(checkpoint): - new_state_dict = OrderedDict() - for k, v in checkpoint.items(): - if "module." in k: - name = k.replace("module.", "") - else: - name = k - new_state_dict[name] = v - return new_state_dict -net = DnCNN(channels=1, num_of_layers=opt.num_of_layers) -model = net #model = nn.DataParallel(net, device_ids=device_ids).cuda() -checkpoint = torch.load(os.path.join('./', 'net.pth'), map_location='cpu') -checkpoint = proc_nodes_module(checkpoint) -model.load_state_dict(checkpoint) -``` -而对于910训练出的权重文件,删除前缀module.可能需要修改如下: -``` -def proc_nodes_module(checkpoint, AttrName): -... - for k, v in checkpoint[AttrName].items(): -... -checkpoint['state_dict'] = proc_nodes_module(checkpoint, 'state_dict') -... -model.load_state_dict(checkpoint['state_dict']) -``` - - 可视化工具netron可以查看模型图,[获取可视化工具netron](https://github.com/lutzroeder/netron/releases/download/v4.9.5/Netron-Setup-4.9.5.exe),om模型文件与atc工具dump的.pbtxt中间图模型文件也可以用最新版本的netron查看。使用netron可以方便的查看模型结构,权重与算子属性,比如输入节点名与其shape,输出的所有节点名与其shape - - - 有些pytorch算子onnx还不支持,根据开源社区提供的方法等价替换这些算子,如果不能完全等价替换而且npu已经支持该算子,则需要修改模型代码将该算子封装为自定义算子,然后导出包含自定义算子的onnx - - 例如,pytorch代码的adaptive_avg_pool2d目前onnx还不支持,所以导出onnx时报错,解决方案是尝试使用avg_pool2d替换adaptive_avg_pool2d,但当input的最后两维不是output的整数倍时,adaptive_avg_pool2d不能完全等价替换为avg_pool2d,而npu有adaptive_avg_pool2d算子的实现,所以解决方案变为将adaptive_avg_pool2d改为自定义算子导出onnx,自定义算子不需要具体实现代码(因此导出的onnx不能使用onnxruntime进行推理,还需要将pytorch的_check_onnx_proto(proto)改为pass去除导出onnx时进行检查),只要自定义算子返回的输出shape与原算子输出的shape保持一致即可,相当于onnx只包含这个算子的声明(数据类型与属性需要与npu版算子对应),在onnx转为om时,atc工具的onnx插件如果支持该算子,atc工具会根据这个声明找到该算子npu的实现。 +对于onnx模型,可以借助可视化工具[netron](https://github.com/lutzroeder/netron/releases/download/v4.9.5/Netron-Setup-4.9.5.exe)查看模型图,最新版本支持查看:om模型文件/模型转换的中间图模型文件(*.pbtxt)。使用netron可以方便的查看模型结构,权重与算子属性,比如输入节点名与其shape,输出的所有节点名与其shape。 - 在CANN包安装目录的opp下搜索AdaptiveAvgPool2d,查看npu的adaptive_avg_pool2d声明: -``` -REG_OP(AdaptiveAvgPool2d) - .INPUT(x, TensorType({DT_FLOAT, DT_FLOAT16})) - .OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16})) - .REQUIRED_ATTR(output_size, ListInt) - .OP_END_FACTORY_REG(AdaptiveAvgPool2d) -``` -修改模型代码,将adaptive_avg_pool2d改为自定义算子,然后导出onnx,其中output_size_i代表int64类型的算子属性: -``` -class AdaptiveAvgPoolOp(torch.autograd.Function): - @staticmethod - def forward(ctx, x, output_size): - out = torch.randn(x.shape[0], x.shape[1], output_size[0], output_size[1]).to(x.dtype) - return out - @staticmethod - def symbolic(g, x, output_size): - out = g.op('AdaptiveAvgPool2d', x, output_size_i = output_size) +另外补充部分转换onnx过程中常见的问题: + + 1. DataParallel模型加载 + + 导出onnx是需要可以在cpu上执行的脚本,因此可能需要将权重map到cpu。如果gpu训练时使用了DataParallel,map到cpu上时模型结构需要去掉DataParallel,同时删除权重节点名前缀module.: + + ```python + def proc_nodes_module(checkpoint): + new_state_dict = OrderedDict() + for k, v in checkpoint.items(): + if "module." in k: + name = k.replace("module.", "") + else: + name = k + new_state_dict[name] = v + return new_state_dict + net = DnCNN(channels=1, num_of_layers=opt.num_of_layers) + model = net #model = nn.DataParallel(net, device_ids=device_ids).cuda() + checkpoint = torch.load(os.path.join('./', 'net.pth'), map_location='cpu') + checkpoint = proc_nodes_module(checkpoint) + model.load_state_dict(checkpoint) + ``` + + 而对于910训练出的权重文件,删除前缀`module`需要修改如下: + + ```python + def proc_nodes_module(checkpoint, AttrName): + ... + for k, v in checkpoint[AttrName].items(): + ... + checkpoint['state_dict'] = proc_nodes_module(checkpoint, 'state_dict') + ... + model.load_state_dict(checkpoint['state_dict']) + ``` + + + + 2. 转换onnx算子不支持 + + 部分pytorch算子onnx还不支持,根据开源社区提供的方法等价替换这些算子,如果不能完全等价替换而且npu已经支持该算子,则需要修改模型代码将该算子封装为自定义算子,然后导出包含自定义算子的onnx + + 例如,pytorch代码的adaptive_avg_pool2d目前onnx还不支持,所以导出onnx时报错,解决方案是尝试使用avg_pool2d替换adaptive_avg_pool2d,但当input的最后两维不是output的整数倍时,adaptive_avg_pool2d不能完全等价替换为avg_pool2d,而npu有adaptive_avg_pool2d算子的实现,所以解决方案变为将adaptive_avg_pool2d改为自定义算子导出onnx,自定义算子不需要具体实现代码(因此导出的onnx不能使用onnxruntime进行推理,还需要将pytorch的_check_onnx_proto(proto)改为pass去除导出onnx时进行检查),只要自定义算子返回的输出shape与原算子输出的shape保持一致即可,相当于onnx只包含这个算子的声明(数据类型与属性需要与npu版算子对应),在onnx转为om时,atc工具的onnx插件如果支持该算子,atc工具会根据这个声明找到该算子npu的实现。 + + 在CANN包安装目录的opp下搜索`AdaptiveAvgPool2d`,查看npu的`adaptive_avg_pool2d`声明: + + ```c++ + REG_OP(AdaptiveAvgPool2d) + .INPUT(x, TensorType({DT_FLOAT, DT_FLOAT16})) + .OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16})) + .REQUIRED_ATTR(output_size, ListInt) + .OP_END_FACTORY_REG(AdaptiveAvgPool2d) + ``` + + 修改模型代码,将`adaptive_avg_pool2d`改为自定义算子,然后导出onnx,其中`output_size_i`代表int64类型的算子属性: + + ```python + class AdaptiveAvgPoolOp(torch.autograd.Function): + @staticmethod + def forward(ctx, x, output_size): + out = torch.randn(x.shape[0], x.shape[1], output_size[0], output_size[1]).to(x.dtype) + return out + @staticmethod + def symbolic(g, x, output_size): + out = g.op('AdaptiveAvgPool2d', x, output_size_i = output_size) + return out + def adaptive_avg_pool_op(x, output_size): + out = AdaptiveAvgPoolOp.apply(x, output_size) return out -def adaptive_avg_pool_op(x, output_size): - out = AdaptiveAvgPoolOp.apply(x, output_size) - return out -x = F.adaptive_avg_pool2d(input, output_size=bin_size)替换为x = adaptive_avg_pool_op(input, (bin_size, bin_size)) -``` + ``` + + 3. 小算子动态shape场景 + + 目标检测类网络nms与roi模块会将包含的许多动态shape小算子引入onnx,但是atc工具暂不支持动态shape的算子,解决方案是使用大颗粒npu的nms与roi自定义算子替换pytorch模型的nms与roi函数(这些小算子可以在转onnx时的verbose打印中找到其对应的pytorch模型代码,从而找到引入这些算子的函数)。参见[mmdetection框架的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90mmdetection%E9%A2%84%E8%AE%AD%E7%BB%83%E7%9A%84maskrcnn%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) + + 4. PyTorch版本不适配 + + 典型案例:开源detectron2目前仅支持pytorch1.8导出onnx,但是基于detectron2框架Ascend 910训练的模型代码依赖华为npu版的pytorch1.5.0。但是,无论cpu,gpu还是npu训练出的权重都是数值,只要保存权重的网络节点结构相同,就可以使用开源的detectron2加载npu训练的权重基于pytorch1.8导出onnx。参见[detectron2框架npu训练的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90detectron2%E8%AE%AD%E7%BB%83%E7%9A%84npu%E6%9D%83%E9%87%8D%E7%9A%84maskrcnn%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) - - 目标检测类网络nms与roi模块会将包含的许多动态shape小算子引入onnx,但是atc工具暂不支持动态shape的算子,解决方案是使用大颗粒npu的nms与roi自定义算子替换pytorch模型的nms与roi函数(这些小算子可以在转onnx时的verbose打印中找到其对应的pytorch模型代码,从而找到引入这些算子的函数)。参见[mmdetection框架的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90mmdetection%E9%A2%84%E8%AE%AD%E7%BB%83%E7%9A%84maskrcnn%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) - - - 开源detectron2目前仅支持pytorch1.8导出onnx,但是基于detectron2框架ascend 910训练的模型代码依赖华为npu版的pytorch1.5.0,无论cpu,gpu还是npu训练出的权重都是数值,只要保存权重的网络节点结构相同,就可以使用开源的detectron2加载npu训练的权重基于pytorch1.8导出onnx。参见[detectron2框架npu训练的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90detectron2%E8%AE%AD%E7%BB%83%E7%9A%84npu%E6%9D%83%E9%87%8D%E7%9A%84maskrcnn%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) - #### 2.1.2 转换为om模型 - 使用Ascend atc工具将onnx转换为om -``` -CANN安装目录 +```shell +# CANN安装目录 export install_path=/usr/local/Ascend/ascend-toolkit/latest export PATH=/usr/local/python3.7.5/bin:${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH export PYTHONPATH=${install_path}/atc/python/site-packages:$PYTHONPATH export LD_LIBRARY_PATH=${install_path}/atc/lib64:${install_path}/acllib/lib64:$LD_LIBRARY_PATH export ASCEND_OPP_PATH=${install_path}/opp export ASCEND_AICPU_PATH=/usr/local/Ascend/ascend-toolkit/latest -将atc日志打印到屏幕 +# 将atc日志打印到屏幕 #export ASCEND_SLOG_PRINT_TO_STDOUT=1 -设置日志级别 +# 设置日志级别 #export ASCEND_GLOBAL_LOG_LEVEL=0 #debug 0 --> info 1 --> warning 2 --> error 3 -开启ge dump图 +# 开启ge dump图 #export DUMP_GE_GRAPH=2 -参考命令 +# 参考命令 atc --framework=5 --model=efficientnet-b0_sim.onnx --output=efficientnet-b0_bs16 --input_format=NCHW --input_shape="image:16,3,224,224" --log=debug --soc_version=Ascend310 ``` >![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/public_sys-resources/icon-note.gif) @@ -353,152 +281,128 @@ atc --framework=5 --model=efficientnet-b0_sim.onnx --output=efficientnet-b0_bs16 #### 2.1.3 测试集预处理 - 参考模型代码仓在验证集上评测精度的推理脚本里的数据预处理代码进行预处理脚本的编写 - - 请确保预处理脚本的数据集预处理方法与代码仓评测精度的脚本采用的预处理方法保持一致,通常包括减均值除方差,缩放加pad、中心裁剪,除以255,nhwc转换为nchw,rgb转换为bgr等 - - ImageNet官网的5万张验证集图片与标签分别是datasets/ImageNet/val_union与datasets/ImageNet/val_label.txt。预处理有两种方式:不使用aipp的二进制输入即需要编写预处理脚本处理数据集,以获得最佳精度;使用aipp的jpg输入可以直接读取原图即硬件进行预处理,需要使用昇腾开发的DVPP模块和AIPP模块,因为解码、缩放等处理和官网训练预处理有一定区别,最终精度可能会下降0.7%左右。因此本文推荐使用不使用aipp进行预处理,这里给出的使用aipp进行预处理的方法用作学习 + - 请确保预处理脚本的数据集预处理方法与代码仓评测精度的脚本采用的预处理方法保持一致,通常包括减均值除方差,缩放加pad、中心裁剪,Normalize,数据类型转换等 + + 本案例中,数据集为[ImageNet](https://www.image-net.org/),含有5万张验证集图片与标签分别是datasets/ImageNet/val_union与datasets/ImageNet/val_label.txt。预处理有两种方式: + + - 不使用`aipp`的二进制输入:需要编写预处理脚本处理数据集,以获得最佳精度; + - 使用`aipp`的输入:`aipp`为华为自研的图像处理库,支持直接读取原图并基于硬件进行预处理,需要使用昇腾开发的DVPP模块和AIPP模块。但是因为解码、缩放等处理和官网训练预处理有一定区别,最终精度会有一定下降(但是性能会有一定提升)。基于保持精度的原则,本文推荐不使用`aipp`进行预处理,这里给出的使用`aipp`进行预处理的方法可用作学习 + +1. 不使用`aipp`进行预处理 + + 参考EfficientNet-PyTorch中的预处理代码,通过缩放、中心裁剪,totensor、均值方差归一化,输出为二进制文件: + + ```python + def preprocess(src_path, save_path): - 1.不使用aipp进行预处理,参考EfficientNet-PyTorch中的预处理代码,通过缩放、中心裁剪,totensor、均值方差归一化,输出为二进制文件: - ``` - def preprocess(src_path, save_path): - - preprocess = transforms.Compose([ - transforms.Resize(256, Image.BICUBIC), - transforms.CenterCrop(224), - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), - ]) - - i = 0 - in_files = os.listdir(src_path) - for file in in_files: - i = i + 1 - print(file, "===", i) - input_image = Image.open(src_path + '/' + file).convert('RGB') - input_tensor = preprocess(input_image) - img = np.array(input_tensor).astype(np.float32) - img.tofile(os.path.join(save_path, file.split('.')[0] + ".bin")) - ``` - ``` - python3.7 imagenet_torch_preprocess.py datasets/ImageNet/val_union ./prep_dataset + preprocess = transforms.Compose([ + transforms.Resize(256, Image.BICUBIC), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ]) + + in_files = os.listdir(src_path) + for idx, file in enumerate(in_files): + idx = idx + 1 + print(file, "===", idx) + input_image = Image.open(src_path + '/' + file).convert('RGB') + input_tensor = preprocess(input_image) + img = np.array(input_tensor).astype(np.float32) + img.tofile(os.path.join(save_path, file.split('.')[0] + ".bin")) + ``` + + + +2. 使用`aipp`进行预处理 + + 其功能原理为:通过DVPP实现解码、缩放功能,输出YUV数据,再通过AIPP进行色域转换及裁剪,最终直接输入网络中进行推理,方便快捷。benchmark工具已集成DVPP功能,只需添加命令行参数`-useDvpp=true`开启DVPP。AIPP功能的开启需要在atc工具转换的过程中通过选项`--insert_op_conf=xxx.config`添加配置文件,即可与DVPP处理后的数据无缝对接。AIPP配置可以参考[CANN 5.0.1 开发辅助工具指南 (推理) 01](https://support.huawei.com/enterprise/zh/doc/EDOC1100191944?idPath=23710424%7C251366513%7C22892968%7C251168373),本文案例配置文件示例`aipp_efficientnet-b0_pth.config`: + + ```sh + aipp_op{ + aipp_mode:static + input_format : YUV420SP_U8 + + csc_switch : true + //是true还是false请根据精度尝试 + rbuv_swap_switch : true + + //缩放,中心裁剪,aipp源图大小为16的倍数,偏移与裁剪大小尽量为偶数,若因此导致aipp裁剪图片大小与模型预训练使用的图片裁剪大小有几个像素的差值,使用aipp的端到端推理流程中需要将涉及到模型输入大小的地方调整为aipp裁剪图片大小,使用源图大小的地方调整为aipp源图大小,这可能会引起约0.7%的精度下降 + src_image_size_w : 256 + src_image_size_h : 256 + crop: true + load_start_pos_h : 16 + load_start_pos_w : 16 + crop_size_w : 224 + crop_size_h: 224 + + //均值:255x[0.485, 0.456, 0.406],方差:1/(255x[0.229, 0.224, 0.225]) + min_chn_0 : 123.675 + min_chn_1 : 116.28 + min_chn_2 : 103.53 + var_reci_chn_0: 0.0171247538316637 + var_reci_chn_1: 0.0175070028011204 + var_reci_chn_2: 0.0174291938997821 + + //输入为RGB序时的设置 + matrix_r0c0: 256 + matrix_r0c1: 0 + matrix_r0c2: 359 + matrix_r1c0: 256 + matrix_r1c1: -88 + matrix_r1c2: -183 + matrix_r2c0: 256 + matrix_r2c1: 454 + matrix_r2c2: 0 + input_bias_0: 0 + input_bias_1: 128 + input_bias_2: 128 + } + ``` + +最后,需要将生成预处理数据,生成对应的`info`文件,作为benchmark工具推理的输入(样例脚本:[gen_dataset_info.py](https://gitee.com/ascend/modelzoo/blob/master/contrib/ACL_PyTorch/Research/cv/classfication/EfficientNet-B3/gen_dataset_info.py))): + + ```shell + # 预处理数据为jpg格式 + python3.7 get_info.py jpg ./prep_dataset ./efficientnet_prep_jpg.info 224 224 + # 预处理数据为bin格式,本例采用 python3.7 get_info.py bin ./prep_dataset ./efficientnet_prep_bin.info 224 224 ``` 预处理后的数据集信息文件efficientnet_prep_bin.info: -``` +```shell +# 第一列为样本序号,第二列为预处理后的样本路径,第三四列为预处理后样本的宽高 0 ./prep_dataset/ILSVRC2012_val_00005654.bin 224 224 1 ./prep_dataset/ILSVRC2012_val_00033427.bin 224 224 2 ./prep_dataset/ILSVRC2012_val_00004213.bin 224 224 ... ``` -第一列为样本序号,第二列为预处理后的样本路径,第三四列为预处理后样本的宽高 -2.使用AIPP进行预处理,通过DVPP实现解码、缩放功能,输出YUV数据,再通过AIPP进行色域转换及裁剪,最终直接输入网络中进行推理,方便快捷,benchmark工具已集成DVPP功能,只需添加命令行参数-useDvpp=true开启DVPP。AIPP功能的开启需要在atc工具转换的过程中通过选项--insert_op_conf=xxx.config添加配置文件,即可与DVPP处理后的数据无缝对接。AIPP配置可以参考[CANN 5.0.1 开发辅助工具指南 (推理) 01](https://support.huawei.com/enterprise/zh/doc/EDOC1100191944?idPath=23710424%7C251366513%7C22892968%7C251168373),aipp_efficientnet-b0_pth.config: -``` -aipp_op{ - aipp_mode:static - input_format : YUV420SP_U8 - - csc_switch : true - //是true还是false请根据精度尝试 - rbuv_swap_switch : true - - //缩放,中心裁剪,aipp源图大小为16的倍数,偏移与裁剪大小尽量为偶数,若因此导致aipp裁剪图片大小与模型预训练使用的图片裁剪大小有几个像素的差值,使用aipp的端到端推理流程中需要将涉及到模型输入大小的地方调整为aipp裁剪图片大小,使用源图大小的地方调整为aipp源图大小,这可能会引起约0.7%的精度下降 - src_image_size_w : 256 - src_image_size_h : 256 - crop: true - load_start_pos_h : 16 - load_start_pos_w : 16 - crop_size_w : 224 - crop_size_h: 224 - - //均值:255x[0.485, 0.456, 0.406],方差:1/(255x[0.229, 0.224, 0.225]) - min_chn_0 : 123.675 - min_chn_1 : 116.28 - min_chn_2 : 103.53 - var_reci_chn_0: 0.0171247538316637 - var_reci_chn_1: 0.0175070028011204 - var_reci_chn_2: 0.0174291938997821 - - //输入为RGB序时的设置 - matrix_r0c0: 256 - matrix_r0c1: 0 - matrix_r0c2: 359 - matrix_r1c0: 256 - matrix_r1c1: -88 - matrix_r1c2: -183 - matrix_r2c0: 256 - matrix_r2c1: 454 - matrix_r2c2: 0 - input_bias_0: 0 - input_bias_1: 128 - input_bias_2: 128 -} -``` -``` -python3.7 get_info.py jpg datasets/ImageNet/val_union ImageNet.info -``` -查看ImageNet.info: -``` -0 datasets/ImageNet/val_union/ILSVRC2012_val_00005654.jpeg 500 334 -1 datasets/ImageNet/val_union/ILSVRC2012_val_00033427.jpeg 500 334 -2 datasets/ImageNet/val_union/ILSVRC2012_val_00004213.jpeg 116 87 -... +以上为CV场景模型的输入格式,另外补充NLP场景的输入格式如下: + +```shell +# 第一列为样本序号,第二列为输入节点名_{序号}.bin +0 ./bin/input_ids_0.bin +0 ./bin/attention_mask_0.bin +0 ./bin/token_type_ids_0.bin +1 ./bin/input_ids_1.bin +1 ./bin/attention_mask_1.bin +1 ./bin/token_type_ids_1.bin ``` + + + +另外,部分PyTorch模型支持动态长宽的输入,可以基于固定尺寸得到精度误差范围内的模型,相关前后处理操作可参考:[detectron2框架npu权重的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90detectron2%E8%AE%AD%E7%BB%83%E7%9A%84npu%E6%9D%83%E9%87%8D%E7%9A%84maskrcnn%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) + >![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/public_sys-resources/icon-note.gif) **说明:** > -> 这里只给出示例代码,前后处理,配置与评价等脚本来源: +> 上文示例代码,前后处理,配置与评价等脚本来源: > 1.[gitee benchmark 脚本](https://gitee.com/ascend/cann-benchmark/tree/master/infer/src/scripts) > 2.[modelzoo案例](https://www.hiascend.com/zh/software/modelzoo) - - 此外对于一些其它模型,pytorch模型支持动态hw的输入,但是onnx模型输入shape的hw维是固定的,因此图片预处理的等比例缩放加pad不会与代码仓的完全一致,但是处理的合理的话对精度影响仅在0.5%之内。如果代码仓导出onnx的脚本或评测精度的脚本推荐了hw,就使用该hw。否则为了对于胖矮型与瘦高型图片不敏感,让预处理后的h与w相同,在评测脚本model推理数据集前添加打印出输入shape的hw维,根据结果为hw选择一个合适的值,或者根据代码仓推理预处理限定的最长边最短边与pad为hw选择一个合适的值,然后将图片按最长边比例将图片等比例缩放到该值,最短边两边补齐pad达到该值。 -以mmdetection框架的maskrcnn预处理为例,由于pytorch模型支持动态hw的输入,参考代码仓转onnx的脚本需要将onnx模型输入h维固定为800,w维固定为1216,通过等比例缩放加pad固定模型预处理后的输入样本的hw维为800,1216: -``` -def resize(img, size): - org_h = img.shape[0] - org_w = img.shape[1] - scale_ratio = min(size[0] / org_w, size[1] / org_h) - new_w = int(np.floor(org_w * scale_ratio)) - new_h = int(np.floor(org_h * scale_ratio)) - resized_img = mmcv.imresize(img, (new_w, new_h), backend='cv2') - return resized_img -def gen_input_bin(file_batches, batch): - for file in file_batches[batch]: - image = mmcv.imread(os.path.join(flags.image_src_path, file), backend='cv2') - image = resize(image, (flags.model_input_width, flags.model_input_height)) - mean = np.array([123.675, 116.28, 103.53], dtype=np.float32) - std = np.array([58.395, 57.12, 57.375], dtype=np.float32) - image = mmcv.imnormalize(image, mean, std) - rh = image.shape[0] - rw = image.shape[1] - pad_left = (flags.model_input_width - rw) // 2 - pad_top = (flags.model_input_height - rh) // 2 - pad_right = flags.model_input_width - pad_left - rw - pad_bottom = flags.model_input_height - pad_top - rh - image = mmcv.impad(image, padding=(pad_left, pad_top, pad_right, pad_bottom), pad_val=0) - image = image.transpose(2, 0, 1) - image.tofile(os.path.join(flags.bin_file_path, file.split('.')[0] + ".bin")) -``` -相应的后处理: -``` -def postprocess_bboxes(bboxes, image_size, net_input_width, net_input_height): - org_w = image_size[0] - org_h = image_size[1] - scale = min(net_input_width / org_w, net_input_height / org_h) - pad_w = net_input_width - org_w * scale - pad_h = net_input_height - org_h * scale - pad_left = pad_w // 2 - pad_top = pad_h // 2 - bboxes[:, 0] = (bboxes[:, 0] - pad_left) / scale - bboxes[:, 1] = (bboxes[:, 1] - pad_top) / scale - bboxes[:, 2] = (bboxes[:, 2] - pad_left) / scale - bboxes[:, 3] = (bboxes[:, 3] - pad_top) / scale - return bboxes -``` -基于detectron2框架的预处理参见[detectron2框架npu权重的maskrcnn](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/ONNX%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/benchmark/cv/segmentation/%E5%9F%BA%E4%BA%8E%E5%BC%80%E6%BA%90detectron2%E8%AE%AD%E7%BB%83%E7%9A%84npu%E6%9D%83%E9%87%8D%E7%9A%84maskrcnn%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC.md) - #### 2.1.4 离线推理 benchmark工具为华为自研的模型推理工具,支持多种模型的离线推理,能够迅速统计出模型在Ascend310上的性能,支持真实数据和纯推理两种模式,配合后处理脚本,可以实现诸多模型的端到端过程,获取工具及使用方法可以参考[CANN 5.0.1 推理benchmark工具用户指南 01](https://support.huawei.com/enterprise/zh/doc/EDOC1100191895?idPath=23710424%7C251366513%7C22892968%7C251168373) - 二进制输入 -``` +```shell ./benchmark.x86_64 -model_type=vision -device_id=0 -batch_size=1 -om_path=efficientnet-b0_bs1.om -input_text_path=./efficientnet_prep_bin.info -input_width=224 -input_height=224 -output_binary=False -useDvpp=False ``` >![](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/raw/master/public_sys-resources/icon-note.gif) @@ -514,21 +418,23 @@ benchmark工具为华为自研的模型推理工具,支持多种模型的离 > -output_binary为以预处理后的数据集为输入,benchmark工具推理om模型的输出数据保存为二进制还是txt,但对于输出是int64类型的节点时,指定输出为txt时会将float类型的小数转换为0而出错 > -useDvpp为是否使用aipp进行数据集预处理 - 输出结果默认保存在当前目录result/dumpOutput_device{device_id},性能数据默认保存在result/perf_{vision}_batchsize_{16}_device_{0}.txt。模型只有一个名为class的输出,shape为bs * 1000,数据类型为FP32,对应1000个分类的预测结果,每个输入的输出对应一个{input}_1.bin文件。此外,如果模型有三个输出,则三个输出分别对应{input}_1.bin,{input}_2.bin,{input}_3.bin。 +输出结果默认保存在当前目录result/dumpOutput_device{device_id};性能数据默认保存在result/perf{vision}batchsize{16}device{0}.txt。 + +对于本案例,模型只有一个名为class的输出,shape为bs * 1000,数据类型为FP32,对应1000个分类的预测结果,每个输入的输出对应一个{input}1.bin文件。对于多输出模型,如有三个输出,则三个输出分别对应{input}1.bin,{input}2.bin,{input}_3.bin。 - jpg输入 -``` +```shell ./benchmark.x86_64 -model_type=vision -device_id=0 -batch_size=1 -om_path=efficientnet-b0_bs1.om -input_text_path=ImageNet.info -input_width=256 -input_height=256 -output_binary=False -useDvpp=true ``` ImageNet.info为图片信息,注意这里的“input_height”和“input_weight”与AIPP节点输入一致,值为256因为AIPP中做了裁剪,参数-useDvpp=true。 #### 2.1.5 精度统计 - 参考模型代码仓评测精度的脚本,编写后处理统计精度的脚本,然后评测om模型的精度: -``` +```shell python3.7 imagenet_acc_eval.py result/dumpOutput_device0/ datasets/imagenet/val_label.txt ./ result.json ``` -将om输出与数据集标签对比,统计出精度: -``` +将om输出与数据集标签对比,统计出精度,最后输出结果如下: +```json {"key": "Top1 accuracy", "value": "76.76%"} {"key": "Top5 accuracy", "value": "93.2%"} ``` 对bs1与bs16的om模型进行精度评测,与pth权重文件的精度相比,下降不超过1%,故精度达标 @@ -536,67 +442,85 @@ python3.7 imagenet_acc_eval.py result/dumpOutput_device0/ datasets/imagenet/val_ #### 2.1.6 性能对比 - npu性能数据 - benchmark工具在整个数据集上推理时也会统计性能数据,但是推理整个数据集较慢,如果这么测性能那么整个推理期间需要确保独占device。为快速获取性能数据,也可以使用benchmark纯推理功能测得性能数据,但是由于随机数不能模拟数据分布,纯推理功能测的有些模型性能数据可能不太准。这里给出两种方式,benchmark纯推理功能测性能仅为快速获取大概的性能数据以便调试优化使用,模型的性能以使用benchmark工具在整个数据集上推理得到bs1与bs16的性能数据为准。 +benchmark工具在整个数据集上推理时也会统计性能数据,但是推理整个数据集较慢,如果这么测性能那么整个推理期间需要确保独占device。为快速获取性能数据,也可以使用benchmark纯推理功能测得性能数据,但是由于随机数不能模拟数据分布,纯推理功能测的有些模型性能数据可能不太准。 - 1.benchmark工具在整个数据集上推理获得性能数据 +这里给出两种方式,benchmark纯推理功能测性能仅为**快速获取大概的性能数据以便调试优化使用**,模型最终交付性能以**使用benchmark工具在整个数据集上推理得到的性能数据为准**。 - batch1的性能,benchmark工具在整个数据集上推理后生成result/perf_vision_batchsize_1_device_0.txt: -``` -[e2e] throughputRate: 243.034, latency: 205733 -[data read] throughputRate: 258.963, moduleLatency: 3.86155 -[preprocess] throughputRate: 258.404, moduleLatency: 3.86991 -[infer] throughputRate: 244.435, Interface throughputRate: 382.328, moduleLatency: 3.35758 -[post] throughputRate: 244.435, moduleLatency: 4.09107 -``` -Interface throughputRate: 382.328,382.328x4=1529.312既是batch1 310单卡吞吐率 -bs1 310单卡吞吐率:382.328x4=1529.312fps/card +**注意**: 测试npu性能要确保device空闲,使用npu-smi info命令可查看device是否在运行其它推理任务 - batch16的性能,benchmark工具在整个数据集上推理后生成result/perf_vision_batchsize_16_device_1.txt: -``` -[e2e] throughputRate: 173.173, latency: 288729 -[data read] throughputRate: 174.62, moduleLatency: 5.72673 -[preprocess] throughputRate: 174.357, moduleLatency: 5.73535 -[infer] throughputRate: 173.844, Interface throughputRate: 519.634, moduleLatency: 3.36724 -[post] throughputRate: 10.865, moduleLatency: 92.0383 -``` -bs16 310单卡吞吐率:519.634x4=2078.536fps/card -2.benchmark纯推理功能测得性能数据 +1. benchmark工具基于数据集推理 - batch1性能: - 测试npu性能要确保device空闲,使用npu-smi info命令可查看device是否在运行其它推理任务 -``` -./benchmark.x86_64 -round=20 -om_path=efficientnet-b0_bs1.om -device_id=0 -batch_size=1 -``` -执行20次纯推理取均值,统计吞吐率与其倒数时延(benchmark的时延是单个数据的推理时间),npu性能是一个device执行的结果 -``` -[INFO] Dataset number: 19 finished cost 2.635ms -[INFO] PureInfer result saved in ./result/PureInfer_perf_of_efficientnet-b0_bs1_in_device_0.txt ------------------PureInfer Performance Summary------------------ -[INFO] ave_throughputRate: 374.313samples/s, ave_latency: 2.67914ms -``` -bs1 310单卡吞吐率:374.313x4=1497.252fps/card + batch1的性能,benchmark工具在整个数据集上推理后生成result/perf_vision_batchsize_1_device_0.txt: + + ```shell + [e2e] throughputRate: 243.034, latency: 205733 + [data read] throughputRate: 258.963, moduleLatency: 3.86155 + [preprocess] throughputRate: 258.404, moduleLatency: 3.86991 + [infer] throughputRate: 244.435, Interface throughputRate: 382.328, moduleLatency: 3.35758 + [post] throughputRate: 244.435, moduleLatency: 4.09107 + ``` + + Interface throughputRate: 382.328,当前设备为4核,所以382.328x4=1529.312即是batch1 310单卡吞吐率 + bs1 310单卡吞吐率:382.328x4=1529.312fps/card + + batch16的性能,benchmark工具在整个数据集上推理后生成result/perf_vision_batchsize_16_device_1.txt: + + ```shell + [e2e] throughputRate: 173.173, latency: 288729 + [data read] throughputRate: 174.62, moduleLatency: 5.72673 + [preprocess] throughputRate: 174.357, moduleLatency: 5.73535 + [infer] throughputRate: 173.844, Interface throughputRate: 519.634, moduleLatency: 3.36724 + [post] throughputRate: 10.865, moduleLatency: 92.0383 + ``` + + bs16 310单卡吞吐率:519.634x4=2078.536fps/card + +2. benchmark纯推理 + + batch1性能: + + ```shell + /benchmark.x86_64 -round=20 -om_path=efficientnet-b0_bs1.om -device_id=0 -batch_size=1 + ``` + + 执行20次纯推理取均值,统计吞吐率与其倒数时延(benchmark的时延是单个数据的推理时间),npu性能是一个device执行的结果 + + ```shell + [INFO] Dataset number: 19 finished cost 2.635ms + [INFO] PureInfer result saved in ./result/PureInfer_perf_of_efficientnet-b0_bs1_in_device_0.txt + -----------------PureInfer Performance Summary------------------ + [INFO] ave_throughputRate: 374.313samples/s, ave_latency: 2.67914ms + ``` + + bs1 310单卡吞吐率:374.313x4=1497.252fps/card + + batch16性能: + + ```shell + ./benchmark.x86_64 -round=20 -om_path=efficientnet-b0_bs16.om -device_id=0 -batch_size=16 + ``` + + ```shell + [INFO] Dataset number: 19 finished cost 30.514ms + [INFO] PureInfer result saved in ./result/PureInfer_perf_of_efficientnet-b0_bs16_in_device_0.txt + -----------------PureInfer Performance Summary------------------ + [INFO] ave_throughputRate: 524.094samples/s, ave_latency: 1.9101ms + ``` + + bs16 310单卡吞吐率:524.094x4=2096.376fps/card - batch16性能: -``` -./benchmark.x86_64 -round=20 -om_path=efficientnet-b0_bs16.om -device_id=0 -batch_size=16 -``` -``` -[INFO] Dataset number: 19 finished cost 30.514ms -[INFO] PureInfer result saved in ./result/PureInfer_perf_of_efficientnet-b0_bs16_in_device_0.txt ------------------PureInfer Performance Summary------------------ -[INFO] ave_throughputRate: 524.094samples/s, ave_latency: 1.9101ms -``` -bs16 310单卡吞吐率:524.094x4=2096.376fps/card - gpu性能数据 - 在装有T4卡的服务器上使用TensorRT测试gpu性能 + 在装有对应gpu卡(T4)的服务器上使用TensorRT测试gpu性能 + +**注意**: 测试gpu性能要确保device空闲,使用nvidia-smi命令可查看device是否在运行其它推理任务 batch1性能: -``` +```shell trtexec --onnx=efficientnet-b0-sim.onnx --fp16 --shapes=image:1x3x224x224 ``` -gpu T4是4个device并行执行的结果,mean是时延(tensorrt的时延是batch个数据的推理时间),即吞吐率的倒数乘以batch -``` +gpu得到的结果`GPU Compute: mean`是时延(tensorrt的时延是batch个数据的推理时间),即吞吐率的倒数乘以batch +```shell [03/24/2021-03:54:47] [I] GPU Compute [03/24/2021-03:54:47] [I] min: 1.26575 ms [03/24/2021-03:54:47] [I] max: 4.41528 ms @@ -605,13 +529,13 @@ gpu T4是4个device并行执行的结果,mean是时延(tensorrt的时延是b [03/24/2021-03:54:47] [I] percentile: 1.40723 ms at 99% [03/24/2021-03:54:47] [I] total compute time: 2.9972 s ``` -batch1 t4单卡吞吐率:1000/(1.31054/1)=763.044fps +batch1 gpu单卡吞吐率:1000/(1.31054/1)=763.044fps batch16性能: -``` +```shell trtexec --onnx=efficientnet-b0_sim.onnx --fp16 --shapes=image:16x3x224x224 ``` -``` +```shell [03/24/2021-03:57:22] [I] GPU Compute [03/24/2021-03:57:22] [I] min: 12.5645 ms [03/24/2021-03:57:22] [I] max: 14.8437 ms @@ -620,23 +544,28 @@ trtexec --onnx=efficientnet-b0_sim.onnx --fp16 --shapes=image:16x3x224x224 [03/24/2021-03:57:22] [I] percentile: 14.8377 ms at 99% [03/24/2021-03:57:22] [I] total compute time: 3.03173 s ``` -batch16 t4单卡吞吐率:1000/(12.9561/16)=1234.940fps +batch16 gpu单卡吞吐率:1000/(12.9561/16)=1234.940fps - 性能对比 -bs1: 310/t4=1529.312/763.044=2.00倍 -bs16: 310/t4=2078.536/1234.940=1.68倍 +bs1: Ascend 310/gpu=1529.312/763.044=2.00倍 +bs16: Ascend 310/gpu=2078.536/1234.940=1.68倍 性能达标 ### 2.2 模型转换指导 -- 导出onnx +[模型转换专题](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E5%8A%9F%E8%83%BD%E6%89%93%E9%80%9A) - [导出onnx文件](#211-导出onnx文件) - [模型转换专题](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E5%8A%9F%E8%83%BD%E6%89%93%E9%80%9A) +具体案例: + +| 案例 | 简单描述 | +| ------------------------------------------------------------ | -------------------------- | +| [【动态Shape】-推理指导(ResNet50)](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch离线推理指导/专题案例/功能打通/【动态Shape】-推理指导(ResNet50).md) | 基于pyACL打通动态shape模型 | +| | | +| | | ### 2.3 精度调试指导 -- 精度调试基本思路 +- 精度调试基本思路如下: 1. 根据推理流程逐步排查引起精度下降的问题点,先粗调确认是哪个流程的问题再微调 @@ -650,19 +579,31 @@ bs16: 310/t4=2078.536/1234.940=1.68倍 6. 如果是om算子缺陷导致精度下降,则指定算子为om的输出节点,然后与在线推理时该算子(使用开源netron工具查看onnx模型算子,开启verbose导出onnx时会打印算子对应的py文件代码行)的输出对比,查看是否一致。二分法排查对比pytorch或onnx与om哪个算子输出不一致,需要保证条件是输入一样 - 7. pytorch模型在线推理支持模型输入的hw维是变化的,而om模型由于不支持动态算子暂需要固定输入的hw维,即每个预处理后输入样本的hw都是一样的。为了验证预处理等比例缩放加pad固定样本的hw维对精度的影响,可以修改开源模型代码评测脚本,加载预处理后的样本,然后替换掉model的输入再评测精度,后处理需要做相应适配恢复到原图,查看精度是否下降。同样为了验om模型输出的结果与后处理,可以修改开源模型代码评测脚本,加载om模型输出的结果,然后替换掉model的输出再评测精度,查看精度是否下降。 + 7. pytorch模型在线推理支持模型输入的hw维是变化的,而om模型对于动态算子支持不亲和,可能需要固定输入的hw维,即每个预处理后输入样本的hw都是一样的。为了验证预处理等比例缩放加pad固定样本的hw维对精度的影响,可以修改开源模型代码评测脚本,加载预处理后的样本,然后替换掉model的输入再评测精度,后处理需要做相应适配恢复到原图,查看精度是否下降。同样为了验om模型输出的结果与后处理,可以修改开源模型代码评测脚本,加载om模型输出的结果,然后替换掉model的输出再评测精度,查看精度是否下降。 8. 用替换法对比输入输出排查某个修改的或自定义或关键算子的影响,如果某算子导致精度下降问题,尝试是否可以修改模型使用其它方法替换掉该算子。 -- 精度调试工具与案例 +- 精度调试工具 + + 一键式全流程精度比对(msquick官方工具仓):[精度对比工具](https://gitee.com/ascend/tools/tree/master/msquickcmp) + 一步式精度比对工具(精简版):[精度对比工具简洁版](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7/one_step_accuracy_cmp) + +- 精度调试专题 - [精度对比工具](https://gitee.com/ascend/tools/tree/master/msquickcmp) - [精度对比工具简洁版](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7/one_step_accuracy_cmp) [精度调试专题](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E7%B2%BE%E5%BA%A6%E8%B0%83%E8%AF%95) + + | 案例 | 简单描述 | + | ------------------------------------------------------------ | ------------------------------------------------------------ | + | [【典型案例】-SSD(mmdetection)模型精度调试](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch离线推理指导/专题案例/精度调试/【典型案例】-SSD(mmdetection)模型精度调试.md) | SSD模型精度调试:存在自定义算子,存在精度溢出场景如何进行算子定位 | + | [【典型案例】-VitBase模型精度调试](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch离线推理指导/专题案例/精度调试/【典型案例】-VitBase模型精度调试.md) | VitBase模型精度调试:混合精度不生效,存在融合算子,精度逐步下降场景 | + ### 2.4 性能优化指导 -- 性能分析工具profiling +- 性能分析工具 + + profiling基本操作如下: + ``` 新建/home/HwHiAiUser/test/run文件,内容如下: #! /bin/bash @@ -682,11 +623,14 @@ bs16: 310/t4=2078.536/1234.940=1.68倍 python3.7 msprof.py export timeline -dir /home/HwHiAiUser/test/生成的profiling目录 --iteration-id 1 在chrome的地址栏输入chrome://tracing/加载打点数据查看打点图 ``` - 其中op_statistic_0_1.csv文件统计了模型中每类算子总体耗时与百分比,op_summary_0_1.csv中包含了模型每个算子的aicore耗时 + 重点关注两个文件(summary文件夹下): + + op_statistic_0_1.csv文件统计了模型中每类算子总体耗时与百分比,op_summary_0_1.csv中包含了模型每个算子的aicore耗时 profiling工具使用详情请参考[CANN 5.0.1 开发辅助工具指南 (推理) 01](https://support.huawei.com/enterprise/zh/doc/EDOC1100191944?idPath=23710424%7C251366513%7C22892968%7C251168373) - + - 性能优化实例 - Inception-V3性能不达标,使用profiling工具分析,可以从输出的csv文件看到算子统计结果 + Inception-V3性能不达标,使用profiling工具分析,可以从输出的csv文件看到算子统计结果(op_summary_0_1.csv): + ``` Model Name OP Type Core Type Count Total Time(us) Min Time(us) Avg Time(us) Max Time(us) Ratio(%) inception_v3_bs16 TransData AI Core 22 399586.005 20.883 18163 105754.996 46.091391 @@ -701,21 +645,38 @@ bs16: 310/t4=2078.536/1234.940=1.68倍 inception_v3_bs16 MatMulV2 AI Core 1 126.037 126.037 126.037 126.037 0.014538 inception_v3_bs16 Flatten AI Core 1 20.415 20.415 20.415 20.415 0.002355 ``` - profiling也会统计每个算子aicore耗时,结合使用netron查看onnx模型结构图,可以看出pad和pad前后的transdata耗时很长,经过分析pad的功能可以由其后的averagepool中的pad属性完成,可以节约大量时间,于是进行PadV3D和Pooling算子的graph融合。从op_summary_0_1.csv中看出单个TransData算子aicore的耗时已经很短了,本模型TransData算子没有优化空间。 - -- 性能优化案例 + 上边案例可以看到: + + profiling会统计每个算子aicore耗时,结合使用netron查看onnx模型结构图,可以看出pad和pad前后的transdata耗时很长,经过分析pad的功能可以由其后的averagepool中的pad属性完成,可以节约大量时间,于是进行PadV3D和Pooling算子的graph融合。从op_summary_0_1.csv中看出单个TransData算子aicore的耗时已经很短了,本模型TransData算子没有优化空间。 + +- 性能优化案例 - [性能初步优化案例](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86-Xxx%E6%A8%A1%E5%9E%8B%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A.docx) [性能优化专题](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/tree/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98) + + | 案例 | 简单描述 | + | ------------------------------------------------------------ | ---------------------- | + | [案例-Conv1D算子优化](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch离线推理指导/专题案例/性能调优/案例-Conv1D算子优化.md) | 对于Conv1D算子进行优化 | ## 3 附录 +模型案例附录: + +| 模型类型 | 案例链接 | +| ----------------------- | -------- | +| 常见CV类模型 | | +| NLP类模型 | | +| DCN等包含自定义算子模型 | | +| 视频检测类 | | +| Audio语音类 | | + ### **附录1 [模型案例](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86-%E6%A8%A1%E5%9E%8B%E6%A1%88%E4%BE%8B.md)** - + ### **附录2 [交付标准与规范](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86%E6%8C%87%E5%AF%BC/PyTorch%E7%A6%BB%E7%BA%BF%E6%8E%A8%E7%90%86-%E4%BA%A4%E4%BB%98%E6%A0%87%E5%87%86%E4%B8%8E%E4%BA%A4%E4%BB%98%E4%BB%B6.md)** +### **附录3 [推理FAQ](https://gitee.com/wangjiangben_hw/ascend-pytorch-crowdintelligence-doc/blob/master/Ascend-PyTorch离线推理指导/PyTorch离线推理-FAQ.md)** + ### 3.3 深度学习指导 - 书籍推荐 ```