如果問 mapreduce 和 spark 什么關系,或者說有什么共同屬性,你可能會回答他們都是大數據處理引擎。如果問 spark 與 tensorflow 呢,就可能有點迷糊,這倆關注的領域不太一樣啊。但是再問 spark 與 MPI 呢?這個就更遠了。雖然這樣問多少有些不嚴謹,但是它們都有共同的一部分,這就是我們今天談論的一個話題,一個比較大的話題:分布式計算框架。
不管是 mapreduce,還是 spark 亦或 tensorflow,它們都是利用分布式的能力,運行某些計算,解決一些特定的問題。從這個 level 講,它們都定義了一種“分布式計算模型”,即提出了一種計算的方法,通過這種計算方法,就能夠解決大量數據的分布式計算問題。它們的區別在于提出的分布式計算模型不同。Mapreduce 正如其名,是一個很基本的 map-reduce 式的計算模型(好像沒說一樣)。Spark 定義了一套 RDD 模型,本質上是一系列的 map/reduce 組成的一個 DAG 圖。
Tensorflow 的計算模型也是一張圖,但是 tensorflow 的圖比起 spark 來,顯得更“復雜”一點。你需要為圖中的每個節點和邊作出定義。根據這些定義,可以指導 tensorflow 如何計算這張圖。Tensorflow 的這種具體化的定義使它比較適合處理特定類型的的計算,對 tensorflow 來講就是神經網絡。而 spark 的 RDD 模型使它比較適合那種沒有相互關聯的的數據并行任務。那么有沒有一種通用的、簡單的、性能還高的分布式計算模型?我覺著挺難。通用往往意味著性能不能針對具體情形作出優化。而為專門任務寫的分布式任務又做不到通用,當然也做不到簡單。
插一句題外話,分布式計算模型有一塊伴隨的內容,就是調度。雖然不怎么受關注,但這是分布式計算引擎必備的東西。mapreduce 的調度是 yarn,spark 的調度有自己內嵌的調度器,tensorflow 也一樣。MPI 呢?它的調度就是幾乎沒有調度,一切假設集群有資源,靠 ssh 把所有任務拉起來。調度實際上應當分為資源調度器和任務調度器。前者用于向一些資源管理者申請一些硬件資源,后者用于將計算圖中的任務下發到這些遠程資源進行計算,其實也就是所謂的兩階段調度。近年來有一些 TensorflowOnSpark 之類的項目。這類項目的本質實際上是用 spark 的資源調度,加上 tensorflow 的計算模型。
當我們寫完一個單機程序,而面臨數據量上的問題的時候,一個自然的想法就是,我能不能讓它運行在分布式的環境中?如果能夠不加改動或稍加改動就能讓它分布式化,那就太好了。當然現實是比較殘酷的。通常情況下,對于一個一般性的程序,用戶需要自己手動編寫它的分布式版本,利用比如 MPI 之類的框架,自己控制數據的分發、匯總,自己對任務的失敗做容災(通常沒有容災)。如果要處理的目標是恰好是對一批數據進行批量化處理,那么 可以用 mapreduce 或者 spark 預定義的 api。對于這一類任務,計算框架已經幫我們把業務之外的部分(腳手架代碼)做好了。同樣的,如果我們的任務是訓練一個神經網絡,那么用 tensorflow pytorch 之類的框架就好了。這段話的意思是,如果你要處理的問題已經有了對應框架,那么拿來用就好了。但是如果沒有呢?除了自己實現之外有沒有什么別的辦法呢?
今天注意到一個項目,Ray,聲稱你只需要稍微修改一下你的代碼,就能讓它變為分布式的(實際上這個項目早就發布了,只是一直沒有刻意關注它)。當然這個代碼僅局限于 python,比如下面這個例子,
這么簡單?這樣筆者想到了 openmp(注意不是 openmpi)。來看看,
把頭文件導入,添加一行預處理指令就可以了,這段代碼立馬變為并行執行。當然 openmp 不是分布式,只是借助編譯器將代碼中需要并行化的部分編譯為多線程運行,本身還是一個進程,因此其并行度收到 CPU 線程數量所限。如果 CPU 是雙線程,那只能 2 倍加速。在一些服務器上,CPU 可以是單核 32 線程,自然能夠享受到 32 倍加速(被并行化的部分)。不過這些都不重要,在用戶看來,Ray 的這個做法和 openmp 是不是有幾分相似之處?你不需要做過多的代碼改動,就能將代碼變為分布式執行(當然 openmp 要更絕一點,因為對于不支持 openmp 的編譯器它就是一行注釋而已)。
那么 Ray 是怎么做到這一點的呢?其實 Ray 的做法說起來也比較簡單,就是定義了一些 API,類似于 MPI 中的定義的通信原語。使用的時候,將這些 API “注入”到代碼合適的位置,那么代碼就變成了用戶代碼夾雜著一些 Ray 框架層的 API 調用,整個代碼實際上就形成了一張計算圖。接下來的事情就是等待 Ray 把這張計算圖完成返回就好了。Ray 的論文給了個例子:
生成的計算圖為
所以,用戶要做的事情,就是在自己的代碼里加入適當的 Ray API 調用,然后自己的代碼就實際上變成了一張分布式計算圖了。作為對比,我們再來看看 tensorflow 對圖的定義,
可以看出,tensorflow 中是自己需要自己顯式的、明確的定義出圖的節點,placeholder Variable 等等(這些都是圖節點的具體類型),而 Ray 中圖是以一種隱式的方式定義的。我認為后者是一種更自然的方式,站在開發者的角度看問題,而前者更像是為了使用 tensorflow 把自己代碼邏輯去適配這個輪子。
那么 ray 是不是就我們要尋找的那個即通用、又簡單、還靈活的分布式計算框架呢?由于筆者沒有太多的 ray 的使用經驗,這個問題不太好說。從官方介紹來看,有限的幾個 API 確實是足夠簡單的。僅靠這幾個 API 能不能達成通用且靈活的目的還不好講。本質上來說,Tensorflow 對圖的定義也足夠 General,但是它并不是一個通用的分布式計算框架。由于某些問題不在于框架,而在于問題本身的分布式化就存在困難,所以試圖尋求一種通用分布式計算框架解決單機問題可能是個偽命題。
話扯遠了。假設 ray 能夠讓我們以一種比較容易的方式分布式地執行程序,那么會怎么樣呢?前不久 Databricks 開源了一個新項目,Koalas,試圖以 RDD 的框架并行化 pandas。由于 pandas 的場景是數據分析,和 spark 面對的場景類似,兩者的底層存儲結構、概念也是很相似的,因此用 RDD 來分布式化 pandas 也是可行的。我想,如果 ray 足夠簡單好用,在 pandas 里加一些 ray 的 api 調用花費的時間精力可能會遠遠小于開發一套 koalas。但是在 pandas 里加 ray 就把 pandas 綁定到了 ray 上,即便單機也是這樣,因為 ray 做不到像 openmp 那樣如果支持,很好,不支持也不影響代碼運行。
啰嗦這么多,其實就想從這么多引擎的細節中跳出來,思考一下到底什么是分布式計算框架,每種框架又是設計的,解決什么問題,有什么優缺點。最后拿大佬的一個觀點結束本文。David Patterson 在演講 “New Golden Age For Computer Architecture” 中提到,通用硬件越來越逼近極限,要想要達到更高的效率,我們需要設計面向領域的架構(Domain Specific Architectures)。這是一個計算架構層出不窮的時代,每種架構都是為了解決其面對的領域問題出現的,必然包含對其問題的特殊優化。通用性不是用戶解決問題的出發點,而更多的是框架設計者的“一廂情愿”,用戶關注的永遠是領域問題。從這個意義上講,面向領域的計算架構應該才是正確的方向。