如何处理从 joblib.Parallel() 返回的非常大的对象?

How to handle really large objects returned from the joblib.Parallel()?

我有以下代码,我在其中尝试并行化:

import numpy as np
from joblib import Parallel, delayed

lst = [[0.0, 1, 2], [3, 4, 5], [6, 7, 8]]
arr = np.array(lst)
w, v = np.linalg.eigh(arr)

def proj_func(i):
    return np.dot(v[:,i].reshape(-1, 1), v[:,i].reshape(1, -1))

proj = Parallel(n_jobs=-1)(delayed(proj_func)(i) for i in range(len(w)))

proj returns 一个非常大的列表,它会导致内存问题。

有什么办法可以解决这个问题吗?

我想过返回一个生成器而不是一个列表,但我不知道该怎么做。也欢迎任何其他方式。

Q : "Is there a way I could work around this?"

这取决于 this 代表什么。

为公平使用 np.linalg.eigh() 方法而设置的先决条件在上面的 MCVE 代码片段 posted 中意外地没有满足,但这些仍然超出了本文的范围post。如果任何复杂的输入和结果将得到相应的处理,一些这里提到的 N-scaled RAM 分配,出于显而易见的原因,实际上将达到 2*N-sized 或 4*N*N-sized或下面描述的 RAM 占用空间要求的缩放比例中的 8*N*N*N 大小的因素,但 核心信息应该从普通的 N- 清晰可靠 下面使用的分解大小依赖性:


内存大小是瓶颈吗?
Space 对于静态大小的数据:

给定您的 MCVE,正如上面 post 编辑的那样,内存大小取决于 N = arr.size 并且您的系统至少有:
- N * 3 * 8 [B] RAM lst, arr, w
- N * N * 8 [B] 用于保存 v

的 RAM

加起来,肯定比<_nCPUs_> * 8 * N * ( 3 + N ) [B] RAM-space多得多,只介绍n_jobs == -1全本python 解释器进程(肯定是 MacOS / WinOS,很可能也是 linux,因为 fork-method 在 2019/2020 中被记录以产生 unstable/unsafe 结果)之前,代码甚至试图第一次调用proj_func( i )

如果那不是你系统的容量,你可能会直接停止阅读。


下一步 ?
Space 对于动态数据:

下一个 N 的任何调用 - 对 proj_func( i ) 的调用,每个调用都会增加 - [= 的额外 RAM 分配22=] RAM-space 用于保存 np.dot()-results

总计超过 k * N * N * N * 8 [B] RAM 用于保存 np.dot()-结果,其中 k >> 2 ,因为每个 N-结果都必须得到 SER-packed(再次分配一些 RAM-space 这样做),接下来每个这样的 SER-ed-payload 必须从远程-joblib.Parallel()(delayed()(...))-执行器传输 转发到主进程(这里再次为 SER-ed 有效载荷分配一些 RAM-space)接下来这个 RAM 存储的中间二进制有效载荷必须得到 DES-erialised(因此再次分配一些额外的 RAM-space 用于存储原始大小 [=22= 的 DES-ed 数据] ) 以便最终得到这个 SER/DES-pipelined 产品 N 次附加到初始 proj == [] 上面指定的语法使用
joblib.Parallel(…)( delayed( proj_func )( i ) for i in range( len( w ) ) )-条款坚持并强制执行。

<_nCPUs_> * 8 * N * ( 3 + N ) //     static storage: data + all python process-replicas
+
<_nCPUs_> * 8 * N * N * k     //    dynamic storage: SER/DES on joblib.Parallel()(delayed…)
+
            8 * N * N * N     // collective storage: proj-collected N-( np.dot() )-results
~
=           8 * N * ( N * N + <_nCPUs_> * ( 3 + N * ( k + 1 ) ) )

简历:

这很快就会扩展(即使我们假设没有其他 python-进程 import-s 和静态数据)远高于 "ordinary" 主机计算设备的任何 N == arr.size >= 1E3 :

>>> nCPUs =  4; k = 2.1; [ ( 8 * N * ( N * N + nCPUs * (3+N*(k+1)))/1E9 ) for N in ( 1E3, 1E4, 1E5, 1E6 ) ]
[8.099296, 8009.92096, 8000992.0096, 8000099200.096]
>>> nCPUs =  8; k = 2.1; [ ( 8 * N * ( N * N + nCPUs * (3+N*(k+1)))/1E9 ) for N in ( 1E3, 1E4, 1E5, 1E6 ) ]
[8.198592, 8019.84192, 8001984.0192, 8000198400.192]
>>> nCPUs = 16; k = 2.1; [ ( 8 * N * ( N * N + nCPUs * (3+N*(k+1)))/1E9 ) for N in ( 1E3, 1E4, 1E5, 1E6 ) ]
[8.397184, 8039.68384, 8003968.0384, 8000396800.384]
 8[GB]     |...[GB]    |  |...[GB]   |  |  |...[GB]
           8   [TB]    |...   [TB]   |  |...   [TB]
                       8      [PB]   |...      [PB]
                                     8         [EB]

结语:

因此,一个简单的 SLOC,使用与 joblib.Parallel()(delayed()()) 一样简单的语法,如果不花费适当的设计努力,可以立即以一种无法挽救的方式破坏迄今为止计算图的全部成果至少在原始数据处理定量估计上。