算子工程目录结构
├── cpukernel
│ ├── impl //存放算子实现文件xx.h与xx.cc
│ ├── less.cc
│ ├── less.h
│ ├── op_info_cfg
│ ├── aicpu_kernel
│ ├── xx.ini //算子信息库定义文件xx.ini
├── framework
│ ├── tf_plugin //存放算子适配插件实现文件xx.cc
│ ├── less_plugin.cc
├── op_proto //存放原型定义文件xx.h与xx.cc
│ ├── less.h // 算子原型定义文件
│ ├── less.c // 算子原型定义文件
├──test
│ ├── ut // 单元测试文件
│ ├── aicpu_test
│ ├── st // 系统测试文件
│ ├── aicpu_test
│ ├── json
算子的IR用于进行算子的描述,包括算子的输入输出信息、属性信息等,用于把算子注册到算子原型库中。即算子名称.cc和算子名称.h两个文件
#ifndef GE_OP_OPERATORTYPE_H //条件编译
#define GE_OP_OPERATORTYPE_H //进行宏定义
将算子注册的头文件包含到算子IR实现的文件中,operator_reg.h存在于CANN软件安装后文件存储路径的“include/graph/”路径下
#include "graph/operator_reg.h"
REG_OP宏,以***'.'链接INPUT、OUTPUT、ATTR等接口注册算子的输入、输出和属性信息***,最终以OP_END_FACTORY_REG接口结束,完成算子的注册。
输入输出的描述信息顺序需要与算子实现中定义保持一致
namespace ge{
REG_OP(OpType) //算子类型名称
// 输入参数,x宏参数,是算子的输入名称,用户自定义
.INPUT(x, TensorType({ DT_FLOAT, DT_INT32 }))
// 算子输出,算子输出名称
.OUTPUT(y, TensorType({ DT_FLOAT, DT_INT32 }))
// 注册算子时的可选属性,包括算子的属性名称,属性类型以及属性值的默认值
.ATTR(x, Type, DefaultValue)
.OP_END_FACTORY_REG(OpType) // 结束
}
#endif
主要功能
// IR头文件
# include "算子名称.h"
# include
# include
调用接口IMPLEMT_COMMON_INFERFUNC(func_name)
:生成类型为Operator的对象,可直接调用Operator类的接口进行InferShape的实现
推理出算子的输出张量描述
IMPLEMT_COMMON_INFERFUNC(CacheUpdateInferShape)
{
TensorDesc out_desc = op.GetOutputDescByName("x");
out_desc.SetDataType(op.GetInputDescByName("x").GetDataType());
if (op.UpdateOutputDesc("x", out_desc) != GRAPH_SUCCESS) {
return GRAPH_FAILED;
}
return GRAPH_SUCCESS;
}
IMPLEMT_COMMON_INFERFUNC(MatrixDiagPartInferShape)
{
Shape shape = op.GetInputDescByName("x").GetShape();
DataType input_dtype = op.GetInputDescByName("x").GetDataType();
Format input_format = op.GetInputDescByName("x").GetFormat();
std::vector<int64_t> dim_vector;
int64_t dimsInput_1 = shape.GetDimNum() - 1;
int64_t dimsInput_2 = shape.GetDimNum() - 2;
int64_t dimNums_1 = shape.GetDim(dimsInput_1);
int64_t dimNums_2 = shape.GetDim(dimsInput_2);
if (dimNums_1 > dimNums_2) {
for (size_t i = 0; i < shape.GetDimNum() - 1; i++) {
dim_vector.push_back(shape.GetDim(i));
}
} else {
for (size_t i = 0; i < shape.GetDimNum() - 2; i++) {
dim_vector.push_back(shape.GetDim(i));
}
dim_vector.push_back(dimNums_1);
}
Shape output_shape(dim_vector);
TensorDesc td = op.GetOutputDesc("y");
td.SetShape(output_shape);
td.SetDataType(input_dtype);
td.SetFormat(input_format);
(void)op.UpdateOutputDesc("y", td);
return GRAPH_SUCCESS;
}
调用接口IMPLEMT_VERIFIER (OpType, func_name)
:OpType:自定义算子的类型;func_name:自定义的verify函数名称。
验证对于多输入算子,多个tensor的dtype需要保持一致
IMPLEMT_VERIFIER(Pow, PowVerify) {
DataType input_type_x = op.GetInputDescByName("x").GetDataType();
DataType input_type_y = op.GetInputDescByName("y").GetDataType();
if (input_type_x != input_type_y) {
return GRAPH_FAILED;
}
return GRAPH_SUCCESS;
}
COMMON_INFER_FUNC_REG(OpType, func_name);
VERIFY_FUNC_REG(OpType, func_name);
CPU算子的实现包括两部分
文件目录位于cpukernel/impl/xx.h
,进行算子类的声明
// CpuKernel基类以及注册宏定义
#include "cpu_kernel.h"
namespace aicpu {
class SampleCpuKernel : public CpuKernel { // 继承
public:
~SampleCpuKernel() = default;
uint32_t Compute(CpuKernelContext &ctx) override; // 重写
};
} // namespace aicpu
文件目录cpukernel/impl/xx.cc
,实现算子的计算逻辑
#include sample_kernels.h
namespace {
const char *SAMPLE = "sample"; //sample为算子的OpType
}
在命名空间aicpu中定义算子的Compute函数,aicpu命名空间名称不可变,基类及相关定义都在aicpu命名空间中
namespace aicpu {
uint32_t SampleCpuKernel::Compute(CpuKernelContext &ctx) {
... ...
}
// 从context中获取input tensor
Tensor *input = ctx.Input(0);
// 对输入tensor进行基本校验
// 例如,对获取到的input进行空指针校验
if (input == nullptr) {
return 1;
}
// 获取input tensor的shape信息
auto inputShape = input->GetTensorShape();
for (int32_t i = 0; i < inputShape->GetDims(); ++i) {
std::cout << "dim[" << i << "] size:" << inputShape->GetDimSize(i) << std::endl;
}
// 获取input tensor的DataType
DataType inputType = input->GetDataType();
// 获取input tensor的数据地址
auto inputData = input->GetData();
// 获取第i个输入的类型
auto data_type = ctx.Input(i)->GetDataType();
switch (data_type) {
case DT_FLOAT16:
return OpCompute<Eigen::half>(...);
case DT_FLOAT:
return OpCompute<float>(...);
case DT_DOUBLE:
return OpCompute<double>(...);
case DT_INT8:
return OpCompute<int8_t>(...);
case DT_INT16:
return OpCompute<int16_t>(...);
... ...
default:
return PARAM_INVAILD; // const uint32_t PARAM_INVAILD = 1
}
其中,OpCompute函数为算子的计算过程实现函数
// 获取输出tensor的数据地址以及shape
Tensor *output = ctx.Output(0);
auto outputShape = output->GetTensorShape();
auto outputData = output->GetData();
// 保存输出结果
outputData[0] = inputData[0];
主要体现算子在昇腾AI处理器上的具体实现规格,算子支持输入输出type、name等信息。
网络运行时,根据算子信息库做基本校验,并进行算子匹配。
文件目录位于cpukernel/op_info_cfg/aicpu_kernel/xx.ini
算子信息库配置说明
(必选项)
[OpType]
:表示一个算子信息的开始,与原算子原型定义中REG_OP(OpType)的OpType保持一致opInfo.engine
:配置算子调用的引擎,AI CPU固定为DNN_VM_AICPU
opInfo.flagPartial
:默认False
opInfo.computeCost
:默认100opInfo.flagAsync
:默认False
opInfo.opKernelLib
:算子调用的kernelLib,AICPU固定为CUSTAICPUKernel
opInfo.kernelSo
:配置AI CPU算子实现文件编译生成的动态库文件的名称。生成的动态库文件的名称来源于算子工程编译的“build.sh”脚本中配置的“AICPU_KERNEL_TARGET”,例如“AICPU_KERNEL_TARGET”配置的为“cust_aicpu_kernels_3.3.0”,则生成的so名称为:“libcust_aicpu_kernels_3.3.0.so”。opInfo.functionName
:自定义算子调用的kernel函数接口名称,根据是否执行分块进行取值不同。本例使用RunCpuKernel
opInfo.workspaceSize
:配置为内存空间,用于分配算子临时计算的内存,默认1024在真实的硬件环境中,验证算子功能的正确性。
主要功能: