PyTorch 框架 AICPU 算子开发全流程

PyTorch 框架 AICPU 算子开发全流程,第1张

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算子开发流程如下所示:

  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算子的设计规格
    nameshapedtypeformat
    start(0,)float16、float32ND
    end(0,)float16、float32ND
    y(steps,)float16、float32ND
四. LogSpace算子工程创建
  1. 打开MindStudio进入算子工程创建界面

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

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

  2. 创建算子工程

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

    表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”,在d出的页面中配置算子相关信息,选择Empty Template,如表2算子信息配置

    表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 算子原型表格说明
    OPclassifynametyperangedtypeRequiredDOCDefault
    LogSpaceINPUTstartTensorfloat16、float32TRUE点集的起始值
    INPUTendTensorfloat16、float32TRUE点集的最终值
    ATTRstepsintint32FALSE采集的点数100
    ATTRbaseintint32FALSE对数函数的基数10
    ATTRdtypeintint32FALSE输出值类型1
    OUTPUTyTensorfloat16、float32TRUE输出的张量
  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…”。d出如下窗口。

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

      表5 编译参数配置
    参数说明
    Build Configuration编译配置名称,默认为Build-Configuration。
    Build Mode编译方式:Remote Build:远端编译。Local Build:本地编译。
    DeploymentRemote Build模式下显示该配置。deployment可以将指定项目中的文件、文件夹同步到远程指定机器的指定目录。
    Environment VariablesRemote Build模式下显示该配置。为远程环境配置ASCEND_OPP_PATH、TOOLCHAIN_DIR等相关环境变量。
    AICPU SoC Version用于配置昇腾AI处理器的类型。
  • 单击“Build”进行工程编译
  • 在界面最下方的窗口查看编译结果,并在算子工程的cmake-build目录下生成自定义算子安装包custom_opp_Ubuntu_18.04_x86_64.run。

  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选择配置项。

选择算子部署的目标服务器,单击“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测试用例。

  • 在d出的算子选择界面,选择需要创建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,运行单个算子实现代码的测试用例。

    第一次运行时会d出运行配置页面,请参考配置,然后单击Run。后续如需修改运行配置,请参考修改运行配置。

  • 查看运行结果。

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

    表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测试用例。

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

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

  • 配置算子测试用例定义文件。在算子测试用例文件中配置算子期望数据生成函数。有两种场景,分别为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’”。
表7运行配置信息
参数说明
Name运行配置名称,用户可以自定义。
Test Type选择st_cases 。
Execute ModeRemote Execute 远程执行测试Local Execute 本地执行测试说明:Local Execute不支持Windows *** 作系统。
Deployment当Execute Mode选择Remote Execute时deployment功能,详细请参见Deployment,可以将指定项目中的文件、文件夹同步到远程指定机器的指定目录。
CANN MachineCANN工具所在设备deployment信息。说明:该参数仅支持Windows *** 作系统。
Environment Variables在文本框中添加环境变量:PATH_1=路径1;PATH_2=路径2多个环境变量用英文分号隔开。也可以点击文本框后的图标,在d出的对话框中填写。在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编译设置按钮,单击按钮后d出对话框,可以在对话框中配置以下内容。Include Directories:第三方库的头文件路径,支持多路径输入,中间用英文冒号分隔。Link Directories:库路径,支持多路径输入,中间用英文冒号分隔。Link Libraries:库,支持多文件输入,中间用英文冒号分隔。须知:该出的配置项会添加到算子工程目录下“cpukernel/CMakeLists.txt”文件中。最新的配项会覆盖上一次配置项。第三方库在Ascend310和Ascend910处理器上需用Hccl编译器生成,在RC场景需用交叉编译器生成。仅算子实现文件编译时使用该配置。
  1. 运行后会输出测试用例总数,成功用例总数,失败用例总数,错误用例总数。

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

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

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

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

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

十一.关于MindStudio更多的内容

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

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

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/916103.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-16
下一篇2022-05-16

发表评论

登录后才能评论

评论列表(0条)

    保存