From f7c8306e9aef0fc595c3dabbdcda807887d35bfd Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 4 Oct 2022 12:18:41 -0400 Subject: [PATCH 1/4] Deprecate lists for argument input on transpile() Right now the support for the argument broadcasting with list inputs for various transpiler options on the transpile() function causes a significant performance overhead to support, primarily do to how we have to handle the multiple arguments across a parallel dispatch boundary. It also significantly increases the code complexity of the function to support more than one input for each argument (except circuits). The utility of doing this type of argument handling is quite limited since a similar result can be achieved with a for loop and would like be simpler for users to reason about. When weighing all these factors the best path forward is to just remove this functionality. This commit starts the process of removing this feature by marking it as deprecated. Once the deprecation cycle is complete we can greatly simplify the code in transpile and primarily replace it with a call to generate_preset_pass_manager() and passmanager.run() (the only thing I think we'll have to handle out of band is faulty qubits defined in a BackendProperties for BackendV1). Related to #7741 --- qiskit/compiler/transpiler.py | 59 +++++++++++++++---- ...-list-args-transpile-f92e5b3d411f361f.yaml | 40 +++++++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 7bb3d0d74f97..c65677c9c91e 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -88,22 +88,22 @@ def transpile( ) -> Union[QuantumCircuit, List[QuantumCircuit]]: """Transpile one or more circuits, according to some desired transpilation targets. - All arguments may be given as either a singleton or list. In case of a list, - the length must be equal to the number of circuits being transpiled. + .. deprecated:: 0.23.0 + + All arguments (except for ``circuits``) may be given as either a singleton or list. In + case of a list, the length must be equal to the number of circuits being transpiled. The + support for using a list for an argument is deprecated and will be removed in the 0.25.0 + release. If you need to use multiple values for an argument you can + use multiple :func:`~.transpile` calls (and potentially use :func:`~.parallel_map` + to leverage multiprocessing if needed). Transpilation is done in parallel using multiprocessing. Args: circuits: Circuit(s) to transpile - backend: If set, transpiler options are automatically grabbed from - ``backend.configuration()`` and ``backend.properties()``. - If any other option is explicitly set (e.g., ``coupling_map``), it + backend: If set, the transpiler will compile the input circuit to this target + device. If any other option is explicitly set (e.g., ``coupling_map``), it will override the backend's. - - .. note:: - - The backend arg is purely for convenience. The resulting - circuit may be run on any backend as long as it is compatible. basis_gates: List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll. inst_map: Mapping of unrolled gates to pulse schedules. If this is not provided, @@ -626,7 +626,9 @@ def _parse_transpile_args( # Each arg could be single or a list. If list, it must be the same size as # number of circuits. If single, duplicate to create a list of that size. num_circuits = len(circuits) - + user_input_durations = instruction_durations + user_input_timing_constraints = timing_constraints + user_input_initial_layout = initial_layout # If a target is specified have it override any implicit selections from a backend # but if an argument is explicitly passed use that instead of the target version if target is not None: @@ -702,6 +704,41 @@ def _parse_transpile_args( "hls_config": hls_config, }.items(): if isinstance(value, list): + # This giant if statement is to resolve different argument + # handling special cases. For scheduling timing related parameters + # (timing_constraints and instruction_durations) are always + # expanded to a list. The intial_layout parameter can be a list + # object so it needs special handling to determin it's unique. + # This path is super buggy in general (outside of the warning) and + # since we're deprecating this it's better to just remove it than + # try to clean it up. + # pylint: disable=too-many-boolean-expressions + if ( + key not in {"instruction_durations", "timing_constraints", "initial_layout"} + or ( + key == "initial_layout" + and user_input_initial_layout + and isinstance(user_input_initial_layout, list) + and isinstance(user_input_initial_layout[0], (Layout, dict, list)) + ) + or ( + key == "instruction_durations" + and user_input_durations + and isinstance(user_input_durations, list) + and isinstance(user_input_durations[0], (list, InstructionDurations)) + ) + or ( + key == "timing_constraints" + and user_input_timing_constraints + and isinstance(user_input_timing_constraints, list) + ) + ): + warnings.warn( + f"Passing in a list of arguments for {key} is deprecated and will no longer work " + "starting in the 0.25.0 release.", + DeprecationWarning, + stacklevel=3, + ) unique_dict[key] = value else: shared_dict[key] = value diff --git a/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml b/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml new file mode 100644 index 000000000000..7e53418f08a9 --- /dev/null +++ b/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml @@ -0,0 +1,40 @@ +--- +deprecations: + - | + Support for passing in lists of argument values to the :func:`~.transpile` + function is deprecated and will be removed in the 0.25.0 release. This + is being done to facilitate greatly reducing the overhead for parallel + execution for transpiling multiple circuits at once. If you're using + this functionality currently you can call :func:`~.transpile` multiple + times instead. For example if you were previously doing something like:: + + from qiskit.transpiler import CouplingMap + from qiskit import QuantumCircuit + from qiskit import transpile + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + cmaps = [CouplingMap.from_heavy_hex(d) for d in range(3, 15, 2)] + results = transpile([qc] * 6, coupling_map=cmaps) + + instead you should run something like: + + from itertools import cycle + from qiskit.transpiler import CouplingMap + from qiskit import QuantumCircuit + from qiskit import transpile + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + cmaps = [CouplingMap.from_heavy_hex(d) for d in range(3, 15, 2)] + + results = [] + for qc, cmap in zip(cycle([qc]), cmaps): + results.append(transpile(qc, coupling_map=cmap)) + + You can also leverage :func:`~.parallel_map` or ``multiprocessing`` from + the Python standard library if you want to run this in parallel. From 98900f1ff26c718f27825f0b916bcb0663cc74c3 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 11 Oct 2022 17:36:50 -0400 Subject: [PATCH 2/4] Update qiskit/compiler/transpiler.py Co-authored-by: Kevin Hartman --- qiskit/compiler/transpiler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index c65677c9c91e..67da14bf6249 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -704,14 +704,14 @@ def _parse_transpile_args( "hls_config": hls_config, }.items(): if isinstance(value, list): - # This giant if statement is to resolve different argument - # handling special cases. For scheduling timing related parameters - # (timing_constraints and instruction_durations) are always - # expanded to a list. The intial_layout parameter can be a list - # object so it needs special handling to determin it's unique. - # This path is super buggy in general (outside of the warning) and - # since we're deprecating this it's better to just remove it than - # try to clean it up. + # This giant if-statement detects deprecated use of argument + # broadcasting. For arguments that previously supported broadcast + # but were not themselves of type list (the majority), we simply warn + # when the user provides a list. For the others, special handling is + # required to disambiguate an expected value of type list from + # an attempt to provide multiple values for broadcast. This path is + # super buggy in general (outside of the warning) and since we're + # deprecating this it's better to just remove it than try to clean it up. # pylint: disable=too-many-boolean-expressions if ( key not in {"instruction_durations", "timing_constraints", "initial_layout"} From 42789f46bae40a17b67f88c0087c9b3eac8913aa Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 14 Oct 2022 12:14:29 -0400 Subject: [PATCH 3/4] Update qiskit/compiler/transpiler.py Co-authored-by: Kevin Hartman --- qiskit/compiler/transpiler.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 67da14bf6249..337b13aa7811 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -90,12 +90,12 @@ def transpile( .. deprecated:: 0.23.0 - All arguments (except for ``circuits``) may be given as either a singleton or list. In - case of a list, the length must be equal to the number of circuits being transpiled. The - support for using a list for an argument is deprecated and will be removed in the 0.25.0 - release. If you need to use multiple values for an argument you can - use multiple :func:`~.transpile` calls (and potentially use :func:`~.parallel_map` - to leverage multiprocessing if needed). + Previously, all arguments accepted lists of the same length as ``circuits``, + which was used to specialize arguments for circuits at the corresponding + indices. Support for using such argument lists is now deprecated and will + be removed in the 0.25.0 release. If you need to use multiple values for an + argument, you can use multiple :func:`~.transpile` calls (and potentially + :func:`~.parallel_map` to leverage multiprocessing if needed). Transpilation is done in parallel using multiprocessing. From de3b0cf91f11b938458e48eec825fbc9ad401de1 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 17 Oct 2022 14:00:57 -0400 Subject: [PATCH 4/4] Fix release note typo --- .../notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml b/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml index 7e53418f08a9..dbd52083ad38 100644 --- a/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml +++ b/releasenotes/notes/deprecate-list-args-transpile-f92e5b3d411f361f.yaml @@ -19,7 +19,7 @@ deprecations: cmaps = [CouplingMap.from_heavy_hex(d) for d in range(3, 15, 2)] results = transpile([qc] * 6, coupling_map=cmaps) - instead you should run something like: + instead you should run something like:: from itertools import cycle from qiskit.transpiler import CouplingMap