背景 当使用 scikit-learn 在大型数据集上执行极其并行的任务时,在高性能计算 (HPC) 环境中的集群上执行此操作会很方便。 Scikit-learn 允许用户通过 n_jobs 参数指定并行使用的核心数量,从而提供对并行化的支持。原则上,这应该可以轻松地将分析管道中使用的核心数量与 HPC 环境中的调度程序请求的处理器数量进行匹配。
问题 IT 部门向我指出,每个作业运行的进程数量(即 7)超过了我请求的处理器数量(即 2),尽管我实际上已将 scikit-learn 的 n_job 参数设置为 2。据了解,结果是这些进程现在互相阻止使用有限的计算资源(我想象有点像音乐椅),从而产生不必要的开销并使集群的使用效率低下。通过运行
pstree PID
检测到线程数。
问题 额外的5个进程从哪里来?如何控制 scikit-learn 中的进程数量以匹配实际分配给集群作业的处理器数量?
代码示例 我创建了一个小示例来说明这种行为(Python 3.6.8、numpy 1.18.1、scikit-learn 0.20.3)
#!/usr/bin/env python
import numpy as np
import os
import time
from sklearn.model_selection import permutation_test_score
from sklearn import datasets, svm
n_folds = 3
n_perm = 12000
n_jobs = 2 # The number of tasks to run in parallel
n_cpus = 2 # Number of CPUs assigned to this process
pid = os.getpid()
print("PID: %i" % pid)
print("loading iris dataset")
X, y = datasets.load_iris(return_X_y=True)
# Control which CPUs are made available for this script
cpu_arg = ''.join([str(ci) + ',' for ci in list(range(n_cpus))])[:-1]
cmd = 'taskset -cp %s %i' % (cpu_arg, pid)
print("executing command '%s' ..." % cmd)
os.system(cmd)
t1 = time.time()
res = permutation_test_score(svm.SVC(kernel='linear'), X, y, cv=n_folds,
n_permutations=n_perm, n_jobs=n_jobs,
verbose=3)
t_delta = time.time() - t1
ips = n_perm / t_delta
print("number of iterations per second: %.02f it/s" % ips)
当使用
htop
检查时,似乎正是我通过 n_jobs=2
和 n_cpus=2
请求的 CPU 数量以 100% 运行 - 在本例中是两个。然而,当我使用 pstree PID
查看线程时,我可以看到 6 个进程正在运行,而不是预期的两个进程。
仅请求
n_jobs=1
和 n_cpus=1
会导致 htop
显示一个 CPU 100% 繁忙,而 pstree PID
同样仅显示单个处理正在运行,如预期的那样。
其他信息和进一步的尝试
有关我的操作系统的一些信息,如
uname -a
:
Linux COMPUTERNAME 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
...以及关于 CPU 的信息,如下
lscpu | grep -e CPU -e core
:
CPU op-mode(s): 32-bit, 64-bit
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
CPU family: 6
Model name: Intel(R) Xeon(R) W-2123 CPU @ 3.60GHz
CPU MHz: 1200.050
CPU max MHz: 3900,0000
CPU min MHz: 1200,0000
NUMA node0 CPU(s): 0-7
我的示例代码现在允许使用
taskset
命令控制可用于代码执行的 CPU 数量。因此,我可以分别操纵 scikit-learn 中请求并行运行的任务数量 (n_jobs
) 和可用 CPU 的数量 (n_cpus
)。这使我能够相互比较四种场景:
n_jobs=1
和n_cpus=1
:360.24 it/s,htop
显示1个CPU繁忙,pstree PID
显示1个线程正在运行。n_jobs=2
和n_cpus=2
:658.40 it/s,htop
显示两个CPU繁忙,pstree PID
显示6个线程正在运行。n_jobs=2
和 n_cpus=1
:336.80 it/s,htop
显示 1 个 CPU 忙碌,pstree PID
显示 6 个线程正在运行。n_jobs=1
和n_cpus=2
:358.60 it/s,htop
显示1个CPU繁忙,pstree PID
显示1个线程正在运行。似乎
pstree
始终取决于 scikit-learn API 中请求的线程数。 htop
显示的活动 CPU 数量是可用于脚本 和 且正在使用的 CPU 数量。因此只有场景 2 会导致两个 CPU 繁忙。同样,如果 CPU 可用且已使用(也是场景 2),则处理速度(以每秒迭代次数衡量)只会在并行情况下提高。至关重要的是,根据 pstree
,线程数量为 6,因此大于 CPU 数量,但这并不表明它们在有限的 CPU 上相互干扰。只有场景 3 表明,当请求两个作业但只有一个 CPU 可用时,处理速度显得较慢。这比场景 1 慢。我开始想知道
pstree
是否真的是并行处理效率的良好诊断以及它与
htop
有何不同。 from joblib import parallel_backend</code>
with parallel_backend("threading", n_jobs=1):
model.fit(X,y)
您可能还需要设置 scikit-learn 模型的 n_jobs:
import sklearn.neighbors.KNeighborsClassifier as KNN
model = KNN(n_jobs=1)