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 将log与softmax合并为一次归约运算,数值上更稳定,也减少了中间张量的访存开销。本项目中的log_softmax_v2同时提供了 L0 层 API(l0op::LogSoftmaxV2)与 aclnn 接口(aclnnLogSoftmaxV2),其中 aclnn 接口是应用侧推荐的调用方式。
二、参数说明
2.1 算子属性与输入输出(README 参数表)
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| axes | 输入(属性) | 指定的归约轴 | ListInt | / |
| input | 输入 | 待进行归约的 tensor | FLOAT、FLOAT16、BFLOAT16 | ND |
| out | 输出 | 归约的输出 tensor | FLOAT、FLOAT16、BFLOAT16 | ND |
补充说明(依据 算子定义源码):
input与out均声明为REQUIRED参数,数据类型三选一:DT_FLOAT、DT_FLOAT16、DT_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):传入的
self或out是空指针。 - 161002(ACLNN_ERR_PARAM_INVALID):
self和out的数据类型不在支持范围之内;self和out的 shape 不一致;self、out的维度超过 8;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该程序具备三个实用特性,可用于算子正确性验证:
- CPU 期望值计算:
CalculateExpectedLogSoftmax用纯 CPU 逻辑(最大值裁剪 + 指数累加 + 对数)计算期望输出; - 结果自动比对:
VerifyResult以相对误差1e-3(fp16)/1e-4(fp32)为容差逐元素比对,全部通过时打印✓ All results are correct!; - 随机数据生成:使用
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_FLOAT、DT_FLOAT16、DT_BF16三种类型且限定 ND 格式,属性axes为可选ListInt,默认{-1}。InferShape与InferDataType在 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(最后一维),并计算归约轴的左右边界axisL、axisR; - 维度重组:若归约轴是最后一维,将张量重排为 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); - 输出结构:最终将
axis、dims、shape[8]写入 LogSoftmaxV2TilingData(结构体仅含axis/dims/shape[8]三个字段),并通过context->SetBlockDim(coreNum)设置并行核数。
5.4 Kernel:按分片模式执行归约
log_softmax_v2.cpp 中的 Kernel 入口log_softmax_v2按DTYPE_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):
- 轴较小(
shape[1] <= 16):利用 L1 workspace(上限 8192)按 128 对齐分片,Process4逐片处理; - 大 shape:按 2048 的 chunk 切分内层,
Process2/Process3遍历 batch × chunk; - 小批量:按各类型
smallBatchAlign(float 16 / half 64 / bf16 128)对齐后Process1批量处理; - 默认场景:按
defaultAlign(float 128 / half 256 / bf16 64)对齐,float/half 走Process2,bf16 走Process3。
同时 log_softmax_v2.h 定义了常量(FLOAT_NEG_INF、HALF_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),仅供参考