最近由于工作需要,重新研究了下horovod,然後簡單總結下。
Horovod原理
詳細可看原論文 總結: 1. Horovod 使用ring-all-reduce分布式計算方式 2. 運作過程: * 前向 每個模型收到batch size大小的輸入以及計算gradients * 後向時,每個模型分别計算gradients * 使用allreduce計算average gradients * 使用gradient進行更新 不同的gpu使用不同的訓練資料訓練,再使用allredece做整合,相當于增加了batch size
Horovod 安裝
推薦使用滿足cuda版本的各個軟體的最新版本。
openmpi
nccl
horovod
tf/pytorch
Tensorflow例子
tf的session運作時可以添加hooks(鈎子)和scanfold(腳手架)指定sess跑前,跑後要幹些什麼額外操作。 常見的hook有: LoggingTensorHook:自動個n步輸出日志。 CheckpointSaverHook:自動儲存檔案以及自動從最新的ckpt恢複。 簡單的來說,
hook就是将一些每一步訓練前後常用操作寫成一個端口,友善複用。
Horovod定義了兩種初始化變量的方法 1. 不适用hook
bcast = hvd.broadcast_global_variables(0)
- 使用hooks
hooks = [hvd.BroadcastGlobalVariablesHook(0)]
以下會分别做相關的介紹。
1. Session(不使用hooks)
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.
bcast = hvd.broadcast_global_variables(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
with tf.train.Session(config=config,hooks=hooks) as sess:
bcast.run()
while not sess.should_stop():
# Perform synchronous training.
mon_sess.run(train_op)
2. MonitoredTrainingSession版本(使用hooks)
Session版本也可以使用hooks, 使用以下代碼可以
使用session調用hooks。
call hooks.begin()
sess = tf.compat.v1.Session()
call hooks.after_create_session()
while not stop is requested:
call hooks.before_run()
try:
results = sess.run(merged_fetches, feed_dict=merged_feeds)
except (errors.OutOfRangeError, StopIteration):
break
call hooks.after_run()
call hooks.end()
sess.close()
直接使用MonitoredTrainingSession,自動調用hooks會比較友善。
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)
運作
- train.py指定使用哪些gpu
os.environ["CUDA_VISIBLE_DEVICES"] = "2,3"
- 運作指令
horovodrun -np 2 python *.py
注意事項
- 保證安裝環境正常 Open MPI 3.1.3可能有問題,推薦使用 3.1.2或者更新到Open MPI 4.0.0. 使用正确版本的tensorflow,tf1.13.1對于多程序處理可能有問題。
- 正确的gpu cpu綁定參數 運作horovodrun和miprun時使用正确的參數(理想情況下,控制gpu的程序應該和最近的cpu接口綁定,這個取決于伺服器的主機闆以及cpu類型,推薦使用—map-by socket。)
mpirun —map-by socket
- 每個gpu一個程序 訓練代碼中加入
Python config.gpu_options.visible_device_list = str(hvd.local_rank())
# 4為gpu數量 mpirun -np 4
4. 檢視每個gpu使用的程序數目以及占用顯存大小是否正常。
- GPU使用率低下,資料讀取過慢 保證使用tf.data多線程讀取方式或者搞笑的資料預處理方式 一般來說gpu使用率要達到80-95,10-25說明使用率過低
- 檢查哪些問題使得gpu運作過慢 使用horovod timeline 或者 nvprof 檢視哪裡有問題
- tf data pipeline過慢,檢視官方代碼https://github.com/tensorflow/models/tree/master/official/vision/image_classification
- GPU之間資料交換有問題,保證你正在使用 InfiniBand.使用NCCL_DEBUG=INFO檢視資料交換細節進行檢查
mpirun -np 4 — map-by socket -x NCCL_DEBUG=INFO python something.py -{params} or use horovodrun which includes the –x binding.
- GPUs 資料交換 檢視gpu通訊保證gpu之間正常分布式訓練。 用NCCL_DEBUG=INFO檢視資料交換細節。 下為正常運作的輸出結果。
gpu002:1299562:1299573 [0] NCCL INFO Ring 00 : 0[0] -> 1[1] via P2P/IPC gpu028:149460:149495 [0] NCCL INFO Ring 01 : 16 -> 0 [send] via NET/IB/0 gpu009:164181:164216 [0] NCCL INFO Ring 01 : 12 -> 8 [receive] via NET/IB/0 gpu009:164181:164216 [0] NCCL INFO Ring 01 : 4 -> 8 [receive] via NET/IB/0
- 其他通用的建議
- 使用 較大的batchsize 塞滿gpu,但要考慮batchsize和 收斂性 的問題。
- 學習率使用正常的學習率$times$gpu數目,同僚使用正常的學習率和scaled的學習率做訓練,看哪個能好
- 使用最新版本的軟體
注意事項總結
- 正确的cpu gpu綁定方式
- 不浪費多餘的gpu,一個gpu一個程序
- 使用高效的data pipeline
- 優化gpu使用率達到80%以上
- 使用最新版本的cuda相關軟體,cuda以及nccl
分布式訓練總結
- tensorflow使用horovod能夠有效的利用多gpu進行訓練,而且随着gpu使用的越多,horovod提升的效果越明顯。
- 個人覺得使用多gpu的初衷是需要試驗較大的batch size或者網絡模型比較大,達到臨界的batch size需要做大量的實驗。如果為了提升網絡模型訓練速度,不如多用用profile或者改進data pipeline。
參考資料
- https://github.com/horovod/horovod
- https://towardsdatascience.com/why-is-your-horovod-slower-than-the-usual-201b4b8574d5
- https://mc.ai/a-quick-guide-to-distributed-training-with-tensorflow-and-horovod-on-amazon-sagemaker/