Load-balance, active-box windowing, block-structured AMR - #1628
sbryngelson wants to merge 951 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in (“default-off”) family of performance/diagnostic features (load-weight and SFC partition diagnostics, weighted init-time decomposition, rank timing), plus major simulation capabilities (active-box RHS windowing and block-structured AMR) and corresponding post-processing support and documentation/validation updates.
Changes:
- Adds new runtime parameters and toolchain metadata/validation hooks for the experimental performance/AMR feature family.
- Extends the simulation code with new modules for active-box restriction, load-weight diagnostics, SFC partition reporting, rank timing, and AMR integration points (including restart/output plumbing).
- Updates post_process to read/write AMR fine-block overlays and adds/updates golden metadata plus documentation/indexing.
Reviewed changes
Copilot reviewed 82 out of 94 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| toolchain/mfc/params/descriptions.py | Adds user-facing descriptions for new experimental/performance parameters. |
| toolchain/mfc/params/definitions.py | Registers new parameters (AMR, hybrid sensors, load-balance diagnostics) and target applicability. |
| toolchain/mfc/lint_docs.py | Treats new validator checks as non-physics doc checks. |
| tests/F980C769/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/ECABA006/golden-metadata.txt | Adds golden metadata for active-box test coverage. |
| tests/DD4CD8F3/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/CC4213FD/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/BD21A5C0/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/BCBA6E74/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/ACE05393/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/987D9025/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/852CCB81/golden-metadata.txt | Adds golden metadata for AMR-related golden tests. |
| tests/65C375B4/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/4DADE04B/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/454C565F/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/3A474BEE/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/2FC423D3/golden-metadata.txt | Adds golden metadata for a new/updated test. |
| tests/13945217/golden-metadata.txt | Adds golden metadata for moving-IB under AMR test coverage. |
| src/simulation/m_viscous.fpp | Clamps FD coefficient indexing to avoid ghost-region coefficient OOB in IB drag gradient evaluation. |
| src/simulation/m_time_steppers.fpp | Integrates active-box bounds into RK update loops and interleaves AMR fine-stage/subcycle operations. |
| src/simulation/m_start_up.fpp | Wires up new modules (rank timing, active-box, load-weight, SFC partition, AMR) into init/timestep/finalize and restart I/O. |
| src/simulation/m_sfc_partition.fpp | Adds analysis-only SFC tiling + weighted partition prediction and reporting. |
| src/simulation/m_rank_timing.fpp | Adds per-rank wall-time imbalance measurement helpers and reporting. |
| src/simulation/m_load_weight.fpp | Adds per-cell load-weight field construction and rank-level imbalance reporting. |
| src/simulation/m_hypoelastic.fpp | Refactors FD coefficient setup into a callable update routine (supporting AMR grid swaps). |
| src/simulation/m_global_parameters.fpp | Adds AMR working-state mirrors and slot selection helper plus defaults for new parameters. |
| src/simulation/m_data_output.fpp | Adds output/report hooks for load-weight, SFC partition, and rank-time diagnostics. |
| src/simulation/m_checker.fpp | Adds input validation/prohibits for active-box, hybrid sensors, load-balance, and AMR configurations. |
| src/simulation/m_active_box.fpp | Adds active-box initialization/growth and debug envelope checking. |
| src/simulation/m_acoustic_src.fpp | Adds AMR-aware handling of acoustic source support (bounding boxes and overlap abort). |
| src/post_process/m_start_up.fpp | Calls AMR fine-data reader and AMR overlay writer when amr is enabled. |
| src/post_process/m_global_parameters.fpp | Adds default-off amr flag for post_process overlay behavior. |
| src/post_process/m_data_output.fpp | Implements AMR fine-block overlay mesh/variables output (Silo/binary) and multimesh registration. |
| src/common/m_phase_change.fpp | Exposes per-cell Newton iteration count and threads it through relaxation to support load-weighting. |
| src/common/m_global_parameters_common.fpp | Adjusts start_idx lifecycle/allocation and makes load_weight_wrt visible to GPU macros. |
| src/common/m_derived_types.fpp | Introduces a simple t_box type used by new partitioning infrastructure. |
| src/common/m_box.fpp | Adds box/partition arithmetic helpers (equal/weighted splits, box-from-splits). |
| src/common/m_boundary_common.fpp | Skips BC buffer population during AMR fine advance to rely on coarse-driven ghost fill. |
| docs/module_categories.json | Registers new modules under documentation categories. |
| docs/documentation/readme.md | Adds AMR section link to the documentation index. |
| .typos.toml | Adds project-specific abbreviations to the spelling allowlist. |
| D = ((gs_min(lp) - 1.0_wp)*cvs(lp))/((gs_min(vp) - 1.0_wp)*cvs(vp)) | ||
|
|
||
| #ifdef MFC_SIMULATION | ||
| if (relax .and. load_weight_wrt) then |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1628 +/- ##
==========================================
- Coverage 61.26% 60.60% -0.67%
==========================================
Files 84 96 +12
Lines 22330 31022 +8692
Branches 3265 5040 +1775
==========================================
+ Hits 13680 18800 +5120
- Misses 6207 8423 +2216
- Partials 2443 3799 +1356 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Upstream latent gap found during the MHD+AMR investigation (independent of this PR): Also for the record: MHD+AMR was attempted and re-gated on measured evidence rather than assumption — the coarse/fine seam is a continuous O(1) div(B) source that cleaning spreads but cannot remove (details in the amr.md support matrix row and commit ac203b1). |
|
…isting lint debt Merges the 20 commits added to sbryngelson/up-mega since ee7758b: the CCE descriptor and GPU_DECLARE fixes for the Frontier AMR abort, the NVHPC declare-ordering fix, restart format v2 (per-block owner/extents), amr_blocking_factor defaulting to 4, and the level-order clustering reductions. The delta is confined to AMR: m_amr, m_amr_regrid, m_amr_restart, m_constants, m_global_parameters, cmake/GPU.cmake, 14 AMR goldens, and two AMR-only toolchain hunks. No JWL, IBM, Riemann, or EOS source file is touched and no non-AMR golden changes. Merge was conflict-free; the local static-AMR GPU allocation cap for IBM survives intact. Also clears lint debt that predates this merge and blocked any commit: formatter line-wrapping in six JWL/IBM files, the duplicated rxn_val PROHIBITs in pre_process/m_checker.fpp (already enforced in case_validator.check_stiffened_eos, as its own docstring states), the stale hyperelasticity row in case.md (removed upstream), the missing sec-mhd anchor, and m_jwl/m_jwl_sources missing from module_categories.json.
…(47) glob_bytes counted 18 metadata ints per block (72 B) and ignored the amr_slots struct array, whose descriptors dominate at ~1 kB/slot -- a 10-20x under-report that silently propped up the metadata-distribution-deferred decision and would have steered the production-size rung. Ledger entry 47 records the landing, the toolchain-lock incident, and the forensic corrections.
… verdicts The [amr-mig] counters counted only blocks rank 0 itself sends and printed them unreduced -- the zero-cost audit read a frozen 141 MB startup transient at every P off what looked like machine-wide migration. Now SUM-reduced across ranks (ml_g, mirroring the [amr-halo] reduce just above; rank0_bytes kept for cross-run continuity). Ledger 48 records: the rhs growth above np128 is ranks-per-node density (memory-bandwidth contention), not P, pending the pre-registered np1024 confirmation; the dirty-box payoff is 4/np independent of bin occupancy (127x at np512); ladder migration is a frozen startup transient so keep-tol requires a moving-feature deck; escaped is 0 through np512; and the memory probe measured 201 MB/rank replicated metadata plus a ~3.3 GB/rank fixed footprint that OOMs dense packings.
After a fusion only the surviving box changed, so instead of restarting the whole pass the merge now (a) re-tests earlier survivors against the grown box (minimum index first, which is provably what the restart would find), else (b) re-tests later survivors, else resumes at the survivor's live successor. Bins are built once per cellw epoch and maintained incrementally (absorbed boxes unlinked, the survivor re-filed when its lo crosses a bin; extent growth doubles cellw and rebuilds, amortized log). Sequence identity was proven by a differential control (amr-bench/tools/merge_ctl.f90) against the retained restart implementation: identical fusion sequences on directed + random suites spanning 864 back-fusion chains; mutation skip-(a) diverges on 57 cases proving the control can fail; stale-resume is proven benign by containment + (b)-exhaustion; the survivor refile is kept as O(1) insurance with the Morton-order masking argument documented in the control. The [amr-merge] mean_outer_pos field is renamed mean_visit because its semantics change with the restart's death (per-pass scan depth -> monotone visit index) -- old cbar formulas must not be applied to new logs. Expected from the counter laws (F ~ 90 np, mop = np/2): pair tests fall ~mop/2-fold, two orders of magnitude at np512, growing with P. Gates: CPU 9/9 bit-identical with counters exactly matching the restart implementation (pair_tests 496/fusions 31 and 171/18 on the churn control); GPU 66/66.
… NaN) ab11150 put the same scratch arrays in both private() and map(alloc:) on one combined OMP construct in the HLLC and WENO kernels -- non-conforming OpenMP that its own commit message flagged as unverified on the other CI compilers. nvfortran resolves the conflict map-wins: cross-compiled PTX shows every scratch-array store with no thread index in the address chain, i.e. all threads share one vel_L etc., and the resulting races produce the deterministic first-step 'NaN(s) in timestep output' that has held the Phoenix NVHPC gpu-omp lane red on basic 1D tests since Aug 13 (lane green Aug 2; master unaffected -- it never carried the clause). amdflang resolves it private-wins, which is why the local gate could never see it, and an explicit conforming split (map on an enclosing target data region) breaks amdflang instead -- no conforming formulation reproduces its fast path. Measured tonight, interleaved 3x3 on the production case: the hack is worth 6.3 +/- 3% on amdflang (not the 26.4% recorded at introduction; the sequential-order measurement that suggested 12.6% was drift-confounded). Decision: pay the ~6% for conforming code on all four compilers; block-local scratch (option B) can recover it later as pure performance work.
…gesort The Morton sort ran an insertion sort over R concatenated per-rank runs -- O(n^2) in inversions, measured growing 5.1x over the first rank doubling (2.2x the next), of the same order as the merge residual beside it. Now a stable bottom-up mergesort on an index permutation with the payload gathered once at the end. Stability at key ties (fall back to acceptance order) is load-bearing for B1: proven by a differential control (amr-bench/tools/sort_ctl.f90, verdict saved in logs/sort_ctl_final.log -- identical orders on tie-heavy suites, an unstable mutation diverges on all 48 cases), and ties cannot occur in production anyway since accepted boxes have distinct lo corners. Sort-then-gather equals sort-with-payload because the stable order is unique (independently audited, including the gather-vs-scatter direction). Block-locals renamed msw/mslo/msmid/mshi to avoid shadowing the host's BR-stack arrays. Gates: CPU 9/9 with all 20 output files byte-identical to the pre-change binary; GPU gate follows.
Per-site XOR folds of mix(unordered pair id, per-channel sequence, block/dim/face key), BXOR-reduced at finalize and compared BY FAMILY (sends and receives of the same traffic live under paired audit sites -- a per-site compare mismatches structurally, found by exactly that false positive). Under the FIFO order contract the sender's and receiver's triples coincide message-for-message so the family hashes cancel; any cross-rank ordering divergence misaligns key against sequence and the finalize check aborts with the family named. The mixing hash is load-bearing: a raw packed-field XOR is provably blind to pairwise transpositions -- the property control (amr-bench/tools/oracle_ctl.f90, verdict saved in logs/oracle_ctl_final.log) demonstrates raw-XOR blind on 50/50 transposition trials and the splitmix64 fold catching 50/50. Wired at the eight F5 wave sites (faces + freg, lo/hi distinguished in the key, loop-scope of every peer/block variable verified); other sites opt in incrementally via the new optional peer/key arguments. A seeded canary (MFC_XA_SEED=1 corrupts exactly one fold, latched -- an XOR accumulator can return to zero, so testing it would allow self-cancelling double fires) proves the wiring can fail: it aborts fatally with family 5 named. The per-(site,dir,peer) sequence counters are O(P) per rank -- accepted for M0, replaced by plan-derived sequences at M1. Gates: healthy churn np=2 silent; canary aborts; 9/9 subset.
…ecv-plan scan The stage-fill wave rebuilt its coarse-owner receive plan by scanning every block in the machine at every RK stage -- at 1e5 ranks x 75 boxes that is ~360 M predicate evaluations per rank per step to find ~75 survivors. The surviving set is O(local) (level + not-mine + interior-overlap are all regrid-invariant), so it is now cached in amr_l1r_blk, rebuilt lazily when amr_mesh_epoch moves (NOT the owner-only dirty flag, which regrid and restart never set). List order is ascending block id, identical to the scan it replaces, so the wave's message order is unchanged -- verified live by the M0 order oracle (silent on the converted wave; the seeded canary still aborts). Resurrected from the never-landed stash pilot, adjudicated against the W1 conversion recipe: this site carries no safety calls between loop head and filters (the s_amr_check_lag_clear trap lives in other routines). Gates: 9/9 goldens, oracle silent at np=2, reldebug bounds arm clean. First of the ten per-stage scan conversions -- the 8.5-12% O(P)-growing family behind the weak-scaling gap.
Three more per-stage global scans converted (4 of 10 done). The stage-fill gather scan is the recipe's documented trap site: s_amr_check_lag_clear ran for EVERY level-1 block above the owner filter, so it keeps a dedicated scan gated on bubbles_lagrange (coverage unchanged, cost only in that configuration) while the gather loop walks a NEW padded list -- the same epoch-keyed walk now builds both flavors: raw region vs my interior (amr_l1r, the batch-1 list) and region +/- amr_cpat_mar vs my coarse range (amr_l1p, this loop's exact predicate); the body keeps its own intersection and empty-cycle as belt-and-braces. The two owner-side scans (stage-fill send prep, reflux-faces owner side) walk amr_my_blk with the level filter kept (the list carries all owned levels) and the ownership test retained defensively. List orders are ascending block id, identical to the scans replaced -- wave message order unchanged, verified by the M0 oracle (silent at np=2). Gates: 9/9 goldens, oracle silent, reldebug arm zero violations.
reflux-recv rides amr_l1p (the participates predicate is provably inside the padded-list predicate); freg recv, parent-fill send, and restrict-parent recv share the new amr_fch_blk list (level>=2, my parent, foreign child); freg send walks amr_my_blk; the parent-fill consume loop gets its own amr_own_blk list because it tests the MULTI-owner amr_owns_all notion, which amr_my_blk would silently narrow. The bigger find: f_amr_parent_block is itself an O(global blocks) scan and these waves called it per block per RK stage, making them quadratic in the global block count -- it is now cached per mesh epoch in the same builder walk, alongside a children adjacency (CSR) that replaces the identical hidden scan inside s_amr_sibling_face_weights. The lag guard keeps its full-coverage scan, gated on bubbles_lagrange. Survivor sets and ascending order are preserved at every site (exact predicates kept in bodies), so MPI posting order is unchanged by construction. Independently audited: the audit added the orphan-parent query guard (an unguarded amr_child_ptr(-1) would have turned a broken mesh into silent weight corruption), removed a re-entrant list refresh from inside a loop over that list, and keyed the cache on (epoch, num_blocks). Gates: 69/69 + 14/14 GPU goldens (isolated worktree), multi-level np=2 order oracle silent with all families balanced and the F2 traffic proven per-stage by 10-vs-20-step differencing (20 vs 51 msgs, 3 per marginal step = one per RK stage), seeded canary aborts as required, reldebug clean.
Every message in the reflux-faces and freg waves (payloads and the debug identity headers, both directions) now carries its own tag: base + band*2^16 + gen*2^12 + seq, with the band space starting at the next 65536 boundary above every legacy tag and asserted against MPI_TAG_UB at init. gen (mod 16) bumps at wave entry on every rank -- both call sites are rank-unconditional, and the subcycle path skips them on all ranks together -- separating successive waves that share a band. seq is the message's position in the pair's canonically ordered transfer list (ascending block id, then dim, lo before hi), derived independently by each end from the same replicated metadata, so message matching no longer depends on posting order or MPI non-overtaking; the in-wave per-peer counters reset touched entries only (O(active peers), never O(P)). The audit's xa_seq array -- the order oracle's per-peer O(P) interim state, 368 B x P per rank -- is deleted: call sites pass the plan-derived seq and the oracle folds it directly. New seeded gate MFC_XA_SEED=2 shifts one plan seq on rank 0's send side, modeling a sender deriving a different plan order; the finalize oracle must abort. Gates: family counts byte-identical to the pre-M1 baseline (130/51/6/32/120/16 on the multi-level np=2 oracle deck), seeds 1 and 2 both abort, reldebug clean, 16/16 goldens at np=1/2/4 including wide-L2 and force-migrated.
…eX, kernels are the per-GPU gap
…W1 wall-neutral at this scale; rs:rfp claim retracted
…aunch, but leader order and the 8-member batch cap keep it from reaching the rank that sets the lockstep
…np=2 sibling case
… tiles evenly inside the two-cell floor
…ime-feedback balance
…rger; stop rule fired, design void
…s are locals, not privatized region entities
…guments, 46.9 -> 4.0 descriptor copies per launch)
The s_hllc_riemann_solver 5-equation kernel privatized 22 work arrays; on amdflang every private array costs ~31 us of descriptor materialisation per launch (LIBOMPTARGET_INFO: 46.9 copies per launch, A5DAD70D 8 ranks). The per-face body, both hypoelastic emissions and all three directions, now lives in s_hllc_face_{x,y,z}[_hypo] device routines whose locals cost nothing; the two rsx arrays stay dummies. Copies per launch 46.9 -> 4.0, launches unchanged (3060), arguments 137 -> 23. The dead _hllc_priv fypp lists are removed with the private clause.
wave_speeds is a host-only namelist scalar: the kernel took it as an implicit firstprivate argument, but a declare-target routine reads the never-updated device copy (dflt_int) and every wave-speed branch flipped (9/9 NaN on gpu-omp, CPU goldens byte-identical). It now comes in by value like Re_size_loc1/2. Also: function_name inside GPU_ROUTINE is not fypp-expanded, so the Cray INLINEALWAYS names in m_weno and here now use string concatenation.
Gates: CPU goldens byte-identical on the 8 dynamic-regrid tests + A5DAD70D with -ffp-contract=off (2,130,902 values, amr-bench/bytecmp.sh vs 8afa115); amdflang gpu-omp 9/9 pass; precheck clean.
…g (the un-inlined call cost more device time than the launch saved) rhstrace 418669 vs 418457, untouched kernels flat within 1.5 %: the routine versions of weno ran +6/+22/+8 % and hllc +41/+41/+47 % per launch (scratch 28 -> 92 bytes), more than the host-side saving (gaps per batched call 6.03 -> 4.36 ms). amdflang does not inline a large declare-target routine by itself and the cray_inline directive is CCE-only. The new macro emits flang's call-site directive (!dir$ forceinline, verified in device IR to inline at -O0) under __flang__ and nothing elsewhere; it is built as a fypp string because the formatter rewrites a literal !dir$ into a comment. Applied at the three call sites; documented in gpuParallelization.md with the host-only-scalar rule. Gates: amdflang gpu-omp 9/9 pass on the 8 dynamic-regrid tests + A5DAD70D; CPU unaffected (directive is flang-only).
…(16.1 -> ~4 descriptor copies per launch) s_convert_conservative_to_primitive_variables privatized six work arrays (alpha_K, alpha_rho_K, Re_K, nRtmp, rhoYks, B); they are now locals of s_convert_cell, called per cell with GPU_INLINE_CALL. The kernel loop keeps only j, k, l; the outer routine's dead declarations go. The relativity branch's cycle becomes a return. Every module scalar the body reads is device-updated or a parameter (audited). Gates: CPU goldens byte-identical on the 8 dynamic-regrid tests + A5DAD70D with -ffp-contract=off (2,130,902 values, bytecmp.sh vs 8afa115); pre_process, simulation and post_process build; amdflang gpu-omp 9/9 pass.
…hrough private scalars rhstrace 418732: forced inlining changed nothing on the device (hllc still +41 %, scratch 92; weno +6/+22/+8 %). The assumed-shape dummies make every work-item rebuild two descriptors per cell, so the rsx arrays now arrive as explicit-shape (bounds as integers, base address only; the outer dummies are declared contiguous so no copy-in check is emitted). The WENO call passed array elements by reference, which the routine's stores could alias with its loads; the results now go to private scalars and are stored after the call. Local non-copt A5DAD70D trace: hllc median -9 %, weno -3 %; the pinned trace is the verdict. Gates: CPU goldens byte-identical (9/9, contraction off, vs 8afa115); amdflang gpu-omp 9/9.
…0, convert 16.1 -> 4.2 descriptor copies per launch, weno stays 13.9; the device-routine form is withdrawn The three kernel-body-as-device-routine conversions (25663cd, 1c7e1df, b68af5a and the 7926d48/453ba960 repairs) are replaced by the original loop bodies with the work arrays declared in a block inside the loop. Block-locals are iteration-private by the language, so the region maps nothing for them: on the descr.f90 probe the block variant runs at the module-only floor (83.0 us per launch vs 265.6 with six private arrays). The routine form was a net loss on the device however it was called (rhstrace 418669/418732/418529: hllc +34..47 %, weno +6..42 % per launch, untouched kernels flat): the inlined body inherited the declare-target routine's 128-VGPR budget (weno 130/134/170 -> 118 VGPRs, 148 vs 102 s_waitcnt) and nothing in Fortran lifts it. GPU_INLINE_CALL is removed; gpuParallelization.md documents the block idiom and why not a routine. Gates: CPU goldens byte-identical on the 8 dynamic-regrid tests + A5DAD70D with -ffp-contract=off (2,130,902 values, bytecmp.sh vs 8afa115); pre_process, simulation, post_process build; amdflang gpu-omp 9/9 pass; precheck clean.
…iteration of it rhstrace 418529b (205ec78): with the block inside the inner seq loop the WENO kernels ran +30..50 % per launch over base (5465/5404/5617 vs 3624/3732/4258 us), while HLLC, whose block sits directly in the collapsed body, returned to base (2387/2429/2369 vs 2228/2332/2270). Same declarations, one level up. Gates: CPU goldens byte-identical (9/9, contraction off, vs 8afa115); amdflang gpu-omp 9/9.
Conflicts: m_cbc.fpp (keep the module flux_rsx_vf use alongside upstream's f_vel_ramp; s_cbc keeps this branch's signature and gains upstream's ramp local) and m_time_steppers.fpp (upstream's stage-top GPU_UPDATE of mytime goes ahead of this branch's coarse/deferred/tiled/fine RHS block; the later update upstream removed is gone). Gates: CPU and amdflang gpu-omp on the 7 CBC tests + the 8 dynamic-regrid tests + A5DAD70D, all pass; all targets build.
…HLLC's 47 descriptor copies per launch out; wall -69 ms/step (t -5.2), MFC excess 0.615 -> 0.530 s/step node-matched (1.60x -> 1.53x); the device-routine form withdrawn as a device-side loss
…skew, ranks 3/5 set every one; the wall is the heaviest rank's own work plus its floors; host staging priced at ~40 ms/step of copies
…ds and receives them by device address The four wire pools (amr_fw_sq/sp/rq/rp) were host allocatables: every box's slice crossed PCIe twice per wave through the pack/unpack kernels' copyout/copyin (~240 copies of 30 us-10 ms, ~40 ms per step on the critical rank, ledger 161). With rdma_mpi the pools are now created on the device when (re)allocated (s_amr_fw_szr dev=) and the ten data-carrying MPI_IRECV/ISEND sites pass device addresses through GPU_HOST_DATA, as the base halo already does; the kernels' copyin/copyout then find the slice present and copy nothing. Every step-path writer and reader of a pool is a device kernel; the regrid gather uses its own buffers; the exchange audit's host-written headers keep the host path (amr_fw_dev is off when XA_NH > 0), as do CPU builds and rdma_mpi = F. Gates: CPU goldens byte-identical (9/9, contraction off); amdflang gpu-omp 9/9 at rdma_mpi = F, and GPU-vs-GPU goldens byte-identical (9/9) between this and its parent with rdma_mpi forced on in the test base config; LIBOMPTARGET_INFO on A5DAD70D: the m_amr.fpp host<->device copies >= 8 KB fall 184 -> 32 per 4 steps.
…ctions pass), statement 2 node-matched 0.557 -> 0.404 s/step (1.56x -> 1.11x AMReX)
…largest-first + cap 16: calls -44 % on the critical rank, launches -27 %, step unchanged); not landed
The wall is one rank's serial chain (ledger 161) and a lever is worth pricing only against that rank's own segments, which needs every phase per rank, not the twelve rows the report picked. Under rank_time_wrt only; rows whose global sum is zero are skipped. Readers (mfclog) parse the rows generically.
…terior On a fine block (or batched slab) s_reconstruct_cell_boundary_values ran WENO over the full buffered range in the two transverse directions -- four ghost planes each side whose reconstructions feed nothing, since the Riemann faces and the flux differences stay inside the block. On a ~98^3 block that is 17 % of the reconstruction's planes (the uniform 200^3 grid pays 8 %); WENO and its pack are ~150 ms/step of the critical rank's fine RHS. The window is 0:m/n/p, not idwint, because the batched advance widens idwint to the ghost shells for the conversion. The normal direction keeps its shell (block boundary faces); viscous runs keep the full window. A5DAD70D: WENO grid 57600 -> 36864 work-items, 69-73 -> 55-58 us per launch. Gates: CPU goldens byte-identical (9/9, contraction off) and GPU-vs-GPU goldens byte-identical (9/9) against 6b5c734; precheck clean.
…(rhs -12.5 ms/step, wall -22); every phase per rank; the second-arm stall is the node
Lines of Code
|
Summary
An opt-in, default-off family of performance features and the measurement infrastructure they rest on. With all flags at their defaults the only touched production path is
s_mpi_decompose_computational_domain, refactored through the newm_boxmodule (byte-identical; covered by the existing suite).m_box(partition arithmetic),m_load_weight/load_weight_wrt(per-cell load-weight field + imbalance metric),m_sfc_partition/sfc_partition_wrt(Morton-SFC predicted-imbalance diagnostic),m_load_balance/load_balance(weighted static decomposition at init; AMR-fine-work-aware),m_rank_timing/rank_time_wrt(per-rank compute-time diagnostic).m_active_box/active_box: restricts reconstruction/Riemann/RK windows to a light-cone-grown box around non-ambient flow; strict-subset golden-tested.hybrid_wenoandhybrid_riemann(+hybrid_weno_eps,hybrid_smooth_flux): linear-optimal weights / central-or-Rusanov flux in smooth cells, full WENO/HLLC at flagged discontinuities (Jameson sensor, stencil-dilated, per-level under AMR).m_amr+m_amr_registers: two-level 2:1 refined block hierarchy; conservative restriction and conservative-linear prolongation with physics-specific closures; per-stage flux registers with Berger–Colella refluxing; Berger–Rigoutsos multi-block dynamic regrid; optional dt/2 subcycling; multi-rank (single-owner blocks assigned by Morton-SFC work balancing at each regrid, with migration; blocks may span rank seams via P2P coarse↔fine gather/scatter; same-level seam halo; distributed registers); restart (both IO modes, regridded-layout persistence); AMR-aware post-processing (fine blocks visualizable as Silo overlay domains); GPU-resident fine level on both OpenACC and OpenMP offload.Full algorithm and user documentation:
docs/documentation/amr.md(support matrix enforced at runtime by the checker — unsupported combinations abort with named messages, never silently).AMR physics support matrix (abridged; authoritative table in amr.md)
Supported and golden-tested: single- and multi-fluid (5-eq,
mpp_lim) · 6-eq with per-block pressure relaxation · viscous (refluxed) · phase change (relax) · chemistry incl. species diffusion · Euler–Euler bubbles (polytropic/non-polytropic, mono/polydisperse, QBMM incl. non-polytropic with per-blockpb/mvside-state; dynamic regrid + subcycle) · acoustic sources (coarse-grid support with regrid exclusion) · immersed boundaries (multi-body, static or prescribed-motion, incl. dynamic regrid with body-containment expansion and per-substage guards) · 2D axisymmetric (per-block WENO-coefficient recompute) · stretched grids (exact parent-bisection ghost coordinates + per-swap coefficient recompute) · hybrid WENO/Riemann sensors (per-level) · Lagrangian bubbles (cloud excluded from blocks; two-way coupling on the coarse grid; regrid clips around the moving cloud) ·active_box(blocks contained in the growing window; agrees with plain AMR to ~1e-14) · IGR (restriction-only coupling: fine sigma solve seeded/Dirichlet-bounded by the coarse solve; documented truncation-order seam, exact free-stream) · 1D MHD/RMHD (div(B)=0 by construction in 1D; HLL and HLLD, incl. relativistic).Gated with named aborts (documented rationale): surface tension (seam force imbalance is structural — three fixes attempted and diagnosed in amr.md) · 2D/3D MHD (attempted and measured: the c/f seam is a continuous O(1) div(B) source GLM cleaning cannot remove — needs constrained-transport-class B prolongation/reflux) · hyperelasticity · 3D cylindrical (global azimuthal filter) · force-driven IB (
moving_ibm=2) · STL bodies · Riemann-extrapolation BCs (bc=-4) ·amr_subcycleunder IGR · stretched grids with Lagrangian/IB-regrid (uniform-spacing index conversions).Validation evidence
Performance
Measured on AMD MI210 (gfx90a), OpenMP offload, one MPI rank per GPU, double precision throughout — MFC and the reference are compared at the same precision. Every figure is a per-step wall differenced between two from-scratch runs of different length, so start-up and mesh ramp-in cancel, and is the mean of three repetitions with the two codes interleaved inside one node reservation.
Reference. AMReX's
Tests/GPU/CNSblob test, rebuilt withNUM_GROW = 4to match MFC's WENO5 ghost width, on a deck matched to MFC's: unit periodic box, entropy blob advected at u=1, gamma 1.4, inviscid, 400^3 base,max_level = 2,ref_ratio = 2,regrid_int = 20, no subcycling, reflux on, density-gradient tagging thresholded to tag the same cells.AMR overhead on the matched deck (8 GPUs, 400^3 base + 2 levels)
The two codes do not run the same numerics, so the comparable quantity is each code's excess over its own uniform-grid arm at the same advanced-cell count:
MFC pays 1.11x AMReX's AMR overhead on this deck (both codes on the same node, same session; the control build of this branch read 0.557 / 1.56x on the same node the same afternoon, treatment-control-treatment). Two changes carried it there, one commit each:
rdma_mpithe four wire pools now live on the device and MPI sends and receives them by device address, exactly as the base halo already did; the pack/unpack kernels find the pool present and copy nothing. Pre-registered A/B against the parent build, 3 reps: wall -73 ms/step (t = -5.7), gather phase -47, restriction -35, host unpack/fill -27, transfer waits -20, launches and device work unchanged, goldens byte-identical GPU-vs-GPU withrdma_mpion.An earlier revision of this section read 1.73x (0.627 / 0.362) on a different node; MFC's host-bound overhead moves with the host, AMReX's device-bound step does not, so every ratio here is read against a control on the same node. The previous revision of this section read 1.73x (0.627 / 0.362) on a different node; a node-matched control of that revision's code reads 0.615 / 0.384 = 1.60x on this one, so the change described next is worth -0.085 s/step of MFC's excess (-14%), and the rest of the difference is the node (MFC's host-bound overhead moves with the host, AMReX's device-bound step does not). AMR does not slow the base solve: the AMR and uniform arms agree on the base-solve phases to -6.9 ms/step, inside noise.
The descriptor tax (the second change). On amdflang every OpenMP target launch materialises a descriptor for each mapped entity, and a
privatearray costs one per launch (~31 us each); the HLLC kernel privatized 22 of them (47 copies per launch on 137 kernel arguments), WENO5 six, the cons-to-prim kernel six. Declaring those work arrays in ablockinside the loop body instead makes them iteration-local by the language and maps nothing: HLLC 47 -> 5 copies per launch, cons-to-prim 16 -> 4, launches unchanged, goldens byte-identical with FMA contraction pinned. Pre-registered A/B against the parent build, 3 reps: wall -69 ms/step (-6.0%, t = -5.2), RHS phase -50 ms/step, fixed cost per batched RHS call 3.7 -> 1.0 ms. (Moving the kernel bodies into device routines takes the copies out just the same and makes the device 35-45% slower on those kernels: the inlined body inherits the routine's register budget. Withdrawn; seegpuParallelization.md.)Where MFC's 627 ms/step went before that change: 521 ms is AMR machinery, of which 352 ms (56%) is MPI wait. Largest phases: reflux 110, coarse/fine gather 109, restriction 104 ms/step. Reducing that wait is the active work; it is not a correctness or an API concern for this PR.
That wait is imbalance, not a transfer floor. An earlier revision of this section read the per-rank minimum MPI wait (251 ms/step) as a communication floor. That was wrong twice over: the
[mpiwait] TOTALrow also sums host-side rows, and a per-rank minimum is not a floor when ranks are waiting on each other. A per-wave-instance trace (every rank's entry and exit for each rendezvous, validated against[mpiwait]to within 1%, and free — an instrumented-vs-parent A/B is a null on every row, largest |t| = 1.9 at n = 3) decomposes it properly: for each instance, the smallest wait any rank achieved is the cost everyone pays, and the rest is one rank waiting on another.The common cost is 28 ms/step, stable to 3% across three repetitions, and 61-90% of each rank's wait is imbalance. It concentrates in two of the seven wave bands — reflux faces and level-1 restriction carry 78% of it, at 90-96% imbalance — while the parent-fill and restrict-parent bands are floor-dominated. So transfer time and any device synchronisation inside the wait brackets are together bounded by 28 ms/step, and the reachable quantity is load balance.
Scaling
Weak ladder, fixed work per rank, 8 -> 48 GPUs (multi-node), on the lower-order scaling deck with the same geometry and refinement settings:
a fitted 1.222x per rank-doubling, against 1.234x for AMReX on the matched ladder. The scaling slope is at parity — the gap above is a constant factor, not a worsening one.
Notes for anyone reproducing these
--case-optimization. On this deck it is worth a paired -12.4% on the AMR step (-0.581 s/step, sd 0.084, n=3), 78% of it in the RHS. The excess figures above are case-optimized; without it the same comparison gave 1.80x rather than 1.73x at the time, since case optimization largely cancels in the difference.Known issues (all non-gating or in progress)
continue-on-error): an intermittent post-detected NaN on the two Lagrangian+AMR goldens. Exhaustively unreproducible off GitHub's runners — the exact failing stack (NVHPC 24.3 SDK,-tp=px -Kieee, HPC-X MPI, and the CI docker image itself under apptainer) passes elsewhere, as do native/zen2 builds; 24.5+ green. Documented at the golden definitions.Review guide
The commit history is arc-ordered (active-box → load-weight → SFC → weighted decomposition → rank timing → hybrid → m_box → AMR rungs → physics envelope → CI/GPU hardening); reviewing by arc is much easier than by file. The AMR arc builds stepwise: static hierarchy → restriction/prolongation → fine advance → refluxing → regrid → subcycling → multi-rank → GPU → each physics rung with its own validation. Commit messages carry the validation evidence for their change (measured defects, golden UUIDs, repro details for CI fixes).
All parameters ship default-off with
case_validatorentries, runtime checker gates, andcase.md/amr.mddocumentation.