在PyTorch中固定内存实际上速度较慢吗?

时间:2019-11-07 04:10:33

标签: python pytorch

我想知道为什么在PyTorch中固定内存会使事情变得更慢。通过阅读torch.utils.data.dataloader的代码,我发现pin_memory=True的{​​{1}}选项仅在返回每个批次之前调用DataLoader。返回的张量仍在CPU上,此后我必须手动调用.pin_memory()。因此,整个过程将是

.cuda(non_blocking=True)

我将其性能与

for x in some_iter:
    yield x.pin_memory().cuda(non_blocking=True)

这是实际的代码

for x in some_iter:
    yield x.cuda()

结果,不固定内存既减少了CPU时间,又缩短了实际时间。固定内存不应该使数据传输异步并因此更快吗?如果不是这种情况,为什么我们要进行引脚存储?


PS。我考虑了预先固定整个a = torch.rand(1024, 655360) %%time for i in a: i.pin_memory().cuda(non_blocking=True) # CPU times: user 1.35 s, sys: 55.8 ms, total: 1.41 s # Wall time: 396 ms %%time for i in a: i.pin_memory().cuda() # CPU times: user 1.6 s, sys: 12.2 ms, total: 1.62 s # Wall time: 404 ms %%time for i in a: i.cuda(non_blocking=True) # CPU times: user 855 ms, sys: 3.87 ms, total: 859 ms # Wall time: 274 ms %%time for i in a: i.cuda() # CPU times: user 314 ms, sys: 12 µs, total: 314 ms # Wall time: 313 ms 的可能性(而不是每次都固定批次)。但这无法固定大于GPU内存的张量:

TensorDataset

如果我想固定一个小的张量,为什么不提前将整个张量直接移到GPU内存中?

2 个答案:

答案 0 :(得分:2)

TL:DR

您的代码较慢,因为您每次调用生成器时都会分配一个新的固定内存块。每次分配新内存都需要同步,这使得它比非固定内存慢得多。很可能,您正在衡量这个开销。

您在编辑中的代码示例在 THCCachingHostAllocator.cpp 中失败。不是 GPU 内存不足,而是您的主机拒绝您分配 68GB 的​​固定物理内存。


<块引用>

PyTorch 中的固定内存实际上更慢?

创建或释放固定内存(cudaHostAlloc()/cudaFreeHost() 通过 CUDA Runtime)比 malloc/free 慢得多,因为它涉及设备之间的同步(GPU 和主机)。很可能,您正在测量的在很大程度上是这种开销,因为您正在逐步分配固定内存。

<块引用>

固定内存不应该使数据传输异步从而更快吗?如果不是这样,我们为什么要做pin memory?

它可以,但如果您在每次传输之前暂停/加入同步以分配内存,则不能。

pinning memory 最终的作用是防止内存块被操作系统换出;它保证保留在 RAM 中。这种保证使 GPU 的 DMA 可以在该块上运行而无需通过 CPU(CPU 必须检查数据是否需要换回)。因此,CPU 在此期间可以自由地做其他事情。

这不是一个完美的类比,但您可以将固定内存视为 GPU 和主机之间的共享内存。双方可以在不通知对方的情况下对其进行操作;有点像一个进程中的多个线程。如果您实现非阻塞代码,这 会快得多。但是,如果各方最终总是join进行,速度也会慢得多。

将此与非固定方法进行对比,在这种方法中,CPU 从 RAM 加载数据(必要时交换),然后将其发送到 GPU。它不仅速度较慢(需要两次通过北桥),而且还使线程(因此一个 CPU 内核)保持忙碌。 Python 还具有臭名昭著的 GIL,因此可能是您的整个应用程序都在等待同步 I/O。

如果您想使用固定内存将成批数据混洗到 GPU 中,那么一种方法是使用固定内存作为(循环)缓冲区。 CPU 可以从磁盘加载数据,应用预处理,并将批处理放入缓冲区。然后,GPU 可以在其自己的时间内从缓冲区中提取批次并进行推理。如果实现得好,那么GPU不会闲置过多,主机和GPU之间也不再需要同步。

<块引用>

如果我确实想固定一个小张量,为什么不提前将整个张量直接移动到 GPU 内存中?

如果不需要从 CPU 访问张量并且它适合 GPU,那么确实没有必要将其放入 pinned memory。

在您的示例中,您正在打开一个内存映射的 numpy 数组 memmap,然后要求将其传输到固定内存。内存映射文件的工作方式与分页内存非常相似,因为不再适合 RAM 的数据会刷新到磁盘,并在再次访问时重新加载。

这种“交换”不会发生在固定内存中,因为我们需要保证整个块在任何时候都驻留在 RAM 中。因此,我们需要首先将整个数组加载到主机内存中 - 一个 68 GB 的连续块 - 可能会在进程中创建该数组的副本以不破坏 memmap 对象,然后我们需要将其固定内存块,告诉主机放弃 68GB 的​​托管物理内存给我们的应用程序。这两个步骤中的任何一个都可能被操作系统拒绝并引发 OutOfMemory 错误。

这几乎就是您所看到的,因为您在 THCCachingHostAllocator.cpp 中失败了。

答案 1 :(得分:1)

Pytorch开发人员的答案:

“固定内存是页面锁定的内存。如果用户为所有功能启用了页面锁定的内存,那么用户很容易to脚,因为它不能被抢占。这就是为什么我们没有将其设为默认值来自“ here

这意味着根据您当前的内存情况(RAM量,碎片等),可能会延迟您的系统。