本文参考编译自NVIDIA Blog
软件性能分析是达到系统最佳效能的关键,数据科学和机器学习应用程序也是如此。在 GPU 加速深度学习的时代,当剖析深度神经网络时,必须了解 CPU、GPU,甚至是可能会导致训练或推理变慢的内存瓶颈
01
使用 GPU 的第一个重要工具是 nvidia-smi Linux 命令。此命令会显示出与 GPU 有关的实用统计数据,例如内存用量、功耗以及在 GPU 上执行的进程。目的是查看是否有充分利用 GPU 执行模型。
首先,是检查利用了多少 GPU 内存。通常是希望看到模型使用了大部分的可用 GPU 内存,尤其是在训练深度学习模型时,因为表示已充分利用GPU。功耗是 GPU 利用率的另一个重要指标。通常,启动的 CUDA 或 Tensor 核心越多,消耗的 GPU 功率越高。
如图 1 所示,未充分利用GPU。此结论是根据两个指标获得:
GPU-Util显示利用率为 62%,证实了此结论。解决方法之一是增加批次大小。启动更多核心,以处理更大的批次。于此情形下,即可充分利用 GPU。
增加批次大小及进行相同的 Python 程序呼叫。如图 2 所示,GPU 利用率为 98%。检查功耗和内存用量,即可证实此结果,它们已接近极限。
您已经完成初步优化,使用较大的批次大小,即几乎占用所有 GPU 内存的批次大小,是在深度学习领域中提高 GPU 利用率最常使用的优化技术。
nvidia-smi 显示的不是仅有功耗和内存用量。您也可以尝试 nvidia-smi dmon,以滚动方式列出更多的 GPU 统计数据,如图 3。
每一个 GPU 都有多个串流多处理器(streaming multiprocessors),执行 CUDA 核心。使用众多串流多处理器表示已充分利用 GPU。如图 3 所示,串流多处理器利用率在呼叫开始时大约为 0%,之后在实际训练开始时上升至 90 几。除串流多处理器利用率外,nvidia-smi dmon 也会列出下列统计资料:
截至目前为止,范例仅使用一个 GPU。在有多个 GPU 的情况下,nvidia-smi 和 nvidia-smi dmon 会分别显示出各个 GPU 的指标。在有多个 GPU 时,可以利用的另一个工具是 nvidia-topo -m。此呼叫会显示出 GPU 装置的拓扑以及彼此连接的方式。
图 4 所示为 DGX A100 系统的拓扑配置,有 8 个 A100 GPU 与 NVLink 连接。选择特定 GPU 执行工作负载时,建议选择与 NVLink 连接的 GPU,因为它们具有较高的带宽,尤其是在 DGX-1 系统上。
截至目前为止,我们已经示范如何使用 nvidia-smi 工具分析 GPU 的利用率。这些指标系指出是否有充分利用 GPU。在建模时,应始终以彻底利用 GPU 为目标,以充分利用加速运算。
(Lady注:Jetson产品不支持nvidia-smi命令。)
02
GPU 利用率是进行剖析和优化之极佳的起点。您可以采用 DLProf、PyProf 等工具,进行更多详细的建模分析。您也可以利用使用者介面目视检查程序代码。Deep Learning Profiler(DLProf)支持 TensorBoard,让您可以目视检查模型。
DLProf更详细使用说明可以参考:
https://docs.nvidia.com/deeplearning/frameworks/dlprof-user-guide/index.html
以下程序代码范例是使用 TensorFlow 1.15 训练 ResNet50 模型。其同时可链接 DLProf 参数,在训练模型时执行剖析。
dlprof --nsys_opts="--sample=cpu --trace 'nvtx,cuda,osrt,cudnn'" \
--profile_name=/ecan/tf_a100_profiling --nsys_base_name=resnet50_tf_fp32_b408 \
--output_path=/ecan/tf_a100_profiling --tb_dir=resnet50_tf_fp32_b408 \
--force=true --iter_start=20 --iter_stop=40 \
python main.py \
--arch resnet50 \
--mode train \
--data_dir /ecan/tfr \
--export_dir /ecan/results \
--batch_size 256 \
--num_iter 100 \
--iter_unit batch \
--results_dir /ecan/results \
--display_every 100 \
--lr_init 0.01 \
--seed 12345
(可以左右拖动代码)
python main.py 以后的程序代码,是开始针对 ResNet50 模型(取自 NVIDIA DeepLearningExamples GitHub 储存库)进行训练。开头的 dlprof 命令设定,是用于进行剖析的 DLProf 参数。下列 DLProf 参数是用于设定输出档案和文件夹名称:
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 剖析器。使用以下命令启动 TensorBoard:
tensorboard --logdir events_folder
图 5 所示为 DLProf 插件 TensorBoard 范例。
DLProf 插件 TensorBoard 提供大量的模型信息,从迭代花费的平均时间,到前 10 名的耗时核心。若需要更多与 DLProf 用户接口有关的信息,请参阅:
https://docs.nvidia.com/deeplearning/frameworks/tensorboard-plugin-user-guide/#features
图 6 所示为与训练有关的运行时间指标。20 次迭代花费的总时间为 12.3 秒,该命令定义从第 20 次迭代开始,到第 40 次迭代停止,每一次迭代平均 588 毫秒。
每一次迭代平均花费 588 毫秒时,表示未利用 A100 支持的新精度类型 TF32。TF32 在矩阵乘法中使用较少的位,同时提供相同的模型准确度,因此可加快迭代速度。除需要处理的位较少外,TF32 同时利用了 Tensor 核心,一种深度学习的专用硬件,有助于加快矩阵乘法和累加运算。Volta (V100)、Turing (T4) 和 Ampere (A100) 世代 GPU 皆具有 Tensor 核心。
TF32 在 NVIDIA NGC TensorFlow 和 PyTorch 容器中是预设为启用,并由 NVIDIA_TF32_OVERRIDE=0 和 NVIDIA_TF32_OVERRIDE=1 环境变量控制。
在启用 TF32 后,进行相同的呼叫,而不变更任何参数。图 7 呈现出前 10 名 GPU 运算以及是否使用 Tensor 核心(TC)。
您可以看到某些运算已经使用Tensor 核心,非常好。查看每一次迭代花费的平均时间,以检查是否有加速效果。
在切换至TF32 精度时,平均迭代时间从588 毫秒缩短成399 毫秒。切换一个环境变量即可大幅加速。重点在于是否可以做到比399 毫秒更好。您知道是由DLProf 提出此建议,因此可以做到比588 毫秒更好。
DLProf 不仅提供大量的模型信息,同时会提出改进建议。在此范例中,其建议启用 XLA 和 AMP(automatic mixed precision,自动混合精度)。XLA 是以加快线性代数运算为目标的线性代数编译程序。数值精度描述是用于表示值的位数。混合精度是以运算方法结合不同的数值精度。将某些张量上的储存的需求和内存流量减半,能以较低的精度训练深度学习网络,以达到高传输量。混合精度可以加快大型矩阵到矩阵乘加运算的训练速度。
想要启用 XLA 和 AMP,请在 NVIDIA 容器中设定以下环境变量 container:
export TF_XLA_FLAGS="--tf_xla_auto_jit=2"
export TF_ENABLE_AUTO_MIXED_PRECISION=1
最近,大多数储存库皆已内建XLA 和AMP,通常只需传递相关参数。在此范例中,它们是 use_xla 和 use_tf_amp。在启用 XLA 和 AMP 之后,可以让模型有效率地使用 Tensor 核心、减少需要的内存数量,并利用更快的线性代数运算。
如图10 所示,几乎所有符合Tensor 核心条件的运算,皆已使用Tensor 核心,圆饼图中没有粉红色分类。这是理想的情况,然而更重要的是有助于缩短训练时间。
平均迭代时间从 399 毫秒以及 588 毫秒缩短成 341 毫秒。使用半精度产生的内存用量较少。为了进行公平的比较,请勿变更混合精度的批次大小。启用 AMP 可以使模型的批次大小比全浮点精度高出一倍,并进一步缩短训练时间。
总结来说,首先采用 TF32 精度及缩短训练时间。然后,启用 AMP 和 XLA,并进一步缩短使用 DLProf 辅助剖析时的训练时间。
03
PyTorch 和 PyProf
本节示范如何在使用 PyTorch 建立模型时进行剖析。截至目前为止,我们已经示范数种优化技术。在 PyTorch 中,使用 TF32 和 AMP优化模型。
接着遵循更进阶的途径,在程序代码基础中加入额外的程序代码。此外,直接使用 PyProf 和 Nsight Systems 剖析器,无须呼叫 DLProf。您仍可以使用 DLProf 和 TensorBoard 剖析 PyTorch 模型,因为 DLProf 亦可支持 PyTorch。但是,我们想要示范替代的剖析方式。
您可以挑选需要剖析的项目,例如仅剖析第 17 次迭代。在资料迭代循环中,检查是否处于第 17 次迭代。如果是,则使用剖析器,开始和结束标记包围执行正向传递、损失计算、梯度计算(反向)及更新参数(步进)的程序代码行。
从相同的储存库取用 ResNet50 训练程序代码。在训练程序代码中变更剖析,并增加 pyprof 参数,以针对唯一的正向传递启用剖析。您可以留下反向传播,并任意设定范围,然后推送至此分支,以供参考。在变更之后进行呼叫,以执行 PyTorchResNet50 训练及剖析:
nsys profile --trace 'nvtx,cuda,osrt,cudnn' -c cudaProfilerApi --stop-on-range-end true \
--show-output true --sample=cpu --export=sqlite \
-o /ecan/pytorch_a100_profiling/resnet50_pytorch_fp32_b256 \
python main.py /ecan/imagenet_small \
--raport-file raport.json -j16 -p 100 --lr 2.048 \
--optimizer-batch-size 256 --warmup 8 --arch resnet50 \
-c fanin --label-smoothing 0.1 \
--lr-schedule cosine --training-only --mom 0.875 --wd 3.0517578125e-05 -b 256\
--epochs 1 --workspace /ecan/results \
--pyprof
(左右滑动代码)
这一次,直接呼叫 Nsight Systems 剖析器。您已经知道 trace、sample 和 output (-o) 参数。增加 -c cudaProfilerApi –stop-on-range-endtrue 参数以通知剖析器,已导入开始和停止标记,使剖析器仅剖析两者之间的事件。将 –show-output 参数设为 true 时,会将目标进程 stdout 和 stderr 数据流打印至控制台。
此呼叫会产生两个档案:qdrep 和 sqlite。在 TensorFlow 中已使用 TensorBoard 的 event_files 文件夹,但是未碰触 qdrep 档案。这一次是使用 qdrep,在 Nsight Systems 应用程序中目视检查剖析结果。
以下程序代码范例是使用 PyProf 呼叫,分析核心:
python -m pyprof.parse (resulting_sqlite_file_from_our_call) > a_file
python -m pyprof.prof a_file -w 100 -c idx,trace,sil,tc,flops,bytes,kernel \
| (read -r; printf “%s\n” “$REPLY”; sort -k5 -n -r)
w 参数可设定字段宽度,以及 c 参数可指定需要印出的选项。有多个选项,且我们选择了这些选项,完整列表如下。我们同依据浮点运算次数排序,进行更好的分析,否则,依据执行顺序排序。
我们提供一些来自清单顶部的核心。前几个是批次正规化核心。您也可以识别呼叫档案的行号,例如 resnet50.py:201。有助于进一步了解这些核心统计数据,因为模型中可能有多个批次正规化。最后一行是使用半精度的矩阵乘法。它同时使用 Tensor 核心,非常好。
变更先前之PyProf 呼叫的最后一行,以取得花在迭代正向传递上的总奈秒数:
python -m pyprof.prof a_file -w 100 -c idx,trace,sil,tc,flops,bytes,kernel \
| awk ‘{total+=$3}END{print total}’
呼叫的结果为 188,388,811 ns (188.4 ms)。截至目前为止,已使用 FP32 精度类型完成剖析。您已经知道切换至 TF32 精度类型,可以将程序代码优化。切换 NVIDIA_TF32_OVERRIDE 环境变量,即可利用 TF32 精度类型。
如果训练和剖析呼叫相同,但是这一次是启用 TF32 精度类型时,总时间为 110,250,534 ns (110.25 ms)。在切换至 TF32 之后,运行时间几乎减半。
您已习惯在 TensorFlow 上进行优化,现在可以在 PyTorch 上,将程序代码优化。还有一个步骤:启用混合精度,并检查是否可以进一步将程序代码优化。
nsys profile --trace 'nvtx,cuda,osrt,cudnn' -c cudaProfilerApi --stop-on-range-end true \
--show-output true --sample=cpu --export=sqlite \
-o /ecan/pytorch_a100_profiling/resnet50_pytorch_amp_b256 \
python main.py /ecan/imagenet_small \
--raport-file raport.json -j16 -p 100 --lr 2.048 \
--optimizer-batch-size 256 --warmup 8 --arch resnet50 \
-c fanin --label-smoothing 0.1 \
--lr-schedule cosine --training-only --mom 0.875 --wd 3.0517578125e-05 -b 256 \
--amp --static-loss-scale 128 \
--epochs 1 --workspace /ecan/results \
--pyprof
大部分参数都与先前的呼叫相同,除 amp 和 static-loss-scale 参数外。amp 参数启用 AMP,因为程序代码基础可为其提供支持。static-loss-scale 参数调整损失。若需要更多与 ResNet50 训练参数有关的信息,请参阅 ResNet50 v1.5For PyTorch 指南中的命令行选项一节。
在开启AMP 模式之情况下,执行呼叫的程序代码范例时,获得72,860,695 ns (72.86 ms)。这是好消息,因为已使用混合精度进一步将程序代码优化。在TensorFlow 上可以获得类似的改善。虽然TensorFlow 已进行额外的优化(XLA),也可以仅使用AMP,在PyTorch 上获得进一步的改善。
04
使用 Nsight Systems 进行剖析
截至目前为止,您已经使用透过剖析器呼叫从训练中收集的统计资料。您同时已利用 PyProf 快速浏览模型中使用的核心。您使用 TensorBoard 和 DLProf 插件产生绝佳的可视化。在本文开头,您是使用 nvidia-smi 检查 GPU 利用率。如果您认为还不足够,而想要深入探索时,无须担心,我们还有更多的内容。
在完成包含剖析器呼叫的训练之后,取得 qdrep 档案。现在,让我们透过 NVIDIA Nsight Systems 剖析器的用户接口,更深入地分析模型。若需要更多信息,请参阅 Nsight Systems 使用指南。https://docs.nvidia.com/nsight-systems/UserGuide/index.html
即使将剖析限制为仅限迭代正向传播,也可以使用Nsight Systems 剖析器目视检查大量信息。有时候我们会放大此画面的特定区域,以进行进一步分析。想要仔细查看,请将训练的开头放大,并聚焦于几毫秒。
首先看到一些绿色的内存运算,接着是卷积运算。然后,开始将批次正规化。不出所料,下一步就是启用函式。于此范例中,它是ReLU。最后,看到执行最大池化。这是在程序代码基础和大多数ResNet 模型中看到的顺序。您也可以查看堆栈追踪,以取得更多与选择之运算有关的信息,在选取时会变成青绿色。
在结束本篇文章之前,我们想要示范另一种优化方法。在PyTorch 中,可以变更记忆体格式。通常是使用以下格式储存数据:
[ number of elements in the batch, number of channels (depth or number of filters), height, width ]
PyTorch 以 [n, h, w, c] 格式运作。以 [n,h, w, c] 格式处理类批次正规化层的速度较快。最耗时的运算是批次正规化,如图 14 所示。此外,Tensor 核心原生采用 [n,h, w, c] 格式。基本上,透过变更记忆体格式,可以在处理类批次正规化层时节省一些时间,且可在CUDNN 核心中避免一些格式转换的时间。
在先前的呼叫中增加 –memoryformat nchw 即可,且让您可以使用 [n,c, h, w] 记忆体格式。在采用 [n,c, h, w] 记忆体格式之后,训练不再需要记忆体格式转换操作,例如 nhwcToNchwKernel 和 nchwToNhwcKernel,请参见图 18。因此可以节省更多时间。换言之,您已透过变更记忆体格式,再次完成优化。为了确认这一点,请计算花在核心的总时间。我们的结果是 45,631,828 ns(45.6 ms)。在采用 [n,c, h, w] 记忆体格式时,大约为 70 毫秒。利用记忆体格式优化技术进一步缩短运行时间。
总结
本文详细介绍了如何使用各种工具剖析深度学习模型:nvidia-smi、DLProf 和 PyProf,以及 NVIDIA Nsight Systems 剖析器。每一个工具都可以指出不同层级的效能改善机会。剖析是使用两个常见的深度学习框架执行:PyTorch 和 TensorFlow。DeepLearningExamples GitHub 储存库中提供了程序代码范例,同时有 PyProf 和 PyTorch 呼叫的程序代码变更。建议您复制这些步骤,以便能更熟悉剖析工具。
https://github.com/ethem-kinginthenorth/DeepLearningExamples