资讯动态

CANN ops-nn 算子详解:aclnnHardtanhBackward 两段式接口实现 Hardtanh 激活函数反向传播

发布时间:2026/9/20 2:21:09 来源:尧图企业网站定制
人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载导读aclnnHardtanhBackward是 CANN 神经网络算子库ops-nn中 Hardtanh 激活函数的反向算子接口用于在 NPU 上完成梯度回传计算根据正向传播的输入self与上一层的梯度gradOutput计算出本层应继续回传的梯度out。本文以 aclnnHardtanhBackward.md 为核心系统讲解该算子的数学原理、两段式 API 原型、参数约束、错误码处理、完整调用示例并结合仓库源码op_host / op_kernel / 单元测试深入剖析其入参校验、Tiling 切分与 Kernel 实现原理帮助读者在 Atlas A2 训练系列产品/Atlas 800I A2 推理产品上正确、高效地调用该算子。产品支持情况产品是否支持Atlas A2 训练系列产品 / Atlas 800I A2 推理产品√从仓库实现看该算子在 Host 侧的接口适配层根据 SoC 版本对数据类型支持范围做了区分详见 aclnn_hardtanh_backward.cppASCEND910_DTYPE_SUPPORT_LISTFLOAT、FLOAT16Atlas A2 之外的 910 系列默认支持列表ASCEND910B_DTYPE_SUPPORT_LISTFLOAT、FLOAT16、BF16SoC 版本位于 ASCEND910B 与 ASCEND910E 之间时启用。对应地算子的 Kernel 侧统一支持 BF16、FLOAT16、FLOAT 三种数据类型产品支持的差异主要体现在接口层的 dtype 校验策略上。功能说明与数学原理aclnnHardtanhBackward完成的是激活函数aclnnHardtanh的反向传播其梯度掩码由正向输入self与裁剪区间[min, max]决定。计算公式如下$$ grad_self_{i} \begin{cases} 0,\ \ \ \ \ \ \ if \ \ self_{i}max \ 0,\ \ \ \ \ \ \ if\ \ self_{i}min \ 1,\ \ \ \ \ \ \ \ \ \ \ \ otherwise \ \end{cases} $$$$ res_{i} grad_output_{i} \times grad_self_{i} $$即当self落在区间[min, max]内时grad_self为 1梯度原样透传当self超出区间边界 min或 max时grad_self为 0对应位置的梯度被置零与 Hardtanh 前向中饱和区的导数为 0 一致。这与 PyTorch 等框架中HardtanhBackward的语义一致实现的是分段线性激活函数的精确梯度。一个直观的算例设min 1.2、max 2.4正向输入self [1, 1, 1, 2, 1, 2, 3, 3]上游梯度gradOutput [0, 1, 2, 3, 4, 5, 6, 7]self 1 1.2grad_self 0→ 输出 0self 2在[1.2, 2.4]内grad_self 1→ 输出等于gradOutput原值self 3 2.4grad_self 0→ 输出 0。最终out [0, 0, 0, 3, 0, 5, 0, 0]该算例与仓库中 test_aclnn_hardtanh_grad.cpp 的示例数据完全对应读者可在本地运行验证。函数原型与两段式接口每个算子都遵循两段式接口的调用模式先调用aclnnHardtanhBackwardGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器executor再调用aclnnHardtanhBackward执行实际计算。第一段接口GetWorkspaceSizeaclnnStatus aclnnHardtanhBackwardGetWorkspaceSize( const aclTensor* gradOutput, const aclTensor* self, const aclScalar* min, const aclScalar* max, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口执行计算aclnnStatus aclnnHardtanhBackward( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)从源码看第二段接口最终通过CommonOpExecutorRun完成算子执行这是一个统一的框架级入口见 aclnn_hardtanh_backward.cpp业务侧不需要也不应该直接操作底层 kernel。aclnnHardtanhBackwardGetWorkspaceSize 参数说明第一段接口完成入参校验、tensor 连续性处理、算子图构建与 workspace 大小计算。参数表参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutput输入反向传播过程中上一步输出的梯度公式中的 grad_output不支持空TensorgradOutput、self、out 的 shape 一致gradOutput、self、out 的数据类型一致BFLOAT16、FLOAT16、FLOATND0-8√self输入正向的输入数据公式中的 self不支持空TensorgradOutput、self、out 的 shape 一致gradOutput、self、out 的数据类型一致BFLOAT16、FLOAT16、FLOATND0-8√min输入线性范围的下限公式中的 min-BFLOAT16、FLOAT16、FLOAT---max输入线性范围的上限公式中的 max-BFLOAT16、FLOAT16、FLOAT---out输出计算得到的梯度不支持空TensorgradOutput、self、out 的 shape 一致gradOutput、self、out 的数据类型一致BFLOAT16、FLOAT16、FLOATND0-8√workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----executor输出返回 op 执行器包含了算子计算流程-----参数校验的源码级实现仓库在 aclnn_hardtanh_backward.cpp 中实现了CheckParams校验逻辑分三层空指针检查CheckNotNullgradOutput、self、out、min、max任一为空指针时返回ACLNN_ERR_PARAM_NULLPTR数据类型检查CheckDtypeValid三个 tensor 的数据类型必须位于平台支持列表内且gradOutput与self的 dtype 必须一致否则返回ACLNN_ERR_PARAM_INVALIDshape 检查CheckShapeValidgradOutput、self的维度不能超过 8 维MAX_DIM_LEN 8且两者 shape 必须一致否则返回ACLNN_ERR_PARAM_INVALID。校验通过后接口内部还会做三件关键工作将min、max两个 aclScalar 通过ToFloat()转为 float32作为算子属性传给底层l0op::HardtanhGrad对非连续的输入输出 tensor 通过l0op::Contiguous转成连续张量计算完成后用l0op::ViewCopy将结果拷贝回out因此接口本身支持非连续 tensor这也是上表中非连续Tensor列为 √ 的原因空 tensorshape 中存在 0 维在 kernel 层是支持的此时直接返回workspaceSize 0不触发实际计算见 aclnn_hardtanh_backward.cpp。返回值与错误码返回值类型为aclnnStatus具体状态码参见 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 gradOutput、self、out、min、max 是空指针ACLNN_ERR_PARAM_INVALID161002gradOutput、self、out 的数据类型不在支持范围之内ACLNN_ERR_PARAM_INVALID161002gradOutput 和 self 数据类型不同ACLNN_ERR_PARAM_INVALID161002gradOutput 和 self 的 shape 不同ACLNN_ERR_PARAM_INVALID161002gradOutput 或 self 的维度大于 8这些错误码场景在 test_aclnn_hardtanh_backward.cpp 中均有对应的单元测试用例覆盖例如空指针场景abnormal_grad_output_nullptr等、dtype 不支持场景abnormal_dtype_ACL_INT64_format_nd等、dtype 不一致场景abnormal_dtype_grad_output_self_unequal、shape 不一致与维度超限场景abnormal_shape_grad_output_self_unequal、abnormal_shape_dim_greater_than_threshold读者可以参考这些用例理解各错误码的具体触发条件。aclnnHardtanhBackward 参数说明第二段接口接收第一段接口产出的 workspace 与 executor在指定 stream 上异步执行计算。参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnHardtanhBackwardGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值仍为aclnnStatus具体参见 aclnn 返回码。约束说明确定性计算aclnnHardtanhBackward默认采用确定性实现即相同输入在多次执行中产生一致的输出相关概念可参考 确定性计算。输出out的数据类型与输入保持一致README 中同样有此约束见 hardtanh_grad README。输入输出均支持非连续 tensor接口内部会自动完成连续性转换。调用示例以下示例代码直接取自仓库文档演示了完整的 aclnn 两段式调用流程。完整可编译版本参见 examples/test_aclnn_hardtanh_grad.cpp具体编译与执行过程请参考编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_hardtanh_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 outShape {4, 2}; void* gradOutputDeviceAddr nullptr; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* gradOutput nullptr; aclTensor* self nullptr; aclTensor* out nullptr; aclScalar* clipValueMin nullptr; aclScalar* clipValueMax nullptr; std::vectorfloat gradOutputHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat selfHostData {1, 1, 1, 2, 1, 2, 3, 3}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; float clipValueMinValue 1.2f; float clipValueMaxValue 2.4f; // 创建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); // 创建clipValueMin aclScalar clipValueMin aclCreateScalar(clipValueMinValue, aclDataType::ACL_FLOAT); CHECK_RET(clipValueMin ! nullptr, return ret); // 创建clipValueMax aclScalar clipValueMax aclCreateScalar(clipValueMaxValue, aclDataType::ACL_FLOAT); CHECK_RET(clipValueMax ! nullptr, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnHardtanhBackward第一段接口 ret aclnnHardtanhBackwardGetWorkspaceSize(gradOutput, self, clipValueMin, clipValueMax, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHardtanhBackwardGetWorkspaceSize 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;); } // 调用aclnnHardtanhBackward第二段接口 ret aclnnHardtanhBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHardtanhBackward 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(outShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, 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和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(gradOutput); aclDestroyTensor(self); aclDestroyScalar(clipValueMin); aclDestroyScalar(clipValueMax); aclDestroyTensor(out); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(gradOutputDeviceAddr); aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }调用流程拆解初始化 ACL 环境aclInit→aclrtSetDevice→aclrtCreateStream固定写法构造 tensor/scalarCreateAclTensor模板函数完成 device 内存申请aclrtMalloc、Host→Device 数据拷贝aclrtMemcpy以及 aclTensor 创建aclCreateTensorND 格式 连续 stridesmin、max通过aclCreateScalar创建第一段接口调用aclnnHardtanhBackwardGetWorkspaceSize获取workspaceSize与executor随后仅在workspaceSize 0时用aclrtMalloc申请 workspace 内存第二段接口调用aclnnHardtanhBackward(workspaceAddr, workspaceSize, executor, stream)在指定 stream 上提交计算任务同步等待aclrtSynchronizeStream等待任务完成结果回拷aclrtMemcpy将 Device 侧结果拷回 Host 并逐元素打印资源释放依次释放 tensor、scalar、device 内存、stream并aclrtResetDeviceaclFinalize。上述流程中的设备初始化、tensor 构造、资源释放三步是 aclnn 算子的通用固定写法实际业务中可封装复用min/max的取值与数据需根据模型裁剪区间配置。底层实现解析从 Host 到 Kernel算子定义OpDefhardtanh_grad_def.cpp 中通过OpDef注册了底层算子HardtanhGrad输入result即 self、输入grad即 gradOutput输出y即 out三者数据类型均为ge::DT_BF16 / DT_FLOAT16 / DT_FLOAT格式为 ND属性min_val、max_val为必选 Float 属性默认值分别为-1.0与1.0AICore 配置注册到ascend910b。这与 aclnn 接口层将min/max两个 aclScalar 通过ToFloat()转成 float32 后作为l0op::HardtanhGrad(gradOutputContiguous, selfContiguous, minValue, maxValue, outContiguous, executor)调用时的minValue/maxValue参数一一对应见 aclnn_hardtanh_backward.cpp。Shape 推导InferShapehardtanh_grad_infershape.cpp 实现InferShapeHardtanhGrad输出y的 shape 直接复制输入selfIDX_1的 shape即输出与输入形状完全一致符合逐元素算子的语义。Tiling 策略hardtanh_grad_tiling.cpp 负责在 Host 侧根据输入规模进行任务切分核心逻辑如下获取平台信息UB 内存大小、可用核数coreNum按数据类型计算每个 GM 块可容纳的元素数elemsPerGmBlock GM_ALIGN(512) / typeLength并将输入元素总数向上对齐到 512BinputLengthAlgin512大 shape 分支inputNum DB_THRESHOLD_VALUE即超过 6144×20采用ELEMENTWISE_TPL_SCH_MODE_1调度模式在核间均匀切分数据支持双缓冲BUFFER_NUM 2并计算每个核的大小核/大核数据量、tile 数量与尾部数据小 shape 分支采用ELEMENTWISE_TPL_SCH_MODE_0调度模式按 8KB 阈值TILE_BASE_SIZE × TILE_THRESHOLD_FACTOR分级选择 tile 大小1024 起步最多 4 级到 MAX_TILEDATA 6144mask 缓冲区按位运算需求计算大小maskTileDataNum最终将min/max一并写入HardtanhGradTilingData结构体定义见 hardtanh_grad_tiling_data.h并通过SetTilingKey/SetBlockDim下发给 kernel。Tiling 相关的关键常量DB_THRESHOLD_VALUE、MAX_TILEDATA、TILE_BASE_SIZE、TILE_THRESHOLD_FACTOR、TILE_MAX_REGULAR_INDEX等在 hardtanh_grad_tiling.cpp 顶部集中定义并有对应的单测 test_hardtanh_grad_tiling.cpp 验证切分结果的正确性。Kernel 计算原理hardtanh_grad.cpp 与 hardtanh_grad.h 实现了 AIV 核上的实际计算核心思路是用比较运算生成 mask再以 mask 做 Select 选择避免逐元素分支判断CompareScalar(mask1, x, 1.0, LE)self 1.0的位置置位与 1.0 的比较是 saturate 区外的兜底判断CompareScalar(mask3, x, max, LE)与CompareScalar(mask2, x, min, GE)经And得到min self max的区间内掩码将两个掩码Or合并为最终grad_self掩码区间内为 1区间外为 0Select(y, mask, grad, 0, VSEL_TENSOR_SCALAR_MODE)掩码为 1 时取gradOutput原值否则取 0。对于 FLOAT16 / BF16 输入Kernel 先通过Cast将self转为 float32 参与比较tmpBuf缓冲保证比较精度Select再按原类型输出。双缓冲模式下使用TPipe的VECIN/VECOUT队列实现 CopyIn → Compute → CopyOut 流水小 shape 场景则退化为KernelHardtanhGrad_NoDB单缓冲实现减少调度开销。单元测试覆盖仓库为该算子提供了完整的 UT 验证tests/ut 目录op_api 层覆盖正常场景FLOAT/FLOAT16/BF16 的 ND 格式与各类异常场景不支持 dtype、dtype 不一致、shape 不一致、维度超限、空指针并包含空 tensor 与多种非 ND 格式NHWC、NCHW、HWCN、NCDHW、NDHWC的验证见 test_aclnn_hardtanh_backward.cppop_host 层infer shape 与 tiling 切分逻辑均有单测op_kernel 层kernel 级计算正确性由 test_hardtanh_grad.cpp 验证。此外README.md 的调用说明一节明确指出通过 aclnn 接口方式调用 HardtanhGrad 算子的入口样例可作为快速上手的路线图。常见问题与排查建议返回 161001ACLNN_ERR_PARAM_NULLPTR检查 gradOutput、self、out、min、max 是否均已正确创建尤其是aclCreateScalar返回的 min/max 是否为空返回 161002ACLNN_ERR_PARAM_INVALID逐一核对三点——三个 tensor 的 dtype 是否均为 BFLOAT16/FLOAT16/FLOAT 之一gradOutput 与 self 的 dtype 是否一致两者的 shape 是否一致且维度不超过 8 维。若误传 INT64、INT16 等整数类型或 NHWC/NCHW 等格式接口层校验仅按 ND 处理其它格式由测试用例验证为通过但需注意平台差异都会在此阶段被拦截结果全为 0 或部分为 0检查 min/max 取值是否符合预期饱和区外self min或self max的输出本应被置 0这是 Hardtanh 反向的正确行为而非计算错误期望的调用位置该算子用于训练反向传播链路self应为前向 Hardtanh 的输入张量而非输出gradOutput为后一层的梯度输入。总结aclnnHardtanhBackward是 CANN ops-nn 中 Hardtanh 反向传播的标准 aclnn 接口两段式 API 完成了从参数校验、workspace 计算到 NPU 异步执行的全流程底层通过 mask Select 的向量化实现保证计算效率通过大/小 shape 分支与双缓冲机制优化资源利用仓库自带的示例程序与多级单测为二次开发与移植提供了可直接参考的范本。读者若需在自有框架中接入 Hardtanh 的反向算子可将本文的示例代码与 两段式接口、编译与运行样例 结合使用。赞分享人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载相关推荐CANN ops-nn 算子实战aclnnSwishBackward 两段式接口实现 Swish 激活函数梯度反向传播CANN ops nn 算子实战aclnnSwishBackward 两段式接口实现 Swish 激活函数梯度反向传播 aclnnSwishBackward人工智能算子库深度学习CANNAscendCANN ops-nn 算子详解aclnnSoftMarginLossBackward 两段式接口与 SoftMarginLoss 反向传播实现CANN ops nn 算子详解aclnnSoftMarginLossBackward 两段式接口与 SoftMarginLoss 反向传播实现 导读 acl人工智能算子库深度学习CANNAscendCANN ops-nn 算子开发实战aclnnGeluBackward 两段式接口实现 Gelu 反向传播CANN ops nn 算子开发实战aclnnGeluBackward 两段式接口实现 Gelu 反向传播 本篇技术指南以 CANN ops nn 开源仓库中人工智能算子库深度学习CANNAscend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价