[深度學習大講堂]從NNVM看2016年深度學習框架發展趨勢

本文爲微信公衆號[深度學習大講堂]特約稿,轉載請註明出處

虛擬框架殺入

從發現問題到解決問題

半年前的這時候,暑假,我在SIAT MMLAB實習。前端

看着同事一下子跑Torch,一下子跑MXNet,一下子跑Theano。算法

SIAT的服務器通常是不給sudo權限的,我看着同事掙扎在編譯這一坨框架的海洋中,開始思考:編程

是否能夠寫一個框架:後端

import xx.tensorflow as tensorflow
import xx.mxnet as mxnet
import xx.theano as theano
import xx.caffe as caffe

這樣,利用工廠模式只編譯執行部件的作法,只需編譯惟一的後端便可,框架的不一樣僅僅在於前端腳本的不一樣。設計模式

Caffe2Keras的作法彷佛是這樣,但Keras自己是基於Theano的編譯後端,而咱們的更但願Theano都不用編譯。服務器

當我9月份拍出一個能跑cifar10的大概原型的時候:微信

import dragon.vm.caffe as caffe
import dragon.vm.theano as theano
import dragon.updaters as updaters

if __name__ == '__main__':

    net = caffe.Net('cifar10.prototxt')
    loss = net.blobs['loss'].data
    updater = updaters.SGDUpdater(base_lr=0.001, momentum=0.9, l2_decay=0.004)
    for k,v in net.params().iteritems():
        for blob in v:
           updater.append(v, theano.grad(loss, v))
    train = theano.function(outputs=loss)
    update = theano.function(updater=updater)
    max_iters = 2333
    iter = 0
    while iter < max_iters:
        train()
        update()

我爲這種怪異的寫法取名叫CGVM(Computational Graph Virtual Machine)網絡

而後過了幾天,在微博上看到了陳天奇在MXNet的進一步工做NNVM的發佈 (o(╯□╰)o)......數據結構

NNVM使用2000行模擬出了TensorFlow,我大概用了500行模擬出了Caffe1。app

VM(Virtual Machine)的想法實際上是一個很正常的想法,這幾年咱們搞了不少新框架,

名字一個比一個炫,可是本質都差很少,框架的使用者其實是苦不堪言的:

○ 這篇paper使用了A框架,我要花1天配置A框架。

○ 這篇paper使用了B框架,我要花1天配置B框架。

.......

正如LLVM不是一種編譯器,NNVM也不是一種框架,看起來更像是框架的屠殺者。

NNVM的可行性偏偏證實了現行的各大框架底層的重複性,而上層的多樣性只是一個幌子。

咱們真的須要爲僅僅是函數封裝不一樣的框架買單嗎?這是值得思考的。

計算圖走向成熟

計算圖的兩種形式

計算圖最先的出處應該是追溯到Bengio在09年的《Learning Deep Architectures for AI》,

Bengio使用了有向圖結構來描述神經網絡的計算:

如圖,符號集合{*,+,sin} 構成圖的結點,整張圖可當作三部分:輸入結點、輸出結點、從輸入到輸出的計算函數。

隨後在Bengio組的Theano框架執行中,Graph就被隱式應用於Op的鏈接。

不過這時候,Op仍是執行時-動態編譯的。

 

Caffe1中計算圖其實就是Net,由於Net能夠被Graph模擬出來(CGVM和Caffe2Keras都實現了)。

賈揚清在Caffe1中顯式化了計算圖的表示,用戶能夠經過編輯net.prototxt來設計計算圖。

Caffe1在Jonathan LongEvan Shelhamer接手後,他們開發了PyCaffe

PyCaffe經過Python自然的工廠(__getattr__),實現了net.prototxt的隱式生成。

以後的Caffe2,也就直接取消了net.prototxt的編輯,一樣利用Python的(__getattr__)獲取符號類型定義。

Caffe1帶來一種新的計算圖組織Op的描述方式,不一樣於Theano直接翻譯Op爲C執行代碼,而後動態編譯,

軟件工程中的高級設計模式——工廠模式被普遍使用。

計算圖被劃分爲三個階段,定義階段、構造階段、執行階段:

一、定義階段:定義Layer/Op的name、type、bottom(input),top(output)及預設參數。

二、構造階段:經過工廠模式,由字符串化的定義腳本構造類對象。

三、執行階段:根據傳入的bottom(input),獲得額外參數(如shape),此時計算圖才能開始執行。

階段劃分帶來的主要問題是限制了編譯代碼的完整性和優化程度。

在Theano中,C代碼生成是最後一步,你能夠組合數片細粒度的代碼塊,依靠編譯器作硬件執行前的超級優化。

編譯器優化是的提高指令流水線效率的重要手段,編譯器調度技術減小數據衝突,編譯器分支預測技術減小控制衝突。

而工廠模式編譯符號時只考慮了單元自己,編譯器沒有上下文環境可供參考,故最終只能順序執行多個預先編譯的符號單元。

當符號粒度過細時,一個Layer的實現就會變成連續執行數個單元,每一個單元都要處理一遍向量/矩陣,致使「TensorFlowSlow」。

計算圖做爲中間表示(IR)

PyCaffe和Caffe2將定義階段移到Python中,而將構造和執行階段保留在C++中作法,是計算圖做爲IR的思想啓蒙。

Python與C++最大的不一樣在於:一個是腳本代碼,用於前端。一個是本地代碼,用於後端。

腳本代碼建立/修改模型方便(無需因模型變更而從新編譯)、執行慢,本地代碼則正好相反。

二者取長補短,因此深度學習框架在2016年,迎來了先後端開發的黃金時代。

如上圖,不管是9月份先提出的NNVM,仍是最近Intel曝光的Nervana,都分離了先後端。

後端的獨立,不只減小了編譯工做,最大的優點在於下降了傳統框架作跨設備計算的代碼耦合度。

在paper每週都有一大堆的如今,若是後端的每一次變更都要大量修改前端,那麼框架的維護開銷是很是大的。

 

在前端定義用於描述輸入-輸出關係的計算圖有着良好的交互性,咱們能夠經過函數和重載腳本語言的操做符,

定義出媲美MATLAB的運算語言,這些語言以顯式的Tensor做爲數據結構,Operator做爲計算符和函數,

Theano和MXNet都是這樣隱蔽處理由表達式向計算圖過渡的。

而Caffe2則比較直接,你須要先建立一個Graph,而後顯示地調用Graph.AddOperator(xxx)

TensorFlow一樣能夠顯式化處理Graph。

 

與用戶交互獲得的計算圖描述字串是惟一的,可是與用戶交互的方式倒是不惟一的。

因此IR之上,分爲兩派:

第一派要搞本身的API,函數封裝很是有個性,宣示這是本身的專利、獨門語言。

第二派不搞本身的API,反而去模擬現有的API,表示我很低調。

顯然,用戶更喜歡用本身熟悉框架的寫法去描述模型,不喜歡每天揹着個函數速查手冊。

計算圖優化

用於中間表示獲得的計算圖描述最好不要直接構造,由於存在冗餘的求解目標,且可共享變量還沒有提取。

當限制計算圖描述爲有向無環圖(DAG)時,一些基本的圖論算法即可應用於計算圖描述的化簡與變換。

陳天奇在今年的MSR Talk:Programming Models and Systems Design for Deep Learning中,總結了計算圖優化的三個點:



①依賴性剪枝

分爲前向傳播剪枝,例:已知A+B=X,A+B=Y,求X?

      反向傳播剪枝,  例:A+B=X,A+B=Y,求X、Y,dX/dA?

根據用戶的求解需求,能夠剪掉沒有求解的圖分支。

②符號融合

符號融合的自動實現是困難的,由於Kernel基本再也不實時編譯了,因此更多體如今符號粗細粒度的設計上。

粗粒度的符號融合了數個細粒度的符號,一次編譯出連續多個執行步驟的高效率代碼。

粗粒度和細粒度並沒有好壞區分,一個速度快,一個更靈活。

從貪心角度,VM框架一般會提供粗細粒度兩種實現給用戶,於是須要更多人力維護編譯後端。

③內存共享

Caffe1對於激活函數大多使用的inplace處理——即bottom和top是同一個Blob。

inplace使用新的輸出y當即覆蓋的輸入x,須要如下兩個條件:

  一、bottom和top數量都爲1,即:計算圖中構成一條直線路徑,

  二、d(y)/d(x)與x是無關的,因此x被y覆蓋不影響求導結果。

常見的激活函數都符號以上兩個條件,於是能夠減小內存的開銷。

可是Caffe1在多網絡內存共享優化上極其糟糕的,以致於Caffe1並不適合用來跑GAN,以及更復雜的網絡。

一個簡單例子是交叉驗證上的優化:訓練網絡和驗證網絡的大部分Layer都是能夠共享的,

可是因爲Caffe1錯誤地將Blob獨立的放在每一個Net裏,使得跨Net間很難共享數據。

除此以外,Caffe1還錯誤地將臨時變量Blob獨立放在每一個Layer裏,致使列卷積重複佔用幾個G內存。

讓Net和Layer都能共享內存,只須要將Tensor/Blob置於最頂層,採用MVC來寫框架便可。

Caffe2引入了Workspace來管理Tensor,並將工做空間的指針傳給每個Op、每個Graph的構造函數。

這將使內存區域徹底暴露在全局(相似MFC的Document),與TensorFlow同樣,提供Feed/Fetch這一組API用於Python的外部訪問。

這種內存的管理方式,同時也爲模擬Theano的API提供便利(e.g. theano.shared和get_value,本質就是Feed與Fetch)。

使用Workspace優化顯存,可以使Caffe1作列卷積僅比CUDNN多300M(VGG16全鏈接) / 900M(VGG16全卷積),且時間開銷近似爲零。

遺憾的是,Caffe1臃腫、錯誤的代碼結構彷佛是無緣Workspace的引入了(這將面臨大面積的代碼重寫,後果就是社區爆炸)。

P.S: 賈揚清在知乎還吐槽過Caffe1中大面積錯誤的模板寫法,致使Caffe1彷佛也是無緣FP16了..(你們趕忙研究Caffe2吧)

面向計算圖的直接調試

不少用戶抱怨TensorFlow調試困難,不像Caffe1那樣更容易命中Bug的要害。

Caffe1的調試簡單,源於Layer/Op的執行段很容易定位,Debug信息能夠有效的輸出。

而TensorFlow在計算圖之上,爲了迎合工業界搞了許多莫名其妙的API。

面向計算圖的調試技術宗旨就是,能夠實時輸出模型執行計算圖的文本描述。

對於一個符號單元而言:

①明確它的輸入輸出是什麼Tensor,附加的靜態參數是什麼。

②它的符號名是什麼,是什麼符號類型,若是懷疑錯了,直接if(name=xxx) {.....} 便可針對性調試。

對於幾個符號組成的局部圖單元:

只須要各個符號間輸入輸出的拓撲鏈接關係,這個和看net.prototxt沒什麼區別。

以上兩種規格的單元調試,最好可以跳過API,直接暴露給用戶。

只要符號單元的實現正確、計算圖的拓撲鏈接及傳入參數正確,那麼模型的執行結果理論上是不會錯的。

新的風暴已經出現

VM的側重點

CGVM和NNVM的側重點是不太同樣的,CGVM更強調前端上的擴展化,後端上的惟一化。

因此CGVM不會去支持Torch編譯後端,也不會去支持Caffe編譯後端。

在NNVM的知乎討論帖中,有一種觀點認爲VM是輕視Operator的實現。

但實際上,咱們手裏的一堆框架,在Operator、Kernel、Math級別的很多實現是沒有多少區別的。

但偏偏折磨用戶的正是這些沒有多少區別的編譯後端:各類依賴庫、裝Linux、編譯各類錯。

因此我我的更傾向整個DL社區可以提供一份完善的跨平臺、跨設備解決方案,而不是多而雜的備選方案。

從這點來看,CGVM彷佛是一個更完全的框架殺手,但在ICML'15上, Jürgen Schmidhuber指出:

真正運行AI 的代碼是很是簡短的,甚至高中生都能玩轉它。不用有任何擔憂會有行業壟斷AI及其研究。

 

簡短的AI代碼,未必就是簡單的框架提供的,有多是本身熟悉的框架,這種需求體如今前端而不是後端。

VM指出了一條多框架混合思路:功能A,框架X寫簡單。功能B,框架Y寫簡單。

功能A和功能B又要end-to-end,那麼顯然混起來用不就好了。

只有使用頻率不高的框架纔會消亡,VM將框架混合使用後,熟悉的味道更濃了,那麼便構不成」框架屠殺者「。

強大的AI代碼,未必就是VM提供的,有多是龐大的後端提供的。

隨着paper的快速迭代,後端的擴展仍然是最繁重的編程任務。

VM和後端側重點各有不一樣,難分好壞。但分離二者的作法確實是成功的一步。

VM的形式

VM及計算圖描述方式是鏈接先後端的橋樑。

即使後端是惟一的,根據支持前端的不一樣,各家寫的VM也很難統一。

實際上這就把框架之間的鬥爭引向了VM之間的鬥爭。

兩人見面談笑風生,與其問對方用什麼框架,不如問對方用什麼VM。

VM的主要工做

合成計算圖描述的過程是乏味的,在Caffe1中,咱們恐怕已經受夠了人工編輯prototxt。

API交互方面,即使是MXNet提供給用戶的API也是複雜臃腫的,或許仍然須要一個handbook。

TensorFlow中的TensorBoard借鑑了WebOS,VM上搞一個交互性更強的操做系統也是可行的。

除此以外,我可能比較熟悉一些經典框架,那麼不妨讓VM去實現那些耳熟能詳的函數吧!

一、模擬theano.function

Theano的function是一個很是貼近數學表達計算圖掩飾工具。

function內部轉化表達式爲計算圖定義,同時返回一個lambda函數引向計算圖的執行。

總之這是一個百看不膩的API。

二、模擬theano.tensor.grad

結合計算圖優化,咱們如今能夠指定任意一對求導二元組(cost, wrt)。

於是,放開手,讓自動求導在你的模型中飛舞吧。

三、模擬theano.scan

theano.scan是一個用來搭建RNN的神器。儘管最近Caffe1更新了RNN,可是隻支持固定循環步數的RNN。

而theano.scan則能夠根據Tensor的shape,爲RNN建動態的計算圖,這適合在NLP任務中處理不定長句子。

四、模擬pyCaffe

pyCaffe近來在RCNN、FCN、DeepDream中獲得普遍應用,成爲搞CV小夥伴們的最愛。

pyCaffe大部分是由C++數據結構經過Boost.Python導出的,

不幸的是,Boost.Thread導出以後與Python的GIL衝突,致使PyCaffe裏沒法執行C++線程。

嘗試模擬移除Boost.Python後的PyCaffe,在Python裏把Solver、Net、Layer給寫出來吧。

五、模擬你熟悉的任意框架

.......等等,怎麼感受在寫模擬器.....

固然寫模擬器基本就是在重複造輪子,這個在NNVM的知乎討論帖中已經指明瞭。

VM的重要性

VM是深度學習框架去中心化、解耦化發展邁出的重要一步。

同時暴露了目前框架圈混亂的本質:計算圖之下,衆平生等。計算圖之上,羣魔亂舞。

在今年咱們能夠看多許多框架PK對比的文章,然而大多隻是從用戶觀點出發的簡單評測。

對比之下,NNVM關注度不高、反對者還很多這種狀況,確實讓人感到意外。

回顧與展望

回顧2016:框架圈減肥大做戰的開始

高調宣佈開源XXX框架,再封裝一些API,實際上已經多餘了。

VM的出現,將上層接口的編寫引向模擬經典的框架,從而達到減肥的目的。

框架維護者應當將大部分精力主要放在Kernel的編寫上,而不是考慮搞一些大新聞。

展望2017:DL社區可否聯合開源出跨平臺、跨設備的後端解決方案

後端上,隨着ARM、神經芯片的引入,咱們迫切須要緊跟着硬件來完成繁重的編程。

後端是一個敏感詞,由於硬件能夠拿來賣錢,因此更傾向於閉源。

除此以外,即使出現了開源的後端,在山寨和混戰以前是否能普及也是一個問題。

展望2017:來寫框架吧

VM的出現,帶來另外一個值得思考的問題:如今是否是人人應該學寫框架了?

傳統框架編寫的困難在代碼耦合度高,學習成本昂貴。

VM流框架分離了先後端以後,前端編寫難度很低,後端的則相對固定。

這樣一來,框架的編程層次更加分明,Keras地位彷佛要危險了。

展望2017:更快迭代的框架,更多變的風格,更難的壟斷地位

相比於paper的迭代,框架的迭代彷佛更快了一點。

餘凱老師前段時間發出了TensorFlow壟斷的擔心,但咱們能夠很樂觀地看到:愈來愈多的用戶,在深刻框架的底層。

TensorFlow並非最好的框架,MXNet也不是,最好的框架是本身用的舒服的框架,最好是一行行本身敲出來的。

若是你已經積累的數個框架的使用經驗,是時候把它們無縫銜接在一塊兒了。

相關文章
相關標籤/搜索