在为 SOFIE 的 RModel 实现 Keras 解析器之后,我开始开发 PyTorch 解析器。 PyTorch 支持原生导出到.onnx格式,因此具有跟踪 PyTorch 模型中使用的 ONNX 运算符的内部机制。 PyTorch 是一个快速发展的深度学习框架,通过 Python 绑定的 PyRoot 接口在 ROOT TMVA 中得到支持。为了保存和导入已保存的模型,ROOT 使用 TorchScript。

在我的 GSoC'21 与 CERN-HSF 系列的这篇博客中,我讨论了 PyTorch 解析器的实现,用于将.pt文件转换为 SOFIE 的 RModel 以用于后续生成推理代码。

目录

1.PyTorch & TorchScript

2、解析PyTorch.pt文件

3.测试

1. PyTorch 和 TorchScript

正如 PyTorch 的官方存储库将其描述为 Tensors 和 Python 中具有强大 GPU 加速的动态神经网络的框架,PyTorch 提供了一个强大的基于 Python 的平台和一个基于 Torch 的 C++ 后端,用于实现神经网络和进行基于张量的高级计算。开源框架由 Facebook 开发,后来被公开。 PyTorch 采用命令式方法,维护一个动态计算图,用于定义计算顺序和计算神经网络梯度下降优化的梯度。计算图是在声明变量时动态构建的。因此,在每次连续的训练迭代之后,再次构建该图。它的动态特性使其更加灵活和易于调试。在 TensorFlow2 Alpha 出现之前,TensorFlow 维护了一个静态计算图。这些范式的主要区别可以通过各种类比来理解。遵循_“定义并运行”_哲学的 TensorFlow 的工作方式与任何编译器一样,其中所有变量首先在图中定义、连接、初始化,然后被重用,但内部结构无法在运行中再次重建。 PyTorch 遵循“定义并运行”的概念,因此 DCG 就像解释器一样工作,在流程的每个步骤中都构建了图形,并且可以在运行时修改其内部结构。

TorchScript

与 Keras & TensorFlow 的model.save(filename)保存完整的深度学习模型及其配置和权重不同,使用 PyTorch 的torch.save()保存模型将序列化数据绑定到特定的类和目录结构,因此需要在加载之前定义模型类模型。

TorchScript 提供了优化 PyTorch 模型并从 Python 进程序列化它们的功能,该进程稍后可以加载到不一定依赖 Python 的环境中,例如 C++ 独立程序。将 TorchScript 添加到 PyTorch 实现了一个统一的框架,用于将模型从研究过渡到生产。将 PyTorch 模块序列化为 TorchScript 可以让我们保存整个模型以及配置和权重,以便以后用于推理。 TorchScript 提供了两种执行方法,即TraceScript。跟踪模型需要一个输入张量,该输入张量和计算在张量上运行,直到记录输出,因此可以稍后用于类似形状的输入张量。为模型编写脚本更加通用,其中整个模型的所有配置都是序列化的,以后可以与任何适用形状的张量一起使用。

Root TMVA 的 PyTorch 接口允许在 TMVA 环境中训练 PyTorch 模型,并使用 TorchScript 对它们进行序列化,稍后可用于推理。

2.解析 PyTorch.pt文件

由于 PyTorch 在构建神经网络模型中提供了在图中动态添加模型计算的灵活性,因此很难提取所包含层的信息,从而解析配置并构建 RModel 对象。 PyTorch 模型可以构建在nn.Sequentialnn.Modulenn.ModuleList容器中,并且还支持将层封装到单元中以进行子类化。由于 ROOT TMVA 的 PyTorch 接口使用 TorchScript 保存和加载模型的脚本模块,该模块只包含用于推理的模型例程和权重,因此与 Keras Sequential API 和函数式不同,提取构成 PyTorch 模型的底层特征很困难API模型,图可以迭代遍历。

为了解决这个问题并找到提取包含层及其属性的方法,我们可以借助 PyTorch 提供的 Native ONNX 支持。

https://github.com/pytorch/pytorch/tree/master/torch/onnx

PyTorch 具有将模型转换为其 ONNX 等效图的内部实现。为此,需要一个通过模型传递的虚拟输入张量,并记录其过程中应用的运算符以开发 ONNX 图。在为此使用保存的脚本模块时,我们还需要形状推断的预期示例输出。为了处理具有动态轴的张量,我们再次使用 PyTorch 内部功能为它们设置形状推断。

#Converting the model to ONNX Graph representation
import torch
from torch.onnx.utils import _model_to_graph
from torch.onnx.symbolic_helper import _set_onnx_shape_inference

model= torch.jit.load('model.pt')
model.eval()
model.float() 

#Building the dummy input tensor
s1=[120,1]                       
dummy=torch.rand(*s1) 
input=[dummy]
output=model(*input)

#Setting inference for dynamic axes
_set_onnx_shape_inference(True)      

#Generating the equivalent ONNX graph
graph=_model_to_graph(model,input,example_outputs=output)

图的输出看起来像这样

>>> graph
(graph(%x.1 : Float(2, 6, strides=[6, 1], requires_grad=0, device=cpu),
      %fc1.weight : Float(12, 6, strides=[6, 1], requires_grad=0, device=cpu),
      %fc1.bias : Float(12, strides=[1], requires_grad=0, device=cpu)):
  %3 : Float(2, 12, strides=[12, 1], device=cpu) = onnx::Gemm[alpha=1., beta=1., transB=1](%x.1, %fc1.weight, %fc1.bias) 
  %4 : Float(2, 12, strides=[12, 1], device=cpu) = onnx::Relu(%3)
  %5 : Float(12, 2, strides=[1, 12], requires_grad=1, device=cpu) = onnx::Transpose[perm=[1, 0]](%4) 
  return (%5)
, {'fc1.bias': tensor([ 0.1858, -0.4581, -0.3743, -0.0609, -0.1052,  0.0538,  0.0467, -0.1544,
        -0.3622, -0.3611, -0.2269,  0.1716]), 'fc1.weight': tensor([[ 1.9536e-01,  3.3694e-02, -9.5294e-02, -3.2092e-01, -2.6642e-01,
          3.2872e-01],
        [ 2.5622e-01, -2.5419e-01,  3.2304e-01, -5.7926e-02,  1.8545e-01,
         -3.3965e-01],
        [ 2.3855e-01, -3.0029e-01, -3.2645e-01, -3.6467e-01, -1.1971e-01,
         -1.5546e-01],
        [-3.9777e-01, -2.4680e-01,  4.6634e-02,  7.5872e-05,  5.0814e-02,
         -3.0577e-01],
        [ 9.8371e-03, -3.5033e-01, -1.8954e-01,  2.4746e-01, -1.9828e-01,
         -3.3955e-01],
        [-2.3662e-02, -3.1937e-02, -8.0010e-02,  1.6615e-01,  2.1353e-01,
         -3.5624e-01],
        [-1.0537e-01, -1.0636e-01,  3.1500e-01, -2.0943e-01,  6.0947e-01,
          2.3454e-01],
        [-1.7886e-01, -1.6449e-01, -7.1962e-02, -1.4553e-01,  2.7605e-01,
          2.8320e-02],
        [-4.0435e-01,  3.2550e-01, -2.1856e-01, -3.2801e-02, -3.6782e-01,
         -2.4415e-01],
        [ 1.9953e-01,  2.1276e-01, -6.8311e-02,  7.6783e-02,  1.1836e-01,
          1.2552e-01],
        [ 1.5363e-01, -2.6658e-01, -4.5881e-01, -2.5509e-01,  1.9992e-01,
          6.6146e-02],
        [-3.3991e-01, -1.3903e-01, -1.1859e-01, -8.4289e-02,  1.4896e-02,
          1.6103e-01]])}, None)

这里的图表是一个元组,其中graph[0]保存模型配置,graph[1]保存模型权重。由于图是ONNX图格式,所以我们可以直接遍历它,提取节点数据。

modelData = []

for i in graph[0].nodes():
 globals().update(locals())

 #For type of node
 nodeData = []
 nodeData.append(i.kind())

 #For attrbutes of node
 nodeAttributeNames = [x for x in i.attributeNames()]
 nodeAttributes     = {j:i[j] for j in nodeAttributeNames}
 nodeData.append(nodeAttributes)

 #For input tensor info of node
 nodeInputs     = [x for x in i.inputs()]
 nodeInputNames = [x.debugName() for x in nodeInputs]
 nodeData.append(nodeInputNames)

 #For output tensor info of node
 nodeOutputs     = [x for x in i.outputs()]
 nodeOutputNames = [x.debugName() for x in nodeOutputs]
 nodeData.append(nodeOutputNames)

 modelData.append(nodeData)

然后以与 Keras 解析器类似的方式使用这些提取的数据,将运算符、输入和输出张量信息以及初始化的张量添加到 RModel 对象中,以实现成功的翻译。 Root 是一个主要用 C++ 开发的框架,因此 C-Python API 用于所有这些 Python 变量的提取和操作。官方的C-Python API可用于理解用法,使用IBM 的 DeveloperWorks提供的更简单指南可在此处找到。

可以使用PR#8684跟踪 SOFIE 的 PyTorch 解析器的开发。

https://github.com/root-project/root/pull/8684

3.测试

为验证已实现的解析器而开发了测试,这与 Keras 解析器的测试类似。开发 Python 脚本,从 C-Python API 运行以生成并保存 PyTorch 模型,然后将该模型解析为 RModel 对象,随后生成推理代码。然后通过比较 PyTorch 模型的输出和在相同输入张量上调用时生成的推理代码,在 Google 的 GTest 框架上找到正确性。

这标志着为 SOFIE 实现 PyTorch 解析器的完成,至此,我的 Google Summer of Code 项目的主要任务也完成了。随着官方编码阶段的剩余时间,我也开始为 BDT 模型开发 Root-Storage,用于它们的序列化和推理代码生成。在 GSoC 之后,我将描述 BDT 根存储的开发,届时将实现大部分功能,我的以下博客将在 GSoC 的最终报告中总结和总结这 10 周的惊人旅程.

暂时告别,

San级板

图片来源

infoq.com/presentations/pytorch-torchscript..

miro.medium.com/max/700/1*5PLIVNA5fIqEC8-kZ..

Logo

ModelScope旨在打造下一代开源的模型即服务共享平台,为泛AI开发者提供灵活、易用、低成本的一站式模型服务产品,让模型应用更简单!

更多推荐