Skip to content

Commit

Permalink
Refactor parallelism utilities for public API
Browse files Browse the repository at this point in the history
`should_run_in_parallel` was added in a stable manner to enable backport
to the 1.1 series, but from 1.3 onwards, we want this to be part of the
public interface so that others can rely on it too.

As part of this, the parallelisation configuration was made more robust
and controllable with context managers.  This is convenient beyond just
for users - it makes it far easier to control the parallelism during the
test suite runs.  Several instances where different parts of Qiskit and
its test suite reached into deep internals of the parallelism utilities
and made significant assumptions about the internal logic are refactored
to use public interfaces to achieve what they wanted to.

The multiprocessing detection is changed from making OS-based
assumptions about what Python does to simply querying the module for its
configuration.  This makes it more robust to changes in Python's
handling (especially important since 3.14 will change the default start
method on Unix).  In the future, we may want to change to making these
assumptions only if the user hasn't configured the `multiprocessing`
start method themselves.
  • Loading branch information
jakelishman committed Nov 1, 2024
1 parent 667ee8e commit e4fea41
Show file tree
Hide file tree
Showing 24 changed files with 381 additions and 294 deletions.
1 change: 1 addition & 0 deletions .azure/test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ jobs:
popd
env:
QISKIT_PARALLEL: FALSE
QISKIT_IGNORE_USER_SETTINGS: TRUE
RUST_BACKTRACE: 1
displayName: 'Run Python tests'
Expand Down
1 change: 1 addition & 0 deletions .azure/test-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
stestr run
env:
QISKIT_PARALLEL: FALSE
QISKIT_IGNORE_USER_SETTINGS: TRUE
RUST_BACKTRACE: 1
displayName: "Run tests"
Expand Down
1 change: 1 addition & 0 deletions .azure/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
LANG: 'C.UTF-8'
PYTHONIOENCODING: 'utf-8:backslashreplace'
QISKIT_PARALLEL: FALSE
QISKIT_IGNORE_USER_SETTINGS: TRUE
RUST_BACKTRACE: 1
displayName: 'Run tests'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
env:
QISKIT_TEST_CAPTURE_STREAMS: 1
QISKIT_PARALLEL: FALSE
QISKIT_IGNORE_USER_SETTINGS: TRUE
PYTHON: "coverage run --source qiskit --parallel-mode"

- name: Convert to lcov and combine data
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/qpy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ jobs:
- name: Run QPY backwards compatibility tests
working-directory: test/qpy_compat
run: ./run_tests.sh
env:
QISKIT_IGNORE_USER_SETTINGS: TRUE
1 change: 1 addition & 0 deletions .github/workflows/randomized_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
run: make test_randomized
env:
RUST_BACKTRACE: 1
QISKIT_IGNORE_USER_SETTINGS: TRUE
- name: Create comment on failed test run
if: ${{ failure() }}
uses: peter-evans/create-or-update-comment@v4
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/slow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
env:
RUST_BACKTRACE: 1
QISKIT_TESTS: "run_slow"
QISKIT_IGNORE_USER_SETTINGS: TRUE
- name: Create comment on failed test run
if: ${{ failure() }}
uses: peter-evans/create-or-update-comment@v4
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ jobs:
if: matrix.python-version == '3.10'
- name: 'Run tests'
run: stestr run
env:
QISKIT_PARALLEL: FALSE
QISKIT_IGNORE_USER_SETTINGS: TRUE
10 changes: 4 additions & 6 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import collections.abc
import copy as _copy
import itertools
import multiprocessing as mp
import multiprocessing
import typing
from collections import OrderedDict, defaultdict, namedtuple
from typing import (
Expand All @@ -41,7 +41,6 @@
from qiskit._accelerate.circuit import CircuitData
from qiskit._accelerate.circuit import StandardGate
from qiskit.exceptions import QiskitError
from qiskit.utils.multiprocessing import is_main_process
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.gate import Gate
from qiskit.circuit.parameter import Parameter
Expand Down Expand Up @@ -1498,11 +1497,10 @@ def _cls_prefix(cls) -> str:

def _name_update(self) -> None:
"""update name of instance using instance number"""
if not is_main_process():
pid_name = f"-{mp.current_process().pid}"
else:
if multiprocessing.parent_process() is None:
pid_name = ""

else:
pid_name = f"-{multiprocessing.current_process().pid}"
self.name = f"{self._base_name}-{self._cls_instances()}{pid_name}"

def has_register(self, register: Register) -> bool:
Expand Down
5 changes: 2 additions & 3 deletions qiskit/pulse/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
from qiskit.pulse.instructions import Instruction, Reference
from qiskit.pulse.utils import instruction_duration_validation
from qiskit.pulse.reference_manager import ReferenceManager
from qiskit.utils.multiprocessing import is_main_process
from qiskit.utils import deprecate_arg
from qiskit.utils.deprecate_pulse import deprecate_pulse_func

Expand Down Expand Up @@ -146,7 +145,7 @@ def __init__(

if name is None:
name = self.prefix + str(next(self.instances_counter))
if sys.platform != "win32" and not is_main_process():
if sys.platform != "win32" and mp.parent_process() is not None:
name += f"-{mp.current_process().pid}"

self._name = name
Expand Down Expand Up @@ -1007,7 +1006,7 @@ def __init__(

if name is None:
name = self.prefix + str(next(self.instances_counter))
if sys.platform != "win32" and not is_main_process():
if sys.platform != "win32" and mp.parent_process() is not None:
name += f"-{mp.current_process().pid}"

# This points to the parent schedule object in the current scope.
Expand Down
6 changes: 3 additions & 3 deletions qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from qiskit.transpiler.passes.routing.sabre_swap import _build_sabre_dag, _apply_sabre_result
from qiskit.transpiler.target import Target
from qiskit.transpiler.coupling import CouplingMap
from qiskit.utils.parallel import CPU_COUNT
from qiskit.utils import default_num_processes

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -174,11 +174,11 @@ def __init__(
self.max_iterations = max_iterations
self.trials = swap_trials
if swap_trials is None:
self.swap_trials = CPU_COUNT
self.swap_trials = default_num_processes()
else:
self.swap_trials = swap_trials
if layout_trials is None:
self.layout_trials = CPU_COUNT
self.layout_trials = default_num_processes()
else:
self.layout_trials = layout_trials
self.skip_routing = skip_routing
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from qiskit.transpiler.target import Target
from qiskit.transpiler.passes.layout import disjoint_utils
from qiskit.dagcircuit import DAGCircuit, DAGOpNode
from qiskit.utils.parallel import CPU_COUNT
from qiskit.utils import default_num_processes

from qiskit._accelerate.sabre import sabre_routing, Heuristic, SetScaling, NeighborTable, SabreDAG
from qiskit._accelerate.nlayout import NLayout
Expand Down Expand Up @@ -167,7 +167,7 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t
self.heuristic = heuristic
self.seed = seed
if trials is None:
self.trials = CPU_COUNT
self.trials = default_num_processes()
else:
self.trials = trials

Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
SXGate,
SXdgGate,
)
from qiskit.utils.parallel import CPU_COUNT
from qiskit.utils import default_num_processes
from qiskit import user_config

CONFIG = user_config.get_config()
Expand Down Expand Up @@ -1029,5 +1029,5 @@ def _swap_mapped(property_set):

def _get_trial_count(default_trials=5):
if CONFIG.get("sabre_all_threads", None) or os.getenv("QISKIT_SABRE_ALL_THREADS"):
return max(CPU_COUNT, default_trials)
return max(default_num_processes(), default_trials)
return default_trials
12 changes: 8 additions & 4 deletions qiskit/user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,19 @@ def set_config(key, value, section=None, file_path=None):


def get_config():
"""Read the config file from the default location or env var
"""Read the config file from the default location or env var.
It will read a config file at either the default location
~/.qiskit/settings.conf or if set the value of the QISKIT_SETTINGS env var.
It will read a config file at the location specified by the ``QISKIT_SETTINGS`` environment
variable if set, or ``$HOME/.qiskit/settings.conf`` if not.
If the environment variable ``QISKIT_IGNORE_USER_SETTINGS`` is set to the string ``TRUE``, this
will return an empty configuration, regardless of all other variables.
It will return the parsed settings dict from the parsed config file.
Returns:
dict: The settings dict from the parsed config file.
"""
if os.getenv("QISKIT_IGNORE_USER_SETTINGS", "false").lower() == "true":
return {}
filename = os.getenv("QISKIT_SETTINGS", DEFAULT_FILENAME)
if not os.path.isfile(filename):
return {}
Expand Down
19 changes: 13 additions & 6 deletions qiskit/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
Multiprocessing
===============
.. autofunction:: local_hardware_info
.. autofunction:: default_num_processes
.. autofunction:: is_main_process
.. autofunction:: local_hardware_info
.. autofunction:: should_run_in_parallel
A helper function for calling a custom function with Python
:class:`~concurrent.futures.ProcessPoolExecutor`. Tasks can be executed in parallel using this function.
Expand All @@ -62,28 +64,33 @@
deprecate_func,
deprecate_function,
)
from .multiprocessing import local_hardware_info
from .multiprocessing import is_main_process
from .units import apply_prefix, detach_prefix
from .classtools import wrap_method
from .lazy_tester import LazyDependencyManager, LazyImportTester, LazySubprocessTester

from . import optionals

from .parallel import parallel_map, should_run_in_parallel
from .parallel import (
parallel_map,
should_run_in_parallel,
local_hardware_info,
is_main_process,
default_num_processes,
)

__all__ = [
"LazyDependencyManager",
"LazyImportTester",
"LazySubprocessTester",
"add_deprecation_to_docstring",
"apply_prefix",
"default_num_processes",
"deprecate_arg",
"deprecate_arguments",
"deprecate_func",
"deprecate_function",
"local_hardware_info",
"is_main_process",
"apply_prefix",
"local_hardware_info",
"parallel_map",
"should_run_in_parallel",
]
56 changes: 0 additions & 56 deletions qiskit/utils/multiprocessing.py

This file was deleted.

Loading

0 comments on commit e4fea41

Please sign in to comment.