![01c19f909a2dc950453594af98ed9e9d.png](https://i-blog.csdnimg.cn/blog_migrate/e8d900f0d009885ec678219c97166bcc.jpeg)
网络延迟是将深度网络部署到生产环境中要考虑的重要因素之一。大多数实际应用程序都需要非常快的推理时间,从几毫秒到一秒不等。但是,正确而有意义地测量神经网络的推理时间或延迟的任务需要对网络有深刻的理解。即使是经验丰富的程序员也经常会犯一些常见的错误,从而导致延迟测量不准确。这些错误有可能会引发错误的决策和不必要的支出。
在这篇文章中,我们将回顾一些需要解决的主要问题,以便正确地度量延迟时间。
异步执行
我们首先讨论GPU执行机制。在多线程或多设备编程中,两个独立的代码块可以并行执行,这意味着可以在第一个块完成之前执行第二个块,这个过程称为异步执行。在深度学习环境中,我们经常使用这种执行方式,因为GPU操作在默认情况下是异步的。
![cc5cf15077360d772db6a25a5858f295.png](https://i-blog.csdnimg.cn/blog_migrate/f21e19b4dda9feb901e76c3bc6adac04.jpeg)
左:同步进程,其中进程A等待进程B的响应,然后才能继续工作。右:异步进程A继续工作,而无需等待进程B完成。
异步执行为深度学习提供了巨大的优势,可以大大减少运行时间。例如,在推断多个批次时,第二个批次可以在CPU上进行预处理,而第一个批次则通过GPU上的网络进行前馈。通常,在推断时尽可能使用异步方式是有益的。
异步执行的效果对用户是不可见的,但在进行时间测量时,可能会引起许多麻烦。当您使用Python中的“time”库计算时间时,测量是在CPU设备上执行的,由于GPU的异步特性,停止计时的代码行将在GPU进程完成之前执行。因此,这个时间将不正确或与实际推理时间无关。
GPU warm-up(预热)
现代GPU设备可以存在于几种不同的电源状态之一。当GPU不被用于任何目的,且不启用持久模式时,GPU会自动将其功耗状态降低到非常低的水平,有时甚至会完全关闭。在低功耗状态下,GPU会关闭不同的硬件,包括显存子系统、内部子系统,甚至是计算核心和缓存。
任何试图与GPU交互的程序的调用都会导致驱动程序加载和/或初始化GPU。这个驱动程序加载行为是值得注意的。由于纠错代码的清理行为,触发GPU初始化的应用程序可能会导致长达3秒钟的延迟。
由于我们希望尽可能地启用GPU省电模式,因此让我们看一下如何在测量时间的同时克服GPU的初始化。
测量推理时间的正确方法
下面的PyTorch代码片段展示了如何正确地度量时间。在这里,我们使用Efficient-net-b0,但您可以使用任何其他网络。在进行时间测量之前,我们通过网络运行一些虚拟实例来进行“ GPU warm-up”。这将自动初始化GPU,并防止它在我们测量时间时进入省电模式。接下来,我们使用tr.cuda.event来测量GPU上的时间。在这里至关重要的是使用torch.cuda.synchronize(),这行代码执行主机和设备之间的同步,在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)starter, ender = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)repetitions = 300timings=np.zeros((repetitions,1))#GPU-WARM-UPfor _ in range(10): _ = model(dummy_input)# MEASURE PERFORMANCEwith 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_timemean_syn = np.sum(timings) / repetitionsstd_syn = np.std(timings)print(mean_syn)
测量时间时的常见错误
当我们测量网络的延迟时,我们的目标是只测量网络的前馈。通常,即使是专家,在测量时也会犯一些常见的错误。
1.在主机和设备之间传输数据。这篇文章的观点是仅测量神经网络的推理时间。从这个角度来看,最常见的错误之一是在进行时间测量时,CPU和GPU之间存在数据传输。这通常是在CPU上创建一个张量,然后在GPU上执行推断,这种内存分配需要相当长的时间,从而增加了推理的时间。这个错误对测量的均值和方差的影响如下所示:
![a673252128334bdcba80ba2134e763da.png](https://i-blog.csdnimg.cn/blog_migrate/fc05be57af4af3b4341e10c115146c47.jpeg)
左:均值和标准差的正确测量值。右:在每次调用网络时,在CPU和GPU之间传输输入张量时的平均值和标准差。X轴是计时方法,Y轴是时间(以毫秒为单位)。
2.不使用GPU warm-up。如上所述,在GPU上首次运行会提示其初始化。GPU初始化可能需要3秒钟的时间,如果以毫秒为单位,则差异很大。
3.使用标准CPU计时。最常见的错误是测量时间而没有同步。即使是经验丰富的程序员也会使用下面这段代码。
s = time.time() _ = model(dummy_input)curr_time = (time.time()-s )*1000
当然,这完全忽略了前面提到的异步执行,因此输出不正确的时间。这个错误对测量的均值和方差的影响如下所示:
![2c896be1b333a554f8011cc30b91721e.png](https://i-blog.csdnimg.cn/blog_migrate/d20b7c98038ee9f786e6d0fc2009b6a5.jpeg)
左:均值和标准差的正确测量值。右:不同步过程时的平均值和标准差。X轴是计时方法,Y轴是时间(以毫秒为单位)。
4. 一个样本。与计算机科学中的许多过程一样,神经网络的前馈具有(小的)随机成分。运行时间的差异可能很大,尤其是在测量低延迟网络时。为此,必须在几个样本上运行网络,然后对结果求平均值(300个样本可能是比较好)。
测量吞吐量
神经网络的吞吐量被定义为网络在单位时间内(如一秒)所能处理的输入实例的最大数量。与延迟(涉及处理单个实例)不同,为了实现最大的吞吐量,我们希望并行处理尽可能多的实例。有效的并行性明显依赖于数据、模型和设备。因此,为了正确地度量吞吐量,我们执行以下两个步骤:
(1)估计允许最大并行度的最优batch size;
(2)给定最优batch size,我们测量网络一秒钟内可以处理的实例数量。
为了找到最佳的batch size,一个好的经验法则是达到给定数据类型的GPU内存限制。当然,此大小取决于硬件类型和网络的大小。找到此最大batch size的最快方法是执行二分查找。当时间无关紧要时,简单的顺序搜索就足够了,使用for循环将batch size增加1,直到达到运行时错误为止,这表明GPU可以处理的最大batch size。
找到最佳batch size后,然后计算实际吞吐量。使用以下公式:
(batches数量X batch size)/(总时间(以秒为单位))。
该公式给出了我们的网络在一秒钟内可以处理的实例数。下面的Python代码提供了执行上述计算的简单方法(给定了最佳batch size):
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=100total_time = 0with 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_timeThroughput = (repetitions*optimal_batch_size)/total_timeprint(‘Final Throughput:’,Throughput)
结论
准确测量神经网络的推理时间并不像听起来那么简单。本文详细介绍了深度学习实践者应该注意的几个问题,比如异步执行和GPU节能模式。这里给出的PyTorch代码演示了如何在神经网络中正确地测量时间。最后,我们提到了一些导致人们错误地测量时间的常见错误。