如何同时有效 运行 多个 Pytorch 进程/模型?回溯:分页文件太小,无法完成此操作

How to efficiently run multiple Pytorch Processes / Models at once ? Traceback: The paging file is too small for this operation to complete

背景

我有一个非常小的网络,我想用不同的随机种子进行测试。 该网络几乎不使用我 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:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\__init__.py", line 117, in <module>
    import torch
  File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\__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:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\lib\cudnn_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 上)

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

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

如果您在 Windows 中,最好使用比 物理内核总数 少 1 个(或更多)内核作为 [=22] 中的多处理模块=] 在 Windows 中,如果您使用全部并实际尝试获得所有 逻辑 核心,则倾向于尽可能获得所有内容。

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)

详情请参考这里:

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

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

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

加载这些 DLL 的每个 Python 进程将占用数 GB 的内存来加载这些 DLL。因此,如果 1 Python 进程浪费了 2GB 内存,而您尝试 运行 宁 8 个工作人员,则需要 16GB 内存来备用 仅用于加载 DLL。这个内存似乎真的没有使用,只是提交。

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

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

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

运行 它在你的手电筒和 cuda DLL 上。在 OP 的情况下,命令行可能如下所示:

python fixNvPe.py --input=C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\lib\*.dll

(你还想 运行 这个无论你的 cusolver64_*.dll 和朋友在哪里。这个 可能 在你的 torch\lib 文件夹中,或者它可能是,例如,C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\vXX.X\bin。如果它在 Program Files 下,您将需要 运行 具有管理权限的脚本)

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

ASLR 是 'address space layout randomization.' 它是一种安全功能,运行将 DLL 加载到内存中的位置定位。我们为此 DLL 禁用它,以便所有 Python 进程将 DLL 加载到相同的基本虚拟地址。如果使用该 DLL 的所有 Python 个进程将其加载到相同的基地址,则它们都可以共享该 DLL。否则每个进程都需要自己的副本。

标记部分 'read-only' 让 Windows 知道内容不会在内存中改变。如果将文件映射到内存 read/write,Windows 必须提交足够的内存,由页面文件支持,以防万一您对其进行修改。如果该部分是只读的,则无需在页面文件中备份它。我们知道它没有任何修改,所以它总是可以在 DLL 中找到。

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

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

编辑 2022-01-20: 根据 NVIDIA:“我们已经将 nv_fatb 部分标记为只读,此更改将针对下一个主要 CUDA 版本 11.7。我们没有更改 ASLR,因为这被认为是安全功能 ."

这肯定有帮助。如果没有 ASLR 还不够,那么脚本应该仍然有效

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

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

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

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