Model release 21 September 2026. Target preference is version 2, built 21 September 2026, and it answers both kinds of comparison; compound preference is unchanged from 13 September 2026. Trained on ChEMBL 37 alone.
Two bundles, one per question. Both run entirely on your own machine, nothing is sent anywhere, and each carries everything it needs to score.
| Quantity | Compound preference | Target preference |
|---|---|---|
| The question | which of two compounds a target prefers | which of two targets a compound prefers, from different families or from one |
| Row layout | ligand, sequence, ligand | sequence, ligand, sequence |
| Feature width | 2,556 | 1,998 |
| Forest on disk | 1.18 GB | 0.42 GB |
| Targets it can score | 2,079 | 2,279 |
| Protein families | 32 | 34 |
| Held-out accuracy, across families | — | 0.75 on 8,689 |
| Held-out accuracy, within one family | — | 0.78 on 32,738 |
| Held-out accuracy | 0.71 on 65,725 | — |
The target-preference forest loads to about 3.4 GB of memory in about five seconds, so a laptop with 8 GB runs it comfortably. The preference forest is the larger of the two and wants 16 GB, because it loads to rather more than its size on disk. Load either once at process start rather than per request. No GPU is used at any point.
Held-out accuracy for each is measured on its own compound-disjoint holdout, and the two holdouts are different sets, so read each figure on its own, as set out on the methods page. For target preference the two kinds of comparison are measured apart and never pooled.
| Bundle | File | Size | SHA-256 |
|---|---|---|---|
| Compound preference one target, two compounds | lsl_v1.tar.gz | 1.18 GB | 63efc35e584c20b5e881055d317aa9e3… |
| Target preference one compound, two targets, from different families or from one family | xfam_v2.tar.gz | 0.42 GB | 5f4bfb34f2a2611864151a74f88c15b4… |
Those are the checksums of the archives themselves,
shown as a prefix, enough to spot a wrong or truncated download. Inside each
archive the forest carries its own SHA-256 in MANIFEST.json, and
that is the one the installer verifies before it will report success.
Add your own data to either bundle. The extension tools merge trees fitted on your measurements into a released model, on your own machine, and can add targets beyond the roster: how it works, and the full guide, EXTEND.md.
The command line tools, the inference API and everything needed to rebuild the training set are on GitHub: github.com/smuskal/ffm.
That installs the environment with the versions pinned below and fetches both
bundles, then proves each one works: it checks the forest against the SHA-256 in
that bundle's own manifest and replays the reference predictions shipped beside
it. If they do not reproduce to 1e-6 the install fails rather than reporting
success. FFM_MODELS=selectivity or FFM_MODELS=preference
fetches just one.
Rank targets for one compound, the target-preference question:
Rank compounds at one target, the compound-preference question:
Or from Python, loading either bundle by path and keeping the two modules under distinct names:
Both bundles hold the same two properties, asserted by their own self-tests:
every prediction averages both argument orders, so compare(A,B) +
compare(B,A) is exactly 1 and a thing compared with itself returns exactly
0.5. Ranking n items costs n(n-1)/2 comparisons, so keep a call to a sane panel
size. An unparseable SMILES raises rather than being dropped.
Targets are named by UniProt accession. Gene symbols are many-to-many against accessions and the failure is silent, so the accession is what the model is given. The full roster is on the targets page.
The extension tool, extend/ffm_extend.py, fits decision trees on
measurements you hold and merges them into a released forest, on your own
machine. Your structures, sequences and activity values stay on your computer
and are never written into the bundle; only the trees fitted from them are. It
works on both bundles and reads the layout from the bundle's own manifest, so one
command serves either. The base bundle is opened read-only, and the output is a
complete bundle whose predict.py loads the merged forest
unchanged.
Your targets can go beyond the roster. The released bundles score {n(L["targets_servable"])} and {n(F["targets_servable"])} targets. Name a target by UniProt accession to use one a bundle already carries, or supply its amino acid sequence to add a new one: the sequence is embedded with the same ESM2 recipe the models were built on, your trees are fitted on it, and it is written into the output bundle's sequence vectors and target index, so it can be scored afterwards like any other target.
Measured on held-out public ChEMBL data. For compound preference, 1,858 added comparisons across five targets, with 797 more from those targets held out: 80 added trees moved accuracy on those targets from 0.69 to 0.73, with a breadth reference across the roster steady at 0.69. What your own data adds depends on your data.
An extended model is measured on your data. Added trees change every prediction, so the figures on this site describe the released bundles. The tool replaces the performance block in the output manifest with a withdrawal notice; measure the extended model on your own held-out comparisons, and re-measure the strength operating point there, before quoting a number from it. Keep comparisons between two families and comparisons inside one family apart when you measure, as the released figures do.
Adding a target by sequence also needs torch and
transformers; naming targets by accession needs neither. The
scikit-learn version must match the bundle manifest. Input formats, worked
examples and the tree-count sweep are in
EXTEND.md.
Both bundles ship the same file set, differing only in the forest and in the target roster each can score.
| File | What it is |
|---|---|
familyfm_selectivity.joblib or familyfm_preference.joblib | the forest, one per bundle |
predict.py | the inference API; load it by file path, nothing to install |
sequence_vectors.npz | ESM2 vectors for every servable target, precomputed |
sequence_index.json | sequence key to row |
target_index.json | accession to sequence key, and the family per target |
MANIFEST.json | provenance, hyperparameters, measured performance |
reference_predictions.json | 30 fixed inputs and the outputs this bundle produced |
heldout_scored.csv | every held-out comparison with its probability, so the headline can be recomputed |
Because heldout_scored.csv ships with the bundle, every number on
this site can be recomputed from the download without rerunning the model.
A joblib forest is a pickled object graph, so scikit-learn, numpy and joblib are pinned to the build versions. Another scikit-learn warns when it loads a forest; both bundles were checked under 1.6.1 and reproduce their reference predictions, and the installer's replay is how you confirm any environment. RDKit computes 1,024 of every ligand block's 1,038 dimensions, so it is held to the releases measured to compute byte-identical features: 2024.03.6 through 2025.09.5.
Install on Python 3.10 to 3.13, on macOS or Linux; both bundles verify
identically on 3.12. install.sh picks a suitable Python and installs
the pinned libraries, RDKit included, and
python -m familyfm.rdkit_check confirms your RDKit computes the same
ligand features. The models were built with:
| Package | Pinned |
|---|---|
| numpy | 2.2.6 |
| python | 3.10.15 |
| sklearn | 1.7.2 |
The code is Apache 2.0. The model weights are CC BY-SA 4.0, which is the share-alike term ChEMBL itself carries: this model is trained on ChEMBL 37 and nothing else, so the weights inherit its attribution and share-alike conditions. Cite ChEMBL alongside this model in anything you publish.
The sibling Kinase Foundation Model ships its weights under research-and-evaluation terms instead, because it is trained on a commercial database. This one is not, which is why it can be downloaded freely.
Model release 21 September 2026, target preference version 2.
For each bundle the forest,
the reference predictions and the held-out scores all come from that build, and
the checksum in its MANIFEST.json is what the installer verifies.
The preference forest carries
e693f791edf1cdf4….