CANN ops-nn 中 LogSoftmaxV2 算子的原理、ACLNN 接口与实战指南
2026/9/20 5:50:57 网站建设 项目流程

CANN ops-nn 中 LogSoftmaxV2 算子的原理、ACLNN 接口与实战指南

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

导读

本文以 CANN ops-nn 开源仓库中experimental/activation/log_softmax_v2目录下的 README.md 与 aclnnLogSoftmaxV2.md 为核心,系统讲解 LogSoftmaxV2 算子的数学原理、参数约束、两段式 aclnn 接口调用流程,并结合仓库内算子定义、Tiling 与 Kernel 源码揭示其在 NPU 上的实现细节。读完本文,你将能够独立编写可运行的 C++ 调用样例,在 Atlas A2 训练系列产品 / Atlas 800I A2 推理产品上正确完成张量指定维度的对数 Softmax 计算。

一、算子概述

1.1 功能说明

LogSoftmaxV2 算子的功能是:对输入张量在指定维度上执行 LogSoftmax 归一化计算,即先对指定维度内的元素求 Softmax(指数归一化),再取自然对数,输出与输入形状相同的张量。

其计算公式为:

$$ out_i = input_i - \log\left(\sum_j \exp(input_j)\right) $$

其中下标j遍历指定归约轴上的所有元素。从公式可以看出,LogSoftmax 的实质是"减 LogSumExp":out_i等于当前元素值减去该归约轴上的对数指数和。

1.2 产品支持情况

产品是否支持
Atlas A2 训练系列产品 / Atlas 800I A2 推理产品

从 算子定义源码 可以看到,该算子在注册时通过AICore().AddConfig("ascend910b")声明其 AICore 配置,Ascend910B 系列即对应 Atlas A2 系列硬件平台,与文档中的产品支持声明相互印证。

1.3 应用场景

LogSoftmax 广泛用于分类网络的输出层与交叉熵损失的中间计算中。相较于直接计算 Softmax 后再取对数,LogSoftmax 将logsoftmax合并为一次归约运算,数值上更稳定,也减少了中间张量的访存开销。本项目中的log_softmax_v2同时提供了 L0 层 API(l0op::LogSoftmaxV2)与 aclnn 接口(aclnnLogSoftmaxV2),其中 aclnn 接口是应用侧推荐的调用方式。

二、参数说明

2.1 算子属性与输入输出(README 参数表)

参数名输入/输出/属性描述数据类型数据格式
axes输入(属性)指定的归约轴ListInt/
input输入待进行归约的 tensorFLOAT、FLOAT16、BFLOAT16ND
out输出归约的输出 tensorFLOAT、FLOAT16、BFLOAT16ND

补充说明(依据 算子定义源码):

  • inputout均声明为REQUIRED参数,数据类型三选一:DT_FLOATDT_FLOAT16DT_BF16,数据格式固定为FORMAT_ND,未知 shape 场景同样仅支持 ND 格式;
  • axes属性类型为OPTIONAL,默认值为{-1},即不显式指定时默认沿最后一个维度归约。Tiling 源码中也处理了axes == -1时映射到dims - 1的逻辑(见 log_softmax_v2_tiling.cpp)。

2.2 aclnn 接口参数(aclnnLogSoftmaxV2)

参数说明
self计算输入:Device 侧的aclTensor*,数据格式支持 ND,维度不超过 8 维,支持非连续 Tensor。Atlas A2 训练系列产品 / Atlas 800I A2 推理产品上数据类型支持 FLOAT32、FLOAT16、BFLOAT16
dim计算输入int64_t,指定进行 LogSoftmax 运算的维度,取值范围为[-rank, rank-1],其中rank是输入 Tensorself的维度
out计算输出:Device 侧的aclTensor*,数据格式支持 ND,shape 必须与self一致,维度不超过 8 维,支持非连续 Tensor。支持的数据类型同self
workspaceSize出参uint64_t*,返回需要在 Device 侧申请的 workspace 大小
executor出参aclOpExecutor**,返回算子执行器,包含算子计算流程

三、数值稳定性:为什么实际计算要减去最大值

文档 aclnnLogSoftmaxV2.md 明确指出:算子核心功能是沿指定轴进行归约(Reduce)计算,为保证数值计算稳定性,实际计算采用"减去最大值"的优化方法,其实现步骤涉及 Reduce 与 Broadcast 操作,最终等效于公式logsoftmax(x_i) = x_i - log(Σe^(x_j))

具体而言,直接计算log(Σ exp(x_j))在输入元素较大时exp(x_j)会溢出为无穷大。工程实现改为:

$$ out_i = (x_i - max) - \log\left(\sum_j e^{(x_j - max)}\right) $$

其中max为归约轴上的最大值。由于每个指数项都乘了公共因子e^{-max},数学上结果不变,但x_j - max ≤ 0,指数不会溢出,log参数始终落在(0, N]区间,从而保证了数值稳定性。

这一点同样体现在示例程序 test_aclnn_log_softmax_v2.cpp 的期望结果计算函数CalculateExpectedLogSoftmax中:该函数先在指定维度上寻找最大值maxVal,再用exp(input - maxVal)累加sumExp,最终以(input - maxVal) - log(sumExp)得到期望值,作为与 NPU 实际输出的对比基准。

四、两段式 aclnn 接口与调用流程

4.1 函数原型

aclnnLogSoftmaxV2是两段式接口,必须先调用aclnnLogSoftmaxV2GetWorkspaceSize获取 workspace 大小和执行器,再调用aclnnLogSoftmaxV2执行计算:

aclnnStatus aclnnLogSoftmaxV2GetWorkspaceSize( const aclTensor *self, int64_t dim, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor ); aclnnStatus aclnnLogSoftmaxV2( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream );

4.2 aclnnLogSoftmaxV2GetWorkspaceSize 的校验行为

返回值aclnnStatus状态码。

第一段接口完成入参校验,出现如下场景时报错:

  • 161001(ACLNN_ERR_PARAM_NULLPTR):传入的selfout是空指针。
  • 161002(ACLNN_ERR_PARAM_INVALID)
    1. selfout的数据类型不在支持范围之内;
    2. selfout的 shape 不一致;
    3. selfout的维度超过 8;
    4. dim参数的值超出有效范围[-rank, rank-1]

4.3 aclnnLogSoftmaxV2 执行段参数

参数说明
workspace入参void*,在 Device 侧申请的 workspace 内存地址
workspaceSize入参uint64_t,在 Device 侧申请的 workspace 大小,由第一段接口aclnnLogSoftmaxV2GetWorkspaceSize获取
executor入参aclOpExecutor*,op 执行器,包含算子计算流程
stream入参aclrtStream,指定执行任务的 Stream

4.4 完整调用流程

标准调用分为七个步骤,仓库文档 aclnnLogSoftmaxV2.md 给出了完整示例。这里结合文档示例并参照仓库可编译样例 test_aclnn_log_softmax_v2.cpp,梳理其完整骨架:

// 1. device/stream 初始化 int32_t deviceId = 0; aclrtStream stream; aclInit(nullptr); aclrtSetDevice(deviceId); aclrtCreateStream(&stream); // 2. 构造输入与输出 aclTensor(shape 均为 {2,3},dim=1 沿每行做 LogSoftmax) std::vector<int64_t> selfShape = {2, 3}; std::vector<int64_t> outShape = {2, 3}; int64_t dim = 1; // 用 aclCreateTensor 创建 self、out(数据格式 ACL_FORMAT_ND,含 strides) // 3. 第一段接口:获取 workspaceSize 与执行器 uint64_t workspaceSize = 0; aclOpExecutor* executor; aclnnLogSoftmaxV2GetWorkspaceSize(self, dim, out, &workspaceSize, &executor); // 4. 按 workspaceSize 申请 Device 内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // 5. 第二段接口:执行计算并同步 aclnnLogSoftmaxV2(workspaceAddr, workspaceSize, executor, stream); aclrtSynchronizeStream(stream); // 6. 拷贝输出到 Host 并打印 // 7. 释放 aclTensor、workspace、stream 等资源

对于输入{0, 1, 2, 5, 2, 1}dim=1的情况,文档给出的预期输出为:

{-2.4076, -1.4076, -0.4076, -0.0659, -3.0659, -4.0659}

例如第一行[0, 1, 2]log(exp(0)+exp(1)+exp(2)) ≈ 2.4076,因此out = [0-2.4076, 1-2.4076, 2-2.4076] = [-2.4076, -1.4076, -0.4076]

4.5 仓库样例:命令行参数化验证

仓库中的 test_aclnn_log_softmax_v2.cpp 是一个比文档示例更完整的可运行程序,支持命令行参数:

Usage: ./test_aclnn_log_softmax_v2 <dim> <shape> <dtype> dim: LogSoftmax dimension (0-based) shape: Comma-separated shape, e.g., "512,512" or "2,3,4" dtype: Data type: float32 or fp16 Examples: ./test_aclnn_log_softmax_v2 0 "512,512" float32 ./test_aclnn_log_softmax_v2 1 "2,3,4" fp16

该程序具备三个实用特性,可用于算子正确性验证:

  1. CPU 期望值计算CalculateExpectedLogSoftmax用纯 CPU 逻辑(最大值裁剪 + 指数累加 + 对数)计算期望输出;
  2. 结果自动比对VerifyResult以相对误差1e-3(fp16)/1e-4(fp32)为容差逐元素比对,全部通过时打印✓ All results are correct!
  3. 随机数据生成:使用std::uniform_real_distribution<float>(-1.0f, 1.0f)生成输入,避免手工数据覆盖不全。

注意该样例直接使用#include "aclnn_log_softmax.h"并调用aclnnLogSoftmaxGetWorkspaceSize/aclnnLogSoftmax(非 V2 命名),实际使用时应按接口文档包含正确的头文件(aclnn_log_softmax_v2.h)并调用 V2 版本接口。

五、源码级实现解析

5.1 算子注册与类型约束

log_softmax_v2_def.cpp 中通过OP_ADD(LogSoftmaxV2)注册算子定义:输入input与输出out均支持DT_FLOATDT_FLOAT16DT_BF16三种类型且限定 ND 格式,属性axes为可选ListInt,默认{-1}InferShapeInferDataType在 log_softmax_v2_infershape.cpp 中实现,由于输入输出 shape 保持一致,推断逻辑直接返回成功。

5.2 L0 API 的 AICore / AICpu 双路径调度

op_api/log_softmax_v2.cpp 实现了 L0 层算子入口l0op::LogSoftmaxV2,其调度策略如下:

  • AICORE_DTYPE_SUPPORT_LIST = {DT_FLOAT, DT_FLOAT16, DT_BF16}
  • IsAiCoreSupport(self)检查输入数据类型是否在该列表中;
  • 支持时走LogSoftmaxAiCore,通过ADD_TO_LAUNCHER_LIST_AICORE下发到 AI Core 执行;
  • 不支持(即非上述三种类型)时回退到LogSoftmaxAiCpu,通过ADD_TO_LAUNCHER_LIST_AICPU交由 AICPU 执行,此时属性名使用axes

这说明该算子具备 AI Core 与 AICPU 双后端能力,aclnn 层会根据输入数据类型自动选择合适的执行后端。

5.3 Tiling:维度重组与核数规划

log_softmax_v2_tiling.cpp 是性能调度的关键。其核心思想是把任意维度的归约问题重排为 2D/3D 形态,再按数据规模决定对齐策略与核数:

  • 属性解析:读取axes列表,-1被映射为dims - 1(最后一维),并计算归约轴的左右边界axisLaxisR
  • 维度重组:若归约轴是最后一维,将张量重排为 2D(shape[0]为外部分块,shape[1]为归约轴长度);否则重排为 3D(shape[0]外层、shape[1]归约轴、shape[2]内层),并将归约轴统一移动到中间位置(axis = 1);
  • 核数规划:根据任务总量动态收敛核数,如 2D 场景requiredCore = shape[0] * 2,取min(requiredCore, coreNum)
  • 分块策略:针对 FLOAT16 / FLOAT / BF16 分别设计对齐策略,例如 shape 小批量(shape[0]*shape[2] <= 1024)时按 16/64 字节对齐,大 shape(shape[2] >= 8192>= 40*2048)时按 2048 分块,其余场景按 128/256 对齐(见 log_softmax_v2_tiling.cpp);
  • workspace 计算:通过GetWorkspaceSize汇总库 API 所需的系统 workspace 大小(log_softmax_v2_tiling.cpp);
  • 输出结构:最终将axisdimsshape[8]写入 LogSoftmaxV2TilingData(结构体仅含axis/dims/shape[8]三个字段),并通过context->SetBlockDim(coreNum)设置并行核数。

5.4 Kernel:按分片模式执行归约

log_softmax_v2.cpp 中的 Kernel 入口log_softmax_v2DTYPE_INPUT模板实例化,通过LogSoftmaxTraits<T>float / half / bfloat16_t分别映射到LogSoftmax / LogSoftmaxHalf / LogSoftmaxBf16(2D)与LogSoftmaxCol / LogSoftmaxHalfCol / LogSoftmaxBF16Col(3D)算子类(log_softmax_v2.cpp)。

ProcessLogSoftmaxInternal根据 Tiling 结果分四类场景执行(log_softmax_v2.cpp):

  1. 轴较小shape[1] <= 16):利用 L1 workspace(上限 8192)按 128 对齐分片,Process4逐片处理;
  2. 大 shape:按 2048 的 chunk 切分内层,Process2/Process3遍历 batch × chunk;
  3. 小批量:按各类型smallBatchAlign(float 16 / half 64 / bf16 128)对齐后Process1批量处理;
  4. 默认场景:按defaultAlign(float 128 / half 256 / bf16 64)对齐,float/half 走Process2,bf16 走Process3

同时 log_softmax_v2.h 定义了常量(FLOAT_NEG_INFHALF_NEG_INF)以及align_to/mmin等辅助函数,Kernel 内部使用SIXTEEN_THOUSAND_THREE_HUNDRED_EIGHTY_FOUR(16KB)级 work buffer 承载归约中间结果,体现出对 UB(Unified Buffer)容量与访存对齐的精细控制。

5.5 测试体系

仓库为该算子配备了双层次 UT:

  • Host 侧 Tiling 测试:tests/ut/op_host/test_log_softmax_v2_tiling.cpp 校验 Tiling 数据的生成;
  • Kernel 侧测试:tests/ut/op_kernel/test_log_softmax_v2.cpp 运行算子并比对结果;
  • 数据生成与比对脚本:tests/ut/op_kernel/logsoftmax_data/gen_data.py 与 compare_data.py 用于构造输入数据并做精度比对,可作为自行验证算子的参考工具。

六、约束与使用建议

  • 约束说明:README 明确该算子当前无额外约束。
  • 维度限制:输入与输出维度不超过 8 维,dim取值范围为[-rank, rank-1],支持负索引(如-1表示最后一维)。
  • 形状一致性:输出out的 shape 必须与输入self完全一致,且数据类型保持一致。
  • 数据格式:仅支持 ND 格式;支持非连续 Tensor。
  • Workspace 申请workspaceSize为 0 时可以跳过aclrtMalloc,但执行段接口仍需传入该值与 executor;申请内存建议使用ACL_MEM_MALLOC_HUGE_FIRST
  • 同步要求aclnnLogSoftmaxV2为异步接口,读取结果前必须调用aclrtSynchronizeStream(stream)等待任务完成。

七、小结

LogSoftmaxV2 是 CANN ops-nn 中面向 Atlas A2 系列产品提供的高性能 LogSoftmax 算子:数学上通过"减最大值"策略保证数值稳定性;接口上采用两段式 aclnn 设计,兼顾入参校验与 workspace 预分配;实现上由 AICore/AICPU 双路径调度、维度重排式 Tiling 与分片式 Kernel 协作完成。开发者只需遵循本文梳理的七步调用流程,即可在 NPU 上稳定完成指定维度的对数归一化计算,并可借助仓库示例与测试脚本对结果进行自动验证。

【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询