轉自:http://blog.csdn.net/stdcoutzyx/article/details/51645396
本片博文是參考文獻[1]的閱讀筆記,特此聲明
TensorFlow,以下簡稱TF,是Google去年發布的機器學習平台,發布以后由於其速度快,擴展性好,推廣速度還是蠻快的。江湖上流傳着Google的大戰略,Android占領了移動端,TF占領神經網絡提供AI服務,未來的趨勢恰好是語音圖像以及AI的時代,而Google IO上發布的Gbot似乎正是這一交叉領域的初步嘗試。
TF的特點之一就是可以支持很多種設備,大到GPU、CPU,小到手機平板,五花八門的設備都可以跑起來TF。不得不說這一點很有前瞻性,可以預見的是,mobile-end的用戶將會享受到越來越多的AI服務。說個極端,說不定以后某天,單機版的AlphaGo會出現也是可以的。
話不多說,開始正文。
Basic Concepts
張量(Tensor)
名字就是TensorFlow,直觀來看,就是張量的流動。張量(tensor),即任意維度的數據,一維、二維、三維、四維等數據統稱為張量。而張量的流動則是指保持計算節點不變,讓數據進行流動。這樣的設計是針對連接式的機器學習算法,比如邏輯斯底回歸,神經網絡等。連接式的機器學習算法可以把算法表達成一張圖,張量從圖中從前到后走一遍就完成了前向運算;而殘差從后往前走一遍,就完成了后向傳播。
算子(operation)
在TF的實現中,機器學習算法被表達成圖,圖中的節點是算子(operation),節點會有0到多個輸出,下圖是TF實現的一些算子。
每個算子都會有屬性,所有的屬性都在建立圖的時候被確定下來,比如,最常用的屬性是為了支持多態,比如加法算子既能支持float32,又能支持int32計算。
核(kernel)
TF中還有一個概念是kernel,kernel是operation在某種設備上的具體實現。TF的庫通過注冊機制來定義op和kernel,所以可以通過鏈接一個其他的庫來進行kernel和op的擴展。
邊(edge)
TF的圖中的邊分為兩種:
- 正常邊,正常邊上可以流動數據,即正常邊就是tensor
- 特殊邊,又稱作控制依賴,(control dependencies)
- 沒有數據從特殊邊上流動,但是特殊邊卻可以控制節點之間的依賴關系,在特殊邊的起始節點完成運算之前,特殊邊的結束節點不會被執行。
- 也不僅僅非得有依賴關系才可以用特殊邊,還可以有其他用法,比如為了控制內存的時候,可以讓兩個實際上並沒有前后依賴關系的運算分開執行。
- 特殊邊可以在client端被直接使用
會話(Session)
客戶端使用會話來和TF系統交互,一般的模式是,建立會話,此時會生成一張空圖;在會話中添加節點和邊,形成一張圖,然后執行。
下圖有一個TF的會話樣例和所對應的圖示。
變量(Variables)
機器學習算法都會有參數,而參數的狀態是需要保存的。而參數是在圖中有其固定的位置的,不能像普通數據那樣正常流動。因而,TF中將Variables實現為一個特殊的算子,該算子會返回它所保存的可變tensor的句柄。
Implementation
首先是實現中的幾個部分:
- TF中最重要的Tensor被支持的非常全面,8bit到64bit, signed和unsigned,IEEE float/double,complex number等等。使用引用計數來保存tensor,當計數到0時,tensor被回收。
- 客戶端,用戶會使用;與master和一些worker process交流
- master,用來與客戶端交互,同時調度任務;
- worker process,工作節點,每個worker process可以訪問一到多個device。
- device,TF的計算核心,通過將device的類型、job名稱、在worker process中的索引將device命名。可以通過注冊機制來添加新的device實現,每個device實現需要負責內存分配和管理調度TF系統所下達的核運算需求。
TF的實現分為了單機實現和分布式實現,在分布式實現中,需要實現的是對client,master,worker process不在同一台機器上時的支持。此時,關於這些進程的調度,使用的是原始論文中參考文獻51的調度方式。關於分布式和單機的不同如下圖所示:
Single-Device Execution
構建好圖后,使用拓撲算法來決定執行哪一個節點,即對每個節點使用一個計數,值表示所依賴的未完成的節點數目,當一個節點的運算完成時,將依賴該節點的所有節點的計數減一。如果節點的計數為0,將其放入准備隊列待執行
Multi-Device Execution
當系統到了分布式情況下時,事情就變得復雜了很多,還好前述調度用了現有的框架。那么對於TF來說,剩下的事情就是:
- 決定運算在哪個設備上運行
- 管理設備之間的數據傳遞
決定設備
使用一個cost model算法來進行預估時間,計算后使用貪心算法來分配設備。在決定設備的時候,也可以預先設置一些約束,比如,某個op只能在GPU上執行等。
預估時間有兩種方法:
- 使用啟發式的算法,通過把輸入和輸出的類型以及tensor的大小輸入進去,得到時間的預估
- 使用模擬的方法,對圖的計算進行一個模擬,得到各個計算在其可用的設備上的時間。
尋找合適設備是TF系統區分與之前很多系統的地方,之前的系統比如Parameter Server,是參數分離出來,運算在一起,同時使用數據切分來達到分布式。而TF是把每個op都映射到某個機器上,意味着每個op可能在不同的機器上,這是對系統的進一步剖離,因而可以達到更高的可擴展性。
跨設備通信
當兩個需要通信的op在不同的機器上時,就需要跨設備通信,當它們需要通信時,TF會在它們之間的聯系中添加Send和Recv節點,通過Send和Recv之間進行通信來達到op之間通信的效果。如下所示:
為了優化網絡通信,TF會將相同的數據傳送合並,如a->b和a->c的傳送合並,這一點可以通過Send和Recv很方便的實現。而通過實現Send和Recv,將master節點的通信調度任務解放出來,master就只需要向圖中的各個節點發出運行命令就夠了,增加了系統的可擴展性。
Send和Recv通過TCP或RDMA來傳輸數據
錯誤處理
在分布式系統中,常見的錯誤來自於兩個方面:
- Send/Recv的網絡傳輸
- master和worker process之間的心跳同步
當錯誤發生的時候,TF會將整個圖的計算停止,並從上一次保存的狀態重新執行。為了保存狀態,每個Variable的節點都去連接一個Save的節點。這些save節點會每隔一段時間或每隔幾次迭代運行一次。
自從TF將op剖離之后,所有的策略都依賴於節點來實現,Variable利用節點實現,狀態保存也用節點實現。感覺還是很不一樣的。
一個節點出了錯誤,要停掉整個圖的計算,我覺得這樣的恢復模式會不會代價太大?
Extensions
Gradient Computation(梯度計算)
連接式的機器學習算法往往需要使用梯度下降法來求取參數,TF通過擴展圖的方式實現了自動求導,TF做法如下:
對於每張計算圖,得到從輸入I到輸出C的路徑,並從C到I回溯,回溯過程中對於路徑上的每個節點A,添加另一個節點來計算A’來計算偏導,在計算偏導的過程中,A’不僅僅將上一層傳下來的反向導數作為輸入,還可能將A的輸入和輸出也作為其輸入。
在執行前向計算的時候,啟發式的優化算法通過觀察圖中的節點的計算順序,來決定哪種操作放在哪個節點上,從而幫助用戶來內存重用;當啟發式的算法無效的時候,用戶還可以通過添加控制依賴來自行實現內存上的優化。
而當反向傳播加入的時候,事情變得有點復雜,在正向計算中較前位置的計算數據在反向傳播的后期會被經常用到。這就需要把這些數據存在內存中,從而整個圖的內存都將被占用,使得本來就少的GPU內存更加的捉襟見肘。
有三種方法來對其進行優化:
- 更加復雜的啟發式算法來決定圖的計算順序
- 重新計算這些向量而不是保存下來
- 將長期在GPU內存中的tensor轉移到CPU內存中
Partial Execution(局部執行)
TF支持部分執行,對於多輸出的TF圖,可能用戶只想獲取一個輸出,此時可以指定需要的輸入(feed)和輸出(fetch)值。然后TF會自動計算該輸出的依賴,然后只計算必要的部分。
如上圖所示,指定b為輸入,f為輸出,那么此時d、e是不會被計算的。
Control Flow(控制流)
雖然TF的圖已經有了很強的表達能力,但還不夠,還需要控制流的表達,比如已經實現的Switch、Merge、Enter、Leave和NextIteration等一系列控制算子。
TF使用和MIT Token-Tagged machine相似的表示系統,將循環的每次迭代標記為一個tag,迭代的執行狀態標記為一個frame,但迭代所需的數據准備好的時候,就可以開始計算,從而多個迭代可以同時執行。
對於分布式系統來說,控制循環的狀態是一個大問題。而TF使用圖重寫的方式來實現它,在圖切分的時候,添加一個小的狀態機來監控迭代的開始和結束,
而對於有梯度計算的圖來說,在有控制流的情況下,需要記錄各種狀態,比如對於if算子,需要記錄哪個分支被運行了;而對於循環,需要記錄循環了幾次。TF仍然使用圖重寫來實現記錄狀態的功能,細節不贅述了。
Input Operations(輸入操作)
為Input也構建了一個Node,來管理數據從硬盤到內存的過程。往往需要提前將數據讀入進來以減少內存瓶頸。
Queues(隊列)
TF實現了一個隊列來支持異步操作,EnQueue可以阻塞直到隊列中的空間足夠;DeQueue也可以阻塞直到隊列中一系列的要求得到滿足。
隊列有兩個典型應用:
- 讀入數據,數據在隊列中,這樣可以達到數據處理和數據載入的並行
- 梯度的累加,讓梯度存儲在隊列中,直到隊列中的梯度積累到一定的數值,這樣可以達到多個mini-batch組成一個大的batch
- 句子的聚合,對LSTM中的輸入句子按長度來進行聚合,統一計算以提高效率。
除了FIFO隊列外,TF還實現了一個shuffle隊列。
Containers(容器)
普通的Cotainer可以長期的存儲可變狀態,但Container不止於此,用Container,甚至不同的會話中的圖之間也可以通過Container來共享狀態。
Optimizaiton
TF給了用戶以極其易用的接口,這就需要底層來自動的做很多優化。
Common Subexpression Elimination
用戶給出的圖定義中可能會存在重復的計算操作,TF使用Click(原始論文參考文獻12)中的算法來進行圖的重復表達式的刪減
Controlling Data Combination and Memory Usage
對於復雜的網絡模型,GPU是必須的;而對於GPU來說,它的內存是不足的,因而要用良好的管理來提高GPU內存的使用效率。
在這一點上,TF主要關注數據的網絡傳輸,這主要集中在Recv節點何時去遠程讀取數據,TF會自動分析圖上的關鍵路徑,通過設置依賴的方式來使得非關鍵路徑上的數據傳輸如何不影響關鍵路徑。
Asynchronous Kernels
異步核在執行后立即返回,同時會執行一個回調函數。這樣,可以防止等待計算完成的同時眼看着沒有做的IO任務也不做。即異步核也可以提升並行能力。異步核的典型樣例就是Recv節點和Enqueue和Dequeue操作。
Optimized Libraries for Kernel Implementations
對於已經存在的線性代數庫自然是要利用的,但TF團隊對一些庫還擴展了其對任意維度的tensor的支持。
常見的線性計算庫包括:
- BLAS、cuBLAS,在很多設備上都優化了矩陣乘法
- cuda-convnet、CuDNN,在GPU上優化
Lossy Compression
在數據傳輸過程中,為了加快傳輸效率,往往需要對精度進行壓縮。在TF中,傳輸之前將32bit的float轉變為16float,在傳輸完之后再轉回來,轉回來時用0補齊。
Common Programming Idioms
上面講的都是一些系統級別的優化,還有一些機器學習算法所用到的技巧。這里假定用戶都用SGD來求解機器學習算法。
Data Parallel Training
通過數據並行的方式來提升SGD的效率,比如,假如每次SGD的mini-batch是1000個樣本,那么,切成10份,每份100個,然后將模型復制10份,每份都將梯度傳到參數服務器上。
數據切分也分為同步和異步兩種方式,同步的切分是等待每個獨立的model傳上來的梯度都到齊后再進行更新,這樣和一個大的batch沒有區別。異步的切分則是不用等待,每個獨立的模型的參數更新直接更新。
如下圖所示:
Model Parallel Training
還可以對模型進行切分,讓模型的不同部分執行在不同的設備上,這樣可以一個迭代的樣本可以在不同的設備上同時執行。如下圖所示的LSTM模型:
Concurrent Steps for Model Computation PipeLine
為了充分利用同一台設備的計算能力,TF會盡量讓相鄰的計算在同一台設備上,這樣可以節省網絡開銷,比如對於模型並行來說,如果放在同一個設備上,如下圖所示:
我個人覺得這是TF區分與Parameter Server的一個大區別,對於TF來說,計算節點是分離的,參數也是分離的,因而其PS也是分離的。每個設備上可能分配了計算節點,然后其對應的ps也在該設備上。因而,傳統的PS中,計算和參數是分開的,但計算和參數他們分別是在一起的;而TF中,計算本身是分離的,參數也是分離的,而計算和參數是在一起的。
Related Work
TF出現之前,已經有很多的類似的平台了。
- Theano
- Torch
- Caffe
- Chainer
- Computational Network
可以這么說,TF從他們每一個中都吸取了一些feature和設計思想。根據進化論的觀點,我不能說TF要優於他們,而要說,TF更能適應當前的需求。在之前寫Parameter Server的時候我就隱隱的感覺到,一種設計的產生是和它的需求緊緊相關的,TF的設計可能會有很多人想到,但TF卻只能由google設計和實現,因為需求。而TF的產生也是google大一統移動和PC和Server的戰略需求。
TF的易用性、跨平台能力是其功能亮點,而其可擴展性和高效性則是其根基。不知TF一出,下一代的平台會是什么樣子? 還是說平台的演化是否到此就像android和iOS那樣已經比較成熟了?
而我更感興趣的其實是,對於這樣一個大的平台,大哥是怎么調試的呢? 恐怕這是程序員的能力所在。扯遠了。
TF與其他平台的區別於聯系:
- 支持符號推導,如Theano和Chainer
- 使用C++寫內核,從而跨平台部署很方便,如Caffe,
- 支持跨設備計算,讓用戶高層表達模型,如Adam和Distbelief,但比Adam和DistBelief優越的是,更有彈性,支持更多的模型。
- 相對於Adam、DistBelief和Parameter Server,TF把參數節點化,更新操作也是圖中的一個節點。而Adam等三個會有一個獨立的Parameter Server。
- Halide擁有和TF相似的中間表達但卻有更高級的語義表示,在並行方面優化的更多,但卻是單機的,TF有意向向此方向發展,將Halide的東西添加到TF中來。
其他還有很多像TF那樣支持數據流圖的分布式平台,比如:
- Dryad、Flume、CIEL(數據流調度算法從此借鑒而來)
- Naind(分布式架構非常像)
- Spark、Naind(在內存足夠的情況下,TF和他們一樣運行的很好)
- Dandelion(跨設備)
- TF的迭代是混合的方法:多個數據流但卻一次執行,同時共享狀態。多個數據流通過變量來共享,或使用圖中的同步機制,比如隊列。TF還支持圖內的迭代,這是CIEL和Naiad的混合,簡單來說,像CIEL一樣每個節點只有當准備好之后才會啟動;但為了效率整張圖則表示為一個靜態的循環的圖,像Naiad一樣。
TF內的迭代似乎是很重要的一個點,但論文中含糊不清,想必機器學習算法中的控制流都是很糾結的。
Reference
[1]. Abadi M, Agarwal A, Barham P, et al. Tensorflow: Large-scale machine learning on heterogeneous distributed systems[J]. arXiv preprint arXiv:1603.04467, 2016.