Import XGBoost, LightGBM, and CatBoost dumps
duckboost_import(backend, dump) turns a vendor dump into the same JSON model that duckboost_predict and duckboost_to_sql already understand. These examples use the fixtures under test/sql/duckboost/data/ that test/sql/duckboost/import_dumps.test and multiclass.test load with read_text. A default make build can import these dumps; it does not link the vendor training libraries.
Imported models set learning_rate to 1. XGBoost and LightGBM dump leaf values already include learning-rate shrinkage and are imported unchanged, while CatBoost scale and bias are applied during import. LightGBM <= splits become duckboost < splits via nextafter toward +∞.
XGBoost JSON
dump_model(..., dump_format='json'). The yes child is feature < split_condition. This stump predicts 0.5 at f0 = 1 and -0.25 at f0 = 2.
LOAD 'build/release/extension/duckboost/duckboost.duckdb_extension';
CREATE TABLE models AS
SELECT duckboost_import(
'xgboost',
(SELECT content FROM read_text('test/sql/duckboost/data/xgb_stump.json')),
MAP {'task': 'regression', 'base_score': '0.0'}
) AS model;
SELECT
duckboost_predict(model, [1.0]) AS pred_1,
duckboost_predict(model, [2.0]) AS pred_2
FROM models;┌────────┬────────┐
│ pred_1 │ pred_2 │
├────────┼────────┤
│ 0.5 │ -0.25 │
└────────┴────────┘
Passing a duckboost JSON document back through duckboost_import keeps the backend name. The test does that with the model just imported.
LightGBM text
booster_.save_model() text. The left child is feature <= threshold in LightGBM; after import, 1.5 stays on the left leaf and the next representable double goes right.
LOAD 'build/release/extension/duckboost/duckboost.duckdb_extension';
CREATE TABLE models AS
SELECT duckboost_import(
'lightgbm',
(SELECT content FROM read_text('test/sql/duckboost/data/lgb_stump.txt'))
) AS model;
SELECT
duckboost_predict(model, [1.5]) AS at_threshold,
duckboost_predict(model, [1.5000001]) AS above_threshold
FROM models;┌──────────────┬─────────────────┐
│ at_threshold │ above_threshold │
├──────────────┼─────────────────┤
│ 0.5 │ -0.25 │
└──────────────┴─────────────────┘
test/sql/duckboost/data/lgb_shrink.txt is a second fixture with leaf_value=2.0 and shrinkage=0.1. Because LightGBM already stores the shrunken value in leaf_value, the import test expects duckboost_predict(model, []) = 2.0 without applying shrinkage again.
CatBoost JSON
save_model(..., format='json') float trees. A FloatFeature split is true when the value is greater than the border, which is the opposite direction from the reference trainer’s < split. The stump fixture has scale_and_bias [1.0, [0.1]], and the import test expects about 0.6 and -0.15.
LOAD 'build/release/extension/duckboost/duckboost.duckdb_extension';
CREATE TABLE models AS
SELECT duckboost_import(
'catboost',
(SELECT content FROM read_text('test/sql/duckboost/data/catboost_stump.json'))
) AS model;
SELECT
duckboost_predict(model, [1.0]) AS pred_1,
duckboost_predict(model, [2.0]) AS pred_2
FROM models;┌────────┬────────┐
│ pred_1 │ pred_2 │
├────────┼────────┤
│ 0.6 │ -0.15 │
└────────┴────────┘
A depth-2 oblivious tree in catboost_depth2.json expands to four leaf values. The import test expects 10, 20, 30, and 40 for the four sign combinations of two features:
LOAD 'build/release/extension/duckboost/duckboost.duckdb_extension';
CREATE TABLE models AS
SELECT duckboost_import(
'catboost',
(SELECT content FROM read_text('test/sql/duckboost/data/catboost_depth2.json'))
) AS model;
SELECT
duckboost_predict(model, [-1.0, -1.0]) AS nn,
duckboost_predict(model, [-1.0, 1.0]) AS np,
duckboost_predict(model, [1.0, -1.0]) AS pn,
duckboost_predict(model, [1.0, 1.0]) AS pp
FROM models;┌──────┬──────┬──────┬──────┐
│ nn │ np │ pn │ pp │
├──────┼──────┼──────┼──────┤
│ 10.0 │ 20.0 │ 30.0 │ 40.0 │
└──────┴──────┴──────┴──────┘
Multiclass CatBoost
catboost_multiclass.json stores class-blocked leaf_values. Import expands each oblivious tree into n_classes duckboost trees. duckboost_predict returns the argmax class index. The multiclass test expects class 0 at feature 1 and class 1 at feature 2, and the class probabilities sum to 1.
LOAD 'build/release/extension/duckboost/duckboost.duckdb_extension';
CREATE TABLE models AS
SELECT duckboost_import(
'catboost',
(SELECT content FROM read_text('test/sql/duckboost/data/catboost_multiclass.json'))
) AS model;
SELECT
duckboost_predict(model, [1.0]) AS class_at_1,
duckboost_predict(model, [2.0]) AS class_at_2,
duckboost_predict_proba(model, [1.0]) AS proba_at_1
FROM models;┌────────────┬────────────┬───────────────────────────────────────────────────────────────┐
│ class_at_1 │ class_at_2 │ proba_at_1 │
├────────────┼────────────┼───────────────────────────────────────────────────────────────┤
│ 0.0 │ 1.0 │ [0.7782683188104925, 0.1053271633603004, 0.11640451782920691] │
└────────────┴────────────┴───────────────────────────────────────────────────────────────┘
Other fixtures
These files are also covered by test/sql/duckboost/cat_features.test. They are real dumps, not snippets invented for the docs:
catboost_onehot.json—OneHotFeaturecatboost_ctr_counter.json,catboost_ctr_float_combo.json,catboost_ctr_onehot_combo.json—OnlineCtr(Counter and Borders)
OnlineCtr needs ctr_data in the dump (save_model(..., pool=...)). duckboost_to_sql inlines those hash lookups. Categorical values are passed as numbers (CityHash), not as strings.
Optional import map keys, from ImportOptions::FromMap: task (alias objective), base_score (alias bias), learning_rate (aliases eta, lr, scale), feature_names (comma-separated), and n_classes (aliases classes_count, num_class, num_classes).