You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Successor to #505. #505 took the HST rectangular numba likelihood evaluation from 1.56 s to 0.60 s by
routing the mapper x linear-func block of the curvature matrix F through the batched FFT Convolver.
The post-#505 split is now mapper x mapper 0.277 s (46%), MGE operated mapping matrix ~0.22 s
(37%), everything else <= 0.05 s. Phase 2 attacks those two blocks, targeting <= ~0.35 s/eval
with the log-likelihood unchanged to pinned tolerance.
Blocked until #505 merges — phase 2 edits the same files (sparse.py, inversion_imaging_numba_util.py, the two autolens_profiling breakdown harness scripts, the results
note). Branch from main once #505 is in. Registered in PyAutoMind/planned.md as blocked.
Plan
Step 0 — instrument first (checkpoint gates everything after). Split the MGE operated mapping matrix harness row into three timed sub-rows and record the geometry constants into the JSON, so the
lever is chosen from a measurement rather than an estimate.
Step 1 — mapper x mapper, bit-identical restructuring. Write the oracle test the kernel does not
have today, then hoist the re-gathered (pix, weight) pairs into locals. Estimated 10-20%.
Step 2 — mapper x mapper, two-stage reformulation. Per-data-pixel dense source-space accumulator
followed by contiguous AXPYs, replacing ~2e8 irregular read-modify-writes into a 4.9 MB matrix with
~5e7 L1 scatters plus vectorisable dense adds. Estimated 1.7-2.5x on the block (0.277 -> 0.11-0.16 s),
at rtol=1e-6.
Step 3 — MGE operated matrix (gated by the step-0 split). A small bit-identical OverSampler
divisor cache in PyAutoArray, plus a shared-geometry fast path in PyAutoGalaxy for LightProfileLinearObjFuncList — the 60 MGE Gaussians share centre/ell_comps and differ only in sigma, so the transform and eccentric-radius grid are recomputed 120x per evaluation. Estimated
MGE 0.22 -> ~0.10-0.13 s.
Step 4 — measure and ship. Paired same-session before/after (alternate B/A/B/A; host variance is
20-30%), three pins at explicit rtol=1e-6 plus a curvature_matrix array comparison, a Nautilus
pool run, note appended in autolens_profiling, then ship_library PyAutoArray -> ship_library PyAutoGalaxy -> ship_workspace autolens_profiling.
Two findings from the planning read-through shaped this: the MGE half of the cost lives in PyAutoGalaxy, not PyAutoArray (the Convolver batching from #505 is already applied there); and two
of the four mapper x mapper candidate levers are dead on arrival — upper-triangle symmetry is already
exploited (the sparse preload stores ip1 >= ip0 and the kernel folds A + A^T) and unique-mappings
compression is already what the kernel iterates over. The live lever is the two-stage reformulation.
Detailed implementation plan
Affected Repositories
PyAutoArray (primary)
PyAutoGalaxy
autolens_profiling
Branch Survey
Repository
Current Branch
Dirty?
./PyAutoArray
main
clean
./PyAutoGalaxy
main
clean
./autolens_profiling
main
clean
worktree_check_conflict reports PyAutoArray and autolens_profiling are both claimed by the in-flight
task numba-hst-curvature-matrix-speedup (#505), and autolens_profiling additionally by nuts-warm-start-driver-and-a100-probe. Hence: queued in planned.md, not started.
Files: autolens_profiling/scripts/imaging/likelihood_breakdown/{pixelization_numba,delaunay_numba}.py
(keep STEP_ACCESSORS in sync as #505 did).
Split the MGE operated mapping matrix row into 3 timed sub-rows — 60x image_2d_from(grid),
60x image_2d_from(blurring_grid), 1x psf.convolved_mapping_matrix_via_real_space_np_from(...) —
with the existing row becoming the residual (same scheme as the F rows; the override is a cached_property on the func object so sub-timings do not prime it; the four rows must sum).
Record geometry constants once into the JSON: sparse_operator.lengths.sum()/mean()/max(), unique_mappings.pix_lengths.mean()/max(), data_to_pix_unique.shape, mapper.params, n_image_pixels, PSF shape.
Do not split mapper x mapper further (single mapper -> one kernel call; fold loops are
n_source^2, noise).
Run hst/euclid rectangular + hst Delaunay-1250 (OMP_NUM_THREADS=1 AUTOARRAY_NUMBA_OPERATED_MEMO=0, n_repeats 10); post the split to this issue. Checkpoint: confirms (a) profile-eval vs convolution share of MGE, (b) the mapper x mapper op count.
Step 1 — mapper x mapper, bit-identical restructuring (own commit)
Write the oracle first — it has no direct unit test today. New test__curvature_matrix_via_sparse_operator_from__matches_dense_psf_precision_operator in test_autoarray/inversion/inversion/imaging/test_inversion_imaging_util.py: dense M^T W M vs the
kernel, parametrised over KERNELS_ODD (asymmetric, non-square). Plus a symmetry / halved-diagonal
test. Keep the current kernel as curvature_matrix_via_sparse_operator_reference_from.
Hoist data_1's <= u1 (pix, weight) pairs into locals once per stored pair (today re-gathered u0x
from wide-stride 2-D arrays) and take curvature_matrix[pix_0] as a row view. Estimated 10-20%.
Verify np.array_equal on inversion.curvature_matrix before/after (euclid + hst).
For each data_0: accumulate the dense a[p] = sum_{data_1} W(data_0,data_1) * sum_b w1_b delta(pix1_b, p) into an L1-resident n_source vector, then A[pix_0,:] += w0 * a for each of data_0's u0 mappings; keep the A + A^T fold and the halved-diagonal contract.
Valid while n_source <~ P * u1 (~3400 at HST; 784/1250 well inside). Keep the reference loop behind
a runtime branch on pix_pixels; measure the crossover, and if it is dataset-dependent expose it
as an explicit setting rather than a silent heuristic.
Tests: ..._two_stage_matches_reference_kernel parametrised over KERNELS_ODD x pix_pixels on both
sides of the threshold; inversion-level test__curvature_matrix_mapper_diag__matches_reference_kernel
in test_curvature_matrix_func_list_blocks.py; the multi-mapper twin curvature_matrix_off_diags_via_sparse_operator_from gets its own oracle if touched. Every rtol test
gets a recorded control run (a deliberately wrong variant must fail).
prange: not planned. Zero gain under Nautilus one-process-per-core, and it needs per-thread
n_source^2 buffers plus a lazily cached prefix-offset array on SparseLinAlgImagingNumba (pickled to
workers — no new ctor args). Only if step 2 misses target, default off, both arms measured.
Rejected: pruning small psf_precision_operator entries (moves the likelihood; fails the pin).
Step 3 — MGE operated matrix (gated by the step-0 split)
PyAutoArray, small, bit-identical:OverSampler.binned_array_2d_from
(autoarray/operators/over_sampling/over_sampler.py ~:202) recomputes the model-independent np.bincount(segment_ids) divisor on every one of ~120 calls/eval. Cache it as an immutable cached_property (today the code mutates counts in place — the cached array must not be mutated).
Wider blast radius (every numpy light-profile eval) -> np.array_equal test across repeated calls
plus the harness Blurred image row as witness.
PyAutoGalaxy: shared-geometry fast path in LightProfileLinearObjFuncList
(autogalaxy/profiles/light/linear/abstract.py, mapping_matrix ~:291, operated_mapping_matrix_override ~:320): when all profiles are the same class with identical centre/ell_comps, compute the transform and eccentric radii once per grid and evaluate the 60
Gaussians vectorised over sigma; binning stays a loop initially (binned_array_2d_from is 1-D).
Fall back to the current loop otherwise. Tests: ..._shared_geometry_matches_per_profile_loop
(assert bit-identical first; relax to rtol 1e-6 only with a recorded reason) and ..._mixed_geometry_falls_back. Estimated MGE 0.22 -> ~0.10-0.13 s.
Not pursued: reusing ConvolverState.fft_kernel on the numpy path (scipy already avoids the 3-D FFT;
single-digit %; a 10-line microbench only), and a narrower memo keyed on centre+ell_comps (weaker
than the hoist).
Step 4 — measure + ship
Paired same-session before/after (host variance 20-30%; alternate B/A/B/A), three paired cells + RTU
once for currency (unpaired, GPU-only by decision). Discard the first post-rebuild run (numba cache: true; documented cold-cache psf_weighted_data_from hazard).
Pins at explicit rtol=1e-6 (the harness default is 1e-4) plus a curvature_matrix array comparison.
Pool run: cpu_fast_modeling.py with PYAUTO_TEST_MODE=1, PYAUTO_SMALL_DATASETS unset, cores =
host, plus a linear MGE bulge (the smoke profile runs no pool); compare the parallel speed-up ratio.
Note section appended to autolens_profiling/results/notes/numba_curvature_matrix_f_split.md.
PyAutoGalaxy: autogalaxy/profiles/light/linear/abstract.py, autogalaxy/profiles/light/standard/gaussian.py, autogalaxy/profiles/geometry_profiles.py,
new tests under test_autogalaxy/profiles/light/linear/
pytest test_autoarray (baseline 1296) and pytest test_autogalaxy green; the oracle test passes on the
unmodified kernel before any change; control runs recorded for every rtol test; bit-identical curvature_matrix for step 1 and the OverSampler change; three pins at rtol 1e-6 (hst rectangular
27661.910133664103, hst-rtu 27180.704715696862, hst-delaunay 29090.52721044813); smoke cpu_fast_modeling.py; paired before/after table + pool ratio in the note and on this issue.
Marked undetermined (step 0 measures these, never guessed)
The MGE internal split; the actual psf_precision_lengths.sum() / pix_lengths.mean(); whether LLVM
already hoists the scalar product; the two-stage crossover n_source; the numba threading layer on WSL
(only if prange ever runs).
Original Prompt
Click to expand starting prompt
Numba CPU likelihood at HST resolution, phase 2: the mapper×mapper block and the MGE operated matrix
profiling
Difficulty: large
Autonomy: supervised
Priority: high
Status: draft
Filed: 2026-08-28
Original request
yeah do that phase 2
(in reply to: "What's left for a phase 2, if you want one: mapper×mapper (0.28 s) and the MGE
operated matrix (0.22 s) now lead at HST.")
Context
PyAutoArray#505 (branch feature/numba-hst-curvature-matrix-speedup, shipping 2026-08-28) routed
the mapper × linear-func block of the curvature matrix F through the batched FFT Convolver
(Convolver.reversed_kernel), taking F 1.20 → 0.36 s and the HST rectangular numba evaluation
1.56 → 0.60 s (OMP_NUM_THREADS=1, AUTOARRAY_NUMBA_OPERATED_MEMO=0, paired same-session
measurements; note in autolens_profiling/results/notes/numba_curvature_matrix_f_split.md).
Delaunay-1250: mapper×mapper 0.123 s of 0.76 s; F is no longer dominant there — the inversion build
(~0.5 s) is, which is out of scope here.
Plan findings (2026-08-28)
Two read-through findings changed the task's shape at planning time. First, the MGE half of the
cost lives in PyAutoGalaxy, not PyAutoArray: the #505Convolver batching is already applied in LightProfileLinearObjFuncList.operated_mapping_matrix_override (the 60 Gaussians are stacked into
one convolution) and scipy already skips the length-60 axis, so the bulk of the ~0.22 s is the 120
per-profile image evaluations, all of which recompute an identical transform and eccentric-radius
grid because the MGE basis shares centre/ell_comps and varies only in sigma — hence @PyAutoGalaxy added to Repos above. Second, two of the four mapper×mapper candidate levers are
dead on arrival: upper-triangle symmetry is already exploited (the sparse preload stores ip1 >= ip0 and the kernel folds A + Aᵀ) and unique-mappings compression is already what the
kernel iterates over. The live lever is a two-stage reformulation — a per-data-pixel dense
source-space accumulator followed by contiguous AXPYs.
Goal
Take the HST rectangular numba evaluation from ~0.60 s to ≤ ~0.35 s with the log-likelihood
unchanged to pinned tolerance (pins: hst rectangular 27661.910133664103, hst-rtu
27180.704715696862, hst-delaunay 29090.52721044813; rtol 1e-6 where summation order changes,
bit-identical otherwise).
Decompose first, as in perf: numba CPU curvature matrix F at HST resolution (phase 1: split + FFT mapper×func block) #505. Instrument the mapper×mapper kernel
(inversion_imaging_numba_util.py, sparse-op diag + off-diag kernels) and the MGE operated
mapping matrix step in the autolens_profiling breakdown harness; record the split at hst +
euclid rectangular and hst Delaunay-1250. Checkpoint: the split picks the lever.
mapper×mapper candidates, cheapest first: unique-mappings compression (is the inner loop over
data pixels × PSF footprint × source pixels where a per-unique-mapping formulation is smaller);
upper-triangle-only + mirror; whether the preloaded PSF-precision products are fully exploited
(nothing mapper-independent recomputed per evaluation); prange measured both at OMP_NUM_THREADS=1 and under the Nautilus pool.
MGE operated matrix candidates: determine FFT- vs scatter-bound; batch the varying profiles
through the same Convolver path as perf: numba CPU curvature matrix F at HST resolution (phase 1: split + FFT mapper×func block) #505 if the per-func convolution is the cost; check whether
any profile subset is invariant across evaluations for a given model (memo, already AUTOARRAY_NUMBA_OPERATED_MEMO).
Ship behind parity tests; test_autoarray green; smoke autolens_workspace/scripts/imaging/features/pixelization/cpu_fast_modeling.py; paired
before/after on the four breakdown cells + one Nautilus pool run; note in autolens_profiling.
Out of scope: the JAX path; RTU / kernel-CDF meshes (GPU-only by decision); the Delaunay inversion
build; the NNLS solve.
Overview
Successor to #505. #505 took the HST rectangular numba likelihood evaluation from 1.56 s to 0.60 s by
routing the mapper x linear-func block of the curvature matrix F through the batched FFT
Convolver.The post-#505 split is now mapper x mapper 0.277 s (46%), MGE operated mapping matrix ~0.22 s
(37%), everything else <= 0.05 s. Phase 2 attacks those two blocks, targeting <= ~0.35 s/eval
with the log-likelihood unchanged to pinned tolerance.
Blocked until #505 merges — phase 2 edits the same files (
sparse.py,inversion_imaging_numba_util.py, the twoautolens_profilingbreakdown harness scripts, the resultsnote). Branch from
mainonce #505 is in. Registered inPyAutoMind/planned.mdas blocked.Plan
MGE operated mapping matrixharness row into three timed sub-rows and record the geometry constants into the JSON, so thelever is chosen from a measurement rather than an estimate.
have today, then hoist the re-gathered
(pix, weight)pairs into locals. Estimated 10-20%.followed by contiguous AXPYs, replacing ~2e8 irregular read-modify-writes into a 4.9 MB matrix with
~5e7 L1 scatters plus vectorisable dense adds. Estimated 1.7-2.5x on the block (0.277 -> 0.11-0.16 s),
at
rtol=1e-6.OverSamplerdivisor cache in PyAutoArray, plus a shared-geometry fast path in PyAutoGalaxy for
LightProfileLinearObjFuncList— the 60 MGE Gaussians sharecentre/ell_compsand differ only insigma, so the transform and eccentric-radius grid are recomputed 120x per evaluation. EstimatedMGE 0.22 -> ~0.10-0.13 s.
20-30%), three pins at explicit
rtol=1e-6plus acurvature_matrixarray comparison, a Nautiluspool run, note appended in
autolens_profiling, thenship_libraryPyAutoArray ->ship_libraryPyAutoGalaxy ->ship_workspaceautolens_profiling.Two findings from the planning read-through shaped this: the MGE half of the cost lives in
PyAutoGalaxy, not PyAutoArray (the
Convolverbatching from #505 is already applied there); and twoof the four mapper x mapper candidate levers are dead on arrival — upper-triangle symmetry is already
exploited (the sparse preload stores
ip1 >= ip0and the kernel foldsA + A^T) and unique-mappingscompression is already what the kernel iterates over. The live lever is the two-stage reformulation.
Detailed implementation plan
Affected Repositories
Branch Survey
worktree_check_conflictreports PyAutoArray and autolens_profiling are both claimed by the in-flighttask
numba-hst-curvature-matrix-speedup(#505), and autolens_profiling additionally bynuts-warm-start-driver-and-a100-probe. Hence: queued inplanned.md, not started.Suggested branch:
feature/numba-hst-curvature-matrix-phase2Worktree:
~/Code/PyAutoLabs-wt/numba-hst-curvature-matrix-phase2/Implementation Steps
Step 0 — instrument (checkpoint gates everything after)
Files:
autolens_profiling/scripts/imaging/likelihood_breakdown/{pixelization_numba,delaunay_numba}.py(keep
STEP_ACCESSORSin sync as #505 did).MGE operated mapping matrixrow into 3 timed sub-rows — 60ximage_2d_from(grid),60x
image_2d_from(blurring_grid), 1xpsf.convolved_mapping_matrix_via_real_space_np_from(...)—with the existing row becoming the residual (same scheme as the F rows; the override is a
cached_propertyon the func object so sub-timings do not prime it; the four rows must sum).sparse_operator.lengths.sum()/mean()/max(),unique_mappings.pix_lengths.mean()/max(),data_to_pix_unique.shape,mapper.params,n_image_pixels, PSF shape.n_source^2, noise).
OMP_NUM_THREADS=1 AUTOARRAY_NUMBA_OPERATED_MEMO=0, n_repeats 10); post the split to this issue.Checkpoint: confirms (a) profile-eval vs convolution share of MGE, (b) the mapper x mapper op count.
Step 1 — mapper x mapper, bit-identical restructuring (own commit)
Kernel:
curvature_matrix_via_sparse_operator_from(inversion_imaging_numba_util.py~:496).test__curvature_matrix_via_sparse_operator_from__matches_dense_psf_precision_operatorintest_autoarray/inversion/inversion/imaging/test_inversion_imaging_util.py: denseM^T W Mvs thekernel, parametrised over
KERNELS_ODD(asymmetric, non-square). Plus a symmetry / halved-diagonaltest. Keep the current kernel as
curvature_matrix_via_sparse_operator_reference_from.data_1's <= u1(pix, weight)pairs into locals once per stored pair (today re-gathered u0xfrom wide-stride 2-D arrays) and take
curvature_matrix[pix_0]as a row view. Estimated 10-20%.Verify
np.array_equaloninversion.curvature_matrixbefore/after (euclid + hst).Step 2 — mapper x mapper, two-stage reformulation (own commit, rtol 1e-6)
data_0: accumulate the densea[p] = sum_{data_1} W(data_0,data_1) * sum_b w1_b delta(pix1_b, p)into an L1-residentn_sourcevector, thenA[pix_0,:] += w0 * afor each ofdata_0's u0 mappings; keep theA + A^Tfold and the halved-diagonal contract.n_source <~ P * u1(~3400 at HST; 784/1250 well inside). Keep the reference loop behinda runtime branch on
pix_pixels; measure the crossover, and if it is dataset-dependent expose itas an explicit setting rather than a silent heuristic.
..._two_stage_matches_reference_kernelparametrised overKERNELS_ODDxpix_pixelson bothsides of the threshold; inversion-level
test__curvature_matrix_mapper_diag__matches_reference_kernelin
test_curvature_matrix_func_list_blocks.py; the multi-mapper twincurvature_matrix_off_diags_via_sparse_operator_fromgets its own oracle if touched. Every rtol testgets a recorded control run (a deliberately wrong variant must fail).
prange: not planned. Zero gain under Nautilus one-process-per-core, and it needs per-threadn_source^2 buffers plus a lazily cached prefix-offset array on
SparseLinAlgImagingNumba(pickled toworkers — no new ctor args). Only if step 2 misses target, default off, both arms measured.
psf_precision_operatorentries (moves the likelihood; fails the pin).Step 3 — MGE operated matrix (gated by the step-0 split)
OverSampler.binned_array_2d_from(
autoarray/operators/over_sampling/over_sampler.py~:202) recomputes the model-independentnp.bincount(segment_ids)divisor on every one of ~120 calls/eval. Cache it as an immutablecached_property(today the code mutatescountsin place — the cached array must not be mutated).Wider blast radius (every numpy light-profile eval) ->
np.array_equaltest across repeated callsplus the harness
Blurred imagerow as witness.LightProfileLinearObjFuncList(
autogalaxy/profiles/light/linear/abstract.py,mapping_matrix~:291,operated_mapping_matrix_override~:320): when all profiles are the same class with identicalcentre/ell_comps, compute the transform and eccentric radii once per grid and evaluate the 60Gaussians vectorised over
sigma; binning stays a loop initially (binned_array_2d_fromis 1-D).Fall back to the current loop otherwise. Tests:
..._shared_geometry_matches_per_profile_loop(assert bit-identical first; relax to rtol 1e-6 only with a recorded reason) and
..._mixed_geometry_falls_back. Estimated MGE 0.22 -> ~0.10-0.13 s.ConvolverState.fft_kernelon the numpy path (scipy already avoids the 3-D FFT;single-digit %; a 10-line microbench only), and a narrower memo keyed on centre+ell_comps (weaker
than the hoist).
Step 4 — measure + ship
once for currency (unpaired, GPU-only by decision). Discard the first post-rebuild run (numba
cache: true; documented cold-cachepsf_weighted_data_fromhazard).rtol=1e-6(the harness default is 1e-4) plus acurvature_matrixarray comparison.cpu_fast_modeling.pywithPYAUTO_TEST_MODE=1,PYAUTO_SMALL_DATASETSunset, cores =host, plus a linear MGE bulge (the smoke profile runs no pool); compare the parallel speed-up ratio.
autolens_profiling/results/notes/numba_curvature_matrix_f_split.md.ship_libraryPyAutoArray ->ship_libraryPyAutoGalaxy (if touched) ->ship_workspaceautolens_profiling. Merge is human.
Key Files
autoarray/inversion/inversion/imaging_numba/inversion_imaging_numba_util.py,autoarray/inversion/inversion/imaging_numba/sparse.py,autoarray/operators/over_sampling/over_sampler.py,test_autoarray/inversion/inversion/imaging/test_inversion_imaging_util.py,test_autoarray/inversion/inversion/test_curvature_matrix_func_list_blocks.pyautogalaxy/profiles/light/linear/abstract.py,autogalaxy/profiles/light/standard/gaussian.py,autogalaxy/profiles/geometry_profiles.py,new tests under
test_autogalaxy/profiles/light/linear/scripts/imaging/likelihood_breakdown/pixelization_numba.py,scripts/imaging/likelihood_breakdown/delaunay_numba.py,_profile_cli.py,results/notes/numba_curvature_matrix_f_split.mdVerification
pytest test_autoarray(baseline 1296) andpytest test_autogalaxygreen; the oracle test passes on theunmodified kernel before any change; control runs recorded for every rtol test; bit-identical
curvature_matrixfor step 1 and the OverSampler change; three pins at rtol 1e-6 (hst rectangular27661.910133664103, hst-rtu 27180.704715696862, hst-delaunay 29090.52721044813); smoke
cpu_fast_modeling.py; paired before/after table + pool ratio in the note and on this issue.Marked undetermined (step 0 measures these, never guessed)
The MGE internal split; the actual
psf_precision_lengths.sum()/pix_lengths.mean(); whether LLVMalready hoists the scalar product; the two-stage crossover
n_source; the numba threading layer on WSL(only if prange ever runs).
Original Prompt
Click to expand starting prompt
Numba CPU likelihood at HST resolution, phase 2: the mapper×mapper block and the MGE operated matrix
Type: feature
Epic: none (successor to
numba_cpu_hst_curvature_matrix_speedup, PyAutoArray#505)Target: autoarray
Repos:
Themes:
Difficulty: large
Autonomy: supervised
Priority: high
Status: draft
Filed: 2026-08-28
Original request
(in reply to: "What's left for a phase 2, if you want one: mapper×mapper (0.28 s) and the MGE
operated matrix (0.22 s) now lead at HST.")
Context
PyAutoArray#505 (branch
feature/numba-hst-curvature-matrix-speedup, shipping 2026-08-28) routedthe mapper × linear-func block of the curvature matrix F through the batched FFT
Convolver(
Convolver.reversed_kernel), taking F 1.20 → 0.36 s and the HST rectangular numba evaluation1.56 → 0.60 s (
OMP_NUM_THREADS=1,AUTOARRAY_NUMBA_OPERATED_MEMO=0, paired same-sessionmeasurements; note in
autolens_profiling/results/notes/numba_curvature_matrix_f_split.md).Post-#505 HST rectangular split (0.60 s/eval):
Delaunay-1250: mapper×mapper 0.123 s of 0.76 s; F is no longer dominant there — the inversion build
(~0.5 s) is, which is out of scope here.
Plan findings (2026-08-28)
Two read-through findings changed the task's shape at planning time. First, the MGE half of the
cost lives in PyAutoGalaxy, not PyAutoArray: the #505
Convolverbatching is already applied inLightProfileLinearObjFuncList.operated_mapping_matrix_override(the 60 Gaussians are stacked intoone convolution) and scipy already skips the length-60 axis, so the bulk of the ~0.22 s is the 120
per-profile image evaluations, all of which recompute an identical transform and eccentric-radius
grid because the MGE basis shares
centre/ell_compsand varies only insigma— hence@PyAutoGalaxyadded to Repos above. Second, two of the four mapper×mapper candidate levers aredead on arrival: upper-triangle symmetry is already exploited (the sparse preload stores
ip1 >= ip0and the kernel foldsA + Aᵀ) and unique-mappings compression is already what thekernel iterates over. The live lever is a two-stage reformulation — a per-data-pixel dense
source-space accumulator followed by contiguous AXPYs.
Goal
Take the HST rectangular numba evaluation from ~0.60 s to ≤ ~0.35 s with the log-likelihood
unchanged to pinned tolerance (pins: hst rectangular 27661.910133664103, hst-rtu
27180.704715696862, hst-delaunay 29090.52721044813; rtol 1e-6 where summation order changes,
bit-identical otherwise).
(
inversion_imaging_numba_util.py, sparse-op diag + off-diag kernels) and the MGE operatedmapping matrix step in the
autolens_profilingbreakdown harness; record the split at hst +euclid rectangular and hst Delaunay-1250. Checkpoint: the split picks the lever.
data pixels × PSF footprint × source pixels where a per-unique-mapping formulation is smaller);
upper-triangle-only + mirror; whether the preloaded PSF-precision products are fully exploited
(nothing mapper-independent recomputed per evaluation);
prangemeasured both atOMP_NUM_THREADS=1and under the Nautilus pool.through the same
Convolverpath as perf: numba CPU curvature matrix F at HST resolution (phase 1: split + FFT mapper×func block) #505 if the per-func convolution is the cost; check whetherany profile subset is invariant across evaluations for a given model (memo, already
AUTOARRAY_NUMBA_OPERATED_MEMO).test_autoarraygreen; smokeautolens_workspace/scripts/imaging/features/pixelization/cpu_fast_modeling.py; pairedbefore/after on the four breakdown cells + one Nautilus pool run; note in
autolens_profiling.Out of scope: the JAX path; RTU / kernel-CDF meshes (GPU-only by decision); the Delaunay inversion
build; the NNLS solve.