小米開源框架MACE 原始碼閱讀筆記 1
前扯
在前不久的某高峰論壇上,小米開源了其移動端的深度學習框架Mobile AI Compute Engine(MACE)
。這對於很多致力於嵌入式端優化的人來說,無疑是巨大的驚喜(新坑出現,在 NCNN
、TVM
、TensorLite
的坑裡不斷徘徊的人表示淚目...)。要掌握並使用一個框架,不單單能夠跑通 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_party
和tools
資料夾是需要用到的一些第三方模組和程式碼構建、測試所用到的相關指令碼。其中tools/converter.py
核心的程式碼在mace
資料夾下。同樣的,先看一下mace
資料夾下的結構:
仍然是聚焦一些與框架程式碼相關的資料夾。此處預設讀者已事先學習過mace\examples\cli\examples.cc
,如沒有建議先過一遍,對如何在專案裡使用 mace
有個瞭解。也可以參考 <小米開源框架MACE> 如何構建和使用 一文的最後內容。按照 examples.cc
裡的引用,先來看 public
資料夾裡的兩個標頭檔案:mace.h
和mace_runtime.h
。
mace\public\mace.h 剖析
mace.h
標頭檔案定義了mace框架的幾個核心API,包括 CallStats
、ConvPoolArgs
、OperatorStats
三個結構體以及 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
標頭檔案中還定義了三個類:RunMetadata
、MaceTensor
和 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
類中shape
和data
兩個變數的初始化工作,以下貼出其中一種形式的建構函式,更多程式碼請查閱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
的定義,其中涉及到的Workspace
、NetBase
等類,暫時不需要過多關注,只需要知道這個內嵌類實現了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
類中有Impl
、Init
、Run
三個函式(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
類本身還有Init
、Run
函式,都是通過呼叫 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 )。
版權所有,歡迎轉載,轉載請註明出處