Skip to content

fix(ngmix): release FitModel observations after each metacal fit#843

Merged
cailmdaley merged 1 commit into
developfrom
pr/ngmix-release-fit-obs
Jul 16, 2026
Merged

fix(ngmix): release FitModel observations after each metacal fit#843
cailmdaley merged 1 commit into
developfrom
pr/ngmix-release-fit-obs

Conversation

@cailmdaley

Copy link
Copy Markdown
Contributor

fix(ngmix): release FitModel observations after each metacal fit

Problem

The ngmix module's memory climbs steadily as it works through a tile — by roughly 5 MB for every galaxy it fits — and never comes back down until the tile is done. Each finished fit is held in full, so a tile with tens of thousands of galaxies accumulates gigabytes of image data it no longer needs. On a full production tile (~27,000 galaxies) during the Nibi run in #808, this reached ~11 GB per worker; in the controlled test below (3,000 galaxies) it reaches ~6 GB. Both are the same steady climb, just stopped at different points.

The current CFIS configs set SAVE_BATCH = 1000 to periodically write results out and clear them, which caps how high the climb gets — but the module's own default is to never clear until the tile finishes, and even with the cap the memory is pure waste. That per-worker footprint is what limits how many workers fit on a node, so it directly throttles throughput.

Root cause

Each per-object result in the resdict returned by MetacalBootstrapper.go in do_ngmix_metacal is an ngmix FitModel — a dict subclass that retains the full per-epoch metacal observations on the plain attribute .obs (set by FitModel._set_obs), plus the pixel arrays derived from them in ._pixels_list, together ~5 MB per object. ShapePipe accumulates these results and compile_results reads only scalar keys from each (g, g_cov, T, flux, s2n, flags, …) — the retained image arrays are never touched again. They are dead weight held to the end of the tile.

Fix

In shapepipe/modules/ngmix_package/ngmix.py, immediately after the metacal fit: null .obs and ._pixels_list on each FitModel in resdict, and drop the obsdict reference once average_multiepoch_psf has consumed it. Dict-like access to the fit results is untouched. +14 lines.

Evidence / testing

A/B on tile 196-305, objects 0-3000, SAVE_BATCH=1000, single core, RSS sampled every 30 s:

  • Baseline (unpatched): RSS ramps 1.1 GB → 6.1-6.5 GB over the run (~4.8 MB/object).
  • With fix: RSS flat, peak 0.90 GB over the identical 53-minute run.
  • Output catalog: numerically identical to the unpatched baseline — same HDUs, same 2950 rows and columns in each of 1M/1P/2M/2P/NOSHEAR, all data arrays equal (NaN-aware comparison; only timestamp headers differ).

Found during the Nibi test run in #808.

— Claude (Fable) on behalf of Cail.

🤖 Generated with Claude Code

Each resdict value returned by MetacalBootstrapper.go is an ngmix
FitModel — a dict subclass that retains the full per-epoch metacal
observations on the plain attribute .obs (set by FitModel._set_obs)
plus the pixel arrays derived from them in ._pixels_list, ~5 MB per
object. do_ngmix_metacal results accumulate in final_res until the
SAVE_BATCH flush, so process RSS ramps ~4.8 MB/object (to ~6 GB at
SAVE_BATCH=1000 on a 3000-object test) even though compile_results
reads only scalar dict keys.

Null .obs and ._pixels_list immediately after the fit (and drop the
obsdict reference once average_multiepoch_psf has consumed it). With
the fix, RSS stays flat at ~0.9 GB over the same run and the output
catalog is numerically identical to the unpatched baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WuMuqutUtBCgRxCoWjoebV

@martinkilbinger martinkilbinger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another nice and simple fix for a long-standing problem (that I had forgotten about...)

@cailmdaley
cailmdaley merged commit 97e16d5 into develop Jul 16, 2026
3 checks passed
@cailmdaley
cailmdaley deleted the pr/ngmix-release-fit-obs branch July 16, 2026 16:41
cailmdaley added a commit that referenced this pull request Jul 16, 2026
…star-cat store naming

Three P0-blocking fixes found by running against real data:
- exposures(name): the fabricated per-unit exp_numbers list must carry the
  original exposure name verbatim (2605805p — get_images matches
  <name>.fits.fz; the bare dedup id matches nothing). The index now stores
  both; exp_get_images passes --exp-name from the parse-time dict.
- tile_star_cat: the pre-generated store names tiles in ShapePipe's
  image-number convention (dots->dashes); translate when linking.
- profile: PYTHONPATH pinned to this branch's src/ (identical to
  develop@97e16d50 — orchestration commits never touch src/); NOT
  shapepipe-prod (drifted to a PR branch mid-run, and the live p3-batch1
  job reads it) and not the sif default (frozen pre-#843).
Run scoped to the 210/211 overlap quad (19 unique exposures, 15 shared —
exercises structural dedup; the zero-overlap 196 quad is kept for the
append-invariant test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NA8M1oLZLbWJxwoyTNrTAi
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.

2 participants