kindling
A hybrid recommender that grows with your data — closed-form, no training loop, no GPU. One fused base score per (user, item) built from EASE / wilson-cooccurrence plus auto-gated z-normalized channels (trend, last-item, transitions, user-CF), with a Rust core for the numerics.
Design goals (learned the hard way — see docs/EXPERIMENTS.md):
- A wheel that imports is a wheel that works. numpy / pandas / scipy
only; the linear algebra that matters (the EASE inversion) runs on a
pure-Rust core (
kindling_core). No PyTorch, no BLAS system deps. - Closed-form shallow models, gated per dataset, beat speculative complexity. Every channel is closed-form or a counting statistic; every channel is activated by a measurable property of the data; every gate exists because the ungated version measurably hurt somewhere.
Install
pip install kindling-rec # from PyPI
pip install -e ".[dev]" # dev / from source
pip install -e ".[dev,bench]" # + benchmark harness
Quickstart
from kindling import Engine
from kindling.loaders import movielens
interactions = movielens.load_1m() # entity_id, item_id, timestamp[, rating]
engine = Engine()
engine.fit(interactions)
for rec in engine.recommend(entity_id=42, n=10):
print(rec.item_id, rec.score, rec.base_kind)
# Many users at once — runs in parallel in the Rust core (GIL released).
batches = engine.recommend_batch([42, 99, 7], n=10)
Recommendation is served end-to-end by the Rust core (kindling_core): the
EASE/cooc base, the channel blend, the boost layer, and cold-slots all run
natively. Single recommend is sub-millisecond; batch is the parallel path.
New / anonymous users (absent from training) are served from ad-hoc seed items with no per-user training — and a zero/all-unknown seed set falls back to popularity:
engine.recommend_for_items(item_ids=[101, 205], n=10) # personalized from seeds
engine.recommend_for_items(item_ids=[], n=10) # → popularity fallback
Intelligent activation
Channels turn on by regime, not configuration. The base is EASE for
catalogs ≤ 20k items and wilson-normalized cooccurrence above that;
the trend channel needs timestamps; transitions additionally need the
data not to be a rating-burst; user-CF activates only on sparse-history
data; rating-weighting engages only when true ratings are present. Each
decision is made from the data at fit() time. See
docs/REFERENCE.md §2 for the gate table.
Where it stands (full-ranking NDCG@10, engine defaults)
Full results — discovery growth and the repeat-regime dominance — in
docs/RESULTS.md.
| dataset | NDCG@10 | notes |
|---|---|---|
| movielens-1m | 0.293 | rating-weighted EASE |
| amazon-beauty | 0.033 | + user-CF channel |
| steam (realistic tier) | 0.066 | open-catalog + cold slots |
| amazon-book-chrono | 0.032 | timestamps activate trend/transitions |
Strongest personalized model on all four; beats implicit ALS everywhere;
wins cold-user buckets on cold-heavy catalogs. The full benchmark
record — including the negative results, which are half the value — is in
docs/EXPERIMENTS.md.
On repeat-regime datasets (grocery/retail), a held-out gate turns on reorder
recommendation; under repeat-aware eval kindling separates from the field —
e.g. Dunnhumby 0.48 NDCG@10 vs ~0.05 for every baseline — while correctly
declining on fake-repeat data like Steam (re-logs aren't repurchase). See
docs/REPEAT-GATE.md. An opt-in EASE+ (EDLAE) base is
available but off by default (docs/EASE-VARIANTS-ASSESSMENT.md).
Growth curves
How accuracy grows from cold to hot, against the standard baselines
(bench/plot_growth_curves.py):
Serving performance (native engine, bench/final_state_perf.py)
| dataset | fit | single recommend p50 | batch throughput | NDCG@10 |
|---|---|---|---|---|
| movielens-1m | 4.2 s | 0.17 ms | 15.4k recs/s | 0.2928 |
| amazon-beauty | 13.1 s | 1.21 ms | 3.0k recs/s | 0.0328 |
| steam | 110 s | 5.81 ms | 0.8k recs/s | 0.0659 |
The recommend path is pure Rust with the GIL released for the batch path — single recommend dropped from ~200 ms (the earlier Python path) to sub-millisecond, with byte-identical rankings.
Large-catalog fit got the same treatment (v1.0.4): the cooccurrence base is built and returned across the Rust boundary as zero-copy numpy arrays (not Python lists), and its pair accumulation is rayon-parallel over users — a fit that previously swap-thrashed a 24 GB box now runs in stable memory, and a 250k-basket / 15M-interaction fit dropped 250 s → 35 s (7.2×), byte-identical.
Serving
Persist a fit as a self-contained artifact and serve it with no re-fit:
from kindling.serving import KindlingServer
KindlingServer.from_engine(engine).save("artifact/")
# ── in the serving process ──
server = KindlingServer.load("artifact/")
server.recommend("user-42", n=10)
A FastAPI example (kindling.serving_app) ships behind the optional
serve extra: pip install 'kindling[serve]'.
Project layout
src/kindling/ library source (engine, serving, Rust bindings, loaders)
native/kindling_core/ Rust core (EASE, cooccurrence, channel blend, recommend)
bench/ regression gate (bench/verify.py) + frozen reports + plots
docs/ RESULTS.md (what it brings) · REFERENCE.md (architecture) ·
EXPERIMENTS.md (record) · LESSONS.md (what the build taught)
tests/ unit, property, integration
License
Apache 2.0.
kindling is an Awry Labs project — independent software, a little off on purpose.
Also from Awry Labs: Gravel — a C++/Python network-fragility library for measuring and stress-testing graph resilience. (GitHub)
Metadata
Release files for kindling-rec 1.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kindling_rec-1.0.4.tar.gz | 128.4 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kindling_rec-1.0.4-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| kindling_rec-1.0.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| kindling_rec-1.0.4-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 2.3 MB
Release files / kindling_rec-1.0.4.tar.gz
| Download URL | kindling_rec-1.0.4.tar.gz |
|---|---|
| Size | 128.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
23d375d4c0000cdca68fb4f982e88ccdf82920af4f3c9949649163091180435c
|
|
BLAKE2b-256 checksum How to use checksums |
4584facd25ad5be5f8edf523e8e20633f7434b16cb337f9c2a7fb8192c7ba1bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / kindling_rec-1.0.4-cp311-abi3-win_amd64.whl
| Download URL | kindling_rec-1.0.4-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 725.2 kB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f9520ad4fe7eb92bbea57c66eb80f384af55fc8bbea68969c1f2bd36b0dc8511
|
|
BLAKE2b-256 checksum How to use checksums |
10a3f0262b9e1adc617226ac8db93a46905d0cde141c601cabadf53563a740a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / kindling_rec-1.0.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | kindling_rec-1.0.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 816.6 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
09f99dea8ee3415941cd8088fc5aec4475a303d2e900bb5a48fb86c009a982e9
|
|
BLAKE2b-256 checksum How to use checksums |
123a8efdf7bf950e49616463a06a27cf7abb63a1a48e69160ad076a1d88cddca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / kindling_rec-1.0.4-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | kindling_rec-1.0.4-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 668.8 kB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
7a4dd2a39586421e8dbdf07b0b0cb76f81afa87f3ea9e24ecca222276959c2fc
|
|
BLAKE2b-256 checksum How to use checksums |
f6ed604e796b6bfeed28047aebf4473d86d77a2dda024725585b4fb23b0c464e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency log