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

资讯详情

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

CANN ops-nn HardSigmoidGrad 反向算子深度解析:ACLNN 接口、数学原理与 NPU 实现

CANN ops-nn HardSigmoidGrad 反向算子深度解析:ACLNN 接口、数学原理与 NPU 实现 CANN ops-nn HardSigmoidGrad 反向算子深度解析ACLNN 接口、数学原理与 NPU 实现【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nnHardSigmoidGrad 是 CANN 神经网络算子库 ops-nn 中 HardSigmoid 激活函数的反向算子用于在 NPU 上计算反向传播梯度。本文基于 HardSigmoidGrad README 与仓库源码完整讲解其产品支持范围、数学公式、入参规格、aclnnHardsigmoidBackward 两段式调用流程及 Kernel 层的数值精度设计帮助读者在 Ascend 平台上正确、高效地使用与验证该算子。产品支持情况HardSigmoidGrad 算子在不同 Ascend 产品上的支持情况如下表所示数据来源activation/hard_sigmoid_grad/README.md产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品√Atlas 训练系列产品√需要说明的是在不同产品上支持的数据类型存在差异其中Atlas 推理系列产品、Atlas 训练系列产品即 910/310P 平台的数据类型仅支持FLOAT16、FLOAT而 Ascend 950 系列、Atlas A2/A3 系列在接口层同时支持BFLOAT16、FLOAT16、FLOAT对应实现见 aclnn_hardsigmoid_backward.cpp 中的ASCEND910_DTYPE_SUPPORT_LIST与ASCEND910B_DTYPE_SUPPORT_LIST两组支持列表。功能说明与计算公式HardSigmoidGrad 是 HardSigmoid 前向算子的反向backward算子其功能是计算反向传播的梯度 grad_input。前向 HardSigmoid 的数学定义为参见 HardSigmoid README$$ HardSigmoid(self)clip(\alpha \times self \beta, 0, 1) $$其中默认 $\alpha\frac{1}{6}$、$\beta\frac{1}{2}$等价于分段形式$$ HardSigmoid(self)\begin{cases} 1, if(self\gt3) \ 0, if(self\le-3) \ \frac{self}{6} \frac{1}{2}, otherwise \end{cases} $$由于分段函数在中间线性区间的导数为 $\alpha\frac{1}{6}$、在饱和区间导数为 0因此反向算子 HardSigmoidGrad 的计算公式为README 原文$$ HardsigmoidBackward(self, grad_output) \begin{cases} grad_output \ast \alpha, if(0 \alpha \ast self \beta 1) \ 0, otherwise \end{cases} $$即仅当 $\alpha \times self \beta$ 落在开区间 $(0, 1)$ 内时梯度才等于 grad_output 乘以 $\alpha$否则梯度为 0。其中 $\alpha$ 固定为 $1/6$、$\beta$ 固定为 $0.5$不对外暴露为可配置属性。从实现层面看该常量在算子定义OpDef中登记为可选属性并赋予默认值alpha 1.0f / 6.0f、beta 0.5f见 hard_sigmoid_grad_def.cpp。接口层在组装算子图时同样显式传入alpha 1.0f / 6.0f、beta 0.5f并且源码注释特别强调使用1.0f/6.0f而非字面量0.16666666f是为了在边界点 x-3.0 处与 PyTorch 的 hardsigmoid 语义严格对齐——1/6*(-3)0.5 0严格成立而0.16666666f*(-3)0.5 ≈ 2.98e-08会泄漏进 mask1 的区域导致边界处梯度错误见 aclnn_hardsigmoid_backward.cpp。参数说明HardSigmoidGrad 算子共包含两个输入和一个输出README 参数表参数名输入/输出/属性描述数据类型数据格式grads输入反向传播过程中上一步输出的梯度作为本反向算子的输入公式中的 grad_outputFLOAT、FLOAT16、BFLOAT16NDinput_x输入表示 HardSigmoid 前向算子的输入 Tensor公式中的 selfFLOAT、FLOAT16、BFLOAT16NDy输出反向传播梯度输出公式中的 HardsigmoidBackward(self, grad_output)FLOAT、FLOAT16、BFLOAT16ND以上类型与格式约束可以在算子注册定义 hard_sigmoid_grad_def.cpp 中得到印证grads、input_x两个输入与y输出均为 REQUIRED 参数数据类型限定为DT_FLOAT / DT_FLOAT16 / DT_BF16数据格式统一为FORMAT_ND且声明了AutoContiguous()输入会自动做连续性处理。从推理侧看输出 shape 与输入 shape 完全一致——hard_sigmoid_grad_infershape.cpp 中的InferShape4HardSigmoidGrad直接将输出 shape 赋值为输入 shape*output_shape *input_shape这也是逐元素反向算子的通用行为。约束说明README 明确标注约束说明无。不过在接口层GetWorkspaceSize 阶段仍存在如下入参校验约束违反时会返回对应的 aclnn 返回码返回码错误码触发场景ACLNN_ERR_PARAM_NULLPTR161001gradOutput、self、out 传入空指针ACLNN_ERR_PARAM_INVALID161002gradOutput/self 数据类型不在支持范围内或三个 Tensor 的 shape 不一致或 gradOutput 与 self 互推导后的数据类型无法转换成 out 的数据类型这些校验逻辑对应 aclnn_hardsigmoid_backward.cpp 中的CheckParams函数依次完成空指针检查CheckNotNull、数据类型检查CheckDtypeValid其中out的数据类型需满足PromoteType(gradOutput, self)的互推导可转换关系与 shape 检查CheckShape限制最大维度MAX_SUPPORT_DIMS_NUMS且三个 Tensor 形状必须相等。接口层还支持空 Tensor 与 0-8 维 shape当任意输入为空 Tensor 时第一段接口直接返回workspaceSize 0并跳过计算见 aclnn_hardsigmoid_backward.cpp。调用说明aclnnHardsigmoidBackward 两段式接口调用方式总览HardSigmoidGrad 算子通过aclnn 接口方式调用完整可编译样例见 test_aclnn_hard_sigmoid_grad.cpp接口详细定义见 aclnnHardsigmoidBackward 文档。与 CANN 所有算子一致该接口采用两段式调用参见两段式接口说明必须先调用aclnnHardsigmoidBackwardGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器再调用aclnnHardsigmoidBackward执行计算。函数原型// 第一段获取 workspace 大小与执行器 aclnnStatus aclnnHardsigmoidBackwardGetWorkspaceSize( const aclTensor* gradOutput, // 输入上一层梯度公式中 grad_output const aclTensor* self, // 输入前向算子输入公式中 self aclTensor* out, // 输出self 的梯度 grad_input uint64_t* workspaceSize,// 输出Device 侧需申请的 workspace 大小 aclOpExecutor** executor) // 输出op 执行器 // 第二段执行计算 aclnnStatus aclnnHardsigmoidBackward( void* workspace, // Device 侧 workspace 内存地址 uint64_t workspaceSize, // 由第一段接口获取 aclOpExecutor* executor, // op 执行器 aclrtStream stream) // 执行任务的 Stream第一段接口 GetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 TensorgradOutputaclTensor*输入计算入参支持空 TensorgradOutput 与 self、out 的 shape 一致BFLOAT16、FLOAT16、FLOATND0-8√selfaclTensor*输入计算入参支持空 TensorgradOutput 与 self、out 的 shape 一致BFLOAT16、FLOAT16、FLOATND0-8√outaclTensor*输出为 self 的梯度值公式中的 grad_inputgradOutput 与 self、out 的 shape 一致数据类型需是 self 与 gradOutput 推导之后可转换的数据类型见互推导关系BFLOAT16、FLOAT16、FLOATND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含算子计算流程-----两个输入 Tensor 均支持**非连续Non-Contiguous**布局接口内部会自动调用Contiguous补齐连续性同时支持空 Tensor空 Tensor 时跳过实际计算。第二段接口执行说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入workspace 大小由第一段接口aclnnHardsigmoidBackwardGetWorkspaceSize获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream接口内部执行链路从 aclnn_hardsigmoid_backward.cpp 可以看到GetWorkspaceSize阶段会完成一层通用算子编排随后真正计算由 HardSigmoidGradl0op 层算子执行整体链路为入参校验空指针、数据类型、shape 检查空 Tensor 短路任一输入为空时workspaceSize 0直接返回连续性处理与类型提升对 gradOutput、self 分别执行Contiguous→Cast提升到PromoteType(gradOutput, self)的公共类型组装算子图调用l0op::HardSigmoidGrad(grads, x, alpha, beta, executor)其中输出 Tensory通过executor-AllocTensor(x-GetViewShape(), x-GetDataType())自动按输入 shape 与数据类型分配见 hardsigmoid_grad.cpp并通过ADD_TO_LAUNCHER_LIST_AICORE挂载到 AiCore 执行器列表结果回写对计算结果再次Cast回 out 的目标数据类型并用ViewCopy写入 out汇总 workspaceuniqueExecutor-GetWorkspaceSize()返回整个链路Contiguous/Cast/HardSigmoidGrad/ViewCopy所需的 workspace 总大小。第二段接口aclnnHardsigmoidBackward则通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)统一驱动执行器在指定 stream 上运行。完整调用示例以下为可直接编译运行的最小示例节选自 test_aclnn_hard_sigmoid_grad.cpp完整编译执行流程参考编译与运行样例#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_hardsigmoid_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 手册按实际设备填写 deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出gradOutput/self/out 的 shape 必须一致 std::vectorint64_t gradOutShape {4, 2}; std::vectorint64_t selfShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* gradOutDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* gradOut nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat gradOutHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; // 创建 self / gradOut / out 三个 aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(gradOutHostData, gradOutShape, gradOutDeviceAddr, aclDataType::ACL_FLOAT, gradOut); CHECK_RET(ret ACL_SUCCESS, return ret); 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; // 第一段获取 workspace 大小与执行器 ret aclnnHardsigmoidBackwardGetWorkspaceSize(gradOut, self, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHardsigmoidBackwardGetWorkspaceSize 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;); } // 第二段执行计算 ret aclnnHardsigmoidBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHardsigmoidBackward 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(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 aclDestroyTensor(self); aclDestroyTensor(gradOut); aclDestroyTensor(out); // 7. 释放 device 资源 aclrtFree(selfDeviceAddr); aclrtFree(gradOutDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例的调用流程可归纳为 7 步初始化aclInit/aclrtSetDevice/aclrtCreateStream→构造 TensoraclrtMallocaclrtMemcpy后aclCreateTensor创建 ND 格式 aclTensor→两段式接口调用→同步等待aclrtSynchronizeStream→结果回拷aclrtMemcpyDEVICE_TO_HOST→释放 TensoraclDestroyTensor→释放资源aclrtFree/aclrtDestroyStream/aclrtResetDevice/aclFinalize。以示例数据self {0,1,...,7}、gradOut {0,1,...,7}为例由于 $\alpha \times self \beta self/6 0.5$ 在self ∈ (-3, 3)时位于 $(0,1)$ 区间因此输出result[i] gradOut[i] / 6超出该区间的元素self4、5、6、7 对应 index 4-7输出为 0。确定性计算接口文档明确说明aclnnHardsigmoidBackward 默认确定性实现即相同输入在多次运行中产生逐位一致的输出满足确定性计算要求参见 aclnnHardsigmoidBackward 文档 的约束说明章节。Kernel 层实现原理与数值精度设计在算子定义 hard_sigmoid_grad_def.cpp 中HardSigmoidGrad 声明为AiCore 动态算子DynamicCompileStaticFlag(true)、DynamicShapeSupportFlag(true)、DynamicRankSupportFlag(true)并针对 arch35Ascend 950 系列注册了hard_sigmoid_grad_apt的 kernel 入口。Kernel 入口 hard_sigmoid_grad_apt.cpp 按 tiling 生成的schMode模板参数分发到三种类型实例schMode HARD_SIGMOID_GRAD_MODE_FLOAT32→HardSigmoidGradfloatschMode HARD_SIGMOID_GRAD_MODE_FLOAT16→HardSigmoidGradhalfschMode HARD_SIGMOID_GRAD_MODE_BFLOAT16→HardSigmoidGradbfloat16_t三个数据类型共用同一个模板类见 hard_sigmoid_grad.h通过if constexpr (std::is_same_vT, float)在编译期拆分出两条计算流水线fp32 原生流水线Tfloat直接在输入队列缓冲上完成Muls(tmp, self, alpha)→Adds(tmp, tmp, beta)→Muls(result, gradOut, alpha)再通过两次CompareScalar分别与 0 和 1 比较生成 mask 后Select选择保留或置 0全程无类型转换UB 占用最少。Cast→fp32→Cast 回写流水线half/bf16先将 half/bf16 无损拓宽为 fp32Cast ... CAST_NONE在 fp32 精度下完成同样的乘加与比较选择最后按类型专属的舍入模式转回原类型half 使用CAST_RINT就近偶数舍入bf16 使用CAST_ROUND。kernel 头文件注释明确说明了这样做的原因避免 fp16 在边界点 x≈±3HardSigmoid 拐点处ε≈9.77e-4的边界精度风险见 hard_sigmoid_grad.h。此外kernel 在计算前会先把临时缓冲预填充为-1.0f位于 (0,1) 区间之外保证CompareScalar按 256B 对齐窗口读取的尾部位tail bits结果确定这是算子确定性实现在数据面DataCopy/Compare 对齐上的落地保障。Tiling 阶段见 hard_sigmoid_grad_tiling_arch35.cpp则按数据类型核算 UB 缓冲占用fp32 路径约 7 个 fp32 槽位、fp16/bf16 路径约 6 个 fp32 槽位并将ubFactor对齐到max(ubBlockSize, 256B)从而保证 kernel 层 256B 对齐的读取窗口始终不会越出InitBuffer边界。整体上HardSigmoidGrad 采用了host 端 tiling 切分 核内双缓冲BUFFER_NUM2Tile 流水CopyIn→Compute→CopyOut 类型感知的统一模板 Kernel的典型 AiCore 算子架构兼顾了三种浮点数据类型的精度正确性与 UB 资源效率。相关资源索引算子说明文档activation/hard_sigmoid_grad/README.md接口参考文档docs/aclnnHardsigmoidBackward.md调用样例examples/test_aclnn_hard_sigmoid_grad.cpparch35 版本位于 examples/arch35接口实现op_api/aclnn_hardsigmoid_backward.cpp、op_api/hardsigmoid_grad.cpp算子定义与 shape 推导op_host/hard_sigmoid_grad_def.cpp、op_host/hard_sigmoid_grad_infershape.cppKernel 实现op_kernel/hard_sigmoid_grad_apt.cpp、op_kernel/arch35/hard_sigmoid_grad.hTiling 实现op_host/arch35/hard_sigmoid_grad_tiling_arch35.cpp前向算子activation/hard_sigmoid/README.md 与 aclnnHardsigmoid 接口文档单元测试tests/ut/op_host/op_api/test_aclnn_hardsigmoid_backward.cpp【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表