Skip to content

perf: interferometer sparse data vector without the dense mapping matrix - #630

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/interferometer-sparse-data-vector
Oct 15, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/interferometer-sparse-data-vector

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

InversionInterferometerSparse.data_vector computed mapping_matrix.T @ d~, which builds the dense (masked pixels x source pixels) mapping matrix with hstack. On the Aris 512x512 mask (91,507 masked pixels, 5,746 Delaunay vertices) that matrix is 4.2 GB, and it accounted for all of the RSS growth in one likelihood call on both the numba and FFT routes.

Each Mapper block is now a segment-sum of vals * d~[rows] over its sparse COO triplets. It uses np.bincount on NumPy and .at[].add on JAX. A new private helper, _sparse_triplets_slim_from, rebuilds the triplets so they match mapper_util.mapping_matrix_from exactly: it keeps the sub_fraction normalisation and gives zero weight to padding slots (slot >= pix_sizes, or index -1). Linear function lists keep their small dense block. Blocks are joined in linear_obj_list order. The numba subclass inherits the new path.

Closes #626

Witness: Aris 512x512, use_jax=False, before vs after

run before after
numba, 5,746 vertices, 1 thread, peak RSS 7.17 GB 3.14 GB (two runs)
numba logL (eager, prior medians) 14067486.830760108 14067486.830835108 (rel 5e-12)
numba logL call0 / call3 14070143.563820157 / 14065379.98260254 14070143.563815987 / 14065379.982959531 (rel <= 2.5e-11)
JAX jit, 1,459 vertices, peak RSS 3.65 GB 2.60 GB
JAX jit, 1,459 vertices, logL 14088615.924248978 14088615.924248978 (identical)
JAX, 5,746 vertices, XLA compile temp (vmap b1, lower only) 8.95 GB 1.86 GB
JAX jit, 5,746 vertices infeasible runs, peak RSS 3.24 GB

Direct check at 1,459 vertices: the sparse data vector matches the dense mapping_matrix.T @ d~ to a maximum relative difference of 5.8e-16. Timings are not claimed: the machine's load average was 2 to 14 while measuring. The data-vector change is not on a hot path.

Campaign: this task belongs to the PyAutoPulse interferometer campaign as task interferometer_large_mask_aris (PyAutoLabs/PyAutoPulse#62). At close-out, copy the before/after witness table above into that task file.

Decision taken

  • Decision: The witness said "log likelihood unchanged to 1e-8". I read it as "the data vector is identical to float64 precision, and any logL difference is rounding". On that reading the witness is a PASS. The data vector matches the dense product to 5.8e-16 relative. The logL moves by at most 2.5e-11 relative, which is about 3.5e-4 nats at |logL| ≈ 1.4e7. An absolute 1e-8 is below float64 resolution at that size (eps x 1.4e7 ≈ 3e-9 per value), and the ill-conditioned AdaptSplit solve magnifies rounding from the changed summation order.
  • Rejected alternative: fail the witness on the absolute 1e-8 figure and park the task.
  • Revert: git revert 8676b6a0

API Changes

None. Internal changes only.

Behaviour note: in JAX mixed-precision mode the data vector now uses a float64 data-vector block instead of a float32 mapping matrix. This is slightly more accurate, but not bit-identical in that mode.

Test Plan

  • pytest test_autoarray -x -q in the worktree: 2033 passed, 4 xfailed, 0 failed. The interferometer and interferometer_numba suites (125 tests) are included.
  • Two new tests compare against the dense oracle mapping_matrix.T @ d~ to 1e-12. They cover an over-sampled (sub 2) Delaunay mapper, a mapper plus a linear function list in both orders, and a DatasetInterface with its own sparse_dirty_image (with a control check). The JAX backend case uses importorskip, matching the existing JAX tests in this file.
  • Downstream library test suites: not run. The only changes are two private helpers and the internals of an existing property, so there is no public-API change. The gate needs dependents' suites only for public-API changes.
  • Witness benchmark on the Aris 512x512 cache (table above).

Validation checklist (--auto run — plan was not pre-approved)

  • Effective level: safe (header: safe, cap: feature/medium → safe)
  • Plan: on the issue (perf: interferometer sparse data vector without the dense mapping matrix #626), written at start, unmodified since
  • Gate: tests 2033 passed / 4 xfailed (downstream n/a, private API) · smoke PASS: pyauto-heart smoke autolens autogalaxy autolens_test --root <worktree> ran 92/92 scripts and 4/4 notebooks with 0 failures. That covers autolens_workspace 41+2, autogalaxy_workspace 14+2 and autolens_workspace_test 37, including 15 interferometer scripts (interferometer/features/pixelization/delaunay.py, jax_assertions/fit_interferometer_sparse_operator.py, interferometer/jax_likelihood/*, datacube/shared_preloads.py, subhalo_recovery_interferometer.py, …). There were no pre-existing failures to separate out. · review CLEAN (independent Opus reader; the author was Fable). Claim checked: "JAX jit, 1,459 vertices: peak RSS 3.65 -> 2.60 GB, logL identical". Evidence: before_jit_s8.json/after_jit_s8.json give peak_rss_gb 3.653/2.604 and out_sample 14088615.924248978 in both. · Heart RED, under a recorded development override (not green):
    • Exact RED reasons at launch, from perf: interferometer sparse data vector without the dense mapping matrix #626 "Heart RED development override": Heart: RED — Release RED; monitoring RED · 10/100 · 415 unresolved; [red] release validation FAILED (stage integrate); [red] PyAutoHands/workflows/Python Version Matrix: failure; [red] PyAutoHeart/workflows/Release Integrate: failure; [red] PyAutoMind/workflows/Lifecycle Drift: failure; [red] PyAutoArray/Tests: red; [red] autofit_workspace_test/Smoke Tests: red; [red] autogalaxy_workspace_test/Smoke Tests: red; [red] autolens_workspace_test/Smoke Tests: red; [red] Release validation: NOT release_ready — v2026.10.15.1.dev81701; [red] Worktree drift: 5 orphan / 0 missing / 4 dirty; [red] autolens_inference: red; [red] autolens_profiling: red; [red] euclid_strong_lens_modeling_pipeline: red.
    • Heart at ship time (pyauto-heart readiness --json after a vitals refresh, 2026-10-15 ~18:51): verdict red, reasons release validation FAILED (stage integrate); PyAutoFit: open PR 8d old.
    • Authorization: the live human in session 3e3dea38 answered "Yes, override for development (Recommended)" for the three PyAutoArray Aris prompts. The override permits commit, push and PR-open only. Merge is a separate human act through /prm, and Heart stays RED for release.
  • Ends at PR-open. Not auto-merged, because this PR is decision-taken and shipped under a Heart RED override.
  • Human: plan sound in hindsight?
  • Human: diff matches plan (no scope creep)?
  • Human: does the witness reading in "Decision taken" hold?
  • Human: merge, amend, or reject, then log the outcome
Full API Changes (for automation & release notes)

Added (private)

  • InversionInterferometerSparse._sparse_triplets_slim_from(mapper): slim-row COO triplets that reproduce mapping_matrix_from
  • InversionInterferometerSparse._data_vector_mapper_from(mapper, dirty_image): segment-sum Aᵀ d~ block

Changed Behaviour

  • InversionInterferometerSparse.data_vector: same values to float64 precision. The dense mapping matrix is no longer built. JAX mixed precision now uses a float64 block instead of a float32 mapping matrix.

Migration

  • None.

Generated by the PyAutoLabs agent workflow.

🤖 Generated with Claude Code

InversionInterferometerSparse.data_vector computed mapping_matrix.T @ d~,
materialising the dense (masked pixels x source pixels) mapping matrix via
hstack: 4.2 GB at 91,507 x 5,746 on the Aris 512x512 mask, the entire RSS
growth of one likelihood call on both the numba and FFT routes.

Each Mapper block is now a segment-sum of vals * d~[rows] over its sparse
COO triplets (slim rows; np.bincount on NumPy, .at[].add on JAX), built by a
new _sparse_triplets_slim_from helper that reproduces mapping_matrix_from
exactly (sub_fraction normalisation, padding slots beyond pix_sizes and -1
indices zeroed). Linear function lists keep their small dense block; blocks
are concatenated in linear_obj_list order.

Aris 512x512 (91,507 masked pixels), bench.py:
- numba route, 5,746 vertices, 1 thread: peak RSS 7.17 -> 3.14 GB;
  logL rel. change <= 2.5e-11 (data vector rel. diff 6e-16 vs dense)
- JAX jit, 1,459 vertices: peak RSS 3.65 -> 2.60 GB, logL identical
- JAX, 5,746 vertices: XLA compile temp 8.95 -> 1.86 GB; the jit run that
  was previously infeasible now runs at peak RSS 3.24 GB

Closes #626.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Jammy2211 Jammy2211 added pending-release PR queued for the next release build decision-taken autonomous run took one flagged decision (AUTONOMY.md decide-and-flag) labels Oct 15, 2026
@Jammy2211
Jammy2211 merged commit 7cc7123 into main Oct 15, 2026
3 checks passed
@Jammy2211
Jammy2211 deleted the feature/interferometer-sparse-data-vector branch October 15, 2026 18:19
@Jammy2211 Jammy2211 removed the pending-release PR queued for the next release build label Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

decision-taken autonomous run took one flagged decision (AUTONOMY.md decide-and-flag)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: interferometer sparse data vector without the dense mapping matrix

1 participant