[TF] Architecture - Computational Graphs

閱讀筆記: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)編程

    • 白皮書[1]、
    • TF Github[2]
    • TF官方教程[3]的理解,

從系統和代碼實現角度講解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,

    • m_data保存了Tensor的數據塊,T是泛化的數據類型,
    • m_dimensions保存了Tensor的維度信息。

 

Eigen::Tensor的成員變量很簡單,卻支持很是多的基本運算,再借助Eigen的加速機制實現快速計算,參考章節3.2。

Eigen::Tensor主要包含了

一元運算(Unary),如sqrtsquareexpabs等。

二元運算(Binary),如addsubmuldiv

選擇運算(Selection),即if/else條件運算

概括運算(Reduce),如reduce_sumreduce_mean

幾何運算(Geometry),如reshape,slice,shuffle,chip,reverse,pad,concatenate,extract_patches,extract_image_patches

張量積(Contract)卷積運算(Convolve)是重點運算,後續會詳細講解。

 

 

2.2 符號編程

編程模式一般分爲命令式編程(imperative style programs)符號式編程(symbolic style programs)

    • 命令式編程容易理解和調試,命令語句基本沒有優化,按原有邏輯執行。            --> Torch是典型的命令式風格
    • 符號式編程涉及較多的嵌入和優化,不容易理解和調試,但運行速度有同比提高。 --> theanoTensorflow都使用了符號式編程。

caffemxnet 則採用了兩種編程模式混合的方法。

 

* 命令式編程是常見的編程模式,編程語言如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爲判別表達式,
    • fn1和fn2爲運算表達式。

當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正向計算圖和反向計算圖。圖中

    • x是輸入,
    • W是參數權值,
    • b是誤差值,
    • MatMul和Add是計算操做,
    • dMatMul和dAdd是梯度計算操做,
    • C是正向計算的目標函數,
    • 1是反向計算的初始值,
    • dC/dW和dC/dx是模型參數的梯度函數。

圖 3 1 tensorflow計算流圖示例

 

以圖 3 1爲例實現的TF代碼見圖 3 2(略)。

    • 首先聲明參數變量W、b和輸入變量x,構建線性模型y=W*x+b,
    • 目標函數loss採用偏差平方和最小化方法,
    • 優化函數optimizer採用隨機梯度降低方法。
    • 而後初始化全局參數變量,
    • 利用session與master交互實現圖計算。

圖 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中,

  • 子圖(a)描述了MatMul_grad計算邏輯,
  • 子圖(b)描述了MatMul_grad輸入輸出,
  • 子圖(c)描述了update_W的計算邏輯。

 

首先明確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系統開發使用了:

    1. bazel工具實現工程代碼自動化管理,
    2. protobuf實現了跨設備數據傳輸,
    3. swig庫實現python接口封裝。
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)。
SWIG簡介

如下將從這三方面介紹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的難度並不是傳聞中很高。 

相關文章
相關標籤/搜索