CANN学习资源开源仓的中级算子开发一

举报
黄生 发表于 2026/03/26 20:46:14 2026/03/26
【摘要】 与将host和device代码混合到单个asc源文件的开发模式不同,Ascend C单算子工程实现了从Host侧的算子注册、形状推导、Tiling分块、任务下发与内存管理,到kernel侧使用 Ascend C编写核函数计算逻辑的完整流程,最终生成一个可编译和部署的完整算子包。msOpGen可基于算子原型定义输出算子工程:包括算子host侧代码实现文件(大致框架,代码自己写)、算子kerne...

与将host和device代码混合到单个asc源文件的开发模式不同,Ascend C单算子工程实现了从Host侧的算子注册、形状推导、Tiling分块、任务下发与内存管理,到kernel侧使用 Ascend C编写核函数计算逻辑的完整流程,最终生成一个可编译和部署的完整算子包。

msOpGen可基于算子原型定义输出算子工程:包括算子host侧代码实现文件(大致框架,代码自己写)、算子kernel侧实现文件(同样)以及工程编译配置文件等。CANN安装好后就自带有msopgen等工具的。这里仓里的做法却是从头编译安装,从https://gitcode.com/Ascend/msopgen.git克隆,编译过程中又下载子模块https://gitcode.com/cann/asc-tools.git。编译安装好,并写好算子原型文件add_custom.json,运行:

msopgen gen -i Sources/add_custom.json -c ai_core-ascend910b1 -lan cpp -out Sources/custom_op

在我看来尽是瞎折腾,折腾出来就是个毛坯房,有啥意思。我去拷贝一个人家开发好的精装修完的算子工程,自己改吧改吧不省事吗。

在Ascend C算子工程中,Host侧是算子执行的控制与管理层,弥补kernel侧仅擅长高密度并行计算的短板,承担三类关键工作:

  • 算子原型注册: 注册算子的原型定义,从而确保算子能够被框架正确识别、编译和执行。算子原型主要描述了算子的输入输出、属性等信息以及算子在AI处理器上相关实现信息,并关联tiling实现等函数。
  • Shape、Dtype推导函数实现: 根据算子的输入张量描述、算子逻辑及算子属性,推理出算子的输出张量描述,包括张量的形状、数据类型及数据排布格式等信息。这样算子构图准备阶段就可以为所有的张量静态分配内存,避免动态内存分配带来的开销(应该只有在输入形状固定或在编译时确定,才能做到吧?)。
  • Tiling实现: 大多数情况下,Local Memory的存储,无法完整的容纳算子的输入与输出,需要每次搬运一部分输入进行计算然后搬出,再搬运下一部分输入进行计算,直到得到完整的最终结果,这个数据切分、分块计算的过程称之为Tiling。根据算子的shape等信息来确定数据切分算法相关参数(比如每次搬运的块大小,以及总共循环多少次)的计算程序,称之为Tiling实现。可看下面示意图(设定场景:假设某 AI Core 的片上存储容量上限为 10 个数据元素):

与前面单个ASC文件的算子类实现不同,算子工程支撑动态shape输入,所以在Init函数中需要根据Tiling结构体数据来动态初始化内存,而不是使用固定的大小进行初始化。

算子工程编译和安装完成后,运行预先编写的单算子API调用程序,对该算子工程进行测试

# 编译测试代码
g++ -I$ASCEND_TOOLKIT_HOME/include -I${HOME}/vendors/customize/op_api/include -L$ASCEND_TOOLKIT_HOME/lib64 -L${HOME}/vendors/customize/op_api/lib Sources/test/main.cpp -lcust_opapi -lnnopbase -lacl_rt -o execute_add_op;
# 设置自定义算子so路径并执行调用代码
source ${HOME}/vendors/customize/bin/set_env.bash;./execute_add_op

单算子API调用,是直接调用单算子API接口,无需提供单算子描述文件进行离线模型的转换,是最重要的一种调用方式。算子编译安装后可以在安装目录下找到生成的单算子API:单算子调用的头文件.h和动态库libcust_opapi.so

.
|____customize
|    |____scripts
|    |    |____uninstall.sh
|    |____framework
|    |    |____plugin
|    |    |    |____npu_supported_ops.json
|    |    |____tensorflow
|    |    |    |____libcust_tf_parsers.so
|    |____op_api
|    |    |____lib
|    |    |    |____libcust_opapi.so #这里
|    |    |____include
|    |    |    |____aclnn_add_custom_template.h #还有这里
....

aclnn_add_custom_template.h中的算子API形式定义为“两段式接口”:

 15 /* funtion: aclnnAddCustomTemplateGetWorkspaceSize
 16  * parameters :
 17  * x : required
 18  * y : required
 19  * out : required
 20  * workspaceSize : size of workspace(output).
 21  * executor : executor context(output).
 22  */
 23 __attribute__((visibility("default")))
 24 aclnnStatus aclnnAddCustomTemplateGetWorkspaceSize(
 25     const aclTensor *x,
 26     const aclTensor *y,
 27     const aclTensor *out,
 28     uint64_t *workspaceSize,
 29     aclOpExecutor **executor);
 30 
 31 /* funtion: aclnnAddCustomTemplate
 32  * parameters :
 33  * workspace : workspace memory addr(input).
 34  * workspaceSize : size of workspace(input).
 35  * executor : executor context(input).
 36  * stream : acl stream.
 37  */
 38 __attribute__((visibility("default")))
 39 aclnnStatus aclnnAddCustomTemplate(
 40     void *workspace,
 41     uint64_t workspaceSize,
 42     aclOpExecutor *executor,
 43     aclrtStream stream);

然后单算子API调用代码与之前大同小异,除了调用本身,调用之前要分配内存准备好Tensor,计算标杆数据,调用后比较结果等。这里说一下代码里定义的CHECK_ACL宏,目的是简化代码的编写,用于检查ACL API调用是否成功:

#define CHECK_ACL(expr)                                                                                 \
    do {                                                                                                \
        auto __ret = (expr);                                                                            \
        int32_t __code = static_cast<int32_t>(__ret);                                                   \
        if (__code != 0) {                                                                              \
            fprintf(stderr, "[ERROR] %s failed at %s:%d, ret=%d\n", #expr, __FILE__, __LINE__, __code); \
        }                                                                                               \
    } while (0)

这里面有一个很大的空隙,就是算子工程的CMake编译配置已经大大的复杂化了,构建产出也大大的增加,但是对这块并没有一个讲解。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。