Skip to content

fix(su2): zero-allocation measurement loops — stop the ~5GB/measurement retention - #24

Merged
ThinkOffApp merged 1 commit into
feat/qcd-gpu-gauge-measurementfrom
fix/measurement-memory-leak
Jul 7, 2026
Merged

fix(su2): zero-allocation measurement loops — stop the ~5GB/measurement retention#24
ThinkOffApp merged 1 commit into
feat/qcd-gpu-gauge-measurementfrom
fix/measurement-memory-leak

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

Root cause (matches @claudemb's localization exactly)

wilson_loop_trace / wilson_loop_field / the flux shift+product path allocate a fresh full-lattice field on every step — thousands of ~96–191 MB alloc/free cycles per single_measurement. Python frees the objects, but the churn retains memory below the Python layer: ~5 GB per measurement, linear in sub-measurement count (2×1 fits, 8×2 OOMs), recovered only at process exit. That's why gc.collect(), gpt cache clears and GRID_ALLOC_NCACHE=0 all did nothing — there is no Python-level object leaking; it's allocator churn.

Fix

Per-grid _LatticeWorkspace (5 matrix + 4 complex buffers, ~1 GB held once on 24^4 SU(3) double). _loop_matrix builds the rectangle via ping-pong g.eval/dst-form g.cshift into fixed destinations — identical leg order and shift decomposition to the old code. Flux probe shifts and W·P products reuse the same workspace. Steady-state per-measurement allocation: zero.

Also: save-cfg-every default 1→0 (a 4-seed ×200 chain writes 76–153 GB of configs nothing consumes — the independent leak-hunt's top finding); dashboard SSE 30 s socket timeout (dead clients pinned multi-MB payload threads).

Gate before merge — @claudemb

  1. python3 tools/test_loop_workspace_parity.py in your working env — must print PARITY PASS (thresh 1e-11).
  2. One real single_measurement on the 8×2 config: peak compressor should stay flat instead of +5 GB per sub-measurement.
  3. Caveat to check in review: wilson_loop_field now returns a shared buffer (valid until the next loop call on that grid) — flux uses it within-iteration only, but flag anything else that stores it.

🤖 Generated with Claude Code

…nt retention

Root cause of the 9101 wedge (78GB compressor, freed only on process exit):
wilson_loop_trace/wilson_loop_field/the flux shift+product path allocate a
fresh full-lattice field on every g(expr)/g.cshift step — thousands of
~96-191MB alloc/free cycles per single_measurement. Python frees them, but
the churn retains memory below the Python layer (gc.collect, gpt cache
clears and GRID_ALLOC_NCACHE=0 all measured ineffective), linear in
sub-measurement count exactly as observed (2x1 fits, 8x2 OOMs).

Fix: a per-grid _LatticeWorkspace of 5 matrix + 4 complex preallocated
buffers; _loop_matrix builds the rectangle with ping-pong eval/cshift into
fixed destinations — identical leg order and shift decomposition, zero
steady-state allocation. Flux probe shifts and W*P products use the same
workspace. Holds ~1GB once instead of unbounded growth.

Also:
- save-cfg-every default 1 -> 0 (chain tool + app): at every-1 a 4-seed
  x200 24^4 chain writes 76-153GB of per-measurement configs nothing
  consumes; resume uses the bounded checkpoint file.
- dashboard SSE: 30s socket timeout so dead clients can't pin multi-MB
  payload threads for the run's lifetime.
- tools/test_loop_workspace_parity.py: old-vs-new numerical parity
  (traces, fields, shifts; threshold 1e-11). MUST pass on the mb before
  merge — the Mini has no working cgpt build to run it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@ThinkOffApp
ThinkOffApp merged commit 143257f into feat/qcd-gpu-gauge-measurement Jul 7, 2026
1 check passed
ThinkOffApp added a commit that referenced this pull request Jul 7, 2026
Addresses the residual ~17MB/substep growth @claudemb measured at 24^4 full
volume (climbed 41->52GB at 21% of measurement 0), which is SEPARATE from the
per-step allocation churn PR #24 already fixed (that part is bit-identical +
flat at 1GB on 8^4).

Two changes:
- clear_gpt_caches() now also clears gpt.core.foundation.lattice.cshift_plans
  — the one gpt cache the function was missing. It's bounded (str(grid) is
  dimension-based), so not the unbounded leak, but each plan can hold a
  full-lattice halo buffer on 24^4, pinning a few GB per measurement.
- maybe_mem_report() (SU2_MEM_REPORT=1): per-tdir gpt.mem_report census of
  every live gpt lattice + its creation stack. This LOCALIZES the residual:
  if the live-lattice total climbs across tdirs, the grower is a retained gpt
  lattice and its stack names the line; if it stays flat while RSS climbs,
  the retention is below gpt (a cgpt/Grid mempool that never shrinks) and the
  fix belongs in Grid allocator config, not this script.

Static analysis ruled out the Python layer: accumulators (loops_acc/
flux_profiles_acc) hold only scalars, and the full-volume sampler.mean is a
scalar g.sum. So the residual is C++-side — this instruments it precisely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant