开发者分享 | Ascend C算子开发及单算子调用

本文分享自《AscendC算子开发及单算子调用》,作者:goldpancake。

笔者在阅读Ascend C官方文档的过程中发现,对于初学者来说,尤其是第一次接触异构编程思想的初学者,有部分内容是无需特别关注的,例如算子工程的相关的CmakeLists.txt,以及单算子调用的一些通用工具类文件。同时,在环境配置的过程中,也发现了一些需要注意的地方,特此记录备忘。

1 环境准备

笔者的硬件及系统环境如下:

  • 操作系统:openEuler release 20.03 (LTS-SP3)

  • 设备:Ascend 910

开发环境需要准备三个run包,分别是驱动、固件和cann-toolkit开发套件,笔者这里使用当前的最新版本CANN开发套件,版本号为7.0.RC1.alpha003,并在昇腾社区下载好对应驱动和固件的run包。

1.1 安装流程

上述准备的三个包,按照驱动 -> 固件 -> CANN开发套件包的顺序来安装。

首先安装驱动,执行如下命令:

/path/to/Ascend-hdk-910-npu-driver_23.0.rc2_linux-aarch64.run --full --install-for-all

注意:笔者使用root用户进行安装,以full模式执行run包,并加上install-for-all选项来为所有用户安装。

接下来安装固件,执行如下命令:

/path/to/Ascend-hdk-910-npu-firmware_6.4.12.1.241.run --full

驱动和固件都安装完成后,最好重启一次系统:

reboot

重启完成后,安装CANN开发套件包:

path/to/Ascend-cann-toolkit_7.0.RC1.alpha003_linux-aarch64.run --full --install-for-all

安装完成后,开发环境就准备好了。

1.2 安装过程中可能的问题

笔者在安装过程中,遇到了一个问题,很蠢,但值得注意。

问题的表现是,在按照上述的流程安装好开发环境之后,除root用户外的其他普通用户使用msopgen工具生成算子工程时,出现了权限不足的问题。但因为加上了install-for-all选项,所以不应该是CANN包的权限问题。然后又查看msopgen的代码发现,该工具将python解释器指定为了root用户下的conda环境中的解释器。

#!/root/miniconda3/bin/python3
# coding=utf-8
"""
Function:
This file mainly involves main function of op generation module.
Copyright Information:
Huawei Technologies Co., Ltd. All Rights Reserved © 2020
"""

原来是root用户下的conda配置为了默认激活base环境,笔者安装时没有注意这一点,导致在CANN包安装的过程中,选择到了conda环境下的python解释器,这样一来,其他用户肯定是没有权限的。在关闭base环境重新安装CANN包后,问题解决。

2 算子开发流程

至此,环境准备好后,开始正式的算子开发步骤。

2.1 算子工程配置文件

CANN包中提供了一个自动生成算子工程的工具msopgen,该工具可以通过一个json配置文件来生成完整的算子工程,具体的编写方式请参考Ascend C官方文档。

这里以sinh算子为例,该算子是一元操作,所以只需要一个输入,且输出形状与输入形状一致。根据该特征来编写json文件,为了贴合Ascend C官方建议的编程范式,将文件命名为sinh_custom.json。为了简洁,这里我们只实现一种数据类型的操作。

[{"op": "SinhCustom","language": "cpp","input_desc": [{"name": "x","param_type": "required","format": ["ND"],"type": ["fp16"]}],"output_desc": [{"name": "y","param_type": "required","format": ["ND"],"type": ["fp16"]}]}
]
 

2.2 生成算子工程

创建一个文件夹用作算子工程目录,使用msopgen工具执行如下命令来生成算子工程。

mkdir /path/to/SinhCustom
/path/to/msopgen gen -i /path/to/sinh_custom.json -c ai_core-Ascend910 -lan cpp -out /path/to/SinhCustom

命令行会输出类似如下的信息:

2023-10-07 14:58:42 (942445) - [INFO] Start to generate AI Core operator files.
2023-10-07 14:58:42 (942445) - [INFO] Start to parse the ir template:/path/to/SinhCustom/sinh_custom.json
2023-10-07 14:58:42 (942445) - [INFO] Start to parse the op: SinhCustom
2023-10-07 14:58:42 (942445) - [INFO] Start to parse the input_desc: x
2023-10-07 14:58:42 (942445) - [INFO] Start to parse the output_desc: y
2023-10-07 14:58:42 (942445) - [WARNING] The "attr" value is invalid or no "attr" exists in the map.
2023-10-07 14:58:42 (942445) - [INFO] Start to check the type and format between the inputs/outputs in IR template.
2023-10-07 14:58:42 (942445) - [INFO] Start to generate a new project.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/cmake/config.cmake generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/op_host/sinh_custom_tiling.h generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/op_host/sinh_custom.cpp generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/op_kernel/sinh_custom.cpp generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/framework/tf_plugin/tensorflow_sinh_custom_plugin.cc generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] File /path/to/SinhCustom/framework/tf_plugin/CMakeLists.txt generated successfully.
2023-10-07 14:58:42 (942445) - [INFO] Generation completed.
此时会发现指定的输出目录只已经生成了一系列的算子工程文件。
SinhCustom
├── build.sh
├── cmake
├── CMakeLists.txt
├── CMakePresets.json # 这个配置项需要修改
├── framework
├── op_host
│   ├── CMakeLists.txt
│   ├── sinh_custom.cpp # 算子host侧核心逻辑
│   └── sinh_custom_tiling.h # 算子tiling结构体定义
├── op_kernel
│   ├── CMakeLists.txt
│   └── sinh_custom.cpp # 算子kernel侧核心逻辑
├── scripts
└── sinh_custom.json # 笔者此处将工程配置文件和算子工程目录放在了一起
我们只需要专注于上述带有注释的几个文件即可。

此处先修改与算子核心逻辑无关的配置项CMakePresets.json,官方文档中也描述的非常清楚,只需要将ASCEND_CANN_PACKAGE_PATH配置项修改为实际的CANN包安装路径即可。在root用户下安装的默认路径为/usr/local/Ascend/ascend-toolkit/latest。

以上将所有无关算子逻辑的内容修改完毕,接下来就可以专注于算子开发了。

2.3 算子逻辑开发

官方文档中推荐先实现kernel侧的逻辑,但笔者有一些不同的看法。我推荐先实现算子tiling结构体的定义与具体策略,这样做的好处是,可以提前将tiling策略所需的变量确定下来,并且借助于CANN包只提供的一系列宏,这一过程并不需要很大的工作量。在实现kernel侧逻辑的过程中,这些变量将有助于思考数据在逻辑核上如何具体分配和执行,当然这只是笔者的观点,可以根据自己的编程习惯作调整。

2.3.1 tiling结构体定义及策略实现

首先确定tiling过程中所需的变量,参考官方样例,需要定义整块、尾块的个数及其中的元素个数,还需要定义最小对齐单位。op_host/sinh_custom_tiling.h代码如下:

#ifndef SINH_CUSTOM_TILING_H // 头文件保护记得加上,自动生成的文件中不包含
#define SINH_CUSTOM_TILING_H
#include "register/tilingdata_base.h"namespace optiling
{BEGIN_TILING_DATA_DEF(TilingData)TILING_DATA_FIELD_DEF(uint32_t, formerNum);    // 整块个数TILING_DATA_FIELD_DEF(uint32_t, tailNum);      // 尾块个数TILING_DATA_FIELD_DEF(uint32_t, formerLength); // 整块内元素个数TILING_DATA_FIELD_DEF(uint32_t, tailLength);   // 尾块内元素个数TILING_DATA_FIELD_DEF(uint32_t, alignNum);     // 最小对齐单位,元素个数END_TILING_DATA_DEF;REGISTER_TILING_DATA_CLASS(SinhCustom, TilingData)
}#endif
 然后在op_host/sinh_custom.cpp中实现具体的tiling策略,代码如下: 
namespace optiling
{constexpr uint32_t BLOCK_DIM = 24;                        // 划分核心数量constexpr uint32_t SIZE_OF_HALF = 2;                      // 数据类型的字节数constexpr uint32_t BLOCK_SIZE = 32;                       // 昇腾设备上的数据block为32字节constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / SIZE_OF_HALF; // 最小对齐单位static ge::graphStatus TilingFunc(gert::TilingContext *context){TilingData tiling;uint32_t totalLength = context->GetInputTensor(0)->GetShapeSize();context->SetBlockDim(BLOCK_DIM);// 使输入向上对齐uint32_t totalLengthAligned = ((totalLength + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;// 计算整块和尾块个数uint32_t formerNum = (totalLengthAligned / ALIGN_NUM) % BLOCK_DIM;uint32_t tailNum = BLOCK_DIM - formerNum;// 计算整块和尾块的元素个数uint32_t formerLength = ((totalLengthAligned / BLOCK_DIM + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;uint32_t tailLength = (totalLengthAligned / BLOCK_DIM / ALIGN_NUM) * ALIGN_NUM;// 设置tiling参数tiling.set_formerNum(formerNum);tiling.set_tailNum(tailNum);tiling.set_formerLength(formerLength);tiling.set_tailLength(tailLength);tiling.set_alignNum(ALIGN_NUM);// 以下为固定写法,不用纠结tiling.SaveToBuffer(context->GetRawTilingData()->GetData(), context->GetRawTilingData()->GetCapacity());context->GetRawTilingData()->SetDataSize(tiling.GetDataSize());context->SetTilingKey(1);size_t *currentWorkspace = context->GetWorkspaceSizes(1);currentWorkspace[0] = 0;return ge::GRAPH_SUCCESS;}
}

2.3.2 kernel侧实现

有了上述实现的tiling策略,我们就可以根据数据划分的逻辑来确定kernel侧的具体实现。根据官方推荐的矢量编程范式,我们可以先将算子类的框架写出来,再慢慢填充内容。在op_kernel/sinh_custom.cpp中写出算子类框架。

using namespace AscendC; // 记得开启AscendC命名空间
constexpr int32_t BUFFER_NUM = 2; // TQue的缓冲数量,此处开启双Bufferclass KernelSinh
{
public:__aicore__ inline KernelSinh() {} // 类构造函数,无须任何代码__aicore__ inline void Init(GM_ADDR x, GM_ADDR y,   // 初始化函数的参数为输入、输出uint32_t formerNum, uint32_t tailNum, // 以及上面定义的一系列tiling参数uint32_t formerLength, uint32_t tailLength,uint32_t alignNum) { /* TODO */ }__aicore__ inline void Process() { /* TODO */ }private:__aicore__ inline void CopyIn() { /* TODO */ }__aicore__ inline void Compute() { /* TODO */ }__aicore__ inline void CopyOut() { /* TODO */ }private:/* TODO */
};
第一步,分析算子类的私有数据成员。

首先一定需要的是用来管理内存的Tpipe,同时需要输入输出分别对应的TQue和GlobalTensor,同时每个逻辑核还需要直到当前处理的数据个数,所以需要一个变量tileLength来确定分片大小。

第二步,分析算子。

公式:$$ {\bf y}=\text{sinh}({\bf x})=\frac{e^{\bf x}-e^{-{\bf x}}}{2.0} $$

可以观察到,我们需要计算两个中间结果,分别是$e^{\bf x}$和$e^{-{\bf x}}$,所以需要相应的数据结构来存放这两个中间结果,Ascend C提供的TBuf可以很好的承担这一责任。

至此我们就将算子类需要的私有数据成员确定了下来。

TPipe pipe;                                      // 用于操作队列
TBuf<QuePosition::VECCALC> tempBuf;              // 存放中间结果
TQue<QuePosition::VECIN, BUFFER_NUM> inQueueX;   // 输入队列
TQue<QuePosition::VECOUT, BUFFER_NUM> outQueueY; // 输出队列
GlobalTensor<DTYPE_X> xGm;                       // 输入数据对应的GM内存空间
GlobalTensor<DTYPE_Y> yGm;                       // 输出数据对应的GM内存空间
uint32_t tileLength;                             // 每个逻辑核需要知道分片数据个数

第三步,完善算子类的初始化函数Init()。

在该函数中我们需要为GlobalTensor分配内存,并初始化相应的TQue,同时需要针对某些变量做合法性判断。

__aicore__ inline void Init(GM_ADDR x, GM_ADDR y,uint32_t formerNum, uint32_t tailNum,uint32_t formerLength, uint32_t tailLength,uint32_t alignNum)
{if (GetBlockIdx() < formerNum){// 处理整块逻辑this->tileLength = formerLength;xGm.SetGlobalBuffer((__gm__ DTYPE_X *)x + formerLength * GetBlockIdx(), formerLength);yGm.SetGlobalBuffer((__gm__ DTYPE_Y *)y + formerLength * GetBlockIdx(), formerLength);}else{// 处理尾块逻辑this->tileLength = tailLength;xGm.SetGlobalBuffer((__gm__ DTYPE_X *)x + formerLength * formerNum + tailLength * (GetBlockIdx() - formerNum), tailLength);yGm.SetGlobalBuffer((__gm__ DTYPE_Y *)y + formerLength * formerNum + tailLength * (GetBlockIdx() - formerNum), tailLength);}ASSERT(alignNum != 0 && "align num can not be zero!");pipe.InitBuffer(inQueueX, BUFFER_NUM, (((this->tileLength + alignNum - 1) / alignNum) * alignNum) * sizeof(half));pipe.InitBuffer(outQueueY, BUFFER_NUM, (((this->tileLength + alignNum - 1) / alignNum) * alignNum) * sizeof(half));
}

第四步,完成算子最核心的部分:根据矢量编程范式实现算子计算逻辑。

__aicore__ inline void CopyIn()
{LocalTensor<DTYPE_X> xLocal = inQueueX.AllocTensor<DTYPE_X>();DataCopy(xLocal, xGm, this->tileLength); // GM -> LMinQueueX.EnQue<DTYPE_X>(xLocal);
}
__aicore__ inline void Compute()
{LocalTensor<DTYPE_X> xLocal = inQueueX.DeQue<DTYPE_X>();LocalTensor<DTYPE_Y> yLocal = outQueueY.AllocTensor<DTYPE_Y>();pipe.InitBuffer(tempBuf, this->tileLength * sizeof(DTYPE_X));LocalTensor<DTYPE_X> tempLocal = tempBuf.Get<DTYPE_X>(this->tileLength);// 计算exp(x)Exp(yLocal, xLocal, this->tileLength);// 计算-xhalf nagOne(-1.0);Muls(tempLocal, xLocal, nagOne, this->tileLength);// 计算exp(-x)Exp(tempLocal, tempLocal, this->tileLength);// 计算exp(x)-exp(-x)Sub(yLocal, yLocal, tempLocal, this->tileLength);// 计算最终结果half denominator(0.5);Muls(yLocal, yLocal, denominator, this->tileLength);outQueueY.EnQue<DTYPE_Y>(yLocal);inQueueX.FreeTensor(xLocal);
}
__aicore__ inline void CopyOut()
{LocalTensor<DTYPE_Y> yLocal = outQueueY.DeQue<DTYPE_Y>();DataCopy(yGm, yLocal, this->tileLength); // LM -> GMoutQueueY.FreeTensor(yLocal);
}

实现的具体细节与接口可以参考Ascend C官方文档。

第五步,将Process()函数补全,并完善核函数。

__aicore__ inline void Process()
{CopyIn();Compute();CopyOut();
}extern "C" __global__ __aicore__ void
sinh_custom(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)
{GET_TILING_DATA(tiling_data, tiling);KernelSinh op;op.Init(x, y,tiling_data.formerNum, tiling_data.tailNum,tiling_data.formerLength, tiling_data.tailLength,tiling_data.alignNum);if (TILING_KEY_IS(1)){op.Process();}
}

至此就完成了kernel侧的实现。

2.3.3 host侧实现

我们回到op_host/sinh_custom.cpp,关于类型推导函数,这个算子输入输出的形状一致。msopgen生成的算子工程中,默认即为输入输出形状一致,所以无须改动。如果在写其他复杂算子的时候,需要仔细分析数据形状的变化。关于算子原型注册,也无须改动。

现在就完成了整个算子的逻辑,可以执行build.sh来验证有没有编译时错误,若没有错误则可以进行运行时验证。

3 核函数调用

笔者直接将官方的核函数调用样例拿来做了一些修改,需要修改的地方如下。

kernel_invocation
├── cmake
├── CMakeLists.txt
├── data_utils.h
├── input
├── main.cpp # 需要修改
├── output
├── run.sh # 需要修改
├── add_custom.cpp # 替换为自己的算子实现
├── add_custom.py # 需要修改
└── verify_result.py # 添加的代码,用于验证结果

首先,将官方样例中的add_custom.cpp替换为自己实现的kernel侧算子,笔者这里的名称为sinh_custom.cpp。同时为了CPU侧调试,需要添加一个核函数的包装函数,代码如下。

#ifndef __CCE_KT_TEST__
void sinh_custom_do(uint32_t blockDim, void *l2ctrl, void *stream, uint8_t *x, uint8_t *y)
{sinh_custom<<<blockDim, l2ctrl, stream>>>(x, y);
}
#endif

注意:为了快速验证逻辑,在核函数验证过程中未使用动态tiling,所以没有之前提到的那些tiling参数。

然后是sinh_custom.py,官方样例中是add_custom.py,这里修改文件名称,因为后面的run.sh中是通过算子文件名来调用这一python脚本的。

由于本算子只需要一个输入向量,所以只生成一个input数据,然后修改golden数据的生成方式,调用numpy中与算子功能相同的函数来计算,注意数据类型,代码如下。

import numpy as npdef gen_golden_data_simple():np.random.seed(42)input_x = np.random.randn(8, 2048).astype(np.float16)golden = np.sinh(input_x).astype(np.float16)print(f'-----------------------{input_x[0][0]}')input_x.tofile("./input/input_x.bin")golden.tofile("./output/golden.bin")if __name__ == "__main__":gen_golden_data_simple()

main.cpp中要调整相应的内存申请等操作,只需要一个input,CPU侧调试和NPU侧调试的代码都需要修改,具体如下。

#include <stdio.h>#include "data_utils.h"
#ifndef __CCE_KT_TEST__
#include "acl/acl.h"
extern void sinh_custom_do(uint32_t coreDim, void *l2ctrl, void *stream, uint8_t *x, uint8_t *y);
#else
#include "tikicpulib.h"
extern "C" __global__ __aicore__ void sinh_custom(GM_ADDR x, GM_ADDR y);
#endifint32_t main(int32_t argc, char *argv[])
{size_t inputByteSize = 8 * 2048 * sizeof(uint16_t);size_t outputByteSize = 8 * 2048 * sizeof(uint16_t);uint32_t blockDim = 8;#ifdef __CCE_KT_TEST__uint8_t *x = (uint8_t *)AscendC::GmAlloc(inputByteSize);uint8_t *y = (uint8_t *)AscendC::GmAlloc(outputByteSize);ReadFile("./input/input_x.bin", inputByteSize, x, inputByteSize);AscendC::SetKernelMode(KernelMode::AIV_MODE);ICPU_RUN_KF(sinh_custom, blockDim, x, y);WriteFile("./output/output_y.bin", y, outputByteSize);AscendC::GmFree((void *)x);AscendC::GmFree((void *)y);
#elseCHECK_ACL(aclInit(nullptr));aclrtContext context;int32_t deviceId = 0;CHECK_ACL(aclrtSetDevice(deviceId));CHECK_ACL(aclrtCreateContext(&context, deviceId));aclrtStream stream = nullptr;CHECK_ACL(aclrtCreateStream(&stream));uint8_t *xHost, *yHost;uint8_t *xDevice, *yDevice;CHECK_ACL(aclrtMallocHost((void **)(&xHost), inputByteSize));CHECK_ACL(aclrtMallocHost((void **)(&yHost), outputByteSize));CHECK_ACL(aclrtMalloc((void **)&xDevice, inputByteSize, ACL_MEM_MALLOC_HUGE_FIRST));CHECK_ACL(aclrtMalloc((void **)&yDevice, outputByteSize, ACL_MEM_MALLOC_HUGE_FIRST));ReadFile("./input/input_x.bin", inputByteSize, xHost, inputByteSize);CHECK_ACL(aclrtMemcpy(xDevice, inputByteSize, xHost, inputByteSize, ACL_MEMCPY_HOST_TO_DEVICE));sinh_custom_do(blockDim, nullptr, stream, xDevice, yDevice);CHECK_ACL(aclrtSynchronizeStream(stream));CHECK_ACL(aclrtMemcpy(yHost, outputByteSize, yDevice, outputByteSize, ACL_MEMCPY_DEVICE_TO_HOST));WriteFile("./output/output_y.bin", yHost, outputByteSize);CHECK_ACL(aclrtFree(xDevice));CHECK_ACL(aclrtFree(yDevice));CHECK_ACL(aclrtFreeHost(xHost));CHECK_ACL(aclrtFreeHost(yHost));CHECK_ACL(aclrtDestroyStream(stream));CHECK_ACL(aclrtDestroyContext(context));CHECK_ACL(aclrtResetDevice(deviceId));CHECK_ACL(aclFinalize());
#endifreturn 0;
}
 

原样例中的验证方式是求md5和,但由于核函数中调用了Exp、Muls等API,所以精度可能会有损失,不适合用md5sum的方式来验证。这里就需要引入新的文件verify_result.py,这里使用了numpy.isclose函数来进行验证,这也是官方单算子API调用的结果验证方式。

import sys
import math
import numpy as npdef data_compare(file1, file2,file3):input1 = np.fromfile(file1, dtype=np.float16)print("input1: ", input1)golden = np.fromfile(file2, dtype=np.float16)output = np.fromfile(file3, dtype=np.float16)print("output: ", output)print("-------------golden is :")print("golden: ", golden)different_element_results = np.isclose(output, golden,rtol=5e-2,atol=1e-3,equal_nan=True)different_element_indexes = np.where(different_element_results != np.array((True,)))[0]if different_element_indexes.size == 0:print("result correct!")else:print("result error!")return 0 if different_element_indexes.size == 0 else 1if __name__ == '__main__':intput_file1 = sys.argv[1]golden_file = sys.argv[2]output_file = sys.argv[3]cmp_result = data_compare(intput_file1, golden_file, output_file)if (cmp_result == 0):sys.exit(0)else:sys.exit(1)

最后是修改run.sh脚本,需要修改的只有最后验证结果的部分。

原样例的验证方式是md5sum:

echo "md5sum: ";md5sum output/*.bin

修改为调用脚本判断:

echo "result verification: " python3 verify_result.py ./input/input_x.bin ./output/golden.bin ./output/output_y.bin

4 单算子API调用

单算子调用是通过自动生成的两段式API来执行的,为了快速验证,同样是将官方样例中的单算子API调用样例拿来做了一些修改。需要修改的几处关键代码如下。

aclnn_online_model
├── build
├── inc
├── README.md
├── run
│   └── out
│       ├── execute_sinh_op
│       ├── result_files
│       └── test_data
│           ├── config
│           └── data
│               ├── generate_data.py # 生成测试数据脚本,需要修改
├── run.sh # 需要修改
├── scripts
│   └── verify_result.py # 调整验证方式,例如相对和绝对误差参数等
└── src├── CMakeLists.txt # 需要修改├── common.cpp├── main.cpp # 需要修改├── operator_desc.cpp└── op_runner.cpp # 需要修改
具体细节如下。

generate_data.py中,按照算子来修改测试数据生成方式。本算子需要half类型的测试数据,故代码改为:

import numpy as np
a = np.random.randn(8, 2048).astype(np.float16)
a.tofile('input_0.bin')

verify_result.py中,根据实际读取的输入和输出,利用np.isclose来进行比较,该函数详细用法参考numpy官方文档。

import sys
import math
import numpy as npdef data_compare(file1, file2):input1 = np.fromfile(file1, dtype=np.float16)print("input1: ", input1)golden = np.sinh(input1).astype(np.float16)output = np.fromfile(file2, dtype=np.float16)print("output: ", output)print("-------------golden is :")print("golden: ", golden)different_element_results = np.isclose(output, golden,rtol=5e-2,atol=1e-3,equal_nan=True)different_element_indexes = np.where(different_element_results != np.array((True,)))[0]return 0 if different_element_indexes.size == 0 else 1if __name__ == '__main__':intput_file1 = sys.argv[1]output_file = sys.argv[2]cmp_result = data_compare(intput_file1, output_file)if (cmp_result == 0):sys.exit(0)else:sys.exit(1)

main.cpp中,需要将CreateOpDesc()函数根据具体的输入输出来做修改。

OperatorDesc CreateOpDesc()
{std::vector<int64_t> shape{8, 2048};aclDataType dataType = ACL_FLOAT16;aclFormat format = ACL_FORMAT_ND;OperatorDesc opDesc;opDesc.AddInputTensorDesc(dataType, shape.size(), shape.data(), format);opDesc.AddOutputTensorDesc(dataType, shape.size(), shape.data(), format);return opDesc;
}

op_runner.cpp中将两段式API修改为自己算子的API,请善用Ctrl + F搜索关键代码进行修改,具体的API名称可以查看算子目录下的build_out/autogen目录。

...
auto ret = aclnnSinhCustomGetWorkspaceSize(inputTensor_[0], outputTensor_[0], &workspaceSize, &handle);
...
INFO_LOG("Execute aclnnSinhCustomGetWorkspaceSize success, workspace size %lu", workspaceSize);
...
if (aclnnSinhCustom(workspace, workspaceSize, handle, stream) != ACL_SUCCESS)
{...
}
INFO_LOG("Execute aclnnSinhCustom success");
...

接着修改src/CMakeLists.txt。

set(AUTO_GEN_PATH "../SinhCustom/build_out/autogen") # 16行# 50行以后,修改可执行文件的名称
add_executable(execute_sinh_op${AUTO_GEN_PATH}/aclnn_sinh_custom.cppoperator_desc.cppop_runner.cppmain.cppop_runner.cppcommon.cpp
)target_link_libraries(execute_sinh_opascendclacl_op_compilernnopbasestdc++
)install(TARGETS execute_sinh_op DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

最后修改run.sh脚本中关于路径的部分。

修改完成后,就可以执行run.sh脚本进行单算子API调用了。

INFO: acl executable run success!
input1:  [ 0.468  -0.2585 -3.066  ...  0.9136 -1.117  -1.368 ]
output:  [  0.485   -0.2615 -10.71   ...   1.047   -1.365   -1.837 ]
-------------golden is :
golden:  [  0.4854  -0.2615 -10.71   ...   1.046   -1.364   -1.837 ]
INFO: compare golden data success!

出现上述提示证明算子通过验证。

5 Ascend C学习资源

Ascend C配套丰富的学习资料,包括教程文档、交流社区、案例代码等,这些资源将帮助您理解Ascend C编程语言的各种概念和技巧,为您的自主学习提供便利。

  • Ascend C学习资源汇聚页:Ascend C-昇腾社区

  • Ascend C官方文档:昇腾社区-官网丨昇腾万里 让智能无所不及

  • Ascend C视频课程(入门):昇腾社区-官网丨昇腾万里 让智能无所不及

  • Ascend C视频课程(进阶):昇腾社区-官网丨昇腾万里 让智能无所不及

  • Ascend C论坛:https://www.hiascend.com/forum/forum-0163125572293226003-1.html

  • Ascend C sample:开发者分享 | Ascend C算子开发及单算子调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/148103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux systemd start stop enable disable命令区别

一、systemd 的服务在三个文件件下 /lib/systemd/system /etc/systemd/system /usr/lib/systemd/system 终于明白这几个命令的区别 systemd star systemd stop systemd enable systemd disable 二、 1、用ssh服务为例&#xff0c;&#xff0c;ssh是客户端&#xff0c;远程ss…

线性表--顺序表-1

文章目录 主要内容一.基础练习题1.从顺序表中删除具有最小值的元素&#xff08;假设唯一&#xff09;并由函数返回被删元素的值。空出位置由最后元素填补&#xff0c;若顺序表为空&#xff0c;则显示出错信息并退出运行。代码如下&#xff08;示例&#xff09;: 2.设计一个高效…

持续集成交付CICD:Jenkins通过API触发流水线

目录 一、理论 1.HTTP请求 2.调用接口的方法 3.HTTP常见错误码 二、实验 1.Jenkins通过API触发流水线 三、问题 1.如何拿到上一次jenkinsfile文件进行自动触发流水线 一、理论 1.HTTP请求 &#xff08;1&#xff09;概念 HTTP超文本传输协议&#xff0c;是确保服务器…

JS特效:跟随鼠标移动的小飞机

前端网页中&#xff0c;用JS实现鼠标移动时&#xff0c;页面中的小飞机向着鼠标移动。 效果 源码 <!DOCTYPE html> <html><head><style>*{margin: 0;padding: 0;}body{height: 100vh;background: linear-gradient(200deg,#005bea,#00c6fb);}#plane{…

[C/C++]数据结构 链表(单向链表,双向链表)

前言: 上一文中我们介绍了顺序表的特点及实现,但是顺序表由于每次扩容都是呈二倍增长(扩容大小是自己定义的),可能会造成空间的大量浪费,但是链表却可以解决这个问题. 概念及结构: 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接…

HC-SR501传感器制作一个报警系统

接线图&#xff1a; 引脚连接&#xff1a; 1. 将 PIR 信号引脚连接到 arduino 数字 引脚 13。 2. 将 PIR V 引脚连接 到 arduino 5v 引脚。 3. 将 PIR GND 引脚连接到 arduino GND 引脚。 4. 将arduino数字 引脚12连接 到220欧姆电阻&#xff0c;并将该电阻连接到 LED V …

Java Swing猜单词游戏

内容要求 1&#xff09; 本次程序设计是专门针对 Java 课程的,要求使用 Java 语言进行具有一定代码量的程序开发。程序的设计要结合一定的算法&#xff0c;在进行代码编写前要能够设计好自己的算法。 2&#xff09;本次程序设计涉及到 Java 的基本语法&#xff0c;即课堂上所…

提升工作效率,打造精细思维——OmniOutliner 5 Pro for Mac

在当今快节奏的工作环境中&#xff0c;如何高效地组织和管理我们的思维和任务成为了关键。而OmniOutliner 5 Pro for Mac正是为此而生的一款强大工具。无论你是专业写作者、项目经理还是学生&#xff0c;OmniOutliner 5 Pro for Mac都能帮助你提升工作效率&#xff0c;打造精细…

Fibonacci 数列与黄金分割

mapp[1 for item in range(30)] for item in range(3,30):mapp[item]mapp[item-1]mapp[item-2]pass numint(input()) if num>19:print("0.61803399")pass else:anss float((mapp[num]*1.0) / (mapp[num 1]*1.0))print(format(anss,.8f))进行短程的打表就可以看出…

实用篇-ES-DSL查询文档

数据的存储不是目的&#xff0c;我们希望从海量的酒店数据中检索出需要的信息&#xff0c;这就是ES的搜索功能 官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。DSL是用来查询文档的 Elasticsearch提供了基于JSON的DSL来定…

将给定的表达式树(二叉树)转换为等价的中缀表达式(通过括号反映操作符的计算次序)并输出

要将给定的表达式树转换为等价的中缀表达式&#xff0c;可以通过遍历表达式树的方式来实现。以下是一个以递归方式实现的示例代码&#xff1a; class Node:def __init__(self, value):self.value valueself.left Noneself.right Nonedef convert_to_infix_expression(root)…

阿里云ESSD云盘、高效云盘和SSD云盘介绍和IOPS性能参数表

阿里云服务器系统盘或数据盘支持多种云盘类型&#xff0c;如高效云盘、ESSD Entry云盘、SSD云盘、ESSD云盘、ESSD PL-X云盘及ESSD AutoPL云盘等&#xff0c;阿里云服务器网aliyunfuwuqi.com详细介绍不同云盘说明及单盘容量、最大/最小IOPS、最大/最小吞吐量、单路随机写平均时延…

SpringBoot-AOP-基础到进阶

SpringBoot-AOP AOP基础 学习完spring的事务管理之后&#xff0c;接下来我们进入到AOP的学习。 AOP也是spring框架的第二大核心&#xff0c;我们先来学习AOP的基础。 在AOP基础这个阶段&#xff0c;我们首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c…

【我和Python算法的初相遇】——体验递归的可视化篇

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:PYTHON数据结构与算法学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 目录 递归的起源 什么是递归? 利用递归解决列表求和问题 递归三定律 递归应用-整数转换为任意进制数 递归可视化 画…

Docker安装MinIO遇到的问题汇总——持续更新中

文章目录 Docker安装MinIO遇到的坑前言问题1&#xff1a;执行docker run报错Error response from daemon问题2&#xff1a;启动MinIO容器浏览器无法访问问题3&#xff1a;上传文件报错InvalidResponseException问题4&#xff1a;上传文件报错Connection refused最终的启动指令问…

Jmeter 吞吐量Per User作用

第一点&#xff1a;Per User仅在Total Execution时生效 第二点&#xff1a;Per User 选中后 聚合报告中将统计的的样本数将变成线程组配置的线程数*吞吐量控制器配置的执行样本数量&#xff08;前提是线程组配置执行接口的次数线程数*循环数 大于吞吐量控制器配置的执行样本数…

gittee启动器

前言 很多小伙伴反馈不是使用gitee&#xff0c;不会寻找好的项目&#xff0c;在拿到一个项目不知道从哪里入手。 鼠鼠我呀就是宠粉&#xff0c;中嘞&#xff0c;老乡。整&#xff01;&#xff01;&#xff01; git的基本指令 在使用gitee的时候呢&#xff0c;我们只需要记住…

Adversarially Robust Neural Architecture Search for Graph Neural Networks

Adversarially Robust Neural Architecture Search for Graph Neural Networks----《面向图神经网络的对抗鲁棒神经架构搜索》 摘要 图神经网络&#xff08;GNN&#xff09;在关系数据建模方面取得了巨大成功。尽管如此&#xff0c;它们仍然容易受到对抗性攻击&#xff0c;这对…

力扣周赛372 模拟 思维 位运算 java

100131. 使三个字符串相等 ⭐ AC code class Solution {public int findMinimumOperations(String s1, String s2, String s3) {int len1 s1.length();int len2 s2.length();int len3 s3.length();int n Math.min(len1,len2);n Math.min(n,len3);int i 0;while(i < n…

在Java代码中指定用JAXB的XmlElement注解的元素的顺序

例如&#xff0c;下面的类RegisterResponse 使用了XmlRootElement注解&#xff0c;同时也使用XmlType注解&#xff0c;并用XmlType注解的propOrder属性&#xff0c;指定了两个用XmlElement注解的元素出现的顺序&#xff0c;先出现flag&#xff0c;后出现enterpriseId&#xff0…