初始问题:
我正在测试一个名为 Ray 的多处理 Python 包来并行化我的代码。原始代码在我的笔记本电脑(core-i7-13800H、32GB RAM)上运行良好。然而,当在本地集群上运行时,当我指定大于 40 的
num_cpus
(Ray 关键字)时,作业会失败,尽管已从集群请求了 50 个 CPU 和 200GB RAM。作为参考,我在笔记本电脑上使用 num_cpus=18
。 STDOUT 返回一个巨大的简介,我认为这可能是 OOM 错误(见下文)。
> ^[[36m(func_parallel pid=70267)^[[0m [2024-11-01 10:33:28,278 E 70267 75787] logging.cc:108: Unhandled exception: N5boost10wrapexceptINS_6system12system_errorEEE. what(): thread: Resource temporarily unavailable [system:11]
> ^[[36m(func_parallel pid=70115)^[[0m OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 50: Resource temporarily unavailable
> ^[[36m(func_parallel pid=70115)^[[0m OpenBLAS blas_thread_init: RLIMIT_NPROC 16384 current, 16384 max
> ^[[36m(func_parallel pid=69306)^[[0m 2024-11-01 10:33:29.811669: F external/local_tsl/tsl/platform/default/env.cc:74] Check failed: ret == 0 (11 vs. 0)Thread tf_numa_-1_Eigen creation via pthread_create() failed.
> ^[[36m(func_parallel pid=69306)^[[0m *** SIGABRT received at time=1730482409 on cpu 55 ***
> ^[[36m(func_parallel pid=69306)^[[0m PC: @ 0x14a9fbfa8cbb (unknown) raise
> ^[[36m(func_parallel pid=69306)^[[0m @ 0x14a9fcabc8c0 186786352 (unknown)
> ^[[33m(raylet)^[[0m A worker died or was killed while executing a task by an unexpected system error. To troubleshoot the problem, check the logs for the dead worker. RayTask ID: 4e9c3bd9b738a771af2ce9c4f66013ce9dbb521201000000 Worker ID: 52b96fe61ede380cde1d513fc65c222ba65a0100a2a1a8b00ad8f310 Node ID: ea8ebb2f21de741cf396d92301075881372f10000e39b7518e080cbb Worker IP address: Worker port: Worker PID: Worker exit type: SYSTEM_ERROR Worker exit detail: Worker unexpectedly exits with a connection error code 2. End of file. There are some potential root causes. (1) The process is killed by SIGKILL by OOM killer due to high memory usage. (2) ray stop --force is called. (3) The worker is crashed unexpectedly due to SIGSEGV or other unexpected errors.
运行
top
显示每个核心的 RES 仅 1.5GB,但每个核心的 VMEM 高达 92GB,导致该作业的 VMEM 总使用量超过 2TB!代码中没有任何内容可以保证如此高的使用率。
我无法分享原始代码,但我测试了一个简化的代码来检查是否存在内存泄漏,并在没有所有其他模块导入和数据预处理的情况下重现此代码。
简化问题:
简化的代码如下所示,其中并行函数接受 10000x10000 矩阵,将其与自身相乘,对所有元素求和,将其添加到循环中,然后输出求和结果。我尝试使用 Python 多处理包、Ray Core 和 Ray 多处理池。
import os
os.environ['RAY_DEDUP_LOGS'] = '0'
import numpy as np
import ray
from ray.util.multiprocessing import Pool
# Used by Ray Core
@ray.remote
def func_parallel(db):
a = 0
for _ in range(5):
a = a + np.sum(np.matmul(db, db))
print(a)
return a
# Used by Ray Multiprocessing Pool
# def func_parallel(db):
# a = 0
# for _ in range(5):
# a = a + np.sum(np.matmul(db, db))
# print(a)
# return a
nloop = 50
mat = np.ones((10000, 10000))
# Tested with Ray Core
ray.init(num_cpus=40, object_store_memory=80*1024*1024)
db_ref = ray.put(mat)
task = [func_parallel.remote(db_ref) for _ in range(nloop)]
val = ray.get(task)
# Tested with Ray Multiprocessing Pool
# with Pool(ray_remote_args={'num_cpus': 40}) as pool:
# val = pool.map(func_parallel, [mat for _ in range(nloop)], chunksize=1)
print(val)
同样,这在我的笔记本电脑上运行良好,但集群上每个核心仍然消耗超过 60GB 的 VMEM,并且当
num_cpus
超过一定数量时很容易失败。我发现控制这个荒谬数字的唯一方法是将 object_store_memory
指定为可能的最小值(大约 80 MB),这会将每个核心的 VMEM 使用量降至 9GB。 RES 使用量仅为 1GB,这对于矩阵的大小来说似乎是正确的(1 亿个元素双精度略低于 1GB)。
仍然不确定为什么使用 9GB,但是好吧,我可以接受它,它可以让我增加
num_cpus
。在我的原始代码中指定 object_store_memory
可以将每个核心的 VMEM 使用量降至 27GB。该值在 5GB 到 27GB 之间波动(如 top
所报告),我不知道这对应于代码的哪个阶段,因为我的 print
语句似乎是批量出现的。振荡可能是由于 Ray 的重试策略造成的。同样,当 num_cpus
超过一定数量时,作业就会失败。
问题:
因此,经过更多搜索,我认为它失败的问题与其 VMEM 消耗无关。正如@teapot418上面提到的,这可能是一个多线程问题。
在导入 Numpy、Tensorflow 和 Ray 之前,我在导入语句中添加了以下内容:
import os
os.environ['OPENBLAS_NUM_THREADS'] = '1'
os.environ['OMP_NUM_THREADS'] = '1'
os.environ['RAY_num_server_call_thread'] = '1'
os.environ['TF_NUM_INTEROP_THREADS'] = '1'
os.environ['TF_NUM_INTRAOP_THREADS'] = '1'
现在它可以运行了 - 仍然消耗 4TB 的 VMEM,但我不再关心了。
我猜测问题是由于当我实际上想要多核处理时,一个或某些包启用了多线程。更好的人必须向我解释它们的区别是什么,为什么混合多线程和多处理不好,以及为什么我在笔记本电脑上运行时没有遇到这种情况。
以上内容是根据以下讨论拼凑而成:
请注意,单独设置环境变量
OPENBLAS_NUM_THREADS
和 OMP_NUM_THREADS
不适用于原始代码,但适用于上面帖子中的简化代码。我的猜测是 Numpy 使用 OpenBLAS 进行多线程操作,并试图产生太多线程,从而导致它失败。原始代码不涉及如此大的矩阵乘法,但确实使用了 Tensorflow,因此需要将 TF_NUM_INTEROP_THREADS
和 TF_NUM_INTRAOP_THREADS
环境变量设置为 1 以停止多线程。
这确实提出了一个问题:在涉及大型矩阵乘法或 Numpy 多线程以及 Tensorflow 操作的其他操作的更复杂代码中,我应该使用多线程还是多处理?哪个更快?但再问一次...我困了...