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

KoK*_*KlA 11 python python-multiprocessing pytorch

背景

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

问题

不幸的是,我什至无法在多个进程中导入 pytorch。当进程数超过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()
Run Code Online (Sandbox Code Playgroud)

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

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

import torch
import time
time.sleep(10)
Run Code Online (Sandbox Code Playgroud)
 
 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

Run Code Online (Sandbox Code Playgroud)

进一步的调查

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

问题

有解决方法吗?

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

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

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

Chr*_*yan 30

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

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

当 Python 导入“torch”时,它会加载这些 DLL,并将 .nv_fatb 部分映射到内存中。由于某种原因,它不仅仅是一个内存映射文件,它实际上占用了内存。该部分设置为“写入时复制”,因此可能会写入某些内容?我不知道。但无论如何,如果您使用 VMMap ( https://learn.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 )。

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

python fixNvPe.py --input=C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\lib\*.dll
Run Code Online (Sandbox Code Playgroud)

(您还希望在您的 cusolver64_*.dll 和朋友所在的任何位置运行此程序。这可能位于您的 torch\lib 文件夹中,或者可能位于 C:\Program Files\NVIDIA GPU Compute Toolkit\CUDA\vXX.X \bin 。如果它位于 Program Files 下,则需要使用管理权限运行脚本)

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

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

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

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

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

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

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

  • 有人在 Linux 上尝试过这个技巧(它也应该适用于 .so 吗?) (2认同)