提交 56b421a5 编写于 作者: S ShenLiang 提交者: Yi Liu

Update dist_training_gpu.rst (#1154)

* dist training on gpu, test=document_preview

* fix the doc

* fix doc

* fix doc

* add the link

* test=develop test=document_preview
上级 3166645f
.. _best_practice_dist_training_gpu:
#####################
分布式GPU训练优秀实践
分布式GPU训练最佳实践
#####################
开始优化您的GPU分布式训练任务
-------------------------
---------------------------
PaddlePaddle Fluid可以支持在现代GPU [#]_ 服务器集群上完成高性能分布式训练。
通常可以通过以下方法优化在多机多卡环境训练性能,建议在进行性能优化时,
检查每项优化点并验证对应提升,最终获得最优性能。
一个简单的验证当前的训练程序是否需要进一步优化性能的方法,
是查看GPU的计算利用率 [#]_ ,通常用 :code:`nvidia-smi`命令查看。
是查看GPU的计算利用率 [#]_ ,通常用 :code:`nvidia-smi` 命令查看。
如果GPU利用率较低,则可能存在较大的优化空间。
下面主要从环境变量设置、训练策略设置、数据准备和训练方式四个方向介绍GPU分布式训练中常用的方法。
下列表格中列出本文将介绍的所有可优化点的概述:
1、环境变量设置
=============
可配置项一览
++++++++++
环境变量设置表
.. csv-table:: GPU分布式训练性能调节项
:header: "调节项", "可选值说明", "配置方法"
.. csv-table::
:header: "调节项", "可选值", "说明"
:widths: 3, 3, 5
"通信模式", "pserver模式;NCCL2模式(collective [#]_ )", "配置方法参考::ref:`cluster_howto`"
"执行模式", "单进程;单进程ParallelGraph;多进程", "配置方法参考::ref:`cluster_howto`"
"同步AllReduce操作", "开启则使每次调用等待AllReduce同步", "设置环境变量 :code:`FLAGS_sync_nccl_allreduce`"
"CPU线程数", "int值,配置使用的CPU线程数", "参考本篇后续说明"
"预先分配足够的显存", "0~1之间的float值,预先分配显存的占比", "设置环境变量 :code:`FLAGS_fraction_of_gpu_memory_to_use`"
"scope drop频率", "int值,设置每隔N个batch的迭代之后执行一次清理scope", "设置 :code:`fluid.ExecutionStrategy().num_iteration_per_drop_scope`"
"fetch频率", "代码配置", "参考本篇后续说明"
"启用RDMA多机通信", "如果机器硬件支持,可以选择开启RDMA支持", "配置环境变量 :code:`NCCL_IB_DISABLE` "
"使用GPU完成部分图片预处理", "代码配置", "参考本篇后续说明"
"设置通信频率(batch merge)", "代码配置", "参考本篇后续说明"
"优化reader性能", "代码优化", "参考本篇后续说明"
"混合精度", "FP32训练;混合FP32,FP16训练(在V100下启用TensorCore)", "参考项目:`图像分类 <https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification>`_ "
":code:`FLAGS_sync_nccl_allreduce`", "0,1", "是否同步AllReduce操作。1表示开启,每次调用等待AllReduce同步"
":code:`FLAGS_fraction_of_gpu_memory_to_use`", "0~1之间的float值", "预先分配显存的占比"
":code:`NCCL_IB_DISABLE` ", "0,1", "是否启用RDMA多机通信。如果机器硬件支持,可以设置1,开启RDMA支持"
说明:
选择通信模式和执行模式
+++++++++++++++++++
- 关于 :code:`FLAGS_sync_nccl_allreduce` ,配置 :code:`FLAGS_sync_nccl_allreduce=1` 让每次allreduce操作都等待完成,可以提升性能,详细原因和分析可以参考:https://github.com/PaddlePaddle/Paddle/issues/15049。
- 关于 :code:`FLAGS_fraction_of_gpu_memory_to_use` ,配置 :code:`FLAGS_fraction_of_gpu_memory_to_use=0.95` ,0.95是指95%的显存会预先分配。设置的范围是0.0~1.0。注意,设置成0.0会让每次显存分配都调用 :code:`cudaMalloc` 这样会极大的降低训练性能。
- 关于 :code:`NCCL_IB_DISABLE` ,在使用NCCL2模式训练时,其会默认尝试开启RDMA通信,如果系统不支持,则会自动降级为使用TCP通信。可以通过打开环境变量 :code:`NCCL_DEBUG=INFO` 查看NCCL是否选择了开启RDMA通信。如果需要强制使用TCP方式通信,可以设置 :code:`NCCL_IB_DISABLE=1` 。
GPU分布式训练场景,使用多进程+NCCL2模式(collective)通常可以获得最好的性能。参考 :ref:`cluster_howto` 配置您的程序使用多进程NCCL2模式训练。
在多进程模式下,每台服务器的每个GPU卡都会对应启动一个训练进程,
集群中的所有进程之间会互相通信完成训练。以此方式最大限度的降低进程内部资源抢占的开销。
对比在单进程开启ParallelGraph方法,多进程模式不但可以获得更高性能,
而且无需考虑reader在多卡下io性能不足的问题,直接使用多进程提升数据读取IO效率。
2、训练策略设置
===========
使用ParallelGraph模式相对而言会减少多进程管理,并提升性能,而且可以无需修改代码,只需要开启下列开关即可:
训练参数设置表
.. code-block:: bash
:linenos:
export FLAGS_enable_parallel_graph=1
如果是单机多卡模式,同样可以通过开启ParallelGraph来提升性能:
.. code-block:: bash
:linenos:
.. csv-table::
:header: "选项", "类型", "默认值", "说明"
:widths: 3, 3, 3, 5
export FLAGS_enable_parallel_graph=1
export FLAGS_sync_nccl_allreduce=1
":code:`num_threads`", "int", "1", "CPU线程数"
":code:`nccl_comm_num`", "int", "1", "nccl通信器数量"
":code:`fuse_all_reduce_ops`", "bool", "False", "多卡训练时,将AllReduce操纵进行融合"
":code:`use_hierarchical_allreduce` ", "bool", "False","分级式reduce"
":code:`num_iteration_per_drop_scope`", "int", "1", "scope drop频率,设置每隔几个batch的迭代之后执行一次清理scope"
":code:`fetch_frequency`", "int", "1", "fetch的刷新频率"
注:在单机多卡ParallelGraph模式下,
配置 :code:`FLAGS_sync_nccl_allreduce=1` 让每次allreduce操作都等待完成,可以提升性能,
详细原因和分析可以参考:https://github.com/PaddlePaddle/Paddle/issues/15049
说明:
- 关于设置合适的CPU线程数 :code:`num_threads` 和nccl通信器数量 :code:`nccl_comm_num` 。PaddlePaddle Fluid使用“线程池” [#]_ 模型调度并执行Op,Op在启动GPU计算之前,通常需要CPU的协助,然而如果Op本身占用时间很小,“线程池”模型下又会带来额外的调度开销。使用多进程模式时,如果神经网络的计算图 [#]_ 节点间有较高的并发度,即使每个进程只在一个GPU上运行,使用多个线程可以更大限度的提升GPU利用率。nccl通信器数量 :code:`nccl_comm_num` 可以加快GPU之间的通信效率,建议单机设置为1,多机设置为2。针对CPU线程数 :code:`num_threads` ,建议单机设置为1,多机设置为 :code:`nccl_comm_num` +1。
- 关于AllReduce融合 :code:`fuse_all_reduce_ops` ,默认情况下会将同一layer中参数的梯度的AllReduce操作合并成一个,比如对于 :code:`fluid.layers.fc` 中有Weight和Bias两个参数,打开该选项之后,原本需要两次AllReduce操作,现在只用一次AllReduce 操作。此外,为支持更大粒度的参数梯度融合,Paddle提供了 :code:`FLAGS_fuse_parameter_memory_size` 和 :code:`FLAGS_fuse_parameter_groups_size` 两个环境变量选项。用户可以指定融合AllReduce操作之后,每个AllReduce操作的梯度字节数,比如希望每次AllReduce调用传输16MB的梯度,:code:`export FLAGS_fuse_parameter_memory_size=16` ,经验值为总通信量的十分之一。可以指定每次AllReduce操作的最大层数,即到达该层数就进行AllReduce,如指定50层 :code:`export FLAGS_fuse_parameter_groups_size=50` 。注意:目前不支持sparse参数梯度。
- 关于使用分级式reduce :code:`use_hierarchical_allreduce` 。对于多机模式,针对小数据量的通信,Ring AllReduce通信效率低,采用Hierarchical AllReduce可以解决该问题。
- 关于降低scope drop频率 :code:`num_iteration_per_drop_scope` 和fetch频率 :code:`fetch_frequency` 。减少scope drop和fetch频率,可以减少频繁的变量内存申请、释放和拷贝,从而提升性能。
- 其他训练策略的参数可以参考 `这里 <../best_practice/training_best_practice.html>`_ 。
设置合适的CPU线程数
+++++++++++++++++
PaddlePaddle Fluid使用“线程池” [#]_ 模型调度并执行Op,Op在启动GPU计算之前,
通常需要CPU的协助,然而如果Op本身占用时间很小,“线程池”模型下又会带来额外的调度开销。
使用多进程模式时,如果神经网络的计算图 [#]_ 节点间有较高的并发度,
即使每个进程只在一个GPU上运行,使用多个线程可以更大限度的提升GPU利用率。
这项配置需要根据运行模型的情况来配置,通常在多进程模式,设置线程数为1和4,
然后观察是否存在提升,然后逐步调整此项配置。设置CPU线程数的方法参考:
设置这些参数可以参考:
.. code-block:: python
:linenos:
exe_st = fluid.ExecutionStrategy()
exe_st.num_threads = 1
exe = fluid.ParallelExecutor(
True,
avg_loss.name,
main_program=train_prog,
exec_strategy=exe_st,
num_trainers=num_trainers,
trainer_id=trainer_id)
预先分配足够的显存
+++++++++++++++
通过设置环境变量 :code:`FLAGS_fraction_of_gpu_memory_to_use=0.95` 设置预先分配的显存占比,
比如0.95是指95%的显存会预先分配。设置的范围是0.0~1.0。注意,
设置成0.0会让每次显存分配都调用 :code:`cudaMalloc` 这样会极大的降低训练性能。
降低scope drop频率和fetch频率
+++++++++++++++++++++++++++
减少scope drop和fetch频率,可以减少频繁的变量内存申请、释放和拷贝,
从而提升性能。配置这两项的方法参考下面代码:
.. code-block:: python
:linenos:
exe_st = fluid.ExecutionStrategy()
strategy.num_iteration_per_drop_scope = 30
exe = fluid.ParallelExecutor(
True,
avg_loss.name,
main_program=train_prog,
exec_strategy=exe_st,
num_trainers=num_trainers,
trainer_id=trainer_id)
for pass_id in xrange(PASS_NUM):
dist_strategy = DistributedStrategy()
dist_strategy.nccl_comm_num = 2 #建议多机设置为2,单机设置为1
exec_strategy = fluid.ExecutionStrategy()
exe_st.num_threads = 3 #建议多机设置为nccl_comm_num+1,单机设置为1
exec_strategy.num_iteration_per_drop_scope = 30 #scope drop频率
dist_strategy.exec_strategy = exec_strategy
dist_strategy.fuse_all_reduce_ops = True #AllReduce是否融合
...
with fluid.program_guard(main_prog, startup_prog): #组网
params = model.params
optimizer = optimizer_setting(params)
dist_optimizer = fleet.distributed_optimizer(optimizer, strategy=dist_strategy)
dist_optimizer.minimize(avg_cost)
...
for pass_id in range(PASS_NUM):
batch_id = 0
while True:
if batch_id % 30 == 0:
fetched = exe.run(fetch_list)
if batch_id % fetch_frequency == 0: #fetch频率
fetched = exe.run(main_prog, fetch_list)
else:
exe.run([])
启用RDMA多机通信
++++++++++++++
在使用NCCL2模式训练时,其会默认尝试开启RDMA通信,如果系统不支持,
则会自动降级为使用TCP通信。
可以通过打开环境变量 :code:`NCCL_DEBUG=INFO` 查看NCCL是否选择了开启RDMA通信。
如果需要强制使用TCP方式通信,可以设置 :code:`NCCL_IB_DISABLE=1` 。
3、数据准备
===========
使用GPU完成部分图片预处理
++++++++++++++++++++++
1、使用GPU完成部分图片预处理
如果可能,使用GPU完成可以部分数据预处理,比如图片Tensor的归一化:
如果可能,使用GPU完成部分数据预处理,比如图片Tensor的归一化:
.. code-block:: python
:linenos:
......@@ -152,63 +107,14 @@ PaddlePaddle Fluid使用“线程池” [#]_ 模型调度并执行Op,Op在启
对输入的图片Tensor,使用 :code:`fluid.layers` 完成图片数据归一化预处理,
这样可以减轻CPU预处理数据的负担,提升总体训练速度。
增大batch_size或使用设置通信频率(batch merge)
++++++++++++++++++++++++++++++++++++++++++
分布式同步训练,跨节点通信或多或少会带来性能影响,增大训练的batch_size,
可以保持通信开销不变的情况下,增大计算吞吐从而降低通信在整个训练过程中的占比来提升总体的训练吞吐。
然而增大batch_size会带来同等比例的显存消耗提升,为了进一步的增大batch_size,Fluid提供“batch merge”功能,
通过在一个GPU上串行计算多个小的batch并积累梯度,然后再执行多机多卡之间的通信,
此模式同样也可以被称为“可变通信频率“。使用batch merge功能,在同样的模型,
可以极大的增加batch size,提升多机训练的总吞吐。
使用方法可以参考实例:https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification/dist_train
优化reader性能
+++++++++++++
2、优化reader性能
数据读取的优化在GPU训练中至关重要,尤其在不断增加batch_size提升吞吐时,计算对reader性能会有更高对要求,
优化reader性能需要考虑的点包括:
1. 使用 :code:`pyreader`
参考 `这里 <../../user_guides/howto/prepare_data/use_py_reader.html>`_
使用pyreader,并开启 :code:`use_double_buffer`
2. reader返回uint8类型数据
图片在解码后一般会以uint8类型存储,如果在reader中转换成float类型数据,会将数据体积扩大4倍。直接返回uint8数据,然后在GPU
上转化成float类型进行训练
3. reader pin memory
reader读取的数据会在训练时组成batch,并从CPU拷贝到GPU上,如果在CPU上分配pin memory内存,这个拷贝过程可以通过硬件
DMA完成拷贝提升性能。在使用pyreader的方式下,可以使用下面的实例代码开启pin memory batch reader:
.. code-block:: python
:linenos:
def batch_feeder(batch_reader, pin_memory=True, img_dtype="uint8"):
# batch((sample, label)) => batch(sample), batch(label)
def _feeder():
for batch_data in batch_reader():
sample_batch = []
label_batch = []
for sample, label in batch_data:
sample_batch.append(sample)
label_batch.append([label])
tensor = core.LoDTensor()
label = core.LoDTensor()
place = core.CUDAPinnedPlace() if pin_memory else core.CPUPlace()
tensor.set(np.array(sample_batch, dtype=img_dtype, copy=False), place)
label.set(np.array(label_batch, dtype="int64", copy=False), place)
yield [tensor, label]
return _feeder
pyreader.decorate_tensor_provider(
batch_feeder(
paddle.batch(rd, batch_size=batch_size_per_gpu),
pin_memory=True,
img_dtype='uint8'
)
)
4. 减少reader初始化时间 (infinite read)
- 使用 :code:`pyreader` 。参考 `这里 <../../user_guides/howto/prepare_data/use_py_reader.html>`_ 使用pyreader,并开启 :code:`use_double_buffer` 。
- reader返回uint8类型数据。图片在解码后一般会以uint8类型存储,如果在reader中转换成float类型数据,会将数据体积扩大4倍。直接返回uint8数据,然后在GPU上转化成float类型进行训练
- 减少reader初始化时间 (infinite read)
在训练任务开始执行第一轮训练时,reader开始异步的,不断的从磁盘或其他存储中读取数据并执行预处理,然后将处理好的数据
填充到队列中供计算使用。从0开始填充这个队列直到数据可以源源不断供给计算,需要一定时间的预热。所以,如果每轮训练
都重新填充队列,会产生一些时间的开销。所以,在使用pyreader时,可以让reader函数不断的产生数据,直到训练循环手动break:
......@@ -231,13 +137,41 @@ PaddlePaddle Fluid使用“线程池” [#]_ 模型调度并执行Op,Op在启
exe.run()
pyreader.reset()
4、训练方式
===========
1、Local SGD
GPU多机多卡同步训练过程中存在慢trainer现象,
即每步中训练快的trainer的同步通信需要等待训练慢的trainer。
由于每步中慢trainer的rank具有随机性,
因此我们使用局部异步训练的方式——LocalSGD,
通过多步异步训练(无通信阻塞)实现慢trainer时间均摊,
从而提升同步训练性能。
Local SGD训练方式主要有三个参数,分别是:
.. csv-table::
:header: "选项", "类型", "可选值", "说明"
:widths: 3, 3, 3, 5
":code:`use_local_sgd`", "bool", "False/True", "是否开启Local SGD,默认不开启"
":code:`local_sgd_is_warm_steps`", "int", "大于0", "训练多少轮之后才使用Local SGD方式训练"
":code:`local_sgd_steps`", "int", "大于0", "Local SGD的步长"
说明:
- Local SGD的warmup步长 :code:`local_sgd_is_warm_steps` 影响最终模型的泛化能力,一般需要等到模型参数稳定之后在进行Local SGD训练,经验值可以将学习率第一次下降时的epoch作为warmup步长,之后再进行Local SGD训练。
- Local SGD步长 :code:`local_sgd_steps` ,一般该值越大,通信次数越少,训练速度越快,但随之而来的时模型精度下降。经验值设置为2或者4。
具体的Local SGD的训练代码可以参考:
https://github.com/PaddlePaddle/Fleet/tree/develop/examples/local_sgd/resnet
使用混合精度训练
++++++++++++++
2、使用混合精度训练
V100 GPU提供了 `Tensor Core <https://www.nvidia.com/en-us/data-center/tensorcore/>`_ 可以在混合精度计算
场景极大的提升性能。使用混合精度计算的例子可以参考:
https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification#using-mixed-precision-training
https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification#using-mixed-precision-training
目前Paddle只提供在两个模型(ResNet, BERT)的混合精度计算实现并支持static loss scaling,其他模型使用混合精度也
可以参考以上的实现完成验证。
......@@ -247,6 +181,5 @@ https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classif
.. [#] 现代GPU:指至少支持运行 `CUDA <https://developer.nvidia.com/cuda-downloads>`_ 版本7.5以上的GPU
.. [#] GPU利用率:这里指GPU计算能力被使用部分所占的百分比
.. [#] Collective定义参考:https://en.wikipedia.org/wiki/Message_Passing_Interface#Collective_basics
.. [#] https://en.wikipedia.org/wiki/Thread_pool
.. [#] https://en.wikipedia.org/wiki/Data-flow_diagram
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册