如何对深度学习训练进行性能调优?

对于深度学习训练来说,它既可能是计算密集(Compute intensive),又可能是数据密集(Data intensive),还可能是内存密集(Memory intensive)型的工作负载。

不同的模型面临的情况不同,其瓶颈点也就不尽相同。随着对模型的优化,其瓶颈点也可能随之变化。此外,提升模型训练的性能与提升整体的GPU利用率又不是完全的正相关,这也使得如何提升深度学习模型训练的性能与GPU利用率成为一个比较复杂的工程。

本文就来剖析下,如何进行模型的性能瓶颈分析以及如何进行模型调优。

优化模型性能的方法

当前绝大数的深度学习模型都是跑在Nvidia GPU服务器上的,GPU 任务会交替的使用 CPU 和 GPU 进行计算,当 CPU 计算成为瓶颈时,就会出现 GPU 等待的问题,GPU 空跑那利用率就低了,如下图所示:

那么优化的方向就是缩短一切使用 CPU 计算环节的耗时,减少 CPU 计算对 GPU 的阻塞情况。高效的利用GPU计算,是提升模型训练性能与GPU利用率最快捷的办法。

那么如何分析模型性能瓶颈呢?

1. 认识模型结构

在优化模型的训练性能前,首先要宏观上认识整个模型,将模型分为多个阶段,包括数据加载、数据预处理、模型计算和模型保存。

还要注意模型的训练策略,是采用哪种分布式训练方式,在当前数据量下是否适合模型。

此外,还要注意当前模型训练的数据,例如GPU利用率,显存、内存的占用量等。

在了解模型的一些结构和数据后,就需要宏观上分析下当前的模型,这里有一些点可以作为分析的方向:

  • 如果有A100, A800, H800等高性能服务器,中小模型尽量采用ring-Allreduce的方式进行训练,充分利用GPU性能。超大模型可以采用远程ps(即外挂cpu参数服务器)的方式进行训练。

  • 如果显存占用过大,可以在模型训练部分采用减少batch size或者梯度累积或者半精度训练等来减少显存的占用。这样做的好处是一方面可以减少机器资源的占用,另一方面减少机器间的通信时延。

  • 如果内存占用过大,可以将数据量较大的Embedding向量表,采用远程外挂的形式进行读取更新。

  • 如果IO不稳定或CPU争用较多,可以采用单独的DataService来进行处理。

  • 如果您的训练工作需要很长时间才能开始,那么大部分时间都花在下载数据上和IO处理上;

  • 如果 GPU 利用率较低,但磁盘或 CPU 利用率较高,则数据加载或预处理可能是潜在的瓶颈,可以在训练之前就对数据进行预处理。

  • 如果 GPU 利用率较低,并且 CPU 和磁盘利用率持续较低但不为零,尽管数据集足够大,但这可能意味着您的代码未有效利用底层资源,可以增加数据加载器 API 中的工作线程数量。

2. 不同阶段的模型优化方法

针对划分好的模型结构,对不同阶段进行微观上的更细致的分析,进一步优化模型性能。

1)数据加载

  1. 存储和计算是否跨域,跨城加载数据太慢导致 GPU 利用率低;

  2. 小文件是否太多,导致文件 io 耗时太长,读取会浪费很多时间在寻道上,如果是HDFS存储还会导致大量的访问NN节点,影响性能,可以合并单数据文件的大小;

  3. 存储介质是否已达到瓶颈,可以监控存储介质的繁忙度,如果达到瓶颈可以增加存储介质缓解读取性能;

  4. 是否启用多进程并行读取数据,另外可以注意线程争用问题,监控线程等待时候是否过长,可以采用私有线程池进行环境;

  5. 是否启用提前加载机制来实现 CPU 和 GPU 的并行;

2)数据预处理

  1. 是否设置开启共享内存 pin_memory,可以直接将数据放置在pin_memory中;

  2. 数据预处理逻辑太复杂,如果太复杂可以拆分为多个map函数进行执行;

  3. 利用 GPU 进行数据预处理 – Nvidia DALI;

  4. 优化 I/O 和网络操作,确保数据以与其计算相匹配的速率馈送到 GPU;

  5. 如果是A100或以上的机器,可以考虑开启numa绑定,缓解争用,提升性能;

3)模型训练

  1. 优化模型代码以确保GPU硬件得到充分利用;
  2. 是否存在大量的CPU运算,可以通过实现GPU实现或去除指定CPU设备,尽可能的让模型运行在GPU上;
  3. 模型是否存在GPU利用率不均的情况,尽可能得不在代码里指定GPU运行的卡;
  4. 对于比较复杂运行效率较低的模块,可以通过实现融合的大GPU算子提升训练速度;
  5. 避免指标和日志打印太频繁,CPU 和 GPU 频繁切换导致 GPU 利用率低;
  6. 是否部分开启XLA来提升模型的训练性能;
  7. 是否开启AMP来提升模型的训练性能;
  8. 使用最新的高性能库和 GPU 驱动程序,cuda是否升级到最新版本;
  9. 在中小模型上,如果绝大部分算子已经运行GPU上,可以通过开启force_gpu_compatible,使得训练过程中cpu到gpu拷贝通过pin_memory;

4)模型保存

  1. 避免模型保存太频繁

总之,要分析模型性能瓶颈,可以从以下两个方面入手:

宏观上,可以从认识模型结构、训练策略和资源利用情况,包括数据加载、数据预处理、模型计算和模型保存等阶段入手。
微观上,则可以针对不同阶段的模型进行更细致的分析和优化。

如何分析模型的瓶颈

上面我们给出一些性能优化的方法和措施,但是如何能够准确或者说能过看到模型的瓶颈点呢?这就离不开开源的一系列性能分析工具了。

下面我们会简单介绍并总结下使用方式和用法。

1. Nvidia SMI

如果你想获得一些硬件的指标,可以通过简单的方式:nvidia-smi

你可以通过 nvidia-smi 命令查看GPU的型号架构,驱动版本和CUDA的版本,以及不同进程的 GPU 显存和GPU利用率的使用情况。

通常你会希望训练进程占用了绝大多数的可用显存,这说明你的模型正在很好地使用 GPU。

在使用方式上,你可以直接在GPU服务器上执行nvidia-smi这个命令来获取这些信息。如果你希望获得更多的指标,可以通过 nvidia-smi dmon 命令来获得。

其它技巧:调用 watch -n 1 nvidia-smi 可以每一秒进行自动的刷新,nvidia-smi 也可以通过添加 –format=csv 以 CSV 格式输。

此外,可以通过添加 --gpu-query=... 参数来选择显示的指标,例如下面的命令:

1nvidia-smi --query-gpu=timestamp,pstate,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.free,memory.used --format=csv | tee gpu-log.csv

2. TensorBoard

TensorBoard 是 TensorFlow 提供的一个可视化工具,它可以帮助用户更好地理解和调试他们的 TensorFlow 模型。

根据 TensorFlow 版本的不同版本,TensorBoard 的使用方式也略有不同:

对于 TensorFlow 1.x 版本,使用 TensorBoard 需要执行以下步骤:

  1. 在 TensorFlow 代码中加入 tf.summary API 来记录感兴趣的变量

  2. 启动 TensorFlow session 和 summarizer,将数据写入一些汇总文件(event files)

  3. 在终端中使用 tensorboard 命令即可启动 TensorBoard。

具体步骤如下:

 1# TensorFlow 1.x 版本使用示例
2import tensorflow as tf
3
4# 定义计算图
5x = tf.placeholder(tf.float32, name="input")
6y = tf.multiply(x, 2, name="output")
7
8# 定义一个汇总操作
9tf_summary_writer = tf.summary.FileWriter('./logs')
10tf_summary_op = tf.summary.scalar('summary', y)
11
12# 启动 TensorFlow session
13with tf.Session() as sess:
14    # 初始化全局变量
15    sess.run(tf.global_variables_initializer())
16
17    # 运行模型并汇总到 event 文件
18    for step in range(10):
19        _, summary = sess.run([y, tf_summary_op], feed_dict={x: step})
20        tf_summary_writer.add_summary(summary, step)
21
22# 启动 TensorBoard
23# 假设运行 tensorboard 命令时,当前目录为 logs 目录所在的父目录
24!tensorboard --logdir=logs

对于 TensorFlow 2.x 版本,TensorBoard 的使用方式有所改变,主要变化如下:

  1. 原来的 tf.summary API 改成了 tf.keras.callbacks.TensorBoard,更加贴合 Keras 风格。

  2. 原来需要手动启动 summarizer 和 session 的过程已经被自动化了,只需要在训练模型时传入 TensorBoard 对象即可。

  3. 在命令行中启动 TensorBoard 时需要加上 -logdir 参数指定 event 文件目录。

具体步骤如下:

 1# TensorFlow 2.x 版本使用示例
2import tensorflow as tf
3
4# 定义计算图
5x = tf.Variable(1.0, name="input")
6y = tf.Variable(2.0, name="output")
7model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,), name="dense")])
8
9# 定义一个 TensorBoard 回调对象
10tb_callback = tf.keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=1)
11
12# 训练模型,并将 TensorBoard 回调对象传入
13model.compile(optimizer=tf.optimizers.Adam(learning_rate=0.1), loss='mse')
14model.fit(x, y, epochs=10, callbacks=[tb_callback], verbose=0)
15
16# 启动 TensorBoard
17# 假设运行 tensorboard 命令时,当前目录为 logs 目录所在的父目录
18!tensorboard --logdir=logs

此外,我们可以将TensorFlow 的 Profiler 和 Tensorboard进行结合,详细分析TF的性能瓶颈,例如分析数据处理的瓶颈。

不仅 TensorFlow 和 Keras,PyTorch 也开始支持了 TensorBoard。

3. Timeline

Tensorflow的Timeline模块是用于描述张量图一个工具,可以记录在会话中每个操作执行时间和资源分配及消耗的情况。

使用方法也非常简单,即在执行代码sess.run()加入参数options和run_metadata即可。

 1import tensorflow as tf
2from tensorflow.python.client import timeline
3
4a = tf.random_normal([20005000])
5b = tf.random_normal([50001000])
6res = tf.matmul(a, b)
7
8with tf.Session() as sess:
9    # add additional options to trace the session execution
10    options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
11    run_metadata = tf.RunMetadata()
12    sess.run(res, options=options, run_metadata=run_metadata)
13
14    # Create the Timeline object, and write it to a json file
15    fetched_timeline = timeline.Timeline(run_metadata.step_stats)
16    chrome_trace = fetched_timeline.generate_chrome_trace_format()
17    with open('timeline_01.json''w'as f:
18        f.write(chrome_trace)

sess.run() 加入 option和run_metadata参数,然后创建timeline对象,并写入到timeline.json文件中

查看timeline对象也很简单执行后,我们将获得一个timeline_01.json文件,打开Google Chrome,转到该页面 chrome://tracing并加载该文件。在该页面上可以查看每个操作的耗时,以及op的详细信息。

在顶部您将看到以毫秒为单位的时间轴,要获取有关某些操作的更准确信息,只需单击它即可。右侧还有一些简单的工具:选择、平移、缩放和计时,也可以通过S、W、A、D等快捷键操作。

通过这个视图,我们可以清晰的看出,哪些操作在cpu,哪些在gpu上,也可以找出模型中耗时较长的部分。

4. Nvidia Nsight Systems

Nvidia Nsight 是一个强大的性能分析工具,尤其在与 TensorFlow 结合使用时。Nvidia Nsight 提供了一套丰富的功能,可以帮助开发者分析和优化 TensorFlow 在 NVIDIA GPU 上的性能。

在 TensorFlow 中,GPU 加速是实现高效计算的关键因素之一。而要充分利用 GPU 的性能,就需要理解代码中的瓶颈和性能瓶颈所在,并针对性地进行优化,这正是 Nvidia Nsight 的作用所在。

什么是 Nsight ?

NVIDIA Nsight Developer Tools 是一套工具,提供对 NVIDIA GPU 最直接、最全面的访问,以及与之交互的底层代码。

  1. Nsight Systems:用于系统级的性能分析和优化工具。可以帮助开发人员分析整个应用程序的性能瓶颈,并提供详细的时间线和统计信息,以便定位和解决性能问题。

  2. Nsight Compute:用于 CUDA 核心级别的性能分析和优化工具。它提供了深入的指令级分析,可以查看每个 CUDA 核心的活动情况、内存访问模式、算术指令等,帮助开发人员优化 CUDA 程序的性能。

如何安装使用?

1. 下载安装nsight

可以从下面这个网址下载安装nsight工具包:

https://developer.nvidia.com/gameworksdownload#?dn=nsight-systems-2022-4

如果在安装的过程中报错can't locate Env.pm in @INC,可以安装下面的组件

yum install perl-Env

下载好后,直接bash运行即可安装,在安装过程中选择accept其协议并注意安装地址。

 2. 下载安装nvtx

nvtx 安装包可以从 https://pypi.org/project/nvtx/ 下载,并注意TF的版本与py的版本。

对于TF2还可以使用https://pypi.org/project/nvtx-plugins/ nvtx-plugins 插件进行安装,可以跟踪更多的信息

3. 使用方法

在sess.run 的节点前后加上如下语句:

1rng = nvtx.start_range(message='Iteration_'+str(idx), color='blue')
2loss = self._step(samples, labels, idx == 0)
3nvtx.end_range(rng)

并在启动的时候这样进行启动即可记录下其profiler信息。

1nsys profile --sample=none --backtrace=none --cudabacktrace=none --cpuctxsw=none --trace-fork-before-exec=true python main.py

然后可以通过下的Nsight System 软件进行打开查看。

Nsight System相比timeline可以查看更加详细的性能,同时会将多个stream执行都放在一起。如果使用nv定义的TF还可以查看不同计算对应的Op算子。

5. Nvidia DLProf

DLProf 是对 Nvidia Nsight 的一个封装。你可以通过 dlprof python main.py 来收集训练过程中的指标。它会生成两个文件:sqlite 和 qdrep,以及 events_folder。接下来可以使用 TensorBoard 来基于 events_folder 进行可视化展示。

DLProf更详细使用说明可以参考:

https://docs.nvidia.com/deeplearning/frameworks/dlprof-user-guide/index.html

以下程序代码范例是使用 TensorFlow 1.15 训练 ResNet50 模型。其同时可链接 DLProf 参数,在训练模型时执行剖析。

 1dlprof --nsys_opts="--sample=cpu --trace 'nvtx,cuda,osrt,cudnn'" \
2--profile_name=/ecan/tf_a100_profiling --nsys_base_name=resnet50_tf_fp32_b408 \
3--output_path=/ecan/tf_a100_profiling --tb_dir=resnet50_tf_fp32_b408 \
4--force=true --iter_start=20 --iter_stop=40 \
5python main.py \
6    --arch resnet50 \
7    --mode train \
8    --data_dir /ecan/tfr \
9    --export_dir /ecan/results \
10    --batch_size 256 \
11    --num_iter 100 \
12    --iter_unit batch \
13    --results_dir /ecan/results \
14    --display_every 100 \
15    --lr_init 0.01 \
16    --seed 12345

python main.py 以后的程序代码,是开始针对 ResNet50 模型(取自 NVIDIA DeepLearningExamples GitHub 储存库)进行训练。开头的 dlprof 命令设定,是用于进行剖析的 DLProf 参数。下列 DLProf 参数是用于设定输出档案和文件夹名称:

  • profile_name

  • base_name

  • output_path

  • tb_dir

force 参数设为 true,以覆盖现有的输出档案。iter_start 和 iter_stop 参数指定剖析工具注意的迭代范围。如果是较大的模型,请限制剖析量,因为产生的档案会快速变大。

DLProf 使用内部的 NVIDIA Nsight Systems 剖析器,而 nsys_opts 参数可用于传递 NVIDIA Nsight 参数。sample 参数用于指定是否收集 CPU 样本。trace 参数用于选择追踪的呼叫。

在此设定中,我们选择收集 nvtx API、CUDA API、操作系统运行时间,以及 CUDNN API 呼叫。DLProf 可以与预设参数搭配使用,例如 dlprof python main.py,预设参数可以提供良好的涵盖范围。我们在此处使用更多选项,示范如何透过 DLProf 自定义 NVIDIA Nsight 参数,并获得更详细的剖析输出。

DLProf 呼叫产生两个档案,sqlite 和 qdrep,以及 events_folder。这些档案包含剖析器追踪的所有运算。您可以将 Qdrep 档案馈入 Nsight Systems,在其中目视检查剖析输出。您可以从命令行以及透过具有可视化用户接口的应用程序,使用 Nsight Systems 剖析器。

总结

深度学习训练可以是计算密集、数据密集或内存密集型的工作负载,不同模型面临的情况和瓶颈点也不相同。

为了优化模型训练性能,需要缩短使用CPU计算的耗时,减少CPU对GPU的阻塞情况,充分利用GPU计算。

在分析模型性能瓶颈时,首先要了解模型的结构和数据,包括数据加载、数据预处理、模型计算和模型保存等阶段,针对不同阶段,可以采取不同的优化方法。

此外,为了分析模型的瓶颈,可以使用多种开源性能分析工具。其中,Nvidia SMI可查看硬件指标和GPU利用率;TensorBoard可用于可视化分析TensorFlow模型;Timeline能够记录Op操作执行时间和资源消耗;Nvidia Nsight Systems用于系统级性能分析和优化和更加详细的执行时间;DLProf封装了Nvidia Nsight,可以对剖析文件生成更加友好和丰富的可视化展示。由于担心文章过长,后面有时间我们再针对性的说明各个工具如何更细粒度的使用和找出模型的瓶颈点。


 

如果觉得这篇文章对你有所帮助,
请点一下或者在看,是对我的肯定和支持~

请使用浏览器的分享功能分享到微信等