AICPU算子,是运行在昇腾AI处理器中AICPU计算单元上的表达一个完整计算逻辑的运算,如下情况下,开发者需要自定义AICPU算子。
在NN模型训练或者推理过程中,将第三方开源框架转化为适配昇腾AI处理器的模型时遇到了昇腾AI处理器不支持的算子。此时,为了快速打通模型执行流程,用户可以通过自定义AICPU算子进行功能调测,提升调测效率。功能调通之后,后续性能调测过程中再将AICPU自定义算子转换成TBE算子实现。
某些场景下,无法通过AI Core实现自定义算子(比如部分算子需要int64类型,但AI Core指令不支持),且该算子不是网络的性能瓶颈,此时可以通过开发AICPU自定义算子实现昇腾AI处理器对此算子的支持。
整体的AICPU算子开发流程如下所示:
算子分析:明确算子的功能、输入、输出,规划算子类型名称以及算子编译生成的库文件名称等。
工程创建:通过MindStudio工具创建AICPU算子工程,创建完成后,会自动生成算子工程目录及相应的文件模板,开发者可以基于这些模板进行算子开发。
算子开发:
算子编译:将算子适配插件实现文件、原型定义文件、算子原型库、算子信息库。
算子部署:将算子实现文件、插件库文件、原型库、信息库部署到算子库中(opp的对应目录下)。
算子实现验证:
LogSpace算子交付件目录展示
├── .idea
├── build //编译生成的中间文件
├── cmake //编译相关公共文件存放目录
├── cmake-build //编译相关生成文件存放目录
├── cpukernel //AI CPU算子文件目录
│ ├── context //算子公共环境文件目录
│ ├── impl //算子实现文件目录
│ │ ├── utils //算子公共工具资源文件目录
│ │ ├── log_space_kernel.cc //算子源文件
│ │ ├── log_space_kernel.h //算子头文件
│ ├── op_info_cfg //算子信息库文件目录
│ │ ├── aicpu_kernel
│ │ │ ├── log_space.ini //算子信息定义文件
│ ├── CMakeLists.txt //编译规则文件,会被算子工程根目录中的CMakeLists.txt文件调用
│ ├── toolchain.cmake
├── framework //算子插件实现文件目录
│ ├── CMakeLists.txt //编译规则文件,会被算子工程根目录中的CMakeLists.txt文件调
├── op_proto //算子IR定义文件目录
│ ├── inc
│ ├── utils //算子IR定义工具目录
│ ├── log_space.cc //add为算子类型
│ ├── log_space.h //add为算子类型
│ ├── CMakeLists.txt //编译规则文件,会被算子工程根目录的CMakeLists.txt文件调用
├── scripts //工程相关脚本
├── testcases //工程ut测试和st测试相关代码目录
│ │ ├── libs // gtest框架,为第三方依赖,用户无需关注
│ │ ├── st
│ │ │ ├── log_space
│ │ │ │ ├── aicpu_kernel
│ │ │ │ │ ├── LogSpace_case_20220415185113.json //测试用例编写
│ │ ├── ut
│ │ │ ├── aicpu_test
│ │ │ │ ├── reshape_cust
│ │ │ │ │ ├── CMakeLists.txt //用于编译可执行文件
│ │ │ │ │ ├── test_reshape_cust_impl.cc //算子实现代码的测试用例文件
│ │ │ │ │ ├── test_reshape_cust_proto.cc //算子原型定义代码的测试用例文件
│ │ │ │ ├── CMakeLists.txt //用于编译可执行文件
│ │ │ │ ├── test_main.cc //测试用例调用总入口
│ │ ├ CMakeLists.txt
├── third_party //工程第三方依赖目录
├── .project //工程信息文件,包含工程类型、工程描述、运行目标设备类型以及CANN版本
├── CMakeLists.txt
├── MyOperator.iml
LogSpace算子交付件介绍
本交付件包括算子分析、代码实现以及ut测试等LogSpace算子的所有开发流程,以及使用MindStudio开发算子的图文介绍。第三节介绍了算子的分析,包括算子的输入输出,属性以及数学表达式。第四节介绍了MindStudio算子工程的创建。第五节介绍了算子的原型定义,并对代码进行注释讲解。第六节对算子的逻辑部分进行代码的具体实现,并进行注释讲解。第七节介绍了算子的编译和部署的MindStudio的操作讲解。第八节介绍了算子的UT测试操作,第九节介绍了算子的ST测试操作。
阅读本交付件前,请先安装MindStudio,MindStudio安装教程请参考here。安装完Mindstudio后开始配置cann,cann配置请参考here。
明确算子功能及数学表达式
LogSpace的算子数学表达式为:
y [ i ] = b a s e s t a r t ∗ ( b a s e e n d − s t a r t s t e p s − 1 ) i i = 1 , 2 , 3 , 4... s t e p s y[i]=base^{start}*(base^{\frac{end-start}{steps-1}})^i \quad i=1,2,3,4...steps y[i]=basestart∗(basesteps−1end−start)ii=1,2,3,4...steps
计算过程为:输入点集的起始值start和点集的最终值end,以base为对数函数的基,采样steps个点数,生成对数间距一维Tensor。
明确算子输入和输出
明确算子实现文件名称以及算子的类型(OpType)
算子类型定义为“LogSpace”,算子的代码实现文件名称为“log_space.h”与“log_space.cc”。通过以上分析,得到LogSpace算子的设计规格如下:
name | shape | dtype | format |
---|---|---|---|
start | (0,) | float16、float32 | ND |
end | (0,) | float16、float32 | ND |
y | (steps,) | float16、float32 | ND |
打开MindStudio进入算子工程创建界面
创建算子工程
参数 | 参数说明 | 示例 |
---|---|---|
Name | 工程名称,用户自行配置。名称必须以字母开头,数字或字母结尾,只能包含字母、数字、中划线和下划线,且长度不能超过64个字符。 | MyOperator8 |
Description | 工程描述信息,自行配置。 | 可选配置 |
CANN Version | 当前CANN的版本号。 | 选择当前CANN的版本号 |
Project Location | 工程的存储路径。 | 保持默认 |
注意:如果没有cann信息,请参考[here](https://www.hiascend.com/document/detail/zh/ mindstudio/304/instg/atlasms_02_0023.html)进行配置
*参数* | *参数说明* |
---|---|
Empty Template | 表示创建空的算子工程。选择此选项,下方会显示“Operator Type”配置项,请在此处输入需要创建的算子的类型,请根据算子分析进行配置。 |
Plugin Framework | 算子所在模型文件的框架类型。如果选择“Sample Template”创建算子工程时不显示此配置项。l MindSporel PyTorchl TensorFlowl Caffel ONNX |
Compute Unit | 有以下两种选项,选择“Sample Template”创建算子工程时不显示此配置项。l AI Core / Vector Core:算子如果运行在AI Core或者Vector Core上,则代表是TBE算子。l AICPU:算子如果运行在AICPU上,则代表是AICPU算子。如果“Plugin Framework”选择“MindSpore”, 则仅支持选择“AI Core / Vector Core” |
3. 单击Finish,完成算子工程的创建
若工作窗口已打开其他工程,会出现如图所示提示。
简介。算子原型定义规定了在昇腾AI处理器上可运行算子的约束,主要体现算子的数学含义,包含定义算子输入、输出和属性信息,基本参数的校验和shape的推导,原型定义的信息会被注册到GE的算子原型库中。网络模型生成时,GE会调用算子原型库的校验接口进行基本参数的校验,校验通过后,会根据原型库中的推导函数推导每个节点的输出shape与dtype,进行输出tensor的静态内存的分配。IR定义算子原型说明如表4。
OP | classify | name | typerange | dtype | Required | DOC | Default |
---|---|---|---|---|---|---|---|
LogSpace | INPUT | start | Tensor | float16、float32 | TRUE | 点集的起始值 | |
INPUT | end | Tensor | float16、float32 | TRUE | 点集的最终值 | ||
ATTR | steps | int | int32 | FALSE | 采集的点数 | 100 | |
ATTR | base | int | int32 | FALSE | 对数函数的基数 | 10 | |
ATTR | dtype | int | int32 | FALSE | 输出值类型 | 1 | |
OUTPUT | y | Tensor | float16、float32 | TRUE | 输出的张量 |
定义算子代码实现
a. 头文件算子原型的注册op_proto/log_space.h的代码实现,和代码解析。
#ifndef GE_OP_LOG_SPACE_H
#define GE_OP_LOG_SPACE_H
#include "graph/operator_reg.h"
namespace ge {
REG_OP(LogSpace)
.INPUT(start, TensorType({DT_FLOAT, DT_FLOAT16}))//第一个输入
.INPUT(end, TensorType({DT_FLOAT, DT_FLOAT16}))//第二个输入
.OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16}))//定义输出
.ATTR(steps, Int, 100)//定义第一个属性
.ATTR(base, Int, 10)//定义第二个属性
.ATTR(dtype, Int, 1)//定义第三个属性
.OP_END_FACTORY_REG(LogSpace)//注册算子输出信息
}
#endif //GE_OP_LOG_SPACE_H
REG_OP(LogSpace)
LogSpace:注册到昇腾AI处理器的自定义算子库的算子类型,可以任意命名但不能和已有的算子命名冲突。
INPUT(start, TensorType({ DT_FLOAT,DT_FLOAT16 }))注册算子的输入参数信息。
start:宏参数,算子的输入名称,用户自定义。
TensorType({ DT_FLOAT,DT_FLOAT16}):“{ }”中为此输入支持的数据类型的列表,支持的数据类型请参见 DataType,TensorType提供了一些接口指定支持的数据类型,详细定义请参见TensorType。
这里有两个输入,每个输入需要使用一条INPUT(……)语句进行描述。
ATTR(steps, Int, 100),注册属性steps,属性类型为int64_t,默认值为100。若算子有多个属性,则每个属性需要使用一条ATTR(x, Type,DefaultValue)语句进行注册。
OUTPUT(y, TensorType({ DT_FLOAT,DT_FLOAT16 }))注册算子的输出信息。
y:宏参数,算子的输出名称,用户自定义。
TensorType({ DT_FLOAT,DT_FLOAT16 }):“{ }”中为此输出支持的数据类型的列表,支持的数据类型请参见DataType,TensorType提供了一些接口指定支持的数据类型,详细定义请参见TensorType。若算子有多个输出,则每个输出都需要使用一条OUTPUT(x, TensorType { DT_FLOAT , DT_FLOAT16 , … }) )语句进行注册。
b. op_proto/log_space.cc文件中进行校验函数与shape推导函数的实现。
// 对steps属性进行校验
static bool CheckSteps(const Operator& op, const string& attr_num_steps) {
int64_t steps = 0;
int64_t steps_ori = 100;
//如果成功获得steps属性,就将其设置到steps变量中,否则就设为默认值100
if (ge::GRAPH_SUCCESS != op.GetAttr(attr_num_steps.c_str(), steps)) {
steps = steps_ori;
}
//判断steps属性是否大于0,小于0则返回false,大于返回true
if (steps < 0) {
return false;
}
return true;
}
IMPLEMT_VERIFIER(LogSpace, LogSpaceVerify)
{
//检验start输入维度
if (op.GetInputDesc("start").GetShape().GetDims().size() != 1) {
OP_LOGE(op.GetName().c_str(), "Input start size must be 1.");
return GRAPH_FAILED;
}
//检验end输入维度
if (op.GetInputDesc("end").GetShape().GetDims().size() != 1) {
OP_LOGE(op.GetName().c_str(), "Input end size must be 1.");
return GRAPH_FAILED;
}
//获取start和end的输入数据类型
DataType input_type_start = op.GetInputDescByName("start").GetDataType();
DataType input_type_end = op.GetInputDescByName("end").GetDataType();
if (input_type_start != input_type_end) {
return GRAPH_FAILED;
}
return GRAPH_SUCCESS;
}
IMPLEMT_COMMON_INFERFUNC(LogSpaceInferShape)
{
// 获得算子的输出
TensorDesc v_output_desc = op.GetOutputDescByName("y");
int64_t steps;
int64_t num_rows = 1;
//获取算子的steps属性并设置到steps中
op.GetAttr("steps", steps);
//判断steps属性是否是设置正确的,不是的话就返回失败
if (!CheckSteps(op, "steps")) {
OP_LOGE(op.GetName().c_str(),
"the attr 'steps' should be greater than or equal to 0.");
return GRAPH_FAILED;
}
std::vector dim_vec;
dim_vec.push_back(num_rows);
dim_vec.push_back(steps);
//设置输出维度
v_output_desc.SetShape(ge::Shape(dim_vec));
int64_t dtype = 1;
//获取dtype属性值并设置输出属性类型
if (op.GetAttr("dtype", dtype) != GRAPH_SUCCESS) {
v_output_desc.SetDataType(DT_FLOAT16);
} else {
if (dtype == 1) {
v_output_desc.SetDataType(DT_FLOAT16);
}
if (dtype == 0) {
v_output_desc.SetDataType(DT_FLOAT);
}
}
//更新输出属性
(void)op.UpdateOutputDesc("y", v_output_desc);
return GRAPH_SUCCESS;
}
算子信息库
算子信息库主要体现算子在昇腾AI处理器上的具体实现规格,包括算子支持输入输出type、name等信息。网络运行时,根据算子信息库中的算子信息做基本校验,并进行算子匹配。代码如下。
[LogSpace]
opInfo.engine=DNN_VM_AICPU
opInfo.flagPartial=False
opInfo.computeCost=100
opInfo.flagAsync=False
opInfo.opKernelLib=CUSTAICPUKernel
opInfo.kernelSo=libcust_aicpu_kernels.so
opInfo.functionName=RunCpuKernel
opInfo.workspaceSize=1024
opInfo.opsFlag=OPS_FLAG_OPEN
opInfo.userDefined=False
opInfo.subTypeOfInferShape=2
opInfo.formatAgnostic=True
input0.type=DT_FLOAT,DT_FLOAT16
input0.name=start
input1.type=DT_FLOAT,DT_FLOAT16
input1.name=end
output0.type=DT_FLOAT,DT_FLOAT16
output0.name=y
AI CPU算子的实现包括两部分:
头文件(.h文件):进行算子类的声明,自定义算子类需要继承CpuKernel基类。
源文件(.cc文件):重写算子类中的Compute函数,进行算子计算逻辑的实现。
头文件代码模块介绍
用户需要在算子工程的“cpukernel/impl/log_space_kernel.h”文件中进行算子类的声明,代码模块介绍如下所示:
#ifndef _LOG_SPACE_KERNELS_H_
#define _LOG_SPACE_KERNELS_H_
// CpuKernel基类以及注册宏定义
#include "cpu_kernel.h"
//数据类型以及格式等定义
#include "cpu_types.h"
namespace aicpu {//定义命名空间aicpu
class LogSpaceCpuKernel : public CpuKernel {//Reshapecust算子类继承CpuKernel林类
public:
~LogSpaceCpuKernel() = default;
virtual uint32_t Compute(CpuKernelContext &ctx) override;//声明函数Compute,Compute函数需要重写
private:
uint32_t LogSpaceCheck(CpuKernelContext &ctx);//对输入和属性进行校龄
template
uint32_t LogSpaceCompute(CpuKernelContext &ctx);//逻辑实现部分
};
} // namespace aicpu
#endif
引入相关头文件。
头文件cpu_kernel.h,其包含了AI CPU算子基类CpuKernel的定义,以及Kernels的注册宏的定义。
头文件cpu_types.h,包含了AI CPU的数据类型以及格式等定义。
进行算子类的声明。此类为CpuKernel类的派生类,并需要声明重载函数Compute,Compute函数需要在算子实现文件中进行实现。算子类的声明需要在命名空间“aicpu”中,命名空间的名字“aicpu”为固定值,不允许修改。
源文件代码块介绍
用户需要在算子工程的“cpukernel/impl/log_space_kernel.cc”文件中进行算子的计算逻辑实现,代码模块介绍如下所示
#include "log_space_kernels.h"
#include "cpu_kernel_utils.h"
#include "log.h"
#include "utils/eigen_tensor.h"
#include "utils/kernel_util.h"
namespace {
constexpr uint32_t kLogSpaceInputNum = 2;//输入参数数量
constexpr uint32_t kLogSpaceOutputNum = 1;//输出参数数量
const char *kLogSpace = "LogSpace";//LogSpace为算子原型中注册的算子的类型
//判断计算结果是否正确
#define LOGSPACE_COMPUTE_CASE(DTYPE, TYPE, CTX) \
case (DTYPE): { \
uint32_t result = LogSpaceCompute(CTX); \
if (result != KERNEL_STATUS_OK) { \
KERNEL_LOG_ERROR("LogSpace kernel compute failed."); \
return result; \
} \
break; \
}
} // namespace
namespace aicpu {// 定义命名空间aicpu
// 实现自定义算子类的Compute函数
uint32_t LogSpaceCpuKernel::Compute(CpuKernelContext &ctx)
{
// 检验属性是否符合要求
KERNEL_HANDLE_ERROR(LogSpaceCheck(ctx), "[%s] check params failed.",
kLogSpace);
DataType data_type = ctx.Output(0)->GetDataType();
// 判断输出是什么类型,并传入相应的类型的数据计算
switch (data_type) {
//半精度类型计算
LOGSPACE_COMPUTE_CASE(DT_FLOAT16, Eigen::half, ctx)
//单精度类型计算
LOGSPACE_COMPUTE_CASE(DT_FLOAT, float, ctx)
default:
KERNEL_LOG_ERROR("LogSpace kernel data type [%s] not support.",
DTypeStr(data_type).c_str());
return KERNEL_STATUS_PARAM_INVALID;
}
return KERNEL_STATUS_OK;
}
//检验属性steps和base
uint32_t LogSpaceCpuKernel::LogSpaceCheck(CpuKernelContext &ctx) {
// 获取属性steps
AttrValue *steps_attr_ptr = ctx.GetAttr("steps");
if (steps_attr_ptr) {
// 判断属性steps是否大于0
int64_t steps_data = steps_attr_ptr->GetInt();
KERNEL_CHECK_FALSE((steps_data >= 0), KERNEL_STATUS_PARAM_INVALID,
"Attr [steps] data has to be greater than or equal to 0.");
}
// 获取属性base
AttrValue *base_attr_ptr = ctx.GetAttr("base");
if (base_attr_ptr) {
// 检验属性base是否大于0且不等于1
int64_t base_data = base_attr_ptr->GetInt();
KERNEL_CHECK_FALSE((base_data > 0), KERNEL_STATUS_PARAM_INVALID,
"Attr [base] data must be positive.");
KERNEL_CHECK_FALSE((base_data != 1), KERNEL_STATUS_PARAM_INVALID,
"Attr [base] data has to be different than 1.");
}
return KERNEL_STATUS_OK;
}
template
uint32_t LogSpaceCpuKernel::LogSpaceCompute(CpuKernelContext &ctx) {
//获取输入和输出的数据类型
DataType data_type_in = ctx.Input(0)->GetDataType();
DataType data_type = ctx.Output(0)->GetDataType();
//获取属性值
AttrValue *steps_data = ctx.GetAttr("steps");
AttrValue *base_data = ctx.GetAttr("base");
int64_t steps_value = 100;//默认属性值
int base_value = 10;//默认属性值
if (steps_data) {
steps_value = steps_data->GetInt();
}
if (base_data) {
base_value = base_data->GetInt();
}
//获取输出数据
auto output_y = reinterpret_cast(ctx.Output(0)->GetData());
if (data_type_in == data_type) {
//获取输入数据
T input_start = *reinterpret_cast(ctx.Input(0)->GetData());
T input_end = *reinterpret_cast(ctx.Input(1)->GetData());
//实现logspce的数学表达式
if (steps_value != 1) {
double b = (input_end - input_start) / (steps_value - 1);
double q = pow(base_value, b);
double input_start_value = input_start;
for (int64_t i = 0; i < steps_value; i++) {
double end_num = pow(base_value, input_start_value) * pow(q, i);
*(output_y + i) = static_cast(end_num);
}
}else {
double end_num = pow(base_value, double(input_start));
*(output_y) = static_cast(end_num);
}
}
else if (data_type_in == DT_FLOAT) {
//获取输入数据,并转换类型
float input_start_ = *reinterpret_cast(ctx.Input(0)->GetData());
float input_end_ = *reinterpret_cast(ctx.Input(1)->GetData());
Eigen::half input_start = Eigen::half(input_start_);
Eigen::half input_end = Eigen::half(input_end_);
//实现logspce的数学表达式
if (steps_value != 1) {
double b = (input_end - input_start) / (steps_value - 1);
double q = pow(base_value, b);
double input_start_value = input_start;
for (int64_t i = 0; i < steps_value; i++) {
double end_num = pow(base_value, input_start_value) * pow(q, i);
*(output_y + i) = static_cast(end_num);
}
}else {
double end_num = pow(base_value, double(input_start));
*(output_y) = static_cast(end_num);
}
}
else if (data_type_in == DT_FLOAT16) {
//获取输入数据,并转换类型
Eigen::half input_start_ = *reinterpret_cast(ctx.Input(0)->GetData());
Eigen::half input_end_ = *reinterpret_cast(ctx.Input(1)->GetData());
float input_start = float(input_start_);
float input_end = float(input_end_);
double base_value_ = double(base_value);
//实现logspce的数学表达式
if (steps_value != 1) {
double b = (input_end - input_start) / (steps_value - 1);
double q = pow(base_value_, b);
for (int64_t i = 0; i < steps_value; i++) {
double end_num = double(pow(base_value_, double(input_start)) * pow(q, i));
*(output_y + i) = static_cast(end_num);
}
}else{
double end_num = pow(base_value, double(input_start));
*(output_y) = static_cast(end_num);
}
}
return KERNEL_STATUS_OK;
}
// 注册该算子实现
REGISTER_CPU_KERNEL(kLogSpace, LogSpaceCpuKernel);
} // namespace aicpu
引入相关头文件
头文件log_space_kernel.h,[头文件代码模块介绍](javascript:;)中声明的头文件。若在[头文件代码模块介绍](javascript:;)已经引入,无需在重复引入。
定义命名空间,声明常量字符指针指向算子的OpType
如下所示:
namespace { const char *kLogSpace = “LogSpace”};
其中,LogSpace为算子的OpType,kLogSpace为声明的指向算子OpType的常
量指针。
**定义命名空间aicpu,并在命名空间aicpu中实现自定义算子的Compute函数,定义算子的计算逻辑。**命名空间的名称aicpu为固定值,基类及相关定义都在aicpu
命名空间中。
Compute函数声明。
uint32_t LogSpaceCpuKernel::Compute(CpuKernelContext &ctx)
LogSpaceCpuKernel为头文件中定义的自定义算子类,形参CpuKernelContext为CPU Kernel的上下文,包括算子的输入输出Tensor以及属性等相关信息。
Compute函数体中。
根据算子开发需求,编写相关代码实现获取输入tensor相关信息,并根据输入信息组织计算逻辑,得出输出结果,最后将输出结果设置到输出tensor中。
注册算子的cpukerne实现。
REGISTER_CPU_KERNEL(kLogSpace, LogSpaceCpuKernel);
第一个参数kLogSpace为[2](javascript:;)中定义的指向算子OpType的字符串指针。
第二个参数LogSpaceCpuKernel为自定义算子类的名称。
编译基本概念:将算子适配插件实现文件、原型定义文件、信息定义文件编译成算子插件库文件、算子原型库、算子信息库。
在MindStudio工程界面,选中算子工程。单击顶部菜单栏的“ Build > Edit Build Configuration…”。弹出如下窗口。
进入编译配置页面。检查Environment Variables配置是否配置完整,点击输入框右边文档图标,查看环境变量配置,确保对应参数路径正确,如图。配置解析请参考下表5进行编译配置。
参数 | 说明 |
---|---|
Build Configuration | 编译配置名称,默认为Build-Configuration。 |
Build Mode | 编译方式:Remote Build:远端编译。Local Build:本地编译。 |
Deployment | Remote Build模式下显示该配置。deployment可以将指定项目中的文件、文件夹同步到远程指定机器的指定目录。 |
Environment Variables | Remote Build模式下显示该配置。为远程环境配置ASCEND_OPP_PATH、TOOLCHAIN_DIR等相关环境变量。 |
AICPU SoC Version | 用于配置昇腾AI处理器的类型。 |
单击顶部菜单栏的“Ascend > Operator Deploymen”,进入算子打包部署界面。请在Deploy Remotely > Deployment选择配置项。
选择算子部署的目标服务器,单击“Operator deploy”。
算子部署过程即算子工程编译生成的自定义算子安装包的安装过程,部署完成后,算子被部署在Host侧算子库OPP对应文件夹中,若不选择指定的算子库OPP包则默认路径为/usr/local/Ascend/opp/
Host侧自定义算子部署完成后目录结构示例如下所示:
├── opp //算子库目录
│ ├── op_impl
│ ├── built-in
│ ├── custom
│ ├── ai_core
│ ├── cpu
│ ├── aicpu_kernel/
│ ├── custom_impl //自定义算子实现代码文件
│ ├── libcust_aicpu_kernels.so
│ ├── config
│ ├── cust_aicpu_kernel.json //自定义算子信息库文件
│ ├── vector_core //此目录预留,无需关注
│ ├── op_proto
│ ├── built-in
│ ├── custom
│ ├── libcust_op_proto.so //自定义算子原型库文件
MindStudio提供了基于gtest框架的新的UT测试方案,简化了开发者开发UT测试用例的复杂度。UT(Unit Test:单元测试)是开发人员进行单算子运行验证的手段之一,主要目的是:
测试算子代码的正确性,验证输入输出结果与设计的一致性。
UT侧重于保证算子程序能够跑通,选取的场景组合应能覆盖算子代码的所有分支(一般来说覆盖率要达到100%),从而降低不同场景下算子代码的编译失败率。
需完成自定义算子的开发,包括算子实现代码和算子原型定义。
安装CMake,要求版本为3.14及以上,若CMake版本不满足要求,请升级CMake版本。
MindStudio已连接硬件设备。
创建UT测试用例,有以下三种方式。
在弹出的算子选择界面,选择需要创建UT测试用例的算子,单击OK。创建完成后,会在算子工程根目录下生成testcases文件夹,并生成ut测试用例。
编写算子实现代码的UT C++测试用例。
在“testcases/ut/aicpu_test/reshape_cust/test_log_space_impl.cc”文件中,编写算子实现代码的UT C++测试用例,计算出算子执行结果,并取回结果和预期结果进行比较,来测试算子逻辑的正确性。
TEST_F(LogSpaceTest, log_space_test_case_1) {
vector> shapes = {{1}, {1}, {1, 10}, {1}};
vector data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
//输入输出数据定义
float input1[1] = {2};
float input2[1] = {10};
float output[10] = {(float)0};
float expect_out[10] = {4.0, 7.4069977, 13.715903, 25.398417,
47.031506, 87.09056, 161.2699, 298.63144, 552.9906, 1024.0};
vector datas = { (void *)input1,
(void *)input2,
(void *)output };
CREATE_NODEDEF(shapes, data_types, datas);
CpuKernelContext ctx(DEVICE);
EXPECT_EQ(ctx.Init(node_def.get()), 0);
//定义算子
LogSpaceCpuKernel cpuKernel;
//计算输入数据
cpuKernel.Compute(ctx);
//对比计算输出是否正确
bool compare1 = CompareResult(output, expect_out, 6);
EXPECT_EQ(compare1, true);
}
编写算子原型定义的UT C++测试用例。在“testcases/ut/aicpu_test/reshape_cust/test_log_space_proto.cc”文件中,编写算子原型定义的UT C++测试用例,用于定义算子实例、更新算子输入输出并调用InferShapeAndType函数,最后验证InferShapeAndType函数执行过程及结果的正确性。
TEST_F(LogSpaceTest, logspace_test_end_failed) {
ge::op::LogSpace LogSpace_op;
std::int64_t attr_value = 1;
LogSpace_op.SetAttr("dtype", attr_value);
std::int64_t num_steps_value = 4;
LogSpace_op.SetAttr("steps", num_steps_value);
ge::TensorDesc tensorStartDesc;
ge::TensorDesc tensorEndDesc;
ge::Shape shape0({1});
tensorStartDesc.SetDataType(ge::DT_FLOAT16);
tensorStartDesc.SetShape(shape0);
tensorStartDesc.SetOriginShape(shape0);
LogSpace_op.UpdateInputDesc("start", tensorStartDesc);
ge::Shape shape({2,3});
tensorEndDesc.SetDataType(ge::DT_FLOAT16);
tensorEndDesc.SetShape(shape);
tensorEndDesc.SetOriginShape(shape);
LogSpace_op.UpdateInputDesc("end", tensorEndDesc);
auto ret = LogSpace_op.VerifyAllAttr(true);
EXPECT_EQ(ret, ge::GRAPH_FAILED);
}
运行算子实现文件的UT测试用例。
开发人员可以执行当前工程中所有算子的UT测试用例,也可以执行单个算子的UT测试用例。
右键单击“testcases/ut/aicpu_test”文件夹,选择Run AI CPU Operator‘All’UT Impl with coverage,运行整个文件夹下算子实现代码的测试用例。
右键单击“testcases/ut/aicpu_test算子名称”文件夹,选择Run AI CPU Operator ‘算子名称’ UT Impl with coverage,运行单个算子实现代码的测试用例。
第一次运行时会弹出运行配置页面,请参考配置,然后单击Run。后续如需修改运行配置,请参考修改运行配置。
查看运行结果。
运行完成后,通过界面下方的日志打印窗口,查看运行结果。结果中展示此次一共运行几个用例,如下图。
参数 | 说明 |
---|---|
Name | 运行配置名称,用户可以自定义。 |
Test Type | 选择ut_impl。 |
Compute Unit | 选择计算单元:l AI Core/Vector Core;l AI CPU。选择不同的计算单元可以实现AI Core/Vector Core和AICPU_UT测试配置界面的切换。 |
Operator Name | 选择运行的测试用例。all表示运行所有用例。其他表示运行某个算子下的测试用例。 |
Case Names | 勾选需要运行的测试用例,即算子实现代码的UT C++测试用例。支持全选和全不选所有测试用例。 |
MindStudio提供了新的ST(System Test)测试框架,可以自动生成测试用例,在真实的硬件环境中,验证算子功能的正确性和计算结果准确性,并生成运行测试报告,包括:
基于算子信息库生成算子测试用例定义文件。
基于算子测试用例定义文件生成不同shape、dtype的测试数据和基于AscendCL的测试用例。
编译算子工程并将算子部署到算子库,最后在硬件环境中执行测试用例,验证算子运行的正确性。
自动生成运行报表(st_report.json)功能,报表记录了测试用例信息及各阶段运行情况。
根据用户定义并配置的算子期望数据生成函数,回显期望算子输出和实际算子输出的对比测试结果,验证计算结果的准确性。
完成自定义算子的开发,请参见算子代码实现、算子原型定义、算子信息库定义。
lMindStudio已连接硬件设备。
创建ST测试用例。有以下三种入口:
右键单击算子工程根目录,选择“New Cases > ST Case”。
右键单击算子信息定义文件:{工程名}/cpukernel/op_info_cfg/aicpu_kernel/ xx.ini,选择“New Cases > ST Case”。
若已经存在了对应算子的ST Case,可以右键单击“testcases”目录,或者“testcases > st”目录,选择“New Cases > ST Case”,追加ST测试用例。
在弹出的“Create ST Cases for an Operator”界面中选择需要创建ST测试用例的算子。如下图所示。
配置算子测试用例定义文件。在算子测试用例文件中配置算子期望数据生成函数。有两种场景,分别为Design视图和Text视图下进行配置。
若在Design视图下,可以手动编写输入输出设置,以及属性的设置。
若在Text视图下,可以以编写json文件的方式,设置输入输出和属性的设置
2.修改Case信息后,单击“Save”,修改会保存到算子测试用例定义文件。算子测试用例定义文件存储目录为算子工程根目录下的“ testcases/st/OpType/ aicpu_kernel”文件夹下,命名为LogSpace_case_20220415185113.json
{
"case_name":"Test_Cast_002",
"op":"Cast",
"calc_expect_func_file":"",
"gui_calc_expect_func_file":"",
"input_desc":[
{
"name":"x_tensor",
"format":[
"ND"
],
"type":[
"int32"
],
"shape":[
2,
3
],
"value_range":[
[
0.1,
10.0
]
],
"data_distribute":[
"uniform"
],
"value":[
[
2,
3,
5
],
[
6,
80,
20
]
]
}
],
"output_desc":[
{
"format":[
"ND"
],
"type":[
"int64"
],
"shape":[
2,
3
]
}
],
"attr":[
{
"name":"dst_type",
"value":9,
"type":"int"
}
]
},
参数 | 说明 |
---|---|
Name | 运行配置名称,用户可以自定义。 |
Test Type | 选择st_cases 。 |
Execute Mode | Remote Execute 远程执行测试Local Execute 本地执行测试说明:Local Execute不支持Windows操作系统。 |
Deployment | 当Execute Mode选择Remote Execute时deployment功能,详细请参见Deployment,可以将指定项目中的文件、文件夹同步到远程指定机器的指定目录。 |
CANN Machine | CANN工具所在设备deployment信息。说明:该参数仅支持Windows操作系统。 |
Environment Variables | 在文本框中添加环境变量:PATH_1=路径1;PATH_2=路径2多个环境变量用英文分号隔开。也可以点击文本框后的图标,在弹出的对话框中填写。在Name中输入环境变量名称:PATH_1。在Value中输入环境变量值:路径1。勾选Instead system environment variables可以显示系统环境变量。 |
Operator Name | 选择需要运行的算子。 |
SoC Version | 配置 昇腾AI处理器的类型。 |
Product Form | 配置产品形态。若SoC Version配置为Ascend310时显示该配置项。EPRC |
Executable File Name | 下拉选择需要执行的测试用例定义文件。若对AI CPU算子进行ST测试,测试用例文件前有(AI CPU)标识。 |
Target OS | 针对Ascend EP:选择昇腾AI处理器所在硬件环境的Host侧的操作系统。针对Ascend RC:选择板端环境的操作系统。 |
Target Architecture | 选择Target OS的操作系统架构。 |
Case Names | 选择运行的Case Name。说明:默认全选所有用例,可以去除勾选部分不需要运行的用例。 |
Enable Auto Build&Deploy | 选择是否在ST运行过程中执行编译和部署。 |
Advanced Options | 高级选项 |
ATC Log Level | 选择ATC 日志级别INFODEBUGWARNINGERRORNULL |
Precision Mode | 精度模式force_fp16allow_mix_precisionallow_fp32_to_fp16must_keep_origin_dtype |
Device Id | 设备ID,设置运行ST测试的芯片ID。用户根据使用的AI芯片ID填写。 |
Error Threshold | 配置自定义精度标准,取值为含两个元素的列表:[val1,val2]val1:算子输出结果与标杆数据误差阈值,若误差大于该值则记为误差数据。val2:误差数据在全部数据占比阈值。若误差数据在全部数据占比小于该值,则精度达标,否则精度不达标。取值范围为[0.0,1.0]。 |
Compile Settings | 编译设置按钮,单击按钮后弹出对话框,可以在对话框中配置以下内容。Include Directories:第三方库的头文件路径,支持多路径输入,中间用英文冒号分隔。Link Directories:库路径,支持多路径输入,中间用英文冒号分隔。Link Libraries:库,支持多文件输入,中间用英文冒号分隔。须知:该出的配置项会添加到算子工程目录下“cpukernel/CMakeLists.txt”文件中。最新的配项会覆盖上一次配置项。第三方库在Ascend310和Ascend910处理器上需用Hccl编译器生成,在RC场景需用交叉编译器生成。仅算子实现文件编译时使用该配置。 |
点击Build > Edit Build Configuration…进入编译配置页面。检查Environment Variables配置是否配置完整,点击输入框右边文档图标,查看环境变量配置,确保对应参数路径正确,如图:
这时候直接运行会导致如下错误。
如果需要了解关于MindStudio更多的信息,请查阅昇腾社区中MindStudio的用户手册,里面有算子开发、模型开发等各种使用操作的详细介绍。
如果在使用MindStudio过程中遇到任何问题,也可以在昇腾社区中的昇腾论坛进行提问,会有华为内部技术人员对其进行解答,如下图。