ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CANN ops-nn 中 LeakyReluGrad 算子详解:LeakyReLU 反向的数学定义、aclnn 调用与源码实现

2026/9/18 22:39:37 拓冰建站 浏览量
CANN ops-nn 中 LeakyReluGrad 算子详解:LeakyReLU 反向的数学定义、aclnn 调用与源码实现 CANN ops-nn 中 LeakyReluGrad 算子详解LeakyReLU 反向的数学定义、aclnn 调用与源码实现【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本文围绕 CANN ops-nn 仓库中 LeakyReluGrad 算子文档 展开完整继承文档中的产品支持矩阵、计算公式、参数表与错误码并结合 aclnnLeakyReluBackward 接口文档、官方调用样例 以及 op_api、op_host、op_kernel 三层源码讲解该算子的两段式接口调用方式、参数校验逻辑与内核实现细节。读完本文你能够独立完成 LeakyReLU 激活反向的 NPU 调用、排查常见返回码并理解其广播推导、类型提升与 DAG 计算图的底层机制。一、算子概述LeakyReLU 反向的数学定义LeakyReluGrad 是 aclnnLeakyRelu 前向激活接口 的反向算子用于深度学习训练中将上游梯度gradOutput按 LeakyReLU 的导数规则回传到self张量。其计算公式为$$ output \begin{cases} gradOutput, \text{if } self 0 \ gradOutput \times negativeSlope, \text{if } self \le 0 \end{cases} $$语义上self表示前向阶段参与判断符号的张量当self 0时梯度原样通过当self 0时梯度被缩放negativeSlope倍。negativeSlope与正向 LeakyReLU 中的斜率属性保持一致因此反向计算才能与正向激活严格互逆。产品支持情况根据 README 的定义各产品支持情况如下产品是否支持Ascend 950PR / Ascend 950DT√Atlas A3 训练系列 / Atlas A3 推理系列产品√Atlas A2 训练系列 / Atlas A2 推理系列产品√Atlas 200I / 500 A2 推理产品×Atlas 推理系列产品√Atlas 训练系列产品√需要注意Atlas 200I/500 A2 推理产品不支持该算子这是使用前的第一道筛选条件。二、参数说明以下参数表完整继承自算子文档并补充了接口文档中的维度、非连续 Tensor 支持与使用说明参数名输入/输出/属性描述数据类型数据格式维度非连续 TensorgradOutput输入表示梯度。支持空 Tensor数据类型与 self 需满足数据类型互推导关系shape 需与 self 满足 broadcast 关系FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持self输入表示输入张量前向输出或前向输入。支持空 Tensor推导与广播约束同上FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持negativeSlope输入aclScalar表示 self 0 时的斜率FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT8、BOOL、INT16、UINT8、BFLOAT16---selfIsResult输入bool标记 self 是否为前向 LeakyReLU 的输出张量为 true 时 negativeSlope 不可以是负数----out输出表示计算输出。数据类型需是 gradOutput 与 self 推导之后可转换的数据类型其他数据类型通过自动 cast 支持但会额外申请空间FLOAT、FLOAT16、BFLOAT16、DOUBLEND0–8支持workspaceSize输出第一段接口返回需要在 Device 侧申请的 workspace 大小----executor输出第一段接口返回 op 执行器包含算子计算流程----产品差异说明Atlas 推理系列、Atlas 训练系列上gradOutput、self 和 out 的数据类型支持 FLOAT、FLOAT16、DOUBLE无 BFLOAT16negativeSlope 支持 FLOAT、FLOAT16、DOUBLE、INT32、INT64、INT8、BOOL、INT16、UINT8。这一点与源码中的按架构区分数据类型列表相互印证见下文第四节。selfIsResult 语义解析selfIsResult用于区分两种典型场景——self 是前向 LeakyReLU 的输出in-place 反向场景还是前向的原始输入。文档约定当selfIsResult为 true 时negativeSlope不可以是负数违反该约定时第一段接口会报ACLNN_ERR_PARAM_INVALID错误码 161002。文档「约束说明」章节明确本算子无额外约束确定性计算方面aclnnLeakyReluBackward默认采用确定性实现。三、两段式接口与函数原型与 CANN 其他 aclnn 接口一致本算子采用两段式调用先调用aclnnLeakyReluBackwardGetWorkspaceSize获取 workspace 大小与执行器再调用aclnnLeakyReluBackward在指定 stream 上执行。aclnnStatus aclnnLeakyReluBackwardGetWorkspaceSize( const aclTensor *gradOutput, const aclTensor *self, const aclScalar *negativeSlope, bool selfIsResult, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnLeakyReluBackward( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)第二段接口的入参workspaceDevice 侧申请的 workspace 内存地址、workspaceSize由第一段接口返回的大小、executorop 执行器、stream指定执行任务的 Stream。第一段接口的错误码第一段接口会完成入参校验出现以下场景时报错返回值类型均为aclnnStatus返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、negativeSlope 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002gradOutput、self、negativeSlope 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002gradOutput、self 或 out 的 shape 超过 8 维ACLNN_ERR_PARAM_INVALID161002gradOutput、self 的 shape 不满足广播关系或 out 的 shape 与广播结果不一致ACLNN_ERR_PARAM_INVALID161002selfIsResult 为 true 时negativeSlope 是负数四、端到端调用示例官方样例 test_aclnn_leaky_relu_backward.cpp 展示了完整的两段式调用流程可整体复制后按需修改 deviceId 与张量数据编译运行。核心结构如下#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_leaky_relu_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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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初始化根据自己的实际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. 构造输入与输出 std::vectorint64_t gradShape {4}; std::vectorint64_t selfShape {4}; std::vectorint64_t outShape {4}; void* gradDeviceAddr nullptr; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* grad nullptr; aclTensor* self nullptr; aclScalar* negativeSlope nullptr; aclTensor* out nullptr; std::vectorfloat gradHostData {2, 3, 4, 5}; std::vectorfloat selfHostData {1, 2, 3, 4}; std::vectorfloat outHostData {0, 0, 0, 0}; float negativeSlopeValue 0.01f; // 本例 self 全为正值梯度将原样通过 bool selfIsResultValue true; // 创建grad aclTensor ret CreateAclTensor(gradHostData, gradShape, gradDeviceAddr, aclDataType::ACL_FLOAT, grad); 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); // 创建negativeSlope aclScalar negativeSlope aclCreateScalar(negativeSlopeValue, aclDataType::ACL_FLOAT); CHECK_RET(negativeSlope ! nullptr, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnLeakyReluBackward第一段接口 ret aclnnLeakyReluBackwardGetWorkspaceSize(grad, self, negativeSlope, selfIsResultValue, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLeakyReluBackwardGetWorkspaceSize 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); } // 调用aclnnLeakyReluBackward第二段接口 ret aclnnLeakyReluBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLeakyReluBackward 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侧 auto size GetShapeSize(outShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[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(result[%ld] is: %f\n, i, resultData[i]); } // 6. 释放aclTensor和aclScalar aclDestroyTensor(grad); aclDestroyTensor(self); aclDestroyScalar(negativeSlope); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(gradDeviceAddr); aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例中self全部取正值因此输出out应逐元素等于gradOutput{2, 3, 4, 5}若将selfHostData改为含负值负值位置的输出将变为gradOutput * 0.01可直接验证公式的两个分支。五、源码解读参数校验与数据类型策略aclnn 实现入口 中的CheckParams函数约 L131-L151按顺序执行五步校验与上文错误码表逐条对应空指针检查CheckNotNull依次检查 gradOutput、self、negativeSlope、out任一为空即返回 161001数据类型检查CheckDtypeValid约 L75-L84按当前 NPU 架构选择数据类型支持列表——DAV_2201或 Regbase 架构A2 系列的张量列表为 FLOAT/FLOAT16/DOUBLE/BFLOAT16其他架构910/310p仅 FLOAT/FLOAT16/DOUBLE标量列表额外包含 INT32、INT64、INT8、UINT8、BOOL、INT16Regbase 架构标量还多支持 BFLOAT16。这与文档「Atlas 推理/训练系列不支持 BFLOAT16 张量」的说明完全一致shape 检查CheckShape约 L86-L100校验 gradOutput/self 不超过MAX_DIM 8维通过OP_CHECK_BROADCAST_AND_INFER_SHAPE推导广播形状并要求 out 的 view shape 与广播结果一致selfIsResult 检查CheckIsResult约 L102-L113在selfIsResult true且negativeSlope 0时输出明确的错误日志提示这是以负斜率调用了 in-place 前向导致的应改用 out-of-place 版本类型推导检查CheckPromoteType约 L115-L129调用PromoteType校验 gradOutput 与 self 可互推导且推导结果可转换为 out 的数据类型。六、源码解读算子编排与执行流程理解aclnnLeakyReluBackwardGetWorkspaceSize约 L153-L208的内部流程可以看出两段式接口是如何把用户输入加工为可执行的算子链的空 Tensor 快速路径若self为空 Tensor直接置workspaceSize 0并返回成功输出也是空 Tensor连续化l0op::Contiguous分别把 gradOutput、self 转为连续 Tensor用户可传入非连续张量类型统一l0op::Cast将两路输入 cast 到PromoteType推导出的隐式数据类型核心计算l0op::LeakyReluGrad(gradOutputCast, selfCast, negativeSlope-ToFloat(), ...)调用真正的算子 kernel注意 negativeSlope 标量在此以 float 值注入输出类型转换l0op::Cast把计算结果 cast 回 out 声明的数据类型结果写回l0op::ViewCopy把结果拷贝到可能是非连续的 out 张量最终*workspaceSize uniqueExecutor-GetWorkspaceSize()汇总整条算子链的 workspace 需求执行器移交executor输出参数供第二段接口调用CommonOpExecutorRun在 stream 上执行。这一编排解释了文档中out 的其他数据类型通过自动 cast 支持但会额外申请空间的原因——cast 中间张量本身就要占用 workspace。七、算子定义、Shape 推导与 Tiling算子定义leaky_relu_grad_def.cppL18-L48声明了图模式下的算子签名——输入gradients、features输出backprops三者的数据类型均为 BF16/FLOAT16/FLOAT、格式均为 ND属性negative_slope为 OPTIONAL 的 Float默认值 0.0。AICore 配置针对ascend950与ascend350两个平台注册并开启动态 shape/rank 支持DynamicShapeSupportFlag(true)、DynamicRankSupportFlag(true)因此算子才能支撑 0–8 维的任意形状输入。Shape 推导leaky_relu_grad_infershape.cpp 仅一行核心逻辑——注册InferShape4Broadcast即输出 shape 由两路输入按广播规则推导与 aclnn 层CheckShape的验证规则保持一致。Tilingleaky_relu_grad_tiling.cpp 的DoOpTiling约 L37-L98揭示了图模式下的一条硬约束gradients、features、backprops三者的数据类型必须相同否则直接返回失败。随后根据输入类型实例化BroadcastBaseTilingFLOAT16/BF16/FLOAT 三个分支生成对应模板的 tilingKey并把negativeSlope以 float 标量形式写入 tiling 数据SetScalar。由于 aclnn 路径在进入 kernel 前已完成类型统一 cast因此该约束在 API 调用层面不会暴露给使用者。八、内核实现基于 DAG 的广播计算图Kernel 入口leaky_relu_grad_apt.cpp 定义了一个__aicore__模板函数任务类型为KERNEL_TYPE_AIV_ONLY仅 AI Vector 核执行内部实例化BroadcastSchschMode, OpDag调度器Process(gradients, features, backprops)一条语句完成全部数据搬运与计算。计算图定义leaky_relu_grad_dag.hL31-L47以数据流图精确刻画了公式的每个环节OpCopyInY/OpCopyInX把 gradients 与 features 分别按广播方式搬入向量寄存器CompareVec::Compareuint8_t, U, COMPARE_MODE_GT对 features 与 0 做大于比较产生掩码OpCopyInYCastMul将 gradients cast 到 float 后与Placeholder::Varfloat, 0即 tiling 中注入的 negativeSlope 标量相乘Select按掩码二选一——features 0 取原梯度否则取gradOutput * negativeSlopeSelectCast/OpCopyOutcast 回原始类型并写回输出。可见 kernel 与文档公式逐分支对应features 0分支直接取 gradientsfeatures 0分支乘 negativeSlope比较、选择、标量乘法全部在 AI Core 上以向量指令完成。九、测试与验证方式仓库为算子提供了多层测试可作为自研调用的验证参考op_api UTtest_leaky_relu_backward.cpp 覆盖 aclnn 接口的正常与异常路径Tiling UTtest_leaky_relu_grad_tiling.cpp 验证 tiling 生成逻辑Kernel 数据驱动测试test_leaky_relu_grad_apt.cpp 配合 gen_data.py 生成数据并可用 golden.py 生成参考结果对比ST 模糊测试ttk_kernel_leaky_relu_grad_st.csv 定义了不同数据类型、shape 与negative_slope取值的 fuzz 用例覆盖 FP32/FP16、多 shape、极端数值区间如 nan/inf容差 1e-08。十、相关目录与参考资料汇总内容相对路径算子 README产品支持、公式、参数、调用方式activation/leaky_relu_grad/README.mdaclnnLeakyReluBackward 接口文档原型、错误码、示例activation/leaky_relu_grad/docs/aclnnLeakyReluBackward.md官方调用样例activation/leaky_relu_grad/examples/test_aclnn_leaky_relu_backward.cppaclnn 参数校验与算子编排实现activation/leaky_relu_grad/op_api/aclnn_leaky_relu_backward.cpp算子定义输入/输出/属性activation/leaky_relu_grad/op_host/leaky_relu_grad_def.cppShape 广播推导注册activation/leaky_relu_grad/op_host/leaky_relu_grad_infershape.cppTiling 实现activation/leaky_relu_grad/op_host/arch35/leaky_relu_grad_tiling.cppKernel 入口activation/leaky_relu_grad/op_kernel/leaky_relu_grad_apt.cppDAG 计算图定义activation/leaky_relu_grad/op_kernel/arch35/leaky_relu_grad_dag.h前向算子 LeakyReLU 文档activation/leaky_relu/docs/aclnnLeakyReluaclnnInplaceLeakyRelu.md适用前提与限制小结本文所有描述以当前仓库代码与文档为准调用前请确认目标产品不在Atlas 200I/500 A2 推理产品之列张量维度不超过 8 维selfIsResult为 true 时保证negativeSlope非负Atlas 推理/训练系列上避免对张量输入使用 BFLOAT16。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考