一、昇腾系列处理器与Ascend C
1、昇腾AI计算
昇腾AI芯片的计算核心主要由AI Core构成,负责执行标量、向量和张量相关的计算密集型算子。AI Core采用了达芬奇架构,
从控制上可以看成是一个相对简化的现代微处理器的基本架构。它包括了三种基础计算资源:矩阵计算单元(Cube Unit)、向量计算单元(Vector Unit)和标量计算单元(Scalar Unit)。这三种计算单元分别对应了张量、向量和标量三种常见的计算模式,在实际的计算过程中各司其职,形成了三条独立的执行流水线,在系统软件的统一调度下互相配合达到优化的计算效率。此外在矩阵计算单元和向量计算单元内部还提供了不同精度、不同类型的计算模式。AI Core中的矩阵计算单元目前可以支持INT8、INT4和FP16的计算;向量计算单元目前可以支持FP16和FP32的计算。
昇腾计算基于华为昇腾系列(HUAWEI Ascend)AI处理器和基础软件构建Atlas人工智能计算解决方案,包括Atlas系列模块、板卡、小站、服务器、集群等丰富的产品形态,打造面向“端、边、云”的全场景AI基础设施方案,覆盖深度学习领域推理和训练全流程。
2、CANN
CANN(Compute Architecture for Neural Networks)是华为针对AI场景推出的异构计算架构,对上支持多种AI框架,对下服务AI处理器与编程,发挥承上启下的关键作用,是提升昇腾AI处理器计算效率的关键平台。同时针对多样化应用场景,提供高效易用的编程接口,支持用户快速构建基于昇腾平台的AI应用和业务
3、Ascend C
面向算子开发场景的编程语言Ascend C,原生支持C和C++标准规范,最大化匹配用户开发习惯;通过多层接口抽象、自动并行计算、孪生调试等关键技术,极大提高算子开发效率,助力AI开发者低成本完成算子开发和模型调优部署。
二、算子开发流程
根据图示内容进行kernel类的定义与编写即可,昇腾的Ascend C开发流程还是比较清晰明了
其中此处是固定shape大小,同时BUFFER_NUM = 2;说明了一个分成两个部分的queue进行操作
动态shape
之后实验进行动态的shape的推断
此处在host侧进行totalLength和tileNum变量的定义
动态算子
三、调用方式
1、kernel直调
开发者完成算子核函数的开发和tiling实现后可通过ascendcl运行时接口完成算子调用
2、自定义算子工程
按照工程创建->算子实现->编译部署->算子调用的流程完成开发
一、昇腾系列处理器与Ascend C
1、昇腾AI计算
昇腾AI芯片的计算核心主要由AI Core构成,负责执行标量、向量和张量相关的计算密集型算子。AI Core采用了达芬奇架构,
从控制上可以看成是一个相对简化的现代微处理器的基本架构。它包括了三种基础计算资源:矩阵计算单元(Cube Unit)、向量计算单元(Vector Unit)和标量计算单元(Scalar Unit)。这三种计算单元分别对应了张量、向量和标量三种常见的计算模式,在实际的计算过程中各司其职,形成了三条独立的执行流水线,在系统软件的统一调度下互相配合达到优化的计算效率。此外在矩阵计算单元和向量计算单元内部还提供了不同精度、不同类型的计算模式。AI Core中的矩阵计算单元目前可以支持INT8、INT4和FP16的计算;向量计算单元目前可以支持FP16和FP32的计算。
昇腾计算基于华为昇腾系列(HUAWEI Ascend)AI处理器和基础软件构建Atlas人工智能计算解决方案,包括Atlas系列模块、板卡、小站、服务器、集群等丰富的产品形态,打造面向“端、边、云”的全场景AI基础设施方案,覆盖深度学习领域推理和训练全流程。
2、CANN
CANN(Compute Architecture for Neural Networks)是华为针对AI场景推出的异构计算架构,对上支持多种AI框架,对下服务AI处理器与编程,发挥承上启下的关键作用,是提升昇腾AI处理器计算效率的关键平台。同时针对多样化应用场景,提供高效易用的编程接口,支持用户快速构建基于昇腾平台的AI应用和业务
3、Ascend C
面向算子开发场景的编程语言Ascend C,原生支持C和C++标准规范,最大化匹配用户开发习惯;通过多层接口抽象、自动并行计算、孪生调试等关键技术,极大提高算子开发效率,助力AI开发者低成本完成算子开发和模型调优部署。
二、算子开发流程
根据图示内容进行kernel类的定义与编写即可,昇腾的Ascend C开发流程还是比较清晰明了
/* * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. * * Function : z = x + y * This sample is a very basic sample that implements vector add on Ascend plaform. */ #include "kernel_operator.h" using namespace AscendC; constexpr int32_t TOTAL_LENGTH = 8 * 2048; // total length of data constexpr int32_t USE_CORE_NUM = 8; // num of core used constexpr int32_t BLOCK_LENGTH = TOTAL_LENGTH / USE_CORE_NUM; // length computed of each core constexpr int32_t TILE_NUM = 8; // split data into 8 tiles for each core constexpr int32_t BUFFER_NUM = 2; // tensor num for each queue constexpr int32_t TILE_LENGTH = BLOCK_LENGTH / TILE_NUM / BUFFER_NUM; // seperate to 2 parts, due to double buffer class KernelAdd { public: __aicore__ inline KernelAdd() {} __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z) { xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH); yGm.SetGlobalBuffer((__gm__ half*)y + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH); zGm.SetGlobalBuffer((__gm__ half*)z + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH); pipe.InitBuffer(inQueueX, BUFFER_NUM, TILE_LENGTH * sizeof(half)); pipe.InitBuffer(inQueueY, BUFFER_NUM, TILE_LENGTH * sizeof(half)); pipe.InitBuffer(outQueueZ, BUFFER_NUM, TILE_LENGTH * sizeof(half)); } __aicore__ inline void Process() { int32_t loopCount = TILE_NUM * BUFFER_NUM; for (int32_t i = 0; i < loopCount; i++) { CopyIn(i); Compute(i); CopyOut(i); } } private: __aicore__ inline void CopyIn(int32_t progress) { LocalTensor<half> xLocal = inQueueX.AllocTensor<half>(); LocalTensor<half> yLocal = inQueueY.AllocTensor<half>(); DataCopy(xLocal, xGm[progress * TILE_LENGTH], TILE_LENGTH); DataCopy(yLocal, yGm[progress * TILE_LENGTH], TILE_LENGTH); inQueueX.EnQue(xLocal); inQueueY.EnQue(yLocal); } __aicore__ inline void Compute(int32_t progress) { LocalTensor<half> xLocal = inQueueX.DeQue<half>(); LocalTensor<half> yLocal = inQueueY.DeQue<half>(); LocalTensor<half> zLocal = outQueueZ.AllocTensor<half>(); Add(zLocal, xLocal, yLocal, TILE_LENGTH); outQueueZ.EnQue<half>(zLocal); inQueueX.FreeTensor(xLocal); inQueueY.FreeTensor(yLocal); } __aicore__ inline void CopyOut(int32_t progress) { LocalTensor<half> zLocal = outQueueZ.DeQue<half>(); DataCopy(zGm[progress * TILE_LENGTH], zLocal, TILE_LENGTH); outQueueZ.FreeTensor(zLocal); } private: TPipe pipe; TQue<QuePosition::VECIN, BUFFER_NUM> inQueueX, inQueueY; TQue<QuePosition::VECOUT, BUFFER_NUM> outQueueZ; GlobalTensor<half> xGm; GlobalTensor<half> yGm; GlobalTensor<half> zGm; }; extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z) { KernelAdd op; op.Init(x, y, z); op.Process(); } #ifndef __CCE_KT_TEST__ // call of kernel function void add_custom_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y, uint8_t* z) { add_custom<<<blockDim, l2ctrl, stream>>>(x, y, z); } #endif其中此处是固定shape大小,同时BUFFER_NUM = 2;说明了一个分成两个部分的queue进行操作
动态shape
之后实验进行动态的shape的推断
#include "register/tilingdata_base.h" namespace optiling { BEGIN_TILING_DATA_DEF(SinhCustomTilingData) TILING_DATA_FIELD_DEF(uint32_t, totalLength); TILING_DATA_FIELD_DEF(uint32_t, tileNum); END_TILING_DATA_DEF; REGISTER_TILING_DATA_CLASS(SinhCustom, SinhCustomTilingData) }此处在host侧进行totalLength和tileNum变量的定义
#include "sinh_custom_tiling.h" #include "register/op_def_registry.h" namespace optiling { const uint32_t BLOCK_DIM = 8; const uint32_t TILE_NUM = 8; static ge::graphStatus TilingFunc(gert::TilingContext* context) { SinhCustomTilingData tiling; uint32_t totalLength = context->GetInputShape(0)->GetOriginShape().GetShapeSize(); tiling.set_totalLength(totalLength); tiling.set_tileNum(TILE_NUM); context->SetBlockDim(BLOCK_DIM); tiling.SaveToBuffer(context->GetRawTilingData()->GetData(), context->GetRawTilingData()->GetCapacity()); context->GetRawTilingData()->SetDataSize(tiling.GetDataSize()); size_t *currentWorkspace = context->GetWorkspaceSizes(1); currentWorkspace[0] = 0; return ge::GRAPH_SUCCESS; } } namespace ge { static ge::graphStatus InferShape(gert::InferShapeContext* context) { const gert::Shape* x1_shape = context->GetInputShape(0); gert::Shape* y_shape = context->GetOutputShape(0); *y_shape = *x1_shape; return GRAPH_SUCCESS; } } namespace ops { class SinhCustom : public OpDef { public: explicit SinhCustom(const char* name) : OpDef(name) { this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16}) .Format({ge::FORMAT_ND}) .UnknownShapeFormat({ge::FORMAT_ND}); this->Output("y") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16}) .Format({ge::FORMAT_ND}) .UnknownShapeFormat({ge::FORMAT_ND}); this->SetInferShape(ge::InferShape); this->AICore() .SetTiling(optiling::TilingFunc); this->AICore().AddConfig("ascend310b"); } }; OP_ADD(SinhCustom); }动态算子
#include "kernel_operator.h" using namespace AscendC; constexpr int32_t BUFFER_NUM = 2; class KernelSinh { public: __aicore__ inline KernelSinh() {} __aicore__ inline void Init( */GM_ADDR x, GM_ADDR y, uint32_t totalLength, uint32_t tileNum) { ASSERT(GetBlockNum() != 0 && "block dim can not be zero!"); this->blockLength = totalLength / GetBlockNum(); this->tileNum = tileNum; ASSERT(tileNum != 0 && "tile num can not be zero!"); this->tileLength = this->blockLength / tileNum / BUFFER_NUM; xGm.SetGlobalBuffer((__gm__ half*)x + this->blockLength * GetBlockIdx(), this->blockLength); yGm.SetGlobalBuffer((__gm__ half*)y + this->blockLength * GetBlockIdx(), this->blockLength); pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(half)); pipe.InitBuffer(outQueueY, BUFFER_NUM, this->tileLength * sizeof(half)); } __aicore__ inline void Process() { int32_t loopCount = this->tileNum * BUFFER_NUM; for (int32_t i = 0; i < loopCount; i++) { CopyIn(i); Compute(i); CopyOut(i); } } private: __aicore__ inline void CopyIn(int32_t progress) { LocalTensor<half> xLocal = inQueueX.AllocTensor<half>(); DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength); inQueueX.EnQue(xLocal); } __aicore__ inline void Compute(int32_t progress) { LocalTensor<half> xLocal = inQueueX.DeQue<half>(); LocalTensor<half> yLocal = outQueueY.AllocTensor<half>(); half temp = 0.5; Exp(yLocal,xLocal,this->tileLength); Reciprocal(yLocal,yLocal,this->tileLength); Exp(xLocal,xLocal,this->tileLength); Sub(yLocal,xLocal,yLocal,this->tileLength); Muls(yLocal,yLocal,temp,this->tileLength); outQueueY.EnQue<half>(yLocal); inQueueX.FreeTensor(xLocal); } __aicore__ inline void CopyOut(int32_t progress) { LocalTensor<half> yLocal = outQueueY.DeQue<half>(); DataCopy(yGm[progress * this->tileLength], yLocal, this->tileLength); outQueueY.FreeTensor(yLocal); } private: TPipe pipe; //create queue 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<half> xGm; GlobalTensor<half> yGm; uint32_t blockLength; uint32_t tileNum; uint32_t tileLength; }; 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; //补充init和process函数调用内容 op.Init(x, y, tiling_data.totalLength, tiling_data.tileNum); op.Process(); }三、调用方式
1、kernel直调
开发者完成算子核函数的开发和tiling实现后可通过ascendcl运行时接口完成算子调用
2、自定义算子工程
按照工程创建->算子实现->编译部署->算子调用的流程完成开发