导出ONNX模型
模型训练完成后,用户可以使用pth文件和pth.tar文件导出ONNX模型,然后通过ATC工具将其转换为适配昇腾AI处理器的.om文件用于离线推理。将ONNX模型转换为适配昇腾AI处理器的.om文件流程请参考《CANN ATC离线模型编译工具用户指南》。离线推理应用构建请参考《CANN AscendCL应用开发指南 (C&C++)》。
简介
ONNX是业内目前比较主流的模型格式,广泛用于模型交流及部署。PyTorch模型在昇腾AI处理器上的部署策略是基于PyTorch官方支持的ONNX模块实现的。
本节主要介绍如何将Checkpoint文件通过torch.onnx.export()接口导出为ONNX模型。
.pth或.pt文件导出ONNX模型
保存的.pth或.pt文件可以通过PyTorch构建模型,再加载权重的方法恢复,然后导出ONNX模型,样例如下:
import torch import torch.nn as nn import torch.onnx import torch_npu from torch_npu.contrib import transfer_to_npu device = torch.device("cuda") class ToyModel(nn.Module): def __init__(self): super(ToyModel, self).__init__() self.weight = nn.Parameter(torch.randn(20, 10)) self.net1 = nn.Linear(10, 10) self.relu = nn.ReLU() self.net2 = nn.Linear(10, 5) def forward(self, x): return self.net2(self.relu(self.net1(x))) def convert(): model = ToyModel() model.load_state_dict(torch.load('state_dict_model.pt', map_location='cuda'),strict=False) #根据实际文件名称修改 # 模型设置为推理模式 model.eval() dummy_input = torch.randn(20, 10) # 定义输入shape torch.onnx.export(model, dummy_input, "model.onnx", input_names = ["input"], # 构造输入名 output_names = ["output"], # 构造输出名 opset_version=11, # ATC工具目前支持opset_version=9,10,11,12,13 dynamic_axes={"input":{0:"batch_size"}, "output":{0:"batch_size"}}) #支持输出动态轴 if __name__ == "__main__": convert()

- 在导出ONNX模型之前,必须调用model.eval()将dropout和batch normalization层设置为推理模式。
- 样例脚本中的model来自于torchvision模块中的定义,用户使用自己的模型时需自行指定。
- 构造输入输出需要对应训练时的输入输出,否则无法正常推理。
.pth.tar文件导出ONNX模型
.pth.tar在导出ONNX模型时需要先确定保存时的信息,有时保存的节点名称和模型定义中的节点会有差异,例如会多出前缀和后缀。在进行转换的时候,可以对节点名称进行修改。转换代码样例如下:
import torch import torch.nn as nn import torch.onnx import torch_npu from torch_npu.contrib import transfer_to_npu from collections import OrderedDict device = torch.device("cuda") class ToyModel(nn.Module): def __init__(self): super(ToyModel, self).__init__() self.weight = nn.Parameter(torch.randn(20, 10)) self.net1 = nn.Linear(10, 10) self.relu = nn.ReLU() self.net2 = nn.Linear(10, 5) def forward(self, x): return self.net2(self.relu(self.net1(x))) def proc_nodes_module(checkpoint, AttrName): new_state_dict = OrderedDict() for key, value in checkpoint[AttrName].items(): if key == "module.features.0.0.weight": print(value) #根据实际前缀后缀情况修改 if(key[0:7] == "module."): name = key[7:] else: name = key[0:] new_state_dict[name] = value return new_state_dict def convert(): model = ToyModel() checkpoint = torch.load('checkpoint.pth.tar', map_location='cuda') # 根据实际文件名称修改 checkpoint['state_dict'] = proc_nodes_module(checkpoint, 'state_dict') model.load_state_dict(checkpoint['state_dict'], strict=False) # 模型设置为推理模式 model.eval() dummy_input = torch.randn(20, 10) # 定义输入shape torch.onnx.export(model, dummy_input, "model.onnx", # 输出文件名根据实际情况修改 input_names = ["input"], # 构造输入名 output_names = ["output"], # 构造输出名 opset_version=11, # ATC工具目前支持opset_version=9,10,11,12,13 dynamic_axes={"input":{0:"batch_size"}, "output":{0:"batch_size"}}) #支持输出动态轴 if __name__ == "__main__": convert()
自定义算子导出ONNX模型
对于非NPU自定义算子,导出ONNX的逻辑和限制遵循PyTorch框架,请参考官方网站的PyTorch框架issue或者文档进行修改。
- 仅支持使用torch_npu方式调用,如torch_npu.fast_gelu(x),不能使用torch.fast_gelu(x)。
- 对于inplace和out类算子,在实际推理过程中并不会使用这类算子,如果使用的话会导致断图,请使用对应算子代替。示例如下:
torch_npu.npu_silu_(input)修改为input = torch_npu.npu_silu(input)
torch_npu.npu_broadcast(tensor, size, out=result)修改为result = torch_npu.npu_broadcast(tensor, size)
- 仅支持部分自定义算子的导出,支持清单参见自定义算子导出ONNX支持清单。
- 对于存在定制化正反向流程的模型(比如继承自torch.autograd.Function),例如:
class MyFunction(torch.autograd.Function): @staticmethod def forward(ctx, tensor1, pyscalar, tensor2): result = ... return result @staticmethod def backward(ctx, grad_output): result = ... return result
由于ONNX导出原理的限制,这类模型需要修改实现逻辑才能导出ONNX模型,有两种解决方式:
- 不能继承torch.autograd.Function,修改其实现逻辑。
- 自定义onnx导出符号逻辑(symbolic函数),对应插件等也需要自行定义。
- 由于原生PyTorch框架bug,npu_conv2d和npu_conv3d算子ONNX导出时会出现如下报错信息:
TypeError: _convolution() missing 1 required positional argument: 'allow_tf32'
自定义算子导出ONNX模型使用样例如下:
import torch import torch_npu import torch_npu.onnx # 自定义算子导出功能使能,仅在onnx导出脚本中使用,其他场景如训练使能可能导致错误 #定义一个简单的模型,使用NPU自定义算子 class Model(torch.nn.Module): def __init__(self): super(Model, self).__init__() def forward(self, x): x = torch_npu.npu_one_hot(x, depth=5) #使用NPU自定义算子 return x inputs = torch.IntTensor([5, 3, 2, 1]).npu() #模型的样例输入,一般随机值即可 model = Model().to("npu") # 得到模型结构,将模型加载到device侧,需要保证模型中自定义算子使用方式已经满足前述要求 model.eval() # 设置为推理模式,在推理模式下BatchNorm层、Dropout层等用于优化训练而添加的网络层会被关闭,从而使得推理时不会发生偏移 model(inputs) # 验证模型正常运行 onnx_model_name = "npu_model.onnx" # 导出的onnx模型名称 with torch.no_grad(): torch_npu.onnx.export(model, inputs, onnx_model_name) # 导出onnx模型