深度學習利器:TensorFlow系統架構及高性能程序設計
2015年11月9日谷歌開源了人工智能平臺TensorFlow,同時成為2015年最受關注的開源項目之一。經歷了從v0.1到v0.12的12個版本迭代后,谷歌于2017年2月15日發布了TensorFlow 1.0 版本,并同時在美國加州山景城舉辦了首屆TensorFlow Dev Summit會議。
TensorFlow 1.0及Dev Summit(2017)回顧
和以往版本相比,TensorFlow 1.0 的特性改進主要體現在以下幾個方面:
- 速度更快 :TensorFlow 1.0版本采用了XLA的編譯技術,改進了TensorFlow的運行性能及內存利用。從Benchmark問題的測試結果來看,對單機Inception v3模型,實現了在單機8 GPUs上7.3倍的運算加速;對分布式Inception v3模型,實現了在多機64 GPUs上58倍的運算加速。
- 更加靈活 :該版本除了支持tf.layers,tf.metrics及tf.losses模型的High-Level API外,實現了對keras(high-level neural networks library)API的全面兼容。
- 更產品化 :TensorFlow Python API在v1.0版本中趨于穩定,為產品兼容性打下堅實基礎。
在TensorFlow 1.0版本發布的當天,谷歌公司還舉辦了TensorFlow 2017 DEV Summit。該 日程主要包括以下幾個方面的主題演講:
- Hands-on TensorBoard可視化技術:介紹了如何使用TensorBoard,以及TensorFlow圖模型、訓練數據的可視化等。
- TensorFlow High-Level API:介紹了使用Layers, Estimators, and Canned Estimators High-Level API定義訓練模型。
- Integrating Keras & TensorFlow: 介紹了如何在TensorFlow中使用Keras API進行模型定義及訓練。
- TensorFlow at DeepMind:介紹了在DeepMind中使用TensorFlow平臺的典型案例,包括AlphaGo等應用。
- Skin Cancer Image Classification:介紹了斯坦福醫學院使用TensorFlow分類皮膚癌照片,用于醫學診斷。
- Mobile and Embedded TensorFlow:介紹了如何把TensorFlow模型運行在移動終端、嵌入式設備,包括安卓,iOS等系統。
- Distributed TensorFlow:系統性地介紹了分布式TensorFlow的相關技術,以及如何應用于大規模模型訓練。
- TensorFlow Ecosystem:講解了TensorFlow的生態系統,包括生成訓練數據,分布式運行TensorFlow和serving models的產品化流程。
- Serving Models in Production with TensorFlow Serving:系統性講解了如何在生產環境中應用TensorFlow Serving模型。
- ML Toolkit:介紹了TensorFlow的機器學習庫,如線性回歸,KMeans等算法模型的使用。
- Sequence Models and the RNN API:介紹了如何構建高性能的sequence-to-sequence模型,以及相關API。
- Wide & Deep Learning: 介紹了如何結合Wide模型和Deep模型構建綜合訓練模型。
- Magenta,Music and Art Generation:使用增強型深度學習模型生成音樂聲音和藝術圖片。
- Case Study,TensorFlow in Medicine – Retinal Imaging:使用TensorFlow機器學習平臺對醫學視網膜圖片進行分類,輔助醫學診斷。
TensorFlow系統架構
TensorFlow作為分布式機器學習平臺,主要架構如下圖所示。RPC和RDMA為網絡層,主要負責傳遞神經網絡算法參數。CPU和GPU為設備層,主要負責神經網絡算法中具體的運算操作。Kernel為TensorFlow中算法操作的具體實現,如卷積操作,激活操作等。Distributed Master用于構建子圖;切割子圖為多個分片,不同的子圖分片運行在不同的設備上;Master還負責分發子圖分片到Executor/Work端。Executor/Work在設備(CPUs,GPUs,etc.)上,調度執行子圖操作;并負責向其它Worker發送和接收圖操作的運行結果。C API把TensorFlow分割為前端和后端,前端(Python/C++/Java Client)基于C API觸發TensorFlow后端程序運行。Training libraries和Inference libs是模型訓練和推導的庫函數,為用戶開發應用模型使用。
下圖為Client、Master及Worker的內部工作原理。”/job:worker/task:0″ 和 “/job:ps/task:0” 表示worker中的執行服務。”job:ps”表示參數服務器,用于存儲及更新模型參數。”job:worker”用于優化模型參數,并發參數發送到參數服務器上。Distributed Master和Worker Service只存在于分布式TensorFlow中。單機版本的TensorFlow實現了Local的Session,通過本地進程的內部通訊實現上述功能。
用戶編寫TensorFlow應用程序生成計算圖,Client組件會創建Session,并通過序列化技術,發送圖定義到Distributed Master組件。下圖中,Client創建了一個 s+=w*x+b的圖計算模型。
當Client觸發Session運算的時候,Maser構建將要運行的子圖。并根據設備情況,切割子圖為多個分片。下面為Master構建的運行子圖:
接著切割子圖,把模型參數分組在參數服務器上,圖計算操作分組在運算Worker上。下圖為一種可行的圖切割策略:
Distributed Master會根據模型參數的分區情況進行切割邊,在Task間插入發送和接收Tensor信息的通信節點,如下圖所示:
接著Distributed Master通過RegisterGraph方法發送子圖分片給Task,如下圖所示:
Master通過RunGraph觸發子圖運算,Worker會使用GPU/CPU運算設備執行TensorFlow Kernel運算。在本節點的CPU和GPU之間,使用cudaMemcpyAsync傳輸數據;在本節點GPU和GPU之間,使用peer-to-peer DMA傳輸數據,避免通過CPU復制數據。TensorFlow使用gRPC(TCP)和RDMA (Converged Ethernet)技術,實現Worker間的數據通信及傳輸,如下圖所示:
高性能程序設計
TensorFlow內核采用C/C++開發,并提供了C++,Python,Java,Go語言的Client API。特別是Python API,是目前主流的TensorFlow模型開發接口。但為什么還需要采用C++ API去訓練模型呢?本文基于如下 兩點考慮 ,首先當我們采用Python API去訓練模型的時候,需要不斷地用Python API調用C/C++底層接口,重復的接口調用一定程度上影響了程序的執行性能。更為重要的是,在GPU上訓練模型的時候需要大量的內存交換;如果采用C++ API去訓練模型,可提供更好的運算性能及更好地控制GPU內存的分配。
下圖為Python API的運算架構:在模型訓練的每次迭代中,程序通過Python API讀取Batch Data,然后通過TensorFlow Session Run接口,傳遞數據給C++,并觸發神經網絡訓練。如下圖所示:
下圖為C++ API的運算架構:在模型訓練的每次迭代中,通過C++ API讀取Batch Data后,直接觸發模型訓練。減少了不同語言間API接口的循環調用及數據傳輸。如下圖所示:
為了采用C++ API進行模型訓練,我們首先需要編寫訓練模型,這個編寫過程可以采用Python語言來完成。我們首先采用Python API編寫訓練模型,然后把圖模型轉換為Protobuf的序列化文件。接著通過C++ API加載該模型文件,創建TensorFlow Session,初始化模型變量,以及加載訓練數據并執行神經網絡訓練。程序架構如下圖所示:
下面為使用Python API定義訓練模型的示例:
with tf.Session() as sess:
- #定義Placeholder Tensor接入訓練數據
- x = tf.placeholder(tf.float32, [None, 32], name="x")
- y = tf.placeholder(tf.float32, [None, 8], name="y")
- #定義訓練模型
- w1 = tf.Variable(tf.truncated_normal([32, 16], stddev=0.1))
- b1 = tf.Variable(tf.constant(0.0, shape=[16]))
- w2 = tf.Variable(tf.truncated_normal([16, 8], stddev=0.1))
- b2 = tf.Variable(tf.constant(0.0, shape=[8]))
- a = tf.nn.tanh(tf.nn.bias_add(tf.matmul(x, w1), b1))
- y_out = tf.nn.tanh(tf.nn.bias_add(tf.matmul(a, w2), b2), name="y_out")
- cost = tf.reduce_sum(tf.square(y-y_out), name="cost")
- optimizer = tf.train.AdamOptimizer().minimize(cost, name="train")
- #定義變量初始化操作
- init = tf.initialize_variables(tf.all_variables(), name='init_all_vars_op')
- #把圖模型轉換為Protobuf文件
- tf.train.write_graph(sess.graph_def, './', 'mlp.pb', as_text=False)
下面為使用C++ API加載Protobuf圖模型,并執行訓練的示例:
- #include "tensorflow/core/public/session.h"
- #include "tensorflow/core/graph/default_device.h"
- using namespace tensorflow;
- int main(int argc, char* argv[]) {
- //Protobuf模型文件名
- std::string graph_definition = "mlp.pb";
- //Tensorflow Sesssion
- Session* session;
- //定義圖模型對象
- GraphDef graph_def;
- SessionOptions opts;
- //存儲Session會話的運行結果
- std::vector<Tensor> outputs;
- #加載Protobuf模型文件到圖模型對象中
- TF_CHECK_OK(ReadBinaryProto(Env::Default(), graph_definition, &graph_def));
- // 默認在gpu 0上執行模型的訓練操作
- graph::SetDefaultDevice("/gpu:0", &graph_def);
- //設定GPU顯存使用參數
- opts.config.mutable_gpu_options()->set_per_process_gpu_memory_fraction(0.5);
- opts.config.mutable_gpu_options()->set_allow_growth(true);
- //創建TensorFlow會話
- TF_CHECK_OK(NewSession(opts, &session));
- // 加載圖對象到會話中
- TF_CHECK_OK(session->Create(graph_def));
- // 執行模型參數初始化操作
- TF_CHECK_OK(session->Run({}, {}, {"init_all_vars_op"}, nullptr));
- //定義模型輸入數據,包括數據類型和維度信息
- Tensor x(DT_FLOAT, TensorShape({100, 32}));
- Tensor y(DT_FLOAT, TensorShape({100, 8}));
- //把Tensor轉換為矩陣,并初始化Tensor數據
- auto _XTensor = x.matrix<float>();
- auto _YTensor = y.matrix<float>();
- _XTensor.setRandom();
- _YTensor.setRandom();
- for (int i = 0; i < 10; ++i) {
- //執行模型的訓練操作,{{"x", x}, {"y", y}}表示輸入數據Tensor名稱和Tensor對象;{"cost"}表示要獲取輸出值的操作名稱;&outputs表示執行"cost"操作后返回的Tensor對象
- TF_CHECK_OK(session->Run({{"x", x}, {"y", y}}, {"cost"}, {}, &outputs));
- //獲取執行“cost“操作后的運算結果
- float cost = outputs[0].scalar<float>()(0);
- std::cout << "Cost: " << cost << std::endl;
- //執行"train"操作
- TF_CHECK_OK(session->Run({{"x", x}, {"y", y}}, {}, {"train"}, nullptr)); // Train
- outputs.clear();
- }
- //關閉Session及刪除Session對象
- session->Close();
- delete session;
- return 0;
- }
當C++程序寫好后,編譯時候需要鏈接的頭文件,開源已經幫我們整理好了,存放于目錄/usr/lib/python2.7/site-packages/tensorflow/include下。編譯和運行的時候需要鏈接libtensorflow_cc.so,可以按照下面的方式編譯該庫文件:bazel build -c opt //tensorflow:libtensorflow_cc.so –copt=-m64 –linkopt=-m64 –spawn_strategy=standalone –genrule_strategy=standalone –verbose_failures。具體可參考TensorFlow源代碼的官方編譯文檔。
總結
本文首先回顧了TensorFlow 1.0主要新特性及TensorFlow 2017 Dev Summit的主要議程。到目前為止TensorFlow的GitHub Star排名為51000+, Fork排名已達24000+,有15000+ commits。并隨著TensorFlow新版本的不斷發布以及新特性的不斷增加,TensorFlow使用更加靈活,運行速度更快,使用方式更產品化,已成為目前主流的深度學習平臺之一。
接著介紹了TensorFlow的系統架構,包括Client,Master,Worker,Kernel的相關概念及運行方式,是一種適合大規模分布式訓練的機器學習平臺。從上述系統架構中可以看到,TensorFlow內核采用C/C++開發,當采用Python API去訓練模型的時候,需要不斷地用Python調用C/C++底層接口,重復的接口調用一定程度上影響了程序的執行性能。如果有最求高性能運算的朋友,可以嘗試用下本文高性能運算章節推薦的方法。
參考文獻
- http://www.tensorflow.org
- 深度學習利器:分布式TensorFlow及實例分析
- 深度學習利器:TensorFlow使用實戰
作者介紹
武維,博士,現為IBM Spectrum Computing 研發工程師。主要從事大數據,深度學習,云計算等領域的研發工作。


































