人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载LogitGrad 是 CANN ops-nn 神经网络算子库中 Logit 算子的反向传播算子用于在 NPU 上完成概率到 logit 变换的梯度回传计算。本文基于 loss/logit_grad/README.md 及其配套文档系统讲解 LogitGrad 的数学原理、产品支持矩阵、参数与边界约束并结合 aclnnLogitGrad 接口文档 与 op_host/op_kernel 源码深入剖析两段式 aclnn 调用流程、错误码语义以及 tiling 与 AscendC kernel 的实现细节帮助读者快速掌握该算子的使用与原理。功能与计算原理LogitGrad 完成aclnnLogit的正向变换y ln(x / (1 - x))的反向传播给定正向输入x、上游梯度dy计算出对输入的反向梯度dx。其计算公式如下$$ dx_i \begin{cases} NaN, \text{if } x 0 \text{ or } x 1 ,eps 0 \ 0, \text{if } x \text{eps} \text{ or } x 1 - \text{eps},eps \geq 0 \ \frac{dy_i}{x_i \cdot (1 - x_i)}, \text{if } \text{eps} \leq x_i \leq 1 - \text{eps} \ \end{cases} $$从公式可以看出当输入x落在有效区间[eps, 1 - eps]内时梯度为dy / (x * (1 - x))当eps非负且x越界时输出 0当eps为负且x不在[0, 1]时输出 NaN。这一设计既保证了对 logit 函数导数的精确回传又通过eps边界避免了x 0或x 1时除零导致的数值异常。产品支持情况LogitGrad 算子对当前各产品形态的支持情况如下表所示与 README 中产品支持矩阵一致产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品×在算子注册侧logit_grad_def.cpp 通过this-AICore().AddConfig(...)分别添加了ascend910b、ascend910_93、ascend950三个 AI Core 配置与上表中 Ascend A2、A3、950 系列的支持情况一一对应op_host/config 目录下也分别维护了 ascend910b/logit_grad_binary.json、ascend910_93/logit_grad_binary.json 与 ascend950/logit_grad_binary.json 三份产品配置供对应平台构建时使用。参数说明LogitGrad 算子的核心参数如下表参数名输入/输出/属性描述数据类型数据格式x输入公式中的 x正向输入BFLOAT16、FLOAT16、FLOATNDdy输入公式中的 dy上游梯度BFLOAT16、FLOAT16、FLOATNDeps属性输入 x 的 eps 限制边界防止除 0 错误建议值 -1DOUBLE-out输出公式中的 dx反向梯度BFLOAT16、FLOAT16、FLOATND几点值得注意eps 为 DOUBLE 类型属性默认值为 -1.0。在 op_graph/logit_grad_proto.h 的 IR 注册中可见ATTR(eps, Float, -1.0)而 OpDef 注册中为Attr(eps).AttrType(OPTIONAL).Float(-1.0)见 logit_grad_def.cpp即 eps 为可选属性、默认取 -1.0用户未显式指定时按 -1.0 处理。数据类型约束x、dy、out 均仅支持 BFLOAT16、FLOAT16、FLOAT 三种且三者保持一致格式固定为 NDshape 范围 0~8 维支持非连续 Tensor。在 aclnn 接口aclnnLogitGrad.md中dy要求与x数据类型、数据格式和 shape 保持一致out同样如此。约束与边界行为eps与输入取值的组合会触发不同的边界行为README 中给出了明确约束使用时应特别留意eps 取值对 x 和输出的影响eps 小于 0 时x 取值范围不在 [0, 1]输出 NaNeps 大于等于 0 时x 取值范围不在 [eps, 1 - eps]输出 0eps 大于 1 时输出 NaN取值为 1 时为 Infeps 取值为 Inf 时输出 0eps 取值为 NaN 时输出 NaN。x 取值对输出的影响x 取值 0 和 1 时输出 Infx 为 Inf 或 NaN 时输出为 NaN。dy 取值对输出的影响dy 为 Inf 或 NaN 时输出为 Inf 或 NaN。此外aclnnLogitGrad.md 中特别注明aclnnLogitGrad 默认采用确定性实现即相同输入在多次执行中结果可复现这对梯度校验和精度对比场景十分友好。调用方式README 中列出了两种调用方式调用方式调用样例说明aclnn 调用test_aclnn_logit_grad.cpp通过aclnnLogitGrad接口方式调用 LogitGrad 算子图模式调用-通过算子 IR 构图方式调用 LogitGrad 算子aclnn 两段式接口调用aclnnLogitGrad采用 CANN 算子库通用的两段式接口详见 两段式接口说明必须先调用aclnnLogitGradGetWorkspaceSize完成入参校验、计算所需的 workspace 大小并获取 op 执行器再调用aclnnLogitGrad真正下发计算任务。第一段接口原型aclnnStatus aclnnLogitGradGetWorkspaceSize( const aclTensor *x, const aclTensor *dy, double eps, const aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口原型aclnnStatus aclnnLogitGrad( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)图模式调用图模式调用不依赖 aclnn 接口而是通过算子 IR 构图。算子原型在 logit_grad_proto.h 中注册REG_OP(LogitGrad) .INPUT(x, TensorType({DT_BF16, DT_FLOAT16, DT_FLOAT})) .INPUT(dy, TensorType({DT_BF16, DT_FLOAT16, DT_FLOAT})) .OUTPUT(dx, TensorType({DT_BF16, DT_FLOAT16, DT_FLOAT})) .ATTR(eps, Float, -1.0) .OP_END_FACTORY_REG(LogitGrad)该注册表明输入x、dy的类型为 BF16/FP16/FP32输出dx类型一致eps为 float 类型属性且默认 -1.0与 README 参数说明完全吻合。构造计算图时将x、dy挂为输入节点dx作为输出节点并可为LogitGrad节点设置eps属性后编译下发。aclnnLogitGradGetWorkspaceSize 参数与返回值第一段接口参数说明如下详见 aclnnLogitGrad.md参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 TensorxaclTensor*输入输入的张量公式中的 x支持空 TensorBFLOAT16、FLOAT16、FLOATND0-8√dyaclTensor*输入正向输出结果的梯度公式中的 dy支持空 Tensor数据类型、数据格式和 shape 与输入 x 保持一致BFLOAT16、FLOAT16、FLOATND0-8√epsdouble输入输入 input 的 epsilon 限制边界防止除 0 错误公式中的 eps建议值 -1----outaclTensor*输出输出张量公式中的输出 dx输出的数据类型、数据格式和 shape 与输入 x 保持一致BFLOAT16、FLOAT16、FLOATND0-8√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----第一段接口会完成入参校验出现以下场景时报错返回值语义可参考 aclnn 返回码返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 x、dy 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002x、dy 或 out 的数据类型不在支持的范围之内第二段接口aclnnLogitGrad的参数中workspace为在 Device 侧申请的 workspace 内存地址workspaceSize由第一段接口返回executor为第一段接口生成的 op 执行器stream指定执行任务的 Stream。完整调用示例以下示例来自 examples/test_aclnn_logit_grad.cpp演示了从初始化、构造张量到两段式调用、取回结果的完整流程。示例代码中xShape {1, 4}输入x {0.1, 0.2, 0.3, 0.4}dy {1.0, 2.0, 3.0, 4.0}eps -1.0输出dx的 shape 同样为{1, 4}#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_logit_grad.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; } // 将 device 侧结果拷回 host 并打印 void PrintOutResult(std::vectorint64_t shape, void** deviceAddr) { auto size GetShapeSize(shape); std::vectorfloat resultData(size, 0); auto ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), *deviceAddr, 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); for (int64_t i 0; i size; i) { LOG_PRINT(mean result[%ld] is: %f\n, i, resultData[i]); } } 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 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 xShape {1, 4}; std::vectorint64_t dyShape {1, 4}; std::vectorfloat xHostData {0.1, 0.2, 0.3, 0.4}; std::vectorfloat dyHostData {1.0, 2.0, 3.0, 4.0}; void* xDeviceAddr nullptr; void* dyDeviceAddr nullptr; aclTensor* x nullptr; aclTensor* dy nullptr; ret CreateAclTensor(xHostData, xShape, xDeviceAddr, aclDataType::ACL_FLOAT, x); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dyHostData, dyShape, dyDeviceAddr, aclDataType::ACL_FLOAT, dy); CHECK_RET(ret ACL_SUCCESS, return ret); double eps -1.0; std::vectorint64_t dxShape {1, 4}; std::vectorfloat dxHostData(4, 1); aclTensor* dx nullptr; void* dxDeviceAddr nullptr; ret CreateAclTensor(dxHostData, dxShape, dxDeviceAddr, aclDataType::ACL_FLOAT, dx); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用 CANN 算子库 API uint64_t workspaceSize 16 * 1024 * 1024; aclOpExecutor* executor; // 调用 aclnnLogitGrad 第一段接口 ret aclnnLogitGradGetWorkspaceSize(x, dy, eps, dx, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogitGradGetWorkspaceSize 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); } // 调用 aclnnLogitGrad 第二段接口 ret aclnnLogitGrad(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogitGrad 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 侧 PrintOutResult(dxShape, dxDeviceAddr); // 6. 释放 aclTensor aclDestroyTensor(x); aclDestroyTensor(dy); aclDestroyTensor(dx); // 7. 释放 device 资源 aclrtFree(xDeviceAddr); aclrtFree(dyDeviceAddr); aclrtFree(dxDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例的执行流程可归纳为七个步骤①aclInit/aclrtSetDevice/aclrtCreateStream初始化② 通过aclrtMallocaclrtMemcpyaclCreateTensor构造输入输出aclTensor③ 先调用aclnnLogitGradGetWorkspaceSize获取 workspace 大小与 executor按需申请 workspace 内存再调用aclnnLogitGrad下发任务④aclrtSynchronizeStream同步等待⑤ 拷贝结果回 host 侧打印⑥ 释放aclTensor⑦ 释放 device 内存与 stream。示例中workspaceSize初始值仅为占位实际应由第一段接口返回的数值覆盖后再申请内存。示例编译与运行的具体步骤可参考 编译与运行样例。源码实现深度解析算子定义与 shape 推导在 logit_grad_def.cpp 中LogitGrad类继承OpDef完成注册x、dy两个 REQUIRED 输入与输出dx均限定{DT_BF16, DT_FLOAT16, DT_FLOAT}三种类型、FORMAT_ND格式并设置AutoContiguous()eps为 OPTIONAL 属性默认 -1.0。shape 推导逻辑见 logit_grad_infershape.cppInferShape4LogitGrad将输出dx的维度数与 shape 直接赋值为输入x的 shape*dxShape *xShapeInferDataType4LogitGrad将输出数据类型设置为输入x的数据类型这与接口文档中输出与输入 x 的数据类型、格式、shape 保持一致的约束完全对应也解释了为什么示例中dxShape与xShape相同。Tiling 策略Tiling 代码见 logit_grad_tiling.cpp其关键逻辑包括tilingKey 按数据类型区分FP16 对应TILING_KEY_HALF 1FP32 对应TILING_KEY_FLOAT 2BF16 对应TILING_KEY_BFLOAT16 3通过tilingContext-SetTilingKey(tilingKey)通知 kernel 选择对应模板实例核数分配以MAX_ELEMENT_NUM_EACH_CORE 8 * 1024为每核最大元素数needCoreNum CeilDiv(inputShapeSize, 8192)但不超过平台 AIV 核数GetCoreNumAiv()空 tensor 时至少分配 1 核workspace 大小固定为16 * 1024 * 102416MB通过tilingContext-GetWorkspaceSizes(1)写入tiling 数据LogitGradTilingData见 logit_grad_tiling.h包含elementNum、epsfloat、needCoreNum三个字段其中eps通过attrs-GetFloat(0)从运行时属性中取出后写入 tiling 数据供 kernel 端读取。Kernel 实现Kernel 入口见 logit_grad.cpp根据TILING_KEY分别实例化LogitGradNDhalf、LogitGradNDfloat、LogitGradNDbfloat16_t其中 fp16 与 bf16 实例仅在 AICore 版本为 220/310对应 A2/A3 平台时编译。核心计算类LogitGradNDT见 logit_grad.h的实现要点Init 阶段按 eps 符号预置参数eps 0时epslion eps、selectValue 0.0eps 0时epslion 0.0、selectValue sqrt(-1.0)即 NaN。这正好对应公式中eps 非负时越界输出 0eps 为负时越界输出 NaN的两个分支边界判断被收敛为一次Select操作Process 阶段多核切分以PP_ELEMENT_NUM 8 * 1024元素为一个处理块按块粒度在多核间均分余数由最后一个核处理并使用EVENT_ID0/EVENT_ID1双缓冲ping-pong重叠搬入与计算ComputeStepOne 计算主公式依次执行tmp -x、tmp tmp 1、tmp x * tmp、dy dy / tmp即完成dx dy / (x * (1 - x))的标量运算中间结果以 FP32 精度计算后再回写避免低精度累积误差ComputeStepTwo 实现边界选择用CompareScalar分别生成x lolo epslion与x hihi 1 - epslion两个比较掩码And合并后用Select在计算结果与selectValue0 或 NaN之间按掩码选取一条指令完成公式的边界分支310 平台融合路径针对 AICore 310Atlas A3为 fp16/bf16 提供了ComputeFusedFp16/ComputeFusedBf16寄存器级融合实现__VEC_SCOPE__AscendC::Reg将 Cast、Muls、Adds、Mul、Div、Compare、Select 串成单循环的向量流水减少中间访存。整体而言LogitGrad 采用通用 ND 主路径 310 平台寄存器融合路径的双实现策略既保证了跨平台可移植性又为 A3 平台提供了更高性能的选择。测试与验证仓库在 tests 目录下提供了完整的测试体系UTop_host 侧test_logit_grad_infershape.cpp 与 test_logit_grad_tiling.cpp 分别验证 shape 推导与 tiling 参数生成UTop_kernel 侧test_logit_grad.cpp 配合 gen_data.py 生成测试数据、compare_data.py 比对结果STst 场景atk_aclnnLogitGrad.json 定义 ATK 用例、executor_aclnnLogitGrad.py 实现执行器从 aclnn 接口层面对算子进行端到端验证。这些测试覆盖了公式边界eps 符号分支、x 越界、Inf/NaN 输入等与多核 tiling 切分场景是验证 README 中约束说明与 kernel 实现一致性的直接依据。总结LogitGrad 是 CANN ops-nn 中负责 Logit 反向传播的逐元素算子通过dx dy / (x * (1 - x))结合 eps 边界控制非负 eps 越界置 0、负 eps 越界置 NaN实现数值安全的梯度回传。本文从 README 出发完整覆盖了产品支持矩阵、参数表、边界约束、两段式 aclnn 接口调用与图模式构图方式并结合 op_host/op_kernel 源码剖析了算子注册、shape 推导、tiling 切分与 AscendC kernel 的边界选择实现。开发者可参照 调用示例 快速上手也可通过 接口文档 与 测试用例 进一步验证边界行为将该算子正确集成到训练/推理的反向传播链路中。赞分享人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载相关推荐CANN ops-nn 算子实战aclnnLogitGrad 两段式接口实现 Logit 反向传播CANN ops nn 算子实战aclnnLogitGrad 两段式接口实现 Logit 反向传播 本篇文章以 CANN 开源神经网络算子库 ops nn 中人工智能算子库深度学习CANNAscendCANN ops-nn aclnnSiluBackward 接口详解Silu 反向传播算子原理、两段式调用与 NPU 源码实现CANN ops nn aclnnSiluBackward 接口详解Silu 反向传播算子原理、两段式调用与 NPU 源码实现 本篇技术指南围绕 CANN 神人工智能算子库深度学习CANNAscendCANN ops-nn aclnnThresholdBackward 算子接口详解Threshold 反向传播的两段式调用与源码实现CANN ops nn aclnnThresholdBackward 算子接口详解Threshold 反向传播的两段式调用与源码实现 aclnnThresho人工智能算子库深度学习CANNAscend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
