Skip to content

GPU acceleration

ml4 can push tree-booster training onto the GPU and run torch-backed families (foundation models, neural nets, sentence embeddings) on CUDA. GPU is off by default — enabling it is one runtime knob, plus a CUDA torch build for the torch-backed paths.

In short

  • execution.device is "cpu" by default; "auto" uses a GPU where each booster library can take one and falls back to CPU otherwise; "cuda" refuses to start if one cannot.
  • ml4 probes each library (XGBoost, LightGBM, CatBoost) once per process, and the GPU decision applies to the whole roster: one library short keeps every booster on CPU.
  • Torch-backed families (foundation models, neural nets, embeddings) need a CUDA torch build.
  • CatBoost trains on CPU when monotone constraints are active.
  • GPU runs are not bit-reproducible; train on CPU when exactness matters.

Enabling GPU for boosters

ExecutionConfig.device selects the device for the tree boosters (LightGBM / XGBoost / CatBoost). The three values differ in what happens when the GPU turns out not to be usable:

execution.device GPU present & usable GPU absent or unusable
"cpu" (default) CPU — no GPU code path is exercised at all CPU
"auto" GPU, per library quietly falls back to CPU, logged once per library
"cuda" GPU, per library raises, naming the library and how to install a GPU build
from parleml_ml4 import Ml4Config

cfg = Ml4Config()
cfg.data.target_column = "y"

cfg.execution.device = "auto"      # opportunistic: GPU where available, CPU otherwise
cfg.execution.device = "cuda"      # insist: abort now rather than train on CPU

Why cuda refuses instead of degrading

"cuda" is not an alias for "auto". It serves one case: you provisioned a GPU box, you are paying for it, and a four-hour run that silently drops to CPU is a worse failure mode than a clean abort in the first second. "auto" keeps the opportunistic behaviour for a config that must run anywhere; "cuda" is the assertion, and it fails with the install hint for the library that could not take the device.

How the probe decides

Capability is answered per library, because "is CUDA available" is a different question for each of them and getting it wrong in either direction is expensive. Every probe is lazy and cached — it runs at most once per process, and device = "cpu" short-circuits before any of them, so a CPU run never imports a GPU stack or opens a CUDA context:

Library How availability is established
XGBoost xgboost.build_info() reports the compile-time USE_CUDA / USE_HIP flags — free, and touches no device
LightGBM GPU support is fixed at cmake time with no introspection API at all, so ml4 genuinely trains: one iteration on four rows, OpenCL first, then CUDA
CatBoost catboost.utils.get_gpu_device_count() — the driver query through the same runtime CatBoost will train with
all of them plus AutoGluon's own GPU count, deliberately reused rather than counted independently: it is the number AutoGluon asserts and clamps against

Each decision is logged once per process — the boosters' resolution and the fit-level outcome both — so the log says what the run actually did without repeating it per model.

The GPU decision is all-or-nothing across the roster

A roster of ["GBM:gbdt", "XGB:gbtree"] on a host whose LightGBM is CPU-only trains both on CPU. Narrow roster.models to the libraries your host can actually accelerate, rather than expecting a mixed roster to split across devices.

Details: why one library short keeps the whole fit on CPU

AutoGluon's fit-level num_gpus is a total for the run, applied to every model that does not override it, and ml4 does not compute a per-library allocation from the probe. So one library short takes the whole fit off the GPU: granting it would hand num_gpus=1 to a library that cannot use it, and a CPU-only LightGBM handed a GPU does not fall back — AutoGluon sets device="gpu" and the fit dies inside lgb.train.

This is a design choice, not a missing capability. A roster entry can override the fit-level total (ag_args_fit={"num_gpus": 0}), and ml4 uses exactly that for the CatBoost + monotone rule below. What the probe does not do is turn its per-library verdict into per-library overrides, because the device decision is taken in the config translator, which never sees the roster entries.

The resolved fit-level request is one of three values, and the third one matters:

num_gpus When Effect
0 device = "cpu" Pinned off, so a present GPU cannot be grabbed by a model whose own default would have taken one
1 every GPU-capable family in the roster resolved to a real backend The boosters train on GPU
"auto" device asked for a GPU but the boosters could not take it AutoGluon's per-model defaults are left alone, so the torch-backed families still take a GPU when torch sees one

That last row is deliberate: pinning 0 there would silently demote the foundation challenger, which is not what a user asking for a GPU meant.

Details: why ml4 probes instead of forcing num_gpus=1

AutoGluon's tabular boosters resolve num_gpus="auto" to zero GPUs by their own default, so a GPU run needs an explicit count. But AutoGluon asserts the requested GPU count against the system's, so an unconditional num_gpus=1 errors on a GPU-less host — and AutoGluon does not probe the LightGBM build either (its own source concedes the gap). The probe is the precondition that makes the request safe to emit.

Two GPU paths ml4 deliberately refuses

Both are cases where the device is genuinely available and using it would still be wrong. Under "auto" each logs and falls back; under "cuda" each raises.

  • A CUDA-only LightGBM build. LightGBM ships two mutually exclusive GPU backends — OpenCL (device_type="gpu") and its native CUDA learner. AutoGluon only ever asks for OpenCL, so ml4 cannot reach a CUDA-only build's learner; claiming the GPU would hand lgb.train a backend the build does not have. The probe still reports what it found so the refusal can say why.
  • A tuned CatBoost. ml4's hyperparameter search scores its trials on CPU-pinned fits, so the space it searches offers CPU-only values — MVS bootstrap, which GPU CatBoost rejects outright, and rsm, which CatBoost silently ignores on GPU (making the tuned value a lie about the fitted model). With CAT in the roster and hpo.provider="ml4_optuna", the fit therefore stays off the GPU.

Thread oversubscription

execution.n_jobs = -1 uses all CPUs for sklearn-style estimators. Booster libraries read it too but their internal thread pools may still oversubscribe on shared hosts — pin OMP_NUM_THREADS if that matters.

Foundation & torch-backed families

Foundation models (TabICL) and other torch-backed families run on whatever device torch sees, and ml4 deliberately emits no per-model GPU override for them — an explicit num_gpus=1 errors outright on a GPU-less host. They are also not gated by the booster probe: when the boosters fall back, the fit-level request stays "auto" rather than being pinned to 0, so a torch family still takes the GPU that torch can see. device = "cpu" is the one setting that pins everything off. The only per-model device override ml4 emits is CatBoost→CPU under monotone constraints (below). See Tabular foundation models for the catalogue and the foundation_ensemble preset.

Which torch you get depends on where it comes from. From PyPI, pip install parleml-ml4 brings the CPU build on Windows and the CUDA build on Linux (with several GB of NVIDIA libraries). The ParleML image and the repository's workspace pin the CPU build. Choose the build by installing torch from PyTorch's own index before ml4:

# CPU-only (Linux): keeps the NVIDIA libraries out
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install "parleml-ml4[mlops]"

# GPU torch for foundation / embedding backends (Windows, or a specific CUDA version)
pip install torch --index-url https://download.pytorch.org/whl/cu124

The CatBoost + monotone CPU rule

There is one hard incompatibility: CatBoost honours monotone constraints on CPU only. ml4 handles it with a single, non-fatal per-model override: when the recipe carries an active monotone direction (binary / regression) and the run is not regulated, fit_predictor puts ag_args_fit={"num_gpus": 0} on CatBoost's roster entry even under a global GPU device, while every other model keeps the GPU. The run continues, the constraint binds, and the run's notifications carry a CatBoost trains on CPU warning, once per CatBoost roster entry:

CAT:symmetric is pinned to CPU (num_gpus=0) because monotone constraints are active and
CatBoost honours them on CPU only, so it trains slower than the run's GPU allows.

LightGBM and XGBoost honour monotone constraints on GPU, so a monotone GBM/XGB roster is unaffected.

Regulated runs are not refused

A regulated run trains one transparent model (glm / scorecard / rulefit / ebm) and never CatBoost, so monotone constraints on a GPU device need no refusal there. A regulated EBM with monotone constraints trains normally on a GPU workstation. The CPU pin above is the whole mechanism.

What GPU does and does not cover

Workload GPU support
LightGBM / XGBoost / CatBoost training via execution.device, subject to the per-library probe (CatBoost CPU-only under monotone, and under hpo)
Foundation models (TabICL) via CUDA torch build
Neural / transformer families (NN_TORCH, FT-Transformer, …) via CUDA torch build
Sentence-transformer embeddings via CUDA torch build
Post-fit layers (calibration, thresholds, intervals) CPU (cheap, not a bottleneck)

Verifying the GPU path

Start with execution.device = "auto" and read the once-per-library decision in the run log. If you would rather find out in second one than in hour four, set execution.device = "cuda" and let the run refuse. For torch backends, confirm torch.cuda.is_available() in your serving environment — a CPU torch build reports False regardless of execution.device.

GPU training is not bit-reproducible

CUDA reduces floats non-deterministically, so a GPU run does not reproduce bit-for-bit even at a fixed execution.seed. Train on CPU when exactness matters — see Reproducibility & seeds.

See Troubleshooting for install-gating errors and Signal check for the GPU-friendly zero-shot probe.

Next: Reproducibility & seeds · Tabular foundation models