9.3. 并行主义、资源管理和配置#

9.3.1. 并行性#

一些scikit-learn估计器和实用程序使用多个中央处理器核并行化昂贵的操作。

根据估计器的类型,有时还取决于构造函数参数的值,可以这样做:

  • 通过更高级别的并行性 joblib .

  • 通过在C或Cython代码中使用的OpenMP实现低级别并行。

  • 通过BLAS具有较低级别的并行性,NumPy和SciPy使用该并行性对阵列进行通用操作。

n_jobs 估计器的参数始终控制joblib(取决于joblib后台的进程或线程)管理的并行性量。由scikit-learn自己的Cython代码中的BEP或由scikit-learn中使用的NumPy和SciPy操作使用的BLAS & LAPACK库管理的线程级并行性始终由环境变量或 threadpoolctl 如下所述。请注意,一些估计器可以在其训练和预测方法的不同点利用所有三种并行性。

我们在以下小节中更详细地描述这3种并行性。

9.3.1.1. 与jobib的更高级并行性#

当底层实现使用joblib时,可以通过 n_jobs 参数.

备注

通过指定使用joblib的估计器中在哪里(以及如何)进行并行化 n_jobs is currently poorly documented. Please help us by improving our docs and tackle issue 14228 !

Joblib能够支持多处理和多线程。joblib是否选择产生线程或进程取决于 backend 它正在使用的。

scikit-learn通常依赖于 loky backend,这是joblib的默认后台。Loky是一个多处理后台。在进行多处理时,为了避免每个进程中重复内存(这对于大数据集来说是不合理的),joblib会创建一个 memmap 当数据大于1 MB时,所有进程都可以共享。

在某些特定情况下(当并行运行的代码释放GIL时),scikit-learn将指示 joblib 多线程后台更可取。

作为用户,您可以通过使用上下文管理器来控制joblib将使用的后端(无论scikit-learn推荐什么):

from joblib import parallel_backend

with parallel_backend('threading', n_jobs=2):
    # Your scikit-learn code here

请参阅 joblib's docs 了解更多详细信息。

在实践中,并行是否有助于提高运行时间取决于许多因素。这通常是一个好主意,实验,而不是假设增加工人的数量总是一件好事。在某些情况下,并行运行某些估计器或函数的多个副本可能会对性能造成极大损害(请参见 oversubscription 下面)。

9.3.1.2. 与BEP的低级并行性#

OpenMP用于并行化用Cython或C编写的代码,完全依赖于多线程。默认情况下,使用OpenMP的实现将使用尽可能多的线程,即与逻辑核心一样多的线程。

您可以控制使用的线程的确切数量:

  • 经由 OMP_NUM_THREADS 环境变量,例如:运行Python脚本时:

    OMP_NUM_THREADS=4 python my_script.py
    
  • 或经由 threadpoolctl as explained by this piece of documentation .

9.3.1.3. 来自数字库的并行NumPy和SciPy例程#

scikit-learn严重依赖NumPy和SciPy,它们在内部调用在MKL、OpenBLAS或BLIS等库中实现的多线程线性代数例程(BLAS & LAPACK)。

您可以使用环境变量控制BLAS为每个库使用的线程的确切数量,即:

  • MKL_NUM_THREADS 设置MKL使用的线程数,

  • OPENBLAS_NUM_THREADS 设置OpenBLAS使用的线程数

  • BLIS_NUM_THREADS 设置BLIS使用的线程数

Note that BLAS & LAPACK implementations can also be impacted by OMP_NUM_THREADS. To check whether this is the case in your environment, you can inspect how the number of threads effectively used by those libraries is affected when running the following command in a bash or zsh terminal for different values of OMP_NUM_THREADS:

OMP_NUM_THREADS=2 python -m threadpoolctl -i numpy scipy

备注

在撰写本文时(2022年),NumPy和SciPy包在pypi.org上分发(即通过 pip install )和conda-forge频道(即通过 conda install --channel conda-forge )与OpenBLAS链接,而NumPy和SciPy包则在 defaults 来自Anaconda.org的conda频道(即通过 conda install )默认与MKL链接。

9.3.1.4. 超额订阅:产生太多线程#

通常建议避免使用明显多于计算机上的中央处理器数量的进程或线程。当程序同时运行太多线程时,就会发生过度订阅。

假设您有一台具有8个中央处理器的机器。考虑一个您正在运行的案例 GridSearchCV (与jobib并行)与 n_jobs=8 通过 HistGradientBoostingClassifier (与BEP并行)。的每个实例 HistGradientBoostingClassifier 将产生8个线程(因为您有8个处理器)。一共是 8 * 8 = 64 线程,这会导致线程对物理中央处理器资源的过度订阅,从而导致调度费用。

对于嵌套在joblib调用中的来自MKL、OpenBLAS或BLIS的并行例程,也可能以完全相同的方式出现超额预订。

日起 joblib >= 0.14 ,当 loky backend(默认值),joblib将告诉它的子节点 processes 限制他们可以使用的线程数量,以避免过度订阅。在实践中,jobib使用的启发式是告诉要使用的流程 max_threads = n_cpus // n_jobs ,通过其相应的环境变量。回到上面的例子,因为的jobib后台 GridSearchCVloky ,每个进程将只能使用1个线程而不是8个线程,从而减轻了过度订阅问题。

请注意:

  • 手动设置其中一个环境变量 (OMP_NUM_THREADS , MKL_NUM_THREADS , OPENBLAS_NUM_THREADS ,或者 BLIS_NUM_THREADS )将优先于jobib试图做的事情。线程总数将是 n_jobs * <LIB>_NUM_THREADS .请注意,设置此限制还会影响主进程中的计算,主进程只会使用 <LIB>_NUM_THREADS . Joblib公开了一个上下文管理器,用于更好地控制其工作者中的线程数量(请参阅下面链接的joblib文档)。

  • 当jobib配置为使用 threading 在后台,当调用jobib托管线程中的并行本地库时,没有机制可以避免过度订阅。

  • 所有在其Cython代码中明确依赖BEP的scikit-learn估计器始终使用 threadpoolctl 在内部自动调整BEP和潜在嵌套BLAS调用使用的线程数量,以避免过度订阅。

您可以在中找到有关过度订阅的临时缓解的更多详细信息 joblib documentation .

您将在中的数字Python库中找到有关并行性的更多详细信息 this document from Thomas J. Fan .

9.3.2. 配置开关#

9.3.2.1. Python API#

sklearn.set_configsklearn.config_context 可用于更改控制并行性方面的配置参数。

9.3.2.2. 环境变量#

应在导入scikit-learn之前设置这些环境变量。

9.3.2.2.1. SKLEARN_ASSUME_FINITE#

设置 assume_finite 论点 sklearn.set_config .

9.3.2.2.2. SKLEARN_WORKING_MEMORY#

设置 working_memory 论点 sklearn.set_config .

9.3.2.2.3. SKLEARN_SEED#

在运行测试时设置全局随机生成器的种子,以实现重现性。

请注意,scikit-learn测试预计将通过显式播种其自己的独立RNG实例来确定性地运行,而不是依赖于numpy或Python标准库RNG单例来确保测试结果独立于测试执行顺序。然而,有些测试可能会忘记使用显式种子,而此变量是控制上述单例初始状态的一种方法。

9.3.2.2.4. SKLEARN_TESTS_GLOBAL_RANDOM_SEED#

控制在依赖于 global_random_seed 固定装置。

所有使用此固定装置的测试都接受他们应该确定性地通过的合同,其中包括0到99之间的任何种子值。

在夜间CI构建中, SKLEARN_TESTS_GLOBAL_RANDOM_SEED 环境变量在上述范围内随机抽取,所有固定测试都将针对该特定种子运行。目标是确保随着时间的推移,我们的CI将使用不同种子运行所有测试,同时限制完整测试套件单次运行的测试持续时间。这将检查为使用此固定装置而编写的测试的断言是否依赖于特定的种子值。

可接受种子值的范围仅限于 [0, 99] 因为通常不可能编写适用于任何可能的种子的测试,并且我们希望避免在CI上随机失败的测试。

的有效值 SKLEARN_TESTS_GLOBAL_RANDOM_SEED :

  • SKLEARN_TESTS_GLOBAL_RANDOM_SEED="42" :以固定种子42运行测试

  • SKLEARN_TESTS_GLOBAL_RANDOM_SEED="40-42" :使用40到42之间的所有种子运行测试

  • SKLEARN_TESTS_GLOBAL_RANDOM_SEED="all" :使用0到99之间的所有种子运行测试。这可能需要很长时间:仅用于单个测试,而不是完整测试套件!

如果未设置该变量,则以确定性方式使用42作为全局种子。这确保了默认情况下,scikit-learn测试套件尽可能具有确定性,以避免扰乱我们友好的第三方包维护者。同样,不应在拉取请求的CI配置中设置此变量,以确保我们友好的贡献者不是第一个在与他们自己的PR变化无关的测试中遇到种子敏感性回归的人。只有观看夜间构建结果的scikit-learn维护者预计会对此感到恼火。

当编写使用此fixture的新测试函数时,请使用以下命令来确保它对于本地机器上所有可接受的种子都确定性地通过:

SKLEARN_TESTS_GLOBAL_RANDOM_SEED="all" pytest -v -k test_your_test_name

9.3.2.2.5. SKLEARN_SKIP_NETWORK_TESTS#

当此环境变量设置为非零值时,需要网络访问的测试将被跳过。当未设置此环境变量时,则跳过网络测试。

9.3.2.2.6. SKLEARN_RUN_FLOAT32_TESTS#

当此环境变量设置为“1”时, global_dtype fixture也在float 32数据上运行。当未设置此环境变量时,测试仅在float 64数据上运行。

9.3.2.2.7. SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES#

当此环境变量设置为非零值时, Cython 衍生品, boundscheck 设置为 True .这对于查找segfault很有用。

9.3.2.2.8. SKLEARN_BUILD_ENABLE_DEBUG_SYMBOLS#

当此环境变量设置为非零值时,调试符号将包含在已编译的C扩展中。仅配置了BOX系统的调试符号。

9.3.2.2.9. SKLEARN_PAIRWISE_DIST_CHUNK_SIZE#

这设置了底层要使用的块的大小 PairwiseDistancesReductions 实现方式的默认值为 256 这已被证明在大多数机器上是足够的。

寻找最佳性能的用户可能想要使用2的乘方来调整此变量,以便为其硬件获得最佳并行性行为,尤其是在缓存大小方面。

9.3.2.2.10. SKLEARN_WARNINGS_AS_ERRORS#

此环境变量用于将警告转化为测试和文档构建中的错误。

一些CI(持续集成)构建集 SKLEARN_WARNINGS_AS_ERRORS=1 例如,以确保我们捕获来自依赖项的弃用警告并调整我们的代码。

要使用与这些CI构建中相同的“警告为错误”设置本地运行,您可以设置 SKLEARN_WARNINGS_AS_ERRORS=1 .

默认情况下,警告不会转换为错误。如果是这样, SKLEARN_WARNINGS_AS_ERRORS 未设置,或者 SKLEARN_WARNINGS_AS_ERRORS=0 .

此环境变量使用特定的警告过滤器来忽略一些警告,因为有时警告源自第三方库,而我们对此无能为力。您可以在 _get_warnings_filters_info_list 功能 sklearn/utils/_testing.py .

请注意,对于文档构建, SKLEARN_WARNING_AS_ERRORS=1 正在检查文档构建(尤其是运行的示例)是否不会产生任何警告。这不同于 -W sphinx-build 捕获rst文件中语法警告的参数。