Document why the multigrid coarse level must be small (#644) - #666
Conversation
bknight1 hit an OOM at Pawsey: the FMG coarse solve is redundant+LU, which replicates the coarse factorization on every rank, and his coarsest level still carried 14k velocity DOFs - ~300 MB/rank replicated, killed at the 900 MB cap. Agreed on the issue that this is not a bug but a documentation gap. The premise the docs never stated: multigrid is cheap because the bottom of the hierarchy is solved exactly at negligible cost, and that is only true if the bottom is genuinely tiny. Nothing in the method makes it so and nothing warns when it is not. A 14k-DOF coarsest level means the hierarchy was built from too fine a base with too few levels, so the premise never held - the fix is another level underneath, not a different bottom solve. Doubling the base cell size and adding one refinement level lands on the same finest grid with every coarser level ~4x smaller in 2-D (8x in 3-D); bknight1 measured that as 683 -> 416 MB/rank for +11% linear-solve time, with the finest grid and the SNES iteration sequence unchanged. Also records that this is PETSc's own choice, not ours: PCSetUp_MG selects PCREDUNDANT above one rank and PCLU at one rank, its source comment reading "coarse solve is (redundant) LU by default" (src/ksp/pc/impls/mg/mg.c), which I confirmed by inspection at np = 1, 2 and 4. We set it explicitly only so a bundle cannot inherit a sibling's leftover options (#468). Deleting the keys and letting PETSc decide would change nothing - which is the point, because it puts the attention back on the hierarchy. Two corrections to what was there. The page claimed the default coarse solve was a bare `lu` in both the bundle listing and the parallel section, and told users to set redundant themselves for large partitions - it has been the default all along. And the SVD path is documented as what it actually is: keyed on VERIFIED rotation null modes, not on a problem being "rotated", because a rotated problem with Dirichlet walls has none and a blanket dense SVD coarse solve cost most of ~0.8 s per V-cycle application in #622. Adds a snippet to check the hierarchy you actually built (verified to run) and a rule of thumb: low thousands of DOFs at the bottom, and if it reaches five figures add a level. docs-build: succeeded, no warnings on this page. Underworld development team with AI support from Claude Code
There was a problem hiding this comment.
🟢 Approval recommended
The change is documentation-only, technically consistent with the codebase’s stated multigrid settings, and addresses the gap described in #644 without introducing functional risk.
Pull request overview
Updates the advanced multigrid preconditioning documentation to explain why FMG’s coarse level must remain genuinely small in parallel runs, and clarifies PETSc’s default coarse-solver behavior to prevent misdiagnosis of OOM failures as “coarse solver bugs.”
Changes:
- Corrects the documented FMG coarse-solver defaults to
mg_coarse_pc_type="redundant"withmg_coarse_redundant_pc_type="lu", and explains why this is PETSc’s default choice in parallel. - Adds a detailed explanation of the replicated-memory failure mode for large coarse levels (with the Setonix/#644 measurements) and emphasizes that the fix is to deepen the hierarchy, not swap the coarse PC.
- Clarifies when SVD is required (null-space robustness via verified rotation modes), and adds a snippet to inspect the built DM hierarchy.
File summaries
| File | Description |
|---|---|
| docs/advanced/multigrid-preconditioning.md | Documents coarse-level sizing constraints and correct PETSc/Underworld FMG coarse-solver defaults to prevent OOM surprises at scale. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…vice Reviewed the docs change by running its claims rather than re-reading them. Five findings, fixed here. 1. TELESCOPE CANNOT WORK HERE. The page offered `mg_coarse_pc_type = "telescope"` for large partitions, and my rewrite promoted it to its own subsection. PCTELESCOPE has no DMPlex support and every UW3 mesh is a DMPlex, so the snippet aborts the solve: "Support for DMPLEX is currently not available", PETSc error 56, reproduced at np=2. It is now a warning that it is unavailable - which strengthens the point, since it means there is NO bottom-solve escape hatch from an over-large coarse level. 2. THE SVD SNIPPET WAS INERT. `geometric_mg_bundle(coarse="svd")` builds an MGSettings and discards it; nothing is applied. It read as a user action and did nothing. Removed - the solver already selects SVD from the verified rotation-mode count. 3. THE OVERRIDE PROMISE IS FALSE ON THE ROTATED PATH - the one path that wants SVD. That route writes its bundle under the velocity sub-PC's own prefix, applies it, then deletes the keys; `solver.petsc_options[...]` sits under the SNES prefix and never reaches it. Verified the override DOES survive on the standard path (fieldsplit_velocity_mg_coarse_pc_type = svd after solve), so the promise is now scoped to that path and the rotated exception is a warning. 4. "LANDS ON THE SAME FINEST GRID" OVERCLAIMED. Measured, cellSize 0.10/ref2 gives 3,872 finest cells against 0.20/ref3 at 4,224 - 9% MORE, not equal, while the coarsest drops to 27%. #644's own numbers go the other way (227k vs 233k). Now stated as a few percent either side, with both measurements. 5. THE RULE OF THUMB HAD NO np OR MEMORY BUDGET IN IT, yet those are what set the threshold - the replicated factor is paid per rank against a per-rank cap. Rewritten around that arithmetic, calibrated on #644's 14k DOFs -> ~300 MB/rank at 5.16x fill. Claims that survived the review, all verified rather than asserted: the dm_hierarchy snippet works on a bare mesh (no solver needed); the per-level growth is exactly 4x in 2-D and 8.00x in 3-D (66/264/1056, 184/1472/11776); PETSc's own source picks redundant above one rank and lu at one. docs-build: succeeded, no warnings on this page. Underworld development team with AI support from Claude Code
Adversarial reviewCopilot approved this, but only on the grounds that it is documentation-only. Documentation-only is not the same as harmless: a doc that tells you to run something that cannot work costs more than silence, because you spend the afternoon believing the tool is broken. We reviewed it by running its claims rather than re-reading them. Five findings, two of them wrong advice. All fixed in 1. The telescope recommendation cannot work at all — and we had promoted itThe page offered
PETSc error 56, solve aborted. This was pre-existing text we inherited and endorsed. It is now a warning that the option is unavailable — which actually strengthens the page's thesis, since it means there is no bottom-solve escape hatch from an over-large coarse level. The hierarchy is the only lever. 2. The SVD snippet was inertWe showed: geometric_mg_bundle(coarse="svd")That constructs an 3. The "override it yourself" promise is false on exactly the path that needs itThe page promised you can keep It is false on the rotated path — the one path where an SVD coarse solve is the whole point. That route writes its bundle under the velocity sub-PC's own prefix, applies it, then deletes the keys again; anything set through 4. "Lands on the same finest grid" overclaimedMeasured on a unit box: 5. The rule of thumb omitted the two variables that set it"Low thousands of DOFs" has no rank count and no memory budget in it, yet those are precisely what decide the threshold — the replicated factor is paid per rank against a per-rank cap. Rewritten around that arithmetic and calibrated on #644's own numbers (~14k DOFs → ~300 MB/rank at 5.16x fill). What survivedStated here so the review is not only negative — each of these was run, not assumed:
Underworld development team with AI support from Claude Code |
Closes the documentation gap agreed on #644 (and its superseded twin #643).
The gap
bknight1 hit an OOM at Pawsey: FMG's coarse solve is
redundant+LU, which replicates the coarse factorization on every rank, and his coarsest level still carried 14k velocity DOFs — ~300 MB/rank replicated, killed at the 900 MB/task cap. The conclusion on the issue was that this is not a bug, it needs documenting.The premise the page never stated is the one that matters: multigrid is cheap because the bottom of the hierarchy is solved exactly at negligible cost, and that is only true if the bottom is genuinely tiny. Nothing in the method makes it so, and nothing warns you when it is not.
So a 14,000-DOF coarsest level is not a coarse-solver problem — it means the hierarchy was built from too fine a base with too few levels, and the "solve the coarse problem for free" premise never held. The fix is another level underneath, not a different bottom solve. Doubling the base cell size and adding one refinement level lands on the same finest grid with every coarser level ~4x smaller in 2-D (8x in 3-D). bknight1 measured exactly that: 683 → 416 MB/rank for +11% linear-solve time, finest grid and SNES iteration sequence unchanged.
This is PETSc's choice, not ours
Worth stating plainly, because the issue reads as though Underworld picked
redundantand thereby caused the OOM.PCSetUp_MGselectsPCREDUNDANTwhen the communicator has more than one rank andPCLUwhen it does not — its own source comment reads "coarse solve is (redundant) LU by default" (src/ksp/pc/impls/mg/mg.c). Confirmed by inspection atnp= 1, 2 and 4 on PETSc 3.25.We set it explicitly only so a bundle applied after another cannot inherit a sibling's leftover options (#468). Deleting the keys and letting PETSc decide would change nothing — which is the useful part, because it puts attention back on the hierarchy.
Two corrections to what was there
lu, in both the bundle listing and the "Parallel coarse solve" section, and told users to setredundantthemselves for large partitions. It has been the default all along, and a barelucannot factor a distributed matrix — it fails atnp > 1.svdfor anything rotated would have been an expensive mistake.Also
A snippet to check the hierarchy you actually built (
mesh.dm_hierarchy[0]is the coarsest), verified to run, and a rule of thumb — low thousands of DOFs at the bottom; if it reaches five figures, add a level.docs-buildsucceeds with no warnings on this page.Not done here
The size-aware default from the issue's option 1 (warn or switch when the coarsest level is large). That is a code change, and the call on #644 was documentation. It would be a reasonable follow-up: the check is cheap, and the failure it prevents currently presents as an OOM kill with no hint of the cause.
Underworld development team with AI support from Claude Code