从0到1掌握Ascend C算子工程开发方式
本文系统介绍Ascend C算子的标准工程开发流程,从快速开发与标准开发方式对比入手,详细解析包括数据流设计、算子工程创建、HOST/Device架构理解、Tiling机制等核心内容。标准开发方式通过代码分离、高通用性设计,使算子能真正应用于生产环境,涵盖从工程创建到编译部署的全流程,并强调数据搬移-计算-输出三步骤的关键作用。文章还提供Sigmoid算子开发实战指南,帮助开发者掌握将算子产品化的
从0到1,掌握Ascend C算子工程开发方式实战
训练营简介
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖
报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro
引言
在掌握了Ascend C的基础编程知识后,如何将算子工程化、产品化,使其能够真正应用到实际的深度学习模型中呢?本文将系统介绍Ascend C算子的标准工程开发方式,从算子工程创建到编译部署的完整流程。相比快速开发方式,标准开发方式更加规范、通用,是将算子推向生产环境的必经之路。
思维导图
mindmap
root((算子工程开发))
开发方式对比
快速开发
算法验证
原型开发
学习测试
局限性
标准开发
代码分离
高通用性
可支持性强
便于集成
标准开发流程
完整步骤
创建工程
实现逻辑
编译成功
部署环境
验证功能
数据流设计
Local Tensor
数据搬入
计算处理
数据搬出
核心三步骤
CopyIn
Compute
CopyOut
工程创建
算子规格
输入输出
计算逻辑
数据排布
msopgen工具
原型定义文件
编译配置
Tiling定义
Host侧实现
Shape推断
Kernel目录
HOST与Device
计算机类比
HOST侧CPU
Device侧NPU
硬件对应
服务器CPU
AI加速卡
AI Core
数据流向
Host Memory
Global Memory
Local Memory
Tiling机制
核心概念
Tiling实现
Tiling块
Tiling算法
Tiling结构体
Tiling函数
三大环节
结构体定义
函数实现
Kernel利用
设计要点
容量限制
多核并行
对齐要求
边界处理
Shape支持
固定Shape
硬编码
简单实现
局限性
动态Shape
参数传入
实时计算
高通用性
Shape推导
校验功能
内存优化
实现方法
编译部署
编译配置
CMakeLists
ASCEND_PACKAGE_PATH
编译过程
build.sh
生成安装包
部署安装
运行安装包
环境配置
实战要求
Sigmoid算子
数学公式
具体要求
注意事项
精度控制
API选择
数据类型
边界处理
一、Ascend C两种开发方式对比
1.1 快速开发方式
快速开发方式通过直接编写kernel函数,用一个CPP程序进行调用和编译。这种方式的优点是简单直接,适合算法验证和原型开发。
适用场景:
- 算法可行性验证
- 快速原型开发
- 学习和测试
局限性:
- 无法集成到大型模型中
- 缺乏工程化结构
- 不支持多种调用方式
1.2 标准开发方式的优势
标准开发调试方式更侧重于算子的工程化和产品化,具有以下核心优势:
1. 清晰的代码分离:将算子的实现过程与调用过程清晰分离,提高代码的可维护性。
2. 高通用性:算子编译后可以作为独立的组件,供多种方式调用。
3. 可支持性强:支持NN接口、单算子模型、PyTorch等主流深度学习框架的调用。
4. 便于集成:能够直接集成到更大的模型中,支持端到端的模型部署。
这些优势使得标准开发方式成为生产环境中算子开发的首选方案。
二、标准开发流程架构深度解析
2.1 完整开发流程
标准开发流程包含五个核心步骤:
步骤1:创建独立的算子工程项目
使用工具自动生成标准的工程结构,包含所有必需的文件和目录。
步骤2:在工程中实现算子计算逻辑
编写Host侧和Device侧的代码,实现完整的算子功能。
步骤3:编译成功
配置编译环境,执行编译命令,生成算子库文件。
步骤4:部署到运行环境
将编译生成的安装包部署到目标环境中。
步骤5:编写调用程序验证
通过多种方式调用算子,验证功能正确性和性能表现。
2.2 数据流设计的关键理解
在Ascend C编程中,理解数据流至关重要。很多初学者容易犯的一个错误是:以为向量API可以直接操作Global Memory中的数据。
实际情况:向量API的操作对象是AI Core内部高速缓存中的Local Tensor,而不是全局内存中的数据。
这意味着我们必须设计清晰完整的数据流路径:
数据搬入 → 计算处理 → 数据搬出
2.3 核心数据流三步骤
第一步:数据搬入(CopyIn)
从Global Memory搬运数据到AI Core内部的Local Memory。这一步由DMA(Direct Memory Access)搬运单元负责,可以在后台异步执行。
第二步:计算流程(Compute)
在Local Memory上调用向量计算API(如向量加法、矩阵乘法等)完成高效的数据运算。由于Local Memory的访问速度远高于Global Memory,计算效率得到保证。
第三步:数据搬出(CopyOut)
将计算结果从Local Memory搬回Global Memory。同样由DMA单元负责,支持与下一轮计算的流水并行。
理解这个数据流模型,是掌握Ascend C算子开发的关键基础。
三、算子工程创建与结构解析
3.1 算子规格定义
在创建算子工程前,首先要明确算子的规格。以向量加法算子AddCustom为例:
输入:两个输入Tensor(X, Y)
输出:一个输出Tensor(Z)
计算逻辑:Z = X + Y
数据排布:统一采用ND(N-Dimensional)格式
3.2 使用msopgen工具创建工程

Ascend提供了msopgen工具用于快速创建标准化的算子工程。执行命令后,工具会自动生成以下关键文件:
算子原型定义文件(*.py):
定义算子的接口、参数、数据类型等信息,用于算子注册。
编译配置文件(CMakeLists.txt):
配置编译选项、依赖库、目标平台等信息。
Tiling定义文件(*_tiling.h):
定义数据分块策略的结构体和参数。
Host侧实现(*_host.cpp):
包含tiling计算、shape推导等Host侧逻辑。
形状推断文件(ShapeInfer.cpp):
实现输出Tensor的shape推导功能。
Kernel实现目录(op_kernel/):
存放算子核心计算逻辑的kernel函数。
这种结构化的工程组织,确保了代码的清晰性和可维护性。
四、HOST与Device侧的深度理解
4.1 计算机类比法

为了更好地理解HOST和Device的关系,我们可以做一个类比:
HOST侧就像电脑主机(CPU),负责执行主要的应用程序和任务调度。它决定"做什么"、“怎么做”,但不直接进行高强度的并行计算。
Device侧就像专用的显卡(GPU/NPU),负责执行专项的并行计算任务。它接收HOST的指令,利用强大的并行能力完成实际的计算工作。
4.2 硬件架构对应
HOST侧硬件:
- x86或Arm架构的服务器CPU
- 服务器内存(Host Memory)
- 负责程序控制流、数据准备等工作
Device侧硬件:
- 安装的昇腾AI加速卡
- 包含多个AI Core处理器
- 拥有独立的设备内存(Global Memory)
AI Core内部:
- 更高速的缓存(Local Memory)
- 专用的计算单元(Scalar、Vector、Cube)
- 控制单元和搬运单元
4.3 完整数据流向
理解完整的数据流向对于算子开发至关重要:
阶段1:数据最初存储在Host Memory中。
阶段2:需要计算时,数据从Host Memory拷贝到Device侧的Global Memory。这一步由Host侧代码控制。
阶段3:数据从Global Memory搬运到AI Core内部的Local Memory。这一步在Kernel函数中通过DMA指令完成。
阶段4:在Local Memory上进行高速计算。
阶段5:结果从Local Memory搬回Global Memory。
阶段6:最终结果可以拷贝回Host Memory供应用程序使用。
五、Tiling机制全面解析
5.1 Tiling核心概念
Tiling是Ascend C算子开发中最重要的概念之一。由于AI Core的Local Memory容量有限(通常只有几百KB),无法一次性处理大规模数据,因此需要将数据切分成小块分批处理。
Tiling实现:完整的数据切分策略及参数计算过程。
Tiling块:每次搬运和计算的一部分数据块。
Tiling算法:根据算子不同输入形状,动态决定每个输入块大小的具体策略。
Tiling结构体:用于统一管理tiling算法所需参数的数据结构。
Tiling函数:实现tiling算法并将计算出的参数填入结构体的函数。
5.2 Tiling实现的三个关键环节
环节1:Tiling结构体的定义
在Host侧的头文件中(如op_host/*_tiling.h),使用框架提供的宏定义结构体:
BEGIN_TILING_DATA_DEF(TilingData)
TILING_DATA_FIELD_DEF(uint32_t, totalLength);
TILING_DATA_FIELD_DEF(uint32_t, tileNum);
TILING_DATA_FIELD_DEF(uint32_t, blockLength);
END_TILING_DATA_DEF();
这个结构体定义了数据切分所需的所有参数。
环节2:Tiling函数的实现
在Host侧的CPP文件中(如op_host/*.cpp),实现tiling计算逻辑:
- 定义tiling结构体变量
- 从Context中获取算子输入信息(shape、dtype等)
- 根据分块算法计算每个字段的值
- 为结构体的每个字段进行赋值
- 调用
tiling.SetBuffer()保存结构体
环节3:Kernel侧利用tiling信息
在Kernel函数中,使用GetTilingData宏将tiling数据解析为结构体,然后根据这些参数指导具体的计算流程。
5.3 Tiling算法设计要点
设计tiling算法时需要考虑以下因素:
1. Local Memory容量限制:确保单次搬运的数据不超过可用的Local Memory大小。
2. 多核并行:合理分配数据到多个AI Core,实现负载均衡。
3. 对齐要求:考虑硬件的数据对齐要求,提高访存效率。
4. 边界处理:处理数据总量无法被块大小整除的情况。
六、固定Shape与动态Shape对比
6.1 固定Shape开发
实现方式:在代码中硬编码特定的数据尺寸。
优势:实现简单,开发者只需关注已知数据规模下的优化策略。
示例:
const uint32_t TOTAL_LENGTH = 8192;
const uint32_t TILE_LENGTH = 256;
const uint32_t TILE_NUM = TOTAL_LENGTH / TILE_LENGTH;
局限性:只能处理单一尺寸的数据,当输入shape变化时无法正常工作。在实际应用中,这种限制往往是不可接受的。
6.2 动态Shape开发
核心思路:将数据的Shape信息作为参数动态传入,算子内部实时计算分块策略。
实现方式:
- 在tiling函数中获取实际的输入shape
- 根据shape动态计算tileNum、blockLength等参数
- 通过tiling结构体传递到Kernel侧
- Kernel函数根据这些参数动态调整执行逻辑
关键参数:
- totalLength:数据总长度
- coreNum:参与计算的AI Core数量
- tileNum:每个核上的分块数量
- blockLength:每个块的长度
优势:一次开发,支持任意shape的输入,大大提高了算子的通用性和实用价值。
七、Shape推导功能详解
7.1 Shape推导的核心任务
Shape推导功能负责根据算子输入Tensor的Shape信息,自动推导出输出Tensor的Shape。
示例:
- 输入X的shape:(8, 128, 256)
- 输入Y的shape:(8, 128, 256)
- 对于加法算子,输出Z的shape:(8, 128, 256)
7.2 Shape推导的重要作用
1. 模型构图阶段的校验
在模型构建阶段,框架会根据算子定义的shape推导规则,对模型每一层连接的正确性进行校验。如果shape不匹配,可以在构图阶段就发现错误,避免运行时出现问题。
2. 内存预分配优化
有了精确的输出shape信息,框架能够在执行前就知道需要为算子的输出分配多大的内存空间。这样可以:
- 避免运行时的内存分配开销
- 实现更好的内存规划和复用
- 减少内存碎片
7.3 Shape推导的实现
在ShapeInfer.cpp文件中实现shape推导逻辑:
IMPLEMT_INFERFUNC(AddCustom, AddCustomInfer) {
// 获取输入shape
auto input_shape_x = op.get_input_desc(0).GetShape();
auto input_shape_y = op.get_input_desc(1).GetShape();
// 推导输出shape(加法算子输入输出shape相同)
auto output_shape = input_shape_x;
// 设置输出shape
op.update_output_desc(0, output_shape);
return SUCCESS;
}
八、编译与部署实战
8.1 编译配置
在算子工程根目录下的CMakeLists.txt文件中,需要关注以下关键配置:
ASCEND_PACKAGE_PATH:指定Ascend软件包的安装路径。
默认安装路径:/usr/local/Ascend/ascend-toolkit/latest
如果你的安装路径不同,需要修改这个参数。
8.2 编译过程
在算子工程根目录下执行编译命令:
bash build.sh
编译成功后,会在当前目录下生成build子目录,其中包含:
- 编译生成的库文件
- 安装包(通常命名为
custom_op_package.run) - 其他中间文件
8.3 部署安装
进入build目录,直接运行安装包:
cd build
./custom_op_package.run
安装成功后,自定义算子会被正式安装到当前环境中,可以通过NN接口、PyTorch等框架进行调用。
九、中级认证实战要求
9.1 Sigmoid算子实现要求
中级认证要求实现Sigmoid算子,这是一个经典的激活函数:
数学公式:
[
\text{Sigmoid}(x) = \frac{1}{1 + e^{-x}}
]
具体要求:
- 补充Kernel侧和Host侧的完整代码
- 在Host侧实现tiling结构体的定义和计算
- 算子必须支持Float16类型的输入输出
- 实现动态shape的tiling策略
9.2 实现注意事项
精度控制:
Sigmoid涉及指数计算,需要注意计算精度。可以使用Ascend C提供的高精度API,如Exp、Div等。
API选择:
仔细查看官方API文档,选择合适的向量API。对于指数计算,可以使用:
Exp:指数函数Reciprocal:倒数Add:加法
数据类型支持:
虽然要求支持Float16,但在计算过程中可以考虑提升到Float32进行计算,最后再转回Float16,以保证精度。
边界情况处理:
当输入值很大或很小时,Sigmoid的输出会接近0或1,需要注意数值稳定性。
总结
本文系统介绍了Ascend C算子的标准工程开发方式,从算子设计到部署的完整流程。核心要点包括:
-
HOST与Device侧的架构理解:掌握数据在不同层次内存间的流动路径。
-
Tiling机制:理解数据切分策略的设计与实现,这是处理大规模数据的关键。
-
动态Shape支持:通过动态Shape设计,让算子具备处理任意尺寸输入的能力。
-
Shape推导:实现输出shape的自动推导,支持模型构图验证和内存优化。
-
工程化流程:掌握从编译到部署的完整工程化流程。
通过本文的学习和实践,你已经掌握了将Ascend C算子工程化、产品化的核心技能。这些知识不仅适用于简单的向量加法,也适用于复杂的卷积、注意力等高级算子的开发。
工程化思维和标准开发流程,是将算子从原型推向生产环境的必经之路。持续实践,不断优化,你将能够开发出高性能、高可靠性的AI算子,为深度学习模型的高效运行提供坚实的底层支撑!
更多推荐



所有评论(0)