CANN SiP 信号处理加速库 Asum 算子详解:asdBlasSasum / asdBlasScasum 绝对值求和接口实践指南
【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip
本文是 CANN SiP 信号处理加速库 BLAS 模块中 Asum 算子的完整技术指南,围绕asdBlasMakeAsumPlan、asdBlasSasum与asdBlasScasum三个接口展开,覆盖产品支持情况、数学定义、参数语义、句柄生命周期、约束范围与可编译运行的调用示例,并结合仓库源码剖析其校验逻辑、Plan 缓存机制与 Kernel 分核调度原理。读者读完后可以独立完成 Asum 算子从 Plan 初始化、workspace 申请、流绑定到结果回读的完整开发调试闭环。
产品支持情况
Asum 算子并非在全部昇腾产品上可用,使用前务必核对目标硬件:
- Ascend 950PR/Ascend 950DT:不支持
- Atlas A3 训练系列产品/Atlas A3 推理系列产品:支持
- Atlas A2 训练系列产品/Atlas A2 推理系列产品:支持
- Atlas 200I/500 A2 推理产品:不支持
- Atlas 推理系列产品:不支持
- Atlas 训练系列产品:不支持
从当前仓库的算子实现与示例组织方式(example/A2/BLAS/asum/README.md)看,官方示例面向 Atlas A2/A3 训练系列产品、Atlas 800I A2 推理产品与 Atlas A3 推理系列产品,与上述"支持"项一致,可作为选型依据。
功能说明与计算公式
Asum 是 BLAS Level 1 中经典的"绝对值求和"操作,本仓库提供了实数(Sasum)与复数(Scasum)两个变体:
asdBlasMakeAsumPlan:初始化句柄对应的 Asum 算子配置,将句柄与 Asum 计算流程绑定。asdBlasSasum:对输入的所有元素取绝对值后求和,输入为单精度浮点数。asdBlasScasum:对输入的所有元素取绝对值后求和,输入为单精度复数。
asdBlasSasum 公式
$$ result=\sum{i=0}^n|x{i}| $$
示例:输入x为[1, 2, -3, 4],调用asdBlasSasum后输出result为10(即|1|+|2|+|-3|+|4|)。
asdBlasScasum 公式
$$ result=\sum{i=0}^n(|real(x{i})|+|imag(x_{i})|) $$
示例:输入x为[1+2i, 2-2i, -3+3i, 4-3i],调用asdBlasScasum后输出result为20(逐项计算(1+2)+(2+2)+(3+3)+(4+3))。
注意:复数版本的求和对象是实部绝对值与虚部绝对值之和,而非复数的模长,这一点与 BLAS 标准中scasum的语义一致,在数据校验时需与模长计算(如 nrm2)严格区分。
函数原型
三个接口的原型声明位于公共头文件 include/blas_api.h,具体如下:
AspbStatus asdBlasMakeAsumPlan(asdBlasHandle handle)AspbStatus asdBlasSasum( asdBlasHandle handle, const int64_t n, aclTensor * x, const int64_t incx, aclTensor * result)AspbStatus asdBlasScasum( asdBlasHandle handle, const int64_t n, aclTensor * x, const int64_t incx, aclTensor * result)参数说明
asdBlasMakeAsumPlan
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| handle(asdBlasHandle) | 输入 | 算子的句柄 |
返回值:返回状态码,具体参见 SiP返回码。
asdBlasSasum 与 asdBlasScasum
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| handle(asdBlasHandle) | 输入 | 算子的句柄 |
| n(int64_t) | 输入 | 表示总的元素个数 |
| x(aclTensor *) | 输入 | 表示输入的向量,对应公式中的x;asdBlasScasum支持的数据类型为COMPLEX64,asdBlasSasum支持的数据类型为FLOAT32;数据格式支持ND;shape 为[n] |
| incx(int64_t) | 输入 | 相邻元素间的内存地址偏移量(当前约束为 1) |
| result(aclTensor *) | 输出 | 表示输出的结果,对应公式中的result;数据类型支持FLOAT32;数据格式支持ND;shape 为[1] |
返回值:返回状态码,具体参见 SiP返回码。
参数语义的源码级印证
从 core/blas/asum.cpp 的实现可以看出参数校验远比接口文档严格,调用时需注意:
- 数据类型强校验:
asdBlasSasum要求输入x为ACL_FLOAT、输出result为ACL_FLOAT;asdBlasScasum要求输入x为ACL_COMPLEX64。两者输出均为ACL_FLOAT,否则分别返回ACL_ERROR_UNSUPPORTED_DATA_TYPE。 - 数据格式校验:输入输出张量格式均要求为
ACL_FORMAT_ND,否则返回ACL_ERROR_FORMAT_NOT_MATCH。 - shape 强校验:实现通过
aclGetStorageShape读取存储形状,输入元素数必须等于n(复数场景按2n校验),输出存储 shape 必须为1,否则返回ACL_ERROR_OP_INPUT_NOT_MATCH/ACL_ERROR_OP_OUTPUT_NOT_MATCH。 - incx 容错:当
incx <= 0时不会直接报错,而是记录日志并自动重置为1(blas asum get incx <= 0, change incx = 1),因此实际语义上当前版本仅支持连续存储的向量。 - n 取值范围:源码要求
n > 0 && n <= UINT32_MAX,超出范围返回ACL_ERROR_INVALID_PARAM。
复数到实数的内部映射
一个值得注意的实现细节:asdBlasScasum并未单独实现复数内核,而是借助常量ELEMENTS_EACH_COMPLEX64 = 2将复数向量拆分为2n个"半元素",通过内部参数scaSum区分实/复分支后复用asdBlasSasumImpl(core/blas/asum.cpp)。这也解释了为何文档约束中asdBlasScasum的n语义仍指复数元素个数,而内部按2n展开处理。
约束说明
- 输入的元素个数
n当前覆盖支持[1, 6.71e+06]。 - 算子输入 shape 为
[n],输出 shape 为[1]。 - 算子实际计算时,不支持 ND 高维度运算(不支持维度 ≥ 3 的运算),即仅支持一维向量规约场景。
调用流程:从 Plan 到结果回读
Asum 属于 BLAS 算子,遵循统一的"Plan 生命周期"调用范式,完整流程如下(公共接口说明见 BLAS公共接口):
asdBlasCreate(handle):创建全局唯一的句柄。asdBlasMakeAsumPlan(handle):初始化句柄对应的 Asum 算子配置并完成绑定。asdBlasGetWorkspaceSize(handle, lwork):获取计算所需 workspace 大小。aclrtMalloc按lwork申请 device 侧工作空间;若lwork > 0。asdBlasSetWorkspace(handle, buffer):将 workspace 绑定到 plan。asdBlasSetStream(handle, stream):将 runtime 创建的 stream 与 plan 绑定。- 调用
asdBlasSasum/asdBlasScasum执行计算。 asdBlasSynchronize(handle)同步等待算子执行完成。asdBlasDestroy(handle)销毁 plan 并释放资源,避免内存泄漏。
Plan 绑定与缓存机制
在 core/blas/asum.cpp 的asdBlasMakeAsumPlan中,BlasAsumPlan通过BlasPlanCache::MakePlan(handle, plan)与句柄建立映射,并调用plan->MarkInitialized()标记就绪。源码中还实现了"重复绑定守卫":若句柄已绑定过 plan,再次调用会直接返回ACL_ERROR_INVALID_PARAM(日志提示handle already bound to a plan, repeated initialization is not allowed),防止重复初始化导致的静默失败与释放后使用(use-after-free)问题。asdBlasSasumImpl在每次执行前也会通过BlasPlanCache::doesPlanExist(handle)与plan.IsInitialized()双重确认 plan 可用。
算子内核与 Tiling 调度
从算子侧实现可以还原 Asum 在昇腾硬件上的执行路径:
- 算子入口 ops/blas/asum/asum_operation.cpp 中
AsumOperation::GetBestKernel固定选取名为SasumF32Kernel的内核,其输入输出数量均为 1,InferShapeImpl直接透传输出张量的 dtype、format 与 dims。 - 内核注册见 ops/blas/asum/asum_kernel.cpp:
SasumF32Kernel继承自SasumKernel,CanSupport校验参数类型、输入输出张量个数以及输出 dtype 必须为TENSOR_DTYPE_FLOAT;InitImpl调用SasumTiling生成 tiling 信息。 - Tiling 逻辑见 ops/blas/asum/sasum/tiling/sasum_tiling.cpp:向量核数量取自平台信息
CORE_TYPE_VECTOR,上限DEFAULT_VECTOR_NUM = 40,并通过ConfigCommonTilingData依据元素个数n计算实际使用的核数useCoreNum写入kernelInfo.SetBlockDim;同时申请固定WORKSPACE_SIZE = 16777216(16 MiB)的 scratch 空间,这正是asdBlasGetWorkspaceSize返回值的主要来源。 - 算子计算参数
OpParam::Asum定义于 ops/include/params/asum.h,包含n与incx两个字段,作为OpDesc.specificParam传入RunAsdOpsV2完成下发。
调用示例
以下示例代码旨在提供快速上手、开发和调试算子的最小化实现,核心目标是使用最精简的代码展示算子功能,而非提供生产级的安全保障。不建议用户直接将示例代码作为业务代码;若应用于真实业务场景并发生安全问题,需用户自行承担。
asdBlasSasum(实数绝对值求和)
仓库内的完整可编译版本位于 example/A2/BLAS/asum/example_sasum.cpp,核心流程如下:
#include <iostream> #include <vector> #include "asdsip.h" #include "acl/acl.h" #include "utils/mem_base.h" #include "acl_meta.h" #include <string> 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; } 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 incx = 1; int64_t xSize = 8; int64_t ySize = 1; std::vector<float> tensorInXData; tensorInXData.reserve(xSize); for (int i = 0; i < xSize; i++) { tensorInXData.push_back(1.0 + i); } std::cout << "------- input X -------" << std::endl; for (int64_t i = 0; i < xSize; i++) { std::cout << tensorInXData[i] << " "; } std::cout << std::endl; std::vector<float> tensorInYData; tensorInYData.reserve(ySize); for (int i = 0; i < ySize; i++) { tensorInYData.push_back(0.0); } std::vector<int64_t> xShape = {xSize}; std::vector<int64_t> yShape = {ySize}; aclTensor *inputX = nullptr; aclTensor *inputY = nullptr; void *inputXDeviceAddr = nullptr; void *inputYDeviceAddr = nullptr; ret = CreateAclTensor<float>(tensorInXData, xShape, &inputXDeviceAddr, aclDataType::ACL_FLOAT, &inputX); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor<float>(tensorInYData, yShape, &inputYDeviceAddr, aclDataType::ACL_FLOAT, &inputY); CHECK_RET(ret == ::ACL_SUCCESS, return ret); asdBlasHandle handle; asdBlasCreate(handle); size_t lwork = 0; void *buffer = nullptr; asdBlasMakeAsumPlan(handle); asdBlasGetWorkspaceSize(handle, lwork); std::cout << "lwork = " << lwork << std::endl; if (lwork > 0) { ret = aclrtMalloc(&buffer, static_cast<int64_t>(lwork), ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } asdBlasSetWorkspace(handle, buffer); asdBlasSetStream(handle, stream); ASD_STATUS_CHECK(asdBlasSasum(handle, n, inputX, incx, inputY)); asdBlasSynchronize(handle); asdBlasDestroy(handle); ret = aclrtMemcpy(tensorInYData.data(), ySize * sizeof(float), inputYDeviceAddr, ySize * sizeof(float), 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); std::cout << "------- result -------" << std::endl; std::cout << tensorInYData[0] << std::endl; std::cout << "Execute successfully." << std::endl; aclDestroyTensor(inputX); aclDestroyTensor(inputY); aclrtFree(inputXDeviceAddr); aclrtFree(inputYDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }asdBlasScasum(复数绝对值求和)
完整可编译版本位于 example/A2/BLAS/asum/example_scasum.cpp。与实数版本相比,差异仅在于输入张量的数据类型与构造方式:
#include <iostream> #include "asdsip.h" #include "acl/acl.h" #include "utils/mem_base.h" #include "acl_meta.h" #include <vector> #include <string> using namespace AsdSip; // ASD_STATUS_CHECK / CHECK_RET / LOG_PRINT / GetShapeSize / Init / CreateAclTensor // 定义与 asdBlasSasum 示例一致,此处省略 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 incx = 1; int64_t xSize = 8; int64_t ySize = 1; // 使用 std::complex<float> 构造复数输入 std::vector<std::complex<float>> tensorInXData; tensorInXData.reserve(xSize); for (int i = 0; i < xSize; i++) { tensorInXData.push_back({(float)(1.0 + i), (float)(3.0 + i)}); } std::cout << "------- input X -------" << std::endl; for (int64_t i = 0; i < xSize; i++) { std::cout << tensorInXData[i] << " "; } std::cout << std::endl; std::vector<float> tensorInYData; tensorInYData.reserve(ySize); for (int i = 0; i < ySize; i++) { tensorInYData.push_back(0.0); } std::vector<int64_t> xShape = {xSize}; std::vector<int64_t> yShape = {ySize}; aclTensor *inputX = nullptr; aclTensor *inputY = nullptr; void *inputXDeviceAddr = nullptr; void *inputYDeviceAddr = nullptr; // 复数输入张量使用 ACL_COMPLEX64,输出仍为 ACL_FLOAT ret = CreateAclTensor(tensorInXData, xShape, &inputXDeviceAddr, aclDataType::ACL_COMPLEX64, &inputX); CHECK_RET(ret == ::ACL_SUCCESS, return ret); ret = CreateAclTensor(tensorInYData, yShape, &inputYDeviceAddr, aclDataType::ACL_FLOAT, &inputY); CHECK_RET(ret == ::ACL_SUCCESS, return ret); asdBlasHandle handle; asdBlasCreate(handle); size_t lwork = 0; void *buffer = nullptr; asdBlasMakeAsumPlan(handle); asdBlasGetWorkspaceSize(handle, lwork); std::cout << "lwork = " << lwork << std::endl; if (lwork > 0) { ret = aclrtMalloc(&buffer, static_cast<int64_t>(lwork), ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ::ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } asdBlasSetWorkspace(handle, buffer); asdBlasSetStream(handle, stream); ASD_STATUS_CHECK(asdBlasScasum(handle, n, inputX, incx, inputY)); asdBlasSynchronize(handle); asdBlasDestroy(handle); ret = aclrtMemcpy(tensorInYData.data(), ySize * sizeof(float), inputYDeviceAddr, ySize * sizeof(float), 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); std::cout << "------- result -------" << std::endl; std::cout << tensorInYData[0] << std::endl; std::cout << "Execute successfully." << std::endl; aclDestroyTensor(inputX); aclDestroyTensor(inputY); aclrtFree(inputXDeviceAddr); aclrtFree(inputYDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例运行与编译
- 配置 CANN 环境变量:
source [CANN安装路径]/set_env.sh(默认/usr/local/Ascend/ascend-toolkit/set_env.sh)。 - 编译 SiP 加速库并设置库环境变量:进入仓库根目录执行
bash build.sh,随后source output/set_env.sh。注意:该编译方式仅支持通过 git 下载的仓库,zip 包方式下载不支持;编译过程需要联网下载依赖库,会先获取并编译 ascend-boost-comm 组件,再编译信号加速库。 - 进入
example/A2/BLAS/asum目录,执行bash build.sh编译示例;默认脚本编译example_sasum.cpp,如需运行复数版本,将编译脚本中的源文件替换为example_scasum.cpp后重新编译。 - 更多编译命令说明参见 编译与构建。
示例中生成的数据不代表实际场景,可根据具体使用场景修改数据。
常见问题排查要点
- 返回
ACL_ERROR_FORMAT_NOT_MATCH:输入或输出张量未使用ACL_FORMAT_ND创建,请检查aclCreateTensor的 format 参数。 - 返回
ACL_ERROR_OP_INPUT_NOT_MATCH/ACL_ERROR_OP_OUTPUT_NOT_MATCH:输入元素数与n不一致,或输出 shape 不为[1];复数场景内部按2n校验,需保证存储 shape 与n严格对应。 - 返回
ACL_ERROR_UNSUPPORTED_DATA_TYPE:asdBlasSasum输入必须为ACL_FLOAT,asdBlasScasum输入必须为ACL_COMPLEX64,输出必须为ACL_FLOAT。 asdBlasMakeAsumPlan重复调用失败:同一句柄只能绑定一次 Asum plan,重复初始化会返回ACL_ERROR_INVALID_PARAM;如需复用请走asdBlasDestroy后重新创建句柄。- 结果始终为 0:检查是否遗漏
asdBlasSynchronize,或结果回读(ACL_MEMCPY_DEVICE_TO_HOST)发生在算子异步执行完成之前。
【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考