-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for running MPI applications with
HighThroughputExecutor
(#…
…3016) The HighThroughputExecutor (HTEX) is designed to follow the pilot job model where nodes are provisioned, agents launched per node, which then execute tasks. However, this limits the executor from launching MPI aware applications which are not limited to single node execution. This PR extends HTEX to do: Support an MPI mode that can be toggled with enable_mpi_mode: bool option. In this mode a single manager is launched per batch-job, that in turn keeps track of the provisioned nodes, and can schedule multi-node MPI applications. Support for passing MPI application requirements such as ranks_per_node via the special parsl_resource_specification kwarg. MPI applications with resource specifications defined are launched with environment variables such as PARSL_MPI_PREFIX that can be used to launch the application from within bash_apps. I've got user documentation here -> https://github.com/Parsl/parsl/blob/mpi_experimental_3/docs/userguide/mpi_apps.rst Tested on : Polaris@ALCF with CosmicTagger and LAMMPS Perlmutter@NERSC with LAMMPS
- Loading branch information
Showing
22 changed files
with
1,473 additions
and
120 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import logging | ||
from typing import Dict, List, Tuple, Set | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
VALID_LAUNCHERS = ('srun', | ||
'aprun', | ||
'mpiexec') | ||
|
||
|
||
class InvalidResourceSpecification(Exception): | ||
"""Exception raised when Invalid keys are supplied via resource specification""" | ||
|
||
def __init__(self, invalid_keys: Set[str]): | ||
self.invalid_keys = invalid_keys | ||
|
||
def __str__(self): | ||
return f"Invalid resource specification options supplied: {self.invalid_keys}" | ||
|
||
|
||
def validate_resource_spec(resource_spec: Dict[str, str]): | ||
"""Basic validation of keys in the resource_spec | ||
Raises: InvalidResourceSpecification if the resource_spec | ||
is invalid (e.g, contains invalid keys) | ||
""" | ||
user_keys = set(resource_spec.keys()) | ||
legal_keys = set(("ranks_per_node", | ||
"num_nodes", | ||
"num_ranks", | ||
"launcher_options", | ||
)) | ||
invalid_keys = user_keys - legal_keys | ||
if invalid_keys: | ||
raise InvalidResourceSpecification(invalid_keys) | ||
if "num_nodes" in resource_spec: | ||
if not resource_spec.get("num_ranks") and resource_spec.get("ranks_per_node"): | ||
resource_spec["num_ranks"] = str(int(resource_spec["num_nodes"]) * int(resource_spec["ranks_per_node"])) | ||
if not resource_spec.get("ranks_per_node") and resource_spec.get("num_ranks"): | ||
resource_spec["ranks_per_node"] = str(int(resource_spec["num_ranks"]) / int(resource_spec["num_nodes"])) | ||
return | ||
|
||
|
||
def compose_mpiexec_launch_cmd( | ||
resource_spec: Dict, node_hostnames: List[str] | ||
) -> Tuple[str, str]: | ||
"""Compose mpiexec launch command prefix""" | ||
|
||
node_str = ",".join(node_hostnames) | ||
args = [ | ||
"mpiexec", | ||
"-n", | ||
resource_spec.get("num_ranks"), | ||
"-ppn", | ||
resource_spec.get("ranks_per_node"), | ||
"-hosts", | ||
node_str, | ||
resource_spec.get("launcher_options", ""), | ||
] | ||
prefix = " ".join(str(arg) for arg in args) | ||
return "PARSL_MPIEXEC_PREFIX", prefix | ||
|
||
|
||
def compose_srun_launch_cmd( | ||
resource_spec: Dict, node_hostnames: List[str] | ||
) -> Tuple[str, str]: | ||
"""Compose srun launch command prefix""" | ||
|
||
num_nodes = str(len(node_hostnames)) | ||
args = [ | ||
"srun", | ||
"--ntasks", | ||
resource_spec.get("num_ranks"), | ||
"--ntasks-per-node", | ||
resource_spec.get("ranks_per_node"), | ||
"--nodelist", | ||
",".join(node_hostnames), | ||
"--nodes", | ||
num_nodes, | ||
resource_spec.get("launcher_options", ""), | ||
] | ||
|
||
prefix = " ".join(str(arg) for arg in args) | ||
return "PARSL_SRUN_PREFIX", prefix | ||
|
||
|
||
def compose_aprun_launch_cmd( | ||
resource_spec: Dict, node_hostnames: List[str] | ||
) -> Tuple[str, str]: | ||
"""Compose aprun launch command prefix""" | ||
|
||
node_str = ",".join(node_hostnames) | ||
args = [ | ||
"aprun", | ||
"-n", | ||
resource_spec.get("num_ranks"), | ||
"-N", | ||
resource_spec.get("ranks_per_node"), | ||
"-node-list", | ||
node_str, | ||
resource_spec.get("launcher_options", ""), | ||
] | ||
prefix = " ".join(str(arg) for arg in args) | ||
return "PARSL_APRUN_PREFIX", prefix | ||
|
||
|
||
def compose_all( | ||
mpi_launcher: str, resource_spec: Dict, node_hostnames: List[str] | ||
) -> Dict[str, str]: | ||
"""Compose all launch command prefixes and set the default""" | ||
|
||
all_prefixes = {} | ||
composers = [ | ||
compose_aprun_launch_cmd, | ||
compose_srun_launch_cmd, | ||
compose_mpiexec_launch_cmd, | ||
] | ||
for composer in composers: | ||
try: | ||
key, prefix = composer(resource_spec, node_hostnames) | ||
all_prefixes[key] = prefix | ||
except Exception: | ||
logging.exception( | ||
f"Failed to compose launch prefix with {composer} from {resource_spec}" | ||
) | ||
pass | ||
|
||
if mpi_launcher == "srun": | ||
all_prefixes["PARSL_MPI_PREFIX"] = all_prefixes["PARSL_SRUN_PREFIX"] | ||
elif mpi_launcher == "aprun": | ||
all_prefixes["PARSL_MPI_PREFIX"] = all_prefixes["PARSL_APRUN_PREFIX"] | ||
elif mpi_launcher == "mpiexec": | ||
all_prefixes["PARSL_MPI_PREFIX"] = all_prefixes["PARSL_MPIEXEC_PREFIX"] | ||
else: | ||
raise RuntimeError(f"Unknown mpi_launcher:{mpi_launcher}") | ||
|
||
return all_prefixes |
Oops, something went wrong.