Skip to content

perf: in-place Cholesky buffer + copy-free numba solves for fnnls_cholesky #452

Description

@Jammy2211

Overview

The positive-only inversion solve (fnnls_cholesky) dominates the numba CPU Delaunay likelihood — ~74% of a euclid-resolution evaluation on the production campaign fiducial (Delaunay + Hilbert-1250, PyAutoLabs/autolens_profiling#151). Profiling showed the cost is implementation, not linear algebra: ~150 Bro–Jong active-set iterations, each rebuilding the ~1300² Cholesky factor via np.insert/np.delete (two O(n²) copies per change) and re-copying/finite-scanning it inside scipy's solve_triangular/cho_solve. This task makes the solver maintain its factor in one preallocated buffer with copy-free numba triangular solves — same mathematics, a fraction of the memory traffic.

Plan

  • Hold the factor in the top-left k×k corner of a preallocated buffer, grown/shrunk in place as the active set changes (no np.insert/np.delete).
  • Replace the per-iteration scipy solve_triangular/cho_solve calls with numba kernels that read the buffer's upper triangle directly (no copies, no per-call finite scans).
  • Keep the existing numba Givens up/down-date kernels (proven bitwise-stable on strided views) and the out-of-place functions for reference and tests.
  • Validate against the pinned autolens_profiling delaunay_numba cells and the full test suite.
Detailed implementation plan

Affected Repositories

  • PyAutoArray (primary)

Branch Survey

Repository Current Branch Dirty?
./PyAutoArray main clean

Suggested branch: feature/fnnls-inplace-cholesky-buffer

Implementation Steps

  1. autoarray/util/cholesky_funcs.py: add cholinsertlast_inplace / choldeleteindexes_inplace (buffer-view variants of the existing out-of-place functions) and the numba kernels _solve_upper_transposed_buffer (row-oriented forward substitution for U^T y = b) and _cho_solve_buffer (full (U^T U) s = b solve).
  2. autoarray/util/fnnls.py: allocate U_buffer = np.zeros((n, n)) once per solve; thread (U_buffer, k_active) through the outer loop and fix_constraint_cholesky in place of the reallocated U.
  3. test_autoarray/util/test_cholesky_inplace.py: cross-implementation agreement at 1e-13, exact factor-property checks (U'U = active submatrix), scipy.optimize.nnls cross-checks, warm-start = cold-start.

Key Files

  • autoarray/util/fnnls.py — the Bro–Jong active-set loop
  • autoarray/util/cholesky_funcs.py — factor update + solve kernels
  • autoarray/inversion/inversion/inversion_util.pyreconstruction_positive_only_from (caller; unchanged)

Results (validated 2026-08-20, 4-core cloud container)

  • Real euclid 1310-param system: solve 3.29 s → 1.40 s (2.35×), identical active set, solutions agree to ~1e-9 absolute.
  • Pinned autolens_profiling cells (PR feat: numba CPU sparse-operator likelihood profiling (runtime, breakdown, multiprocessing scaling) autolens_profiling#152, rtol 1e-6) pass: euclid eval 4.92 → 2.68 s (1.83× end-to-end), solve step 3.61 → 1.57 s; hst 3.95 → 3.43 s.
  • test_autoarray/: 1026 passed; the only failures are 3 pre-existing pynufft transformer tests that fail identically on stock main in this environment.
  • Results are not bitwise identical to the old path (LAPACK picks layout-dependent but equally valid kernels; the old implementation already mixed layouts between its own iterations) — equivalence is enforced at 1e-13 in the new tests.

Original Prompt

Click to expand starting prompt

PyAutoMind draft/feature/autoarray/numba_cpu_likelihood_positive_only_solver_speedup.md — "Numba CPU likelihood: positive-only solver speed-up (the Delaunay ~74% euclid lever)". Candidate directions in win/effort order: (1) in-place factor buffer [this issue], (2) block pivoting, (3) cross-evaluation warm starts, (4) BLAS-thread guidance. Full context and the instrumented probe table live in the prompt file and the PyAutoLabs/autolens_profiling#151 findings trail.

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