Fix P3 to read lookup tables on rank 0 and broadcast to other ranks#8547
Fix P3 to read lookup tables on rank 0 and broadcast to other ranks#8547ndkeen wants to merge 2 commits into
Conversation
Summary
Also fixed: a build break in PAM/CRM
|
There was a problem hiding this comment.
Pull request overview
Update EAMxx P3 microphysics initialization to avoid per-rank lookup-table file reads by having the root MPI rank perform the filesystem I/O and broadcasting table data to the other ranks via ekat::Comm. This targets known scalability/filesystem-pressure issues reported in #6654 and #6833.
Changes:
- Change
p3::Functions::p3_initto accept an optionalekat::Comm*and use it to root-read + broadcast both ice and non-ice lookup tables. - Update the EAMxx P3 process initialization to pass its communicator into
p3_init. - Update the offline P3 table-generation tool to construct an
ekat::Command call the newp3_initsignature.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| components/eamxx/src/physics/p3/tests/p3_tables_setup.cpp | Initialize MPI and pass an ekat::Comm to p3_init for table generation. |
| components/eamxx/src/physics/p3/p3_functions.hpp | Update p3_init API to accept an optional communicator and document root-read + broadcast behavior. |
| components/eamxx/src/physics/p3/impl/p3_init_impl.hpp | Implement root-rank file I/O and broadcast logic for P3 lookup tables. |
| components/eamxx/src/physics/p3/eamxx_p3_process_interface.cpp | Pass the atmosphere communicator into p3_init so only root reads tables in production runs. |
| components/eam/src/physics/crm/pam/pam_driver.cpp | Update call to new p3_init signature (currently still using comm=nullptr in PAM). |
| const bool masterproc = comm == nullptr || comm->am_i_root(); | ||
| if (masterproc) { | ||
| std::cout << "Reading ice lookup tables in file: " << filename << std::endl; | ||
| } | ||
|
|
||
| std::ifstream in(filename); | ||
|
|
||
| // read header | ||
| std::string version, version_val; | ||
| in >> version >> version_val; | ||
| EKAT_REQUIRE_MSG(version == "VERSION", "Bad " << filename << ", expected VERSION X.Y.Z header"); | ||
| EKAT_REQUIRE_MSG(version_val == p3_version, "Bad " << filename << ", expected version " << p3_version << ", but got " << version_val); | ||
|
|
||
| // read tables | ||
| double dum_s; int dum_i; // dum_s needs to be double to stream correctly | ||
| for (int jj = 0; jj < densize; ++jj) { | ||
| for (int ii = 0; ii < rimsize; ++ii) { | ||
| for (int i = 0; i < isize; ++i) { | ||
| in >> dum_i >> dum_i; | ||
| int j_idx = 0; | ||
| for (int j = 0; j < 15; ++j) { | ||
| in >> dum_s; | ||
| if (j > 1 && j != 10) { | ||
| ice_table_vals_h(jj, ii, i, j_idx++) = dum_s; | ||
| // Only the root rank (or every calling process, if comm is null, e.g. in | ||
| // stand-alone tools/unit tests that don't provide a communicator) touches | ||
| // the filesystem. The resulting data is then broadcast to the other ranks | ||
| // in comm below. This avoids every single MPI rank independently reading | ||
| // the exact same (small, read-only) text file at initialization, which can | ||
| // put unnecessary stress on shared/parallel filesystems and, in the worst | ||
| // case, cause a slowdown or stall (see E3SM issue #6654 / #6833). | ||
| if (masterproc) { | ||
| std::ifstream in(filename); |
There was a problem hiding this comment.
Split the old conflated masterproc flag into do_io (comm == nullptr || comm->am_i_root(), gates filesystem access) and do_print (comm != nullptr && comm->am_i_root(), gates std::cout). Applied in read_ice_lookup_tables(), io_impl(), and in p3_init() where the value passed to compute_tables() is now do_print instead of the old masterproc (that function does no I/O, only prints). This restores the pre-existing quiet default for the comm == nullptr case, while still performing the actual root-read/broadcast optimization
| if (masterproc) { | ||
| stream_t mu_r_file(mu_r_filename.c_str(), std::ios::binary); | ||
| stream_t revap_file(revap_filename.c_str(), std::ios::binary); | ||
| stream_t vn_file(vn_filename.c_str(), std::ios::binary); | ||
| stream_t vm_file(vm_filename, std::ios::binary); | ||
|
|
||
| // Read/write files | ||
| action(mu_r_file, mu_r_table_vals_h.data(), mu_r_table_vals.size()); | ||
| action(revap_file, revap_table_vals_h.data(), revap_table_vals.size()); | ||
| action(vn_file, vn_table_vals_h.data(), vn_table_vals.size()); | ||
| action(vm_file, vm_table_vals_h.data(), vm_table_vals.size()); | ||
| } |
There was a problem hiding this comment.
Added EKAT_REQUIRE_MSG checks in io_impl() right after opening each binary table stream (mu_r/revap/vn/vm), and again after the read/write action() call, so a missing/truncated file now fails loudly with a clear message instead of silently propagating bad data (which matters more now that root's read gets broadcast to every rank).
| const bool masterproc = comm == nullptr || comm->am_i_root(); | ||
| if (masterproc) { | ||
| std::cout << (IsRead ? "Reading" : "Writing") << " lookup (non-ice) tables in dir " << dir << std::endl; | ||
| } |
There was a problem hiding this comment.
Split the old conflated masterproc flag into do_io (comm == nullptr || comm->am_i_root(), gates filesystem access) and do_print (comm != nullptr && comm->am_i_root(), gates std::cout). Applied in read_ice_lookup_tables(), io_impl(), and in p3_init() where the value passed to compute_tables() is now do_print instead of the old masterproc (that function does no I/O, only prints). This restores the pre-existing quiet default for the comm == nullptr case, while still performing the actual root-read/broadcast optimization
| // PAM does not currently plumb an MPI communicator through the | ||
| // coupler, so pass comm=nullptr here: every rank reads the lookup | ||
| // table files independently, matching the pre-existing behavior. | ||
| P3F::p3_init(/*write_tables=*/false, /*comm=*/nullptr); |
There was a problem hiding this comment.
Probably best to leave this as-is for this PR as it's in PAM
|
Pushed a commit to address comments regarding verbosity regression and missing error checks. |
|
OK I was about to merge to next, but I see Conrad is now assigned. i tested with |
Fix P3 microphysics initialization so only the root MPI rank reads the lookup table files from disk and broadcasts the data to the other ranks, instead of every rank independently reading the same files. This avoids unnecessary load
on shared/parallel filesystems and a possible slowdown or stall at scale, as reported in E3SM issues #6654 and #6833.
Fixes #6654 and #6833
[bfb]