Skip to content

Commit e8cecbd

Browse files
committed
AMR: the batched fine advance turns on by default where the case admits it, decided by the toolchain -- an amr case that leaves amr_batched_advance unset gets it with amr_device_pack and amr_bat_pad = 0.1 when a trial validation with them passes (the same prohibitions that guard an explicit request); the Fortran defaults stay off because a default set there bypasses the validator (ledger 99); case.md row and three validator unit tests
1 parent f5f5152 commit e8cecbd

4 files changed

Lines changed: 54 additions & 2 deletions

File tree

docs/documentation/case.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ To restart the simulation from $k$-th time step, see @ref running "Restarting Ca
734734
| `amr_subcycle` | Logical | Advance the coarse level at the case dt and the fine level at dt/2 (two substeps; Berger-Colella refluxing). Requires `amr`; incompatible with `cfl_dt`. |
735735
| `amr_device_pack` | Logical | Pack and unpack the per-stage coarse-patch gather (F1/F2) over the plan's flat transfer list instead of one launch per transfer. The sends fuse to one kernel per family per stage; the receives fuse per contiguous (box, peer) run, so measured at np=8 the pack dispatches fall about 89x and the unpack about 4.3x. Wire bytes and floating-point values are unchanged. Requires `amr`; incompatible with `amr_subcycle`; the non-polytropic QBMM pb/mv twin keeps its per-transfer path. Default F. |
736736
| `amr_batched_gather` | Logical | Consume the per-stage coarse-patch gather (F1/F2) for all of a rank's blocks at once: the gathered patches live in one pool, and each wave runs one fused own-copy, one fused unpack and one batched ghost fill instead of one set of launches per block. Wire bytes and floating-point values are unchanged. Requires `amr` and `amr_device_pack`; incompatible with `amr_subcycle`. Default F. |
737-
| `amr_batched_advance` | Logical | Advance owned fine blocks of equal level and extent in batches of up to 8, stacked two ghost shells apart along the last active dimension, in one RHS call per batch. Requires `amr`; lock-step, Cartesian, uniform grid only; incompatible with the per-block fine-advance hooks (relaxation, IB, QBMM, IGR, chemistry, hypoelasticity, bubbles, MHD, relativity, damage, surface tension) and with Riemann-extrapolation BCs under `null_weights`; requires `amr_max_grid_size` > 0. Bit-identical to the per-block advance on a grid whose cell spacing is bitwise uniform (stacked blocks share the batch leader's coordinate arrays); roundoff-level differences otherwise, announced once at startup. Default F. |
737+
| `amr_batched_advance` | Logical | Advance owned fine blocks of equal level and extent in batches of up to 8, stacked two ghost shells apart along the last active dimension, in one RHS call per batch. Requires `amr`; lock-step, Cartesian, uniform grid only; incompatible with the per-block fine-advance hooks (relaxation, IB, QBMM, IGR, chemistry, hypoelasticity, bubbles, MHD, relativity, damage, surface tension) and with Riemann-extrapolation BCs under `null_weights`; requires `amr_max_grid_size` > 0. Bit-identical to the per-block advance on a grid whose cell spacing is bitwise uniform (stacked blocks share the batch leader's coordinate arrays); roundoff-level differences otherwise, announced once at startup. Default F. Left unset on an `amr` case, the toolchain turns it on (with `amr_device_pack` and `amr_bat_pad` = 0.1) whenever these rules admit it; set `amr_batched_advance = F` to force the per-block advance. |
738738
| `amr_max_blocks` | Integer | Upper bound on the GLOBAL refined-block count. Sizes replicated per-rank METADATA (~11 kB/block); block slots themselves are allocated lazily for blocks a rank owns, so this is not N x device memory. Exceeding it silently truncates the refined region (the clusterer warns). Must be >= 1 (default 1024) |
739739
| `amr_max_grid_size` | Integer | Absolute cap on a refined block's coarse-cell extent per dimension, the AMReX max_grid_size concept; must be >= 2 when set (default 0). With 0 the cap is derived from the decomposition and so shrinks as ranks are added, which tiles a fixed feature into more blocks the further you scale and makes the box set depend on the rank count. Setting it pins the cap, so the box set is identical at every rank count. The value may exceed half a rank subdomain: the solver scratch is then sized to the cap rather than to the subdomain, so per-rank memory grows as the cap raised to the number of dimensions |
740740
| `amr_max_level` | Integer | Maximum AMR refinement depth (number of refined levels above L0); must be >= 1 (default 1). Multi-level nesting (>= 2) is supported: static AMR (`amr_regrid_int = 0`) nests up to level 2, dynamic regrid (`amr_regrid_int > 0`) nests deeper (see @ref amr_multilevel) |

toolchain/mfc/case_validator.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,28 @@ def _format_errors(self) -> str:
32123212
return "\n".join(lines)
32133213

32143214

3215+
# The batched fine advance and its two companions, turned on for an AMR case that leaves amr_batched_advance unset
3216+
# whenever the case admits it. The Fortran defaults stay F on purpose: a default set there bypasses every rule above
3217+
# (ledger 99: ten of 58 AMR cases ran the documented unsupported combinations unguarded), so the decision lives here,
3218+
# under the same prohibitions that guard an explicit amr_batched_advance = T.
3219+
BATCHING_DEFAULTS: Dict[str, Any] = {"amr_batched_advance": "T", "amr_device_pack": "T", "amr_bat_pad": 0.1}
3220+
3221+
3222+
def apply_batching_default(params: Dict[str, Any]) -> bool:
3223+
"""Set the batching defaults in place when the case is an AMR case, leaves amr_batched_advance unset, and passes
3224+
simulation validation with them on. Returns True when they were applied."""
3225+
if params.get("amr", "F") != "T" or "amr_batched_advance" in params:
3226+
return False
3227+
trial = dict(params)
3228+
trial.update({k: params.get(k, v) for k, v in BATCHING_DEFAULTS.items()})
3229+
try:
3230+
validate_case_constraints(trial, "simulation")
3231+
except CaseConstraintError:
3232+
return False
3233+
params.update({k: params.get(k, v) for k, v in BATCHING_DEFAULTS.items()})
3234+
return True
3235+
3236+
32153237
def validate_case_constraints(params: Dict[str, Any], stage: str = "simulation") -> List[str]:
32163238
"""Convenience function to validate case parameters
32173239

toolchain/mfc/run/input.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ def validate_constraints(self, target) -> None:
123123

124124
# Generate case.fpp & [target.name].inp
125125
def generate(self, target) -> None:
126+
if case_validator.apply_batching_default(self.params):
127+
cons.print("[dim]amr_batched_advance left unset and admissible: batching on (amr_batched_advance = F forces the per-block advance)[/dim]")
126128
# Validate constraints before generating input files
127129
self.validate_constraints(target)
128130
self.generate_inp(target)

toolchain/mfc/test_case_validator.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import unittest
1111

12-
from .case_validator import CaseConstraintError, CaseValidator
12+
from .case_validator import CaseConstraintError, CaseValidator, apply_batching_default, validate_case_constraints
1313

1414
# A minimal 1D case that passes simulation validation.
1515
BASE = {
@@ -387,5 +387,33 @@ def test_not_tripped_without_alt_soundspeed(self):
387387
self.assertNotIn(self.MSG, self.errors_for({**TWO_FLUID, "riemann_solver": 4}))
388388

389389

390+
class TestBatchingDefault(unittest.TestCase):
391+
"""The batched advance turns on by default only where the validator would admit an explicit request."""
392+
393+
AMR = {**BASE, "amr": "T", "amr_regrid_int": 0, "amr_max_grid_size": 16, "time_stepper": 3, "amr_block_beg(1)": 0.25, "amr_block_end(1)": 0.75}
394+
395+
def test_admissible_amr_case_gets_batching(self):
396+
p = dict(self.AMR)
397+
self.assertTrue(apply_batching_default(p))
398+
self.assertEqual(p["amr_batched_advance"], "T")
399+
self.assertEqual(p["amr_device_pack"], "T")
400+
self.assertEqual(p["amr_bat_pad"], 0.1)
401+
validate_case_constraints(p, "simulation")
402+
403+
def test_prohibited_combination_stays_per_block(self):
404+
for k in ("ib", "igr", "stretch_x"):
405+
p = {**self.AMR, k: "T"}
406+
self.assertFalse(apply_batching_default(p), k)
407+
self.assertNotIn("amr_batched_advance", p, k)
408+
409+
def test_explicit_setting_and_non_amr_untouched(self):
410+
p = {**self.AMR, "amr_batched_advance": "F"}
411+
self.assertFalse(apply_batching_default(p))
412+
self.assertEqual(p["amr_batched_advance"], "F")
413+
q = dict(BASE)
414+
self.assertFalse(apply_batching_default(q))
415+
self.assertNotIn("amr_batched_advance", q)
416+
417+
390418
if __name__ == "__main__":
391419
unittest.main()

0 commit comments

Comments
 (0)