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_jobs threads 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=86 gives 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 nested n_jobs=-1 calls 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).

Where these results come from (1 source)
RecordedHardwareRecordsSourceEnvsConfig
2026-11-02 Intel Core Ultra laptop 160 CI run · PR #166 · 365c6ba6 by github-actions[bot] intel, sklearn-cf-mkl, sklearn-pypi configs/hptuning.py

sklearn-pypi

Python 3.14.8

Packages

  • scikit-learn 1.9.1 (pypi)
  • numpy 2.5.3 (pypi)
  • scipy 1.18.1 (pypi)
  • pandas 2.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

view pixi env JSON

sklearn-cf-mkl

Python 3.14.8

Packages

  • scikit-learn 1.9.1 (conda-forge)
  • numpy 2.5.3 (conda-forge)
  • scipy 1.18.1 (conda-forge)
  • pandas 2.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

view pixi env JSON

sklearnex

Python 3.14.8

Packages

  • scikit-learn 1.8.0 (conda-forge)
  • scikit_learn_intelex 2026.1.0 (pypi)
  • daal 2026.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

view pixi env JSON

Ridge / ames_housing

Shape: 1,460 x 79 (→ x 661 after pre-processing)
Params: (defaults)

Ridge / susy

Shape: 5,000,000 x 18
Params: (defaults)

Ridge / year_prediction_msd

Shape: 50,000 x 90 (→ x 990 after pre-processing)
Params: (defaults)

LogisticRegression / amazon_employee_access

Shape: 32,769 x 9 (→ x 99 after pre-processing)
Params: max_iter=1000, solver=lbfgs

LogisticRegression / susy

Shape: 500,000 x 18
Params: max_iter=1000, solver=lbfgs

RandomForestRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: max_bins=1460, n_estimators=200, n_jobs=-1

RandomForestClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: max_bins=20000, max_features=sqrt, n_estimators=100, n_jobs=-1

RandomForestClassifier / susy

Shape: 20,000 x 18
Params: n_estimators=100, n_jobs=-1

ExtraTreesRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: n_estimators=200, n_jobs=-1

HistGradientBoostingRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: early_stopping=False

HistGradientBoostingRegressor / year_prediction_msd

Shape: 30,000 x 90 (→ x 90 after pre-processing)
Params: early_stopping=False

HistGradientBoostingClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: early_stopping=False

Where these results come from (1 source)
RecordedHardwareRecordsSourceEnvsConfig
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-learn 1.9.1 (pypi)
  • numpy 2.5.3 (pypi)
  • scipy 1.18.1 (pypi)
  • pandas 2.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

view pixi env JSON

sklearn-cf-mkl

Python 3.14.8

Packages

  • scikit-learn 1.9.1 (conda-forge)
  • numpy 2.5.3 (conda-forge)
  • scipy 1.18.1 (conda-forge)
  • pandas 2.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

view pixi env JSON

sklearnex

Python 3.14.8

Packages

  • scikit-learn 1.8.0 (conda-forge)
  • scikit_learn_intelex 2026.1.0 (pypi)
  • daal 2026.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

view pixi env JSON

Ridge / ames_housing

Shape: 1,460 x 79 (→ x 661 after pre-processing)
Params: (defaults)

Ridge / susy

Shape: 5,000,000 x 18
Params: (defaults)

Ridge / year_prediction_msd

Shape: 50,000 x 90 (→ x 990 after pre-processing)
Params: (defaults)

LogisticRegression / amazon_employee_access

Shape: 32,769 x 9 (→ x 99 after pre-processing)
Params: max_iter=1000, solver=lbfgs

LogisticRegression / susy

Shape: 500,000 x 18
Params: max_iter=1000, solver=lbfgs

RandomForestRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: max_bins=1460, n_estimators=200, n_jobs=-1

RandomForestClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: max_features=sqrt, n_estimators=100, n_jobs=-1

RandomForestClassifier / susy

Shape: 20,000 x 18
Params: n_estimators=100, n_jobs=-1

ExtraTreesRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: n_estimators=200, n_jobs=-1

HistGradientBoostingRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: early_stopping=False

HistGradientBoostingRegressor / year_prediction_msd

Shape: 30,000 x 90 (→ x 90 after pre-processing)
Params: early_stopping=False

HistGradientBoostingClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: early_stopping=False

Where these results come from (1 source)
RecordedHardwareRecordsSourceEnvsConfig
2026-11-02 High-end Intel Xeon server 256 CI run · PR #167 · 7ee860ce by github-actions[bot] intel, sklearn-cf-mkl, sklearn-pypi configs/hptuning.py

sklearn-pypi

Python 3.14.8

Packages

  • scikit-learn 1.9.1 (pypi)
  • numpy 2.5.3 (pypi)
  • scipy 1.18.1 (pypi)
  • pandas 2.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

view pixi env JSON

sklearn-cf-mkl

Python 3.14.8

Packages

  • scikit-learn 1.9.1 (conda-forge)
  • numpy 2.5.3 (conda-forge)
  • scipy 1.18.1 (conda-forge)
  • pandas 2.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

view pixi env JSON

sklearnex

Python 3.14.8

Packages

  • scikit-learn 1.8.0 (conda-forge)
  • scikit_learn_intelex 2026.1.0 (pypi)
  • daal 2026.1.0 (pypi)

Threadpools

  • libopenblas 0.3.34 (pthreads, 128 threads)
  • libgomp (344 threads)

OpenMP

  • GNU libgomp (OpenMP 5.2)
  • GOMP_SPINCOUNT: 300000

Full environment

view pixi env JSON

Ridge / ames_housing

Shape: 1,460 x 79 (→ x 661 after pre-processing)
Params: (defaults)

Ridge / susy

Shape: 5,000,000 x 18
Params: (defaults)

Ridge / year_prediction_msd

Shape: 50,000 x 90 (→ x 990 after pre-processing)
Params: (defaults)

LogisticRegression / amazon_employee_access

Shape: 32,769 x 9 (→ x 99 after pre-processing)
Params: max_iter=1000, solver=lbfgs

LogisticRegression / susy

Shape: 500,000 x 18
Params: max_iter=1000, solver=lbfgs

RandomForestRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: n_estimators=200, n_jobs=-1

RandomForestClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: max_features=sqrt, n_estimators=100, n_jobs=-1

RandomForestClassifier / susy

Shape: 20,000 x 18
Params: n_estimators=100, n_jobs=-1

ExtraTreesRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: n_estimators=200, n_jobs=-1

HistGradientBoostingRegressor / ames_housing

Shape: 1,460 x 79 (→ x 79 after pre-processing)
Params: early_stopping=False

HistGradientBoostingRegressor / year_prediction_msd

Shape: 30,000 x 90 (→ x 90 after pre-processing)
Params: early_stopping=False

HistGradientBoostingClassifier / kddcup09_churn

Shape: 20,000 x 207 (→ x 207 after pre-processing)
Params: early_stopping=False