Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ on:
tags: ["v*"]
workflow_dispatch:

# Default the GITHUB_TOKEN to read-only; the publish job opts into
# id-token: write for PyPI OIDC and nothing else needs write.
permissions:
contents: read

# Builds the Python wheels (`pip install sqlite-predict`). Each wheel bundles the
# zero-dependency loadable compiled for its platform. Staging runs `make
# python-src` on the host to drop the amalgamation + SQLite ext headers into the
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- **Forest prediction accumulates the learning-rate product in double**, matching
the trainer. The serving path multiplied the learning rate by a tree value as
`float * float` before widening to the `double` accumulator, which could
overflow to infinity (and, under x87 excess precision, differ by platform)
before the widening. The product and the forest accumulation are now computed in
double (individual tree values are still `f32`); served predictions may shift in
their least-significant digits.

## [0.2.0] - 2026-07-31

### Changed
Expand Down
8 changes: 6 additions & 2 deletions predict-student.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ int predict0_forest_predict_row(const Forest *f, const f32 *x, f64 *scbuf, char
if (f->task == 1) {
f64 s = f->init[0];
for (int j = 0; j < f->n_trees; j++)
s += f->lr * forest_tree_value(f, j, x);
/* (f64) before the multiply: keep the product in double, matching the
* trainer (predict-train.c), so a float*float never overflows or picks up
* platform-dependent excess precision before it reaches the accumulator. */
s += (f64)f->lr * forest_tree_value(f, j, x);
*has_conf = 0;
*pred = sqlite3_mprintf("%.17g", s);
return *pred ? SQLITE_OK : SQLITE_NOMEM;
Expand All @@ -285,7 +288,8 @@ int predict0_forest_predict_row(const Forest *f, const f32 *x, f64 *scbuf, char
scbuf[c] = f->init[c];
for (int r = 0; r < f->n_rounds; r++)
for (int s = 0; s < f->n_score; s++)
scbuf[s] += f->lr * forest_tree_value(f, r * f->n_score + s, x);
/* (f64) before the multiply, as above and in the trainer */
scbuf[s] += (f64)f->lr * forest_tree_value(f, r * f->n_score + s, x);
f64 mx = scbuf[0];
int arg = 0;
for (int c = 1; c < f->n_score; c++)
Expand Down