使用 Profiler 优化 TensorFlow 性能

本指南演示了如何使用 TensorFlow Profiler 提供的工具来跟踪 TensorFlow 模型的性能。您将学习如何了解模型在主机 (CPU)、设备 (GPU) 或主机和设备组合上的性能。

分析有助于了解模型中各种 TensorFlow 操作 (op) 的硬件资源消耗(时间和内存),并解决性能瓶颈,最终使模型执行速度更快。

本指南将引导您完成如何安装 Profiler、可用的各种工具、Profiler 收集性能数据的不同模式以及一些优化模型性能的最佳实践建议。

如果您想在 Cloud TPU 上分析模型性能,请参阅 Cloud TPU 指南

安装 Profiler 和 GPU 先决条件

使用 pip 安装 TensorBoard 的 Profiler 插件。请注意,Profiler 需要最新版本的 TensorFlow 和 TensorBoard(>=2.2)。

pip install -U tensorboard_plugin_profile

要在 GPU 上进行分析,您必须

  1. 满足 TensorFlow GPU 支持软件要求 中列出的 NVIDIA® GPU 驱动程序和 CUDA® 工具包要求。
  2. 确保 NVIDIA® CUDA® 分析工具接口 (CUPTI) 存在于路径中

    /sbin/ldconfig -N -v $(sed 's/:/ /g' <<< $LD_LIBRARY_PATH) | \
    grep libcupti
    

如果您在路径中没有 CUPTI,请通过运行以下命令将它的安装目录添加到 $LD_LIBRARY_PATH 环境变量的开头

export LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:$LD_LIBRARY_PATH

然后,再次运行上面的 ldconfig 命令以验证是否找到了 CUPTI 库。

解决权限问题

在 Docker 环境或 Linux 上使用 CUDA® 工具包运行分析时,您可能会遇到与 CUPTI 权限不足相关的问题(CUPTI_ERROR_INSUFFICIENT_PRIVILEGES)。转到 NVIDIA 开发者文档,详细了解如何在 Linux 上解决这些问题。

要解决 Docker 环境中的 CUPTI 权限问题,请运行以下命令

docker run option '--privileged=true'

Profiler 工具

从 TensorBoard 的“分析”选项卡访问 Profiler,该选项卡仅在您捕获了一些模型数据后才会出现。

Profiler 提供了一系列工具来帮助进行性能分析

  • 概述页面
  • 输入管道分析器
  • TensorFlow 统计信息
  • 跟踪查看器
  • GPU 内核统计信息
  • 内存分析工具
  • Pod 查看器

概述页面

概述页面提供模型在分析运行期间性能的顶层视图。该页面显示了主机和所有设备的汇总概述页面,以及一些提高模型训练性能的建议。您也可以在“主机”下拉菜单中选择单个主机。

概述页面以如下方式显示数据

image

  • 性能摘要:显示模型性能的高级摘要。性能摘要分为两部分

    1. 步长时间细分:将平均步长时间细分为多个类别,显示时间消耗在哪些地方

      • 编译:编译内核所花费的时间。
      • 输入:读取输入数据所花费的时间。
      • 输出:读取输出数据所花费的时间。
      • 内核启动:主机启动内核所花费的时间
      • 主机计算时间。
      • 设备间通信时间。
      • 设备上计算时间。
      • 所有其他时间,包括 Python 开销。
    2. 设备计算精度 - 报告设备计算时间中使用 16 位和 32 位计算的百分比。

  • 步长时间图表:显示所有采样步长的设备步长时间(以毫秒为单位)图表。每个步长都细分为多个类别(使用不同的颜色),显示时间消耗在哪些地方。红色区域对应于设备处于空闲状态等待主机输入数据的步长时间部分。绿色区域显示设备实际工作的时间。

  • 设备(例如 GPU)上排名前 10 的 TensorFlow 操作:显示运行时间最长的设备上操作。

    每行显示操作的自用时间(占所有操作所用时间的百分比)、累积时间、类别和名称。

  • 运行环境:显示模型运行环境的高级摘要,包括

    • 使用的主机数量。
    • 设备类型(GPU/TPU)。
    • 设备内核数量。
  • 下一步建议:报告模型何时受输入限制,并推荐可用于定位和解决模型性能瓶颈的工具。

输入管道分析器

当 TensorFlow 程序从文件读取数据时,它会以流水线方式从 TensorFlow 图的顶部开始。读取过程被划分为多个串联连接的数据处理阶段,其中一个阶段的输出是下一个阶段的输入。这种读取数据的系统称为输入管道

从文件读取记录的典型管道具有以下阶段

  1. 文件读取。
  2. 文件预处理(可选)。
  3. 从主机到设备的文件传输。

低效的输入管道会严重降低应用程序的速度。当应用程序在输入管道中花费大量时间时,则认为该应用程序受输入限制。使用从输入管道分析器获得的见解来了解输入管道效率低下的原因。

输入管道分析器会立即告诉您程序是否受输入限制,并引导您完成设备端和主机端分析,以调试输入管道中任何阶段的性能瓶颈。

查看有关输入管道性能的指南,以获取优化数据输入管道的最佳实践建议。

输入管道仪表板

要打开输入管道分析器,请选择分析,然后从工具下拉菜单中选择input_pipeline_analyzer

image

仪表板包含三个部分

  1. 摘要:总结整个输入管道,提供有关应用程序是否受输入限制的信息,以及如果受限,受限程度。
  2. 设备端分析:显示详细的设备端分析结果,包括设备步长时间以及每个步长中跨内核等待输入数据的设备时间范围。
  3. 主机端分析:显示主机端的详细分析,包括主机上输入处理时间的细分。

输入管道摘要

摘要通过显示设备时间中用于等待主机输入的百分比来报告程序是否受输入限制。如果您使用的是经过检测的标准输入管道,则该工具会报告输入处理时间的大部分消耗在何处。

设备端分析

设备端分析提供了有关在设备上花费的时间与在主机上花费的时间的见解,以及设备上花费了多少时间等待主机输入数据。

  1. 步长时间相对于步长编号的图表:显示所有采样步长的设备步长时间(以毫秒为单位)图表。每个步长都细分为多个类别(使用不同的颜色),显示时间消耗在哪些地方。红色区域对应于设备处于空闲状态等待主机输入数据的步长时间部分。绿色区域显示设备实际工作的时间。
  2. 步长时间统计信息:报告设备步长时间的平均值、标准差和范围([最小值,最大值])。

主机端分析

主机端分析报告了输入处理时间(在tf.data API 操作上花费的时间)在主机上的细分,分为几个类别

  • 按需从文件读取数据:在没有缓存、预取和交错的情况下,从文件读取数据所花费的时间。
  • 提前从文件读取数据:读取文件所花费的时间,包括缓存、预取和交错。
  • 数据预处理:在预处理操作(例如图像解压缩)上花费的时间。
  • 将数据排队以传输到设备:将数据放入输入队列以在将数据传输到设备之前花费的时间。

展开输入操作统计信息以检查各个输入操作的统计信息及其按执行时间细分的类别。

image

将出现一个源数据表,其中每个条目包含以下信息

  1. 输入操作:显示输入操作的 TensorFlow 操作名称。
  2. 计数:显示分析期间操作执行实例的总数。
  3. 总时间(以毫秒为单位):显示每个实例上花费时间的累积总和。
  4. 总时间百分比:显示操作上花费的总时间占输入处理总时间的比例。
  5. 总自用时间(以毫秒为单位):显示每个实例上花费的自用时间的累积总和。此处的自用时间衡量的是在函数体内部花费的时间,不包括在它调用的函数中花费的时间。
  6. 总自用时间百分比。显示总自用时间占输入处理总时间的比例。
  7. 类别。显示输入操作的处理类别。

TensorFlow 统计信息

TensorFlow 统计信息工具显示了在分析会话期间在主机或设备上执行的每个 TensorFlow 操作(操作)的性能。

image

该工具在两个窗格中显示性能信息

  • 上窗格最多显示四个饼图

    1. 每个操作在主机上的自执行时间的分布。
    2. 每个操作类型在主机上的自执行时间的分布。
    3. 每个操作在设备上的自执行时间的分布。
    4. 每个操作类型在设备上的自执行时间的分布。
  • 下窗格显示一个表格,该表格报告有关 TensorFlow 操作的数据,每行对应一个操作,每列对应一种数据类型(通过单击列标题对列进行排序)。单击上窗格右侧的导出为 CSV 按钮将此表格中的数据导出为 CSV 文件。

    请注意

    • 如果任何操作具有子操作

      • 操作的总“累积”时间包括在子操作中花费的时间。
      • 操作的总“自用”时间不包括在子操作中花费的时间。
    • 如果操作在主机上执行

      • 操作在设备上产生的总自用时间的百分比将为 0。
      • 设备上总自用时间的累积百分比(包括此操作)将为 0。
    • 如果操作在设备上执行

      • 此操作产生的主机上总自用时间的百分比将为 0。
      • 主机上总自用时间的累积百分比(包括此操作)将为 0。

您可以选择在饼图和表格中包含或排除空闲时间。

跟踪查看器

跟踪查看器显示一个时间线,显示

  • TensorFlow 模型执行的操作的持续时间
  • 哪个系统部分(主机或设备)执行了操作。通常,主机执行输入操作、预处理训练数据并将其传输到设备,而设备执行实际的模型训练

跟踪查看器允许您识别模型中的性能问题,然后采取措施解决这些问题。例如,在高级别上,您可以识别输入或模型训练是否占用了大部分时间。深入挖掘,您可以识别哪些操作执行时间最长。请注意,跟踪查看器限制为每个设备 100 万个事件。

跟踪查看器界面

打开跟踪查看器时,它会显示您最近的运行

image

此屏幕包含以下主要元素

  1. 时间线窗格:显示设备和主机随时间执行的操作。
  2. 详细信息窗格:显示时间线窗格中选定操作的附加信息。

时间线窗格包含以下元素

  1. 顶部栏:包含各种辅助控件。
  2. 时间轴:显示相对于跟踪开始的时间。
  3. 部分和轨道标签:每个部分包含多个轨道,左侧有一个三角形,您可以单击该三角形以展开和折叠部分。系统中的每个处理元素都有一个部分。
  4. 工具选择器:包含用于与跟踪查看器交互的各种工具,例如缩放、平移、选择和计时。使用计时工具标记时间间隔。
  5. 事件:这些显示操作执行的时间或元事件(例如训练步骤)的持续时间。
部分和轨道

跟踪查看器包含以下部分

  • 每个设备节点一个部分,用设备芯片编号和芯片内的设备节点标记(例如,/device:GPU:0 (pid 0))。每个设备节点部分包含以下轨道
    • 步骤:显示在设备上运行的训练步骤的持续时间
    • TensorFlow 操作:显示在设备上执行的操作
    • XLA 操作:显示在设备上运行的XLA 操作(操作),如果 XLA 是使用的编译器(每个 TensorFlow 操作都被转换为一个或多个 XLA 操作。XLA 编译器将 XLA 操作转换为在设备上运行的代码)。
  • 主机机器 CPU 上运行的线程一个部分,标记为“主机线程”。该部分包含每个 CPU 线程的一个轨道。请注意,您可以忽略与部分标签一起显示的信息。
事件

时间轴中的事件以不同的颜色显示;颜色本身没有特定含义。

跟踪查看器还可以显示 TensorFlow 程序中 Python 函数调用的跟踪。如果您使用 tf.profiler.experimental.start API,则可以通过在开始分析时使用 ProfilerOptions 具名元组来启用 Python 跟踪。或者,如果您使用采样模式进行分析,则可以通过在 **捕获分析** 对话框中使用下拉选项来选择跟踪级别。

image

GPU 内核统计信息

此工具显示每个 GPU 加速内核的性能统计信息和源操作。

image

该工具在两个窗格中显示信息

  • 上窗格显示一个饼图,显示总运行时间最长的 CUDA 内核。

  • 下窗格显示一个表格,其中包含每个唯一内核-操作对的以下数据

    • 按内核-操作对分组的总 GPU 运行时间的降序排列。
    • 已启动内核的名称。
    • 内核使用的 GPU 寄存器数量。
    • 以字节为单位的共享(静态 + 动态共享)内存的总大小。
    • blockDim.x, blockDim.y, blockDim.z 表示的块维度。
    • gridDim.x, gridDim.y, gridDim.z 表示的网格维度。
    • 操作是否符合使用 张量核心 的条件。
    • 内核是否包含张量核心指令。
    • 启动此内核的操作的名称。
    • 此内核-操作对出现的次数。
    • 总 GPU 运行时间(微秒)。
    • 平均 GPU 运行时间(微秒)。
    • 最小 GPU 运行时间(微秒)。
    • 最大 GPU 运行时间(微秒)。

内存分析工具

**内存分析** 工具在分析间隔期间监控设备的内存使用情况。您可以使用此工具来

  • 通过查明峰值内存使用量和相应的 TensorFlow 操作的内存分配来调试内存不足 (OOM) 问题。您还可以调试在运行 多租户 推理时可能出现的 OOM 问题。
  • 调试内存碎片问题。

内存分析工具在三个部分中显示数据

  1. 内存分析摘要
  2. 内存时间轴图
  3. 内存细分表

内存分析摘要

本节显示 TensorFlow 程序的内存分析的高级摘要,如下所示

内存分析摘要有六个字段

  1. **内存 ID**:下拉菜单,列出所有可用的设备内存系统。从下拉菜单中选择要查看的内存系统。
  2. **#分配**:在分析间隔期间进行的内存分配次数。
  3. **#释放**:在分析间隔期间进行的内存释放次数
  4. **内存容量**:您选择的内存系统的总容量(GiB)。
  5. **峰值堆使用量**:模型开始运行以来的峰值内存使用量(GiB)。
  6. **峰值内存使用量**:分析间隔期间的峰值内存使用量(GiB)。此字段包含以下子字段
    1. **时间戳**:峰值内存使用量在时间轴图上发生的时间戳。
    2. **堆栈保留**:堆栈上保留的内存量(GiB)。
    3. **堆分配**:堆上分配的内存量(GiB)。
    4. **空闲内存**:空闲内存量(GiB)。内存容量是堆栈保留、堆分配和空闲内存的总和。
    5. **碎片**:碎片百分比(越低越好)。它被计算为 (1 - 最大空闲内存块大小 / 总空闲内存) 的百分比。

内存时间轴图

本节显示内存使用量(GiB)和碎片百分比与时间(ms)的关系图。

image

X 轴表示分析间隔的时间轴(ms)。左侧的 Y 轴表示内存使用量(GiB),右侧的 Y 轴表示碎片百分比。在 X 轴上的每个时间点,总内存被细分为三个类别:堆栈(红色)、堆(橙色)和空闲(绿色)。将鼠标悬停在特定时间戳上以查看该时间点内存分配/释放事件的详细信息,如下所示

image

弹出窗口显示以下信息

  • **时间戳(ms)**:所选事件在时间轴上的位置。
  • **事件**:事件类型(分配或释放)。
  • **请求大小(GiB)**:请求的内存量。对于释放事件,这将是一个负数。
  • **分配大小(GiB)**:实际分配的内存量。对于释放事件,这将是一个负数。
  • **tf_op**:请求分配/释放的 TensorFlow 操作。
  • **步骤 ID**:发生此事件的训练步骤。
  • **区域类型**:此分配内存所属的数据实体类型。可能的值是 temp(临时)、output(激活和梯度)以及 persist/dynamic(权重和常量)。
  • **数据类型**:张量元素类型(例如,uint8 表示 8 位无符号整数)。
  • **张量形状**:正在分配/释放的张量的形状。
  • **内存使用量(GiB)**:此时正在使用的总内存。

内存细分表

此表显示分析间隔期间峰值内存使用量时的活动内存分配。

image

每个 TensorFlow 操作有一行,每行都有以下列

  • **操作名称**:TensorFlow 操作的名称。
  • **分配大小 (GiB)**:分配给此操作的总内存量。
  • **请求大小 (GiB)**:为该操作请求的总内存量。
  • **出现次数**:此操作的分配次数。
  • **区域类型**:此分配内存所属的数据实体类型。可能的值是 temp(临时)、output(激活和梯度)以及 persist/dynamic(权重和常量)。
  • **数据类型**:张量元素类型。
  • **形状**:分配的张量的形状。

Pod 查看器

Pod 查看器工具显示跨所有工作器对训练步骤的细分。

image

  • 上窗格有一个滑块,用于选择步骤号。
  • 下窗格显示一个堆叠柱状图。这是对堆叠在一起的细分步骤时间类别的概览。每个堆叠列代表一个唯一的工作器。
  • 当您将鼠标悬停在堆叠列上时,左侧的卡片将显示有关步骤细分的更多详细信息。

tf.data 瓶颈分析

tf.data 瓶颈分析工具会自动检测程序中 tf.data 输入管道中的瓶颈,并提供有关如何修复它们的建议。它适用于使用 tf.data 的任何程序,无论平台(CPU/GPU/TPU)如何。其分析和建议基于此 指南

它通过以下步骤检测瓶颈

  1. 找到最受输入限制的主机。
  2. 找到 tf.data 输入管道的最慢执行。
  3. 从分析器跟踪中重建输入管道图。
  4. 找到输入管道图中的关键路径。
  5. 将关键路径上最慢的转换识别为瓶颈。

UI 分为三个部分:**性能分析摘要**、**所有输入管道的摘要** 和 **输入管道图**。

性能分析摘要

image

本节提供分析的摘要。它报告在分析中检测到的慢速 tf.data 输入管道。本节还显示最受输入限制的主机及其最慢的输入管道,以及最大延迟。最重要的是,它会识别输入管道的哪个部分是瓶颈以及如何修复它。瓶颈信息将与迭代器类型及其长名称一起提供。

如何读取 tf.data 迭代器的长名称

长名称的格式为 Iterator::<Dataset_1>::...::<Dataset_n>。在长名称中,<Dataset_n> 与迭代器类型匹配,长名称中的其他数据集表示下游转换。

例如,考虑以下输入管道数据集

dataset = tf.data.Dataset.range(10).map(lambda x: x).repeat(2).batch(5)

来自上述数据集的迭代器的长名称将是

迭代器类型 长名称
范围 Iterator::Batch::Repeat::Map::Range
映射 Iterator::Batch::Repeat::Map
重复 Iterator::Batch::Repeat
批处理 Iterator::Batch

所有输入管道的摘要

image

本节提供所有主机上所有输入管道的摘要。通常只有一个输入管道。当使用分布式策略时,有一个主机输入管道运行程序的 tf.data 代码,以及多个设备输入管道从主机输入管道中检索数据并将其传输到设备。

对于每个输入管道,它会显示其执行时间的统计信息。如果调用时间超过 50 微秒,则该调用被计为慢速调用。

输入管道图

image

本节显示带有执行时间信息的输入管道图。您可以使用“主机”和“输入管道”来选择要查看的主机和输入管道。输入管道的执行将按执行时间的降序排列,您可以使用 **排名** 下拉菜单进行选择。

image

关键路径上的节点具有粗体轮廓。瓶颈节点(即关键路径上自时间最长的节点)具有红色轮廓。其他非关键节点具有灰色虚线轮廓。

在每个节点中,**开始时间** 指示执行的开始时间。同一个节点可能会被执行多次,例如,如果输入管道中存在 Batch 操作。如果它被执行多次,则它是第一次执行的开始时间。

**总持续时间** 是执行的挂钟时间。如果它被执行多次,则它是所有执行的挂钟时间的总和。

**自时间** 是不包括其直接子节点的重叠时间的 **总时间**。

“# 调用” 是输入管道执行的次数。

收集性能数据

TensorFlow Profiler 收集 TensorFlow 模型的主机活动和 GPU 跟踪。您可以通过编程模式或采样模式配置 Profiler 来收集性能数据。

分析 API

您可以使用以下 API 执行分析。

  • 使用 TensorBoard Keras 回调的编程模式 (tf.keras.callbacks.TensorBoard)

    # Profile from batches 10 to 15
    tb_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir,
                                                 profile_batch='10, 15')
    
    # Train the model and use the TensorBoard Keras callback to collect
    # performance profiling data
    model.fit(train_data,
              steps_per_epoch=20,
              epochs=5,
              callbacks=[tb_callback])
    
  • 使用 tf.profiler 函数 API 的编程模式

    tf.profiler.experimental.start('logdir')
    # Train the model here
    tf.profiler.experimental.stop()
    
  • 使用上下文管理器的编程模式

    with tf.profiler.experimental.Profile('logdir'):
        # Train the model here
        pass
    

  • 采样模式:使用 tf.profiler.experimental.server.start 启动带有 TensorFlow 模型运行的 gRPC 服务器,以执行按需分析。启动 gRPC 服务器并运行模型后,您可以通过 TensorBoard 分析插件中的“捕获分析”按钮捕获分析。如果 TensorBoard 实例尚未运行,请使用上面“安装分析器”部分中的脚本启动一个 TensorBoard 实例。

    例如,

    # Start a profiler server before your model runs.
    tf.profiler.experimental.server.start(6009)
    # (Model code goes here).
    #  Send a request to the profiler server to collect a trace of your model.
    tf.profiler.experimental.client.trace('grpc://127.0.0.1:6009',
                                          'gs://your_tb_logdir', 2000)
    

    分析多个工作程序的示例

    # E.g., your worker IP addresses are 10.0.0.2, 10.0.0.3, 10.0.0.4, and you
    # would like to profile for a duration of 2 seconds.
    tf.profiler.experimental.client.trace(
        'grpc://10.0.0.2:8466,grpc://10.0.0.3:8466,grpc://10.0.0.4:8466',
        'gs://your_tb_logdir',
        2000)
    

使用“捕获分析”对话框指定

  • 以逗号分隔的分析服务 URL 或 TPU 名称列表。
  • 分析持续时间。
  • 设备、主机和 Python 函数调用跟踪的级别。
  • 如果第一次捕获分析失败,您希望 Profiler 重试捕获分析的次数。

分析自定义训练循环

要分析 TensorFlow 代码中的自定义训练循环,请使用 tf.profiler.experimental.Trace API 为 Profiler 标记步骤边界,对训练循环进行检测。

name 参数用作步骤名称的前缀,step_num 关键字参数附加在步骤名称中,_r 关键字参数使此跟踪事件被 Profiler 作为步骤事件处理。

例如,

for step in range(NUM_STEPS):
    with tf.profiler.experimental.Trace('train', step_num=step, _r=1):
        train_data = next(dataset)
        train_step(train_data)

这将启用 Profiler 的基于步骤的性能分析,并使步骤事件显示在跟踪查看器中。

确保将数据集迭代器包含在 tf.profiler.experimental.Trace 上下文中,以便准确分析输入管道。

以下代码片段是一个反模式

for step, train_data in enumerate(dataset):
    with tf.profiler.experimental.Trace('train', step_num=step, _r=1):
        train_step(train_data)

分析用例

分析器涵盖了四个不同轴上的许多用例。一些组合目前受支持,其他组合将在未来添加。一些用例是

  • 本地与远程分析:这是设置分析环境的两种常见方法。在本地分析中,分析 API 在模型执行的同一台机器上调用,例如,具有 GPU 的本地工作站。在远程分析中,分析 API 在与模型执行不同的机器上调用,例如,在 Cloud TPU 上。
  • 分析多个工作程序:在使用 TensorFlow 的分布式训练功能时,您可以分析多台机器。
  • 硬件平台:分析 CPU、GPU 和 TPU。

下表提供了对上述 TensorFlow 支持的用例的快速概述

分析 API 本地 远程 多个工作程序 硬件平台
TensorBoard Keras 回调 支持 不支持 不支持 CPU、GPU
tf.profiler.experimental start/stop API 支持 不支持 不支持 CPU、GPU
tf.profiler.experimental client.trace API 支持 支持 支持 CPU、GPU、TPU
上下文管理器 API 支持 不支持 不支持 CPU、GPU

最佳实践以获得最佳模型性能

根据您的 TensorFlow 模型,使用以下建议以获得最佳性能。

一般来说,在设备上执行所有转换,并确保您使用平台最新的兼容库版本,例如 cuDNN 和 Intel MKL。

优化输入数据管道

使用来自 [#input_pipeline_analyzer] 的数据来优化您的数据输入管道。高效的数据输入管道可以通过减少设备空闲时间来大幅提高模型执行速度。尝试将 使用 tf.data API 提高性能 指南和以下内容中详细介绍的最佳实践纳入您的数据输入管道,以提高其效率。

  • 一般来说,将任何不需要按顺序执行的操作并行化可以显着优化数据输入管道。

  • 在许多情况下,更改某些调用的顺序或调整参数以使其最适合您的模型会有所帮助。在优化输入数据管道时,仅对数据加载器进行基准测试,不进行训练和反向传播步骤,以独立量化优化的效果。

  • 尝试使用合成数据运行您的模型,以检查输入管道是否为性能瓶颈。

  • 使用 tf.data.Dataset.shard 进行多 GPU 训练。确保您在输入循环的早期阶段进行分片,以防止吞吐量下降。在使用 TFRecords 时,确保您对 TFRecords 列表进行分片,而不是对 TFRecords 的内容进行分片。

  • 通过使用 tf.data.AUTOTUNE 动态设置 num_parallel_calls 的值来并行化多个操作。

  • 考虑限制使用 tf.data.Dataset.from_generator,因为它比纯 TensorFlow 操作慢。

  • 考虑限制使用 tf.py_function,因为它无法序列化,并且不支持在分布式 TensorFlow 中运行。

  • 使用 tf.data.Options 控制对输入管道的静态优化。

还可以阅读 tf.data 性能分析 指南,以获取有关优化输入管道的更多指导。

优化数据增强

在处理图像数据时,通过在应用空间变换(如翻转、裁剪、旋转等)之后 转换为不同的数据类型,使您的 数据增强 更有效。

使用 NVIDIA® DALI

在某些情况下,例如当您的系统具有较高的 GPU 与 CPU 比例时,上述所有优化可能不足以消除由于 CPU 周期限制导致的数据加载器中的瓶颈。

如果您正在使用 NVIDIA® GPU 进行计算机视觉和音频深度学习应用,请考虑使用数据加载库 (DALI) 来加速数据管道。

查看 NVIDIA® DALI:操作 文档,以获取支持的 DALI 操作列表。

使用线程和并行执行

使用 tf.config.threading API 在多个 CPU 线程上运行操作,以更快地执行它们。

TensorFlow 默认情况下会自动设置并行线程数。用于运行 TensorFlow 操作的线程池取决于可用的 CPU 线程数。

使用 tf.config.threading.set_intra_op_parallelism_threads 控制单个操作的最大并行加速。请注意,如果您并行运行多个操作,它们将共享可用的线程池。

如果您有独立的非阻塞操作(图上没有直接路径的操作),请使用 tf.config.threading.set_inter_op_parallelism_threads 使用可用的线程池并发运行它们。

其他

在 NVIDIA® GPU 上处理较小的模型时,您可以设置 tf.compat.v1.ConfigProto.force_gpu_compatible=True,以强制所有 CPU 张量使用 CUDA 固定内存分配,从而显着提高模型性能。但是,在使用未知/非常大的模型时要谨慎使用此选项,因为这可能会对主机(CPU)性能产生负面影响。

提高设备性能

遵循此处和 GPU 性能优化指南 中详细介绍的最佳实践,以优化设备上的 TensorFlow 模型性能。

如果您正在使用 NVIDIA GPU,请通过运行以下命令将 GPU 和内存使用情况记录到 CSV 文件中

nvidia-smi
--query-gpu=utilization.gpu,utilization.memory,memory.total,
memory.free,memory.used --format=csv

配置数据布局

在处理包含通道信息(如图像)的数据时,优化数据布局格式以优先考虑通道最后(NHWC 优先于 NCHW)。

通道最后数据格式提高了 Tensor Core 的利用率,并在与 AMP 结合使用时,尤其是在卷积模型中提供了显着的性能提升。NCHW 数据布局仍然可以由 Tensor Core 操作,但由于自动转置操作会引入额外的开销。

您可以通过为 tf.keras.layers.Conv2Dtf.keras.layers.Conv3Dtf.keras.layers.RandomRotation 等层设置 data_format="channels_last" 来优化数据布局以优先考虑 NHWC 布局。

使用 tf.keras.backend.set_image_data_format 为 Keras 后端 API 设置默认数据布局格式。

最大化 L2 缓存

在使用 NVIDIA® GPU 时,在训练循环之前执行以下代码片段,以将 L2 获取粒度最大化为 128 字节。

import ctypes

_libcudart = ctypes.CDLL('libcudart.so')
# Set device limit on the current device
# cudaLimitMaxL2FetchGranularity = 0x05
pValue = ctypes.cast((ctypes.c_int*1)(), ctypes.POINTER(ctypes.c_int))
_libcudart.cudaDeviceSetLimit(ctypes.c_int(0x05), ctypes.c_int(128))
_libcudart.cudaDeviceGetLimit(pValue, ctypes.c_int(0x05))
assert pValue.contents.value == 128

配置 GPU 线程使用情况

GPU 线程模式决定如何使用 GPU 线程。

将线程模式设置为 gpu_private,以确保预处理不会占用所有 GPU 线程。这将减少训练期间的内核启动延迟。您还可以设置每个 GPU 的线程数。使用环境变量设置这些值。

import os

os.environ['TF_GPU_THREAD_MODE']='gpu_private'
os.environ['TF_GPU_THREAD_COUNT']='1'

配置 GPU 内存选项

一般来说,增加批次大小并扩展模型以更好地利用 GPU 并获得更高的吞吐量。请注意,增加批次大小会改变模型的精度,因此需要通过调整超参数(如学习率)来扩展模型,以满足目标精度。

此外,使用 tf.config.experimental.set_memory_growth 允许 GPU 内存增长,以防止将所有可用内存完全分配给仅需要一小部分内存的操作。这允许其他使用 GPU 内存的进程在同一设备上运行。

要了解更多信息,请查看 GPU 指南中的 限制 GPU 内存增长 指导,以了解更多信息。

其他

  • 将训练小批量大小(每次训练循环中每个设备使用的训练样本数量)增加到最大值,该值在 GPU 上不会出现内存不足 (OOM) 错误。增加批次大小会影响模型的准确性,因此请确保通过调整超参数来扩展模型,以满足目标准确性。

  • 在生产代码中禁用在张量分配期间报告 OOM 错误。在 tf.compat.v1.RunOptions 中设置 report_tensor_allocations_upon_oom=False

  • 对于具有卷积层的模型,如果使用批次归一化,请删除偏差添加。批次归一化通过其均值来移动值,这消除了需要常数偏差项。

  • 使用 TF Stats 找出设备上操作的运行效率。

  • 使用 tf.function 执行计算,并可选地启用 jit_compile=True 标志 (tf.function(jit_compile=True)。要了解更多信息,请访问 使用 XLA tf.function

  • 最大限度地减少步骤之间主机 Python 操作,并减少回调。每隔几个步骤计算一次指标,而不是在每个步骤都计算。

  • 保持设备计算单元繁忙。

  • 并行地将数据发送到多个设备。

  • 考虑 使用 16 位数值表示,例如 fp16(IEEE 指定的半精度浮点格式)或 Brain 浮点 bfloat16 格式。

其他资源

已知限制

在 TensorFlow 2.2 和 TensorFlow 2.3 上分析多个 GPU

TensorFlow 2.2 和 2.3 仅支持单主机系统的多个 GPU 分析;不支持多主机系统的多个 GPU 分析。要分析多工作器 GPU 配置,每个工作器都必须独立分析。从 TensorFlow 2.4 开始,可以使用 tf.profiler.experimental.client.trace API 分析多个工作器。

需要 CUDA® Toolkit 10.2 或更高版本才能分析多个 GPU。由于 TensorFlow 2.2 和 2.3 仅支持 CUDA® Toolkit 版本最高到 10.1,因此您需要创建指向 libcudart.so.10.1libcupti.so.10.1 的符号链接。

sudo ln -s /usr/local/cuda/lib64/libcudart.so.10.2 /usr/local/cuda/lib64/libcudart.so.10.1
sudo ln -s /usr/local/cuda/extras/CUPTI/lib64/libcupti.so.10.2 /usr/local/cuda/extras/CUPTI/lib64/libcupti.so.10.1