CANN SiP asdMul 算子详解:基于 Ascend NPU 的复数向量逐元素乘法(Hadamard 积)API 指南
2026/9/18 17:35:59 网站建设 项目流程

CANN SiP asdMul 算子详解:基于 Ascend NPU 的复数向量逐元素乘法(Hadamard 积)API 指南

【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip

本文是 CANN SiP(Signal Processing,信号处理加速库)基础(BASE)域asdMul算子的完整技术指南。该算子面向华为 Ascend AI 处理器,为复数向量提供逐元素乘积(Hadamard 积)能力,是复数域信号处理、阵列运算中高频复用的基础原语。阅读本文后,你将掌握asdMul的函数原型、参数约束、产品支持矩阵、C++ 调用示例、Torch 自定义算子(torch_sip)封装,以及其在仓库中的源码实现与单测验证链路,可直接上手在 Atlas A2/A3 与 Ascend 950 系列环境编写并运行首个复数乘法样例。

功能说明:复向量逐元素乘积(Hadamard 积)

asdMul的接口功能是:支持向量逐元素乘积(Hadamard)能力,返回一个和输入同样形状大小的复数矩阵。它接受两个复数输入张量AB,输出与二者同形状的结果张量result,计算公式为:

$$ result = A \odot B = (A){ij} (B){ij} $$

即两个矩阵对应位置上的复数元素直接相乘,不做任何约简或广播扩展(输入 shape 必须一致)。这一操作在复数信号处理中对应复平面上的幅度-相位联合缩放,例如对频域信号逐点乘以复滤波器系数即可通过本算子完成。

计算示例

  • 输入A为:

    [ [ 1+1i, 1+1i ], [ 2+2i, 2+2i ] ]
  • 输入B为:

    [ [ 1+1i, 1+1i ], [ 2+2i, 2+2i ] ]
  • 调用asdMul算子后,输出result为:

    [ [ 0+2i, 0+2i ], [ 0+8i, 0+8i ] ]

(1+1i) × (1+1i)为例:1×1 - 1×1 = 0为实部,1×1 + 1×1 = 2为虚部,即0+2i;同理(2+2i) × (2+2i) = 0+8i,与示例输出完全一致,可用于快速自检调用结果。

产品支持情况

asdMul在不同昇腾产品系列上的支持情况如下表(与仓库 docs/zh/API_Reference/base/asdMul.md 保持一致):

产品系列支持情况
Ascend 950PR / Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持
Atlas 200I/500 A2 推理产品不支持
Atlas 推理系列产品不支持
Atlas 训练系列产品不支持

从源码角度印证:在 ops/base/mul/mul_operation.cpp 的MulOperation::GetBestKernel中,算子仅对ASCEND_950(Ascend 950 系列)与ASCEND_910B(对应 Atlas A2 训练/推理系列)两个平台类型返回可用 Kernel,其他平台直接记录错误日志并返回nullptr,与上表“不支持”的产品列完全对应。

函数原型

asdMul的声明位于公共头文件 include/base_api.h,原型如下:

AspbStatus asdMul( int n, const aclTensor * x, const aclTensor * y, aclTensor * z, void * stream, void * workspace = nullptr)
  • 返回值类型AspbStatus为状态码枚举,定义于 core/utils/include/utils/aspb_status.h,ACL_SUCCESS表示执行成功。

参数说明

参数名输入/输出描述
n(int)输入表示输入的元素个数。
x(const aclTensor *)输入表示输入的矩阵,对应公式中的A。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为 [n]。
y(const aclTensor *)输入表示输入的矩阵,对应公式中的B。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为 [n]。
z(aclTensor *)输出表示输出的矩阵,对应公式中的result。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为 [n]。
stream(void *)输入NPU 执行流(aclrtStream)。
workspace(void *)输入asdMul 算子所需要的 workspace,可缺省(默认nullptr)。

要点说明:

  • 数据类型:支持两种复数精度。COMPLEX32 对应半精度复数(实部、虚部各为 fp16);COMPLEX64 对应单精度复数(实部、虚部各为 fp32)。两个输入xy与输出z必须使用同一数据类型。
  • 数据格式:仅支持 ND 连续排布格式,输入 shape 为[n]的一维向量。注意n表示的是复数元素个数,而非实/虚部分开的标量个数。
  • shape 一致性xy的 shape 必须一致,算子按对应位置逐元素相乘,输出z与输入同 shape。

约束说明

  • 输入的元素个数n理论支持范围为[1, 9.22e+18](对应 64 位整数可表示的取值上限)。
  • 实际运行时的可用规模同时受设备内存容量与 Kernel 分块策略限制,建议在目标产品上以实际 benchmark 为准。

返回码

接口返回AspbStatus状态码,各错误码的详细含义请参见 docs/zh/context/SiP返回码.md。典型使用方式是通过ASD_STATUS_CHECK宏在调用失败时打印Execute failed.并退出:

#define ASD_STATUS_CHECK(err) \ do { \ AsdSip::AspbStatus err_ = (err); \ if (err_ != AsdSip::ErrorType::ACL_SUCCESS) { \ std::cout << "Execute failed." << std::endl; \ exit(-1); \ } \ } while (0)

调用示例

以下示例代码来自原文档,旨在提供快速上手、开发和调试算子的最小化实现,核心目标是使用最精简的代码展示算子的核心功能,而非提供生产级的安全保障。不推荐将示例代码直接作为业务代码使用。

前置:环境准备

参照 example/A2/BASE/asd_mul/README.md 中的说明:

  1. 配置 CANN 环境变量:

    source /usr/local/Ascend/ascend-toolkit/set_env.sh
  2. 在 SiP 根目录编译加速库并设置环境变量:

    cd ${SiP_root_path} bash build.sh source output/set_env.sh

    注意:该编译方式仅支持通过 git 下载的加速库;编译过程需要联网下载依赖库。更多编译命令说明参见 docs/compilation_build.md。

  3. 进入示例目录执行构建脚本:

    cd example/A2/BASE/asd_mul bash build.sh

mul_complex32 示例(COMPLEX32 / fp16 复数)

#include <iostream> #include <vector> #include <complex> #include "asdsip.h" #include "acl/acl.h" #include "acl_meta.h" using namespace AsdSip; #define ASD_STATUS_CHECK(err) \ do { \ AsdSip::AspbStatus err_ = (err); \ if (err_ != AsdSip::ErrorType::ACL_SUCCESS) { \ std::cout << "Execute failed." << std::endl; \ exit(-1); \ } \ } while (0) #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) { // 固定写法,acl初始化 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; } void printTensor(const std::complex<op::fp16_t> *tensorData, int64_t nums) { for (int64_t i = 0; i < nums; i++) { std::cout << "(" << (float)tensorData[i].real() << "," << (float)tensorData[i].imag() << ")" << " "; } std::cout << std::endl; } int main(int argc, char **argv) { int 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); int64_t n = 8; int64_t vecSize = n; std::vector<std::complex<op::fp16_t>> tensorInXData; std::vector<std::complex<op::fp16_t>> tensorInYData; tensorInXData.reserve(vecSize); tensorInYData.reserve(vecSize); for (int64_t i = 0; i < vecSize; i++) { tensorInXData.push_back({(op::fp16_t)(9.0f + i), (op::fp16_t)(100.0f + i)}); } for (int64_t i = 0; i < vecSize; i++) { tensorInYData.push_back({(op::fp16_t)(22.0f + i), (op::fp16_t)(33.0f * (i + 1))}); } std::vector<std::complex<op::fp16_t>> tensorOutZData( vecSize, {(op::fp16_t)0.0f, (op::fp16_t)0.0f}); std::cout << "------- input X -------" << std::endl; printTensor(tensorInXData.data(), vecSize); std::cout << "------- input Y -------" << std::endl; printTensor(tensorInYData.data(), vecSize); std::vector<int64_t> xShape = {vecSize}; std::vector<int64_t> yShape = {vecSize}; std::vector<int64_t> zShape = {vecSize}; aclTensor *inputX = nullptr; aclTensor *inputY = nullptr; aclTensor *outputZ = nullptr; void *inputXDeviceAddr = nullptr; void *inputYDeviceAddr = nullptr; void *outputZDeviceAddr = nullptr; ret = CreateAclTensor(tensorInXData, xShape, &inputXDeviceAddr, aclDataType::ACL_COMPLEX32, &inputX); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor(tensorInYData, yShape, &inputYDeviceAddr, aclDataType::ACL_COMPLEX32, &inputY); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor(tensorOutZData, zShape, &outputZDeviceAddr, aclDataType::ACL_COMPLEX32, &outputZ); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ASD_STATUS_CHECK(asdMul(n, inputX, inputY, outputZ, stream)); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); ret = aclrtMemcpy(tensorOutZData.data(), vecSize * sizeof(std::complex<op::fp16_t>), outputZDeviceAddr, vecSize * sizeof(std::complex<op::fp16_t>), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("copy z from device to host failed. ERROR: %d\n", ret); return ret); std::cout << "------- output Z -------" << std::endl; printTensor(tensorOutZData.data(), vecSize); std::cout << "Execute successfully." << std::endl; aclDestroyTensor(inputX); aclDestroyTensor(inputY); aclDestroyTensor(outputZ); aclrtFree(inputXDeviceAddr); aclrtFree(inputYDeviceAddr); aclrtFree(outputZDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

代码结构解读

  1. ACL 初始化(固定写法)Init内依次调用aclInitaclrtSetDeviceaclrtCreateStream完成运行时初始化并创建 NPU 执行流。
  2. host 到 device 的数据搬运CreateAclTensor模板函数先通过aclrtMalloc申请 device 侧内存,再用aclrtMemcpyACL_MEMCPY_HOST_TO_DEVICE)把 host 数据拷入,随后计算连续 tensor 的 strides,最后用aclCreateTensor创建 ND 格式的aclTensor。COMPLEX32 场景使用op::fp16_t类型的std::complex作为元素类型。
  3. 核心调用:一行ASD_STATUS_CHECK(asdMul(n, inputX, inputY, outputZ, stream));即完成算子下发,其中n为复数元素个数。
  4. 结果回读与资源释放aclrtSynchronizeStream同步等待算子执行完成,aclrtMemcpyACL_MEMCPY_DEVICE_TO_HOST方式将结果拷回 host 并打印;最后依次aclDestroyTensoraclrtFreeaclrtDestroyStreamaclrtResetDeviceaclFinalize释放全部资源。

mul_complex64 示例(COMPLEX64 / fp32 复数)

COMPLEX64 场景与上述流程完全一致,仅三处差异:

  • 元素类型从std::complex<op::fp16_t>换为std::complex<float>
  • 创建 tensor 时aclDataTypeACL_COMPLEX32换为ACL_COMPLEX64
  • 数据构造与打印函数按std::complex<float>适配。
#include <iostream> #include <vector> #include <complex> #include "asdsip.h" #include "acl/acl.h" #include "acl_meta.h" using namespace AsdSip; #define ASD_STATUS_CHECK(err) \ do { \ AsdSip::AspbStatus err_ = (err); \ if (err_ != AsdSip::ErrorType::ACL_SUCCESS) { \ std::cout << "Execute failed." << std::endl; \ exit(-1); \ } \ } while (0) #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) { // 固定写法,acl初始化 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; } void printTensor(const std::complex<float> *tensorData, int64_t nums) { for (int64_t i = 0; i < nums; i++) { std::cout << tensorData[i] << " "; } std::cout << std::endl; } int main(int argc, char **argv) { int 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); int64_t n = 8; int64_t vecSize = n; std::vector<std::complex<float>> tensorInXData; std::vector<std::complex<float>> tensorInYData; tensorInXData.resize(vecSize); tensorInYData.resize(vecSize); for (int64_t i = 0; i < vecSize; i++) { tensorInXData[i] = {(float)(1.0 + i), (float)(1.0 + i)}; } for (int64_t i = 0; i < vecSize; i++) { tensorInYData[i] = {(float)(2.0 + i), 3.0}; } std::vector<std::complex<float>> tensorOutZData(vecSize, {0.0f, 0.0f}); std::cout << "------- input X -------" << std::endl; printTensor(tensorInXData.data(), vecSize); std::cout << "------- input Y -------" << std::endl; printTensor(tensorInYData.data(), vecSize); std::vector<int64_t> xShape = {vecSize}; std::vector<int64_t> yShape = {vecSize}; std::vector<int64_t> zShape = {vecSize}; aclTensor *inputX = nullptr; aclTensor *inputY = nullptr; aclTensor *outputZ = nullptr; void *inputXDeviceAddr = nullptr; void *inputYDeviceAddr = nullptr; void *outputZDeviceAddr = nullptr; ret = CreateAclTensor(tensorInXData, xShape, &inputXDeviceAddr, aclDataType::ACL_COMPLEX64, &inputX); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor(tensorInYData, yShape, &inputYDeviceAddr, aclDataType::ACL_COMPLEX64, &inputY); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor(tensorOutZData, zShape, &outputZDeviceAddr, aclDataType::ACL_COMPLEX64, &outputZ); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ASD_STATUS_CHECK(asdMul(n, inputX, inputY, outputZ, stream)); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); ret = aclrtMemcpy(tensorOutZData.data(), vecSize * sizeof(std::complex<float>), outputZDeviceAddr, vecSize * sizeof(std::complex<float>), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("copy z from device to host failed. ERROR: %d\n", ret); return ret); std::cout << "------- Output -------" << std::endl; printTensor(tensorOutZData.data(), vecSize); std::cout << "Execute successfully." << std::endl; aclDestroyTensor(inputX); aclDestroyTensor(inputY); aclDestroyTensor(outputZ); aclrtFree(inputXDeviceAddr); aclrtFree(inputYDeviceAddr); aclrtFree(outputZDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

两个示例的完整可运行版本分别位于 example/A2/BASE/asd_mul/example_mul_complex32.cpp 与 example/A2/BASE/asd_mul/example_mul_complex64.cpp,默认构建脚本可直接编译 complex32 场景,complex64 场景需将编译脚本中的源文件替换为example_mul_complex64.cpp后重新构建。

源码实现:从 aclnnMul 到平台 Kernel 的调度链路

公共 API 层:基于 aclnnMul 的封装

core/base/mul.cpp 实现了算子核心调度逻辑Mul。其工作流程为:

  1. 通过toAclTensor将内部Mki::Tensor转换为aclTensor,任一转换失败即返回ACL_ERROR_INTERNAL_ERROR并打印错误日志;
  2. 调用aclnnMulGetWorkspaceSize查询 workspace 大小并创建aclOpExecutor执行器;
  3. 调用aclnnMul(deviceBuffer, workspaceSize, executor, stream)将算子任务下发到 NPU 执行流。

由此可见,asdMul在公共 API 层复用了 CANN 通用Mul算子(aclnnMul)的异步执行机制,workspace即为aclnnMul所需的设备侧临时缓冲区。

算子内核层:按平台与精度分发的 Kernel

在算子内核侧,ops/base/mul/mul_operation.cpp 中注册了MulOperation算子:

  • GetBestKernel根据平台类型选择 Kernel:Ascend 950 系列(ASCEND_950)使用MulArch35Kernel;Atlas A2 系列(ASCEND_910B)则依据OpParam::CMul::CMulType区分MUL_C64MulC64Kernel)与MUL_C32MulC32Kernel)。
  • InferShapeImpl根据cMulType推断输出张量 dtype(COMPLEX64 或 COMPLEX32),并保持输出与输入x相同的 format 与 dims。
  • 算子入参结构OpParam::CMul定义于 ops/include/params/mul.h,包含复数元素个数n与乘法类型cMulType两个字段。

对应 Kernel 实现位于 ops/base/mul/mul/op_kernel/ 目录(mul_c32.cppmul_c64.cppmul_arch35.cpp),tiling 切分逻辑位于 ops/base/mul/mul/tiling/ 目录,负责将大规模向量按 NPU 计算核资源切块调度。

Torch 集成:通过 torch_sip 在 PyTorch 中调用 asdMul

除 C++ 接口外,仓库还提供了 PyTorch 自定义算子封装(sip_pta,即 SiP PyTorch Adapter),入口为 sip_pta/csrc/base/asd_mul.cpp:

at::Tensor asdMul(const at::Tensor& x, const at::Tensor& y) { c10_npu::NPUGuard guard(x.device()); at::Tensor z = at::empty_like(x); auto sip_stream = c10_npu::getCurrentNPUStream().stream(false); int64_t n = x.numel(); uint8_t* workspace_ptr = nullptr; EXEC_FUNC(AsdSip::asdMul, n, x, y, z, sip_stream, workspace_ptr); return z; } TORCH_LIBRARY_FRAGMENT(torch_sip, m) { m.def("asd_mul(Tensor x, Tensor y) -> Tensor"); } TORCH_LIBRARY_IMPL(torch_sip, PrivateUse1, m) { m.impl("asd_mul", &asdMul); }

封装要点:

  • n = x.numel()直接从输入张量的元素总数推导,无需用户显式传入;
  • 复用当前 NPU 流(c10_npu::getCurrentNPUStream),输出z通过at::empty_like(x)自动与输入保持相同 shape 与 dtype;
  • 通过TORCH_LIBRARY_FRAGMENT+TORCH_LIBRARY_IMPL注册为torch_sip.asd_mul自定义算子。

对应的 Python 测试脚本 sip_pta/test/base/asd_mul.py 展示了完整调用方式:

import torch import torch_npu import torch_sip device = torch.device('npu:0') torch.npu.set_device(device) shape = (4, 4) x = torch.complex(torch.randn(shape, device=device), torch.randn(shape, device=device)) y = torch.complex(torch.randn(shape, device=device), torch.randn(shape, device=device)) expected = x * y result = torch_sip.asd_mul(x, y)

测试通过torch.allclose(result, expected, rtol=1e-3, atol=1e-3)与 PyTorch 原生复数乘法结果对比,验证精度一致性。构建扩展并运行:

cd sip_pta && python setup.py build_ext --inplace python test/base/asd_mul.py

单元测试验证:Golden 数据生成与精度比对

仓库为asdMul提供了基于 Golden 数据比对的 C++ 单元测试,位于 tests/ut/unittest/base/asd_mul/test_asd_mul.cpp:

  • TestAsdMulComplex64Case0TestAsdMulComplex32Case0分别覆盖 COMPLEX64、COMPLEX32 两种精度,输入规模取n = 16
  • 测试流程为:用 asd_mul_data/gen_data.py 生成 Golden 数据 → 构造aclTensor并调用asdMul下发执行 → 将设备侧结果拷回 host 落盘 → 用 asd_mul_data/compare_data.py 与 Golden 数据比对;
  • 精度阈值与示例保持一致(绝对误差ATOL = 0.001、相对误差RTOL = 0.001),该文件同样可作算子正确性回归验证的参考模板。

总结

asdMul是 CANN SiP 中面向复数向量的基础逐元素乘法算子:接口签名简洁(asdMul(n, x, y, z, stream)),仅需n与三个 ND 格式aclTensor即可完成一次 Hadamard 积计算;支持 COMPLEX32 / COMPLEX64 两种精度,覆盖 Ascend 950PR/950DT、Atlas A2、Atlas A3 系列产品。从仓库实现看,它一方面复用了 CANN 通用aclnnMul的异步执行框架,另一方面针对平台差异(Ascend 950 的MulArch35Kernel、Atlas A2 的MulC32Kernel/MulC64Kernel)做了 Kernel 级分发优化,并配套 C++ 示例、torch_sip PyTorch 封装与 Golden 数据单测,形成了一条从 API 到 Kernel、再到框架集成的完整闭环。如果你需要在昇腾 NPU 上对复数信号做逐点复乘(如频域滤波、复数加权),可直接参照本文示例接入该算子。

【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip

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

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

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

立即咨询