Skip to main content

Notes ·

Scoring a LightGBM model without the LightGBM library

The fraud demo's first deploy failed on a missing system library. Reading the model file directly fixed it, and tests show the scores didn't change.

The fraud demo on this site worked on my machine and returned 500 on its first deploy. The function log said why:

OSError: libgomp.so.1: cannot open shared object file: No such file or directory

The lightgbm Python package ships a compiled library that expects the OpenMP runtime, libgomp, to be on the system. Vercel's Python image doesn't include it, and a serverless function can't install system packages.

The options#Link to this section

I could have moved the demo to a container, or tried to bundle libgomp and load it by hand. Both add moving parts to keep a 2-second demo alive. The third option was smaller: the model is already saved as LightGBM's plain-text format, and scoring a tree ensemble is simple arithmetic.

This model only uses numerical splits with no missing values, so each tree is a series of "is this feature at or below this threshold" questions ending in a leaf value. The raw score is the sum of the leaves the transaction lands in, across 304 trees. The loader refuses any model that uses categorical splits or missing-value rules, rather than scoring it wrongly.

The harder part: the reasons#Link to this section

The demo also shows the five features that pushed a score up or down. LightGBM computes those with TreeSHAP when you ask for pred_contrib. I ported LightGBM's own implementation (Tree::TreeSHAP and its three helpers) line for line, using each node's training count as its weight, so the numbers would match the library rather than a textbook variant.

How I know it's the same model#Link to this section

A port is only useful if it agrees with the original, so the tests keep lightgbm as a reference and the function never imports it. For 150 held-out transactions, the test checks that the raw score and every one of the 79 contributions match LightGBM to within one billionth. A second test checks that the function file doesn't import lightgbm at all.

That sits on top of the earlier check: the site's feature code reproduces the original training pipeline's probabilities on 1,000 held-out rows.

What it cost#Link to this section

The pure-Python version is slower at the reasons, because TreeSHAP walks every path in every tree. Locally, a score with all its reasons takes about 136 ms. The model call alone is far quicker: one production call reported 0.38 ms, inside a function that took 154 ms end to end. That's well inside the demo's 2-second budget, and the function's bundle no longer carries lightgbm or scipy.

Evidence#Link to this section

Related case study: Real-time credit-card fraud detection