One budget parameter and three release candidates inside PerpetualBooster
Perpetual is a high-performance gradient boosting machine. It delivers optimal accuracy in a single run without complex tuning through a simple budget parameter. It features out-of-the-box support for causal ML, continual learning, native calibration, and robust drift monitoring, along with Rust core and zero-copy bindings for Python and R
At a glance
- What is it?
- Perpetual is a Rust gradient boosting library that trades hyperparameter search for a single budget dial, with Python, R and Rust bindings under Apache-2.0. Its own documentation is where the interesting detail sits: the benchmark table ignores the budget values the page recommends, zero-copy and export support sit behind optional extras, and the crate on the default branch is still a release candidate.
- Who is it for?
- Perpetual is worth a look if the search phase of your boosting pipeline is the part you actually dread, and if you can accept tuning by budget instead of by search space. Two things deserve checking before you commit to it.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The benchmark table skips every budget value the page recommends
The usage guidance says to start with a small budget, giving 0.5 as the example, and to raise it to 1.0 once you trust your features. The California Housing table then reports results for budgets of 0.76, 0.85 and 1.15. Not one of the three recommended values appears in the results, and the smallest benchmarked budget is half again the value the page tells you to try first, so the table cannot be reproduced from the instructions above it. The same table also outruns the headline claim it is filed under. The text says the approach delivers up to 100x speed-up at equal accuracy, while the rows report 72x, 113x and 405x on wall time, with CPU time reaching 326x, 613x and 1985x. Whether the cap is a conservative summary or a stale one, a reader who quotes the number will quote the smaller of the two. Two further limits sit in the same table. The error column carries three decimals, so any difference below 0.001 between the two boosters is simply invisible, and the whole spread across the three rows is 0.011 mse while the wall-time spread over the same rows is 405x. It is also a single regression dataset, even though the feature list claims classification, multi-class classification and ranking objectives as well.
Each LightGBM row is tuned to land on the same error
The comparison is framed as PerpetualBooster against Optuna plus LightGBM, and the argument is that a hundred tuning iterations are what the plain route costs. The table makes the matching explicit by setting LightGBM n_estimators to whatever reproduces the Perpetual error exactly: 50 trees against budget 0.76 for an mse of 0.201, 100 trees against 0.85 for 0.196, and 200 trees against 1.15 for 0.190. Those are the values you get after a search has already happened, which is precisely the work the project says you no longer need to do. The table therefore measures two different things on each row, the full search against nothing and a hand-tuned baseline against a single run. Optuna appears in the section title but nowhere in the columns, so the trial count, the search space and the stopping rule behind those n_estimators values are not part of the published comparison. The runtime dependency list hints at how the numbers are handled on the Rust side: `approx` for comparing floats and `serde_json` with the float round-trip feature, which is what storing and reloading benchmark results needs, and neither is something a pure estimator requires.
Three release candidates landed inside nine days and no stable tag
The tag history is `v3.0.0-rc.0` on 2026-03-24, `v3.0.0-rc.1` on the same day twenty minutes later, and `v3.0.0-rc.2` on 2026-04-02. Nothing beyond the rc series exists, and the version field in Cargo.toml reads `3.0.0-rc.2`, matching the newest tag exactly. So the code on the default branch is a candidate that has never been promoted to a fixed 3.0.0, and the newest commit is dated 2026-04-02, the same day as the last tag. A pin to `3.0.0-rc.2` therefore also pins to a moving target that any later rc would replace, and anyone installing from PyPI, conda-forge or crates.io needs to decide whether an rc is acceptable for the pipeline that has to stay reproducible. The package description in the manifest calls the library a self-generalizing gradient boosting machine that doesn't need hyperparameter optimization, three authors are listed against it, and the crate is on edition 2024, so the version line is the only place the rc status is visible to a user reading the docs.
The release profile aborts on panic for the whole workspace
The build configuration is unusually aggressive in one specific way. The release profile sets `lto = "fat"`, `codegen-units = 1`, `strip = true`, and `panic = "abort"`, while the bench profile keeps `debug = true` alongside `opt-level = 3` and its own `lto = "fat"`. `panic = "abort"` is the one to think about: a Rust panic terminates the process instead of unwinding, and because the profile applies to the crate and not to a single function, an internal panic anywhere in the estimator takes the host process down with it. For a command line tool that is a defensible tradeoff for the smaller binary, and for a Python extension imported inside a long-running notebook it is a different tradeoff. The page does not discuss the setting, and no code sample shows how a failure inside `fit` is meant to surface to a caller. The parallel loop comes from `rayon`, which reads the usual thread pool environment variables, and no documentation line tells you which of them the library honours or how many threads a fit will use. That gap matters more than it would elsewhere, because the speed-up figures are all measured against wall time on an unspecified machine.
The R package is not in the cargo workspace
Cargo.toml declares a workspace with exactly two members, `.` and `package-python`. The repository root also carries a `package-r/` directory, and the language table gives R a first-class row with `install.packages("perpetual")`, a pkgdown site and an R-universe page, while Python gets `pip install perpetual` or `conda install -c conda-forge perpetual` and Rust gets `cargo add perpetual`. So the Rust core and the Python extension build under one workspace, and the R bindings build by some route the manifest does not describe. Coverage configuration tells you the same story from another angle, because the ignore list for llvm-cov names `package-python` and `package-r` twice each, once with forward slashes and once with escaped backslashes, a pattern that only makes sense for paths that reach that directory from different tools. The three install routes also fetch different things: a wheel for the Python package, a compiled crate for Rust, and for R a package built outside the manifest entirely, with each language pointing at its own documentation site and its own source subdirectory.
Zero-copy and export are optional extras, not defaults
The feature list leads with a Rust core, zero-copy support for Polars and Arrow data, and export to XGBoost or ONNX. Every one of those paths sits behind an optional dependency. `polars` enables zero-copy training on Polars DataFrames, `pandas` enables training directly on DataFrames, `scikit-learn` provides a compatible wrapper interface, `xgboost` enables saving and loading in XGBoost format, and `onnxruntime` enables exporting and loading in ONNX. A plain install therefore gets the estimator and none of the interop. The whole Python surface shown on the page is four lines long:
from perpetual import PerpetualBooster
model = PerpetualBooster(objective="SquaredLoss", budget=0.5)
model.fit(X, y)One objective name and one parameter value, with no mention of how to set the number of threads or how results vary when the budget moves. The badge strip above it runs to thirteen links with repeats, three pointing at the same PyPI project page and three at the same coverage dashboard, so the header is not a reliable map of what exists. The root pyproject.toml is not the package manifest either; it carries ruff settings only, an 88 character line length, E501 ignored, and the E4, E7, E9, F and I rule sets selected. Behind the sample sits a list of ten capabilities, and not one of them appears in the code: native categorical handling, learnable missing value splits, monotonic and feature interaction constraints, treatment effect estimation, drift monitoring for data and concept drift without ground truth labels, continual learning claimed to cut computation from O(n²) to O(n), calibration for marginal and conditional coverage without retraining, and feature importance, partial dependence and SHAP values. Each is one sentence with no parameter name and no threshold, which leaves the tuning question answered for the search and open for everything else.
The examples directory is a benchmark suite the page does not describe
The page says only to check the examples folders, and the repository carries around two dozen Rust examples. Several of them are measurement tools rather than demonstrations: `count_trees.rs`, `time_phases.rs`, `micro_bench.rs`, `quick_bench.rs`, and four separate profiling entry points named `profile_breakdown`, `profile_detailed`, `profile_fit` and `profile_training`. Dataset walkthroughs cover iris, wine, abalone, breast cancer, cover types, titanic, goodreads and California housing, plus two Kaggle scripts registered in Cargo.toml as cargo examples with explicit paths. Two of them have no counterpart anywhere in the feature list, `conformal_prediction.rs` and `custom_loss_function.rs`, so the repository contains working code for capabilities the documentation does not claim. A criterion bench named `training_benchmark` sits alongside them, configured with `harness = false`. The two Kaggle entries go one step further and are registered in Cargo.toml as cargo examples with explicit paths, which is why they build with the rest even though nothing in the prose explains what they are. The root also carries a `benches/` directory and a `resources/` directory that no committed file describes, while `scripts/make_resources.py` shows up only inside a Makefile target, so the generated half of the repository is discoverable only by reading build files.
The Makefile leaves init undeclared and purge deletes every untracked file
The `.PHONY` line names help, venv, clean, purge, build, lint, py-test, rust-test, test and fmt. The file also defines `init`, `py-test-sh` and `py-test-ps`, none of which appear on that line, so a file or directory with one of those names in the repository root would silently shadow the target. `init` is the target that matters most, because it copies `README.md` to `package-python/README_PYTHON.md` and `LICENSE` to `package-python/LICENSE`, installs the package in editable mode with dev extras, adds pandas and seaborn, runs `scripts/make_resources.py` and then installs pre-commit hooks. Next to it, `purge` is two lines wide and is labelled as deleting everything not tracked by git:
purge: ## Delete everything that is NOT tracked by git
git clean -d -x -fThat is a virtual environment and a generated resources directory gone in one call, and the flags include ignored files. The rest of the file assumes a Unix shell, with `SHELL` set to `/bin/bash`, a Python interpreter located by falling back from `python3` to `python`, and a virtual environment directory of `.venv` whose activation script is chosen from `Scripts` on Windows and `bin` everywhere else. Even the help listing is generated rather than written, by grepping the file for target lines that end in a double hash.
Editorial conclusion
Perpetual is worth a look if the search phase of your boosting pipeline is the part you actually dread, and if you can accept tuning by budget instead of by search space. Two things deserve checking before you commit to it. First, the accuracy claim rests on a comparison whose LightGBM column was configured to hit the same error, and only one dataset is shown with a readable table, so measure on your own data rather than inheriting those numbers. Second, the crate is still an rc, and the R package sits outside the cargo workspace, which means the path that looks least likely to break is the Python one. On the operational side, confirm the optional extras you need are installed, since export and zero-copy paths are all gated on them.
Frequently asked questions
How does Perpetual avoid hyperparameter tuning?
It exposes a single budget parameter instead of a search space. Raising the budget increases predictive power, so the guidance is to start at something small like 0.5, move to 1.0 once the features are settled, and stop when further increases stop helping.
How do I install Perpetual for Python?
Either `pip install perpetual` or `conda install -c conda-forge perpetual`. Rust installs with `cargo add perpetual` and R with `install.packages("perpetual")`. Optional extras add pandas, polars, scikit-learn, xgboost and onnxruntime support.
What license is Perpetual released under?
Apache-2.0. The license field in Cargo.toml says so, and a LICENSE file sits at the repository root with the header badge linking to it.
Can Perpetual export models to XGBoost or ONNX?
Both formats are supported but neither is available by default. The xgboost package enables saving and loading in XGBoost format, and onnxruntime enables exporting and loading in ONNX, so a plain install has neither path until one of them is added.
Does Perpetual train on Polars data without copying?
Zero-copy training on Polars DataFrames is enabled by the polars optional dependency. Training directly on pandas DataFrames is a separate capability gated on pandas, and a scikit-learn compatible wrapper interface comes from the scikit-learn extra.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/perpetual-ml-perpetual)