分散式機器學習框架與高維實時推薦系統

第四正規化天樞發表於2020-07-20

導讀:隨著網際網路的高速發展和資訊科技的普及,企業經營過程中產生的資料量呈指數級增長,AI 模型愈發複雜,在摩爾定律已經失效的今天,AI 的落地面臨著各種各樣的困難。本次分享的主題是分散式機器學習框架如何助力高維實時推薦系統機器學習本質上是一個高維函式的擬合,可以透過機率轉換做分類和迴歸。而推薦的本質是二分類問題,推薦或者不推薦,即篩選出有意願的使用者進行推薦。本文將從工程的角度,講述推薦系統在模型訓練與預估上面臨的挑戰,並介紹第四正規化分散式機器學習框架 GDBT 是如何應對這些工程問題的。

主要內容包括:

  • 推薦系統對於機器學習基礎架構的挑戰
  • 大規模分散式機器學習場景下,不同演算法的效能瓶頸和解決思路
  • 第四正規化分散式機器學習框架 GDBT
  • 面臨的網路壓力及最佳化方向

01

推薦系統對於機器學習基礎架構的挑戰

1. 海量資料+高維特徵帶來極致效果

傳統的推薦系統中,我們只用簡單的模型或者規則來擬合資料,就可以得到一個很好的效果 ( 因為使用複雜的模型,很容易過擬合,效果反而越來越差 )。但是當資料量增加到一定的數量級時,還用簡單的模型或者規則來擬合資料,並不能充分的利用資料的價值,因為資料量增大,推薦的效果上限也隨之提升。這時,為了追求精準的效果,我們會把模型構建的越來越複雜,對於推薦系統而言,由於存在大量的離散特徵,如使用者 ID、物品 ID 以及各種組合,於是我們採用高維的模型來做分類/排序。

2. 強時效性帶來場景價值

分散式機器學習框架與高維實時推薦系統

隨著時間的推移,推薦場景面臨的問題也在發生著變化,尤其是新聞、資訊類的推薦,物料的變化非常快。同時,使用者的興趣和意願也在時刻發生著變化。我們的模型都是根據歷史資料總結出來的規律,距離當前時間越近的資料,對於預測越有指導意義。為了增強線上效果,就需要增加模型的時效性,按照資料價值的高低,將時效性分為:硬實時、軟實時、離線,這裡重點介紹下硬實時和軟實時。

硬實時:

硬實時是指毫秒級到秒級的特徵。這類特徵往往具有指導性意義,同時對系統的挑戰也是最大的,很難做到毫秒級或秒級的更新模型。通常的做法是透過快速的更新特徵資料庫,獲取實時特徵,來抓取秒級別的變化。尤其是新使用者冷啟動問題,當新使用者登陸 APP,如果在幾秒內,特徵資料庫就能收集到使用者的實時行為,從而快速的抓取到使用者的興趣愛好,可以在一定程度上解決冷啟動問題。

軟實時:

軟實時是指小時級到天級別的時間段。這時有足夠的時間做批次的模型訓練,可以週期性的更新模型的權重,使模型有更好的時效性。同時軟實時對算力的消耗也是最大的,因為天級別的更新和周級別的更新模型,效果差距非常大。

3. 充分發揮資料的價值

分散式機器學習框架與高維實時推薦系統

因此,為了更好的模型效果,我們需要處理海量資料、高維模型和實時特徵,而這一切都需要 AI 基礎架構提供充沛的算力。

02

大規模分散式機器學習場景下,不同演算法的效能瓶頸和解決思路

1. 算力問題

分散式機器學習框架與高維實時推薦系統

當前面臨的算力問題主要包括:

a. 資料量指數級增長,而摩爾定律已經失效。曾經有個玩笑,當程式設計師覺得程式跑得慢時,不需要最佳化程式碼,只需睡上一覺,換個新機器就好了。但現在摩爾定律已經失效,我們只能想方設法的最佳化程式碼和工程。

b. 模型維度高,單機記憶體難以承受,需要做分散式處理。

c. 模型時效性要求高,需要快迭代,會消耗大量的算力。這時,如何解決算力問題變得非常有價值。

2. 方案

分散式機器學習框架與高維實時推薦系統

可行的解決方案有:

  • 分散式+異構計算解決擴充套件性問題:由於資料增長很快,單機的算力很難提升,尤其是 CPU 算力增長緩慢。我們可以用 GPU、加速卡來提供強有力的算力,用分散式的儲存來更新模型,解決模型的擴充套件問題。
  • 大規模引數伺服器解決高維問題:當模型大到單機放不下時,我們就會使用引數伺服器來解決高維問題。
  • 流式計算解決時效性問題:對於模型的時效性有一種省算力的方法是用流式計算來解決,但是流式計算非常容易出錯。

總結來說,就是如何最佳化模型訓練速度,採用流式計算可以一定程度上解決這個問題。

3. 線性加速並非易事

分散式機器學習框架與高維實時推薦系統

單靠堆機器在機器學習上是不能直接加速的,稍有不慎就會陷入"一核有難八核圍觀"的場景。現在很多分散式的計算都有單點的設計,這會極大的降低系統的擴充套件性。機器學習需要很多機器更新同一個模型,這就需要同步,不管是執行緒同步,還是程式同步,或者機器間依賴網路節點同步。一旦做不好,會消耗大量時間,這時你會發現,寫個單機的程式可能會更快一點。

03

分散式機器學習框架 GDBT

1. GDBT

分散式機器學習框架與高維實時推薦系統

GDBT 是一個分散式資料處理框架,配備了高效能分散式大規模離散引數伺服器。其核心元件包括:分散式資料來源、引數伺服器、計算圖。基於 GDBT 框架我們實現了一系列的高維演算法:邏輯迴歸、GBM ( 樹模型 )、DSN 等,以及自動特徵和 AutoML 相關的演算法。GDBT 的工作流程圖如上圖所示。

接下來,選擇 GDBT 框架中的幾個核心元件為大家詳細介紹下:

2. 分散式資料來源 ( 資料並行 )

分散式機器學習框架與高維實時推薦系統


分散式資料來源 ( DataSource ) 是做資料並行的必備元件,是 GDBT 框架的入口。DataSource 最重要的一點是做負載均衡。負載均衡有很多種做法,這裡設計了一套爭搶機制,因為線上程排程中,執行緒池會採用 work stealing 機制,我們的做法和它類似:資料在一個大池子中,在每一個節點都儘可能讀屬於自己的資料,當消費完自己的資料時,就會去搶其它節點的資料,這樣就避免了節點處理完資料後的空置時間,規避了"一核有難八核圍觀"的現象。

由於 DataSource 也是對外的入口,因此我們會積極的擁抱開源生態,支援多種資料來源,並儘可能多的支援主流資料格式。

最後,我們還最佳化了 DataSource 的吞吐效能,以求更好的效率。因為有的演算法計算量實際上很低,尤其是邏輯迴歸這種比較簡單的機器學習演算法,對 DataSource 的挑戰是比較大的。

實驗結果:

分散式機器學習框架與高維實時推薦系統

這裡我們用 pDataSource 對比了 Spark 和 Dask。Spark 大家都比較熟悉,Dask 類似 python 版的 Spark,Dask 最開始是一個分散式的 DataFrame,漸漸地發展成了一個分散式的框架。如上圖所示,由於我們在記憶體上的最佳化,透過對比吞吐量和記憶體佔用,pDataSource 用30%的記憶體資源就可以達到 Spark2.4.4 120% 的效能。

3. 引數伺服器

引數伺服器類似於分散式的記憶體資料庫,用來儲存和更新模型。引數伺服器會對模型進行切片,每個節點只儲存引數的一部分。一般資料庫都會針對 workload 進行最佳化,在我們的機器學習訓練場景下,引數伺服器的讀寫比例各佔50%,其訓練的過程是不斷的讀取權重、更新權重,不斷的迭代。

對於大部分高維機器學習訓練,引數伺服器的壓力都很大。引數伺服器雖然自身是分散式的,但引數伺服器往往會制約整個分散式任務的擴充套件性。主要是由於高頻的特徵和網路壓力,因為所有的機器都會往引數伺服器推送梯度、拉取權重。在實際測試中,網路壓力非常大,TCP 已經不能滿足我們的需求,所以我們使用 RDMA 來加速。

機器學習中的高頻特徵更新特別頻繁時,引數伺服器就會一直更新高頻特徵對應的一小段記憶體,這制約了引數伺服器的擴充套件性。為了加速這個過程,由於機器學習都是一個 minibatch 更新,可以把一個 minibatch 當中所有高頻 key 的梯度合併成一個 minibatch,交給引數伺服器更新,可以有效的減輕高頻 key 的壓力。並且在兩端都合併後再更新,可以顯著減輕高頻特徵的壓力。

對於大規模離散的模型,引數伺服器往往要做的是大範圍記憶體的 random massage。由於計算機訪問記憶體是非常慢的,我們平常寫程式碼時可能會覺得改記憶體挺快的,其實是因為 CPU 有分級快取,命中快取就不需要修改記憶體,從而達到加速。同時 CPU 還有分級的流水線,它的指令是亂序執行的,在讀取記憶體時,可以有其它的指令插進來,會讓人覺得訪問記憶體和平常執行一條指令的時間差不多,實際上時間差了幾十到幾百倍。這對於執行一般的程式是可行的,但對於引數伺服器的工作負載,是不可行的。因為其工作流程需要高頻的訪問記憶體,會導致大量的時間用在記憶體訪問上。所以,如何增加命中率就顯得尤為重要:

  • 我們會修改整個引數伺服器的資料結構。
  • 我們做了 NUMA friendly。伺服器往往不只一個 CPU,大多數是兩個,有些高階的會有四個 CPU。CPU 周邊會有記憶體,一個 CPU 就是一個 NUMA。我們儘量讓引數伺服器所有的記憶體綁在 NUMA 上,這樣就不需要跨 CPU 訪問記憶體,從而提升了效能。
  • 還有個難點是如何保證執行緒安全。因為引數伺服器是多執行緒的,面臨的請求是高併發的,尤其是離線時,請求往往會把伺服器壓滿。這時要保證模型的安全,就需要一個高效的鎖。這裡我們自研了 RWSpinLock,可以最大化讀寫併發。受限於篇幅,這裡就不再進行展開。
  • 最終的效果可以支援每秒 KV 更新數過億。

4. 分散式機器學習框架的 Workload

① 分散式 SGD 的 workload

分散式機器學習框架與高維實時推薦系統

分散式 SGD 的 workload:

首先 DataSource 會從第三方的儲存去讀資料。這裡畫了三個機器,每個機器是一條流水線,資料來源讀完資料之後,會把資料交給 Process,由 Process 去執行計算圖。計算圖當中可能會有節點之間的同步,因為有時需要同步模式的訓練。當計算圖算出梯度之後,會和引數伺服器進行互動,做 pull/push。最後 Process 透過 Accumulator 把模型 dump 回第三方儲存 ( 主要是 HDFS )。

② 樹模型的 workload

目前樹模型的應用廣泛,也有不少同學問到分散式的樹模型怎麼做。這裡為大家分享下:

首先介紹下 GBDT ( Gradient Boost Decision Tree ),透過 GBDT 可以學出一系列的決策樹。左圖是一個簡單的例子,用 GBDT 來預測使用者是否打遊戲。對於 Tree1,首先問年齡是否小於15歲,再對小於15歲的使用者問是男性還是女性,如果是男性,會得到一個很高的分值+2。對於 Tree2,問使用者是否每天使用電腦,如果每天都使用,也會得到一個分值+0.9,將 Tree1 和 Tree2 的結果相加得到使用者的分值是2.9,是一個遠大於零的數字,那麼該使用者很有可能打遊戲。同理,如果使用者是位老爺爺他的年齡分值是-1,且他每天也使用電腦,分值也是+0.9,所以對於老爺爺來說他的分值是-0.1,那麼他很有可能不會打遊戲。這裡我們可以看出,樹模型的關鍵點是找到合適的特徵以及特徵所對應的分裂點。如 Tree1,第一個問題是年齡小於15歲好,還是小於25歲好,然後找到這個分裂點,作為這個樹的一個節點,再進行分裂。

樹模型的兩種主流訓練方法:

❶ 基於排序

往往很難做分散式的樹模型。

❷ 基於 Histogram:

DataSource 先從第三方的儲存當中讀資料,然後 DataSource 給下游做 Propose,對特徵進行統計,掃描所有特徵,為每個特徵選擇合適的分類點。比如剛剛的例子,我們會用等距分桶,我們發現年齡基本上都是在0到100歲之間,可以以5歲為一個檔,將年齡進行等分,作為後面 Propose 的方案。有了 Propose 的點之後,由於每個機器都只顧自己的資料,所以機器之間要做一次 All Reduce,讓所有的機器都統一按照這些分裂點去嘗試分裂,再後面就進入了一個高頻更新、高頻找特徵的過程:

首先我們會執行 Histogram 過一遍資料,統計出某一個特徵,如年齡小於15歲的增益是多少,把所有特徵的 Propose 點的增益都求出來。由於機器還是隻顧自己的資料,所以當所有機器過完自己的資料之後還會做一次 All Reduce,同步總的增益。然後找一個增益最大的,給它進行分裂,不斷的執行這樣的過程。

其實這個過程最開始時,尤其是 XGboost,計算量都用在如何統計 Histogram 上,因為 Histogram 過資料的次數特別多,而且也是一個記憶體 random massege 的過程,往往對記憶體的壓力非常大。我們通常會做的最佳化是使用 GPU,因為視訊記憶體比記憶體快很多,因此樹模型可以用 GPU 加速。

目前,XGBoost、lightGBM 都支援 GPU 加速。我們也支援了用 FPGA 加速整個過程,但是我們發現 Histogram 和 All Reduce 是交替執行的,Histogram 的時間短了,All Reduce 的時間長了,就回到了剛才說到的問題:機器多了之後,發現大家都在互動,但互動的時間比統計 Histogram 的時間還長。

04

面臨的網路壓力及最佳化方向

1. 網路壓力大

a. 模型同步,網路延遲成為瓶頸。首先分散式 SGD Workload 主要是模型同步,尤其是同步模式時,當機器把梯度都算好,然後同一時刻,幾十個幾百個節點同時發出 push 請求,來更新引數伺服器,引數伺服器承擔的壓力是巨大的,訊息量和流量都非常大。

b. 計算加速,頻寬成為瓶頸。我們可以用計算卡加速,計算卡加速之後,網路頻寬成為了瓶頸。

c. 突發流量大。機器學習中,主要難點是突發流量。因為它是同步完成之後,立刻做下一步,而且大家都齊刷刷的做。另一方面 profile 是非常難做的。當你跑這個任務時會發現,頻寬並沒有用完,計算也沒有用完。這是因為該計算的時候,沒有用網路頻寬,而用網路的時候沒有做計算。

2. RDMA 硬體日漸成熟

分散式機器學習框架與高維實時推薦系統

隨著 RDMA 硬體的日漸成熟,可以帶來很大的好處:

  • 低延遲:首先 RDMA 可以做到非常低的延遲,小於 1μs。1μs 是什麼概念,如果是用傳統的 TCP/IP 的話,大概從兩個機器之間跑完整個協議棧,平均下來是 35μs 左右。
  • 高寬頻:RDMA 可以達到非常高的頻寬,可以做到大於 100Gb/s 的速度。現在有 100G、200G 甚至要有 400G了,400G 其實已經超過了 PCIE 的頻寬,一般我們只會在交換機上看到 400G 這個數字。
  • 繞過核心:RDMA 可以繞過核心。
  • 遠端記憶體直接訪問:RDMA 還可以做遠端記憶體的直接訪問,可以解放 CPU。

用好這一系列的能力,可以把網路問題解決掉。

3. 傳統網路傳輸

傳統網路傳輸是從左邊發一條訊息發到右邊:

首先把樣本模型序列化,copy 到一段連續的記憶體中,形成一個完整的訊息。我們再把訊息透過 TCP 的協議棧 copy 到作業系統作業系統再透過 TCP 協議棧,把訊息發到對面的作業系統。對面的 application 從 OS buffer 把資訊收回,收到一段連續的記憶體裡,再經過一次反序列化,生成自己的樣本模型,供後續使用。

我們可以看到,在傳統的網路傳輸中,共發生了四次 copy,且這四次 copy 是不能並行的,序列化之前也不能傳送,沒發過去時,對方也不能反序列化。由於 CPU 主頻已達瓶頸,不能無限高,這時你的延遲主要就卡在這個流程上了。

4. 第一步最佳化

第一步最佳化是我們自研的序列化框架。我們一開始把樣本模型放在記憶體池中。而這個記憶體池是多段連續的記憶體,使任何資料結構都可以變成多段連續的記憶體。這個序列化的過程,其實就是打一個標記,標明這個樣本模型要傳送,是一個 zero copy 的過程。可以瞬間拿到序列化後的資訊,由網路層透過 TCP 協議棧發到對端,對端收的時候也是不會收成一段大的記憶體,而是多段連續的記憶體。透過共享記憶體池的方式,可以減少兩次 copy,讓速度提升很多,但還是治標不治本。

5. 引入 RDMA

分散式機器學習框架與高維實時推薦系統

進而我們引入了 RDMA:

RDMA 可以直接繞過核心,透過另一種 API 直接去和網路卡做互動,能把最後一次 copy 直接省掉。所以我們引入 RDMA 之後,可以變成一個大的共享記憶體池,網路卡也有了修改操作記憶體的能力。我們只需要產生自己的樣本模型後,去戳一下網路卡,網路卡就可以傳輸到對面。對面可以直接拿來做訓練、做引數、做計算,整個流程變得非常快,吞吐也可以做到非常大。

6. 底層網路 PRPC


分散式機器學習框架與高維實時推薦系統


我這裡對比的是 BRPC 和 GRPC,BRPC 的效能是我現在看到的 RPC 當中最快的,但是因為它不支援 RDMA,所以被甩開了三到五倍。因為 GRPC 相容性的工作特別多,所以 GRPC 的效能會更差一些。這個對比並不是非常的科學,因為我們最大的收益來源是 RDMA 帶來的收益。

7. 線上預估


分散式機器學習框架與高維實時推薦系統


線上大部分時間,我們離線訓練出的模型會放在 HDFS 上,然後把模型載入到引數伺服器。會有一套 controller 去接受運維請求,引數伺服器會給我們提供引數、預估服務對外暴露打分的介面。上圖是一個最簡單的線上預估的 Workload。

8. 流式更新、加速迭代

流式更新比較複雜:

大概是使用者有請求過來,會有資料庫把使用者、物品的資訊聚合起來,再去預估打分,和剛剛最簡單的架構是一樣的。打分之後要把做好的特徵傳送到 message Queue,再實時的做 join。這時 API server 會接受兩種請求,一種是使用者請求打分,還有一種是使用者的 feedback ( 到底是贊,還是踩,還是別的什麼請求 )。這時會想辦法得到 label,透過 ID 去拼 label 和 feature,拼起來之後進一步要把特徵變成高維向量,因為變成高維向量才能進入機器學習的環節,由 Learner pull/push 去更新訓練的引數伺服器,訓練引數伺服器再以一種機制同步到預估的引數伺服器。

有了這樣的一個架構,才能把流式給跑起來,雖然可以做到秒級別的模型更新,但是這個過程非常容易出錯。

今天的分享就到這裡,謝謝大家。

相關文章