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

news2024/9/26 5:21:59

文章目录

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


0 背景

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


1 精确统计方法

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

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

import time

import torch
import torch.nn as nn


class 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 x


def 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.device
    with 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 time

import torch
import torch.nn as nn
import numpy as np


class 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 x


class 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 x


def do_pure_cpu_task():
    x = np.random.randn(1, 3, 512, 512)
    x = x.astype(np.float32)
    x = x * 1024 ** 0.5


def cal_time2(model1, model2, x):
    device = x.device
    with 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.coloradmin.cn/o/2137839.html

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

相关文章

RS-485自收发电路

RS-485自收发电路 RS-485标准在工业控制、电力通讯、智能仪表等领域中使用广泛。 信号自收发电路1:利用74HC14芯片 信号自收发电路我们采用74HC14芯片,利用它的施密特波形翻转性能来控制RE、DE引脚,以实现信号的自收发。其电路连接如下图&am…

C++入门 之 类和对象(上)

目录 一、类的定义 1.类定义格式 2.访问限定符 3.类域 二、实例化 1.实例化概念 2.对象大小 3.this指针 一、类的定义 1.类定义格式 1、class Stack{};class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义…

C++ tracy性能分析(二)

环境搭建 项目根目录下 git clone https://github.com/wolfpld/tracy cmake 配置 add_definitions("-DTRACY_ENABLE") add_subdirectory(tracy) include_directories(${TRACY_PUBLIC_DIR}) target_link_libraries(project TracyClient) test.cpp //#define TRACY_C…

猫狗识别大模型——基于python语言

目录 1.猫狗识别 2.数据集介绍 3.猫狗识别核心原理 4.程序思路 4.1数据文件框架 4.2 训练模型 4.3 模型使用 4.4 识别结果 5.总结 1.猫狗识别 人可以直接分辨出图片里的动物是猫还是狗,但是电脑不可以,要想让电脑也分辨出图片里的动物是猫还是小…

【linux-Day2】linux的基本指令<上>

【linux-Day2】linux的基本指令<上> 一键查看操作系统的重要地位linux下的基本指令&#x1f4e2;ls&#xff1a;显示当前目录下所有的子目录和文件&#x1f4e2;pwd&#xff1a;显示用户当前所在的目录&#xff0c;在windows中&#xff0c;相当于显示当前目录的绝对路径。…

AI工具一键制作爆火的“汉语新解“卡片!

最近出现了一种很火的新玩法“汉语新解”。 AI把一个词汇&#xff0c;以一种特殊的视角&#xff0c;用幽默、讽刺等方式重新定义&#xff0c;然后生成一张精美的卡片。 这个玩法和之前我发的的吐槽工具玩法类似&#xff0c;主打的就是一个新颖、情绪释放。 今天教大家怎么快速…

Python 解析 JSON 数据

1、有如下 JSON 数据&#xff0c;存放在 data.json 文件&#xff1a; [{"id":1, "name": "小王", "gender": "male", "score": 96.8}, {"id":2, "name": "小婷", "gender&qu…

大模型探索式轨迹优化:基于试错的自主智能体学习新方法

人工智能咨询培训老师叶梓 转载标明出处 现有的开源LLMs在构建智能体方面的效果远不如GPT-4。标准的构建开源LLM智能体的方法涉及模仿学习&#xff0c;即基于专家轨迹对LLMs进行微调。然而&#xff0c;这些方法完全依赖于专家演示&#xff0c;由于对目标环境探索不足而可能产生…

windows11+ubuntu20.04.6双系统安装

记录win11和ubuntu20.04.6在单个硬盘上安装的主要流程 系统说明 BIOS模式&#xff1a; UEFI 硬盘&#xff1a; 1TB固态 内存&#xff1a; 32GB 步骤 1、 准备两个不小于16GB的U盘&#xff0c;一个用于装Windows&#xff0c;一个用于装ubuntu&#xff0c;注意8G的U盘虽然能够…

操作系统知识点-进程与线程,一文搞懂!

本文图片均来自王道考研 一、进程的概念、组成和特征 进程&#xff08;Process&#xff09;是计算机中的一个核心概念&#xff0c;它是对正在运行的程序的一个抽象表示。在计算机科学中&#xff0c;一个进程是系统进行资源分配和调度的一个独立单元&#xff0c;是操作系统结构…

Python数据分析 Pandas基本操作

Python数据分析 Pandas基本操作 一、Series基础操作 ​ Series是pandas的基础数据结构&#xff0c;它可以用来创建一个带索引的一维数组&#xff0c;下面开始介绍它的基础操作 1、创建Series 1&#xff09;使用数据创建Series&#xff1a; import pandas as pd pd.Series(1…

学习笔记JVM篇(三)

一、垃圾回收机制 垃圾回收&#xff08;Garbage Collection&#xff09;机制&#xff0c;是自动回收无用对象从而释放内存的一种机制。Java之所以相对简单&#xff0c;很大程度是归功于垃圾回收机制。&#xff08;例如C语言申请内存后要手动的释放&#xff09; 优点&#xff…

基于less和scss 循环生成css

效果 一、less代码 复制代码 item-count: 12; // 生成多少个 .item 类.item-loop(n) when (n > 0) {.icon{n} {background: url(../../assets/images/menu/icon{n}.png) no-repeat;background-size: 100% 100%;}.item-loop(n - 1);}.item-loop(item-count);二、scss代码 f…

在线查看 Android 系统源代码 Android Code Search

在线查看 Android 系统源代码 Android Code Search 1. Android Code Search2. Android2.1. platform/superproject2.2. build/envsetup.sh2.3. build/make/envsetup.sh References 1. Android Code Search https://cs.android.com/ Android https://cs.android.com/android An…

PCIe进阶之TL:Address Spaces, Transaction Types, and Usage

1 Transaction Layer Overview 如上图为PCIe设备的一个分层结构,从上层逻辑看,事务层的关键点是: 流水线式的完整的 split-transaction 协议事务层数据包(TLP)的排序和处理基于信用的流控制机制可选支持的数据中毒功能和端到端数据完整性检测功能事务层包含以下内容: TLP…

【C++】标准库IO查漏补缺

【C】标准库 IO 查漏补缺 文章目录 系统I/O1. 概述2. cout 与 cerr3. cerr 和 clog4. 缓冲区5. 与 printf 的比较 系统I/O 1. 概述 标准库提供的 IO 接口&#xff0c;包含在 iostream 文件中 输入流: cin输出流&#xff1a;cout / cerr / clog。 输入流只有一个 cin&#x…

MFC工控项目实例之十六输入信号验证

承接专栏《MFC工控项目实例之十五定时刷新PC6325A模拟量输入》 验证选定的输入信号实时状态 在BoardTest.cpp文件中添加代码 void CBoardTest::OnButton2() {// TODO: Add your control notification handler code hereisThreadBegin true; //运行线程执行pThre…

medium_socnet

0x00前言 靶场要安装在virtualbox &#xff08;最新版&#xff09;。否者会出现一些问题。 攻击机&#xff1a;kali2024 靶机&#xff1a;medium_socnet 0x01信息搜集 因为把靶机和虚拟机啊放在了同一网段。 所以我先使用了 arp-scan,查看有多少同一网段ipUP 。 经过推断…

OSS对象资源管理

1、登录aliyun 1.1、什么是OSS&#xff1f;有什么用&#xff1f; OSS 是“Object Storage Service”的缩写&#xff0c;中文常称为“对象存储服务”。OSS 是一种互联网云存储服务&#xff0c;主要用于海量数据的存储与管理。 相较于nginx&#xff0c;OSS更灵活&#xff0c;不…

点云深度学习系列:Sam2Point——基于提示的点云分割

文章&#xff1a;SAM2POINT:Segment Any 3D as Videos in Zero-shot and Promptable Manners 代码&#xff1a;https://github.com/ZiyuGuo99/SAM2Point Demo&#xff1a;https://huggingface.co/spaces/ZiyuG/SAM2Point 1&#xff09;摘要 文章介绍了SAM2POINT&#xff0c;这是…