自定义算子开发

地平线工具链中已经支持了丰富的算子,在大多数情况下,您的模型应该可以通过使用hb_mapper工具完成转换并顺利部署到地平线芯片上。 少部分算子不支持情况下,我们建议您先尝试下替换算子的可能性,这样有利于将地平线芯片能力充分发挥出来。
自定义算子目前只提供cpu算子开发能力,可自定义onnx算子以及caffe算子。一个完整的自定义算子应用过程包括注册算子、算子实现、含自定义算子模型转换和运行含自定义op模型四个阶段。
1 自定义onnx算子1.1 将含有自定义算子的pytorch模型导出onnx使用torch.onnx.register_custom_op_symbolic注册自定义算子,再导出onnx模型。有以下几处配置参数需要注意:
1. register_custom_op_symbolic函数的第一个参数'::adaptive_avg_pool2d'为pytorch对应操作符名称,若填写错误,则会导致自定义算子注册失败
2. 操作域必须设置为horizon.custom,算子类型为pyop
3. class_name_s需要与算子实现文件中的类名相对应
4. module_s与算子实现文件名相同,若算子实现文件在当前目录的子目录(custom_op)中,要将相对路径包含进去:custom_op/sample_custom
5. 必须指定input_types_i、output_types_i、output_shape_s三个参数
6. 注意指定opset_version为10或11
参考代码:
import torch 
from horizon_nn.horizon_onnx.onnx_pb import tensorproto 
from torch.onnx.utils import register_custom_op_symbolic 
#prepare your model and input_data
def horizon_pool(g, input, output_size): 
 return g.op( 
        'horizon.custom::pyop', #required, ! must be 'horizon.custom' domain ! 
        input, 
       class_name_s=globalaveragepool,  #required ! must match the class def name in sample_custom python file ! 
        compute_s=compute,  #optional, 'compute' by default 
       module_s=sample_custom, #required ! must match the file name of the op_register_files ! 
        input_types_i=[tensorproto.float],  #required 
       output_types_i=[tensorproto.float], #required 
        output_shape_s=[1, 1024, 1, 1]) #required
register_custom_op_symbolic('::adaptive_avg_pool2d',
                            horizon_pool, 
                            opset_version=11) 
torch.onnx.export(model, input_data, custom_op.onnx, opset_version=11)
1.2 算子实现对应上一节注册自定义算子时配置的算子实现文件(class_name需要保持一致)。
#sample_custom.py
import numpy as np 
from horizon_nn.custom import op_implement_register
@op_implement_register(customidentity)
class customidentity(object): 
    def __init__(self, kernel_size, threshold):
        self._kernel_size = kernel_size 
        self._default_threshold = threshold
def compute(self, x): 
        return x
@op_implement_register(globalaveragepool)
class globalaveragepool(object): 
    def __init__(self): 
        pass
def compute(self, x): 
        return np.nanmean(x, axis=(2, 3)).reshape(-1, 1024, 1, 1)
2 自定义caffe算子2.1 修改prototxt在原始模型文件中,将自定义算子对应的类型标记为custom ,并设置custom_param。params 是算子的传入参数,指定方式为‘param_name’:param_value, 多个参数之间使用 \n 分隔。
layer { 
  name: hr_op 
  type: custom 
  bottom: res3d_in 
  top: res3d 
  custom_param { 
    kind: customidentity 
    shape { 
      dim: 1 
      dim: 512 
      dim: 28 
      dim: 28 
    } 
    params: 'kernel_size': 10 \n'threshold': 0.5 
  } 
}
2.2 算子实现相比于onnx模型,caffe模型的自定义算子实现还需要提供该算子的输出尺寸。
#sample_custom.py
from horizon_nn.custom.op_registration import op_implement_register, op_shape_infer_register 
@op_implement_register(customidentity)
class customidentity(object): 
 def __init__(self, kernel_size, threshold): 
        self._kernel_size = kernel_size 
        self._default_threshold = threshold
def compute(self, x): 
 return x 
@op_shape_infer_register(customidentity)
def infer_shape(inputs_shape): 
 infer the output shapes of the custom operator. 
    arguments: 
        input_shapes: a list of input shapes. 
    returns: 
        return a list of custom operator's output shapes.
outputs_shape = inputs_shape 
 return outputs_shape
3 含自定义算子的模型转换在模型转换配置文件中,添加自定义算子相关参数,示例如下:
custom_op_method:固定使用 register;
op_register_files:自定义算子计算的实现文件,如果有多份实现,使用 ‘;’ 将各个文件分开即可。
4 含自定义算子的模型推理想将包含自定算子的.bin模型顺利部署到开发板上,还需要提供自定义算子的c++代码实现。 您可以使用下文提供的模板进行修改:
头文件:
// custom_identity.h
#ifndef advanced_samples_custom_identity_h_ 
#define advanced_samples_custom_identity_h_ 
#include  
#include  
#include dnn/hb_dnn.h 
#include dnn/plugin/hb_dnn_layer.h 
#include dnn/plugin/hb_dnn_ndarray.h 
namespace hobot { 
namespace dnn { 
layer *customidentity_layer_creator(); 
class customidentity : public layer { 
public:
  customidentity() = default; 
  ~customidentity() override = default; 
public:
  int32_t init(const attribute &attributes) override;
int32_t forward(const std::vector &bottomblobs, 
                  std::vector &topblobs, 
 const hbdnninferctrlparam *inferctrlparam) override;
std::string gettype() const override { return customidentity; } 
private:
  std::string module_; 
}; 
}  // namespace dnn 
}  // namespace hobot 
#endif
cpp文件:
// custom_identity.cpp 
#include custom_identity.h 
namespace hobot { 
namespace dnn { 
layer *customidentity_layer_creator() { return new customidentity; } 
int32_t customidentity::init(const attribute &attributes) { 
 // unused attribute, just demonstrating 
  attributes.getattributevalue(&module_, module); 
 return 0; 

int32_t customidentity::forward(const std::vector &bottomblobs, 
                                std::vector &topblobs, 
 const hbdnninferctrlparam *inferctrlparam) { 
 const ndarray *input = bottomblobs[0]; 
  ndarray *out = topblobs[0]; 
 const auto *input_data = input->dptr(); 
 auto *out_data = out->dptr(); 
  uint32_t size = input->size();
for (uint32_t i = 0u; i < size; i++) { 
    out_data[i] = input_data[i]; 
  } 
 return 0; 

}  // namespace dnn 
}  // namespace hobot
将以上两个文件放在当前工程目录下之后,编写infer代码时仅需要在加载模型之前增加对算子的注册即可,注册可参考以下代码:
//infer.cpp
#include custom_identity.h
// register custom layer 
hbdnnregisterlayercreator(customidentity,
                         hobot::dnn::customidentity_layer_creator)
本文转载自地平线开发者社区:https://developer.horizon.ai
原作者:颜值即正义
原文链接:https://developer.horizon.ai/forumdetail/71036525692881018

STM32L4进入STOP2模式后的漏电问题的分析及解决
Improve Two-Phase Buck Convert
迷你音频放大器电路图分享
笔记本电池的维护
国外各国是如何利用无人机进行防疫工作的
自定义算子开发
关于无线传感器网络MAC协议的详细介绍
屏蔽电缆防止电磁噪声干扰的方法有哪些
使用PMIC管理SoC电源
英特尔证实已获得对华为的出货许可,华为PC及服务器业务将有保障
颁奖盛典完美落幕 硬创大咖群英汇聚
荣耀8青春版:华为又一款颜值爆表的手机,双面玻璃,4+64G售1599元
五类水晶头六类水晶头一字之差区别甚大-科兰
详细解读Zynq的三种启动方式(JTAG,SD,QSPI)
你的星座适合用哪款手机呢?
采用RFDAC的多频段、多标准发射器设计
工业机器人运动轴知识详解
SSD价格还要继续大跌:再降13%
怎样将具有ILI9341芯片的2.8英寸SPI TFT与Arduino Uno连接
智慧物流领域的领军企业仙工智能获“2020深圳十大关键零部件企业”