CANNBot× 码道 × Notebook 全程 0 代码开发 Exp 自定义算子
基于 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 成为开发者的得力助手,让代码之道(码道)越走越宽。
更多推荐



所有评论(0)