【2023 · CANN訓練營第一季】——Ascend C算子開發(fā)入門——第二次課
前言:昇騰AI處理器的算子開發(fā)增加了一種新的方式,稱之為TIK2,正式名稱是Ascend C算子開發(fā)。不同于采用Python的DSL和TIK方式,Ascend C使用C/C++作為前端語言的算子開發(fā)工具,通過四層接口抽象、并行編程范式、孿生調試等技術,極大提高了算子的開發(fā)效率,助力AI開發(fā)者低成本完成算子開發(fā)和模型調優(yōu)部署。為了幫助開發(fā)者快速掌握這一新的技術,2023 CANN訓練營第一季同步開設了相關課程,總共有三節(jié)課。
????????本次是第二節(jié)課,講述了流水線,任務間通信與同步,快速和標準兩種算子開發(fā)模式,并講述基于內核調用方式的快速開發(fā)流程的實例。
課程地址:CANN訓練營2023年第一季_TIK2算子開發(fā)入門
https://www.hiascend.com/zh/developer/courses/detail/1627494761683783682
課程視頻:發(fā)布在B站“昇騰Ascend”:
第1次課:【2023? CANN訓練營第一季】-TIKC++算子開發(fā)入門(上)
https://www.bilibili.com/video/BV1ha4y1V7vK
第2次課:【2023? CANN訓練營第一季】-TIKC++算子開發(fā)入門(中)
https://www.bilibili.com/video/BV1Pa4y157RG/
第3次課:【2023? CANN訓練營第一季】-TIKC++算子開發(fā)入門(下)
https://www.bilibili.com/video/BV1yM411g7nw
技術文檔:“文檔首頁>CANN社區(qū)版>6.3.RC2.alpha001>算子開發(fā)>TIK C++算子開發(fā)>TIK C++簡介”https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_10_0001.html
????????本次課的內容要點如下:

文檔首頁>CANN社區(qū)版>6.3.RC2.alpha001>算子開發(fā)>TIK C++算子開發(fā)>編程模型>編程范式>簡介https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_10_0009.html
????????TIK C++編程范式把算子內部的處理程序,分成多個流水任務( stage ),以張量( Tensor)為數(shù)據(jù)載體,以隊列 ( Queue ) 進行任務之間的通信與同步,以內存管理模塊( Pipe ) 管理任務間的通信內存.
一、流水任務
????流水任務指的是單核處理程序中主程序調度的并行任務。在核函數(shù)內部,可以通過流水任務實現(xiàn)數(shù)據(jù)的并行處理,進一步提升性能。下面舉例來說明,流水任務如何進行并行調度。以下面的流水任務示意圖為例,單核處理程序的功能被拆分成3個流水任務:Stage1、Stage2、Stage3,每個任務專注于完成單一功能;需要處理的數(shù)據(jù)被切分成n片,使用Progress1~n表示,每個任務需要依次完成n個數(shù)據(jù)切片的處理。Stage間的箭頭表達數(shù)據(jù)間的依賴關系,比如Stage1處理完Progress1之后,Stage2才能對Progress1進行處理。

????????若n=3,即待處理的數(shù)據(jù)被切分成3片,則上圖中的流水任務運行起來的示意圖如下,從運行圖中可以看出,對于同一片數(shù)據(jù),Stage1、Stage2、Stage3之間的處理具有依賴關系,需要串行處理;不同的數(shù)據(jù)切片,同一時間點,可以有多個任務在并行處理,由此達到任務并行、提升性能的目的。

????????矢量(Vector)編程范式把算子的實現(xiàn)流程分為3個基本任務:CopyIn,Compute,CopyOut。CopyIn負責搬入操作,Compute負責矢量計算操作,CopyOut負責搬出操作。

二、任務間通信與同步
1、數(shù)據(jù)通信與同步的管理者
????????不同的流水任務之間存在數(shù)據(jù)依賴,需要進行數(shù)據(jù)傳遞。Ascend C算子中使用Queue隊列完成任務之間的數(shù)據(jù)通信和同步,提供EnQue、DeQue等基礎API。Queue隊列管理NPU上不同層級的物理內存時,用一種抽象的邏輯位置(QuePosition)來表達各級別的存儲,代替了片上物理存儲的概念,開發(fā)者無需感知硬件架構。
????????矢量編程中使用到的邏輯位置(QuePosition)定義如下:搬入數(shù)據(jù)的存放位置:VECIN;和搬出數(shù)據(jù)的存放位置:VECOUT。
????????矢量編程主要分為CopyIn、Compute、CopyOut三個任務:
CopyIn任務中將輸入數(shù)據(jù)從Global內存搬運至Local內存后,需要使用EnQue將LocalTensor放入VECIN的Queue中;
Compute任務等待VECIN的Queue中LocalTensor出隊之后才可以完成矢量計算,計算完成后使用EnQue將計算結果LocalTensor放入到VECOUT的Queue中;
CopyOut任務等待VECOUT的Queue中LocalTensor出隊,再將其拷貝到Global內存。

2、數(shù)據(jù)的載體
????????TIK C++使用GlobalTensor和LocalTensor作為數(shù)據(jù)的基本操作單元,它是各種指令API直接調用的對象,也是數(shù)據(jù)的載體。
三、內存管理機制
1、內存管理
????????任務間數(shù)據(jù)傳遞使用到的內存統(tǒng)一由內存管理模塊Pipe進行管理。Pipe作為片上內存管理者,通過InitBuffer接口對外提供Queue內存初始化功能,開發(fā)者可以通過該接口為指定的Queue分配內存。
????????Queue隊列內存初始化完成后,需要使用內存時,通過調用AllocTensor來為LocalTensor分配內存,當創(chuàng)建的LocalTensor完成相關計算無需再使用時,再調用FreeTensor來回收LocalTensor的內存。

2、臨時變量內存管理
????????編程過程中使用到的臨時變量內存同樣通過Pipe進行管理。臨時變量可以使用TBuf數(shù)據(jù)結構來申請指定QuePosition上的存儲空間,并使用Get()來將分配到的存儲空間分配給新的LocaLTensor從TBuf上獲取全部長度,或者獲取指定長度的LocalTensor。

????????使用TBuf申請的內存空間只能參與計算,無法執(zhí)行Queue隊列的入隊出隊操作。
四、算子開發(fā)流程
一)算子開發(fā)流程
????????課程介紹了2種算子開發(fā)流程:快速開發(fā)和標準開發(fā)。
????????1、快速開發(fā)流程:完成kernel側算子實現(xiàn)開發(fā)、通過內核調用符對算子進行調用驗證

? ? ? ? 2、標準開發(fā)流程:完成kernel側算子實現(xiàn)開發(fā)、host側算子實現(xiàn)開發(fā),對算子進行編譯部署后,通過ACL單算子調用的方式,對算子進行運行驗證。

????????快速開發(fā)模式和標準開發(fā)模式對比如下:

二)、Tik C++矢量算子的編程
? ? ? ? 矢量算子開發(fā)流程如下:

????????老師以add作為例子講解了TIK C++矢量算子的快速開發(fā)流程。
????????1、算子分析:分析算子的數(shù)學表達式、輸入、輸出以及計算邏輯的實現(xiàn),明確需要調用的TIK C++接口。
????????例子以Add算子為例,數(shù)學公式:,為簡單起見,設定輸入張量x, y,z為固定shape(8,2048),數(shù)據(jù)類型dtype為half類型,數(shù)據(jù)排布類型format為ND,核函數(shù)名稱為add_tik2。

????????1)算子的數(shù)學表達式及計算邏輯
????????Add算子的數(shù)學表達式為:,
????????計算邏輯:輸入數(shù)據(jù)需要先搬入到片上存儲,然后使用計算接口完成兩個加法運算,得到最終結果,再搬出到外部存儲。
????????2)輸入和輸出
????????Add算子有兩個輸入:與
,輸出為
。輸入數(shù)據(jù)類型為half,輸出數(shù)據(jù)類型與輸入數(shù)據(jù)類型相同。輸入支持固定shape(8,2048)輸出shape與輸入shape相同,輸入數(shù)據(jù)排布類型為ND。
????????3)確定核函數(shù)名稱和參數(shù)
????????自定義核函數(shù)名,如add_tik2。根據(jù)輸入輸出,確定核函數(shù)有3個入參x,y,z。
????????x,y為輸入在Global Memory上的內存地址,z為輸出在Global Memory上的內存地址。
????????4)確定算子實現(xiàn)所需接口
????????涉及內外部存儲間的數(shù)據(jù)搬運,使用數(shù)據(jù)搬移接口:Datacopy實現(xiàn);
????????涉及矢量計算的加法操作,使用矢量雙目指令:Add實現(xiàn);
????????使用到LocalTensor,使用Queue隊列管理,會使用到EnQue、DeQue等接口。
????????2、核函數(shù)定義:
????????在add tik2核函數(shù)的實現(xiàn)中實例化kerneLAdd算子類,調用Init()數(shù)完成內存初始化,調用Process()函數(shù)完成核心邏。

????????3、根據(jù)矢量編程范式實現(xiàn)算子類
????????根據(jù)前面的知識,算子實現(xiàn)三個流水任務CopyIn、Compute、CopyOut。任務間通過隊列VECIN、VECOUT進行通信和同步,由pipe內存管理對象對任務間交互使用到的內存、臨時變量使用到的內存統(tǒng)一進行管理。如下圖所示:

????????CopyIn任務:將Global Memory上的輸入Tensor xGm和yGm搬運至Local Memory,分別存儲在xLocal,yLocal;
????????Compute任務:對xLocal,yLocal執(zhí)行加法操作,計算結果存儲在zLocal中;
????????CopyOut任務:將輸出數(shù)據(jù)從zLocal搬運至Global Memory上的輸出Tensor zGm中
????????CopyIn,Compute任務間通過VECIN隊列inQueuex,inQueuer進行通信和同步????????????????????compute,copyout任務間通過VECOUT隊列outQueuez進行通信和同步
????????1)算子類定義

????????2)Init()函數(shù)實現(xiàn):多核并行+單核處理數(shù)據(jù)

????????3)Process()函數(shù)的實現(xiàn)——CopyIn,Compute、CopyOut三個流水任務

????????4)double buffer機制
????????double buffer通過將數(shù)據(jù)搬運與矢量計算并行執(zhí)行以隱藏數(shù)據(jù)搬運時間并降低矢量指令的等待時間,最終提高矢量計算單元的利用效率1個Tensor同一時間只能進行搬入、計算和搬出三個流水任務中的一個,其他兩個流水任務涉及的硬件單元則處于ldle狀態(tài)如果將待處理的數(shù)據(jù)一分為二,比如Tensor1、Tensor2:
當矢量計算單元對Tensor1進行Compute時,Tensor2可以執(zhí)行CopvIn的任務
當矢量計算單元對Tensor2進行Compute時,Tensor1可以執(zhí)行CopyOut的任務
當矢量計算單元對Tensor2進行CopyOut時Tensor1可以執(zhí)行CopyIn的任務由此,數(shù)據(jù)的進出搬運和矢量計算之間實現(xiàn)并行,硬件單元閑置問題得以有效緩解

????????4、基于內核調用符方式驗證
?????????先使用python腳本生成x,y,并計算出z(golden)并落盤。然后再用相同的x,y,在cpu和npu模式下調用add算子,計算出結果z,并與python腳本采用計算md5sum的方式進行對比,完全一樣,則表示結果正確。
????????為了運行方便,課程提供了一個run.sh,寫有cpu和npu模式的編譯命令,通過輸入參數(shù)進行選擇cpu或npu模式進行編譯,運行。
????????1)CPU模式下:

?????? ?運行結果:

????????2)NPU模式下:
????????NPU模式使用<<<>>>方式調用,由于CPU模式g++沒有<<<>>>的表達,需要使用內置宏 __CCE_KT_TEST。

????????運行結果如下:

????????