一文教你如何呼叫Ascend C運算元

华为云开发者联盟發表於2024-05-29

本文分享自華為雲社群《一文教你如何呼叫Ascend C運算元》,作者: 昇騰CANN。

Ascend C是CANN針對運算元開發場景推出的程式語言,原生支援C和C++標準規範,兼具開發效率和執行效能。基於Ascend C編寫的運算元程式,透過編譯器編譯和執行時排程,執行在昇騰AI處理器上。使用Ascend C,開發者可以基於昇騰AI硬體高效實現自定義的創新演算法。

本文重點介紹基於Ascend C運算元程式語言完成自定義運算元的開發和部署後,如何呼叫自定義運算元驗證運算元功能。

三種常見的運算元呼叫方式

目前,Ascend C運算元有三種常見的呼叫方式:

  • Kernel直調:完成運算元核函式開發和Tiling實現後,可基於核心呼叫符方式進行完成運算元的呼叫,用來快速驗證演算法邏輯。
  • 單運算元呼叫:相比於Kernel直調,單運算元呼叫是一種較為標準的呼叫方式。開發者在完成所有運算元交付件開發、編譯部署之後,一般透過單運算元呼叫方式驗證單運算元功能以滿足交付條件,包括兩種呼叫方式:
  • 單運算元API執行:基於C語言的API執行運算元,直接呼叫單運算元API介面,無需提供單運算元描述檔案進行離線模型的轉換。
  • 單運算元模型執行:基於圖IR執行運算元,先編譯運算元(例如,使用ATC工具將Ascend IR定義的單運算元描述檔案編譯成運算元om模型檔案),再呼叫AscendCL介面載入運算元模型,最後呼叫AscendCL介面執行運算元。
  • 在PyTorch、ONNX、TensorFlow等三方框架中呼叫運算元:需要完成框架適配開發,即可從第三方框架實現運算元呼叫。

當然,除了可以呼叫自定義運算元進行功能驗證外,開發者也可以透過單運算元呼叫方式直接呼叫昇騰運算元庫中預製的運算元,使用昇騰算力。

透過單運算元API執行方式呼叫運算元

透過單運算元API執行方式呼叫運算元,是運算元交付階段最重要的一種呼叫方式,也是Ascend C運算元開發人員必須掌握的運算元呼叫手段,下面做重點講解。開發者若想了解其他方式,可以移至文末查閱“Ascend C一站式學習資源”[1]。

Ascend C運算元開發並編譯部署完成後,會在運算元包安裝目錄下的op_api目錄下會自動生成單運算元API,以預設安裝場景為例,單運算元呼叫的標頭檔案.h和動態庫libcust_opapi.so所在的目錄結構為:

├── opp    //運算元庫目錄 
│   ├── vendors     //自定義運算元所在目錄 
│       ├── config.ini 
│       └── vendor_name1   // 儲存對應廠商部署的自定義運算元,此名字為編譯自定義運算元安裝包時配置的vendor_name,若未配置,預設值為customize 
│           ├── op_api 
│           │   ├── include 
│           │   │  └── aclnn_xx.h 
│           │   └── lib 
│           │       └── libcust_opapi.so 
...

aclnn_xx.h中的運算元API形式一般定義為“兩段式介面”,形如:

aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t workspaceSize, aclOpExecutor **executor); 
aclnnStatus aclnnXxx(void* workspace, int64 workspaceSize, aclOpExecutor* executor, aclrtStream stream);

單運算元API可以直接在應用程式中呼叫,大致過程為:

  1. 使用第一段介面aclnnXxxGetWorkspaceSize計算本次API呼叫計算過程中需要多少的workspace記憶體
  2. 獲取到本次API計算需要的workspace大小後,按照workspaceSize大小申請Device側記憶體
  3. 呼叫第二段介面aclnnXxx,呼叫對應的單運算元二進位制執行計算

完整呼叫流程如下:

1.png

下面提供單運算元呼叫的關鍵程式碼示例,供開發者參考:

// 1.AscendCL初始化 
aclRet = aclInit("../scripts/acl.json"); 
 
// 2.執行管理資源申請 
int deviceId = 0; 
aclRet = aclrtSetDevice(deviceid); 
// 獲取軟體棧的執行模式,不同執行模式影響後續的介面呼叫流程(例如是否進行資料傳輸等) 
aclrtRunMode runMode; 
bool g_isDevice = false; 
aclError aclRet = aclrtGetRunMode(&runMode); 
g_isDevice = (runMode == ACL_DEVICE); 
 
// 3.申請記憶體存放運算元的輸入輸出 
// ...... 
 
// 4.傳輸資料 
if (aclrtMemcpy(devInputs_[i], size, hostInputs_[i], size, kind) != ACL_SUCCESS) { 
    return false; 
} 
 
// 5.計算workspace大小並申請記憶體 
size_t workspaceSize = 0; 
aclOpExecutor *handle = nullptr; 
auto ret = aclnnAddCustomGetWorkspaceSize(inputTensor_[0], inputTensor_[1], outputTensor_[0], 
                                          &workspaceSize, &handle); 
// ... 
void *workspace = nullptr; 
if (workspaceSize != 0) { 
    if (aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_NORMAL_ONLY) != ACL_SUCCESS) { 
        ERROR_LOG("Malloc device memory failed"); 
    } 
} 
 
// 6.執行運算元 
if (aclnnAddCustom(workspace, workspaceSize, handle, stream) != ACL_SUCCESS) { 
    (void)aclrtDestroyStream(stream); 
    ERROR_LOG("Execute Operator failed. error code is %d", static_cast<int32_t>(ret)); 
    return false; 
} 
 
// 7.同步等待 
aclrtSynchronizeStream(stream); 
 
// 8.處理執行運算元後的輸出資料,例如在螢幕上顯示、寫入檔案等,由使用者根據實際情況自行實現 
// ...... 
 
// 9.釋放執行管理資源 
aclRet = aclrtResetDevice(deviceid); 
// .... 
 
// 10.AscendCL去初始化 
aclRet = aclFinalize();

執行一個完整的運算元呼叫程式

昇騰的gitee倉中提供了完整的樣例工程LINK,工程目錄結構如下:

├──input                                                 // 存放指令碼生成的輸入資料目錄 
├──output                                                // 存放運算元執行輸出資料和真值資料的目錄 
├── inc                           // 標頭檔案目錄  
│   ├── common.h                 // 宣告公共方法類,用於讀取二進位制檔案  
│   ├── operator_desc.h          // 運算元描述宣告檔案,包含運算元輸入/輸出,運算元型別以及輸入描述與輸出描述  
│   ├── op_runner.h              // 運算元執行相關資訊宣告檔案,包含運算元輸入/輸出個數,輸入/輸出大小等  
├── src  
│   ├── CMakeLists.txt    // 編譯規則檔案 
│   ├── common.cpp         // 公共函式,讀取二進位制檔案函式的實現檔案 
│   ├── main.cpp    // 單運算元呼叫應用的入口 
│   ├── operator_desc.cpp     // 構造運算元的輸入與輸出描述  
│   ├── op_runner.cpp   // 單運算元呼叫主體流程實現檔案 
├── scripts 
│   ├── verify_result.py    // 真值對比檔案 
│   ├── gen_data.py    // 輸入資料和真值資料生成指令碼檔案 
│   ├── acl.json    // acl配置檔案

步驟1 增加標頭檔案引用。

安裝部署完成後,會在運算元包安裝目錄下的op_api目錄生成單運算元呼叫的標頭檔案aclnn_xx.h和動態庫libcust_opapi.so,編寫單運算元的呼叫程式碼時,要包含自動生成的單運算元API執行介面標頭檔案:

#include "aclnn_add_custom.h"

步驟2 修改CMakeLists檔案。

編譯運算元呼叫程式時,需要在標頭檔案的搜尋路徑include_directories中增加運算元包安裝目錄下的op_api/include目錄,便於找到該標頭檔案;同時需要連結cust_opapi動態庫。

  • 設定CUST_PKG_PATH變數為運算元包安裝目錄下的op_api目錄,以下樣例僅為參考,請根據運算元包部署的實際目錄位置進行設定。
if (NOT DEFINED ENV{DDK_PATH}) 
    set(INC_PATH "/usr/local/Ascend/ascend-toolkit/latest") 
    message(STATUS "set default INC_PATH: ${INC_PATH}") 
else () 
    message(STATUS "env INC_PATH: ${INC_PATH}") 
endif() 
set(CUST_PKG_PATH "${INC_PATH}/opp/vendors/customize/op_api")
  • 在標頭檔案的搜尋路徑include_directories中增加運算元包安裝目錄下的op_api/include目錄。
include_directories( 
    ${INC_PATH}/runtime/include 
    ${INC_PATH}/atc/include 
    ../inc 
    ${CUST_PKG_PATH}/include 
)
  • 連結cust_opapi連結庫。
target_link_libraries(execute_add_op 
    ascendcl 
    cust_opapi 
    acl_op_compiler 
    nnopbase 
    stdc++ 
)

步驟3 生成測試資料。

在樣例工程目錄下,執行如下命令:

python3 scripts/gen_data.py

會在工程目錄下input目錄中生成兩個shape為(8,2048),資料型別為float16的資料檔案input_0.bin與input_1.bin,用於進行AddCustom運算元的驗證。程式碼樣例如下:

import numpy as np 
a = np.random.randint(100, size=(8, 2048,)).astype(np.float16) 
b = np.random.randint(100, size=(8, 2048,)).astype(np.float16) 
a.tofile('input_0.bin') 
b.tofile('input_1.bin')

步驟4 程式編譯與執行。

1. 開發環境上,設定環境變數,配置AscendCL單運算元驗證程式編譯依賴的標頭檔案與庫檔案路徑,如下為設定環境變數的示例。${INSTALL_DIR}表示CANN軟體安裝目錄,例如,$HOME/Ascend/ascend-toolkit/latest。{arch-os}為執行環境的架構和作業系統,arch表示作業系統架構,os表示作業系統,例如x86_64-linux。

export DDK_PATH=${INSTALL_DIR} 
export NPU_HOST_LIB=${INSTALL_DIR}/{arch-os}/lib64

2. 編譯樣例工程,生成單運算元驗證可執行檔案。

a. 切換到樣例工程根目錄,然後在樣例工程根目錄下執行如下命令建立目錄用於存放編譯檔案,例如,建立的目錄為“build”。

mkdir -p build

b. 進入build目錄,執行cmake編譯命令,生成編譯檔案,命令示例如下所示:

cd build 
cmake ../src

c. 執行如下命令,生成可執行檔案。

make

會在工程目錄的output目錄下生成可執行檔案execute_add_op。

3. 執行單運算元。

a. 以執行使用者(例如HwHiAiUser)複製開發環境中樣例工程output目錄下的execute_add_op到執行環境任一目錄。

說明: 若您的開發環境即為執行環境,此複製操作可跳過。

b. 在執行環境中,執行execute_add_op:

chmod +x execute_add_op 
./execute_add_op

會有如下屏顯資訊:

[INFO]  Set device[0] success 
[INFO]  Get RunMode[1] success 
[INFO]  Init resource success 
[INFO]  Set input success 
[INFO]  Copy input[0] success 
[INFO]  Copy input[1] success 
[INFO]  Create stream success 
[INFO]  Execute aclnnAddCustomGetWorkspaceSize success, workspace size 0 
[INFO]  Execute aclnnAddCustom success 
[INFO]  Synchronize stream success 
[INFO]  Copy output[0] success 
[INFO]  Write output success 
[INFO]  Run op success 
[INFO]  Reset Device success 
[INFO]  Destory resource success

如果有Run op success,表明執行成功,會在output目錄下生成輸出檔案output_z.bin。

4. 比較真值檔案。

切換到樣例工程根目錄,然後執行如下命令:

python3 scripts/verify_result.py output/output_z.bin output/golden.bin

會有如下屏顯資訊:

test pass

可見,AddCustom運算元驗證結果正確。

更多學習資源

[1]Ascend C一站式學習資源:https://www.hiascend.com/ascend-c

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章