lazy-tfm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. lazy_tfm-0.1.0/LICENSE +21 -0
  2. lazy_tfm-0.1.0/PKG-INFO +406 -0
  3. lazy_tfm-0.1.0/README.md +358 -0
  4. lazy_tfm-0.1.0/pyproject.toml +247 -0
  5. lazy_tfm-0.1.0/pyproject.toml.orig +250 -0
  6. lazy_tfm-0.1.0/src/lazy/__init__.py +119 -0
  7. lazy_tfm-0.1.0/src/lazy/_inputs.py +170 -0
  8. lazy_tfm-0.1.0/src/lazy/_namespace.py +58 -0
  9. lazy_tfm-0.1.0/src/lazy/_typing.py +22 -0
  10. lazy_tfm-0.1.0/src/lazy/base.py +686 -0
  11. lazy_tfm-0.1.0/src/lazy/datasets.py +1211 -0
  12. lazy_tfm-0.1.0/src/lazy/distributions.py +786 -0
  13. lazy_tfm-0.1.0/src/lazy/grid.py +452 -0
  14. lazy_tfm-0.1.0/src/lazy/metrics.py +1035 -0
  15. lazy_tfm-0.1.0/src/lazy/models/__init__.py +60 -0
  16. lazy_tfm-0.1.0/src/lazy/models/_conformance.py +126 -0
  17. lazy_tfm-0.1.0/src/lazy/models/_device.py +85 -0
  18. lazy_tfm-0.1.0/src/lazy/models/_ensemble.py +601 -0
  19. lazy_tfm-0.1.0/src/lazy/models/_hub.py +451 -0
  20. lazy_tfm-0.1.0/src/lazy/models/_icl_stream.py +405 -0
  21. lazy_tfm-0.1.0/src/lazy/models/_limix_preprocess.py +202 -0
  22. lazy_tfm-0.1.0/src/lazy/models/_limix_source.py +294 -0
  23. lazy_tfm-0.1.0/src/lazy/models/_limix_stream.py +370 -0
  24. lazy_tfm-0.1.0/src/lazy/models/_members.py +369 -0
  25. lazy_tfm-0.1.0/src/lazy/models/_progress.py +104 -0
  26. lazy_tfm-0.1.0/src/lazy/models/_transforms.py +231 -0
  27. lazy_tfm-0.1.0/src/lazy/models/lazy_model.py +345 -0
  28. lazy_tfm-0.1.0/src/lazy/models/limix.py +538 -0
  29. lazy_tfm-0.1.0/src/lazy/models/registry.py +93 -0
  30. lazy_tfm-0.1.0/src/lazy/models/tabfm.py +820 -0
  31. lazy_tfm-0.1.0/src/lazy/models/tabicl.py +382 -0
  32. lazy_tfm-0.1.0/src/lazy/models/tabpfn.py +458 -0
  33. lazy_tfm-0.1.0/src/lazy/plotting/__init__.py +62 -0
  34. lazy_tfm-0.1.0/src/lazy/plotting/diagnostics.py +718 -0
  35. lazy_tfm-0.1.0/src/lazy/plotting/paper.mplstyle +50 -0
  36. lazy_tfm-0.1.0/src/lazy/plotting/style.py +813 -0
  37. lazy_tfm-0.1.0/src/lazy/selection.py +474 -0
lazy_tfm-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Biprateep Dey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,406 @@
1
+ Metadata-Version: 2.4
2
+ Name: lazy-tfm
3
+ Version: 0.1.0
4
+ Summary: Lazy but Accurate z for Yinz: conditional distributions from tabular foundation models
5
+ Keywords: foundation model,tabular,in-context learning,probabilistic regression,conditional density estimation,photometric redshift,astronomy
6
+ Author: Biprateep Dey
7
+ Author-email: Biprateep Dey <biprateep@pitt.edu>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Dist: numpy>=1.26
19
+ Requires-Dist: scipy>=1.11
20
+ Requires-Dist: scikit-learn>=1.4
21
+ Requires-Dist: pandas>=2.1
22
+ Requires-Dist: matplotlib>=3.8
23
+ Requires-Dist: huggingface-hub>=0.24
24
+ Requires-Dist: h5py>=3.10
25
+ Requires-Dist: tqdm>=4.66
26
+ Requires-Dist: lazy-tfm[limix,tabfm,tabicl,tabpfn] ; extra == 'all'
27
+ Requires-Dist: torch>=2.9.1 ; extra == 'limix'
28
+ Requires-Dist: einops>=0.8 ; extra == 'limix'
29
+ Requires-Dist: kditransform>=1.0 ; extra == 'limix'
30
+ Requires-Dist: nvtx>=0.2 ; extra == 'limix'
31
+ Requires-Dist: qp-prob>=1.1 ; extra == 'qp'
32
+ Requires-Dist: tabfm[pytorch]>=1.0.1 ; extra == 'tabfm'
33
+ Requires-Dist: safetensors>=0.4.3 ; extra == 'tabfm'
34
+ Requires-Dist: tabicl>=2.2.0 ; extra == 'tabicl'
35
+ Requires-Dist: tabpfn>=9.0.0 ; extra == 'tabpfn'
36
+ Requires-Python: >=3.12
37
+ Project-URL: Source Code, https://github.com/biprateep/lazy-tfm
38
+ Project-URL: Documentation, https://lazy-tfm.readthedocs.io
39
+ Project-URL: Bug Tracker, https://github.com/biprateep/lazy-tfm/issues
40
+ Project-URL: Changelog, https://github.com/biprateep/lazy-tfm/blob/main/CHANGELOG.md
41
+ Provides-Extra: all
42
+ Provides-Extra: limix
43
+ Provides-Extra: qp
44
+ Provides-Extra: tabfm
45
+ Provides-Extra: tabicl
46
+ Provides-Extra: tabpfn
47
+ Description-Content-Type: text/markdown
48
+
49
+ # LAZY
50
+
51
+ **L**azy but **A**ccurate ***z***\* for **Y**inz — full conditional distributions
52
+ of a continuous target from pretrained tabular foundation models.
53
+
54
+ \*where *z* is whatever you want to predict from tabular features: a
55
+ redshift, a metallicity, a mass, a yield.
56
+
57
+ [![PyPI](https://img.shields.io/pypi/v/lazy-tfm)](https://pypi.org/project/lazy-tfm/)
58
+ [![Python](https://img.shields.io/pypi/pyversions/lazy-tfm)](https://pypi.org/project/lazy-tfm/)
59
+ [![Unit test and code coverage](https://github.com/biprateep/lazy-tfm/actions/workflows/testing-and-coverage.yml/badge.svg)](https://github.com/biprateep/lazy-tfm/actions/workflows/testing-and-coverage.yml)
60
+ [![Documentation](https://readthedocs.org/projects/lazy-tfm/badge/?version=latest)](https://lazy-tfm.readthedocs.io)
61
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/biprateep/lazy-tfm/blob/main/LICENSE)
62
+
63
+ The models are pretrained and never fine-tuned. You hand them labelled rows as
64
+ *context* and they answer queries in one forward pass — no training loop, no
65
+ hyper-parameter search, no per-dataset retraining. Hence lazy.
66
+
67
+ The package grew out of photometric redshifts, so its worked examples and
68
+ benchmark tools (the LSST DESC DC1 catalogue, the HSC spectroscopic selection,
69
+ the Data Challenge metrics) are photo-z's; the estimators work on any tabular
70
+ regression problem.
71
+
72
+ > **Pre-release.** The API is still moving and the paper numbers are not final.
73
+ > Pin a version if you depend on this.
74
+
75
+ **Documentation:** [lazy-tfm.readthedocs.io](https://lazy-tfm.readthedocs.io) ·
76
+ **Tutorial:** [open in Colab](https://colab.research.google.com/github/biprateep/lazy-tfm/blob/tutorials/introduction.ipynb) ·
77
+ **Changelog:** [CHANGELOG.md](https://github.com/biprateep/lazy-tfm/blob/main/CHANGELOG.md) · **Contributing:** [CONTRIBUTING.md](https://github.com/biprateep/lazy-tfm/blob/main/CONTRIBUTING.md)
78
+
79
+ ## Install
80
+
81
+ ```console
82
+ pip install lazy-tfm # grid, metrics, plots, datasets
83
+ pip install 'lazy-tfm[tabfm]' # + the TabFM backend
84
+ pip install 'lazy-tfm[tabicl]' # + the TabICLv2 backend
85
+ pip install 'lazy-tfm[tabpfn]' # + the TabPFN backend (v2 to v3.5)
86
+ pip install 'lazy-tfm[limix]' # + the LimiX-2 backend (its code installs separately)
87
+ pip install 'lazy-tfm[all]' # + all four
88
+ pip install 'lazy-tfm[qp]' # + qp interoperability, for RAIL
89
+ ```
90
+
91
+ Pretrained weights are not bundled: they are fetched from the Hugging Face Hub
92
+ at the first `fit` and cached thereafter. TabFM's classification checkpoint is
93
+ ~6.6 GB and carries a **non-commercial** licence from Google; TabICLv2's is
94
+ ~100 MB (BSD-3-Clause); the TabPFN checkpoints run from ~41 MB to ~880 MB and
95
+ are **non-commercial** from Prior Labs, except `v2`, which is Apache-2.0 with an
96
+ attribution clause; LimiX-2's is ~1.6 GB under Stable AI's Apache-2.0-based
97
+ licence, which requires the attribution "Built with StableAI LimiX". Every licence is quoted in `lazy.CHECKPOINTS`. Warm the
98
+ cache ahead of time with `lazy.download_checkpoint("tabfm")`.
99
+
100
+ ## Use
101
+
102
+ ```python
103
+ import lazy
104
+ from lazy import LazyModel, Grid
105
+ from lazy.datasets import fetch_dc1
106
+
107
+ train, test = fetch_dc1(split=True) # or fetch_dc1() for both, concatenated
108
+ X_train, X_test = train.features("mag-color"), test.features("mag-color")
109
+
110
+ X_test, z_test = X_test.iloc[:20_000], test.redshift[:20_000] # 391k in all
111
+
112
+ model = LazyModel() # TabPFN-3.5, the default; wants a GPU
113
+ model.fit(X_train, train.redshift)
114
+
115
+ pdfs = model.predict_proba(X_test, Grid.linear(0, 2, 200))
116
+ z = model.predict(X_test, method="z_peak") # or z_mean, z_weight, z_median
117
+ lo, med, hi = model.predict_quantiles(X_test, [0.16, 0.5, 0.84]).T
118
+ print(model.evaluate(X_test, z_test, lazy.DC1_GRID))
119
+ ```
120
+
121
+ No GPU? TabPFN is slow on a CPU and refuses contexts above 5,000 rows there;
122
+ `LazyModel("tabicl")` is small, BSD-licensed and quick on a laptop. The
123
+ [supported models](https://lazy-tfm.readthedocs.io/en/latest/guide/models.html)
124
+ page compares them all.
125
+
126
+ `LazyModel(name, ...)` picks the backend by name, so switching is a string
127
+ change; the concrete classes (`TabPFNBarDistribution`, `LimiXBarDistribution`,
128
+ `TabICLQuantile`, `TabFMHistogram`) are importable and identical. The API is scikit-learn's, except `predict_proba` returns a
129
+ density on a grid of the target rather than class probabilities — the natural
130
+ output of a model that knows its own uncertainty. `get_params`/`set_params`/`clone` work, so models drop
131
+ into scikit-learn pipelines and search objects unmodified.
132
+
133
+ **`z_grid` belongs to the prediction, not the fit.** Nothing about fitting
134
+ depends on the output binning — these models place their internal bins by the
135
+ distribution of the context targets, and the grid only enters at the final exact
136
+ rebinning step — so one fitted model answers on as many grids as you like
137
+ without refitting:
138
+
139
+ ```python
140
+ import numpy as np
141
+
142
+ model.predict_proba(X_test) # the native grid
143
+ model.predict_proba(X_test, Grid.linear(0, 3, 300))
144
+ model.predict_proba(X_test, np.linspace(0.005, 2.995, 300)) # or bin centres
145
+ ```
146
+
147
+ With no grid a model answers on its *native* grid, the one it thinks in (for
148
+ TabPFN and LimiX-2, their 5,000 buckets), so nothing is lost to rebinning.
149
+ `predict_distribution(X)` returns that native answer itself, with exact
150
+ `pdf`/`cdf`/`ppf`/`mean`/`interval`/`rvs` in the vocabulary of scipy.stats and
151
+ LSST DESC's qp, and `.to_qp()` for RAIL.
152
+
153
+ | Backend | Method | Versions | Weights |
154
+ | -------- | ---------------------------------------------------------------- | -------- | -------- |
155
+ | `tabpfn` | Bucket masses of the bar distribution | `v2` … `v3.5` | 41 MB – 880 MB |
156
+ | `limix` | Bucket masses of LimiX-2's 5,000-bucket head | `v2` | ~1.6 GB |
157
+ | `tabicl` | Quantiles of an in-context regression head | `v2` | ~100 MB |
158
+ | `tabfm` | Hierarchy of in-context classifiers over equal-mass bins of the target | `v1.0` | ~6.6 GB |
159
+
160
+ **One set of parameters, on every model.** `kv_cache` (process the context
161
+ once, at fit; exact; on by default), `n_estimators`, `feature_shuffle`,
162
+ `transforms` (a shared vocabulary — `power`, `quantile`, `robust`, … — plus
163
+ recipes such as `"limix"`; `"auto"` keeps each model's own), `bag_size`
164
+ (per-member subsets of the context) and `z_grid` mean the same thing on every
165
+ backend. Each is translated to the model's own machinery where it has it and
166
+ built around the model where it does not, and registering a new backend
167
+ without them fails. LimiX-2 degrades above ~20,000 context rows and warns when
168
+ a larger context arrives without `bag_size`.
169
+
170
+ Memory is bounded by `chunk_size` on every backend, and chunking is exact: a
171
+ query row's answer never depends on which other query rows share its chunk
172
+ (bit for bit on TabPFN and TabICL, to float rounding on LimiX-2).
173
+
174
+ ### For TabFM, install its repository build
175
+
176
+ `pip install 'lazy-tfm[tabfm]'` installs TabFM's PyPI release, which works
177
+ but has no KV-cache API: every chunk of query rows re-encodes the whole training
178
+ context, about **13.7 ms per member-row against 0.53 ms** on the cached path,
179
+ roughly **26× the compute** for identical answers. For anything beyond a few
180
+ thousand query rows, install the repository build as well:
181
+
182
+ ```bash
183
+ pip install 'lazy-tfm[tabfm]'
184
+ pip install --force-reinstall --no-deps 'tabfm[pytorch] @ git+https://github.com/google-research/tabfm@fbb665569425fd2f490c6576b3af967876fe11ff'
185
+ ```
186
+
187
+ `--force-reinstall` matters: the repository build calls itself 1.0.1, like the
188
+ release, so without it pip keeps the release and changes nothing.
189
+ A checkout of this repository gets the repository build automatically (`uv sync
190
+ --extra all`), pinned through `[tool.uv.sources]`. On the release build the
191
+ backend warns at `fit` with a `TabFMPerformanceWarning` rather than silently
192
+ taking 26× longer. To check which you have:
193
+
194
+ ```python
195
+ from lazy.models._icl_stream import streaming_available
196
+ streaming_available() # True means the fast path is in use
197
+ ```
198
+
199
+ Pass `kv_cache=False` to choose the slow path deliberately and silence the
200
+ warning.
201
+
202
+ ### For LimiX-2, install its code
203
+
204
+ LimiX is not on PyPI. The `limix` extra brings its dependencies; the code
205
+ comes from the repository, at the commit this package was validated on (or
206
+ from a checkout named by `LAZY_LIMIX_SRC`):
207
+
208
+ ```bash
209
+ pip install 'lazy-tfm[limix]'
210
+ pip install 'LimiX @ git+https://github.com/limix-ldm-ai/LimiX@516bf396333feb3198cf7aff8a6c10421f218e24'
211
+ ```
212
+
213
+ Upstream LimiX-2 has no key/value cache and its answers depend on how the
214
+ queries are chunked; the backend runs its network with a ported cache,
215
+ context-only preprocessing and a dedicated random generator, which removes
216
+ both. LimiX's network needs `triton`, so the backend is Linux-only for now.
217
+ Built with StableAI LimiX.
218
+
219
+ They all write onto any `Grid` you ask for — any number of bins, any
220
+ spacing, any range. (TabFM's ten-class ceiling constrains its internal
221
+ hierarchy, never your output grid.)
222
+
223
+ ### Progress bars
224
+
225
+ Every backend draws a `tqdm` bar while it predicts, counting whatever that
226
+ model's loop actually iterates over. For 20,000 DC1 test galaxies on a
227
+ 1,000-galaxy context (one GB10 GPU):
228
+
229
+ ```
230
+ TabPFN v3.5: 100%|██████████| 20000/20000 [00:15<00:00, 1279.46row/s, buckets=5000, context=1000]
231
+ TabICL v2: 100%|██████████| 20000/20000 [00:10<00:00, 1968.82row/s, context=1000, quantiles=999]
232
+ TabFM v1.0 (20,000 rows): 100%|██████████| 33/33 [08:47<00:00, 16.00s/stage, context=34, dither=3/3, level=fine 10/10]
233
+ ```
234
+
235
+ TabPFN, LimiX and TabICL answer in chunks of query rows, so their bars count
236
+ rows. TabFM is a hierarchy of in-context classifications, each over every
237
+ query row, so its bar counts *stages* — `n_dither × (1 + n_coarse_bins)` of
238
+ them — and shows which dither and level is running on how many context rows.
239
+
240
+ `progress="auto"` (the default) shows the bar on a terminal or in a notebook and
241
+ stays silent when output goes to a file, so batch logs stay clean. Pass
242
+ `progress=True` to force it on or `progress=False` to turn it off. The bar goes
243
+ to stderr; `verbose=True` log messages go to stdout.
244
+
245
+ ## The harder benchmark: a biased training set
246
+
247
+ DC1 hands every training galaxy a redshift. Real spectroscopic samples do not
248
+ look like that — they are bright, incomplete, and cut in redshift by which
249
+ features fall in the observed window — and that mismatch, not the estimator, is
250
+ usually what limits a survey's redshifts. `fetch_dc1_biased` merges both DC1
251
+ files and cuts the realistic case out of them:
252
+
253
+ ```python
254
+ from lazy.datasets import fetch_dc1_biased
255
+
256
+ split = fetch_dc1_biased() # 35,011 biased / 10,000 calibration / 19,383 test
257
+ split.biased # what a HSC-like campaign would have got
258
+ split.calibration # representative, yours to repair the model with
259
+ split.test # representative, held out from all of it
260
+ print(split.summary()) # each subset against the parent catalogue
261
+ ```
262
+
263
+ Both files are merged and reshuffled, then cut in two: the first part is run
264
+ through the selection and what it keeps is the training set, the second is
265
+ divided at random into the calibration sample and the test set. Where the cut
266
+ falls is solved for, not chosen, because the selection keeps a near-fixed
267
+ fraction of whatever it sees — `n_train` is the knob. The calibration sample
268
+ comes *out* of the hold-out rather than on top of it, so no galaxy is both given
269
+ to a model and scored on.
270
+
271
+ `n_train` cannot reach the DC1 training file's 43,486: the selection keeps 8.66
272
+ per cent of a representative sample, so all 434,476 galaxies yield at most
273
+ 37,626, and that with nothing left to test on. 35,000 is the largest round
274
+ number that still leaves a usable hold-out.
275
+
276
+ `control=True` adds `split.unbiased`, exactly as many galaxies drawn at random
277
+ from the same pool — the control that separates the selection from the sample
278
+ size. It is drawn last, so asking for it changes nothing else in the split.
279
+
280
+ The selection is a port of RAIL's HSC `GridSelection`: each galaxy is kept with
281
+ the HSC spectroscopic success rate of its (i, g−z) pixel, below a
282
+ colour-dependent redshift ceiling. It is reproduced galaxy-for-galaxy against
283
+ the runs this work reports. It also acts on any photometry, so a catalogue of
284
+ your own can be biased the same way:
285
+
286
+ ```python
287
+ from lazy.selection import grid_selection, selection_summary
288
+
289
+ keep, diagnostics = grid_selection(catalog.raw, catalog.redshift)
290
+ print(selection_summary(keep, catalog.raw["I"], catalog.redshift))
291
+ # i in [16.0, 20.0) ... fraction 0.661
292
+ # i in [24.0, 25.3) ... fraction 0.0005
293
+ ```
294
+
295
+ `diagnostics` carries each galaxy's pixel success rate and redshift ceiling —
296
+ everything the selection knew, for a method that tries to estimate it back.
297
+
298
+ ## Which model, exactly
299
+
300
+ A backbone is a family, not a model, so every backend takes a `version` and each
301
+ version is a separately pinned checkpoint:
302
+
303
+ ```python
304
+ lazy.list_versions("tabpfn")
305
+ # ['v2', 'v2.5', 'v2.6', 'v3', 'v3.5', 'v3.5-fast']
306
+
307
+ model = LazyModel("tabpfn", version="v2.5")
308
+ model.name_ # 'tabpfn:v2.5' -- also the label evaluate() puts on its row
309
+ ```
310
+
311
+ Sweeping `version` compares model versions on equal terms, and a results table
312
+ says which one produced each row rather than only which family. What actually
313
+ answered is recorded on the fitted model, ready to be written out beside the
314
+ numbers:
315
+
316
+ ```python
317
+ model.fit(X_train, z_train).provenance_
318
+ # {'backend': 'tabpfn', 'version': 'v2.5',
319
+ # 'repo_id': 'Prior-Labs/tabpfn_2_5',
320
+ # 'filename': 'tabpfn-v2.5-regressor-v2.5_default.ckpt',
321
+ # 'revision': '6c45f3a6d0d07c6c5f62572e04a0c2929de91b8b',
322
+ # 'package': 'tabpfn 9.0.0', 'lazy': 'lazy-tfm 0.1.0', 'device': 'cuda',
323
+ # 'n_estimators': 8, 'transforms': ['auto', ...], 'kv_cache': True, ...}
324
+ ```
325
+
326
+ It holds the weights, the code that read them and the ensemble that answered,
327
+ and no local paths, so it means the same thing on another machine and
328
+ survives a trip through JSON. The rest of the settings are in `get_params()`. `CHECKPOINTS` is keyed
329
+ `"backend:version"` and is the authority on what each name loads;
330
+ `download_checkpoint("tabpfn", "v2.5")` warms exactly that one.
331
+
332
+ Versions are not interchangeable. TabPFN `v2` is pretrained for at most 10,000
333
+ context rows and `v2.5` for 50,000, against a million for `v3`; `v2` is also the
334
+ only one under a commercial-use licence. `lazy.get_checkpoint(name, version)`
335
+ carries both notes.
336
+
337
+ ## Caches and offline use
338
+
339
+ Nothing platform-specific is baked in: the wheel is pure Python
340
+ (`py3-none-any`), the lockfile resolves for macOS/Linux/Windows on Python
341
+ 3.12–3.14, and the device is chosen at run time (`device="auto"`).
342
+
343
+ | Cache | Default | Override |
344
+ | ----- | ------- | -------- |
345
+ | Pretrained weights | `~/.cache/huggingface/hub` | `HF_HOME` |
346
+ | Benchmark catalogues | `~/.cache/lazy-tfm` | `LAZY_DATA_HOME`, else `XDG_CACHE_HOME` |
347
+
348
+ For a compute node with no network, warm both on a login node first, then run
349
+ with `download_checkpoint`/`is_cached` and `fetch_dc1(..., download_if_missing=False)`
350
+ so a missing file fails immediately instead of hanging. The ~14 MB HSC selection
351
+ grid caches beside the catalogues and `fetch_hsc_grid` takes the same flag. The
352
+ checkpoint a model loads is the one `download_checkpoint` fetches — the pinned revision is handed
353
+ to the backend rather than left to its default.
354
+
355
+ Memory is bounded by default on every backend via `chunk_size` (8,192 or
356
+ 16,384 query rows), and chunking is exact, so the default costs nothing.
357
+
358
+ ## What's in the box
359
+
360
+ | Module | Contents |
361
+ | ---------------- | ---------------------------------------------------------------------------- |
362
+ | `lazy.models` | `LazyModel`, the concrete backends, and the name registry |
363
+ | `lazy.grid` | `Grid`: binning, normalisation, mass-conserving rebinning |
364
+ | `lazy.metrics` | LSST DESC PZ Data Challenge point and PDF metrics, and `summarize` |
365
+ | `lazy.selection` | The HSC spectroscopic selection function, for biasing a catalogue of your own |
366
+ | `lazy.plotting` | Publication figure style, and the standard diagnostic figures |
367
+ | `lazy.datasets` | The DC1 catalogue (download, checksum, cache), `Catalog.features`, `fetch_dc1_biased` |
368
+
369
+ ## Develop
370
+
371
+ ```console
372
+ git clone https://github.com/biprateep/lazy-tfm
373
+ cd lazy-tfm
374
+ uv sync # locked environment, including dev tooling
375
+ uv run pre-commit install
376
+ uv run pytest
377
+ ```
378
+
379
+ The test suite needs neither a GPU nor a checkpoint. See
380
+ [CONTRIBUTING.md](https://github.com/biprateep/lazy-tfm/blob/main/CONTRIBUTING.md) for the checks CI runs, building the docs and
381
+ making a release.
382
+
383
+ ## Repository layout
384
+
385
+ This repository is the library alone: the public, installable package, its
386
+ tests and its documentation. The production runs and figures behind the paper
387
+ live in a separate repository that uses this package through its public API.
388
+
389
+ ## Citing
390
+
391
+ A paper is in preparation. Until it appears, cite the repository and the
392
+ checkpoint revisions recorded in `lazy.CHECKPOINTS` — a foundation model's
393
+ weights are part of the method. The `provenance_` of a fitted model is that
394
+ citation for the run you actually did.
395
+
396
+ Built from the
397
+ [LINCC Frameworks Python Project Template](https://github.com/lincc-frameworks/python-project-template).
398
+
399
+ ## License
400
+
401
+ MIT, for this code. The pretrained checkpoints carry their own licences; TabFM's
402
+ is non-commercial, and LimiX-2's requires the attribution "Built with StableAI
403
+ LimiX". The HSC selection grid is redistributed by DESC's
404
+ [rail_astro_tools](https://github.com/LSSTDESC/rail_astro_tools) (MIT) and
405
+ derives from HSC PDR2 (Aihara et al. 2019); `lazy.selection` downloads it from
406
+ there, at a pinned commit, rather than bundling it.