CANN学习资源开源仓的算子调用快速入门

举报
黄生 发表于 2026/03/23 15:02:31 2026/03/23
【摘要】 算子API调用:调用C语言实现的单算子API接口完成算子计算,设计为两段式接口。aclnnStatus aclxxXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, ..., uint64_t *workspaceSize, aclOpExecutor **executor);aclnnStatus aclxxXxx(...

算子API调用:调用C语言实现的单算子API接口完成算子计算,设计为两段式接口。

aclnnStatus aclxxXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, ..., uint64_t *workspaceSize, aclOpExecutor **executor);
aclnnStatus aclxxXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);

以CANN内置abs算子为例,第一段接口原型与参数:

// 第一段接口
aclnnStatus aclnnAbsGetWorkspaceSize(const aclTensor *self,  aclTensor *out,  uint64_t *workspaceSize,  aclOpExecutor  **executor)
参数名 输入/输出 描述 使用说明 数据类型 数据格式 维度(shape) 非连续Tensor
self 输入 待进行abs计算的入参,公式中的self。 - FLOAT、FLOAT16、DOUBLE、BFLOAT16、INT8、INT16、INT32、INT64、UINT8、BOOL、COMPLEX64 ND 0-8
out 输出 待进行abs计算的出参,公式中的out。 shape与self相同。 FLOAT、FLOAT16、DOUBLE、BFLOAT16、INT8、INT16、INT32、INT64、UINT8、BOOL ND 0-8
workspaceSize 输出 返回需要在Device侧申请的workspace大小。 - - - - -
executor 输出 返回op执行器,包含了算子计算流程。 - - - - -

第二段接口原型与参数:

// 第二段接口
aclnnStatus aclnnAbs( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)
参数名 输入/输出 描述
workspace 输入 在Device侧申请的workspace内存地址。
workspaceSize 输入 在Device侧申请的workspace大小,由第一段接口aclnnAbsGetWorkspaceSize获取。
executor 输入 op执行器,包含了算子计算流程。
stream 输入 指定执行任务的Stream。

以下是代码备注。首先,头文件引入

  • acl.h是ACL框架的汇总头文件,内部已通过#include引入所有核心子头文件,引入该文件即可调用ACL的全部核心功能,是单算子API调用的固定引入头文件;//同自定义算子开发快速入门
  • aclnn_abs.h是CANN算子库中Abs算子的专属头文件,内含Abs算子两段式API的接口定义;开发不同算子的API调用时,需替换为对应算子的专属头文件。//不同。自定义算子开发快速入门引入的是:“kernel_operator.h”

然后下面2步和自定义算子开发快速入门(下面简称前例)是一样的。

  1. aclInit()
  2. aclrtSetDevice() + aclrtCreateStream()

然后在Device侧申请内存存放算子的输入和输出数据,通常申请的内存指针数量与算子的输入输出张量数量保持一致。通过aclrtMalloc接口实现,在Device侧分配指定大小的线性内存,返回已分配内存的指针,且内存首地址默认64字节对齐;需要注意的是,aclrtMalloc分配内存时会做额外的字节对齐处理,会将用户传入的申请大小size向上对齐为32字节的整数倍,再额外多分配32字节。这里定义的输入为单个形状为 [32, 32] 的 float32 ,输出为同形状、同数据类型。

然后构造算子的输入数据,通常是通过读取文件或者从其他算子输出中获取,这里模拟数据,从host的inputData.data()拷贝到device。而不像前例,专门使用了aclrtMallocHost。哦不太对,aclrtMallocHost是专门为输出而使用的。输入和这里也是类似的。

std::vector<float> inputData(INPUT_DIM0 * INPUT_DIM1, -1.0f); //32*32

void FillWithNegativeRandomSimple() {
     for (size_t i = 0; i < inputData.size(); ++i) {
        inputData[i] = -static_cast<float>(i + 1);
    }
}

所以这2步和前例也基本一样:

  1. aclrtMalloc() 分配设备内存

  2. aclrtMemcpy() 拷贝数据到设备

在然后,就是重大区别了。前例使用内核调用符 add<<<blockDim, nullptr, stream>>>(xDevice, yDevice, zDevice, tiling);直接调用自定义核函数,传递设备内存指针和tiling结构体。这里却要从构造输入输出Tensor开始,并与tiling毫不相干。Tensor是算子计算数据的容器,包含了数据内容和Tensor属性信息。

属性 定义
形状 Tensor的形状,如形状(3,4)表示第一维有3个元素,第二维有4个元素,表示一个3行4列的矩阵数组...
数据类型 Tensor对象的数据类型:float16, float32, int8, int16, int32, uint8, uint16, bf16at16, bool等。
数据排布格式 ND、NC1HWC0、NCHW、NHWC等。

由于单算子API调用时输入输出都是Tensor,因此需要先构造输入输出Tensor。这里需要使用aclCreateTensor接口根据已有的Device内存(selfDevice和outDevice)和Tensor属性信息构造Tensor。

aclTensor *inputTensor = nullptr;
aclTensor *outputTensor = nullptr;

std::vector<int64_t> inputShape = {32, 32};
std::vector<int64_t> outputShape = {32, 32};

int ConstructingTensors() {
  inputTensor = aclCreateTensor(inputShape.data(), inputShape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, inputShape.data(),
                              inputShape.size(), selfDevice);
  outputTensor = aclCreateTensor(outputShape.data(), outputShape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, outputShape.data(),
                              outputShape.size(), outDevice);
  return 0;
}

在aclCreateTensor() 创建Tensor对象后,还需要显式的aclnnAbsGetWorkspaceSize() 获取workspace大小,然后再aclrtMalloc() 申请workspace内存。在此之后才可以像前例调用核函数一样,调用第二段接口aclnnAbs() 执行算子运算。

再往后的步骤,就与前例大同小异了:

  1. aclrtSynchronizeStream()
  2. aclrtMemcpy() 拷贝结果回主机(Device侧的数据无法直接打印或者保存文件)
  3. aclrtFree() 释放资源(包括workspace)
  4. aclFinalize()

最后是编译和运行

g++ -std=c++11 -fPIC -O0 -g -Wall  \
-I$ASCEND_TOOLKIT_HOME/include \
-I$ASCEND_TOOLKIT_HOME/include/aclnn \
Sources/test_abs.cpp -o Sources/opapi_test \
$ASCEND_TOOLKIT_HOME/lib64/libascendcl.so $ASCEND_TOOLKIT_HOME/lib64/libnnopbase.so $ASCEND_TOOLKIT_HOME/lib64/libopapi_math.so

./Sources/opapi_test

附完整代码:

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_abs.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)

int InitACL() {
  auto ret = aclInit(nullptr);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
  return 0;
}

int32_t deviceId = 0;
aclrtStream stream;

int InitResource() {
  auto 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;
}

// 定义输入输出参数
constexpr uint32_t INPUT_DIM0 = 32;
constexpr uint32_t INPUT_DIM1 = 32;
constexpr float VALUE_X = 0.0f;
constexpr float VALUE_Y = 0.0f;

void* selfDevice = nullptr;
void* outDevice = nullptr;

uint32_t dataSize = INPUT_DIM0 * INPUT_DIM1 * sizeof(float);

int AllocateMemory() {

  auto ret = aclrtMalloc(&selfDevice, dataSize, ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for x failed. ERROR: %d\n", ret));

  ret = aclrtMalloc(&outDevice, dataSize, ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for z failed. ERROR: %d\n", ret));
  return 0;
}

std::vector<float> inputData(INPUT_DIM0 * INPUT_DIM1, -1.0f);


void FillWithNegativeRandomSimple() {
     for (size_t i = 0; i < inputData.size(); ++i) {
        inputData[i] = -static_cast<float>(i + 1);
    }
}

int TransferData() {
  auto ret = aclrtMemcpy(selfDevice, dataSize, inputData.data(), dataSize, ACL_MEMCPY_HOST_TO_DEVICE);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy H2D for x failed. ERROR: %d\n", ret); return ret);

  return 0;
}
aclTensor *inputTensor = nullptr;

aclTensor *outputTensor = nullptr;

std::vector<int64_t> inputShape = {32, 32};

std::vector<int64_t> outputShape = {32, 32};
int ConstructingTensors() {
  inputTensor = aclCreateTensor(inputShape.data(), inputShape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, inputShape.data(),
                              inputShape.size(), selfDevice);
  outputTensor = aclCreateTensor(outputShape.data(), outputShape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, outputShape.data(),
                              outputShape.size(), outDevice);
  return 0;
}

void* workspace = nullptr;
uint64_t  workspaceSize = 0;
aclOpExecutor* executor;
int CalculateAndAllocateWorkspace() {
  auto ret = aclnnAbsGetWorkspaceSize(inputTensor, outputTensor, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAbsGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);

  if (workspaceSize > 0) {
    ret = aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for workspace failed. ERROR: %d\n", ret); return ret);
  }

  return 0;
}

int ExecuteOperator() {
  auto ret = aclnnAbs(workspace, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAbs failed. ERROR: %d\n", ret); return ret);
  return 0;
}

int SynchronizeStream() {
  // 等待流执行完成
  auto ret = aclrtSynchronizeStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

  return 0;
}

int ProcessOutput() {
  std::vector<float> outHost(INPUT_DIM0 * INPUT_DIM1);
  uint32_t dataSize = INPUT_DIM0 * INPUT_DIM1 * sizeof(float);

  auto ret = aclrtMemcpy(outHost.data(), dataSize, outDevice, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy D2H for z failed. ERROR: %d\n", ret); return ret);

  bool resultCorrect = true;
  for (size_t i = 0; i < outHost.size(); ++i) {
    printf("abs(%f) =  is: %f\n", inputData[i], outHost[i]);
  }
  return 0;
}

int ReleaseResource() {
  aclDestroyTensor(inputTensor);
  aclDestroyTensor(outputTensor);

  // 7. 释放device资源
  aclrtFree(selfDevice);
  aclrtFree(outDevice);
  if (workspaceSize > 0) {
    aclrtFree(workspace);
  }
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);

  return 0;
}
int FinalizeACL(){
    aclFinalize();
    return 0;
}
int main() {
    InitACL();
    InitResource();
    AllocateMemory();
    FillWithNegativeRandomSimple();
    TransferData();
    ConstructingTensors();
    CalculateAndAllocateWorkspace();
    ExecuteOperator();
    SynchronizeStream();
    ProcessOutput();
    ReleaseResource();
    FinalizeACL();
}

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。