From b10bead33c7c67b6b31dbf55899ffc691de6bbb5 Mon Sep 17 00:00:00 2001 From: lilong12 Date: Thu, 14 May 2020 13:26:32 +0800 Subject: [PATCH] Modify the best practice guider for multi-node gpu training (#2094) * modify the doc, test=develop --- .../dist_training_gpu.rst | 120 +++++------------- 1 file changed, 34 insertions(+), 86 deletions(-) diff --git a/doc/fluid/advanced_guide/performance_improving/multinode_training_improving/dist_training_gpu.rst b/doc/fluid/advanced_guide/performance_improving/multinode_training_improving/dist_training_gpu.rst index 7f9ed7631..ebe02dc6f 100644 --- a/doc/fluid/advanced_guide/performance_improving/multinode_training_improving/dist_training_gpu.rst +++ b/doc/fluid/advanced_guide/performance_improving/multinode_training_improving/dist_training_gpu.rst @@ -7,34 +7,39 @@ 开始优化您的GPU分布式训练任务 --------------------------- -PaddlePaddle Fluid可以支持在现代GPU [#]_ 服务器集群上完成高性能分布式训练。 -通常可以通过以下方法优化在多机多卡环境训练性能,建议在进行性能优化时, -检查每项优化点并验证对应提升,从而提升最终的性能。 +PaddlePaddle Fluid支持在现代GPU [#]_ 服务器集群上完成高性能分布式训练。通常可以通过以下方法优化在多机多卡环境训练性能,建议在进行性能优化时,检查每项优化点并验证对应提升,从而提升最终的性能。 -一个简单的验证当前的训练程序是否需要进一步优化性能的方法, -是查看GPU的计算利用率 [#]_ ,通常用 :code:`nvidia-smi` 命令查看。 -如果GPU利用率较低,则可能存在较大的优化空间。 -下面主要从环境变量设置、训练策略设置、数据准备和训练方式四个方向介绍GPU分布式训练中常用的方法。 +一个简单的验证当前的训练程序是否需要进一步优化性能的方法,是查看GPU的计算利用率 [#]_ ,通常用 :code:`nvidia-smi` 命令查看。如果GPU利用率较低,则可能存在较大的优化空间。下面主要从数据准备、训练策略设置和训练方式三个方面介绍GPU分布式训练中常用的优化方法。 -1、环境变量设置 -============= +1、数据准备 +=========== -环境变量设置表 +数据读取的优化在GPU训练中至关重要,尤其在不断增加batch_size提升吞吐时,计算对reader性能会有更高对要求,优化reader性能需要考虑的点包括: -.. csv-table:: - :header: "调节项", "可选值", "说明" - :widths: 3, 3, 5 + - 使用 :code:`DataLoader` 。参考 `这里 `_ 使用DataLoader,并建议开启 :code:`use_double_buffer` 。 + - reader返回uint8类型数据。图片在解码后一般会以uint8类型存储,如果在reader中转换成float类型数据,会将数据体积扩大4倍。直接返回uint8数据,然后在GPU上转化成float类型进行训练可以提升数据读取效率。 + - 减少reader初始化时间 (infinite read)。在训练任务开始执行第一轮训练时,reader开始不断异步地从磁盘或其他存储中读取数据并执行预处理,然后将处理好的数据填充到队列中供计算使用。从0开始填充这个队列直到数据可以源源不断供给计算,需要一定时间的预热。所以,如果每轮训练都重新填充队列,会产生一些时间的开销。所以,在使用DataLoader时,可以让reader函数不断地产生数据,直到训练循环结束: - ":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-block:: python + :linenos: -说明: + def infinite_reader(file_path): + while True: + with open(file_path) as fn: + for line in fn: + yield process(line) + + def train(): + ... + for pass_id in xrange(NUM_PASSES): + if pass_id == 0: + data_loader.start() + for batch_id in (iters_per_pass): + exe.run() + data_loader.reset() -- 关于 :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` 。 +另外,可以使用DALI库提升数据处理性能。DALI是NVIDIA开发的数据加载库,更多内容请参考 `官网文档 `_ 。飞桨中如何结合使用DALI库请参考 `使用示例 `_ 。 2、训练策略设置 =========== @@ -48,9 +53,11 @@ PaddlePaddle Fluid可以支持在现代GPU [#]_ 服务器集群上完成高性 ":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:`use_hierarchical_allreduce` ", "bool", "False", "分级式reduce" ":code:`num_iteration_per_drop_scope`", "int", "1", "scope drop频率,设置每隔几个batch的迭代之后执行一次清理scope" ":code:`fetch_frequency`", "int", "1", "fetch的刷新频率" + ":code:`fuse_bn_act_ops`", "bool", "False", "是否开启batch normalization和激活函数的融合" + ":code:`fuse_elewise_add_act_ops`", "bool", "False", "是否开启elementwise add函数和激活函数的融合" 说明: @@ -58,7 +65,7 @@ PaddlePaddle Fluid可以支持在现代GPU [#]_ 服务器集群上完成高性 - 关于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>`_ 。 +- 关于操作融合:通过参数融合可以提升训练性能。 设置这些参数可以参考: @@ -88,67 +95,12 @@ PaddlePaddle Fluid可以支持在现代GPU [#]_ 服务器集群上完成高性 exe.run([]) -3、数据准备 -=========== - -1、使用GPU完成部分图片预处理 - -如果可能,使用GPU完成部分数据预处理,比如图片Tensor的归一化: - -.. code-block:: python - :linenos: - - image = fluid.layers.data() - img_mean = fluid.layers.create_global_var([3, 1, 1], 0.0, "float32", name="img_mean", persistable=True) - img_std = fluid.layers.create_global_var([3, 1, 1], 0.0, "float32", name="img_std", persistable=True) - t1 = fluid.layers.elementwise_sub(image / 255.0, img_mean, axis=1) - image = fluid.layers.elementwise_div(t1, img_std, axis=1) - -对输入的图片Tensor,使用 :code:`fluid.layers` 完成图片数据归一化预处理, -这样可以减轻CPU预处理数据的负担,提升总体训练速度。 - -2、优化reader性能 - -数据读取的优化在GPU训练中至关重要,尤其在不断增加batch_size提升吞吐时,计算对reader性能会有更高对要求, -优化reader性能需要考虑的点包括: - - - 使用 :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: - - .. code-block:: python - :linenos: - - def infinite_reader(file_path): - while True: - with open(file_path) as fn: - for line in fn: - yield process(line) - - def train(): - ... - for pass_id in xrange(NUM_PASSES): - if pass_id == 0: - pyreader.start() - for batch_id in (iters_per_pass): - exe.run() - pyreader.reset() - -4、训练方式 +3、训练方式 =========== 1、Local SGD -GPU多机多卡同步训练过程中存在慢trainer现象, -即每步中训练快的trainer的同步通信需要等待训练慢的trainer。 -由于每步中慢trainer的rank具有随机性, -因此我们使用局部异步训练的方式——LocalSGD, -通过多步异步训练(无通信阻塞)实现慢trainer时间均摊, -从而提升同步训练性能。 -Local SGD训练方式主要有三个参数,分别是: +GPU多机多卡同步训练过程中存在慢trainer现象,即每步中训练快的trainer的同步通信需要等待训练慢的trainer。由于每步中慢trainer的rank具有随机性,因此我们使用局部异步训练的方式——LocalSGD,通过多步异步训练(无通信阻塞)实现慢trainer时间均摊,从而提升同步训练性能。Local SGD训练方式主要有三个参数,分别是: .. csv-table:: :header: "选项", "类型", "可选值", "说明" @@ -163,18 +115,14 @@ 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 +具体的Local SGD的训练代码可以参考:https://github.com/PaddlePaddle/Fleet/tree/develop/examples/local_sgd/resnet 2、使用混合精度训练 -V100 GPU提供了 `Tensor Core `_ 可以在混合精度计算 -场景极大的提升性能。使用混合精度计算的例子可以参考: -https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification#using-mixed-precision-training +V100 GPU提供了 `Tensor Core `_ 可以在混合精度计算场景极大的提升性能。使用混合精度计算的例子可以参考:https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification#using-mixed-precision-training -目前Paddle只提供在两个模型(ResNet, BERT)的混合精度计算实现并支持static loss scaling,其他模型使用混合精度也 -可以参考以上的实现完成验证。 +目前Paddle只提供在两个模型(ResNet, BERT)的混合精度计算实现并支持static loss scaling,其他模型使用混合精度也可以参考以上的实现完成验证。 附录 ---- -- GitLab