Skip to content

perf: symmetric row-restricted direct_conv curvature kernel (serial numba, large extents) #635

Closed

Description

@Jammy2211

Overview

At 512×512 (Aris SPT2319-55: 91k masked pixels, extent 341²) the serial numba curvature_direct_conv kernel is 96–98% of a NumPy interferometer likelihood call. F is symmetric: after sorting source columns by y, each column only needs the extent rows spanned by later-ranked columns, and each row only needs the mask's x-range. On a quiet RAL core the prototype was 1.76× / 1.74× / 1.66× faster at S = 1,428 / 2,540 / 5,712, single-threaded (RAL 411833, PyAutoPulse#62). It reproduces the library's upper triangle exactly. Serial is the target, because Aris parallelises at the Nautilus level.

Plan

  • Add a serial symmetric direct_conv kernel and its set-up function beside the existing kernel, which stays as the fallback. The existing parallel path is unchanged.
  • Turn it on automatically from a geometry work fraction (≤ 0.5 → on; 0.42 on Aris 512, 0.60 on Aris 125). One config value is the kill switch, and users don't need to set anything.
  • Give the feat: route interferometer numba vs FFT curvature by a cost model #632 cost model one correction factor for the cheaper kernel, so routing picks direct_conv where it now wins.
  • Tests on several geometries: exact upper triangle, unchanged log likelihood, gate behaviour, routing.
  • Witness: a RAL quiet-core rerun of library branch against main (≥1.6× at 2,540 and 5,712), plus one real Aris dataset log likelihood matching main to round-off.
  • Write-up phase (autolens_profiling note + PyAutoPulse Knowledge/decision record), stated as data-driven decision rules for autolens_assistant to apply.
  • Tier: glance — merge mode: auto-merge on green if the Witness passes, post-merge summary (a Heart RED override ship stops at PR-open → human /prm).
Detailed implementation plan

Affected Repositories

  • PyAutoArray (primary); then autolens_profiling + PyAutoPulse (write-up phase)

Branch Survey

Repository Current Branch Dirty?
./array/PyAutoArray main clean

Suggested branch: feature/direct-conv-symmetric

Implementation Steps

  1. autoarray/inversion/inversion/interferometer_numba/inversion_interferometer_numba_util.py
    • symmetric_restriction_inputs_from(iy, ix, cscptr, csc_row, ny, nx, pix_pixels): a vectorised version of the prototype's prep. It computes the column order by centroid y (stable) and rank, then ylo_suffix (the suffix minimum of each column's y-min in rank order). It also builds the per-row mask xlo/xhi, pixels grouped by row (pix_by_row_ptr, pix_by_row), and work_fraction = Σ nnz_col·Σ_{y≥ylo} width(y) / (nnz·ny·nx).
    • curvature_direct_conv_symmetric(...) (@numba_util.jit()): the prototype kC in library style, followed by a mirror pass.
    • DIRECT_CONV_SYMMETRIC_MAX_WORK_FRACTION default, and the κ factor in curvature_route_seconds_from (t_dc ·= min(1, κ·f) when the symmetric kernel would run). κ is fitted from the RAL quiet-core ratios and the 125 points, and the fit table is documented in the constants block as feat: route interferometer numba vs FFT curvature by a cost model #632 did.
  2. autoarray/inversion/inversion/interferometer_numba/sparse.py::curvature_matrix_diag: with serial numba and f ≤ threshold, use the symmetric kernel. The parallel path keeps the full kernel.
  3. autoarray/config/general.yaml: add interferometer_direct_conv_symmetric_max_work_fraction: 0.5 (0 = off), with a comment.
  4. Routing call site (factory / curvature_route_seconds_from caller): pass the work fraction when it is cheaply available. If it isn't available there, compute it from the mapper's triplets only on the NumPy single-mapper path.
  5. Tests (test_autoarray/inversion/inversion/interferometer/test_curvature_direct_conv_symmetric.py):
    • upper triangle == the library kernel and the whole matrix to 1e-14, on square, circular and offset masks, Delaunay-like and rectangular-like triplets, with empty columns
    • the work fraction on a known geometry
    • the gate's on, off (0) and threshold behaviour
    • the routing flip
    • an InversionInterferometerSparseNumba end-to-end log likelihood unchanged
  6. Witness: the RAL ral-partition single-thread A/B (private checkouts of the branch and main, plus PyAutoNerves), and one real Aris dataset log likelihood. Then the PyAutoArray/Galaxy/Lens suites, interferometer smoke tests, /review, and /ship_library.
  7. Write-up phase: an autolens_profiling results/notes/interferometer_direct_conv_symmetric_aris_2026_10.md (decision rule + tables), the campaign wiki pointer, and the PyAutoPulse tasks/interferometer_large_mask_aris.md Knowledge entry + decisions/direct-conv-symmetric-gate.md.

Key Files

  • autoarray/inversion/inversion/interferometer_numba/inversion_interferometer_numba_util.py: kernels and cost model
  • autoarray/inversion/inversion/interferometer_numba/sparse.py: kernel selection
  • autoarray/config/general.yaml: settings

Activity

  1. Jammy2211 commented on Oct 16, 2026

    @Jammy2211
    CollaboratorAuthor

    Checkpoint 2026-10-16 (session 13efb443, paused by the human; resume from here)

    State: uncommitted, on worktree ~/Code/PyAutoLabs-wt/direct-conv-symmetric/PyAutoArray (branch feature/direct-conv-symmetric, base e59d5fa9). Nothing is pushed and there is no PR. Scripts, patches and logs are in ~/Code/PyAutoLabs-wt/direct-conv-symmetric/l2_scratch/, outside git.

    Done

    • Serial curvature_direct_conv_symmetric with a branch-free ranked-CSC gather (v2).
      • Computed entries are bit-identical to curvature_direct_conv.
      • Gate interferometer_direct_conv_symmetric_max_work_fraction; non-finite weights fall back to the full kernel.
      • Cost model t_dc ×= min(1, κf) with κ = 1.32, and the factory routes on f.
      • real_space_mask_from refactor.
      • Tests: PyAutoArray 2086 passed / 4 xfailed (v2); PyAutoGalaxy 1357; PyAutoLens 832 / 1 xfailed.
    • RAL witness, synthetic Aris-512 geometry (job 411835): 1.81× / 1.85× / 1.90× at S 1,428 / 2,540 / 5,712, spreads ≤ 1.004. Passes.
    • Independent review: CLEAN (600-trial adversarial fuzz). Both minor findings are fixed: a null config value now means the default, and the mask is resolved only on NumPy.

    Real Aris data finding (l2_realdata.py, wf_orderings.py, laptop)

    • Real Delaunay columns are tall: the median row span is 90 of 341 rows, against 15 synthetic. Most likely multiple imaging.
    • So f is 0.587 with centroid order and 0.511 with ascending-ymin order. The default 0.5 gate kept Aris on the full kernel.
    • Measured full 40.4 s, symmetric 30.5 s (centroid) and 27.6 s (ymin): 1.32× / 1.46×. The κ = 1.32 model predicts 1.29× / 1.48×, so it is validated on real data.

    Human decision (2026-10-16): rank columns by ascending ymin and raise the default gate from 0.5 to 0.7. The model says the kernel wins while κf < 1 (f < 0.76).

    In flight at pause: v3 implementation of that decision, uncommitted. It may have finished in the worktree after the pause; a v3 patch is written to the scratchpad, so look in l2_scratch and run git diff.

    Next on resume

    1. Check v3: git diff, the new test file, the interferometer_numba, factory and settings tests, the full suite, and black.
    2. RAL real-data witness. The data and private clones are already staged in /mnt/ral/jnightin/scratch/aris_l2_20261016/:
      • aris_data/
      • PyAutoArray_l2 (apply the v3 patch)
      • PyAutoNerves, PyAutoFit, PyAutoGalaxy and PyAutoLens mains
        Run l2_realdata.py --n-pixels 512 --mesh-step 8 --repeats 2 [--nufft-chunk-size 1000000] on ral, serial. Gate: ≥ 1.3× on the curvature step and a matching log likelihood (round-off). Re-confirm the synthetic witness with ymin order too.
    3. Heart check (it was RED for unrelated reasons; an override is needed again if still RED), then commit, /ship_library, and PR. Tier is glance, but a RED override stops at PR-open.
    4. Write-up phase, which the human asked for explicitly because autolens_assistant will use it to choose setups: an autolens_profiling results note with the decision rule, plus the PyAutoPulse task Knowledge section and a decision record direct-conv-symmetric-gate. Include the multiple-imaging and tall-column insight.
  2. Jammy2211 commented on Oct 17, 2026

    @Jammy2211
    CollaboratorAuthor

    Library PR created: #636 (resumed 2026-10-17)

    v3 confirmed. Columns are ranked by ascending ymin and the default gate is 0.7, as the human decided on 2026-10-16. Two fixes went into v3:

    • Two docstrings that still described centroid ordering.
    • test__numba_inversion__curvature_matrix_and_data_vector_evaluated_once_per_likelihood did not count the symmetric kernel. Its geometry now passes the 0.7 gate, so the test counts either kernel once.

    Suites: PyAutoArray 2086 passed / 4 xfailed; PyAutoGalaxy 1357; PyAutoLens 832 / 1 xfailed. Smoke: 165/165 passed (the autocti environments have no GSL headers on the laptop, unrelated).

    RAL real-data witness: PASS (euclid-ral-compute-1, ral, exclusive, 1 thread, load ≤ 1, compile excluded, min of 2 repeats)

    vertices f full symmetric speed-up FFT route logL
    1,459 0.511 22.43 s 15.08 s 1.49× 15.32 s bit-identical
    2,577 0.504 22.83 s 14.95 s 1.53× — rel 3e-16
    5,746 0.495 24.22 s 15.35 s 1.58× — rel 2e-12 (log det Δ 0.0071 nats)
    • 5,746 log det. Control job 412493 factorised the full kernel's F from its lower and from its upper triangle. That alone moves the log det by 0.0077 nats at cond(F+H) = 7.2e13, so the 0.0071 difference is round-off.
    • Synthetic, ymin order: 1.83× / 1.86× / 1.90× (f 0.414 / 0.408 / 0.403). The ranked triangle is bit-identical.
    • Honest caveat. At 1,459 vertices the previous default route was FFT, which takes 15.32 s on this node, so the gain over that default is about 1.02×. On RAL, FFT/full is 0.68, against 0.89 for the laptop-calibrated constants. Refitting them belongs to the FFT size-lever phase and is not part of this PR.

    Heart: canonical pyauto-heart readiness is STALE (score 85, red_reasons: [], "release validation incomplete: no rehearsal for current source"). STALE passes for development, so no RED override was used.

    Jobs: 412490 (synthetic + real step 8), 412491 (FFT arm), 412492 (steps 6 and 4), 412493 (log-det control). Outputs are in /mnt/ral/jnightin/scratch/aris_l2_20261016/ and copied to l2_scratch/.

    Next: the write-up (autolens_profiling results note + campaign journal). Merge stays with the human (/prm).

  3. Jammy2211 commented on Oct 17, 2026

    @Jammy2211
    CollaboratorAuthor

    Shipped

    Merged 2026-10-17: library #636 (symmetric row-restricted curvature_direct_conv_symmetric kernel, ascending-ymin ranking, gate interferometer_direct_conv_symmetric_max_work_fraction = 0.7, κf route cost model; serial numba only) then write-up PyAutoLabs/autolens_profiling#425 (decision rule + RAL real-data Aris witness 1.49×/1.53×/1.58×, log L to round-off).

    Pending release: PyAutoArray#636 ships with the next PyAutoArray release. Campaign PyAutoLabs/PyAutoPulse#62 (lever L2). Human decision 2026-10-17: interferometer CPU work is closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions