如何精确统计Pytorch模型推理时间

文章目录

  • 0 背景
  • 1 精确统计方法
  • 2 手动synchronize和Event适用场景


0 背景

在分析模型性能时需要精确地统计出模型的推理时间,但仅仅通过在模型推理前后打时间戳然后相减得到的时间其实是Host侧向Device侧下发指令的时间。如下图所示,Host侧下发指令与Device侧计算实际上是异步进行的。
在这里插入图片描述


1 精确统计方法

比较常用的精确统计方法有两种,一种是手动调用同步函数等待Device侧计算完成。另一种是通过Event方法在Device侧记录时间戳。
在这里插入图片描述

下面示例代码中分别给出了直接在模型推理前后打时间戳相减,使用同步函数以及Event方法统计模型推理时间(每种方法都重复50次,忽略前5次推理,取后45次的平均值)。

import timeimport torch
import torch.nn as nnclass CustomModel(nn.Module):def __init__(self):super().__init__()self.part0 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=512, kernel_size=3, stride=2, padding=1),nn.GELU(),nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=2, padding=1),nn.GELU())self.part1 = nn.Sequential(nn.AdaptiveAvgPool2d(output_size=(1, 1)),nn.Flatten(),nn.Linear(in_features=1024, out_features=2048),nn.GELU(),nn.Linear(in_features=2048, out_features=512),nn.GELU(),nn.Linear(in_features=512, out_features=1))def forward(self, x):x = self.part0(x)x = self.part1(x)return xdef cal_time1(model, x):with torch.inference_mode():time_list = []for _ in range(50):ts = time.perf_counter()ret = model(x)td = time.perf_counter()time_list.append(td - ts)print(f"avg time: {sum(time_list[5:]) / len(time_list[5:]):.5f}")def cal_time2(model, x):device = x.devicewith torch.inference_mode():time_list = []for _ in range(50):torch.cuda.synchronize(device)ts = time.perf_counter()ret = model(x)torch.cuda.synchronize(device)td = time.perf_counter()time_list.append(td - ts)print(f"syn avg time: {sum(time_list[5:]) / len(time_list[5:]):.5f}")def cal_time3(model, x):with torch.inference_mode():start_event = torch.cuda.Event(enable_timing=True)end_event = torch.cuda.Event(enable_timing=True)time_list = []for _ in range(50):start_event.record()ret = model(x)end_event.record()end_event.synchronize()time_list.append(start_event.elapsed_time(end_event) / 1000)print(f"event avg time: {sum(time_list[5:]) / len(time_list[5:]):.5f}")def main():device = torch.device("cuda:0")model = CustomModel().eval().to(device)x = torch.randn(size=(32, 3, 224, 224), device=device)cal_time1(model, x)cal_time2(model, x)cal_time3(model, x)if __name__ == '__main__':main()

终端输出:

avg time: 0.00023
syn avg time: 0.04709
event avg time: 0.04710

通过终端输出可以看到,如果直接在模型推理前后打时间戳相减得到的时间非常短(因为并没有等待Device侧计算完成)。而使用同步函数或者Event方法统计的时间明显要长很多。


2 手动synchronize和Event适用场景

通过上面的代码示例可以看到,通过同步函数统计的时间和Event方法统计的时间基本一致(差异1ms内)。那两者有什么区别呢?如果只是简单统计一个模型的推理时间确实看不出什么差异。但如果要统计一个完整AI应用通路(其中可能包含多个模型以及各种CPU计算)中不同模型的耗时,而又不想影响到整个通路的性能,那么建议使用Event方法。因为使用同步函数可能会让Host长期处于等待状态,等待过程中也无法干其他的事情,从而导致计算资源的浪费。可以看看下面这个示例,整个通路由Model1推理+一段纯CPU计算+Model2推理串行构成,假设想统计一下model1、model2推理分别用了多长时间:

import timeimport torch
import torch.nn as nn
import numpy as npclass CustomModel1(nn.Module):def __init__(self):super().__init__()self.part0 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=512, kernel_size=3, stride=2, padding=1),nn.GELU(),nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=2, padding=1),nn.GELU())def forward(self, x):x = self.part0(x)return xclass CustomModel2(nn.Module):def __init__(self):super().__init__()self.part1 = nn.Sequential(nn.AdaptiveAvgPool2d(output_size=(1, 1)),nn.Flatten(),nn.Linear(in_features=1024, out_features=2048),nn.GELU(),nn.Linear(in_features=2048, out_features=512),nn.GELU(),nn.Linear(in_features=512, out_features=1))def forward(self, x):x = self.part1(x)return xdef do_pure_cpu_task():x = np.random.randn(1, 3, 512, 512)x = x.astype(np.float32)x = x * 1024 ** 0.5def cal_time2(model1, model2, x):device = x.devicewith torch.inference_mode():time_total_list = []time_model1_list = []time_model2_list = []for _ in range(50):torch.cuda.synchronize(device)ts1 = time.perf_counter()ret = model1(x)torch.cuda.synchronize(device)td1 = time.perf_counter()do_pure_cpu_task()torch.cuda.synchronize(device)ts2 = time.perf_counter()ret = model2(ret)torch.cuda.synchronize(device)td2 = time.perf_counter()time_model1_list.append(td1 - ts1)time_model2_list.append(td2 - ts2)time_total_list.append(td2 - ts1)avg_model1 = sum(time_model1_list[5:]) / len(time_model1_list[5:])avg_model2 = sum(time_model2_list[5:]) / len(time_model2_list[5:])avg_total = sum(time_total_list[5:]) / len(time_total_list[5:])print(f"syn avg model1 time: {avg_model1:.5f}, model2 time: {avg_model2:.5f}, total time: {avg_total:.5f}")def cal_time3(model1, model2, x):with torch.inference_mode():model1_start_event = torch.cuda.Event(enable_timing=True)model1_end_event = torch.cuda.Event(enable_timing=True)model2_start_event = torch.cuda.Event(enable_timing=True)model2_end_event = torch.cuda.Event(enable_timing=True)time_total_list = []time_model1_list = []time_model2_list = []for _ in range(50):model1_start_event.record()ret = model1(x)model1_end_event.record()do_pure_cpu_task()model2_start_event.record()ret = model2(ret)model2_end_event.record()model2_end_event.synchronize()time_model1_list.append(model1_start_event.elapsed_time(model1_end_event) / 1000)time_model2_list.append(model2_start_event.elapsed_time(model2_end_event) / 1000)time_total_list.append(model1_start_event.elapsed_time(model2_end_event) / 1000)avg_model1 = sum(time_model1_list[5:]) / len(time_model1_list[5:])avg_model2 = sum(time_model2_list[5:]) / len(time_model2_list[5:])avg_total = sum(time_total_list[5:]) / len(time_total_list[5:])print(f"event avg model1 time: {avg_model1:.5f}, model2 time: {avg_model2:.5f}, total time: {avg_total:.5f}")def main():device = torch.device("cuda:0")model1 = CustomModel1().eval().to(device)model2 = CustomModel2().eval().to(device)x = torch.randn(size=(32, 3, 224, 224), device=device)cal_time2(model1, model2, x)cal_time3(model1, model2, x)if __name__ == '__main__':main()

终端输出:

syn avg model1 time: 0.04725, model2 time: 0.00125, total time: 0.05707
event avg model1 time: 0.04697, model2 time: 0.00099, total time: 0.04797

通过终端打印的结果可以看到无论是使用同步函数还是Event方法统计的model1、model2的推理时间基本是一致的。但对于整个通路而言使用同步函数时总时间明显变长了。下图大致解释了为什么使用同步函数时导致整个通路变长的原因,主要是在model1发送完指令后使用同步函数时会一直等待Device侧计算结束,期间啥也不能干。而使用Event方法时在model1发送完指令后不会阻塞Host,可以立马去进行后面的CPU计算任务。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/140751.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

发展与监管协同发力 人工智能算法领域已形成良好生态

发展与监管协同发力 人工智能算法领域已形成良好生态 近日,全国组织机构统一社会信用代码数据服务中心对国家网信办公示的人工智能领域备案信息进行了详尽的分析,揭示了我国人工智能产业的蓬勃景象。据统计,我国人工智能领域的备案主体遍布各…

MySQL之表的约束

目录 前言 一:空属性 二:默认值 三:列描述 四:zerofill 五:主键 六:自增长 七:唯一键 八:外键 接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧 前言 表中一定要有各种约束,通过约束…

零倾覆力矩点(ZMP)

系列文章目录 前言 在机器人学中,零倾力矩点(ZMP)是一个特征点,主要用于足式运动。在下文的一些假设中,我们将看到,它非正式地代表了一个系统接触反作用力的结果点。例如,下图中的刚体处于静态平…

【深入理解SpringCloud微服务】深入理解nacos配置中心(四)——配置新增或修改源码分析

【深入理解SpringCloud微服务】深入理解nacos配置中心(四)——配置新增或修改源码分析 原理回顾源码分析ConfigController#publishConfig()ConfigOperationService#publishConfig()nacos事件监听机制ConfigChangePublisher#notifyConfigChange()NotifyCe…

在 FlexSim 中使用 OpenUSD 分析、可视化和优化现实世界的流程

对于制造和工业企业而言,效率和精度至关重要。为了简化运营、降低成本和提高生产力,各公司正在转向数字孪生和离散事件模拟。 离散事件模拟使制造商能够通过试验不同的输入和行为来优化流程,这些输入和行为可以逐步进行建模和测试。 FlexSi…

6. Transforms的使用(一)--ToTensor()

Transforms的使用(一) 1.使用ToTensor类将数据转化为Tensor形式 导入需要使用的transforms类 from torchvision import transforms创建ToTensor类的实例 totensor transforms.ToTensor()将读取的图片ndarray数据转化为Tensor数据 img cv.imread(img_p…

Java网络编程 TCP通信(Socket 与 ServerSocket)

1.TCP通信原理 TCP通信涉及两个端点:客户端和服务器。服务器端使用 ServerSocket 监听特定端口,等待客户端的连接请求。客户端使用 Socket 连接到服务器的IP地址和端口。一旦连接建立,双方就可以通过输入输出流进行数据交换. ServerSocket是…

视频工具EasyDarwin生成RTMP给WVP拉流列表

效果 运行 登录 http://127.0.0.1:10086/ admin/admin 创建RTMP服务

微型导轨在光学仪器中的应用!

微型导轨在光学仪器中扮演着至关重要的角色,以其高精度、高稳定性的特点,提供稳定的光学路径和精确的光学元件位置。接下来,我们一起来看看微型导轨在光学仪器中的应用实例! 1、显微镜:在显微镜中,微型导轨…

鹏哥C语言自定义笔记重点(67-)

67. 68. 69. 70. 71.结构体内容 72.理解结构体的字节数 73. #pragma once //头文件中使用,功能是:防止头文件被多次引用 74.结构体传参 结论:结构体传参时,要传结构体地址。 75.位段 76.static是只能在该文件中看到,其他地方看不到 77.…

【6大设计原则】迪米特法则:解密软件设计中的“最少知识原则”

引言 在软件设计中,设计原则是指导我们构建高质量、可维护系统的基石。迪米特法则(Law of Demeter,LoD),也被称为“最少知识原则”,是六大设计原则之一。它强调对象之间的松耦合,确保系统的各个…

8. Transforms的使用(三)-- Resize

Transforms的使用(三) 1. 为什么要使用Resize 在模型的训练过程中往往需要图片数据的维度相同,才能适应深度学习模型中的相关神经网络结构,这时候就需要使用Resize保证所有的图片保持相同的尺寸2. 使用Resize调整图片的尺寸 在pytorch2.3的版本上,Resize()支持对Tensor类…

1405 问题 E: 世界杯

废话 这个题,我估计 22 22 22 年的时候写过一次,当时应该是搞明白了,现在重新写还是不会写,有点无奈 题目 问题 E: 世界杯:现在的 OJ 把题目加到一个活动里面去之后,感觉之后这个链接就访问不了了。题目…

CSS—4

1.定位 1.相对定位 2.绝对定位 3.固定定位 4.粘性定位 5.定位的特殊应用 2.布局-版心 3.布局-常用布局名词 4.布局-重置默认样式

321. 拼接最大数

1. 题目 321. 拼接最大数 2. 解题思路 题目精简一下: 给你两个数组,从每个数组选取N个元素(需要保持相对顺序,比如从数组[4,8,2]选取两个元素,选取出来后必须保持顺序,比如选4和2,那么组成新…

对操作系统(OS)管理和进程的理解

文章目录 从冯诺依曼体系入手来了解计算机硬件部分操作系统操作系统的概念设计操作系统(OS)的目的对下(硬件)OS的管理对上如何理解系统调用 进程 在计算机系统中,硬件、操作系统和进程是三个至关重要的概念。它们相互协…

C# 反射之动态生成dll/exe

这个可能应该属于反射的高级使用范围了,平常在项目中使用的人估计也不是很多。由于使用反射的话会降低性能,比如之前用到的GetValue、SetValue等之类,但是使用这种方式会大大提高效率,在这里我只想说,都直接写IL指令了…

C++八股文之面向对象篇

🤖个人主页:晚风相伴-CSDN博客 思维导图链接:面向对象的性质 持续更新中…… 💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧 …

【CSS in Depth 2 精译_031】5.3 Grid 网格布局的两种替代语法

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一章 层叠、优先级与继承(已完结) 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位(已完结) 2.1 相对…

【VSCode】VSCode Background 背景插件辅助窗口程序

前排贴上Github项目链接 GitHub窗口项目链接 这是一个基于VSCode上由shalldie上传的background扩展制作的windows窗口程序。 该程序旨在通过窗口程序尽可能的完善该扩展原有的功能。 background - shalldie 的最大优势是我目前仅在其扩展上发现了UseFront的选项,这…