CANN AICPU算子开发学习笔记

认识算子

  • 算子是深度学习框架的一个个功能模块,例如实现卷积功能的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、异常用例;该类用例用于提高自定义算子的鲁棒性,确保自定义算子在接收到异常数据时,可以正常抛出异常。

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)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值