NekoMio

NekoMio

telegram
github

Ascend C 學習筆記

AI 處理器#

組成#

  • HBM(存儲)
  • DVPP(硬體編解碼)
  • CPU(4 核,ARM):指令分發等功能
  • AI Core NPU(AI 計算):執行 AI 計算,矩陣計算(16x16x16),標量計算,向量計算(128)等。

通用計算與 AI 計算的區別#

通用計算:適用於大多數的計算任務,比如加減乘除等。
比如計算矩陣乘法,只能串行計算,速度慢。

NPU 計算,可以使用向量計算單元,同時計算多個數據,速度更快,或者直接使用矩陣計算單元,直接計算矩陣乘法。

異構計算:CPU + NPU,CPU 負責控制,NPU 負責計算。

Ascend C#

原生算子開發,自動調度。

圖優化,自動流水。
算子深度融合,多個不同的算子,自動融合,減少數據內存拷貝。

計算圖執行下沉,將所有算子都下沉到 NPU 上執行。

自動調優 AOE。

Ascend C#

AI Core#

計算單元,存儲單元,控制單元。

20240711102909

內部結構:
標量計算單元(CPU)下發指令
20240711103146

Tensor 概念
name
shape
dtype
format

format#

N: batch size,圖像數量
H: Height,高度
W: Width,寬度
C: Channel,通道數,RGB 為 3

Ascend C 優勢#

20240711105258

Device 模塊#

Host 與 Device

Host 指伺服器
Device 指 NPU

Ascend C 算子開發#

Add 算子開發#

算子註冊#

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();
}

在這裡定義了我們這個 kernel 算子函數 add_custom,這個函數接受三個參數,分別是 x,y,z,這三個參數分別是輸入的兩個張量和輸出的張量。

GM_ADDR 是 __gm__ uint8_t* 的別名。表示 global memory 上的地址。

算子實現#

在這裡使用了一個類來實現算子。

class KernelAdd {
public:
    __aicore__ inline KernelAdd() {}
    __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z)
    {
        ...
    }
    __aicore__ inline void Process()
    {
        ...
    }

private:
    __aicore__ inline void CopyIn(int32_t progress)
    {
        ...
    }
    __aicore__ inline void Compute(int32_t progress)
    {
        ...
    }
    __aicore__ inline void CopyOut(int32_t progress)
    {
        ...
    }

private:
    ...
};

類中 public 了兩個主要的函數,Init 和 Process。

__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));
}

Init 主要是對內存地址進行分配以及初始化。

__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);
    }
}

Process 函數循環調用了 CopyIn、Compute、CopyOut 這三個組成流水的函數。

其中 CopyIn 函數是將數據從全局內存拷貝到局部內存,Compute 函數是計算,CopyOut 函數是將計算結果從局部內存拷貝到全局內存。

__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);
}

在計算中,使用 Add 向量加法來實現這個算子。

由於所有的指令都會並發執行,所以這個算子中使用了 queue 來保證數據依賴。

DeQue 會阻塞,直到隊列中有需要的數據。

算子調用#

在調用內核算子時

需要使用 <<<...>>> 來對函數進行調用。

blockDim:根據不同的設備,設置不同的 block 大小。
l2ctrl:目前為 nullptr
stream:表示整體的流

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);
}

Sinh 算子開發#

Sinh 的其他的結構與 Add 類似,區別主要是參數數量的差異。

其核心的區別點在於 Compute 函數的實現。

這裡我使用了 Muls Exp,先求出了exe^{-x}
然後求出了exe^x
最後相減,再除以 2,就得到了 sinh 的結果。

__aicore__ inline void Compute(int32_t progress)
{
    LocalTensor<DTYPE_X> xLocal = inQueueX.DeQue<DTYPE_X>();
    LocalTensor<DTYPE_Y> yLocal = outQueueY.AllocTensor<DTYPE_Y>();
    LocalTensor<DTYPE_Y> tmpLocal = tmpBuffer.AllocTensor<DTYPE_Y>();
    // e^-x
    Muls(tmpLocal, xLocal, static_cast<DTYPE_X>(-1), this->tileLength);
    Exp(tmpLocal, tmpLocal, this->tileLength);
    // e^x 
    Exp(yLocal, xLocal, this->tileLength);
    // (e^x - e^-x)
    Sub(yLocal, yLocal, tmpLocal, this->tileLength);
    // (e^x - e^-x) / 2
    Muls(yLocal, yLocal, static_cast<DTYPE_X>(0.5), this->tileLength);
    outQueueY.EnQue<DTYPE_Y>(yLocal);
    inQueueX.FreeTensor(xLocal);
    tmpBuffer.FreeTensor(tmpLocal);
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。