天天看點

Ascend C sqrt算子實戰

作者:華為雲開發者聯盟

本文分享自華為雲社群《【2023 · CANN訓練營第一季】——Ascend C sqrt算子實戰-雲社群-華為雲》,作者:dayao。

前言

編寫一個Ascend C的sqrt算子,并通過核心調用方式在cpu和npu模式下進行驗證。在訓練營沙箱環境下,cpu模式工作正常結果正确。

一、概述

先簡單回顧下TIK C++算子矢量程式設計的流程和實作。

矢量算子開發流程如下:

Ascend C sqrt算子實戰

主要工作内容有:

1、算子分析:确定輸入輸出,确定數學表達式以及底層實作接口,确定核函數定義。

2、算子類的實作:實作init()和process()。init()完成記憶體初始化,實質上展現的是多核運作,和單核資料切分以及是否開啟double buffer優化;Process()實作的是CopyIn,Compute、CopyOut三個流水任務。

3、算子驗證:通過核函數的核心調用符的方式調用算子,計算出結果,并于使用相同輸入用numpy計算結果進行比對,誤差在一定範圍内即可。實際應用中,需要使用原有架構的算子進行計算精度比對。

二、算子分析

算子定義如下:假定仍是8個邏輯核。

Ascend C sqrt算子實戰

查詢TIK C++的API可知,可以使用(TIK C++ API/矢量計算/單目/Sqrt,采用2級接口)完成運算,得到最終結果。

Ascend C sqrt算子實戰

三、代碼分析

直接在訓練營課程提供的add_tik2算子工程上修改。代碼位址:https://gitee.com/zgx950813/samples/tree/master/tik2_demo/kernel_samples/kernel_add_sample

修改代碼目錄結構如下:CMakeLists.txt和data_utils.h未作修改,編譯和執行腳本run.sh隻改了計算結果與真值比對部分。

Ascend C sqrt算子實戰

一)、核函數定義

與例程相比,輸入參數隻有x。

extern "C" __global__ __aicore__ void sqrt_tik2(__gm__ uint8_t* x, __gm__ uint8_t* z)

{

KernelSqrt op;

op.Init(x, z);

op.Process();

}           

二)、算子類

實作方式與add例程類似。init()函數裡初始化記憶體:x,y的Global Memory ;流水線任務通訊記憶體;Process()實作流水線任務;按範式編寫CopyIn、Compute、CopyOut。與add例程最大差異是,在compute函數中,調用sqrt的2類接口API實作計算。

class KernelSqrt {

public:

__aicore__ inline KernelSqrt() {}

__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* z)

{

// get start index for current core, core parallel

xGm.SetGlobalBuffer((__gm__ half*)x + block_idx * BLOCK_LENGTH, BLOCK_LENGTH);

zGm.SetGlobalBuffer((__gm__ half*)z + block_idx * BLOCK_LENGTH, BLOCK_LENGTH);

// pipe alloc memory to queue, the unit is Bytes

pipe.InitBuffer(inQueueX, BUFFER_NUM, TILE_LENGTH * sizeof(half));

pipe.InitBuffer(outQueueZ, BUFFER_NUM, TILE_LENGTH * sizeof(half));

}

__aicore__ inline void Process()

{

// loop count need to be doubled, due to double buffer

constexpr int32_t loopCount = TILE_NUM * 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<half> xLocal = inQueueX.AllocTensor<half>();

// copy progress_th tile from global tensor to local tensor

DataCopy(xLocal, xGm[progress * TILE_LENGTH], TILE_LENGTH);

// enque input tensors to VECIN queue

inQueueX.EnQue(xLocal);

}

__aicore__ inline void Compute(int32_t progress)

{

// deque input tensors from VECIN queue

LocalTensor<half> xLocal = inQueueX.DeQue<half>();

LocalTensor<half> zLocal = outQueueZ.AllocTensor<half>();

// call Sqrt instr for computation

Sqrt(zLocal, xLocal, TILE_LENGTH);

// enque the output tensor to VECOUT queue

outQueueZ.EnQue<half>(zLocal);

// free input tensors for reuse

inQueueX.FreeTensor(xLocal);

}

__aicore__ inline void CopyOut(int32_t progress)

{

// deque output tensor from VECOUT queue

LocalTensor<half> zLocal = outQueueZ.DeQue<half>();

// copy progress_th tile from local tensor to global tensor

DataCopy(zGm[progress * TILE_LENGTH], zLocal, TILE_LENGTH);

// free output tensor for reuse

outQueueZ.FreeTensor(zLocal);

}

private:

TPipe pipe;

// 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> outQueueZ;

GlobalTensor<half> xGm, zGm;

};           

三)、核函數調用

1、在CPU模式下,通過ICPU_RUN_KF調用

ICPU_RUN_KF(sqrt_tik2, blockDim, x, z); // use this macro for cpu debug           

2、在NPU模式下,通過<<<>>>調用

#ifndef __CCE_KT_TEST__

// call of kernel function

void sqrt_tik2_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* z)

{

sqrt_tik2<<<blockDim, l2ctrl, stream>>>(x, z);

}

#endif           

由于<<<>>>,隻能在NPU模式下調用,是以需要用條件編譯,不在CPU調試模式下有效。在調用sqrt_tik2_do,需要按ascendcl應用程式設計的要求進行。

3、調用代碼

通過“__CCE_KT_TEST__”宏區分CPU和NPU模式。

int32_t main(int32_t argc, char* argv[])

{

size_t inputByteSize = 8 * 2048 * sizeof(uint16_t); // uint16_t represent half

size_t outputByteSize = 8 * 2048 * sizeof(uint16_t); // uint16_t represent half

uint32_t blockDim = 8;

#ifdef __CCE_KT_TEST__

uint8_t* x = (uint8_t*)tik2::GmAlloc(inputByteSize);

uint8_t* z = (uint8_t*)tik2::GmAlloc(outputByteSize);

ReadFile("./input/input_x.bin", inputByteSize, x, inputByteSize);

// PrintData(x, 16, printDataType::HALF);

ICPU_RUN_KF(sqrt_tik2, blockDim, x, z); // use this macro for cpu debug

// PrintData(z, 16, printDataType::HALF);

WriteFile("./output/output_z.bin", z, outputByteSize);

tik2::GmFree((void *)x);

tik2::GmFree((void *)z);

#else

aclInit(nullptr);

aclrtContext context;

aclError error;

int32_t deviceId = 0;

aclrtCreateContext(&context, deviceId);

aclrtStream stream = nullptr;

aclrtCreateStream(&stream);

uint8_t *xHost, *zHost;

uint8_t *xDevice, *zDevice;

aclrtMallocHost((void**)(&xHost), inputByteSize);

aclrtMallocHost((void**)(&zHost), outputByteSize);

aclrtMalloc((void**)&xDevice, inputByteSize, ACL_MEM_MALLOC_HUGE_FIRST);

aclrtMalloc((void**)&zDevice, outputByteSize, ACL_MEM_MALLOC_HUGE_FIRST);

ReadFile("./input/input_x.bin", inputByteSize, xHost, inputByteSize);

// PrintData(xHost, 16, printDataType::HALF);

aclrtMemcpy(xDevice, inputByteSize, xHost, inputByteSize, ACL_MEMCPY_HOST_TO_DEVICE);

sqrt_tik2_do(blockDim, nullptr, stream, xDevice, zDevice); // call kernel in this function

aclrtSynchronizeStream(stream);

aclrtMemcpy(zHost, outputByteSize, zDevice, outputByteSize, ACL_MEMCPY_DEVICE_TO_HOST);

// PrintData(zHost, 16, printDataType::HALF);

WriteFile("./output/output_z.bin", zHost, outputByteSize);

aclrtFree(xDevice);

aclrtFree(zDevice);

aclrtFreeHost(xHost);

aclrtFreeHost(zHost);

aclrtDestroyStream(stream);

aclrtResetDevice(deviceId);

aclFinalize();

#endif

return 0;

}           

四)、基準資料生成——sqrt_tik2.py

使用numpy生成input_x和基準結果golden。

import numpy as np

def gen_golden_data_simple():

input_x = np.random.uniform(0, 100, [8, 2048]).astype(np.float16)

golden = np.sqrt(input_x).astype(np.float16)

input_x.tofile("./input/input_x.bin")

golden.tofile("./output/golden.bin")

if __name__ == "__main__":

gen_golden_data_simple()           

五)、計算結果比較

使用numpy的allclose()函數比較算子計算與基準資料的結果。實際上由于npu模式編譯出錯,實際未執行改函數進行比較。CPU模式下,算子計算出的結果與基準golden資料完全一緻,兩者的md5相同。

四、編譯運作

本次課程提供了沙箱運作環境,想個辦法把代碼搞進去。

Ascend C sqrt算子實戰

一)、配置環境變量

Ascend C sqrt算子實戰

二)、CPU模式

cpu模式順利編譯運作,結果與對比組完全一緻。

Ascend C sqrt算子實戰

三)、NPU模式

npu模式下編譯報錯,因為沙箱時間有限,以後有機會再研究。

Ascend C sqrt算子實戰

關注#華為雲開發者聯盟# 點選下方,第一時間了解華為雲新鮮技術~

華為雲部落格_大資料部落格_AI部落格_雲計算部落格_開發者中心-華為雲