寫在前面
我會盡可能地將一些關鍵概念進行描述和解釋,但基于 深度學習 和 程序設計 的天坑,固然無法讓一個完全沒有相關概念基礎的人完全跟上文章的節(jié)奏。對此請看不懂的各位多多見諒咯,畢竟這不是一片基礎教程(當然,有問題可以在評論區(qū)提出,我會一一答復)。
參考閱讀適宜人群:
1 關注AI實現技術的人們
2 對數據結構癡迷的人們
3 MNN的使用者
1 MNN
我們知道,如今的AI主要 以深度學習神經網絡的方式 進行實踐。神經網絡模型的基本操作有 訓練(Train,即創(chuàng)造模型)和 推理(Inference,即使用模型)。神經網絡模型自有它的復雜性,所以,出現了 神經網絡框架 這樣讓我們可以 忽略模型細節(jié) 來 使用 訓練、推理 功能的工具。我們熟知的框架比如:Caffe,TensorFlow,Pytroch,Mxnet,Ncnn,當然,還有我們今天的主角之一, 阿里巴巴的深度神經網絡推理引擎 MNN。
我們會好奇 MNN模型文件(.mnn)的秘密,想知道它組織結構的秘密(這點對 進行模型轉換和模型調試 有至關重要的意義),即:
它是像一個.json文件一樣使用字符串結構化的存儲?
還是像ncnn的.bin文件一樣只是將二進制的數值直接無隙存儲?
調試MNN模型加載過程中我發(fā)現,.mnn文件的內容竟似乎是 莫名其妙地 從一個“不可讀” 的狀態(tài)變?yōu)榱艘粋€“可使用”的狀態(tài),即:
調試過程中我找不到字符串解析代碼
調試過程中我也找不到二進制數據映射代碼
我看到的只有:
const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *tensorName() const {
return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(VT_TENSORNAME);
}
--->
template<typename P> P GetPointer(voffset_t field) const {
return const_cast<Table *>(this)->GetPointer<P>(field);
}
--->
template<typename P> P GetPointer(voffset_t field) {
auto field_offset = GetOptionalFieldOffset(field);
auto p = data_ + field_offset;
return field_offset ? reinterpret_cast<P>(p + ReadScalar<uoffset_t>(p))
: nullptr;
}
--->
voffset_t GetOptionalFieldOffset(voffset_t field) const {
auto vtable = GetVTable();
auto vtsize = ReadScalar<voffset_t>(vtable);
return field < vtsize ? ReadScalar<voffset_t>(vtable + field) : 0;
}
呃……試問一下大家看到這些代碼的感受,應該不會很興奮吧……
2 FlatBuffers
其實,無法解讀MNN模型文件的秘密在于:MNN模型文件采用的存儲結構是 FlatBuffers,而FlatBuffer的特點之一即為“ Access to serialized data without parsing/unpacking”,即 沒有解析過程,沒有解包過程。
我們(某一批特定的“我們”)知道,數據結構有 基礎數據結構 和 結構化數據結構,那么,先在就優(yōu)先了解一下FlatBuffer中常用的基礎數據結構:
flatbuffers::String

該圖即 字符串“OK”在 內存中,又或 文件中 的 存儲字節(jié)排布(左邊低字節(jié),右邊高字節(jié))。
- 標黃的部分說明 字符串的長度,占用4個字節(jié)(圖示存儲模式為 小端模式)。
圖中表示字符串長度為2,即(2 = 0 * 256^3 + 0 * 256^2 + 0 * 256^1 + 2 * 256^0); - 緊跟著字符串長度的存儲空間即為 字符串的內容,圖中字符串長度為2,所以緊跟其后的2個字節(jié)為該字符串的有效值。
flatbuffers::Vector & flatbuffers::Offset
這兩個數據結構結合在一起使用會比較常見:如:flatbuffers::Vector<flatbuffers::Offset<Op>>
通俗地說,理解其為:一個指向特定數據結構的指針的數組。

類似flatbuffers::String的理解,4字節(jié)的Vector長度描述后,緊跟6個4字節(jié)的指針描述。
flatbuffers::Table
這個結構比較復雜,一片文章中的描述也比較清晰,我就不自己辛苦畫圖了。
文章:Improving Facebook’s performance on Android with FlatBuffers

簡單來說,一個Table結構 被分為左右兩部分(如圖中黃色部分pivot point for John即為分界線),左邊表示數據信息的偏移,右邊表示數據信息。我們把圖中黃色位置計做0,則:
- 圖中最左側的 1 指向黃色位置右邊 第一個矩形;
- 圖中左側的 6 指向黃色位置右邊 第6個矩形;
- 每個矩形我們依然理解為 1字節(jié)(8位) 的數據存儲空間
3 MNN 中描述模型的數據結構
有了上面的基礎,我們會好奇flatbuffers的存儲內容與數據結構的 具體舉例 或者 應用場景。所以,我們來初步了解一下MNN的底層模型數據結構。
3.1 MNN 底層模型數據結構全家福

從MNN的源碼中把這些內容扒出來然后繪圖貌似也沒有讓它能那么得容易閱讀。哈哈,那就圈一些重點吧(當然圖中的信息比重點多好多喲):
1 圖中中部靠下的Op部分衍生了近100個操作(或說神經網絡層)的flatbuffers::Table結構的定義,這邊我只象征性地列舉了常見的4個;
2 FlatBuffer數據結構是和存儲對應的,所以我們可以認為MNN的設計者們期望過將整張圖的內容放置到一個MNN模型文件中(至少他們預留了這樣的擴展性);
3 當然,現階段的大多MNN模型文件并沒有包含上述全部的信息,詳情見下圖。
3.2 MNN 底層模型數據結構關鍵成員

這張圖就簡單了很多,這是我經過對一些真實MNN模型的調試,篩出來的模型關鍵信息相關的數據結構。說白了:
1 網絡結構信息:Op與Op的排布順序
2 網絡操作的參數信息(權重等):存儲在具體的Op衍生數據結構中(最右邊的部分)
4 預告
這篇文章真的相當干燥!是否很期待一些更實踐一些的內容呢?
后續(xù)我會再擬草一片文章以舉例的方式來詳細地分析 MNN模型文件的存儲結構的關系(這篇文章即作為一片技術基礎參照),我會拿出證據告訴大家,MNN真的是遵照 “這種規(guī)則” 來設計和運作的。所以我們知道了規(guī)則,完全可以按照你的思想去 修改和調試 MNN源碼 和 MNN模型。
想把這 有些復雜并夾雜著大量基礎知識 的 知識實踐 用 短篇幅 說清楚真的不是一件容易的事情啊。(還是講課做分享更輕松哈哈,畢竟有即時的聽眾反饋提問)
先看預告張草圖吧,證明我的每篇文章都是很用心地希望創(chuàng)造一些幫助哦~:)
