前言:
AscendC算子先进,硬核吗?必须是!那AscendC算子开发很难了,必须不是,算子开发很难,但AscendC开发算子不难!把复杂的事情流程(范式)化,把困难的事情简单(SPMD)化,这才是AscendC算子开发的硬核所在。这篇文档,交流下通过参考例程,动手写(改)出自己的AscendC算子。
选择LeakyReLU算子作为样例,有点投机取巧的成分,因为sample仓本身就有部分代码。拿它作为例子,是为了展示一个知识点“通过TilingData传递算子的属性信息”。LeakyReLU算子有一个“negative_slope”,这是个标量,本例就是将这个属性值通过TilingData传递给核函数,进行运算的。本篇以实战为主,相关知识都给了官方链接,有兴趣的小伙伴可以参考阅读。受限于篇幅,本篇仅完成算子工程代码和简单核函数方式调用工程的编写,单算子调用工程以后再展开写。下面开始我们的AscendC编程之旅。
一、准备开发环境
按照“Ascend C环境准备 https://www.hiascend.com/forum/thread-0235128261452483095-1-1.html?fid=0163125572293226003”贴,选择免费的modelarts CodeLab搭建开发环境,在此环境上,还可以完成CPU侧的验证,而且这个环境自带pytorch,所以也可以在算子分析阶段用来熟悉算子。如果需要进行NPU实际环境的验证,可参考“AscendC算子NPU开发调试环境成功搭建篇https://www.bilibili.com/read/cv26991439/ ”,完成环境搭建。
使用此环境需要注意,由于modelarts,只保留work目录的内容(安装包放在work目录,就需要每次重新下载了),所以每次使用前需要重新安装toolkits和算子开发包,并配置环境变量。
安装toolkit和算子开发包:
./Ascend-cann-toolkit_7.0.RC1.alpha002_linux-x86_64.run --install --force
tar -xf Ascend-cann-communitysdk_7.0.RC1.alpha002_linux-x86_64.tar.gz -C ~/Ascend/ascend-toolkit/latest
配置环境变量:
source /home/ma-user/Ascend/ascend-toolkit/set_env.sh
export ASCEND_CUSTOM_PATH=$HOME/Ascend/ascend-toolkit/latest
export ASCEND_HOME_DIR=$HOME/Ascend/ascend-toolkit/latest
export PATH=/home/ma-user/work/cmake-3.26.4-linux-x86_64/bin:$PATH
二、下载参考例程:
昇腾的sample仓(https://gitee.com/ascend/samples.git)提供了AscendC算子的代码例程,目前有两处:
1)samples/operator目录下,有“AddCustomSample”和“LeakyReLUCustomSample”两个例程。
2)samples/ cplusplus / level1_single_api / 4_op_dev / 6_ascendc_custom_op目录下,有一些例程。
两部分例程都很好,第2个目录下例程知识点比较全,第1个目录下例程比较简单且结构更为简洁,比较适合拿来作为初学的样板做练习。本文以“samples/operator目录”下的AddCustomSample工程作为模板。
三、开发过程
1、算子分析
1)先去官网找一下,算子的定义https://pytorch.org/docs/stable/generated/torch.nn.LeakyReLU.html#torch.nn.LeakyReLU,然后在pytorch环境下,运行下算子,了解各种参数的意义。
2)分析计算实现的方法,查找AscendC官方文档,在标量双目指令中有LeakyRelu算子,本例可以直接使用。对没有直接API实现的算子,需要先分解成基本API的组合。
3)出算子设计规格表:假定输入类型为float16,形状8 * 1024(和AddCustom保持一致),且不考虑“inplace的情形
关于算子分析可参看“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(基础篇)>矢量编程>算子分析 昇腾社区-官网丨昇腾万里 让智能无所不及”,
如下图所示:左侧是Add例程,右侧是参考例程写出的“LeakyReluCustom”。红色的是有差异的地方,请记住这些或者等我们完成代码编写后再回头看一下这儿,会发现所做的修改都和此处有关系。
2、编写LeakyReLU算子原型定义json文件
为了方便描述,我将add_custom.json输入类型做了简化,仅保留了float16。根据上面的分析表,将输入和输出写对就可以了,这里面需要注意“属性”的写法。具体写法可参考文档:“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(进阶篇)>基于msopgen工具创建算子工程
3、生成算子工程
使用msopgen生成算子工程。
/home/ma-user/Ascend/ascend-toolkit/latest/python/site-packages/bin/msopgen gen -i ./leakyrely_custom.json -c ai_core-ascend910,ai_core-ascend910B,ai_core-ascend310p -lan cpp -out ./LeakyReLUCustom
关于msopgen的使用,请参考昇腾社区的文档“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>附录>msopgen工具使用>使用说明”
参数含义:
- -i:指定算子原型定义文件add_custom.json所在路径,请根据实际情况修改。
- -c:ai_core-<soc_version>代表算子在AI Core上执行,<soc_version>为昇腾AI处理器的型号。多个处理器之间用逗号“,”分割。
- -lan: 参数cpp代表算子基于Ascend C编程框架,使用C++编程语言开发。
- -out:生成文件所在路径,可配置为绝对路径或者相对路径,并且工具执行用户对路径具有可读写权限。若不配置,则默认生成在执行命令的当前路径。
生成的目录结构如下图右侧:此处主要修改三个文件:算子tiling定义文件;host侧实现文件;kernel侧实现文件。
4、修改device侧和host侧的代码
1)修改host/leaky_relu_custom_tiling.h
tilingdata是用来计算数据切分的,在固定算子shape的情形下,只需要totalLength和tileNum即可,leakyreluCustom算子需要传递value的属性值,所以增加1个value。改动处都用红框标记了,这里需要注意的就是增加value那一行。进一步学习,可参考“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(进阶篇)>host侧算子实现>Tiling实现 昇腾社区-官网丨昇腾万里 让智能无所不及”。
2)修改host/leaky_relu_custom.cpp代码
host侧算子实现开发包括Tiling实现、Shape推导等函数实现、算子原型注册。进一步学习,可参考“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(进阶篇)>host侧算子实现>host侧算子实现概述 昇腾社区-官网丨昇腾万里 让智能无所不及”
首先是Tiling实现,这里面需要注意的是获取算子value属性代码,其余的可以和例程保持一致即可。GetAttrs()可在API参考里查找。“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>API参考>Host侧实现API>basics>ComputeNodeInfo类>GetAttrs”
Shape推导的代码不用修改,输出shape和输入shape保持一致。
算子原型注册代码有差异,仔细看的话,会发现和算子原型定义的那个json文件是一致的。但此处通过工具自动生成,也不需要修改。请注意右侧用红框标出的部分,特别是关于属性的定义“this->Attr("value").Float();”
3)修改device侧代码
这是算子编程的很重要的部分,需要修改的地方如下图所示。进一步可阅读“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(进阶篇)>kernel侧算子实现 昇腾社区-官网丨昇腾万里 让智能无所不及”。
算子类实现代码,进一步阅读,参考“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(基础篇)>矢量编程>算子类实现 昇腾社区-官网丨昇腾万里 让智能无所不及”。这部分内容建议刚开始学的小伙伴要好好看明白,这里面包含了“多核并行”,“编程范式”,“流水线”等众多AscendC编程思想。虽说以后编写代码也可以ctrl+c,和ctrl+v,但理解了就能一劳永逸。
5、编写简单核函数调用代码
简单、粗暴,直接将例程的“kernel_direct_call"目录拷过来,然后将下图中的三个文件改名。这三个文件,加上run.sh,我们只需要修改这四个文件,就可以完成调用代码的改写。这部分内容,有兴趣的小伙伴,参看“文档首页>CANN社区版>7.0.RC1.alpha003>算子开发>Ascend C算子开发>算子开发(基础篇)>核函数运行验证 昇腾社区-官网丨昇腾万里 让智能无所不及”
1)kernel_call_leakyrelu_custom_tiling.h
这个文件,是模拟算子工程host侧算子实现和kernel侧算子实现,传递tilingdata数据用的。改动很小,但需要花时间去好好阅读下,感觉此处解密了AscendC算子这一功能的实现方式。
2)kernle_call_leakyrelu_custom.py
此脚本,生成输入数据,然后计算出输出数据,并落盘,然后再run.sh脚本里与AscendC算子的结果进行比较。
3)main.cpp
这里面是分别cpu和npu方式调用核函数的代码,“孪生调试”。代码修改主要就是名称,函数的参数数量之类,版面首限,也没有办法展开,不再赘述。此处需要注意的是读取tilingdata模拟数据时,因为增加了算子属性value,所以读取长度,需要再原来的基础上增加sizeof(float)。
4)run.sh
主要修改2处:
修改算子工程名
需要将算子工程op_kernel的算子实现代码拷贝到核函数调用工程里,需要对路径名和文件名做相应修改。run.sh其余部分无需修改,它是通过比较kernle_call_leakyrelu_custom.py和AscendC算子对相同数据的计算结果的md5sum,来判断结果是否一致的。
四、编译运行
1、CPU模式
1)将代码做成zip格式的压缩包,启动环境,将代码上传到环境。
2)安装toolkit和算子开发包。(参见第一节,搭建cpu开发运行环境章节)
3)解压缩。并给算子工程“/util”目录下文件,赋权限750,否则会报错。
4)编译运行
进入“kernel_direct_call”目录,运行“bash run.sh ascend910B1 cpu”
运行结果:
2、npu模式
参考“AscendC算子NPU开发调试环境成功搭建篇https://www.bilibili.com/read/cv26991439/ ”搭建NPU的调试运行环境。并把代码搞里头,过程参考cpu模式。
进入kernel_direct_call目录,运行 bash run.sh ascend910B1 npu_onboard
运行结果如下:
至此,完成了leakyrelu算子的开发和核函数调用方式的验证。
附录:
看到这儿的小伙伴,如果还没有加入CANN训练营,可以按下面的链接进行报名,不仅可以学到基于昇腾AI开发的知识,还有大奖可以拿。
报名链接:https://www.hiascend.com/developer/activities/details/84b950830fc44476851860f51f0873a2/signup?channelCode=0&recommended=234384