1. 程式人生 > >小米開源框架MACE 原始碼閱讀筆記 1

小米開源框架MACE 原始碼閱讀筆記 1

前扯

在前不久的某高峰論壇上,小米開源了其移動端的深度學習框架Mobile AI Compute Engine(MACE)。這對於很多致力於嵌入式端優化的人來說,無疑是巨大的驚喜(新坑出現,在 NCNNTVMTensorLite 的坑裡不斷徘徊的人表示淚目...)。要掌握並使用一個框架,不單單能夠跑通 demo,還需要對其原始碼有充分的認識,知其優而優。筆者開啟此帖(坑),希望能夠分享自己學習原始碼的過程,水平及理解有限,歡迎交流(點贊)和指正(讚賞)。

另外,Github上的程式碼會一直處於更新中,後續會盡量緊跟更新。

開篇

首先看一下從Github上拉下來的程式碼結構:

下邊的幾個檔案是一些版本釋出與介紹性檔案,與程式碼的相關性不大,簡單看下 README_zh.md

 裡邊說的:

Mobile AI Compute Engine (MACE) 是一個專為移動端異構計算平臺優化的神經網路計算框架。主要從以下的角度做了專門的優化:

  • 效能
    程式碼經過NEON指令,OpenCL以及Hexagon HVX專門優化,並且採用 Winograd演算法 來進行卷積操作的加速。此外,還對啟動速度進行了專門的優化。

  • 功耗
    支援晶片的功耗管理,例如ARM的big.LITTLE排程,以及高通Adreno GPU功耗選項。

  • 系統響應
    支援自動拆解長時間的OpenCL計算任務,來保證UI渲染任務能夠做到較好的搶佔排程,從而保證系統UI的相應和使用者體驗。

  • 記憶體佔用
    通過運用記憶體依賴分析技術,以及記憶體複用,減少記憶體的佔用。另外,保持儘量少的外部依賴,保證程式碼尺寸精簡。

  • 模型加密與保護
    模型保護是重要設計目標之一。支援將模型轉換成C++程式碼,以及關鍵常量字元混淆,增加逆向的難度。

  • 硬體支援範圍
    支援高通,聯發科,以及松果等系列晶片的CPU,GPU與DSP(目前僅支援Hexagon)計算加速。同時支援在具有POSIX介面的系統的CPU上執行。

看其介紹,MACE團隊應該是傾注了不少心血,希望能夠不斷完善下去。

回到程式碼目錄上來。docker資料夾是幾個與docker安裝相關的檔案,不用過多關注。docs資料夾是一些文件,與程式碼關係不大。third_partytools資料夾是需要用到的一些第三方模組和程式碼構建、測試所用到的相關指令碼。其中tools/converter.py

是構建程式碼的頂層指令碼,有興趣的可以詳細看一下,之前的文章裡也有提到 <小米開源框架MACE> 如何構建和使用

核心的程式碼在mace資料夾下。同樣的,先看一下mace資料夾下的結構:

仍然是聚焦一些與框架程式碼相關的資料夾。此處預設讀者已事先學習過mace\examples\cli\examples.cc,如沒有建議先過一遍,對如何在專案裡使用 mace 有個瞭解。也可以參考 <小米開源框架MACE> 如何構建和使用 一文的最後內容。按照 examples.cc 裡的引用,先來看 public 資料夾裡的兩個標頭檔案:mace.hmace_runtime.h

mace\public\mace.h 剖析

mace.h標頭檔案定義了mace框架的幾個核心API,包括 CallStatsConvPoolArgsOperatorStats 三個結構體以及 DeviceType 和 MaceStatus 兩個列舉型別。

enum DeviceType { CPU = 0, GPU = 2, HEXAGON = 3 }; //框架支援的裝置型別及其對應ID.
enum MaceStatus {
  MACE_SUCCESS = 0,
  MACE_INVALID_ARGS = 1,
  MACE_OUT_OF_RESOURCES = 2
}; // 函式的返回型別

mace.h標頭檔案中還定義了三個類:RunMetadataMaceTensor 和 MaceEngine

RunMetadata:定義了一個public型別的變數 op_stats,型別為vector <OperatorStats>
MaceTensor:定義了 MACE 輸入輸出 tensor,有幾個不同形式的建構函式
MaceEngine:作為 MACE 框架引擎,根據建構函式傳入的DeviceType執行相應的功能程式碼

MaceTensor 和 MaceEngine兩個類中都分別定義了一個巢狀類Impl,這個巢狀類才是真正實現具體功能的類。

mace\core\mace.cc剖析

對應於mace\public\mace.h標頭檔案的實現檔案為mace\core\mace.cc
作為深入原始碼的第一步,下面我們一起看一下這個檔案。

首先是LoadModelData 和 UnloadModelData兩個與模型資料匯入和清空的函式。

接下來是MaceTensor中的巢狀類Impl的定義:

class MaceTensor::Impl {
 public:
  std::vector<int64_t> shape;
  std::shared_ptr<float> data;
};

然後是MaceTensor的建構函式實現。MaceTensor的建構函式主要實現Impl類中shapedata兩個變數的初始化工作,以下貼出其中一種形式的建構函式,更多程式碼請查閱mace\core\mace.cc

MaceTensor::MaceTensor(const std::vector<int64_t> &shape,
                       std::shared_ptr<float> data) {
  MACE_CHECK_NOTNULL(data.get());
  impl_ = std::unique_ptr<MaceTensor::Impl>(new MaceTensor::Impl());
  impl_->shape = shape;
  impl_->data = data;
}

MaceTensor 類中還有三個成員函式,用於獲取impl的變數值。

const std::vector<int64_t> &MaceTensor::shape() const { return impl_->shape; }

const std::shared_ptr<float> MaceTensor::data() const { return impl_->data; }

std::shared_ptr<float> MaceTensor::data() { return impl_->data; }

接下來是MaceEngine中的巢狀類Impl的定義,其中涉及到的WorkspaceNetBase等類,暫時不需要過多關注,只需要知道這個內嵌類實現了MACE引擎的建立、初始化和執行。下邊是Impl類的主體程式碼:

class MaceEngine::Impl {
 public:
  explicit Impl(DeviceType device_type);

  ~Impl();

  MaceStatus Init(const NetDef *net_def,
                  const std::vector<std::string> &input_nodes,
                  const std::vector<std::string> &output_nodes,
                  const unsigned char *model_data);

  MaceStatus Init(const NetDef *net_def,
                  const std::vector<std::string> &input_nodes,
                  const std::vector<std::string> &output_nodes,
                  const std::string &model_data_file);

  MaceStatus Run(const std::map<std::string, MaceTensor> &inputs,
                 std::map<std::string, MaceTensor> *outputs,
                 RunMetadata *run_metadata);

 private:
  const unsigned char *model_data_;
  size_t model_data_size_;
  std::shared_ptr<OperatorRegistry> op_registry_;
  DeviceType device_type_;
  std::unique_ptr<Workspace> ws_;
  std::unique_ptr<NetBase> net_;
  std::map<std::string, mace::InputInfo> input_info_map_;
  std::map<std::string, mace::OutputInfo> output_info_map_;
#ifdef MACE_ENABLE_HEXAGON
  std::unique_ptr<HexagonControlWrapper> hexagon_controller_;
#endif

  MACE_DISABLE_COPY_AND_ASSIGN(Impl);
};

Impl 類中有ImplInitRun三個函式(Init有一個過載函式)。

  • Impl函式
    Impl 函式是建構函式,完成一些變數的初始化工作。與 Tensor 相關的一些操作會在 Workspace 下,因而此處建立了一個 Workspace物件。

  • Init函式
    Init 函式完成 MaceEngine 的初始化工作。包括根據網路定義建立輸入輸出的儲存 map,根據輸入輸出節點和 deviceType 建立Tensor,初始化整個網路模型。Init還有一個過載函式,區別只是const unsigned char *model_data 和 const std::string &model_data_file 這兩個引數。後者通過 LoadModelData 載入模型資料後呼叫前者完成初始化。

  • Run函式
    Run 函式根據輸入輸出檔案資訊,初始化的模型資訊和模型資料,以及 device_type_型別呼叫相應的執行程式碼。完成模型的前向運算。輸出運算結果並儲存。

MaceEngine 類本身還有InitRun 函式,都是通過呼叫 Impl 類的相應函式實現的。

mace\core\mace.cc 中還有一個 CreateMaceEngineFromProto 函式。這個函式和 mace/codegen/engine/mace_engine_factory.h 中 CreateMaceEngineFromCode 函式是對應的。兩者都是通過呼叫MaceEngine 的 Init 函式(也即呼叫 Impl 的Init函式) 實現初始化過程。 區別在於,前者是通過模型proto定義檔案進行初始化,後者是將模型編為程式碼(在模型部署檔案 .yaml 中設定CODE_TYPE 為 code)。mace/codegen/engine/mace_engine_factory.h 這個檔案是程式碼構建之後產生的。

以上,對 mace\public\mace.h 和 mace\core\mace.cc 進行了分解,並分析了 mace 框架頂層的幾個API,也是框架的基礎。

後續將陸續擴充套件到框架中核心程式碼的實現。

敬請拍磚(輕拍o( ̄▽ ̄)d )。

版權所有,歡迎轉載,轉載請註明出處