Skip to content

profiling: phase-2 instrumentation and the measured result (#507) - #190

Merged
Jammy2211 merged 4 commits into
mainfrom
feature/numba-hst-curvature-matrix-phase2
Aug 28, 2026
Merged

profiling: phase-2 instrumentation and the measured result (#507)#190
Jammy2211 merged 4 commits into
mainfrom
feature/numba-hst-curvature-matrix-phase2

Conversation

@Jammy2211

Copy link
Copy Markdown
Contributor

Summary

The measurement half of PyAutoLabs/PyAutoArray#507 phase 2: instrument first,
then record the result.

Step 0 split the MGE operated mapping matrix row into its three pieces plus a
residual (the same residual scheme the phase-1 F rows use, so the four rows sum
to the un-split step) and recorded the sparse operator's model-independent
geometry into every result JSON. That checkpoint is what chose the phase's two
levers: profile evaluation is 78 % of the MGE row, not the PSF convolution,
and the mapper x mapper kernel does exactly 1.773e8 inner accumulations at
the HST fiducial (the plan had estimated "~2e8").

Step 4 appends the phase-2 section to
results/notes/numba_curvature_matrix_f_split.md — the step-0 split, the
per-step paired tables, the measured two-stage crossover sweep, the pins, the
pool ratio, and what was deliberately not paired — and re-runs the four cells on
the final branch.

cell base (1b89404b) after steps 1-2 final whole phase
hst rectangular, step total 0.6214 s 0.4324 s 0.3334 s 1.86x
euclid rectangular 0.2226 s 0.1870 s 0.1417 s 1.57x
hst Delaunay-1250 0.6331 s 0.6076 s 0.4884 s 1.30x
hst: F mapper x mapper 0.2581 s 0.0819 s 0.0825 s 3.13x
hst: MGE total 0.1970 s 0.1937 s 0.0844 s 2.33x

Three arms, paired B/A/B/A in one session, three rounds per cell in rotating arm
order with the first discarded (numba cache: true recompiles on an arm
switch), mean of the last two.

Scripts Changed

  • scripts/imaging/likelihood_breakdown/pixelization_numba.py — step 0: split
    the MGE row into MGE: image_2d_from(grid) x60, MGE: image_2d_from(blurring_grid) x60 and MGE: batched PSF convolution, with the
    pre-existing row becoming the residual; mge_split_reproduces_step records a
    one-off bit-identical check that the three pieces reconstruct the step's
    output; _geometry_constants writes the PSF shape, the sparse operator's
    stored-pair count and row lengths, and per mapper its params,
    data_to_pix_unique shape and pix_lengths into the JSON, plus the exact
    inner-operation count of both kernel forms. Step 4: _mge_blurring_stack
    now calls the same path operated_mapping_matrix_override calls instead of
    hand-rolling the per-profile loop — after perf(linear): share one eccentric-radius grid across an MGE basis (PyAutoArray#507) PyAutoGalaxy#590 the
    hand-rolled version would have kept timing the old code on the new library
    and mis-attributed the win to the residual row. It dispatches on the attribute
    so both libraries stay measurable and records which branch it took as
    mge_blurring_stack_path.
  • scripts/imaging/likelihood_breakdown/delaunay_numba.py — the same two
    changes, kept in sync as STEP_ACCESSORS has been throughout.
  • results/notes/numba_curvature_matrix_f_split.md — new "Phase 2 (#507)"
    section.
  • results/breakdown/imaging/{pixelization_numba_breakdown_hst, pixelization_numba_breakdown_hst_rtu, pixelization_numba_breakdown_euclid, delaunay_numba_breakdown_hst}_v2026.8.17.1.{json,png} — re-run on the final
    branch.
  • README.md, scripts/misc/likelihood_breakdown/README.md — auto-tables
    regenerated by build_readme.py; only this task's three rows move.
  • A final commit runs ruff format on the two harnesses: the step-0 commit had
    left them failing this repo's ruff format --check lint gate (the branch had
    not been PR'd, so nothing had run it). origin/main was and is clean.

Upstream PR

Merge order: PyAutoArray#508 -> PyAutoGalaxy#590 -> this PR. The note and the
artifacts here describe library behaviour that must already be on main.

Test Plan

  • ruff check . — All checks passed.
  • ruff format --check . — 181 files already formatted (was 2 failing
    before the format commit).
  • python scripts/misc/tooling/build_readme.py --check — clean, i.e.
    idempotent after the regeneration.
  • AUTOLENS_PROFILING_SMOKE=1 on both changed harnesses — imports + module
    setup OK, which is what the lint job runs.
  • Both harnesses run for real on all four cells (that is where the committed
    artifacts come from); mge_split_reproduces_step is True on every one, and
    mge_blurring_stack_path reads shared_geometry on the final artifacts
    and per_profile_loop on the base arms.
  • Pinned log-likelihood checks at the explicit rtol=1e-6 PASSED on all
    three pinned cells and are unchanged to every recorded digit.

Heart

Heart is RED at ship time for a pre-existing reason unrelated to this task.
Verbatim from pyauto-heart readiness --json (2026-08-28T21:34:42Z, score 45):

  • red_reasons: "release validation FAILED (stage integrate)"
  • yellow_reasons: "workspace validation not passing (2 failed, cloud#33179766004: autolens_test scripts/imaging/rectangular_mge.py, autolens_test scripts/imaging/rectangular_mge_rtu.py)",
    "manifest drift: session-start hooks (generated) — 32 mismatch(es) vs PyAutoMind/repos.yaml"

Human authorisation to ship over it, given 2026-08-28, verbatim:

prm and then kick off phase 2, I authorize the heart RED thing

Generated by the PyAutoLabs agent workflow.

Jammy2211 and others added 4 commits August 28, 2026 17:32
…(#507)

Step 0 of PyAutoArray#507: instrument before optimizing, so the phase-2 levers
are chosen from measurements rather than the plan's estimates.

Two additions to both numba CPU breakdown harnesses:

1. The `MGE operated mapping matrix` row is split into the three pieces of
   `LightProfileLinearObjFuncList.operated_mapping_matrix_override` -- 60x
   `image_2d_from(grid)`, 60x `image_2d_from(blurring_grid)`, one batched
   real-space PSF convolution -- with the pre-existing row becoming the
   residual, exactly the scheme the #505 F sub-blocks use. The override is a
   `cached_property` and the three pieces are recomputed uncached, so timing
   them does not prime the step that follows, and the four rows sum to the raw
   un-split cost (`mge_operated_mapping_matrix_total_s` in the JSON).
   `mge_split_reproduces_step` records a one-off bit-identical check that the
   three pieces really do reconstruct the dict the step returns, so a dataset
   that took a different branch of the override would be flagged rather than
   silently mis-attributed. It is True on all three cells.

2. Model-independent geometry constants are recorded once into the JSON:
   the PSF shape, the sparse operator's stored-pair count and per-row
   lengths (sum/mean/max), and per mapper its `params`, `data_to_pix_unique`
   shape and `pix_lengths` (mean/max/sum). From those, the exact innermost
   accumulation count of `curvature_matrix_via_sparse_operator_from`,

       sum over stored (data_0, data_1) pairs of u0(data_0) * u1(data_1),

   plus the same geometry costed for the phase-2 two-stage reformulation.
   This turns the plan's "~2e8 ops" estimate into a measurement.

Measured (OMP_NUM_THREADS=1, AUTOARRAY_NUMBA_OPERATED_MEMO=0, n_repeats 10):

  hst rectangular (bilinear, 784)   mapper x mapper 0.2551 s   MGE 0.2086 s
      MGE = 0.1160 (grid eval) + 0.0460 (blurring eval) + 0.0541 (convolution)
            - 0.0075 (residual); profile evaluation is 78 % of the MGE row.
      mapper x mapper inner ops 1.773e8 (plan estimated ~2e8).
  euclid rectangular (bilinear, 784) mapper x mapper 0.0491 s  MGE 0.0704 s
  hst delaunay (Hilbert, 1250)      mapper x mapper 0.1304 s   MGE 0.2229 s

The `pixelization_numba` euclid and hst cells and the `delaunay_numba` hst cell
are re-run and their artifacts regenerated; `build_readme.py` re-run for the
generated headline tables (only this task's three rows move).

Refs PyAutoLabs/PyAutoArray#507

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fr6iJ5T1RDARWfxttCuGkK
…ture kernel (#507)

Artifacts only -- no harness change. Re-run on PyAutoArray 88e14bc6 (#507 step 2,
the two-stage mapper x mapper source-space accumulator), same environment as the
step-0 baseline: OMP_NUM_THREADS=1, AUTOARRAY_NUMBA_OPERATED_MEMO=0, n_repeats
10, worktree PyAutoArray on PYTHONPATH, first post-rebuild run discarded.

  step-by-step total          before    after
    hst rectangular          0.6334 s  0.3869 s
    euclid rectangular       0.1978 s  0.1652 s
    hst delaunay             0.7344 s  0.5403 s

  F: mapper x mapper block
    hst rectangular          0.2551 s  0.0719 s
    euclid rectangular       0.0491 s  0.0168 s
    hst delaunay             0.1304 s  0.0607 s

All three pinned log likelihoods pass at rtol=1e-6 (euclid has no pin and prints
its value, unchanged at 6213.306873885871).

These harness numbers are *unpaired* across sessions, so they carry this host's
20-30 % run-to-run variance -- the MGE rows move here too, and nothing in this
task touched them. The paired same-session before/after that the speed-up claim
rests on is in the PyAutoArray commit message and on the issue.

Auto-tables regenerated (`build_readme.py`); only this task's three rows move.

Refs PyAutoLabs/PyAutoArray#507

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fr6iJ5T1RDARWfxttCuGkK
…ess fix (#507)

Appends a "Phase 2 (#507)" section to
`results/notes/numba_curvature_matrix_f_split.md` and re-runs the four numba
CPU cells on the final branch.

The headline, paired B/A/B/A in one session across three arms (phase-2 base =
PyAutoArray main @1b89404b + PyAutoGalaxy main @0fbe863d; after steps 1-2;
after step 3 in both libraries), three rounds per cell in rotating arm order
with the first discarded:

  step-by-step total        base     step 2   final
    hst rectangular        0.6214 s  0.4324 s 0.3334 s   (1.86x)
    euclid rectangular     0.2226 s  0.1870 s 0.1417 s   (1.57x)
    hst delaunay-1250      0.6331 s  0.6076 s 0.4884 s   (1.30x)

  F: mapper x mapper (hst) 0.2581 s  0.0819 s 0.0825 s   (3.13x)
  MGE total (hst)          0.1970 s  0.1937 s 0.0844 s   (2.33x)
  MGE 60x image_2d_from    0.1042 s  0.0995 s 0.0233 s   (4.48x)

The issue's goal -- 0.60 -> ~0.35 s at HST rectangular -- is met (0.333 s
step total, 0.301 s directly timed). Rows the change does not touch hold still:
`Blurred image` 0.98-1.02x, the batched MGE PSF convolution 0.96-1.00x.

Correctness recorded in the note: all three pinned log likelihoods pass at the
explicit `rtol=1e-6` on every arm and are unchanged to every recorded digit
(euclid, unpinned, likewise); the MGE operated mapping matrix is bit-identical
base -> final; `curvature_matrix` is bit-identical for step 3 alone and agrees
to 1.24e-14 relative across the whole phase (`allclose(rtol=1e-12)` True).

Pool run (8 cores, `PYAUTO_TEST_MODE=1`, `PYAUTO_SMALL_DATASETS` unset, 2828
masked pixels, 60-Gaussian linear MGE bulge): per evaluation 0.2000 -> 0.1697 s
in a pool of 8 and 0.4099 -> 0.3634 s serial, so the parallel speed-up ratio is
2.05x -> 2.14x. Flat to slightly up is the pass condition -- it is what would
fall if a lever had introduced hidden threads.

**Harness fix (the one behaviour change here).** The
`MGE: image_2d_from(blurring_grid) x60` row hand-rolled the per-profile loop
that `operated_mapping_matrix_override` used to contain. PyAutoArray#507 step 3b
moved that stack behind `LightProfileLinearObjFuncList._image_slim_list_from`,
so the hand-rolled loop would have kept timing the *old* code on the new library
and mis-attributed the entire win to the residual row. `_mge_blurring_stack` now
calls the same path the override calls, dispatching on the attribute so both
libraries stay measurable, and records which branch it took as
`mge_blurring_stack_path` in the result JSON (`per_profile_loop` on the base
arms, `shared_geometry` on the final artifacts). `mge_split_reproduces_step`
stays True on all four cells.

Artifacts re-run on the final branch: `pixelization_numba` hst + euclid,
`delaunay_numba` hst, and `pixelization_numba` hst `--rect-mesh rtu` once for
currency (unpaired; it is a 7.1 s cell dominated by its 6.73 s mapper-sparse-
triplets step and is GPU-only by the 2026-08-28 decision). Auto-tables
regenerated with `build_readme.py`, `--check` clean; only this task's three rows
move.

Refs PyAutoLabs/PyAutoArray#507

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fr6iJ5T1RDARWfxttCuGkK
`ruff format --check .` is a lint-CI gate in this repo and the step-0 commit
(`3f69b34`) left both harnesses failing it -- the branch had not been PR'd yet,
so nothing had run the gate. `origin/main` is clean; only these two files
regressed, and only in the blocks step 0 and step 4 added.

Formatting only (line-length 100 collapses of list comprehensions and call
chains); no statement, timing or artifact changes. `ruff check .` and
`build_readme.py --check` were already clean and stay clean; both harnesses
still pass the `AUTOLENS_PROFILING_SMOKE=1` import path the lint job runs.

Refs PyAutoLabs/PyAutoArray#507

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fr6iJ5T1RDARWfxttCuGkK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release Queued for the next release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant