Ascend C 自定义算子 Kernel Launch调用入门

这篇具有很好参考价值的文章主要介绍了Ascend C 自定义算子 Kernel Launch调用入门。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文分享自华为云社区《Ascend C 自定义算子 Kernel Launch调用入门》,作者: jackwangcumt。

1 Kernel Launch概述

根据官方说明文档的介绍,Ascend C对外开放核函数的基础调用(Kernel Launch)方式,是为了简化Ascend C 自定义算子的开发流程,提供更易用的调试调优功能。当开发者完成算子核函数的开发和Tiling实现后,即可通过AscendCL运行时接口,完成算子的调用并实现自己的推理应用;同时提供简易的kernel开发工程,开发者仅需提供kernel侧实现,基于工程框架可以快速实现Kernel Launch。本文实验前提是完成了《Ascend C 自定义PRelu算子》博文的相关算子开发工程。网址为:https://bbs.huaweicloud.com/blogs/425244 。请注意:

  • 8.0.RC1.alpha002 当前版本,Kernel Launch开放式编程为试用特性,不支持应用于商用产品中。
  • 8.0.RC1.alpha002 当前版本暂不支持获取用户workspace特性。

2 Kernel Launch调用方式

ACLRT_LAUNCH_KERNEL调用方式对内核调用符方式进行了功能加强,核函数的调用是异步的,调用接口的使用方法如下:

ACLRT_LAUNCH_KERNEL(kernel_name)(blockDim, stream, argument list);
  • kernel_name:算子核函数的名称。
  • blockDim:规定了核函数将会在几个核上执行。每个执行该核函数的核会被分配一个逻辑ID,即block_idx,可以在核函数的实现中调用GetBlockIdx来获取block_idx。
  • stream,类型为aclrtStream,stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。
  • argument list:参数列表,与核函数的参数列表保持一致。

为帮助开发者快速的完成算子的Kernel Launch调试,官方提供了简易的算子工程,我们可以基于该算子工程中的样例代码和工程框架进行算子开发。算子工程支持的如下:

  • 该工程支持调试功能,如PRINTF功能、DumpTensor。
  • 工程编译生成的应用程序,可通过msprof命令行方式采集和解析性能数据。

可以参考工程样例:https://gitee.com/ascend/samples/blob/master/operator/AddCustomSample/KernelLaunch/AddKernelInvocationTilingNeo ,其目录结构如下所示:

AddKernelInvocationNeo
|-- cmake                                                 // CMake编译文件
|-- scripts
|  ├── gen_data.py                                     // 输入数据和真值数据生成脚本文件
|  ├── verify_result.py                                // 验证输出数据和真值数据是否一致的验证脚本
|-- CMakeLists.txt                                        // CMake编译配置文件
|-- add_custom.cpp                                     // 矢量算子kernel实现
|-- data_utils.h                                          // 数据读入写出函数
|-- main.cpp                                              // 主函数,调用算子的应用程序,含CPU域及NPU域调用
|-- run.sh                                                // 编译运行算子的脚本

基于该算子工程,开发者进行算子开发的步骤如下:

  • 完成算子kernel侧实现。
  • 编写算子调用应用程序main.cpp。
  • 编写CMake编译配置文件CMakeLists.txt。

  • 根据实际需要修改输入数据和真值数据生成脚本文件gen_data.py和验证输出数据和真值数据是否一致的验证脚本verify_result.py。
  • 根据实际需要修改编译运行算子的脚本run.sh并执行该脚本,完成算子的编译运行和结果验证。

3 Kernel Launch实现

在PReluSample目录下新建一个目录KernelLaunch,用于存放Kernel Launch调用方式的工程代码,我这里参考官方的https://gitee.com/ascend/samples/tree/master/operator/LeakyReluCustomSample/KernelLaunch/

LeakyReluKernelInvocation样例工程,并修改了相关参数,p_relu_custom.cpp 代码如下所示:

#include "kernel_operator.h"
using namespace AscendC;

constexpr int32_t BUFFER_NUM = 2; 
constexpr int32_t TOTAL_LENGTH = 8 * 200 * 1024;    
constexpr int32_t TILE_NUM = 32;                           
constexpr float alpha = 0.002;

class KernelPRelu {
public:
    __aicore__ inline KernelPRelu() {}
    __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, uint32_t totalLength, uint32_t tileNum, float alpha)
    {
        PRINTF("[npu debug] >>> GetBlockNum() %d", GetBlockNum());
        ASSERT(GetBlockNum() != 0 && "block dim can not be zero!");
        this->blockLength = totalLength / GetBlockNum();
        this->tileNum = tileNum;
        this->alpha = static_cast<float>(alpha);
        ASSERT(tileNum != 0 && "tile num can not be zero!");
        this->tileLength = this->blockLength / tileNum / BUFFER_NUM;

        // get start index for current core, core parallel
        xGm.SetGlobalBuffer((__gm__ float*)x + this->blockLength * GetBlockIdx(), this->blockLength);
        yGm.SetGlobalBuffer((__gm__ float*)y + this->blockLength * GetBlockIdx(), this->blockLength);
        // pipe alloc memory to queue, the unit is Bytes
        pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(float));
        pipe.InitBuffer(outQueueY, BUFFER_NUM, this->tileLength * sizeof(float));
        pipe.InitBuffer(tmpBuffer1, this->tileLength * sizeof(float));
        //pipe.InitBuffer(tmpBuffer2, this->tileLength * sizeof(float));
    }
    __aicore__ inline void Process()
    {
        // loop count need to be doubled, due to double buffer
        int32_t loopCount = this->tileNum * BUFFER_NUM;
        // tiling strategy, pipeline parallel
        for (int32_t i = 0; i < loopCount; i++) {
            CopyIn(i);
            Compute(i);
            CopyOut(i);
        }
    }

private:
    __aicore__ inline void CopyIn(int32_t progress)
    {
        // alloc tensor from queue memory
        LocalTensor<float> xLocal = inQueueX.AllocTensor<float>();
        // copy progress_th tile from global tensor to local tensor
        DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength);
        // enque input tensors to VECIN queue
        inQueueX.EnQue(xLocal);
    }
    __aicore__ inline void Compute(int32_t progress)
    {
        // deque input tensors from VECIN queue
        LocalTensor<float> xLocal = inQueueX.DeQue<float>();
        LocalTensor<float> yLocal = outQueueY.AllocTensor<float>();
        LocalTensor<float> tmpTensor1 = tmpBuffer1.Get<float>();
        float inputVal = 0.0;
        Maxs(tmpTensor1, xLocal, inputVal, this->tileLength); // x >= 0  --> x
        // x < 0 
        Mins(xLocal, xLocal, inputVal, this->tileLength);
        Muls(xLocal, xLocal, this->alpha, this->tileLength);
        Add(yLocal, xLocal, tmpTensor1, this->tileLength);
        outQueueY.EnQue<float>(yLocal);
        // free input tensors for reuse
        inQueueX.FreeTensor(xLocal);
    }
    __aicore__ inline void CopyOut(int32_t progress)
    {
        // deque output tensor from VECOUT queue
        LocalTensor<float> yLocal = outQueueY.DeQue<float>();
        // copy progress_th tile from local tensor to global tensor
        DataCopy(yGm[progress * this->tileLength], yLocal, this->tileLength);
        // free output tensor for reuse
        outQueueY.FreeTensor(yLocal);
    }

private:
    TPipe pipe;
    TBuf<QuePosition::VECCALC> tmpBuffer1;
    //TBuf<QuePosition::VECCALC> tmpBuffer1, tmpBuffer2;
    // create queues for input, in this case depth is equal to buffer num
    TQue<QuePosition::VECIN, BUFFER_NUM> inQueueX;
    // create queue for output, in this case depth is equal to buffer num
    TQue<QuePosition::VECOUT, BUFFER_NUM> outQueueY;
    GlobalTensor<float> xGm, yGm;
    uint32_t blockLength;
    uint32_t tileNum;
    uint32_t tileLength;
    float alpha;
};
extern "C" __global__ __aicore__ void p_relu_custom(GM_ADDR x, GM_ADDR y) {
    //GET_TILING_DATA(tiling_data, tiling);
    // TODO: user kernel impl
    KernelPRelu op;
    op.Init(x, y, TOTAL_LENGTH, TILE_NUM, alpha);
    op.Process();
}

#ifndef __CCE_KT_TEST__
// call of kernel function
void p_relu_custom_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y)
{
    p_relu_custom<<<blockDim, l2ctrl, stream>>>(x, y);
}
#endif

main.cpp 代码如下所示 :

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * This file constains code of cpu debug and npu code.We read data from bin file
 * and write result to file.
 */
#include "data_utils.h"
#ifndef __CCE_KT_TEST__
#include "acl/acl.h"
extern void p_relu_custom_do(uint32_t coreDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y);
#else
#include "tikicpulib.h"
extern "C" __global__ __aicore__ void p_relu_custom(GM_ADDR x, GM_ADDR y);
#endif

int32_t main(int32_t argc, char* argv[])
{
    uint32_t blockDim = 8;
    size_t inputByteSize = 8 * 200 * 1024 * sizeof(float);
    size_t outputByteSize = 8 * 200 * 1024 * sizeof(float);

#ifdef __CCE_KT_TEST__
    // CPU
    uint8_t* x = (uint8_t*)AscendC::GmAlloc(inputByteSize);
    uint8_t* y = (uint8_t*)AscendC::GmAlloc(outputByteSize);
    printf("[cpu debug]>>> inputByteSize: %d\n", inputByteSize); 

    ReadFile("./input/input_x.bin", inputByteSize, x, inputByteSize);
    AscendC::SetKernelMode(KernelMode::AIV_MODE);
    ICPU_RUN_KF(p_relu_custom, blockDim, x, y); // use this macro for cpu debug
    WriteFile("./output/output_y.bin", y, outputByteSize);
    AscendC::GmFree((void *)x);
    AscendC::GmFree((void *)y);
    
#else
   // NPU 
    //CHECK_ACL(aclInit(nullptr));
    CHECK_ACL(aclInit("./acl.json"));
    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));

    p_relu_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());
#endif
    return 0;
}

执行如下代码进行NPU上板调试和CPU调试:

#npu
bash run.sh Ascend310P1 npu_onboard
# cpu
bash run.sh Ascend310P1 cpu

 文章来源地址https://www.toymoban.com/news/detail-845331.html

点击关注,第一时间了解华为云新鲜技术~

 

到了这里,关于Ascend C 自定义算子 Kernel Launch调用入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 体验昇腾Ascend C 编程语言极简易用的算子开发

    摘要: 昇腾Ascend C编程语言,让基于昇腾AI的算法创新更加简单。 本文分享自华为云社区《CANN黑科技解密|昇腾Ascend C编程语言 — 极简易用的算子开发体验》,作者:昇腾CANN 。 AI应用的大脑是神经网络,而构成神经网络的基石是一个个算子。为了让开发者的网络在昇腾硬件

    2024年02月08日
    浏览(34)
  • 纯干货!一文get昇腾Ascend C编程入门全部知识点

    本文分享自华为云社区《昇腾Ascend C编程入门教程》,作者:昇腾CANN 。 2023年5月6日,在昇腾AI开发者峰会上,华为正式发布了面向算子开发场景的昇腾Ascend C编程语言。Ascend C原生支持C/C++编程规范,通过多层接口抽象、并行编程范式、孪生调试等技术,极大提高了算子的开发

    2024年02月10日
    浏览(35)
  • js封装SDK 在VUE、小程序、公众号直接调用js调用后端接口(本文以vue项目为例)

    1.封装一个js文件 msgSdk.js 注意:需要修改这个请求地址  apiServiceAddress 2.在index.html中引入 msgSdk.js文件 和 jquery文件 3.在页面中调用

    2024年04月27日
    浏览(38)
  • Flink---5、聚合算子、用户自定义函数、物理分区算子、分流、合流

                           星光下的赶路人star的个人主页                        欲买桂花同载酒,终不似,少年游 计算的结果不仅依赖当前数据,还跟之前的数据有关,相当于要把所有数据聚在一起进行汇总合并—这就是

    2024年02月07日
    浏览(45)
  • ROS入门学习三——launch文件

    launch作用:便于一次启动多个节点,可启动本地节点和远程节点及修改添加参数服务器参数。 launch文件的建立 在功能包下,新建launch文件夹 ,在launch文件夹下添加后缀为launch的文件。  launch文件的启动  一、 launch文件之node标签 上面例子中launch文件启动时是多线程的,不一

    2024年02月16日
    浏览(36)
  • Linux Kernel源码阅读: x86-64 系统调用实现细节(超详细)

    本文采用Linux 内核 v3.10 版本 本文不涉及调试、跟踪及异常处理的细节 一、系统调用简介 系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同,因为它调用的是内核里的代码。使用系统调用时,需要特殊指令以使处理器权限转换到内核态。另外,被

    2024年02月06日
    浏览(40)
  • 【ROS2指南-8】入门Launch启动脚本

    目标: 创建launch文件以运行复杂的 ROS 2 系统。 教程级别: 初学者 时间: 10分钟 内容 背景 先决条件 任务 1 设置 2 编写启动文件 3 ros2发射 4 使用 rqt_graph 反省系统 概括 下一步 在到目前为止的教程中,您一直在为您运行的每个新节点打开新终端。随着越来越多的节点同时运

    2024年02月15日
    浏览(69)
  • Semantic Kernel 入门系列:?Kernel 内核和?Skills 技能

    理解了LLM的作用之后,如何才能构造出与LLM相结合的应用程序呢? 首先我们需要把LLM AI的能力和原生代码的能力区分开来,在Semantic Kernel(以下简称SK),LLM的能力称为 semantic function ,代码的能力称为 native function,两者平等的称之为function(功能),一组功能构成一个技能(

    2023年04月09日
    浏览(47)
  • 【Opencv入门到项目实战】(四):图像梯度计算|Sobel算子|Scharr算子|Laplacian算子

    在图像处理中,梯度是指图像中像素灰度变化的速率或幅度,我们先来看下面这张图 假设我们想要计算出A点的梯度,我们可以发现A点位于边缘点,A点左边为黑色,右边为白色,而计算图像的梯度可以提取出图像中的边缘信息,我们常用的方法是使用 Sobel算子 或 Scharr算子

    2024年02月13日
    浏览(49)
  • pytorch自定义算子并导出onnx计算图详细代码教程

    解决:     # enable_onnx_checker=False 更改为:     operator_export_type=torch.onnx.OperatorExportTypes.ONNX_ATEN_FALLBACK pytorch自定义算子并导出onnx计算图详细代码教程_operatorexporttypes_蛇皮小娃娃的博客-CSDN博客  

    2024年02月10日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包