Skip to content

warmup

Compile the numba kernels up front, so the first fit or predict in a fresh process is not slow. See Deployment.

Pre-compile ChimeraBoost's numba kernels.

The hot loops are numba kernels, compiled on first use and cached on disk (cache=True). A fresh machine or container still pays the full JIT cost (~5-15 s) inside its first fit, and ~0.2-2 s inside the first predict of a fresh process (kernel compile or cache load).

Long-lived processes never notice. Fleets of short-lived workers -- benchmark harnesses, serverless inference, ray/spark tasks -- pay it on every task, where it can dwarf the real fit/predict work.

warmup() runs a few tiny synthetic fits and predictions that touch every kernel on the default fit and predict paths, so later real calls run at steady-state speed. Call it at import or startup time, outside anything you time or bill -- or run chimeraboost-warmup once after installing.

numba stamps each cache entry with its source file's modification time and size, so upgrading ChimeraBoost invalidates the cache: the first run after pip install -U pays the compile again.

warmup

warmup(verbose=False, background=False, shap=False)

Compile (or load from the on-disk cache) all default-path kernels.

Covers binary classification with linear leaves, a categorical feature and a validation set; multiclass; multi-quantile regression; regression with ordered boosting and non-uniform sample weights (the weighted ordered-TS kernel); and the gdiff cross-feature group-sum kernel. Together these touch every fit- and predict-path numba kernel except the SHAP kernels (shap=True).

Instead of calling this yourself, run chimeraboost-warmup once after installing, or set the environment variable CHIMERABOOST_WARMUP=1 to run it automatically when chimeraboost is imported (=background uses a daemon thread instead).

Parameters:

Name Type Description Default
verbose bool

Print per-stage timings.

False
background bool

Run in a daemon thread and return it immediately, so compilation overlaps the caller's own startup (data loading, connections). A fit issued before the thread finishes simply blocks on numba's per-kernel compile locks, so it is never slower than compiling inline.

False
shap bool

Also compile the SHAP kernels. Off by default: it adds ~3.7 s to a cold warmup and most callers never use shap_values.

False

Returns:

Type Description
float or Thread

Wall-clock seconds spent warming up, or the started daemon thread when background=True (.join() it to wait for readiness).

Notes

A few kernels never fire inside a fit this small: they need more rows than a warmup fit has, or a weighted fit, or a degenerate column. Those are called directly below, with the dtypes the real call passes.

Source code in chimeraboost/warmup.py
def warmup(verbose=False, background=False, shap=False):
    """Compile (or load from the on-disk cache) all default-path kernels.

    Covers binary classification with linear leaves, a categorical feature and
    a validation set; multiclass; multi-quantile regression; regression with
    ordered boosting and non-uniform sample weights (the weighted ordered-TS
    kernel); and the gdiff cross-feature group-sum kernel. Together these touch
    every fit- and predict-path numba kernel except the SHAP kernels
    (``shap=True``).

    Instead of calling this yourself, run ``chimeraboost-warmup`` once after
    installing, or set the environment variable ``CHIMERABOOST_WARMUP=1`` to
    run it automatically when ``chimeraboost`` is imported (``=background``
    uses a daemon thread instead).

    Parameters
    ----------
    verbose : bool, default False
        Print per-stage timings.
    background : bool, default False
        Run in a daemon thread and return it immediately, so compilation
        overlaps the caller's own startup (data loading, connections). A fit
        issued before the thread finishes simply blocks on numba's per-kernel
        compile locks, so it is never slower than compiling inline.
    shap : bool, default False
        Also compile the SHAP kernels. Off by default: it adds ~3.7 s to a
        cold warmup and most callers never use ``shap_values``.

    Returns
    -------
    float or threading.Thread
        Wall-clock seconds spent warming up, or the started daemon thread
        when ``background=True`` (``.join()`` it to wait for readiness).

    Notes
    -----
    A few kernels never fire inside a fit this small: they need more rows than
    a warmup fit has, or a weighted fit, or a degenerate column. Those are
    called directly below, with the dtypes the real call passes.
    """
    global _NOTICE_DONE

    if background:
        t = threading.Thread(target=warmup,
                             kwargs={"verbose": verbose, "shap": shap},
                             name="chimeraboost-warmup", daemon=True)
        t.start()
        return t

    _NOTICE_DONE = True   # this call *is* the compile -- do not narrate it

    t0 = time.perf_counter()
    rng = np.random.default_rng(0)

    # Row count that reaches the PARALLEL predict kernels. Derived from the
    # dispatch threshold, not hardcoded: a raised threshold once pulled every
    # warmup predict onto the serial twins, leaving the parallel forest walk to
    # compile on the user's first real batch -- the exact stall warmup exists
    # to prevent.
    from .binning import _SERIAL_PREDICT_N
    npar = _SERIAL_PREDICT_N + 1

    def _log(msg):
        if verbose:
            print(f"chimeraboost.warmup: {msg} ({time.perf_counter() - t0:.2f}s)")

    # Binary. At least LINEAR_LEAVES_MIN_SAMPLES rows so the linear-leaf
    # kernels compile (they are the binary default), one categorical column for
    # the ordered-TS kernel, an eval_set for the per-round validation predict.
    n = 1152
    X = np.column_stack([rng.standard_normal((n, 3)),
                         rng.integers(0, 3, size=n).astype(np.float64)])
    y = (X[:, 0] + X[:, 1] > 0).astype(np.int64)
    clf = ChimeraBoostClassifier(n_estimators=2, random_state=0)
    clf.fit(X[128:], y[128:], cat_features=[3], eval_set=(X[:128], y[:128]))
    clf.predict_proba(X[:npar])
    clf.predict_proba(X[:1])   # tiny-batch serial predict kernels
    _log("binary + linear leaves + categoricals")

    # Multiclass: vector-leaf tree build and vector forest predictors. The
    # npar-row call warms the parallel kernel, the 1-row call the serial twin.
    ym = np.digitize(X[:320, 0], [-0.5, 0.5])
    mc = ChimeraBoostClassifier(n_estimators=2, random_state=0)
    mc.fit(X[:320, :3], ym)
    mc.predict_proba(X[:npar, :3])
    mc.predict_proba(X[:1, :3])   # vector-leaf serial twin
    _log("multiclass")

    # Multi-quantile head: the leaf-quantile kernels (quickselect) and the
    # vector forest predictors. The kernels are generic in K, so a short grid
    # compiles the same code as a long one and costs less.
    yq = X[:320, 0] + 0.5 * rng.standard_normal(320)
    qr = ChimeraBoostQuantileRegressor(quantiles=[0.1, 0.5, 0.9],
                                       n_estimators=2, random_state=0,
                                       early_stopping=False)
    qr.fit(X[:320, :3], yq)
    qr.predict(X[:npar, :3])
    qr.predict(X[:1, :3])      # serial twin

    # The parallel leaf-quantile kernel only runs above tree._SMALL_N rows, and
    # the weighted twins only on a weighted or subsampled fit. Call them
    # directly.
    from .tree import (_leaf_quantiles_vec, _leaf_quantiles_vec_w,
                       _leaf_quantiles_vec_w_serial)
    _wl = np.arange(8, dtype=np.int64) % 2
    _wy = rng.standard_normal(8)
    _wF = np.zeros((8, 3))
    _wt = np.array([0.1, 0.5, 0.9])
    _ww = np.ones(8)
    _leaf_quantiles_vec(_wl, _wy, _wF, _wt, 2, 0.1)
    _leaf_quantiles_vec_w(_wl, _wy, _wF, _ww, _wt, 2, 0.1)
    _leaf_quantiles_vec_w_serial(_wl, _wy, _wF, _ww, _wt, 2, 0.1)
    _log("multi-quantile")

    # Regression with ordered boosting on (the LOO leaf-step kernel), a
    # categorical column and NON-uniform sample weights. The weighted
    # ordered-TS kernel (`_ordered_ts_weighted`) and the weighted binner borders
    # only compile on a weighted fit: uniform weights collapse to the unweighted
    # path, which the other stages deliberately keep.
    yr = X[:320, 0] + 0.1 * rng.standard_normal(320)
    sw = rng.uniform(0.5, 1.5, size=320)
    reg = ChimeraBoostRegressor(n_estimators=2, random_state=0,
                                ordered_boosting=True)
    reg.fit(X[:320], yr, cat_features=[3], sample_weight=sw)
    reg.predict(X[:npar])

    # 320 rows is below LINEAR_LEAVES_MIN_SAMPLES, so this model has constant
    # leaves, and this sub-threshold call is the only thing that compiles the
    # plain serial predict twin. Without it a warmed serving process still
    # stalls ~0.3 s on its first single-row request.
    reg.predict(X[:1])
    _log("regression + ordered boosting + weighted categoricals")

    # The gdiff (group-centered cross feature) kernel sits on the default path
    # only for fits of CROSS_MIN_SAMPLES rows or more -- too big for a warmup
    # fit. Call it directly.
    from .preprocessing import _grouped_kahan_sum
    _grouped_kahan_sum(np.zeros(4, dtype=np.int64), np.ones(4), 2)
    _log("gdiff group-sum kernel")

    # The parallel descend only runs above tree._ASSIGN_PAR_N rows (large eval
    # sets, replay refits) -- too big for a warmup fit. Call it directly, with
    # the dtypes `ObliviousTree.apply` passes: int64 leaves, a uint16 binned
    # feature row, an int64 threshold.
    from .binning import BIN_DTYPE
    from .tree import _descend_leaves, _predict_tree
    _descend_leaves(np.zeros(4, dtype=np.int64),
                    np.zeros(4, dtype=BIN_DTYPE), np.int64(1))

    # The fused walk+gather no longer runs during a warmup fit (eval-set scoring
    # is leaf-assign plus fused add now), but staged_predict and linear-leaf
    # eval still call it -- keep it warm.
    _predict_tree(np.zeros((2, 4), dtype=BIN_DTYPE),
                  np.zeros(2, dtype=np.int64), np.zeros(2, dtype=np.int64),
                  np.zeros(4))
    _log("parallel descend + tree-walk kernels")

    # The greedy-border sweep only fires when a column's quantile borders
    # collapse (one value holding more than an even bin's share of mass), and no
    # warmup column does. Call it directly, with the dtypes `_greedy_borders`
    # passes.
    from .binning import _greedy_border_fill
    _greedy_border_fill(np.arange(4, dtype=np.float64), np.ones(4),
                        np.zeros(4, dtype=np.bool_), 2.0, 3)
    _log("greedy-border sweep kernel")

    # The fused level kernel (`_build_split_descend_q`) has one signature for
    # both its small-n and large-n branches, so the small fits above already
    # compile the whole tree-build path -- no direct call needed.

    # SHAP is opt-in: `_shap_forest_linear` is an expensive parallel kernel
    # (~3.7 s cold) that most callers never reach. The same call also compiles
    # the column-major `_predict_forest_linear`, which the SHAP path uses.
    # `_shap_forest_vec` is its vector-leaf twin, reached by the multiclass and
    # multi-quantile heads; `_predict_forest_vec_rm` is already warmed by the
    # stages above, so the vector call adds only the one kernel.
    if shap:
        clf.shap_values(X[:8])
        qr.shap_values(X[:8, :3])
        _log("shap")

    return time.perf_counter() - t0

main

main(argv=None)

chimeraboost-warmup -- compile the kernels now, not on first fit.

Run once after pip install, and again after every upgrade, which invalidates numba's cache. Also reachable as python -m chimeraboost.warmup or python -m chimeraboost.

Source code in chimeraboost/warmup.py
def main(argv=None):
    """``chimeraboost-warmup`` -- compile the kernels now, not on first fit.

    Run once after ``pip install``, and again after every upgrade, which
    invalidates numba's cache. Also reachable as
    ``python -m chimeraboost.warmup`` or ``python -m chimeraboost``.
    """
    from . import __version__

    parser = argparse.ArgumentParser(
        prog="chimeraboost-warmup",
        description="Compile ChimeraBoost's numba kernels and cache them on "
                    "disk, so later runs start at full speed. Re-run after "
                    "upgrading ChimeraBoost -- an upgrade resets the cache.")
    parser.add_argument("--shap", action="store_true",
                        help="also compile the SHAP kernels (~3.7 s more)")
    parser.add_argument("--verbose", action="store_true",
                        help="print per-stage timings")
    parser.add_argument("--quiet", action="store_true",
                        help="print nothing on success")
    args = parser.parse_args(argv)

    elapsed = warmup(verbose=args.verbose, shap=args.shap)
    if not args.quiet:
        print(f"chimeraboost {__version__}: kernels ready in {elapsed:.1f}s "
              f"(cache: {_cache_dir()})")

    return 0