Skip to content

Commit af33bbc

Browse files
authored
Merge pull request #82 from cropsinsilico/topic/hpc
Topic/hpc
2 parents 1e56588 + cf64044 commit af33bbc

161 files changed

Lines changed: 5687 additions & 731 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-install.yml

Lines changed: 255 additions & 12 deletions
Large diffs are not rendered by default.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ prof
3939

4040
# Coverage
4141
.coveragerc
42+
.coveragerc_parallel
4243
.coverage
4344
htmlcov
4445
.coverage.*

HISTORY.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
History
33
=======
44

5+
1.7.0 (2021-08-26) Support for MPI communicators, MPI execution, and pika >= 1.0.0
6+
------------------
7+
8+
* Allow models to be run on distributed processes via MPI
9+
* Added support for MPI based comms
10+
* Update the required version of pika to be >=1.0.0 and update the RMQComm/RMQAsyncComm code to use the updated API
11+
* Added C, C++, Fortran, Matlab, R versions of server in rpc_lesson1 example
12+
* Added C, C++, Fortran, Matlab, R versions of server in rpc_lesson2 example
13+
* Added C, C++, Fortran, Matlab, R versions of server in rpc_lesson2b example
14+
* Added C, C++, Fortran, Matlab, R versions of server in rpc_lesson3 example
15+
* Added C, C++, Fortran, Matlab, R versions of server in rpc_lesson3b example
16+
* Added C++, Fortran, and Python versions of client in rpc_lesson3b example (still need to thread the Python version and add R & Matlab versions)
17+
* Fixed bug in yggdevup CLI for missing language directories
18+
* Enhance debug information w/ task status
19+
520
1.6.4 (2021-08-10) More minor bug fixes & Automated iteration
621
------------------
722

docs/source/hpc.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.. _hpc_rst:
2+
3+
4+
High Performance Computing (HPC)
5+
================================
6+
7+
As of version 1.7, |yggdrasil| includes support for running integrations (networks of models) on High Performance Computing (HPC) clusters via MPI (either `OpenMPI <https://www.open-mpi.org/>`_ or `MPICH <https://www.mpich.org/>`_) and `mpi4py <https://mpi4py.readthedocs.io/en/stable/>`_. |yggdrasil| will distribute models in an integration evenly amongst the available processes and coordinate message passing between them.
8+
9+
You can run a |yggdrasil| integration using MPI, by passing the usual ``yggrun`` command to ``mpiexec/mpirun/srun`` e.g.::
10+
11+
$ mpiexec -n 2 yggrun model1.yml model2.yml
12+
13+
Setup
14+
-----
15+
16+
To run an integration using |yggdrasil|, you must be able to install |yggdrasil| on the cluster. The existing packages and permissions of the cluster will determine how |yggdrasil| should be installed.
17+
18+
* If conda is already installed on the target cluster, you can install |yggdrasil| directly into an environment of your choosing. If the target cluster uses a package manager, you may need to activate/load the conda installation.::
19+
20+
$ conda install -c conda-forge yggdrasil
21+
22+
* If conda is not installed, you can install miniconda into your home directory (which you should have permission for) via a login node and then install |yggdrasil| via conda (you may need to log out and back in before using conda for the first time). e.g.::
23+
24+
$ wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
25+
$ bash miniconda.sh -b -p $HOME/miniconda export PATH="$HOME/miniconda/bin:$PATH"
26+
27+
* If conda is not installed and you cannot install conda due to limitations on packages for cluster users, but Python and pip are installed, you can install |yggdrasil| via pip as a user package. If the target cluster uses a package manager, you may need to activate/load a Python installation (version 3.6 or higher). You will need to complete additional installation steps for non-Python languages as described for the normal :ref:`pip-based installation <manual_install_rst>`::
28+
29+
$ python -m pip install yggdrasil-framework --user
30+
31+
32+
Writing the Job Script
33+
----------------------
34+
35+
Many of the specifics of how you would set up your job script will depend on the scheduler used by the cluster you are targeting (e.g. SLURM, Torque), but there are some general tips:
36+
37+
* Make sure that the YAML files defining the integration and the model codes are located in a directory accessible on compute nodes (usually your home directory or a dedicated drive).
38+
* Activate/load packages you will need that are provided by the cluster package manager (e.g. conda, Python, MPI).
39+
* If you used conda to install |yggdrasil|, activate the environment that |yggdrasil| was installed in before calling ``mpiexec/mpirun/srun``.
40+
41+
Example SLURM Job Script
42+
~~~~~~~~~~~~~~~~~~~~~~~~
43+
44+
.. code-block:: slurm
45+
46+
#!/bin/bash
47+
#SBATCH --partition=general
48+
#SBATCH --job-name=example_name
49+
#SBATCH --cpus-per-task 4
50+
#SBATCH --ntasks=4
51+
#SBATCH --mem-per-cpu=500MB
52+
53+
module load miniconda
54+
55+
conda activate env_name
56+
57+
mpiexec -np $SLURM_NTASKS yggrun model1.yml model2.yml > output.txt

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Welcome to yggdrasil's documentation!
2222
yaml
2323
config
2424
units
25+
hpc
2526
c_format_strings
2627
debugging
2728
threading

recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set name = "yggdrasil" %}
2-
{% set version = "1.6.3" %}
2+
{% set version = "1.7.0" %}
33

44
package:
55
name: {{ name|lower }}

requirements_optional.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
astropy # [astropy]
2-
pika<1.0.0b1 # [rmq]
2+
pika>=1.0.0 # [rmq]
33
pygments # [pygments]
44
trimesh # [trimesh]
55
libroadrunner # [pip,sbml]
66
ccache; platform_system != 'Windows' # [conda]
7+
mpi4py # [mpi]

requirements_testing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ pytest-timeout
99
pytest-drop-dup-tests # [pip]
1010
pytest-rerunfailures
1111
pytest-repeat
12+
pytest-mpi

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ omit =
2929
*/yggdrasil/doctools.py
3030
*/yggdrasil/demos/*
3131
*/yggdrasil/__main__.py
32+
*/yggdrasil/communication/tests/conftest.py
33+
*/yggdrasil/examples/tests/conftest.py
3234

3335
[coverage:report]
3436
sort = Cover

utils/setup_test_env.py

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -105,34 +105,50 @@ def call_script(lines, force_bash=False):
105105
"""
106106
if not lines:
107107
return
108-
if _is_win and (not force_bash): # pragma: windows
109-
script_ext = '.bat'
110-
error_check = 'if %errorlevel% neq 0 exit /b %errorlevel%'
111-
for i in range(len(lines), 0, -1):
112-
lines.insert(i, error_check)
113-
else:
114-
script_ext = '.sh'
115-
if lines[0] != '#!/bin/bash':
116-
lines.insert(0, '#!/bin/bash')
117-
error_check = 'set -e'
118-
if error_check not in lines:
119-
lines.insert(1, error_check)
120-
fname = 'ci_script_%s%s' % (str(uuid.uuid4()), script_ext)
121-
try:
122-
pprint.pprint(lines)
123-
with open(fname, 'w') as fd:
124-
fd.write('\n'.join(lines))
125-
126-
call_kws = {}
127-
if _is_win: # pragma: windows
128-
call_cmd = [fname]
108+
# Split lines that should be allowed to fail
109+
line_sets = []
110+
idx = 0
111+
for i, line in enumerate(lines):
112+
if line.endswith('# [ALLOW FAIL]'):
113+
line_sets.append(lines[idx:i])
114+
line_sets.append([lines[i]])
115+
idx = i + 1
116+
line_sets.append(lines[idx:])
117+
for lines in line_sets:
118+
allow_failure = lines[0].endswith('# [ALLOW FAIL]')
119+
if allow_failure:
120+
lines = [lines[0].split('#')[0].strip()]
121+
if _is_win and (not force_bash): # pragma: windows
122+
script_ext = '.bat'
123+
error_check = 'if %errorlevel% neq 0 exit /b %errorlevel%'
124+
for i in range(len(lines), 0, -1):
125+
lines.insert(i, error_check)
129126
else:
130-
call_cmd = ['./%s' % fname]
131-
os.chmod(fname, 0o755)
132-
subprocess.check_call(call_cmd, **call_kws)
133-
finally:
134-
if os.path.isfile(fname):
135-
os.remove(fname)
127+
script_ext = '.sh'
128+
if lines[0] != '#!/bin/bash':
129+
lines.insert(0, '#!/bin/bash')
130+
error_check = 'set -e'
131+
if error_check not in lines:
132+
lines.insert(1, error_check)
133+
fname = 'ci_script_%s%s' % (str(uuid.uuid4()), script_ext)
134+
try:
135+
pprint.pprint(lines)
136+
with open(fname, 'w') as fd:
137+
fd.write('\n'.join(lines))
138+
139+
call_kws = {}
140+
if _is_win: # pragma: windows
141+
call_cmd = [os.environ['COMSPEC'], '/c', 'call', fname]
142+
else:
143+
call_cmd = ['./%s' % fname]
144+
os.chmod(fname, 0o755)
145+
subprocess.check_call(call_cmd, **call_kws)
146+
except subprocess.CalledProcessError:
147+
if not allow_failure:
148+
raise
149+
finally:
150+
if os.path.isfile(fname):
151+
os.remove(fname)
136152

137153

138154
def conda_env_exists(name):
@@ -217,6 +233,7 @@ def get_install_opts(old=None):
217233
'omp': (os.environ.get('INSTALLOMP', '0') == '1'),
218234
'docs': (os.environ.get('BUILDDOCS', '0') == '1'),
219235
'no_sudo': False,
236+
'mpi': (os.environ.get('INSTALLMPI', '0') == '1'),
220237
}
221238
if not _is_win:
222239
new['c'] = True # c compiler usually installed by default
@@ -235,6 +252,7 @@ def get_install_opts(old=None):
235252
'omp': False,
236253
'docs': False,
237254
'no_sudo': False,
255+
'mpi': False,
238256
}
239257
if _is_win:
240258
new['os'] = 'win'
@@ -585,6 +603,18 @@ def itemize_deps(method, for_development=False,
585603
out['os'] += ['libomp', 'llvm']
586604
elif install_opts['os'] == 'win':
587605
pass
606+
if install_opts['mpi'] and (not fallback_to_conda):
607+
if install_opts['os'] == 'linux':
608+
out['os'] += ['openmpi-bin', 'libopenmpi-dev']
609+
elif install_opts['os'] == 'osx':
610+
out['os'].append('openmpi')
611+
elif install_opts['os'] == 'win':
612+
pass
613+
elif ((install_opts['os'] == 'win') and install_opts['mpi']
614+
and (method == 'conda')):
615+
# Force mpi4py to be installed last on Windows to avoid
616+
# conflicts
617+
out['skip'].append('mpi4py')
588618
if install_opts['fortran'] and (not fallback_to_conda):
589619
# Fortran is not installed via conda on linux/macos
590620
if install_opts['os'] == 'linux':
@@ -851,6 +881,18 @@ def install_deps(method, return_commands=False, verbose=False,
851881
raise RuntimeError("Could not detect conda environment. "
852882
"Cannot proceed with a conda deployment "
853883
"(required for LPy).")
884+
# if _is_win and install_opts['mpi'] and fallback_to_conda:
885+
# if verbose:
886+
# install_flags = '-vvv'
887+
# else:
888+
# install_flags = '-q'
889+
# install_flags = '-vv'
890+
# install_flags += conda_flags
891+
# # This is required as the install script for mpi4py aborts without
892+
# # an error message when called inside a Python subprocess. This seems
893+
# # to occur during cleanup for the installation process as the mpi4py
894+
# # installation is functional. Possibly triggered by the activation script?
895+
# cmds += ["%s install %s mpi4py # [ALLOW FAIL]" % (CONDA_CMD, install_flags)]
854896
if return_commands:
855897
return cmds
856898
cmds = SUMMARY_CMDS + cmds + SUMMARY_CMDS
@@ -962,6 +1004,8 @@ def install_pkg(method, python=None, without_build=False,
9621004
# "%s install %s --update-deps -c %s yggdrasil \"blas=*=openblas\"" % (
9631005
# CONDA_CMD, install_flags, index_channel)
9641006
]
1007+
if _is_win and install_opts['mpi']:
1008+
cmds[-1] = cmds[-1] + ' mpi4py # [ALLOW FAIL]'
9651009
elif method == 'pip':
9661010
if verbose:
9671011
install_flags = '--verbose'

0 commit comments

Comments
 (0)