Skip to content

perf: NNLS solver controls for rectangular + Delaunay JAX pipelines #369

Description

@Jammy2211

Overview

Optimize the non-negative least squares (NNLS) solve used by the pixelization inversion pipelines. The JAX path solves via jaxnnls.solve_nnls_primal (PDIP); the 2026-07-09 Delaunay review measured that the solver is a modest but real lever (tolerance loosening ≈15–20%, hard-coded MAX_ITER=50 under vmap pays worst-case-lane, fresh ~n² Cholesky every PDIP iteration) while refuting warm-starts (cond(Q)~1e16). Priority is the rectangular pipeline — upcoming work concentrates there and it has no host callback, so the linear-algebra solve dominates its 52.7ms/32.9ms (A100 single/vmap) per-eval cost. All profiling uses the production pixelization + MGE lens light configuration.

Two deliverables beyond the speedup itself:

  1. A go/no-go checkpoint with measured NNLS shares per pipeline before any source edit.
  2. An evidence-based answer to: what is the right next optimization target for Delaunay post-refactor: qhull-only Delaunay callback, exact JAX visibility-walk point location #368 — residual scipy/qhull callback (still host-side and vmap_method="sequential"), the NNLS solve, or something else in the per-eval budget.

Plan

  • Re-baseline Delaunay + rectangular per-eval costs post PyAutoArray#368 (qhull-only callback), pixelization+MGE configs; local CPU now, A100 when RAL returns.
  • Measure the NNLS PDIP share and iteration counts per pipeline; instrument per-iteration Cholesky cost and vmap worst-lane amplification.
  • Checkpoint: report go/no-go + chosen lever set on this issue, including the Delaunay next-target verdict.
  • If go: expose solver controls (solver_tol, max_iter) — vendor the PDIP loop into autoarray or land params upstream in jaxnnls (decided at checkpoint with numbers).
  • New config keys default to current behavior; mirror into workspace configs.
  • Stability gates: Δlog_evidence parity on production rect + Delaunay fits, JIT-vs-eager drift, vmap batch checks, noise-scale invariance; before/after benchmarks.
  • Design note written for later PyAutoBrain ingestion; ship via ship_library.
Detailed implementation plan

Affected Repositories

  • PyAutoArray (primary — all source edits)
  • PyAutoLens (context/validation only; no edits expected)
  • autolens_profiling (untracked scratch/ only — repo is claimed by profiling-preopt-campaign, so no branch there)

Branch Survey

Repository Current Branch Dirty?
./PyAutoArray main clean
./PyAutoLens main clean
./autolens_profiling main untracked results only (claimed by profiling-preopt-campaign)

Suggested branch: feature/nnls-solver-optimization
Work Classification: Library
Worktree root: ~/Code/PyAutoLabs-wt/nnls-solver-optimization/ (created by /start_library)

Implementation Steps

Phase A — assessment (standalone, no source edits)

  1. Build autolens_profiling/scratch/nnls_speedup/ PoC scripts (extend the prior scratch/delaunay_speedup/demo_2_nnls_speedup.py), HST pixelization+MGE configs, rectangular first, Delaunay second.
  2. Re-baseline both pipelines against the merged refactor: qhull-only Delaunay callback, exact JAX visibility-walk point location #368 code path (qhull-only callback + visibility walk) so Delaunay numbers reflect current main.
  3. Instrument jaxnnls.pdip: iteration counts, per-iteration cho_factor cost (n≈1560), share of total per-eval; measure lax.while_loop-under-vmap worst-lane amplification on realistic batches.
  4. Decompose the Delaunay per-eval budget post-refactor: qhull-only Delaunay callback, exact JAX visibility-walk point location #368: residual host callback (qhull, ~8.8ms local, still vmap_method="sequential") vs NNLS vs mapping/curvature ops → the "right next Delaunay target" verdict.
  5. Checkpoint on this issue: measured shares, go/no-go, vendor-vs-upstream decision.

Phase B — implementation (PyAutoArray, only if go)
6. Expose solver_tol / max_iter controls. Preferred route decided at checkpoint:

  • vendor: autoarray/util/jax_nnls.py PDIP loop with configurable tol/max_iter (and any measured Cholesky-reuse win), dispatch in autoarray/inversion/inversion/inversion_util.py (~lines 275–321);
  • upstream: params added to jaxnnls.solve_nnls_primal, autoarray passes them through.
  1. Config keys in autoarray/config/general.yamlinversion (alongside nnls_jacobi_preconditioning, nnls_target_kappa), defaults = current behavior; mirror into workspace configs.
  2. NumPy-only unit tests for config plumbing/dispatch (no JAX in library unit tests).

Phase C — validation + ship
9. JAX validation via autolens_workspace_test parity scripts + fitness._vmap; Δlog_evidence parity on production fits (caustic-geometry lesson from #367: production grids, not toy grids).
10. Before/after benchmark table; design note for PyAutoBrain ingestion; ship_library + smoke tests.

Key Files

  • autoarray/inversion/inversion/inversion_util.py — NNLS dispatch (JAX → jaxnnls PDIP, NumPy → fnnls_cholesky)
  • autoarray/util/fnnls.py — NumPy Bro & Jong solver (not the lever; unchanged unless profiling says otherwise)
  • autoarray/config/general.yaml — inversion solver config keys
  • site-packages jaxnnls/pdip.py — hard-coded MAX_ITER=50, solver_tol = n*EPSILON, per-iter cho_factor (third-party, already PyAuto-modified)
  • autolens_profiling/scratch/delaunay_speedup/ — prior PoC + design notes this work builds on

Prior evidence (2026-07-09 Delaunay review)

  • Warm-start REFUTED: exact unconstrained init worsens 17→38 iters; jaxnnls (Q+I) init already optimal.
  • tol 1e-6 saves 1–2 of 17 iters (~15–20%), objective drift 5e-16 — config-key material.
  • A100 baselines (pre-refactor: qhull-only Delaunay callback, exact JAX visibility-walk point location #368): MGE 5.9ms/0.4–0.8ms; Rect 52.7ms/32.9ms; Delaunay 80ms/106.8ms (single/vmap). Delaunay callback shrink merged as PyAutoArray#368; A100 re-profile outstanding.

Original Prompt

Click to expand starting prompt

Optimize the fast non-negative least squares solver for the Delaunay and rectangular mesh pipelines

Type: feature
Target: autolens_profiling
Repos:

  • PyAutoArray
  • PyAutoLens
    Difficulty: medium
    Autonomy: supervised
    Priority: normal
    Status: formalised

You are working in the PyAutoLens codebase. First, review the recent Delaunay mesh speed-up work and understand what was changed and why. Then assess whether optimizing the fast non-negative least squares solver is the right next target, considering both Delaunay and rectangular mesh pipelines. If it is, propose and implement a careful optimization with benchmarks and numerical stability checks. Keep the design clean and document it so that it can be ingested into PyAutoBrain later. Before coding, summarize your understanding, confirm the optimization target, and outline a step-by-step plan.

(formalised by the Intake (Conception) Agent on 2026-07-09; user refinements at issue time: rectangular pipeline is the priority; profile pixelization+MGE configs only; re-baseline Delaunay post-#368; work standalone; report the correct next Delaunay target before implementation.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions