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后台 GridSearchCV
是 loky
,每个进程将只能使用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_config
和 sklearn.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文件中语法警告的参数。