Paddle模型性能分析工具Profiler:定位瓶颈点、优化程序、提升性能
项目链接,fork一下即可使用
https://aistudio.baidu.com/aistudio/projectdetail/4482932?contributionType=1
Paddle模型性能分析Profiler:定位性能瓶颈点优化程序提升性能
Paddle Profiler是飞桨框架自带的低开销性能分析器,可以对模型运行过程的性能数据进行收集、统计和展示。性能分析器提供的数据可以帮助定位模型的瓶颈,识别造成程序运行时间过长或者GPU利用率低的原因,从而寻求优化方案来获得性能的提升。
1.使用Profiler工具调试程序性能
在模型性能分析中,通常采用如下四个步骤:
获取模型正常运行时的ips(iterations per second, 每秒的迭代次数),给出baseline数据。
开启性能分析器,定位性能瓶颈点。
优化程序,检查优化效果。
获取优化后模型正常运行时的ips,和baseline比较,计算真实的提升幅度。
下面是使用神经网络对cifar10进行分类的示例代码,里面加上了启动性能分析的代码。通过这个比较简单的示例,来看性能分析工具是如何通过上述四个步骤在调试程序性能中发挥作用。
1.1 使用cifar10数据集卷积神经网络进行图像分类
import paddle
import paddle.nn.functional as F
from paddle.vision.transforms import ToTensor
import numpy as np
import matplotlib.pyplot as plt
print(paddle.__version__)
加载数据集
cifar10数据集由60000张大小为32 * 32的彩色图片组成,其中有50000张图片组成了训练集,另外10000张图片组成了测试集。这些图片分为10个类别,将训练一个模型能够把图片进行正确的分类。
transform = ToTensor()
cifar10_train = paddle.vision.datasets.Cifar10(mode='train',
transform=transform)
cifar10_test = paddle.vision.datasets.Cifar10(mode='test',
transform=transform)
组建网络
接下来使用飞桨定义一个使用了三个二维卷积( Conv2D ) 且每次卷积之后使用 relu 激活函数,两个二维池化层( MaxPool2D ),和两个线性变换层组成的分类网络,来把一个(32, 32, 3)形状的图片通过卷积神经网络映射为10个输出,这对应着10个分类的类别
class MyNet(paddle.nn.Layer):
def __init__(self, num_classes=1):
super(MyNet, self).__init__()
self.conv1 = paddle.nn.Conv2D(in_channels=3, out_channels=32, kernel_size=(3, 3))
self.pool1 = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
self.conv2 = paddle.nn.Conv2D(in_channels=32, out_channels=64, kernel_size=(3,3))
self.pool2 = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
self.conv3 = paddle.nn.Conv2D(in_channels=64, out_channels=64, kernel_size=(3,3))
self.flatten = paddle.nn.Flatten()
self.linear1 = paddle.nn.Linear(in_features=1024, out_features=64)
self.linear2 = paddle.nn.Linear(in_features=64, out_features=num_classes)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = F.relu(x)
x = self.pool2(x)
x = self.conv3(x)
x = F.relu(x)
x = self.flatten(x)
x = self.linear1(x)
x = F.relu(x)
x = self.linear2(x)
return x
模型训练&预测
接下来,用一个循环来进行模型的训练,将会:
使用 paddle.optimizer.Adam 优化器来进行优化。
使用 F.cross_entropy 来计算损失值。
使用 paddle.io.DataLoader 来加载数据并组建batch。
import paddle.profiler as profiler
#参数设置
epoch_num = 10
batch_size = 32
learning_rate = 0.001
val_acc_history = []
val_loss_history = []
def train(model):
print('start training ... ')
# turn into training mode
model.train()
opt = paddle.optimizer.Adam(learning_rate=learning_rate,
parameters=model.parameters())
train_loader = paddle.io.DataLoader(cifar10_train,
shuffle=True,
batch_size=batch_size,
num_workers=4)
valid_loader = paddle.io.DataLoader(cifar10_test, batch_size=batch_size)
# 创建性能分析器相关的代码
def my_on_trace_ready(prof):# 定义回调函数,性能分析器结束采集数据时会被调用
callback = profiler.export_chrome_tracing('./profiler_demo') # 创建导出性能数据到profiler_demo文件夹的回调函数
callback(prof) # 执行该导出函数
prof.summary(sorted_by=profiler.SortedKeys.GPUTotal) # 打印表单,按GPUTotal排序表单项
p = profiler.Profiler(scheduler = [3,14], on_trace_ready=my_on_trace_ready, timer_only=True) # 初始化Profiler对象
p.start() # 性能分析器进入第0个step
for epoch in range(epoch_num):
for batch_id, data in enumerate(train_loader()):
x_data = data[0]
y_data = paddle.to_tensor(data[1])
y_data = paddle.unsqueeze(y_data, 1)
logits = model(x_data)
loss = F.cross_entropy(logits, y_data)
if batch_id % 1000 == 0:
print("epoch: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, loss.numpy()))
loss.backward()
opt.step()
opt.clear_grad()
p.step() # 指示性能分析器进入下一个step
if batch_id == 19:
p.stop() # 关闭性能分析器
exit() # 做性能分析时,可以将程序提前退出
# evaluate model after one epoch
model.eval()
accuracies = []
losses = []
for batch_id, data in enumerate(valid_loader()):
x_data = data[0]
y_data = paddle.to_tensor(data[1])
y_data = paddle.unsqueeze(y_data, 1)
logits = model(x_data)
loss = F.cross_entropy(logits, y_data)
acc = paddle.metric.accuracy(logits, y_data)
accuracies.append(acc.numpy())
losses.append(loss.numpy())
avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)
print("[validation] accuracy/loss: {}/{}".format(avg_acc, avg_loss))
val_acc_history.append(avg_acc)
val_loss_history.append(avg_loss)
model.train()
model = MyNet(num_classes=10)
train(model)
**部分结果展示:**
epoch: 6, batch_id: 0, loss is: [0.91811454]
epoch: 6, batch_id: 1000, loss is: [0.89851004]
[validation] accuracy/loss: 0.7232428193092346/0.8434960246086121
epoch: 7, batch_id: 0, loss is: [0.60690844]
epoch: 7, batch_id: 1000, loss is: [0.6912922]
[validation] accuracy/loss: 0.7049720287322998/0.887704074382782
epoch: 8, batch_id: 0, loss is: [0.6330824]
epoch: 8, batch_id: 1000, loss is: [0.5715592]
[validation] accuracy/loss: 0.7176517844200134/0.8511289954185486
epoch: 9, batch_id: 0, loss is: [0.29487646]
epoch: 9, batch_id: 1000, loss is: [0.9094696]
[validation] accuracy/loss: 0.7097643613815308/0.9166476130485535
1.1.1 获取性能调试前模型正常运行的ips
上述程序在创建Profiler时候,timer_only设置的值为True,此时将只开启benchmark功能,不开启性能分析器,程序输出模型正常运行时的benchmark信息如下
Reader Ratio:表示数据读取部分占训练batch迭代过程的时间占比,
reader_cost:代表数据读取时间,
batch_cost:代表batch迭代的时间,
ips:表示每秒能迭代多少次,即跑多少个batch。
可以看到,此时的ips为70.99,可将这个值作为优化对比的baseline。
============================================Perf Summary============================================
Reader Ratio: 35.240%
Time Unit: s, IPS Unit: steps/s
| | avg | max | min |
| reader_cost | 0.00496 | 0.00542 | 0.00469 |
| batch_cost | 0.01408 | 0.01325 | 0.01246 |
| ips | 70.99914 | 80.24470 | 75.46403 |
1.1.2. 开启性能分析器,定位性能瓶颈点
修改程序,将Profiler的timer_only参数设置为False, 此时代表不只开启benchmark功能,还将开启性能分析器,进行详细的性能分析。
p = profiler.Profiler(scheduler = [3,14], on_trace_ready=my_on_trace_ready, timer_only=False)
性能分析器会收集程序在第3到14次(不包括14)训练迭代过程中的性能数据,并在profiler_demo文件夹中输出一个json格式的文件,用于展示程序执行过程的timeline,可通过chrome浏览器的chrome://tracing 插件打开这个文件进行查看。
如图所示,把json文件load即可:
性能分析器还会直接在终端打印统计表单(建议重定向到文件中查看),查看程序输出的Model Summary表单
-----------------------------------------------Model Summary-----------------------------------------------
Time unit: ms
--------------- ------ ---------------------------------------- ----------------------------------------
Name Calls CPU Total / Avg / Max / Min / Ratio(%) GPU Total / Avg / Max / Min / Ratio(%)
--------------- ------ ---------------------------------------- ----------------------------------------
ProfileStep 11 138.99 / 12.64 / 17.91 / 10.65 / 100.00 8.81 / 0.80 / 0.80 / 0.80 / 100.00
Dataloader 11 16.88 / 1.53 / 6.91 / 0.09 / 12.14 0.00 / 0.00 / 0.00 / 0.00 / 0.00
Forward 11 45.18 / 4.11 / 4.41 / 3.61 / 32.50 2.73 / 0.25 / 0.25 / 0.25 / 31.01
Backward 11 27.63 / 2.51 / 2.85 / 2.37 / 19.88 4.04 / 0.37 / 0.37 / 0.36 / 45.81
Optimization 11 19.75 / 1.80 / 1.89 / 1.61 / 14.21 1.05 / 0.10 / 0.10 / 0.09 / 11.56
Others - 29.55 / - / - / - / 21.26 1.05 / - / - / - / 11.63
--------------- ------ ---------------------------------------- ----------------------------------------
Note:
在此表中,GPU 时间是该阶段调用的所有设备(GPU)事件的总和。
与概述摘要不同,如果两个设备(GPU)事件在不同的流上执行重叠时间,我们直接在这里求和。
其中ProfileStep表示训练batch的迭代step过程,对应代码中每两次调用p.step()的间隔时间;
Dataloader表示数据读取的时间,即for batch_id, data in enumerate(train_loader())的执行时间;
Forward表示模型前向的时间,即logits = model(x_data)的执行时间,
Backward表示反向传播的时间,即loss.backward()的执行时间;
Optimization表示优化器的时间,即opt.step()的执行时间。
通过timeline可以看到,Dataloader占了执行过程的很大比重,Model Summary显示其接近了12%。分析程序发现,这是由于模型本身比较简单,需要的计算量小,再加上Dataloader 准备数据时只用了单进程来读取,使得程序读取数据时和执行计算时没有并行操作,导致Dataloader占比过大。
1.1.3. 优化程序,检查优化效果
识别到了问题产生的原因,对程序继续做如下修改,将Dataloader的num_workers设置为4,使得能有多个进程并行读取数据。
train_loader = paddle.io.DataLoader(cifar10_train,
shuffle=True,
batch_size=batch_size,
num_workers=4)
重新对程序进行性能分析,新的timeline和Model Summary如下所示
-----------------------------------------------Model Summary-----------------------------------------------
Time unit: ms
--------------- ------ ---------------------------------------- ----------------------------------------
Name Calls CPU Total / Avg / Max / Min / Ratio(%) GPU Total / Avg / Max / Min / Ratio(%)
--------------- ------ ---------------------------------------- ----------------------------------------
ProfileStep 11 89.44 / 8.13 / 8.76 / 7.82 / 100.00 8.82 / 0.80 / 0.80 / 0.80 / 100.00
Dataloader 11 1.51 / 0.14 / 0.16 / 0.12 / 1.69 0.00 / 0.00 / 0.00 / 0.00 / 0.00
Forward 11 31.67 / 2.88 / 3.17 / 2.82 / 35.41 2.72 / 0.25 / 0.25 / 0.24 / 36.11
Backward 11 25.35 / 2.30 / 2.49 / 2.20 / 28.34 4.07 / 0.37 / 0.37 / 0.37 / 42.52
Optimization 11 11.67 / 1.06 / 1.16 / 1.01 / 13.04 1.04 / 0.09 / 0.10 / 0.09 / 10.59
Others - 19.25 / - / - / - / 21.52 1.06 / - / - / - / 10.78
--------------- ------ ---------------------------------------- ----------------------------------------
可以看到,从Dataloader中取数据的时间大大减少,从12%变成了平均只占一个step的1.69%,并且平均一个step所需要的时间也相应减少了从1.53到0.14。
### 1.1.4 获取优化后模型正常运行的ips,确定真实提升幅度
重新将timer_only设置的值为True,获取优化后模型正常运行时的benchmark信息
============================================Perf Summary============================================
Reader Ratio: 1.718%
Time Unit: s, IPS Unit: steps/s
| | avg | max | min |
| reader_cost | 0.00013 | 0.00015 | 0.00012 |
| batch_cost | 0.00728 | 0.00690 | 0.00633 |
| ips | 137.30879 | 158.01126 | 144.91796 |
此时从原来的Reader Ratio: 35.240%---->Reader Ratio: 1.718%
ips的值变成了137.3,相比优化前的baseline70.99,模型真实性能提升了193%。
注意点:
由于Profiler开启的时候,收集性能数据本身也会造成程序性能的开销,因此正常跑程序时请不要开启性能分析器,性能分析器只作为调试程序性能时使用。
1.如果想获得程序正常运行时候的 benchmark信息(如ips),可以像示例一样将Profiler的timer_only参数设置为True,此时不会进行详尽的性能数据收集,几乎不影响程序正常运行的性能,所获得的benchmark信息也趋于真实。
2.benchmark信息计算的数据范围是从调用Profiler的start方法开始,到调用stop方法结束这个过程的数据。而Timeline和性能数据的统计表单的数据范围是所指定的采集区间,如这个例子中的第3到14次迭代,这会导致开启性能分析器时统计表单和benchmark信息输出的值不同(如统计到的Dataloader的时间占比)。
3.当benchmark统计的范围和性能分析器统计的范围不同时, 由于benchmark统计的是平均时间,如果benchmark统计的范围覆盖了性能分析器开启的范围,也覆盖了关闭性能调试时的正常执行的范围,此时benchmark的值没有意义,因此开启性能分析器时请以性能分析器输出的统计表单为参考,这也是为何上面示例里在开启性能分析器时没贴benchmark信息的原因。
1.1.5 结果展示
#结果展示
plt.plot(val_acc_history, label = 'validation accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5, 0.8])
plt.legend(loc='lower right')
2.2 统计表单展示
统计表单负责对采集到的数据(Event)从多个不同的角度进行解读,也可以理解为对timeline进行一些量化的指标计算。 目前提供Device Summary、Overview Summary、Model Summary、Distributed Summary、Operator Summary、Kernel Summary、Memory Manipulation Summary和UserDefined Summary的统计表单,每个表单从不同的角度进行统计计算。每个表单的统计内容简要叙述如下:
Device Summary
-------------------Device Summary-------------------
------------------------------ --------------------
Device Utilization (%)
------------------------------ --------------------
CPU(Process) 77.13
CPU(System) 25.99
GPU2 55.50
------------------------------ --------------------
Note:
CPU(进程) 利用率 = 当前进程在所有 cpu 内核上的 CPU 时间/经过的时间,因此最大利用率可以达到 100% * cpu 内核数。
CPU(系统)利用率=所有进程在所有cpu内核上的CPU时间(忙碌时间)/(忙碌时间+空闲时间)。
GPU 利用率 = 当前进程 GPU 时间 / 已用时间。
----------------------------------------------------
DeviceSummary提供CPU和GPU的平均利用率信息。其中
CPU(Process): 指的是进程的cpu平均利用率,算的是从Profiler开始记录数据到结束这一段过程,进程所利用到的 cpu core的总时间与该段时间的占比。因此如果是多核的情况,对于进程来说cpu平均利用率是有可能超过100%的,因为同时用到的多个core的时间进行了累加。
CPU(System): 指的是整个系统的cpu平均利用率,算的是从Profiler开始记录数据到结束这一段过程,整个系统所有进程利用到的cpu core总时间与该段时间乘以cpu core的数量的占比。可以当成是从cpu的视角来算的利用率。
GPU: 指的是进程的gpu平均利用率,算的是从Profiler开始记录数据到结束这一段过程,进程在gpu上所调用的kernel的执行时间 与 该段时间 的占比。
Overview Summary
Overview Summary用于展示每种类型的Event一共分别消耗了多少时间,对于多线程或多stream下,如果同一类型的Event有重叠的时间段,采取取并集操作,不对重叠的时间进行重复计算。
---------------------------------------------Overview Summary---------------------------------------------
Time unit: ms
------------------------- ------------------------- ------------------------- -------------------------
Event Type Calls CPU Time Ratio (%)
------------------------- ------------------------- ------------------------- -------------------------
ProfileStep 8 4945.15 100.00
CudaRuntime 28336 2435.63 49.25
UserDefined 486 2280.54 46.12
Dataloader 8 1819.15 36.79
Forward 8 1282.64 25.94
Operator 8056 1244.41 25.16
OperatorInner 21880 374.18 7.57
Backward 8 160.43 3.24
Optimization 8 102.34 2.07
------------------------- ------------------------- ------------------------- -------------------------
Calls GPU Time Ratio (%)
------------------------- ------------------------- ------------------------- -------------------------
Kernel 13688 2744.61 55.50
Memcpy 496 29.82 0.60
Memset 104 0.12 0.00
Communication 784 257.23 5.20
------------------------- ------------------------- ------------------------- -------------------------
Note:
在此表中,我们根据事件类型汇总了所有收集到的事件。
在主机上收集的事件时间显示为 CPU 时间,如果在设备上则显示为 GPU 时间。
不同类型的事件可能会重叠或包含,例如 Operator 包括 OperatorInner,因此比率之和不是 100%。
有重叠的同类型事件的时间不会计算两次,合并后所有时间相加。
Example:
Thread 1:
Operator: |___________| |__________|
Thread 2:
Operator: |____________| |___|
After merged:
Result: |______________| |__________|
----------------------------------------------------------------------------------------------------------
不在继续详细说明,参考项目即可:
项目链接,fork一下即可使用
https://aistudio.baidu.com/aistudio/projectdetail/4482932?contributionType=1
Paddle模型性能分析工具Profiler:定位瓶颈点、优化程序、提升性能的更多相关文章
- 11个Visual Studio代码性能分析工具
软件开发中的性能优化对程序员来说是一个非常重要的问题.一个小问题可能成为一个大的系统的瓶颈.但是对于程序员来说,通过自身去优化代码是十分困难的.幸运的是,有一些非常棒的工具可以帮助程序员进行代码分析和 ...
- Linux性能分析工具与图形化方法
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~. 作者:赵坤|腾讯魔王工作室后台开发工程师 在项目开发中,经常会遇到程序启动时间过长.CPU使用率过高等问题,这个时候需要依靠性能分析工具来 ...
- Linux 性能分析工具汇总合集
出于对Linux操作系统的兴趣,以及对底层知识的强烈欲望,因此整理了这篇文章.本文也可以作为检验基础知识的指标,另外文章涵盖了一个系统的方方面面.如果没有完善的计算机系统知识,网络知识和操作系统知识, ...
- [转]Linux性能分析工具汇总合集
出于对Linux操作系统的兴趣,以及对底层知识的强烈欲望,因此整理了这篇文章.本文也可以作为检验基础知识的指标,另外文章涵盖了一个系统的方方面面.如果没有完善的计算机系统知识,网络知识和操作系统知识, ...
- 超全整理!Linux性能分析工具汇总合集
转自:http://rdc.hundsun.com/portal/article/731.html?ref=myread 出于对Linux操作系统的兴趣,以及对底层知识的强烈欲望,因此整理了这篇文章. ...
- (转)超全整理!Linux性能分析工具汇总合集
超全整理!Linux性能分析工具汇总合集 原文:http://rdc.hundsun.com/portal/article/731.html 出于对Linux操作系统的兴趣,以及对底层知识的强烈欲望, ...
- Linux 性能分析 工具命令
背景知识:具备背景知识是分析性能问题时需要了解的.比如硬件 cache:再比如操作系统内核.应用程序的行为细节往往是和这些东西互相牵扯的,这些底层的东西会以意想不到的方式影响应用程序的性能,比如某些程 ...
- CLR Profiler 性能分析工具
CLR Profiler 性能分析工具 CLR Profiler 性能分析工具 CLR Profiler 有两个版本,分别用于CLR1.1 和 CLR2.0,至于CLR4试了一些也可以,但不知道是否完 ...
- profiler跟踪事件存为表之后性能分析工具
使用profiler建立跟踪,将跟踪结果存到表中,使用下面存储过程执行 exec temp_profiler 'tra_tablename'对表数据进行处理归类,然后进行性能分析 1.先建存储过程 2 ...
- 【转载】推荐5款超实用的.NET性能分析工具
来源:http://www.csdn.net/article/2012-11-23/2812174-5-Good-and-useful-.NET-Profilers 虽然.NET框架号称永远不会发生内 ...
随机推荐
- VS 进行了无法编译的编辑
Debug的时候碰到这个很烦人得重新运行 工具=>选项=>常规=>调试=>常规 将启用"编辑"并继续,勾取消掉
- C# Winform 自定义窗口,最大化遮住任务栏
解决 C# Winform 自定义窗口,最大化遮住任务栏 的问题,可以通过获取屏幕大小来控制最大值,来实现,代码如下 Rectangle ScreenArea = System.Windows.For ...
- python版本升级到3.8以及安装虚拟环境
linux系统升级python版本看起来复杂,如果知道其中步骤也不会觉得很困难.下面记录在deepin系统 15.11和ubuntu16.04系统下将python从2.7升级到3.8过程 升级pyth ...
- 2019 篇 - 分享数百个 HT的工业互联网 2D 3D 可视化应用案例
继<分享数百个 HT 工业互联网 2D 3D 可视化应用案例>2018 篇,图扑软件定义 2018 为国内工业互联网可视化的元年后,2019 年里我们与各行业客户进行了更深度合作,拓展了H ...
- <vue 路由 8、keep-alive的使用>
一. 知识点 1.什么是keep-alive? keep-alive是Vue.js的一个内置组件. 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们.它自身不会渲染一个 DOM 元素,也 ...
- uni-app app定位当前地理位置
https://blog.csdn.net/HXH_csdn/article/details/112258398?utm_medium=distribute.pc_relevant.none-task ...
- 房贷LPR该如何选择
一.新政是否和你有关? 原文:是指2020年1月1日前金融机构已发放的和已签订合同但未发放的参考贷款基准利率定价的浮动利率贷款(不包括公积金个人住房贷款). 二.新政如何调整? 一种是按照LPR加 ...
- Kubernetes: client-go 源码剖析(二)
kubernetes:client-go 系列文章: Kubernetes: client-go 源码剖析(一) Kubernetes: client-go 源码剖析(二) 2.3 运行 inform ...
- MyBatis 系列:MyBatis 源码环境搭建
目录 一.环境准备 二.下载 MyBatis 源码和 MyBatis-Parent 源码 三.创建空项目.导入项目 四.编译 mybatis-parent 五.编译 mybatis 六.测试 总结 一 ...
- cs 保研经验贴 | 数学试题 · 自动化所特供版
据(2022 年我所看的)往年经验,自动化所比较重视数学. 感觉,按照自动化所的数学题库复习,就足以应付大多数夏令营的笔试面试了. 目录 高等数学 线性代数 概率论 机器学习 复变函数 其他 同站相关 ...