尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CANN ops-nn 中 aclnnSoftplusBackward 算子接口详解:SoftplusV2Grad 反向传播的两段式调用实战

CANN ops-nn 中 aclnnSoftplusBackward 算子接口详解:SoftplusV2Grad 反向传播的两段式调用实战 CANN ops-nn 中 aclnnSoftplusBackward 算子接口详解SoftplusV2Grad 反向传播的两段式调用实战【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本篇技术指南聚焦 CANN 神经网络算子库ops-nn中aclnnSoftplusBackward反向传播算子接口系统讲解其数学原理、产品支持范围、两段式接口GetWorkspaceSize 执行的完整参数语义、错误码约束与可运行的调用示例并结合activation/softplus_v2_grad目录下的 op_api、op_hosttiling与 op_kernel 源码剖析该算子在 NPU 上的实现链路。读者读完可掌握在 Ascend 平台上编写、编译并运行 Softplus 反向梯度计算的完整方案。功能说明Softplus 前向算子的反向传播aclnnSoftplusBackward是 aclnnSoftplus 前向算子的反向传播接口。它根据前向传播的原始输入self与上游传入的梯度gradOutput计算对前向输入的梯度gradInput属于元素级element-wise反向算子。计算公式如下$$ gradInput gradOutput \cdot \begin{cases} \dfrac{1}{1e^{(-\beta \cdot self)}}, \beta \cdot self \le threshold \ 1, \beta \cdot self threshold \end{cases} $$其中self为前向传播输入gradOutput为上游梯度gradInput为输出梯度beta用于控制 softplus 曲线的陡峭程度beta越大softplus 越接近 ReLUthreshold为数值稳定性阈值。当 $\beta \cdot self threshold$ 时梯度直接退化为1.0即gradInput gradOutput避免对极大负数求exp造成溢出保证数值稳定性。从算子注册代码可以印证这一语义softplus_v2_grad_def.cpp 中定义了 2 个输入input_gradients、input_features、1 个输出output_backprops以及两个标量属性beta默认1.0f与threshold默认20.0f并通过OP_ADD(SoftplusV2Grad)完成算子注册。产品支持情况aclnnSoftplusBackward在当前仓库中登记的产品支持情况如下产品是否支持Ascend 950PR/Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品支持需要说明的是不同产品在数据类型支持上存在差异在 Atlas 训练系列产品上gradOutput、gradInput仅支持 FLOAT、FLOAT16、DOUBLE 三种类型详见接口文档而 aclnn_softplus_backward.cpp 中按 SoC 版本区分了ASCEND910_DTYPE_SUPPORT_LIST与ASCEND910B_DTYPE_SUPPORT_LIST后者额外支持 BFLOAT16说明 dtype 支持矩阵随平台演进。两段式接口与函数原型与 CANN 其他算子接口一致aclnnSoftplusBackward遵循两段式接口设计必须先调用aclnnSoftplusBackwardGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器executor再调用aclnnSoftplusBackward执行计算。aclnnStatus aclnnSoftplusBackwardGetWorkspaceSize( const aclTensor* gradOutput, const aclTensor* self, const aclScalar* beta, const aclScalar* threshold, aclTensor* gradInput, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnSoftplusBackward( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)从实现看第一段接口 aclnnSoftplusBackwardGetWorkspaceSize 内部完成了参数校验、输入连续性整理l0op::Contiguous、必要时 dtype 提升l0op::Cast到 FLOAT、构图l0op::SoftplusV2Grad并返回executor-GetWorkspaceSize()第二段接口则直接通过CommonOpExecutorRun提交执行。aclnnSoftplusBackwardGetWorkspaceSize 参数说明第一段接口的完整参数语义如下表参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutputaclTensor*输入输入的梯度。公式中的 gradOutput。支持空 Tensorshape 需与 self、gradInput 相同需与 self 满足 broadcast 关系且 Broadcast 后 shape 与 self 的 shape 相等。FLOAT、FLOAT16、DOUBLE、BFLOAT16ND0-8√selfaclTensor*输入输入数据。公式中的 self。支持空 Tensor为 softplus 的正向输入值shape 需与 gradOutput、gradInput 相同。INT8、INT16、INT32、INT64、UINT8、BOOL、FLOAT16、FLOAT、DOUBLE、BFLOAT16ND0-8√betaaclScalar*输入公式中的 beta可表示与 ReLU 的近似程度。数据类型满足数据类型互推导关系支持可转换为 FLOAT 的类型。----thresholdaclScalar*输入公式中的 threshold表示阈值大于此值时恢复为线性函数。数据类型满足数据类型互推导关系支持可转换为 FLOAT 的类型。----gradInputaclTensor*输出公式中的 gradInput。支持空 Tensorshape 需与 gradOutput、self 相同gradInput 的 shape 和数据类型与 self 相同。FLOAT、FLOAT16、DOUBLE、BFLOAT16ND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小。-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程。-----几点补充说明dtype 的宽松输入 内部 cast策略self支持的类型范围明显宽于gradOutput/gradInput如 INT8、INT64、BOOL 等。其原因是 aclnn_softplus_backward.cpp 中的CheckDtypeValid会先校验输入在支持列表内随后判断 dtype 是否落在 Kernel 支持的KERNEL_SUPPORT_LISTFLOAT、FLOAT16、BF16中若不在则通过l0op::Cast提升为 FLOAT 计算最后再把结果 cast 回gradInput的原 dtype 写入从而扩大接口可用性broadcast 支持gradOutput的 shape 可与self满足 broadcast 关系Broadcast 后的 shape 需与self相等输出gradInput的 shape 与self相同。这一语义在 softplus_v2_grad_infershape.cpp 中通过InferShape4Broadcast(context, 2)对 input_gradients 与 input_features 做广播推理落地空 Tensor 支持当任一输入为空 Tensor 时第一段接口直接返回workspaceSize 0并提前结束无需下发计算。返回值与错误码两段接口均返回aclnnStatus状态码具体可参见 aclnn返回码。第一段接口会完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、beta、threshold、gradInput 是空指针时。ACLNN_ERR_PARAM_INVALID161002gradOutput、self 和 gradInput 的数据类型不在支持的范围之内。ACLNN_ERR_PARAM_INVALID161002gradOutput、self 和 gradInput 的 shape 不同。对照源码CheckParams 的校验顺序为空指针检查CheckNotNull→ 数据类型合法性检查CheckDtypeValid→ shape 检查CheckShape含最大维度MAX_SUPPORT_DIMS_NUMS与 shape 一致性任一失败即返回对应错误码。此外beta、threshold为空指针时同样报ACLNN_ERR_PARAM_NULLPTR。aclnnSoftplusBackward 参数说明第二段接口的 4 个参数全部为输入语义如下参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址。workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnSoftplusBackwardGetWorkspaceSize 获取。executor输入op 执行器包含了算子计算流程。stream输入指定执行任务的 Stream。返回值为aclnnStatus具体参见 aclnn返回码。约束说明确定性计算aclnnSoftplusBackward默认确定性实现即相同输入在任何运行条件下得到逐位一致的结果便于调试与结果比对dtype 一致性input_gradients与input_features的 dtype 必须一致不支持混合精度输入见 README.md标量属性beta与threshold是 Host 侧标量属性而非 Tensor分别默认1.0与20.0该默认值与 softplus_v2_grad_def.cpp 中的 Attr 注册值、softplus_v2_grad_tiling_struct.h 中的kDefaultBeta/kDefaultThreshold三处保持一致是 tiling 侧运行期的兜底默认值。调用示例可编译运行下面给出完整示例代码仅供参考具体编译与执行过程请参考编译与运行样例。该示例与仓库 examples/test_aclnn_softplus_v2_grad.cpp 保持一致#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_softplus_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 shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } 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初始化, 参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); // check根据自己的需要处理 CHECK_RET(ret 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t gradOutputShape {4, 2}; std::vectorint64_t selfShape {4, 2}; std::vectorint64_t gradInputShape {4, 2}; void* gradOutputDeviceAddr nullptr; void* selfDeviceAddr nullptr; void* gradInputDeviceAddr nullptr; aclTensor* gradOutput nullptr; aclTensor* self nullptr; aclTensor* gradInput nullptr; aclScalar* beta nullptr; aclScalar* threshold nullptr; std::vectorfloat gradOutputHostData {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; std::vectorfloat selfHostData {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; std::vectorfloat gradInputHostData {0, 0, 0, 0, 0, 0, 0, 0}; float betaValue 1.1f; float thresholdValue 1.2f; // 创建 gradOutput, self, gradInput aclTensor ret CreateAclTensor(gradOutputHostData, gradOutputShape, gradOutputDeviceAddr, aclDataType::ACL_FLOAT, gradOutput); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(gradInputHostData, gradInputShape, gradInputDeviceAddr, aclDataType::ACL_FLOAT, gradInput); CHECK_RET(ret ACL_SUCCESS, return ret); beta aclCreateScalar(betaValue, aclDataType::ACL_FLOAT); CHECK_RET(beta ! nullptr, return ret); threshold aclCreateScalar(thresholdValue, aclDataType::ACL_FLOAT); CHECK_RET(threshold ! nullptr, return ret); // 3. 调用CANN算子库API需要修改为具体的API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnSoftplusBackward第一段接口 ret aclnnSoftplusBackwardGetWorkspaceSize(gradOutput, self, beta, threshold, gradInput, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSoftplusBackwardGetWorkspaceSize 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); } // 调用aclnnSoftplusBackward第二段接口 ret aclnnSoftplusBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSoftplusBackward 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::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), gradInputDeviceAddr, size * sizeof(float), 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需要根据具体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; }示例代码的关键流程可归纳为aclInit初始化 → 构造aclTensor/aclScalar→ 第一段接口取 workspace 与 executor → 申请 workspace 内存 → 第二段接口执行 →aclrtSynchronizeStream同步 → 结果回拷 → 资源释放。示例中beta 1.1、threshold 1.2输入 shape 为{4, 2}输出结果即每个元素按公式计算得到的gradInput。源码级实现剖析从 Host 到 Kernel 的完整链路aclnnSoftplusBackward并非孤立接口其背后是activation/softplus_v2_grad目录下完整的op_api → op_host(tiling) → op_kernel三层实现。理解这条链路有助于排查性能与精度问题。op_api 层构图与数据类型适配aclnn_softplus_backward.cpp 在第一段接口中完成如下工作通过CREATE_EXECUTOR()创建执行器随后执行CheckParams参数校验对gradOutput、self执行l0op::Contiguous保证输入连续若输入 dtype 不在 Kernel 支持列表FLOAT/FLOAT16/BF16或两者 dtype 不一致则l0op::Cast提升为 FLOAT将beta、threshold通过ToFloat()转为 float 标量调用 softplus_v2_grad.h 中声明的l0op::SoftplusV2Grad(grad_output, self, beta, threshold, executor)构图结果再 Cast 回gradInput的原始 dtype并通过l0op::ViewCopy写入输出最后返回executor-GetWorkspaceSize()。op_host 层shape 推导与 tiling 切分shape 推导softplus_v2_grad_infershape.cpp 通过InferShape4Broadcast对两个输入做 broadcast 推理产出output_backprops的 shapetilingsoftplus_v2_grad_tiling_arch35.cpp 实现了 arch35Ascend 950 等下的切分策略先用PadAndSqueeze将不同 rank 的输入/输出归一化到最大广播坐标系再用FindSplitAxis依据 UB 容量确定切分轴与 tile 大小a_i/a_o/a_i_tail随后MultiCoreSplit按 AIV 核数将总 tile 数均分到多核含tiles_main/cores_tail尾块处理最后把归一化 shape/stride 与beta、threshold写入SoftplusV2GradTilingData并通过ctx_-SetTilingKey区分 RANK4/RANK8 两档模板实例化。此外该算子在 tiling 阶段声明 workspace 大小为 0TilingFuncSoftplusV2Grad 中workspaces[0] 0。op_kernel 层向量流水与数值实现Kernel 入口 softplus_v2_grad.cpp 按 RANK 模板实例化SOFTPLUS_V2_GRAD_RANK_4/SOFTPLUS_V2_GRAD_RANK_8调用SoftplusV2GradKernelT, RANK。核心计算在 softplus_v2_grad_kernel.h 中FP32 路径P3 流水CopyInBrcNDDMA 多维广播搬运→ 向量函数SoftplusV2GradVF→CopyOutOne搬出通过MTE2→V→MTE3→MTE2事件对做流水同步FP16/BF16 路径P4 流水多出两次AscendC::Cast输入先扩精度为 FP32 计算结果再以CAST_RINT缩回原精度输出向量指令序列S1~S9Muls(beta*self)→Neg→Exp→Adds(1)→Div(gradOutput/(1e^{-β·self}))→ 构造阈值寄存器 →Compare(GT)生成比较掩码 →Select在gradOutput与Div结果间选择。这与接口文档中的数学公式逐条对应且Compare/Select恰好实现了β·self threshold时梯度退化为 1的分支逻辑。测试与验证仓库为aclnnSoftplusBackward提供了多级测试资产可作为自行验证的参考UT单元测试tests/ut/op_api/test_aclnn_softplus_backward.cpp 覆盖 op_api 层接口调用tests/ut/op_host/arch35/test_softplus_v2_grad_tiling.cpp 覆盖 tiling 切分结果tests/ut/op_kernel/test_softplus_v2_grad.cpp 配合gen_data.py/compare_data.py完成 Kernel 级数据比对tests/ut/op_host/test_softplus_v2_grad_infershape.cpp 覆盖 shape 推导ST系统测试tests/st/aclnnSoftplusBackward 提供 ATK 测试配置atk_aclnnSoftplusBackward.json与executor_aclnnSoftplusBackward.py可在真实 NPU 环境验证精度与确定性调用方式对照除 aclnn 单算子调用外test_geir_softplus_v2_grad.cpp 演示了通过算子 IRsoftplus_v2_grad_proto.h构图调用 SoftplusV2Grad 的图模式路径适合在图编译场景下使用。总结aclnnSoftplusBackward是 CANN ops-nn 中 Softplus 前向算子的标准反向接口数学上通过threshold阈值将梯度分段为 sigmoid 形式与线性形式以保证数值稳定接口上采用两段式设计第一段完成校验、构图与 workspace 计算第二段提交执行实现上由 op_api 层做 dtype 适配与广播处理、op_host 层完成 shape 推导与多核 tiling、op_kernel 层以向量指令流水实现核心公式。无论是 PyTorch 等上层框架接入反向传播还是在昇腾平台手写推理/训练算子本文给出的参数语义、错误码表与可运行示例均可直接指导开发与调试。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表