【教程】PyTorch 训练优化:通过 GPU 分析和内存分析实现 5× 吞吐量
【教程】PyTorch 训练优化:通过 GPU 分析和内存分析实现 5× 吞吐量
小锋学长生活大爆炸

【教程】PyTorch 训练优化:通过 GPU 分析和内存分析实现 5× 吞吐量

hualala
2024-08-05 / 0 评论 / 108 阅读
温馨提示:
本文最后更新于2024年08月05日,已超过120天没有更新,若内容或图片失效,请留言反馈。
https://medium.com/@alishafique3/pytorch-training-optimizations-5-throughput-with-gpu-profiling-and-memory-analysis-31cb2b1f95cc

训练优化技术在机器学习中至关重要,因为它们可以提高效率、加快收敛速度、确保稳定性、提高泛化能力并实现可扩展性。这些技术对于开发有效的模型至关重要,这些模型在各种任务和数据集上表现良好,同时有效利用计算资源。

本教程的重点将放在使用 PyTorch 框架的单个 GPU 上优化训练阶段。我们将使用 PyTorch 内置的性能分析器、PyTorch Profiler 和 PyTorch Profiler TensorBoard 插件来分析训练阶段的性能。本教程中使用的优化技术包括自动混合精度、增加批量大小、减少 H2D 复制、多处理和固定内存,以改善训练时间和内存使用率。

用法:
该代码是使用 Pytorch 的 NVIDIA 容器镜像 23.10 构建的,该镜像在 NGC 上可用。代码是使用以下库构建的:

  • Ubuntu 22.04,包括 Python 3.10
  • NVIDIA cuDNN 8.9.5
  • PyTorch 23.10

对于 docker 19.03 或更高版本,启动容器的典型命令是:

docker run --gpus all -it --rm nvcr.io/nvidia/pytorch:xx.xx-py3

对于 docker 19.02 或更早版本,启动容器的典型命令是:

nvidia-docker run -it --rm -v nvcr.io/nvidia/pytorch:xx.xx-py3

其中,xx.xx 是容器版本,即 23.10

优化 #1:自动混合精度

在 GPU 内核视图中,一个有趣的观察结果是 GPU 张量核心的利用率为零。这些组件在最近的 GPU 设计中找到,具有双重功能。首先,它们充当执行矩阵乘法的专用单元,这大大提高了 AI 应用程序的性能。其次,张量核心通过使用混合精度运算,与传统 GPU 核心相比,提供了显着的性能改进。

在自动混合精度 (AMP) 中,模型的特定部分会自动转换为精度较低的 16 位浮点数,并在 GPU 张量核心上执行。这种方法通过以半精度格式执行运算,同时以单精度存储最少的数据,以保留网络关键部分的基本信息,从而提供了显着的计算加速。随着 Volta 和 Turing 架构中张量核心的引入,通过混合精度实现了显着的速度增强。

实施混合精确训练涉及三个关键步骤:

  • 转换模型的适用部分以利用 float16 数据类型。
  • 保留 float32 个主权重,以累积每次迭代的权重更新。
  • 采用损失缩放来保持较小的梯度值。

对 AMP 训练步骤的修改显示在下面的代码块中。

def train(data):
    inputs, labels = data[0].to(device=device), data[1].to(device=device)
    with torch.autocast(device_type='cuda', dtype=torch.float16):
        outputs = model(inputs)
        loss = criterion(outputs, labels) 
    #outputs = model(inputs)
    #loss = criterion(outputs, labels)
    # Note - torch.cuda.amp.GradScaler() may be required
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

仅用几行代码,利用率就从 0% 跃升至 19%。除了提高张量核心利用率外,AMP 还降低了 GPU 内存利用率,从而释放了更多空间来增加批处理大小。训练阶段的吞吐量指标也从每秒 273 个样本(批量大小 32 为 117 毫秒)增加到每秒 395 个样本(批量大小 32 为 81 毫秒)。

优化#2:增加批量大小

之前的优化(自动混合精度)已将步进时间从 117 毫秒显着减少到 81 毫秒。这种技术还使 GPU 内存从 2.5GB 几乎减半到 1.3GB。它使 GPU 未得到充分利用,并允许我们增加批量大小。在下图中,我们显示了将批处理大小增加到 128 时的性能结果。

train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True)

由于这一变化,训练速度显着提高,从每秒 395 个样本(批量大小 32 为 81 毫秒)增加到每秒 551 个样本(批量大小为 128 为 232 毫秒)。

优化 #3:减少主机到设备的复制

另一个可以提高 GPU 利用率的优化是减少主机 (CPU) 操作和从主机到设备的内存传输。解决这种瓶颈的一种方法是减少每个批次的数据量和操作量。这可以通过在执行数据复制后将数据类型从 8 位无符号整数转换为 32 位浮点数(由于归一化)来实现。在下面的代码块中,一旦数据位于 GPU 上,就会执行数据类型转换和归一化:

transform = T.Compose(
    [T.Resize(224),
     T.ToTensor()#,
     ]) #T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

而 Train 将更新为

def train(data):
    inputs, labels = data[0].to(device=device), data[1].to(device=device)
    inputs = (inputs.to(torch.float32) / 255. - 0.5) / 0.5
    with torch.autocast(device_type='cuda', dtype=torch.float16):
        outputs = model(inputs)
        loss = criterion(outputs, labels) 
    #outputs = model(inputs)
    #loss = criterion(outputs, labels)
    # Note - torch.cuda.amp.GradScaler() may be required
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

由于此更改,内存副本未更改,但 CPU 执行和其他时间因素显着减少。它还提高了 GPU 利用率。这种优化使我们每秒有 631 个样本(批量大小为 128 时为 202 毫秒)。

优化 #4:多进程数据加载

多处理数据加载器是 PyTorch 等机器学习框架中使用的一个组件,用于在模型训练或评估期间并行加载和处理数据。它利用多个处理器内核或线程来加速数据加载、提高效率并支持数据增强等任务。多处理数据加载器通常在 CPU 上运行。它利用多个 CPU 内核或线程并行加载和预处理数据,从而提高效率并加快数据加载过程。要启用此优化,将进行以下更改:

train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2)

对单行代码的更改将训练步骤时间从 202.6 毫秒减少到 145.8 毫秒。

优化 #5:内存固定

如果我们在 Overview 窗口中分析上次优化的性能,我们可以看到仍然花费大量时间将训练数据处理到 GPU 中。为了解决这个问题,我们将实施另一个 PyTorch 推荐的优化,旨在简化数据输入流并利用内存固定。利用固定内存可以显著提高从主机到设备的数据传输速度,重要的是,它可以实现异步操作。此功能使我们能够在 GPU 上同时准备下一个训练批次,同时在当前批次上执行训练步骤。要了解有关内存固定的更多信息,请查看此链接:第 21 讲 — 固定内存和流

这种内存固定优化需要更改两行代码。首先,我们将 DataLoader 的 pin_memory 标志设置为 True。

train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2, pin_memory=True)

然后,我们将主机到设备的内存传输(在 train 函数中)修改为非阻塞:

inputs, labels = data[0].to(device=device, non_blocking=True), \
                 data[1].to(device=device, non_blocking=True)

我们的 GPU 利用率从 79.54% 增加到 86.22%,步进时间进一步减少到 92.6 毫秒。

lzfsum3c.png

结果

以下结果是在配备 8GB 内存的 Quadro RTX 4000 GPU 上收集的。将各种优化技术的性能与基础模型进行了比较。GPU 内存以 GB 为单位,而平均步进时间以微秒为单位。每秒的样本数可以通过将批量值除以平均步进时间来计算。百分比优化值是每秒优化样本数/每秒基础样本数的比率。

+----------------------------+------------+--------------+---------------------+-----------------+-----------------------------+
|     Optim. Techniques      | Batch Size | GPU Mem (GB) | Avg. Step Time (ms) | Samples per sec | Optimization (rel. to base) |
+----------------------------+------------+--------------+---------------------+-----------------+-----------------------------+
| Base_Model                 |         32 |         2.51 |               117.1 |           273.5 | 100%                        |
| Automatic Mixed Precision  |         32 |         1.32 |                81.1 |           395.1 | 144%                        |
| Increase Batch Size        |        128 |         4.95 |               232.1 |           551.5 | 201%                        |
| Reduce H2D Copy            |        128 |         4.95 |               202.6 |          631.63 | 230%                        |
| Multi-process Data Loading |        128 |         4.95 |               145.8 |          877.84 | 320%                        |
| Memory Pinning             |        128 |         4.95 |                92.6 |          1381.6 | 505%                        |
+----------------------------+------------+--------------+---------------------+-----------------+-----------------------------+
0

评论 (0)

取消