從分散式計算到分散式訓練

ThoughtWorks發表於2017-08-21

對計算機來講,所謂的計算,不過是將儲存在各個地方的資料通過資料匯流排進行傳輸,然後經過算術邏輯單元執行一系列預設好的規則,最終再將輸出寫入到某個位置。

在計算能力有限、儲存成本偏高的情況下,就需要利用好計算機的資源,讓它的計算能力發揮出最大的價值,所以在程式設計初期用指令直接操作硬體,例如組合語言中常見的操縱暫存器,本質上都是為了減少資料傳輸的時間,充分利用CPU的計算能力,避免因為資料的長時間傳輸導致CPU進行過長的等待。

分散式計算的到來

隨著科技的發展,“資料儲存”領域有了質和量的雙向發展,除了穩定性、安全性的提升外,容量也呈指數級增長。因此可以在單機上直接構建整套服務,類似LAMP類似的這種一鍵搭建伺服器的套裝軟體有了更多的應用場景。

然而隨著業務的發展,另一個問題逐漸顯現出來:雖然磁碟容量增加了,但是機器的訪問速度並沒有變快。

什麼意思呢?舉個例子:雖然20年前一個盤最大的儲存空間只有100MB,但是讀取完整磁碟只需要1分鐘。如今雖然磁碟容量可以輕易的變成1TB、1PB,然而讀取完整個盤的資料需要數小時之上。

這背後的問題在於技術發展的限制:磁頭在磁軌上移動速度的增速遠遠低於磁碟容量的增長。用通俗的話來說就是,倉庫的面積已經從10平米擴充套件到100平米甚至到1000平米了,但是一個搬運工一天搬運貨物的速度並沒有顯著的提升,所以雖然倉庫的容量越來越大,但是搬完整個倉庫的貨物需要的時間卻越來越多。

不過好在我們還有另一個好訊息:頻寬逐漸變得廉價。相比20年前,GB頻寬的光纖已經非常普遍,網路能夠實現一秒傳輸,資料量已經遠遠超過了整塊盤的容量。於是一個大膽的想法被提出來了:既然讀取完一個盤的資料需要幾個小時,那把資料分成N份,分別放在不同的機器上並行讀取,是不是一秒鐘就讀取完了?

採用網路並行的方式進行讀取,將瓶頸從磁頭移動轉移到了網路,而要增加一條高速頻寬,已經不需要付出多麼大的代價。

還是倉庫的例子,既然一個搬運工速度這麼慢,搬完1000平米倉庫需要1000分鐘,那麼我用1000個搬運工搬1000平米是不是1分鐘就完了?這個時候影響搬運工的,僅僅是大門的大小,需要同時容納1000個搬運工進出而已,但是開個大門似乎成本並不高,大不了把四面的牆都拆了做成門嘛。

MR一代

一個優秀的思想被提出來後,總會有許多追隨者嘗試將其落地,Google率先丟出了三大論文:BigTable、GFS、MapReduce,從理論上講述了在分散式下如何做到資料的儲存、計算,甚至提出了可以在分散式下做結構化的檢索。

三大論文開啟了分散式計算的時代,然而對於工程界來說,僅有三篇論文並不足以解決生產上的問題,Google並沒有將內部實現的內容進行開源,於是另一幫團隊:Yahoo,自行根據論文進行實現,而後將其貢獻給Apache,逐漸發展成時至今日依舊如日中天的:HDFS、Mapreduce、HBase。

其中尤為重要的分散式計算模型:MapReuce,我們常稱為第一代MR,也就是:MRV1。

上圖是MRV1的主要架構圖,我們可以看到,在MRV1裡面,主要分為兩個部分:執行環境和程式設計模型,所謂的執行環境,指的是用來進行分散式任務排程、資源分配等任務執行過程中涉及到的資訊,而程式設計模型,則指的是提供給開發人員進行開發的介面。

對於MRV1來說,它的執行結構圖如下所示:

可以看到,在MRV1裡面,當我們的一個任務被提交上去之後,由統一的排程器進行任務的監控、分發,以及資源的申請、回收控制等操作。

MRV1有著明顯的兩個階段:Map和Reduce,Map階段主要負責處理輸入,每一個Map任務對應一個分片的資料,而後將資料送入到一個特有的資料結構:環形緩衝區。所謂的環形緩衝區,是用來記錄資料和索引的一個區域。當環形緩衝區快要溢位的時候,資料將會被落地到磁碟。在資料輸入完成後,將會呼叫使用者自己實現的map函式,而後通過與jobtracker的通訊,保持著聯絡,然後分別進入到reduce的階段,renduce階段會彙集所有的資料,這個動作在廣義上會被很多人稱為:shuffle。實際上shuffle並不是reduce才發生的,對於MR來說,從資料從HDFS上載入開始,shuffle就已經開始了,一直伴隨到reduce結束。

MRV1類似於工廠生產辣椒醬,很多工人負責把流水線送到自己身邊的辣椒切碎,這個就是Map操作,所有工人切碎的辣椒彙集在一起做成辣椒醬,這個就是Reduce操作。也許某個工人把辣椒切成塊的速度趕不上流水線送給他辣椒的速度,那麼他就需要把辣椒從流水線拿下來放在他的自己的某個地方存著慢慢切,這個動作就相當於shuffle操作。因為最後彙總會等到所有的人都把辣椒切成塊之後再處理,所以如果有一個人沒有完成,就需要等待,這個時候就發生了我們常說的,資料傾斜。

MR二代

MRV1是統一管理資源的,類似於一家公司的所有決策都需要通過CEO來發出指令,所有人都聽命於CEO,每個人做什麼事全都是CEO一一安排,所以如果CEO忙不過來了,或者有事聯絡不上了,整個組織就成了無頭蒼蠅、完蛋了。

因此對於MRV1來說,雖然它實現了一個平行計算模型,但是其暴露出來的問題也顯而易見:

  • 固化的兩階段模式,限制了迭代任務的進行
  • 多次資料落地,整個執行時間大大延長
  • 所有任務由統一的jobtracker排程,存在單點故障。
  • 對資源的控制不到位,沒有明確的任務優先順序
  • 資源利用不合理,例如在V1裡面,資源分為map solt和reduce solt,導致執行map的時候,reduce的solt全部閒置
  • 安全控制

在這些問題逐漸暴露出來後,有很多補救的措施逐漸出現,例如Tez就是一個非常好的例子,它通過接管MRV1的輸入和輸出,減少其落地到磁碟的動作,目前Tez已經是Hive的內建計算模型。

但是這些補救框架,並不能從根本上解決MRV1的問題,於是第二代MR被研究出來,也就是MRV2,那麼對於MRV2來說,它是怎麼做的呢?既然一個公司全靠CEO去安排任務和進行管理有風險,那麼我們就把公司的所有人分成N個小團隊,每個團隊有自己的Lead負責進行工作安排,CEO幹什麼呢?CEO只負責把要做的事情丟給小團隊的Lead,小團隊的Lead自己去安排手下的人幹活。

大多數時候我們對MRV2這個名字並不熟悉,但是我們一定熟悉一個名字:Yarn。Yarn就是MRV2下最核心的功能。

通過上面的圖我們發現,對於MRV2來說,它的資源的申請、控制、回收,不再由統一的jobtracker(前面舉例中的CEO)來排程了。在MRV2裡面,它產生了幾個新的概念:

  • Resource Manager:負責統一管理所有資源
  • Application Master:負責一個任務的監控、資源分配、回收等工作(前面例子中的小團隊Lead)
  • Node Manager:各個節點的資源監控

這裡面並沒有提到Yarn,因為Yarn並不是一個技術,而是一個概念,代表V2裡面整個任務排程和資源管理系統。我們合併起來統一稱為:Yarn。

我們可以對比一下MRV1和MRV2的機構圖:

在MRV2裡面,依舊分為兩個部分:執行環境和程式設計模型。然而不一樣的地方在於,每一個應用程式需要實現自己的Application Master,也就是資源管理系統。Resource Manager進行一次統一的資源分配,由Application Master自己去決定怎麼把資源分給每一個Task,在實際開發中,我們發現自己似乎並沒有寫過資源分配相關的程式碼,MR的程式碼依舊可以執行,那是因為MRV2裡面,預設提供了MR的Application Master,在MRV2裡面,API也發生了變化,而為了相容MRV1,分別存在兩套API。

同時由於MRV2的超高思想,將整個資源排程獨立出來,這帶來一個好處,那就是Yarn不單單能排程MR計算引擎,還能排程其他計算引擎,例如Spark。雖然目前有Mesos,但是大多數情況下我們還是會選擇採用Yarn去作為資源排程器。

Spark分散式計算模型

看起來似乎MRV2向前邁進了一大步,解決了不少問題,然而對於MRV2來說,依然存在它無法跨越的問題。首先為了相容MR計算模型,它依然保留著兩階段計算的模型,因為對迭代計算基本乏力。MR模型就像一個工廠流水線要生產辣椒醬,要先把辣椒切碎,然後再彙集起來做成辣椒醬,固定的2步操作,如果想在切碎之前再做點啥,或者做成辣椒醬之後再貼個標籤啥的,MR模型就支撐不了,因此“需要任意靈活的進行迭代”這一需求就出來了,這個就是Spark的特點。

同時,MR的核心思想是:執行在廉價伺服器上,挪資料,所以對於實時計算,MVR2基本抓瞎。

在這些問題之上,Spark誕生。Spark的思想比較簡單:挪計算不挪資料。既然要挪計算,那怎麼去描述這個計算呢?於是通過RDD封裝一個針對資料對應關係記錄,在這個封裝之上來記錄計算。所以在Spark裡面,操作分為兩類:Action和Transformation。

為什麼會有這兩類操作?我們可以想一下,如果資料被分散在100個階段,我們需要做的是查詢某個欄位大於0的資料,那麼這個計算根本不用把資料彙集在一起,統一過濾,分別在不同節點進行過濾就行了。

而如果我們的操作是統計共有多少條資料,則需要將資料彙總,所以對於Spark來說,Action才真正會觸發“挪資料”這個動作,Transformation只是做了一個標記轉換。我們對Spark的各種調優,大部分時間也是在儘量減少Action的操作。由於在Spark裡面,RDD是隻讀的,所以每一次操作,都會產生一個新的RDD,因此可以形成一系列的RDD依賴,我們也叫RDD鏈。

模型訓練

模型訓練更多的偏向於AI領域,在AI領域有兩個明顯的分支:概率論和神經網路。在計算能力欠缺的時候,概率論模型是最為普遍的做法,但是近年來發展起來的計算能力,讓深度神經網路模型逐漸的展現出風采,很多框架都表明自己就是一個深度學習框架。

模型訓練本質上是對資料特徵的提取,訓練本身和大資料沒有必然的關係,但是卻相輔相成,資料量越大,提取的特徵越多,模型訓練出來的效果自然越好,然而資料量越大,對計算的要求就越高,也正因為如此,對模型的探索始終是在小資料、抽樣領域進行嘗試。

那麼什麼是特徵呢?舉個例子,我們如果想要預測一個人能活多少歲,最簡單的辦法就是返回已知去世的人的平均年齡,無論是誰都返回這個值,要做這樣的系統當然沒有問題。但是仔細觀察就會發現,男效能活多少歲和女性似乎不一樣,那麼我們可以簡單的修改一下,在預測之前先判斷一下性別,如果是男的就返回男的平均,女的則返回女性的平均。在這裡我們已經無形的用了性別這個特徵,是因為我們認為性別對結果是有影響的,而訓練就需要找出無數個這樣的特徵。

然而目前對於大資料的處理能力,似乎已經發展到了一個非常好的階段,至少在分散式計算上,理論上是可以通過水平擴充套件無限的增加計算能力。

可是模型的訓練和應用在工程中的發展一直不是那麼順利,大約總結起來有如下幾個原因:

  • 門檻較高,首先需要有比較專業的背景知識,同時還需要具備較強的程式設計能力,方能將其應用於工程之上。
  • 對於模型訓練來說,沒有大資料量的支援,生產上的效果始終差強人意,而資料量增大,如何去處理資料又成了另外一個領域的問題,能夠同時處理好兩方面的問題,人員較少。
  • 在實際工程中,我們獲取到的資料集,往往不是訓練模型直接能用的,要達到能夠直接用於訓練模型,還需要非常多的額外處理,這些代價甚至會高於模型訓練本身,因此讓模型訓練這件事的成本變高。
  • 部分使用者,往往並沒有達到模型訓練的程度,例如連基本的資料平臺都不存在,茫然的使用模型,導致效果不如預期,而將結果歸結於模型本身的好壞之上。

雖然模型訓練的發展過程中有諸多問題,但是依舊能夠看到其在向前發展,目前來說,基於GPU的訓練,已經成了所有做模型訓練的人的標配,Google甚至研發了自己的GPU:TPU。而很多晶片研發公司,也在致力於研究開發出專門用於模型訓練的晶片。

對於模型訓練來說,目前一般會有兩種做法:

  • 單機模型訓練
  • 分散式模型訓練

單機模型訓練

所謂的單機訓練,其實就是在一臺機器上訓練了,對於單機模型訓練來講,瓶頸主要在於提升單機的效能配置,例如不停的提高單個GPU的計算能力。而對於資料來說,大部分都是利用本地資料,雖然我們可以讀取分散式檔案系統的資料,但是實際上還是經過了shuffle操作,將資料讀取到本地,而模型的訓練,都是全程單機訓練,我們可以通過各種優化演算法,例如奇異值分解等手段,來降低計算成本。

分散式模型訓練

對於單機訓練來說,單個GPU,始終會陷入瓶頸,所以對於模型訓練,也有人開始嘗試,是否可以分散式訓練?

模型的分散式,相對於其他分散式計算會困難許多,首先模型依賴於資料,而模型本身的計算又要依賴於GPU,那麼要如何將資料和計算能力結合?

對於目前來講,模型的分散式一般會有以下幾種做法:

  • 資料分散式訓練
  • 模型分散式訓練
  • 混合訓練

上面的圖片比較形象的描述了幾種不同的訓練方式,首先對於資料分散式來說,每一個節點都有一個完整模型的副本,而對於模型分散式來說,模型的計算會被分散到不同的節點上,例如Tensorflow就通過圖形化的表達方法,將計算描述為一個圖,然後再判斷圖中的哪些計算可以並行執行,分別拆分到不同的節點上進行訓練,從而達到分散式訓練的效果。在混合訓練中,模型訓練會被分散,同時資料也會分散,無論是哪種分散式訓練,最終都會涉及一個操作:模型的歸一。在目前來說,有不同的做法,可以將模型最終歸一,例如整合演算法就是邏輯上實現了模型的歸一。

結尾

對於大資料和人工智慧來講,現在僅僅是萌芽時期,後面還有大量的工作要做,而模型的訓練無論是單機還是分散式,都還沒有達到真正穩定的生產批量效果,這些挑戰,不僅僅來自於技術的實現,同時也來自於業務的配合,如何利用現有的技術能力,將其推廣到業務上解決問題,才是重點需要關注的地方。

文/ThoughtWorks白髮川

相關文章