BMNET 前端插件¶
BMNET前端插件用于在已提供的BMNET模型编译器的基础上增加用户自定义层或者算子。
BMNETC 插件¶
BMNetC前端下自定义layer实现的可编程环境为imp_bmnetc,该编程环境与Caffe相同。
基于BMLang开发¶
BMLang是提供用户针对Sohpon TPU的上层编程模型,所实现的算法可以在Sohpon TPU中运行,详细见 BMLang 编程 。
这里介绍如何在bmnetc编程环境下对自定义Layer和bmnetc未支持的layer进行TPU编程,并将BMLang实现的layer插入到网络中,与其他layer一起进行网络级的编译,生成网络的bmodel。
以下是以exp layer为例介绍基于BMLang开发网络中未支持layer的步骤:
修改prototxt文件
首先需要修改prototxt中bmnetc不支持的layer type的param。bmnetc提供给用户自定义layer的google proto parameters格式如下:
message UserDefinedParameter { repeated float float_value = 1; repeated string string_value = 2; }
修改.prototxt里面的type为Exp的layer param。caffe原版的Exp prototxt格式为:
layer { name: "exp" type: "Exp" bottom: "log" top: "usr" exp_param { base: 2 scale: 2 shift: 3 } }
需要修改成:
layer { name: "exp" type: "Exp" bottom: "log" top: "usr" user_defined_param { float_value: 2 float_value: 2 float_value: 3 } }
然后在imp_bmnetc的inclue和src里,加入exp layer的.hpp和.cpp代码文件。
示例.hpp代码如下:
#ifndef CAFFE_USER_DEFINED_LAYER_HPP_ #define CAFFE_USER_DEFINED_LAYER_HPP_ #include <vector> #include "bmnetc/blob.hpp" #include "bmnetc/layer.hpp" #include "bmnetc/proto/bmnetc.pb.h" #include "bmnetc/layers/neuron_layer.hpp" namespace bmnetc { /** * @brief Computes @f$ y = \gamma ^ {\alpha x + \beta} @f$, * as specified by the scale @f$ \alpha @f$, shift @f$ \beta @f$, * and base @f$ \gamma @f$. */ template <typename Dtype> class ExpLayer : public NeuronLayer<Dtype> { public: /** * @param param provides UserDefinedParameter UserDefined_param, * with ExpLayer options: */ explicit ExpLayer(const LayerParameter& param) : NeuronLayer<Dtype>(param) {} virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual inline const char* type() const { return "Exp"; } protected: /** * @param bottom input Blob vector (length 1) * -# @f$ (N \times C \times H \times W) @f$ * the inputs @f$ x @f$ * @param top output Blob vector (length 1) * -# @f$ (N \times C \times H \times W) @f$ * the computed outputs @f$ * y = \gamma ^ {\alpha x + \beta} * @f$ */ virtual void CheckBlobCounts(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top){}; virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void layer_deploy(void* p_bmcompiler, const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Forward_bmtpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); void bmtpu_module(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); Dtype inner_scale_, outer_scale_; }; } // namespace bmnetc #endif
示例.cpp代码如下:
其中,LayerSetup需要获取的是UserDefinedParameter,然后根据该UserDefinedParameter设置Exp Layer的变量。
bmtpu_module是用BMLang对Exp编程的模块。
Forward_bmtpu中,需要set_mode(BMLANG_COMPUTE),表明使用cpu来模拟bmtpu_module的计算,以便于我们调试用BMLang对exp layer编程对不对。
layer_deploy则需要set_mode(BMLANG_COMPILE),此时进入compile模式。在运行bmnetc编译caffe model时,会进入layer_deploy函数,并生成可在BMTPU芯片运行的bmodel。
#include <vector> #include "exp_layer.hpp" #include "bmnetc/util/math_functions.hpp" namespace bmnetc { template <typename Dtype> void ExpLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { NeuronLayer<Dtype>::LayerSetUp(bottom, top); const UserDefinedParameter& param = this->layer_param_.user_defined_param(); const Dtype base = param.float_value(0); if (base != Dtype(-1)) { CHECK_GT(base, 0) << "base must be strictly positive."; } // If base == -1, interpret the base as e and set log_base = 1 exactly. // Otherwise, calculate its log UserDefinedlicitly. const Dtype log_base = (base == Dtype(-1)) ? Dtype(1) : log(base); CHECK(!isnan(log_base)) << "NaN result: log(base) = log(" << base << ") = " << log_base; CHECK(!isinf(log_base)) << "Inf result: log(base) = log(" << base << ") = " << log_base; const Dtype input_scale = param.float_value(1); const Dtype input_shift = param.float_value(2); inner_scale_ = log_base * input_scale; outer_scale_ = (input_shift == Dtype(0)) ? Dtype(1) : ( (base != Dtype(-1)) ? pow(base, input_shift) : exp(input_shift) ); } template <typename Dtype> void ExpLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const int count = bottom[0]->count(); const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); if (inner_scale_ == Dtype(1)) { bmnetc_exp(count, bottom_data, top_data); } else { bmnetc_cpu_scale(count, inner_scale_, bottom_data, top_data); bmnetc_exp(count, top_data, top_data); } if (outer_scale_ != Dtype(1)) { bmnetc_scal(count, outer_scale_, top_data); } } template <typename Dtype> void ExpLayer<Dtype>::layer_deploy(void* p_bmcompiler, const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { #if defined(BMCOMPILE) && defined(BMLANG) bmlang::set_mode(bmlang::COMPILE_TPU); bmtpu_module(bottom, top); #endif } template <typename Dtype> void ExpLayer<Dtype>::Forward_bmtpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { #if defined(BMCOMPILE) && defined(BMLANG) bmlang::set_mode(bmlang::COMPUTE_CPU); bmtpu_module(bottom, top); #endif } template <typename Dtype> void ExpLayer<Dtype>::bmtpu_module(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { #if defined(BMCOMPILE) && defined(BMLANG) // Create a bmlang::Tensor for the input bmlang::Tensor bottom_tensor(this->layer_param_.bottom(0), bmlang::FLOAT32, bottom[0]->shape()); // Set data to the created bottom_tensor // If we use bmlang::COMPUTE_CPU or bmlang::BOTH mode for debug, // we must set data from bottom[0] to the bottom_tensor bottom_tensor.set_data((const char*)bottom[0]->cpu_data()); // Create a bmlang::Tensor for the output bmlang::Tensor top_tensor(this->layer_param_.top(0), bmlang::FLOAT32, top[0]->shape()); if (inner_scale_ != Dtype(1)) { // Create a bmlang::Scalar with a constant value "inner_scale_" bmlang::Scalar tmp_inner_scale_((float)inner_scale_, bmlang::FLOAT32); // Create a intermediate bmlang::Tensor bmlang::Tensor mul_tensor("mul_res", bmlang::FLOAT32); // mul_tensor = bottom_tensor * tmp_inner_scale_ bmlang::muls(bottom_tensor, tmp_inner_scale_, mul_tensor); // top_tensor = EXP(mul_tensor) bmlang::active(mul_tensor, top_tensor, ACTIVE_EXP); } else { // top_tensor = EXP(bottom_tensor) bmlang::active(bottom_tensor, top_tensor, ACTIVE_EXP); } if (outer_scale_ != Dtype(1)) { // Create a bmlang::Scalar with a constant value "outer_scale_" bmlang::Scalar tmp_outer_scale_((float)outer_scale_, bmlang::FLOAT32); // top_tensor = top_tensor * tmp_outer_scale_ bmlang::muls(top_tensor, tmp_outer_scale_, top_tensor); } // Get data to the top_tensor // If we use bmlang::COMPUTE_CPU or bmlang::BOTH mode for debug, // we must get data from top_tensor to top[0] top_tensor.get_data((char*)top[0]->mutable_cpu_data()); #endif } INSTANTIATE_CLASS(ExpLayer); REGISTER_LAYER_CLASS(Exp); } // namespace bmnetc
完成代码后,在imp_bmnetc下make即可编译,将生成的libimpbmnetc.so覆盖bmcompiler/libimpbmnetc.so。或者直接make && make install即可。
启动bmnetc编译该caffe model,其中prototxt为修改后的,.caffemodel为已经用原版.prototxt训练好的。最终生成bmodel,在bmruntime时可将bmodel下发到BMTPU芯片中运行。
我们也可以对BMLang程序进行单元测试并调试,看bmlang描述的计算模式对不对。可以写一个test程序,创建该ExpLayer,设置参数,启动Forward_cpu(),再启动Forward_tpu(),对比两个的结果是否相同。如果不同,则对bmtpu_module中的程序进行调试并修改。
基于BMCPU开发¶
BMCPU是提供用户对TPU不能实现的layer进行CPU编程的环境,BMCPU开发用户CPU Layer详细见 BMCPU 插件使用 。
这里介绍如何在bmnetc编程环境下将BMCPU中开发的用户CPU Layer插入到Caffe网络中,并与其他layer一起进行网络级 编译,生成bmodel。
用户Layer的CPU实现
这里假设用户已经在BMCPU下开发完了CPU Layer程序,编译、测试通过,并且通过make install安装libusercpu.so。
修改prototxt文件
和基于BMLang开发一样,请见基于BMLang的开发。
然后在前端imp_bmnetc的inclue和src里,加入layer的.hpp和.cpp代码文件。
这里以user_exp_layer为例,示例.hpp代码请见imp_bmnetc的example代码。与BMLang开发不同的是, 没有Forward_bmtpu和bmtpu_module函数。
user_exp_layer.cpp代码如下,与BMLang开发不同的是,Forward_cpu通过调用bmcpu.h中的bmcpu_user_process 来实现,用户需要将user_cpu_param_t设置正确。bmcpu_user_process根据op_type找到用户自己的cpu layer, 然后将param传给用户cpu layer并启动计算。
layer_deploy函数用于给bmnetc加入该用户cpu layer到graph中,该函数需要调用bmcompiler的add_user_cpu_layer 接口。add_user_cpu_layer需要传入输入输出tensor的信息,以及user_cpu_param_t参数。
template <typename Dtype> void UserExpLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); user_cpu_param_t param; param.op_type = USER_EXP; param.u.exp.inner_scale_ = this->inner_scale_; param.u.exp.outer_scale_ = this->outer_scale_; output_shapes_v.push_back(top[0]->shape()); void* bmcpu_handle = bmcpu_init(); bmcpu_user_process(bmcpu_handle, ¶m, vector<float *>(1, (float *)(const_cast<Dtype *>(bottom_data))), vector<vector<int>>(1, bottom[0]->shape()), vector<float *>(1, (float *)(top_data)), output_shapes_v); bmcpu_uninit(bmcpu_handle); } template <typename Dtype> void UserExpLayer<Dtype>::layer_deploy(void* p_bmcompiler, const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { #if defined(BMCOMPILE) user_cpu_param_t param; param.op_type = USER_EXP; param.u.exp.inner_scale_ = this->inner_scale_; param.u.exp.outer_scale_ = this->outer_scale_; #define LAYER_INPUT_NUM 1 #define LAYER_OUTPUT_NUM 1 /* input shape/dim/name */ int* input_shapes[LAYER_INPUT_NUM]; int input_shape_dims[LAYER_INPUT_NUM]; char *input_names[LAYER_INPUT_NUM]; for (int idx = 0; idx < LAYER_INPUT_NUM; idx++) { input_shapes[idx] = const_cast<int*>(&(bottom[idx]->shape()[0])); input_shape_dims[idx] = (int)(bottom[idx]->shape().size()); input_names[idx] = const_cast<char*>(this->layer_param_.bottom(idx).c_str()); } /* output shape/dim/name */ int* output_shapes[LAYER_OUTPUT_NUM]; int output_shape_dims[LAYER_OUTPUT_NUM]; char *output_names[LAYER_OUTPUT_NUM]; for (int idx = 0; idx < LAYER_OUTPUT_NUM; idx++) { output_shapes[idx] = const_cast<int*>(&(top[idx]->shape()[0])); output_shape_dims[idx] = (int)(top[idx]->shape().size()); output_names[idx] = const_cast<char*>(this->layer_param_.top(idx).c_str()); } add_user_cpu_layer( p_bmcompiler, LAYER_INPUT_NUM, input_shapes, input_shape_dims, input_names, LAYER_OUTPUT_NUM, output_shapes, output_shape_dims, output_names, (void *)(¶m), sizeof(param) ); #endif }
完成代码后,在imp_bmnetc下make即可编译,将生成的libimpbmnetc.so覆盖bmcompiler/libimpbmnetc.so。或者直接make && make install即可。
启动bmnetc编译该caffe model,其中prototxt为修改后的,.caffemodel为已经用原版.prototxt训练好的。最终生成bmodel,在bmruntime时可将bmodel下发到BMTPU芯片中运行。
基于BMKernel开发¶
BMKernel是提供用户针对Sophon TPU的底层编程模型,所实现的算法可以在Sophon TPU中运行,详细见 OKKernel(原BMKernel)介绍 。
这里介绍如何在bmnetc插件编程环境下将BMKernel开放的用户TPU Layer插入到Caffe网络中,并与其他的layer一起进行网络级编译,生成bmodel。
编译器插入用户Layer的BMKernel实现
详细见 编译器自定义TPU层插件 。
修改prototxt文件
和基于BMLang开发一样,请见基于BMLang的开发。
然后在前端imp_bmnetc的inclue和src里,加入layer的.hpp和.cpp代码文件。
deploy时要调用add_tpu_layer这个接口。
完成代码后,在imp_bmnetc下make即可编译,将生成的libimpbmnetc.so覆盖bmcompiler/libimpbmnetc.so。或者直接make && make install即可。
启动bmnetc编译该caffe model,其中prototxt为修改后的,.caffemodel为已经用原版.prototxt训练好的。最终生成bmodel,在bmruntime时可将bmodel下发到BMTPU芯片中运行。
BMNETM 插件¶
BMNetM前端下自定义layer实现的可编程环境为python环境,该环境请见layers_ext文件夹。Note:bmnetm下自定义layer加入网络一起编译目前支持使用Python接口的方式。
基于BMLang开发¶
BMLang是提供用户针对BMTPU编程的接口,所实现的算法可以在BMTPU中运行,详细见 BMLang 编程 。
这里介绍如何在bmnetm编程环境下对自定义layer或者bmnetm未支持的layer进行TPU编程,并将BMLang实现的layer插入到网络中,与其他layer一起进行网络级的编译,生成网络的bmodel。
这里我们以activate_layer为例,介绍基于BMLang开发网络中未支持layer的步骤:
MxNet的python包更新
在进行BMLang开发前,需要将包含自定义layer实现的mxnet python包安装替换原来的mxnet python包。如果已经安装的mxnet python包中已经包含用户自定义layer的cpu实现,则直接进入第2步。
在layer_ext文件夹里加入代码文件
文件夹中已经存在activate_layer.py这个example。其源代码为:
其中,activation_core函数是使用BMLang对activate这个算法进行编程。user_activation则是解析改OP的参数,并调用activation_core。
''' The following is an example that use bmlang(python) to implement a operation for SOPHON TPU A typical bmlang program for an OP implementation should include 1. A compute core that is written by bmlang 2. A register that import the bmlang program to bmnetm compiler 3. (Optional) A debug module that check the accuracy of bmlang program ''' import bmlang import bmnetm ACTIVE_TYPE_DICT = { 'tanh' : bmlang.tanh, 'sigmoid' : bmlang.sigmoid, 'Sigmoid' : bmlang.sigmoid, 'relu' : bmlang.relu, 'exp' : bmlang.exp, 'elu' : bmlang.elu, 'sqrt' : bmlang.sqrt, 'square' : bmlang.square, 'rsqrt' : bmlang.rsqrt, 'absval' : bmlang.abs, 'ln' : bmlang.ln, } ''' The following is the activation compute core that is written by bmlang API The parameters in this function are defined by users ''' def activation_core(bottom_name, top_name, shape, active_func): print('in tensor name: ', bottom_name, 'out tensor name: ', top_name) print('add active layer, shape is {0}'.format(shape)) ## create bmlang tensor of activation input inp_tensor = bmlang.Tensor(bottom_name, dtype = 'float32', shape = shape) ## bmlang compution operation oup_tensor = active_func(inp_tensor, top_name) ''' The following is the register that import bmlang program to bmnetm compiler When we finish it. We must register this function in layer_ext_register.py The name of the function can be defined by users But the paramteters must be set as follows @param layer instance of class bmnetm.Layer, contains layer_name, layer_type, in_tensors, out_tensors, params member layer.in_tensors, layer.out_tensors are lists of bmnetm.Tensor instances, contains name, shape, dims member layer.params is instance of bmnetm.ParamDict herits from OrderedDict, contains key/values from the node['attr'] info ''' def user_activation(layer): ''' In the register, we should firstly parse the params of the OP The following is parser of this OP ''' layer_type, params = layer.layer_type, layer.params if (ACTIVE_TYPE_DICT.__contains__(layer_type)): active_func = ACTIVE_TYPE_DICT.get(layer_type) elif (ACTIVE_TYPE_DICT.__contains__(params["act_type"])): active_func = ACTIVE_TYPE_DICT.get(params["act_type"]) elif (ACTIVE_TYPE_DICT.__contains__(params["act_type"])): active_func = ACTIVE_TYPE_DICT.get(params["act_type"]) else: raise RuntimeError("Not support activate layer type") print('EXT Factory: Bmlang activation is called') ## Get information of the input and output tensor in_tensors, out_tensors = layer.in_tensors, layer.out_tensors bottom_name = in_tensors[0].name top_name = out_tensors[0].name tensor_shape = out_tensors[0].shape ## Then set the bmlang model to COMPILE ## Now we set the mode of bmlang to compile with tpu ## This mean compile this OP through bmcompiler bmlang.set_mode('tpu') ## At last, call the compute core written through bmlang ## Compile the activation core activation_core(bottom_name, top_name, tensor_shape, active_func) ''' BMLang debug example ''' def bmlang_active_debug(): ## 1. call mxnet active cpu compuation ## 2. bmlang.set_mode(bmlang.BMLANG_COMPILE) ## 3. call activation_core() ## 4. compare results from 1 and 3 above
在layer_ext_register.py代码中给bmnetm注册user_activation这个函数
代码如下,如果要注册其它的自定义layer可以类似Activation一样在这里增加注册。
import bmnetm from layers_ext.activate_layer import * ## The layer that is wrote by bmlang can be registered here def layer_ext_register(): ## bmnetm.register('node_op_name', bmlang_register_func) bmnetm.register('Activation', user_activation)
其中’Activation’为该OP,对应.json文件中的”op”: “Activation”
{ "op": "Activation", "name": "relu4", "attrs": {"act_type": "relu"}, "inputs": [[19, 0, 0]] }
基于Python来编译MxNet模型
以下是以lenet为例来介绍,在使用bmnetm compile接口前,需要执行之前的注册程序layer_ext_register()。
#model = r'/path/to/xxx-symbol.json' model = r'../../../nnmodel/mxnet_models/lenet/lenet-symbol.json' #weight = r'/path/to/xxxx-xxxx.params' weight = r'../../../nnmodel/mxnet_models/lenet/lenet-0100.params' #export_dir = r'./xxx' export_dir = r'./compilation' #set target target = r'BM1684' #set input shapes shapes = [[1, 1, 28, 28]] #set network name net_name = r'lenet' ## If user writes user-defined layers through bmlang, please register these layers firstly ## If user does not have user-defined layer through bmlang, please delete the following import layers_ext.layer_ext_register as new_register new_register.layer_ext_register() ## Launch bmnetm compilation import bmnetm bmnetm.compile(model, weight, export_dir, target, shapes, net_name)
运行Python进行模型编译,最终将生成.bmodel,之后可用BMRuntime接口驱动.bmodel在BMTPU芯片上运行。假设上述程序文件是example.py,则:
python3 example.py
我们也可以对BMLang程序进行单元测试并调试,看bmlang描述的计算模式对不对。可以写一个test程序,调用mxnet的activation计算,在使用bmlang.set_mode(‘cpu’)后调用activation_core,对两个输出结果进行比对。若不正确,则调试修改activation_core程序。
BMNETU 插件¶
TBD