使用PyTorch Profiler进行模型性能分析,改善并加速PyTorch训练

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,5000CU*H 3个月
Elasticsearch Serverless检索通用型,资源抵扣包 100CU*H
简介: 加速机器学习模型训练是工程师的关键需求。PyTorch Profiler提供了一种分析工具,用于测量CPU和CUDA时间,以及内存使用情况。通过在训练代码中嵌入分析器并使用tensorboard查看结果,工程师可以识别性能瓶颈。Profiler的`record_function`功能允许为特定操作命名,便于跟踪。优化策略包括使用FlashAttention或FSDP减少内存使用,以及通过torch.compile提升速度。监控CUDA内核执行和内存分配,尤其是避免频繁的cudaMalloc,能有效提升GPU效率。内存历史记录分析有助于检测内存泄漏和优化批处理大小。

如果所有机器学习工程师都想要一样东西,那就是更快的模型训练——也许在良好的测试指标之后

加速机器学习模型训练是所有机器学习工程师想要的一件事。更快的训练等于更快的实验,更快的产品迭代,还有最重要的一点需要更少的资源,也就是更省钱。

熟悉PyTorch Profiler

在进行任何优化之前,你必须了解代码的某些部分运行了多长时间。Pytorch profiler是一个用于分析训练的一体化工具。它可以记录:

CPU操作时间、CUDA内核计时、内存消耗历史

要记录事件,只需要将训练嵌入到分析器上下文中,如下所示:

 import torch.autograd.profiler as profiler

 with profiler.profile(
   activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
   on_trace_ready=torch.profiler.tensorboard_trace_handler('./logs'),
 ) as prof:
   train(args)

然后就可以启动tensorboard查看分析轨迹。如果这一步有问题,请查看是否安装了torch-tb-profiler。

Profiler有很多不同的选项,但最重要的是activities和profile_memory,一般情况下我们只需要这两个选项,因为启用的选项越少,开销就越小。

如果只想要分析CUDA内核执行时间,那么关闭CPU分析和所有其他功能也是可以的。因为在这种模式下,我们可以理解为显卡能力的真实评测。

为了方便分析,我们可以为每一步操作指定名称,例如

 with profiler.record_function("forward_pass"):
   result = model(**batch)

 with profiler.record_function("train_step"):
   step(**result)

或者增加更精细的自定义的标签,这里的名称将在跟踪中可见,我们就可以更简单的追踪想要的东西了。

 with profiler.record_function("transformer_layer:self_attention"):
   data = self.self_attention(**data)

 ...

 with profiler.record_function("transformer_layer:encoder_attention"):
   data = self.encoder_attention(**data, **encoder_data)

查看PyTorch Traces

收集完信息后,tensorboard显示是这样的

训练的过程一般包括:数据加载、前向传播、反向传播

反向传播由PyTorch在一个单独的线程中处理(上图中的线程16893),因此很容易识别,这部分门也控制不了,因为都是Pytorch根据我们的计算来自动进行的。(当然也可以自定义反向传播,但是这过于复杂,一般不建议自己实现)

首先看看数据加载:对于数据加载我们希望时间接近于零。

这是因为在数据加载过程中,GPU什么也不做,这会使可用资源利用率不足。并且在Pytorch的训练时数据处理可以与GPU计算重叠,因为它们是独立的部分,也就是说我们加载一个批次的时间只要与一个前向和一个反向传播的时间相近就可以了,这样就可以最大化的利用GPU的资源。

这里可以很容易地识别GPU空闲的区域-查看性能分析器跟踪中的GPU Est. SM效率和GPU利用率数字。没有活动的区域是我们的关注点,因为GPU什么都不做。

如果使用PyTorch DataLoader,则可以通过指定num_workers来多线程处理数据。如果您使用IterableDataset,则会更复杂,因为数据将被复制。这个问题可以通过使用get_worker_info()来解决,需要以某种方式调整迭代,以便每个worker接收不同的、不相交的行,所以这个比较麻烦,一般尽量避免IterableDataset。

内存分配器 memory allocator

当你在CUDA设备上使用PyTorch分配张量时,PyTorch将使用缓存分配器。这里是CUDA的执行机制:cudaMalloc和cudaFree的操作比较昂贵,我们要尽量避免。所以PyTorch会尝试重用以前通过cudaMalloc块分配的,如果PyTorch的分配器有一个合适的块可用,它会直接给出它,而不调用cudaMalloc。这样cudaMalloc只在开始时被调用。

但是如果你处理的是可变长度的数据(比如文本数据),不同的正向传播将需要不同大小的中间张量。因此,PyTorch的分配器可能没有适当的可用数据块。在这种情况下,分配器会调用cudaFree释放以前分配的块,为新的分配释放空间。

然后分配器再次开始构建它的缓存,进行大量的cudaMalloc,这是一个昂贵的操作,但是可以通过tensorboard分析器查看器的内存分析器部分来发现这个问题。

可以看到与分配器的保留内存相对应的红线不断变化。这意味着PyTorch分配器不能有效地处理分配请求。而当分配程序在没有频繁调用的情况下处理分配时,红线是完全笔直的,如下图所示:

我们如何解决呢?

第一件值得尝试的事情是设置PyTorch相对较新的分配器模式:

 PYTORCH_CUDA_ALLOC_CONF="expandable_segments:True"

这告诉PyTorch分配器分配可以在将来扩展的块。但是,如果大小变化太大,它仍然可能无法解决问题。

所以我们智能手动来进行优化,那就是是使数据形状一致。这样分配器就更容易找到合适的数据块进行重用。

比如最简单的将数据填充到相同的大小。或者可以通过运行具有最大输入大小的模型来预热分配器。

内存历史记录

我们想要最大化的使用所有可用的GPU内存——这让我们能够运行大量数据,并更快地处理数据。但是在某些时候,当增加批处理太大时,将遇到CUDA内存不足错误。是什么导致了这个错误?

为了调试它,我们可以查看分配器的内存历史记录。它可以通过PyTorch记录,然后在https://pytorch.org/memory_viz上可视化

  • Start:torch.cuda.memory._record_memory_history(max_entries=100000)
  • Save:torch.cuda.memory._dump_snapshot(file_name)
  • Stop:torch.cuda.memory._record_memory_history(enabled=None)

可视化会画出这样的东西:

x轴表示时间,y轴表示已使用的总内存,彩色块表示张量。它显示了张量何时被分配,何时被释放。

你可能会注意到狭窄的尖峰,这些是持续时间很短的张量,并且占据了很多空间。通过点击一个张量,可以得到这个张量被分配到哪里的信息。我们希望的就是最小化这些峰值,因为它们限制了有效的内存使用。检查导致这个峰值的原因,并考虑优化或者使用其他计算方法替代。

除了峰值之外,很容易检测到内存泄漏:

第一次运行之后的一些数据没有被清除,所以导致内存占用过高。通过点击块,可以知道这些张量是从哪里来的。在图像中,梯度在训练步骤之后没有被清除,因此它们在向前传递过程中处于无用状态,占用了宝贵的内存。

提高模型速度,减少内存使用

我们知道了原因,并且可以通过Profiler来找到瓶颈,那么我们可以通过什么方法来加速训练呢?

1、FlashAttention

首先可以使用FlashAttention来计算点积注意力来提高效率。如果你没有听说过它,它是一种计算精确的点积注意力的方法,并且不需要明确地构建注意力矩阵。这优化了GPU的io操作,提高了速度,也极大地减少了内存消耗。

但是FlashAttention仅适用于兼容硬件上的fp16和bf16精度。那就是NVIDIA Ampere, Hooper以上的GPU

当然也有其他的库可以替换,例如XFormers,和NV自己的Transformer Engine

新版本的PyTorch也内置了FlashAttention的支持,在文档中:

torch.backends.cuda.enable_flash_sdp()

: Globally enables or disables FlashAttention.

2、 FSDP 优化多gpu数据冗余

如果使用多个gpu来运行训练,基本的解决方案是使用DistributedDataParallel。生成了几个相同的进程,并且在反向传播期间聚合梯度。

当我们生成相同的进程时,在每个GPU上都有相同的模型和优化器状态,这是冗余的。可以通过跨数据分片来优化内存使用

当在多个gpu上进行训练时,每个进程在使用DDP进行训练时都有相同数据的精确副本。可以通过实现以下几个增强功能来优化它:

ZeRO 1 :分片优化器状态

当使用DDP进行训练时,每个进程都拥有优化器状态的完整副本。对于zer01,可以让每个rank只保留优化器状态的一部分。在反向传播期间,每个rank只需要收集与其参数相关的优化器状态来进行优化步骤。这种冗余的减少有助于节省内存。

💡在Adam的情况下,它保存的参数大约是模型大小的两倍,将优化器状态分片为8个rank意味着每个rank只存储总状态大小的四分之一(2/8)。

ZeRO 2:梯度分片

除对优化器状态进行分片外,还可以修改优化器步骤来切分梯度。我么可以

将所有与该rank持有的状态相关的梯度集合起来,计算优化步骤,然后将部分参数的优化步骤发送给所有其他rank

现在每个rank不需要保存一个完整的梯度副本,这样可以进一步降低峰值内存消耗。

ZeRO 3 :模型参数分片

我么不需要在每个rank上存储模型的完整副本,我们将在向前和向后期间及时获取所需的参数。在大型模型的情况下,这些优化可以显著降低内存消耗

如何使用FSDP?

其实很简单。我们所需要的就是用FSDP包裹模型:

 import torch
 import torch.nn as nn
 import torch.optim as optim
 from torch.distributed.fsdp import FullyShardedDataParallel as FSDP


 model = FSDP(model)

 # it's critical to get parameters from the wrapped model
 # as only a portion of them returned (sharded part)
 optimizer = optim.Adam(model.parameters())

 # consuct training as usual
 train(model, optimizer)

可以指定FSDP的分片策略。例如可以选择SHARD_GRAD_OP策略来实现与ZeRO2类似的行为。

3、torch.compile

这是最简单也是最直接的优化方式了,只要启用torch compile,它就可以将代码的速度提高几个百分点。

在Torch2.0中增加了compile方法,他会跟踪执行图,并尝试将其编译成一种有效的格式,以便几乎无需Python调用即可执行模型。

 import torch

 model = torch.compile(model)

也就是说,2.0以后只要你的模型能用compile那么就用compile吧。

总结

本文中介绍了使用PyTorch Profiler来查找运行瓶颈,并且介绍了一些简单的提速方法,虽然这篇文章没有完整的解释,但是里面提供的方法都是值得马上尝试方法,希望对大家有所帮助。

https://avoid.overfit.cn/post/95f7fa956805466db713e797d9d62e67

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
1月前
|
机器学习/深度学习 PyTorch API
PyTorch量化感知训练技术:模型压缩与高精度边缘部署实践
本文深入探讨神经网络模型量化技术,重点讲解训练后量化(PTQ)与量化感知训练(QAT)两种主流方法。PTQ通过校准数据集确定量化参数,快速实现模型压缩,但精度损失较大;QAT在训练中引入伪量化操作,使模型适应低精度环境,显著提升量化后性能。文章结合PyTorch实现细节,介绍Eager模式、FX图模式及PyTorch 2导出量化等工具,并分享大语言模型Int4/Int8混合精度实践。最后总结量化最佳策略,包括逐通道量化、混合精度设置及目标硬件适配,助力高效部署深度学习模型。
155 21
PyTorch量化感知训练技术:模型压缩与高精度边缘部署实践
|
3月前
|
机器学习/深度学习 JavaScript PyTorch
9个主流GAN损失函数的数学原理和Pytorch代码实现:从经典模型到现代变体
生成对抗网络(GAN)的训练效果高度依赖于损失函数的选择。本文介绍了经典GAN损失函数理论,并用PyTorch实现多种变体,包括原始GAN、LS-GAN、WGAN及WGAN-GP等。通过分析其原理与优劣,如LS-GAN提升训练稳定性、WGAN-GP改善图像质量,展示了不同场景下损失函数的设计思路。代码实现覆盖生成器与判别器的核心逻辑,为实际应用提供了重要参考。未来可探索组合优化与自适应设计以提升性能。
206 7
9个主流GAN损失函数的数学原理和Pytorch代码实现:从经典模型到现代变体
|
8天前
|
机器学习/深度学习 PyTorch 算法框架/工具
提升模型泛化能力:PyTorch的L1、L2、ElasticNet正则化技术深度解析与代码实现
本文将深入探讨L1、L2和ElasticNet正则化技术,重点关注其在PyTorch框架中的具体实现。关于这些技术的理论基础,建议读者参考相关理论文献以获得更深入的理解。
35 4
提升模型泛化能力:PyTorch的L1、L2、ElasticNet正则化技术深度解析与代码实现
|
28天前
|
机器学习/深度学习 PyTorch 编译器
深入解析torch.compile:提升PyTorch模型性能、高效解决常见问题
PyTorch 2.0推出的`torch.compile`功能为深度学习模型带来了显著的性能优化能力。本文从实用角度出发,详细介绍了`torch.compile`的核心技巧与应用场景,涵盖模型复杂度评估、可编译组件分析、系统化调试策略及性能优化高级技巧等内容。通过解决图断裂、重编译频繁等问题,并结合分布式训练和NCCL通信优化,开发者可以有效提升日常开发效率与模型性能。文章为PyTorch用户提供了全面的指导,助力充分挖掘`torch.compile`的潜力。
108 17
|
21天前
|
机器学习/深度学习 搜索推荐 PyTorch
基于昇腾用PyTorch实现CTR模型DIN(Deep interest Netwok)网络
本文详细讲解了如何在昇腾平台上使用PyTorch训练推荐系统中的经典模型DIN(Deep Interest Network)。主要内容包括:DIN网络的创新点与架构剖析、Activation Unit和Attention模块的实现、Amazon-book数据集的介绍与预处理、模型训练过程定义及性能评估。通过实战演示,利用Amazon-book数据集训练DIN模型,最终评估其点击率预测性能。文中还提供了代码示例,帮助读者更好地理解每个步骤的实现细节。
|
2月前
|
存储 自然语言处理 PyTorch
从零开始用Pytorch实现LLaMA 4的混合专家(MoE)模型
近期发布的LLaMA 4模型引入混合专家(MoE)架构,以提升效率与性能。尽管社区对其实际表现存在讨论,但MoE作为重要设计范式再次受到关注。本文通过Pytorch从零实现简化版LLaMA 4 MoE模型,涵盖数据准备、分词、模型构建(含词元嵌入、RoPE、RMSNorm、多头注意力及MoE层)到训练与文本生成全流程。关键点包括MoE层实现(路由器、专家与共享专家)、RoPE处理位置信息及RMSNorm归一化。虽规模小于实际LLaMA 4,但清晰展示MoE核心机制:动态路由与稀疏激活专家,在控制计算成本的同时提升性能。完整代码见链接,基于FareedKhan-dev的Github代码修改而成。
78 9
从零开始用Pytorch实现LLaMA 4的混合专家(MoE)模型
|
2月前
|
机器学习/深度学习 数据可视化 机器人
比扩散策略更高效的生成模型:流匹配的理论基础与Pytorch代码实现
扩散模型和流匹配是生成高分辨率数据(如图像和机器人轨迹)的先进技术。扩散模型通过逐步去噪生成数据,其代表应用Stable Diffusion已扩展至机器人学领域形成“扩散策略”。流匹配作为更通用的方法,通过学习时间依赖的速度场将噪声转化为目标分布,适用于图像生成和机器人轨迹生成,且通常以较少资源实现更快生成。 本文深入解析流匹配在图像生成中的应用,核心思想是将图像视为随机变量的实现,并通过速度场将源分布转换为目标分布。文中提供了一维模型训练实例,展示了如何用神经网络学习速度场,以及使用最大均值差异(MMD)改进训练效果。与扩散模型相比,流匹配结构简单,资源需求低,适合多模态分布生成。
120 13
比扩散策略更高效的生成模型:流匹配的理论基础与Pytorch代码实现
|
2月前
|
机器学习/深度学习 编解码 PyTorch
从零实现基于扩散模型的文本到视频生成系统:技术详解与Pytorch代码实现
本文介绍了一种基于扩散模型的文本到视频生成系统,详细展示了模型架构、训练流程及生成效果。通过3D U-Net结构和多头注意力机制,模型能够根据文本提示生成高质量视频。
98 1
从零实现基于扩散模型的文本到视频生成系统:技术详解与Pytorch代码实现
|
4月前
|
机器学习/深度学习 算法 安全
用PyTorch从零构建 DeepSeek R1:模型架构和分步训练详解
本文详细介绍了DeepSeek R1模型的构建过程,涵盖从基础模型选型到多阶段训练流程,再到关键技术如强化学习、拒绝采样和知识蒸馏的应用。
493 3
用PyTorch从零构建 DeepSeek R1:模型架构和分步训练详解
|
4月前
|
存储 机器学习/深度学习 PyTorch
PyTorch Profiler 性能优化示例:定位 TorchMetrics 收集瓶颈,提高 GPU 利用率
本文探讨了机器学习项目中指标收集对训练性能的影响,特别是如何通过简单实现引入不必要的CPU-GPU同步事件,导致训练时间增加约10%。使用TorchMetrics库和PyTorch Profiler工具,文章详细分析了性能瓶颈的根源,并提出了多项优化措施
156 1
PyTorch Profiler 性能优化示例:定位 TorchMetrics 收集瓶颈,提高 GPU 利用率