Uh oh!
There was an error while loading. Please reload this page.
The adapt level loop stops on a collective fact, not a rank-local one - #596
The adapt level loop stops on a collective fact, not a rank-local one#596lmoresi wants to merge 1 commit into
Conversation
Each level of the marking loop ends with a DM refinement, which is collective, so leaving that loop is a decision every rank takes together. The sbr path broke on `refine.size == 0` — this rank's own cells are all fine enough, which says nothing about its peers'. The rank that finished first left for good and the others went round again into the refinement and waited. Measured on development at np=2: a callable metric demanding h=0.01 within r<0.15 of one corner and nothing elsewhere, 132 cells per rank, hung with neither rank returning; mpirun reached its timeout. Fixed, sbr returns (2421/132 cells), as do edge_split and nvb. No empty rank is needed and the metric is a callable, so global_evaluate is not involved. #512 attributes the hazard instead to `eval_metric` being global_evaluate and therefore collective, with a cell-less rank skipping it. Measured, that is not so: with one rank asleep for 10 s the other's global_evaluate returned in 0.06 s, for in-domain points and for points stranded outside the mesh alike. The rank-local `if cur_h.size:` guards were not hanging. They now route through `marking_metric` for uniformity with the stop, and the docstring says which of the two the measurement supports. The pure-Python nvb cell-list engine keeps its rank-local marking and break, with a comment: it raises NotImplementedError at np>1, so it is serial by construction and has nothing to protect. reconnect: the "a shared point was deleted" guard is assert-class but was raised rank-locally, which would hang the peers it was trying to inform. It is now a collective verdict naming the offending ranks. The emptiness test is read before the verdict and acted on after, so a rank that shares nothing still reaches it. test_pinned_set_is_partition_independent compared len(union) with allreduce(len(union), MAX) over a union every rank builds from the same gathered list — equal by construction, unable to fail. Replaced by an assertion that can: every rank pins every band vertex present in its own coordinate array. Closes#512. Underworld development team with AI support from Claude Code
lmoresi
commented
Aug 17, 2026
Adversarial reviewReviewed at 1. 2. The 0.06 s measurement bounds the call shapes we tried, not 3. The premise test constrains the fixture, not the defect. Checked and clean: the Underworld development team with AI support from Claude Code |
Closes#512.
Three follow-ups from the #488 re-review. The first turned out to be a live
hang rather than a latent one, and for a different reason than the issue gives.
The stop, not the metric
Each level of the adapt marking loop ends with a DM refinement, which is
collective. Leaving that loop is therefore a decision every rank has to take
together. The sbr path broke on
refine.size == 0— this rank's own cells areall fine enough — which says nothing about its peers'. The rank that ran out of
work left for good; the others went round again into the refinement and waited.
Measured on
developmentat np=2, with a callable metric demanding h = 0.01within r < 0.15 of one corner and nothing elsewhere, on 132 cells per rank:
and with the fix:
No empty rank is required, and the metric is a callable — evaluated rank-locally
— so
global_evaluateis not involved.What the issue attributes it to, and what we measured
#512 describes the rank-local
if cur_h.size:guards as skipping a collective,on the grounds that
eval_metricisglobal_evaluatefor a field or expressionmetric. That is not what happens. With one rank asleep for ten seconds, the
other's
global_evaluatereturned in 0.06 s — for in-domain points, and forpoints stranded outside the mesh entirely, which is the branch that allgathers:
It does not wait for its peers on these shapes, so a cell-less rank skipping it
does not hang. Those guards now route through a
marking_metrichelper foruniformity with the collective stop, and its docstring records which of the two
readings the measurement supports — working from the issue's premise, the next
person would "fix" sites that were never broken.
The pure-Python nvb cell-list engine keeps its rank-local marking and break, with
a comment saying why: it raises
NotImplementedErrorat np>1, so it is serial byconstruction.
reconnect.py — a collective verdict
The "a shared point was deleted" guard is assert-class, but raising it on one
rank while the others carry on into the next collective hangs the peers it is
trying to inform. It is now an allgathered verdict naming the offending ranks.
Note the shape of the fix: the emptiness test above it is read early and
acted on after the verdict. Putting the allgather where the guard was would
have left a rank that shares nothing returning before it — introducing exactly
the defect being fixed.
The vacuous test
test_pinned_set_is_partition_independentallgathered the pinned coordinates,took the union, and asserted
len(union) == allreduce(len(union), MAX). Everyrank builds that union from the same gathered list, so the two numbers are equal
by construction and the assertion could not fail whatever the pinning did.
Replaced with one that can fail: every rank must pin every band vertex present in
its own coordinate array, so a rank-local pin that misses a vertex a peer pinned
is caught — which is the defect class, since the owner is then free to move a
vertex the seam expects to stay.
Verified
test_0873_adapt_collective_stop_mpi.py: 4 passed at np=2, and withptest_0845, 7 passed at np=4. It carries a premise test asserting the metricreally does leave a rank with no work — without that, a passing run says only
that adapt returned.
./uw test: 1509 passed, 32 skipped, 2 xfailed.Underworld development team with AI support from Claude Code