ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CANN ops-nn 算子库 aclnnNLLLoss2d 接口详解:负对数似然损失(NLLLoss2d)的昇腾 NPU 计算指南

CANN ops-nn 算子库 aclnnNLLLoss2d 接口详解:负对数似然损失(NLLLoss2d)的昇腾 NPU 计算指南 人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载本篇技术指南以 CANN ops-nn 算子库中 aclnnNLLLoss2d 接口文档 为核心系统讲解负对数似然损失NLL Loss二维变体在昇腾 NPU 上的两段式调用方式、参数约束、错误码语义与完整示例代码并结合仓库源码op_api / op_host / op_kernel / 单元测试揭示其底层实现原理。读完本文你将掌握如何在 CANN 环境中通过 aclnnNLLLoss2d 完成 NLLLoss2d 前向计算并理解其 shape 推导、数据流转与 kernel 计算细节。一、产品支持情况aclnnNLLLoss2d 接口在以下昇腾产品上具备不同的支持状态对应源码目录 loss/nll_loss产品支持情况Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持Atlas 训练系列产品支持从算子注册源码 nll_loss_def.cpp 可以看到NLLLoss 算子的 AICore 配置针对ascend950与ascend350A3 系列对应架构分别注册并开启了动态编译DynamicCompileStaticFlag、动态 rankDynamicRankSupportFlag与动态 shapeDynamicShapeSupportFlag支持同一份实现同时覆盖了 910B/A2、910/A、310P 等系列产品。二、功能说明与计算公式接口功能计算负对数似然损失值。与 PyTorch 的F.nll_loss对齐aclnnNLLLoss2d 面向 4D 输入N, C, H, W的二维损失计算。2.1 reduction 为none时$$ \ell(x, y) L {l_1,\dots,l_N}^\top, \quad l_n - w_{y_n} x_{n,y_n}, \quad w_{c} \text{weight}[c] \cdot \mathbb{1}{c \not \text{ignoreIndex}}, $$其中 $x$ 是self$y$ 是target$w$ 是weight$N$ 是 batch 的大小。2.2 reduction 为mean/sum时$$ \ell(x, y) \begin{cases} \sum_{n1}^N \frac{1}{\sum_{n1}^N w_{y_n}} l_n, \text{if reduction} \text{mean}\ \sum_{n1}^N l_n, \text{if reduction} \text{sum} \end{cases} $$同时仓库 README.md 补充给出了 totalWeight 的定义$$ totalWeight \sum_{n1}^N w_{y_n} $$即totalWeightOut输出的是按 target 索引取出的所有 weight 之和。特别地当reduction mean且某样本的权重为 0 时kernel 计算结果为NAN见 nll_loss_simt.h 中curWeight 0分支这与 PyTorch 语义保持一致。三、两段式接口架构每个算子分为两段式接口详见 两段式接口说明必须先调用aclnnNLLLoss2dGetWorkspaceSize获取计算所需 workspace 大小以及包含了算子计算流程的执行器aclOpExecutor再调用aclnnNLLLoss2d执行计算。这种设计将计算资源评估与实际执行解耦允许用户在拿到 workspace 大小后自行管理 Device 侧内存。四、函数原型aclnnStatus aclnnNLLLoss2dGetWorkspaceSize( const aclTensor *self, const aclTensor *target, const aclTensor *weight, int64_t reduction, int64_t ignoreIndex, aclTensor *out, aclTensor *totalWeightOut, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnNLLLoss2d( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)函数声明位于 aclnn_nll_loss2d.h属于aclnn_ops_train领域接口。五、aclnnNLLLoss2dGetWorkspaceSize 参数详解下表完整列出第一段接口的 9 个参数参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入待进行计算的张量输入公式中的 xshape 为 4 维第 2 维是 CC 表示类别数FLOAT、FLOAT16、BFLOAT16ND4√targetaclTensor*输入表示真实标签公式中的 yshape 为 3 维target 的第 1 维与 self 的第 1 维相等、第 2 维与 self 的第 3 维相等、第 3 维与 self 的第 4 维相等每个元素取值范围是 [0, C - 1]INT64、UINT8、INT32ND3√weightaclTensor*输入表示每个类别的缩放权重公式中的 wshape 为 (C,)数据类型和 self 保持一致ND(C,)√reductionint64_t输入指定要应用到输出的缩减支持 0(none) / 1(mean) / 2(sum)none 表示不应用缩减mean 表示输出的总和将除以输出中的元素数sum 表示输出将被求和INT64---ignoreIndexint64_t输入指定一个被忽略且不影响输入梯度的目标值-INT64---outaclTensor*输出公式中的 out当 reduction 为 0none时shape 与 target 的 shape 相同否则为 (1,)数据类型和 self 保持一致ND--totalWeightOutaclTensor*输出公式中的 totalWeightOut在 reduction 为非 0非 none下输出值有效shape 为 (1,)数据类型和 self 保持一致ND--workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----5.1 参数校验的源码级印证从 aclnn_nll_loss2d.cpp 的CheckShape2D实现可以看到文档中 shape 约束的落地self必须是 4DselfDimNum 4target必须是 3Dweight[0]必须等于self[1]类别数 Cself[0] target[0]、self[2] target[1]、self[3] target[2]必须同时成立否则报size mismatch got input...错误reduction 0时要求out与targetshape 相等否则要求out的元素数为 1totalWeightOut元素数必须为 1。同时 nll_loss_infershape.cpp 的 shape 推导也验证了4D 输入 → 3D 输出的规则当 reduction 为none且 x 为 [N, C, H, W] 时输出 y 为 [N, H, W]reduction 为mean/sum时输出为标量。六、返回值与错误码aclnnStatus返回状态码的完整说明参见 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、target、weight、out、totalWeightOut 为空指针ACLNN_ERR_PARAM_INVALID161002self、target、weight、out 或 totalWeightOut 的数据类型或数据格式不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002self、weight、out 或 totalWeightOut 的数据类型不一致ACLNN_ERR_PARAM_INVALID161002self、target、weight、out、totalWeightOut 的 shape 和 format 不正确ACLNN_ERR_PARAM_INVALID161002reduction 值不在 0~2 范围之内源码中 CheckParams 依序执行空指针检查CheckNotNull2D见第 61-71 行、数据类型检查CheckDtypeValid2D见第 82-98 行self/weight/out 类型需一致、target 仅支持 INT64/UINT8/INT32、并校验结果可强转、reduction 范围检查CheckReduction2D见第 104-112 行reduction 必须在 [0, 2] 区间以及 shape 检查。七、aclnnNLLLoss2d 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnNLLLoss2dGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream第二段接口实现极简直接调用CommonOpExecutorRun提交任务见 aclnn_nll_loss2d.cpp。八、约束说明确定性计算Atlas A3 训练/推理系列产品、Atlas A2 训练/推理系列产品、Atlas 200I/500 A2 推理产品、Atlas 推理系列产品、Atlas 训练系列产品aclnnNLLLoss2d默认非确定性实现支持通过aclrtCtxSetSysParamOpt开启确定性。Ascend 950PR / Ascend 950DTaclnnNLLLoss2d默认确定性实现。九、调用示例完整可运行示例代码取自仓库 test_aclnn_nll_loss_2d.cpp与文档示例一致完整演示了两段式调用全流程。具体编译与执行过程请参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_nll_loss2d.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初始化参考acl API手册 // 根据自己的实际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. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t selfShape {1, 2, 3, 2}; std::vectorint64_t targetShape {1, 3, 2}; std::vectorint64_t weightShape {2}; std::vectorint64_t outShape {1, 3, 2}; std::vectorint64_t totalWeightOutShape {1}; void* selfDeviceAddr nullptr; void* targetDeviceAddr nullptr; void* weightDeviceAddr nullptr; void* outDeviceAddr nullptr; void* totalWeightOutDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* target nullptr; aclTensor* weight nullptr; aclTensor* out nullptr; aclTensor* totalWeightOut nullptr; std::vectorfloat selfHostData {0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1, 9.1, 10.1, 11.1}; std::vectorint32_t targetHostData {1, 0, 1, 1, 2, 1}; std::vectorfloat weightHostData {1.1, 1.2}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0}; std::vectorfloat totalWeightOutHostData {0}; int64_t reduction 0; int64_t ignoreIndex -100; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建target aclTensor ret CreateAclTensor(targetHostData, targetShape, targetDeviceAddr, aclDataType::ACL_INT32, target); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建weight aclTensor ret CreateAclTensor(weightHostData, weightShape, weightDeviceAddr, aclDataType::ACL_FLOAT, weight); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建totalWeightOut aclTensor ret CreateAclTensor(totalWeightOutHostData, totalWeightOutShape, totalWeightOutDeviceAddr, aclDataType::ACL_FLOAT, totalWeightOut); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnNLLLoss2d第一段接口 ret aclnnNLLLoss2dGetWorkspaceSize(self, target, weight, reduction, ignoreIndex, out, totalWeightOut, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnNLLLoss2dGetWorkspaceSize 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); } // 调用aclnnNLLLoss2d第二段接口 ret aclnnNLLLoss2d(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnNLLLoss2d 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(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需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(target); aclDestroyTensor(weight); aclDestroyTensor(out); aclDestroyTensor(totalWeightOut); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(targetDeviceAddr); aclrtFree(weightDeviceAddr); aclrtFree(outDeviceAddr); aclrtFree(totalWeightOutDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }9.1 示例数据手算校验以上示例中selfShape {1, 2, 3, 2}N1, C2, H3, W2targetShape {1, 3, 2}reduction 0noneignoreIndex -100。逐元素计算l_n -w[y_n] * x[n, y_n, ...]位置 (0,0)target1x[0,1,0,0]1.1weight[1]1.2 → -1.32位置 (0,1)target0x[0,0,0,1]1.1即 selfHostData[1]weight[0]1.1 → -1.21位置 (0,2)target1x[0,1,1,0]3.1selfHostData[4]weight[1]1.2 → -3.72位置 (0,3)target1x[0,1,1,1]4.1weight[1]1.2 → -4.92位置 (0,4)target2x[0,2,2,0]10.1selfHostData[10]weight[2] 不存在但 C2此处 target 越界提示需保证 target ∈ [0, C-1]位置 (0,5)target1x[0,1,2,1]8.1selfHostData[7]weight[1]1.2 → -9.72可见 target 的每个元素直接决定了从 self 的哪个类别通道取值这正是 NLLLoss 的按标签取 log-probability 再取负语义在 2D 场景下的体现。测试用例 test_aclnn_nll_loss2d_l2.cppcase_002即构造了带ignoreIndex 0、weight 含 0 值的场景做精度比对Precision 0.0001。十、底层实现原理从 aclTensor 到 kernel 的完整调用链10.1 第一段接口的计算图组装aclnn_nll_loss2d.cpp 展示了aclnnNLLLoss2dGetWorkspaceSize内部如何将用户输入编排为 l0 级算子链空 Tensor 特判若self、target、weight任一为空走 NLLLossEmptyTensorComputenone模式直接成功mean模式将 out 填NANsum模式将 out 填0totalWeightOut 填0workspaceSize 置 0类型提升promoteType2D在非 regbase 平台将 BF16 保持 BF16、其余提升为 FLOAT见第 248-253 行连续性处理l0op::Contiguous将 self/target/weight 转为连续 tensor第 256-269 行布局转换非 regbaseReFormat转 ND → 若 C1 则Transpose按{0, 2, 3, 1}置换第 281-291 行将 [N,C,H,W] 变为 [N,H,W,C]→Reshape为 [-1, C] 的 2D 输入target 展平为一维第 293-301 行核心计算调用l0op::NLLLoss(selfReshape, targetCasted, weightCasted, reductionStr, ignoreIndex, executor)其中 reduction 字符串由 GetReductionStr2d 将 0/1/2 映射为none/mean/sum第 311-314 行结果整形与拷贝none模式将一维 loss reshape 回 [N,H,W]最终Cast到 out 的数据类型并ViewCopy到可能非连续的 out 上非 none 模式额外将 totalWeight 拷入 totalWeightOut第 343-356 行返回 workspace 大小*workspaceSize uniqueExecutor-GetWorkspaceSize()第 359 行随后把执行器释放给调用方。10.2 Kernel 侧的 SIMT 计算算子内核实现位于 nll_loss_simt.h核心计算逻辑如下SimtComputeNLLLoss2dNone第 100-115 行按网格步长循环每个样本若target ignoreIndex则输出 0 并跳过否则通过TargetCheck断言 target ∈ [0, C)计算y -curWeight * x[i*C target]SimtComputeNLLLoss2dSum第 117 行起累加各样本的加权负对数似然并通过 SimtComputeBinaryReduction 做线程块内二分归约求和mean 模式见第 87-92 行逻辑当curWeight 0时输出 NAN否则输出-x[targetIndex]mean 的除法在归约后完成。kernel 使用THREAD_DIM 512的 SIMT 并行维度第 39 行并区分了REDUCTION_NONE/MEAN/SUM三种编译期分派第 40-42 行。10.3 算子定义与动态 shape 支持算子注册文件 nll_loss_def.cpp 定义了 NLLLoss 的输入x、target、weightweight 为 OPTIONAL、输出y、total_weight、属性reduction字符串默认meanignore_index整型默认 -100。其中 weight 为可选输入——kernel 中通过isWeightPresent_标志判断若未提供则按权重 1 计算见 nll_loss_simt.h。infershape 实现 nll_loss_infershape.cpp 同时支持 1D、2D、4D 三种输入形态分别对应一维 NLLLoss、二维 NLLLoss 与 2d 变体是 aclnnNLLLoss2d 与 aclnnNLLLoss 共用同一底层算子的证据。十一、测试验证体系仓库在 tests/ut/op_api/test_aclnn_nll_loss2d_l2.cpp 中提供了覆盖 27 场景的 L2 级单元测试可作为二次开发的参考精度场景case_001~009、015、016、026、027、029覆盖 ND/NCHW/NHWC/HWCN/NDHWC/NCDHW 多种 format、reduction 0/1/2、FLOAT/FLOAT16、INT32/INT64 target、不同 batch 与类别数、含 ignoreIndex 与 0 权重等组合均通过TestGetWorkspaceSizeTestPrecision双重校验异常入参case_010~012、023~025、029INT16/INT8 等不支持的数据类型、self 非 4D、weight 类别数与 C 不匹配、reduction3 越界等断言返回ACLNN_ERR_PARAM_INVALID空指针case_018~022self/target/weight/out/totalWeightOut 任一为 nullptr 时断言返回ACLNN_ERR_PARAM_NULLPTR与文档错误码表完全对应空 Tensorcase_017shape 含 0 维如 {3, 2, 0, 2}时仍能正常完成 GetWorkspaceSize 与精度测试对应第一段接口的空 tensor 特判分支。十二、与一维 NLLLoss 的关系同目录下的 aclnnNLLLoss 文档 描述一维变体self 为 [N, C] 或 [C]而本接口处理 4D 输入。两者共享同一算子定义nll_loss_def.cpp与同一套 kernel 实现aclnnNLLLoss2d 内部通过 Transpose Reshape 将 4D 输入规整为 2D 后复用一维计算路径见 aclnn_nll_loss2d.cpp最终再恢复为 [N, H, W] 输出。这种上层适配、底层复用的架构在需要同时支持多形态输入时非常值得借鉴。十三、常见使用要点小结shape 对应关系self为 [N, C, H, W] 时target必须为 [N, H, W]weight为 [C]none模式下out为 [N, H, W]其余模式下out与totalWeightOut均为 (1,)数据类型self支持 FLOAT/FLOAT16/BFLOAT16910B 及更新平台含 BF16见 aclnn_nll_loss2d.cpp 的平台差异化支持列表target支持 INT64/UINT8/INT32weight、out、totalWeightOut与self保持一致target 取值范围每个元素必须在 [0, C-1] 内kernel 层有TargetCheck断言若等于ignoreIndex默认 -100则该样本 loss 记为 0 且不参与 totalWeight 统计非连续 Tensorself/target/weight 均支持非连续输入第一段接口内部会先做 Contiguous 再计算内存管理workspace 需按第一段接口返回的大小在 Device 侧申请aclrtMalloc计算结束释放out结果需通过aclrtMemcpy从 Device 拷回 Host 查看确定性除 Ascend 950 系列默认确定性实现外其余平台默认非确定性如需可复现结果请通过aclrtCtxSetSysParamOpt开启确定性计算。赞分享人工智能算子库深度学习CANNAscend【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-nn点击查看免费下载相关推荐CANN ops-nn 负对数似然损失反向算子 aclnnNLLLoss2dBackward 接口详解与实战CANN ops nn 负对数似然损失反向算子 aclnnNLLLoss2dBackward 接口详解与实战 导读 本文围绕 CANN ops nn 开源仓库人工智能算子库深度学习CANNAscendCANN ops-nn 算子详解aclnnNLLLoss 负对数似然损失NLL Loss两段式接口开发指南CANN ops nn 算子详解aclnnNLLLoss 负对数似然损失NLL Loss两段式接口开发指南 导读 aclnnNLLLoss 是 CANN人工智能算子库深度学习CANNAscendCANN ops-nn NllLossGrad 算子完全指南负对数似然损失反向传播原理、接口与 NPU 调用实战CANN ops nn NllLossGrad 算子完全指南负对数似然损失反向传播原理、接口与 NPU 调用实战 导读 NllLossGrad 是 CANN人工智能算子库深度学习CANNAscend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表