A hyper-parameter search has two levels of parallelism: its outer
n_jobsParameter setting how many jobs scikit-learn runs in parallel with joblib. -1 means one per CPU. More in the scikit-learn glossary fits candidatesHyperparameter combinations tried by the search. Each one is fitted and scored on every cross-validation split. in parallel, and each fit can use
threadsUnits of execution that share the memory of their process. Each one can run on its own CPU core. itself. This dashboard sweeps the outer n_jobs to
check how the two levels interact.
Each search fits 8 fixed candidates, each on n_jobs random 80/20
CV splitsTrain/test splits of the data used by cross-validation: each candidate is fitted on the train part and scored on the test part. More in the scikit-learn glossary. So every outer worker has 8 fits to do at every
n_jobs, and each fit is the same on every machine (same data size and
number of trees). With many workers, that is more splits than anyone runs
in practice: this measures the throughput of the search, not its
statistical quality.
How to read
The outer n_jobsParameter setting how many jobs scikit-learn runs in parallel with joblib. -1 means one per CPU. More in the scikit-learn glossary uses joblibThe library scikit-learn uses to run tasks in parallel, in worker processes or threads. More in the scikit-learn glossary's default backend (lokyjoblib's default backend. It runs tasks in separate worker processes that are reused across calls.),
which fits candidates in separate worker processesSeparate Python processes running tasks in parallel. Unlike threads, they don't share memory.. Inner parallelism
depends on the estimator:
- HistGradientBoosting uses OpenMP threadsStandard for running the loops of compiled code on several threads. scikit-learn's Cython code (HistGradientBoosting, KMeans, ...) uses it., Ridge and
LogisticRegression use BLASBasic Linear Algebra Subprograms: the library that runs the matrix and vector products of NumPy and SciPy. OpenBLAS and MKL are two implementations of it. threads. joblib limits both to
n_logical_cpus // n_jobsthreads in each worker process. With SMTSimultaneous multithreading, called hyper-threading by Intel: one physical core runs two threads at once by sharing its execution units., that is 2 threads per physical coreOne hardware core of the CPU. With SMT, it shows up as two logical CPUs.: on the Xeon server (172 cores, 344 logical CPUsWhat the operating system sees as one CPU. With SMT, each physical core shows up as two logical CPUs.),n_jobs=86gives 4 threads per worker. The laptop has no SMT. - RandomForest and ExtraTrees fit their trees with joblib's threading
backendjoblib backend that runs tasks in threads of the same process. It only helps when the code releases the GIL, as tree building does. and
n_jobs=-1. joblib does not limit this nested call, so each worker process starts one thread per core, which can lead to oversubscriptionMore busy threads than CPU cores. They compete for the cores and the operating system keeps switching between them, which slows everything down.. joblib#1825 would limit nestedn_jobs=-1calls like it does for OpenMP and BLAS. - scikit-learn-intelex uses oneTBBoneAPI Threading Building Blocks: Intel's C++ library for parallel tasks. oneDAL uses it to run on several threads, with its own thread pool, separate from OpenMP and BLAS. More threads. It reads the OpenMP limit set by joblib and applies it to its own threads, so they don't oversubscribe the machine either.
There is one tab per machine and one plot per (estimator, dataset)
pair. The x-axis is the outer n_jobs (log scale), the y-axis
is the wall time of the search divided by its number of
.fit()Training a model on data. More in the scikit-learn glossary calls, with one line per environmentOne Pixi environment of this repository: a fixed set of package versions and builds.. Since each fit
is the same everywhere, the y-axis also compares machines. The marker
size is the CPU loadMean share of all the machine's CPUs that were busy during the run. 100% means all cores busy. (100% means all cores busy). The line shape tells
how the two levels interact:
- going down as 1/n_jobs: a single fit has little inner parallelism, and the search scales with outer workers;
- flat: a single fit was likely already using all cores, so outer workers bring nothing;
- rising: oversubscription, or bad interactions between joblib and the inner threading layer (they don't coordinate).
sklearn-pypi
Python 3.14.8
Packages
scikit-learn1.9.1 (pypi)numpy2.5.3 (pypi)scipy1.18.1 (pypi)pandas2.3.3 (pypi)
Threadpools
- libscipy_openblas 0.3.31.dev (pthreads, 16 threads)
- libscipy_openblas 0.3.34.106.0 (pthreads, 16 threads)
- libgomp (16 threads)
OpenMP
- GNU libgomp (OpenMP 4.5)
- GOMP_SPINCOUNT: 300000
Full environment
sklearn-cf-mkl
Python 3.14.8
Packages
scikit-learn1.9.1 (conda-forge)numpy2.5.3 (conda-forge)scipy1.18.1 (conda-forge)pandas2.3.3 (conda-forge)
Threadpools
- libmkl_rt 2026.1-Product (libomp, 16 threads)
- libomp (16 threads)
OpenMP
- LLVM OpenMP (spec 201611)
- KMP_BLOCKTIME: 0ms
Full environment
sklearnex
Python 3.14.8
Packages
scikit-learn1.8.0 (conda-forge)scikit_learn_intelex2026.1.0 (pypi)daal2026.1.0 (pypi)
Threadpools
- libopenblas 0.3.34 (pthreads, 16 threads)
- libgomp (16 threads)
OpenMP
- GNU libgomp (OpenMP 5.2)
- GOMP_SPINCOUNT: 1
Full environment
Ridge / ames_housing
Ridge / susy
Ridge / year_prediction_msd
LogisticRegression / amazon_employee_access
LogisticRegression / susy
RandomForestRegressor / ames_housing
RandomForestClassifier / kddcup09_churn
RandomForestClassifier / susy
ExtraTreesRegressor / ames_housing
HistGradientBoostingRegressor / ames_housing
HistGradientBoostingRegressor / year_prediction_msd
HistGradientBoostingClassifier / kddcup09_churn
Where these results come from (1 source)
| Recorded | Hardware | Records | Source | Envs | Config |
|---|---|---|---|---|---|
| 2026-11-03 | Intel Core Ultra desktop | 120 |
Manual run
· 46c4f9d2 by Arthur Lacote
|
intel, sklearn-cf-mkl, sklearn-pypi |
configs/hptuning.py |
sklearn-pypi
Python 3.14.8
Packages
scikit-learn1.9.1 (pypi)numpy2.5.3 (pypi)scipy1.18.1 (pypi)pandas2.3.3 (pypi)
Threadpools
- libscipy_openblas 0.3.31.dev (pthreads, 24 threads)
- libscipy_openblas 0.3.34.106.0 (pthreads, 24 threads)
- libgomp (24 threads)
OpenMP
- GNU libgomp (OpenMP 4.5)
- GOMP_SPINCOUNT: 300000
Full environment
sklearn-cf-mkl
Python 3.14.8
Packages
scikit-learn1.9.1 (conda-forge)numpy2.5.3 (conda-forge)scipy1.18.1 (conda-forge)pandas2.3.3 (conda-forge)
Threadpools
- libmkl_rt 2026.1-Product (libomp, 24 threads)
- libomp (24 threads)
OpenMP
- LLVM OpenMP (spec 201611)
- KMP_BLOCKTIME: 0ms
Full environment
sklearnex
Python 3.14.8
Packages
scikit-learn1.8.0 (conda-forge)scikit_learn_intelex2026.1.0 (pypi)daal2026.1.0 (pypi)
Threadpools
- libopenblas 0.3.34 (pthreads, 24 threads)
- libgomp (24 threads)
OpenMP
- GNU libgomp (OpenMP 5.2)
- GOMP_SPINCOUNT: 1
Full environment
Ridge / ames_housing
Ridge / susy
Ridge / year_prediction_msd
LogisticRegression / amazon_employee_access
LogisticRegression / susy
RandomForestRegressor / ames_housing
RandomForestClassifier / kddcup09_churn
RandomForestClassifier / susy
ExtraTreesRegressor / ames_housing
HistGradientBoostingRegressor / ames_housing
HistGradientBoostingRegressor / year_prediction_msd
HistGradientBoostingClassifier / kddcup09_churn
sklearn-pypi
Python 3.14.8
Packages
scikit-learn1.9.1 (pypi)numpy2.5.3 (pypi)scipy1.18.1 (pypi)pandas2.3.3 (pypi)
Threadpools
- libscipy_openblas 0.3.31.dev (pthreads, 64 threads)
- libscipy_openblas 0.3.34.106.0 (pthreads, 64 threads)
- libgomp (344 threads)
OpenMP
- GNU libgomp (OpenMP 4.5)
- GOMP_SPINCOUNT: 300000
Full environment
sklearn-cf-mkl
Python 3.14.8
Packages
scikit-learn1.9.1 (conda-forge)numpy2.5.3 (conda-forge)scipy1.18.1 (conda-forge)pandas2.3.3 (conda-forge)
Threadpools
- libmkl_rt 2026.1-Product (libomp, 172 threads)
- libomp (344 threads)
OpenMP
- LLVM OpenMP (spec 201611)
- KMP_BLOCKTIME: 200ms
Full environment
sklearnex
Python 3.14.8
Packages
scikit-learn1.8.0 (conda-forge)scikit_learn_intelex2026.1.0 (pypi)daal2026.1.0 (pypi)
Threadpools
- libopenblas 0.3.34 (pthreads, 128 threads)
- libgomp (344 threads)
OpenMP
- GNU libgomp (OpenMP 5.2)
- GOMP_SPINCOUNT: 300000