Skip to content

Reuse linear rotated free-slip solver workspace#418

Open
gthyagi wants to merge 1 commit into
underworldcode:developmentfrom
gthyagi:bugfix/rotated-freeslip-linear-reuse
Open

Reuse linear rotated free-slip solver workspace#418
gthyagi wants to merge 1 commit into
underworldcode:developmentfrom
gthyagi:bugfix/rotated-freeslip-linear-reuse

Conversation

@gthyagi

@gthyagi gthyagi commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the unbounded resident-memory growth observed when a linear rotated free-slip Stokes system is solved repeatedly, as in transient mantle-convection models.

The change caches the rotated linear solver workspace and reuses it while the operator is unchanged. It also adds explicit invalidation for coefficient, time, boundary-condition, constitutive-model, solver, and DM changes.

Tracks #417.

Root cause

Repeated solves rebuilt two expensive classes of PETSc state:

  1. _residual_is_nonlinear() assembled and compared two Jacobians on every solve.
  2. The rotated solve path rebuilt the rotation operators, transformed matrix, pressure mass, nullspaces, field splits, KSP, and GAMG hierarchy.

Stage-isolation measurements showed approximately 76.77 MiB growth across 12 repeated nonlinearity probes and 117.23 MiB across 12 direct rotated solves. Explicitly destroying only the previous result vectors did not address the retained PETSc solver state.

Implementation

  • Cache the rotated residual linearity classification.
  • Keep one owned rotated linear workspace containing Q, Q^T, transformed operators, pressure mass, nullspaces, KSP/PC, coefficient state, and deterministic operator probes.
  • Skip Jacobian/preconditioner assembly for RHS-only changes when coefficient MeshVariable._state values are unchanged.
  • Refresh the operator when coefficient fields change.
  • Force refresh/comparison when an explicit physical time= is supplied, covering mesh.t-dependent symbolic operators.
  • Destroy the workspace when solver, DM, BC, or constitutive state is rebuilt.
  • Store the Cartesian reaction directly for topography instead of retaining matrix/RHS copies solely for later reconstruction.
  • Expose and forward time= separately from timestep= in Stokes.solve().
  • Preserve the direct-LU path.

The design and invalidation contract are documented in docs/developer/design/ROTATED_FREESLIP_LINEAR_REUSE.md.

Validation

The isolated fix was cherry-picked onto the current development tip. The branch is exactly one commit ahead with no merge conflicts; git diff --check and Python syntax checks pass.

Focused serial and MPI validation covered repeated RHS solves, viscosity invalidation, explicit-time refresh, spherical velocity and leakage, Zhong 3-D reaction topography, boundary traction, dynamic topography, and partition independence.

Case Repeated solves RSS change after fix Correctness
Serial spherical, cell size 1/4 30 peak +1.91 MiB RHS scaling error < 1e-9
8-rank coarse box 30 +11.84 MiB aggregate, +1.48 MiB/rank scaling error < 8e-14
8-rank spherical, cell size 1/4 10 +12.78 MiB aggregate, +1.60 MiB/rank scaling error < 3e-10
Serial viscosity x2 invalidation 1 rebuild old KSP destroyed; cache not reused expected half-velocity error 8.10e-11
Serial forced rebuild 1 rebuild old KSP destroyed; cache not reused scaling error 1.60e-10

Before this change, the serial spherical probe grew by 102.03 MiB over 12 solves (about 8.54 MiB/solve). The production 8-rank Zhong A1 run had reached 54.29-59.18 GiB aggregate RSS on a 16 GiB machine and triggered macOS watchdog restarts.

MPI checks used the Pixi environment's Open MPI launcher. The Homebrew Open MPI launcher is ABI/version-mismatched with the environment and can hang during startup; no FI_PROVIDER=tcp workaround was used.

Scope

This PR changes only the UW3 rotated free-slip solve lifecycle, focused tests, and developer documentation. Benchmark-repository scripts and result documents are not included.

Fix repeated transient Stokes solves rebuilding the rotated PtAP operator, fieldsplit Schur preconditioner, and GAMG hierarchy on every timestep.

Cache the linearity classification, rotation matrices, transformed operator, pressure mass, nullspaces, KSP/PC, and deterministic operator probes. Skip Jacobian assembly for RHS-only updates, refresh safely when constitutive coefficient MeshVariable state or explicit physical time changes, and destroy all owned PETSc state during solver rebuilds.

Store the Cartesian reaction directly for rotated topography instead of retaining native operator/RHS copies. Expose the existing physical time argument through the high-level Stokes.solve wrapper.

Add regression coverage for RHS reuse, viscosity-driven invalidation, explicit-time refresh, and inverse-viscosity scaling. Document lifecycle, invalidation, MPI launcher requirements, and validation for issue underworldcode#417.
@gthyagi
gthyagi marked this pull request as ready for review July 25, 2026 10:44
@gthyagi
gthyagi requested a review from lmoresi as a code owner July 25, 2026 10:44
@gthyagi

gthyagi commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Bounded production A1 restart: timing and memory

I reran the actual Zhong A1 production path after this fix, rather than only the synthetic repeated-solve probe.

Configuration:

  • restart from the durable step-500 checkpoint;
  • advance to step 510 (10 coupled steps);
  • 8 MPI ranks, cellsize=1/8, P2P1T1, Ra=7000;
  • rotated strong free slip and the evolved production temperature field;
  • Pixi Open MPI launcher, UW_MEMPROBE=1;
  • external 0.25-second per-rank RSS sampling;
  • hard abort at 12 GiB aggregate rank RSS or 900 seconds.

The run completed normally in 87.79 seconds and neither guard was reached.

The external RSS value is the first sample after each Stokes completion. Stokes RSS delta is the sum of the eight rank-local changes measured strictly inside that stokes.solve() call.

Phase Coupled step time (s) Aggregate rank RSS (MiB) Mean RSS/rank (MiB) Stokes RSS delta (MiB)
Initial Stokes setup - 3467.58 433.45 +1003.98
Step 501 12.87 2175.98 272.00 +178.07
Step 502 6.15 2240.27 280.03 +3.36
Step 503 5.98 2289.95 286.24 +0.90
Step 504 5.20 2322.45 290.31 +2.45
Step 505 5.53 2353.30 294.16 +0.68
Step 506 5.42 2583.67 322.96 +1.41
Step 507 5.67 2822.48 352.81 -26.38
Step 508 5.39 2832.45 354.06 +0.48
Step 509 5.61 2816.72 352.09 +1.34
Step 510 5.38 2842.64 355.33 +0.96

Timing summary

Measurement Result
Ten coupled steps, excluding initial Stokes setup 63.21 s
Mean, steps 501-510 6.32 s/step
Post-warm-up mean, steps 502-510 5.59 s/step
Post-warm-up range 5.20-6.15 s/step
PETSc SNES_Stokes.solve, 11 calls including setup 40.41 s
Previous production rate, steps 400-500 approximately 10.75 s/step
Bounded post-fix improvement approximately 1.92x

Interpretation

  • Initial rotated operator/fieldsplit/GAMG construction was the run's RSS peak.
  • Setup temporaries were released before step 501.
  • Whole-process thermal/SLCN workspace warmed through approximately step 507.
  • Aggregate RSS changed by only +20.16 MiB from step 507 to step 510 (+2.52 MiB/rank over three steps).
  • Stokes-local changes sum to -14.80 MiB over steps 502-510, so the repeated rotated solver workspace is not accumulating in this production run.

Step-510 diagnostics continue the checkpoint trajectory: paper time 0.242451, RMS velocity 57.8450, surface/CMB Nu 2.69543/3.20704, and mean temperature 0.317257.

This validates the fix over a bounded ten-step production continuation. It does not yet prove long-run whole-process stability; a guarded run of at least 100 steps on a larger-memory host remains appropriate.

Full benchmark report: results_bench_100_zhong2008_thermal_convection_A1.py.md

@gthyagi

gthyagi commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Guarded production follow-up: 100 additional A1 steps

I extended the real Zhong A1 cellsize=1/8 production trajectory from checkpoint 510 through checkpoint 610 on 8 ranks. To protect the 16 GiB Mac, this was run as two consecutive 50-step restart processes. Each used the Pixi MPI runtime, UW_MEMPROBE=1, 0.25 s rank-RSS sampling, an 8 GiB aggregate-rank hard stop, and a 1200 s timeout. Neither guard fired.

Segment Advancing steps Process elapsed Mean advancing interval Peak aggregate RSS Process-final RSS
510 -> 560 50 303.20 s 5.56 s 3456.72 MiB 3011.14 MiB
560 -> 610 50 310.50 s 5.73 s 3615.84 MiB 3194.33 MiB

Each process includes one extra restart-state Stokes solve, excluded from the advancing-step timing. In the first process, aggregate RSS reached 3220.98 MiB after step 559 and then released about 644 MiB at step 560. The second process released about 141 MiB at step 570; its external RSS slope over steps 590-609 was 0.74 MiB/step aggregate. Warm Stokes-local deltas were mostly zero or sub-MiB and included periodic net-negative releases. This is unlike the former per-step PtAP/fieldsplit/GAMG reconstruction growth.

The physical trajectory remained continuous. At step 610 (t=0.272219 in paper d^2/kappa units):

Vrms Surface Nu CMB Nu Mean T
57.8317 2.69388 3.20751 0.317261

This is deliberately a segmented 100-step validation, not a single uninterrupted 100-step memory profile. It supports the cache/reuse fix and validates a guarded checkpoint workflow; it does not justify an unmonitored full run on this machine. Full logs, RSS interpretation, and checkpoint metrics are documented in benchmark commit d797be4.

@gthyagi

gthyagi commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Extended production validation: checkpoint 810

The real Zhong A1 cellsize=1/8, 8-rank trajectory has now advanced another 200 steps, from checkpoint 610 through checkpoint 810, using four consecutive guarded 50-step processes. Every process used the Pixi MPI runtime, UW_MEMPROBE=1, 0.25 s RSS sampling, an 8 GiB aggregate-rank hard stop, and a 1200 s timeout. No guard fired.

Segment Process elapsed Mean advancing interval Peak aggregate RSS Final aggregate RSS
610 -> 660 323.90 s 5.88 s 3126.52 MiB 3087.09 MiB
660 -> 710 281.43 s 5.16 s 3189.25 MiB 3189.25 MiB
710 -> 760 280.76 s 5.16 s 3277.72 MiB 3277.72 MiB
760 -> 810 286.31 s 5.28 s 3735.03 MiB 3266.83 MiB

The four processes completed in 1172.39 s (19.54 min). Advancing solve intervals averaged 5.37 s, with a 5.21 s median. The largest process peak was 3.74 GiB, less than half the configured limit. Warm Stokes-local RSS repeatedly flattened to zero/sub-MiB changes and included multiple collective releases rather than cumulative workspace growth.

Physical checkpoint results remained continuous:

Step Paper time Vrms Surface Nu CMB Nu Mean T
610 0.27222 57.8317 2.69388 3.20751 0.31726
660 0.28710 57.8491 2.69535 3.20769 0.31740
710 0.30196 57.8811 2.69825 3.20824 0.31767
760 0.31678 57.9300 2.70239 3.20925 0.31802
810 0.33156 57.9729 2.70582 3.21041 0.31830

Guarded post-fix production evidence now covers 310 evolved steps after checkpoint 500. Full details are in benchmark commit 943a93e. This remains segmented validation; an unmonitored full local run is still excluded.

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