CANN SiP 算子 asdInterpWithCoeff 使用指南:基于系数的向量插值接口原理、参数与实战
【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip
本篇技术指南围绕 CANN 信号处理加速库(SiP)中的向量插值算子asdInterpWithCoeff展开,系统讲解其在信道估计与均衡系数插值场景中的功能定位、公式语义、产品支持矩阵、完整参数约束、ACL 调用流程与源码级实现原理。读者读完本文后,将能够独立完成该算子的 workspace 查询、Tensor 构造、异步调度、结果回读与资源释放,并理解从 Host 侧 API 到 Ascend 算子内核的完整调用链。
一、接口功能与计算公式
asdInterpWithCoeff是 CANN 信号处理加速库(SiP)提供的向量插值算子接口,主要用于数据符号的信道估计与均衡系数插值两类信号处理场景。接口由一对函数构成,遵循 SiP 算子"先查 workspace、再调度执行"的标准两步式调用范式:
asdInterpWithCoeffGetWorkspaceSize:计算asdInterpWithCoeff算子执行所需的 workspace 大小(单位:字节);asdInterpWithCoeff:执行向量插值计算,将输入信号x与插值系数coefficient组合得到输出y。
接口文档给出的计算公式为:
$$ result = A \odot B = (A){ij}(B){ij} $$
其中A对应系数张量coefficient,B对应输入信号张量x,result对应输出张量y。文档中的示意性示例(两个输入均为2×2的复矩阵)如下:
输入
A(即coefficient):[ [ 1+1i, 1+1i ], [ 2+2i, 2+2i ] ]输入
B(即x):[ [ 1+1i, 1+1i ], [ 2+2i, 2+2i ] ]调用
asdInterpWithCoeff后输出result:[ [ 0+2i, 0+2i ], [ 0+8i, 0+8i ] ]
容易验证,该示例符合复数逐元素相乘规则:$(1+1i)\times(1+1i)=0+2i$,$(2+2i)\times(2+2i)=0+8i$。
补充说明:仓库中位于 example/A2/Interpolation/asd_interp_with_coeff/README.md 的示例 README 从插值语义给出了另一种表述形式 $y[b,i]=\sum_{j=0}^{L-1} coefficient[b,i,j]\cdot x[b, base[b,i]+j]$,即输出是输入信号按参考信号位置与系数加权组合的结果;而在 sip_pta/test/Interpolation/interp_with_coeff.py 的 PTA 测试中,CPU 参考实现采用torch.bmm(coefficient, x)的批量矩阵乘形式构造(shape 为(batch, 14-nRs, nRs) × (batch, nRs, totalSubcarrier))。这些表述从不同角度刻画了同一算子的"按系数组合信号"本质,实际计算语义以算子内核实现为准。
二、产品支持情况
该算子在当前仓库支持的 Ascend 产品如下表所示(对应 docs/zh/API_Reference/Interpolation/asdInterpWithCoeff.md 中声明):
| 产品型号 | 支持情况 |
|---|---|
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Ascend 950PR / Ascend 950DT | 不支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 不支持 |
示例目录 example/A2/Interpolation/asd_interp_with_coeff/README.md 进一步说明该算子适用于 Atlas A2/A3 训练系列产品、Atlas 800I A2 推理产品、Atlas A3 推理系列产品。在目标设备上运行前,请先确认硬件型号属于上述支持范围。
三、函数原型与头文件
两个接口均声明于命名空间AsdSip,头文件为仓库根目录下的 include/interp_api.h:
namespace AsdSip { AspbStatus asdInterpWithCoeff( const aclTensor *x, const aclTensor *coefficient, aclTensor *output, void *stream, void *workSpace = nullptr); AspbStatus asdInterpWithCoeffGetWorkspaceSize(size_t &workspaceSize); } // namespace AsdSip从 include/interp_api.h 可以看到,该头文件依赖acl/acl.h(Tensor 与流相关定义)、utils/aspb_status.h(返回状态码AspbStatus)与utils/mem_base.h(内存基类),使用前需确保 CANN 环境变量已正确配置(详见本文"编译与运行"一节)。
两个接口的完整函数原型如下:
AspbStatus asdInterpWithCoeffGetWorkspaceSize( size_t & workspaceSize)AspbStatus asdInterpWithCoeff( const aclTensor * x, const aclTensor * coefficient, aclTensor * y, void * stream, void * workSpace = nullptr)四、参数详解
4.1 asdInterpWithCoeffGetWorkspaceSize
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspaceSize(size_t &) | 输出 | 算子执行所需要的 workspace 大小(字节数) |
返回值:返回状态码AspbStatus,具体取值参见 SiP返回码。
从实现看,core/interpolation/asdInterpWithCoeff.cpp 第 120~124 行将该值固定为常量INTERP_WORKSPACE_SIZE = 65536 * 20 * 2 = 2621440字节(约 2.5 MB),并始终返回ACL_SUCCESS:
AspbStatus asdInterpWithCoeffGetWorkspaceSize(size_t& workspaceSize) { workspaceSize = INTERP_WORKSPACE_SIZE; return ErrorType::ACL_SUCCESS; }调用方应在调度算子前先调用该函数获取大小,再通过aclrtMalloc在 Device 侧申请对应大小的内存;只有当返回值lwork > 0时才需要申请并传入 workspace。
4.2 asdInterpWithCoeff
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| x(aclTensor *) | 输入 | 对应公式中的B(输入信号)。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为[batch, nRs, totalSubcarrier] |
| coefficient(aclTensor *) | 输入 | 对应公式中的A(插值系数)。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为[batch, 14-nRs, nRs] |
| y(aclTensor *) | 输出 | 对应公式中的result。数据类型支持 COMPLEX32、COMPLEX64;数据格式支持 ND;shape 为[batch, 14-nRs, totalSubcarrier] |
| stream(void *) | 输入 | NPU 执行流(aclrtStream) |
| workSpace(void *) | 输入 | 算子执行所需的 workspace,由asdInterpWithCoeffGetWorkspaceSize查询大小后申请,缺省为nullptr |
返回值:返回状态码AspbStatus,具体取值参见 SiP返回码。
4.3 维度与取值范围详解
三个张量共享如下维度语义(源自 docs/zh/API_Reference/Interpolation/asdInterpWithCoeff.md):
- batch:波束数量,取值范围 1~1024。6G 场景下最大取值为
16(终端的流数)× 64(基站接收的波束数)= 1024; - nRs:参考信号数(Reference Signal 数),取值仅为2 或 4;
- totalSubcarrier:总子载波数,满足
totalSubcarrier = nRB * 12; - nRB:资源块数,取值范围 1~2730。每个 RB 包含 12 个子载波;5G 时 nRB 取值范围为 1~273,6G 时取值为 5G 的 4 倍到 10 倍;
14 - nRs:输出/系数在第一维度上的长度,源于每个时隙(slot)包含 14 个 OFDM 符号,减去 nRs 个参考信号位置后即待插值符号数,即源码中interpLength = 14 - rsNum。
上述取值范围与 core/interpolation/asdInterpWithCoeff.cpp 中的常量完全对应,可作为参数校验的硬性约束依据:
constexpr uint32_t INTERP_WORKSPACE_SIZE = 65536 * 20 * 2; constexpr uint64_t INTERP_DIMS_THREE = 3; // 张量必须为 3 维 constexpr int64_t MAX_BATCH = 1024; // batch 上限 constexpr int64_t INTERP_RS_TWO = 2; // nRs 可取 2 constexpr int64_t INTERP_RS_FOUR = 4; // nRs 可取 4 constexpr int64_t MAX_TOTAL_SUBCARRIER = 32760; // totalSubcarrier 上限 = 2730 * 12 constexpr int64_t MAX_SIGNAL_NUM = 14; // 符号总数 14 constexpr int DIMS_TWO = 2;同时,Host 侧入口会对输入做一致性校验:x必须为 3 维且storageDims[0] <= 1024、storageDims[1] ∈ {2, 4}、totalSubcarrier <= 32760;coefficient必须为 3 维且第二维在[0, 14]区间;此外还要求x的 batch 与coefficient的 batch 一致,且x的nRs与coefficient的第三维长度一致(即storageDims[0] == coeffDims[0] && storageDims[1] == coeffDims[2]),否则返回ACL_ERROR_OP_INPUT_NOT_MATCH。
五、约束说明与调用前置条件
接口文档声明该算子"约束说明:无",即除上文参数取值范围的硬性约束外无额外限制。但在实际工程使用中仍需注意:
- 输入/输出/系数张量数据类型必须为 COMPLEX32 或 COMPLEX64,且建议三者的类型保持一致(PTA 适配层 sip_pta/csrc/Interpolation/asd_interp_with_coeff.cpp 中显式要求
x与coefficient数据类型一致,否则报错); - 数据格式仅支持 ND(连续内存布局),不支持其他 format;
- 张量必须为 3 维,维度数不合法时 Host 层校验会直接拒绝;
- 由于涉及复数运算,内存字节数需按"元素个数 × 2 × 元素字节数"计算(COMPLEX64 每元素 16 字节,COMPLEX32 每元素 8 字节)。
六、调用示例(完整可运行代码)
文档给出的调用示例旨在提供快速上手、开发和调试算子的最小化实现,其核心目标是使用最精简的代码展示算子核心功能,而非生产级安全保障。若将示例代码应用于真实业务场景并产生安全问题,需用户自行承担。完整代码同样位于 example/A2/Interpolation/asd_interp_with_coeff/example_asd_interp_with_coeff.cpp,核心流程如下:
#include <iostream> #include <complex> #include <vector> #include "interp_api.h" #include "acl/acl.h" #include "acl_meta.h" using namespace AsdSip; 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初始化 aclInit(nullptr); aclrtSetDevice(deviceId); aclrtCreateStream(stream); 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) * 2; // 2 : complex // 调用aclrtMalloc申请device侧内存 aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // 调用aclrtMemcpy将host侧数据复制到device侧内存上 aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); // 计算连续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) { // 设置算子使用的device id int deviceId = 0; //(固定写法)创造执行流 aclrtStream stream; Init(deviceId, &stream); // 创造tensor的Host侧数据 int64_t batch = 1; int64_t nRs = 2; int64_t totalSubcarrier = 32; int64_t nSignal = 14; int64_t xSize = batch * nRs * totalSubcarrier * 2; std::vector<float> tensorInXData; tensorInXData.resize(xSize); for (int64_t i = 0; i < xSize; i++) { tensorInXData[i] = 1.0 + i; } int64_t coeffSize = batch * (nSignal - nRs) * nRs * 2; std::vector<float> coeffData; coeffData.resize(xSize); for (int64_t i = 0; i < coeffSize; i++) { coeffData[i] = 1; } int64_t resultSize = batch * (nSignal - nRs) * totalSubcarrier * 2; std::vector<float> resultData; resultData.resize(resultSize); for (int64_t i = 0; i < resultSize; i++) { resultData[i] = 2; } // int64_t xSize = batch * nRs * totalSubcarrier; // std::vector<std::complex<float>> tensorInXData(xSize, std::complex<float>(0, 0)); // for (int i = 0; i < xSize; i++) { // tensorInXData[i] = std::complex<float>(i * 2, i * 2 + 1); // } // int64_t coeffSize = batch * (nSignal - nRs) * nRs; // std::vector<std::complex<float>> coeffData(xSize, std::complex<float>(0, 0)); // for (int i = 0; i < coeffSize; i++) { // coeffData[i] = std::complex<float>(1, 1); // } // int64_t resultSize = batch * (nSignal - nRs) * totalSubcarrier; // std::vector<std::complex<float>> resultData(xSize, std::complex<float>(0, 0)); // for (int i = 0; i < resultSize; i++) { // resultData[i] = std::complex<float>(2, 2); // } std::cout << "------- input x -------" << std::endl; for (int64_t i = 0; i < xSize; i++) { std::cout << tensorInXData[i] << " "; } std::cout << std::endl; std::cout << "------- input coeff -------" << std::endl; for (int64_t i = 0; i < coeffSize; i++) { std::cout << coeffData[i] << " "; } std::cout << std::endl; // 创造输入/输出tensor aclTensor *inputX = nullptr; aclTensor *inputCoeff = nullptr; aclTensor *result = nullptr; void *inputXDeviceAddr = nullptr; void *inputYDeviceAddr = nullptr; void *resultDeviceAddr = nullptr; CreateAclTensor(tensorInXData, {batch, nRs, totalSubcarrier}, &inputXDeviceAddr, aclDataType::ACL_COMPLEX64, &inputX); CreateAclTensor(coeffData, {batch, nSignal-nRs, nRs}, &inputYDeviceAddr, aclDataType::ACL_COMPLEX64, &inputCoeff); CreateAclTensor(resultData, {batch, nSignal-nRs, totalSubcarrier}, &resultDeviceAddr, aclDataType::ACL_COMPLEX64, &result); size_t lwork = 0; void *buffer = nullptr; AsdSip::asdInterpWithCoeffGetWorkspaceSize(lwork); if (lwork > 0) { aclrtMalloc(&buffer, static_cast<int64_t>(lwork), ACL_MEM_MALLOC_HUGE_FIRST); } asdInterpWithCoeff(inputX, inputCoeff, result, stream, buffer); aclrtSynchronizeStream(stream); // 将输出tensor的Device侧数据复制到Host侧内存上 aclrtMemcpy(resultData.data(), resultSize * sizeof(float), resultDeviceAddr, resultSize * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); std::cout << "------- result -------" << std::endl; for (int64_t i = 0; i < nSignal - nRs; i++) { for (int64_t j = 0; j < totalSubcarrier * 2; j++) { std::cout << resultData[i * totalSubcarrier * 2 + j] << " "; } std::cout << std::endl; } // 资源释放 aclDestroyTensor(inputX); aclDestroyTensor(inputCoeff); aclDestroyTensor(result); aclrtFree(inputXDeviceAddr); aclrtFree(inputYDeviceAddr); aclrtFree(resultDeviceAddr); if (lwork > 0) { aclrtFree(buffer); } // 调度算子后重置算子使用的deviceId aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }6.1 示例代码要点拆解
结合上文参数说明,该示例的调用流程可归纳为以下六个阶段,供读者复用为通用模板:
- ACL 初始化(固定写法):
aclInit(nullptr)→aclrtSetDevice(deviceId)→aclrtCreateStream(stream),在Init函数中一次性完成; - 构造 Host 侧数据:示例采用"实部/虚部交错存储"的
float数组表达复数(长度 = 元素个数 × 2),并附带了使用std::complex<float>的注释版本,两种写法等价,可按习惯选择; - 创建 aclTensor:模板函数
CreateAclTensor依次完成 Device 侧内存申请(aclrtMalloc,注意复数 size 需× 2)、Host→Device 拷贝(aclrtMemcpy)、连续 tensor 的 strides 计算以及aclCreateTensor创建(数据格式固定为ACL_FORMAT_ND)。三个张量的 shape 分别为{batch, nRs, totalSubcarrier}、{batch, nSignal-nRs, nRs}、{batch, nSignal-nRs, totalSubcarrier},与第 4.3 节的维度约束完全一致; - 查询并申请 workspace:先调用
asdInterpWithCoeffGetWorkspaceSize(lwork)获取大小,若lwork > 0则用aclrtMalloc申请buffer,作为asdInterpWithCoeff的最后一个入参传入; - 异步调度与结果回读:调用
asdInterpWithCoeff(inputX, inputCoeff, result, stream, buffer)异步下发算子,随后aclrtSynchronizeStream(stream)等待完成,再通过aclrtMemcpy将结果从 Device 拷贝回 Host 并打印; - 资源释放:依次销毁三个
aclTensor(aclDestroyTensor)、释放 Device 内存(aclrtFree,含 workspace buffer)、销毁流并复位设备(aclrtDestroyStream→aclrtResetDevice→aclFinalize)。
示例中的数据为演示用途,不代表实际业务场景,可根据具体使用场景修改batch、nRs、totalSubcarrier与数据内容(示例 README 亦如此说明)。
七、源码级实现解析
7.1 Host 侧入口:参数校验、OpDesc 组装与调度
core/interpolation/asdInterpWithCoeff.cpp 是 Host 侧核心实现,其执行流程为:
getInputShape(x):通过aclGetStorageShape获取x的存储形状,校验维度数必须为 3、batch <= 1024、nRs ∈ {2, 4}、totalSubcarrier <= 32760,任一不满足即记录错误日志并返回空指针;getCoeffShape(coefficient):校验coefficient为 3 维且第二维(插值长度)在[0, 14]区间;- 交叉一致性校验:
x的 batch 必须等于coefficient的 batch,x的 nRs 必须等于coefficient的第三维长度,否则返回ACL_ERROR_OP_INPUT_NOT_MATCH; - 组装算子描述:
opDesc.opName = "InterpByCoeffOperation",并将batch、rsNum、totalSubcarrier、interpLength填充到OpParam::InterpByCoeff结构体中(结构体定义见 ops/include/params/interpbycoeff.h,其中interpLength = 14 - rsNum为插值长度); - 将
{x, coefficient}作为输入张量、{output}作为输出张量,连同 stream、opDesc、workspace 一并交给RunAsdOpsV2完成实际调度。
7.2 算子注册与 Shape 推导
算子本体注册于 ops/blas/interpbycoeff/interbycoeff_operation.cpp:
InterpByCoeffOperation继承自OperationBase,输入张量数为 2、输出张量数为 1;GetBestKernel返回名为InterpByCoeffKernel的内核;InferShapeImpl中,输出张量描述直接继承输入张量x的描述,仅将第 1 维(dims[1])改写为param.interpLength(即14 - nRs),与文档中输出 shape[batch, 14-nRs, totalSubcarrier]吻合;- 最终通过
REG_OPERATION(InterpByCoeffOperation)宏完成注册,可被 Host 侧RunAsdOpsV2按算子名InterpByCoeffOperation查找并调用。
7.3 内核与 Tiling
算子内核与切分逻辑位于 ops/blas/interpbycoeff/interpbycoeff 目录下:
kernel/interpbycoeff.cpp、interpbycoeff_aic.h、interpbycoeff_aiv.h、interpbycoeff_const.h:AIC/AIV 双核架构的内核实现与常量定义;tiling/interpbycoeff_tiling.cpp、interpbycoeff_tiling.h、interpbycoeff_tiling_data.h:负责将batch × (14-nRs) × totalSubcarrier的输出空间切分为可被多核并行执行的 tile,并下发切分参数;interbycoeff_kernel.cpp:内核侧入口封装。
从源码结构看,该算子采用"Host 侧组参 + Tiling 切分 + AIC/AIV 异构内核"的典型昇腾算子工程结构,batch(波束)维度天然提供了并行度来源。
7.4 PyTorch 适配层(PTA)
仓库同时提供了 PyTorch 适配实现 sip_pta/csrc/Interpolation/asd_interp_with_coeff.cpp,将算子注册为torch_sip库下的自定义算子asd_interp_with_coeff(Tensor x, Tensor coefficient) -> Tensor:
- 入参为两个
at::Tensor,要求均为 3 维、数据类型一致且为ComplexFloat(COMPLEX64)或ComplexHalf(COMPLEX32); - 输出 shape 计算为
{batch, coefficient.size(1), x.size(2)},即[batch, 14-nRs, totalSubcarrier]; - 内部复用
asdInterpWithCoeffGetWorkspaceSize查询 workspace,通过at_npu::native::allocate_workspace在 NPU 上分配,并取当前 NPU 流(getCurrentNPUStream)调用AsdSip::asdInterpWithCoeff。
配套的连通性测试脚本为 sip_pta/test/Interpolation/interp_with_coeff.py,覆盖(batch, nRs, totalSubcarrier)为(1,2,32)、(4,4,64)、(8,2,128)的 COMPLEX64 用例以及(1,2,32)、(4,4,64)的 COMPLEX32 用例,CPU 参考使用torch.bmm(coefficient, x)构造,精度阈值取rtol=atol=1e-3(COMPLEX64)或1e-2(COMPLEX32),可作为算子行为正确性的快速验证依据。
八、编译与运行
在支持产品上编译并运行该算子示例,可参照 example/A2/Interpolation/asd_interp_with_coeff/README.md 的说明:
配置 CANN 环境变量:
source [CANN安装路径]/set_env.sh默认路径为:
source /usr/local/Ascend/ascend-toolkit/set_env.sh编译 SiP 信号处理加速库:进入 SiP 仓库根目录执行:
cd ${SiP_root_path} bash build.sh source output/set_env.sh特别说明:
- 上述编译方式仅支持编译通过 git 下载的加速库,以 zip 压缩包方式下载的加速库不支持该编译方式;
- 编译过程需要联网下载依赖库,因此编译环境需要联网;
- 该编译过程包含获取并编译 ascend-boost-comm(昇腾分布式通信加速库)组件、以及编译信号加速库两个步骤;
- 更多编译命令说明可参考 docs/compilation_build.md 与 docs/compilation_build_en.md。
编译并运行示例:进入示例所在目录执行构建脚本:
cd ${示例所在目录} bash build.sh运行后程序将依次打印输入
x、输入coefficient以及输出result,其中result以(14-nRs) × (totalSubcarrier×2)的矩阵形式输出,可直接与理论值比对验证算子正确性。
九、总结
asdInterpWithCoeff是 CANN SiP 信号处理加速库中面向信道估计与均衡系数插值场景的核心算子,通过asdInterpWithCoeffGetWorkspaceSize与asdInterpWithCoeff两步式接口,屏蔽了底层 Tiling 切分与 AIC/AIV 异构调度的复杂性。本文从功能公式、产品支持矩阵、参数与维度约束、完整 ACL 调用示例、Host 侧源码实现、算子注册与内核结构、PyTorch 适配及编译运行方式等维度做了系统梳理,读者可基于 docs/zh/API_Reference/Interpolation/asdInterpWithCoeff.md 与上述源码路径进一步深入研读,并结合 SiP返回码 完成异常处理与调试。
【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库,基于华为Ascend AI处理器,专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考