Torch 自定义算子,Onnx自定义算子,TensorRT自定义算子

上代码

从torch导出算子

class KDTreeFpsample(torch.autograd.Function):
    @staticmethod
    def symbolic(g, xyz: torch.Tensor, nsample: int):
        return g.op('WX::KDTreeFpsample', xyz, nsample_i=nsample)
    @staticmethod
    def forward(ctx, xyz: torch.Tensor, nsample: int):
        fps_idx = kdtree_fpsample(xyz, nsample)
        return fps_idx
 
# 示例使用 
# xyz (B x N x 3), nsample 采样点
# fps_idx (B x nsample) 采样后的索引
fps_idx = KDTreeFpsample.apply(xyz, nsample)

def symbolic(g, xyz: torch.Tensor, nsample: int):

forward(ctx, xyz: torch.Tensor, nsample: int):
对应的,xyz是输入,nsample就是一个标量数据

return g.op('WX::KDTreeFpsample', xyz, nsample_i=nsample)
这里的 WX 是一个防止算子名称重复的命名空间,KDTreeFpsample是算子名称,如果导出为onnx的话,在加载onnx模型时也需要自定义一个onnx的算子,onnx的算子也会是这个名称。

nsample_i=nsample
这里这么写的原因是nsample只是一个标量,就是一个常数,_i 表示是一个整数标量,这是表示这个算子有一个 nsample 的标量属性。具体的请参考别的资料,我也说不清楚。
这里可以继续看到后面加载onnx模型时,就知道什么意思了。

fps_idx = kdtree_fpsample(xyz, nsample)
这是一个使用C++扩展的函数

这种方式定义的算子不需要额外的去显示注册,你只要使用了
KDTreeFpsample.apply
导出为onnx的时候就会自动把这个算子加入的。

导出onnx模型的方式请参考逼得相关教程。

在onnxruntime的python版本里,加载有自定义算子的onnx模型

如果使用上面自定义的算子导出onnx模型,那加载这个onnx模型的时候,也需要实现和注册这个算子,以下是我自己使用的方式

需要 包 onnxruntime 和 onnxruntime_extensions

import onnxruntime as ort
from onnxruntime_extensions import onnx_op, PyCustomOpDef
from onnxruntime_extensions import get_library_path as _lib_path

@onnx_op(op_type="WX::KDTreeFpsample",
         inputs=[PyCustomOpDef.dt_float], # 输入类型
         outputs=[PyCustomOpDef.dt_int32], # 输出类型
         attrs={'nsample': PyCustomOpDef.dt_int64} # 这里就是torch算子里定义的 nsample_i=nsample
         )
def KDTreeFpsample(xyz: np.ndarray, **kwargs) -> np.ndarray:
    """

    Args:
        xyz : BxNxC

    Returns:
        _type_: Idx B x nsample
    """
    nsample = kwargs.get('nsample', None)
    fps_idx = kdtree_fpsample(xyz, nsample)

    return fps_idx

so = ort.SessionOptions()
so.register_custom_ops_library(_lib_path())

# 然后就可以正常加载onnx模型了
# ......

fps_idx = kdtree_fpsample(xyz, nsample)
和上面的上面是一样的函数,是C++扩展的python函数

在onnxruntime的C++版本里,加载有自定义算子的onnx模型

onnxruntime的环境配置不用多说,也可以参考别的教程。

插件定义

base.h文件

#pragma once
#include <cassert>
#include <iostream>
#include <stdexcept>

#include <onnxruntime/onnxruntime_lite_custom_op.h>

#define CHECK_ATTRIBUTE( attribute_name ) CheckAttribute( ort_api, info, #attribute_name, attribute_name )

template <typename>
constexpr bool always_false = false;
template <typename T>
static void CheckAttribute( const OrtApi* ort_api, const OrtKernelInfo* info, const char* name, T& attribute ) {
    OrtStatus* status = nullptr;

    if constexpr ( std::is_same<T, int64_t>::value ) {
        status = ort_api->KernelInfoGetAttribute_int64( info, name, &attribute );
    }
    else if constexpr ( std::is_same<T, int32_t>::value ) {
        // status = ort_api->KernelInfoGetAttribute_int64( info, name, &attribute );
    }
    else if constexpr ( std::is_same<T, float>::value ) {
        status = ort_api->KernelInfoGetAttribute_float( info, name, &attribute );
    }
    else {
        static_assert( always_false<T>, "Unsupported type for KernelInfoGetAttribute" );
    }

#ifdef _DEBUG
    assert( status == nullptr && ( std::string( "Attribute '" ) + name + "' not found" ).c_str() );
#else
    if ( status != nullptr ) {
        std::cerr << "Failed to retrieve '" << name << "' attribute in release mode." << std::endl;
        ort_api->ReleaseStatus( status );
    }
    // else {
    //     std::cout << name << ": " << attribute << std::endl;
    // }
#endif
}

新建头文件 FurthestPointSampling.h

#pragma once
#include "base.h"

#include <onnxruntime/onnxruntime_cxx_api.h>

struct FurthestPointSampling {

    int64_t nsample;  // 下采样点数,就是上面的 nsample

    FurthestPointSampling( const OrtApi* ort_api, const OrtKernelInfo* info );

    void Compute( const Ort::Custom::Tensor<float>& xyz, Ort::Custom::Tensor<int32_t>& out_fps_idx );
};

新建源文件 FurthestPointSampling.cpp

#include "FurthestPointSampling.h"

#include <omp.h>

#include "../kdtree_fpsample/kdtree_fpsample.h"

FurthestPointSampling::FurthestPointSampling( const OrtApi* ort_api, const OrtKernelInfo* info )
    : nsample( 0 ) {
    CHECK_ATTRIBUTE( nsample ); // 就是在python torch 算子里定义的nsample
}

void FurthestPointSampling::Compute( const Ort::Custom::Tensor<float>& xyz, Ort::Custom::Tensor<int32_t>& out_fps_idx ) {

    /*
    In  xyz: (B, N, 3)
    Out fps_idx: (B, nsample)
    */

    std::vector<int64_t> input_shape = xyz.Shape();

    int64_t B = input_shape[ 0 ];
    int64_t N = input_shape[ 1 ];
    int64_t D = input_shape[ 2 ];

    const float* xyz_raw = xyz.Data();

    std::vector<int64_t> output_shape = { B, nsample};
    int32_t*             fps_idx_raw  = out_fps_idx.Allocate( output_shape );
    std::fill_n( fps_idx_raw, B * nsample, 0 );

#pragma omp parallel for
    for ( int ib = 0; ib < B; ++ib ) {
        const float* batch_xyz = xyz_raw + ib * N * D;
        wx::kdline_fpsample( batch_xyz, N, nsample, fps_idx_raw + ib * nsample );
    }
}
注册插件
/// 注册自定义算子
#include "./custom_ops/FurthestPointSampling.h"

void RegisterOps( Ort::CustomOpDomain& domain ) {
    domain.Add( Ort::Custom::CreateLiteCustomOp<FurthestPointSampling>( "FurthestPointSampling", "CPUExecutionProvider" ) ); // 名字要对应
}

// 其余代码

	// 注册
	m_env = std::make_unique<Ort::Env>( ORT_LOGGING_LEVEL_WARNING, "Onn" );
	
	// 创建会话选项
	Ort::SessionOptions session_options;
	
	session_options.SetGraphOptimizationLevel( GraphOptimizationLevel::ORT_ENABLE_ALL );
	
	// 注册自定义算子
	Ort::CustomOpDomain custom_domain{ "WX" };
	RegisterOps( custom_domain );
	session_options.Add( custom_domain );
        
// 其余代码

C++版本的自定义onnx算子就这样的。

在TensorRT的C++版本里,加载有自定义算子的onnx模型

见我的另一个博客文章

TensorRT 10.x 自定义算子

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的乘积。 注意,以上仅为一般步骤示例,实际上发布自定义算子需要编写更多的代码和配置文件,具体实现需要根据具体需求和环境进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值