Run computations in parallel#
PyPhi’s core computations are combinatorially expensive: finding the minimum information partition means evaluating many candidate partitions, purviews, concepts, and relations. These evaluations are independent of one another, so PyPhi can distribute them across CPU cores. Parallelism is off by default; this page shows how to turn it on and how to tune it.
import pyphi
pyphi.config.progress_bars = False
The global gate#
pyphi.config.parallel is the master gate for parallelism. It is necessary
but not sufficient: when it is False (the default), everything runs
sequentially no matter what else is configured — and setting it to True by
itself parallelizes nothing, because each level of the computation also has
its own switch (see the per-level options below). The gate only ever forces
things off.
It is in the infrastructure layer of the configuration, so it can also be read and written through its full path:
pyphi.config.infrastructure.parallel
False
Writing to the flat name routes to the same underlying option:
pyphi.config.parallel = True
pyphi.config.infrastructure.parallel
True
pyphi.config.parallel = False # back to the default
Number of workers#
parallel_workers sets how many CPU cores to use. The default of -1 means
“all available cores”.
pyphi.config.infrastructure.parallel_workers
-1
Set it to a positive integer to cap the worker count, for example to leave cores free for other work:
pyphi.config.parallel_workers = 4
pyphi.config.infrastructure.parallel_workers
4
pyphi.config.parallel_workers = -1 # back to the default
Enabling parallelism for a single computation#
The best way to run one computation in parallel, without leaving the
global configuration changed, is the override context manager. Everything
inside the with block sees the overridden settings; outside, the previous
values are restored. Two things must be switched on: the global gate, and
the specific level (here, the system-partition search):
substrate = pyphi.examples.iit4_2023_fig1a_substrate()
state = (0, 1, 1)
opts = dict(pyphi.config.infrastructure.parallel_partition_evaluation)
opts["parallel"] = True
with pyphi.config.override(
parallel=True, parallel_partition_evaluation=opts, parallel_workers=2
):
analysis = pyphi.analyze(substrate, state)
round(float(analysis.sia.phi), 6)
0.133873
(This small example has fewer partitions than the level’s
sequential_threshold, so it runs sequentially anyway — the threshold
exists because dispatching tiny workloads to workers costs more than it
saves. The configuration is what matters here; a larger system fans out
automatically.)
Parallelism changes only how the work is scheduled, never the result. The same computation run sequentially gives the identical value:
sequential = pyphi.analyze(substrate, state)
round(float(sequential.sia.phi), 6)
0.133873
Per-level options#
Beyond the global gate, PyPhi parallelizes at several distinct levels of the
computation, each with its own option. Each is a dictionary with the keys
parallel, sequential_threshold, chunksize, and progress:
dict(pyphi.config.infrastructure.parallel_distinction_evaluation)
{'parallel': False,
'sequential_threshold': 64,
'chunksize': 256,
'progress': True}
The keys mean:
parallel— whether to parallelize this level at all.sequential_threshold— workloads with fewer than this many items run sequentially, because for small counts the overhead of spawning workers and sending data to them costs more than it saves.chunksize— how many items each worker receives per batch. This governs granularity only, not the result.progress— whether to show a progress bar for this level.
The available levels are:
Config option |
Parallelizes over |
|---|---|
|
Candidate systems (complexes) within a substrate |
|
Distinctions within a cause-effect structure |
|
System partitions when searching for the minimum partition |
|
Candidate purviews for a mechanism |
|
Partitions of a single mechanism |
|
Relations between distinctions |
|
Macro (coarse-grained) systems |
To tune one level, assign a new dictionary. A common pattern is to read the current value, change one key, and write it back:
opts = dict(pyphi.config.infrastructure.parallel_distinction_evaluation)
opts["parallel"] = True
opts["sequential_threshold"] = 32
pyphi.config.parallel_distinction_evaluation = opts
dict(pyphi.config.infrastructure.parallel_distinction_evaluation)
{'parallel': True,
'sequential_threshold': 32,
'chunksize': 256,
'progress': True}
# Restore the default (parallel off, threshold 64)
pyphi.config.parallel_distinction_evaluation = {
"parallel": False,
"sequential_threshold": 64,
"chunksize": 256,
"progress": True,
}
When parallelism helps#
Parallelism is not free: starting worker processes and sending data to and
from them takes time, and that cost is fixed regardless of how much real work
is being done. It pays off only when the work per level is large enough to
dwarf that overhead. This is exactly what the sequential_threshold on each
level encodes.
As a rule of thumb:
Small networks (a handful of nodes) usually run faster sequentially. The default
sequential_thresholdon each level already runs these workloads sequentially, so turning onpyphi.config.parallelcosts little for small problems.Larger networks — where there are many mechanisms, purviews, or partitions to evaluate — benefit the most.
Parallelism needs no extra installation: the process-pool backend is part of PyPhi’s core dependencies.
When in doubt, benchmark both. Because the result is identical either way, you can compare wall-clock time directly and pick whichever is faster for your network size.
Running on a cluster#
The dask backend distributes the same parallel levels across a
dask.distributed cluster — a laptop LocalCluster, lab workstations, or
an HTCondor/Slurm pool via dask-jobqueue. Install the cluster extra
(pip install "pyphi[cluster]"), connect a distributed.Client, and set
pyphi.config.parallel_backend = "dask". See Run PyPhi on a cluster (CHTC) for cluster
deployment, including UW–Madison’s CHTC. For batch sweeps on an HTCondor
pool with no live worker connection, use a campaign instead — see
Run a sweep as an HTCondor campaign.