Skip to content

Backends

RenewableHuberRegressor(backend=..., device=..., dtype=...) selects where the update runs. Every backend shares one numerical contract; the support matrix lists exactly what each one supports.

backend Runs on Needs
"auto" (default) NumPy on the CPU, native_cpu when measured faster, CuPy for device="cuda" nothing extra
"numpy" CPU nothing extra
"native_cpu" CPU, Rust/Rayon renewable-huber-native-cpu
"cupy" CUDA the gpu-cupy extra
"native_cuda" CUDA, Rust/CUDA whole-batch engine renewable-huber-native-cuda
"torch" CPU or CUDA the gpu-torch extra
"tensorflow" CPU or CUDA, eager execution only the gpu-tensorflow extra

The examples on this page assume from renewable_huber import RenewableHuberRegressor and the placeholder batches X_batch, y_batch, X_train, y_train and X_test from the Quick start.

Automatic selection

backend="auto" follows predictable device rules: it chooses CuPy only when device="cuda" is set explicitly and otherwise stays on the CPU. On the CPU, auto still defaults to NumPy; it switches to native_cpu only when the batch is large enough and a runtime measurement on this machine shows that the Rust native CPU engine is clearly faster. The decision rests entirely on live measurements of the current host and the current execution environment: it never reads a CPU model string and never writes a cache file. Measurements live only in memory and are valid only for the execution environment in which they were taken: they are discarded as soon as the CPU affinity mask (not just the core count), the *_NUM_THREADS settings, or the effective BLAS/OpenMP thread-pool sizes observable through optional threadpoolctl change, and a child process after fork also clears them and measures again. Within one execution environment the same shape always gets the same answer, regardless of what other estimators asked before. Any failure along the way (a missing extension, an engine that cannot be constructed, insufficient measurements, or any ordinary exception raised by a native engine that auto selected) quietly falls back to NumPy.

auto does not guess a backend from PyTorch or TensorFlow tensors passed in; set backend="torch" or backend="tensorflow" explicitly when you need those frameworks. Explicitly setting backend="numpy" or backend="native_cpu" never triggers the measurement above.

For details and cost bounds, see the CPU auto-dispatch RFC.

Rust CPU engine

Install renewable-huber-native-cpu as described in Installation, then:

from renewable_huber import RenewableHuberRegressor

native_model = RenewableHuberRegressor(
    backend="native_cpu",
    device="cpu",
    dtype="float64",
    n_jobs=-1,  # or a positive integer, e.g. n_jobs=8
)
native_model.fit(X_train, y_train)

n_jobs=None uses the native extension's default Rayon pool, n_jobs=-1 uses every logical CPU, and a positive integer builds a fixed-size, dedicated thread pool for this estimator. After fitting, native_model.n_jobs_ reports the actual number of workers. If an outer layer already parallelises several models with joblib/GridSearchCV(n_jobs=...), set the inner estimator to n_jobs=1 so that nested thread pools do not compete for the CPU.

It supports penalty="none" and penalty="l1", with C-contiguous NumPy float32/float64 input. backend="auto" on the CPU may choose this engine, but only when the batch is large enough and the local measurement supports it; to always use it, set backend="native_cpu" explicitly. For build, correctness and benchmark details see Native-core P1; for the dispatch rules see the CPU auto-dispatch RFC.

CuPy

To run on a GPU, install the CUDA 12 CuPy extra and keep batches and state on CUDA:

import cupy as cp

gpu_model = RenewableHuberRegressor(backend="cupy", device="cuda", dtype="float32")
gpu_model.partial_fit(cp.asarray(X_batch), cp.asarray(y_batch))
gpu_prediction = gpu_model.predict(cp.asarray(X_test))  # cupy.ndarray, not copied back to the CPU

Native CUDA engine

Install renewable-huber-native-cuda as described in Installation.

With the separate native CUDA extension installed, you can explicitly select the Rust/CUDA whole-batch engine, which consumes CuPy, PyTorch CUDA or TensorFlow eager GPU batches directly through DLPack with no host staging at any point:

native_gpu = RenewableHuberRegressor(
    backend="native_cuda",
    device="cuda",
    dtype="float32",
    penalty="l1",  # "none" or "l1"; both are solved whole-batch on the GPU
    cuda_graphs=True,  # opt-in; falls back safely when capture is unavailable
    cuda_fast_math=False,  # opt-in TF32; strict float32 by default
)
native_gpu.partial_fit(cp.asarray(X_batch), cp.asarray(y_batch))
gpu_prediction = native_gpu.predict(cp.asarray(X_test))  # reads device X in place, returns NumPy

The 0.7.1 native CUDA engine (C ABI 2/Python API 4) supports penalty="none" and penalty="l1"; L1 uses the same LAMM proximal-gradient update as NumPy and Rust CPU, and checkpoints can be resumed across all three engines.

The native CUDA Python API also offers opt-in cuda_graphs=True, and a float32-only cuda_fast_math=True (TF32) high-speed mode. Both are off by default; when a CUDA Graph cannot be captured safely, execution falls back to the regular path. After fitting, inspect native_gpu.cuda_features_ for the actual enabled state and the capture, replay and fallback counts.

All device batch inputs must reside on the same GPU, have exactly the same dtype and be C-contiguous; anything else raises an error immediately instead of being copied implicitly. CuPy/PyTorch use DLPack consumer-stream negotiation; the TensorFlow legacy DLPack adapter first performs the necessary producer synchronization, but still does not copy storage. From API 4, predict also accepts CUDA DLPack tensors that meet the same conditions: the engine reads X in place on its own stream (appending the intercept column on the device when needed), only the n_rows predictions come back to the host, and the return type remains numpy.ndarray.

The CUDA performance paths, and when native CUDA is faster than CuPy, are described in CUDA performance paths.

PyTorch and TensorFlow

PyTorch can use native torch.Tensor objects on the CPU or on an explicitly specified CUDA device:

import torch

torch_model = RenewableHuberRegressor(backend="torch", device="cuda", dtype="float32")
torch_model.partial_fit(
    torch.as_tensor(X_batch, device="cuda"), torch.as_tensor(y_batch, device="cuda")
)
torch_prediction = torch_model.predict(
    torch.as_tensor(X_test, device="cuda")
)  # detached torch.Tensor

The TensorFlow backend uses eager execution and likewise supports native tf.Tensor objects:

import tensorflow as tf

tensorflow_model = RenewableHuberRegressor(backend="tensorflow", device="cuda", dtype="float32")
tensorflow_model.partial_fit(
    tf.convert_to_tensor(X_batch),
    tf.convert_to_tensor(y_batch),
)
tensorflow_prediction = tensorflow_model.predict(tf.convert_to_tensor(X_test))  # tf.Tensor