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#
計算單元,存儲單元,控制單元。
內部結構:
標量計算單元(CPU)下發指令
Tensor 概念
name
shape
dtype
format
format#
N: batch size,圖像數量
H: Height,高度
W: Width,寬度
C: Channel,通道數,RGB 為 3
Ascend C 優勢#
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,先求出了
然後求出了
最後相減,再除以 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);
}