- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
本篇文章以 CANN 神经网络算子库 ops-nn 中 aclnnSiluBackward 官方文档 为主体,结合仓库内算子定义、Host 侧 API 实现、AscendC Kernel 与 Tiling 源码,系统讲解如何通过 aclnnSiluBackwardGetWorkspaceSize / aclnnSiluBackward 两段式接口在 Atlas A2 系列产品上完成 SiLU(Swish)激活函数反向传播的梯度计算。读完本文,你将掌握该算子的数学原理、参数约束、错误码语义、完整可编译的调用示例,以及从 aclnn API 到 AI Core Kernel 的底层实现链路。
产品支持情况
aclnnSiluBackward 当前支持的产品如下:
| 产品 | 是否支持 |
|---|---|
| Atlas A2 训练系列产品 / Atlas 800I A2 推理产品 | √ |
功能说明与计算公式
aclnnSiluBackward 是 aclnnSilu 激活函数(即 SiLU,也称 Swish,β=1)的反向传播接口,用于计算 SiLU 激活函数的梯度。它在网络的反向阶段根据正向输入x与上游传回的梯度gradOutput计算输入侧的梯度gradInput,是训练 SiLU 激活层必不可少的算子。
- SiLU 函数公式
$$ y = x \cdot \sigma(x) $$
- SiLU 函数的导数
$$ \sigma(x) = \frac{1}{1 + e^{-x}} $$
$$ y' = \sigma(x) \cdot (1 + x \cdot (1 - \sigma(x))) $$
$$ gradInput = gradOutput \cdot \sigma(x) \cdot (1 + x \cdot (1 - \sigma(x))) $$
其中 $\sigma(x)$ 为 Sigmoid 函数,$y$ 为 SiLU 函数,$y'$ 为 SiLU 函数的导数,gradOutput为反向传播中上游传来的梯度,gradInput为算子的最终输出。
从仓库的 Kernel 实现(experimental/activation/silu_grad/op_kernel/silu_grad.h)可以看到,该公式在 Vector 单元上被拆解为如下指令序列(float 路径):
// SiLU 梯度公式: dx = dy * sigmoid(x) * (1 + x * (1 - sigmoid(x))) Muls(tmp1, xLocal, -1.0f, processDataNum); // tmp1 = -x Exp(tmp1, tmp1, processDataNum); // tmp1 = e^(-x) Adds(tmp1, tmp1, 1.0f, processDataNum); // tmp1 = 1 + e^(-x) Duplicate(dxLocal, 1.0f, processDataNum); // dx = 1 Div(tmp2, dxLocal, tmp1, processDataNum); // tmp2 = sigmoid(x) Sub(tmp1, dxLocal, tmp2, processDataNum); // tmp1 = 1 - sigmoid(x) Mul(tmp1, xLocal, tmp1, processDataNum); // tmp1 = x * (1 - sigmoid(x)) Adds(tmp1, tmp1, 1.0f, processDataNum); // tmp1 = 1 + x * (1 - sigmoid(x)) Mul(tmp1, tmp2, tmp1, processDataNum); // tmp1 = sigmoid(x) * (1 + x * (1 - sigmoid(x))) Mul(dxLocal, dyLocal, tmp1, processDataNum); // dx = dy * tmp1对于 FLOAT16 / BF16 输入,Kernel 会先将数据Cast到 float32 计算,最后再以CAST_RINT舍入模式转回原类型输出,以保证中间过程精度(见 silu_grad.h)。
两段式接口与函数原型
与 CANN 其他 aclnn 算子一致,aclnnSiluBackward 采用两段式接口:
- 先调用
aclnnSiluBackwardGetWorkspaceSize:完成入参校验、构图与算子执行器创建,并返回计算所需的 workspace 大小; - 再调用
aclnnSiluBackward:传入 workspace 与执行器,在指定 Stream 上异步执行计算。
第一段接口原型:
aclnnStatus aclnnSiluBackwardGetWorkspaceSize( const aclTensor* gradOutput, const aclTensor* self, aclTensor* gradInput, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口原型:
aclnnStatus aclnnSiluBackward( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)从 Host 侧实现(experimental/activation/silu_grad/op_host/op_api/aclnn_silu_backward.cpp)可以还原第一段接口的完整内部流程:
- 对
workspaceSize与executor做公共入参检查,创建 OpExecutor; - 依次执行空指针、数据类型、shape 三组参数校验(
CheckParams); - 若
gradOutput或self为空 Tensor,直接返回workspaceSize = 0并快速退出; - 通过
l0op::Contiguous将非连续输入转换为连续张量; - 调用底层
l0op::SiluGrad构建 SiluGrad 算子计算流程; - 通过
l0op::ViewCopy将计算结果回拷到输出gradInput(兼容其非连续场景); - 通过
executor->GetWorkspaceSize()获取 workspace 大小并转移执行器。
第二段接口则统一调用CommonOpExecutorRun完成实际计算(aclnn_silu_backward.cpp)。
aclnnSiluBackwardGetWorkspaceSize 参数说明
第一段接口参数如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| gradOutput | 输入 | 表示 SiLU 激活函数正向输出的梯度,公式中的 gradOutput | 支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| self | 输入 | 表示用于计算激活函数的张量,公式中的 x | 支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| gradInput | 输出 | backward 计算的输出,为 SiLU 正向输入的梯度值,即对输入求导后的结果,公式中的 gradInput | 支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者数据类型一致 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| workspaceSize | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
上述约束与源码中CheckDtypeValid、CheckShapeValid的逻辑一一对应(aclnn_silu_backward.cpp):
- 支持的数据类型列表在源码中定义为
DTYPE_SUPPORT_LIST = {DT_FLOAT16, DT_FLOAT, DT_BF16}; - 在支持 broadcast 的平台(
IsRegbase为真)上,允许gradOutput与self做广播后与gradInput对齐;在不支持广播的场景下严格要求三者 shape 一致; - 混合精度场景(gradOutput 与 self 类型不同)下,要求输出 gradInput 为 FLOAT;
- BF16 仅在 DAV_2201(Atlas A2 系列)等架构上受支持,源码通过
CheckSocVersionIsSupportBf16做了平台级校验。
返回值与错误码
返回aclnnStatus状态码,具体参见 aclnn 返回码。第一段接口会完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 gradOutput、self 或 gradInput 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 或 gradInput 的数据类型不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 和 gradInput 的数据类型不同 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 和 gradInput 的 shape 不同 |
aclnnSiluBackward 参数说明
第二段接口参数如下:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnSiluBackwardGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
返回值为aclnnStatus状态码,具体参见 aclnn 返回码。
约束说明
无额外约束。实际限制均已通过第一段接口的参数校验与平台能力校验体现(数据类型、shape 对齐、维度上限 8、BF16 平台支持等)。
调用示例
下面为完整调用示例,具体编译和执行过程请参考编译与运行样例。仓库中还提供了可直接参考的工程化样例 test_aclnn_silu_grad.cpp 与对应单元测试 tests/ut/op_api/test_aclnn_silu_grad.cpp。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_silu_backward.h" #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); return 0; } template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> gradOutputShape = {2, 3}; std::vector<int64_t> selfShape = {2, 3}; std::vector<int64_t> gradInputShape = {2, 3}; void* gradOutputDeviceAddr = nullptr; void* selfDeviceAddr = nullptr; void* gradInputDeviceAddr = nullptr; aclTensor* gradOutput = nullptr; aclTensor* self = nullptr; aclTensor* gradInput = nullptr; std::vector<float> gradOutputHostData = {1, 1, 1, 1, 1, 1}; std::vector<float> selfHostData = {1, 2, 3, 4, 5, 6}; std::vector<float> gradInputHostData = {0, 0, 0, 0, 0, 0}; // 创建gradOutput aclTensor ret = CreateAclTensor(gradOutputHostData, gradOutputShape, &gradOutputDeviceAddr, aclDataType::ACL_FLOAT, &gradOutput); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建gradInput aclTensor ret = CreateAclTensor(gradInputHostData, gradInputShape, &gradInputDeviceAddr, aclDataType::ACL_FLOAT, &gradInput); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnSiluBackward第一段接口 ret = aclnnSiluBackwardGetWorkspaceSize(gradOutput, self, gradInput, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSiluBackwardGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } // 调用aclnnSiluBackward第二段接口 ret = aclnnSiluBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnSiluBackward failed. ERROR: %d\n", ret); return ret); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(gradInputShape); std::vector<float> outData(size, 0); ret = aclrtMemcpy(outData.data(), outData.size() * sizeof(outData[0]), gradInputDeviceAddr, size * sizeof(outData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("out result[%ld] is: %f\n", i, outData[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(gradOutput); aclDestroyTensor(self); aclDestroyTensor(gradInput); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(gradOutputDeviceAddr); aclrtFree(selfDeviceAddr); aclrtFree(gradInputDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }以示例数据推算:当self = {1, 2, 3, 4, 5, 6}、gradOutput全为 1 时,由公式dx = dy · σ(x) · (1 + x·(1 - σ(x)))可得到各位置对应的梯度输出,可作为验证结果正确性的参考。
底层实现纵深:从 aclnn API 到 AI Core Kernel
1. 算子注册与 Shape 推导
SiluGrad 算子的 IR 定义位于 experimental/activation/silu_grad/op_host/silu_grad_def.cpp:输入dy、x与输出dx均为必选参数,支持 FLOAT16 / FLOAT / BF16 三种数据类型,数据格式为 ND,AI Core 计算配置为ascend910b(Atlas A2 系列对应的芯片架构)。
Shape 推导位于 experimental/activation/silu_grad/op_host/silu_grad_infershape.cpp:输出dx的 shape 直接继承输入dy的 shape,输出数据类型继承输入数据类型,保证反向传播中张量形状在图上无缝传递。
2. Tiling 策略:单缓冲与双缓冲
Host 侧 Tiling 逻辑位于 experimental/activation/silu_grad/op_host/silu_grad_tiling.cpp,其核心决策流程为:
- 获取平台信息:UB 内存大小与可用核数(
GetPlatformInfo); - 依据输入 shape 与数据类型(FLOAT 为 4 字节、其余为 2 字节)计算输入总字节数与 UB 块大小;
- 以“每核约 1024 个元素”为目标计算实际并行核数(
finalCoreNum),并对平台核数、数据块上限做三向取小,避免过度并行; - 通过
CalculateCoreBlockNums计算每个核处理的数据量,区分smallCoreDataNum、bigCoreDataNum与tailBlockNum,实现核间负载均衡; - 当单核 tile 数不超过 2 时选择单缓冲模式(
schMode = 0,增大 tile 消除尾部开销),否则选择双缓冲流水线模式(schMode = 1),并通过 TilingKey 下发。
对应的 Tiling 数据结构定义于 silu_grad_tiling_data.h,包含smallCoreDataNum、bigCoreDataNum、tileDataNum、tailBlockNum四个字段;调度模式常量定义于 silu_grad_tiling_key.h。
3. AI Core Kernel 执行
Kernel 主体位于 experimental/activation/silu_grad/op_kernel/silu_grad.cpp 与 silu_grad.h:
- 每个 AI Core 根据
coreId与tailBlockNum计算自身负责的全局数据偏移,分别通过bigCoreDataNum或smallCoreDataNum定位dy、x、dx三段 Global Memory 缓冲; CopyIn使用DataCopyPad将 dy 与 x 拷入 UB,CopyOut将结果写回;- 单缓冲模式(
ProcessSingleBuffer)对单个 tile 串行执行 CopyIn → Compute → CopyOut;双缓冲模式(ProcessDoubleBuffer)通过预加载首 tile、主循环重叠“下一 tile 的 CopyIn 与当前 tile 的 Compute/CopyOut”实现 DMA 与 Vector 单元并行,末段排空流水线; - 计算统一以 float32 进行,FP16/BF16 输入输出通过
Cast完成精度保底转换。
4. L0 层 API 封装
Host 侧 L0 封装位于 experimental/activation/silu_grad/op_host/op_api/silu_grad.cpp:l0op::SiluGrad先对两个输入做 broadcast shape 推导,混合精度下输出类型取 FLOAT,随后调用SiluGradAiCore通过ADD_TO_LAUNCHER_LIST_AICORE将算子加入 AI Core 启动列表,最终返回输出张量,供第一段接口构图与 ViewCopy 使用。
总结
aclnnSiluBackward 是 CANN ops-nn 仓库中 SiLU(Swish)激活函数反向传播的标准化 aclnn 接口,覆盖 Atlas A2 训练系列与 Atlas 800I A2 推理产品,支持 FLOAT16 / FLOAT / BF16 三种数据类型、ND 格式、0-8 维 shape 以及非连续 Tensor。通过两段式接口调用,配合源码级的参数校验、broadcast 推断、Tiling 调度(单/双缓冲)与 float32 中间精度计算,可安全高效地完成神经网络训练中 SiLU 层的梯度回传。算子整体实现、测试与调用样例可在 experimental/activation/silu_grad 目录下完整查阅。
- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
相关推荐
CANN ops-nn aclnnSiluBackward 接口详解:Silu 反向传播算子原理、两段式调用与 NPU 源码实现
CANN ops nn aclnnSiluBackward 接口详解:Silu 反向传播算子原理、两段式调用与 NPU 源码实现 本篇技术指南围绕 CANN 神
人工智能算子库深度学习CANNAscendCANN ops-nn 算子解析:aclnnSeluBackward 两段式接口实现 SELU 反向梯度计算
CANN ops nn 算子解析:aclnnSeluBackward 两段式接口实现 SELU 反向梯度计算 aclnnSeluBackward 是 CANN
人工智能算子库深度学习CANNAscendCANN ops-nn 算子详解:aclnnSoftmaxGrad 两段式接口实现 Softmax 反向梯度计算
CANN ops nn 算子详解:aclnnSoftmaxGrad 两段式接口实现 Softmax 反向梯度计算 Softmax 反向传播是神经网络训练中最常见
人工智能算子库深度学习CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考