文章目录
最近由于工作需要,重新研究了下horovod。
Horovod原理
详细可看原论文
总结:
- Horovod 使用ring-all-reduce分布式计算方式。
- 运行过程:
- 前向 每个模型收到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定义了两种初始化变量的方法
- 不适用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.
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一个进程
训练代码中加入
config.gpu_options.visible_device_list = str(hvd.local_rank())
# 4为gpu数量
mpirun -np 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查看数据交换细节。
下为正常运行的输出结果。
gpu001:1299562:1299573 [0] NCCL INFO Ring 00 : 0[0] -> 1[1] via P2P/IPC
gpu000:149460:149495 [0] NCCL INFO Ring 01 : 16 -> 0 [send] via NET/IB/0
gpu002:164181:164216 [0] NCCL INFO Ring 01 : 12 -> 8 [receive] via NET/IB/0
gpu002:164181:164216 [0] NCCL INFO Ring 01 : 4 -> 8 [receive] via NET/IB/0
- 其他通用的建议
- 使用较大的batchsize塞满gpu,但要考虑batchsize和收敛性的问题。
- 学习率使用正常的学习率 × \times ×gpu数目。
- 使用最新版本的软件。
注意事项总结
- 正确的cpu gpu绑定方式
- 不浪费多余的gpu,一个gpu一个进程
- 使用高效的data pipeline
- 优化gpu使用率达到80%以上
- 使用最新版本的cuda相关软件,cuda以及nccl
- 确保不同的gpu通信正常
分布式训练总结
- tensorflow使用horovod能够有效的利用多gpu进行训练,而且随着gpu使用的越多,horovod提升的效果越明显。
- 个人觉得使用多gpu的初衷是需要试验较大的batch size或者网络模型比较大,找到最优的临界batch size需要做大量的实验。如果为了提升网络模型训练速度,不如多用用timeline+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/