摘要:為了進一步了解的邏輯,圖對和進行了展開分析。另外,在命名空間中還隱式聲明了控制依賴操作,這在章節控制流中相關說明。簡述是高效易用的開源庫,有效支持線性代數,矩陣和矢量運算,數值分析及其相關的算法。返回其中一塊給用戶,并將該內存塊標識為占用。
3. TF 代碼分析初步
3.1 TF總體概述
為了對TF有整體描述,本章節將選取TF白皮書[1]中的示例展開說明,如圖 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 Tensorboard顯示的TF線性模型graph
圖 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控制流中相關說明。
3.2 Eigen介紹
在Tensoflow中核心數據結構和運算主要依賴于Eigen和Stream Executor庫,其中Eigen支持CPU和GPU加速計算,Stream Executor主要用于GPU環境加速計算。下面簡單講述Eigen庫的相關特性,有助于進一步理解Tensorflow。
3.2.1 Eigen簡述
Eigen是高效易用的C++開源庫,有效支持線性代數,矩陣和矢量運算,數值分析及其相關的算法。不依賴于任何其他依賴包,安裝使用都很簡便[8]。具有如下特性:
? ?支持整數、浮點數、復數,使用模板編程,可以為特殊的數據結構提供矩陣操作。比如在用ceres-solver進行做優化問題(比如bundle adjustment)的時候,有時候需要用模板編程寫一個目標函數,ceres可以將模板自動替換為內部的一個可以自動求微分的特殊的double類型。而如果要在這個模板函數中進行矩陣計算,使用Eigen就會非常方便。
? ?支持逐元素、分塊、和整體的矩陣操作。
? ?內含大量矩陣分解算法包括LU,LDLt,QR、SVD等等。
? ?支持使用Intel MKL加速
? ?部分功能支持多線程
? ?稀疏矩陣支持良好,到今年新出的Eigen3.2,已經自帶了SparseLU、SparseQR、共軛梯度(ConjugateGradient solver)、bi conjugate gradient stabilized solver等解稀疏矩陣的功能。同時提供SPQR、UmfPack等外部稀疏矩陣庫的接口。
? ?支持常用幾何運算,包括旋轉矩陣、四元數、矩陣變換、AngleAxis(歐拉角與Rodrigues變換)等等。
? ?更新活躍,用戶眾多(Google、WilliowGarage也在用),使用Eigen的比較著名的開源項目有ROS(機器人操作系統)、PCL(點云處理庫)、Google Ceres(優化算法)。OpenCV自帶到Eigen的接口。
Eigen庫包含 Eigen模塊和unsupported模塊,其中Eigen模塊為official module,unsupported模塊為開源貢獻者開發的。
Eigen unsupported 模塊中定義了數據類型Tensor及相關函數,包括Tensor的存儲格式,Tensor的符號表示,Tensor的編譯加速,Tensor的一元運算、二元運算、高維度泛化矩陣運算,Tensor的表達式計算。本章后續所述Tensor均為Eigen::Tensor
Eigen運算性能評估如圖 3 6所示[9],eigen3的整體性能比eigen2有很大提升,與GOTO2、INTEL_MKL基本持平。
圖 3 6矩陣運算常用庫比較
3.2.2 Eigen 存儲順序
Eigen中的Tensor支持兩種存儲方式:
? ?Row-major表示矩陣存儲時按照row-by-row的方式。
? ?Col-major表示矩陣存儲時按照column-by-column的方式。
Eigen默認采用Col-major格式存儲的(雖然也支持Row-major,但不推薦),具體采用什么存儲方式取決于算法本身是行遍歷還是列遍歷為主。例如:A=[[a11, a12, a13], [a21, a22, a23]]的存儲序列見圖 3 7。
圖 3 7 Row-major和Column-major存儲順序
3.2.3 Eigen 惰性求值
在編程語言理論中,存在及早求值(Eager Evaluation) 和惰性求值(Lazy Evaluation)
? ?及早求值:大多數編程語言所擁有的普通計算方式
? ?惰性求值:也認為是“延遲求值”,可以提高計算性能,最重要的好處是它可以構造一個無限的數據類型。
關于惰性求值,舉例如下:
Vec3 = vec1 + vec2;
及早求值形式需要臨時變量vec_temp存儲運算結果,再賦值給vec3,計算效率和空間效率都不高:
Vec_temp = vec1 + vec2;
Vec3 = vec_temp
而惰性求值不需要臨時變量保存中間結果,提高了計算性能:
Vec_symbol_3 = (vec_symbol_1 + vec_symbol_2);
Vec3 = vec_symbol_3.eval(vec1, vec2)
由于Eigen默認采用惰性計算,如果要求表達式的值可以使用Tensor::eval()函數。Tensor::eval()函數也是session.run()的底層運算。例如:
Tensor
3.2.4 Eigen 編譯加速
編譯加速可以充分發揮計算機的并行計算能力,提高程序運行速度。
舉例如下:
普通的循環相加運算時間復雜度是O(n):
如果指令集支持128bit并行計算,則時間復雜度可縮短為O(n/4):
Eigen編譯時使用了SSE2加速。假設處理float32類型,指令集支持128bit并行計算,則一次可以計算4個float32類型,速度提升4倍。
3.2.5 Eigen::half
Tensorflow支持的浮點數類型有float16, float32, float64,其中float16本質上是Eigen::half類型,即半精度浮點數[10]。關于半精度浮點數,英偉達2002年首次提出使用半精度浮點數達到降低數據傳輸和存儲成本的目的。
在分布式計算中,如果對數據精度要求不那么高,可以將傳輸數據轉換為float16類型,這樣可以大大縮短設備間的數據傳輸時間。在GPU運算中,float16還可以減少一般的內存占用。
在Tensorflow的分布式傳輸中,默認會將float32轉換為float16類型。Tensorflow的轉換方式不同于nvidia的標準,采用直接截斷尾數的方式轉化為半精度浮點數,以減少轉換時間。
圖 3 8是雙精度浮點數(float64)存儲格式。
圖 3 8 雙精度浮點數
圖 3 9是單精度浮點數(float32)存儲格式。
圖 3 9 單精度浮點數
圖 3 10是半精度浮點數(float16)存儲格式。
圖 3 10 半精度浮點數
浮點數存儲格式分成3部分,符號位,指數和尾數。不同精度是指數位和尾數位的長度不一樣。
3.3 設備內存管理
TF設備內存管理模塊利用BFC算法(best-fit with coalescing)實現。BFC算法是Doung Lea’s malloc(dlmalloc)的一個非常簡單的版本。它具有內存分配、釋放、碎片管理等基本功能[11]。
BFC將內存分成一系列內存塊,每個內存塊由一個chunk數據結構管理。從chunk結構中可以獲取到內存塊的使用狀態、大小、數據的基址、前驅和后繼chunk等信息。整個內存可以通過一個chunk的雙鏈表結構來表示。
圖 3 11內存分塊結構圖
用戶申請一個內存塊(malloc)。根據建立的chunk雙鏈表找到一個合適的內存塊(后面會說明什么是合適的內存塊),如果該內存塊的大小是用戶申請大小的兩倍以上,那么將該內存塊切分成兩塊,這就是split操作。返回其中一塊給用戶,并將該內存塊標識為占用。Spilt操作會新增一個chunk,所以需要修改chunk雙鏈表以維持前驅和后繼關系。
用戶釋放一個內存塊(free)。先將該塊標記為空閑。然后根據chunk數據結構中的信息找到其前驅和后繼內存塊。如果前驅和后繼塊中有空閑的塊,那么將剛釋放的塊和空閑的塊合并成一個更大的chunk(這就是merge操作,合并當前塊和其前后的空閑塊)。再修改雙鏈表結構以維持前驅后繼關系。這就做到了內存碎片的回收。
BFC的核心思想是:將內存分塊管理,按塊進行空間分配和釋放;通過split操作將大內存塊分解成小內存塊;通過merge操作合并小的內存塊,做到內存碎片回收。
但是還留下許多疑問。比如說申請內存空間時,什么樣的塊算合適的內存塊?如何快速管理這種塊?
BFC算法采取的是被動分塊的策略。最開始整個內存是一個chunk,隨著用戶申請空間的次數增加,最開始的大chunk會被不斷的split開來,從而產生越來越多的小chunk。當chunk數量很大時,為了尋找一個合適的內存塊而遍歷雙鏈表無疑是一筆巨大的開銷。為了實現對空閑塊的高效管理,BFC算法設計了bin這個抽象數據結構。
Bin數據結構中,每個bin都有一個size屬性,一個bin是一個擁有chunk size >= bin size的空閑chunk的集合。集合中的chunk按照chunk size的升序組織成單鏈表。BFC算法維護了一個bin的集合:bins。它由多個bin以及從屬于每個bin的chunks組成。內存中所有的空閑chunk都由bins管理。
圖 3 12 bins集合的結構圖
圖 3 12中每一列表示一個bin,列首方格中的數字表示bin的size。bin size的大小都是256的2^n的倍。每個bin下面掛載了一系列的空閑chunk,每個chunk的chunk size都大于等于所屬的bin的bin size,按照chunk size的升序掛載成單鏈表。BFC算法針對bins這個集合設計了三個操作:search、insert、delete。
Search 操作:給定一個chunk size,從bins中找到大于等于該chunk size的最小的那個空閑chunk。Search操作具體流程如下。如果bin以數組的形式組織,那么可以從index = chunk size /256 >>2的那個bin開始查找。較好的情況是開始查找的那個bin的chunk鏈表非空,那么直接返回鏈表頭即可。這種情況時間復雜度是常數級的。最壞的情況是遍歷bins數組中所有的bin。對于一般大小的內存來說,bins數組元素非常少,比如4G空間只需要23個bin就足夠了(256 * 2 ^ 23 > 4G),因此也很快能返回結果。總體來說search操作是非常高效的。對于固定大小內存來說,查找時間是常數量級的。
Insert 操作:將一個空閑的chunk插入到一個bin所掛載的chunk鏈表中,同時需要維持chunk鏈表的升序關系。具體流程是直接將chunk插入到index = chunk size /256 >>2的那個bin中即可。
Delete操作:將一個空閑的chunk從bins中移除。
TF中內存分配算法實現文件core/common_runtime/bfc_allocator.cc,GPU內存分配算法實現文件core/common_runtime/gpu/gpu_bfc_allocator.cc。
3.4 TF開發工具介紹
TF系統開發使用了bazel工具實現工程代碼自動化管理,使用了protobuf實現了跨設備數據傳輸,使用了swig庫實現python接口封裝。本章將從這三方面介紹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”為規則名,其中冒號用來分隔包名和規則名;如果某條規則所依賴的規則在其他目錄下,就用"http://"開頭,如果在同一目錄下,可以忽略包名而用冒號開頭。
圖 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結構。
作者簡介:
姚健,畢業于中科院計算所網絡數據實驗室,畢業后就職于360天眼實驗室,主要從事深度學習和增強學習相關研究工作。目前就職于騰訊MIG事業部,從事神經機器翻譯工作。聯系方式: yao_62995@163.com
歡迎加入本站公開興趣群商業智能與數據分析群
興趣范圍包括各種讓數據產生價值的辦法,實際應用案例分享與討論,分析工具,ETL工具,數據倉庫,數據挖掘工具,報表系統等全方位知識
QQ群:81035754
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/4499.html
閱讀 1093·2021-10-12 10:11
閱讀 876·2019-08-30 15:53
閱讀 2286·2019-08-30 14:15
閱讀 2960·2019-08-30 14:09
閱讀 1196·2019-08-29 17:24
閱讀 971·2019-08-26 18:27
閱讀 1282·2019-08-26 11:57
閱讀 2145·2019-08-23 18:23