vortex-nwp 2.0.0b1__py3-none-any.whl → 2.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vortex/__init__.py +59 -45
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +940 -614
- vortex/algo/mpitools.py +802 -497
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +428 -225
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +544 -413
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +420 -271
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +210 -122
- vortex/nwp/algo/monitoring.py +132 -76
- vortex/nwp/algo/mpitools.py +321 -191
- vortex/nwp/algo/odbtools.py +617 -353
- vortex/nwp/algo/oopsroot.py +449 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +125 -59
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +311 -149
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +632 -298
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +402 -333
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1211 -631
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +377 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.0.0b2.dist-info/METADATA +66 -0
- vortex_nwp-2.0.0b2.dist-info/RECORD +142 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/top_level.txt +0 -0
vortex/algo/mpitools.py
CHANGED
|
@@ -70,6 +70,7 @@ Note: Namelists and environment changes are orchestrated as follows:
|
|
|
70
70
|
|
|
71
71
|
import collections
|
|
72
72
|
import collections.abc
|
|
73
|
+
import importlib
|
|
73
74
|
import itertools
|
|
74
75
|
import locale
|
|
75
76
|
import re
|
|
@@ -92,6 +93,7 @@ logger = loggers.getLogger(__name__)
|
|
|
92
93
|
|
|
93
94
|
class MpiException(Exception):
|
|
94
95
|
"""Raise an exception in the parallel execution mode."""
|
|
96
|
+
|
|
95
97
|
pass
|
|
96
98
|
|
|
97
99
|
|
|
@@ -99,89 +101,98 @@ class MpiTool(footprints.FootprintBase):
|
|
|
99
101
|
"""Root class for any :class:`MpiTool` subclass."""
|
|
100
102
|
|
|
101
103
|
_abstract = True
|
|
102
|
-
_collector = (
|
|
104
|
+
_collector = ("mpitool",)
|
|
103
105
|
_footprint = dict(
|
|
104
|
-
info
|
|
105
|
-
attr
|
|
106
|
-
sysname
|
|
107
|
-
info
|
|
106
|
+
info="MpiTool class in charge of a particular MPI implementation",
|
|
107
|
+
attr=dict(
|
|
108
|
+
sysname=dict(
|
|
109
|
+
info="The current OS name (e.g. Linux)",
|
|
108
110
|
),
|
|
109
|
-
mpiname
|
|
110
|
-
info
|
|
111
|
+
mpiname=dict(
|
|
112
|
+
info="The MPI implementation one wishes to use",
|
|
111
113
|
),
|
|
112
|
-
mpilauncher
|
|
113
|
-
info
|
|
114
|
-
optional = True
|
|
114
|
+
mpilauncher=dict(
|
|
115
|
+
info="The MPI launcher command to be used", optional=True
|
|
115
116
|
),
|
|
116
|
-
mpiopts
|
|
117
|
-
info
|
|
118
|
-
optional
|
|
119
|
-
default
|
|
117
|
+
mpiopts=dict(
|
|
118
|
+
info="Extra arguments for the MPI command",
|
|
119
|
+
optional=True,
|
|
120
|
+
default="",
|
|
120
121
|
),
|
|
121
|
-
mpiwrapstd
|
|
122
|
-
info
|
|
123
|
-
type
|
|
124
|
-
optional
|
|
125
|
-
default
|
|
126
|
-
doc_visibility
|
|
127
|
-
doc_zorder
|
|
122
|
+
mpiwrapstd=dict(
|
|
123
|
+
info="When using the Vortex' global wrapper redirect stderr/stdout",
|
|
124
|
+
type=bool,
|
|
125
|
+
optional=True,
|
|
126
|
+
default=False,
|
|
127
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
128
|
+
doc_zorder=-90,
|
|
128
129
|
),
|
|
129
|
-
mpibind_topology
|
|
130
|
-
optional
|
|
131
|
-
default
|
|
132
|
-
doc_visibility
|
|
133
|
-
doc_zorder
|
|
130
|
+
mpibind_topology=dict(
|
|
131
|
+
optional=True,
|
|
132
|
+
default="numapacked",
|
|
133
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
134
|
+
doc_zorder=-90,
|
|
134
135
|
),
|
|
135
|
-
optsep
|
|
136
|
-
info
|
|
137
|
-
optional
|
|
138
|
-
default
|
|
136
|
+
optsep=dict(
|
|
137
|
+
info="Separator between MPI options and the program name",
|
|
138
|
+
optional=True,
|
|
139
|
+
default="--",
|
|
139
140
|
),
|
|
140
|
-
optprefix
|
|
141
|
-
info
|
|
142
|
-
optional = True,
|
|
143
|
-
default = '--'
|
|
141
|
+
optprefix=dict(
|
|
142
|
+
info="MPI options prefix", optional=True, default="--"
|
|
144
143
|
),
|
|
145
|
-
optmap
|
|
146
|
-
info
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
optmap=dict(
|
|
145
|
+
info=(
|
|
146
|
+
"Mapping between MpiBinaryDescription objects "
|
|
147
|
+
+ "internal data and actual command line options"
|
|
148
|
+
),
|
|
149
|
+
type=footprints.FPDict,
|
|
150
|
+
optional=True,
|
|
151
|
+
default=footprints.FPDict(nn="nn", nnp="nnp", openmp="openmp"),
|
|
151
152
|
),
|
|
152
|
-
binsep
|
|
153
|
-
info
|
|
154
|
-
optional
|
|
155
|
-
default
|
|
153
|
+
binsep=dict(
|
|
154
|
+
info="Separator between multiple binary groups",
|
|
155
|
+
optional=True,
|
|
156
|
+
default="--",
|
|
156
157
|
),
|
|
157
|
-
basics
|
|
158
|
-
type
|
|
159
|
-
optional
|
|
160
|
-
default
|
|
158
|
+
basics=dict(
|
|
159
|
+
type=footprints.FPList,
|
|
160
|
+
optional=True,
|
|
161
|
+
default=footprints.FPList(
|
|
162
|
+
[
|
|
163
|
+
"system",
|
|
164
|
+
"env",
|
|
165
|
+
"target",
|
|
166
|
+
"context",
|
|
167
|
+
"ticket",
|
|
168
|
+
]
|
|
169
|
+
),
|
|
161
170
|
),
|
|
162
|
-
bindingmethod
|
|
163
|
-
info
|
|
164
|
-
values
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
bindingmethod=dict(
|
|
172
|
+
info="How to bind the MPI processes",
|
|
173
|
+
values=[
|
|
174
|
+
"vortex",
|
|
175
|
+
],
|
|
176
|
+
access="rwx",
|
|
177
|
+
optional=True,
|
|
178
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
179
|
+
doc_zorder=-90,
|
|
169
180
|
),
|
|
170
|
-
)
|
|
181
|
+
),
|
|
171
182
|
)
|
|
172
183
|
|
|
173
|
-
_envelope_bit_kind =
|
|
174
|
-
_envelope_wrapper_tpl =
|
|
175
|
-
_wrapstd_wrapper_tpl =
|
|
176
|
-
_envelope_wrapper_name =
|
|
177
|
-
_wrapstd_wrapper_name =
|
|
178
|
-
_envelope_rank_var =
|
|
184
|
+
_envelope_bit_kind = "basicenvelopebit"
|
|
185
|
+
_envelope_wrapper_tpl = "@envelope_wrapper_default.tpl"
|
|
186
|
+
_wrapstd_wrapper_tpl = "@wrapstd_wrapper_default.tpl"
|
|
187
|
+
_envelope_wrapper_name = "./global_envelope_wrapper.py"
|
|
188
|
+
_wrapstd_wrapper_name = "./global_wrapstd_wrapper.py"
|
|
189
|
+
_envelope_rank_var = "MPIRANK"
|
|
179
190
|
_supports_manual_ranks_mapping = False
|
|
180
191
|
_needs_mpilib_specific_mpienv = True
|
|
181
192
|
|
|
182
193
|
def __init__(self, *args, **kw):
|
|
183
194
|
"""After parent initialization, set master, options and basics to undefined."""
|
|
184
|
-
logger.debug(
|
|
195
|
+
logger.debug("Abstract mpi tool init %s", self.__class__)
|
|
185
196
|
super().__init__(*args, **kw)
|
|
186
197
|
self._launcher = self.mpilauncher or self.generic_mpiname
|
|
187
198
|
self._binaries = []
|
|
@@ -192,29 +203,31 @@ class MpiTool(footprints.FootprintBase):
|
|
|
192
203
|
self._ranks_map_cache = None
|
|
193
204
|
self._complex_ranks_map = None
|
|
194
205
|
for k in self.basics:
|
|
195
|
-
self.__dict__[
|
|
206
|
+
self.__dict__["_" + k] = None
|
|
196
207
|
|
|
197
208
|
@property
|
|
198
209
|
def realkind(self):
|
|
199
|
-
return
|
|
210
|
+
return "mpitool"
|
|
200
211
|
|
|
201
212
|
@property
|
|
202
213
|
def generic_mpiname(self):
|
|
203
|
-
return self.mpiname.split(
|
|
214
|
+
return self.mpiname.split("-")[0]
|
|
204
215
|
|
|
205
216
|
def __getattr__(self, key):
|
|
206
217
|
"""Have a look to basics values provided by some proxy."""
|
|
207
218
|
if key in self.basics:
|
|
208
|
-
return getattr(self,
|
|
219
|
+
return getattr(self, "_" + key)
|
|
209
220
|
else:
|
|
210
|
-
raise AttributeError(
|
|
221
|
+
raise AttributeError(
|
|
222
|
+
"Attribute [%s] is not a basic mpitool attribute" % key
|
|
223
|
+
)
|
|
211
224
|
|
|
212
225
|
def import_basics(self, obj, attrs=None):
|
|
213
226
|
"""Import some current values such as system, env, target and context from provided ``obj``."""
|
|
214
227
|
if attrs is None:
|
|
215
228
|
attrs = self.basics
|
|
216
229
|
for k in [x for x in attrs if x in self.basics and hasattr(obj, x)]:
|
|
217
|
-
setattr(self,
|
|
230
|
+
setattr(self, "_" + k, getattr(obj, k))
|
|
218
231
|
for bin_obj in self.binaries:
|
|
219
232
|
bin_obj.import_basics(obj, attrs=None)
|
|
220
233
|
|
|
@@ -227,7 +240,9 @@ class MpiTool(footprints.FootprintBase):
|
|
|
227
240
|
|
|
228
241
|
def _set_launcher(self, value):
|
|
229
242
|
"""Set current launcher mpi name. Should be some special trick, so issue a warning."""
|
|
230
|
-
logger.warning(
|
|
243
|
+
logger.warning(
|
|
244
|
+
"Setting a new value [%s] to mpi launcher [%s].", value, self
|
|
245
|
+
)
|
|
231
246
|
self._launcher = value
|
|
232
247
|
|
|
233
248
|
launcher = property(_get_launcher, _set_launcher)
|
|
@@ -242,11 +257,22 @@ class MpiTool(footprints.FootprintBase):
|
|
|
242
257
|
|
|
243
258
|
def _set_envelope(self, value):
|
|
244
259
|
"""Set the envelope description."""
|
|
245
|
-
if not (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
if not (
|
|
261
|
+
isinstance(value, collections.abc.Iterable)
|
|
262
|
+
and all(
|
|
263
|
+
[
|
|
264
|
+
isinstance(b, dict)
|
|
265
|
+
and all(
|
|
266
|
+
[
|
|
267
|
+
bk in ("nn", "nnp", "openmp", "np")
|
|
268
|
+
for bk in b.keys()
|
|
269
|
+
]
|
|
270
|
+
)
|
|
271
|
+
for b in value
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
):
|
|
275
|
+
raise ValueError("This should be an Iterable of dictionaries.")
|
|
250
276
|
self._valid_envelope(value)
|
|
251
277
|
self._envelope = list()
|
|
252
278
|
for e in value:
|
|
@@ -271,22 +297,33 @@ class MpiTool(footprints.FootprintBase):
|
|
|
271
297
|
for a_bin in self.binaries:
|
|
272
298
|
if a_bin.group is None:
|
|
273
299
|
# The usual (and easy) case
|
|
274
|
-
new_envelope.append(
|
|
275
|
-
|
|
300
|
+
new_envelope.append(
|
|
301
|
+
{
|
|
302
|
+
k: v
|
|
303
|
+
for k, v in a_bin.options.items()
|
|
304
|
+
if k in ("nn", "nnp", "openmp", "np")
|
|
305
|
+
}
|
|
306
|
+
)
|
|
276
307
|
elif a_bin.group in groups:
|
|
277
308
|
# Deal with group of binaries
|
|
278
309
|
group = groups.pop(a_bin.group)
|
|
279
|
-
n_nodes = {g_bin.options.get(
|
|
310
|
+
n_nodes = {g_bin.options.get("nn", None) for g_bin in group}
|
|
280
311
|
if None in n_nodes:
|
|
281
|
-
raise ValueError(
|
|
282
|
-
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"To build a proper envelope, "
|
|
314
|
+
+ '"nn" needs to be specified in all binaries'
|
|
315
|
+
)
|
|
283
316
|
done_nodes = 0
|
|
284
317
|
for n_node in sorted(n_nodes):
|
|
285
318
|
new_desc = {}
|
|
286
|
-
new_desc[
|
|
287
|
-
new_desc[
|
|
288
|
-
for g_bin in [
|
|
289
|
-
|
|
319
|
+
new_desc["nn"] = n_node - done_nodes
|
|
320
|
+
new_desc["nnp"] = 0
|
|
321
|
+
for g_bin in [
|
|
322
|
+
g_bin
|
|
323
|
+
for g_bin in group
|
|
324
|
+
if g_bin.options["nn"] >= n_node
|
|
325
|
+
]:
|
|
326
|
+
new_desc["nnp"] += g_bin.options["nnp"]
|
|
290
327
|
new_envelope.append(new_desc)
|
|
291
328
|
done_nodes = n_node
|
|
292
329
|
self.envelope = new_envelope
|
|
@@ -301,17 +338,27 @@ class MpiTool(footprints.FootprintBase):
|
|
|
301
338
|
|
|
302
339
|
def _set_binaries(self, value):
|
|
303
340
|
"""Set the list of :class:`MpiBinaryDescription` objects associated with this instance."""
|
|
304
|
-
if not (
|
|
305
|
-
|
|
306
|
-
|
|
341
|
+
if not (
|
|
342
|
+
isinstance(value, collections.abc.Iterable)
|
|
343
|
+
and all([isinstance(b, MpiBinary) for b in value])
|
|
344
|
+
):
|
|
345
|
+
raise ValueError(
|
|
346
|
+
"This should be an Iterable of MpiBinary instances."
|
|
347
|
+
)
|
|
307
348
|
has_bin_groups = not all([b.group is None for b in value])
|
|
308
349
|
if not (self._supports_manual_ranks_mapping or not has_bin_groups):
|
|
309
|
-
raise ValueError(
|
|
350
|
+
raise ValueError(
|
|
351
|
+
"Binary groups are not supported by this MpiTool class"
|
|
352
|
+
)
|
|
310
353
|
has_bin_distribution = not all([b.distribution is None for b in value])
|
|
311
|
-
if not (
|
|
312
|
-
|
|
354
|
+
if not (
|
|
355
|
+
self._supports_manual_ranks_mapping or not has_bin_distribution
|
|
356
|
+
):
|
|
357
|
+
raise ValueError(
|
|
358
|
+
"Binary distribution option is not supported by this MpiTool class"
|
|
359
|
+
)
|
|
313
360
|
self._binaries = value
|
|
314
|
-
if not self.envelope and self.bindingmethod ==
|
|
361
|
+
if not self.envelope and self.bindingmethod == "vortex":
|
|
315
362
|
self._set_envelope_from_binaries()
|
|
316
363
|
elif not self.envelope and (has_bin_groups or has_bin_distribution):
|
|
317
364
|
self._set_envelope_from_binaries()
|
|
@@ -328,8 +375,12 @@ class MpiTool(footprints.FootprintBase):
|
|
|
328
375
|
def _mpilib_data(self):
|
|
329
376
|
"""From the binaries, try to detect MPI library and mpirun paths."""
|
|
330
377
|
if self._mpilib_data_cache is None:
|
|
331
|
-
mpilib_guesses = (
|
|
332
|
-
|
|
378
|
+
mpilib_guesses = (
|
|
379
|
+
"libmpi.so",
|
|
380
|
+
"libmpi_mt.so",
|
|
381
|
+
"libmpi_dbg.so",
|
|
382
|
+
"libmpi_dbg_mt.so",
|
|
383
|
+
)
|
|
333
384
|
shp = self.system.path
|
|
334
385
|
mpilib_data = set()
|
|
335
386
|
for binary in self.binaries:
|
|
@@ -351,19 +402,24 @@ class MpiTool(footprints.FootprintBase):
|
|
|
351
402
|
mpilib = shp.normpath(mpilib)
|
|
352
403
|
mpitoolsdir = None
|
|
353
404
|
mpidir = shp.dirname(shp.dirname(mpilib))
|
|
354
|
-
if shp.exists(shp.join(mpidir,
|
|
355
|
-
mpitoolsdir = shp.join(mpidir,
|
|
356
|
-
if not mpitoolsdir and shp.exists(
|
|
357
|
-
|
|
405
|
+
if shp.exists(shp.join(mpidir, "bin", "mpirun")):
|
|
406
|
+
mpitoolsdir = shp.join(mpidir, "bin")
|
|
407
|
+
if not mpitoolsdir and shp.exists(
|
|
408
|
+
shp.join(mpidir, "..", "bin", "mpirun")
|
|
409
|
+
):
|
|
410
|
+
mpitoolsdir = shp.normpath(
|
|
411
|
+
shp.join(mpidir, "..", "bin")
|
|
412
|
+
)
|
|
358
413
|
if mpilib and mpitoolsdir:
|
|
359
|
-
mpilib_data.add(
|
|
360
|
-
|
|
414
|
+
mpilib_data.add(
|
|
415
|
+
(shp.realpath(mpilib), shp.realpath(mpitoolsdir))
|
|
416
|
+
)
|
|
361
417
|
# All the binary must use the same library !
|
|
362
418
|
if len(mpilib_data) == 0:
|
|
363
|
-
logger.info(
|
|
419
|
+
logger.info("No MPI library was detected.")
|
|
364
420
|
self._mpilib_data_cache = ()
|
|
365
421
|
elif len(mpilib_data) > 1:
|
|
366
|
-
logger.error(
|
|
422
|
+
logger.error("Multiple MPI library were detected.")
|
|
367
423
|
self._mpilib_data_cache = ()
|
|
368
424
|
else:
|
|
369
425
|
self._mpilib_data_cache = mpilib_data.pop()
|
|
@@ -373,8 +429,11 @@ class MpiTool(footprints.FootprintBase):
|
|
|
373
429
|
for line in rclines:
|
|
374
430
|
matched = regex.match(line)
|
|
375
431
|
if matched:
|
|
376
|
-
logger.info(
|
|
377
|
-
|
|
432
|
+
logger.info(
|
|
433
|
+
"MPI implementation detected: %s (%s)",
|
|
434
|
+
which,
|
|
435
|
+
" ".join(matched.groups()),
|
|
436
|
+
)
|
|
378
437
|
return [which] + [int(res) for res in matched.groups()]
|
|
379
438
|
return False
|
|
380
439
|
|
|
@@ -386,7 +445,7 @@ class MpiTool(footprints.FootprintBase):
|
|
|
386
445
|
mpi_lib, mpi_tools_dir = self._mpilib_data()
|
|
387
446
|
ld_libs_extra = set()
|
|
388
447
|
sh = self.system
|
|
389
|
-
mpirun_path = sh.path.join(mpi_tools_dir,
|
|
448
|
+
mpirun_path = sh.path.join(mpi_tools_dir, "mpirun")
|
|
390
449
|
if sh.path.exists(mpirun_path):
|
|
391
450
|
try:
|
|
392
451
|
libs = sh.ldd(mpirun_path)
|
|
@@ -399,30 +458,47 @@ class MpiTool(footprints.FootprintBase):
|
|
|
399
458
|
for lib, libpath in sh.ldd(binary.master).items():
|
|
400
459
|
if libpath:
|
|
401
460
|
libscache[lib] = sh.path.dirname(libpath)
|
|
402
|
-
for missing_lib in [
|
|
403
|
-
|
|
461
|
+
for missing_lib in [
|
|
462
|
+
lib for lib, libname in libs.items() if libname is None
|
|
463
|
+
]:
|
|
404
464
|
if missing_lib in libscache:
|
|
405
465
|
ld_libs_extra.add(libscache[missing_lib])
|
|
406
466
|
with self.env.clone() as localenv:
|
|
407
467
|
for libpath in ld_libs_extra:
|
|
408
|
-
localenv.setgenericpath(
|
|
409
|
-
rc = sh.spawn(
|
|
468
|
+
localenv.setgenericpath("LD_LIBRARY_PATH", libpath)
|
|
469
|
+
rc = sh.spawn(
|
|
470
|
+
[mpirun_path, "--version"], output=True, fatal=False
|
|
471
|
+
)
|
|
410
472
|
if rc:
|
|
411
473
|
id_res = self._mpilib_match_result(
|
|
412
|
-
re.compile(
|
|
413
|
-
|
|
414
|
-
|
|
474
|
+
re.compile(
|
|
475
|
+
r"^.*Intel.*MPI.*Version\s+(\d+)\s+Update\s+(\d+)",
|
|
476
|
+
re.IGNORECASE,
|
|
477
|
+
),
|
|
478
|
+
rc,
|
|
479
|
+
"intelmpi",
|
|
480
|
+
)
|
|
415
481
|
id_res = id_res or self._mpilib_match_result(
|
|
416
|
-
re.compile(
|
|
417
|
-
|
|
418
|
-
|
|
482
|
+
re.compile(
|
|
483
|
+
r"^.*Open\s*MPI.*\s+(\d+)\.(\d+)(?:\.(\d+))?",
|
|
484
|
+
re.IGNORECASE,
|
|
485
|
+
),
|
|
486
|
+
rc,
|
|
487
|
+
"openmpi",
|
|
488
|
+
)
|
|
419
489
|
if id_res:
|
|
420
490
|
ld_libs_extra = tuple(sorted(ld_libs_extra))
|
|
421
|
-
self._mpilib_identification_cache = tuple(
|
|
422
|
-
|
|
491
|
+
self._mpilib_identification_cache = tuple(
|
|
492
|
+
[mpi_lib, mpi_tools_dir, ld_libs_extra] + id_res
|
|
493
|
+
)
|
|
423
494
|
if self._mpilib_identification_cache is None:
|
|
424
495
|
ld_libs_extra = tuple(sorted(ld_libs_extra))
|
|
425
|
-
self._mpilib_identification_cache = (
|
|
496
|
+
self._mpilib_identification_cache = (
|
|
497
|
+
mpi_lib,
|
|
498
|
+
mpi_tools_dir,
|
|
499
|
+
ld_libs_extra,
|
|
500
|
+
"unknown",
|
|
501
|
+
)
|
|
426
502
|
return self._mpilib_identification_cache
|
|
427
503
|
|
|
428
504
|
def _get_sources(self):
|
|
@@ -432,7 +508,7 @@ class MpiTool(footprints.FootprintBase):
|
|
|
432
508
|
def _set_sources(self, value):
|
|
433
509
|
"""Set the list of of directories that may contain source files."""
|
|
434
510
|
if not isinstance(value, collections.abc.Iterable):
|
|
435
|
-
raise ValueError(
|
|
511
|
+
raise ValueError("This should be an Iterable.")
|
|
436
512
|
self._sources = value
|
|
437
513
|
|
|
438
514
|
sources = property(_get_sources, _set_sources)
|
|
@@ -446,14 +522,16 @@ class MpiTool(footprints.FootprintBase):
|
|
|
446
522
|
klast = None
|
|
447
523
|
options = collections.defaultdict(list)
|
|
448
524
|
for optdef in shlex.split(self._actual_mpiopts()):
|
|
449
|
-
if optdef.startswith(
|
|
450
|
-
optdef = optdef.lstrip(
|
|
525
|
+
if optdef.startswith("-"):
|
|
526
|
+
optdef = optdef.lstrip("-")
|
|
451
527
|
options[optdef].append([])
|
|
452
528
|
klast = optdef
|
|
453
529
|
elif klast is not None:
|
|
454
530
|
options[klast][-1].append(optdef)
|
|
455
531
|
else:
|
|
456
|
-
raise MpiException(
|
|
532
|
+
raise MpiException(
|
|
533
|
+
"Badly shaped mpi option around {!s}".format(optdef)
|
|
534
|
+
)
|
|
457
535
|
return options
|
|
458
536
|
|
|
459
537
|
def _hook_binary_mpiopts(self, binary, options):
|
|
@@ -466,14 +544,18 @@ class MpiTool(footprints.FootprintBase):
|
|
|
466
544
|
if self._ranks_map_cache is None:
|
|
467
545
|
self._complex_ranks_map = False
|
|
468
546
|
if not self.envelope:
|
|
469
|
-
raise RuntimeError(
|
|
547
|
+
raise RuntimeError(
|
|
548
|
+
"Ranks mapping should always be used within an envelope."
|
|
549
|
+
)
|
|
470
550
|
# First deal with bingroups
|
|
471
551
|
ranks_map = dict()
|
|
472
552
|
has_bin_groups = not all([b.group is None for b in self.binaries])
|
|
473
553
|
cursor = 0 # The MPI rank we are currently processing
|
|
474
554
|
if has_bin_groups:
|
|
475
555
|
if not self._supports_manual_ranks_mapping:
|
|
476
|
-
raise RuntimeError(
|
|
556
|
+
raise RuntimeError(
|
|
557
|
+
"This MpiTool class does not supports ranks mapping."
|
|
558
|
+
)
|
|
477
559
|
self._complex_ranks_map = True
|
|
478
560
|
cursor0 = 0 # The first available "real" slot
|
|
479
561
|
group_cache = collections.defaultdict(list)
|
|
@@ -487,24 +569,42 @@ class MpiTool(footprints.FootprintBase):
|
|
|
487
569
|
if not reserved:
|
|
488
570
|
# It is the first time this group of binaries is seen
|
|
489
571
|
# Find out what are the binaries in this group
|
|
490
|
-
bin_buddies = [
|
|
491
|
-
|
|
492
|
-
|
|
572
|
+
bin_buddies = [
|
|
573
|
+
bin_b
|
|
574
|
+
for bin_b in self.binaries
|
|
575
|
+
if bin_b.group == a_bin.group
|
|
576
|
+
]
|
|
577
|
+
if all(
|
|
578
|
+
[
|
|
579
|
+
"nn" in bin_b.options
|
|
580
|
+
for bin_b in bin_buddies
|
|
581
|
+
]
|
|
582
|
+
):
|
|
493
583
|
# Each of the binary descriptions should define the number of nodes
|
|
494
|
-
max_nn = max(
|
|
584
|
+
max_nn = max(
|
|
585
|
+
[
|
|
586
|
+
bin_b.options["nn"]
|
|
587
|
+
for bin_b in bin_buddies
|
|
588
|
+
]
|
|
589
|
+
)
|
|
495
590
|
for i_node in range(max_nn):
|
|
496
591
|
for bin_b in bin_buddies:
|
|
497
|
-
if bin_b.options[
|
|
498
|
-
group_cache[bin_b].extend(
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
592
|
+
if bin_b.options["nn"] > i_node:
|
|
593
|
+
group_cache[bin_b].extend(
|
|
594
|
+
range(
|
|
595
|
+
cursor0,
|
|
596
|
+
cursor0
|
|
597
|
+
+ bin_b.options["nnp"],
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
cursor0 += bin_b.options["nnp"]
|
|
502
601
|
else:
|
|
503
602
|
# If the number of nodes is not defined, revert to the number of tasks.
|
|
504
603
|
# This will probably result in strange results !
|
|
505
604
|
for bin_b in bin_buddies:
|
|
506
|
-
group_cache[bin_b].extend(
|
|
507
|
-
|
|
605
|
+
group_cache[bin_b].extend(
|
|
606
|
+
range(cursor0, cursor0 + bin_b.nprocs)
|
|
607
|
+
)
|
|
508
608
|
cursor0 += bin_b.nprocs
|
|
509
609
|
reserved = group_cache[a_bin]
|
|
510
610
|
for rank in range(a_bin.nprocs):
|
|
@@ -517,37 +617,58 @@ class MpiTool(footprints.FootprintBase):
|
|
|
517
617
|
ranks_map[rank + cursor] = rank + cursor
|
|
518
618
|
cursor += a_bin.nprocs
|
|
519
619
|
# Then deal with distribution
|
|
520
|
-
do_bin_distribution = not all(
|
|
521
|
-
|
|
620
|
+
do_bin_distribution = not all(
|
|
621
|
+
[b.distribution in (None, "continuous") for b in self.binaries]
|
|
622
|
+
)
|
|
522
623
|
if self._complex_ranks_map or do_bin_distribution:
|
|
523
624
|
if not self.envelope:
|
|
524
|
-
raise RuntimeError(
|
|
625
|
+
raise RuntimeError(
|
|
626
|
+
"Ranks mapping shoudl always be used within an envelope."
|
|
627
|
+
)
|
|
525
628
|
if do_bin_distribution:
|
|
526
629
|
if not self._supports_manual_ranks_mapping:
|
|
527
|
-
raise RuntimeError(
|
|
630
|
+
raise RuntimeError(
|
|
631
|
+
"This MpiTool class does not supports ranks mapping."
|
|
632
|
+
)
|
|
528
633
|
self._complex_ranks_map = True
|
|
529
|
-
if all(
|
|
634
|
+
if all(
|
|
635
|
+
[
|
|
636
|
+
"nn" in b.options and "nnp" in b.options
|
|
637
|
+
for b in self.envelope
|
|
638
|
+
]
|
|
639
|
+
):
|
|
530
640
|
# Extract node information
|
|
531
641
|
node_cursor = 0
|
|
532
642
|
nodes_id = list()
|
|
533
643
|
for e_bit in self.envelope:
|
|
534
|
-
for _ in range(e_bit.options[
|
|
535
|
-
nodes_id.extend(
|
|
644
|
+
for _ in range(e_bit.options["nn"]):
|
|
645
|
+
nodes_id.extend(
|
|
646
|
+
[
|
|
647
|
+
node_cursor,
|
|
648
|
+
]
|
|
649
|
+
* e_bit.options["nnp"]
|
|
650
|
+
)
|
|
536
651
|
node_cursor += 1
|
|
537
652
|
# Re-order ranks given the distribution
|
|
538
653
|
cursor = 0
|
|
539
654
|
for a_bin in self.binaries:
|
|
540
655
|
if a_bin.distribution == "roundrobin":
|
|
541
656
|
# The current list of ranks
|
|
542
|
-
actual_ranks = [
|
|
543
|
-
|
|
657
|
+
actual_ranks = [
|
|
658
|
+
ranks_map[i]
|
|
659
|
+
for i in range(cursor, cursor + a_bin.nprocs)
|
|
660
|
+
]
|
|
544
661
|
# Find the node number associated with each rank
|
|
545
|
-
nodes_dict = collections.defaultdict(
|
|
662
|
+
nodes_dict = collections.defaultdict(
|
|
663
|
+
collections.deque
|
|
664
|
+
)
|
|
546
665
|
for rank in actual_ranks:
|
|
547
666
|
nodes_dict[nodes_id[rank]].append(rank)
|
|
548
667
|
# Create a new list of ranks in a round-robin manner
|
|
549
668
|
actual_ranks = list()
|
|
550
|
-
iter_nodes = itertools.cycle(
|
|
669
|
+
iter_nodes = itertools.cycle(
|
|
670
|
+
sorted(nodes_dict.keys())
|
|
671
|
+
)
|
|
551
672
|
for _ in range(a_bin.nprocs):
|
|
552
673
|
av_ranks = None
|
|
553
674
|
while not av_ranks:
|
|
@@ -558,8 +679,10 @@ class MpiTool(footprints.FootprintBase):
|
|
|
558
679
|
ranks_map[cursor + i] = actual_ranks[i]
|
|
559
680
|
cursor += a_bin.nprocs
|
|
560
681
|
else:
|
|
561
|
-
logger.warning(
|
|
562
|
-
|
|
682
|
+
logger.warning(
|
|
683
|
+
"Cannot enforce binary distribution if the envelope"
|
|
684
|
+
+ "does not contain nn/nnp information"
|
|
685
|
+
)
|
|
563
686
|
# Cache the final result !
|
|
564
687
|
self._ranks_map_cache = ranks_map
|
|
565
688
|
return self._ranks_map_cache
|
|
@@ -577,10 +700,12 @@ class MpiTool(footprints.FootprintBase):
|
|
|
577
700
|
if not self.mpiwrapstd:
|
|
578
701
|
return None
|
|
579
702
|
# Create the launchwrapper
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
703
|
+
with importlib.resources.as_file(
|
|
704
|
+
importlib.resources.files("vortex.algo")
|
|
705
|
+
) as path:
|
|
706
|
+
tplpath = path / "mpitools_templates" / self._wrapstd_wrapper_tpl
|
|
707
|
+
wtpl = config.load_template(tplpath, encoding="utf-8")
|
|
708
|
+
with open(self._wrapstd_wrapper_name, "w", encoding="utf-8") as fhw:
|
|
584
709
|
fhw.write(
|
|
585
710
|
wtpl.substitute(
|
|
586
711
|
python=self.system.executable,
|
|
@@ -599,12 +724,14 @@ class MpiTool(footprints.FootprintBase):
|
|
|
599
724
|
wrapstd = self._wrapstd_mkwrapper()
|
|
600
725
|
for bin_obj in self.binaries:
|
|
601
726
|
if bin_obj.master is None:
|
|
602
|
-
raise MpiException(
|
|
727
|
+
raise MpiException("No master defined before launching MPI")
|
|
603
728
|
# If there are no options, do not bother...
|
|
604
729
|
if len(bin_obj.expanded_options()):
|
|
605
730
|
if effective > 0 and self.binsep:
|
|
606
731
|
cmdl.append(self.binsep)
|
|
607
|
-
e_options = self._hook_binary_mpiopts(
|
|
732
|
+
e_options = self._hook_binary_mpiopts(
|
|
733
|
+
bin_obj, bin_obj.expanded_options()
|
|
734
|
+
)
|
|
608
735
|
for k in sorted(e_options.keys()):
|
|
609
736
|
if k in self.optmap:
|
|
610
737
|
cmdl.append(self.optprefix + str(self.optmap[k]))
|
|
@@ -620,8 +747,9 @@ class MpiTool(footprints.FootprintBase):
|
|
|
620
747
|
|
|
621
748
|
def _envelope_fix_envelope_bit(self, e_bit, e_desc):
|
|
622
749
|
"""Set the envelope fake binary options."""
|
|
623
|
-
e_bit.options = {
|
|
624
|
-
|
|
750
|
+
e_bit.options = {
|
|
751
|
+
k: v for k, v in e_desc.items() if k not in ("openmp", "np")
|
|
752
|
+
}
|
|
625
753
|
e_bit.master = self._envelope_wrapper_name
|
|
626
754
|
|
|
627
755
|
def _envelope_mkwrapper_todostack(self):
|
|
@@ -630,18 +758,23 @@ class MpiTool(footprints.FootprintBase):
|
|
|
630
758
|
todostack = dict()
|
|
631
759
|
for bin_obj in self.binaries:
|
|
632
760
|
if bin_obj.master is None:
|
|
633
|
-
raise MpiException(
|
|
761
|
+
raise MpiException("No master defined before launching MPI")
|
|
634
762
|
# If there are no options, do not bother...
|
|
635
763
|
if bin_obj.options and bin_obj.nprocs != 0:
|
|
636
764
|
if not bin_obj.nprocs:
|
|
637
|
-
raise ValueError(
|
|
765
|
+
raise ValueError(
|
|
766
|
+
"nranks must be provided when using envelopes"
|
|
767
|
+
)
|
|
638
768
|
for mpirank in range(ranksidx, ranksidx + bin_obj.nprocs):
|
|
639
769
|
if bin_obj.allowbind:
|
|
640
|
-
ranks_bsize[mpirank] = bin_obj.options.get(
|
|
770
|
+
ranks_bsize[mpirank] = bin_obj.options.get("openmp", 1)
|
|
641
771
|
else:
|
|
642
772
|
ranks_bsize[mpirank] = -1
|
|
643
|
-
todostack[mpirank] = (
|
|
644
|
-
|
|
773
|
+
todostack[mpirank] = (
|
|
774
|
+
bin_obj.master,
|
|
775
|
+
bin_obj.arguments,
|
|
776
|
+
bin_obj.options.get("openmp", None),
|
|
777
|
+
)
|
|
645
778
|
ranksidx += bin_obj.nprocs
|
|
646
779
|
return todostack, ranks_bsize
|
|
647
780
|
|
|
@@ -651,18 +784,25 @@ class MpiTool(footprints.FootprintBase):
|
|
|
651
784
|
ranks_idx = 0
|
|
652
785
|
dispensers_map = dict()
|
|
653
786
|
for e_bit in self.envelope:
|
|
654
|
-
if
|
|
655
|
-
for _ in range(e_bit.options[
|
|
656
|
-
cpu_disp = self.system.cpus_ids_dispenser(
|
|
787
|
+
if "nn" in e_bit.options and "nnp" in e_bit.options:
|
|
788
|
+
for _ in range(e_bit.options["nn"]):
|
|
789
|
+
cpu_disp = self.system.cpus_ids_dispenser(
|
|
790
|
+
topology=self.mpibind_topology
|
|
791
|
+
)
|
|
657
792
|
if not cpu_disp:
|
|
658
|
-
raise MpiException(
|
|
659
|
-
|
|
660
|
-
|
|
793
|
+
raise MpiException(
|
|
794
|
+
"Unable to detect the CPU layout with topology: {:s}".format(
|
|
795
|
+
self.mpibind_topology,
|
|
796
|
+
)
|
|
797
|
+
)
|
|
798
|
+
for _ in range(e_bit.options["nnp"]):
|
|
661
799
|
dispensers_map[ranks_idx] = (cpu_disp, totalnodes)
|
|
662
800
|
ranks_idx += 1
|
|
663
801
|
totalnodes += 1
|
|
664
802
|
else:
|
|
665
|
-
logger.error(
|
|
803
|
+
logger.error(
|
|
804
|
+
"Cannot compute a proper binding without nn/nnp information"
|
|
805
|
+
)
|
|
666
806
|
raise MpiException("Vortex binding error.")
|
|
667
807
|
return dispensers_map
|
|
668
808
|
|
|
@@ -674,72 +814,113 @@ class MpiTool(footprints.FootprintBase):
|
|
|
674
814
|
# Actually generate the binding map
|
|
675
815
|
ranks_idx = 0
|
|
676
816
|
for e_bit in self.envelope:
|
|
677
|
-
for _ in range(e_bit.options[
|
|
678
|
-
for _ in range(e_bit.options[
|
|
679
|
-
cpu_disp, i_node = dispensers_map[
|
|
817
|
+
for _ in range(e_bit.options["nn"]):
|
|
818
|
+
for _ in range(e_bit.options["nnp"]):
|
|
819
|
+
cpu_disp, i_node = dispensers_map[
|
|
820
|
+
self._ranks_mapping[ranks_idx]
|
|
821
|
+
]
|
|
680
822
|
if ranks_bsize.get(ranks_idx, 1) != -1:
|
|
681
823
|
try:
|
|
682
|
-
binding_stack[ranks_idx] = cpu_disp(
|
|
824
|
+
binding_stack[ranks_idx] = cpu_disp(
|
|
825
|
+
ranks_bsize.get(ranks_idx, 1)
|
|
826
|
+
)
|
|
683
827
|
except (StopIteration, IndexError):
|
|
684
828
|
# When CPU dispensers are exhausted (it might happened if more tasks
|
|
685
829
|
# than available CPUs are requested).
|
|
686
|
-
dispensers_map =
|
|
687
|
-
|
|
688
|
-
|
|
830
|
+
dispensers_map = (
|
|
831
|
+
self._envelope_mkwrapper_cpu_dispensers()
|
|
832
|
+
)
|
|
833
|
+
cpu_disp, i_node = dispensers_map[
|
|
834
|
+
self._ranks_mapping[ranks_idx]
|
|
835
|
+
]
|
|
836
|
+
binding_stack[ranks_idx] = cpu_disp(
|
|
837
|
+
ranks_bsize.get(ranks_idx, 1)
|
|
838
|
+
)
|
|
689
839
|
else:
|
|
690
|
-
binding_stack[ranks_idx] = set(
|
|
840
|
+
binding_stack[ranks_idx] = set(
|
|
841
|
+
self.system.cpus_info.cpus.keys()
|
|
842
|
+
)
|
|
691
843
|
binding_node[ranks_idx] = i_node
|
|
692
844
|
ranks_idx += 1
|
|
693
845
|
return binding_stack, binding_node
|
|
694
846
|
|
|
695
847
|
def _envelope_mkwrapper_tplsubs(self, todostack, bindingstack):
|
|
696
|
-
return dict(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
848
|
+
return dict(
|
|
849
|
+
python=self.system.executable,
|
|
850
|
+
sitepath=self.system.path.join(self.ticket.glove.siteroot, "site"),
|
|
851
|
+
mpirankvariable=self._envelope_rank_var,
|
|
852
|
+
todolist=(
|
|
853
|
+
"\n".join(
|
|
854
|
+
[
|
|
855
|
+
" {:d}: ('{:s}', [{:s}], {:s}),".format(
|
|
856
|
+
mpi_r,
|
|
857
|
+
what[0],
|
|
858
|
+
", ".join(["'{:s}'".format(a) for a in what[1]]),
|
|
859
|
+
str(what[2]),
|
|
860
|
+
)
|
|
861
|
+
for mpi_r, what in sorted(todostack.items())
|
|
862
|
+
]
|
|
863
|
+
)
|
|
864
|
+
),
|
|
865
|
+
bindinglist=(
|
|
866
|
+
"\n".join(
|
|
867
|
+
[
|
|
868
|
+
" {:d}: [{:s}],".format(
|
|
869
|
+
mpi_r, ", ".join(["{:d}".format(a) for a in what])
|
|
870
|
+
)
|
|
871
|
+
for mpi_r, what in sorted(bindingstack.items())
|
|
872
|
+
]
|
|
873
|
+
)
|
|
874
|
+
),
|
|
875
|
+
)
|
|
709
876
|
|
|
710
877
|
def _envelope_mkwrapper(self, cmdl):
|
|
711
878
|
"""Generate the wrapper script used when an envelope is defined."""
|
|
712
879
|
# Generate the dictionary that associate rank numbers and programs
|
|
713
880
|
todostack, ranks_bsize = self._envelope_mkwrapper_todostack()
|
|
714
881
|
# Generate the binding stuff
|
|
715
|
-
bindingstack, bindingnode = self._envelope_mkwrapper_bindingstack(
|
|
882
|
+
bindingstack, bindingnode = self._envelope_mkwrapper_bindingstack(
|
|
883
|
+
ranks_bsize
|
|
884
|
+
)
|
|
716
885
|
# Print binding details
|
|
717
|
-
logger.debug(
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
886
|
+
logger.debug(
|
|
887
|
+
"Vortex Envelope Mechanism is used"
|
|
888
|
+
+ (" & vortex binding is on." if bindingstack else ".")
|
|
889
|
+
)
|
|
890
|
+
env_info_head = "{:5s} {:24s} {:4s}".format(
|
|
891
|
+
"#rank", "binary_name", "#OMP"
|
|
892
|
+
)
|
|
893
|
+
env_info_fmt = "{:5d} {:24s} {:4s}"
|
|
721
894
|
if bindingstack:
|
|
722
|
-
env_info_head +=
|
|
723
|
-
env_info_fmt2 =
|
|
895
|
+
env_info_head += " {:5s} {:s}".format("#node", "bindings_list")
|
|
896
|
+
env_info_fmt2 = " {:5d} {:s}"
|
|
724
897
|
binding_str = [env_info_head]
|
|
725
898
|
for i_rank in sorted(todostack):
|
|
726
|
-
entry_str = env_info_fmt.format(
|
|
727
|
-
|
|
728
|
-
|
|
899
|
+
entry_str = env_info_fmt.format(
|
|
900
|
+
i_rank,
|
|
901
|
+
self.system.path.basename(todostack[i_rank][0])[:24],
|
|
902
|
+
str(todostack[i_rank][2]),
|
|
903
|
+
)
|
|
729
904
|
if bindingstack:
|
|
730
|
-
entry_str += env_info_fmt2.format(
|
|
731
|
-
|
|
732
|
-
|
|
905
|
+
entry_str += env_info_fmt2.format(
|
|
906
|
+
bindingnode[i_rank],
|
|
907
|
+
",".join([str(c) for c in sorted(bindingstack[i_rank])]),
|
|
908
|
+
)
|
|
733
909
|
binding_str.append(entry_str)
|
|
734
|
-
logger.debug(
|
|
910
|
+
logger.debug(
|
|
911
|
+
"Here are the envelope details:\n%s", "\n".join(binding_str)
|
|
912
|
+
)
|
|
735
913
|
# Create the launchwrapper
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
914
|
+
with importlib.resources.as_file(
|
|
915
|
+
importlib.resources.files("vortex.algo")
|
|
916
|
+
) as path:
|
|
917
|
+
tplpath = path / "mpitools_templates" / self._envelope_wrapper_tpl
|
|
918
|
+
wtpl = config.load_template(tplpath, encoding="utf-8")
|
|
919
|
+
with open(self._envelope_wrapper_name, "w", encoding="utf-8") as fhw:
|
|
740
920
|
fhw.write(
|
|
741
|
-
wtpl.substitute(
|
|
742
|
-
|
|
921
|
+
wtpl.substitute(
|
|
922
|
+
**self._envelope_mkwrapper_tplsubs(todostack, bindingstack)
|
|
923
|
+
)
|
|
743
924
|
)
|
|
744
925
|
self.system.xperm(self._envelope_wrapper_name, force=True)
|
|
745
926
|
return self._envelope_wrapper_name
|
|
@@ -754,7 +935,9 @@ class MpiTool(footprints.FootprintBase):
|
|
|
754
935
|
for effective, e_bit in enumerate(self.envelope):
|
|
755
936
|
if effective > 0 and self.binsep:
|
|
756
937
|
cmdl.append(self.binsep)
|
|
757
|
-
e_options = self._hook_binary_mpiopts(
|
|
938
|
+
e_options = self._hook_binary_mpiopts(
|
|
939
|
+
e_bit, e_bit.expanded_options()
|
|
940
|
+
)
|
|
758
941
|
for k in sorted(e_options.keys()):
|
|
759
942
|
if k in self.optmap:
|
|
760
943
|
cmdl.append(self.optprefix + str(self.optmap[k]))
|
|
@@ -773,7 +956,9 @@ class MpiTool(footprints.FootprintBase):
|
|
|
773
956
|
|
|
774
957
|
def mkcmdline(self):
|
|
775
958
|
"""Builds the MPI command line."""
|
|
776
|
-
cmdl = [
|
|
959
|
+
cmdl = [
|
|
960
|
+
self.launcher,
|
|
961
|
+
]
|
|
777
962
|
for k, instances in sorted(self._reshaped_mpiopts().items()):
|
|
778
963
|
for instance in instances:
|
|
779
964
|
cmdl.append(self.optprefix + str(k))
|
|
@@ -789,17 +974,23 @@ class MpiTool(footprints.FootprintBase):
|
|
|
789
974
|
"""post-execution cleaning."""
|
|
790
975
|
if self.mpiwrapstd:
|
|
791
976
|
# Deal with standard output/error files
|
|
792
|
-
for outf in sorted(self.system.glob(
|
|
977
|
+
for outf in sorted(self.system.glob("vwrap_stdeo.*")):
|
|
793
978
|
rank = int(outf[12:])
|
|
794
|
-
with open(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
979
|
+
with open(
|
|
980
|
+
outf,
|
|
981
|
+
encoding=locale.getlocale()[1] or "ascii",
|
|
982
|
+
errors="replace",
|
|
983
|
+
) as sfh:
|
|
984
|
+
for i, l in enumerate(sfh):
|
|
798
985
|
if i == 0:
|
|
799
|
-
self.system.highlight(
|
|
800
|
-
|
|
986
|
+
self.system.highlight(
|
|
987
|
+
"rank {:d}: stdout/err".format(rank)
|
|
988
|
+
)
|
|
989
|
+
print(l.rstrip("\n"))
|
|
801
990
|
self.system.remove(outf)
|
|
802
|
-
if self.envelope and self.system.path.exists(
|
|
991
|
+
if self.envelope and self.system.path.exists(
|
|
992
|
+
self._envelope_wrapper_name
|
|
993
|
+
):
|
|
803
994
|
self.system.remove(self._envelope_wrapper_name)
|
|
804
995
|
if self.mpiwrapstd:
|
|
805
996
|
self.system.remove(self._wrapstd_wrapper_name)
|
|
@@ -809,16 +1000,24 @@ class MpiTool(footprints.FootprintBase):
|
|
|
809
1000
|
|
|
810
1001
|
def find_namelists(self, opts=None):
|
|
811
1002
|
"""Find any namelists candidates in actual context inputs."""
|
|
812
|
-
namcandidates = [
|
|
813
|
-
|
|
814
|
-
|
|
1003
|
+
namcandidates = [
|
|
1004
|
+
x.rh
|
|
1005
|
+
for x in self.context.sequence.effective_inputs(
|
|
1006
|
+
kind=("namelist", "namelistfp")
|
|
1007
|
+
)
|
|
1008
|
+
]
|
|
1009
|
+
if opts is not None and "loop" in opts:
|
|
815
1010
|
namcandidates = [
|
|
816
|
-
x
|
|
817
|
-
|
|
1011
|
+
x
|
|
1012
|
+
for x in namcandidates
|
|
1013
|
+
if (
|
|
1014
|
+
hasattr(x.resource, "term")
|
|
1015
|
+
and x.resource.term == opts["loop"]
|
|
1016
|
+
)
|
|
818
1017
|
]
|
|
819
1018
|
else:
|
|
820
|
-
logger.info(
|
|
821
|
-
self.system.highlight(
|
|
1019
|
+
logger.info("No loop option in current parallel execution.")
|
|
1020
|
+
self.system.highlight("Namelist candidates")
|
|
822
1021
|
for nam in namcandidates:
|
|
823
1022
|
nam.quickview()
|
|
824
1023
|
return namcandidates
|
|
@@ -831,18 +1030,30 @@ class MpiTool(footprints.FootprintBase):
|
|
|
831
1030
|
"""MPI information to be written in namelists."""
|
|
832
1031
|
for namrh in self.find_namelists(opts):
|
|
833
1032
|
namc = namrh.contents
|
|
834
|
-
changed = self.setup_namelist_delta(
|
|
1033
|
+
changed = self.setup_namelist_delta(
|
|
1034
|
+
namc, namrh.container.actualpath()
|
|
1035
|
+
)
|
|
835
1036
|
# Call the dedicated method en registered MPI binaries
|
|
836
1037
|
for bin_obj in self.binaries:
|
|
837
|
-
changed =
|
|
1038
|
+
changed = (
|
|
1039
|
+
bin_obj.setup_namelist_delta(
|
|
1040
|
+
namc, namrh.container.actualpath()
|
|
1041
|
+
)
|
|
1042
|
+
or changed
|
|
1043
|
+
)
|
|
838
1044
|
if changed:
|
|
839
1045
|
if namc.dumps_needs_update:
|
|
840
|
-
logger.info(
|
|
1046
|
+
logger.info(
|
|
1047
|
+
"Rewritting the %s namelists file.",
|
|
1048
|
+
namrh.container.actualpath(),
|
|
1049
|
+
)
|
|
841
1050
|
namc.rewrite(namrh.container)
|
|
842
1051
|
|
|
843
1052
|
def _logged_env_set(self, k, v):
|
|
844
1053
|
"""Set an environment variable *k* and emit a log message."""
|
|
845
|
-
logger.info(
|
|
1054
|
+
logger.info(
|
|
1055
|
+
'Setting the "%s" environment variable to "%s"', k.upper(), v
|
|
1056
|
+
)
|
|
846
1057
|
self.env[k] = v
|
|
847
1058
|
|
|
848
1059
|
def _logged_env_del(self, k):
|
|
@@ -867,8 +1078,11 @@ class MpiTool(footprints.FootprintBase):
|
|
|
867
1078
|
try:
|
|
868
1079
|
v = str(v).format(**envsub)
|
|
869
1080
|
except KeyError:
|
|
870
|
-
logger.warning(
|
|
871
|
-
|
|
1081
|
+
logger.warning(
|
|
1082
|
+
"Substitution failed for the environment "
|
|
1083
|
+
+ "variable %s. Ignoring it.",
|
|
1084
|
+
k,
|
|
1085
|
+
)
|
|
872
1086
|
else:
|
|
873
1087
|
self._logged_env_set(k, v)
|
|
874
1088
|
# Call the dedicated method en registered MPI binaries
|
|
@@ -885,56 +1099,60 @@ class MpiTool(footprints.FootprintBase):
|
|
|
885
1099
|
class MpiBinaryDescription(footprints.FootprintBase):
|
|
886
1100
|
"""Root class for any :class:`MpiBinaryDescription` subclass."""
|
|
887
1101
|
|
|
888
|
-
_collector = (
|
|
1102
|
+
_collector = ("mpibinary",)
|
|
889
1103
|
_abstract = True
|
|
890
1104
|
_footprint = dict(
|
|
891
|
-
info
|
|
892
|
-
attr
|
|
893
|
-
kind
|
|
894
|
-
info
|
|
895
|
-
values
|
|
1105
|
+
info="Holds information about a given MPI binary",
|
|
1106
|
+
attr=dict(
|
|
1107
|
+
kind=dict(
|
|
1108
|
+
info="A free form description of the binary's type",
|
|
1109
|
+
values=[
|
|
1110
|
+
"basic",
|
|
1111
|
+
],
|
|
1112
|
+
),
|
|
1113
|
+
nodes=dict(
|
|
1114
|
+
info="The number of nodes for this MPI binary",
|
|
1115
|
+
type=int,
|
|
1116
|
+
optional=True,
|
|
1117
|
+
access="rwx",
|
|
896
1118
|
),
|
|
897
|
-
|
|
898
|
-
info
|
|
899
|
-
type
|
|
900
|
-
optional
|
|
901
|
-
access
|
|
1119
|
+
tasks=dict(
|
|
1120
|
+
info="The number of tasks per node for this MPI binary",
|
|
1121
|
+
type=int,
|
|
1122
|
+
optional=True,
|
|
1123
|
+
access="rwx",
|
|
902
1124
|
),
|
|
903
|
-
|
|
904
|
-
info
|
|
905
|
-
type
|
|
906
|
-
optional
|
|
907
|
-
access
|
|
1125
|
+
openmp=dict(
|
|
1126
|
+
info="The number of threads per task for this MPI binary",
|
|
1127
|
+
type=int,
|
|
1128
|
+
optional=True,
|
|
1129
|
+
access="rwx",
|
|
908
1130
|
),
|
|
909
|
-
|
|
910
|
-
info
|
|
911
|
-
type
|
|
912
|
-
optional
|
|
913
|
-
access
|
|
1131
|
+
ranks=dict(
|
|
1132
|
+
info="The number of MPI ranks to use (only when working in an envelope)",
|
|
1133
|
+
type=int,
|
|
1134
|
+
optional=True,
|
|
1135
|
+
access="rwx",
|
|
914
1136
|
),
|
|
915
|
-
|
|
916
|
-
info
|
|
917
|
-
type
|
|
918
|
-
optional
|
|
919
|
-
|
|
1137
|
+
allowbind=dict(
|
|
1138
|
+
info="Allow the MpiTool to bind this executable",
|
|
1139
|
+
type=bool,
|
|
1140
|
+
optional=True,
|
|
1141
|
+
default=True,
|
|
920
1142
|
),
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1143
|
+
basics=dict(
|
|
1144
|
+
type=footprints.FPList,
|
|
1145
|
+
optional=True,
|
|
1146
|
+
default=footprints.FPList(
|
|
1147
|
+
["system", "env", "target", "context"]
|
|
1148
|
+
),
|
|
926
1149
|
),
|
|
927
|
-
|
|
928
|
-
type = footprints.FPList,
|
|
929
|
-
optional = True,
|
|
930
|
-
default = footprints.FPList(['system', 'env', 'target', 'context'])
|
|
931
|
-
)
|
|
932
|
-
)
|
|
1150
|
+
),
|
|
933
1151
|
)
|
|
934
1152
|
|
|
935
1153
|
def __init__(self, *args, **kw):
|
|
936
1154
|
"""After parent initialization, set master and options to undefined."""
|
|
937
|
-
logger.debug(
|
|
1155
|
+
logger.debug("Abstract mpi tool init %s", self.__class__)
|
|
938
1156
|
super().__init__(*args, **kw)
|
|
939
1157
|
self._master = None
|
|
940
1158
|
self._arguments = ()
|
|
@@ -944,16 +1162,18 @@ class MpiBinaryDescription(footprints.FootprintBase):
|
|
|
944
1162
|
def __getattr__(self, key):
|
|
945
1163
|
"""Have a look to basics values provided by some proxy."""
|
|
946
1164
|
if key in self.basics:
|
|
947
|
-
return getattr(self,
|
|
1165
|
+
return getattr(self, "_" + key)
|
|
948
1166
|
else:
|
|
949
|
-
raise AttributeError(
|
|
1167
|
+
raise AttributeError(
|
|
1168
|
+
"Attribute [%s] is not a basic mpitool attribute" % key
|
|
1169
|
+
)
|
|
950
1170
|
|
|
951
1171
|
def import_basics(self, obj, attrs=None):
|
|
952
1172
|
"""Import some current values such as system, env, target and context from provided ``obj``."""
|
|
953
1173
|
if attrs is None:
|
|
954
1174
|
attrs = self.basics
|
|
955
1175
|
for k in [x for x in attrs if x in self.basics and hasattr(obj, x)]:
|
|
956
|
-
setattr(self,
|
|
1176
|
+
setattr(self, "_" + k, getattr(obj, k))
|
|
957
1177
|
|
|
958
1178
|
def _get_options(self):
|
|
959
1179
|
"""Retrieve the current set of MPI options."""
|
|
@@ -967,25 +1187,25 @@ class MpiBinaryDescription(footprints.FootprintBase):
|
|
|
967
1187
|
if value is None:
|
|
968
1188
|
value = dict()
|
|
969
1189
|
if self.ranks is not None:
|
|
970
|
-
self._options[
|
|
1190
|
+
self._options["np"] = self.ranks
|
|
971
1191
|
if self.nodes is not None or self.tasks is not None:
|
|
972
|
-
raise ValueError(
|
|
1192
|
+
raise ValueError("Incompatible options provided.")
|
|
973
1193
|
else:
|
|
974
1194
|
if self.nodes is not None:
|
|
975
|
-
self._options[
|
|
1195
|
+
self._options["nn"] = self.nodes
|
|
976
1196
|
if self.tasks is not None:
|
|
977
|
-
self._options[
|
|
1197
|
+
self._options["nnp"] = self.tasks
|
|
978
1198
|
if self.openmp is not None:
|
|
979
|
-
self._options[
|
|
1199
|
+
self._options["openmp"] = self.openmp
|
|
980
1200
|
for k, v in value.items():
|
|
981
|
-
self._options[k.lstrip(
|
|
1201
|
+
self._options[k.lstrip("-").lower()] = v
|
|
982
1202
|
|
|
983
1203
|
options = property(_get_options, _set_options)
|
|
984
1204
|
|
|
985
1205
|
def expanded_options(self):
|
|
986
1206
|
"""The MPI options actually used by the :class:`MpiTool` object to generate the command line."""
|
|
987
1207
|
options = self.options.copy()
|
|
988
|
-
options.setdefault(
|
|
1208
|
+
options.setdefault("np", self.nprocs)
|
|
989
1209
|
return options
|
|
990
1210
|
|
|
991
1211
|
def _get_group(self):
|
|
@@ -1001,12 +1221,12 @@ class MpiBinaryDescription(footprints.FootprintBase):
|
|
|
1001
1221
|
@property
|
|
1002
1222
|
def nprocs(self):
|
|
1003
1223
|
"""Figure out what is the effective total number of tasks."""
|
|
1004
|
-
if
|
|
1005
|
-
nbproc = int(self.options[
|
|
1006
|
-
elif
|
|
1007
|
-
nbproc = int(self.options.get(
|
|
1224
|
+
if "np" in self.options:
|
|
1225
|
+
nbproc = int(self.options["np"])
|
|
1226
|
+
elif "nnp" in self.options and "nn" in self.options:
|
|
1227
|
+
nbproc = int(self.options.get("nnp")) * int(self.options.get("nn"))
|
|
1008
1228
|
else:
|
|
1009
|
-
raise MpiException(
|
|
1229
|
+
raise MpiException("Impossible to compute nprocs.")
|
|
1010
1230
|
return nbproc
|
|
1011
1231
|
|
|
1012
1232
|
def _get_master(self):
|
|
@@ -1030,7 +1250,7 @@ class MpiBinaryDescription(footprints.FootprintBase):
|
|
|
1030
1250
|
elif isinstance(args, collections.abc.Iterable):
|
|
1031
1251
|
self._arguments = [str(a) for a in args]
|
|
1032
1252
|
else:
|
|
1033
|
-
raise ValueError(
|
|
1253
|
+
raise ValueError("Improper *args* argument provided.")
|
|
1034
1254
|
|
|
1035
1255
|
arguments = property(_get_arguments, _set_arguments)
|
|
1036
1256
|
|
|
@@ -1051,9 +1271,11 @@ class MpiEnvelopeBit(MpiBinaryDescription):
|
|
|
1051
1271
|
"""Set NPROC and NBPROC in namelists given the MPI distribution."""
|
|
1052
1272
|
|
|
1053
1273
|
_footprint = dict(
|
|
1054
|
-
attr
|
|
1055
|
-
kind
|
|
1056
|
-
values
|
|
1274
|
+
attr=dict(
|
|
1275
|
+
kind=dict(
|
|
1276
|
+
values=[
|
|
1277
|
+
"basicenvelopebit",
|
|
1278
|
+
],
|
|
1057
1279
|
),
|
|
1058
1280
|
)
|
|
1059
1281
|
)
|
|
@@ -1061,10 +1283,10 @@ class MpiEnvelopeBit(MpiBinaryDescription):
|
|
|
1061
1283
|
|
|
1062
1284
|
class MpiBinary(MpiBinaryDescription):
|
|
1063
1285
|
_footprint = dict(
|
|
1064
|
-
attr
|
|
1286
|
+
attr=dict(
|
|
1065
1287
|
distribution=dict(
|
|
1066
1288
|
info="Describes how the various nodes are distributed accross nodes",
|
|
1067
|
-
values=[
|
|
1289
|
+
values=["continuous", "roundrobin"],
|
|
1068
1290
|
optional=True,
|
|
1069
1291
|
),
|
|
1070
1292
|
)
|
|
@@ -1075,9 +1297,11 @@ class MpiBinaryBasic(MpiBinary):
|
|
|
1075
1297
|
"""Set NPROC and NBPROC in namelists given the MPI distribution."""
|
|
1076
1298
|
|
|
1077
1299
|
_footprint = dict(
|
|
1078
|
-
attr
|
|
1079
|
-
kind
|
|
1080
|
-
values
|
|
1300
|
+
attr=dict(
|
|
1301
|
+
kind=dict(
|
|
1302
|
+
values=[
|
|
1303
|
+
"basicsingle",
|
|
1304
|
+
],
|
|
1081
1305
|
),
|
|
1082
1306
|
)
|
|
1083
1307
|
)
|
|
@@ -1090,10 +1314,12 @@ class MpiBinaryBasic(MpiBinary):
|
|
|
1090
1314
|
for nam_block in namcontents.values():
|
|
1091
1315
|
nam_macros.update(nam_block.macros())
|
|
1092
1316
|
# Look for relevant once
|
|
1093
|
-
nprocs_macros = (
|
|
1317
|
+
nprocs_macros = ("NPROC", "NBPROC", "NTASKS")
|
|
1094
1318
|
if any([n in nam_macros for n in nprocs_macros]):
|
|
1095
1319
|
for n in nprocs_macros:
|
|
1096
|
-
logger.info(
|
|
1320
|
+
logger.info(
|
|
1321
|
+
"Setup macro %s=%s in %s", n, self.nprocs, namlocal
|
|
1322
|
+
)
|
|
1097
1323
|
namcontents.setmacro(n, self.nprocs)
|
|
1098
1324
|
namw = True
|
|
1099
1325
|
return namw
|
|
@@ -1103,16 +1329,18 @@ class MpiBinaryIOServer(MpiBinary):
|
|
|
1103
1329
|
"""Standard binary description for IO Server binaries."""
|
|
1104
1330
|
|
|
1105
1331
|
_footprint = dict(
|
|
1106
|
-
attr
|
|
1107
|
-
kind
|
|
1108
|
-
values
|
|
1332
|
+
attr=dict(
|
|
1333
|
+
kind=dict(
|
|
1334
|
+
values=[
|
|
1335
|
+
"ioserv",
|
|
1336
|
+
],
|
|
1109
1337
|
),
|
|
1110
1338
|
)
|
|
1111
1339
|
)
|
|
1112
1340
|
|
|
1113
1341
|
def __init__(self, *args, **kw):
|
|
1114
1342
|
"""After parent initialization, set launcher value."""
|
|
1115
|
-
logger.debug(
|
|
1343
|
+
logger.debug("Abstract mpi tool init %s", self.__class__)
|
|
1116
1344
|
super().__init__(*args, **kw)
|
|
1117
1345
|
thisenv = env.current()
|
|
1118
1346
|
if self.ranks is None:
|
|
@@ -1136,28 +1364,22 @@ class MpiRun(MpiTool):
|
|
|
1136
1364
|
"""Standard MPI launcher on most systems: `mpirun`."""
|
|
1137
1365
|
|
|
1138
1366
|
_footprint = dict(
|
|
1139
|
-
attr
|
|
1140
|
-
sysname
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
values = ['mpirun', 'mpiperso', 'default'],
|
|
1145
|
-
remap = dict(
|
|
1146
|
-
default = 'mpirun'
|
|
1147
|
-
),
|
|
1367
|
+
attr=dict(
|
|
1368
|
+
sysname=dict(values=["Linux", "Darwin", "UnitTestLinux"]),
|
|
1369
|
+
mpiname=dict(
|
|
1370
|
+
values=["mpirun", "mpiperso", "default"],
|
|
1371
|
+
remap=dict(default="mpirun"),
|
|
1148
1372
|
),
|
|
1149
|
-
optsep
|
|
1150
|
-
default
|
|
1373
|
+
optsep=dict(
|
|
1374
|
+
default="",
|
|
1151
1375
|
),
|
|
1152
|
-
optprefix
|
|
1153
|
-
default
|
|
1376
|
+
optprefix=dict(
|
|
1377
|
+
default="-",
|
|
1154
1378
|
),
|
|
1155
|
-
optmap
|
|
1156
|
-
|
|
1379
|
+
optmap=dict(default=footprints.FPDict(np="np", nnp="npernode")),
|
|
1380
|
+
binsep=dict(
|
|
1381
|
+
default=":",
|
|
1157
1382
|
),
|
|
1158
|
-
binsep = dict(
|
|
1159
|
-
default = ':',
|
|
1160
|
-
)
|
|
1161
1383
|
)
|
|
1162
1384
|
)
|
|
1163
1385
|
|
|
@@ -1166,50 +1388,51 @@ class SRun(MpiTool):
|
|
|
1166
1388
|
"""SLURM's srun launcher."""
|
|
1167
1389
|
|
|
1168
1390
|
_footprint = dict(
|
|
1169
|
-
attr
|
|
1170
|
-
sysname
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1391
|
+
attr=dict(
|
|
1392
|
+
sysname=dict(values=["Linux", "UnitTestLinux"]),
|
|
1393
|
+
mpiname=dict(
|
|
1394
|
+
values=[
|
|
1395
|
+
"srun",
|
|
1396
|
+
],
|
|
1175
1397
|
),
|
|
1176
|
-
optsep
|
|
1177
|
-
default
|
|
1398
|
+
optsep=dict(
|
|
1399
|
+
default="",
|
|
1178
1400
|
),
|
|
1179
|
-
optprefix
|
|
1180
|
-
default
|
|
1401
|
+
optprefix=dict(
|
|
1402
|
+
default="--",
|
|
1181
1403
|
),
|
|
1182
|
-
optmap
|
|
1183
|
-
default
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
type = int,
|
|
1187
|
-
optional = True
|
|
1404
|
+
optmap=dict(
|
|
1405
|
+
default=footprints.FPDict(
|
|
1406
|
+
nn="nodes", nnp="ntasks-per-node", np="ntasks"
|
|
1407
|
+
)
|
|
1188
1408
|
),
|
|
1189
|
-
|
|
1190
|
-
|
|
1409
|
+
slurmversion=dict(type=int, optional=True),
|
|
1410
|
+
mpiwrapstd=dict(
|
|
1411
|
+
default=True,
|
|
1191
1412
|
),
|
|
1192
|
-
bindingmethod
|
|
1193
|
-
info
|
|
1194
|
-
values
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1413
|
+
bindingmethod=dict(
|
|
1414
|
+
info="How to bind the MPI processes",
|
|
1415
|
+
values=[
|
|
1416
|
+
"native",
|
|
1417
|
+
"vortex",
|
|
1418
|
+
],
|
|
1419
|
+
access="rwx",
|
|
1420
|
+
optional=True,
|
|
1421
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
1422
|
+
doc_zorder=-90,
|
|
1199
1423
|
),
|
|
1200
1424
|
)
|
|
1201
1425
|
)
|
|
1202
1426
|
|
|
1203
|
-
_envelope_nodelist_name =
|
|
1204
|
-
_envelope_rank_var =
|
|
1427
|
+
_envelope_nodelist_name = "./global_envelope_nodelist"
|
|
1428
|
+
_envelope_rank_var = "SLURM_PROCID"
|
|
1205
1429
|
_supports_manual_ranks_mapping = True
|
|
1206
1430
|
|
|
1207
1431
|
@property
|
|
1208
1432
|
def _actual_slurmversion(self):
|
|
1209
1433
|
"""Return the slurm major version number."""
|
|
1210
|
-
slurmversion = (
|
|
1211
|
-
|
|
1212
|
-
from_config(section="mpitool", key="slurmversion")
|
|
1434
|
+
slurmversion = self.slurmversion or from_config(
|
|
1435
|
+
section="mpitool", key="slurmversion"
|
|
1213
1436
|
)
|
|
1214
1437
|
if not slurmversion:
|
|
1215
1438
|
raise ValueError("No slurm version specified")
|
|
@@ -1217,65 +1440,94 @@ class SRun(MpiTool):
|
|
|
1217
1440
|
|
|
1218
1441
|
def _set_binaries_hack(self, binaries):
|
|
1219
1442
|
"""Set the list of :class:`MpiBinaryDescription` objects associated with this instance."""
|
|
1220
|
-
if
|
|
1443
|
+
if (
|
|
1444
|
+
not self.envelope
|
|
1445
|
+
and len(
|
|
1446
|
+
[binary for binary in binaries if binary.expanded_options()]
|
|
1447
|
+
)
|
|
1448
|
+
> 1
|
|
1449
|
+
):
|
|
1221
1450
|
self._set_envelope_from_binaries()
|
|
1222
1451
|
|
|
1223
1452
|
def _valid_envelope(self, value):
|
|
1224
1453
|
"""Tweak the envelope ddescription values."""
|
|
1225
1454
|
for e in value:
|
|
1226
|
-
if not (
|
|
1227
|
-
raise MpiException(
|
|
1455
|
+
if not ("nn" in e and "nnp" in e):
|
|
1456
|
+
raise MpiException(
|
|
1457
|
+
"Srun needs a nn/nnp specification to build the envelope."
|
|
1458
|
+
)
|
|
1228
1459
|
|
|
1229
1460
|
def _set_envelope(self, value):
|
|
1230
1461
|
"""Set the envelope description."""
|
|
1231
1462
|
super()._set_envelope(value)
|
|
1232
|
-
if len(self._envelope) > 1 and self.bindingmethod not in (
|
|
1463
|
+
if len(self._envelope) > 1 and self.bindingmethod not in (
|
|
1464
|
+
None,
|
|
1465
|
+
"vortex",
|
|
1466
|
+
):
|
|
1233
1467
|
logger.warning("Resetting the binding method to 'Vortex'.")
|
|
1234
|
-
self.bindingmethod =
|
|
1468
|
+
self.bindingmethod = "vortex"
|
|
1235
1469
|
|
|
1236
1470
|
envelope = property(MpiTool._get_envelope, _set_envelope)
|
|
1237
1471
|
|
|
1238
1472
|
def _set_binaries_envelope_hack(self, binaries):
|
|
1239
1473
|
"""Tweak the envelope after binaries were setup."""
|
|
1240
|
-
if self.bindingmethod not in (None,
|
|
1241
|
-
openmps = {b.options.get(
|
|
1474
|
+
if self.bindingmethod not in (None, "vortex"):
|
|
1475
|
+
openmps = {b.options.get("openmp", None) for b in binaries}
|
|
1242
1476
|
if len(openmps) > 1:
|
|
1243
|
-
logger.warning(
|
|
1244
|
-
|
|
1245
|
-
|
|
1477
|
+
logger.warning(
|
|
1478
|
+
"Resetting the binding method to 'Vortex' because "
|
|
1479
|
+
+ "the number of threads is not uniform."
|
|
1480
|
+
)
|
|
1481
|
+
self.bindingmethod = "vortex"
|
|
1246
1482
|
|
|
1247
1483
|
@property
|
|
1248
1484
|
def _cpubind_opt(self):
|
|
1249
|
-
return self.optprefix + (
|
|
1250
|
-
|
|
1485
|
+
return self.optprefix + (
|
|
1486
|
+
"cpu_bind" if self._actual_slurmversion < 18 else "cpu-bind"
|
|
1487
|
+
)
|
|
1251
1488
|
|
|
1252
1489
|
def _build_cpumask(self, cmdl, what, bsize):
|
|
1253
1490
|
"""Add a --cpu-bind option if needed."""
|
|
1254
1491
|
cmdl.append(self._cpubind_opt)
|
|
1255
|
-
if self.bindingmethod ==
|
|
1492
|
+
if self.bindingmethod == "native":
|
|
1256
1493
|
assert len(what) == 1, "Only one item is allowed."
|
|
1257
1494
|
if what[0].allowbind:
|
|
1258
|
-
ids = self.system.cpus_ids_per_blocks(
|
|
1259
|
-
|
|
1260
|
-
|
|
1495
|
+
ids = self.system.cpus_ids_per_blocks(
|
|
1496
|
+
blocksize=bsize,
|
|
1497
|
+
topology=self.mpibind_topology,
|
|
1498
|
+
hexmask=True,
|
|
1499
|
+
)
|
|
1261
1500
|
if not ids:
|
|
1262
|
-
raise MpiException(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1501
|
+
raise MpiException(
|
|
1502
|
+
"Unable to detect the CPU layout with topology: {:s}".format(
|
|
1503
|
+
self.mpibind_topology,
|
|
1504
|
+
)
|
|
1505
|
+
)
|
|
1506
|
+
masklist = [
|
|
1507
|
+
m
|
|
1508
|
+
for _, m in zip(
|
|
1509
|
+
range(what[0].options["nnp"]), itertools.cycle(ids)
|
|
1510
|
+
)
|
|
1511
|
+
]
|
|
1512
|
+
cmdl.append("mask_cpu:" + ",".join(masklist))
|
|
1267
1513
|
else:
|
|
1268
|
-
cmdl.append(
|
|
1514
|
+
cmdl.append("none")
|
|
1269
1515
|
else:
|
|
1270
|
-
cmdl.append(
|
|
1516
|
+
cmdl.append("none")
|
|
1271
1517
|
|
|
1272
1518
|
def _simple_mkcmdline(self, cmdl):
|
|
1273
1519
|
"""Builds the MPI command line when no envelope is used.
|
|
1274
1520
|
|
|
1275
1521
|
:param list[str] cmdl: the command line as a list
|
|
1276
1522
|
"""
|
|
1277
|
-
target_bins = [
|
|
1278
|
-
|
|
1523
|
+
target_bins = [
|
|
1524
|
+
binary
|
|
1525
|
+
for binary in self.binaries
|
|
1526
|
+
if len(binary.expanded_options())
|
|
1527
|
+
]
|
|
1528
|
+
self._build_cpumask(
|
|
1529
|
+
cmdl, target_bins, target_bins[0].options.get("openmp", 1)
|
|
1530
|
+
)
|
|
1279
1531
|
super()._simple_mkcmdline(cmdl)
|
|
1280
1532
|
|
|
1281
1533
|
def _envelope_mkcmdline(self, cmdl):
|
|
@@ -1284,9 +1536,12 @@ class SRun(MpiTool):
|
|
|
1284
1536
|
:param list[str] cmdl: the command line as a list
|
|
1285
1537
|
"""
|
|
1286
1538
|
# Simple case, only one envelope description
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1539
|
+
openmps = {b.options.get("openmp", 1) for b in self.binaries}
|
|
1540
|
+
if (
|
|
1541
|
+
len(self.envelope) == 1
|
|
1542
|
+
and not self._complex_ranks_mapping
|
|
1543
|
+
and len(openmps) == 1
|
|
1544
|
+
):
|
|
1290
1545
|
self._build_cpumask(cmdl, self.envelope, openmps.pop())
|
|
1291
1546
|
super()._envelope_mkcmdline(cmdl)
|
|
1292
1547
|
# Multiple entries... use the nodelist stuff :-(
|
|
@@ -1295,15 +1550,24 @@ class SRun(MpiTool):
|
|
|
1295
1550
|
base_nodelist = []
|
|
1296
1551
|
totalnodes = 0
|
|
1297
1552
|
totaltasks = 0
|
|
1298
|
-
availnodes = itertools.cycle(
|
|
1299
|
-
|
|
1300
|
-
|
|
1553
|
+
availnodes = itertools.cycle(
|
|
1554
|
+
xlist_strings(
|
|
1555
|
+
self.env.SLURM_NODELIST
|
|
1556
|
+
if self._actual_slurmversion < 18
|
|
1557
|
+
else self.env.SLURM_JOB_NODELIST
|
|
1558
|
+
)
|
|
1559
|
+
)
|
|
1301
1560
|
for e_bit in self.envelope:
|
|
1302
1561
|
totaltasks += e_bit.nprocs
|
|
1303
|
-
for _ in range(e_bit.options[
|
|
1562
|
+
for _ in range(e_bit.options["nn"]):
|
|
1304
1563
|
availnode = next(availnodes)
|
|
1305
|
-
logger.debug(
|
|
1306
|
-
base_nodelist.extend(
|
|
1564
|
+
logger.debug("Node #%5d is: %s", totalnodes, availnode)
|
|
1565
|
+
base_nodelist.extend(
|
|
1566
|
+
[
|
|
1567
|
+
availnode,
|
|
1568
|
+
]
|
|
1569
|
+
* e_bit.options["nnp"]
|
|
1570
|
+
)
|
|
1307
1571
|
totalnodes += 1
|
|
1308
1572
|
# Re-order the nodelist based on the binary groups
|
|
1309
1573
|
nodelist = list()
|
|
@@ -1313,20 +1577,20 @@ class SRun(MpiTool):
|
|
|
1313
1577
|
else:
|
|
1314
1578
|
nodelist.append(base_nodelist[i_rank])
|
|
1315
1579
|
# Write it to the nodefile
|
|
1316
|
-
with open(self._envelope_nodelist_name,
|
|
1580
|
+
with open(self._envelope_nodelist_name, "w") as fhnl:
|
|
1317
1581
|
fhnl.write("\n".join(nodelist))
|
|
1318
1582
|
# Generate wrappers
|
|
1319
1583
|
self._envelope_mkwrapper(cmdl)
|
|
1320
1584
|
wrapstd = self._wrapstd_mkwrapper()
|
|
1321
1585
|
# Update the command line
|
|
1322
|
-
cmdl.append(self.optprefix +
|
|
1586
|
+
cmdl.append(self.optprefix + "nodelist")
|
|
1323
1587
|
cmdl.append(self._envelope_nodelist_name)
|
|
1324
|
-
cmdl.append(self.optprefix +
|
|
1588
|
+
cmdl.append(self.optprefix + "ntasks")
|
|
1325
1589
|
cmdl.append(str(totaltasks))
|
|
1326
|
-
cmdl.append(self.optprefix +
|
|
1327
|
-
cmdl.append(
|
|
1590
|
+
cmdl.append(self.optprefix + "distribution")
|
|
1591
|
+
cmdl.append("arbitrary")
|
|
1328
1592
|
cmdl.append(self._cpubind_opt)
|
|
1329
|
-
cmdl.append(
|
|
1593
|
+
cmdl.append("none")
|
|
1330
1594
|
if wrapstd:
|
|
1331
1595
|
cmdl.append(wrapstd)
|
|
1332
1596
|
cmdl.append(e_bit.master)
|
|
@@ -1346,70 +1610,82 @@ class SRun(MpiTool):
|
|
|
1346
1610
|
if not shp.exists(self.launcher):
|
|
1347
1611
|
actlauncher = self.system.which(actlauncher)
|
|
1348
1612
|
if not actlauncher:
|
|
1349
|
-
logger.error(
|
|
1613
|
+
logger.error("The SRun launcher could not be found.")
|
|
1350
1614
|
return sdict
|
|
1351
|
-
sdict[
|
|
1615
|
+
sdict["srunpath"] = actlauncher
|
|
1352
1616
|
# Detect the path to the PMI library
|
|
1353
|
-
pmilib = shp.normpath(
|
|
1354
|
-
|
|
1617
|
+
pmilib = shp.normpath(
|
|
1618
|
+
shp.join(shp.dirname(actlauncher), "..", "lib64", "libpmi.so")
|
|
1619
|
+
)
|
|
1355
1620
|
if not shp.exists(pmilib):
|
|
1356
|
-
pmilib = shp.normpath(
|
|
1357
|
-
|
|
1621
|
+
pmilib = shp.normpath(
|
|
1622
|
+
shp.join(shp.dirname(actlauncher), "..", "lib", "libpmi.so")
|
|
1623
|
+
)
|
|
1358
1624
|
if not shp.exists(pmilib):
|
|
1359
|
-
logger.error(
|
|
1625
|
+
logger.error("Could not find a PMI library")
|
|
1360
1626
|
return sdict
|
|
1361
|
-
sdict[
|
|
1627
|
+
sdict["pmilib"] = pmilib
|
|
1362
1628
|
return sdict
|
|
1363
1629
|
|
|
1364
1630
|
def setup_environment(self, opts):
|
|
1365
1631
|
"""Tweak the environment with some srun specific settings."""
|
|
1366
1632
|
super().setup_environment(opts)
|
|
1367
|
-
if (
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1633
|
+
if (
|
|
1634
|
+
self._complex_ranks_mapping
|
|
1635
|
+
and self._mpilib_identification()
|
|
1636
|
+
and self._mpilib_identification()[3] == "intelmpi"
|
|
1637
|
+
):
|
|
1638
|
+
logger.info(
|
|
1639
|
+
"(Sadly) with IntelMPI, I_MPI_SLURM_EXT=0 is needed when a complex arbitrary"
|
|
1640
|
+
+ "ranks distribution is used. Exporting it !"
|
|
1641
|
+
)
|
|
1642
|
+
self.env["I_MPI_SLURM_EXT"] = 0
|
|
1373
1643
|
if len(self.binaries) == 1 and not self.envelope:
|
|
1374
|
-
omp = self.binaries[0].options.get(
|
|
1644
|
+
omp = self.binaries[0].options.get("openmp", None)
|
|
1375
1645
|
if omp is not None:
|
|
1376
|
-
self._logged_env_set(
|
|
1377
|
-
if self.bindingmethod ==
|
|
1378
|
-
self._logged_env_set(
|
|
1646
|
+
self._logged_env_set("OMP_NUM_THREADS", omp)
|
|
1647
|
+
if self.bindingmethod == "native" and "OMP_PROC_BIND" not in self.env:
|
|
1648
|
+
self._logged_env_set("OMP_PROC_BIND", "true")
|
|
1379
1649
|
# cleaning unwanted environment stuff
|
|
1380
1650
|
unwanted = set()
|
|
1381
1651
|
for k in self.env:
|
|
1382
|
-
if k.startswith(
|
|
1652
|
+
if k.startswith("SLURM_"):
|
|
1383
1653
|
k = k[6:]
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1386
|
-
|
|
1654
|
+
if (
|
|
1655
|
+
k in ("NTASKS", "NPROCS")
|
|
1656
|
+
or re.match("N?TASKS_PER_", k)
|
|
1657
|
+
or re.match("N?CPUS_PER_", k)
|
|
1658
|
+
):
|
|
1387
1659
|
unwanted.add(k)
|
|
1388
1660
|
for k in unwanted:
|
|
1389
|
-
self.env.delvar(
|
|
1661
|
+
self.env.delvar("SLURM_{:s}".format(k))
|
|
1390
1662
|
|
|
1391
1663
|
|
|
1392
1664
|
class SRunDDT(SRun):
|
|
1393
1665
|
"""SLURM's srun launcher with ARM's DDT."""
|
|
1394
1666
|
|
|
1395
1667
|
_footprint = dict(
|
|
1396
|
-
attr
|
|
1397
|
-
mpiname
|
|
1398
|
-
values
|
|
1668
|
+
attr=dict(
|
|
1669
|
+
mpiname=dict(
|
|
1670
|
+
values=[
|
|
1671
|
+
"srun-ddt",
|
|
1672
|
+
],
|
|
1399
1673
|
),
|
|
1400
1674
|
)
|
|
1401
1675
|
)
|
|
1402
1676
|
|
|
1403
|
-
_conf_suffix =
|
|
1677
|
+
_conf_suffix = "-ddt"
|
|
1404
1678
|
|
|
1405
1679
|
def mkcmdline(self):
|
|
1406
1680
|
"""Add the DDT prefix command to the command line"""
|
|
1407
1681
|
cmdl = super().mkcmdline()
|
|
1408
1682
|
armtool = ArmForgeTool(self.ticket)
|
|
1409
|
-
for extra_c in reversed(
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1683
|
+
for extra_c in reversed(
|
|
1684
|
+
armtool.ddt_prefix_cmd(
|
|
1685
|
+
sources=self.sources,
|
|
1686
|
+
workdir=self.system.path.dirname(self.binaries[0].master),
|
|
1687
|
+
)
|
|
1688
|
+
):
|
|
1413
1689
|
cmdl.insert(0, extra_c)
|
|
1414
1690
|
return cmdl
|
|
1415
1691
|
|
|
@@ -1418,45 +1694,48 @@ class OmpiMpiRun(MpiTool):
|
|
|
1418
1694
|
"""OpenMPI's mpirun launcher."""
|
|
1419
1695
|
|
|
1420
1696
|
_footprint = dict(
|
|
1421
|
-
attr
|
|
1422
|
-
sysname
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1697
|
+
attr=dict(
|
|
1698
|
+
sysname=dict(values=["Linux", "UnitTestLinux"]),
|
|
1699
|
+
mpiname=dict(
|
|
1700
|
+
values=[
|
|
1701
|
+
"openmpi",
|
|
1702
|
+
],
|
|
1427
1703
|
),
|
|
1428
|
-
optsep
|
|
1429
|
-
default
|
|
1704
|
+
optsep=dict(
|
|
1705
|
+
default="",
|
|
1430
1706
|
),
|
|
1431
|
-
optprefix
|
|
1432
|
-
default
|
|
1707
|
+
optprefix=dict(
|
|
1708
|
+
default="-",
|
|
1433
1709
|
),
|
|
1434
|
-
optmap
|
|
1435
|
-
default
|
|
1710
|
+
optmap=dict(
|
|
1711
|
+
default=footprints.FPDict(np="np", nnp="npernode", xopenmp="x")
|
|
1436
1712
|
),
|
|
1437
|
-
binsep
|
|
1438
|
-
default
|
|
1713
|
+
binsep=dict(
|
|
1714
|
+
default=":",
|
|
1439
1715
|
),
|
|
1440
|
-
mpiwrapstd
|
|
1441
|
-
default
|
|
1716
|
+
mpiwrapstd=dict(
|
|
1717
|
+
default=True,
|
|
1442
1718
|
),
|
|
1443
|
-
bindingmethod
|
|
1444
|
-
info
|
|
1445
|
-
values
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1719
|
+
bindingmethod=dict(
|
|
1720
|
+
info="How to bind the MPI processes",
|
|
1721
|
+
values=[
|
|
1722
|
+
"native",
|
|
1723
|
+
"vortex",
|
|
1724
|
+
],
|
|
1725
|
+
optional=True,
|
|
1726
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
1727
|
+
doc_zorder=-90,
|
|
1449
1728
|
),
|
|
1450
|
-
preexistingenv
|
|
1451
|
-
optional
|
|
1452
|
-
type
|
|
1453
|
-
default
|
|
1729
|
+
preexistingenv=dict(
|
|
1730
|
+
optional=True,
|
|
1731
|
+
type=bool,
|
|
1732
|
+
default=False,
|
|
1454
1733
|
),
|
|
1455
1734
|
)
|
|
1456
1735
|
)
|
|
1457
1736
|
|
|
1458
|
-
_envelope_rankfile_name =
|
|
1459
|
-
_envelope_rank_var =
|
|
1737
|
+
_envelope_rankfile_name = "./global_envelope_rankfile"
|
|
1738
|
+
_envelope_rank_var = "OMPI_COMM_WORLD_RANK"
|
|
1460
1739
|
_supports_manual_ranks_mapping = True
|
|
1461
1740
|
|
|
1462
1741
|
def _get_launcher(self):
|
|
@@ -1466,27 +1745,29 @@ class OmpiMpiRun(MpiTool):
|
|
|
1466
1745
|
else:
|
|
1467
1746
|
mpi_data = self._mpilib_data()
|
|
1468
1747
|
if mpi_data:
|
|
1469
|
-
return self.system.path.join(mpi_data[1],
|
|
1748
|
+
return self.system.path.join(mpi_data[1], "mpirun")
|
|
1470
1749
|
else:
|
|
1471
1750
|
return self._launcher
|
|
1472
1751
|
|
|
1473
1752
|
launcher = property(_get_launcher, MpiTool._set_launcher)
|
|
1474
1753
|
|
|
1475
1754
|
def _set_binaries_hack(self, binaries):
|
|
1476
|
-
if not self.envelope and self.bindingmethod ==
|
|
1755
|
+
if not self.envelope and self.bindingmethod == "native":
|
|
1477
1756
|
self._set_envelope_from_binaries()
|
|
1478
1757
|
|
|
1479
1758
|
def _valid_envelope(self, value):
|
|
1480
1759
|
"""Tweak the envelope description values."""
|
|
1481
1760
|
for e in value:
|
|
1482
|
-
if not (
|
|
1483
|
-
raise MpiException(
|
|
1484
|
-
|
|
1761
|
+
if not ("nn" in e and "nnp" in e):
|
|
1762
|
+
raise MpiException(
|
|
1763
|
+
"OpenMPI/mpirun needs a nn/nnp specification "
|
|
1764
|
+
+ "to build the envelope."
|
|
1765
|
+
)
|
|
1485
1766
|
|
|
1486
1767
|
def _hook_binary_mpiopts(self, binary, options):
|
|
1487
|
-
openmp = options.pop(
|
|
1768
|
+
openmp = options.pop("openmp", None)
|
|
1488
1769
|
if openmp is not None:
|
|
1489
|
-
options[
|
|
1770
|
+
options["xopenmp"] = "OMP_NUM_THREADS={:d}".format(openmp)
|
|
1490
1771
|
return options
|
|
1491
1772
|
|
|
1492
1773
|
def _simple_mkcmdline(self, cmdl):
|
|
@@ -1495,7 +1776,9 @@ class OmpiMpiRun(MpiTool):
|
|
|
1495
1776
|
:param list[str] cmdl: the command line as a list
|
|
1496
1777
|
"""
|
|
1497
1778
|
if self.bindingmethod is not None:
|
|
1498
|
-
raise RuntimeError(
|
|
1779
|
+
raise RuntimeError(
|
|
1780
|
+
"If bindingmethod is set, an enveloppe should allways be used."
|
|
1781
|
+
)
|
|
1499
1782
|
super()._simple_mkcmdline(cmdl)
|
|
1500
1783
|
|
|
1501
1784
|
def _create_rankfile(self, rankslist, nodeslist, slotslist):
|
|
@@ -1503,9 +1786,9 @@ class OmpiMpiRun(MpiTool):
|
|
|
1503
1786
|
|
|
1504
1787
|
def _dump_slot_string(slot_strings, s_start, s_end):
|
|
1505
1788
|
if s_start == s_end:
|
|
1506
|
-
slot_strings.append(
|
|
1789
|
+
slot_strings.append("{:d}".format(s_start))
|
|
1507
1790
|
else:
|
|
1508
|
-
slot_strings.append(
|
|
1791
|
+
slot_strings.append("{:d}-{:d}".format(s_start, s_end))
|
|
1509
1792
|
|
|
1510
1793
|
for rank, node, slot in zip(rankslist, nodeslist, slotslist):
|
|
1511
1794
|
slot_strings = list()
|
|
@@ -1520,20 +1803,26 @@ class OmpiMpiRun(MpiTool):
|
|
|
1520
1803
|
s_end = s_start = s
|
|
1521
1804
|
_dump_slot_string(slot_strings, s_start, s_end)
|
|
1522
1805
|
rf_strings.append(
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1806
|
+
"rank {:d}={:s} slot={:s}".format(
|
|
1807
|
+
rank, node, ",".join(slot_strings)
|
|
1808
|
+
)
|
|
1526
1809
|
)
|
|
1527
|
-
logger.info(
|
|
1528
|
-
if self.preexistingenv and self.system.path.exists(
|
|
1529
|
-
|
|
1810
|
+
logger.info("self.preexistingenv = {}".format(self.preexistingenv))
|
|
1811
|
+
if self.preexistingenv and self.system.path.exists(
|
|
1812
|
+
self._envelope_rankfile_name
|
|
1813
|
+
):
|
|
1814
|
+
logger.info("envelope file found in the directory")
|
|
1530
1815
|
else:
|
|
1531
1816
|
if self.preexistingenv:
|
|
1532
|
-
logger.info(
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1817
|
+
logger.info(
|
|
1818
|
+
"preexistingenv set to true, but no envelope file found"
|
|
1819
|
+
)
|
|
1820
|
+
logger.info("Using vortex computed one")
|
|
1821
|
+
logger.debug(
|
|
1822
|
+
"Here is the rankfile content:\n%s", "\n".join(rf_strings)
|
|
1823
|
+
)
|
|
1824
|
+
with open(self._envelope_rankfile_name, mode="w") as tmp_rf:
|
|
1825
|
+
tmp_rf.write("\n".join(rf_strings))
|
|
1537
1826
|
return self._envelope_rankfile_name
|
|
1538
1827
|
|
|
1539
1828
|
def _envelope_nodelist(self):
|
|
@@ -1541,10 +1830,14 @@ class OmpiMpiRun(MpiTool):
|
|
|
1541
1830
|
base_nodelist = []
|
|
1542
1831
|
totalnodes = 0
|
|
1543
1832
|
for e_bit in self.envelope:
|
|
1544
|
-
for i_node in range(e_bit.options[
|
|
1545
|
-
logger.debug(
|
|
1546
|
-
base_nodelist.extend(
|
|
1547
|
-
|
|
1833
|
+
for i_node in range(e_bit.options["nn"]):
|
|
1834
|
+
logger.debug("Node #%5d is: +n%d", i_node, totalnodes)
|
|
1835
|
+
base_nodelist.extend(
|
|
1836
|
+
[
|
|
1837
|
+
"+n{:d}".format(totalnodes),
|
|
1838
|
+
]
|
|
1839
|
+
* e_bit.options["nnp"]
|
|
1840
|
+
)
|
|
1548
1841
|
totalnodes += 1
|
|
1549
1842
|
return base_nodelist
|
|
1550
1843
|
|
|
@@ -1553,12 +1846,14 @@ class OmpiMpiRun(MpiTool):
|
|
|
1553
1846
|
|
|
1554
1847
|
:param list[str] args: the command line as a list
|
|
1555
1848
|
"""
|
|
1556
|
-
cmdl.append(self.optprefix +
|
|
1557
|
-
if self.bindingmethod in (None,
|
|
1849
|
+
cmdl.append(self.optprefix + "oversubscribe")
|
|
1850
|
+
if self.bindingmethod in (None, "native"):
|
|
1558
1851
|
# Generate the dictionary that associate rank numbers and programs
|
|
1559
1852
|
todostack, ranks_bsize = self._envelope_mkwrapper_todostack()
|
|
1560
1853
|
# Generate the binding stuff
|
|
1561
|
-
bindingstack, _ = self._envelope_mkwrapper_bindingstack(
|
|
1854
|
+
bindingstack, _ = self._envelope_mkwrapper_bindingstack(
|
|
1855
|
+
ranks_bsize
|
|
1856
|
+
)
|
|
1562
1857
|
# Generate a relative nodelist
|
|
1563
1858
|
base_nodelist = self._envelope_nodelist()
|
|
1564
1859
|
# Generate the rankfile
|
|
@@ -1567,21 +1862,23 @@ class OmpiMpiRun(MpiTool):
|
|
|
1567
1862
|
if bindingstack:
|
|
1568
1863
|
slots = [bindingstack[r] for r in ranks]
|
|
1569
1864
|
else:
|
|
1570
|
-
slots = [
|
|
1865
|
+
slots = [
|
|
1866
|
+
sorted(self.system.cpus_info.cpus.keys()),
|
|
1867
|
+
] * len(ranks)
|
|
1571
1868
|
rfile = self._create_rankfile(ranks, nodes, slots)
|
|
1572
1869
|
# Add the rankfile on the command line
|
|
1573
|
-
cmdl.append(self.optprefix +
|
|
1870
|
+
cmdl.append(self.optprefix + "rankfile")
|
|
1574
1871
|
cmdl.append(rfile)
|
|
1575
1872
|
# Add the "usual" call to binaries and setup OMP_NUM_THREADS values
|
|
1576
1873
|
wrapstd = self._wrapstd_mkwrapper()
|
|
1577
1874
|
for i_bin, a_bin in enumerate(self.binaries):
|
|
1578
1875
|
if i_bin > 0:
|
|
1579
1876
|
cmdl.append(self.binsep)
|
|
1580
|
-
openmp = a_bin.options.get(
|
|
1877
|
+
openmp = a_bin.options.get("openmp", None)
|
|
1581
1878
|
if openmp:
|
|
1582
|
-
cmdl.append(self.optprefix +
|
|
1583
|
-
cmdl.append(
|
|
1584
|
-
cmdl.append(self.optprefix +
|
|
1879
|
+
cmdl.append(self.optprefix + "x")
|
|
1880
|
+
cmdl.append("OMP_NUM_THREADS={!s}".format(openmp))
|
|
1881
|
+
cmdl.append(self.optprefix + "np")
|
|
1585
1882
|
cmdl.append(str(a_bin.nprocs))
|
|
1586
1883
|
if wrapstd:
|
|
1587
1884
|
cmdl.append(wrapstd)
|
|
@@ -1591,17 +1888,21 @@ class OmpiMpiRun(MpiTool):
|
|
|
1591
1888
|
# Generate a host file but let vortex deal with the rest...
|
|
1592
1889
|
base_nodelist = self._envelope_nodelist()
|
|
1593
1890
|
ranks = list(range(len(base_nodelist)))
|
|
1594
|
-
rfile = self._create_rankfile(
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1891
|
+
rfile = self._create_rankfile(
|
|
1892
|
+
ranks,
|
|
1893
|
+
[base_nodelist[self._ranks_mapping[r]] for r in ranks],
|
|
1894
|
+
[
|
|
1895
|
+
sorted(self.system.cpus_info.cpus.keys()),
|
|
1896
|
+
]
|
|
1897
|
+
* len(base_nodelist),
|
|
1898
|
+
)
|
|
1598
1899
|
# Generate wrappers
|
|
1599
1900
|
self._envelope_mkwrapper(cmdl)
|
|
1600
1901
|
wrapstd = self._wrapstd_mkwrapper()
|
|
1601
1902
|
# Update the command line
|
|
1602
|
-
cmdl.append(self.optprefix +
|
|
1903
|
+
cmdl.append(self.optprefix + "rankfile")
|
|
1603
1904
|
cmdl.append(rfile)
|
|
1604
|
-
cmdl.append(self.optprefix +
|
|
1905
|
+
cmdl.append(self.optprefix + "np")
|
|
1605
1906
|
cmdl.append(str(len(base_nodelist)))
|
|
1606
1907
|
if wrapstd:
|
|
1607
1908
|
cmdl.append(wrapstd)
|
|
@@ -1616,33 +1917,37 @@ class OmpiMpiRun(MpiTool):
|
|
|
1616
1917
|
def setup_environment(self, opts):
|
|
1617
1918
|
"""Tweak the environment with some srun specific settings."""
|
|
1618
1919
|
super().setup_environment(opts)
|
|
1619
|
-
if self.bindingmethod ==
|
|
1620
|
-
self._logged_env_set(
|
|
1920
|
+
if self.bindingmethod == "native" and "OMP_PROC_BIND" not in self.env:
|
|
1921
|
+
self._logged_env_set("OMP_PROC_BIND", "true")
|
|
1621
1922
|
for libpath in self._mpilib_identification()[2]:
|
|
1622
1923
|
logger.info('Adding "%s" to LD_LIBRARY_PATH', libpath)
|
|
1623
|
-
self.env.setgenericpath(
|
|
1924
|
+
self.env.setgenericpath("LD_LIBRARY_PATH", libpath)
|
|
1624
1925
|
|
|
1625
1926
|
|
|
1626
1927
|
class OmpiMpiRunDDT(OmpiMpiRun):
|
|
1627
1928
|
"""SLURM's srun launcher with ARM's DDT."""
|
|
1628
1929
|
|
|
1629
1930
|
_footprint = dict(
|
|
1630
|
-
attr
|
|
1631
|
-
mpiname
|
|
1632
|
-
values
|
|
1931
|
+
attr=dict(
|
|
1932
|
+
mpiname=dict(
|
|
1933
|
+
values=[
|
|
1934
|
+
"openmpi-ddt",
|
|
1935
|
+
],
|
|
1633
1936
|
),
|
|
1634
1937
|
)
|
|
1635
1938
|
)
|
|
1636
1939
|
|
|
1637
|
-
_conf_suffix =
|
|
1940
|
+
_conf_suffix = "-ddt"
|
|
1638
1941
|
|
|
1639
1942
|
def mkcmdline(self):
|
|
1640
1943
|
"""Add the DDT prefix command to the command line"""
|
|
1641
1944
|
cmdl = super(OmpiMpiRun, self).mkcmdline()
|
|
1642
1945
|
armtool = ArmForgeTool(self.ticket)
|
|
1643
|
-
for extra_c in reversed(
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1946
|
+
for extra_c in reversed(
|
|
1947
|
+
armtool.ddt_prefix_cmd(
|
|
1948
|
+
sources=self.sources,
|
|
1949
|
+
workdir=self.system.path.dirname(self.binaries[0].master),
|
|
1950
|
+
)
|
|
1951
|
+
):
|
|
1647
1952
|
cmdl.insert(0, extra_c)
|
|
1648
1953
|
return cmdl
|