我的项目链接,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 paddleimport paddle.nn.functional as Ffrom paddle.vision.transforms import ToTensorimport numpy as npimport matplotlib.pyplot as pltprint(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 = 10batch_size = 32learning_rate = 0.001val_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.8434960246086121epoch: 7, batch_id: 0, loss is: [0.60690844]epoch: 7, batch_id: 1000, loss is: [0.6912922][validation] accuracy/loss: 0.7049720287322998/0.887704074382782epoch: 8, batch_id: 0, loss is: [0.6330824]epoch: 8, batch_id: 1000, loss is: [0.5715592][validation] accuracy/loss: 0.7176517844200134/0.8511289954185486epoch: 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 |
<font size=3 color="red">此时从原来的Reader Ratio: 35.240%---->Reader Ratio: 1.718%
ips的值变成了137.3,相比优化前的baseline70.99,模型真实性能晋升了193%。
</font>
<font size=3 color="blue">留神点:</font>
因为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