With n_jobs=-1, a hyper-parameter search starts one worker per
logical CPUWhat the operating system sees as one CPU. With SMT, each physical core shows up as two logical CPUs. it is allowed to run on. This dashboard checks how such a
search scales with the CPUs it may use.
Each point restricts the process to some physical coresOne hardware core of the CPU. With SMT, it shows up as two logical CPUs., with their
SMT siblingsThe other logical CPU of the same physical core., through its CPU affinityThe set of logical CPUs a process may run on. joblib only counts those for n_jobs=-1. (like
taskset). The searches are those of the
hyper-parameter search dashboard: 8 candidates
on as many 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 as physical cores.
On the benchmarked cases, compared with one physical core:
- RandomForest and ExtraTrees scale the furthest: up to ~16x on the 24 cores of the desktop and ~55x to ~60x on the 172 cores of the Xeon, or ~70x to ~110x with scikit-learn-intelex.
- The exception is RandomForest on susy on the Xeon. It goes from ~54x at 86 cores to ~30x at 172 cores, except with scikit-learn-intelex.
- HistGradientBoosting reaches ~70x to ~75x on the Xeon, but peaks at ~39x at 86 cores on the small ames_housing dataset.
- The linear models depend more on the dataset on the Xeon: LogisticRegression reaches ~25x to ~35x, and Ridge goes from ~8x on susy to ~55x on year_prediction_msd. Ridge on ames_housing peaks at ~21x at 43 cores.
- On the laptop, the 16 cores give ~2x to ~7x.
- On the Xeon, the CPU affinity fills the NUMA nodesOn big servers, a group of CPU cores and the memory attached to them. A core reaches the memory of its own group faster than that of another group. in order, so the plots also reflect that placement from 22 to 86 cores. In a separate run on PyPIThe Python Package Index, where pip installs packages from. Its scikit-learn wheels bundle their own OpenMP runtime, and NumPy and SciPy bundle OpenBLAS. scikit-learn with the same 43 cores spread evenly over the NUMA nodes, LogisticRegression on susy and Ridge on year_prediction_msd are ~2.3x to ~2.4x faster and HistGradientBoosting on kddcup09_churn and year_prediction_msd is ~1.6x to ~1.8x faster, probably because memory bandwidth limits them (not measured). RandomForest on the small ames_housing dataset barely changes. NUMA nodes 0 and 1 share a bottleneck: 43 cores on nodes 0 and 2 are ~1.6x to ~1.8x faster than on nodes 0 and 1, and crossing sockets costs nothing.
How to read
There is one tab per machine and one plot per (estimator, dataset) pair.
The x-axis is the number of physical cores allowed (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 (log scale), with one color per environmentOne Pixi environment of this repository: a fixed set of package versions and builds..
The marker size is the mean load of the allowed logical CPUs (100% means all of
them busy).
Inner parallelism:
- joblib limits OpenMPStandard for running the loops of compiled code on several threads. scikit-learn's Cython code (HistGradientBoosting, KMeans, ...) uses it. and 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 to 1 in each worker. scikit-learn-intelex applies the same limit to its 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.
- joblib does not limit the nested
n_jobs=-1of RandomForest and ExtraTrees (see the hyper-parameter search dashboard).
On hybrid machines, the affinity takes the lowest CPU ids first, whatever the core type.
Where these results come from (1 source)
| Recorded | Hardware | Records | Source | Envs | Config |
|---|---|---|---|---|---|
| 2026-11-04 | Intel Core Ultra laptop | 160 |
Manual run
· 0bf0a746 by Arthur Lacote
|
intel, sklearn-cf-mkl, sklearn-pypi |
configs/hptuning_taskset.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, 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-04 | Intel Core Ultra desktop | 120 |
Manual run
· 5a1fca23 by Arthur Lacote
|
intel, sklearn-cf-mkl, sklearn-pypi |
configs/hptuning_taskset.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
Where these results come from (2 sources)
| Recorded | Hardware | Records | Source | Envs | Config |
|---|---|---|---|---|---|
| 2026-11-04 to 2026-11-05 | High-end Intel Xeon server | 96 |
Manual run
· 7a4c16e5 by Arthur Lacote
|
intel, sklearn-cf-mkl |
configs/hptuning_taskset.py |
| 2026-11-04 | High-end Intel Xeon server | 160 |
Manual run
· 13c3f439 by Arthur Lacote
|
sklearn-cf-mkl, sklearn-pypi |
configs/hptuning_taskset.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, 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