← back to the showcase

TAKT showcase: LightGBM 4.7.0, drop-in with bit-identical output, model analyses up to 18× faster

The numbers below are measured. The free package is on PyPI (pip install lightgbm-takt); the full version is a commercial licence (see "Availability"). This directory holds the measurement scripts and every reading.

Not affiliated with LightGBM. TAKT is not affiliated with or endorsed by the LightGBM project or Microsoft. The name LightGBM is used only to identify the software we measured and build on.

LightGBM (MIT) is one of the most widely used gradient-boosting libraries; its Python package (lightgbm) wraps the C library lib_lightgbm.so.

TAKT builds LightGBM 4.7.0 so that analyses that call predict many times — permutation importance, partial dependence and ICE curves, MDA feature importance across cross-validation folds, what-if scenario sweeps — and batch prediction run several times faster, with every prediction identical to the official build, bit for bit, on the same machine. The Python package and the public API are unchanged (import lightgbm); only the library is replaced. No code changes: sklearn.inspection.permutation_importance, partial_dependence, PartialDependenceDisplay and shap.PermutationExplainer get the speed-up as they are.

Headline

Factors over the official LightGBM 4.7.0 wheel, by user-mode cycles:u with 95% bootstrap CIs, whole process (interpreter start, imports, data loading, the analysis), one core unless noted. The output was identical in every single run.

Workload Free, Zen 3 Free, Zen 4 Full, Zen 3 Full, Zen 4
Permutation importance, quant features (Alpha158), classifier 3.30× (3.19–3.33) 2.74× (2.70–2.86) 16.75× (16.14–16.96) 18.35× (17.34–18.71)
Partial dependence + ICE, the same model, 20 features 3.67× (3.58–3.76) 3.12× (3.08–3.26) 16.77× (16.57–16.93) 18.31× (18.06–18.71)
Permutation importance, OpenML kick (categorical, missing values) 2.63× (2.62–2.67) 2.82× (2.68–2.87) 12.97× (12.85–13.08) 14.64× (14.09–15.23)
What-if scenarios, kick model, 30 scenarios 2.50× (2.48–2.52) 2.60× (2.53–2.76) — —
MDA importance across 5 cross-validation folds (fitting included) 2.02× (2.02–2.04) 2.05× (2.01–2.08) 6.10× (6.05–6.14) 5.92× (5.79–6.01)
Partial dependence + ICE, California housing regressor 1.75× (1.70–1.78) 1.58× (1.50–1.64) 5.43× (5.37–5.48) 4.77× (4.70–4.87)
shap.PermutationExplainer, California housing 1.02× (1.01–1.03) 1.03× (1.00–1.05) 3.23× (3.16–3.27) 3.23× (3.18–3.29)
shap.PermutationExplainer, quant features — — 3.35× (3.29–3.39) 4.00× (3.94–4.09)
One batch predict_proba, quant features, whole process 1.01× (1.00–1.02) 1.03× (0.99–1.06) 2.12× (2.09–2.14) 2.45× (2.40–2.49)
One batch predict_proba, kick, whole process 1.00× (0.99–1.00) 1.02× (0.99–1.05) 1.59× (1.58–1.59) 1.73× (1.72–1.81)
C API batch prediction, one thread 1.03× (1.01–1.06) 1.03× (1.02–1.04) 4.12× (4.10–4.22) 5.38× (5.25–5.48)
C API batch prediction, all pinned cores 1.03× (1.00–1.04) 1.03× (1.00–1.04) 4.03× (3.99–4.06) 5.32× (5.13–5.42)

"Free" is measured on models of up to 300 trees (the free package's limit), "full" on the larger models of the same kind (500 and 1,000 trees; the MDA workload fits 300-tree models in both). In the free columns, the rows not in bold are outside the free package's scope and run LightGBM's own code, at the official speed within a few percent. For the multi-threaded C API row, the full version's wall-time factors are lower than the cycle factors (2.29× on Zen 3 with 4 threads, 3.48× on Zen 4 with 2 threads): the threads' total CPU time falls more than the elapsed time. Every wall-time factor is in the tables below.

Free package vs full version

Free (pip install lightgbm-takt) Full version (commercial licence)
Repeated-call analyses (permutation importance, PDP/ICE, MDA, what-if) accelerated, models up to 300 trees, one thread accelerated, any model size, several threads
Batch prediction (predict, predict_proba, leaf indices) LightGBM's own code, normal speed accelerated: through the C API 4.0–5.4× in CPU time (2.3–4.4× elapsed), leaf indices 2.8–4.1× (1.5–2.4×); a whole Python process 1.6–2.5×
shap.PermutationExplainer LightGBM's own code, normal speed accelerated (3.2–4.0×)
Models with more than 300 trees LightGBM's own code, normal speed accelerated
Everything else in LightGBM (training, SHAP values, every parameter) LightGBM's own code, full functionality LightGBM's own code, full functionality
Output identical to the official wheel yes, everywhere yes, everywhere
Python wheel (Linux x86-64, glibc 2.27+) yes yes
C/C++ library (lib_lightgbm.so, same exports) — yes
Other platforms / LightGBM versions — on request
Support, source escrow — yes

What was measured

Python workloads (tools/wl.py, tools/worst.py) run with the official wheel and with the TAKT wheel; a C driver (tools/lgbm_block.c) calls LGBM_BoosterPredictForMat with the library swapped through LD_LIBRARY_PATH. Every workload prints every result with 17 significant digits (or the SHA-256 of the raw doubles for large arrays), so a byte compare of the output is a bit compare.

Workload What runs
pi_alpha, pi_alpha300 permutation_importance (neg_log_loss, 3 repeats) on 5,000 rows: a classifier with 1,000 / 300 trees of 63 leaves on 158 Alpha158-style features (qlib formulas) of Binance 5-minute klines (5 pairs), float32 (tools/features_alpha158.py)
pdp_alpha, pdp_alpha300 partial_dependence(kind="both", grid_resolution=50) for 20 features of the same models
pi_kick, pi_kick300 permutation_importance (roc_auc, 5 repeats) on 8,000 rows of OpenML "kick" (32 features, 18 categorical, 6% missing), pandas input, LGBMClassifier 500 / 300 trees
whatif_kick300 30 what-if scenarios on the kick model (two numeric columns changed in each), predict_proba on 8,000 rows each
mda_kick MDA per cross-validation fold: KFold(5) on 40,000 kick rows, each fold fits LGBMClassifier(300 trees) and runs permutation_importance on its test fold (the fitting is LightGBM's own code in both)
pdp_cal, pdp_cal300 partial_dependence(kind="both", grid_resolution=50), California housing LGBMRegressor 500 / 300 trees, all 8 features
shap_alpha, shap_cal, shap_cal300 shap.PermutationExplainer with an independent masker (100 / 200 background rows)
predict_* one predict_proba on 50,000 (quant) / 18,246 (kick) rows, whole Python process
cblock_* C driver: load the model, one LGBM_BoosterPredictForMat (normal or leaf indices); _t1 one thread, _t4 / _t2 all pinned cores (4 on Zen 3, 2 on Zen 4)
worst_* patterns that could cost: 1,500 single-row calls, 300 calls of 16 rows, 40 batches of new shapes, a cycle of 6 different same-shape matrices (1,000 and 300 trees), a 10-tree model on 400,000 rows, a 2,000-feature model with a sweep

All Python workloads run single-threaded (n_jobs=1), as in these functions' defaults, unless noted.

Stands. AMD Threadripper PRO 5975WX (Zen 3), Ubuntu 24.04, glibc 2.39; AMD Ryzen 9 7950X3D (Zen 4), Debian 13, glibc 2.41. Python 3.12, numpy 2.5.4, scipy 1.18.1, scikit-learn 1.9.1, pandas 3.0.6, shap 0.53.0 on both. Each run pinned to one core (the harness too), takt-harness 0.2.1, interleaved rounds (5 for the long workloads, 9 after a warm-up for the short ones), 95% bootstrap CIs. The harness compared stdout across the builds in every run. Raw readings: results/free/zen3, results/free/zen4, results/full/zen3, results/full/zen4 (.json bundles and .log summaries; paths in them are relative to the measurement directory).

Free package — AMD Zen 3

Workload official wall TAKT wall cycles:u factor (95% CI) wall factor (95% CI) Output identical
pi_alpha300 25.01 s 7.807 s 3.30× (3.19–3.33) 3.20× (3.07–3.29) yes
pdp_alpha300 43.57 s 11.86 s 3.67× (3.58–3.76) 3.67× (3.51–3.77) yes
pi_kick300 38.77 s 14.86 s 2.63× (2.62–2.67) 2.61× (2.60–2.64) yes
whatif_kick300 8.415 s 3.498 s 2.50× (2.48–2.52) 2.41× (2.39–2.45) yes
mda_kick 111.9 s 55.63 s 2.02× (2.02–2.04) 2.01× (2.01–2.03) yes
pdp_cal300 25.91 s 14.97 s 1.75× (1.70–1.78) 1.73× (1.69–1.77) yes
predict_alpha300 1.449 s 1.438 s 1.01× (1.00–1.02) 1.01× (0.99–1.02) yes
predict_kick300 1.759 s 1.756 s 1.00× (0.99–1.00) 1.00× (0.99–1.01) yes
shap_cal300 5.280 s 5.138 s 1.02× (1.01–1.03) 1.03× (1.01–1.03) yes
pi_kick 65.03 s 63.97 s 1.01× (0.99–1.03) 1.02× (0.98–1.05) yes
pi_alpha 81.91 s 80.22 s 1.02× (1.01–1.03) 1.02× (1.01–1.03) yes
worst_cycle6_300 1.856 s 1.843 s 1.01× (1.00–1.02) 1.01× (1.00–1.02) yes
cblock_alpha300_t1 504.6 ms 489.7 ms 1.03× (1.01–1.06) 1.03× (1.00–1.05) yes
cblock_alpha300_t4 162.8 ms 161.2 ms 1.03× (1.00–1.04) 1.01× (0.99–1.05) yes

Free package — AMD Zen 4

Workload official wall TAKT wall cycles:u factor (95% CI) wall factor (95% CI) Output identical
pi_alpha300 18.69 s 7.283 s 2.74× (2.70–2.86) 2.57× (2.54–2.68) yes
pdp_alpha300 32.28 s 10.86 s 3.12× (3.08–3.26) 2.97× (2.95–3.10) yes
pi_kick300 33.32 s 12.28 s 2.82× (2.68–2.87) 2.71× (2.59–2.76) yes
whatif_kick300 7.414 s 3.265 s 2.60× (2.53–2.76) 2.27× (2.23–2.42) yes
mda_kick 92.95 s 45.79 s 2.05× (2.01–2.08) 2.03× (1.99–2.07) yes
pdp_cal300 17.35 s 11.19 s 1.58× (1.50–1.64) 1.55× (1.47–1.60) yes
predict_alpha300 1.585 s 1.537 s 1.03× (0.99–1.06) 1.03× (0.98–1.04) yes
predict_kick300 1.887 s 1.845 s 1.02× (0.99–1.05) 1.02× (0.98–1.05) yes
shap_cal300 3.840 s 3.726 s 1.03× (1.00–1.05) 1.03× (1.02–1.05) yes
pi_kick 59.47 s 58.18 s 1.02× (0.97–1.05) 1.02× (0.94–1.09) yes
pi_alpha 71.12 s 68.50 s 1.04× (1.01–1.06) 1.04× (1.01–1.08) yes
worst_cycle6_300 1.774 s 1.750 s 1.01× (1.00–1.09) 1.01× (0.98–1.04) yes
cblock_alpha300_t1 381.3 ms 373.9 ms 1.03× (1.02–1.04) 1.02× (0.99–1.03) yes
cblock_alpha300_t2 212.9 ms 205.6 ms 1.03× (1.00–1.04) 1.03× (0.98–1.09) yes

Full version — AMD Zen 3

Workload official wall TAKT wall cycles:u factor (95% CI) wall factor (95% CI) Output identical
pi_alpha 82.44 s 5.274 s 16.75× (16.14–16.96) 15.63× (15.02–16.22) yes
pdp_alpha 146.9 s 8.972 s 16.77× (16.57–16.93) 16.38× (16.14–16.55) yes
pi_kick 63.41 s 5.098 s 12.97× (12.85–13.08) 12.44× (12.35–12.54) yes
mda_kick 113.1 s 18.75 s 6.10× (6.05–6.14) 6.03× (5.86–6.08) yes
pdp_cal 45.75 s 8.592 s 5.43× (5.37–5.48) 5.33× (5.26–5.38) yes
shap_alpha 8.800 s 2.919 s 3.35× (3.29–3.39) 3.02× (2.98–3.06) yes
shap_cal 8.375 s 2.755 s 3.23× (3.16–3.27) 3.04× (2.99–3.09) yes
predict_alpha 2.679 s 1.405 s 2.12× (2.09–2.14) 1.91× (1.88–1.93) yes
predict_kick 2.116 s 1.417 s 1.59× (1.58–1.59) 1.49× (1.48–1.50) yes
cblock_alpha_normal_t1 1.694 s 472.1 ms 4.12× (4.10–4.22) 3.59× (3.52–3.63) yes
cblock_alpha_leaf_t1 1.696 s 759.0 ms 3.02× (2.97–3.04) 2.23× (2.20–2.26) yes
cblock_kick_normal_t1 857.7 ms 216.4 ms 4.36× (4.26–4.37) 3.96× (3.88–4.03) yes
cblock_alpha_normal_t4 477.3 ms 208.6 ms 4.03× (3.99–4.06) 2.29× (2.23–2.33) yes
cblock_alpha_leaf_t4 549.2 ms 367.2 ms 2.76× (2.75–2.80) 1.50× (1.49–1.53) yes
cblock_kick_normal_t4 244.9 ms 98.05 ms 4.05× (4.03–4.08) 2.50× (2.38–2.52) yes
worst_rows1 1.034 s 1.041 s 1.00× (0.99–1.01) 0.99× (0.99–1.01) yes
worst_rows16 1.101 s 1.042 s 1.07× (1.07–1.08) 1.06× (1.04–1.06) yes
worst_newshapes 4.217 s 1.394 s 3.40× (3.31–3.42) 3.03× (2.98–3.07) yes
worst_cycle6 4.214 s 1.419 s 3.32× (3.30–3.35) 2.97× (2.94–3.00) yes
worst_tiny10 1.121 s 1.096 s 1.05× (1.05–1.07) 1.02× (1.01–1.03) yes
worst_wide2000 2.153 s 1.471 s 1.61× (1.60–1.62) 1.46× (1.45–1.48) yes

Full version — AMD Zen 4

Workload official wall TAKT wall cycles:u factor (95% CI) wall factor (95% CI) Output identical
pi_alpha 71.21 s 4.521 s 18.35× (17.34–18.71) 15.75× (15.21–16.19) yes
pdp_alpha 127.5 s 7.625 s 18.31× (18.06–18.71) 16.73× (16.54–17.09) yes
pi_kick 58.31 s 4.596 s 14.64× (14.09–15.23) 12.69× (12.27–13.11) yes
mda_kick 92.06 s 16.28 s 5.92× (5.79–6.01) 5.66× (5.53–5.73) yes
pdp_cal 32.67 s 7.412 s 4.77× (4.70–4.87) 4.41× (4.34–4.52) yes
shap_alpha 7.297 s 2.415 s 4.00× (3.94–4.09) 3.02× (3.00–3.10) yes
shap_cal 6.235 s 2.345 s 3.23× (3.18–3.29) 2.66× (2.57–2.71) yes
predict_alpha 2.638 s 1.460 s 2.45× (2.40–2.49) 1.81× (1.77–1.84) yes
predict_kick 2.181 s 1.500 s 1.73× (1.72–1.81) 1.45× (1.42–1.48) yes
cblock_alpha_normal_t1 1.488 s 342.4 ms 5.38× (5.25–5.48) 4.35× (4.17–4.41) yes
cblock_alpha_leaf_t1 1.589 s 652.1 ms 4.09× (4.02–4.22) 2.44× (2.40–2.48) yes
cblock_kick_normal_t1 801.6 ms 181.5 ms 4.87× (4.67–4.90) 4.42× (4.27–4.47) yes
cblock_alpha_normal_t2 787.4 ms 226.0 ms 5.32× (5.13–5.42) 3.48× (3.31–3.60) yes
cblock_alpha_leaf_t2 920.3 ms 477.5 ms 3.92× (3.75–4.03) 1.93× (1.85–2.00) yes
cblock_kick_normal_t2 424.5 ms 113.4 ms 4.75× (4.67–4.80) 3.74× (3.62–3.81) yes
worst_rows1 1.153 s 1.153 s 1.00× (0.98–1.03) 1.00× (0.97–1.03) yes
worst_rows16 1.207 s 1.158 s 1.07× (1.05–1.09) 1.04× (1.00–1.09) yes
worst_newshapes 4.048 s 1.469 s 3.66× (3.64–3.71) 2.75× (2.69–2.78) yes
worst_cycle6 4.031 s 1.429 s 3.98× (3.92–4.03) 2.82× (2.71–2.87) yes
worst_tiny10 1.230 s 1.179 s 1.06× (1.05–1.09) 1.04× (1.00–1.09) yes
worst_wide2000 2.159 s 1.516 s 1.75× (1.72–1.79) 1.42× (1.39–1.49) yes

Equivalence

"Same" means byte-identical output, every number at 17 significant digits.

Bit-identity is per system. The TAKT build reproduces the official build's output on the same system, bit for bit. Results that pass through the system maths library (for example probabilities) depend on it, so compare on the same machine.

Where the speed-up applies, and where it does not

LightGBM's own concurrency limits. In LightGBM 4.7.0, with the official library: concurrent predictions on one booster with different start_iteration/num_iteration (LightGBM issue #6142), concurrent calls with different num_threads, and changing a booster (training, set_leaf_output, refit) while another thread predicts with it can crash or return wrong values. Code that avoids these patterns runs the same with TAKT. The parts of TAKT that run LightGBM's own code keep this behaviour; the full version's accelerated calls take the iteration range from each call (a 16-thread test of the first pattern that crashes the official library gave 0 differences with them).

Availability

Reproducing the baseline

# The verify script: a 300-tree model on synthetic data, permutation importance and a PDP, compared bytewise.
python -m venv a && a/bin/pip install -q lightgbm==4.7.0 scikit-learn && a/bin/python tools/verify.py > a.txt
python -m venv c && c/bin/pip install -q lightgbm-takt scikit-learn  && c/bin/python tools/verify.py > c.txt
diff a.txt c.txt && echo identical

# The workloads: prepare the models once (official wheel), then run any workload with each wheel.
python tools/wl.py prep .        # the kick and California models (500 trees)
python tools/wl.py prep_free .   # the 300-tree models (the quant model needs the quant data, see below)
python tools/wl.py pi_kick300 . > out.txt

The kick and California housing workloads download their data themselves (OpenML, scikit-learn). The quant-feature workloads (*_alpha*) read features computed with tools/features_alpha158.py from Binance 5-minute klines of five pairs, and the trained model files; these data files are not in this directory (on request). tools/table.py and tools/pubtable.py print the tables above from the readings in results/.

Contents

Source text: README.md

Telegram