华为CANN训练营笔记——算子开发

10 篇文章 4 订阅

算子工程目录结构

├── 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

1. 算子原型定义

算子的IR用于进行算子的描述,包括算子的输入输出信息、属性信息等,用于把算子注册到算子原型库中。即算子名称.cc算子名称.h两个文件

1.1 算子.h实现

1.1.1 宏定义

#ifndef GE_OP_OPERATORTYPE_H       //条件编译
#define GE_OP_OPERATORTYPE_H       //进行宏定义

1.1.2 包含头文件

将算子注册的头文件包含到算子IR实现的文件中,operator_reg.h存在于CANN软件安装后文件存储路径的“include/graph/”路径下

#include "graph/operator_reg.h"

1.1.3 原型注册

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)  // 结束
}

1.1.4 结束条件编译

#endif

1.2 算子.cc实现

主要功能

  • 算子参数的校验,实现程序健壮性并提高定位效率。
  • 根据算子的输入张量描述、算子逻辑及算子属性,推理出算子的输出张量描述,包括张量的形状、数据类型及数据排布格式等信息

1.2.1 包含头文件

// IR头文件
# include "算子名称.h"		
# include <vector>
# include <string>

1.2.2 实现InferShape

调用接口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;
}

1.2.3 实现Verify

调用接口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;
}

1.2.4 注册InferShape和Verify方法

COMMON_INFER_FUNC_REG(OpType, func_name);  
VERIFY_FUNC_REG(OpType, func_name);

2. 算子代码实现

CPU算子的实现包括两部分

  • 头文件:算子类的声明,自定义算子类需要继承CpuKernel基类
  • 源文件:重写Compute函数,实现计算逻辑

2.1 头文件定义

文件目录位于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

2.2 源文件实现

文件目录cpukernel/impl/xx.cc,实现算子的计算逻辑

2.2.1 引入头文件

#include sample_kernels.h

2.2.2 定义命名空间

namespace {         
const char *SAMPLE = "sample";      //sample为算子的OpType 
}

2.2.3 定义命名空间aicpu

在命名空间aicpu中定义算子的Compute函数,aicpu命名空间名称不可变,基类及相关定义都在aicpu命名空间中

namespace aicpu {
uint32_t SampleCpuKernel::Compute(CpuKernelContext &ctx) {
... ...
}

2.2.4 重写Compute函数

  • 输入的合法性检验
// 从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];

3. 算子信息库定义

主要体现算子在昇腾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:默认100
  • opInfo.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

4. 算子UT测试

  • 测试算子代码的正确性,验证输入输出结果与设计的一致性。
  • UT侧重于保证算子程序能够跑通,降低不同场景下算子代码的编译失败率。

5. ST测试

在真实的硬件环境中,验证算子功能的正确性。
主要功能:

  • 基于算子测试用例定义文件*.json生成单算子的om文件
  • 使用AscendCL接口加载并执行单算子om文件,验证算子执行结果的正确性。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值