使用Pytorch导出自定义ONNX算子

1 篇文章 0 订阅
文章介绍了如何在PyTorch模型中遇到不支持的算子时,通过自定义ONNX算子进行模型导出。以affine_grid算子为例,展示了如何继承`torch.autograd.Function`并实现forward和symbolic方法,以便在ONNX环境中使用。
摘要由CSDN通过智能技术生成

在实际部署模型时有时可能会遇到想用的算子无法导出onnx,但实际部署的框架是支持该算子的。此时可以通过自定义onnx算子的方式导出onnx模型(注:自定义onnx算子导出onnx模型后是无法使用onnxruntime推理的)。下面给出个具体应用中的示例:需要导出pytorch的affine_grid算子,但在pytorch的2.0.1版本中又无法正常导出该算子,故可通过如下自定义算子代码导出。

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Function
from torch.onnx import OperatorExportTypes


class CustomAffineGrid(Function):
    @staticmethod
    def forward(ctx, theta: torch.Tensor, size: torch.Tensor):
        grid = F.affine_grid(theta=theta, size=size.cpu().tolist())
        return grid

    @staticmethod
    def symbolic(g: torch.Graph, theta: torch.Tensor, size: torch.Tensor):
        return g.op("AffineGrid", theta, size)


class MyModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()

    def forward(self, x: torch.Tensor, theta: torch.Tensor, size: torch.Tensor):
        grid = CustomAffineGrid.apply(theta, size)
        x = F.grid_sample(x, grid=grid, mode="bilinear", padding_mode="zeros")
        return x


def main():
    with torch.inference_mode():
        custum_model = MyModel()
        x = torch.randn(1, 3, 224, 224)
        theta = torch.randn(1, 2, 3)
        size = torch.as_tensor([1, 3, 512, 512])

        torch.onnx.export(model=custum_model,
                          args=(x, theta, size),
                          f="custom.onnx",
                          input_names=["input0_x", "input1_theta", "input2_size"],
                          output_names=["output"],
                          dynamic_axes={"input0_x": {2: "h0", 3: "w0"},
                                        "output": {2: "h1", 3: "w1"}},
                          opset_version=16,
                          operator_export_type=OperatorExportTypes.ONNX_FALLTHROUGH)


if __name__ == '__main__':
    main()

在上面代码中,通过继承torch.autograd.Function父类的方式实现导出自定义算子,继承该父类后需要用户自己实现forward以及symbolic两个静态方法,其中forward方法是在pytorch正常推理时调用的函数,而symbolic方法是在导出onnx时调用的函数。对于forward方法需要按照正常的pytorch语法来实现,其中第一个参数必须是ctx但对于当前导出onnx场景可以不用管它,后面的参数是实际自己传入的参数。对于symbolic方法的第一个必须是g,后面的参数任为实际自己传入的参数,然后通过g.op方法指定具体导出自定义算子的名称,以及输入的参数(注:上面示例中传入的都是Tensor所以可以直接传入,对与非Tensor的参数可见下面一个示例)。最后在使用时直接调用自己实现类的apply方法即可。使用netron打开自己导出的onnx文件,可以看到如下所示网络结构。
在这里插入图片描述

有时按照使用的推理框架导出自定义算子时还需要设置一些参数(非Tensor)那么可以参考如下示例,例如要导出int型的参数k那么可以通过传入k_i来指定,要导出float型的参数scale那么可以通过传入scale_f来指定,要导出string型的参数clockwise那么可以通过传入clockwise_s来指定:

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Function
from torch.onnx import OperatorExportTypes


class CustomRot90AndScale(Function):
    @staticmethod
    def forward(ctx, x: torch.Tensor):
        x = torch.rot90(x, k=1, dims=(3, 2))  # clockwise 90
        x *= 1.2
        return x

    @staticmethod
    def symbolic(g: torch.Graph, x: torch.Tensor):
        return g.op("Rot90AndScale", x, k_i=1, scale_f=1.2, clockwise_s="yes")


class MyModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()

    def forward(self, x: torch.Tensor):
        return CustomRot90AndScale.apply(x)


def main():
    with torch.inference_mode():
        custum_model = MyModel()
        x = torch.randn(1, 3, 224, 224)

        torch.onnx.export(model=custum_model,
                          args=(x,),
                          f="custom_rot90.onnx",
                          input_names=["input"],
                          output_names=["output"],
                          dynamic_axes={"input": {2: "h0", 3: "w0"},
                                        "output": {2: "w0", 3: "h0"}},
                          opset_version=16,
                          operator_export_type=OperatorExportTypes.ONNX_FALLTHROUGH)


if __name__ == '__main__':
    main()

使用netron打开自己导出的onnx文件,可以看到如下所示信息。
在这里插入图片描述

PyTorch中,我们可以使用C++或CUDA编写自定义算子,并将其发布为PyTorch的扩展,以便在PyTorch使用。下面是发布自定义算子的一般步骤: 1. 编写C++或CUDA代码实现自定义算子。 2. 使用PyTorch提供的C++ API或CUDA API将算子封装为PyTorch扩展,生成动态链接库文件。可以使用setup.py或CMake来构建和安装扩展。 3. 在Python中导入扩展,并使用torch.ops.register_custom_op_symbolic()函数注册算子。 4. 在Python使用自定义算子。 下面是一个简单的示例,演示了如何发布一个简单的自定义算子。 1. 编写C++代码实现自定义算子。假设我们要实现一个名为mymul的算子,它可以计算两个张量的乘积。以下是mymul的C++实现: ```c++ #include <torch/extension.h> torch::Tensor mymul(torch::Tensor x, torch::Tensor y) { return x * y; } PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { m.def("mymul", &mymul, "My multiply operation"); } ``` 2. 使用PyTorch提供的API将算子封装为扩展。可以使用setup.py或CMake来构建和安装扩展。以下是使用setup.py构建和安装扩展的示例: ```python from setuptools import setup from torch.utils.cpp_extension import BuildExtension, CUDAExtension setup(name='mymul', ext_modules=[ CUDAExtension('mymul_cuda', [ 'mymul_cuda.cpp', 'mymul_cuda_kernel.cu', ]), CppExtension('mymul_cpp', ['mymul.cpp']), ], cmdclass={'build_ext': BuildExtension}) ``` 3. 在Python中导入扩展,并使用torch.ops.register_custom_op_symbolic()函数注册算子。以下是在Python使用mymul的示例: ```python import torch from torch.utils.cpp_extension import load # 导入扩展 mymul_cpp = load('mymul_cpp', ['mymul.cpp']) # 注册算子 torch.ops.load_library(mymul_cpp.__file__) torch.ops.register_custom_op_symbolic('mymul_cpp::mymul', 2) # 创建输入张量 x = torch.tensor([1, 2, 3]) y = torch.tensor([4, 5, 6]) # 使用自定义算子 z = torch.ops.mymul_cpp.mymul(x, y) print(z) ``` 在上面的示例中,我们首先导入了扩展,并使用torch.ops.load_library()函数加载它。然后,我们使用torch.ops.register_custom_op_symbolic()函数注册算子,指定算子的名称和输入参数的数量。最后,我们创建了两个输入张量x和y,并使用torch.ops.mymul_cpp.mymul()函数调用自定义算子,计算x和y的乘积。 注意,以上仅为一般步骤示例,实际上发布自定义算子需要编写更多的代码和配置文件,具体实现需要根据具体需求和环境进行调整。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳花的小绿豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值