From f3e9efbd3d5ea23b17ce56cd22b34ff5a3fde287 Mon Sep 17 00:00:00 2001 From: Radu Gheorghe Date: Mon, 20 Jul 2026 12:38:37 +0300 Subject: [PATCH] use_fast_forest evaluation for GBDT trees --- en/ranking/lightgbm.md | 29 +++++++++++++++++++++++++++++ en/ranking/xgboost.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/en/ranking/lightgbm.md b/en/ranking/lightgbm.md index 36a4fba2ef..69b807059f 100644 --- a/en/ranking/lightgbm.md +++ b/en/ranking/lightgbm.md @@ -174,6 +174,35 @@ Generally the run time complexity is determined by: Serving latency can be brought down by [using multiple threads per query request](../performance/practical-search-performance-guide.html#multithreaded-search-and-ranking). +### Fast forest evaluation + +For large forests, an alternative GBDT evaluator can further reduce evaluation cost. +Enable it by setting the `vespa.eval.use_fast_forest` +[rank-property](../reference/ranking/rank-feature-configuration.html) to `true` in the rank profile: + +
+rank-profile classify inherits default {
+    rank-properties {
+        vespa.eval.use_fast_forest: true
+    }
+    second-phase {
+        expression: lightgbm("lightgbm_model.json")
+    }
+}
+
+ +This is a rank profile setting, not a query parameter: it is applied when the model is compiled, +so it cannot be toggled per query. To A/B test it, create a second rank profile that only adds this property. + +{% include note.html content="Fast forest evaluation only applies when every split in the model is numeric +(`<` or its inverted form) and no tree has more than 2,048 leaves. Models using LightGBM's native +[categorical splits](#using-categorical-features) are not eligible, and neither is a model whose root +expression wraps the tree sum in another function: for instance, a `binary` objective, which Vespa +automatically wraps in `sigmoid(...)`. In any of these cases the setting has no effect and Vespa silently +falls back to the default evaluator." %} + +The speedup depends more on the number of leaves per tree than on the number of trees: +models with at most 64 leaves per tree benefit the most. ## Objective functions diff --git a/en/ranking/xgboost.md b/en/ranking/xgboost.md index baee1177b3..d2d15cc956 100644 --- a/en/ranking/xgboost.md +++ b/en/ranking/xgboost.md @@ -208,6 +208,36 @@ Generally the run time complexity is determined by: Serving latency can be brought down by [using multiple threads per query request](../performance/practical-search-performance-guide.html#multithreaded-search-and-ranking). +### Fast forest evaluation + +For large forests, an alternative GBDT evaluator can further reduce evaluation cost. +Enable it by setting the `vespa.eval.use_fast_forest` +[rank-property](../reference/ranking/rank-feature-configuration.html) to `true` in the rank profile: + +
+rank-profile prediction inherits default {
+    rank-properties {
+        vespa.eval.use_fast_forest: true
+    }
+    second-phase {
+        expression: xgboost("my_model.json")
+    }
+}
+
+ +This is a rank profile setting, not a query parameter - it is applied when the model is compiled, +so it cannot be toggled per query. To A/B test it, create a second rank profile that only adds this property. + +{% include note.html content="Fast forest evaluation only applies when every split in the model is numeric +(`<` or its inverted form), no tree has more than 2,048 leaves, and the root expression is the bare sum of trees. +UBJ models are therefore never eligible: the importer always appends `base_score` to the tree sum. +Legacy JSON models are eligible for `reg:squarederror` and ranking objectives, but not when the expression is +wrapped in another function — for instance `sigmoid(xgboost(...))` for a logistic objective. +In any of these cases the setting has no effect and Vespa silently falls back to the default evaluator." %} + +The speedup depends more on the number of leaves per tree than on the number of trees: +models with at most 64 leaves per tree benefit the most. + ## Categorical features {% include warning.html content="Vespa does **not** support XGBoost's native categorical splits