首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >钉扎内存在PyTorch中实际上要慢一些吗?

钉扎内存在PyTorch中实际上要慢一些吗?
EN

Stack Overflow用户
提问于 2019-11-07 04:10:33
回答 2查看 4.5K关注 0票数 15

我想知道为什么将内存固定在PyTorch中会使事情变得更慢。通过阅读torch.utils.data.dataloader的代码,我发现DataLoaderpin_memory=True选项只是在每个批处理上调用.pin_memory(),然后才返回它们。返回的张量仍然在CPU上,之后我必须手动调用.cuda(non_blocking=True)。因此,整个过程是

代码语言:javascript
复制
for x in some_iter:
    yield x.pin_memory().cuda(non_blocking=True)

我比较了这方面的表现

代码语言:javascript
复制
for x in some_iter:
    yield x.cuda()

这是实际代码

代码语言:javascript
复制
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

因此,没有固定内存的使用更少的CPU时间,并且在实际时间方面更快。固定内存不应该使数据传输异步,因此速度更快吗?如果不是这样的话,我们为什么要做引脚记忆呢?

PS。我考虑过预先固定整个TensorDataset的可能性(而不是每次固定批)。但这不能将大于GPU内存的张量引脚:

代码语言:javascript
复制
a = np.memmap('../dat/R/train.3,31,31B', '3,31,31B', 'r')
a.nbytes // 2**30
## 68

torch.from_numpy(a).pin_memory()
## ---------------------------------------------------------------------------
## RuntimeError                              Traceback (most recent call last)
## <ipython-input-36-d6f2d74da8e7> in <module>
## ----> 1 torch.from_numpy(a).pin_memory()
##
## RuntimeError: cuda runtime error (2) : out of memory at /tmp/pip-req-build-58y_cjjl/aten/src/THC/THCCachingHostAllocator.cpp:296

如果我真的想把一个小张量,为什么我不直接移动整个张量到GPU内存提前?

EN

回答 2

Stack Overflow用户

发布于 2021-02-13 07:35:03

TL:博士

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

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

钉扎内存在PyTorch中实际上要慢一些吗?

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

固定内存不应该使数据传输异步,因此速度更快吗?如果不是这样的话,我们为什么要做引脚记忆呢?

它是可以的,但如果您在每次传输之前停止/连接以同步,以便分配内存,则不行。

钉扎内存最终所做的是防止内存块被操作系统交换;它保证保持在RAM中。这种保证使GPU的DMA能够在不经过CPU的情况下在该块上操作(除其他外,CPU必须检查数据是否需要交换回)。因此,在此期间,CPU可以自由地做其他事情。

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

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

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

如果我真的想把一个小张量,为什么我不直接移动整个张量到GPU内存提前?

如果您不需要从CPU访问张量,并且它适合GPU,那么就没有必要将其放入固定内存中。

在您的示例中,您将打开内存映射的numpy数组memmap,然后请求将其传输到固定内存。内存映射文件的工作原理非常类似于不适合RAM的数据中的分页内存,当它再次被访问时,它会被刷新到磁盘,并加载回内存中。

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

这就是你所看到的,因为你在THCCachingHostAllocator.cpp.考试中失败了

票数 6
EN

Stack Overflow用户

发布于 2020-09-30 20:33:19

Pytorch的回答是:

“固定内存是页面锁定内存。如果用户启用了所有的页面锁定内存,那么用户很容易就会对自己开枪,因为它不能被抢占。这就是为什么我们没有使它成为这里中的默认真”。

这意味着取决于当前内存场景( RAM数量、碎片等),它可能会延迟系统。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58741872

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档