!!! abstract “”
Why MPI is not simply a fourth process pool: the worker count is decided outside Python by `mpiexec`, rank 0 is the master and does no work, `max_workers` cannot change any of it, and `UNIVERSE_SIZE` counts slots rather than ranks.
The "multiprocess", "threads" and
"loky" backends all assume the running process decides how
much parallelism to use by looking at the machine it is on.
max_workers=4 means four workers, and leaving it out means
one per CPU.
MPI breaks that assumption. The processes were created before Python
started, by mpiexec or by a cluster scheduler, and nothing
inside the program can add to them or give them back. Almost everything
else on this page follows from that.
With a local backend the chain is short. You ask for workers, and the pool starts them:
```python { notest } from scinexus import parallel
results = parallel.map(process, items, max_workers=4) # four workers
Under MPI that same argument is a request about an allocation that already exists and cannot be changed. The number that decides your parallelism is the one you gave the launcher:
```bash
mpiexec -n 8 python -m mpi4py.futures my_script.py
So under MPI, mpiexec -n is the single place the worker
count is in Python set so set use_mpi=True but leave
max_workers out.
Every MPI process is a rank. Rank 0 is the master: it holds the data, hands out tasks and collects results, and does not process items itself. A job of 8 ranks therefore has 7 workers, not 8.
This has no equivalent in the local backends, where a pool of 4
workers on a 4 core machine puts all 4 to work while the parent simply
waits. Setting max_workers=$PBS_NCPUS on a cluster is wrong
because it asks for one more worker than the job can ever contain.
It also means -n 2 leaves you with a single worker,
which is serial execution but using two processes. With
if_serial defaulting to "raise",
scinexus will kill a job with one worker You can override
this by setting if_serial="ignore".
!!! info -n 3 is the smallest launch with more than one
worker.
scinexus drives every backend through
concurrent.futures, and for MPI that means you MUST launch
your script using:
mpiexec -n 4 python -m mpi4py.futures my_script.py!!! warning scinexus is not written for the any other
pattern, such as using mpi4py itself to spawn workers on
demand while the program runs.
max_workers
cannot change the worker countBecause the pool is fixed before your code runs,
max_workers has no way to grow or shrink it.
scinexus therefore treats the argument as a statement
about an allocation rather than a request for one:
UNIVERSE_SIZE
counts slots, not ranksMPI exposes an attribute called UNIVERSE_SIZE, which is
easy to mistake for the number of processes. It is the number of
slots the job may occupy, which usually means cores on the
machine or slots granted by the scheduler.
The two numbers are independent, and neither is the worker count. Measured on a six core machine:
| launch | UNIVERSE_SIZE |
ranks | workers |
|---|---|---|---|
mpiexec -n 4 -m mpi4py.futures |
6 | 4 | 3 |
mpiexec --oversubscribe -n 8 -m mpi4py.futures |
6 | 8 | 7 |
mpiexec -n 1 -m mpi4py.futures |
6 | 1 | 1 |
Running more ranks than there are slots is
oversubscription, which
mpiexec --oversubscribe permits deliberately. It is a
legitimate thing to do, for testing on a small machine or for work that
spends its time waiting rather than computing. The third row is the
other end, and it is a launch that will stop rather than run: one worker
is serial, so if_serial refuses it by default.
!!! note
`get_size()` reports the last column, the workers you will actually get. It does not report `UNIVERSE_SIZE` and it does not report the rank count.
Two things behave as they do for the process backends rather than
being MPI peculiarities. Arguments and results are pickled, so closures
and lambdas are refused just as they are under
"multiprocess". And the main logic must sit behind an
if __name__ == "__main__": guard, for the same reason it
must under spawn based multiprocessing.
For the practical steps – installing mpi4py, setting the backend, writing and launching the script – see Run in parallel.