认识算子
- 算子是深度学习框架的一个个功能模块,例如实现卷积功能的conv2d函数就是一个算子。
- CANN算子 { AICPU算子 AICPU完成计算,通用性强,但性能不如AICORE算子 AICORE算子 AICORE完成计算,性能强,但受指令限制,数据类型支持有限,通用性不如AICPU算子 \text {CANN算子} \begin {cases} \text {AICPU算子} & \text {AICPU完成计算,通用性强,但性能不如AICORE算子} \\ \\ \text {AICORE算子} & \text {AICORE完成计算,性能强,但受指令限制,数据类型支持有限,通用性不如AICPU算子} \\ \end {cases} CANN算子⎩ ⎨ ⎧AICPU算子AICORE算子AICPU完成计算,通用性强,但性能不如AICORE算子AICORE完成计算,性能强,但受指令限制,数据类型支持有限,通用性不如AICPU算子
开发流程
算子分析
- 明确算子的功能;通过构造友商框架对应的算子用例以及实现源码,推导算子的计算逻辑。
- 明确算子的输入、输出和属性。
- 明确算子的异常场景。
原型定义实现
-
1、IR注册
- 描述算子的信息,包括输入输出和属性信息。 2、Shape推导与校验
- 根据输入的信息,结合算子功能,推导输出的shape。 3、Verify校验
- 校验算子多输入情况下的输入间的约束,例如对多输入的dtype一致性的校验。
算子代码实现
-
1、头文件
-
对算子类的声明,属于CpuKernel的派生类。
2、源文件
-
重写Compute方法,Compute函数需要完成对算子输入输出以及属性的合法性校验,还有算子计算逻辑的实现。
-
- 合法性校验包括检查输入与输出Tensor是否为空指针,对输入个数的校验,输入内在逻辑的校验(例如dtype约束与shape约束)以及对输入的dtype校验。
-
- 算子计算逻辑需要覆盖算子支持的所有数据类型的计算(采用template模板实现);其中针对半精度的数据类型借助Eigen库完成实现,注意该数据类型的计算实现过程容易出现精度问题,所以实现时要避免会导致精度丢失的操作;此外,算子实现需要借助多线程来提升算子在处理大数据规模场景的性能(借助库中已实现的CpuKernelUtils::ParallelFor方法)。
-
- 扩充:在处理动态shape算子时,需要在Compute函数中完成输出Shape的计算更新,且需要在算子计算逻辑实现之前。
信息库定义
- 信息库充当算子的身份证的作用,在网络运行时,通过信息库来进行算子匹配,并进行基本校验。
- 以Less算子为例:
[Less] //算子名
opInfo.engine=DNN_VM_AICPU //配置算子调用的引擎,固定值
opInfo.flagPartial=False
opInfo.computeCost=100
opInfo.flagAsync=False
opInfo.opKernelLib=AICPUKernel //算子调用的kernelLib
opInfo.kernelSo=libcpu_kernels.so //AI CPU算子实现文件编译生成的动态库文件的名称
opInfo.functionName=RunCpuKernel //自定义算子调用的kernel函数接口名称
opInfo.opsFlag=OPS_FLAG_OPEN //是否开启常量折叠
opInfo.userDefined=False
opInfo.subTypeOfInferShape=1 //针对动态shape的场景,配置AI CPU的infershape方式
opInfo.formatAgnostic=True //是否对数据排布格式敏感
input0.name=x1 //第一个输入的名称,下行为数据类型
input0.type=DT_DOUBLE,DT_FLOAT,DT_FLOAT16,DT_INT16,DT_INT32,DT_INT64,DT_INT8,DT_UINT16,DT_UINT32,DT_UINT64,DT_UINT8
input1.name=x2 //第二个输入的名称,下行为数据类型
input1.type=DT_DOUBLE,DT_FLOAT,DT_FLOAT16,DT_INT16,DT_INT32,DT_INT64,DT_INT8,DT_UINT16,DT_UINT32,DT_UINT64,DT_UINT8
output0.name=y //输出的名称
output0.type=DT_BOOL //输出的数据类型
测试
UT测试
-
1、原型定义UT测试
- 测试原型定义实现的正确性,主要是场景的测试。以下为Less算子的一个测试用例构造:
// 定义测试用例函数TEST_F,其中第一个参数为2中定义的测试类的名字,第二个参数为自定义的测试用例的名字
TEST_F(less, less_infer_shape_fp16) {
// 定义算子实例,Less为算子的类型,需要与原型定义的REG_OP(OpType)中的OpType保持一致。
ge::op::Less op;
// 定义算子输入的shape和dtype,以TensorDesc实例承载
std::vector<std::pair<int64_t,int64_t>> shape_range = {{2, 100}};
auto tensor_desc = create_desc_shape_range({-1},
ge::DT_FLOAT16, ge::FORMAT_ND,
{64},
ge::FORMAT_ND, shape_range);
// 更新算子输入,输入的名称需要与原型定义*.h文件中的名称保持一致,例如x1与x2分别为Less算子的两个输入
op.UpdateInputDesc("x1", tensor_desc);
op.UpdateInputDesc("x2", tensor_desc);
// 调用VerifyAllAttr函数,VerifyAllAttr()接口为固定接口,用例执行时会自动调用算子原型定义中的verify函数。
auto status = op.VerifyAllAttr(true);
// 验证Verify函数校验是否成功
EXPECT_EQ(status, ge::GRAPH_SUCCESS);
// 调用InferShapeAndType函数,InferShapeAndType()接口为固定接口,用例执行时会自动调用算子原型定义中的shape推导函数
auto ret = op.InferShapeAndType();
// 验证调用过程是否成功
EXPECT_EQ(ret, ge::GRAPH_SUCCESS);
// 获取算子输出描述,并将输出shape和type与期望输出的shape和type进行比较
// 算子输出的名字需要与原型定义*.h文件中的名称保持一致,例如:Less算子的输出为y
auto output_desc = op.GetOutputDesc("y");
// 定义期望输出shape,并与实际shape进行比较
std::vector<int64_t> expected_output_shape = {-1};
EXPECT_EQ(output_desc.GetShape().GetDims(), expected_output_shape);
EXPECT_EQ(ret, ge::GRAPH_SUCCESS);
// 将算子输出的DataType与预期DataType进行比较
EXPECT_EQ(output_desc.GetDataType(), ge::DT_BOOL);
// 将算子输出的shape范围与期望输出的shape范围进行比较
std::vector<std::pair<int64_t,int64_t>> output_shape_range;
EXPECT_EQ(output_desc.GetShapeRange(output_shape_range), ge::GRAPH_SUCCESS);
std::vector<std::pair<int64_t,int64_t>> expected_shape_range = {
{2, 100},
};
EXPECT_EQ(output_shape_range, expected_shape_range);
}
// 若有其他场景,请继续追加测试用例函数
TEST_F(less, less_testcase_2) {
... ...
}
-
2、算子实现的UT测试
-
- 验证算子代码实现的正确性
-
- 算子实现的UT测试用例主要包括三类:
1、存在标杆数据的正常用例;该类用例需要对应的友商框架的python代码实现标杆数据的生成,借此来验证自定义算子的计算结果是否达到标杆精度。
2、不存在标杆数据的正常用例;该类用例的标杆数据通过在ut测试代码文件中实现生成期望数据的自定义函数来获取,一般是用于校验友商框架不支持的数据类型。
3、异常用例;该类用例用于提高自定义算子的鲁棒性,确保自定义算子在接收到异常数据时,可以正常抛出异常。
- 算子实现的UT测试用例主要包括三类:
ST测试(简单介绍)
- ST测试依托于昇腾硬件环境,在进行测试前需要对源码进行编译部署。
- 编写测试用例,用例模板如下:
[
{
"case_name": "Test_OpType_001",
"op": "OpType",
"input_desc": [
{
"format": [],
"type": [],
"shape": [],
"data_distribute": [
"uniform"
],
"value_range": [
[
0.1,
1.0
]
]
}
],
"output_desc": [
{
"format": [],
"type": [],
"shape": []
}
]
}
]
- 配置测试环境,借助canndev源码目录下的msopst工具完成测试(tools/op_test_frame/python/op_test_frame/scripts/msopst)。