手把手帶你用香橙派AIpro開發AI推理應用

本文分享自華爲雲社區《如何基於香橙派AIpro開發AI推理應用》,作者:昇騰CANN。

01 簡介

香橙派AIpro開發板採用昇騰AI技術路線,接口豐富且具有強大的可擴展性,提供8/20TOPS澎湃算力,可廣泛使用於AI邊緣計算、深度視覺學習及視頻流AI分析、視頻圖像分析、自然語言處理等AI領域。通過昇騰CANN軟件棧的AI編程接口,可滿足大多數AI算法原型驗證、推理應用開發的需求。

AscendCL(Ascend Computing Language,昇騰計算語言)是昇騰計算開放編程框架,是對底層昇騰計算服務接口的封裝,提供Device管理、Context管理、Stream管理、內存管理、模型加載與執行、算子加載與執行、媒體數據處理等API,支持C&C++、Python編程語言,能夠實現深度學習推理計算、圖形圖像預處理、單算子加速計算等能力。

掌握了AscendCL的編程方法,就意味着可以在香橙派AIpro開發板上充分利用昇騰的算力資源,能夠基於深度學習算法開發圖片分類、目標檢測等一系列深度學習推理計算程序。

1.png

02 開發流程

使用AscendCL開發推理應用時,開發流程大致分爲以下幾步:

  1. AscendCL初始化:初始化AscendCL內部資源,爲運行做準備
  2. 運行管理資源申請:申請運行時相關資源,例如計算設備
  3. 媒體數據處理:可實現摳圖、縮放、視頻或圖片的編解碼等
  4. 模型推理:包括模型加載、執行、卸載
  5. 運行管理資源釋放:資源使用後及時釋放
  6. AscendCL去初始化:與初始化配對使用

首先,我們得先了解下,使用AscendCL時,經常會提到的“數據類型的操作接口” ,這是什麼呢?爲啥會存在?

在C/C++中,對用戶開放的數據類型通常以Struct結構體方式定義、以聲明變量的方式使用,但這種方式一旦結構體要增加成員參數,用戶的代碼就涉及兼容性問題,不便於維護,因此AscendCL對用戶開放的數據類型,均以接口的方式操作該數據類型,例如,調用某個數據類型的Create接口創建該數據類型、調用Get接口獲取數據類型內參數值、調用Set接口設置數據類型內的參數值、調用Destroy接口銷燬該數據類型,用戶無需關注定義數據類型的結構體長什麼樣,這樣即使後續數據類型需擴展,只需增加該數據類型的操作接口即可,也不會引起兼容性問題。

所以,總結下,“數據類型的操作接口”就是創建數據類型、Get/Set數據類型中的參數值、銷燬數據類型的一系列接口,存在的最大好處就是減少兼容性問題

接下來,進入我們今天的主題,怎麼用AscendCL的接口開發網絡模型推理場景下的應用。看完本文介紹的關鍵知識點,也可以到 “昇騰文檔中心[2]”查閱詳細的文檔介紹。

03 AscendCL初始化與去初始化

使用AscendCL接口開發應用時,必須先初始化AscendCL ,否則可能會導致後續系統內部資源初始化出錯,進而導致其它業務異常。在初始化時,還支持以下跟推理相關的配置項(例如,性能相關的採集信息配置),以json格式的配置文件傳入AscendCL初始化接口。如果當前的默認配置已滿足需求(例如,默認不開啓性能相關的採集信息配置),無需修改,可向AscendCL初始化接口中傳入NULL,或者可將配置文件配置爲空json串(即配置文件中只有{})。

有初始化就有去初始化,在確定完成了AscendCL的所有調用之後,或者進程退出之前,需調用AscendCL接口實現AscendCL去初始化。

// 此處以僞代碼的形式展示接口的調用流程

// 初始化
// 此處的..表示相對路徑,相對可執行文件所在的目錄,例如,編譯出來的可執行文件存放在out目錄下,此處的..就表示out目錄的上一級目錄
const char *aclConfigPath = "../src/acl.json";
aclError ret = aclInit(aclConfigPath);

// ......

// 去初始化
ret = aclFinalize();

04 運行管理資源申請與釋放

運行管理資源包括Device、Context、Stream、Event等,此處重點介紹Device、Context、Stream,其基本概念如下圖所示 。

3.PNG

您需要按順序依次申請如下運行管理資源:Device、Context、Stream,確保可以使用這些資源執行運算、管理任務。所有數據處理都結束後,需要按順序依次釋放運行管理資源:Stream、Context、Device

在申請運行管理資源時,Context、Stream支持隱式創建和顯式創建兩種申請方式。

4.PNG

// 此處以僞代碼的形式展示接口的調用流程,以顯式創建Context和Stream爲例

// 運行管理資源申請
// 1、指定運算的Device
aclError ret = aclrtSetDevice(deviceId);
// 2、顯式創建一個Context,用於管理Stream對象
ret = aclrtCreateContext(context, deviceId);
// 3、顯式創建一個Stream,用於維護一些異步操作的執行順序,確保按照應用程序中的代碼調用順序執行任務
ret = aclrtCreateStream(stream);

//......

// 運行管理資源釋放
// 1、銷燬Stream
ret = aclrtDestroyStream(stream);
// 2、銷燬Context
ret = aclrtDestroyContext(context);
// 3、釋放Device資源
ret = aclrtResetDevice(deviceId);

//......

05 媒體數據處理

如果模型對輸入圖片的寬高要求與用戶提供的源圖不一致,AscendCL提供了媒體數據處理的接口,可實現摳圖、縮放、格式轉換、視頻或圖片的編解碼等,將源圖裁剪成符合模型的要求。後續期刊中會展開說明這個功能,本期着重介紹模型推理的部分,以輸入圖片滿足模型的要求爲例。

06 模型加載

模型推理場景下,必須要有適配昇騰AI處理器的離線模型(*.om文件),我們可以使用ATC(Ascend Tensor Compiler)來構建模型。如果模型推理涉及動態Batch、動態分辨率等特性,需在構建模型增加相關配置。關於如何使用ATC來構建模型,請參見“昇騰文檔中心[2]”。

有了模型,就可以開始加載了,當前AscendCL支持以下幾種方式加載模型:

  • 從*.om文件中加載模型數據,由AscendCL管理內存
  • 從*.om文件中加載模型數據,由用戶自行管理內存
  • 從內存中加載模型數據,由AscendCL管理內存
  • 從內存中加載模型數據,由用戶自行管理內存

由用戶自行管理內存時,需關注工作內存、權值內存。工作內存用於存放模型執行過程中的臨時數據,權值內存用於存放權值數據。這個時候,是不是有疑問了,我怎麼知道工作內存、權值內存需要多大?不用擔心,AscendCL不僅提供了加載模型的接口,同時也提供了“根據模型文件獲取模型執行時所需的工作內存和權值內存大小”的接口,方便用戶使用 。

5.PNG

// 此處以僞代碼的形式展示接口的調用流程,以“由用戶管理內存”爲例

// 1.根據om模型文件獲取模型執行時所需的權值內存大小、工作內存大小。
aclError ret = aclmdlQuerySize(omModelPath, &modelWorkSize,
                      &modelWeightSize);
// 2.根據工作內存大小,申請Device上模型執行的工作內存。
ret = aclrtMalloc(&modelWorkPtr, modelWorkSize, 
          ACL_MEM_MALLOC_HUGE_FIRST);
// 3.根據權值內存的大小,申請Device上模型執行的權值內存。
ret = aclrtMalloc(&modelWeightPtr, modelWeightSize, 
          ACL_MEM_MALLOC_HUGE_FIRST);
// 4.以從om模型文件加載模型、由用戶管理工作內存和權值內存爲例
// 模型加載成功,返回標識模型的ID。
ret = aclmdlLoadFromFileWithMem(modelPath, &modelId, modelWorkPtr,  
                             modelWorkSize, modelWeightPtr, 
modelWeightSize);

07 模型執行

在調用AscendCL接口進行模型推理時,模型推理有輸入、輸出數據,輸入、輸出數據需要按照AscendCL規定的數據類型存放。相關數據類型如下:

  • 使用aclmdlDesc類型的數據描述模型基本信息(例如輸入/輸出的個數、名稱、數據類型、Format、維度信息等)。

模型加載成功後,用戶可根據模型的ID,調用該數據類型下的操作接口獲取該模型的描述信息,進而從模型的描述信息中獲取模型輸入/輸出的個數、內存大小、維度信息、Format、數據類型等信息。

  • 使用aclDataBuffer類型的數據來描述每個輸入/輸出的內存地址、內存大小。

調用aclDataBuffer類型下的操作接口獲取內存地址、內存大小等,便於向內存中存放輸入數據、獲取輸出數據。

  • 使用aclmdlDataset類型的數據描述模型的輸入/輸出數據。

模型可能存在多個輸入、多個輸出,調用aclmdlDataset類型的操作接口添加多個aclDataBuffer類型的數據。

6.PNG

// 此處以僞代碼的形式展示如何準備模型的輸入、輸出數據結構

// 1.根據加載成功的模型的ID,獲取該模型的描述信息
aclmdlDesc *modelDesc = aclmdlCreateDesc();
aclError ret = aclmdlGetDesc(modelDesc, modelId);

// 2.準備模型推理的輸入數據結構
// (1)申請輸入內存
// 當前示例代碼中的模型只有一個輸入,所以index爲0,如果模型有多個輸入,則需要先調用aclmdlGetNumInputs接口獲取模型輸入的數量
void *modelInputBuffer = nullptr;
size_t modelInputSize = aclmdlGetInputSizeByIndex(modelDesc, 0);
ret = aclrtMalloc(&modelInputBuffer, modelInputSize,                                              ACL_MEM_MALLOC_NORMAL_ONLY);
// (2)準備模型的輸入數據結構
// 創建aclmdlDataset類型的數據,描述模型推理的輸入
aclmdlDataset *input = aclmdlCreateDataset();
aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize);
ret = aclmdlAddDatasetBuffer(input, inputData);

// 3.準備模型推理的輸出數據結構
// (1)創建aclmdlDataset類型的數據output,描述模型推理的輸出
aclmdlDataset *output = aclmdlCreateDataset();
// (2)獲取模型的輸出個數.
size_t outputSize = aclmdlGetNumOutputs(modelDesc);
// (3)循環爲每個輸出申請內存,並將每個輸出添加到aclmdlDataset類型的數據中
for (size_t i = 0; i < outputSize; ++i) {
size_t buffer_size = aclmdlGetOutputSizeByIndex(modelDesc, i);
void *outputBuffer = nullptr;
 ret = aclrtMalloc(&outputBuffer, buffer_size, 
              ACL_MEM_MALLOC_NORMAL_ONLY);
aclDataBuffer *outputData = aclCreateDataBuffer(outputBuffer, buffer_size);   
ret = aclmdlAddDatasetBuffer(output, outputData);
}

準備好模型執行所需的輸入和輸出數據類型、且存放好模型執行的輸入數據後,可以執行模型推理了,如果模型的輸入涉及動態Batch、動態分辨率等特性,則在模型執行前,還需要調用AscendCL接口告訴模型本次執行時需要用的Batch數、分辨率等。

當前AscendCL支持同步模型執行、異步模型執行兩種方式,這裏說的同步、異步是站在調用者和執行者的角度。

  • 若調用模型執行的接口後需等待推理完成再返回,則表示模型執行是同步的。當用戶調用同步模型執行接口後,可直接從該接口的輸出參數中獲取模型執行的結果數據,如果需要推理的輸入數據量很大,同步模型執行時,需要等所有數據都處理完成後,才能獲取推理的結果數據。
  • 若調用模型執行的接口後不等待推理完成完成再返回,則表示模型執行是異步的。當用戶調用異步模型執行接口時,需指定Stream(Stream用於維護一些異步操作的執行順序,確保按照應用程序中的代碼調用順序在Device上執行),另外,還需調用aclrtSynchronizeStream接口阻塞程序運行,直到指定Stream中的所有任務都完成,纔可以獲取推理的結果數據。如果需要推理的輸入數據量很大,異步模型執行時,AscendCL提供了Callback機制,觸發回調函數,在指定時間內一旦有推理的結果數據,就獲取出來,達到分批獲取推理結果數據的目的,提高效率。
// 此處以僞代碼的形式展示同步模型執行的過程

// 1. 由用戶自行編碼,將模型所需的輸入數據讀入內存
// 如果模型推理之前先進行媒體數據處理,則此處可以將媒體數據處理後的輸出內容作爲模型推理的輸入內存,
// ......

// 2. 執行模型推理
// modelId表示模型ID,在模型加載成功後,會返回標識模型的ID
// input、output分別表示模型推理的輸入、輸出數據,在準備模型推理的輸入、輸出數據結構時已定義
aclError ret = aclmdlExecute(modelId, input, output)
        
// 3. 處理模型推理的輸出數據
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output); ++i) {
//獲取每個輸出的內存地址和內存大小
aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output, i);
void* data = aclGetDataBufferAddr(dataBuffer);
size_t len = aclGetDataBufferSizeV2(dataBuffer);
//獲取到輸出數據後,由用戶自行編碼,處理輸出數據
//......
}

// 4.銷燬模型輸入、輸出數據結構
// 釋放輸入資源,包括數據結構和內存
(void)aclDestroyDataBuffer(dataBuffer);
(void)aclmdlDestroyDataset(mdlDataset);

// 5.釋放內存資源,防止內存泄露
// ......

推理結束後,如果需要獲取並進一步處理推理結果數據,則由用戶自行編碼實現。最後,別忘了,我們還要銷燬aclmdlDataset、aclDataBuffer等數據類型,釋放相關內存,防止內存泄露。

08 模型卸載

在模型推理結束後,還需要通過aclmdlUnload接口卸載模型,並銷燬aclmdlDesc類型的模型描述信息、釋放模型運行的工作內存和權值內存。

// 此處以僞代碼的形式展示模型卸載的過程
// 1. 卸載模型
aclError ret = aclmdlUnload(modelId);

// 2. 釋放模型描述信息
(void)aclmdlDestroyDesc(modelDesc);

// 3. 釋放模型運行的工作內存和權值內存
(void)aclrtFree(modelWorkPtr);
(void)aclrtFree(modelWeightPtr);

09 更多介紹 

[1]香橙派AIpro開源樣例代碼:https://gitee.com/ascend/EdgeAndRobotics

[2]昇騰文檔中心:https://www.hiascend.com/zh/document

[3]香橙派AIpro學習資源一站式導航:https://www.hiascend.com/forum/thread-0285140173361311056-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章