快速入门
本章节以开发一个Add算子为例,带您快速体验CANN算子开发的流程。Add算子的功能是实现两个数的加法操作,并将结果返回。
了解开发过程
全新开发算子的流程如下图所示。
- 算子原型定义:定义算子输入、输出、属性。
- 算子代码实现:描述算子的运算逻辑,完成特定的计算功能。
- 算子信息库定义:描述算子在昇腾AI处理器上的实现限制,包含算子输入输出的dtype、format、shape信息。算子编译时,会根据算子信息库匹配到算子实现。
- 算子适配插件开发:用于进行第三方框架的适配,让第三方框架的算子可以映射到CANN算子。
此处,您不需要深入了解每一个交付件的含义,本章节旨在让您快速了解算子开发流程。您可以通过后续章节进行深入了解。
环境准备
请参见环境准备部署软件环境,并配置相关的环境变量。
准备好CANN软件基础环境后,进行算子开发操作前,你需要了解如下基本概念,以便更好的理解后续操作。
- 开发环境:指编译开发代码的环境。
- 运行环境:指运行算子、推理程序、训练程序等的环境。运行环境必须带昇腾AI处理器。
- 开发环境与运行环境合设场景:指带昇腾AI处理器的机器既作为开发环境又作为运行环境。此种场景下,代码开发与代码运行在同一台机器上。
- 开发环境与运行环境分设场景:开发环境和运行环境不在同一台机器上,开发者使用带有昇腾AI处理器的机器作为运行环境;使用其他独立机器进行代码开发与编译,作为开发环境。
为了简化操作流程,本快速入门以开发环境与运行环境合设的场景为例。
针对开发环境与运行环境分设的场景,区别在于基于msopst工具进行ST测试,详细区别可参见算子包部署与基于msopst工具。
算子分析
下面进行Add算子的详细规格分析:
- 明确算子的数学表达式
y=x1+x2
- 明确输入输出
- 确定使用的计算接口
计算过程涉及加法操作,本样例通过DSL方式实现,查看TBE DSL API,分析可使用tbe.dsl.vadd。
- 明确算子实现文件名称、算子实现函数名称以及算子的类型
为了区别于CANN内置的Add算子,我们将此样例算子的OpType命名为AddDSL,算子实现文件及实现函数名称命名为add_dsl。
工程创建
- 定义AddDSL算子的原型定义json文件,用于生成AddDSL的算子开发工程。例如,定义的json文件的名字为add_dsl.json,存储路径为: $HOME/sample,文件内容如下:
[ { "op":"AddDSL", "input_desc":[ { "name":"x1", "param_type":"required", "format":[ "NCHW" ], "type":[ "fp16" ] }, { "name":"x2", "param_type":"required", "format":[ "NCHW" ], "type":[ "fp16" ] } ], "output_desc":[ { "name":"y", "param_type":"required", "format":[ "NCHW" ], "type":[ "fp16" ] } ] } ]
- 使用msopgen工具生成AddDSL算子的开发工程。
$HOME/Ascend/ascend-toolkit/latest/python/site-packages/bin/msopgen gen -i $HOME/sample/add_dsl.json -f tf -c ai_core-<soc_version> -out $HOME/sample/AddDsl
“$HOME/Ascend”为CANN软件安装目录;“-f tf”参数代表选择的原始框架为TensorFlow;“-c ai_core-<soc_version>”代表算子在AI Core上执行,<soc_version>为昇腾AI处理器的型号。
此命令执行完后,会在$HOME/sample/AddDsl目录下生成算子工程,工程中包含各交付件的模板文件,编译脚本等,如下所示:
AddDsl ├── build.sh // 编译入口脚本 ├── cmake // 编译解析脚本存放目录 ├── CMakeLists.txt ├── framework // 算子适配插件相关文件存放目录 │ ├── CMakeLists.txt │ └── tf_plugin │ ├── CMakeLists.txt │ └── tensorflow_add_dsl_plugin.cc // 算子适配插件实现文件 ├── op_proto // 算子原型定义相关文件存放目录 │ ├── add_dsl.cc │ ├── add_dsl.h │ └── CMakeLists.txt ├── op_tiling │ └── CMakeLists.txt ├── scripts // 自定义算子工程打包脚本存放目录 └── tbe ├── CMakeLists.txt ├── impl // 算子代码实现 │ └── add_dsl.py └── op_info_cfg // 算子信息库存放目录 └── ai_core └── <soc_version> └── add_dsl.ini
上述目录结构中的粗体文件为后续算子开发过程中需要修改的文件,其他文件无需修改。
算子开发过程
- 实现AddDSL算子的原型定义。算子原型定义文件包含算子注册代码的头文件(*.h)以及实现基本校验、Shape推导的实现文件(*.cc)。
- msopgen工具根据add_dsl.json文件在“op_proto/add_dsl.h”中生成了算子注册代码,开发者需要检查自动生成的代码逻辑是否正确,一般无需修改。
- 修改“op_proto/add_dsl.cc”文件,实现算子的输出描述推导函数及校验函数。
- 在IMPLEMT_COMMON_INFERFUNC(AddDSLInferShape)函数中,填充推导输出描述的代码,针对AddDSL算子,输出Tensor的描述信息与输入Tensor的描述信息相同,所以直接将任意一个输入Tensor的描述赋给输出Tensor即可。
IMPLEMT_COMMON_INFERFUNC(AddDSLInferShape) { // 获取输出数据描述 TensorDesc tensordesc_output = op.GetOutputDescByName("y"); tensordesc_output.SetShape(op.GetInputDescByName("x1").GetShape()); tensordesc_output.SetDataType(op.GetInputDescByName("x1").GetDataType()); tensordesc_output.SetFormat(op.GetInputDescByName("x1").GetFormat()); // 直接将输入x1的Tensor描述信息赋给输出 (void)op.UpdateOutputDesc("y", tensordesc_output); return GRAPH_SUCCESS; }
- 在IMPLEMT_VERIFIER(AddDSL, AddDSLVerify)函数中,填充算子参数校验代码。
IMPLEMT_VERIFIER(AddDSL, AddDSLVerify) { // 校验算子的两个输入的数据类型是否一致,若不一致,则返回失败。 if (op.GetInputDescByName("x1").GetDataType() != op.GetInputDescByName("x2").GetDataType()) { return GRAPH_FAILED; } return GRAPH_SUCCESS; }
- 在IMPLEMT_COMMON_INFERFUNC(AddDSLInferShape)函数中,填充推导输出描述的代码,针对AddDSL算子,输出Tensor的描述信息与输入Tensor的描述信息相同,所以直接将任意一个输入Tensor的描述赋给输出Tensor即可。
- 实现AddDSL算子的计算逻辑。
“tbe/impl/add_dsl.py”文件中已经自动生成了算子代码的框架,开发者需要在此文件中修改add_dsl_compute函数,实现此算子的计算逻辑。
add_dsl_compute函数的实现代码如下:
@register_op_compute("add_dsl") def add_dsl_compute(x1, x2, y, kernel_name="add_dsl"): # 调用dsl的vadd计算接口 res = tbe.vadd(x1, x2) return res
- 配置算子信息库。
算子信息库的路径为“tbe/op_info_cfg/ai_core/<soc_version>/add_dsl.ini”,包含了算子的类型,输入输出的名称、数据类型、数据排布格式等信息,该样例中,msopgen工具已经根据add_dsl.json文件将上述内容自动填充,开发者无需修改。
AddDSL算子的信息库如下:
[AddDSL] // 算子的类型 input0.name=x1 // 第一个输入的名称 input0.dtype=float16 // 第一个输入的数据类型 input0.paramType=required // 代表此输入必选,且仅有一个 input0.format=NCHW // 第一个输入的数据排布格式 input1.name=x2 // 第二个输入的名称 input1.dtype=float16 // 第二个输入的数据类型 input1.paramType=required // 代表此输入必选,且仅有一个 input1.format=NCHW // 第二个输入的数据排布格式 output0.name=y // 算子输出的名称 output0.dtype=float16 // 算子输出的数据类型 output0.paramType=required // 代表此输出必选,有且仅有一个 output0.format=NCHW // 算子输出的数据排布格式 opFile.value=add_dsl // 算子实现文件的名称 opInterface.value=add_dsl // 算子实现函数的名称
- 实现算子适配插件。
算子适配插件实现文件的路径为“framework/tf_plugin/tensorflow_add_dsl_plugin.cc”,针对原始框架为TensorFlow的算子,CANN提供了自动解析映射接口“AutoMappingByOpFn”,如下所示:
#include "register/register.h" namespace domi { // register op info to GE REGISTER_CUSTOM_OP("AddDSL") // CANN算子的类型 .FrameworkType(TENSORFLOW) // type: CAFFE, TENSORFLOW .OriginOpType("AddDSL") // 原始框架模型中的算子类型 .ParseParamsByOperatorFn(AutoMappingByOpFn); //解析映射函数 } // namespace domi
以上为工程自动生成的代码,开发者仅需要修改.OriginOpType("AddDSL")中的算子类型即可。此处我们仅展示算子开发流程,不涉及原始模型,我们不做任何修改。
至此,AddDSL算子的所有交付件都已开发完毕。
算子编译部署
- 算子工程编译。
- 修改build.sh脚本,配置算子编译所需环境变量。
将build.sh中环境变量ASCEND_TENSOR_COMPILER_INCLUDE配置为CANN软件头文件所在路径。
修改前,环境变量配置的原有代码行如下:
# export ASCEND_TENSOR_COMPILER_INCLUDE=/usr/local/Ascend/ascend-toolkit/latest/compiler/include
修改后,新的代码行如下:export ASCEND_TENSOR_COMPILER_INCLUDE=${INSTALL_DIR}/include
${INSTALL_DIR}请替换为CANN软件安装后文件存储路径。例如,若安装的Ascend-cann-toolkit软件包,则安装后文件存储路径为:$HOME/Ascend/ascend-toolkit/latest。
- 在算子工程目录下执行如下命令,进行算子工程编译。
编译成功后,会在当前目录下创建build_out目录,并在build_out目录下生成自定义算子安装包custom_opp_<target os>_<target architecture>.run。
- 修改build.sh脚本,配置算子编译所需环境变量。
- 自定义算子安装包部署。
以运行用户执行如下命令,安装自定义算子包。
./custom_opp_<target os>_<target architecture>.run
命令执行成功后,自定义算子包中的相关文件部署到CANN算子库中。
ST测试
- 创建ST测试用例定义文件“*.json”,用于生成测试用例。
例如,定义的json文件的名字为add_dsl.json,存储路径为: $HOME/sample/st,文件内容如下:
[ { "case_name":"Test_AddDSL_001", "op": "AddDSL", "input_desc": [ { "name": "x1", "format": [ "NCHW" ], "type": [ "float16" ], "shape": [8,16], "data_distribute": [ "uniform" ], "value_range": [ [ 1.0, 384.0 ] ] }, { "name": "x2", "format": [ "NCHW" ], "type": [ "float16" ], "shape": [8,16], "data_distribute": [ "uniform" ], "value_range": [ [ 1.0, 384.0 ] ] } ], "output_desc": [ { "name": "y", "format": [ "NCHW" ], "type": [ "float16" ], "shape": [8,16] } ] } ]
- 配置ST测试用例编译所需环境变量。ST测试用例的主要功能为:使用AscendCL接口加载单算子模型文件并执行,所以生成并编译ST测试用例前需要配置AscendCL应用编译所需环境变量,如下所示:
export DDK_PATH=${INSTALL_DIR} export NPU_HOST_LIB=${INSTALL_DIR}/runtime/lib64/stub
${INSTALL_DIR}请替换为CANN软件安装后文件存储路径。例如,若安装的Ascend-cann-toolkit软件包,则安装后文件存储路径为:$HOME/Ascend/ascend-toolkit/latest。
- 生成并执行ST测试用例。
$HOME/Ascend/ascend-toolkit/latest/python/site-packages/bin/msopst run -i $HOME/sample/st/add_dsl.json -soc <soc_version> -out $HOME/sample/st/out
“$HOME/Ascend”为CANN软件安装目录;“-i”为ST测试用例定义json文件的路径,“-soc”为芯片类型,“-out”为输出文件存储路径。
执行后出现类似"Case 'Test_AddDSL_001_case_001' run successfully."的提示信息,代表ST测试用例生成并测试成功。
至此,您已经完成了第一个自定义算子的开发及测试。