- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
本篇技术指南围绕 CANN 数学算子库 ops-math 中Complex算子的 aclnn 两层接口展开,完整讲解aclnnComplex/aclnnComplexGetWorkspaceSize的函数原型、参数约束、返回码、平台支持情况与调用示例,并结合仓库源码(aclnn_complex.cpp)剖析其入参校验、广播推导与 kernel 分发机制。读完本文,你将掌握如何在 NPU 上通过两个实数 Tensor 构造复数 Tensor,并能独立编写、编译和运行基于两段式接口的 aclnn 调用程序。
一、功能说明:从实部、虚部到复数
aclnnComplex是 CANN ops-math 提供的数学类基础算子接口,用于将"实部 Tensor"与"虚部 Tensor"组合成一个复数 Tensor。接口功能要求:输入两个 Shape 满足 broadcast 关系、Dtype 一致的 Tensor,逐元素生成复数输出。
其计算公式为:
$$ \text{out}[i] = \text{real}[i] + \text{imag}[i]\times \mathrm{j} $$
其中 $\mathrm{j}$ 为虚数单位,real代表复数的实部,imag代表复数的虚部,out为复数类型输出。
该算子的官方功能说明与公式定义见 math/complex/docs/aclnnComplex.md,其 aclnn 接口的对外声明位于 aclnn_complex.h,接口实现位于 aclnn_complex.cpp。
数据类型映射关系
输入(实部/虚部)与输出(复数)的数据类型存在严格的一一对应关系:
| 输入 Dtype(real / imag) | 输出 Dtype(out) |
|---|---|
| FLOAT | COMPLEX64 |
| FLOAT16 | COMPLEX32 |
| DOUBLE | COMPLEX128 |
这一映射关系在源码中有明确体现。aclnn_complex.cpp 中定义了DTYPE_PAIR:
static const std::initializer_list<std::pair<op::DataType, op::DataType>> DTYPE_PAIR = { {DataType::DT_FLOAT16, DataType::DT_COMPLEX32}, {DataType::DT_FLOAT, DataType::DT_COMPLEX64}, {DataType::DT_DOUBLE, DataType::DT_COMPLEX128} };同时,第一段接口在入参校验阶段会强制校验该配对关系(见CheckDtypeValid),若 real 为 FLOAT 而 out 为 COMPLEX128 等不配对组合,将直接返回参数非法错误。
二、产品支持情况
依据官方文档,aclnnComplex在不同硬件平台上的支持情况如下:
| 产品型号 | 支持情况 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 支持 |
此外,针对 Atlas A3 训练/推理系列产品与 Ascend 950PR/950DT,官方文档明确 out 的数据类型支持 COMPLEX64(输入只能是 FLOAT)、COMPLEX32(输入只能是 FLOAT16)、COMPLEX128(输入只能是 DOUBLE)。
从源码结构可以进一步印证平台差异:算子的 AICore 配置在 complex_def.cpp 中通过OpAICoreConfig仅注册了ascend950平台,并将DynamicShapeSupportFlag、DynamicRankSupportFlag置为 true,表明 950 平台走动态 shape/rank 的 AICore 执行路径;而 complex.cpp 中针对不同 SoC 版本定义了不同的 AICore dtype 支持列表(如 910B 支持 FLOAT/FLOAT16,950 支持 FLOAT/FLOAT16),不满足条件时则回退到 AICPU(tf_kernel)路径。
三、两段式接口与函数原型
与 CANN aclnn 体系一致,aclnnComplex采用两段式接口设计:必须先调用aclnnComplexGetWorkspaceSize获取计算所需的 workspace 大小以及包含算子计算流程的执行器(executor),再调用aclnnComplex执行计算。
第一段接口原型
aclnnStatus aclnnComplexGetWorkspaceSize( const aclTensor* real, const aclTensor* imag, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口原型
aclnnStatus aclnnComplex( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)两段接口的详细声明同样可以在头文件 aclnn_complex.h 中查阅,其中标注了参数类型、所属域(aclnn_math)与基本约束。
四、aclnnComplexGetWorkspaceSize 参数说明
第一段接口负责入参校验、构建算子计算流程并返回 workspace 大小,其参数说明如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| real (aclTensor*) | 输入 | 公式中的输入 real,代表复数的实部 | shape 需要与 imag 满足 broadcast 关系 | FLOAT、FLOAT16、DOUBLE | ND | - | √ |
| imag (aclTensor*) | 输入 | 公式中的输入 imag,代表复数的虚部 | shape 需要与 real 满足 broadcast 关系 | FLOAT、FLOAT16、DOUBLE | ND | - | √ |
| out (aclTensor*) | 输出 | 公式中的 out,复数类型的 Tensor | shape 需要是 real 与 imag broadcast 之后的 shape | COMPLEX64、COMPLEX32、COMPLEX128 | ND | - | √ |
| workspaceSize (uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor (aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
注意:参数表中"非连续 Tensor"一栏为 √,表示 real、imag、out 均支持非连续 Tensor 输入;数据格式为 ND。
从实现层面看,aclnn_complex.cpp 中第一段接口的执行流程为:
- 创建
OpExecutor(CREATE_EXECUTOR()); - 通过
CheckNotNull校验 real、imag、out 三个指针非空; - 处理空 Tensor 场景:若 real 或 imag 为空,直接返回
workspaceSize = 0并成功退出; - 通过
CheckParams完成 dtype、shape/广播、format 校验; - 分别用
l0op::Contiguous将 real、imag 转为连续 Tensor; - 调用
l0op::Complex构建计算节点; - 用
l0op::ViewCopy将计算结果写入 out(兼容非连续 out); - 通过
GetWorkspaceSize汇总返回 workspace 大小。
返回值与入参校验错误码
aclnnStatus返回状态码的具体含义参见 aclnn 返回码。第一段接口完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 real、imag 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | real 和 imag 的数据类型和数据格式不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | real 和 imag 的 shape 无法做 broadcast |
| ACLNN_ERR_PARAM_INVALID | 161002 | real 和 imag 的维度大于 8 |
| ACLNN_ERR_PARAM_INVALID | 161002 | real 和 imag 的数据类型不一样 |
上述校验逻辑在源码中均有对应实现:
- 空指针检查:
CheckNotNull(aclnn_complex.cpp); - dtype 支持范围、real/imag dtype 一致性、输入输出 dtype 配对:
CheckDtypeValid(aclnn_complex.cpp); - 最大维度(8 维)与广播推断、out shape 一致性:
CheckOutShape(aclnn_complex.cpp),其中MAX_DIM = 8,通过OP_CHECK_BROADCAST_AND_INFER_SHAPE完成广播 shape 推导; - format 校验:
CheckFormat(aclnn_complex.cpp),要求 real、imag、out 三者 format 一致且为非私有格式(ND 系列),该检查仅在 Ascend 950 平台上生效(见CheckParams中GetCurrentPlatformInfo().GetSocVersion() == SocVersion::ASCEND950分支)。
五、aclnnComplex 参数说明
第二段接口执行实际计算,参数说明如下:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnComplexGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
返回值为aclnnStatus状态码,具体参见 aclnn 返回码。实现上,第二段接口直接调用框架统一的执行入口CommonOpExecutorRun(workspace, workspaceSize, executor, stream)(见 aclnn_complex.cpp),由框架完成计算调度。
六、约束说明
- 确定性计算:
aclnnComplex默认采用确定性实现,即相同输入在相同环境下多次执行结果可复现。关于确定性计算的更多背景可参考 确定性计算说明。
七、源码级实现原理
7.1 算子定义与注册
算子通过 complex_def.cpp 完成 OpDef 注册:输入real、imag(FLOAT/FLOAT16,ND 格式,必选),输出out(COMPLEX64/COMPLEX32,ND 格式),属性Tout(可选,Int,默认 0)。同时该文件为 AICore 配置了ascend950平台,并开启动态 shape、动态 rank 支持。
7.2 底层 L0 接口与执行路径选择
l0op::Complex(见 complex.cpp)内部首先通过BroadcastInferShape推导广播后的输出 shape,再按输入 dtype 推导输出 dtype(FLOAT16 → COMPLEX32,否则默认 COMPLEX64),最后根据平台与 dtype 选择执行路径:
IsAiCoreSupport返回 true 时走ComplexAiCore(AICore kernel,通过ADD_TO_LAUNCHER_LIST_AICORE加入任务队列);- 否则走
ComplexAiCpu(AICPU tf_kernel,通过ADD_TO_LAUNCHER_LIST_AICPU加入任务队列),但 COMPLEX32 输出在 AICPU 路径不被支持,会直接报错返回。
其中 AICore kernel 入口由 complex_apt.cpp 提供,分派至arch35架构实现(kernel_operator.h+arch35/complex.h)。
7.3 算子二进制配置
在 complex_binary.json 中,算子按输入 dtype 拆分为两个二进制配置项:Complex_float32(float32 → complex64)与Complex_float16(float16 → complex32),shape 均标记为-2(动态 shape),format 为 ND;complex_simplified_key.ini 则给出简化 key 的默认值。
7.4 框架插件
在 TensorFlow 框架侧,complex_tf_plugin.cpp 通过REGISTER_CUSTOM_OP("Complex")将 TF 原算子Complex映射到本实现,并使用AutoMappingByOpFn自动完成参数映射,ImplyType::TVM表明其映射方式。
八、调用示例
以下示例代码来自官方文档,完整可运行版本也可参考仓库中的 test_aclnn_complex.cpp(该版本使用 RAII 智能指针管理资源)。具体编译和执行过程请参考 编译与运行样例。
#include <iostream> #include <vector> #include <complex> #include "acl/acl.h" #include "aclnnop/aclnn_complex.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::vector<int64_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::vector<T>& hostData, const std::vector<int64_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::vector<int64_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::vector<int64_t> realShape = {4, 2}; std::vector<int64_t> imagShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* realDeviceAddr = nullptr; void* imagDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* real = nullptr; aclTensor* imag = nullptr; aclTensor* out = nullptr; std::vector<float> realHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<float> imagHostData = {1, 1, 1, 2, 2, 2, 3, 3}; std::vector<std::complex<float>> outHostData(8, 0); // 创建real aclTensor ret = CreateAclTensor(realHostData, realShape, &realDeviceAddr, aclDataType::ACL_FLOAT, &real); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建imag aclTensor ret = CreateAclTensor(imagHostData, imagShape, &imagDeviceAddr, aclDataType::ACL_FLOAT, &imag); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_COMPLEX64, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnComplex第一段接口 ret = aclnnComplexGetWorkspaceSize(real, imag, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnComplexGetWorkspaceSize 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); } // 调用aclnnComplex第二段接口 ret = aclnnComplex(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnComplex 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::vector<float> 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(real); aclDestroyTensor(imag); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(realDeviceAddr); aclrtFree(imagDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例关键步骤解读
- 资源初始化:
aclInit→aclrtSetDevice→aclrtCreateStream,固定写法; - 构造输入输出:示例中 real 与 imag 均为
{4, 2}的 FLOAT Tensor(数据分别为{0..7}与{1,1,1,2,2,2,3,3}),out 为{4, 2}的 COMPLEX64 Tensor。由于 real/imag shape 完全相同,广播后的输出 shape 仍为{4, 2}; - 两段式调用:先调用
aclnnComplexGetWorkspaceSize获取workspaceSize与executor,按需用aclrtMalloc申请 workspace 内存(注意workspaceSize > 0时才需要申请),再调用aclnnComplex执行; - 同步与取数:
aclrtSynchronizeStream等待任务结束,随后将结果从 Device 拷贝回 Host 并打印; - 资源释放:依次销毁
aclTensor、释放 Device 内存、销毁 Stream、重置 Device 并aclFinalize。
按公式计算,示例输出的复数应为0+1j, 1+1j, 2+1j, 3+2j, 4+2j, 5+2j, 6+3j, 7+3j。
九、单元测试验证
仓库在 test_aclnn_complex.cpp 中提供了基于 gtest 的接口级单元测试,覆盖以下场景:
- complex64 正常路径:FLOAT 输入 + COMPLEX64 输出,期望
ACLNN_SUCCESS; - complex32 正常路径:FLOAT16 输入 + COMPLEX32 输出,期望
ACLNN_SUCCESS; - dtype 配对校验:COMPLEX32 输入 + COMPLEX128 输出,期望
ACLNN_ERR_PARAM_INVALID(输入 dtype 不支持); - 输出 dtype 校验:DOUBLE 输入 + COMPLEX64 输出,期望
ACLNN_ERR_PARAM_INVALID(dtype 不配对); - 空 Tensor:shape 含 0 维的空输入,期望
ACLNN_SUCCESS(对应第一段接口中的空 Tensor 短路处理)。
这些用例与文档中的错误码表格一一对应,可作为复现"入参校验报错"行为的快捷验证手段。
十、相关文档导航
- 两段式接口说明:了解 aclnn 接口为什么分两段调用;
- broadcast 关系说明:理解 real/imag 之间广播规则;
- aclnn 返回码:查询
ACLNN_ERR_PARAM_NULLPTR、ACLNN_ERR_PARAM_INVALID等状态码含义; - 编译与运行样例:示例代码的编译与运行指引;
- 确定性计算说明:了解默认确定性实现的背景。
- 算子库
- 人工智能
- CANN
【免费下载链接】ops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
相关推荐
CANN ops-math 算子详解:Pow2 张量指数运算的 aclnn 接口与 AscendC 实现剖析
CANN ops math 算子详解:Pow2 张量指数运算的 aclnn 接口与 AscendC 实现剖析 本文是 CANN 开源数学算子库 ops math
算子库人工智能CANNCANN ops-math ComplexV3 算子全解析:实数张量组合为复数张量的实现、编译与调优
CANN ops math ComplexV3 算子全解析:实数张量组合为复数张量的实现、编译与调优 ComplexV3 是 CANN ops math 数学算
算子库人工智能CANNCANN ops-math MatrixDiagV3 算子详解:对角线张量构建的原理、参数与图模式调用实战
CANN ops math MatrixDiagV3 算子详解:对角线张量构建的原理、参数与图模式调用实战 本文围绕 CANN ops math 仓库中 con
算子库人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考