API Reference / utils /

torch_backend.py

Lazy access to the optional PyTorch backend.

A handful of TuiML algorithms — the attention-based tabular models and the deep forecasters — need a tensor library with autograd. PyTorch is therefore an optional dependency, installed with pip install 'tuiml[torch]'.

"Optional" is enforced at three levels, and all three matter:

  1. Import time. Importing tuiml, or any package containing a
torch-backed model, never imports torch. The classes are defined, exported and registered in the algorithm hub whether or not torch is present, so list_algorithms() and the docs show the same catalog on every install.
  1. Construction time. FTTransformerClassifier() succeeds without torch.
Constructing a model only records hyperparameters, and refusing to do that would break parameter grids, serialization round-trips and the generic algorithm contract, none of which need a tensor.
  1. Fit time. require_torch is called from fit, which is the
first operation that genuinely cannot proceed. It raises an ImportError naming the class and the exact install command.

The result is that a user without torch sees the model in the catalog, can inspect its parameters, and gets one clear, actionable message the moment they try to train it — rather than a bare ModuleNotFoundError from deep inside a layer definition.

python
>>> from tuiml.utils.torch_backend import has_torch, require_torch
>>> isinstance(has_torch(), bool)
True

Functions

Func

has_torch

Line 46
has_torch() -> bool

Report whether PyTorch is importable.

Intended for tests and for callers that want to branch on availability. Algorithm code should call require_torch instead, so the user gets an actionable message rather than a silent fallback.

Returns

available
bool
True if import torch succeeds.
python
>>> from tuiml.utils.torch_backend import has_torch
>>> isinstance(has_torch(), bool)
True
Func

require_torch

Line 71
require_torch(cls_name: str) -> Tuple[Any, Any]

Import torch, or raise an error naming the class and the install command.

Call this at the top of fit. It is deliberately not called from __init__: constructing a model records hyperparameters and must keep working on an install without torch, so that parameter grids, pickling and the generic algorithm contract behave identically everywhere.

Parameters

cls_name
str
Name of the calling class, used in the error message so the user knows which model pulled the dependency in.

Returns

torch
module
The imported torch module.
nn
module
torch.nn, returned alongside because every caller needs both.

Raises

ImportError
If torch is not installed, with the exact install command.
python
>>> from tuiml.utils.torch_backend import has_torch, require_torch
>>> if has_torch():
...     torch, nn = require_torch("MyModel")
...     print(hasattr(nn, "Linear"))
... else:
...     print(True)
True
Func

guard_duplicate_openmp

Line 128
guard_duplicate_openmp(torch_module: Any) -> bool

Force torch to one thread when a second OpenMP runtime is loaded.

On macOS, xgboost, LightGBM and CatBoost each load a bundled libomp.dylib, and algorithms imports all three. Torch is then imported afterwards -- which is exactly what the optional-dependency contract demands -- leaving two OpenMP runtimes in one process. The first torch operation to open a parallel region segfaults the interpreter, with no Python traceback.

Running torch single-threaded avoids parallel regions entirely. Note that the usual advice, KMP_DUPLICATE_LIB_OK=TRUE, does not help here: it still segfaults. Only clamping the thread count, or importing torch before the boosting libraries, avoids the crash.

The guard is a mitigation, not a cure -- it costs multi-threaded torch on macOS. The real fix is to stop importing three third-party boosting libraries unconditionally from a core namespace.

Does nothing when no conflicting library is loaded, so a torch-only process keeps all its threads.

Parameters

torch_module
module
The imported torch module.

Returns

applied
bool
Whether the thread count was actually clamped.
Func

resolve_device

Line 177
resolve_device(device: str, torch_module: Any) -> Any

Turn a device string into a torch.device, falling back to CPU.

"auto" prefers CUDA, then Apple Silicon's MPS, then CPU. An explicit request for an unavailable accelerator falls back to CPU rather than raising: the model still trains, only slower, and a hard failure here would make a saved parameter grid non-portable between machines.

Parameters

device
{"auto", "cpu", "cuda", "mps"}
Requested device.
torch_module
module
The torch module, passed in so this function never imports it.

Returns

device
torch.device
The device to place tensors on.