《Pytorch 模型推理及多任务通用范式》第三节作业

1 课程学习

本节课主要对于大白AI课程:https://mp.weixin.qq.com/s/STbdSoI7xLeHrNyLlw9GOg
《Pytorch 模型推理及多任务通用范式》课程中的第三节课进行学习。

2 作业题目

  • 必做题:

(1) 把模型改为resnet18,加载相应的模型权重(Lesson2的物料包中有),跑一下0.jpg和1.jpg,看一下输出结果。官方torchvision训练mobilenet和训练resnet的方式是一样的,所以数据预处理和数据后处理部分完全相同。

 ('umbrella', 0.9995712637901306)
 ('peacock', 0.9999839067459106)

(2) 自己找2张其他图,用resnet18做下推理。
《Pytorch 模型推理及多任务通用范式》第三节作业_第1张图片
《Pytorch 模型推理及多任务通用范式》第三节作业_第2张图片

(‘bee’, 0.5264432430267334)
(‘cab’, 0.991939127445221)

  • 思考题:

(1) 以ResNet18为例,用time模块和for循环,对”./images/0.jpg”连续推理100次,统计时间开销,比如:

model_classify=ModelPipline()
 
import time
image=cv2.imread("./images/0.jpg")
t_all=0
for i in range(100):
    t_start=time.time()
    result=model_classify.predict(image)
    t_end=time.time()
    t_all+=t_end-t_start
print(t_all)

有CUDA的同学,改下代码:self.device=torch.device(‘cuda’)。用上述相同方法测试时间开销。

cpu: 2.3720483779907227
gpu: 0.560788631439209

(2) 在数据预处理和数据后处理的代码实现中,到处在用numpy, opencv, torch 对数组做相应变换,大家至少要把课程中出现的函数们给理解。

cv2.imread函数 有两个参数,第一个参数是图片路径,第二个参数表示读取图片的形式,有三种:
cv2.IMREAD_COLOR:加载彩色图片,这个是默认参数,可以直接写1。
cv2.IMREAD_GRAYSCALE:以灰度模式加载图片,可以直接写0。
cv2.IMREAD_UNCHANGED:包括alpha,可以直接写-1
大多数backbone model 是在公开数据集上训练的,是RGB图像,所以想用这些预训练的权重,输入必须是3通道图像。如果输入是灰度图,则需要以IMREAD_COLOR读入。

numpy的array与torch的tensor的转换


    t = torch.ones(3)
    print("type(t):", type(t))
    print("t:", t)
    # tensor转array
    a = t.numpy()
    print("type(a):", type(a))
    print("a:", a)
    print("-"*10)
    # 此时两个数组(array与tensor)是共用一个储存空间的,也就是说,一个改变,另一个也会改变
    t.add_(1)
    print("t:", t)
    print("a:", a)
    print("-"*10)
    # 将array转换为tensor
    import numpy as np
    tt = torch.from_numpy(a)
    print("type(tt):", type(tt))
    print("tt:", tt)
    print("-"*10)
    # 此时两个数组(array与tensor)是共用一个储存空间的,也就是说,一个改变,另一个也会改变
    np.add(a, 1, out=a)
    print("t:", t)
    print("a:", a)
    print("tt:", tt)
    print("-"*10)
    # 当然还有能在GPU上运算的CUDA tensors
    if torch.cuda.is_available():
        x = torch.randn(1)
        print("type(x):", type(x))
        print("x:", x)
        device = torch.device("cuda")          # a CUDA device object
        y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
        print("type(y):", type(y))
        print("y:", y)
        x = x.to(device)                       # or just use strings ``.to("cuda")``
        print("cuda type(x):", type(x))
        print("cuda x:", x)
        z = x + y
        print(z)
        print("cuda type(z):", type(z))
        print("cuda z:", x)
        print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!

输出:
《Pytorch 模型推理及多任务通用范式》第三节作业_第3张图片
参考torch与numpy数组的转换及注意

(3) 有同学在实验cpu和cuda推理时间的时候发现,cuda的推理时间为2.05s,cpu推理时间是2.57s,cuda和cpu的推理时间没有显示出多大优势。

认为cuda第一次推理比较耗时间。以下为证明cuda第一次推理时间的实验。

  • 增加循环次数,看cuda是否能体现出加速的优势。

思考题(2)中推理100次时间:
cpu: 2.3720483779907227
gpu: 0.560788631439209
cpu/gpu = 4.229

推理10000次时间:
cpu: 226.2179036140442
gpu: 46.84391951560974
cpu/gpu = 4.829

推理20000次时间:
cpu: 452.7243661880493
gpu: 90.42487096786499
cpu/gpu = 5.006

结论:推理次数越多,确实越能体现cuda的优势

  • 实验第一次推理对cuda的影响,推理次数100次
if __name__=='__main__':
	model_classify = ModelPipline()
    import time
    image = cv2.imread("./images/0.jpg")
    # 将第1次推理放在循环外面
    result = model_classify.predict(image)
    t_all = 0
    for i in range(100):
        t_start = time.time()
        result = model_classify.predict(image)
        t_end = time.time()
        t_all += t_end - t_start
    print(t_all)

cpu: 2.1793227195739746
第一次推理时间: 0.044313669204711914
第一次加载后,循环100次时间: 2.3044493198394775
第一次推理时间: 0.03883099555969238
第一次加载后,循环100次时间: 2.6771979331970215
第一次推理时间: 0.02727818489074707
第一次加载后,循环100次时间: 2.232801914215088

gpu:
第一次推理时间: 0.05043458938598633
第一次加载后,循环100次时间: 0.7382602691650391
第一次推理时间: 0.04384970664978027
第一次加载后,循环100次时间: 0.4815845489501953
第一次推理时间: 0.04526329040527344
第一次加载后,循环100次时间: 0.47938036918640137

结论:我的实验并没有体现出“第一次推理比较占用时间”的现象,并且多次运行情况下,第一次推理是否放在外面对cuda的推理时间并没有多大影响。

有同学提出GPU预热的概念,参见文章The Correct Way to Measure Inference Time of Deep Neural Networks:认为测量时间需要在gpu预热之后,且在同步状态下来测量时间。

《深度神经网络的测量推理时间的正确方法》

网络延迟是将深度网络部署到生产环境中更重要的方面之一。 大多数实际应用需要极快的推理时间,从几毫秒到一秒不等。 但是要正确测量神经网络的推理时间或延迟,需要深刻的理解。 即使是有经验的程序员也经常犯导致延迟测量不准确的常见错误。 这些错误的影响有可能引发错误的决策和不必要的支出。
在这篇文章中,我们回顾了一些应该解决的主要问题,以正确测量推理时间或延迟。 我们回顾了使 GPU 执行独一无二的主要过程,包括异步执行和 GPU 预热。 然后我们共享代码示例以在 GPU 上正确测量推理时间。 最后,我们回顾了人们在量化 GPU 上的推理时间时常犯的一些错误。

异步执行 Asynchronous execution

我们首先讨论 GPU 执行机制。 在多线程或多设备编程中,可以并行执行两个独立的代码块; 这意味着第二个块可能会在第一个块完成之前执行。 这个过程称为异步执行。 在深度学习上下文中,我们经常使用这种执行,因为默认情况下 GPU 操作是异步的。 更具体地说,当使用 GPU 调用函数时,操作会排入特定设备的队列,但不一定排到其他设备。 这允许我们在 CPU 或另一个 GPU 上并行执行计算。
《Pytorch 模型推理及多任务通用范式》第三节作业_第4张图片图 1. 异步执行。 左图:同步进程,其中进程 A 在继续工作之前等待来自进程 B 的响应。 右图:异步进程 A 继续工作,无需等待进程 B 完成。

异步执行的效果对用户是不可见的; 但是,当涉及到时间测量时,它可能是许多令人头疼的原因。 当您使用 Python 中的“时间”库计算时间时,测量是在 CPU 设备上执行的。 由于 GPU 的异步特性,停止计时的代码行将在 GPU 进程完成之前执行。 结果,计时将不准确或与实际推理时间无关。 请记住,我们想要使用异步,在这篇文章的后面我们将解释如何在异步过程中正确测量时间。

异步执行为深度学习提供了巨大的优势,例如能够大幅减少运行时间。 例如,在多个批次的推理中,第二批次可以在 CPU 上进行预处理,而第一批次在 GPU 上通过网络前馈。 显然,在推理时尽可能使用异步是有益的。

GPU warm-up

现代 GPU 设备可以处于几种不同的功率状态。 当 GPU 未用于任何目的且持久模式(即保持 GPU 开启)未启用时,GPU 会自动将其功率状态降低到非常低的水平,有时甚至会完全关闭。 在低功耗状态下,GPU 会关闭不同的硬件,包括内存子系统、内部子系统,甚至计算核心和缓存。

任何试图与 GPU 交互的程序的调用都会导致驱动程序加载和/或初始化 GPU。 这种驱动程序加载行为值得注意。 由于纠错代码的清理行为,触发 GPU 初始化的应用程序可能会产生长达 3 秒的延迟。 例如,如果我们测量一个需要 10 毫秒的网络的时间,运行超过 1000 个示例可能会导致我们的大部分运行时间浪费在初始化 GPU 上。 自然,我们不想测量这种副作用,因为时间不准确。 它既不反映通常 GPU 已经初始化的生产环境,也不反映GPU工作的持久模式。

所以,我们想尽可能地启用 GPU 省电模式,那让我们看看如何在测量时间的时候克服 GPU 的初始化。

测量推理时间的正确方法

下面的 PyTorch 代码片段展示了如何正确测量时间。 这里我们使用 Efficient-net-b0,但您可以使用任何其他网络。 在代码中,我们处理了上面描述的两个警告。 在我们进行任何时间测量之前,我们通过网络运行一些虚拟示例来进行“GPU 预热”。这将自动初始化 GPU 并防止它在我们测量时间时进入省电模式。 接下来,我们使用 tr.cuda.event 来测量 GPU 上的时间。 在这里使用 torch.cuda.synchronize() 至关重要。 这行代码执行主机和设备(即 GPU 和 CPU)之间的同步,因此只有在 GPU 上运行的进程完成后才会进行时间记录。 这克服了不同步执行的问题。

model = EfficientNet.from_pretrained('efficientnet-b0')
device = torch.device("cuda")
model.to(device)
dummy_input = torch.randn(1, 3,224,224, dtype=torch.float).to(device)

# INIT LOGGERS
starter, ender = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
repetitions = 300
timings=np.zeros((repetitions,1))
#GPU-WARM-UP
for _ in range(10):
    _ = model(dummy_input)
# MEASURE PERFORMANCE
with torch.no_grad():
    for rep in range(repetitions):
        starter.record()
        _ = model(dummy_input)
        ender.record()
        # WAIT FOR GPU SYNC
        torch.cuda.synchronize()
        curr_time = starter.elapsed_time(ender)
        timings[rep] = curr_time

mean_syn = np.sum(timings) / repetitions
std_syn = np.std(timings)
print(mean_syn)

当我们测量网络的延迟时,我们的目标是只测量网络的前馈,不多也不少。 通常,即使是专家也会在他们的测量中犯一些常见的错误。 以下是其中一些,以及它们的后果:

  1. 主机和设备之间传输数据。 这篇文章的观点是只测量神经网络的推理时间。 从这个角度来看,最常见的错误之一是在进行时间测量时在 CPU 和 GPU 之间传输数据。 这通常是在 CPU 上创建张量然后在 GPU 上执行推理时无意中完成的。 这种内存分配需要相当长的时间,从而增加了推理时间。 此错误对测量值的均值和方差的影响如下所示:
    《Pytorch 模型推理及多任务通用范式》第三节作业_第5张图片
    图 2: CPU 和 GPU 之间传输对测量时间的影响。 左:平均值和标准偏差的正确测量方法。 右图:每次网络调用时输入张量在 CPU 和 GPU 之间传输时的均值和标准差。 X 轴是计时方法,Y 轴是以毫秒为单位的时间。
  2. 不使用 GPU 预热。 如上所述,首次在 GPU 上运行会提示其初始化。 GPU 初始化最多可能需要 3 秒,当时间以毫秒为单位时,这会产生巨大的差异。
  3. 使用标准CPU时序。 最常见的错误是在没有同步的情况下测量推理时间。 众所周知,即使是有经验的程序员也会使用以下代码。
 s = time.time()
 _ = model(dummy_input)
curr_time = (time.time()-s )*1000

这当然完全忽略了前面提到的异步执行,因此输出了错误的时间。 这个错误对测量的均值和方差的影响如下所示:
《Pytorch 模型推理及多任务通用范式》第三节作业_第6张图片图 3:在CPU测量时间的影响。 左:平均值和标准偏差的正确测量方法。 右图:过程不同步时的均值和标准差。 X 轴是计时方法,Y 轴是以毫秒为单位的时间。

  1. 抽取一个样本。 与计算机科学中的许多过程一样**,神经网络的前馈具有(小)随机分量**。 运行时间的差异可能很大,尤其是在测量低延迟网络时。 为此,必须在多个示例上运行网络,然后对结果进行平均(300 个示例可能是一个不错的数字)。 一个常见的错误是使用一个样本并将其称为运行时。 当然,这并不代表真正的运行时间。

测量吞吐量Measuring Throughput

神经网络的吞吐量定义为网络在单位时间内(例如,一秒)可以处理的最大输入实例数。 与涉及单个实例处理的延迟不同,为了实现最大吞吐量,我们希望并行处理尽可能多的实例。 有效的并行性显然依赖于数据、模型和设备。 因此,为了正确测量吞吐量,我们执行以下两个步骤:(1)我们估计允许最大并行度的最佳批量大小; (2),给定这个最佳批量大小,我们测量网络在一秒钟内可以处理的实例数。

要找到最佳批量大小,一个好的经验法则是达到 GPU 对给定数据类型的内存限制。 这个大小当然取决于硬件类型和网络的大小。 找到这个最大批量大小的最快方法是执行二进制搜索。 当时间不重要时,简单的顺序搜索就足够了。 为此,我们使用 for 循环将批量大小增加 1,直到达到运行时错误为止,这确定了 GPU 可以处理的最大批量大小,用于我们的神经网络模型及其处理的输入数据。

在找到最佳批量大小后,我们计算实际吞吐量。 为此,我们希望处理多个批次(100 个批次就足够了),然后使用以下公式:

(number of batches X batch size)/(total time in seconds)

这个公式给出了我们的网络可以在一秒钟内处理的示例数量。 下面的代码提供了一种简单的方法来执行上述计算(给定最佳批量大小):

model = EfficientNet.from_pretrained('efficientnet-b0')
device = torch.device("cuda")
model.to(device)
dummy_input = torch.randn(optimal_batch_size, 3,224,224, dtype=torch.float).to(device)

repetitions=100
total_time = 0
with torch.no_grad():
    for rep in range(repetitions):
        starter, ender = torch.cuda.Event(enable_timing=True),   torch.cuda.Event(enable_timing=True)
        starter.record()
        _ = model(dummy_input)
        ender.record()
        torch.cuda.synchronize()
        curr_time = starter.elapsed_time(ender)/1000
        total_time += curr_time
Throughput =   (repetitions*optimal_batch_size)/total_time
print('Final Throughput:',Throughput)

Conclusion

准确测量神经网络的推理时间并不像听起来那么简单。 我们详细介绍了深度学习从业者应该注意的几个问题,例如异步执行和 GPU 省电模式。 尽管有上述警告,此处提供的 PyTorch 代码演示了如何正确测量神经网络中的时序。 最后,我们提到了一些导致人们错误地测量推理时间的常见错误。 在以后的文章中,我们将更深入地探讨这个主题,并解释现有的深度学习分析器,这些分析器使我们能够实现更准确的网络时间测量。 如果您对如何在不影响其准确性的情况下减少网络延迟感兴趣,请在 Deci 的白皮书中阅读有关此主题的更多信息。

反查yolov5中 detect.py代码,计算时间代码,确实是同步之后测量的时间:

def time_synchronized():
    # pytorch-accurate time
    if torch.cuda.is_available():
        torch.cuda.synchronize()  #等待当前设备上所有流中的所有内核完成
    return time.time()
# Inference
        t1 = time_synchronized()
        pred = model(img, augment=opt.augment)[0]

        # Apply NMS
        pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms)
        t2 = time_synchronized()

同步时间后,再次测量时间:

if __name__=='__main__':
    model_classify = ModelPipline()
    import time

    image = cv2.imread("./images/0.jpg")

    # 将第1次推理放在循环外面
    torch.cuda.synchronize()
    start1 = time.time()
    result = model_classify.predict(image)
    torch.cuda.synchronize()
    end1 = time.time()
    print("第1次推理时间:",end1-start1)
    t_all = 0
    for i in range(100):
        torch.cuda.synchronize()
        t_start = time.time()
        result = model_classify.predict(image)
        torch.cuda.synchronize()
        t_end = time.time()
        print(f"第{i+2}次推理时间:", t_end-t_start)
        t_all += t_end - t_start
    print("第1次加载后,循环100次时间:",t_all)

cpu结果

第1次推理时间: 0.11548161506652832
第2次推理时间: 0.02736973762512207
第3次推理时间: 0.030270099639892578
第4次推理时间: 0.03058028221130371
第5次推理时间: 0.03058338165283203
第6次推理时间: 0.029311418533325195
第7次推理时间: 0.03302454948425293
第8次推理时间: 0.0275876522064209
第9次推理时间: 0.0302274227142334
第10次推理时间: 0.02839207649230957
第11次推理时间: 0.025115966796875
第12次推理时间: 0.02533435821533203
第13次推理时间: 0.03188490867614746
第14次推理时间: 0.027261734008789062
第15次推理时间: 0.02784442901611328
第16次推理时间: 0.03024148941040039
第17次推理时间: 0.029840946197509766
第18次推理时间: 0.029162168502807617
第19次推理时间: 0.02868819236755371
第20次推理时间: 0.02675318717956543
第21次推理时间: 0.02482295036315918
第22次推理时间: 0.02246713638305664
第23次推理时间: 0.02280449867248535
第24次推理时间: 0.0284578800201416
第25次推理时间: 0.02643299102783203
第26次推理时间: 0.02664923667907715
第27次推理时间: 0.026027679443359375
第28次推理时间: 0.027423620223999023
第29次推理时间: 0.023158550262451172
第30次推理时间: 0.023151636123657227
第31次推理时间: 0.025136709213256836
第32次推理时间: 0.02541637420654297
第33次推理时间: 0.026070356369018555
第34次推理时间: 0.026086807250976562
第35次推理时间: 0.024853229522705078
第36次推理时间: 0.02480769157409668
第37次推理时间: 0.024338960647583008
第38次推理时间: 0.024314403533935547
第39次推理时间: 0.022298812866210938
第40次推理时间: 0.023638248443603516
第41次推理时间: 0.02734208106994629
第42次推理时间: 0.02817678451538086
第43次推理时间: 0.02989983558654785
第44次推理时间: 0.02553105354309082
第45次推理时间: 0.024470090866088867
第46次推理时间: 0.024607181549072266
第47次推理时间: 0.02418041229248047
第48次推理时间: 0.022823333740234375
第49次推理时间: 0.028810501098632812
第50次推理时间: 0.03197979927062988
第51次推理时间: 0.031966447830200195
第52次推理时间: 0.031178712844848633
第53次推理时间: 0.03048110008239746
第54次推理时间: 0.030689001083374023
第55次推理时间: 0.03051590919494629
第56次推理时间: 0.033420562744140625
第57次推理时间: 0.0327756404876709
第58次推理时间: 0.032079219818115234
第59次推理时间: 0.028012990951538086
第60次推理时间: 0.024437427520751953
第61次推理时间: 0.024135828018188477
第62次推理时间: 0.02313995361328125
第63次推理时间: 0.022739648818969727
第64次推理时间: 0.02519989013671875
第65次推理时间: 0.030359983444213867
第66次推理时间: 0.025178194046020508
第67次推理时间: 0.026061058044433594
第68次推理时间: 0.026542186737060547
第69次推理时间: 0.02245020866394043
第70次推理时间: 0.021797657012939453
第71次推理时间: 0.02333807945251465
第72次推理时间: 0.022905349731445312
第73次推理时间: 0.02587580680847168
第74次推理时间: 0.02628040313720703
第75次推理时间: 0.024767637252807617
第76次推理时间: 0.026424884796142578
第77次推理时间: 0.02306365966796875
第78次推理时间: 0.022463083267211914
第79次推理时间: 0.022680997848510742
第80次推理时间: 0.021914005279541016
第81次推理时间: 0.0274198055267334
第82次推理时间: 0.02650165557861328
第83次推理时间: 0.026316404342651367
第84次推理时间: 0.025147438049316406
第85次推理时间: 0.024324893951416016
第86次推理时间: 0.024328947067260742
第87次推理时间: 0.022128582000732422
第88次推理时间: 0.021760225296020508
第89次推理时间: 0.02518916130065918
第90次推理时间: 0.025460243225097656
第91次推理时间: 0.026798725128173828
第92次推理时间: 0.02733588218688965
第93次推理时间: 0.0237729549407959
第94次推理时间: 0.025893449783325195
第95次推理时间: 0.02241969108581543
第96次推理时间: 0.024320125579833984
第97次推理时间: 0.022876977920532227
第98次推理时间: 0.022742509841918945
第99次推理时间: 0.026343107223510742
第100次推理时间: 0.02568531036376953
第101次推理时间: 0.027451038360595703
第1次加载后,循环100次时间: 2.6327455043792725

gpu时间:

第1次推理时间: 0.04638981819152832
第2次推理时间: 0.004971742630004883
第3次推理时间: 0.0056874752044677734
第4次推理时间: 0.00563359260559082
第5次推理时间: 0.005725383758544922
第6次推理时间: 0.0056798458099365234
第7次推理时间: 0.005606651306152344
第8次推理时间: 0.005924701690673828
第9次推理时间: 0.0056591033935546875
第10次推理时间: 0.005642414093017578
第11次推理时间: 0.0056269168853759766
第12次推理时间: 0.005686521530151367
第13次推理时间: 0.00565791130065918
第14次推理时间: 0.005598783493041992
第15次推理时间: 0.005628824234008789
第16次推理时间: 0.005654096603393555
第17次推理时间: 0.005718231201171875
第18次推理时间: 0.005612611770629883
第19次推理时间: 0.0056040287017822266
第20次推理时间: 0.005587100982666016
第21次推理时间: 0.005648612976074219
第22次推理时间: 0.0059850215911865234
第23次推理时间: 0.005833864212036133
第24次推理时间: 0.005799055099487305
第25次推理时间: 0.0049648284912109375
第26次推理时间: 0.005852937698364258
第27次推理时间: 0.005812168121337891
第28次推理时间: 0.00575709342956543
第29次推理时间: 0.005793094635009766
第30次推理时间: 0.005698680877685547
第31次推理时间: 0.004698991775512695
第32次推理时间: 0.004705667495727539
第33次推理时间: 0.0047419071197509766
第34次推理时间: 0.004683971405029297
第35次推理时间: 0.005612850189208984
第36次推理时间: 0.0047719478607177734
第37次推理时间: 0.004853725433349609
第38次推理时间: 0.0047528743743896484
第39次推理时间: 0.004765510559082031
第40次推理时间: 0.006181001663208008
第41次推理时间: 0.005424976348876953
第42次推理时间: 0.006127595901489258
第43次推理时间: 0.004857540130615234
第44次推理时间: 0.005173683166503906
第45次推理时间: 0.005965471267700195
第46次推理时间: 0.00577998161315918
第47次推理时间: 0.005101442337036133
第48次推理时间: 0.005086660385131836
第49次推理时间: 0.00548553466796875
第50次推理时间: 0.0049359798431396484
第51次推理时间: 0.004874706268310547
第52次推理时间: 0.004926443099975586
第53次推理时间: 0.005236625671386719
第54次推理时间: 0.004579067230224609
第55次推理时间: 0.004767417907714844
第56次推理时间: 0.00526738166809082
第57次推理时间: 0.004784107208251953
第58次推理时间: 0.005305290222167969
第59次推理时间: 0.005428791046142578
第60次推理时间: 0.0049669742584228516
第61次推理时间: 0.004622459411621094
第62次推理时间: 0.00463104248046875
第63次推理时间: 0.004907846450805664
第64次推理时间: 0.004637002944946289
第65次推理时间: 0.004505157470703125
第66次推理时间: 0.004500150680541992
第67次推理时间: 0.0044803619384765625
第68次推理时间: 0.004585742950439453
第69次推理时间: 0.0045278072357177734
第70次推理时间: 0.004487514495849609
第71次推理时间: 0.004477024078369141
第72次推理时间: 0.004450559616088867
第73次推理时间: 0.00450587272644043
第74次推理时间: 0.004472017288208008
第75次推理时间: 0.00443267822265625
第76次推理时间: 0.0044403076171875
第77次推理时间: 0.004464626312255859
第78次推理时间: 0.004538059234619141
第79次推理时间: 0.004562854766845703
第80次推理时间: 0.005164623260498047
第81次推理时间: 0.004805088043212891
第82次推理时间: 0.005681037902832031
第83次推理时间: 0.004622220993041992
第84次推理时间: 0.004651069641113281
第85次推理时间: 0.004915952682495117
第86次推理时间: 0.005137205123901367
第87次推理时间: 0.0052051544189453125
第88次推理时间: 0.004871368408203125
第89次推理时间: 0.008282184600830078
第90次推理时间: 0.004983663558959961
第91次推理时间: 0.0047833919525146484
第92次推理时间: 0.005022764205932617
第93次推理时间: 0.005772113800048828
第94次推理时间: 0.004518032073974609
第95次推理时间: 0.0050013065338134766
第96次推理时间: 0.00489497184753418
第97次推理时间: 0.004656314849853516
第98次推理时间: 0.004839181900024414
第99次推理时间: 0.005447864532470703
第100次推理时间: 0.004784345626831055
第101次推理时间: 0.004519224166870117
第1次加载后,循环100次时间: 0.5166835784912109

最终结论:我的实验没有重现gpu推理时间慢,约和cpu推理时间差不多的问题,但是可以看出gpu第一次推理时确实慢。 第1次推理时间: 0.0463,约为之后每次推理的10倍。重要的是,cpu第一次推理的时间也长,且比cuda的时间更长。

另外测试了模型的加载时间:gpu和cpu加载时间基本相同。

cpu模型加载时间: 0.2724010944366455
gpu模型加载时间:0.287320613861084

cuda为什么第一次推理慢:

简单的来讲就是 CUDA初始化是懒散的初始化需要调度内存 需要一定的开销 但是这些开销又不是在模型初始化的时候全部调度完 (cpu明明核心比GPU核心强 但是模型加载时间基本相同)有部分还会在模型推理的时候进行调度(第一次cpu的推理就很快 但是gpu特别慢,我的实验显示没有很快)当第一次模型推理完成后 所需要的内存调度都已经到位 再进行后续的推理时 时间会明显降低。
在GPU计算前是要申请block和线程,这个过程要初始化,申请空间,检查是否成功。等一系列操作的。有想了解的可以看看cuda编程
剩下的瓶颈操作就是IO了,还有就是cpu和gpu之间的操作。很多时候数据拿出来要在cpu中操作,这样的一个过程中也导致了延迟问题
关于GPU推理速度的两个问题

关于gpu和cpu的运算性能,网上有这样的答案

pytorch中GPU与CPU的运算性能比较:
有很多朋友说在使用GPU和CPU进行运算的过程中(比如GAN),发现使用的时间都差不多;是不是GPU并不比CPU快多少呢?
其实不是这样,如果你运行一个很小的数据模型,那么CPU和GPU的运算速度是差不多的,但是如果你运行大型模型,就可以看到加速效果。我们不能单纯说GPU一定比CPU快,决定因素除了除了我们GPU的配置,还有我们的网络,数据的大小以及数据的类型,有时候GPU运算反而不如CPU快速。
举例说明:在使用的情况下,在Titan X GPU中运行VGG16比在Dual Xeon E5-2630 v3 CPU中快66倍

疑问:出现问题的同学使用的显卡是3070, 明明用的是同样的代码,输入的同样的图片,却只有该同学出现了这个问题,别人无法重现。如果是由于cuda第一次推理时间慢,那么每个人都应该能够碰到这个问题。《Pytorch 模型推理及多任务通用范式》第三节作业_第7张图片

你可能感兴趣的:(pytorch,深度学习,1024程序员节)