ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CANN ops-nn aclnnNLLLoss 算子详解:负对数似然损失的两段式接口原理、参数校验与调用示例

CANN ops-nn aclnnNLLLoss 算子详解:负对数似然损失的两段式接口原理、参数校验与调用示例 CANN ops-nn aclnnNLLLoss 算子详解负对数似然损失的两段式接口原理、参数校验与调用示例【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nnaclnnNLLLoss 是 CANN ops-nn 神经网络算子库中实现负对数似然损失Negative Log Likelihood LossNLL Loss计算的算子接口用于分类任务中计算模型输出与真实标签之间的损失值。本文基于仓库中 aclnnNLLLoss.md 文档结合算子源码、tiling 逻辑与测试用例系统讲解该算子的产品支持范围、数学原理、两段式接口函数原型、完整参数约束与返回值语义并给出可复制运行的 C 调用示例。读者读完后将掌握 aclnnNLLLoss 从接口声明、参数校验到底层计算流程的完整链路能够独立完成该算子在实际工程中的集成与调优。产品支持情况根据 aclnnNLLLoss.md 文档与 NLLLoss READMEaclnnNLLLoss 在不同产品形态上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持Atlas 训练系列产品支持从源码角度看该算子在 nll_loss_def.cpp 中通过AICore().AddConfig(ascend950, aicoreConfig)与AICore().AddConfig(ascend350, aicoreConfig)注册了 AICore 计算配置其中aicoreConfig开启了动态编译、动态 Rank 与动态 Shape 支持DynamicCompileStaticFlag(true).DynamicRankSupportFlag(true).DynamicShapeSupportFlag(true)与文档所列产品范围相互印证。功能说明与计算公式接口功能aclnnNLLLoss 的接口功能为计算负对数似然损失值。NLL Loss 是分类任务尤其是 Softmax 之后的交叉熵等效形式中最常用的损失函数之一输入为未归一化的对数概率张量self公式中的x与真实类别标签target公式中的y可选输入weight用于对每个类别施加缩放权重。计算公式当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是 selfy是 targetw是 weightN是 batch 大小。当reduction不是none时$$ \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} $$同时会输出归一化分母即有效样本权重之和$$ totalWeight \sum_{n1}^N w_{y_n} $$需要特别注意的是公式中的指示函数 $\mathbb{1}{c \not \text{ignoreIndex}}$ 体现了ignoreIndex的核心作用被标记为 ignoreIndex 的类别对应样本权重被置为 0从而在mean模式下不参与分母累加、在sum模式下贡献为 0且该部分不参与输入梯度计算。默认的 ignoreIndex 取值为-100见 nll_loss_def.cpp 中的DEFAULT_IGNORE_IDX。函数原型两段式接口每个算子采用两段式接口设计参见 two_phase_api.md必须先调用aclnnNLLLossGetWorkspaceSize获取计算所需 workspace 大小及包含算子计算流程的执行器再调用aclnnNLLLoss执行计算aclnnStatus aclnnNLLLossGetWorkspaceSize( 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 aclnnNLLLoss( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)对应的头文件声明位于 aclnn_nll_loss.h其中第一段接口的注释详细标注了domain aclnn_ops_train表明该算子归属于训练领域算子集合。从源码注释可知workspaceSize返回用户需要在 NPU Device 侧申请的 workspace 大小executor返回包含算子计算流程的 op 执行器两者均服务于第二段接口的异步执行。aclnnNLLLossGetWorkspaceSize参数说明第一段接口共 9 个参数完整说明如下参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入表示输入张量-FLOAT、FLOAT16、BFLOAT16ND(N,C) 或者 (C)N 表示 batch sizeC 表示类别数√targetaclTensor*输入表示真实标签当 self 的 shape 为 (N,C) 时target 的 shape 为 (N)当 self 的 shape 为 (C) 时target 的 shape 为 ()其中每个元素的取值范围是 [0, C - 1]INT64、UINT8、INT32ND-√weightaclTensor*输入表示每个类别的缩放权重-FLOAT、FLOAT16、BFLOAT16ND(C)√reductionint64_t输入表示要应用到输出的缩减支持 0(none)|1(mean)|2(sum)。none 表示不应用缩减mean 表示输出的总和将除以输出中的元素数sum 表示输出将被求和INT64---ignoreIndexint64_t输入表示一个被忽略且不影响输入梯度的目标值-INT64---outaclTensor*输出公式中的 out当 reduction 为 0none且 self 的 shape 为 2 维时out shape 为 (N,)否则为 (1,)FLOAT、FLOAT16、BFLOAT16ND--totalWeightOutaclTensor*输出公式中的 totalWeightOut在 reduction 为非 0非 none下输出值有效shape 为 (1,)FLOAT、FLOAT16、BFLOAT16ND--workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----参数校验的源码实现上述参数约束并非仅停留在文档层面而是在 aclnn_nll_loss.cpp 的CheckParams中逐项落地校验顺序依次为空指针检查CheckNotNullself、target、weight、out、totalWeightOut任一为空即返回ACLNN_ERR_PARAM_NULLPTR数据类型检查CheckDtypeValid要求weight与self数据类型一致out、totalWeightOut可从self类型转换得到同时按芯片架构区分支持列表——Ascend 910B 系列DAV_2201、DAV_3510支持 FLOAT/FLOAT16/BF16Ascend 910DAV_1001仅支持 FLOAT/FLOAT16见 GetDtypeSupportListSocVersionreduction 范围检查CheckReduction必须在 0~2 之间否则报ACLNN_ERR_PARAM_INVALIDshape 检查CheckShapeself 必须为 1D 或 2Dtarget 为 0D 或 1D当 self 为 1D 时 target 必须为 0D当 self 为 2D 时 target 必须为 1D 且两者第 0 维相等weight 必须为一维且长度等于类别数 Creduction 为 none 且 self 为 2D 时 out 必须为 (N,)其余情形 out 必须为单元素totalWeightOut 必须为 (1,) 形状。此外CheckFormat会对FRACTAL_NZ格式的 self 打印告警日志提示该格式可能导致精度问题——这也解释了文档中要求数据格式为 ND 的原因。对于空张量self-IsEmpty()的特殊场景会走NLLLossEmptyTensorCompute分支reductionmean时 out 填充 NANreductionsum时 out 填充 0totalWeightOut填充 0见 aclnn_nll_loss.cpp与 PyTorch 对空输入 NLL Loss 的行为保持一致。返回值与错误码aclnnStatus返回状态码具体参见 aclnn_return_code.md。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、target、weight、out、totalWeightOut 为空指针ACLNN_ERR_PARAM_INVALID161002self、target、weight 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002self、weight 的数据类型不一致ACLNN_ERR_PARAM_INVALID161002self、weight、out、totalWeightOut 的 shape 不正确ACLNN_ERR_PARAM_INVALID161002reduction 值不在 0~2 范围之内aclnnNLLLoss参数说明第二段接口共 4 个参数是真正的计算执行入口参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnNLLLossGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream其实现非常简洁aclnnNLLLoss内部通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)调用框架能力完成异步计算见 aclnn_nll_loss.cpp。这意味着第一段接口负责构图与资源规划第二段接口负责提交执行二者配合实现先规划、后执行的两段式异步模型。第一段接口内部的算子链路在aclnnNLLLossGetWorkspaceSize的CheckParams通过之后源码依次完成以下构图步骤见 aclnn_nll_loss.cpp类型提升非 regbase 模式下self 与 weight 统一提升为 FLOATBF16 保持 BF16target 统一转为 INT64非 INT64 转 INT32随后通过l0op::Cast完成转换连续化对 self、target、weight 分别调用l0op::Contiguous处理非连续 Tensor——这正是参数表中非连续 Tensor 支持 √的实现基础维度规整当 self 为一维时通过UnsqueezeNd升维便于统一进入底层l0op::NLLLoss计算计算后对 reduction 为 none 的一维场景再SqueezeNd还原核心计算调用l0op::NLLLoss(selfReshape, targetCasted, weightCast, GetReductionStrLoss(reduction), ignoreIndex, executor)其中GetReductionStrLoss将 0/1/2 映射为字符串 none/mean/sum见 aclnn_nll_loss.cpp结果写回通过CastViewCopy将 loss 与 totalWeightreduction 非 0 时转换并拷贝到用户输出张量兼容 out 为非连续 Tensor 的场景。算子定义与形状推导底层算子NLLLoss在图编译侧的注册信息位于 nll_loss_def.cpp输入x、target必选、weight可选输出y、total_weight必选属性reduction字符串默认 mean与ignore_index整型默认 -100。值得注意该算子定义层面将输入维数放宽到 1D/2D/4D4D 场景即 NLLLoss2daclnnNLLLoss2d接口对应见 aclnnNLLLoss2d.md。形状推导实现位于 nll_loss_infershape.cpp当 reduction 为 none 且 x 为 2D 时输出 y 为 (N,)x 为 4D 时输出为 (N,H,W)其余情形mean/sum 或 1D输出为标量。数据类型推导则限定 x 仅支持 FLOAT/FLOAT16/BF16、target 仅支持 INT32/INT64/UINT8输出与 x 同类型。约束说明确定性计算Atlas A3 / A2 / 200I/500 A2 / 推理系列 / 训练系列产品aclnnNLLLoss 默认非确定性实现支持通过aclrtCtxSetSysParamOpt开启确定性Ascend 950PR/Ascend 950DTaclnnNLLLoss 默认确定性实现。这一差异在 tiling 与 kernel 层有所体现。从 nll_loss_tiling_arch35.cpp 可以看到tiling 阶段定义了三种调度类型SIMT_TILINGKEY0、SIMD_TILINGKEY1与SCHID_EMPTY2空张量场景。调度策略选择逻辑为x 为 1D、reduction 为 none、2D 且 N 小于MIX_MODE_THRESHOD524288、或 x 为 4D 时走 SIMT 路径否则走 SIMD 归约路径见 nll_loss_tiling_arch35.cppSIMT 路径最多使用 512 线程THREAD_DIMSIMD 路径则按DATA_NUM_SINGLE_CORE256划分归约粒度并按 128 字节对齐补 padding。kernel 侧入口 nll_loss.cpp 根据 tilingKey 分发到KernelNLLLossSimt/KernelNLLLossSimd/KernelNLLLossEmpty三类实现。对sum/mean归约模式tiling 还会额外申请maxCoreNum * sizeof(float) * 2的 workspace 用于跨核归约见 nll_loss_tiling_arch35.cpp这就是第一段接口返回 workspaceSize 可能大于 0 的底层原因之一。调用示例以下示例代码来自 aclnnNLLLoss.md同源实现可参考仓库中的 test_aclnn_nll_loss.cpp。编译和执行过程请参考 compile_and_run_sample.md。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_nll_loss.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 {2, 3}; std::vectorint64_t targetShape {2}; std::vectorint64_t weightShape {3}; std::vectorint64_t outShape {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, 2, 3, 4, 5}; std::vectorint32_t targetHostData {0, 2}; std::vectorfloat weightHostData {1.1, 1.2, 1.3}; std::vectorfloat outHostData(2, 0); std::vectorfloat totalWeightOutHostData(1, 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; // 调用aclnnNLLLoss第一段接口 ret aclnnNLLLossGetWorkspaceSize(self, target, weight, reduction, ignoreIndex, out, totalWeightOut, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnNLLLossGetWorkspaceSize 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); } // 调用aclnnNLLLoss第二段接口 ret aclnnNLLLoss(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnNLLLoss 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; }示例要点解读示例构造的输入为selfShape{2,3}batch2、类别数3、targetShape{2}、weightShape{3}、reduction0none、ignoreIndex-100。由于 reduction 为 none 且 self 为 2D输出 out 的 shape 取(N,)即{2}对应文档中out 在 reduction 为 0 且 self 为 2 维时为 (N,)的规则target的每个元素取值必须在[0, C-1]范围内即 0、1、2示例中{0, 2}均合法workspace 的申请采用按需模式workspaceSize 0才调用aclrtMalloc避免无效的内存开销步骤 4 的aclrtSynchronizeStream必不可少保证异步任务执行完成后再从 Device 侧拷贝结果步骤 6、7 的资源释放顺序为先 Tensor 后 Device 内存与创建顺序严格对称。测试与验证仓库为该算子提供了完整的单测覆盖可作为自验证与二次开发的参考test_aclnn_nll_loss_l0.cpp 与 test_aclnn_nll_loss_l2.cpp覆盖 aclnn 接口在 L0/L2 级别的调用路径test_aclnn_nll_loss2d_l2.cpp对应 4D 输入的 NLLLoss2d 接口见 aclnnNLLLoss2d.mdtest_nll_loss_infershape.cpp验证不同 shape/reduction 组合下的输出形状推导test_nll_loss_tiling.cpp验证 arch35 平台 tiling 参数计算。此外loss/nll_loss/tests/assets/golden.py提供了 golden 数据生成脚本可用于将计算结果与标准 NLL Loss 语义进行对照确保算子数值行为符合预期。总结aclnnNLLLoss 是 CANN ops-nn 中实现负对数似然损失的标准接口覆盖了从 Ascend 910/310P 到 A2/A3 以及 950 系列的多种产品形态。本文从数学公式出发系统梳理了两段式接口的完整参数语义、错误码约定、确定性约束并结合源码揭示了参数校验、类型提升、tiling 调度与 kernel 分发的完整实现链路。配合文档内完整的调用示例与仓库测试用例开发者可以快速将该算子集成到训练脚本或推理流程中并能够依据错误码与 shape 规则进行高效的故障定位。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表