【螞蟻】Alluxio在螞蟻集團大規模訓練中的應用

語言: CN / TW / HK

本期內容我們邀請到了來自螞蟻集團的開發工程師陳傳迎老師,給大家分享Alluxio在螞蟻集團是如何支援大規模模型訓練的。

首先是關於引入Alluxio的背景:

為什麼要引入Alluxio?Alluxio到底解決了什麼問題?

帶著這些問題,我們快速get陳老師分享的核心內容:

第一部分:穩定性建設

>> 穩定性建設主要從兩塊進行:worker register follower和master遷移。

【價值】可以把整個叢集做FO的時間控制在30秒以內,如果再配合一些其他機制,比如client端有一些元資料快取機制,就可以達到一種使用者無感知的條件下進行FO。

第二部份:效能優化

>> 效能優化主要進行了follower read only的過程。

【價值】單個叢集的吞吐已經形成了三倍以上提升,整個效能也會提升上來,可以支援更大併發的模型訓練任務。

第三部分:規模提升

>> 規模提升主要是橫向擴充套件。

【價值】模型訓練集合越來越大,可以把這種模型訓練引入進來,對外提供支援。

以上僅為大咖演講概覽,完整內容點選視訊觀看

附件:大咖分享文字版完整內容可見下文

背景介紹

首先是我們為什麼要引入Alluxio,其實我們面臨的問題和業界基本上是相同的:

√ 第一個是儲存IO的效能問題,目前gpu的模型訓練速度越來越快,勢必會對底層儲存造成一定的壓力,如果底層儲存難以支援目前gpu的訓練速度,就會嚴重製約模型訓練的效率。

√ 第二個是單機儲存容量問題,目前我們的模型集合越來越大,那麼勢必會造成單機無法存放的問題。那麼對於這種大模型訓練,我們是如何支援的?

√ 第三個是網路延遲問題,目前我們有很多儲存解決方案,但都沒辦法把一個高吞吐、高併發以及低延時的效能融合到一起,而Alluxio為我們提供了一套解決方案,Alluxio比較小型化,隨搭隨用,可以和計算機型部署在同一個機房,這樣可以把網路延時、效能損耗降到最低,主要出於這個原因我們決定把Alluxio引入螞蟻集團。

以下是分享的核心內容:總共分為3個部分,也就是Alluxio引入螞蟻集團之後,我們主要從以下三個方面進行了效能優化:第一部分是穩定性建設、 第二部分是效能優化、第三部分是規模提升

穩定性建設

首先介紹為什麼要做穩定性的建設,如果我們的資源是受k8s排程的,然後我們頻繁的做資源重啟或者遷移,那麼我們就需要面臨叢集頻繁的做FO,FO的效能會直接反映到使用者的體驗上,如果我們的FO時間兩分鐘不可用,那麼使用者可能就會看到有大量的報錯,如果幾個小時不可用,那使用者的模型訓練可能就會直接kill掉,所以穩定性建設是至關重要的,我們做的優化主要是從兩塊進行:一個是worker register follower,另外一個是master遷移。

Worker Register Follower

先介紹下這個問題的背景:上圖是我們Alluxio執行的穩定狀態,由master進行元資料服務,然後內部通過raft的進行元資料一致性的同步,通過primary對外提供元資料的服務,然後通過worker節點對外提供data資料的服務,這兩者之間是通過worker註冊primary進行一個發現,也就是worker節點的發現,這樣就可以保證在穩定狀態下執行。那如果這時候對primary進行了重啟,就需要做一次FO的遷移,也就是接下來這個過程,比如這時候對primary進行了重啟,那麼內部的standby就需要通過raft進行重新選舉,選舉出來之前,其實primary的元資料和worker是斷聯的,斷連的狀態下就需要進行raft的一致性選舉,進行一次故障的轉移,接下來如果這臺機器選舉出來一個新的primary,這個時候work就需要重新進行一次發現,發現之後註冊到primary裡面,這時新的primary就對外提供元資料的服務,而worker對外提供data資料的服務,這樣就完成了一次故障的轉移,那麼問題點就發生在故障發生在做FO的時候,worker發現新的primary後需要重新進行一次註冊,這個部分主要面臨三個問題:

第一個就是首個worker註冊前叢集是不可用的,因為剛開始首個worker恢復了新的primary領導能力,如果這個時候沒有worker,其實整個primary是沒有data節點的,也就是隻能訪問元資料而不能訪問data資料。

第二個是所有worker註冊過程中,冷資料對效能的影響。如果首個worker註冊進來了,這時就可以對外提供服務,因為有data節點了,而在陸續的註冊的過程當中如果首個節點註冊進來了,然後後續的節點在註冊的過程當中,使用者訪問worker2的快取block 的時候,worker2處於一種miss的狀態,這時候data資料是丟失的,會從現存的worker中選舉出來到底層去讀檔案,把檔案讀進來後重新對外提供服務,但是讀的過程當中,比如說worker1去ufs裡面讀的時候,這就牽扯了一個預熱的過程,會把效能拖慢,這就是註冊當中的問題。

第三個是worker註冊完成之後的資料冗餘清理問題。註冊完成之後,其實還有一個問題就是在註冊的過程當中不斷有少量資料進行了重新預熱,worker全部註冊之後,註冊過程中重新快取的這部分資料就會造成冗餘, 那就需要進行事後清理,按照這個嚴重等級其實就是第一個worker註冊前,這個叢集不可用,如果worker規格比較小,可能註冊的時間2-5分鐘,這2-5分鐘這個叢集可能就不可用,那使用者看到的就是大量報錯,如果worker規格比較大,例如一個磁碟有幾tb的體量,完全註冊上來需要幾個小時。那這幾個小時整個叢集就不可對外提供服務,這樣在使用者看來這個叢集是不穩定的,所以這個部分是必須要進行優化的。

我們目前的優化方案是:把所有的worker向所有的master進行註冊,提前進行註冊,只要worker起來了 那就向所有的master重新註冊一遍,然後中間通過這種實時的心跳保持worker狀態的更新。那麼這個優化到底產生了怎樣效果?可以看下圖:

這個時候如果primary被重啟了,內部通過raft進行選舉,選舉出來的這個新的primary對外提供服務,primary的選舉需要經歷幾部分:第一部分就是primary被重啟之後,raft進行自發現,自發現之後兩者之間進行重新選舉,選舉出來之後這個新的primary經過catch up後就可以對外提供服務了,就不需要重新去獲取worker進行一個register,所以這就可以把時間完全節省下來,只需要三步:自發現、選舉、catch up。

這個方案的效率非常高,只需要30秒以內就可以完成,這就大大縮短了FO的時間。另一個層面來說,這裡也有一些負面的影響,主要是其中一個master如果進行了重啟,那麼對外來說這個primary是可以提供正常服務的,然後這個standby重啟的話,在對外提供服務的同時,worker又需要重新註冊這個block的元資料資訊,這個block元資料資訊其實流量是非常大的,這時會對當前的worker有一定影響,而且對部分註冊上來的master效能也有影響,如果這個時候叢集的負載不是很重的話,是完全可以忽略的,所以做了這樣的優化。

Master的遷移問題

如圖所示,其實剛開始是由這三者master對外提供服務, 這三者達到一個穩定的狀態,然後worker註冊到primary對外提供服務,這個時候如果對機器做了一些騰挪,比如standby3把standby1替換掉,然後standby4把standby2替換掉,然後新的primary把老的primary替換掉,這個時候新的這個master的叢集節點就是由這三者組成:standby3、standby4、新的primary,按照正常的流程來說,這個worker是需要跟當前這個新的叢集進行建聯的,維持一個正常的心跳,然後對外提供服務,但是這時候並沒有,主要原因就是worker識別的master資訊其實是一開始由configer進行靜態注入的,在初始化的時候就已經寫進去了,而且後臺是靜態管理的,沒有動態的更新,所以永遠都不能識別這三個節點, 識別的永遠是三個老節點,相當於是說這種場景直接把整個叢集搞掛了,對外沒有data節點就不可提供服務了,恢復手段主要是需要手動把這三個新節點註冊到configer當中,重新把這個worker重啟一遍,然後進行識別,如果這個時候叢集規模比較大,worker節點數量比較多,那這時的運維成本就會非常大,這是我們面臨的master遷移問題,接下來看一下怎麼應對這種穩定性:

我們的解決方案是在primary和worker之間維持了一個主心跳,如果master節點變更了就會通過主心跳同步當前的worker,實現實時更新master節點,比如standby3把standby1替換掉了,這個時候primary會把當前的這三個節點:primary、standby2、standby3通過主心跳同步過來給當前的worker,這個時候worker就是最新的,如果再把standby4、standby2替換,這時候又會把這三者之間的狀態同步過來,讓他保持是最新的,如果接下來把新的primary加進來,就把這四者之間同步過來,重啟之後進行選舉,選舉出來之後 這就是新的primary,由於worker節點最後的一步是存著這四個節點,在這四個節點當中便利尋找當前的leader,然後就可以識別新的primary,再把這三個新的master同步過來 這樣就達到一個安全的迭代過程,這樣的情況下再受資源排程騰挪的時候,就可以穩定的騰挪下去。

以上兩部分就是穩定性建設的內容。

效能優化

效能優化我們主要進行了follower read only的過程,首先給大家介紹一下背景,如圖所示:

這個是當前Alluxio的整體框架,首先client端從leader拿取到元資料,根據元資料去訪問正常的worker,leader和standby之間通過raft進行與元資料一致性的同步,leader進行元資料的同步只能通過leader發起然後同步到standby,所以說他是有先後順序的。而standby不能通過發起新的資訊同步到leader,這是一個違背資料一致性原則的問題。

另一部分就是當前的這個standby經過前面的worker register follower的優化之後,其實standby和worker之間也是有一定聯絡的,而且資料都會收集上來,這樣就是standby在資料的完整性上已經具備了leader的屬性,也就是資料基本上和leader是保持一致的。

而這一部分如果再把它作為backup,即作為一種穩定性備份的話,其實就是一種資源的浪費,想利用起來但又不能打破raft資料一致性的規則,這種情況下我們就嘗試是不是可以提供只讀服務, 因為只讀服務不需要更新raft的journal entry,對一致性沒有任何的影響,這樣standby的效能就可以充分利用起來,所以說這裡想了一些優化的方案,而且還牽扯了一個業務場景,就是如果我們的場景適用於模型訓練或者檔案的cache加速的,那只有第一次預熱的時候資料才會有寫入,後面是隻讀的,針對大量只讀場景應用standby對整個叢集的效能取勝是非常可觀的。

下面是詳細的優化方案,如圖所示:

主要是針對前面進行的總結,所有的worker向所有的standby進行註冊,這時候standby的資料和primary的資料基本上是一致的,另一部分還是primary和worker之間維護的主心跳,這個時候如果client端再發起只讀請求的時候,就會隨機雜湊到當前所有的master上由他們進行處理,處理完成之後返回client端,對於寫的請求還是會發放到primary上去。然後在不打破raft一致性的前提下,又可以把只讀的效能提升,這個機器擴展出來,按照正常推理來說,只讀效能能夠達到三倍以上的擴充套件,通過follower read實際測驗下來效果也是比較明顯的。這是我們引入Alluxio之後對效能的優化。

規模提升

規模提升主要是橫向擴充套件,首先看一下這個問題的背景:如圖所示:

還是Alluxio的框架,master裡面主要包含了很多構件元素,第一個就是block master,第二個是file master,另外還有raft和snapshot,這個部分的主要影響因素就是在這四個方面:

√ Bblock master,如果我們是大規模叢集建立下,block master面臨的瓶頸就是記憶體,它會侵佔掉大量master的記憶體,主要是儲存的worker的block資訊;

√ File master,主要是儲存了inode資訊,如果是大規模場景下,對本地儲存的壓力是非常大的;

√ Raft面臨的同步效率問題;

√ snapshot的效率,如果snapshot的效率跟不上,可以發現後臺會積壓非常多journal entry,這對效能提升也有一定影響;

做了一些測試之後,在大規模場景下,其實機器規格不是很大的話,也就支援3-6個億這樣的規模,如果想支援10億甚至上百億這樣的規模,全部靠擴大儲存機器的規格是不現實的,因為模型訓練的規模可以無限增長,但是機器的規格不可以無限擴充,那麼針對這個問題我們是如何優化的呢?

這個優化我們主要借鑑了Redis的實現方案,就是可以在底層對元資料進行分片,然後由多個cluster叢集對外提供服務,這樣做的一個好處就是對外可以提供一個整體,當然也可以採取不同的優化策略,比如多個叢集完全由使用者自己去掌控, 把不同的資料分配到每一個叢集上,但這樣對使用者的使用壓力就會比較大。先來介紹一下這個框架,首先我們把這個元資料進行一個分片,比如使用者拿到的整體資料規模集合比較大,單叢集放不下了,這時候會把大規模的資料集合進行一個分片,把元資料進行一些雜湊(Hash)對映,把一定hash的值對映到其中某一個shard上,這樣cluster這個小叢集就只需要去快取對應部分key對應的檔案,這樣就可以在叢集上面有目標性的進行選擇。

那麼接下來其他的資料就會留給其他cluster,把全量的hash分配到一個設定的叢集規模上,這樣就可以通過幾個shard把整個大的模型訓練檔案數量cache下來,對外提供大規模的模型訓練,然後我們的前端是增加了proxy,proxy其實內部是維護一張hash對映表的,使用者過來的請求其實是通過proxy進行hash的對映查詢,然後分配到固定的某一個叢集上進行處理,比如過來的一個檔案請求通過計算它的hash 對映可以判定hash 對映路由到cluster1上面去,這樣其實就可以由cluster1負責,其他key的對映分配到其他cluster上,把資料打散,這樣的好處有很多方面:

√ 第一個就是元資料承載能力變大了;

√ 第二個就把請求的壓力分配到多個叢集上去,整體的qps能力、叢集的吞吐能力都會得到相應的提升;

√ 第三個就是通過這種方案,理論上可以擴展出很多的cluster叢集,如果單個叢集支援的規模是3-6個億,那三個叢集支援的規模就是9-18億,如果擴充套件的更多,對百億這種規模也可以提供一種支援的解決方案。

以上是我們對模型進行的一些優化。整個的框架包括穩定性的建設、效能的優化和規模的提升。

 在穩定建設方面:我們可以把整個叢集做FO的時間控制在30秒以內,如果再配合一些其他機制,比如client端有一些元資料快取機制,就可以達到一種使用者無感知的條件下進行FO,這種效果其實也是使用者最想要的,在他們無感知的情況下,底層做的任何東西都可以恢復,他們的業務訓練也不會中斷,也不會有感到任何的錯誤,所以這種方式對使用者來說是比較友好的。

 在效能優化方面:單個叢集的吞吐已經形成了三倍以上提升,整個效能也會提升上來,可以支援更大併發的模型訓練任務。

 在模型規模提升方面:模型訓練集合越來越大,可以把這種模型訓練引入進來,對外提供支援。

在Alluxio引入螞蟻適配這些優化之後,目前執行下來對各個方向業務的支援效果都是比較明顯的。另外目前我們跟開源社群也有很多的合作,社群也給我們提供很多幫助,比如在一些比較著急的問題上,可以給我們提供一些解決方案和幫助,在此我們表示感謝!

想要了解更多關於Alluxio的乾貨文章、熱門活動、專家分享,可點選進入【Alluxio智庫】