Stokes JIT: rank-0 compile + CSE before printing + configurable flags (#547) - #612
Stokes JIT: rank-0 compile + CSE before printing + configurable flags (#547)#612bknight1 wants to merge 1 commit into
Conversation
…#547) Four fixes for the HPC OOM in the runtime JIT compilation of Stokes pointwise functions (gcc: fatal error: Killed signal terminated program cc1 under mpirun -np N): 1. SNES_Stokes_SaddlePt._setup_pointwise_functions now calls getext with cache=True (the only call site that opted out), activating the existing rank-0-only compile + cross-rank disk handoff. Previously every rank compiled its own copy of the (potentially enormous) pointwise module, exhausting node memory (20x -> 1x concurrent compilers). 2. CSE before printing in generate_c_source: shared subexpressions become double xN = ...; temps evaluated in dependency order, shrinking the generated C ~63x on large rheologies (1.45 MB -> 23 KB header) and gcc peak RSS from 1.32 GB to 62-74 MB — comfortably inside a 900 MB per-rank cap. Semantics-preserving (temps are exact aliases); recovers _ccodestr on the new coordinate instances cse can mint; UW_JIT_NOCSE=1 escapes. 3. JIT compile flags configurable via UW3_JIT_CFLAGS (e.g. "-O1 -g0"); default adds -g0 to drop the debug info sysconfig injects. The env var is the memory lever for constrained HPC nodes. 4. Matrix-level sympy.diff for the Stokes uu/up Jacobian blocks instead of per-entry loops (~1.7x faster derivatives, bit-identical entries, flat PETSc [fc,gc,df,dg] layout preserved). Verified: 132 tests pass (incl. the FD-oracle Jacobian layout test, JIT determinism, constants routing) plus the CSE-off path; MPI rank-0-only compile and disk-cache reuse confirmed under mpirun -np 2. Underworld development team with AI support from Claude Code
There was a problem hiding this comment.
Pull request overview
This PR addresses MPI/HPC failures during runtime JIT compilation of Stokes pointwise functions by reducing redundant compilation work, shrinking generated C via SymPy common-subexpression elimination, and improving JIT compile configurability/performance.
Changes:
- Enable Stokes JIT disk caching so MPI runs use rank-0 compilation with cross-rank reuse.
- Add optional SymPy CSE prior to C printing to dramatically reduce generated code size for large expressions.
- Make JIT compile flags configurable via
UW3_JIT_CFLAGS(defaulting to-O3 -g0) and speed up Jacobian generation via matrix-level differentiation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/underworld3/utilities/_jitextension.py |
Adds lowering gate, CSE-based code shrinking, and configurable JIT compile flags for generated extensions. |
src/underworld3/cython/petsc_generic_snes_solvers.pyx |
Switches Stokes pointwise-function JIT to cached/rank-0 compile and optimizes Jacobian construction with matrix-level sympy.diff. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if verbose: | ||
| print( | ||
| f"JIT compile flags: {extra_compile_args}" | ||
| f"{' (from UW3_JIT_CFLAGS)' if _jit_cflags_env is not None else ' (default)'}", | ||
| flush=True, | ||
| ) |
Adversarial reviewReviewed against the PR head. CI is green and the change does what it says; one 1. The
|
The collective, demonstrated — and a branch you can takeFollowing finding 1 above, we reproduced it and fixed it rather than leaving it The deadlock is realMonkeypatching the disk lookup so rank 0 sees the cache and the other ranks do
Nothing exotic is needed to provoke it: the divergence is in which branch each What the fix doesBoth predicates are reduced before they are used to choose a branch:
Ranks that already hold the module keep it — the rank-0 compile, the ScopeThe gate predates this PR; what this PR changes is the exposure. Stokes was the Verified: full @bknight1 — the branch is there to cherry-pick or ignore as you prefer; say the Underworld development team with AI support from Claude Code |
How to take the fix — pick one, all three are fineThe commit is Option A — cherry-pick onto this branch (recommended). git fetch origin bugfix/jit-collective-gate
git checkout bugfix/stokes-jit-oom-cse-cache
git cherry-pick aaba1417b7d9ecd359d5407ceedd12987a406cf5
./uw build
git push origin bugfix/stokes-jit-oom-cse-cacheOption B — merge the branch in. Same result, keeps the fix as its own git fetch origin bugfix/jit-collective-gate
git checkout bugfix/stokes-jit-oom-cse-cache
git merge origin/bugfix/jit-collective-gate
./uw build
git push origin bugfix/stokes-jit-oom-cse-cacheOption C — say so here and we will push it onto this branch for you. It adds Verifying it yourselfThe reproduction is a monkeypatch, not a real filesystem race, so it is import sympy
import underworld3 as uw
from underworld3.utilities import _jit_cache as _jc
_real = _jc.load_module
# Rank 0 sees the published module, the others do not — what NFS attribute
# caching or Lustre metadata lag produces just after rank 0 publishes.
_jc.load_module = lambda *a, **k: _real(*a, **k) if uw.mpi.rank == 0 else None
mesh = uw.meshing.StructuredQuadBox(elementRes=(4, 4))
v = uw.discretisation.MeshVariable("Vj", mesh, mesh.dim, degree=2)
p = uw.discretisation.MeshVariable("Pj", mesh, 1, degree=1)
s = uw.systems.Stokes(mesh, velocityField=v, pressureField=p)
s.constitutive_model = uw.constitutive_models.ViscousFlowModel
s.constitutive_model.Parameters.shear_viscosity_0 = 1
s.bodyforce = sympy.Matrix([0, -1])
s.add_dirichlet_bc((0.0, 0.0), "Bottom")
s.add_dirichlet_bc((0.0, 0.0), "Top")
print(f"[{uw.mpi.rank}] entering solve", flush=True)
s.solve()
print(f"[{uw.mpi.rank}] SOLVE RETURNED", flush=True)mpirun --timeout 120 -n 4 python -u jit_divergence.py ; echo "rc=$?"
Clear the JIT cache between runs if you want a genuinely cold start After applyingNothing else in the PR needs to change. Findings 2 and 3 in the review above — Underworld development team with AI support from Claude Code |
Summary
Fixes the HPC compiler OOM and wall-clock timeout during runtime JIT compilation of Stokes pointwise functions (e.g.
gcc: fatal error: Killed signal terminated program cc1undermpirun -np N) described in #547.This PR implements four coordinated changes:
Rank-0 Compile and Disk-Cache Activation:
SNES_Stokes_SaddlePt._setup_pointwise_functionsnow callsgetextwithcache=True(the only call site that previously opted out). Under MPI, this activates the rank-0-only compile gate and cross-rank disk handoff, preventing N concurrent compiler invocations per node.Common Subexpression Elimination (CSE):
generate_c_sourcein_jitextension.pyperforms CSE prior to C printing. Shared subexpressions become intermediatedouble xN = ...;statements evaluated in topological dependency order. This shrinks generated C headers by up to ~63x on complex rheologies (1.45 MB → 23 KB) and lowers peakgccRSS from ~1.32 GB to ~62–74 MB. Escapable viaUW_JIT_NOCSE=1.Configurable JIT CFLAGS: Defaults to
-O3 -g0to eliminate debug symbol bloat from Pythonsysconfig. Customizable viaUW3_JIT_CFLAGS(e.g.-O1 -g0) for memory-constrained HPC nodes.Matrix-Level SymPy Differentiation: Replaces per-entry derivative loops with
sympy.diffacross whole block matrices (~1.7x faster Jacobian generation, bit-identical entries, PETSc[fc, gc, df, dg]layout preserved).Fixes #547
Verification
mpirun -np 2(ptest_jit_cache.py).test_1066_stokes_jacobian_layout.py).test_jit_cache.py,test_jit_deterministic_ordering.py,test_0103_jit_rampable_constants.py).Underworld development team with AI support from Claude Code