• 教程 >
  • 从原理出发掌握 PyTorch Intel CPU 性能(第二部分)
快捷键

从原理出发深入理解 PyTorch Intel CPU 性能(第二部分)¶

创建时间:2025 年 4 月 1 日 | 最后更新时间:2025 年 4 月 1 日 | 最后验证:未验证

作者:Min Jean Cho, Jing Xu, Mark Saroufim

在《从原理出发深入理解 PyTorch Intel CPU 性能》教程中,我们介绍了如何调整 CPU 运行时配置,如何进行性能分析,以及如何将它们集成到 TorchServe 中,以实现优化的 CPU 性能。

在本教程中,我们将通过 Intel® Extension for PyTorch* Launcher 的内存分配器来提升性能,并通过 Intel® Extension for PyTorch*的优化内核在 CPU 上实现性能提升,并将它们应用于 TorchServe,展示了 ResNet50 的吞吐量提升了 7.71 倍,BERT 的吞吐量提升了 2.20 倍。

../_images/1.png

前提条件 _

在本教程中,我们将使用自顶向下的微架构分析(TMA)来分析并展示后端绑定(内存绑定、核心绑定)通常是欠优化或欠调优的深度学习工作负载的主要瓶颈,并通过 Intel® Extension for PyTorch*展示优化技术来提高后端绑定。我们将使用 toplev 工具,它是基于 Linux perf 构建的 pmu-tools 的一部分,用于 TMA。

我们还将使用 Intel® VTune™ Profiler 的仪器和跟踪技术(ITT)进行更细粒度的分析。

自顶向下微架构分析方法(TMA)¶

当调整 CPU 以实现最佳性能时,了解瓶颈所在非常有用。大多数 CPU 核心都有片上性能监控单元(PMU)。PMU 是 CPU 核心内用于计算系统上发生的特定硬件事件的专用逻辑部件。这些事件的可能例子包括缓存未命中或分支预测错误。PMU 用于自顶向下的微架构分析(TMA)以识别瓶颈。TMA 由以下所示的分层级别组成:

../_images/26.png

顶级、一级指标收集退休、错误推测、前端绑定、后端绑定。CPU 流水线在概念上可以简化并分为两个部分:前端和后端。前端负责获取程序代码并将它们解码成称为微操作(uOps)的低级硬件操作。然后,这些 uOps 通过一个称为分配的过程被送入后端。一旦分配,后端就负责在可用的执行单元中执行 uOp。uOp 执行完成被称为退休。相反,错误推测是在退休之前取消推测性获取的 uOps,例如在预测错误的分支的情况下。这些指标中的每一个都可以在后续级别进一步分解,以确定瓶颈。

优化后端绑定¶

大多数未调优的深度学习工作负载将是后端绑定。解决后端绑定通常是解决导致退休时间比必要更长的问题的来源。如上所示,后端绑定有两个子指标——核心绑定和内存绑定。

内存绑定停滞的原因与内存子系统有关。例如,最后一级缓存(LLC 或 L3 缓存)未命中导致访问 DRAM。扩展深度学习模型通常需要大量的计算。而高计算利用率要求数据在执行单元需要执行 uOps 时可用。这需要预取数据并在缓存中重用数据,而不是从主内存多次获取相同的数据,这会导致执行单元在数据返回时饥饿。在本教程中,我们将展示更有效的内存分配器、算子融合、内存布局格式优化可以减少内存绑定开销,并提高缓存局部性。

核绑定停滞表示在没有任何未完成的内存访问的情况下,对可用执行单元的使用不理想。例如,连续的几个通用矩阵-矩阵乘法(GEMM)指令可能会竞争融合乘加(FMA)或点积(DP)执行单元,从而引起核绑定停滞。包括 DP 内核在内的关键深度学习内核已经通过 oneDNN 库(oneAPI 深度神经网络库)进行了优化,从而减少了核绑定带来的开销。

如 GEMM、卷积、反卷积等操作是计算密集型操作。而如池化、批量归一化、ReLU 等激活函数则是内存密集型操作。

Intel® VTune™ Profiler 的仪器和跟踪技术 (ITT) ¶

Intel® VTune Profiler 的 ITT API 是一个有用的工具,可以注释您的工作负载区域以进行跟踪,以便以更细粒度(操作/函数/子函数粒度)进行分析和可视化。通过在 PyTorch 模型 OP 的粒度上进行注释,Intel® VTune Profiler 的 ITT 可以实现操作级别的分析。Intel® VTune Profiler 的 ITT 已集成到 PyTorch 自动微分分析器中。 1

  1. 该功能必须通过 torch.autograd.profiler.emit_itt() 显式启用。

TorchServe 与 Intel®扩展 PyTorch* ¶

Intel® Extension for PyTorch* 是一个 Python 包,用于通过针对 Intel 硬件进行优化的方式扩展 PyTorch,以获得额外的性能提升。

Intel® Extension for PyTorch* 已集成到 TorchServe 中,以实现开箱即用的性能提升。 2 对于自定义处理脚本,我们建议添加 intel_extension_for_pytorch 包。

  1. 该功能必须通过在 config.properties 中设置 ipex_enable=true 来显式启用。

在本节中,我们将展示后端绑定通常是欠优化或欠调优的深度学习工作负载的主要瓶颈,并通过 Intel® Extension for PyTorch*演示优化技术来提高后端绑定,后端绑定有两个子指标 - 内存绑定和核心绑定。一个更高效的内存分配器、算子融合、内存布局格式优化可以改善内存绑定。理想情况下,通过优化的算子和更好的缓存局部性,可以将内存绑定改善为核心绑定。关键深度学习原语,如卷积、矩阵乘法、点积,已经通过 Intel® Extension for PyTorch*和 oneDNN 库得到了很好的优化,从而提高了核心绑定。

利用高级启动器配置:内存分配器 ¶

从性能角度来看,内存分配器起着重要的作用。更高效的内存使用可以减少不必要的内存分配或销毁的开销,从而加快执行速度。在实际的深度学习工作负载中,尤其是在像 TorchServe、TCMalloc 或 JeMalloc 这样的大型多核系统或服务器上运行时,通常可以获得比默认的 PyTorch 内存分配器 PTMalloc 更好的内存使用。

TCMalloc、JeMalloc、PTMalloc ¶

TCMalloc 和 JeMalloc 都使用线程局部缓存来减少线程同步的开销,并通过使用自旋锁和每个线程的存储区域来减少锁竞争。TCMalloc 和 JeMalloc 减少了不必要的内存分配和释放的开销。这两个分配器按大小对内存分配进行分类,以减少内存碎片的开销。

使用启动器,用户可以通过选择三个启动器旋钮之一(-enable_tcmalloc(TCMalloc)、-enable_jemalloc(JeMalloc)、-use_default_allocator(PTMalloc))来轻松尝试不同的内存分配器。

练习

让我们来对比 PTMalloc 和 JeMalloc 的性能。

我们将使用启动器来指定内存分配器,并将工作负载绑定到第一个插槽的物理核心,以避免任何 NUMA 复杂性问题——仅为了分析内存分配器的影响。

以下示例测量了 ResNet50 的平均推理时间:

import torch
import torchvision.models as models
import time

model = models.resnet50(pretrained=False)
model.eval()
batch_size = 32
data = torch.rand(batch_size, 3, 224, 224)

# warm up
for _ in range(100):
    model(data)

# measure
# Intel® VTune Profiler's ITT context manager
with torch.autograd.profiler.emit_itt():
    start = time.time()
    for i in range(100):
   # Intel® VTune Profiler's ITT to annotate each step
        torch.profiler.itt.range_push('step_{}'.format(i))
        model(data)
        torch.profiler.itt.range_pop()
    end = time.time()

print('Inference took {:.2f} ms in average'.format((end-start)/100*1000))

让我们收集一级 TMA 指标。

../_images/32.png

一级 TMA 显示 PTMalloc 和 JeMalloc 都受后端限制。超过一半的执行时间被后端阻塞。让我们再深入一级。

../_images/41.png

二级 TMA 显示后端限制是由内存限制引起的。让我们再深入一级。

../_images/51.png

大多数内存限制下的指标确定了从 L1 缓存到主内存的内存层次结构中哪一级是瓶颈。在给定级别上受限的热点表明大部分数据都是从该缓存或内存级别检索的。优化应侧重于将数据移动到核心附近。三级 TMA 显示 PTMalloc 受限于 DRAM 限制。另一方面,JeMalloc 受限于 L1 限制——JeMalloc 将数据移动到核心附近,从而实现更快的执行。

让我们看看 Intel® VTune Profiler ITT 跟踪。在示例脚本中,我们对推理循环中的每个 step_x 进行了注释。

../_images/61.png

每个步骤都在时间线图中进行了跟踪。在最后一个步骤(step_99)上模型推理的持续时间从 304.308 毫秒减少到 261.843 毫秒。

使用 TorchServe 进行练习

让我们使用 TorchServe 来分析 PTMalloc 与 JeMalloc。

我们将使用 TorchServe apache-bench 基准测试,使用 ResNet50 FP32,批大小 32,并发 32,请求数 8960。所有其他参数与默认参数相同。

如前所述的练习,我们将使用启动器指定内存分配器,并将工作负载绑定到第一个插槽的物理核心。为此,用户只需在 config.properties 中添加几行即可:

PTMalloc

cpu_launcher_enable=true
cpu_launcher_args=--node_id 0 --use_default_allocator

JeMalloc

cpu_launcher_enable=true
cpu_launcher_args=--node_id 0 --enable_jemalloc

让我们收集一级 TMA 指标。

../_images/71.png

让我们再深入一层。

../_images/81.png

让我们使用 Intel® VTune Profiler ITT 来注释 TorchServe 推理范围,以在推理级别进行性能分析。由于 TorchServe 架构由多个子组件组成,包括处理请求/响应的 Java 前端和运行模型实际推理的 Python 后端,因此使用 Intel® VTune Profiler ITT 来限制在推理级别收集跟踪数据是有帮助的。

../_images/9.png

每个推理调用都在时间线图中进行跟踪。最后一个模型推理的持续时间从 561.688 毫秒减少到 251.287 毫秒 - 速度提高了 2.2 倍。

../_images/101.png

时间线图可以展开以查看操作级别的分析结果。aten::conv2d 的持续时间从 16.401 毫秒减少到 6.392 毫秒 - 速度提升了 2.6 倍。

在本节中,我们展示了 JeMalloc 可以比默认的 PyTorch 内存分配器 PTMalloc 提供更好的性能,高效的线程局部缓存可以提升后端绑定。

Intel® PyTorch*扩展

如下所示,Intel® PyTorch 扩展*的三大优化技术:操作符、图、运行时:

Intel® PyTorch*扩展优化技术

运算符

运行时

  • 向量化与多线程

  • 低精度 BF16/INT8 计算

  • 数据布局优化以提升缓存局部性

  • 常量折叠以减少计算

  • 缓存局部性优化融合操作

  • 线程亲和性

  • 内存缓冲池

  • GPU 运行时

  • 启动器

运算符优化

通过 PyTorch 调度机制注册优化的运算符和内核。这些运算符和内核利用了 Intel 硬件的本地向量化特性和矩阵计算特性进行加速。在执行过程中,Intel® Extension for PyTorch* 会拦截 ATen 运算符的调用,并用这些优化后的运算符替换原始的运算符。在 Intel® Extension for PyTorch* 中已经对流行的运算符,如卷积、线性进行了优化。

练习

让我们使用 Intel® Extension for PyTorch*对优化操作进行性能分析。我们将比较代码中添加和删除这些行前后的情况。

如前所述的练习,我们将工作负载绑定到第一个插槽的物理核心。

import torch

class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv = torch.nn.Conv2d(16, 33, 3, stride=2)
        self.relu = torch.nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

model = Model()
model.eval()
data = torch.rand(20, 16, 50, 100)

#################### code changes ####################
import intel_extension_for_pytorch as ipex
model = ipex.optimize(model)
######################################################

print(model)

该模型由两个操作组成——Conv2d 和 ReLU。通过打印模型对象,我们得到以下输出。

../_images/11.png

让我们收集一级 TMA 指标。

../_images/121.png

注意后端边界从 68.9 减少到 38.5 - 1.8 倍速度提升。

此外,让我们使用 PyTorch Profiler 进行性能分析。

../_images/131.png

注意 CPU 时间从 851 微秒减少到 310 微秒 - 2.7 倍速度提升。

图优化

非常推荐用户利用 Intel® Extension for PyTorch*与 TorchScript 进行进一步图优化。为了进一步利用 TorchScript 优化性能,Intel® Extension for PyTorch*支持常用 FP32/BF16 运算符模式(如 Conv2D+ReLU、Linear+ReLU 等)的 oneDNN 融合,以减少运算符/内核调用开销,并提高缓存局部性。一些运算符融合允许保持临时计算、数据类型转换、数据布局,以更好地提高缓存局部性。此外,对于 INT8,Intel® Extension for PyTorch*内置量化配方,以提供良好的统计精度,适用于包括 CNN、NLP 和推荐模型在内的流行深度学习工作负载。量化模型随后在 oneDNN 融合支持下进行优化。

练习

让我们使用 TorchScript 对 FP32 图优化进行性能分析。

如前所述的练习,我们将工作负载绑定到第一个插槽的物理核心。

import torch

class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv = torch.nn.Conv2d(16, 33, 3, stride=2)
        self.relu = torch.nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

model = Model()
model.eval()
data = torch.rand(20, 16, 50, 100)

#################### code changes ####################
import intel_extension_for_pytorch as ipex
model = ipex.optimize(model)
######################################################

# torchscript
with torch.no_grad():
    model = torch.jit.trace(model, data)
    model = torch.jit.freeze(model)

让我们收集一级 TMA 指标。

../_images/141.png

注意后端边界从 67.1 减少到 37.5 - 1.8 倍加速。

此外,让我们使用 PyTorch Profiler 进行性能分析。

../_images/151.png

注意,使用 Intel® Extension for PyTorch*时,Conv + ReLU 算子被融合,CPU 时间从 803 微秒减少到 248 微秒 - 3.2 倍加速。oneDNN eltwise 后操作允许将一个原语与一个逐元素原语融合。这是最受欢迎的融合类型之一:一个 eltwise(通常是激活函数,如 ReLU)与前面的卷积或内积。请查看下一节中显示的 oneDNN 详细日志。

通道最后记忆格式

当在模型上调用 ipex.optimize 时,Intel®扩展 PyTorch*会自动将模型转换为优化的内存格式,即通道最后。通道最后是一种更符合 Intel 架构的内存格式。与 PyTorch 默认的通道首先 NCHW(批次、通道、高度、宽度)内存格式相比,通道最后 NHWC(批次、高度、宽度、通道)内存格式通常可以加速卷积神经网络,并具有更好的缓存局部性。

需要注意的是,转换内存格式成本较高。因此,最好在部署前一次性转换内存格式,并在部署期间尽量减少内存格式转换。当数据通过模型的层传播时,通道最后内存格式会通过连续的通道最后支持的层(例如,Conv2d -> ReLU -> Conv2d)得到保留,转换仅在通道最后不支持层之间进行。有关更多详细信息,请参阅内存格式传播。

练习

让我们来演示通道最后优化。

import torch

class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv = torch.nn.Conv2d(16, 33, 3, stride=2)
        self.relu = torch.nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

model = Model()
model.eval()
data = torch.rand(20, 16, 50, 100)

import intel_extension_for_pytorch as ipex
############################### code changes ###############################
ipex.disable_auto_channels_last() # omit this line for channels_last (default)
############################################################################
model = ipex.optimize(model)

with torch.no_grad():
    model = torch.jit.trace(model, data)
    model = torch.jit.freeze(model)

我们将使用 oneDNN 详细模式,这是一个工具,可以帮助在 oneDNN 图级别收集信息,例如操作融合、执行 oneDNN 原语所花费的内核执行时间。有关更多信息,请参阅 oneDNN 文档。

../_images/161.png
../_images/171.png

上面的内容是来自通道第一的 oneDNN 详细模式。我们可以验证从权重和数据中存在重排序,然后进行计算,最后将输出重新排序。

../_images/181.png

上面的内容是来自通道最后的 oneDNN 详细模式。我们可以验证通道最后内存格式避免了不必要的重排序。

使用英特尔®扩展的 PyTorch*提升性能

以下总结了使用英特尔® Extension for PyTorch* 对 TorchServe 进行性能提升的 ResNet50 和 Bert-base-uncased。

../_images/191.png

使用 TorchServe 进行练习

让我们使用 TorchServe 分析英特尔® Extension for PyTorch* 优化。

我们将使用 TorchServe apache-bench 基准测试,使用 ResNet50 FP32 TorchScript,批处理大小 32,并发 32,请求数 8960。所有其他参数与默认参数相同。

如前所述的练习,我们将使用启动器将工作负载绑定到第一个插槽的物理核心。为此,用户只需在 config.properties 中添加几行即可。

cpu_launcher_enable=true
cpu_launcher_args=--node_id 0

让我们收集一级 TMA 指标。

../_images/20.png

一级 TMA 显示两者都受后端限制。如前所述,大多数未经调整的深度学习工作负载将是后端限制。注意后端限制从 70.0 降至 54.1。让我们再深入一层。

../_images/211.png

如前所述,后端绑定有两个子指标——内存绑定和核心绑定。内存绑定表示工作负载未优化或未充分利用,理想情况下,可以通过优化操作和改进缓存局部性将内存绑定操作改进为核心绑定。二级 TMA 显示后端绑定从内存绑定改进为核心绑定。让我们再深入一层。

../_images/221.png

在 TorchServe 等模型服务框架上对深度学习模型进行生产扩展需要高计算利用率。这要求数据通过预取并在执行单元需要时在缓存中重用数据来执行 uOps。三级 TMA 显示后端内存绑定从 DRAM 绑定改进为核心绑定。

如同之前使用 TorchServe 的练习,让我们使用 Intel® VTune Profiler ITT 来注释 TorchServe 推理范围,以在推理级别粒度进行性能分析。

../_images/231.png

每个推理调用都在时间线图中进行追踪。最后一个推理调用的持续时间从 215.731 毫秒减少到 95.634 毫秒,速度提升了 2.3 倍。

../_images/241.png

时间线图可以展开以查看操作级别的分析结果。注意,卷积+ReLU 已经融合,持续时间从 6.393 ms + 1.731 ms 减少到 3.408 ms,速度提升了 2.4 倍。

结论 ¶

在本教程中,我们使用了自顶向下的微架构分析(TMA)和英特尔® VTune™ 分析器的仪器和跟踪技术(ITT)来证明

  • 通常,未优化或未调优的深度学习工作负载的主要瓶颈是后端绑定,它有两个子指标,内存绑定和核心绑定。

  • 一个更高效的内存分配器,通过 Intel® Extension for PyTorch*的算子融合、内存布局格式优化,提升内存绑定性能。

  • 关键深度学习原语,如卷积、矩阵乘法、点积等,已通过 Intel® Extension for PyTorch*和 oneDNN 库得到良好优化,提升核心绑定性能。

  • Intel® Extension for PyTorch*已集成到 TorchServe 中,并提供易于使用的 API。

  • 使用 Intel® Extension for PyTorch*的 TorchServe 在 ResNet50 上实现了 7.71 倍的吞吐量提升,在 BERT 上实现了 2.20 倍的吞吐量提升。

致谢

我们要感谢 Ashok Emani(英特尔)和 Jiong Gong(英特尔)在许多步骤中的巨大指导和支持,以及详尽的反馈和审查。我们还要感谢 Hamid Shojanazeri(Meta)和 Li Ning(AWS)在代码审查和教程中的有益反馈。


评分这个教程

© 版权所有 2024,PyTorch。

使用 Sphinx 构建,主题由 Read the Docs 提供。
//暂时添加调查链接

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

获取初学者和高级开发者的深入教程

查看教程

资源

查找开发资源并获得您的疑问解答

查看资源