CANN ops-nn 中 SwishGrad 算子解析:原理、实现与 aclnnSwishBackward 调用指南
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
导读
SwishGrad 是 CANN 神经网络算子库(ops-nn)中用于计算 Swish 激活函数梯度的反向算子,实现在仓库的 experimental/activation/swish_grad 目录下,面向 Atlas A2 训练系列产品/Atlas 800I A2 推理产品提供 fp16、fp32、bf16 三种精度的梯度计算能力。本文以 swish_grad/README.md 为核心骨架,结合仓库中算子定义、Host 侧 tiling、Kernel 侧昇腾 AI Core 实现与单元测试,完整讲解 SwishGrad 的数学原理、参数约束、两段式 aclnn 接口调用流程与源码级实现机制,帮助开发者快速上手在 NPU 上完成 Swish 反向传播的接入与调试。
一、算子功能与数学原理
1.1 功能定位
SwishGrad 算子是 Swish 激活函数(activation/swish)的反向传播算子,求 Swish 函数的梯度。它接收反向传播传入的梯度grad、正向输入x以及可调节参数scale,输出对输入x的梯度grad_x。
从算子库的接口关系来看,上层对外暴露的 API 名为aclnnSwishBackward,其功能说明文档 docs/aclnnSwishBackward.md 明确指出它是 aclnnSwish(仓库实际路径为 activation/swish/docs/aclnnSwish.md)激活函数的反向传播,用于计算 Swish 激活函数的梯度。这与 torch 生态中的swish_backward语义一一对应。
1.2 计算公式
根据 swish_grad/README.md 的"功能说明",SwishGrad 的计算公式如下:
$$ y = sigmoid(scalex) + xsigmoid(scale*x) $$
$$ sigmoid = sigmoid*(1 - sigmoid) $$
其中scale为可调节的斜率参数。更精确的推导形式见 docs/aclnnSwishBackward.md:
Swish 正向函数:
$$ s(x) = x*\sigma(\beta x) $$
其导数:
$$ s^\prime(x)= \beta s(x)+\sigma(\beta x)(1-\beta s(x))= \sigma(\beta x)*(1+\beta x(1-\sigma(\beta x))) $$
最终输出梯度:
$$ gradInput = gradOutput * s^\prime(x) $$
其中 Sigmoid 函数定义为:
$$ \sigma(x) = {\frac{1} {1+{e}^{-x}}} $$
可见 README 中的两条公式本质是"先算 sigmoid 值,再算 1 - sigmoid(即导数因子),最后与梯度逐元素相乘"这一计算链的简写。对照 op_kernel/swish_grad.h 的Compute实现,可以逐行印证该公式:
σ(βx) = 1 / (1 + exp(-βx)) σ'(βx) = σ(βx) * (1 - σ(βx)) s'(x) = σ(βx) + βx * σ'(βx) // 即 σ(βx) * (1 + βx(1 - σ(βx))) grad_x = grad * s'(x)1.3 参数说明
README 中对算子原语的参数定义如下:
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| grad | 输入 | 待进行 SwishGrad 计算的入参,公式中的 grad | fp16、fp32、bf16 | ND, FRACTAL_NZ, NC1HWC0 |
| x | 输入 | 待进行 SwishGrad 计算的入参,公式中的 x | fp16、fp32、bf16 | ND, FRACTAL_NZ, NC1HWC0 |
| scale | 输入 | 待进行 SwishGrad 计算的入参,公式中的 scale | fp32 | 1(标量) |
| y | 输入 | 待进行 SwishGrad 计算的入参,暂时不参与运算 | fp16、fp32、bf16 | ND, FRACTAL_NZ, NC1HWC0 |
| grad_x | 输出 | 待进行 SwishGrad 计算的出参,公式中的输出 | fp16、fp32、bf16 | ND, FRACTAL_NZ, NC1HWC0 |
约束说明:无。
需要指出两点值得注意的细节:
- 算子定义中的
y输入"暂时不参与运算"。这在 op_kernel/swish_grad.cpp 的 kernel 入口中可以看到:swish_grad内核函数虽然接收GM_ADDR y,但 op_kernel/swish_grad.h 的Init只为grad、x、grad_x建立了 GlobalBuffer,y并未被读取,属于为后续版本预留的入参。 - 上述表格描述的是底层算子的参数形态;对上层用户而言,真正接触的是
aclnnSwishBackward接口的参数(见第三节)。
二、算子源码结构
SwishGrad 在仓库中采用标准的 CANN 算子工程布局:
experimental/activation/swish_grad/ ├── docs/aclnnSwishBackward.md # aclnn 接口使用文档 ├── examples/test_aclnn_swish_grad.cpp # 可运行调用样例 ├── op_host/ │ ├── op_api/ # l0 算子接口封装(l0op::SwishGrad) │ │ ├── aclnn_swish_backward.cpp/h # 对外 aclnn 两段式接口 │ │ └── swish_grad.cpp/h # l0 算子封装与 launcher │ ├── swish_grad_def.cpp # 算子原语注册(OpDef) │ ├── swish_grad_infershape.cpp # 输出 shape 推导 │ └── swish_grad_tiling.cpp # Host 侧 tiling 计算 ├── op_kernel/ │ ├── swish_grad.cpp # AI Core 内核入口(__global__ __aicore__) │ ├── swish_grad.h # Kernel 类实现(双缓冲流水) │ ├── swish_grad_tiling_data.h # tiling 参数结构体 │ └── swish_grad_tiling_key.h # tiling key └── tests/ut/ # op_api / op_host / op_kernel 三级单测整个调用链为:aclnnSwishBackwardGetWorkspaceSize→l0op::SwishGrad(Host 侧拼装算子)→ADD_TO_LAUNCHER_LIST_AICORE下发 → AI Core 内核swish_grad执行。下面逐层展开。
2.1 算子定义注册(op_def)
swish_grad_def.cpp 中通过OP_TYPE_REGISTER/OP_ADD注册名为SwishGrad的算子:
- 四个数据对象:输入
grad、x、y,输出grad_x,数据类型支持DT_FLOAT16、DT_FLOAT、DT_BF16,格式支持ND、NC1HWC0、FRACTAL_NZ三种; - 属性
sconf:AttrType(OPTIONAL).Float(1.0),即可选的 float 型斜率参数,默认值为 1.0,对应公式中的scale/β; - AICore 配置:
DynamicCompileStaticFlag(true)、DynamicRankSupportFlag(true)、DynamicShapeSupportFlag(true)、PrecisionReduceFlag(true),注册到ascend910bSoC(对应 Atlas A2 系列),并通过ExtendCfgInfo("opFile.value", "swish_grad")指定 kernel 入口文件名为swish_grad.cpp。
2.2 输出 shape 推导(infershape)
swish_grad_infershape.cpp 的实现非常直接:读取输入x的 shape,并将输出grad_x的 shape 赋值为与之完全相同(*yShape = *xShape),即逐元素反向算子,输出与输入同 shape、同 dtype。这与 README 中"grad、x 与 grad_x 的 shape/数据类型一致"的约束一致。
2.3 Host 侧 tiling(swish_grad_tiling.cpp)
swish_grad_tiling.cpp 负责在 Host 侧根据输入规模与硬件资源计算切分方案:
GetPlatformInfo:通过platform_ascendc::PlatformAscendC获取 UB(Unified Buffer)大小、AI Core 核数以及块对齐粒度(GetUbBlockSize);GetWorkspaceSize:查询系统库 API 所需 workspace 大小并回填给框架;GetShapeAttrsInfo:根据输入存储 shape 与数据类型字节数计算总数据量,结合 UB 容量与双缓冲需求(BUFFER_NUM = 2)确定每个核负责的数据量(bigCoreDataNum/smallCoreDataNum)、tile 内数据量(tileDataNum)以及尾部数据(tailDataNum)。
最终产出的 tiling 数据结构定义在 swish_grad_tiling_data.h:
struct SwishGradTilingData { uint64_t smallCoreDataNum; uint64_t bigCoreDataNum; uint64_t finalBigTileNum; uint64_t finalSmallTileNum; uint64_t tileDataNum; uint64_t smallTailDataNum; uint64_t bigTailDataNum; uint64_t tailBlockNum; float sconf; };其核心思想是多核负载均衡 + 尾部数据处理:把总数据量按核数均分,若不能整除,则前tailBlockNum个核多承担bigCoreDataNum个数据,其余核承担smallCoreDataNum个数据,每个核内部再按tileDataNum切成多个 tile 循环处理,最后的余量作为尾 tile 单独处理。
2.4 AI Core 内核实现(op_kernel)
Kernel 入口 swish_grad.cpp 通过REGISTER_TILING_DEFAULT读取 tiling 数据,实例化NsSwishGrad::KernelSwishGrad<DTYPE_X>并调用Init与Process。
核心实现位于 swish_grad.h,其设计要点如下:
(1)双缓冲流水线:TPipe上建立了 VECIN 队列inQueueGrad、inQueueX、inQueueY与 VECOUT 队列outQueueGrad,缓冲区个数均为BUFFER_NUM = 2,实现 CopyIn → Compute → CopyOut 三阶段重叠执行。
(2)fp16/bf16 与 fp32 两条计算路径:
- 对
float16_t/bfloat16_t:先把x通过Cast提升到 float 计算(使用临时缓冲区tmpQueue0/1/2),经历Muls(乘 β)→Muls(-1.0)→Exp→Adds(1.0)→Div(得到 σ(βx))→Sub(得到 1-σ(βx))→Mul(得到 βx·σ'(βx))→Add(1.0)(得到 1+βx(1-σ(βx)))→Mul与 σ(βx) 相乘 → 再与 grad 相乘,最后以CAST_RINT舍入模式转回半精度输出; - 对
float:直接在浮点路径上完成同样序列的运算,全程使用 float 精度,输出不降精度。
(3)多核数据切片:Init中根据coreId与tailBlockNum的关系,选择大核/小核的数据区间与 GlobalBuffer 偏移,每个核只处理自己负责的连续数据段。
内核从sconf(即 scale/β)读取斜率参数,其值由 Host 侧在aclnnSwishBackwardGetWorkspaceSize中从betaOptional标量转换而来(缺省为 1.0f)。
2.5 测试用例佐证
仓库为算子提供了三级单元测试,可用于验证实现与本文结论:
- op_api 层:tests/ut/op_api/test_aclnn_swish_backward.cpp,覆盖 float32、float16、bf16 等场景,构造
{2,5}的 gradOutput、self(值域 [-1,1])与 beta(如 1.1f、0.01f),断言aclnnSwishBackwardGetWorkspaceSize返回ACLNN_SUCCESS并设置了精度容差; - op_host 层:tests/ut/op_host/test_swish_grad_tiling.cpp,验证 tiling 计算产物(各核数据量、tile 数等)符合预期;
- op_kernel 层:tests/ut/op_kernel/test_swish_grad.cpp,直接对 AI Core 内核做数值验证。
三、aclnnSwishBackward 接口详解
3.1 产品支持情况
| 产品 | 是否支持 |
|---|---|
| Atlas A2 训练系列产品/Atlas 800I A2 推理产品 | √ |
对应到 SoC 层面,算子原语注册于ascend910b平台(见 swish_grad_def.cpp),且 aclnn_swish_backward.cpp 中的CheckSocVersionIsSupportBf16显示 bf16 支持范围限定在ASCEND910B至ASCEND910E之间的 SoC 版本。
3.2 两段式接口模型
与 CANN 其他 aclnn 算子一致,aclnnSwishBackward采用两段式接口(详见 docs/zh/context/two_phase_api.md):先调用GetWorkspaceSize接口完成入参校验、算子拼装并获取所需 workspace 大小与执行器,再调用执行接口真正下发计算。
第一段接口原型:
aclnnStatus aclnnSwishBackwardGetWorkspaceSize( const aclTensor* gradOutput, // 正向输出梯度,公式中的 gradOutput const aclTensor* self, // Swish 激活函数输入,公式中的 x const aclScalar* betaOptional, // 可调节参数 β(标量),空指针时按 1.0 计算 aclTensor* gradInput, // 输出:对输入的梯度 uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口原型:
aclnnStatus aclnnSwishBackward( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)3.3 第一段接口参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续 Tensor |
|---|---|---|---|---|---|---|---|
| gradOutput | 输入 | Swish 激活函数正向输出的梯度,公式中的 gradOutput | 支持空 Tensor;gradOutput、self 与 gradInput 的 shape 一致;三者的数据类型一致 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| self | 输入 | 用于计算激活函数的张量,公式中的 x | 支持空 Tensor;同上 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| betaOptional | 输入 | 可调节参数,控制 Swish 函数的形状和斜率,公式中的 β | 数据类型需可转换为 FLOAT(参见 docs/zh/context/deduction_relationship.md 的互推导关系);空指针时以 1.0 计算 | - | - | - | - |
| gradInput | 输出 | backward 计算的输出,Swish 正向输入的梯度值 | 支持空 Tensor;shape 与数据类型同前两者 | BFLOAT16、FLOAT16、FLOAT | ND | 0-8 | √ |
| workspaceSize | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor | 输出 | 返回 op 执行器,包含算子计算流程 | - | - | - | - | - |
返回值(aclnnStatus):具体返回码参见 docs/zh/context/aclnn_return_code.md。第一段接口会完成入参校验,以下场景会报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 gradOutput、self 或 gradInput 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self、betaOptional 或 gradInput 的数据类型不在支持范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 和 gradInput 的数据类型不同 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput、self 和 gradInput 的 shape 不同 |
3.4 第二段接口参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口aclnnSwishBackwardGetWorkspaceSize获取 |
| executor | 输入 | op 执行器,包含算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
返回值同样为aclnnStatus。约束说明:无。
3.5 接口内部实现要点
aclnn_swish_backward.cpp 的实现清晰展示了接口层的处理流程,可作为阅读模板:
- 参数校验:
CheckParams依次执行空指针检查(CheckNotNull)、数据类型检查(CheckDtypeValid,支持列表为 FLOAT16/FLOAT/BF16,且校验 betaOptional 可转 FLOAT、三个张量 dtype 一致、bf16 受 SoC 版本约束)与 shape 检查(CheckShapeValid,维度上限为MAX_SUPPORT_DIMS_NUMS,三个张量 shape 一致); - 空 Tensor 短路:当
gradOutput或self为空时,workspaceSize直接置 0 返回,不执行计算; - 连续性处理:对非连续输入调用
l0op::Contiguous转为连续 Tensor(对应参数表中"非连续 Tensor √"的支持能力); - 高维 reshape:当输入维度超过 8 时,将输入展平为 1 维长 Tensor 交给 kernel,计算完后再
Reshape回原维度; - β 转换:
betaOptional通过ToFloat()转为 float,空指针时取默认值1.0f; - 下发与收尾:调用
l0op::SwishGrad完成算子拼装,通过l0op::ViewCopy将结果拷贝到可能非连续的输出gradInput上,最终以GetWorkspaceSize()汇总整体 workspace 需求;第二段接口则直接调用框架通用执行函数CommonOpExecutorRun完成异步下发。
四、完整调用示例与运行指引
4.1 可运行样例
仓库提供了可直接参考的完整样例 examples/test_aclnn_swish_grad.cpp,编译与运行方法可参照 docs/zh/context/compile_and_run_sample.md。样例的核心流程如下(为便于阅读做了精简,完整代码以仓库文件为准):
#include "acl/acl.h" #include "aclnn_swish_backward.h" int main() { // 1. 固定写法:device/stream 初始化 int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); // aclInit + aclrtSetDevice + aclrtCreateStream // 2. 构造输入与输出(示例 shape 均为 {2, 3}) std::vector<int64_t> gradOutputShape = {2, 3}; std::vector<int64_t> selfShape = {2, 3}; std::vector<int64_t> gradInputShape = {2, 3}; std::vector<float> gradOutHostData = {1, 1, 1, 1, 1, 1}; std::vector<float> selfHostData = {1, 2, 3, 4, 5, 6}; float betaValue = 1.1f; // 用 aclrtMalloc/aclrtMemcpy 把 host 数据搬到 device,并 aclCreateTensor 创建 aclTensor aclTensor* gradOut = CreateAclTensor(gradOutHostData, gradOutputShape, ..., ACL_FLOAT, ...); aclTensor* self = CreateAclTensor(selfHostData, selfShape, ..., ACL_FLOAT, ...); aclTensor* gradInput = CreateAclTensor(/* 输出占位 */, gradInputShape, ..., ACL_FLOAT, ...); aclScalar* betaOptional = aclCreateScalar(&betaValue, aclDataType::ACL_FLOAT); // 3. 两段式调用 uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnSwishBackwardGetWorkspaceSize(gradOut, self, betaOptional, gradInput, &workspaceSize, &executor); void* workspaceAddr = nullptr; if (workspaceSize > 0) { aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } ret = aclnnSwishBackward(workspaceAddr, workspaceSize, executor, stream); // 4. 同步等待执行结束 aclrtSynchronizeStream(stream); // 5. 将 device 侧结果拷回 host 并打印(本例输出为 gradInput = grad * s'(x)) std::vector<float> outData(6, 0); aclrtMemcpy(outData.data(), ..., gradInputDeviceAddr, ..., ACL_MEMCPY_DEVICE_TO_HOST); // 6-7. 释放 aclTensor / aclScalar / device 内存 / stream,并 aclFinalize return 0; }样例中gradOutput = {1,1,1,1,1,1}、self = {1,2,3,4,5,6}、beta = 1.1,执行后打印的 6 个输出值即为对应位置的grad * s'(x)数值,可直接对照第一节的公式手工验算。
4.2 调用步骤要点
- 初始化:
aclInit(nullptr)→aclrtSetDevice(deviceId)→aclrtCreateStream(&stream); - 构造 Tensor:shape/stride 一致、数据类型为 FLOAT16/FLOAT/BF16、格式为 ND;支持非连续 Tensor(接口内部会自动
Contiguous);支持空 Tensor; - 两段式调用:第一段拿到
workspaceSize与executor,若workspaceSize > 0则在 device 侧申请内存,然后调用第二段aclnnSwishBackward; - 同步与取数:
aclrtSynchronizeStream后通过aclrtMemcpy(DEVICE_TO_HOST)读取输出; - 资源释放:依次释放
aclTensor/aclScalar、device 内存、workspace、stream,最后aclrtResetDevice+aclFinalize。
4.3 注意事项
- 三个张量(gradOutput、self、gradInput)的shape 与数据类型必须完全一致,否则第一段接口返回
ACLNN_ERR_PARAM_INVALID(161002); betaOptional传nullptr时按β = 1.0计算,此时算子退化为 PyTorch 中默认swish的梯度形式;传入时其数据类型需可互推导为 FLOAT;- bf16 输入仅在支持 bf16 的 SoC 版本(
ASCEND910B~ASCEND910E)上可用; - 该算子底层为逐元素计算,输出与输入同 shape;
y入参为预留项,当前版本不参与运算。
五、总结
SwishGrad 算子是 ops-nn 仓库中一个典型的逐元素反向算子样例,完整展示了 CANN 算子的标准五件套:OpDef 注册(swish_grad_def.cpp)、InferShape(swish_grad_infershape.cpp)、Host 侧 tiling(swish_grad_tiling.cpp)、AI Core 内核(op_kernel/swish_grad.h)与 aclnn 两段式对外接口(aclnn_swish_backward.cpp)。理解其数学推导(σ(βx)·(1+βx(1-σ(βx))))、多核均衡 tiling 策略、半精度转浮点计算的精度处理,以及两段式接口的调用范式,即可举一反三地接入 ops-nn 中其他同构的激活函数反向算子(如 SiluGrad)。实际开发中若需验证算子行为,可参考仓库 tests/ut 下的 op_api、op_host、op_kernel 三级测试用例,或直接编译运行 examples/test_aclnn_swish_grad.cpp 样例。
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考