半年前的這時候,暑假,我在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 Long和Evan 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」。
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,直接暴露給用戶。
只要符號單元的實現正確、計算圖的拓撲鏈接及傳入參數正確,那麼模型的執行結果理論上是不會錯的。
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。
合成計算圖描述的過程是乏味的,在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是深度學習框架去中心化、解耦化發展邁出的重要一步。
同時暴露了目前框架圈混亂的本質:計算圖之下,衆平生等。計算圖之上,羣魔亂舞。
在今年咱們能夠看多許多框架PK對比的文章,然而大多隻是從用戶觀點出發的簡單評測。
對比之下,NNVM關注度不高、反對者還很多這種狀況,確實讓人感到意外。
高調宣佈開源XXX框架,再封裝一些API,實際上已經多餘了。
VM的出現,將上層接口的編寫引向模擬經典的框架,從而達到減肥的目的。
框架維護者應當將大部分精力主要放在Kernel的編寫上,而不是考慮搞一些大新聞。
後端上,隨着ARM、神經芯片的引入,咱們迫切須要緊跟着硬件來完成繁重的編程。
後端是一個敏感詞,由於硬件能夠拿來賣錢,因此更傾向於閉源。
除此以外,即使出現了開源的後端,在山寨和混戰以前是否能普及也是一個問題。
VM的出現,帶來另外一個值得思考的問題:如今是否是人人應該學寫框架了?
傳統框架編寫的困難在代碼耦合度高,學習成本昂貴。
VM流框架分離了先後端以後,前端編寫難度很低,後端的則相對固定。
這樣一來,框架的編程層次更加分明,Keras地位彷佛要危險了。
相比於paper的迭代,框架的迭代彷佛更快了一點。
餘凱老師前段時間發出了TensorFlow壟斷的擔心,但咱們能夠很樂觀地看到:愈來愈多的用戶,在深刻框架的底層。
TensorFlow並非最好的框架,MXNet也不是,最好的框架是本身用的舒服的框架,最好是一行行本身敲出來的。
若是你已經積累的數個框架的使用經驗,是時候把它們無縫銜接在一塊兒了。