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

问题描述

背景

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

问题

不幸的是,我什至无法在多个进程中导入 pytorch。当进程的 nr超过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 上)

标签: pythonpytorchpython-multiprocessing

解决方案


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

看来这个问题是由 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 部分提交大量已提交的内存。令人沮丧的部分是它似乎没有使用内存。例如,现在我的 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

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

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

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

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

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

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


推荐阅读