Repository navigation
v0.4.0
hessboost v0.4.0
hessboost v0.4.0 introduces bin-once shared histogram storage (BinnedMatrix, zero-copy borrowed dense arrays, and row-view slicing), native model averaging, out-of-fold cross-validation with leakage-safe per-fold stopping, exact quantile and median leaf estimation, faster complete-tree row prediction and sparse multi-output histogram sweeps, and PEP 803 abi3t wheels for CPython 3.15+. (v0.3.1 was bumped in repository history without being tagged or published and is folded into v0.4.0.)
Full Changelog: v0.3.0...v0.4.0
Breaking Changes
- Rust Cross-Validation API (#165):
CrossValidation::early_stopping_rounds(n)is replaced byCrossValidation::early_stopping(CvStopping). UseCvStopping::fold_mean(n)for the previous fold-mean stopping behavior.CrossValidation::run()now returnsCvReportinstead ofVec<CvResult>; read.resultsfor the fold-mean history. - Python 3.12 Minimum & Typing (#162): CPython 3.11 is no longer supported (installers on Python 3.11 remain on
0.3.0), and CPython 3.15+ requiresnumpy >= 2.5. Public Python type aliases are now defined with PEP 695typestatements (TypeAliasTypeinstances at runtime). - Row Selection Returns Views (#169): Rust
DMatrix::select_rowsnow returns a row-mapped view sharing backing storage instead of copying a new matrix. Training with defaultViewCuts::Ownremains bit-for-bit identical to training on a copied matrix;ViewCuts::Sourceopts into reusing the source matrix's bins and split thresholds.
Highlights Since v0.3.0
Data & Shared Binning
- Bin Once and Share (#169): Feature values are binned once and reused across repeated trainings of a matrix and its metadata copies, CV folds, row selections, and concurrent trainings, producing bit-identical model and margin hashes (verified across 7 configurations at 1, 4, and 16 threads).
BinnedMatrix(#169): Newdata::BinnedMatrix(Pythonhessboost.BinnedMatrix) retains the histogram index and metadata while dropping raw feature values, training bit-for-bit identically toDMatrix. Accepted only bytrain; refuses options that require raw feature values (differingmax_bin, continuation, CUDA,approx/exacttree methods, non-gbtreeboosters, linear leaves, refresh, SGLB, and reuse penalties).- Row Views & Source Cuts (#169):
DMatrix::select_rows(PythonDMatrix.slice(rows, cuts="own" | "source")) creates a storage-sharing view.ViewCuts::Own(default) computes quantile cuts on the selected rows just as a copy would;ViewCuts::Sourcegathers the source matrix's bins to share a single binning pass and split thresholds. - Zero-Copy Dense Borrowing (#169, #170): Python
DMatrix(x, copy=False)and RustDMatrix::from_dense_sharedborrow read-only, C-contiguous, alignedfloat32arrays in place, keeping memory-mapped.npyfiles file-backed. Arrays whose base chain reaches an ownerless memory view (e.g.,ctypes.memoryview_atorPyMemoryView_FromMemory) are refused so borrowed buffers cannot be freed while in use (#170). - Memory & Throughput Gains (#169): On a 4M × 173 panel, single-thread 1-round training drops from 15.46s (first) / 15.82s (repeat) to 10.74s (first) / 0.48s (repeat); multi-head concurrent training peak memory drops from 23.54 GiB to 5.10 GiB (8 heads × 4 threads) and from 13.10 GiB to 4.60 GiB (4 heads); full-row refit drops from 5.55 GiB / 4.24s to 4.49 GiB / 1.62s with
ViewCuts::Source.
Model Averaging & Cross-Validation
- Native Model Averaging (#165):
BoostedModel::average(PythonBooster.average) averages ensembles directly in margin space by dividing leaf values by member count, concatenating trees, and averaging intercepts. Refusesgblinear, model shrinkage, DART with non-unit weights, Boulevard, EBM, andbest_iteration. - Per-Fold Stopping & Out-of-Fold Margins (#165, #167):
CvStopping::per_fold(patience, PurgedTail)(Pythoncross_validate(..., per_fold_stopping=PurgedTail)) performs leakage-safe early stopping per fold on a purged chronological tail (computing the cutoff offset inf64, rounding up to a whole tick, and comparing integer offsets exactly) and then retrains each fold on all of its rows.CrossValidation::run()returnsCvReportcontaining.results(fold-mean history),.folds(per-fold test rows, round counts, scores, margins, and tail split), andCvReport::out_of_fold()to assemble per-row OOF margins.CrossValidation::history_only()(used byhessboost.cvand Pythoncv) discards fold models. - Intercept & Residual-Scale Speedups (#165): Faster quantile/absolute-error residual-scale pass and a stable radix sort for the intercept, bit-for-bit unchanged.
Exact Quantile & Median Leaves
LeafEstimation::Exact(#164): Opt-inleaf_estimation(LeafEstimation::Exactor flat keyleaf_estimation_method="exact"; defaultNewtonis bit-identical to previous releases). Forreg:quantileerrorandreg:absoluteerror, tree structure is grown from surrogate gradients, and each leaf output is set toeta / num_parallel_treetimes the alpha-quantile (median forreg:absoluteerror) of the leaf's residuals across all training rows routed to that leaf, whether sampled or not.- Validation & Guards (#164): Refused for other objectives and for DART,
gblinear, Boulevard, EBM, monotone constraints,linear_tree,path_smooth, quantized leaf renewal, SGLB/posterior sampling,process_type="update", and online/budget modes. - Convergence (#164): On the bands benchmark at
eta = 0.06,q90achieves lower pinball loss (1.44137 vs. 1.44271) in 3.0× fewer rounds,q10matches pinball loss in 3.5× fewer rounds, and training both bands takes roughly half the wall time (22.4s vs. 43.3s).
Prediction, Multi-Output Trees & Metadata
- Complete-Tree Row Walks (#166): Single-row prediction methods (
predict_row,predict_row_into, and margin forms) are 2–3× faster, allocation-free on warm calls, and bit-for-bit identical topredict: 48.4 → 18.7 µs warm and 109.1 → 33.0 µs cold on a 4,421-tree ensemble; 132.7 → 45.6 µs warm and 270.2 → 72.5 µs cold on an 11,349-tree ensemble. - Typed Tree Metadata (#166): Added
RegTree::depth,RegTree::has_categorical_splits,BoostedModel::tree_outputs(), and PythonTreeInfo.depthfor external tree walkers. - Sparse Vector-Leaf Histograms (#163): Multi-output tree nodes (
multi_strategy="multi_output_tree") now build histograms by feature sweep on sparse indexes as well as dense, with per-node thread shares and bit-identical output across thread counts. On a 1M × 64 dataset (40% missing, 2 targets, depth 8), training time drops from 107 ms to 56 ms at 16 threads and from 105 ms to 41 ms at 64 threads.
Python Packaging
- PEP 803
abi3tWheels (#162): Ships a singleabi3twheel per platform for CPython 3.15+ covering both GIL-enabled and free-threaded builds as well as future 3.15+ releases (free-threaded 3.14 continues to use its dedicatedcp314twheel).
Migration Guide (v0.3.0 -> v0.4.0)
1) Rust Cross-Validation API (CvStopping and CvReport)
Replace early_stopping_rounds(patience) with early_stopping(CvStopping::fold_mean(patience)), and read .results from the returned CvReport when you need Vec<CvResult> (or call .history_only() before .run() if you do not need retained fold models):
use hessboost::training::{CrossValidation, CvStopping, Fold, PurgedTail};
use std::num::NonZeroUsize;
let patience = NonZeroUsize::new(20).unwrap();
// Before (v0.3.0)
let results: Vec<CvResult> = CrossValidation::new(
¶ms,
&data,
5,
Fold::k_fold(data.n_rows(), 3, 0)?,
)
.early_stopping_rounds(patience)
.run()?;
// After (v0.4.0) — fold-mean stopping (the previous behavior)
let report = CrossValidation::new(
¶ms,
&data,
5,
Fold::k_fold(data.n_rows(), 3, 0)?,
)
.early_stopping(CvStopping::fold_mean(patience))
.run()?;
let results = report.results;
// v0.4.0 — leakage-safe per-fold stopping and out-of-fold margins
let tail = PurgedTail::new(&decision_at, &label_end, 0.2)?;
let report = CrossValidation::new(
¶ms,
&data,
5,
Fold::forward_chaining(data.n_rows(), 3, 3)?,
)
.early_stopping(CvStopping::per_fold(patience, tail))
.run()?;
let oof = report.out_of_fold()?;(In Python, early_stopping_rounds is unchanged for fold-mean mode; pass per_fold_stopping=PurgedTail to cross_validate for per-fold stopping.)
2) Python Version Floor (CPython 3.12+)
Upgrade environments running hessboost >= 0.4.0 to CPython 3.12 or newer (and numpy >= 2.5 on CPython 3.15+). Environments remaining on CPython 3.11 stay on 0.3.0:
# pyproject.toml
requires-python = ">=3.12"
dependencies = [
"hessboost>=0.4.0",
"numpy>=2.5; python_version >= '3.15'",
]3) DMatrix::select_rows Returns a View
DMatrix::select_rows now returns a row-mapped view sharing the parent matrix's storage instead of allocating a copied matrix. Default ViewCuts::Own computes cuts on the selected rows and is bit-for-bit identical to training on a copied matrix; no changes are needed unless you want to share the source matrix's bins and split thresholds via ViewCuts::Source (or dmat.slice(rows, cuts="source") in Python).
Pull Requests by Category
Features
perf(data)!: bin once and share(#169)feat!: model averaging, out-of-fold CV, per-fold stopping(#165)feat: exact quantile leaves(#164)perf(predict): complete-tree row walks(#166)perf(multi): vector-leaf histograms on sparse indexes(#163)feat(python)!: abi3t wheels, CPython 3.12 floor(#162)
Bug Fixes
fix(python): refuse copy=False over ownerless memory(#170)