1. 程式人生 > >TensorFlow架構與設計:OP本質論

TensorFlow架構與設計:OP本質論

TensorFlow的系統結構以C API為界,將整個系統分為「前端」和「後端」兩個子系統。前端系統扮演了Client的角色,完成計算圖的構造,通過轉發Protobuf格式的GraphDef給後端系統的Master,並啟動計算圖的執行過程。

最終,Master將圖進行分裂,通過RegisterGraph介面,將GraphDef的子圖片段註冊到Worker上。因此,GraphDef是描述計算圖的知識模型,整個TensorFlow的計算過程都是圍繞GraphDef所展開的。

圖片描述

領域模型

TensorFlow計算的單位是OP,它表示了某種抽象計算。本章首先闡述NodeDef, OpDef的元資料模型,然後通過一個簡單的例子,講述元資料的流動過程。

元資料

OP表示某種抽象計算,它擁有0個或多個「輸入/輸出」,及其0個或多個「屬性」。其中,輸入/輸出以Tensor的形式存在。

在系統實現中,OP的元資料使用Protobuf格式的OpDef描述,實現前端與後端的資料交換,及其領域模型的統一。

圖片描述

OpDef定義

OpDef定義

OpDef定義包括OP的名字,輸入輸出列表,屬性列表,優化選項等。其中,屬性常常用於描述輸入/輸出的型別,大小,預設值,約束,及其OP的其他特性。

圖片描述

OpDef表示

OP命名

OP通過名字索引,因此必須保證OP的名字全域性唯一。按照規範,OP的名字採用「駝峰」的命名風格,而Python前端則使用「小寫下劃線」的命名風格。後者也常常稱為「OP構造器」,也是公開給使用者的程式設計介面(API)。

另外,以下劃線開頭的OP被系統內部實現保留。例如,_Send, _Recv,它們用於裝置間通訊的OP;_Source, _Sink標識計算圖的開始節點和結束節點。

輸入/輸出

OP的輸入/輸出以Tensor的形式存在,存在如下4種情況。

  • 0個Tensor
    • 零輸入
    • 零輸出
  • 1個Tensor
    • 型別確定
    • 型別不確定
  • 多個Tensor
    • 型別相同
    • 型別不相同

相對於OP的屬性,OP的輸入是動態的,其值每次迭代(Step)時,都會發生變化。

屬性

OP可以擁有「屬性集」,用於描述OP輸入輸出的型別,大小,預設值,約束,及其其他OP的特徵。其中,計算圖構造時,屬性值(AttrValue)被確定(由NodeDef攜帶,通過GraphDef傳遞給後端執行系統)。

也就是說,OP的「屬性定義」與「屬性值設定」是兩個分離的過程。其中,屬性定義在OP註冊時確定,通過AttrDef描述;屬性值設定在計算圖構造時確定(OP新增到計算圖時),由AttrValue描述。

相對於OP的輸入,OP的屬性則是靜態的。OP屬性值在計算圖構造期間確定,包括輸入輸出的型別,大小,形狀等,在計算迭代過程之中不會發生變化。

NodeDef定義

圖片描述

NodeDef表示

OP索引

NodeDef通過op從OpRegistry中索引OpDef。

輸入列表

通過input指定節點的輸入列表,它也是構造計算圖最重要的知識所在。它存在2種情況,分別表示普通邊與控制依賴邊。

按照約定,為了解析方便,input列表前面儲存普通邊,隨後儲存控制依賴邊。

node:src_output

表示此邊為普通邊,承載Tensor的資料流。其中,node為前驅節點的名稱,src_output為前驅節點輸出邊的索引。特殊地,當src_output為0時,可以略去0。

^node

表示該邊為控制依賴邊。其中,node為前驅節點的名稱。

裝置規範

通過device可以支援使用者自定義裝置分配方案。例如,

  • “@other/node”: 與other/node節點分配在同一裝置;
  • “/job:worker/replica:0/task:1/gpu:3”:完整規範
  • “/job:worker/gpu:3”:部分規範
  • “”:空規範

屬性值列表

在計算圖的構造期,OP屬性值得以確定,包括輸入/輸出的型別,Shape等資訊。OP的屬性值承載於OpDef的attr屬性列表之中。

符號程式設計

TensorFlow的計算過程是一個延遲計算,是一種典型的基於符號的程式設計正規化。從計算時間軸看,計算過程基本分為2個階段:

  • 圖構造期:負責計算圖的構造;
  • 圖執行期:負責計算圖的執行。

其中,在系統初始化時,系統實現對所有OP進行掃描註冊,並保存於OpRegistry之中。

註冊OP

理論上,OP的註冊發生在系統初始化階段。後端系統,可以使用REGISTER_OP實用巨集註冊OP。前端系統,也存在類似的OP註冊機制。

使用REGISTER_OP註冊OP過程,實際上是一個REGISTER_OP描述到OpDef表示的翻譯過程。OpDefBuilder通過鏈式呼叫Input, Output, Attr方法分別構造OP的輸入、輸出列表,及其屬性列表。最後,通過呼叫Finalize成員函式,經過解析字串表示,將其翻譯為OpDef的內在表示,最後註冊到OpRegistry之中。

圖片描述

OP構建過程

例如,REGISTER_OP(“ZerosLike”)向系統註冊了一個zeros_like的OP,在執行時實現了OpDef的翻譯表達。

圖片描述

OP註冊

構造OP

在前端,使用者使用OP構造器實現OP的構造,並將OP註冊到計算圖中。在計算圖構造期間,OP的輸入/輸出的型別,Shape得以確定,OP屬性值也得以確定。

計算圖的構造過程,實際上就是GraphDef定義過程。其中,OP的屬性值承載於NodeDef,計算圖構造期間,NodeDef的屬性值得以確定。

在計算圖執行啟動時,通過呼叫Session.run,將整個GraphDef傳遞給後端,並啟動計算圖的執行。例如,存在如下的計算圖構造過程:

tensor = tf.constant([1, 2], name="n1")
zeros  = tf.zeros_like(tensor, name="n2")

ZerosLike的上游節點為n1,其src_output=0輸出邊流入ZerosLike。此時,ZerosLike的屬性T的值自動推演為DT_INT32,兩個節點構造了一個簡單的計算圖。

圖片描述

OP構造

執行OP

在計算圖執行期間,輸入由上游OP流入得以確定,根據特定裝置型別,輸入輸出型別,多型選擇合適的Kernel實現,並啟動Kernel的計算過程。

例如,如果zeros_like上游輸入為[1, 2, 3, 4],進過zeros_like的OP運算,輸出為[0, 0, 0, 0]。

圖片描述

OP執行

圖片描述

圖片描述