是時候放棄TensorFlow集羣,擁抱Horovod了

Horovod是一套面向TensorFlow的分佈式訓練框架,由Uber構建並開源,目前已經運行於Uber的Michelangelo機器學習即服務平臺上。Horovod 能夠簡化並加速分佈式深度學習項目的啓動與運行。通過利用消息傳遞接口(簡稱 MPI)實現應用環狀規約,顯著提升 TensorFlow 模型的實用性與性能表現。
本文作者在實踐中發現,TensorFlow集羣存在諸多缺點,如概念太多、學習曲線陡峭、修改的代碼量大、性能損失較大等,而Horovod則讓深度學習變得更加美好,隨着規模增大,Horovod性能基本是線性增加的,損失遠小於TensorFlow。

當數據較多或者模型較大時,爲提高機器學習模型訓練效率,一般採用多GPU的分佈式訓練。

按照並行方式,分佈式訓練一般分爲數據並行和模型並行兩種:

  • 模型並行:分佈式系統中的不同GPU負責網絡模型的不同部分。例如,神經網絡模型的不同網絡層被分配到不同的GPU,或者同一層內部的不同參數被分配到不同GPU;

  • 數據並行:不同的GPU有同一個模型的多個副本,每個GPU分配到不同的數據,然後將所有GPU的計算結果按照某種方式合併。

注意,上述中的不用GPU可以是同一臺機上的多個GPU,也可以是不用機上的GPU。

注:圖中的Machine其實就是GPU,當然也可以包含CPU,但深度學習很少採用CPU訓練

當然也有數據並行和模型並行的混合模式。

因爲模型並行各個部分存在一定的依賴,規模伸縮性差(意思是不能隨意增加GPU的數量),在實際訓練中用的不多。而數據並行,則各部分獨立,規模伸縮性好,實際訓練中更爲常用,提速效果也更好。

數據並行會涉及到各個GPU之間同步模型參數,一般分爲同步更新和異步更新。同步更新要等到所有GPU的梯度計算完成,再統一計算新權值,然後所有GPU同步新值後,才進行下一輪計算。異步更新,每個GPU梯度計算完後,無需等待其他GPU的梯度計算(有時可以設置需要等待的梯度個數),可立即更新整體權值,然後同步此權值,即可進行下一輪計算。同步更新有等待,異步更新基本沒有等待,但異步更新涉及到梯度過時等更復雜問題。

在實際應用中,單機多卡的同步式數據並行是最常用的,在論文中最常見的訓練方式是單機八卡。數據再多時,一般就需要多機多卡了。

無論是單機多卡,還是多機多卡,均是分佈式訓練,在Horovod出現之前,使用TensorFlow,一般只有官方推薦的集羣訓練方式。

可是TensorFlow的集羣訓練,用起來並不輕鬆。

TensorFlow集羣的缺點

1. 概念多,學習曲線陡峭

TensorFlow的集羣採用的是Parameter Server架構,因此引入了比較多複雜概念,羅列如下

server
client
master
cluster
parameter server
worker
job
task
replica_device_setter
master service
worker service
clone

複製代碼涉及到的函數

tf.train.Server
tf.train.Supervisor
tf.train.SessionManager
tf.train.ClusterSpec
tf.train.replica_device_setter
tf.train.MonitoredTrainingSession
tf.train.MonitoredSession
tf.train.SingularMonitoredSession
tf.train.Scaffold
tf.train.SessionCreator
tf.train.ChiefSessionCreator
tf.train.WorkerSessionCreator

複製代碼我反覆研究過多次,還是沒有徹底弄清楚server、client、master、master service、worker service、clone、session之間的關係。

大致是,在client中創建server實例,session與server一一對應,server內含master service和worker service兩個服務,master service負責與外界通訊,比如sess.run一般都是告訴server的master service要開始工作了,server的master service通知同一個server的worker service去幹活,worker service調動GPU運算,完成後,返回結果給master service,做權值更新,如果是多機多卡的分佈式,Parameter Server與master service之間做梯度傳遞和權值同步。(參考https://stackoverflow.com/questions/38732502/tensorflow-master-and-worker-service

2. 修改的代碼量大

如果想把單機單卡的模型,移植到多機多卡,涉及的代碼量是以天記的,慢的話甚至需要一週。

3. 需要多臺機子跑不同的腳本

TensorFlow集羣是採用Parameter Server架構的,要想跑多機多卡的集羣,每個機子都要啓動一個client,即跑一個腳本,來啓動訓練,100個機子,人就要崩潰了。

4. PS和Worker的比例不好選取

TensorFlow集羣要將服務器分爲PS和Worker兩種job類型,PS設置多少性能最近並沒有確定的計算公式。

5. 性能損失較大

TensorFlow的集羣性能並不好,當超過一定規模時,性能甚至會掉到理想性能的一半以下。

Horovod

由於TensorFlow集羣太不友好,業內也一直在嘗試新的集羣方案。

2017年Facebook發佈了《Accurate, large minibatch SGD: Training ImageNet in 1 hour 》驗證了大數據並行的高效性,同年百度發表了《Bringing HPC techniques to deep learning 》,驗證了全新的梯度同步和權值更新算法的可行性。受這兩篇論文的啓發,Uber開發了Horovod集羣方案。

約定如下:

網絡帶寬記爲:B(單位Mb/s),
模型總參數數據量記爲:D(單位Mb),
總服務器數量記爲:n,
參數服務器數量記爲:n_p(其中有n= n_p+ n_w),
worker服務器數量記爲:n_w(其中有n= n_p+ n_w),
單服務器計算一次耗時記爲:T_0

梯度同步和權值更新算法

1) Parameter Server架構

TensorFlow的集羣架構是Parameter Server架構,數據的傳導模型如下圖。

則可以計算出,Parameter Server架構的集羣方案,總耗時:

可以看出T與總節點數n基本成線性關係,但不同的參數服務器和woker服務器分配方案,總性能也將不同。

假設,e表示worker服務器佔比,即e=n_w/n,則可以計算出最優的e值爲:

可以看出,最優worker服務器佔比與模型大小、網絡帶寬、單機運行時長都有關係,並不是一個一眼能最優值的超參數。

2)Horovod的ring-allreduce算法

百度2017年發表的《Bringing HPC techniques to deep learning 》中,採用了全新的梯度同步和權值同步算法,叫做ring-allreduce。此種算法各個節點之間只與相鄰的兩個節點通信,並不需要參數服務器。因此,所有節點都參與計算也參與存儲。

一次權重更新,主要包含兩個過程:

1)累計梯度

將所有梯度分爲n個片段,每次只與相鄰節點傳遞1個片段的梯度,n-1次後,每一片段的梯度都完成了所有節點這一片段梯度的累計,但不用片段的累計值分佈在不同節點上。如下圖的第2、第3步;

2)將累計後的梯度分發到所有節點

將第一步累計的梯度再次通過n-1次的相互交換後,所有節點的梯度完成同步。如下圖的第4、第5步。再平均後,更新權重,就完成了所有節點權重的更新。

可以計算出ring-allreduce算法的總耗時爲:

可以看出,總耗時基本與總節點數n成線性關係(n較大時,1/n基本爲0)。

Horovod的梯度同步和權值同步就採用了ring-allreduce算法。

概念

Horovod的數據傳遞是基於MPI,因此其涉及的概念也是MPI中的概念。以4個服務器,每個服務器4個GPU爲例,

  • size進程數量,也即所有GPU數量,爲16

  • rank 進程的唯一ID,0-15

  • local rank,每一個server中的進程的本地唯一ID,0-3

  • allreduce 累加所有數據,並同步到所有節點的操作,如下圖

  • allgather 收集所有數據,並同步到所有節點的操作,完成後每個節點都包含所有節點的數據,並且這些數據單獨存在,如下圖。

  • broadcast 將數據(需要由根節點確認)從一個節點傳播到其他所有節點的操作

大概就這麼多概念,簡單清晰。

將單機單卡改爲多機多卡

將一個只支持單機單卡的訓練腳本修改爲支持多機多卡的訓練腳本,以TensorFlow爲例,只需要做如下改動:

import tensorflow as tf
import horovod.tensorflow as hvd


# Initialize Horovod
hvd.init()

# Pin GPU to be used to process local rank (one GPU per process)
config = tf.ConfigProto()
config.gpu_options.visible_device_list = str(hvd.local_rank())

# Build model...
loss = ...
opt = tf.train.AdagradOptimizer(0.01 * hvd.size())

# Add Horovod Distributed Optimizer
opt = hvd.DistributedOptimizer(opt)

# Add hook to broadcast variables from rank 0 to all other processes during
# initialization.
hooks = [hvd.BroadcastGlobalVariablesHook(0)]

# Make training operation
train_op = opt.minimize(loss)

# Save checkpoints only on worker 0 to prevent other workers from corrupting them.
checkpoint_dir = '/tmp/train_logs' if hvd.rank() == 0 else None

# The MonitoredTrainingSession takes care of session initialization,
# restoring from a checkpoint, saving to a checkpoint, and closing when done
# or an error occurs.
with tf.train.MonitoredTrainingSession(checkpoint_dir=checkpoint_dir,
                                       config=config,
                                       hooks=hooks) as mon_sess:
  while not mon_sess.should_stop():
    # Perform synchronous training.
    mon_sess.run(train_op)

可以看出,改動不大,只需添加10行左右的代碼,主要分爲6步:

1)初始化Horovod

hvd.init()

2)一個GPU與一個進程綁定

config = tf.ConfigProto()
config.gpu_options.visible_device_list = str(hvd.local_rank())

3)根據總GPU數量放大學習率

opt = tf.train.AdagradOptimizer(0.01 * hvd.size())

因爲BatchSize會根據GPU數量放大,所以學習率也應該放大。

4)使用hvd.DistributedOptimizer封裝原有的optimizer

opt = hvd.DistributedOptimizer(opt)

分佈式訓練涉及到梯度同步,每一個GPU的梯度計算仍然由原有的optimizer 計算,只是梯度同步由hvd.DistributedOptimizer負責。

5)廣播初始變量值到所有進程

hooks = [hvd.BroadcastGlobalVariablesHook(0)]

主要爲了確保所有進程變量初始值相同

6)只在worker 0上保存checkpoint

checkpoint_dir = '/tmp/train_logs' if hvd.rank() == 0 else None

防止checkpoint保存錯亂

Horovod只是需要改動必要改動的,不涉及Parameter Server架構的device設置等繁瑣的操作。

起訓練

在單機4卡的機上起訓練,只需執行以下命令:

horovodrun -np 4 -H localhost:4 python train.py

在4機,每機4卡的機子上起訓練,只需在一個機子上執行以下命令即可:

horovodrun -np 16 -H server1:4,server2:4,server3:4,server4:4 python train.py

注意無論是單機多卡,還是多機多卡,都只需在一個機子上執行一次命令即可,其他機Horovod會用MPI啓動進程和傳遞數據。

性能對比

Horovod隨着規模增大,性能損失遠小於TensorFlow,基本是線性增加的。

結論

用過TensorFlow集羣的人,會深刻體會到Horovod有多好用,感謝百度、Facebook和Uber讓深度學習更美好。

不過,也要注意到,Horovod的分佈式貌似只支持同步更新式的數據並行,模型並行和異步更新式的數據並行,我沒有嘗試過,根據ring-allreduce算法可知,應該是不支持的。

本文授權轉載自:
https://mp.weixin.qq.com/s/-kWJhy3UwsiYyUy82F0trQ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章