Overview
import autolens costs ~4.1 s (full example-script stanza ~4.4 s), and profiling shows ~2.5 s of it is heavy packages loaded eagerly but not needed at import: the jax chain (nufftax + blackjax + optax, 1.75 s — 43%, measured by control test), IPython, sqlalchemy, numba (decoration-time), and astropy. All 457 autolens_workspace scripts pay this per-process (~30 min of pure import per full serial smoke sweep), and smoke runs with JAX disabled, so its jax import there is pure waste. This task defers each of these to first use across PyAutoFit, PyAutoArray and PyAutoNerves, targeting import autolens ≤ ~1.9 s with zero public-API change.
Plan
- Defer the jax chain: lazy
af.NSS (PEP 562) in PyAutoFit; lazy nufftax load + batcher patch in PyAutoArray's TransformerNUFFT.
- Defer IPython, sqlalchemy (lazy
sa proxy + annotation fixes), and the eager autofit.database pulls in PyAutoFit.
- Defer numba to first call via a lazy-compile wrapper in
autoarray.numba_util.jit (fixes all 29 decorated functions centrally).
- Defer astropy in
autonerves.fitsable; dedupe the workspace-version warning (currently fires 3× per process).
- Verify: import-time absence proof, full suites in all three repos, NSS / NUFFT / aggregator behaviour runs, targeted workspace smoke incl.
# ENV: jax scripts.
- Scoped out: matplotlib deferral (enters via the autolens/autogalaxy visualizer chain; every workspace script imports
aplt anyway, so no smoke/user win — optional follow-up for bare-library import only).
Detailed implementation plan
Affected Repositories
- PyAutoFit (primary)
- PyAutoArray
- PyAutoNerves
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoFit |
main |
clean |
| ./PyAutoArray |
main |
clean |
| ./PyAutoNerves |
main |
clean |
Suggested branch: feature/lazy-heavy-imports
Note: PyAutoFit is also claimed by stored-sample-reconstruction-guard (#1486, library half shipped as PR#1504) — deliberate file-disjoint override, recorded in Mind active.md. The version-stamp-sync-guards branch also touches autofit/__init__.py (version-stamp lines); coordinate at merge if both are in flight.
Implementation Steps (PyAutoFit, ~0.9 s)
autofit/__init__.py — remove eager imports of NSS (L98), GridSearchAggregator (L15), Aggregator (L37), Query (L42); add PEP 562 module __getattr__ resolving these four lazily, caching in globals() (pattern: autolens/__init__.py:165-181). NSS stays fully supported — lazy, never removed.
autofit/non_linear/fitness.py — move from IPython.display import clear_output (L3) into the single use site (~L439, quick-update branch).
autofit/database/sqlalchemy_.py — make sa/declarative lazy proxies: __getattr__ imports sqlalchemy on first attribute access and caches; fall back to the existing MockSQlAlchemy on ImportError.
- Add
from __future__ import annotations to modules evaluating sa.orm.Session in signatures at def time (Py3.12): non_linear/settings.py, search/mcmc/abstract_mcmc.py, search/nest/abstract_nest.py, search/nest/nss/search.py, search/nest/nautilus/search.py, plus a full per-file sweep of every sqlalchemy_ importer. Verify no eager __annotations__ introspection in these modules.
autofit/non_linear/paths/database.py — defer Fit and Info imports to function-local at their use sites (~L112, L236-243, L345).
autofit/non_linear/grid/grid_search/result_builder.py — from __future__ import annotations + move from autofit.database import Prior (annotation-only) under if TYPE_CHECKING.
Implementation Steps (PyAutoArray, ~0.75 s)
autoarray/operators/transformer.py — replace module-level nufftax try-import (L26-29) and version-gated _patch_nufftax_batchers() call (L90-94) with an idempotent _load_nufftax() (import, 0.6.x check, batcher patch — a global JAX registration that must precede the first traced call) invoked at the top of TransformerNUFFT.__init__ (existing _nufftax is None guard at ~L646). All other _nufftax uses are in TransformerNUFFT methods.
autoarray/numba_util.py — jit() returns a wrapper that on first call imports numba (falling back to the plain function on ModuleNotFoundError), compiles with the same nopython/cache/parallel/fastmath options, caches, delegates. Sweep for attribute access on decorated functions (.py_func etc.) first.
Implementation Steps (PyAutoNerves, ~0.17 s + noise)
autonerves/fitsable.py — remove module-level astropy try-import (L12-15, keep TYPE_CHECKING block); lazy _fits() accessor used at the six runtime sites (~L63, 86, 88, 90, 210, 238).
autonerves/workspace.py — module-level _warned_messages set; the three warnings.warn sites (~L227, 237, 257) skip byte-identical repeats, so import autolens warns once, not three times. The direct-call tests use distinct tmp_path roots — unaffected.
Key Files
PyAutoFit/autofit/__init__.py — lazy NSS/Aggregator/Query/GridSearchAggregator
PyAutoFit/autofit/database/sqlalchemy_.py — lazy sa proxy
PyAutoArray/autoarray/operators/transformer.py — lazy nufftax + batcher patch
PyAutoArray/autoarray/numba_util.py — lazy-compile jit wrapper
PyAutoNerves/autonerves/fitsable.py, PyAutoNerves/autonerves/workspace.py
Verification
- Timing: 3× cold
import autolens before/after; target ≤ ~1.9 s (matplotlib retained by design).
- Absence proof:
python -X importtime -c "import autolens" shows no jax/jaxlib/blackjax/optax/nufftax/IPython/sqlalchemy/numba/astropy.
- Behaviour:
af.NSS/af.Aggregator/af.Query/af.GridSearchAggregator resolve; run autofit_workspace_test/scripts/searches/NSS.py, an interferometer TransformerNUFFT script, a database/aggregator flow.
- Full pytest suites in PyAutoFit, PyAutoArray, PyAutoNerves; downstream autogalaxy/autolens suites via ship_library;
unittest-nojax leg stays green.
- Targeted workspace smoke incl.
# ENV: jax scripts before PRs open.
Follow-up (not this task)
- Brain RefactorDecision flags pyautonerves as "unwitnessed" — its witness map lacks
test_autonerves; fix the map.
- Optional: matplotlib deferral for bare-library import (unit-test startup), via the autolens/autogalaxy visualizer chain.
Original Prompt
Click to expand starting prompt
Can you do a profiling of how long import times on autolens_workspace
example scripts and performance in general is, and assess if we can speed
it up as it is important for running smoke tests and general user
performance being fast
Full measured profile in PyAutoMind/active/import_time_lazy_heavy_imports.md.
Overview
import autolenscosts ~4.1 s (full example-script stanza ~4.4 s), and profiling shows ~2.5 s of it is heavy packages loaded eagerly but not needed at import: the jax chain (nufftax + blackjax + optax, 1.75 s — 43%, measured by control test), IPython, sqlalchemy, numba (decoration-time), and astropy. All 457 autolens_workspace scripts pay this per-process (~30 min of pure import per full serial smoke sweep), and smoke runs with JAX disabled, so its jax import there is pure waste. This task defers each of these to first use across PyAutoFit, PyAutoArray and PyAutoNerves, targetingimport autolens≤ ~1.9 s with zero public-API change.Plan
af.NSS(PEP 562) in PyAutoFit; lazy nufftax load + batcher patch in PyAutoArray'sTransformerNUFFT.saproxy + annotation fixes), and the eagerautofit.databasepulls in PyAutoFit.autoarray.numba_util.jit(fixes all 29 decorated functions centrally).autonerves.fitsable; dedupe the workspace-version warning (currently fires 3× per process).# ENV: jaxscripts.apltanyway, so no smoke/user win — optional follow-up for bare-library import only).Detailed implementation plan
Affected Repositories
Branch Survey
Suggested branch:
feature/lazy-heavy-importsNote: PyAutoFit is also claimed by
stored-sample-reconstruction-guard(#1486, library half shipped as PR#1504) — deliberate file-disjoint override, recorded in Mindactive.md. Theversion-stamp-sync-guardsbranch also touchesautofit/__init__.py(version-stamp lines); coordinate at merge if both are in flight.Implementation Steps (PyAutoFit, ~0.9 s)
autofit/__init__.py— remove eager imports ofNSS(L98),GridSearchAggregator(L15),Aggregator(L37),Query(L42); add PEP 562 module__getattr__resolving these four lazily, caching inglobals()(pattern:autolens/__init__.py:165-181). NSS stays fully supported — lazy, never removed.autofit/non_linear/fitness.py— movefrom IPython.display import clear_output(L3) into the single use site (~L439, quick-update branch).autofit/database/sqlalchemy_.py— makesa/declarativelazy proxies:__getattr__imports sqlalchemy on first attribute access and caches; fall back to the existingMockSQlAlchemyon ImportError.from __future__ import annotationsto modules evaluatingsa.orm.Sessionin signatures at def time (Py3.12):non_linear/settings.py,search/mcmc/abstract_mcmc.py,search/nest/abstract_nest.py,search/nest/nss/search.py,search/nest/nautilus/search.py, plus a full per-file sweep of everysqlalchemy_importer. Verify no eager__annotations__introspection in these modules.autofit/non_linear/paths/database.py— deferFitandInfoimports to function-local at their use sites (~L112, L236-243, L345).autofit/non_linear/grid/grid_search/result_builder.py—from __future__ import annotations+ movefrom autofit.database import Prior(annotation-only) underif TYPE_CHECKING.Implementation Steps (PyAutoArray, ~0.75 s)
autoarray/operators/transformer.py— replace module-level nufftax try-import (L26-29) and version-gated_patch_nufftax_batchers()call (L90-94) with an idempotent_load_nufftax()(import, 0.6.x check, batcher patch — a global JAX registration that must precede the first traced call) invoked at the top ofTransformerNUFFT.__init__(existing_nufftax is Noneguard at ~L646). All other_nufftaxuses are inTransformerNUFFTmethods.autoarray/numba_util.py—jit()returns a wrapper that on first call imports numba (falling back to the plain function onModuleNotFoundError), compiles with the same nopython/cache/parallel/fastmath options, caches, delegates. Sweep for attribute access on decorated functions (.py_funcetc.) first.Implementation Steps (PyAutoNerves, ~0.17 s + noise)
autonerves/fitsable.py— remove module-level astropy try-import (L12-15, keep TYPE_CHECKING block); lazy_fits()accessor used at the six runtime sites (~L63, 86, 88, 90, 210, 238).autonerves/workspace.py— module-level_warned_messagesset; the threewarnings.warnsites (~L227, 237, 257) skip byte-identical repeats, soimport autolenswarns once, not three times. The direct-call tests use distincttmp_pathroots — unaffected.Key Files
PyAutoFit/autofit/__init__.py— lazy NSS/Aggregator/Query/GridSearchAggregatorPyAutoFit/autofit/database/sqlalchemy_.py— lazysaproxyPyAutoArray/autoarray/operators/transformer.py— lazy nufftax + batcher patchPyAutoArray/autoarray/numba_util.py— lazy-compile jit wrapperPyAutoNerves/autonerves/fitsable.py,PyAutoNerves/autonerves/workspace.pyVerification
import autolensbefore/after; target ≤ ~1.9 s (matplotlib retained by design).python -X importtime -c "import autolens"shows no jax/jaxlib/blackjax/optax/nufftax/IPython/sqlalchemy/numba/astropy.af.NSS/af.Aggregator/af.Query/af.GridSearchAggregatorresolve; runautofit_workspace_test/scripts/searches/NSS.py, an interferometerTransformerNUFFTscript, a database/aggregator flow.unittest-nojaxleg stays green.# ENV: jaxscripts before PRs open.Follow-up (not this task)
test_autonerves; fix the map.Original Prompt
Click to expand starting prompt
Full measured profile in
PyAutoMind/active/import_time_lazy_heavy_imports.md.