目录

分析你的 PyTorch 模块

创建日期: 2020年12月30日 | 最后更新日期: 2024年1月19日 | 最后验证日期: 2024年11月5日

作者: 苏拉吉·苏布拉马尼安

PyTorch 包含一个性能分析器 API,有助于识别代码中各种 PyTorch 操作的时间和内存开销。性能分析器可以轻松集成到代码中,并且结果可以以表格形式打印或返回到 JSON 跟踪文件中。

注意

Profiler 支持多线程模型。Profiler 在执行操作的同一线程中运行,但它也会对可能在另一个线程中运行的子操作进行分析。同时运行的Profiler 将被限定在其各自的线程内,以防止结果混杂。

注意

PyTorch 1.8 引入了将在未来版本中取代旧的分析器 API 的新 API。请在 此页面 查看新 API。

前往 此配方,快速了解Profiler API的使用方法。


import torch
import numpy as np
from torch import nn
import torch.autograd.profiler as profiler

使用Profiler进行性能调试

Profiler 可以帮助您识别模型中的性能瓶颈。在这个例子中,我们构建了一个自定义模块,该模块执行两个子任务:

  • 对输入进行线性变换,和

  • 使用转换结果在掩码张量上获取索引。

我们使用 profiler.record_function("label") 将每个子任务的代码包裹在单独的带标签的上下文管理器中。在性能分析器输出中,子任务的所有操作的聚合性能指标将会在其对应的标签下显示。

请注意,使用Profiler会带来一定的开销,最好仅用于代码分析。如果您正在测试运行时间,请记得移除它。

class MyModule(nn.Module):
    def __init__(self, in_features: int, out_features: int, bias: bool = True):
        super(MyModule, self).__init__()
        self.linear = nn.Linear(in_features, out_features, bias)

    def forward(self, input, mask):
        with profiler.record_function("LINEAR PASS"):
            out = self.linear(input)

        with profiler.record_function("MASK INDICES"):
            threshold = out.sum(axis=1).mean().item()
            hi_idx = np.argwhere(mask.cpu().numpy() > threshold)
            hi_idx = torch.from_numpy(hi_idx).cuda()

        return out, hi_idx

剖析前向传播

我们初始化随机输入张量和掩码张量,并构建模型。

在我们运行性能分析器之前,我们需要预热CUDA以确保准确的性能基准测试。我们将模块的前向传播过程包裹在profiler.profile上下文管理器中。with_stack=True参数会在跟踪文件中附加操作的文件和行号。

警告

with_stack=True 带来额外的开销,并更适合用于研究代码。 请在性能测试时移除它。

model = MyModule(500, 10).cuda()
input = torch.rand(128, 500).cuda()
mask = torch.rand((500, 500, 500), dtype=torch.double).cuda()

# warm-up
model(input, mask)

with profiler.profile(with_stack=True, profile_memory=True) as prof:
    out, idx = model(input, mask)

提高内存性能

请注意,最昂贵的操作——在内存和时间方面——发生在 forward (10) 表示MASK INDICES 内的操作。让我们先尝试解决内存消耗问题。我们可以看到,在第12行,.to() 操作消耗了953.67 Mb。这个操作将 mask 复制到CPU。mask 初始化为 torch.double 数据类型。我们能否通过将其转换为 torch.float 来减少内存占用?

model = MyModule(500, 10).cuda()
input = torch.rand(128, 500).cuda()
mask = torch.rand((500, 500, 500), dtype=torch.float).cuda()

# warm-up
model(input, mask)

with profiler.profile(with_stack=True, profile_memory=True) as prof:
    out, idx = model(input, mask)

print(prof.key_averages(group_by_stack_n=5).table(sort_by='self_cpu_time_total', row_limit=5))

"""
(Some columns are omitted)

-----------------  ------------  ------------  ------------  --------------------------------
             Name    Self CPU %      Self CPU  Self CPU Mem   Source Location
-----------------  ------------  ------------  ------------  --------------------------------
     MASK INDICES        93.61%        5.006s    -476.84 Mb  /mnt/xarfuse/.../torch/au
                                                             <ipython-input-...>(10): forward
                                                             /mnt/xarfuse/  /torch/nn
                                                             <ipython-input-...>(9): <module>
                                                             /mnt/xarfuse/.../IPython/

      aten::copy_         6.34%     338.759ms           0 b  <ipython-input-...>(12): forward
                                                             /mnt/xarfuse/.../torch/nn
                                                             <ipython-input-...>(9): <module>
                                                             /mnt/xarfuse/.../IPython/
                                                             /mnt/xarfuse/.../IPython/

 aten::as_strided         0.01%     281.808us           0 b  <ipython-input-...>(11): forward
                                                             /mnt/xarfuse/.../torch/nn
                                                             <ipython-input-...>(9): <module>
                                                             /mnt/xarfuse/.../IPython/
                                                             /mnt/xarfuse/.../IPython/

      aten::addmm         0.01%     275.721us           0 b  /mnt/xarfuse/.../torch/nn
                                                             /mnt/xarfuse/.../torch/nn
                                                             /mnt/xarfuse/.../torch/nn
                                                             <ipython-input-...>(8): forward
                                                             /mnt/xarfuse/.../torch/nn

      aten::_local        0.01%     268.650us           0 b  <ipython-input-...>(11): forward
      _scalar_dense                                          /mnt/xarfuse/.../torch/nn
                                                             <ipython-input-...>(9): <module>
                                                             /mnt/xarfuse/.../IPython/
                                                             /mnt/xarfuse/.../IPython/

-----------------  ------------  ------------  ------------  --------------------------------
Self CPU time total: 5.347s

"""

此操作的CPU内存占用减半了。

提高时间性能

虽然所花费的时间也减少了一点,但仍然太高。 事实证明,将矩阵从CUDA复制到CPU是非常昂贵的! aten::copy_ 运算符在 forward (12) 中复制 mask 到CPU, 以便它可以使用NumPy的 argwhere 函数。aten::copy_forward(13) 中将数组作为张量复制回CUDA。如果我们在这里使用一个 torch 函数 nonzero(),就可以消除这两个步骤。

class MyModule(nn.Module):
    def __init__(self, in_features: int, out_features: int, bias: bool = True):
        super(MyModule, self).__init__()
        self.linear = nn.Linear(in_features, out_features, bias)

    def forward(self, input, mask):
        with profiler.record_function("LINEAR PASS"):
            out = self.linear(input)

        with profiler.record_function("MASK INDICES"):
            threshold = out.sum(axis=1).mean()
            hi_idx = (mask > threshold).nonzero(as_tuple=True)

        return out, hi_idx


model = MyModule(500, 10).cuda()
input = torch.rand(128, 500).cuda()
mask = torch.rand((500, 500, 500), dtype=torch.float).cuda()

# warm-up
model(input, mask)

with profiler.profile(with_stack=True, profile_memory=True) as prof:
    out, idx = model(input, mask)

print(prof.key_averages(group_by_stack_n=5).table(sort_by='self_cpu_time_total', row_limit=5))

"""
(Some columns are omitted)

--------------  ------------  ------------  ------------  ---------------------------------
          Name    Self CPU %      Self CPU  Self CPU Mem   Source Location
--------------  ------------  ------------  ------------  ---------------------------------
      aten::gt        57.17%     129.089ms           0 b  <ipython-input-...>(12): forward
                                                          /mnt/xarfuse/.../torch/nn
                                                          <ipython-input-...>(25): <module>
                                                          /mnt/xarfuse/.../IPython/
                                                          /mnt/xarfuse/.../IPython/

 aten::nonzero        37.38%      84.402ms           0 b  <ipython-input-...>(12): forward
                                                          /mnt/xarfuse/.../torch/nn
                                                          <ipython-input-...>(25): <module>
                                                          /mnt/xarfuse/.../IPython/
                                                          /mnt/xarfuse/.../IPython/

   INDEX SCORE         3.32%       7.491ms    -119.21 Mb  /mnt/xarfuse/.../torch/au
                                                          <ipython-input-...>(10): forward
                                                          /mnt/xarfuse/.../torch/nn
                                                          <ipython-input-...>(25): <module>
                                                          /mnt/xarfuse/.../IPython/

aten::as_strided         0.20%    441.587us          0 b  <ipython-input-...>(12): forward
                                                          /mnt/xarfuse/.../torch/nn
                                                          <ipython-input-...>(25): <module>
                                                          /mnt/xarfuse/.../IPython/
                                                          /mnt/xarfuse/.../IPython/

 aten::nonzero
     _numpy             0.18%     395.602us           0 b  <ipython-input-...>(12): forward
                                                          /mnt/xarfuse/.../torch/nn
                                                          <ipython-input-...>(25): <module>
                                                          /mnt/xarfuse/.../IPython/
                                                          /mnt/xarfuse/.../IPython/
--------------  ------------  ------------  ------------  ---------------------------------
Self CPU time total: 225.801ms

"""

进一步阅读

我们已经看到了Profiler如何被用于调查PyTorch模型中的时间与内存瓶颈。 更多关于Profiler的信息,请参阅此处:

脚本的总运行时间: ( 0 分钟 0.000 秒)

通过 Sphinx-Gallery 生成的画廊

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源