PyTorch 框架 AICPU 算子开发全流程

PyTorch 框架 AICPU 算子开发全流程

文章目录

  • PyTorch 框架 AICPU 算子开发全流程
    • 一.AICPU算子基本概念介绍
    • 二.算子开发流程介绍
    • 三. LogSpace算子分析
    • 四. LogSpace算子工程创建
    • 五.LogSpace算子原型定义和代码开发
    • 六.LogSpace算子逻辑实现部分代码开发
    • 七.LogSpace算子编译和部署
    • 八.LogSpace算子UT测试
    • 九.LogSpace算子ST测试
    • 十.经验总结
    • 十一.关于MindStudio更多的内容

详情指路视频专栏: https://www.bilibili.com/video/BV1nS4y1b7Km

一.AICPU算子基本概念介绍

AICPU算子,是运行在昇腾AI处理器中AICPU计算单元上的表达一个完整计算逻辑的运算,如下情况下,开发者需要自定义AICPU算子。

  1. 在NN模型训练或者推理过程中,将第三方开源框架转化为适配昇腾AI处理器的模型时遇到了昇腾AI处理器不支持的算子。此时,为了快速打通模型执行流程,用户可以通过自定义AICPU算子进行功能调测,提升调测效率。功能调通之后,后续性能调测过程中再将AICPU自定义算子转换成TBE算子实现。

  2. 某些场景下,无法通过AI Core实现自定义算子(比如部分算子需要int64类型,但AI Core指令不支持),且该算子不是网络的性能瓶颈,此时可以通过开发AICPU自定义算子实现昇腾AI处理器对此算子的支持。

二.算子开发流程介绍

整体的AICPU算子开发流程如下所示:

PyTorch 框架 AICPU 算子开发全流程_第1张图片

  1. 算子分析:明确算子的功能、输入、输出,规划算子类型名称以及算子编译生成的库文件名称等。

  2. 工程创建:通过MindStudio工具创建AICPU算子工程,创建完成后,会自动生成算子工程目录及相应的文件模板,开发者可以基于这些模板进行算子开发。

  3. 算子开发:

    1. 算子实现:实现算子的计算逻辑。
    2. 算子原型定义:算子原型定义规定了在昇腾AI处理器上可运行算子的约束,主要体现算子的数学含义,包含定义算子输入、输出、属性和取值范围,基本参数的校验和shape的推导,原型定义的信息会被注册到GE的算子原型库中。离线模型转换时,GE会调用算子原型库的校验接口进行基本参数的校验,校验通过后,会根据原型库中的推导函数推导每个节点的输出shape与dtype,进行输出tensor的静态内存的分配。
    3. 算子信息定义:算子信息配置文件用于将算子的相关信息注册到算子信息库中,包括算子的OpType、输入输出dtype、name等信息。网络运行时,AICPU Engine会根据算子信息库中的算子信息做基本校验,并进行算子匹配。
  4. 算子编译:将算子适配插件实现文件、原型定义文件、算子原型库、算子信息库。

  5. 算子部署:将算子实现文件、插件库文件、原型库、信息库部署到算子库中(opp的对应目录下)。

  6. 算子实现验证:

    1. UT测试:即单元测试(Unit Test),仿真环境下验证算子实现的功能正确性,包括算子逻辑实现代码及算子原型定义实现代码。
    2. ST测试:即系统测试(System Test),可以自动生成测试用例,在真实的硬件环境中,验证算子功 能的正确性。
  7. 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    
    
  8. LogSpace算子交付件介绍

    本交付件包括算子分析、代码实现以及ut测试等LogSpace算子的所有开发流程,以及使用MindStudio开发算子的图文介绍。第三节介绍了算子的分析,包括算子的输入输出,属性以及数学表达式。第四节介绍了MindStudio算子工程的创建。第五节介绍了算子的原型定义,并对代码进行注释讲解。第六节对算子的逻辑部分进行代码的具体实现,并进行注释讲解。第七节介绍了算子的编译和部署的MindStudio的操作讲解。第八节介绍了算子的UT测试操作,第九节介绍了算子的ST测试操作。

    阅读本交付件前,请先安装MindStudio,MindStudio安装教程请参考here。安装完Mindstudio后开始配置cann,cann配置请参考here。

三. LogSpace算子分析

  1. 明确算子功能及数学表达式

    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(basesteps1endstart)ii=1,2,3,4...steps
    计算过程为:输入点集的起始值start和点集的最终值end,以base为对数函数的基,采样steps个点数,生成对数间距一维Tensor。

  2. 明确算子输入和输出

    • LogSpace算子有两个输入:start和end,输出为y
    • 算子输入支持的数据类型为float16、float32,算子的输出类型与输出类型相同
    • 算子输入支持的shape为(0,),输出shape为(steps,)
    • 算子输入支持的format为:ND
  3. 明确算子实现文件名称以及算子的类型(OpType)

    算子类型定义为“LogSpace”,算子的代码实现文件名称为“log_space.h”与“log_space.cc”。通过以上分析,得到LogSpace算子的设计规格如下:

    表1 LogSpace算子的设计规格
    name shape dtype format
    start (0,) float16、float32 ND
    end (0,) float16、float32 ND
    y (steps,) float16、float32 ND

四. LogSpace算子工程创建

  1. 打开MindStudio进入算子工程创建界面

    • 首次登录MindStudio:在MindStudio欢迎界面中单击“New Project”,进入创建工程界面。

      PyTorch 框架 AICPU 算子开发全流程_第2张图片

    • 非首次登录MindStudio:在顶部菜单栏中选择“File > New > Project…”,进入创建工程界面。

      PyTorch 框架 AICPU 算子开发全流程_第3张图片

  2. 创建算子工程

    • 左侧导航栏选择“Ascend Operator”,如图所示,在右侧配置算子工程信息,配置示例如表2所示

      PyTorch 框架 AICPU 算子开发全流程_第4张图片

    表2 工程信息配置
    参数 参数说明 示例
    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)进行配置

    • 单击“Next”,在弹出的页面中配置算子相关信息,选择Empty Template,如表2算子信息配置

      PyTorch 框架 AICPU 算子开发全流程_第5张图片

    表3 算子信息配置
    *参数* *参数说明*
    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,完成算子工程的创建

​ 若工作窗口已打开其他工程,会出现如图所示提示。

  • 选择“This Window”,则直接在当前工作窗口打开新创建的工程。
  • 选择“New Window”,则新建一个工作窗口打开新创建的工程

五.LogSpace算子原型定义和代码开发

  1. 简介。算子原型定义规定了在昇腾AI处理器上可运行算子的约束,主要体现算子的数学含义,包含定义算子输入、输出和属性信息,基本参数的校验和shape的推导,原型定义的信息会被注册到GE的算子原型库中。网络模型生成时,GE会调用算子原型库的校验接口进行基本参数的校验,校验通过后,会根据原型库中的推导函数推导每个节点的输出shape与dtype,进行输出tensor的静态内存的分配。IR定义算子原型说明如表4。

    表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 输出的张量
  2. 定义算子代码实现

    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推导函数的实现。

    • CheckSteps(const Operator& op, const string& attr_num_steps)对设置的steps进行校验,判断是否大于等于0,代码如下。
    // 对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 (OpType, func_name),OpType自定义算子的类型,func_name为自定义的verify函数名称。此接口传入的OpType为基于Operator类派生出来的子类,会自动生成一个类型为此子类的对象op,可以使用子类的成员函数获取算子的相关属性,op对象的成员函数可参见Operator类。
    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):此接口自动生成的一个类型为Operator类的对象op,开发者可直接调用Operator类接口进行InferShape的实现,其中func_name由用户自定义。这个函数的功能为对设置的属性steps和dtype进行校验。代码如下。
    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;
        }
    
  3. 算子信息库

    算子信息库主要体现算子在昇腾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
    

六.LogSpace算子逻辑实现部分代码开发

  1. 简介

​ AI CPU算子的实现包括两部分:

  • 头文件(.h文件):进行算子类的声明,自定义算子类需要继承CpuKernel基类。

  • 源文件(.cc文件):重写算子类中的Compute函数,进行算子计算逻辑的实现。

  1. 头文件代码模块介绍

    用户需要在算子工程的“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”为固定值,不允许修改。

  2. 源文件代码块介绍

用户需要在算子工程的“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为自定义算子类的名称。

七.LogSpace算子编译和部署

  1. 编译基本概念:将算子适配插件实现文件、原型定义文件、信息定义文件编译成算子插件库文件、算子原型库、算子信息库。

    • 在MindStudio工程界面,选中算子工程。单击顶部菜单栏的“ Build > Edit Build Configuration…”。弹出如下窗口。

      PyTorch 框架 AICPU 算子开发全流程_第6张图片

    • 进入编译配置页面。检查Environment Variables配置是否配置完整,点击输入框右边文档图标,查看环境变量配置,确保对应参数路径正确,如图。配置解析请参考下表5进行编译配置。

      PyTorch 框架 AICPU 算子开发全流程_第7张图片

      表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处理器的类型。
  • 单击“Build”进行工程编译
  • 在界面最下方的窗口查看编译结果,并在算子工程的cmake-build目录下生成自定义算子安装包custom_opp_Ubuntu_18.04_x86_64.run。

PyTorch 框架 AICPU 算子开发全流程_第8张图片

  1. **算子部署。**将自定义算子安装包custom_opp_Ubuntu_18.04_x86_64.run部署到昇腾AI处理器所在硬件环境的算子库中,为后续算子在网络中运行构造必要条件。
  • 编译算子工程后,在cmake-build目录下会生成custom_opp_Ubuntu_18.04_x8 6_64. run。

单击顶部菜单栏的“Ascend > Operator Deploymen”,进入算子打包部署界面。请在Deploy Remotely > Deployment选择配置项。

PyTorch 框架 AICPU 算子开发全流程_第9张图片

选择算子部署的目标服务器,单击“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    //自定义算子原型库文件

八.LogSpace算子UT测试

  1. 基本概念

MindStudio提供了基于gtest框架的新的UT测试方案,简化了开发者开发UT测试用例的复杂度。UT(Unit Test:单元测试)是开发人员进行单算子运行验证的手段之一,主要目的是:

  • 测试算子代码的正确性,验证输入输出结果与设计的一致性。

  • UT侧重于保证算子程序能够跑通,选取的场景组合应能覆盖算子代码的所有分支(一般来说覆盖率要达到100%),从而降低不同场景下算子代码的编译失败率。

  1. 前提条件
  • 需完成自定义算子的开发,包括算子实现代码和算子原型定义。

  • 安装CMake,要求版本为3.14及以上,若CMake版本不满足要求,请升级CMake版本。

  • MindStudio已连接硬件设备。

  1. 操作步骤
  • 创建UT测试用例,有以下三种方式。

    1. 右键单击算子工程根目录,选择“New Cases > AI CPU UT Case”。

    2. 若已经存在了算子的UT测试用例,可以右键单击“testcases”目录。

    3. 或者“testcases > ut”目录,选择“New Cases > AI CPU UT Case”,创建UT测试用例。

      PyTorch 框架 AICPU 算子开发全流程_第10张图片

  • 在弹出的算子选择界面,选择需要创建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测试用例。

    PyTorch 框架 AICPU 算子开发全流程_第11张图片

    PyTorch 框架 AICPU 算子开发全流程_第12张图片

  • 右键单击“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。后续如需修改运行配置,请参考修改运行配置。

  • 查看运行结果

    运行完成后,通过界面下方的日志打印窗口,查看运行结果。结果中展示此次一共运行几个用例,如下图。

    PyTorch 框架 AICPU 算子开发全流程_第13张图片

    表6 UT测试参数配置信息
参数 说明
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++测试用例。支持全选和全不选所有测试用例。

九.LogSpace算子ST测试

  1. 概述

MindStudio提供了新的ST(System Test)测试框架,可以自动生成测试用例,在真实的硬件环境中,验证算子功能的正确性和计算结果准确性,并生成运行测试报告,包括:

  • 基于算子信息库生成算子测试用例定义文件。

  • 基于算子测试用例定义文件生成不同shape、dtype的测试数据和基于AscendCL的测试用例。

  • 编译算子工程并将算子部署到算子库,最后在硬件环境中执行测试用例,验证算子运行的正确性。

  • 自动生成运行报表(st_report.json)功能,报表记录了测试用例信息及各阶段运行情况。

  • 根据用户定义并配置的算子期望数据生成函数,回显期望算子输出和实际算子输出的对比测试结果,验证计算结果的准确性。

  1. 前提条件
  • 完成自定义算子的开发,请参见算子代码实现、算子原型定义、算子信息库定义。

  • lMindStudio已连接硬件设备。

  1. 生成ST测试用例定义文件
  • 创建ST测试用例。有以下三种入口:

    1. 右键单击算子工程根目录,选择“New Cases > ST Case”。

    2. 右键单击算子信息定义文件:{工程名}/cpukernel/op_info_cfg/aicpu_kernel/ xx.ini,选择“New Cases > ST Case”。

    3. 若已经存在了对应算子的ST Case,可以右键单击“testcases”目录,或者“testcases > st”目录,选择“New Cases > ST Case”,追加ST测试用例。

  • 在弹出的“Create ST Cases for an Operator”界面中选择需要创建ST测试用例的算子。如下图所示。

PyTorch 框架 AICPU 算子开发全流程_第14张图片

  1. Operator Name:下拉选择算子名称。
  2. SoC Version:下拉选择昇腾AI处理器的类型。若对AI CPU算子进行ST测试,默认选择aicpu_kernel。
  3. 单击“OK”后,工具会自动根据首层shape信息dump出选择算子的shape信息,生成对应的算子测 试用例定义文件。

PyTorch 框架 AICPU 算子开发全流程_第15张图片

  • 配置算子测试用例定义文件。在算子测试用例文件中配置算子期望数据生成函数。有两种场景,分别为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"
    			}
    		]
    	},
    
    1. **运行ST测试用例。**右键单击生成ST测试用例定义文件中生成的ST测试用例定义文件(路径:“testcases> st > add > aicpu_kernel >xxxx.json”),选择“Run ST Case 'xxx’”。
      PyTorch 框架 AICPU 算子开发全流程_第16张图片
表7运行配置信息
参数 说明
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场景需用交叉编译器生成。仅算子实现文件编译时使用该配置。
  1. 运行后会输出测试用例总数,成功用例总数,失败用例总数,错误用例总数。

PyTorch 框架 AICPU 算子开发全流程_第17张图片

十.经验总结

  1. 编写算子信息库文件cpukernel/op_info_cfg/aicpu_kernel/log_space.ini时,要确保文件没有多余的空行或者其他符号,否则编译会报如下错误。

PyTorch 框架 AICPU 算子开发全流程_第18张图片

  1. 编译算子工程前,先检查一下配置参数,如果缺少环境变量直接编译会报如下错误

点击Build > Edit Build Configuration…进入编译配置页面。检查Environment Variables配置是否配置完整,点击输入框右边文档图标,查看环境变量配置,确保对应参数路径正确,如图:

PyTorch 框架 AICPU 算子开发全流程_第19张图片

  1. 算子部署前需要先编译成功,生成custom_opp_××××.run,部署的时候会自动检测Operator Package是否存在,否则Operator Package检测不到,会导致如下图所示

PyTorch 框架 AICPU 算子开发全流程_第20张图片

这时候直接运行会导致如下错误。

PyTorch 框架 AICPU 算子开发全流程_第21张图片

十一.关于MindStudio更多的内容

如果需要了解关于MindStudio更多的信息,请查阅昇腾社区中MindStudio的用户手册,里面有算子开发、模型开发等各种使用操作的详细介绍。

如果在使用MindStudio过程中遇到任何问题,也可以在昇腾社区中的昇腾论坛进行提问,会有华为内部技术人员对其进行解答,如下图。

PyTorch 框架 AICPU 算子开发全流程_第22张图片

你可能感兴趣的:(深度学习,人工智能)