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

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 (feat(gradient): gradient_breakdown.py for every likelihood + gradient_p6 campaign generator (phase 6b) #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
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Aug 28, 2026
@Jammy2211
Jammy2211 merged commit ed1e265 into main Aug 28, 2026
1 check passed
@Jammy2211
Jammy2211 deleted the feature/numba-hst-curvature-matrix-phase2 branch August 28, 2026 22:52
@Jammy2211 Jammy2211 removed the pending-release PR queued for the next release build label Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant