CANN SiP 算子 asdInterpWithCoeff 使用指南:基于系数的向量插值接口原理、参数与实战
2026/9/18 2:36:41 网站建设 项目流程

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对应系数张量coefficientB对应输入信号张量xresult对应输出张量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] <= 1024storageDims[1] ∈ {2, 4}totalSubcarrier <= 32760coefficient必须为 3 维且第二维在[0, 14]区间;此外还要求x的 batch 与coefficient的 batch 一致,且xnRscoefficient的第三维长度一致(即storageDims[0] == coeffDims[0] && storageDims[1] == coeffDims[2]),否则返回ACL_ERROR_OP_INPUT_NOT_MATCH

五、约束说明与调用前置条件

接口文档声明该算子"约束说明:无",即除上文参数取值范围的硬性约束外无额外限制。但在实际工程使用中仍需注意:

  1. 输入/输出/系数张量数据类型必须为 COMPLEX32 或 COMPLEX64,且建议三者的类型保持一致(PTA 适配层 sip_pta/csrc/Interpolation/asd_interp_with_coeff.cpp 中显式要求xcoefficient数据类型一致,否则报错);
  2. 数据格式仅支持 ND(连续内存布局),不支持其他 format;
  3. 张量必须为 3 维,维度数不合法时 Host 层校验会直接拒绝;
  4. 由于涉及复数运算,内存字节数需按"元素个数 × 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 示例代码要点拆解

结合上文参数说明,该示例的调用流程可归纳为以下六个阶段,供读者复用为通用模板:

  1. ACL 初始化(固定写法)aclInit(nullptr)aclrtSetDevice(deviceId)aclrtCreateStream(stream),在Init函数中一次性完成;
  2. 构造 Host 侧数据:示例采用"实部/虚部交错存储"的float数组表达复数(长度 = 元素个数 × 2),并附带了使用std::complex<float>的注释版本,两种写法等价,可按习惯选择;
  3. 创建 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 节的维度约束完全一致;
  4. 查询并申请 workspace:先调用asdInterpWithCoeffGetWorkspaceSize(lwork)获取大小,若lwork > 0则用aclrtMalloc申请buffer,作为asdInterpWithCoeff的最后一个入参传入;
  5. 异步调度与结果回读:调用asdInterpWithCoeff(inputX, inputCoeff, result, stream, buffer)异步下发算子,随后aclrtSynchronizeStream(stream)等待完成,再通过aclrtMemcpy将结果从 Device 拷贝回 Host 并打印;
  6. 资源释放:依次销毁三个aclTensoraclDestroyTensor)、释放 Device 内存(aclrtFree,含 workspace buffer)、销毁流并复位设备(aclrtDestroyStreamaclrtResetDeviceaclFinalize)。

示例中的数据为演示用途,不代表实际业务场景,可根据具体使用场景修改batchnRstotalSubcarrier与数据内容(示例 README 亦如此说明)。

七、源码级实现解析

7.1 Host 侧入口:参数校验、OpDesc 组装与调度

core/interpolation/asdInterpWithCoeff.cpp 是 Host 侧核心实现,其执行流程为:

  1. getInputShape(x):通过aclGetStorageShape获取x的存储形状,校验维度数必须为 3、batch <= 1024nRs ∈ {2, 4}totalSubcarrier <= 32760,任一不满足即记录错误日志并返回空指针;
  2. getCoeffShape(coefficient):校验coefficient为 3 维且第二维(插值长度)在[0, 14]区间;
  3. 交叉一致性校验:x的 batch 必须等于coefficient的 batch,x的 nRs 必须等于coefficient的第三维长度,否则返回ACL_ERROR_OP_INPUT_NOT_MATCH
  4. 组装算子描述:opDesc.opName = "InterpByCoeffOperation",并将batchrsNumtotalSubcarrierinterpLength填充到OpParam::InterpByCoeff结构体中(结构体定义见 ops/include/params/interpbycoeff.h,其中interpLength = 14 - rsNum为插值长度);
  5. {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.cppinterpbycoeff_aic.hinterpbycoeff_aiv.hinterpbycoeff_const.h:AIC/AIV 双核架构的内核实现与常量定义;
  • tiling/interpbycoeff_tiling.cppinterpbycoeff_tiling.hinterpbycoeff_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 的说明:

  1. 配置 CANN 环境变量

    source [CANN安装路径]/set_env.sh

    默认路径为:source /usr/local/Ascend/ascend-toolkit/set_env.sh

  2. 编译 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。
  3. 编译并运行示例:进入示例所在目录执行构建脚本:

    cd ${示例所在目录} bash build.sh

    运行后程序将依次打印输入x、输入coefficient以及输出result,其中result(14-nRs) × (totalSubcarrier×2)的矩阵形式输出,可直接与理论值比对验证算子正确性。

九、总结

asdInterpWithCoeff是 CANN SiP 信号处理加速库中面向信道估计与均衡系数插值场景的核心算子,通过asdInterpWithCoeffGetWorkspaceSizeasdInterpWithCoeff两步式接口,屏蔽了底层 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),仅供参考

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

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

立即咨询