Performance, determinism and scale

LightGBM · Expert · 12 min

On large data, configuration choices change training time by an order of magnitude.

Threads. n_jobs (num_threads) defaults to all cores (OpenMP). Set it to the number of physical cores; hyper-threads rarely help. When running several models in parallel (cross-validation, searches), give each a share of cores to avoid oversubscription.

Histogram layout. LightGBM chooses between force_row_wise=True (better with many rows, few features, or many threads) and force_col_wise=True (better with many features or few threads) by timing both at start-up; setting one explicitly saves that overhead and silences the related log message.

Data handling. Build an lgb.Dataset once and reuse it across trainings with the same binning; ds.save_binary("train.bin") caches it on disk. free_raw_data=True (default) drops the raw copy after construction. Using float32 input halves memory.

Determinism. Results can vary slightly between runs with multithreading. For reproducibility set seed (and specific seeds like bagging_seed), deterministic=True and force_row_wise=True or force_col_wise=True, accepting some slowdown.

Hardware and scale. device_type="cuda" uses the CUDA implementation (requires a CUDA-enabled build); "gpu" is the older OpenCL version. lightgbm.dask (DaskLGBMClassifier and friends) trains across a Dask cluster with data-parallel histogram aggregation; the CLI and Spark (SynapseML) support distributed training too.

Profile first: often the bottleneck is data loading or a too-large max_bin, not training itself.

Example

import time
import lightgbm as lgb
import numpy as np

rng = np.random.default_rng(6)
X = rng.normal(size=(100_000, 20)).astype(np.float32)   # float32 halves memory
y = X[:, 0] - 2 * X[:, 1] + rng.normal(0, 0.5, len(X))

ds = lgb.Dataset(X, label=y, params={"verbosity": -1}, free_raw_data=False).construct()
base = {"objective": "regression", "verbosity": -1, "num_threads": 1, "seed": 7,
        "deterministic": True, "force_row_wise": True}

preds = []
for _ in range(2):                                      # reuse the same binned Dataset
    t0 = time.perf_counter()
    booster = lgb.train(base, ds, num_boost_round=100)
    preds.append(booster.predict(X[:1000]))
    print(round(time.perf_counter() - t0, 1) < 30)      # True
print(np.array_equal(preds[0], preds[1]))               # True (deterministic)

Remember

  • Use physical-core thread counts and avoid nested parallelism
  • Set force_row_wise or force_col_wise, reuse Datasets, use float32
  • deterministic=True plus seeds for reproducible runs

Watch out for

  • Expecting bit-identical models across machines with different thread counts without deterministic settings.

Check yourself

  1. Training 8 LightGBM models in parallel on an 8-core machine, each with default threads, is slow. Why?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: lightgbm.readthedocs.io/en/stable/Parameters.html#learning-control-parameters