如何一次有效地运行多个 Pytorch 进程/模型? Traceback:分页文件太小,无法完成此操作

背景

我有一个非常小的网络,我想用不同的随机种子进行测试。该网络几乎不使用我的 GPU 的 1% 计算能力,因此理论上我可以一次运行 50 个进程来一次尝试许多不同的种子。

问题

不幸的是,我什至无法在多个进程中导入 pytorch。当 nr of processes 超过 4 时,我会收到关于页面文件太小的 Traceback。

最少的可重现代码§ - dispatcher.py

from subprocess import Popen
import sys

procs = []
for seed in range(50):
    procs.append(Popen([sys.executable, "ml_model.py", str(seed)]))

for proc in procs:
    proc.wait()

§我增加了种子的数量,所以拥有更好机器的人也可以复制它。

最少的可重现代码 - ml_model.py

import torch
import time
time.sleep(10)

 Traceback (most recent call last):
  File "ml_model.py", line 1, in <module>
    import torch
  File "C:UsersuserAppDataLocalProgramsPythonPython38libsite-packagestorch__init__.py", line 117, in <module>
    import torch
  File "C:UsersuserAppDataLocalProgramsPythonPython38libsite-packagestorch__init__.py", line 117, in <module>
    raise err
 OSError: [WinError 1455] The paging file is too small for this operation to complete. Error loading "C:UsersuserAppDataLocalProgramsPythonPython38libsite-packagestorchlibcudnn_cnn_infer64_8.dll" or one of its dependencies.
    raise err

进一步的调查

我注意到每个进程都会将大量 dll 加载到 RAM 中。当我关闭所有其他使用大量 RAM 的程序时,我最多可以获得 10 个进程而不是 4 个。所以这似乎是一个资源限制。

问题

有解决方法吗?

在单个 gpu 上使用 pytorch 训练许多小型网络的推荐方法是什么?

我应该编写自己的 CUDA 内核,还是使用不同的框架来实现这一点?

我的目标是一次运行大约 50 个进程(在 16GB RAM 机器上,8GB GPU RAM 上)

stack overflow How to efficiently run multiple Pytorch Processes / Models at once ? Traceback: The paging file is too small for this operation to complete
原文答案
author avatar

接受的答案

今晚我对此进行了一些研究。我没有解决方案(编辑:我有缓解措施,请参阅最后的编辑),但我有更多信息。

看来这个问题是由 NVidia fatbins (.nv_fatb) 被加载到内存中引起的。一些 DLL,例如 cusolver64_xx.dll、torka_cuda_cu.dll 和其他一些 DLL,其中包含 .nv_fatb 部分。这些包含用于不同 GPU 的大量不同变体的 CUDA 代码,因此最终会达到几百兆字节到几千兆字节。

当 Python 导入“火炬”时,它会加载这些 DLL,并将 .nv_fatb 部分映射到内存中。由于某种原因,它不仅仅是一个内存映射文件,它实际上是在占用内存。该部分设置为“写入时复制”,因此有可能写入内容吗?我不知道。但是无论如何,如果您使用 VMMap ( https://docs.microsoft.com/en-us/sysinternals/downloads/vmmap ) 查看 Python,您会发现这些 DLL 正在为此 .nv_fatb 部分提交大量已提交的内存。令人沮丧的部分是它似乎不是 using 内存。例如,现在我的 Python.exe 已提交 2.7GB,但工作集只有 148MB。

每个加载这些 DLL 的 Python 进程都会提交几 GB 的内存来加载这些 DLL。因此,如果 1 个 Python 进程正在浪费 2GB 内存,而您尝试运行 8 个工作线程,则需要 16GB 内存来腾出 just to load the DLLs 。看起来这个内存真的不是 used ,只是提交了。

我对这些 fatbinaries 了解不多,无法尝试修复它,但从过去 2 小时的观察来看,它们确实是问题所在。也许这是一个 NVidia 问题,这些正在提交内存?

编辑: 我制作了这个 Python 脚本: https://gist.github.com/cobryan05/7d1fe28dd370e110a372c4d268dcb2e5

获取它并安装它的 pefile 依赖项( python -m pip install pefile )。

在您的 torch 和 cuda DLL 上运行它。在 OP 的情况下,命令行可能如下所示:

python fixNvPe.py --input=C:UsersuserAppDataLocalProgramsPythonPython38libsite-packagestorchlib*.dll

(你还想在你的 cusolver64_*.dll 和朋友所在的任何地方运行它。这个 may 在你的 torchlib 文件夹中,或者它可能是,例如 C:Program FilesNVIDIA GPU Computing ToolkitCUDAvXX.Xbin 。如果它在 Program 下文件,您需要以管理权限运行脚本)

该脚本将扫描输入 glob 指定的所有 DLL,如果找到 .nv_fatb 部分,它将备份 DLL,禁用 ASLR,并将 .nv_fatb 部分标记为只读。

ASLR 是“地址空间布局随机化”。它是一种随机化 DLL 在内存中加载位置的安全功能。我们为此 DLL 禁用它,以便所有 Python 进程将 DLL 加载到相同的基本虚拟地址中。如果所有使用 DLL 的 Python 进程都将其加载到相同的基地址,则它们都可以共享 DLL。否则每个进程都需要自己的副本。

将部分标记为“只读”让 Windows 知道内容不会在内存中更改。如果将文件映射到内存读/写,Windows 必须提交足够的内存,由页面文件支持,以防万一您对其进行修改。如果该部分是只读的,则无需在页面文件中支持它。我们知道没有对其进行任何修改,因此始终可以在 DLL 中找到它。

该脚本背后的理论是,通过更改这两个标志,将为 .nv_fatb 提交更少的内存,而在 Python 进程之间共享更多的内存。在实践中,它是有效的。不如我希望的那么好(它仍然比它使用的要多得多),所以我的理解可能有缺陷,但它 significantly 减少了内存提交。

在我有限的测试中,我没有遇到任何问题,但我不能保证没有代码路径试图写入我们标记为“只读”的部分。但是,如果您开始遇到问题,您可以恢复备份。

编辑 2022-01-20: 每 NVIDIA: "We have gone ahead and marked the nv_fatb section as read-only, this change will be targeting next major CUDA release 11.7 . We are not changing the ASLR, as that is considered a safety feature ."

这肯定会有所帮助。如果没有 ASLR 还不够,那么脚本应该仍然可以工作


答案:

作者头像

好吧,我设法解决了这个问题。打开“高级系统设置”。转到高级选项卡,然后单击与性能相关的设置。再次单击高级选项卡--> 更改--> 取消选择“自动......”。对于所有驱动器,设置“系统管理大小”。重启你的电脑。

作者头像

对于我的情况,系统已经设置为系统管理的大小,但我有同样的错误,那是因为我将一个大的变量传递给一个函数中的多个进程。可能我需要设置一个非常大的页面文件,因为 Windows 无法即时创建它,而是选择退出以减少进程数量,因为它不是一个总是使用的功能。

如果您在 Windows 中,最好使用 1 个(或更多)核心少于 pysical cores 总数,因为 Windows 中的 python 中的多处理模块往往会尽可能获得所有内容,如果您使用 all 并且实际上试图获得所有逻辑内核。

import multiprocessing
multiprocessing.cpu_count()
12  
# I actually have 6 pysical cores, if you use this as base it will likely hog system

import psutil 
psutil.cpu_count(logical = False)
6 #actual number of pysical cores

psutil.cpu_count(logical = True)
12 #logical cores (e.g. hyperthreading)

详情请参考这里: Multiprocessing: use only the physical cores?

作者头像

跟进 @chris-obryan's 的答案(我会发表评论,但没有声誉),我发现在应用修复后的一段时间内,内存利用率急剧下降(大约每个进程提到的 2GB 的顺序)。

为了获得更多性能,可能值得监控内存利用率并在发生内存下降时生成模型的新实例,从而为一些开销留出足够的空间(约 3 或 4 GB 是安全的)。

我看到在设置阶段使用了大约 28GB 的​​ RAM,在迭代一段时间后下降到大约 14GB。

(请注意,我的用例在此处略有不同,因为由于使用 GA 进行优化,我受到主机<->设备传输的瓶颈,因为在每一代之后都需要进行合理数量的 CPU 绑定处理,因此这可以发挥作用我也在使用 concurrent.futures.ProcessPoolExecutor() 而不是手动使用子进程)

作者头像

我已将“num_workers = 10”更改为“num_workers = 1”。它帮助我解决了这个问题。