基于 CANNBot + 码道,完整覆盖 Ascend C 算子从 0 到 1 开发全流程。适合希望快速上手 NPU 算子定制开发的工程师,无需深入底层汇编,亦可在 AI 辅助下完成端到端实现。

一、背景知识

1.1 CANNBot 与码道

CANNBot 是面向 CANN 开发的系列智能体,其配套的 cannbot-skills 仓库 提供了可复用的 Skills 模块,目前已实现 Ascend C / PyPTO 算子开发全流程覆盖, 未来将拓展至 CANN 更多技术领域。仓库地址:https://atomgit.com/cann/cannbot-skills

码道是华为自研的内置 AI 开发助手,集成了 GLM5、GLM5.1,全部免费使用。以代码为舟,渡技术之海——让每一步开发都有章可循、有据可依,是算子开发过程中最可靠的智能伙伴。

1.2 Exp 算子简介

Exp(指数)算子是神经网络中最基础的非线性激活函数之一,负责逐元素计算自然指数 y = exp(x)。尽管实现简单,却是构建复杂模型不可或缺的基础组件。

在实际业务中,通用框架自带的 Exp 算子往往存在以下局限:

无法充分利用 NPU 硬件特性:通用实现面向 CPU/GPU,无法直接调用昇腾 AI Core 的向量化指令

内存拷贝开销大:Global Memory 与 Unified Buffer 之间的数据搬运未经优化

缺乏向量化加速:未充分利用 128-bit / 256-bit 向量指令集

属性 说明
功能 逐元素计算自然指数函数 y = exp(x)
数据类型 float16 / float32
输入形状 任意维度 N×C×H×W
优化特性 多核并行 · 向量化计算 · UB 高效数据复用

示例仓库:https://atomgit.com/VON-/cann_exp_custom

1.3 自研目标

1.3优化目标

维度 优化方向
多核并行 充分利用多 AI Core 并行计算能力
向量化加速 128-bit / 256-bit 向量指令集,单指令多数据
UB 优化 Unified Buffer 高效数据复用,减少 GM 访存次数

二、算子需求与规格说

2.1 核心计算公式

Exp 算子的数学本质极为简洁:

y = exp(x)   # 逐元素计算自然指数函数

计算特性:逐元素独立计算 → 无数据依赖 → 天然并行 → 向量化友好

这一特性使得 Exp 算子天然适合 SIMD(单指令多数据)并行处理,是验证 Ascend C 开发框架的最佳入门案例。

2.2 输入输出规格

属性 输入张量 (x) 输出张量 (y)
数据类型 float16 / float32 与输入相同
形状 任意维度 N×C×H×W 与输入完全相同
数据来源 Global Memory (GM) Global Memory (GM)

2.3 精度与边界值要求

数据类型 相对误差 (rtol) 状态
float32 < 1e-6 完全达标
float16 < 1e-3 完全达标

特殊值处理

输入 x 输出 exp(x) 处理方式
-∞ 0 支持
+∞ +∞ 支持
0 1 支持
NaN NaN 支持

三、Tiling 切分策略

3.1 算子分类分析

Elementwise(逐元素类算子) 特征:

Shape 相同:输入输出形状完全一致,无维度变换

无依赖:每个元素计算相互独立,无数据依赖

向量化:计算密度高,天然适合 SIMD 并行

基于以上特征,我们选择沿 N 维度进行多核切分,以充分发挥昇腾 NPU 的并行计算优势。

3.2 切分公式与设计要点

总元素数计算

total_elements = N × C × H × W
elements_per_core = total_elements / used_core_num

设计要点

● 沿 N 维度切分,保证各核计算范围清晰

● 按行切分,保证内存访问的连续性(Row-major 友好)

● 动态获取 AI Core 数量,实现负载均衡

● 处理剩余(remainder)数据,确保任意 shape 都能正确运行

3.3 UB 切分与 Buffer 规划

Unified Buffer(UB)是 AI Core 的片上高速存储,合理规划其使用能显著提升数据复用率:

数据类型 单次处理量 内存占用 UB 利用率
float32 8192 元素 32KB ~33%
float16 16384 元素 32KB ~33%

数据流向:xGm (GM) → CopyIn → inQueue (UB) → Exp Compute → yLocal → CopyOut → yGm (GM)

这一数据流揭示了 Ascend C 算子开发的核心范式:将数据从 Global Memory 加载到 Unified Buffer,在 UB 内完成计算,再写回 Global Memory。理解这一范式,是写出高效算子的关键。

四、开发环境准备

4.1 安装 CANNBot

在桌面创建项目文件夹,进入后执行对应脚本即可完成环境初始化:

Ascend C 算子开发(推荐)

适用于 Ascend C 算子直调开发场景,自动安装 Skills、配置文件及 asc-devkit 工具包:

git clone https://gitcode.com/cann/cannbot-skills.git
cd cannbot-skills/ops/teams/ops-direct-invoke
bash init.sh project opencode   # OpenCode 用户(默认)
bash init.sh project claude     # Claude Code 用户

PyPTO 算子开发

git clone https://gitcode.com/cann/cannbot-skills.git
cd cannbot-skills/ops/teams/pypto-op-orchestrator
bash init.sh project opencode   # OpenCode 用户(默认)
bash init.sh project claude     # Claude Code 用户

详细说明参见本地项目文件夹内的ops/teams/ops-direct-invoke/quickstart.mdhttps://atomgit.com/cann/cannbot-skills/blob/master/plugins-official/ops-direct-invoke/quickstart.md文档内包含了完整的 skills 文件结构,包括知识库、模板、调试工具、测试用例等。

4.2 创建代码仓库

创建自己的 git 仓库,便于后续不断修改代码、与云端 Notebook 协同开发:

五、算子开发

5.1 启动码道

让码道去学习项目中 skills 目录的内容进行学习,然后引导其完成算子开发:

提示:告诉码道去学习和理解 skills/ 目录下的开发规范,然后让它基于你的需求生成代码。这样码道就能生成符合 CANN 规范的算子代码,而不需要你手工编写一行。

5.2 核心代码逻辑

Host 侧 Tiling 函数 — 计算切分参数,分配各核工作范围:

// 计算总元素数
uint32_t totalLength = 1;
for (size_t i = 0; i < inputShape.GetDimNum(); i++) {
    totalLength *= inputShape.GetDim(i);  // 累乘所有维度
}
 
// 计算 UB 最大 tile 长度
uint32_t maxTileLength = UB_SIZE / 2 / dtypeSize;
uint32_t tileLength = std::min(totalLength, maxTileLength);
 
// 设置 Block 数量(固定使用 8 核并行)
context->SetBlockDim(8);

Kernel 侧核心 API — 在 Unified Buffer 中完成实际计算:

Exp(yLocal, xLocal, length);

参数 方向 类型 说明
yLocal 输出 LocalTensor 结果张量,写入 UB
xLocal 输入 LocalTensor 输入张量,从 GM 加载
length 输入 uint32_t 本次计算的元素个数

Kernel 执行流程

核心循环:获取本核计算范围 → 循环处理每个 tile(GM→UB CopyIn → Compute → UB→GM CopyOut)→ 处理剩余数据 → 返回

5.3 项目工程结构

一个规范的 CANN 算子项目通常包含以下目录结构:

exp_custom/
├── docs/                          文档目录
│    ├── REQUIREMENT.md            需求分析文档
│    ├── DESIGN.md                技术设计文档
│    └── PLAN.md                  开发计划文档
├── kernel/                        Kernel 侧实现(运行在 AI Core)
│    ├── exp_custom_kernel.h      Kernel 头文件
│    └── exp_custom_kernel.cpp    Kernel 实现
├── host/                          Host 侧实现(运行在 CPU)
│    ├── exp_custom.cpp           Host 实现(Tiling 逻辑)
│    └── exp_custom_tiling.h      Tiling 参数定义
├── tests/                         测试用例
│    ├── test_exp_custom.py       完整功能测试
│    └── test_exp_numpy.py        NumPy 对比验证
├── CMakeLists.txt                 编译配置文件
├── exp_custom.ini                算子配置文件
├── BUILD_GUIDE.md                编译指南
├── DEPLOY_GUIDE.md               部署指南
└── README.md                      使用说明

六、编译构建

6.1 两阶段编译架构

编译工具链两阶段分离

Host 侧.cpp/.h → CMake → g++ (GCC 11.4.0) → lib*_host.so

Kernel 侧.cpp/.h → CMake → 毕昇编译器 → lib*_kernel.so

Host 侧和 Kernel 侧必须分开编译,因为 Host 运行在通用 CPU 上(支持标准 C++),而 Kernel 运行在昇腾 AI Core 上(使用自定义编译器和 aicore intrinsics)。

6.2 构建步骤

打开 Notebook 环境,启动后进入 Terminal,执行以下步骤:

Step 1:拉取代码

git clone https://gitcode.com/你的用户名/你的仓库名.git
cd 你的仓库名

Step 2:构建项目

mkdir build && cd build
cmake ..
make

如果看到 Scanning dependencies… 并最终生成了可执行文件或 .so 库,说明编译成功

编译配置参考

配置项 Host 侧 Kernel 侧
编译器 GCC 11.4.0 毕昇编译器
CANN 版本 8.5.0 8.5.0
CMake 版本 ≥ 3.16 ≥ 3.16
输出格式 .so 动态库 .so 动态库

七、测试验证

7.1 功能测试

测试通过率:10/10 = 100%

用例 ID Shape 数据类型 状态
UT_001 (128,) float32 通过
UT_002 (128, 256) float32 通过
UT_003 (32, 64, 128) float32 通过
UT_004 (128,) float16 通过
UT_005 (128, 256) float16 通过

7.2 边界值测试

用例 ID 测试内容 输入值 预期输出 状态
BT_001 负无穷 -∞ 0 通过
BT_002 正无穷 +∞ +∞ 通过
BT_003 零值 0 1 通过
BT_004 NaN NaN NaN 通过
BT_005 大负数 -100 ≈0 通过

7.3 运行验证

运行项目,出现预期输出样式即表示算子开发完成 ✅

八、迭代优化:AI 修 Bug 实战

在实际开发中,AI 生成的代码往往需要多轮调优。以 GLM5 模型为例,同样经历了若干次修正才达到预期效果。

8.1 典型问题与解决方案

问题类型 错误现象 解决方案
1 CMake 找不到 CANN Could not find CANN 设置 ASCEND_HOME_DIR 环境变量
2 头文件路径缺失 kernel_operator.h: No such file 添加完整的 CANN include 子目录路径
3 API 不兼容 GetStorageShape 不存在 使用 GetOriginShape() + GetDim() 替代
4 编译器不支持 aicore 语法错误 Host 侧用 g++,Kernel 侧用毕昇编译器分离编译

8.2 修 Bug 标准流程

Step 1 — 复制报错日志:将终端报错信息完整复制,不要遗漏堆栈行。

Step 2 — 丢给码道:将报错内容直接发给码道,简明扼要地描述问题,如"这段代码报错了,怎么修改?"

Step 3 — 提交更新:本地修改代码后,推送到 Git 仓库:

git add .
git commit -m "修复精度问题,调整 Tensor 定义"
git push

Step 4 — Notebook 执行 git pull 拉取最新代码,重新编译运行。

Step 5 — 循环往复,直到全部测试用例通过。

码道开发心得

深入理解硬件架构:AI Core、UB、GM 三层存储体系是 Ascend C 的核心

掌握 Tiling 策略:合理的切分是性能的关键,切分过大导致 UB 溢出,切分过小则并行度不足

善用工具链:CANN Skills + CannBot 大幅提升开发效率,让 AI 辅助而非替代人类判断

测试驱动开发:完善的测试用例(覆盖普通值、边界值、特殊值)是质量的保障

九、后续优化方向

阶段 优化方向 目标
短期 Kernel 侧毕昇编译优化、

性能基准测试
稳定可靠
中期 Double Buffer 优化、指令流水线优化、内存访问优化 性能提升
长期 更多数据类型支持(int8/bfloat16)、大 shape 验证、集成到模型训练 pipeline 生态完善

十、总结

通过 CANNBot + 码道 的组合,我们实现了从环境准备到算子开发、编译运行、迭代调优的全流程 0 代码手写,极大提升了开发效率。从 0 到 1 完成 Ascend C 算子开发全流程,为后续更复杂算子开发奠定坚实基础。

码道 + CANNBot = 高效算子开发新范式

让 AI 成为开发者的得力助手,让代码之道(码道)越走越宽。

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐