From ae2bfc054807487dab4ebffbf1dc54893143eaea Mon Sep 17 00:00:00 2001 From: Yiqun Liu Date: Thu, 27 Feb 2020 17:00:22 +0800 Subject: [PATCH] Update the content of training best practice for single node (#1845) * Update link in training_best_practice. test=develop * Update the documentation. test=develop * Refine the format. test=develop * Change the format of title. test=develop --- .../analysis_tools/index_cn.rst | 2 + .../training_best_practice.rst | 284 +++++++++++++----- 2 files changed, 204 insertions(+), 82 deletions(-) diff --git a/doc/fluid/advanced_guide/performance_improving/analysis_tools/index_cn.rst b/doc/fluid/advanced_guide/performance_improving/analysis_tools/index_cn.rst index 78d599265..3bb5ba2c5 100644 --- a/doc/fluid/advanced_guide/performance_improving/analysis_tools/index_cn.rst +++ b/doc/fluid/advanced_guide/performance_improving/analysis_tools/index_cn.rst @@ -1,3 +1,5 @@ +.. _api_guide_analysis_tools: + ############### 性能优化分析及工具 ############### diff --git a/doc/fluid/advanced_guide/performance_improving/singlenode_training_improving/training_best_practice.rst b/doc/fluid/advanced_guide/performance_improving/singlenode_training_improving/training_best_practice.rst index 7a8e2573e..95e71abd7 100644 --- a/doc/fluid/advanced_guide/performance_improving/singlenode_training_improving/training_best_practice.rst +++ b/doc/fluid/advanced_guide/performance_improving/singlenode_training_improving/training_best_practice.rst @@ -1,4 +1,5 @@ -.. training_best_practice: +.. _api_guide_singlenode_training_best_practice: + ##################### 单机训练优秀实践 @@ -13,7 +14,7 @@ PaddlePaddle Fluid可以支持在现代CPU、GPU平台上进行训练。如果 1. 网络构建过程中的配置优化 -============= +================== 这部分优化与具体的模型有关,在这里,我们列举出一些优化过程中遇到过的一些示例。 @@ -35,93 +36,145 @@ cuDNN是NVIDIA提供的深度神经网络计算库,其中包含了很多神经 bias_attr=None, use_cudnn=True, act=None, - name=None) + name=None, + data_format="NCHW") 在 :code:`use_cudnn=True` 时,框架底层调用的是cuDNN中的卷积操作。 通常cuDNN库提供的操作具有很好的性能表现,其性能明显优于Paddle原生的CUDA实现,比如 :code:`conv2d` 。但是cuDNN中有些操作的性能较差,比如: :code:`conv2d_transpose` 在 :code:`batch_size=1` 时、:code:`pool2d` 在 :code:`global_pooling=True` 时等,这些情况下,cuDNN实现的性能差于Paddle的CUDA实现,建议手动设置 :code:`use_cudnn=False` 。 -1.2 使用融合功能的API -^^^^^^^^^^^^^^^^ +1.2 减少模型中Layer的个数 +^^^^^^^^^^^^^^^^^^ + +为方便用户使用,飞桨提供一些不同粒度的Layer,其中有些Layer的组合可以通过单个Layer完成。比如: -Paddle提供一些粗粒度的API,这些API融合了多个细粒度API的计算,比如: +(1) :code:`fluid.layers.softmax_with_cross_entropy` ,该操作其实是 :code:`fluid.layers.softmax` 和 :code:`fluid.layers.cross_entropy` 的组合,因此如果模型中有出现 .. code-block:: python logits = fluid.layers.softmax(logits) loss = fluid.layers.cross_entropy(logits, label, ignore_index=255) -和 +可以直接替换成 .. code-block:: python loss = fluid.layers.softmax_with_cross_entropy(logits, label, ignore_index=255, numeric_stable_mode=True) -用户网络配置中使用融合功能的API,通常能取得更好的计算性能。 + +(2) 如果模型中需要对数据进行标准化,可以直接使用 :code:`fluid.layers.data_norm` ,而不用通过一系列layer组合出数据的标准化操作。 + +因此,建议在构建模型时优先使用飞桨提供的单个Layer完成所需操作,这样减少模型中Layer的个数,并因此加速模型训练。 + 2. 数据准备优化 ============= -2.1 分析数据准备部分的耗时 -^^^^^^^^^^^^^^^^ +数据准备通常分为两部分:第一部分是数据加载,即程序从磁盘中加载训练/预测数据;第二部分是数据预处理,程序对加载的数据进行预处理,比如图像任务通常需要进行数据增强、Shuffle等。 +这两部分需要用户根据自己的模型需要进行设置,只需要最后得到Data Reader接口即可。Data Reader返回iterable对象,可以每次返回一条样本或者一组样本。代码示例如下: -数据准备部分通常分为两个部分:数据读取部分和预处理部分。 +.. code-block:: python + + def data_reader(width, height): + def reader(): + while True: + yield np.random.uniform(-1, 1,size=width*height), np.random.randint(0,10) + return reader + train_data_reader = data_reader(32, 32) -- 数据读取部分:用户需要在Python端从磁盘中加载数据,然后将数据feed到Fluid的执行器中。 -- 数据预处理部分:用户需要在Python端进行数据预处理,比如图像任务通常需要进行数据增强、裁剪等。 -Fluid提供了两种数据读取方式:**同步数据读取** 和 **异步数据读取**,详情请参考文档 `如何准备数据 `_ 。 +Paddle提供了两种方式从Data Reader中读取数据: :ref:`user_guide_use_numpy_array_as_train_data` 和 :ref:`user_guides_use_py_reader` ,详情请参考文档 :ref:`user_guide_prepare_data` 。 -2.1.1 同步数据读取 ->>>>>>>>>>>>>>> +2.1 同步数据读取 +^^^^^^^^^^^^^^^^ 同步数据读取是一种简单并且直观的数据准备方式,代码示例如下: .. code-block:: python + image = fluid.data(name="image", shape=[None, 1, 28, 28], dtype="float32") + label = fluid.data(name="label", shape=[None, 1], dtype="int64") + # 模型定义 + # …… + prediction = fluid.layers.fc(input=image, size=10) + loss = fluid.layers.cross_entropy(input=prediction, label=label) + avg_loss = fluid.layers.mean(loss) + # …… + # 读取数据 + # paddle.dataset.mnist.train()返回数据读取的Reader,每次可以从Reader中读取一条样本,batch_size为128 + train_reader = paddle.batch(paddle.dataset.mnist.train(), 128) + # 读取数据 end = time.time() for batch_id, batch in enumerate(train_reader): data_time = time.time() - end # 训练网络 - executor.run(feed=[...], fetch_list=[...]) + executor.run(feed={...}, fetch_list=[...]) batch_time = time.time() - end end = time.time() -用户通过调用自己编写的reader函数,reader每次输出一个batch的数据,并将数据传递给执行器。因此数据准备和执行是顺序进行的,用户可通过加入Python计时函数 time.time() 来统计数据准备部分和执行部分所占用的时间。 -2.1.2 异步数据读取 ->>>>>>>>>>>>>>> +用户首先需要通过 :code:`fluid.data` 定义模型的输入,然后根据输入构建模型,最后从事先自定义的Reader函数中获取一个batch的数据,并将数据传递给执行器。 + +采用同步数据读取方式时,用户可通过加入Python计时函数 :code:`time.time()` 来统计数据准备部分和执行部分所占用的时间。 +由于数据准备和执行是顺序进行的,所以程序的执行速度可能较慢。如果用户想进行模型调试的话,同步数据读取是一个不错的选择。 -Paddle里面使用py_reader接口来实现异步数据读取,代码示例如下: + +2.2 异步数据读取 +^^^^^^^^^^^^^^^^ + +Paddle里面使用 paddle.fluid.io. :ref:`cn_api_fluid_io_DataLoader` 接口来实现异步数据读取,代码示例如下: .. code-block:: python - # 启动py_reader - train_py_reader.start() + image = fluid.data(name="image", shape=[None, 1, 28, 28], dtype="float32") + label = fluid.data(name="label", shape=[None, 1], dtype="int64") + dataloader = fluid.io.DataLoader.from_generator( + feed_list=[image, label], + capacity=64, + iterable=False, + use_double_buffer=True) + # 模型定义 + # …… + prediction = fluid.layers.fc(input=image, size=10) + loss = fluid.layers.cross_entropy(input=prediction, label=label) + avg_loss = fluid.layers.mean(loss) + # …… + # 读取数据 + train_reader = paddle.batch(paddle.dataset.mnist.train(), 128) + data_loader.set_batch_generator(train_reader, places=places) + + # 启动data_loader + data_loader.start() batch_id = 0 try: end = time.time() while True: - print("queue size: ", train_py_reader.queue.size()) + print("queue size: ", data_loader.queue.size()) loss, = executor.run(fetch_list=[...]) # ... batch_time = time.time() - end end = time.time() batch_id += 1 except fluid.core.EOFException: - train_py_reader.reset() + data_loader.reset() -使用异步数据读取时,Paddle的C++端会维护一个数据队列,Python端通过单独的线程向C++端的数据队列传入数据。用户可以在训练过程中输出数据队列中数据的个数,如果queue size始终不为空,表明Python端数据准备的速度比模型执行的速度快,这种情况下Python端的数据读取可能不是瓶颈。 +用户首先需要通过 :code:`fluid.io.DataLoader.from_generator` 定义DataLoader对象,并使用 :code:`set_batch_generator` 方法将自定义的Reader与DataLoader绑定。 +若DataLoader被定义成不可迭代的( :code:`iterable=False` ),在训练开始之前,通过调用 :code:`start()` 方法来启动数据读取。 +在数据读取结束之后, :code:`executor.run` 会抛出 :code:`fluid.core.EOFException` ,表示训练已经遍历完Reader中的所有数据。 -此外,Paddle提供的一些FLAGS也能很好的帮助分析性能,比如通过设置 :code:`export FLAGS_reader_queue_speed_test_mode=True` ,数据队列中的训练数据在被读取之后,不会从数据队列中弹出,这样能够保证数据队列始终不为空,这样就能够很好的评估出数据读取所占的开销。**注意,FLAGS_reader_queue_speed_test_mode只能在分析的时候打开,正常训练模型时需要关闭**。 +采用异步数据读取时,Python端和C++端共同维护一个数据队列,Python端启动一个线程,负责向队列中插入数据,C++端在训练/预测过程中,从数据队列中获取数据,并将该数据从对队列中移除。 +用户可以在程序运行过程中,监测数据队列是否为空,如果队列始终不为空,表明数据准备的速度比模型执行的速度快,这种情况下数据读取可能不是瓶颈。 -2.2 优化数据准备速度的方法 -^^^^^^^^^^^^^^^^ +另外,Paddle提供的一些FLAGS也能很好的帮助分析性能。如果用户希望评估一下在完全没有数据读取开销情况下模型的性能,可以设置一下环境变量::code:`FLAGS_reader_queue_speed_test_mode` ,在该变量为True情况下,C++端从数据队列中获取数据之后,不会从数据队列中移除,这样能够保证数据队列始终不为空,从而避免了C++端读取数据时的等待开销。 -- 为降低训练的整体时间,建议用户使用异步数据读取的方式,并开启 :code:`use_double_buffer` 。此外,用户可根据模型的实际情况设置数据队列的大小。 -- 如果数据准备的时间大于模型执行的时间,或者出现了数据队列为空的情况,这时候需要考虑对Python的用户reader进行加速。常用的方法为:**使用Python多进程准备数据**。一个简单的使用多进程准备数据的示例,请参考 `YOLOv3 `_ 。 -- Python端的数据预处理,都是使用CPU完成。如果Paddle提供了相应功能的API,可将这部分预处理功能写到模型配置中,如此Paddle就可以使用GPU来完成该预处理功能,这样也可以减轻CPU预处理数据的负担,提升总体训练速度。 +**需要特别注意的是,** :code:`FLAGS_reader_queue_speed_test_mode` **只能在性能分析的时候打开,正常训练模型时需要关闭。** + +为降低训练的整体时间,建议用户使用异步数据读取的方式,并开启 :code:`use_double_buffer=True` 。用户可根据模型的实际情况设置数据队列的大小。 +如果数据准备的时间大于模型执行的时间,或者出现了数据队列为空的情况,就需要考虑对数据读取Reader进行加速。 +常用的方法是 **使用Python多进程准备数据** ,一个简单的使用多进程准备数据的示例,可以参考 `YOLOv3 `_ 。 + +Python端的数据预处理,都是使用CPU完成。如果Paddle提供了相应功能的API,可将这部分预处理功能写到模型配置中,如此Paddle就可以使用GPU来完成该预处理功能,这样也可以减轻CPU预处理数据的负担,提升总体训练速度。 3. 模型训练相关优化 ============= @@ -129,85 +182,152 @@ Paddle里面使用py_reader接口来实现异步数据读取,代码示例如 3.1 执行器介绍 ^^^^^^^^^^^^^^^^ -目前Paddle中有两个执行器, :code:`Executor` 和 :code:`ParallelExecutor` ,这两个执行器的区别: +目前Paddle的Python API中提供了 :code:`fluid.compiler.CompiledProgram` 的概念,用户可以通过 :code:`CompiledProgram` 将传入的program进行编译。 +如果希望采用数据并行模式训练,只需要将 :code:`CompiledProgram` 返回的对象调用一下 :code:`with_data_parallel` 即可,最后统一通过 :code:`executor.run(…)` 执行compiled_program。 -执行调度器 ->>>>>>>>>>>>>>> +虽然统一通过 :code:`executor.run(…)` 接口来执行,实际底层的执行策略有两种,对应C++部分的两个执行器,即 :code:`Executor` 和 :code:`ParallelExecutor` ,如果用户采用数据并行模式,C++部分使用的是 :code:`ParallelExecutor` ,除此之外都是使用 :code:`Executor` 。 +这两个执行器的差别: -.. csv-table:: +.. csv-table:: :header: "执行器 ", "执行对象", "执行策略" :widths: 3, 3, 5 ":code:`Executor`", ":code:`Program`", "根据 :code:`Program` 中Operator定义的先后顺序依次运行。" ":code:`ParallelExecutor`", "SSA Graph", "根据Graph中各个节点之间的依赖关系,通过多线程运行。" -为了更好的分析模型, :code:`ParallelExecutor` 内部首先会将输入的 :code:`Program` 转为SSA Graph,然后根据 :code:`build_strategy` 中的配置,通过一系列的Pass对Graph进行优化,比如:memory optimize,operator fuse等优化。最后根据 :code:`execution_strategy` 中的配置执行训练任务。 -此外, :code:`ParallelExecutor` 支持数据并行,即单进程多卡和多进程多卡,关于 :code:`ParallelExecutor` 的具体介绍请参考 `文档 `_ 。 +可以看出, :code:`Executor` 的内部逻辑非常简单,但性能可能会弱一些,因为 :code:`Executor` 对于program中的操作是串行执行的。 +而 :code:`ParallelExecutor` 首先会将program转变为计算图,并分析计算图中节点间的连接关系,对图中没有相互依赖的节点(OP),通过多线程并行执行。 -为了统一 :code:`ParallelExecutor` 接口和 :code:`Executor` 接口,Paddle提供了 :code:`fluid.compiler.CompiledProgram` 接口,在数据并行模式下,该接口底层调用的是 :code:`ParallelExecutor` 。 +因此, :code:`Executor` 是一个轻量级的执行器,目前主要用于参数初始化、模型保存、模型加载。 +:code:`ParallelExecutor` 是 :code:`Executor` 的升级版本,目前 :code:`ParallelExecutor` 主要用于模型训练,包括单机单卡、单机多卡以及多机多卡训练。 -3.2 BuildStrategy中参数配置说明 -^^^^^^^^^^^^^^^^ -BuildStrategy配置选项 ->>>>>>>>>>>>>>> +:code:`ParallelExecutor` 执行计算图之前,可以对计算图进行一些优化,比如使计算图中的一些操作是In-place的、将计算图中的参数更新操作进行融合等。 +用户还可以调整 :code:`ParallelExecutor` 执行过程中的一些配置,比如执行计算图的线程数等。这些配置分别是构建策略(BuildStrategy)和执行策略(ExecutionStrategy)参数来设置的。 + + +一个简单的使用示例如下: + +.. code-block:: python + + build_strategy = fluid.BuildStrategy() + build_strategy.enable_inplace = True + build_strategy.fuse_all_optimizer_ops=True + + exec_strategy = fluid.ExecutionStrategy() + exec_strategy.num_threads = 4 + + train_program = fluid.compiler.CompiledProgram(main_program).with_data_parallel( + loss_name=loss.name, + build_strategy=build_strategy, + exec_strategy=exec_strategy) + + place = fluid.CUDAPlace(0) + exe = Executor(place) + # 使用DataLoader读取数据,因此执行时不需要设置feed + fetch_outs = exe.run(train_program, fetch_list=[loss.name]) -.. csv-table:: + + +3.2 构建策略(BuildStrategy)配置参数介绍 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +BuildStrategy中提供了一些关于计算图优化的策略,这些策略可以在不同程度上提升模型的训练速度,但是其中一些策略与模型的结构有关,比如 :code:`fuse_all_optimizer_ops` 不支持sparse梯度,我们正在积极的完善这些策略,并在下一个版本将这些策略默认打开。 + +构建策略的详细介绍如下: + +.. csv-table:: :header: "选项", "类型", "默认值", "说明" :widths: 3, 3, 3, 5 - ":code:`reduce_strategy`", ":code:`fluid.BuildStrategy.ReduceStrategy`", ":code:`fluid.BuildStrategy.ReduceStrategy.AllReduce`", "使用数据并行训练模型时选用 :code:`AllReduce` 模式训练还是 :code:`Reduce` 模式训练." - ":code:`enable_backward_optimizer_op_deps`", "bool", "FALSE", "在反向操作和参数更新操作之间添加依赖,保证在所有的反向操作都运行结束之后才开始运行参数更新操作." - ":code:`fuse_all_optimizer_ops`", "bool", "FALSE", "对模型中的参数更新算法进行融合." - ":code:`fuse_all_reduce_ops`", "bool", "FALSE", "多卡训练时,将all_reduce Op进行融合." - ":code:`fuse_relu_depthwise_conv`", "bool", "FALSE", "如果模型中存在relu和depthwise_conv,并且是连接的,即relu->depthwise_conv,该选项可以将这两个操作合并为一个." - ":code:`fuse_broadcast_ops`", "bool", "FALSE", "在 :code:`Reduce` 模式下,对最后的多个Broadcast操作融合为一个." - ":code:`mkldnn_enabled_op_types`", "list", "{}", "如果是CPU训练,可以用 :code:`mkldnn_enabled_op_types` 指明模型中的那些操作可以使用MKLDNN库,如果不进行设置,模型可以使用MKLDNN库的所有操作都会使用MKLDNN库." + ":code:`reduce_strategy`", ":code:`fluid.BuildStrategy.ReduceStrategy`", ":code:`fluid.BuildStrategy.ReduceStrategy.AllReduce`", "使用数据并行训练模型时选用 :code:`AllReduce` 模式训练还是 :code:`Reduce` 模式训练。" + ":code:`enable_backward_optimizer_op_deps`", "bool", "True", "在反向操作和参数更新操作之间添加依赖,保证在所有的反向操作都运行结束之后才开始运行参数更新操作。" + ":code:`fuse_all_optimizer_ops`", "bool", "False", "对模型中的参数更新算法进行融合。" + ":code:`fuse_all_reduce_ops`", "bool", "False", "多卡训练时,将all_reduce操作进行融合。" + ":code:`fuse_relu_depthwise_conv`", "bool", "False", "如果模型中存在relu和depthwise_conv,并且是连接的,即relu->depthwise_conv,该选项可以将这两个操作合并为一个。" + ":code:`fuse_broadcast_ops`", "bool", "False", "在 :code:`Reduce` 模式下,将最后的多个Broadcast操作融合为一个。" + ":code:`mkldnn_enabled_op_types`", "list", "{}", "如果是CPU训练,可以用 :code:`mkldnn_enabled_op_types` 指明模型中的那些操作可以使用MKLDNN库。默认情况下,模型中用到的操作如果在Paddle目前支持的可以使用mkldnn库计算的列表中,这些操作都会调用mkldnn库的接口进行计算。" + ":code:`debug_graphviz_path`", "str", "{}", "将Graph以graphviz格式输出到debug_graphviz_path所指定的文件中。" -说明: - - 关于 :code:`reduce_strategy` ,在 :code:`ParallelExecutor` 对于数据并行支持两种参数更新模式: :code:`AllReduce` 和 :code:`Reduce` 。在 :code:`AllReduce` 模式下,各个节点上计算得到梯度之后,调用 :code:`AllReduce` 操作,梯度在各个节点上聚合,然后各个节点分别进行参数更新。在 :code:`Reduce` 模式下,参数的更新操作被均匀的分配到各个节点上,即各个节点计算得到梯度之后,将梯度在指定的节点上进行 :code:`Reduce` ,然后在该节点上,最后将更新之后的参数Broadcast到其他节点。即:如果模型中有100个参数需要更新,训练时使用的是4个节点,在 :code:`AllReduce` 模式下,各个节点需要分别对这100个参数进行更新;在 :code:`Reduce` 模式下,各个节点需要分别对这25个参数进行更新,最后将更新的参数Broadcast到其他节点上. - - 关于 :code:`enable_backward_optimizer_op_deps` ,在多卡训练时,打开该选项可能会提升训练速度。 - - 关于 :code:`fuse_all_optimizer_ops` ,目前只支持SGD、Adam和Momentum算法。**注意:目前不支持sparse参数梯度** 。 - - 关于 :code:`fuse_all_reduce_ops` ,多GPU训练时,可以对 :code:`AllReduce` 操作进行融合,以减少 :code:`AllReduce` 的调用次数。默认情况下会将同一layer中参数的梯度的 :code:`AllReduce` 操作合并成一个,比如对于 :code:`fluid.layers.fc` 中有Weight和Bias两个参数,打开该选项之后,原本需要两次 :code:`AllReduce` 操作,现在只用一次 :code:`AllReduce` 操作。此外,为支持更大粒度的参数梯度融合,Paddle提供了 :code:`FLAGS_fuse_parameter_memory_size` 选项,用户可以指定融合AllReduce操作之后,每个 :code:`AllReduce` 操作的梯度字节数,比如希望每次 :code:`AllReduce` 调用传输64MB的梯度,:code:`export FLAGS_fuse_parameter_memory_size=64` 。**注意:目前不支持sparse参数梯度**。 - - 关于 :code:`mkldnn_enabled_op_types` ,支持mkldnn库的Op有:transpose, sum, softmax, requantize, quantize, pool2d, lrn, gaussian_random, fc, dequantize, conv2d_transpose, conv2d, conv3d, concat, batch_norm, relu, tanh, sqrt, abs. +参数说明: -3.3 ExecutionStrategy中的配置参数 -^^^^^^^^^^^^^^^^ -ExecutionStrategy配置选项 ->>>>>>>>>>>>>>> +(1) 关于 :code:`reduce_strategy` ,在 :code:`ParallelExecutor` 对于数据并行支持两种参数更新模式: :code:`AllReduce` 和 :code:`Reduce` 。在 :code:`AllReduce` 模式下,各个节点上计算得到梯度之后,调用 :code:`AllReduce` 操作,梯度在各个节点上聚合,然后各个节点分别进行参数更新。在 :code:`Reduce` 模式下,参数的更新操作被均匀的分配到各个节点上,即各个节点计算得到梯度之后,将梯度在指定的节点上进行 :code:`Reduce` ,然后在该节点上,最后将更新之后的参数Broadcast到其他节点。即:如果模型中有100个参数需要更新,训练时使用的是4个节点,在 :code:`AllReduce` 模式下,各个节点需要分别对这100个参数进行更新;在 :code:`Reduce` 模式下,各个节点需要分别对这25个参数进行更新,最后将更新的参数Broadcast到其他节点上。注意:如果是使用CPU进行数据并行训练,在Reduce模式下,不同CPUPlace上的参数是共享的,所以在各个CPUPlace上完成参数更新之后不用将更新后的参数Broadcast到其他CPUPlace。 + +(2) 关于 :code:`enable_backward_optimizer_op_deps` ,在多卡训练时,打开该选项可能会提升训练速度。 + +(3) 关于 :code:`fuse_all_optimizer_ops` ,目前只支持SGD、Adam和Momentum算法。 **注意:目前不支持sparse参数梯度** 。 + +(4) 关于 :code:`fuse_all_reduce_ops` ,多GPU训练时,可以对 :code:`AllReduce` 操作进行融合,以减少 :code:`AllReduce` 的调用次数。默认情况下会将同一layer中参数的梯度的 :code:`AllReduce` 操作合并成一个,比如对于 :code:`fluid.layers.fc` 中有Weight和Bias两个参数,打开该选项之后,原本需要两次 :code:`AllReduce` 操作,现在只用一次 :code:`AllReduce` 操作。此外,为支持更大粒度的参数梯度融合,Paddle提供了 :code:`FLAGS_fuse_parameter_memory_size` 选项,用户可以指定融合AllReduce操作之后,每个 :code:`AllReduce` 操作的梯度字节数,比如希望每次 :code:`AllReduce` 调用传输64MB的梯度,:code:`export FLAGS_fuse_parameter_memory_size=64` 。 **注意:目前不支持sparse参数梯度** 。 + +(5) 关于 :code:`mkldnn_enabled_op_types` ,目前Paddle的Op中可以使用mkldnn库计算的操作包括:transpose、sum、softmax、requantize、quantize、pool2d、lrn、gaussian_random、fc、dequantize、conv2d_transpose、conv2d、conv3d、concat、batch_norm、relu、tanh、sqrt、abs。 + + +3.3 执行策略(ExecutionStrategy)配置参数介绍 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +ExecutionStrategy中提供了关于计算图执行时的一些配置,这些配置可能会影响模型的训练速度。同时,这些配置与模型的结构有关,如果用户希望模型训练速度更快,可以调整一下这些配置。在后续的优化中,我们会对这部分进行优化,根据输入模型结构动态调整这些设置。 + +ExecutionStrategy配置选项说明: -.. csv-table:: +.. csv-table:: :header: "选项", "类型", "默认值", "说明" :widths: 3, 3, 5, 5 - ":code:`num_iteration_per_drop_scope`", "INT", "1", "经过多少次迭代之后清理一次local execution scope" + ":code:`num_iteration_per_drop_scope`", "INT", "100", "经过多少次迭代之后清理一次local execution scope" ":code:`num_threads`", "INT", "对于CPU:2*dev_count;对于GPU:4*dev_count. (这是一个经验值)", ":code:`ParallelExecutor` 中执行所有Op使用的线程池大小" 说明: - - 关于 :code:`num_iteration_per_drop_scope` ,框架在运行过程中会产生一些临时变量,这些变量被放在local execution scope中。通常每经过一个batch就要清理一下local execution scope中的变量,但是由于GPU是异步设备,在清理local execution scope之前需要对所有的GPU调用一次同步操作,因此耗费的时间较长。为此我们在 :code:`execution_strategy` 中添加了 :code:`num_iteration_per_drop_scope` 选项。用户可以指定经过多少次迭代之后清理一次local execution scope。 - - 关于 :code:`num_threads` ,:code:`ParallelExecutor` 根据Op之间的依赖关系确定Op的执行顺序,即:当Op的输入都已经变为ready状态之后,该Op会被放到一个队列中,等待被执行。 :code:`ParallelExecutor` 内部有一个任务调度线程和一个线程池,任务调度线程从队列中取出所有Ready的Op,并将其放到线程队列中。 :code:`num_threads` 表示线程池的大小。根据以往的经验,对于CPU任务,:code:`num_threads=2*dev_count` 时性能较好,对于GPU任务,:code:`num_threads=4*dev_count` 时性能较好。**注意:线程池不是越大越好**。 -执行策略配置推荐 ->>>>>>>>>>>>>>> +(1) 关于 :code:`num_iteration_per_drop_scope` ,框架在运行过程中会产生一些临时变量,默认每经过一个batch就要清理一下临时变量。由于GPU是异步设备,在清理之前需要对所有的GPU调用一次同步操作,因此耗费的时间较长。为此我们在execution_strategy中添加了 :code:`num_iteration_per_drop_scope` 选项。用户可以指定经过多少次迭代之后清理一次。 -- 在显存足够的前提下,建议将 :code:`exec_strategy.num_iteration_per_drop_scope` 设置成一个较大的值,比如设置 :code:`exec_strategy.num_iteration_per_drop_scope=100` ,这样可以避免反复地申请和释放内存。该配置对于一些模型的优化效果较为明显。 -- 对于一些较小的模型,比如mnist、language_model等,多个线程乱序调度op的开销大于其收益,因此推荐设置 :code:`exec_strategy.num_threads=1` 。 +(2) 关于 :code:`num_threads` ,:code:`ParallelExecutor` 根据Op之间的依赖关系确定Op的执行顺序,即:当Op的输入都已经变为ready状态之后,该Op会被放到一个队列中,等待被执行。 :code:`ParallelExecutor` 内部有一个任务调度线程和一个线程池,任务调度线程从队列中取出所有Ready的Op,并将其放到线程队列中。 :code:`num_threads` 表示线程池的大小。根据以往的经验,对于CPU任务,:code:`num_threads=2*dev_count` 时性能较好,对于GPU任务,:code:`num_threads=4*dev_count` 时性能较好。 **注意:线程池不是越大越好** 。 -CPU训练设置 ->>>>>>>>>>>>>>> -- 如果使用CPU做数据并行训练,需要指定环境变量CPU_NUM,这个环境变量指定程序运行过程中使用的 :code:`CPUPlace` 的个数。 -- 如果使用CPU进行数据并行训练,并且 :code:`build_strategy.reduce_strategy` = :code:`fluid.BuildStrategy.ReduceStrategy.Reduce` ,所有 :code:`CPUPlace` 上的参数是共享的,因此对于一些使用CPU进行数据并行训练的模型,选用 :code:`Reduce` 模式可能会更快一些。 +4. 运行时FLAGS设置优化 +================= -4. 运行时FLAGS设置 -============= -Fluid中有一些FLAGS可以有助于性能优化: +Paddle中有一些FLAGS可以有助于性能优化: -- FLAGS_fraction_of_gpu_memory_to_use表示每次分配GPU显存的最小单位,取值范围为[0, 1)。由于CUDA原生的显存分配cuMalloc和释放cuFree操作均是同步操作,非常耗时,因此将FLAGS_fraction_of_gpu_memory_to_use设置成一个较大的值,比如0.92(默认值),可以显著地加速训练的速度。 -- FLAGS_cudnn_exhaustive_search表示cuDNN在选取conv实现算法时采取穷举搜索策略,因此往往能选取到一个更快的conv实现算法,这对于CNN网络通常都是有加速的。但穷举搜索往往也会增加cuDNN的显存需求,因此用户可根据模型的实际情况选择是否设置该变量。 -- FLAGS_enable_cublas_tensor_op_math表示是否使用TensorCore加速计算cuBLAS。这个环境变量只在Tesla V100以及更新的GPU上适用,且可能会带来一定的精度损失。 +(1) :code:`FLAGS_cudnn_exhaustive_search` 表示在调用cuDNN中的卷积操作时,根据输入数据的shape等信息,采取穷举搜索的策略从算法库中选取到更快的卷积算法,进而实现对模型中卷积操作的加速。需要注意的是: + - 在搜索算法过程中需要使用较多的显存,如果用户的模型中卷积操作较多,或者GPU卡显存较小,可能会出现显存不足问题。 + - 通过穷举搜索选择好算法之后,该算法会进入Cache,以便下次运行时,如果输入数据的shape等信息不变,直接使用Cache中算法。 -5. 使用Profile工具进行性能分析 -============= +(2) :code:`FLAGS_enable_cublas_tensor_op_math` 表示是否使用TensorCore加速cuBLAS等NV提供的库中的操作。需要注意的是,这个环境变量只在Tesla V100以及更新的GPU上适用,且可能会带来一定的精度损失,通常该损失不会影响模型的收敛性。 + + +5. 优秀实践 +================= + +(1) 尽可能的使用飞桨提供的单个layer实现所需操作。 +(2) 采用异步数据读取。 +(3) 模型训练相关优化: + + - 使用ParallelExecutor作为底层执行器。单卡训练,也可以调用with_data_parallel方法。代码示例: + + .. code-block:: python + + compiled_prog = compiler.CompiledProgram( + fluid.default_main_program()).with_data_parallel( + loss_name=loss.name) + + - 如果模型中参数的梯度都是非sparse的,可以打开fuse_all_optimizer_ops选项,将多个参数更新操作融合为一个。 + - 如果是多卡训练,可以打开enable_backward_optimizer_op_deps、fuse_all_reduce_ops选项。如果想指定每次每次AllReduce操作的数据大小,可以设置 :code:`FLAGS_fuse_parameter_memory_size`,比如 :code:`export FLAGS_fuse_parameter_memory_size=1` ,表示每次AllReduce调用传输1MB的梯度。 + - 使用CPU做数据并行训练时,推荐使用Reduce模型,因为在使用CPU进行数据并行训练时,在Reduce模式下,不同CPUPlace 上的参数是共享的,所以在各个CPUPlace 上完成参数更新之后不用将更新后的参数Broadcast到其他CPUPlace上,这对提升速度也有很大帮助。 + - 如果是Reduce模式,可打开fuse_broadcast_ops选项。 + - 如果用户的模型较小,比如mnist、language_model等,可以将num_threads设为1。 + - 在显存足够的前提下,建议将 :code:`exec_strategy.num_iteration_per_drop_scope` 设置成一个较大的值,比如设置为100,这样可以避免反复地申请和释放内存。 + +目前我们正在推进这些配置自动化的工作:即根据输入的模型结构自动配置这些选项,争取在下一个版本中实现,敬请期待。 + +(4) FLAGS设置 + +.. code-block:: bash + + FLAGS_cudnn_exhaustive_search = True + FLAGS_enable_cublas_tensor_op_math = True + + +6. 使用Profile工具进行性能分析 +====================== -为方便用户更好的发现程序中的性能瓶颈,Paddle提供了多种Profile工具,这些工具的详细介绍和使用说明请参考 `性能调优 `_ 。 +为方便用户更好的发现程序中的性能瓶颈,Paddle提供了多种Profile工具,这些工具的详细介绍和使用说明请参考 :ref:`api_guide_analysis_tools` 。 -- GitLab