閱讀筆記:php
僅但願對底層有必定必要的感性認識,包括一些基本核心概念。node
Here只關注Graph相關,由於對編程有益。python
TF – Kernels模塊部分參見:https://mp.weixin.qq.com/s/vwSlxxD5Ov0XwQCKy1oyuQgit
TF – Session部分,也能夠在起專題總結:https://mp.weixin.qq.com/s/Bi6Rg-fEwyN4uIyRHDPhXggithub
Tensorflow Download: https://github.com/tensorflow/tensorflow/releasesweb
From: https://zhuanlan.zhihu.com/p/25646408算法
-- 大綱 --express
本文依據對Tensorflow(簡稱TF)編程
從系統和代碼實現角度講解TF的內部實現原理。api
以Tensorflow r0.8.0爲基礎,本文由淺入深的闡述Tensor和Flow的概念。
先介紹了TensorFlow的核心概念和基本概述,而後剖析了OpKernels模塊、Graph模塊、Session模塊。
1.2 TF系統架構
若干要點:
第5、六層 | 應用層 | 實現相關實驗和應用 | |
第四層 | API接口層 | 對TF功能模塊的接口封裝,便於其餘語言平臺調用 | |
第三層 | 圖計算層(Graph) | 本地計算流圖 and 分佈式計算流圖的實現 | 包含:Graph的建立、編譯、優化和執行等部分,Graph中每一個節點都是OpKernels類型表示。 |
第二層 | OpKernels | 以Tensor爲處理對象,實現了各類Tensor操做或計算 | 包含:MatMul等計算操做,也包含Queue等非計算操做 |
第一層 | gRPC | 網絡通訊依賴gRPC通訊協議實現不一樣設備間的數據傳輸和更新 |
1.3TF代碼目錄組織
Tensorflow/core目錄 - 包含了TF核心模塊代碼。
- public: API接口頭文件目錄,用於外部接口調用的API定義,主要是session.h 和tensor_c_api.h。
- client: API接口實現文件目錄。
- platform: OS系統相關接口文件,如file system, env等。
- protobuf: 均爲.proto文件,用於數據傳輸時的結構序列化.
- common_runtime: 公共運行庫,包含session, executor, threadpool, rendezvous, memory管理, 設備分配算法等。
- distributed_runtime: 分佈式執行模塊,如rpc session, rpc master, rpc worker, graph manager。
- framework: 包含基礎功能模塊,如log, memory, tensor
- graph: 計算流圖相關操做,如construct, partition, optimize, execute等
- kernels: 核心Op,如matmul, conv2d, argmax, batch_norm等
- lib: 公共基礎庫,如gif、gtl(google模板庫)、hash、histogram等。
- ops: 基本ops運算,ops梯度運算,io相關的ops,控制流和數據流操做
Tensorflow/stream_executor目錄 - 並行計算框架,由google stream executor團隊開發。
Tensorflow/contrib目錄 - contributor開發目錄。
Tensroflow/python目錄 - python API客戶端腳本。
Tensorflow/tensorboard目錄 - 可視化分析工具,不只能夠模型可視化,還能夠監控模型參數變化。
third_party目錄 - TF第三方依賴庫。
- eigen3: eigen矩陣運算庫,TF基礎ops調用
- gpus: 封裝了cuda/cudnn編程庫
2. TF核心概念
TF的核心是圍繞Graph展開的,
簡而言之,就是Tensor 完成Flow的過程。
因此,在介紹Graph以前須要講述一下 (1) 符號編程、(2) 計算流圖、(3) 梯度計算、(4) 控制流的概念。
2.1 Tensor
在數學上,Matrix表示二維線性映射,Tensor表示多維線性映射,Tensor是對Matrix的泛化,能夠表示1-dim、2-dim、N-dim的高維空間。
圖 2 1對比了矩陣乘法(Matrix Product)和張量積(Tensor Contract),能夠看出Tensor的泛化能力,其中張量積運算在TF的MatMul和Conv2D運算中都有用到,
Tensor在高維空間數學運算比Matrix計算複雜,計算量也很是大,加速張量並行運算是TF優先考慮的問題,如add, contract, slice, reshape, reduce, shuffle等運算。
TF中Tensor的維數描述爲階,數值是0階,向量是1階,矩陣是2階,以此類推,能夠表示n階高維數據。
TF中Tensor支持的數據類型有不少,如tf.float16, tf.float32, tf.float64, tf.uint8, tf.int8, tf.int16, tf.int32, tf.int64, tf.string, tf.bool, tf.complex64等,全部Tensor運算都使用泛化的數據類型表示。
TF的Tensor定義和運算主要是調用 Eigen矩陣 計算庫完成的。
【Eigen矩陣運算庫】
Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.
Download: http://eigen.tuxfamily.org/index.php?title=Main_Page
簡單說一下Eigen的特色:
(1) 使用方便、無需預編譯,調用開銷小
(2) 函數豐富,風格有點近似MATLAB,易上手;
(3) 速度中規中矩,比OpenCV快,比MKL、openBLAS慢;
TF中Tensor的UML定義如圖 2 2。其中TensorBuffer指針指向Eigen::Tensor類型。其中,Eigen::Tensor[5][6]不屬於Eigen官方維護的程序,由貢獻者提供文檔和維護,因此Tensor定義在Eigen unsupported模塊中。
圖 2 2中,Tensor主要包含兩個變量m_data和m_dimension,
Eigen::Tensor的成員變量很簡單,卻支持很是多的基本運算,再借助Eigen的加速機制實現快速計算,參考章節3.2。
Eigen::Tensor主要包含了
一元運算(Unary),如sqrt、square、exp、abs等。
二元運算(Binary),如add,sub,mul,div等
選擇運算(Selection),即if/else條件運算
概括運算(Reduce),如reduce_sum, reduce_mean等
幾何運算(Geometry),如reshape,slice,shuffle,chip,reverse,pad,concatenate,extract_patches,extract_image_patches等
張量積(Contract)和卷積運算(Convolve)是重點運算,後續會詳細講解。
2.2 符號編程
編程模式一般分爲命令式編程(imperative style programs)和符號式編程(symbolic style programs)。
caffe、mxnet 則採用了兩種編程模式混合的方法。
* 命令式編程是常見的編程模式,編程語言如python/C++都採用命令式編程。
* 符號式編程將計算過程抽象爲計算圖,計算流圖能夠方便的描述計算過程,全部輸入節點、運算節點、輸出節點均符號化處理。
計算圖經過創建輸入節點到輸出節點的傳遞閉包,從輸入節點出發,沿着傳遞閉包完成數值計算和數據流動,until達到輸出節點。
這個過程通過計算圖優化,以數據(計算)流方式完成,節省內存空間使用,計算速度快,但不適合程序調試,一般不用於編程語言中。
舉上面的例子,先根據計算邏輯編寫符號式程序並生成計算圖
其中A和B是輸入符號變量,C和D是運算符號變量,compile函數生成計算圖F,如圖 2 3所示。
最後獲得A=10, B=10時變量D的值,這裏D能夠複用C的內存空間,省去了中間變量的空間存儲。
圖 2 4是TF中的計算流圖,C=F(Relu(Add(MatMul(W, x), b))),其中每一個節點都是符號化表示的。
經過session建立graph,在調用session.run執行計算。
和目前的符號語言比起來,TF最大的特色是強化了數據流圖,引入了mutation的概念。這一點是TF和包括Theano在內的符號編程框架最大的不一樣。
所謂mutation,就是能夠在計算的過程更改一個變量的值,而這個變量在計算的過程當中會被帶入到下一輪迭代裏面去。
Mutation是機器學習優化算法幾乎必需要引入的東西(雖然也能夠經過immutable replacement來代替,可是會有效率的問題)。
Theano的作法是引入了update statement來處理mutation。
小總結
TF選擇了純符號計算的路線,而且直接把更新引入了數據流圖中去。(兩大特色)
2.3 梯度計算
梯度計算主要應用在偏差反向傳播和數據更新,是深度學習平臺要解決的核心問題。
梯度計算涉及每一個計算節點,每一個自定義的前向計算圖都包含一個隱式的反向計算圖。
從數據流向上看,
圖 2 5是2.2節中圖 2 3對應的反向計算圖。圖中,因爲C=A*B,則dA=B*dC, dB=A*dC。
在反向計算圖中,輸入節點dD,輸出節點dA和dB,計算表達式爲dA=B*dC=B*dD, dB=A*dC=A*dD。每個正向計算節點對應一個隱式梯度計算節點。
反向計算限制了符號編程中內存空間複用的優點,由於在正向計算中的計算數據在反向計算中也可能要用到。
從這一點上講,粗粒度的計算節點比細粒度的計算節點更有優點,而TF大部分爲細粒度操做,雖然靈活性很強,但細粒度操做涉及到更多的優化方案,在工程實現上開銷較大,不及粗粒度簡單直接。在神經網絡模型中,TF將逐步側重粗粒度運算。
2.4 控制流
TF的計算圖如同數據流同樣,數據流向表示計算過程,如圖 2 6。數據流圖能夠很好的表達計算過程,爲了擴展TF的表達能力,TF中引入控制流。
// 感覺」條件判斷「的區別
在編程語言中,if…else…是最多見的邏輯控制,在TF的數據流中也能夠經過這種方式控制數據流向。接口函數以下,
當pred爲true是,執行fn1操做;當pred爲false時,執行fn2操做。
tf.cond(pred, fn1, fn2, name=None)
TF還能夠協調多個數據流,在存在依賴節點的場景下很是有用,例如節點B要讀取模型參數θ更新後的值,而節點A負責更新參數θ,則節點B必須等節點A完成後才能執行,不然讀取的參數θ爲更新前的數值,這時須要一個運算控制器。接口函數以下,tf.control_dependencies函數能夠控制多個數據流執行完成後才能執行接下來的操做,一般與tf.group函數結合使用。
tf.control_dependencies(control_inputs)
TF支持的控制算子有Switch、Merge、Enter、Leave和NextIteration等。
TF不只支持邏輯控制,還支持循環控制。TF使用和MIT Token-Tagged machine類似的表示系統,將循環的每次迭代標記爲一個tag,迭代的執行狀態標記爲一個frame,但迭代所需的數據準備好的時候,就能夠開始計算,從而多個迭代能夠同時執行。
反向計算限制了符號編程中內存空間複用的優點,由於在正向計算中的計算數據在反向計算中也可能要用到。從這一點上講,粗粒度的計算節點比細粒度的計算節點更有優點,而TF大部分爲細粒度操做,雖然靈活性很強,但細粒度操做涉及到更多的優化方案,在工程實現上開銷較大,不及粗粒度簡單直接。在神經網絡模型中,TF將逐步側重粗粒度運算。
From: https://mp.weixin.qq.com/s/-sYn6j3Xiljzw3T6DoJeCA
3. TF 代碼分析初步
3.1 TF整體概述
如圖 3 1所示是一個簡單線性模型的TF正向計算圖和反向計算圖。圖中
圖 3 1 tensorflow計算流圖示例
以圖 3 1爲例實現的TF代碼見圖 3 2(略)。
圖 3 2 TF線性模型示例的實現代碼
圖 3 2中summary能夠記錄graph元信息和tensor數據信息,再利用tensorboard分析模型結構和訓練參數。
圖 3 3是上述代碼在Tensorboard中記錄下的Tensor跟蹤圖。Tensorboard能夠顯示scaler和histogram兩種形式。跟蹤變量走勢可更方便的分析模型和調整參數。
圖 3 3 Tensorboard顯示的TF線性模型參數跟蹤
圖 3 4是圖 3 1示例在Tensorboard中顯示的graph圖。
左側子圖描述的正向計算圖和反向計算圖,正向計算的輸出被用於反向計算的輸入,其中MatMul對應MatMul_grad,Add對應Add_grad等。
右上側子圖指明瞭目標函數最小化訓練過程當中要更新的模型參數W、b,右下側子圖是參數節點W、b展開後的結果。
圖 3 4中,參數W是命名空間(Namespace)類型,展開後的W主要由Assign和Read兩個OpNode組成,分別負責W的賦值和讀取任務。
命名空間gradients是隱含的反向計算圖,定義了反向計算的計算邏輯。
從圖 3 1能夠看出,更新參數W須要先計算dMatMul,即圖 3 4中的MatMul_grad操做,而Update_W節點負責更新W操做。
爲了進一步瞭解UpdateW的邏輯,圖 3 5對MatMul_grad和update_W進行了展開分析。
圖 3 5 MatMul_grad計算邏輯
圖 3 5中,
首先明確MatMul矩陣運算法則,假設 z=MatMul(x, y),則有dx = MatMul(dz, y),dy = MatMul(x, dz),由此能夠推出dW=MatMul(dAdd, x)。
在子圖(a)中左下側的節點b就是輸入節點x,dAdd由Add_grad計算輸出。
update_W的計算邏輯由最優化函數指定,而其中的minimize/update_W/ApplyGradientDescent變量決定,即子圖(b)中的輸出變量Outputs。
另外,在MatMul_grad/tuple命名空間中還隱式聲明瞭control dependencies控制依賴操做,這在章節2.4控制流中相關說明。
看到這裏,對計算流圖的理解須要深刻一些:http://blog.csdn.net/tinyzhao/article/details/52755647
核心概念的補充:
在TensorFlow中,算法都被表示成計算圖(computational graphs)。計算圖也叫數據流圖,能夠把計算圖看作是一種有向圖,圖中的節點表示操做,圖中的邊表明在不一樣操做之間的數據流動。
在這樣的數據流圖中,有四個主要的元素:
* 操做 (operations)
* 張量 (tensors)
* 變量 (variables)
* 會話 (sessions)
把算法表示成一個個操做的疊加,能夠很是清晰地看到數據之間的關係,並且這樣的基本操做也具備廣泛性。
在TensorFlow中,當數據流過操做節點的時候就能夠對數據進行操做。一個操做能夠有零個或多個輸入,產生零個或多個輸出。
一個操做多是一次數學計算,一個變量或常量,一個數據流走向控制,一次文件IO或者是一次網絡通訊。
其中,一個常量能夠看作是沒有輸入,只有一個固定輸出的操做。具體操做以下所示:
操做類型 | 例子 |
---|---|
元素運算 | Add,Mul |
矩陣運算 | MatMul,MatrixInverse |
數值產生 | Constant,Variable |
神經網絡單元 | SoftMax,ReLU,Conv2D |
I/O | Save,Restore |
每一種操做都須要相對應的底層計算支持,好比在GPU上使用就須要實如今GPU上的操做符,在CPU上使用就要實如今CPU上的操做符。
在計算圖中,每一個邊就表明數據從一個操做流到另外一個操做。這些數據被表示爲張量,一個張量能夠看作是多維的數組或者高維的矩陣。
關於TensorFlow中的張量,須要注意的是張量自己並無保存任何值,張量僅僅提供了訪問數值的一個接口,能夠看作是數值的一種引用。
在TensorFlow實際使用中咱們也能夠發現,在run
以前的張量並無分配空間,此時的張量僅僅表示了一種數值的抽象,用來鏈接不一樣的節點,表示數據在不一樣操做之間的流動。
TensorFlow中還提供了SparseTensor
數據結構,用來表示稀疏張量。
變量是計算圖中能夠改變的節點。好比當計算權重的時候,隨着迭代的進行,每次權重的值會發生相應的變化,這樣的值就能夠當作變量。
在實際處理時,通常把須要訓練的值指定爲變量。在使用變量的時候,須要指定變量的初始值,變量的大小和數據類型就是根據初始值來推斷的。
在構建計算圖的時候,指定一個變量實際上須要增長三個節點:
* 實際的變量節點
* 一個產生初始值的操做,一般是一個常量節點
* 一個初始化操做,把初始值賦予到變量
初始化一個變量:
如圖所示,v
表明的是實際的變量,i
是產生初始值的節點,上面的assign
節點將初始值賦予變量,assign
操做之後,產生已經初始化的變量值v'
。
在TensorFlow中,全部操做都必須在會話(session)中執行,會話負責分配和管理各類資源。
在會話中提供了一個run
方法,能夠用它來執行計算圖總體或者其中的一部分節點。
在進行run
的時候,還須要用feed_dict
把相關數據輸入到計算圖。
當run
被調用的時候,TensorFlow將會從指定的輸出節點開始,向前查找全部的依賴界節點,全部依賴節點都將被執行。
這些操做隨後將被分配到物理執行單元上(好比CPU或GPU),這種分配規則由TensorFlow中的分配算法決定。
在神經網絡訓練中,須要使用到反向傳播算法。
在TensorFlow等深度學習框架中,梯度計算都是自動進行的,不須要人工進行梯度計算,
在TensorFlow中,梯度計算也是採用了計算圖的結構。
如圖,在神經網絡中經常須要對權重w
進行求導。這樣的函數前向計算的式子爲:
z=h(y),y=g(x),x=f(w)
對應鏈式求導法則:
z=h(g(f(w)))
在計算圖中,每一個節點邊上會自動增長梯度節點,而後每一個梯度節點與前一個梯度節點相乘,最終在右下角能夠獲得dzdw的值。
3.2 Eigen介紹 (略)
3.3 設備內存管理 (略)
3.4 TF開發工具介紹
TF系統開發使用了:
SWIG 是Simple Wrapper and Interface Generator的縮寫,是一個幫助使用C或者C++編寫的軟件建立其餘編語言的API的工具。例如,我想要爲一個C++編寫的程序建立.NET API,通常狀況下我必須使用託管C++(Managed C++)去編寫大量的代碼才能生成它的.NET API。有了SWIG,這個機械的工做將變得很是簡單。你只需要使用一個接口文件告訴SWIG要爲那些類建立.NET API,SWIG就會自動幫你生成它的.NET API。 當 然,SWIG不只僅支持建立.NET API。最新版本的SWIG支持經常使用腳本語言Perl、PHP、Python、Tcl、Ruby和非腳本語言C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Modula-3, OCAML以及R,甚至是編譯器或者彙編的計劃應用(Guile, MzScheme, Chicken)。
如下將從這三方面介紹TF開發工具的使用。
3.4.1 Swig封裝
Tensorflow核心框架使用C++編寫,API接口文件定義在tensorflow/core/public目錄下,主要文件是tensor_c_api.h文件,C++語言直接調用這些頭文件便可。
Python經過Swig工具封裝TF庫包間接調用,接口定義文件tensorflow/python/ tensorflow.i。其中swig全稱爲Simplified Wrapper and Interface Generator,是封裝C/C++並與其它各類高級編程語言進行嵌入聯接的開發工具,對swig感興趣的請參考相關文檔。
在tensorflow.i文件中包含了若干個.i文件,每一個文件是對應模塊的封裝,其中tf_session.i文件中包含了tensor_c_api.h,實現client向session發送請求建立和運行graph的功能。
3.4.2 Bazel編譯和調試
Bazel是Google開源的自動化構建工具,相似於Make和CMake工具。Bazel的目標是構建「快速並可靠的代碼」,而且能「隨着公司的成長持續調整其軟件開發實踐」。
TF中幾乎全部代碼編譯生成都是依賴Bazel完成的,瞭解Bazel有助於進一步學習TF代碼,尤爲是編譯測試用例進行gdb調試。
Bazel假定每一個目錄爲[package]單元,目錄裏面包含了源文件和一個描述文件BUILD,描述文件中指定了如何將源文件轉換成構建的輸出。
以圖 3 13爲例,左子圖爲工程中不一樣模塊間的依賴關係,右子圖是對應模塊依賴關係的BUILD描述文件。
圖 3 13中name屬性來命名規則,srcs屬性爲模塊相關源文件列表,deps屬性來描述規則之間的依賴關係。」//search: google_search_page」中」search」是包名,」google_search_page」爲規則名,其中冒號用來分隔包名和規則名;若是某條規則所依賴的規則在其餘目錄下,就用"//"開頭,若是在同一目錄下,能夠忽略包名而用冒號開頭。
圖 3 13中cc_binary表示編譯目標是生成可執行文件,cc_library表示編譯目標是生成庫文件。若是要生成google_search_page規則可運行
若是要生成可調試的二進制文件,可運行
圖 3 13 Bazel BUILD文件示例
TF中首次運行bazel時會自動下載不少依賴包,若是有的包下載失敗,打開tensorflow/workspace.bzl查看是哪一個包下載失敗,更改對應依賴包的new_http_archive中的url地址,也能夠把new_http_archive設置爲本地目錄new_local_repository。
TF中測試用例跟相應代碼文件放在一塊兒,如MatMul操做的core/kernels/matmul_op.cc文件對應的測試用例文件爲core/kernels/matmul_op_test.cc文件。
運行這個測試用例須要查找這個測試用例對應的BUILD文件和對應的命令規則,如matmul_op_test.cc文件對應的BUILD文件爲core/kernels/BUILD文件,以下
其中tf_cuda_cc_test函數是TF中自定義的編譯函數,函數定義在/tensorflow/ tensorflow.bzl文件中,它會把matmul_op_test.cc放進編譯文件中。要生成matmul_op_test可執行文件可運行以下腳本:
3.4.3 Protobuf序列化
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。
Protobuf對象描述文件爲.proto類型,編譯後生成.pb.h和.pb.cc文件。
Protobuf主要包含讀寫兩個函數:Writer(序列化)函數SerializeToOstream() 和 Reader(反序列化)函數 ParseFromIstream()。
Tensorflow在core/probobuf目錄中定義了若干與分佈式環境相關的.proto文件,同時在core/framework目錄下定義了與基本數據類型和結構的.proto文件,在core/util目錄中也定義部分.proto文件,感受太隨意了。
在分佈式環境中,不只須要傳輸數據序列化,還須要數據傳輸協議。Protobuf在序列化處理後,由gRPC完成數據傳輸。gRPC數據傳輸架構圖見圖 3 14。
圖 3 14 gRPC數據傳輸架構
gRPC服務包含客戶端和服務端。gRPC客戶端調用stub 對象將請求用 protobuf 方式序列化成字節流,用於線上傳輸,到 server 端後調用真正的實現對象處理。gRPC的服務端經過observer觀察處理返回和關閉通道。
TF使用gRPC完成不一樣設備間的數據傳輸,好比超參數、梯度值、graph結構。
From: https://mp.weixin.qq.com/s/wyr1mUCX3aQaMrA-NsHN6g
TF - Graph模塊
TF把神經網絡模型表達成一張拓撲結構的Graph,Graph中的一個節點表示一種計算算子。
Graph從輸入到輸出的Tensor數據流動完成了一個運算過程,這是對相似機率圖、神經網絡等鏈接式算法很好的表達,同時也是對Tensor + Flow的直觀解釋。
5.1 Graph視圖
Tensorflow採用符號化編程,形式化爲Graph計算圖。
Graph包含節點(Node)、邊(Edge)、NameScope、子圖(SubGraph),圖 5 1是Graph的拓撲描述。
Ø 節點分爲計算節點(Compute Node)、起始點(Source Node)、終止點(Sink Node)。起始點入度爲0,終止點出度爲0。
Ø NameScope爲節點建立層次化的名稱,圖 3 4中的NameSpace類型節點就是其中一種體現。
Ø 邊分爲普通邊和依賴邊(Dependecy Edge)。依賴邊表示對指定的計算節點有依賴性,必須等待指定的節點計算完成才能開始依賴邊的計算。
圖 5 1 Graph的拓撲描述
圖 5 2是Graph的UML視圖模型,左側GraphDef類爲protobuf中定義的graph結構,可將graph結構序列化和反序列化處理,用於模型保存、模型加載、分佈式數據傳輸。右側Graph類爲/core/graph模塊中定義的graph結構,完成graph相關操做,如構建(construct),剪枝(pruning)、劃分(partitioning)、優化(optimize)、運行(execute)等。GraphDef類和Graph類能夠相關轉換,如圖中中間部分描述,函數Graph::ToGraphDef()將Graph轉換爲GraphDef,函數ConvertGraphDefToGraph將GraphDef轉換爲Graph,藉助這種轉換就能實現Graph結構的網絡傳輸。
圖 5 2 Graph的UML視圖
Graph-UML圖中還定義了Node和Edge。Node定義函數操做和屬性信息,Edge鏈接源節點和目標節點。類NodeDef中定義了Op、Input、Device、Attr信息,其中Device多是CPU、GPU設備,甚至是ARM架構的設備,說明Node是與設備綁定的。類FunctionDefLibrary主要是爲了描述各類Op的運算,包括Op的正向計算和梯度計算。FunctionDef的定義描述見圖 5 3。
圖 5 3 FunctionDef的定義
圖 5 4是FunctionDef舉例,對MatMulGrad的梯度描述,其中包含函數參數定義、函數返回值定義、模板數據類型定義、節點計算邏輯。
圖 5 4 FunctionDef舉例:MatMulGrad
5.2 Graph構建
有向圖(DAG)由節點和有向邊組成。本章節主要講述TF如何利用<Nodes, Edges>組合成完整的graph的。
假設有以下計算表達式:t1=MatMul(input, W1)。
圖 5 5 Graph簡單示例
圖 5 5中圖計算表達式包含:3個節點,2條邊,描述爲字符串形式以下。
TF先調用protobuf的解析方法將graph的字符串描述解析並生成GraphDef實例。
而後將GraphDef實例轉化爲tensorflow::Graph實例,這個過程由tensorflow::GraphConstructor類完成。GraphConstructor先判別node的字符串格式是否正確,而後執行convert函數。
首先,按拓撲圖的順序逐步添加node和edge到graph中。
而後,找出全部起始點(source node)和終止點(sink node)。
接着,對graph進行優化。圖優化部分請參考章節6.5。
TF的graph構建模塊測試用例在core/graph/graph_constructor_test.cc文件中。
5.3 Graph局部執行
Graph的局部執行特性容許使用者從任意一個節點輸入(feed),並指定目標輸出節點(fetch)。// <-- 對research頗有幫助!
圖 5 6是TF白皮書中描述Graph局部執行的圖。[15]
圖 5 6 Graph局部執行
圖 5 6中左側爲計算圖,若是要實現f=F(c)運算,代碼以下:
result=sess.run(f, feed_dict={c: input})
TF是如何知道兩個點之間的計算路徑呢?這裏涉及傳遞閉包的概念。
傳遞閉包就是根據graph中節點集合和有向邊的集合,找出從節點A到節點B的最小傳遞關係。如上圖中,點a到點f的傳遞閉包是a -> c -> f。
Graph局部執行過程就是找到feed和fetch的最小傳遞閉包,這個傳遞閉包至關於原graph的subgraph。代碼文件在graph/subgraph.cc中,函數RewriteGraphForExecution()在肯定feed節點和fetch節點後,經過剪枝獲得最小傳遞子圖。
剪枝操做的實現函數以下,Graph經過模擬計算流標記出節點是否被訪問,剔除未被訪問的節點。
5.4 Graph設備分配
TF具備高度設備兼容性,支持X86和Arm架構,支持CPU、GPU運算,可運行於Linux、MacOS、Android和IOS系統。並且,TF的設備無關性特徵在多設備分佈式運行上也很是有用。
Graph中每一個節點都分配有設備編號,表示該節點在相應設備上完成計算操做。用戶既能夠手動指定節點設備,也能夠利用TF自動分配算法完成節點設備分配。設備自動算法須要權衡數據傳輸代價和計算設備的平衡,儘量充分利用計算設備,減小數據傳輸代價,從而提升計算性能。
Graph設備分配用於管理多設備分佈式運行時,哪些節點運行在哪一個設備上。TF設備分配算法有兩種實現算法:
第一種是簡單布放算法(Simple Placer), // 按照指定規則布放,比較簡單粗放,是早期版本的TF使用的模型,並逐步被代價模型方法代替
第二種基於代價模型(Cost Model)評估。
5.4.1 Simple Placer算法
TF實現的Simple Placer設備分配算法使用union-find方法和啓發式方法將部分不相交且待分配設備的Op節點集合合併,並分配到合適的設備上。
Union-find(聯合-查找)算法是並查集數據結構一種應用。並查集是一種樹型的數據結構,其保持着用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。Union-find定義了兩種基本操做:Union和Find。
Ø Find:肯定元素屬於哪個子集。它能夠被用來肯定兩個元素是否屬於同一子集。
Ø Union:將兩個子集合併成同一個集合。即將一個集合的根節點的父指針指向另外一個集合的根節點。
啓發式算法(Heuristic Algorithm)定義了節點分配的基本規則。Simple Placer算法默認將起始點和終止點分配給CPU,其餘節點中GPU的分配優先級高於CPU,且默認分配給GPU:0。啓發式規則適用於如下兩種場景:
Ø 對於符合GeneratorNode條件(0-indegree, 1-outdegree, not ref-type)的節點,讓node與target_node所在device一致,參見圖 5 7。
圖 5 7 啓發式規則A
Ø 對於符合MetaDataNode條件(即直接在原數據上的操做,如reshape)的節點,讓node與source_node所在device一致,參見圖 5 8。
圖 5 8 啓發式規則B
TF中Simple Placer的實現定義在文件core/common_runtime/simple_placer.cc。文件中主要定義了兩個類:ColocationGraph和SimplePlacer。ColocationGraph類利用Union-find算法將節點子集合合併成一個節點集合,參考成員函數ColocationGraph:: ColocateNodes實現。SimplePlacer類實現節點分配過程,下面將主要介紹SimplePlacer:: Run()函數的實現過程。
首先,將graph中的node加入到ColocationGraph實例中,不包含起始點和終止點。
而後,找出graph中受constraint的edge(即src_node被指定了device的edge),強制將dst_node指定到src_node所在的device。
最後,根據graph中已有的constraint條件爲每一個no-constraint的node指定device。
Simple Placer的測試用例core/common_runtime/simple_placer_test.cc文件,要調試這個測試用例,可經過以下方式:
5.4.2 代價模型
TF使用代價模型(Cost Model)會在計算流圖生成的時候模擬每一個device上的負載,並利用啓發式策略估計device上的完成時間,最終找出預估時間最低的graph設備分配方案。[1]
Cost model預估時間的方法有兩種:
Ø 使用啓發式的算法,經過把輸入和輸出的類型以及tensor的大小輸入進去,獲得時間的預估
Ø 使用模擬的方法,對圖的計算進行一個模擬,獲得各個計算在其可用的設備上的時間。
啓發式策略會根據以下數據調整device的分配:節點任務執行的總時間;單個節點任務執行的累計時間;單個節點輸出數據的尺寸。
圖 5 9代價模型UML視圖
TF中代價模型的實現定義在文件core/graph/costmodel.cc和core/common_runtime/ costmodel_manager.cc,其UML視圖參見圖 5 9。
Cost model manager從graph建立cost model,再評估計算時間,以下。
其中評估時間的函數EstimateComputationCosts是對graph中每一個node依次評估,節點計算時間評估函數以下。
5.5 Graph優化
Graph優化算法利用一些優化策略,下降graph的計算複雜度和空間複雜度,提升graph運行速度。
Graph優化算法的實如今文件core/common_runtime/graph_optimizer.cc。
Graph優化策略有三種:
Ø Common Subexpression Elimination (CSE, 公共子表達式消除)
若是一個表達式E已經計算過了,而且從先前的計算到如今的E中的變量都沒有發生變化,那麼E的這次出現就成爲了公共子表達式。
例如:x=(a+c)*12+(c+a)*2; 可優化爲 x=E*14。
CSE實現函數以下,具體細節參考文獻[16]。
CSE測試用例在文件graph/optimizer_cse_test.cc中,調試方法:
Ø Constant Folding (常量合併)
在編譯優化時,變量若是可以直接計算出結果,那麼變量將有常量直接替換。例如:a=3+1-3*1; 可優化爲a=1。
常量合併的實現函數以下。
常量合併的測試用例在common_runtime/constant_folding_test.cc中,調試方法:
Ø Function Inlining (函數內聯)
函數內聯處理可減小方法調用的成本。在TF中包含如下幾種方法:
Ø RemoveListArrayConverter(g):」 Rewrites _ListToArray and _ArrayToList to a set of Identity nodes」.
Ø RemoveDeadNodes(g):刪除DeatNode。DeatNode的特徵是」not statefull, not _Arg, not reachable from _Retval」.
Ø RemoveIdentityNodes(g):刪除Identity節點。如n2=Identity(n1) + Identity(n1); 優化後: n2=n1 + n1;
Ø FixupSourceAndSinkEdges(g):固定source和sink的邊
Ø ExpandInlineFunctions(runtime, g):展開內聯函數的嵌套調用
其中_ListToArray、_ArrayToList、_Arg、_Retval均在core/ops/function_ops.cc中定義。
Graph優化相關測試文件在common_runtime/function_test.cc,調試方法:
瀏覽一遍,看來tensorflow programming的難度並不是傳聞中很高。