Date: 2026-09-04. Scope: every Level 2 mini-app, audited from its build.sh /
run.sh / validate.sh / README, its sources under level2/<app>/, and the
upstream clone under _upstream/level2/. Goal: selectable GPU count
(2/4/8/40/80/all), one MPI rank per GPU, multi-node capability, and per-app
problem decomposition -- not the frozen single-GPU decks. HIP is out of scope
for this round (nothing HIP is claimed verified).
Definitions (execution type):
- native-mpi -- upstream implements a real distributed workload (domain decomposition / distributed solve with inter-rank communication).
- naturally-shardable-but-not-implemented -- one coupled global problem could be sharded, but the shipped code has no comm layer.
- sharded-embarrassingly-parallel (sharded-EP) -- one GLOBAL task with no inter-rank coupling except a final reduction (one global batch of lookups / poses / material points divided over ranks and reduced). This IS a global workload divided across GPUs, unlike replicas, but it counts only once a sharding driver exists and is validated.
- independent-replica-only -- each rank runs the FULL identical input (throughput mode). NEVER counted as distributed multi-GPU.
- no-scaleout-path -- not even replicas make sense.
Do not conflate the last three: "no coupling" does not automatically mean "replica-only" -- if the input is one global set that can be partitioned, the app is sharded-EP (unimplemented until a driver exists).
| App | Execution type | rank->GPU binding | Multi-node capable | CUDA-aware MPI | Topology params | Rank-count constraints | Recommendation |
|---|---|---|---|---|---|---|---|
| amg2023 | native-mpi | in-app (hypre_bind_device, local rank) | yes | optional (hypre-gpuaware variant exists) | -P px py pz (product == NP) |
any factorable N; global unknowns < 2^31 (<=127 ranks @256^3/rank) | keep-in-core |
| laghos | native-mpi | wrapper needed (-dev is one global ordinal) |
yes | optional (-gam) |
mesh path: internal (NC/METIS); -epm path: internal grid |
any N <= elements (mesh path); any N (-epm) |
keep-in-core |
| remhos | native-mpi | wrapper needed (no device-index option at all) | yes | optional (-gam) |
like laghos (-epm supported) |
any N <= elements; GPU solver combo rank-independent | keep-in-core |
| examinimd | native-mpi | in-app (Kokkos local-rank) | yes | required (device buffers to MPI_Send) | internal surface-minimizing grid | any N; <=~20M atoms/rank (int32 neigh list), <=2^31 global atoms | keep-in-core |
| exampm | native-mpi | in-app (Kokkos local-rank) | yes | required (Cabana halo/migration) | HARD-CODED 1xNx1 Y-slabs in examples | Y-cells/N >= halo(3): N <~ 35 @cell 0.01 (unchecked!) | keep-in-core (fix 1D slab before 80 ranks) |
| haccabanapm | native-mpi | in-app (Kokkos local-rank) | yes (parallel HDF5) | required (Cabana + heFFTe device buffers) | PMKOKKOS_TOPOLOGY / indat / MPI_Dims_create |
any N (product check); ng%P warning only; pm_ic/pm_run same N | keep-in-core |
| cloverleaf | native-mpi | wrapper needed (--device one shared value) |
yes | optional (--staging-buffer auto) |
none (auto chunk factorization) | any N | keep-in-core |
| tealeaf | native-mpi | wrapper needed (same) | yes | optional (same) | none (auto chunk factorization) | any N; weak grids need new tea.problems rows | keep-in-core |
| kripke | native-mpi | wrapper needed (no binding code) | yes | optional (build flag exists) | --procs x,y,z (product == NP) |
product == NP; zones divisible per dim; groups%gset, quad%dset | keep-in-core |
| branson | native-mpi (replicated work-split + Allreduce; PARTICLE_PASS = true domain decomposition, unvalidated) | in-app (set_device_ID(rank%ndev) -- GLOBAL rank: needs block mapping) |
yes (caveat above) | no (host-buffer MPI only) | none (REPLICATED); deck <mesh_decomposition> for PARTICLE_PASS |
any N | keep-in-core (replicated); PARTICLE_PASS = later extension |
| hipbone | native-mpi | in-app (hostname local-rank in OCCA props) | yes | optional (-ga) |
-px -py -pz (product == NP; without them NP must be a cube) |
any factorable N once -px/-py/-pz standardized in run.sh | keep-in-core (add -px/py/pz decks) |
| miniweather | native-mpi (1D x-split) | wrapper needed (no binding code) | yes | optional (-DGPU_AWARE_MPI compile flag) |
none (1D x-split only) | N <= nx_glob; problem size is COMPILE-TIME (MINIWEATHER_NX) |
keep-in-core with limitations (multi-GPU capable; compile-time size and 1D split are limitations to document, not disqualifiers) |
| p3_heat3d | naturally-shardable; upstream sibling heat3d_mpi is native-mpi |
sibling: in-app cudaSetDevice(rank%n) (global rank) |
yes | required by sibling (device Views to MPI) | sibling: --px --py --pz (product == NP) |
any factorable N; strong needs divisibility | implement-distributed-extension (adopt upstream heat3d_mpi) |
| p3_vlp4d | naturally-shardable; upstream sibling vlp4d_mpi is native-mpi |
sibling: in-app (same) | yes | required by sibling (halo + device Allreduce) | none (internal 4D recursive bisection) | any N with >=10 points per cut direction | implement-distributed-extension (adopt vlp4d_mpi; NOTE: it switches Lagrange->spline interpolation -- a sibling benchmark, not the same numbers) |
| cabanapic | naturally-shardable-but-not-implemented (no comm layer at all; deck is 1D-in-y) | n/a (single process) | n/a | n/a | none | n/a | keep-as-supplemental (distributed PIC = rewrite; HACCabanaPM covers the Cabana-comm motif) |
| shaw | naturally-shardable-but-not-implemented (global CSR SpMV, no partitioned mesh) | n/a | n/a | n/a | none | n/a | keep-as-supplemental |
| exacmech | sharded-EP possible (global point set split + 6-double Allreduce), NOT implemented; replica-only as shipped | n/a | n/a | n/a | none | n/a | keep-as-supplemental until a sharded driver exists (then a weakly-coupled global workload) |
| minibude | sharded-EP possible (global pose batch split + reduce), NOT implemented; replica-only as shipped | per-process --device flag |
n/a | n/a | none | n/a | keep-as-supplemental until a sharded driver exists |
| xsbench | sharded-EP possible (global lookup batch split + reduce), NOT implemented; upstream's own MPI mode is full-input replicas ("no decomposition ... all ranks accomplish the same work") | none (no cudaSetDevice) | n/a | no | none | n/a | keep-as-supplemental until a sharded driver exists |
| miniem | native-mpi upstream (Trilinos/Panzer + Tpetra) | Tpetra/Kokkos local rank | yes | optional | Tpetra maps | any N | pending (blocked on the Trilinos dependency decision) |
Current distributed core (native-mpi, validated single-node multi-GPU): amg2023, laghos, remhos, examinimd, exampm, haccabanapm, cloverleaf, tealeaf, kripke, branson, hipbone (11), plus miniweather as core with documented limitations (12 native-mpi motifs). Distributed extensions to adopt: p3_heat3d, p3_vlp4d (upstream MPI siblings). Sharded-EP candidates (drivers to write): xsbench, minibude, exacmech. Rewrite-cost shardable: cabanapic, shaw. Pending: miniem.
The goal remains about twenty DIFFERENT motifs, each a logically distributed workload. The 11-12 native-mpi apps are the current progress, not the finish line. Path to the target and what may be counted:
| Counted today | Path to ~20 | Counted only when |
|---|---|---|
| 12 native-mpi (11 core + miniweather with limitations) | -- | selectable-N runs validated |
| -- | p3_heat3d, p3_vlp4d (adopt upstream heat3d_mpi / vlp4d_mpi) | integrated, built, validated at > 1 rank |
| -- | xsbench, minibude, exacmech (sharded-EP drivers: split the global batch, reduce) | driver exists and is validated -- a sharded run, not N replicas |
| -- | cabanapic, shaw (distributed extensions, rewrite cost) | same |
| -- | miniem (Trilinos/Panzer) | dependency decision + build |
Nothing in the right two columns is counted as done; unimplemented distributed paths remain unimplemented in every table of this repository.
- Frozen 1-rank decks: every run.sh defaulted to
mpirun -np 1; rank counts came from ad-hocHPCPERF_NPwith no GPU accounting. - Oversubscription by default:
hpcperf_env.shglobally exportedPRTE_MCA_rmaps_default_mapping_policy=:oversubscribeand each run.sh added--oversubscribe, so ranks > GPUs silently shared GPU 0 for every app without in-app binding (MFEM apps, CloverLeaf/TeaLeaf, Kripke, miniWeather). - Single-GPU problem sizes: the decks were sized for one B200; strong- scaling them to 4+ GPUs is communication-dominated by construction, and no weak-scaling policy existed.
- Single-node transport: the only working CUDA device-buffer transport
(
pml ob1, btl self,sm,smcuda) is single-node-only; the site UCX hangs on CUDA device buffers, so there is currently NO verified multi-node path. - No topology management: apps with
-P/--procs-style products had no helper to pick/validate grids, and infeasible N failed deep inside the app.
The full per-field fact sheets (upstream MPI structure, size semantics with file:line citations, strong/weak input generation, 40/80-GPU topologies, blockers) were collected per app; the load-bearing facts are:
- amg2023:
-nis PER-RANK (amg.c:332); NP must equal pxpypz (amg.c:968); weak = fixed-n, strong = divide a global grid; 32-bit HYPRE_Int caps global unknowns at 2^31 (127 ranks @256^3/rank; 80 ranks = 62.5% of the cap). 40/80:-P 5 4 2/-P 5 4 4. - laghos: mesh path partitions any N <= elements (cube01_hex has 8
elements, x8 per
-rs; rs4 = 32768);-epm <elems/rank>gives exact per-rank weak scaling with an internal rank-grid factorization (2x4x5 @40, 4x4x5 @80).-gamtoggles GPU-aware MPI. FOM counters are 32-bit HYPRE_BigInt. - remhos: same MFEM base;
-ho 3 -lo 5 -fct 2(the GPU combo) is rank-count independent (remhos.cpp:398); FOM overflows 32-bit at unknownssteps3 > 2^31 (cosmetic). - examinimd: the
20M-atom "L170" ceiling is PER-RANK: the 2D neighbor list view is indexed withintstride*index products (neighbor_2d.h:86-94), maxneighs~105 for the LJ melt => ~2.1e9/105 ~ 20M atoms/rank; scale-out raises the total ceiling by N. Global ids are also int => hard 2^31 global atom cap. Internal surface-minimizing rank grid (2x4x5 @40, 4x4x5 @80). Deck box is GLOBAL lattice cells. - exampm: examples HARD-CODE a 1xNx1 Y-slab decomposition
(dam_break.cpp:87-90); slabs thinner than the halo width (3 cells) are
silently wrong -- cell 0.01 => N <~ 35, cell 0.005 => N <~ 70. Needs a
~5-line switch to
DimBlockPartitionerbefore 80-rank runs; no clean weak-scaling knob (domain is a fixed unit cube). - haccabanapm: topology via
PMKOKKOS_TOPOLOGY/indat/MPI_Dims_create(TopologyOverride.hpp); any N; ng need not divide P (warning only); pm_ic and pm_run must use the same N; weak scaling = scale NG=NP=RL together (@40: NG=880 with 5,4,2; @80: NG=1120 with 5,4,4); benchmark indat (256^3) is too small for 40-80 GPUs. - cloverleaf / tealeaf: GLOBAL deck cells, automatic any-N chunk
factorization (auto 5x8 @40, 8x10 @80); CUDA-aware optional via
--staging-buffer auto(elides host staging when MPIX query says aware); TeaLeaf weak-scaled grids need newtea.problemsreference rows. - kripke:
--procsproduct must equal NP AND each--zonesdim must be divisible by procszset in that dim; groups%gset==0, quad%dset==0. Weak: zones = 32procs per dim (--procs 5,4,2 --zones 160,128,64@40;5,4,4 / 160,128,128@80). - branson: REPLICATED mode = global photon count auto-split ~1/N per rank
- per-step tally Allreduce (a real work-split, host-buffer MPI only), any
N; mesh memory replicated (fine at 591k cells). PARTICLE_PASS (METIS
domain decomposition, upstream
3D_hohlraum_multi_node.xml) exists but is unvalidated here. Binding uses GLOBAL rank -> needs block rank mapping.
- per-step tally Allreduce (a real work-split, host-buffer MPI only), any
N; mesh memory replicated (fine at 591k cells). PARTICLE_PASS (METIS
domain decomposition, upstream
- hipbone:
-nx/-ny/-nzare PER-RANK elements ("weak-scale" by design); without-px/-py/-pzthe rank count must be a perfect cube -- the frozen run.sh lacks them, which is the only blocker. FOM is already per-rank normalized. @40:-px 5 -py 4 -pz 2; @80:-px 5 -py 4 -pz 4. - miniweather: 1D x-only decomposition, problem size fixed at COMPILE
time (
MINIWEATHER_NX), no rank->GPU binding, PnetCDF output collective. Genuinely MPI but a poor selectable-N vehicle. - p3_heat3d / p3_vlp4d: upstream P3-miniapps contains ready-made MPI
variants (heat3d_mpi:
--px/--py/--pz+ per-rank--nx/--ny/--nz, CUDA-aware halo; vlp4d_mpi: internal 4D bisection, device-buffer halos + Allreduce). Adoption cost is build wiring, not code. vlp4d_mpi changes the interpolation scheme (Lagrange -> spline): document as its own benchmark configuration. - cabanapic / shaw: shardable physics, but no comm layer exists and nothing upstream to adopt => rewrite-cost; keep as single-GPU motifs.
- exacmech / minibude / xsbench: physically replica-only (no coupling); XSBench upstream documents its MPI mode as replicated work. Any "multi-GPU" claim for these would be throughput mode -- excluded from core by definition.
level2/tools/hpcperf_mpi_launch.sh-- GPU-count-aware launcher (see tools/README.md):--gpus N|all, one rank per GPU, fail-fast on ranks > GPUs, site-profile transport, Slurm slot accounting, dry-run mode, per-rank binding audit lines.level2/tools/hpcperf_topology.py-- balanced process grids with constraint checking (divisibility, power-of-two, aspect); fails with nearby feasible counts instead of silently changing N.level2/tools/mpi_gpu_bind.sh-- scheduler-safe binding: respects scheduler per-task binding, picks the local-rank-th entry of the inheritedCUDA_VISIBLE_DEVICES, FAILS (exit 12) when local rank >= visible GPUs unlessHPCPERF_ALLOW_OVERSUBSCRIBE=1, audit line per rank.- Site profiles
level2/tools/site/{gmu-hopper,generic}.sh: launcher choice and transport per site; gmu-hopper marks multi-node BLOCKED/UNVERIFIED (validatedself,sm,smcudatransport is single-node-only; site UCX hangs on CUDA device buffers). - Refactored
run.shfor amg2023, laghos, examinimd:HPCPERF_GPUS+HPCPERF_SCALE_MODE=smoke|strong|weak, topology/limits checked, launched through the common launcher. Verified 1/2/4 GPUs on this allocation; 8/40/80 validated as dry-run configuration only.
- Launcher resource model split into ACTUAL / REQUESTED / HYPOTHETICAL:
HPCPERF_NODES/HPCPERF_GPUS_PER_NODEmay only select a subset outside dry-run (enforced in placement:mpirun --host node:ranks,srun --nodelist); larger values are accepted only in dry-run and labelled HYPOTHETICAL.allis resolved before theHPCPERF_NPconflict check. Heterogeneous allocations (task slots, CPUs or GPUs per node differing) are refused explicitly; GPUs per node are verified fromscontrol show job -dnode groups, not assumed from the current node. - CPU mapping for mpirun:
HPCPERF_CPUS_PER_RANK=C->--map-by ppr:R:node:PE=C --bind-to coreafter a capacity check (R x C <= CPUs/node); Slurm slot relaxation only after the GPU AND CPU checks pass; per-rankCpus_allowed_listis in the audit line. Verified on the B200 node: 4 ranks x 4 cores bound to cores 24-27 / 28-31 / 32-35 / 36-39 inside the job's (non-contiguous) cpuset. - GPU binding audit split into expected (wrapper, from the local rank; UUID via
nvidia-smi's index table under
CUDA_DEVICE_ORDER=PCI_BUS_ID, never a CUDA ordinal used as a physical index) and observed (launcher samplesnvidia-smi --query-compute-appsduring the run): verified / MISMATCH / unverified. No usable GPU in GPU mode = failure (exit 13). - Every run.sh resolves ranks through
hpcperf_launch_common.sh; unsupported common parameters (HPCPERF_SCALE_MODEon an app without size policies) and extra arguments overriding validated parameters are errors (exit 2). - Topology helper enumerates axis permutations under divisibility constraints
(N=6 on 8x6x4 -> 2x3x1) and has a self-test; run.sh scripts capture the
helper's exit status instead of
read <<< "$(cmd)". setup_level2_deps.sh:mark_builtwrites the fingerprint first and the completion marker only on success (no-patch case is normal);is_builtfails fast on a fingerprint mismatch (legacy unfingerprinted installs are accepted with a warning;--stamp-existingrecords a labelled post-hoc fingerprint);HPCPERF_DEPS_PROFILEgives Level 3 an isolated.deps/<profile>/{build,install}tree. Tests:level2/tools/tests/run_all.sh.
| Variable | Meaning |
|---|---|
HPCPERF_GPUS=N|all |
GPUs to use = MPI ranks (one rank per GPU) |
HPCPERF_NODES, HPCPERF_GPUS_PER_NODE |
allocation shape override (dry-run planning; normally auto-detected from Slurm) |
HPCPERF_CPUS_PER_RANK |
CPUs per rank (srun --cpus-per-task) |
HPCPERF_SCALE_MODE=smoke|strong|weak |
per-app size policy |
HPCPERF_LAUNCHER=auto|mpirun|srun |
launcher; default from site profile |
HPCPERF_ALLOW_OVERSUBSCRIBE=0|1 |
default 0; 1 = debug only, loud warning |
HPCPERF_SITE_PROFILE=<site> |
site profile (default auto: gmu-hopper on dgx003) |
HPCPERF_DRY_RUN=1 |
print resource plan + command, do not execute |
HPCPERF_NP |
backwards-compat alias; must equal HPCPERF_GPUS if both set (error otherwise) |
- Multi-node MPI transport (the hard one): the conda Open MPI's
device-buffer traffic hangs over the site UCX even single-node; the
working
self,sm,smcudatransport cannot cross nodes. Real multi-node needs a site-provided (or purpose-built) GPU-aware UCX/Open MPI stack, plus rebuilds of hypre, MFEM, Cabana, heFFTe against it. Status: BLOCKED/UNVERIFIED (never claimed working). - Allocation shape: 40/80 GPUs = 5/10 nodes on this cluster (8 GPU/node); only single-node allocations have been available to this project so far.
- Per-app work before 80 ranks: exampm 1D-slab fix; hipbone -px/py/pz decks; tealeaf reference rows for weak grids; kripke zones/procs deck family; haccabanapm large indat (NG=1120) + big IC files; branson block-mapping requirement documented.
- Remaining 7 non-refactored MPI apps still use legacy
HPCPERF_NPrun.sh (gated, no launcher integration yet).
Current state: setup_level2_deps.sh builds CUDA-only into flat
.deps/install/<dep> prefixes; .hpcperf-built records only the tag, so a
prefix built for a different GPU arch / compiler / MPI / GPU-aware flag is
indistinguishable; hpcperf_env.sh adds every marked prefix to
CMAKE_PREFIX_PATH (unmarked variants like the hand-built *-gpuaware are
already excluded, which is why they cannot leak into default builds).
Proposed layout (not yet implemented -- do not break the working tree):
.deps/install/<profile>/<dep>
profile := <backend>-<arch>-<site>[-<feature>...]
e.g. cuda-sm100-gmu/hypre
cuda-sm100-gmu-gpuaware/hypre
hip-gfx950-lux/kokkos
HPCPERF_DEPS_PROFILEselects the active profile;hpcperf_env.shputs exactly ONE profile's prefixes onCMAKE_PREFIX_PATH(no cross-profile mixing by construction).- Each install carries
.hpcperf-fingerprint(implemented this round): dep, tag, backend, arch, compiler, CUDA version, MPI, GPU-aware flag, and the sha256 of every applied patch.is_builtshould graduate from "tag matches" to "fingerprint matches the requested profile" so that a CUDA upgrade, a patch edit, or an arch change triggers a rebuild instead of silently reusing a stale install. - Migration: keep the flat layout as the implicit
cuda-sm100-gmuprofile; a symlink.deps/install/cuda-sm100-gmu -> .preserves both paths during the transition.
Implemented (rev 2): HPCPERF_DEPS_PROFILE=<name> selects
.deps/<name>/{build,install,logs} (sources shared) in setup_level2_deps.sh
and the matching install root in hpcperf_env.sh -- Level 3 uses e.g.
HPCPERF_DEPS_PROFILE=level3 and never touches the validated Level 2 tree.
The fingerprint now PARTICIPATES in is_built: a recorded fingerprint that
does not match the current toolchain/arch/patches fails fast; installs without
a fingerprint (built before it existed) are accepted as legacy with a warning
and can be stamped post-hoc (--stamp-existing, labelled as such -- a record
from the current environment, not a build-time record; the nine Level 2
installs on the dev node were stamped this way on 2026-09-04). Fingerprint
schema 2 (2026-09-04) records the full CUDA toolkit version parsed from the
complete nvcc --version output; schema-1 files had an empty cuda= field
(the release line is not the first line of that output) and are re-recorded by
--migrate-fingerprints, which keeps the old provenance line and adds a
migrated= line -- again a post-hoc record from the current environment, not a
rebuild verification. mark_built refuses to record a fingerprint whose CUDA
version cannot be determined. The
*-gpuaware experiment prefixes remain unmarked and outside
CMAKE_PREFIX_PATH. Still open: profile-qualified paths for the default tree
(cuda-sm100-gmu/...) and HIP/ROCm profiles.