Repository navigation
The finest level's FAC blocks are cut from every rank's rows (#925) - #934
Conversation
…925) On row 5's TI band beside a split fault (n_rungs 80) the parallel build cut the zone and the structural block at rank boundaries and held seam rows in blocks on two ranks: the velocity solve took 4 iterations serially, 118 at np 2 and more than 200 at np 4. The test asserts every patch row in one block over all ranks, the serial block count and zone block (1650 rows), at most 5 velocity iterations (4 measured with the serial blocks at np 1, 2 and 4), and an unsplit zone cut into ceil(rows / 500) pieces at fac_block_rows 500. Passes serially; fails at np 2 and 4 before the fix. Underworld development team with AI support from Claude Code
In parallel each rank cut its own part of a fac_zone mask into blocks, with the ghost rows of its masked cells, and on a split level added its own structural rows (or, holding no zone cell, its whole structural subdomain) as one more block. Seam rows sat in blocks on two ranks, and rank boundaries cut the band and the structural block. On row 5's TI band beside a split fault the velocity solve took 4 iterations serially, 118 at np 2 and more than 200 at np 4 (n_rungs 80); at n_rungs 320 the outer solve never finished its first iteration at np 4. Holding each row once was not enough; the serial blocks rebuilt at np 2 and 4 gave 4-6 iterations per solve (campaigns probe 238338f). _gathered_zone_blocks gathers each mask's rows (nodes keyed by their global row, ordered by coordinate), segments each mask along strike as the serial branch does, adds the structural rows outside every mask as one block, and gives each block whole to the rank owning most of its rows. Serially, fed the parallel branch's inputs with every row twice, it returns the serial branch's blocks exactly (test_1139's fixture, caps 2000 and 500, split and unsplit). The structural patch without a zone keeps one block per rank. Underworld development team with AI support from Claude Code
Review findings, fixed: - Every rank recomputed the segmentation (an SVD) from the gathered rows and kept the blocks it owned; a rank whose LAPACK result differed in the last bit would cut different pieces, leaving rows in no block or in two. Rank 0 now gathers, cuts and assigns, and scatters each rank its blocks, which also keeps the gathered rows off the other ranks. - Nodes keyed by global row and ordered by coordinate broke ties between the two nodes of a split pair by row number, so a piece cut fell between them according to the partition (two masks, cap 300: 18 of 29 fake partitions differed by 4-8 rows). Nodes are now taken by coordinate: a split pair stays in one piece and the zone's pieces are the same at any rank count (30 of 30 fake partitions, np 1-7, caps 2000/500/300, split, unsplit and two masks). - test_1135 compared each rank's structural rows with that rank's blocks; a zone-keyed block now goes whole to one rank, so the rows are compared over all ranks. - test_1139 asserts the 500-row cap; the docs no longer claim the serial blocks exactly (the structural halo is _fac_patch_split_parallel's, #896); dated notes in the 2026-08 design record, whose 'seam cuts are free' and 'per-rank lists with local indices' #925 contradicts. Kept: with a zone keyed, the structural block is one LU on its majority owner, as in serial. Leaving it cut per rank (probe 238338f, local mode) took 2-4x the velocity iterations at n_rungs 80 and failed every solve at n_rungs 320, np 4. Its size is #895's question. Underworld development team with AI support from Claude Code
…rank count (#925) With no zone keyed, the finest level's structural patch was one block per rank. On test_1139's fixture with the zone unkeyed (row 5, n_rungs 80) the velocity solve took 3 iterations serially, 4 at np 2 and 6 at np 4 (70, 96 and 143 over the solve, kaiju report 12c). test_1139 asserts one block and at most 3 iterations; test_1133 asserts one structural block and, now that the zone's blocks are gathered, the zone strip's one block of exactly 1510 rows at every rank count. Serially all pass (8 passed); at np 2 and 4 the structural assertions fail until the structural patch is gathered. Underworld development team with AI support from Claude Code
With no zone keyed, the finest level's structural patch was one block per rank: each rank's owned patch rows and halo. It converged, but a partition boundary cut it: on row 5 with the zone unkeyed (n_rungs 80) the velocity solve took 3 iterations serially, 4 at np 2 and 6 at np 4 (kaiju report 12c). _gathered_zone_blocks becomes _gathered_patch_blocks and takes the split's (owned, subdomain) pair: with zone rows the structural rows outside them are one block as before; without, the gathered patch and halo are one block, held by the rank that owns most of its rows, as in serial. The structural LU then sits on one rank; patch_summary's block_rows_per_rank shows it and balancing it is #895's. A serial emulation over 30 fake partitions (np 1-7, two cut directions, rows renumbered) gives the serial block exactly, owned rows included. Underworld development team with AI support from Claude Code
…ty one At np 2 and 4 the zone strip is one block of 1510 rows on one rank, and the other ranks record a block of 0 rows, so comparing the per-rank lists with the serial one failed on a correct patch (kaiju report 14a). The fixture's fault band lies on one rank at np 2 and 4, so its one-block assertion guards the summary; test_1139 is where the partition cut the structural patch. Underworld development team with AI support from Claude Code
The serial branch keys the patch on the masks whenever a zone is keyed; the parallel branch fell back to the structural split when no rank held a zone cell. On test_1139's placed but unsplit fixture with an empty mask, serial installs no patch (passes at np 1). Underworld development team with AI support from Claude Code
…g and doc wording - The parallel branch decided whether to run the structural split from whether any rank held a zone cell; it now keys on fac_zone being given, as the serial branch does. A keyed mask with no cell gives no patch on an unsplit level and the split's rows as one more block on a split one. One allreduce fewer. - _gathered_patch_blocks's docstring said it cuts the zone as the serial branch does; a piece's cut can fall a node away from the serial one at a split pair (one site here, two nodes there). - The subsystem doc's summed rows are the owned rows; the subdomain rows follow the structural halo (#896). Underworld development team with AI support from Claude Code
…recorded The structural patch alone stays one gathered block. On row 5 with no zone at n_rungs 320 (kaiju), cut per rank the velocity solve took 67, 128, 131, 316 and 445 iterations at np 1, 2, 4, 8 and 16 (33 s to 112 s); as one block, 67 to 86 iterations and 22 to 37 s, with the owning rank at about twice the others' memory at every rank count. - test_1139: the structural block's size equals the gate's count of every rank's patch and halo rows, taken before the gather (a guard: no code lost a rank's rows); the iteration bound's comment records 3 at np 1, 2 and 4. - fault-split-multigrid.md: the structural block is one unbounded LU on one rank (#895), with the scaling numbers. - The 2026-08 design record's notes cover the structural patch as well as the zone. Underworld development team with AI support from Claude Code
…o mask test_1139 constructs a hierarchy with fac_zone keyed on rank 0 only (others None), and with an empty list of masks; both must raise ValueError at construction. On this library neither raises: the first would hang in the build (rank 0 in the zone's gather, the others in the cover gate's allreduce), and the second is keyed with no zone. The empty-list test fails serially here (DID NOT RAISE); the per-rank test runs at np >= 2. Underworld development team with AI support from Claude Code
- CustomMGHierarchy checks fac_zone collectively at construction: one allgather of each rank's mask count (-1 for None). If they differ, every rank raises ValueError, where the build would otherwise wait in the zone's gather on some ranks and the cover gate's allreduce on others. This takes the place of the zone_anywhere allreduce ef7804e removed. - An empty list of masks raises: it was keyed but held no zone, so the structural block beside the zone was (owned, sub) in parallel and (sub, sub) in serial. - Docstrings ask for a mask on every rank (all-False where a rank holds no zone cell) or None on every rank. - fault-split-multigrid.md: owned rows equal the serial count only with no zone keyed; with a zone, both counts carry the structural halo. The n_rungs 320 scaling paragraph compares iterations only: np 1 is the same code in both series, and the per-rank series ran before the stack's development merges, so the seconds were not comparable. Serial: test_1139/1133/1135 12 passed (the per-rank test runs at np >= 2), test_1022 fac_zone passed. All library producers pass an array or None. Underworld development team with AI support from Claude Code
…ial row 5 counts Underworld development team with AI support from Claude Code
The doc said the owning rank used about twice the others' memory at np 2 and np 16 alike. Kaiju report 15b: the owner peaked at 512-541 MB at np 2-16 while the others fell from 379 MB (np 2) to 248-284 MB (np 16), so the ratio is 1.35 at np 2 and 1.6-2.2 at np 4-16. Underworld development team with AI support from Claude Code
Adversarial review (round 1, on 32fc081), and what changed in e67aa11We read the diff against Fixed in e67aa11
Kept
Attacks that found nothing
Issues in this territory
Round 2 (on bed177b), and what changed in fc58912, ef7804e and 9b69572The structural patch alone is gathered too (fc58912). Cut per rank, it converged but degraded with the rank count. On row 5 with no zone, n_rungs 80, the velocity solve took 70 / 96 / 143 iterations at np 1 / 2 / 4. The fix now builds it from every rank's rows as one block. test_1139 failed first on the old library: 2 blocks and 4 iterations at np 2, 3 blocks and 6 at np 4. Finding 1, one unbounded LU on one rank: kept, on kaiju's measurements. We modelled the factor cost on one machine and expected the one-rank block to lose from np 8 up. The model's fixture refined the base along the fault, which is not one of the add-on's cases, so we dropped it and timed the cases we run. Row 5 with no zone at n_rungs 320 (30 048 rows in the block):
At np 1 both run the serial branch, the same code. We compare iterations, not seconds, because the per-rank series ran before the stack's development merges. Cut per rank, the largest block was still 19 582-21 072 rows, so the factor saved little while the iterations grew. The owning rank's peak memory was 512-541 MB at np 2-16, while the other ranks fell from 379 MB at np 2 to 248-284 MB at np 16: 1.35 times at np 2, 1.6-2.2 times at np 4-16. On row 3 (#918's case) the structural patch was already one block before this branch, and both overlays agree to 10 digits at np 1-16. The one-rank LU there takes 55-63 s at np 8 and 16 against 66 s serially. Balancing it is #895's question. Fixed
Verified on kaiju: test_1139 (7) and test_1133 (2) pass at np 1, 2 and 4 on ef7804e. Row 3 at r640/r1280 matches #918's iteration counts. The full tests/parallel run on fc58912 adds no failure beyond the baseline (test_1062, test_1064 at np 2, test_1078), apart from test_1133's two, fixed in bed177b. Issues in this territory (round 2)
Round 3 (on 9b69572), and what changed in c094073We found nothing high. The five keying cases (band + split, empty + unsplit, empty + split, none + split, none + unsplit) install in parallel what serial installs, apart from the documented site-cut and halo differences. The new test_1139 assertion equals the gate's pre-gather count by construction, and it fails if the gather drops a rank's rows. Fixed
a2e3839 merges the #921 branch (development through #927); test_1133/1134/1135/1136/1139 pass serially there. Underworld development team with AI support from Claude Code |
Shard 4 owns the whole --p N phase, and every new file under tests/parallel/ had also put its serial run in batch 4. It reached 55:32 on development (run 41564997729, #930) and was cancelled at 60 minutes on this PR, during test_1089 at np 2, which had not hung: development takes 48 s past the same point. Shards 2 and 3 took about 29 minutes. - The ten serial runs of tests/parallel files (test_1069 ... test_1139), about 6 minutes, go to shard 3. - test_05*/06*/07*, about 4 minutes, go to shard 2. --print-shards still gives [1,2,3,4], and check_test_coverage reports every file reachable. Underworld development team with AI support from Claude Code
…fix/custom-mg-many-ranks Clean merge; 99 commits behind before it. Underworld development team with AI support from Claude Code
In parallel each rank cut the finest level's FAC blocks from its own rows, so the blocks depended on the partition:
Of the two, the cut is what stops the solve converging. With a TI zone keyed beside a split fault, the union #921 makes the default, the velocity solves ran to their 200 cap at np 2 and 4 even with every row held once. Built from the serial blocks gathered across ranks, they took 4-6 iterations.
This PR is stacked on the #921 PR (
bugfix/fac-zone-structural-921).Change
_gathered_patch_blocksbuilds the finest level's blocks in parallel. Rank 0 gathers every rank's rows and cuts them as the serial branch does:fac_block_rows, with nodes taken by coordinate so that the two nodes of a split pair stay in one piece;_fac_patch_split_parallel's, which can differ by a few rows where a partition boundary runs along the band (FAC structural patch declined at np 2 on the linear fault fixture: 640 velocity iterations instead of 68 #896);zone_anywhereallreduce is gone.CustomMGHierarchydoes one allgather of each rank's mask count and raisesValueErroron every rank if the counts differ; rank 0 keyed and the others not would leave rank 0 in the zone's gather and the others in the cover gate's allreduce. An empty list of masks raises too. Pass a mask on every rank, all-False where a rank holds no zone cell, or None on every rank; both docstrings say so.patch_summary'sblock_rows_per_rankshows which rank holds each block. Its docstring,fault-split-multigrid.mdand the 2026-08 patch design record (dated notes where FAC zone plus a split fault's structural rows: the solve hangs at np 4 (converges in serial) #925 contradicts it) are updated.Measured on kaiju (Linux)
The zone case: row 5 of the add-on matrix, a TI band keyed beside the split fault, at n_rungs 80. Velocity iterations per solve, and their sum:
At n_rungs 320 (h_b/S = 24) and np 4, the outer solve before this branch did not finish its first iteration in 20 minutes: 31 of 33 velocity solves ran to the cap. After it, the velocity solve takes 110 and 117 iterations at np 2 and 4, against 94 serially.
The structural patch alone: row 5 with no zone, n_rungs 320, 30 048 rows in the block. Cut per rank it converged, but its iterations grew with the rank count; as one block they stay near serial.
At np 1 both columns run the serial branch. The per-rank column is e67aa11, this branch with the structural patch still cut per rank. It predates the stack's development merges, so the table compares iterations, not times. Cut per rank, the largest block was still 19 582-21 072 rows, so cutting saved little factorisation.
Row 3 (#918's case) does not change: its structural patch was already one block on one rank, and the builds with and without this change agree to 10 digits at np 1-16.
The one-rank LU is a known imbalance
The structural block is one unbounded LU on the rank that owns most of its rows, as in serial. That rank's peak memory is 1.6-2.2 times the others' at np 4-16. On row 3's 61k-row block the first solve takes 66 s serially and 63 s at np 8 and 16: the factorisation does not spread over the ranks. Balancing it is left to the parallel decomposition of the add-on (phase 3), under #895. #895's title still describes one LU per rank.
Tests
tests/parallel/test_1139_fac_zone_blocks_any_rank_count_parallel.py(new; level 2, tier B; np 1 in CI's serial phase, np 2 in the MPI phase) runs row 5 at n_rungs 80:ValueError.Each test except the unsplit-zone one failed first on kaiju at np 2 and 4, on the library before the matching change:
The unsplit-zone test passes on the old library; it guards the cut.
test_1133asserts the zone strip as one block of 1510 rows and the structural patch as one block at every rank count. Its band lies on one rank at np 2 and 4, so these assertions guard the summary.test_1135compares rows over all ranks, since each block now goes whole to one rank.On kaiju at c094073, test_1139 (8 passed and 1 skipped at np 1, 9 at np 2 and 4), test_1133 (2) and test_1135 (2) pass at np 1, 2 and 4. tests/parallel at np 2 and 4 on fc58912's library shows no failure beyond the development baseline (test_1062, test_1064 at np 2, test_1078: #923).
scripts/test.shgains a line in serial batch 4. The tip, a2e3839, merges the #921 branch (development through #927) and corrects the memory figures in the doc; the tests above pass serially there.Closes #925. Left open: #895 (the one-rank LU), #901 (the same one-rank pattern on the rotated FMG's coarse level), and #897, #886, #874 and #873 (untouched); verdicts in the review comment.
Underworld development team with AI support from Claude Code