CANN ops-math 中的 aclnnLogdet 接口:矩阵行列式自然对数两段式 API 详解
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
在科学计算、概率图模型和自回归变换(如 Normalizing Flow)等场景中,经常需要对输入方阵或方阵 batch 计算log(det(self))。CANN 算子库 ops-math 提供了Logdet算子的 ACLNN 两段式接口,支持对(*, n, n)形状的 float32 输入批量计算行列式自然对数,并在 Device 侧基于带部分主元选取的 LU 分解实现。读完本篇,你可以掌握该接口的完整参数语义、错误码、调用流程,以及从 Host 校验链、L0 主链到 Ascend C Kernel 的源码级实现原理。
产品支持情况
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | × |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | × |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | × |
| Atlas 训练系列产品 | × |
从仓库结构看,当前算子仅针对 Ascend 910B 平台提供了编译配置(ascend910b/logdet_binary.json),与上表中仅 Atlas A2 系列(910B)支持的情况一致。
功能说明:计算公式与特殊值语义
接口功能:计算输入方阵或方阵 batchself的行列式自然对数,并将结果写入out。
设 $\det(\text{self})$ 表示对self最后两维对应的每个方阵分别计算行列式,公式分解为:
$$ \text{signValue} = \text{sign}(\det(\text{self})) $$
$$ \text{logAbsValue} = \log(|\det(\text{self})|) $$
$$ \text{out_tmp} = \log(\text{signValue}) + \text{logAbsValue} $$
$$ \text{out} = \text{cast}(\text{out_tmp}) $$
其中三种取值情形定义了接口的完整输出语义:
- 当 $\det(\text{self}) > 0$ 时,$\text{out} = \log(\det(\text{self}))$。
- 当 $\det(\text{self}) = 0$ 时(奇异矩阵),$\text{out} = -\infty$。
- 当 $\det(\text{self}) < 0$ 时,$\text{out} = \text{NaN}$(对负数取自然对数无定义)。
输入约束为self的最后两维必须构成方阵,整体 shape 为(*, n, n);out的 shape 为self.shape[:-2](去掉最后两维的 batch shape),当self为 2 维方阵时,out是 0 维标量。
两段式接口设计
每个算子分为两段式接口(参见 两段式 API 说明):必须先调用aclnnLogdetGetWorkspaceSize接口获取计算所需 workspace 大小以及包含了算子计算流程的执行器(aclOpExecutor),再调用aclnnLogdet接口执行计算。
函数原型如下:
aclnnStatus aclnnLogdetGetWorkspaceSize( const aclTensor *self, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnLogdet( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)接口声明位于 aclnn_logdet.h,并以 C 链接(extern "C")导出,头文件中用 mermaid 计算图注释给出了主链:self → Contiguous → Logdet → ViewCopy → out。
aclnnLogdetGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 待计算行列式自然对数的输入方阵或方阵 batch,对应公式中 self | 支持空 Tensor(空 Tensor 时直接返回成功,workspaceSize 为 0);最后两维必须构成方阵;维度数不少于 2 | FLOAT | ND | 至少 2 维,shape 为(*, n, n) | √ |
| out(aclTensor*) | 输出 | self 的行列式自然对数结果,对应公式中 out | 不支持空 Tensor;数据类型需为 FLOAT;shape 需严格等于 self 去掉最后两维后的 batch shape,不支持 broadcast;当 self 为 2 维时,out 为 0 维标量 | FLOAT | ND | self 去掉最后两维后的 batch shape | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
返回值:aclnnStatus,具体参见 aclnn 返回码。
第一段接口完成入参校验,出现以下场景时报错:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | self 或 out 是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 或 out 的数据类型不在支持的范围之内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 的维度数小于 2。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 的最后两维不相等(不是方阵)。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | out 的 shape 不等于 self 去掉最后两维后的 batch shape。 |
aclnnLogdet 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口aclnnLogdetGetWorkspaceSize获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
返回值:aclnnStatus,具体参见 aclnn 返回码。
约束说明与边界行为
- 确定性说明:aclnnLogdet 默认确定性实现。
- 仅支持 float32 数据类型。
- 非连续 self 会先转为连续 Tensor 后计算;非连续 out 通过 ViewCopy 回写。
- 支持大于 8 维的 self,内部先 reshape 到 3 维计算后再 reshape 回原 batch shape。
- 使用带部分主元选取的 LU 分解算法。
源码级实现剖析
以下分析基于 aclnn_logdet.cpp,帮助读者理解文档中各约束在代码中的落点。
Host 侧校验链
第一段接口aclnnLogdetGetWorkspaceSize的执行顺序为:
CheckParams依次执行CheckNotNull→CheckDtypeValid→CheckShape:CheckNotNull校验 self/out 非空,对应ACLNN_ERR_PARAM_NULLPTR(161001);CheckDtypeValid中数据类型支持列表DTYPE_SUPPORT_LIST = {op::DataType::DT_FLOAT}仅含 float32,并额外校验out的 dtype 与self一致,对应ACLNN_ERR_PARAM_INVALID(161002);CheckShape校验self->GetViewShape().GetDimNum() >= 2、最后两维相等(mDim == nDim),以及out的 view shape 严格等于self.shape[:-2],对应文档错误码表中的后三条 161002 场景。
- 空 Tensor 快速路径:若
self->IsEmpty(),直接令*workspaceSize = 0并释放执行器返回成功,不再构建任何子算子图——这正是文档“空 Tensor 时直接返回成功”约束的实现。 - 非空时调用
LogdetExecImpl构建完整计算图并统计 workspace 大小,最后uniqueExecutor->GetWorkspaceSize()回填。
L0 主链:Contiguous → Logdet → Cast → ViewCopy
LogdetExecImpl构建的算子链为:
auto selfContiguous = l0op::Contiguous(self, executor); // 非连续 self 连续化 // 8 维以上:l0op::Reshape 到 (numel/(n*n), n, n) auto logdetOut = l0op::Logdet(selfReshapeOut, executor); // L0 启动 AiCore Logdet kernel // 8 维以上:l0op::Reshape 回原 batch shape auto logdetCastOut = l0op::Cast(logdetReshapeOut, out->GetDataType(), executor); auto logdetCopyResult = l0op::ViewCopy(logdetCastOut, out, executor); // 非连续 out 回写其中:
l0op::Logdet是 L0 级接口,声明在 logdet.h,负责按 batch shape 创建输出 tensor 并 launch AiCore Kernel;- 当输入维度数
dim > MAX_SUPPORT_DIMS_NUMS(8)时,ReshapeDim将 batch 折叠为一维,即{numel/(n*n), n, n},Kernel 只面对 3 维输入,计算完成后再 reshape 回原 batch shape。这与文档“支持大于 8 维的 self”约束一一对应; l0op::Contiguous与l0op::ViewCopy分别处理非连续输入与回写,因此调用方可以直接传入非连续的 self/out。
Graph 侧 InferShape
logdet_infershape.cpp 中,InferShape4Logdet实现与文档一致的形状推导规则:输入 rank 必须 ≥ 2,输出dimNum = inputRank - 2,且逐维复制前rank-2维;InferDataType4Logdet则直接令输出 dtype 等于输入 dtype。
Kernel 侧:带部分主元选取的 LU 分解
AiCore 侧实现位于 logdet.cpp 与 logdet.h,Kernel 入口是模板函数logdet<D_T, MEM_STRATEGY>,其中D_T为数据类型(仅 fp32),MEM_STRATEGY取值 0/1 对应两种存储策略:
- MEM_STRATEGY = 0(FULL_RESIDENT,全驻留):整个 n×n 矩阵一次性加载进 Unified Buffer(UB),适合中小规模矩阵;
- MEM_STRATEGY = 1(BLOCKED,核内分块):U 矩阵常驻 GM workspace,UB 中仅保留 O(n) 的行/列缓冲(pivot 整行、子列、行交换中转、ReduceMax 输出等),适合大 n 场景。
算法流程(以ProcessFull路径为例):batch 按核切分,每个 AiCore 按blockIdx_ + blockDim_*loop步长处理多个无依赖的矩阵(保证确定性);对单个 n×n 矩阵逐列执行部分主元 LU——先用ArgMaxAbsColumn在当前子列中找绝对值最大的元素作为主元(严格大于比较,命中首个最大值的行号,保证确定性),主元为负时翻转符号计数(对应公式中的 signValue),累计logabs += LogScalar(pivAbs)(对应 logAbsValue),再用EliminateFull对下方行做 Axpy 消元;当主元绝对值低于奇异阈值时(CheckSingular),判定矩阵奇异。该实现与文档“使用带部分主元选取的 LU 分解算法”以及det=0 → -inf、det<0 → NaN的输出语义相符:符号累计为负时最终对数结果无定义返回 NaN,奇异时返回 -inf。
调用示例
示例代码如下(来自 aclnnLogdet.md),仅供参考,具体编译和执行过程请参考 编译与运行样例:
#include <cstdint> #include <vector> #include "acl/acl.h" #include "aclnn_logdet.h" int RunLogdetExample(aclrtStream stream, aclTensor* self, aclTensor* out) { uint64_t workspaceSize = 0; aclOpExecutor* executor = nullptr; // 第一段接口:做参数校验并返回 workspace 大小与执行器 aclnnStatus ret = aclnnLogdetGetWorkspaceSize(self, out, &workspaceSize, &executor); if (ret != ACLNN_SUCCESS) { return ret; } void* workspace = nullptr; if (workspaceSize > 0) { aclError aclRet = aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); if (aclRet != ACL_SUCCESS) { return static_cast<int>(aclRet); } } // 第二段接口:在指定 stream 上执行 ret = aclnnLogdet(workspace, workspaceSize, executor, stream); if (ret != ACLNN_SUCCESS) { if (workspace != nullptr) { aclrtFree(workspace); } return ret; } aclrtSynchronizeStream(stream); if (workspace != nullptr) { aclrtFree(workspace); } return ACLNN_SUCCESS; }要点:仅在workspaceSize > 0时才申请 Device 内存,空 Tensor 场景下第一段接口会直接返回 0 大小,因此aclrtMalloc可以被跳过;第二段接口失败时应释放已分配的 workspace 再返回错误码。
示例说明
self为输入 Tensor,shape 需满足(*, n, n),dtype 必须为FLOAT。out为输出 Tensor,shape 必须等于self.shape[:-2],当self为 2 维时,out为 0 维标量。- 当
self为空 Tensor 时,aclnnLogdetGetWorkspaceSize会直接返回成功,且workspaceSize=0。 - 非连续
self/out可以直接传入,接口内部会分别处理连续化和回写。
一个 2x2 输入示例
若输入矩阵为:
[[2.0, 0.0], [0.0, 3.0]]则det(self) = 6.0 > 0,输出为标量:
log(det(self)) = log(6.0)测试与工程组织
算子工程的目录与测试布局如下(以 README.md 为准):
| 目录 / 文件 | 说明 |
|---|---|
| docs/ | ACLNN 接口文档(本文主体) |
| op_api/ | ACLNN 两段式接口实现(aclnn_logdet.cpp/h、L0logdet.cpp/h) |
| op_host/ | Host 侧注册(logdet_def.cpp)、InferShape(logdet_infershape.cpp)、Tiling(logdet_tiling.cpp) |
| op_kernel/ | Ascend C Kernel 实现(logdet.cpp、logdet.h) |
| tests/st/ | ST 测试工程 |
| tests/ut/ | UT 测试工程 |
ST 测试用例定义在 aclnnLogdet_l1_test_cases.csv 等 CSV 文件中,每条用例声明tensor_view_shapes、tensor_dtypes与input_data_ranges。例如:
aclnnLogdet_L1_001,aclnnLogdet,"((4,4,),(,),)","('float32','float32',)",,,(1,),,,((-100,100,)),, aclnnLogdet_L1_005,aclnnLogdet,"((3,8,8,),(3,))",('float32','float32',),,,(1,),,,((-100,100,)),,其中((4,4,),(,),)表示 2 维方阵输入 → 0 维标量输出,((3,8,8,),(3,))表示 batch 为 3、每块 8×8 的方阵 batch →[3]输出,正好覆盖了文档中“2 维输入输出标量、batch 输入输出 batch shape”两种典型形态;另有 L0/L2 用例 扩展取值范围。UT 侧则对 InferShape 与 Tiling 做白盒覆盖(test_logdet_infershape.cpp、test_logdet_tiling.cpp),另有 Kernel 单测 logdet_kernel.asc。
小结
aclnnLogdet是 ops-math 库中面向方阵行列式自然对数的 ACLNN 两段式接口:第一段接口完成空指针/dtype/shape 三重校验、空 Tensor 快速返回与 workspace 统计,第二段接口在指定 stream 上按Contiguous → Logdet → Cast → ViewCopy主链执行;Device 侧 Kernel 采用带部分主元选取的 LU 分解,通过主元符号与log|pivot|累加同时得到 signValue 与 logAbsValue,并按全驻留/核内分块两种内存策略适配不同规模的矩阵。该实现默认确定性、仅支持 float32,对大于 8 维输入通过 reshape 归一化处理,可直接用于 NPU 上需要log(det(·))的模型推理与训练场景。
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考