昇腾 Ascend C 入门:手撕 Add 算子,从核函数到 NPU 跑通
·
写在前面
入门课的第一个自定义算子就是它,看着简单,却是新手最容易卡住的地方。这篇把流程和坑一次讲清。
一、先看全流程
昇腾算子开发固定五步:
算子分析 → 核函数定义 → 算子类实现 → 编译部署 → 调试验证
第一步别省。把输入输出个数、数据类型、计算逻辑写清楚:
算子类型:AddCustom
输入:x, y(float16)
输出:z
公式:z = x + y
并行策略:元素级并行
二、核函数
extern "C" __global__ __aicore__ void add_custom(
__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z,
uint32_t totalLength)
{
KernelAdd op;
op.Init(x, y, z, totalLength);
op.Process();
}
三个关键字是理解昇腾的密码:
__global__— 这段跑在 AI Core 上,不是 CPU__aicore__— 运行在 AI Core 内部__gm__— 指针指向全局内存(GM)
三、算子类:四个阶段
真正干活的是 KernelAdd,拆成 Init / CopyIn / Compute / CopyOut。
Init — 算清楚自己该处理哪一段
uint32_t blockLength = totalLength / GetBlockNum();
uint32_t offset = GetBlockIdx() * blockLength;
GetBlockIdx() 返回当前是第几个 AI Core。你只写一份代码,几十个核同时跑,每个核 offset 不同——这就是 SPMD 模型。
CopyIn / Compute / CopyOut — 搬进来、算、搬出去
// CopyIn
LocalTensor<half> xLocal = inQueueX.AllocTensor<half>();
DataCopy(xLocal, xGm[offset + i * tileLen], tileLen);
inQueueX.EnQue(xLocal);
// Compute
Add(zLocal, xLocal, yLocal, tileLen);
// CopyOut
outQueueZ.EnQue(zLocal);
LocalTensor<half> zOut = outQueueZ.DeQue<half>();
DataCopy(zGm[offset + i * tileLen], zOut, tileLen);
为什么要这么绕? 因为 AI Core 内部只有一块几百 KB 的 UB,你传进来的指针指向的是几 GB 外的 GM。所以必须:
GM → UB → 计算 → UB → GM
一句话:CPU 编程关注"算什么",NPU 编程关注"数据在哪、怎么搬"。
四、CPU / NPU 双模式验证
NPU 模式
CPU 报错就是逻辑错,NPU 报错多半是环境或对齐问题——分开验证能省很多时间。
# CPU 模式
export ASCENDC_CPU_DEBUG=1
./add_custom
# NPU 模式
./run.sh --soc_version=Ascend910B
五、六个坑,都踩过
- DataCopy 必须 32 字节对齐
单元素 float16 占 2 字节,一次搬 16 个才对齐。总长度不能整除时,尾块要单独处理,否则直接报错。 - 多核均分有余数
totalLength / blockNum 除不尽时最后一个核要补上余数,不处理就是精度错误。 - BUFFER_NUM=2 是双缓冲
开 2 份 buffer,计算和搬运能重叠,性能明显好于 1。这是最省事的一次优化。 - 判断条件写 blockLength,别写 totalLength
一个核算的是 blockLength,写错就整个崩。 - 基本 API 和高级 API 别混着调
用 Add 就一路 Add,用 Adds 不要又调 Add,混用容易出对齐问题。 - CPU 通过 ≠ NPU 通过
把浮点精度问题放到最后,先用整数数据验证功能。
六、总结
Add 算子值得亲手写一遍:它把异构计算、SPMD 并行、多级存储搬运、双缓冲流水全串起来了。后面写复杂算子,套路一模一样。
`
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)