Skip to content

FIX: reduce memory in flatfield by evaluating tilts only at slit pixels#2098

Open
tepickering wants to merge 2 commits intopypeit:developfrom
tepickering:flatfield_memory_fix
Open

FIX: reduce memory in flatfield by evaluating tilts only at slit pixels#2098
tepickering wants to merge 2 commits intopypeit:developfrom
tepickering:flatfield_memory_fix

Conversation

@tepickering
Copy link
Copy Markdown
Collaborator

Replace full-frame meshgrid tilt evaluation with per-slit-pixel evaluation using PypeItFit.eval directly. For spectrographs with many slits (e.g., fiber-fed IFUs with hundreds of fibers), the previous approach allocated a full-frame tilts array per slit, causing excessive memory usage.

This was originally implemented in #2080 and was required to get the original implementation there to work. However, it's a massive improvement for any multi-slit mode.

Replace full-frame meshgrid tilt evaluation with per-slit-pixel evaluation
using PypeItFit.eval directly. For spectrographs with many slits (e.g.,
fiber-fed IFUs with hundreds of fibers), the previous approach allocated
a full-frame tilts array per slit, causing excessive memory usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@kbwestfall kbwestfall left a comment

Choose a reason for hiding this comment

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

I'm approving this as it is, given that it reduces memory so substantially. But I had some questions that I hope we can think through before we merge this.

# Build a full-frame tilts image placeholder with only slit pixels filled
tilts = np.zeros(rawflat.shape, dtype=float)
tilts[onslit_padded] = _tilts_slit
del _tilts_slit, _spec, _spat
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

3 questions:

  • Instead of adding this here, could we instead pass onslit_padded to tracewave.fit2tilts and essentially get the same thing?
  • Do we need to explicitly delete the "work" arrays, or can we lean on garbage collection?
  • I'm wondering if there's a way we could minimize the number of times we need to create the tilts array, and/or try to use the same memory block.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

3 questions:

  • Instead of adding this here, could we instead pass onslit_padded to tracewave.fit2tilts and essentially get the same thing?

maybe. i'll take a deeper look.

  • Do we need to explicitly delete the "work" arrays, or can we lean on garbage collection?

i don't see any downside to just manually deleting them if we know they're not going to be used. especially given what look like GC issues with python 3.14.

  • I'm wondering if there's a way we could minimize the number of times we need to create the tilts array, and/or try to use the same memory block.

given the scale of the bug being fixed here, i am sure there are plenty of places in the code that can be streamlined and made more efficient.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

asked claude and got this response:

  1. Could we pass onslit_padded to fit2tilts instead? — Yes, this is a clean approach. fit2tilts currently creates a full nspec x nspat meshgrid (line 893), which is the memory problem. Adding an optional mask parameter would let it evaluate only at masked pixels and return either a sparse full-frame array or just the 1D values. There are only 2 other callers (wavetilts.py:154 and wavetilts.py:817), so backward compatibility is easy with a default mask=None. This would also benefit those callers if they ever need it.

  2. Explicit del vs garbage collection — The del statements are a belt-and-suspenders measure. In a tight loop over hundreds of slits, it ensures the previous iteration's arrays are freed before allocating the next. GC would eventually collect them, but in CPython the reference counting means del triggers immediate deallocation. Given the memory-sensitive context, it's cheap insurance.

  3. Reuse the tilts array across iterations — Currently tilts = np.zeros(...) allocates a new array each iteration. You could allocate once before the loop and tilts[:] = 0 each iteration to reuse the memory block. However, spec_coo is derived from tilts on the next line, so both would need coordinated handling. Modest win for standard spectrographs, bigger win for many-slit cases.

claude's point about cpython is a good one since the objects in question are numpy arrays.

Add an optional `slit_mask` parameter to `tracewave.fit2tilts` so that
tilt evaluation at only the relevant slit pixels is handled inside the
function rather than being inlined at each call site.  This addresses
review feedback on the memory optimization: the logic now lives in the
canonical location and both callers in `flatfield.py` and `wavetilts.py`
benefit from reduced memory usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tepickering
Copy link
Copy Markdown
Collaborator Author

went ahead and implemented @kbwestfall's idea of doing the fix at the fit2tilts level.

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