From 0ebfb43cfde51705685776c9e2e389f1562166c0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 17 Mar 2022 16:40:44 -0400 Subject: [PATCH 01/17] Leverage Python SharedMemory for common transpile args This commit uses the Python shared memory library to reduce the overhead of launching parallel processes as part of transpile. As the size of the backends grow the payload size we're serializing and copying between worker processes is also increasing. When we're running a lot of small circuits at once on a big backend we can easily spend far more time dealing with IO overhead than running the transpilation. By using shared memory this reduces the overhead to only serializing and copying it once and then each worker process just needs to serializing it. While this doesn't remove all the overhead it should reduce the impact somewhat. Fixes #7741 --- qiskit/compiler/transpiler.py | 244 ++++++++++++++++------------------ requirements.txt | 1 + 2 files changed, 119 insertions(+), 126 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index f322935fed7b..8c39f477b4c7 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -12,10 +12,16 @@ """Circuit transpile function""" import datetime +import io +from itertools import cycle import logging -import warnings +from multiprocessing.shared_memory import SharedMemory +from multiprocessing.managers import SharedMemoryManager from time import time from typing import List, Union, Dict, Callable, Any, Optional, Tuple, Iterable +import warnings + +import pickle from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -272,36 +278,47 @@ def callback_func(**kwargs): UserWarning, ) - # Get transpile_args to configure the circuit transpilation job(s) - transpile_args = _parse_transpile_args( - circuits, - backend, - basis_gates, - inst_map, - coupling_map, - backend_properties, - initial_layout, - layout_method, - routing_method, - translation_method, - scheduling_method, - instruction_durations, - dt, - approximation_degree, - seed_transpiler, - optimization_level, - callback, - output_name, - timing_constraints, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - target, - ) - - _check_circuits_coupling_map(circuits, transpile_args, backend) - - # Transpile circuits in parallel - circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_args))) + with SharedMemoryManager() as smm: + with io.BytesIO() as buf: + # Get transpile_args to configure the circuit transpilation job(s) + unique_transpile_args, shared_args = _parse_transpile_args( + circuits, + backend, + basis_gates, + inst_map, + coupling_map, + backend_properties, + initial_layout, + layout_method, + routing_method, + translation_method, + scheduling_method, + instruction_durations, + dt, + approximation_degree, + seed_transpiler, + optimization_level, + callback, + output_name, + timing_constraints, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + target, + ) + if coupling_map in unique_transpile_args: + cmap_conf = unique_transpile_args["coupling_map"] + else: + cmap_conf = [shared_args["coupling_map"]] * len(circuits) + _check_circuits_coupling_map(circuits, cmap_conf, backend) + pickle.dump(shared_args, buf) + data = buf.getvalue() + smb = smm.SharedMemory(size=len(data)) + smb.buf[:] = data[:] + # Transpile circuits in parallel + circuits = parallel_map( + _transpile_circuit, + list(zip(enumerate(circuits), cycle([smb.name]), unique_transpile_args)), + ) end_time = time() _log_transpile_time(start_time, end_time) @@ -312,11 +329,9 @@ def callback_func(**kwargs): return circuits[0] -def _check_circuits_coupling_map(circuits, transpile_args, backend): +def _check_circuits_coupling_map(circuits, cmap_conf, backend): # Check circuit width against number of qubits in coupling_map(s) - coupling_maps_list = list( - config["pass_manager_config"].coupling_map for config in transpile_args - ) + coupling_maps_list = cmap_conf for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): # If coupling_map is not None or num_qubits == 1 num_qubits = len(circuit.qubits) @@ -363,9 +378,24 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua Raises: TranspilerError: if transpile_config is not valid or transpilation incurs error """ - circuit, transpile_config = circuit_config_tuple - - pass_manager_config = transpile_config["pass_manager_config"] + (index, circuit), name, unique_config = circuit_config_tuple + existing_shm = SharedMemory(name=name) + try: + with io.BytesIO(existing_shm.buf) as buf: + shared_transpiler_args = pickle.load(buf) + finally: + existing_shm.close() + + optimization_level = shared_transpiler_args.pop("optimization_level") + # backend_num_qubits = shared_transpiler_args.pop("backend_num_qubits") + pass_manager_config = shared_transpiler_args + pass_manager_config.update(unique_config.pop("pass_manager_config")) + pass_manager_config = PassManagerConfig(**pass_manager_config) + + transpile_config = unique_config + transpile_config["pass_manager_config"] = pass_manager_config + transpile_config["optimization_level"] = optimization_level + # transpile_config["backend_num_qubits"] = backend_num_qubits if transpile_config["faulty_qubits_map"]: pass_manager_config.initial_layout = _remap_layout_faulty_backend( @@ -548,61 +578,51 @@ def _parse_transpile_args( "Transpiling a circuit with a scheduling method" "requires a backend or instruction_durations." ) + unique_dict = { + "callback": callback, + "output_name": output_name, + "faulty_qubits_map": faulty_qubits_map, + "backend_num_qubits": backend_num_qubits, + } + shared_dict = { + "optimization_level": optimization_level, + "basis_gates": basis_gates, + } list_transpile_args = [] - for kwargs in _zip_dict( - { - "basis_gates": basis_gates, - "inst_map": inst_map, - "coupling_map": coupling_map, - "backend_properties": backend_properties, - "initial_layout": initial_layout, - "layout_method": layout_method, - "routing_method": routing_method, - "translation_method": translation_method, - "scheduling_method": scheduling_method, - "durations": durations, - "approximation_degree": approximation_degree, - "timing_constraints": timing_constraints, - "seed_transpiler": seed_transpiler, - "optimization_level": optimization_level, - "output_name": output_name, - "callback": callback, - "backend_num_qubits": backend_num_qubits, - "faulty_qubits_map": faulty_qubits_map, - "unitary_synthesis_method": unitary_synthesis_method, - "unitary_synthesis_plugin_config": unitary_synthesis_plugin_config, - "target": target, - } - ): + for key, value in { + "inst_map": inst_map, + "coupling_map": coupling_map, + "backend_properties": backend_properties, + "initial_layout": initial_layout, + "layout_method": layout_method, + "routing_method": routing_method, + "translation_method": translation_method, + "scheduling_method": scheduling_method, + "instruction_durations": durations, + "approximation_degree": approximation_degree, + "timing_constraints": timing_constraints, + "seed_transpiler": seed_transpiler, + "unitary_synthesis_method": unitary_synthesis_method, + "unitary_synthesis_plugin_config": unitary_synthesis_plugin_config, + "target": target, + }.items(): + if isinstance(value, list): + unique_dict[key] = value + else: + shared_dict[key] = value + + for kwargs in _zip_dict(unique_dict): transpile_args = { - "pass_manager_config": PassManagerConfig( - basis_gates=kwargs["basis_gates"], - inst_map=kwargs["inst_map"], - coupling_map=kwargs["coupling_map"], - backend_properties=kwargs["backend_properties"], - initial_layout=kwargs["initial_layout"], - layout_method=kwargs["layout_method"], - routing_method=kwargs["routing_method"], - translation_method=kwargs["translation_method"], - scheduling_method=kwargs["scheduling_method"], - instruction_durations=kwargs["durations"], - approximation_degree=kwargs["approximation_degree"], - timing_constraints=kwargs["timing_constraints"], - seed_transpiler=kwargs["seed_transpiler"], - unitary_synthesis_method=kwargs["unitary_synthesis_method"], - unitary_synthesis_plugin_config=kwargs["unitary_synthesis_plugin_config"], - target=kwargs["target"], - ), - "optimization_level": kwargs["optimization_level"], - "output_name": kwargs["output_name"], - "callback": kwargs["callback"], - "backend_num_qubits": kwargs["backend_num_qubits"], - "faulty_qubits_map": kwargs["faulty_qubits_map"], + "output_name": kwargs.pop("output_name"), + "callback": kwargs.pop("callback"), + "faulty_qubits_map": kwargs.pop("faulty_qubits_map"), + "backend_num_qubits": kwargs.pop("backend_num_qubits"), + "pass_manager_config": kwargs, } list_transpile_args.append(transpile_args) - return list_transpile_args + return list_transpile_args, shared_dict def _create_faulty_qubits_map(backend): @@ -654,11 +674,6 @@ def _parse_basis_gates(basis_gates, backend, circuits): basis_gates = getattr(backend.configuration(), "basis_gates", None) else: basis_gates = backend.operation_names - # basis_gates could be None, or a list of basis, e.g. ['u3', 'cx'] - if basis_gates is None or ( - isinstance(basis_gates, list) and all(isinstance(i, str) for i in basis_gates) - ): - basis_gates = [basis_gates] * len(circuits) return basis_gates @@ -674,10 +689,6 @@ def _parse_inst_map(inst_map, backend, num_circuits): inst_map = getattr(backend.defaults(), "instruction_schedule_map", None) else: inst_map = backend.target.instruction_schedule_map() - # inst_maps could be None, or single entry - if inst_map is None or isinstance(inst_map, InstructionScheduleMap): - inst_map = [inst_map] * num_circuits - return inst_map @@ -715,14 +726,13 @@ def _parse_coupling_map(coupling_map, backend, num_circuits): # coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]] if coupling_map is None or isinstance(coupling_map, CouplingMap): - coupling_map = [coupling_map] * num_circuits - elif isinstance(coupling_map, list) and all( + return coupling_map + if isinstance(coupling_map, list) and all( isinstance(i, list) and len(i) == 2 for i in coupling_map ): - coupling_map = [coupling_map] * num_circuits + return CouplingMap(coupling_map) coupling_map = [CouplingMap(cm) if isinstance(cm, list) else cm for cm in coupling_map] - return coupling_map @@ -844,8 +854,6 @@ def _parse_backend_properties(backend_properties, backend, num_circuits): backend_properties.gates = gates else: backend_properties = _target_to_backend_properties(backend.target) - if not isinstance(backend_properties, list): - backend_properties = [backend_properties] * num_circuits return backend_properties @@ -901,33 +909,22 @@ def _layout_from_raw(initial_layout, circuit): # even if one layout, but multiple circuits, the layout needs to be adapted for each initial_layout = [_layout_from_raw(initial_layout, circ) for circ in circuits] - if not isinstance(initial_layout, list): - initial_layout = [initial_layout] * len(circuits) - return initial_layout def _parse_layout_method(layout_method, num_circuits): - if not isinstance(layout_method, list): - layout_method = [layout_method] * num_circuits return layout_method def _parse_routing_method(routing_method, num_circuits): - if not isinstance(routing_method, list): - routing_method = [routing_method] * num_circuits return routing_method def _parse_translation_method(translation_method, num_circuits): - if not isinstance(translation_method, list): - translation_method = [translation_method] * num_circuits return translation_method def _parse_scheduling_method(scheduling_method, num_circuits): - if not isinstance(scheduling_method, list): - scheduling_method = [scheduling_method] * num_circuits return scheduling_method @@ -965,22 +962,23 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits): def _parse_approximation_degree(approximation_degree, num_circuits): + + if approximation_degree is None: + return approximation_degree if not isinstance(approximation_degree, list): - approximation_degree = [approximation_degree] * num_circuits - if not all(0.0 <= d <= 1.0 for d in approximation_degree if d): - raise TranspilerError("Approximation degree must be in [0.0, 1.0]") + if 0.0 <= approximation_degree <= 1.0: + raise TranspilerError("Approximation degree must be in [0.0, 1.0]") + else: + if not all(0.0 <= d <= 1.0 for d in approximation_degree if d): + raise TranspilerError("Approximation degree must be in [0.0, 1.0]") return approximation_degree def _parse_unitary_synthesis_method(unitary_synthesis_method, num_circuits): - if not isinstance(unitary_synthesis_method, list): - unitary_synthesis_method = [unitary_synthesis_method] * num_circuits return unitary_synthesis_method def _parse_unitary_plugin_config(unitary_synthesis_plugin_config, num_circuits): - if not isinstance(unitary_synthesis_plugin_config, list): - unitary_synthesis_plugin_config = [unitary_synthesis_plugin_config] * num_circuits return unitary_synthesis_plugin_config @@ -988,20 +986,14 @@ def _parse_target(backend, target, num_circuits): backend_target = getattr(backend, "target", None) if target is None: target = backend_target - if not isinstance(target, list): - target = [target] * num_circuits return target def _parse_seed_transpiler(seed_transpiler, num_circuits): - if not isinstance(seed_transpiler, list): - seed_transpiler = [seed_transpiler] * num_circuits return seed_transpiler def _parse_optimization_level(optimization_level, num_circuits): - if not isinstance(optimization_level, list): - optimization_level = [optimization_level] * num_circuits return optimization_level diff --git a/requirements.txt b/requirements.txt index b5e020bfb9b3..a3eb818548e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ python-dateutil>=2.8.0 stevedore>=3.0.0 symengine>=0.9 ; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64' tweedledum>=1.1,<2.0 +shared-memory38;python_version<'3.8' From d4ef7bd01c0773cf2c3124ffbbc944de14e4906c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 17 Mar 2022 17:07:41 -0400 Subject: [PATCH 02/17] Fix python 3.7 compat --- qiskit/compiler/transpiler.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 8c39f477b4c7..b9aad2fe1dcc 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -15,14 +15,12 @@ import io from itertools import cycle import logging -from multiprocessing.shared_memory import SharedMemory -from multiprocessing.managers import SharedMemoryManager +import pickle +import sys from time import time from typing import List, Union, Dict, Callable, Any, Optional, Tuple, Iterable import warnings -import pickle - from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import Qubit @@ -49,6 +47,12 @@ from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.target import Target +if sys.version_info >= (3, 8): + from multiprocessing.shared_memory import SharedMemory + from multiprocessing.managers import SharedMemoryManager +else: + from shared_memory import SharedMemory, SharedMemoryManager + logger = logging.getLogger(__name__) From a196074ead4b9085aa72c709262b72c4f7d06a03 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 23 Mar 2022 18:29:13 -0400 Subject: [PATCH 03/17] Fix approximation degree parsing --- qiskit/compiler/transpiler.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index b9aad2fe1dcc..c4330f4fed1b 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -31,7 +31,7 @@ from qiskit.providers.models import BackendProperties from qiskit.providers.models.backendproperties import Gate from qiskit.pulse import Schedule, InstructionScheduleMap -from qiskit.tools.parallel import parallel_map +from qiskit.tools import parallel from qiskit.transpiler import Layout, CouplingMap, PropertySet from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.exceptions import TranspilerError @@ -319,9 +319,9 @@ def callback_func(**kwargs): smb = smm.SharedMemory(size=len(data)) smb.buf[:] = data[:] # Transpile circuits in parallel - circuits = parallel_map( + circuits = parallel.parallel_map( _transpile_circuit, - list(zip(enumerate(circuits), cycle([smb.name]), unique_transpile_args)), + list(zip(circuits, cycle([smb.name]), unique_transpile_args)), ) end_time = time() @@ -382,7 +382,7 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua Raises: TranspilerError: if transpile_config is not valid or transpilation incurs error """ - (index, circuit), name, unique_config = circuit_config_tuple + circuit, name, unique_config = circuit_config_tuple existing_shm = SharedMemory(name=name) try: with io.BytesIO(existing_shm.buf) as buf: @@ -598,13 +598,13 @@ def _parse_transpile_args( "inst_map": inst_map, "coupling_map": coupling_map, "backend_properties": backend_properties, + "approximation_degree": approximation_degree, "initial_layout": initial_layout, "layout_method": layout_method, "routing_method": routing_method, "translation_method": translation_method, "scheduling_method": scheduling_method, "instruction_durations": durations, - "approximation_degree": approximation_degree, "timing_constraints": timing_constraints, "seed_transpiler": seed_transpiler, "unitary_synthesis_method": unitary_synthesis_method, @@ -966,11 +966,10 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits): def _parse_approximation_degree(approximation_degree, num_circuits): - if approximation_degree is None: - return approximation_degree + return None if not isinstance(approximation_degree, list): - if 0.0 <= approximation_degree <= 1.0: + if approximation_degree < 0.0 or approximation_degree > 1.0: raise TranspilerError("Approximation degree must be in [0.0, 1.0]") else: if not all(0.0 <= d <= 1.0 for d in approximation_degree if d): From 3980d61e3779aee550c0e3d0e199b40e7f1a9e3e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 23 Mar 2022 18:30:42 -0400 Subject: [PATCH 04/17] Fix broken test --- test/python/providers/test_backend_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 22a33b11dc65..21c3c1813598 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -170,5 +170,5 @@ def test_transpile_mumbai_target(self): def test_transpile_parse_inst_map(self): """Test that transpiler._parse_inst_map() supports BackendV2.""" - inst_map = _parse_inst_map(inst_map=None, backend=self.backend, num_circuits=1)[0] + inst_map = _parse_inst_map(inst_map=None, backend=self.backend, num_circuits=1) self.assertIsInstance(inst_map, InstructionScheduleMap) From 2fdde48f373d5eec260c526f72345df5905c85cd Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 23 Mar 2022 18:40:06 -0400 Subject: [PATCH 05/17] Fix more test failures --- qiskit/compiler/transpiler.py | 2 +- test/python/compiler/test_transpiler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index c4330f4fed1b..51338b605eea 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -590,7 +590,7 @@ def _parse_transpile_args( } shared_dict = { "optimization_level": optimization_level, - "basis_gates": basis_gates, + "basis_gates": list(basis_gates), } list_transpile_args = [] diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 3a6de35f49a7..21cda77b5345 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1425,7 +1425,7 @@ def test_target_ideal_gates(self, opt_level): theta = Parameter("θ") phi = Parameter("ϕ") lam = Parameter("λ") - target = Target() + target = Target(2) target.add_instruction(UGate(theta, phi, lam)) target.add_instruction(CXGate()) target.add_instruction(Measure()) From 559eb7be7335a79d96158fad2fb3dd6f1bce87f5 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 24 Mar 2022 09:26:19 -0400 Subject: [PATCH 06/17] Simplify arg parsing logic --- qiskit/compiler/transpiler.py | 77 ++++++------------------ test/python/providers/test_backend_v2.py | 2 +- 2 files changed, 19 insertions(+), 60 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 51338b605eea..107aa851be69 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=import-error,invalid-sequence-index + """Circuit transpile function""" import datetime import io @@ -552,31 +554,19 @@ def _parse_transpile_args( if backend_properties is None: backend_properties = _target_to_backend_properties(target) - basis_gates = _parse_basis_gates(basis_gates, backend, circuits) - inst_map = _parse_inst_map(inst_map, backend, num_circuits) + basis_gates = _parse_basis_gates(basis_gates, backend) + initial_layout = _parse_initial_layout(initial_layout, circuits) + inst_map = _parse_inst_map(inst_map, backend) faulty_qubits_map = _parse_faulty_qubits_map(backend, num_circuits) - coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits) - backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits) + coupling_map = _parse_coupling_map(coupling_map, backend) + backend_properties = _parse_backend_properties(backend_properties, backend) backend_num_qubits = _parse_backend_num_qubits(backend, num_circuits) - initial_layout = _parse_initial_layout(initial_layout, circuits) - layout_method = _parse_layout_method(layout_method, num_circuits) - routing_method = _parse_routing_method(routing_method, num_circuits) - translation_method = _parse_translation_method(translation_method, num_circuits) - approximation_degree = _parse_approximation_degree(approximation_degree, num_circuits) - unitary_synthesis_method = _parse_unitary_synthesis_method( - unitary_synthesis_method, num_circuits - ) - unitary_synthesis_plugin_config = _parse_unitary_plugin_config( - unitary_synthesis_plugin_config, num_circuits - ) - seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) - optimization_level = _parse_optimization_level(optimization_level, num_circuits) + approximation_degree = _parse_approximation_degree(approximation_degree) output_name = _parse_output_name(output_name, circuits) callback = _parse_callback(callback, num_circuits) durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits) - scheduling_method = _parse_scheduling_method(scheduling_method, num_circuits) timing_constraints = _parse_timing_constraints(backend, timing_constraints, num_circuits) - target = _parse_target(backend, target, num_circuits) + target = _parse_target(backend, target) if scheduling_method and any(d is None for d in durations): raise TranspilerError( "Transpiling a circuit with a scheduling method" @@ -590,7 +580,7 @@ def _parse_transpile_args( } shared_dict = { "optimization_level": optimization_level, - "basis_gates": list(basis_gates), + "basis_gates": basis_gates, } list_transpile_args = [] @@ -667,7 +657,7 @@ def _create_faulty_qubits_map(backend): return faulty_qubits_map -def _parse_basis_gates(basis_gates, backend, circuits): +def _parse_basis_gates(basis_gates, backend): # try getting basis_gates from user, else backend if basis_gates is None: backend_version = getattr(backend, "version", 0) @@ -678,11 +668,12 @@ def _parse_basis_gates(basis_gates, backend, circuits): basis_gates = getattr(backend.configuration(), "basis_gates", None) else: basis_gates = backend.operation_names - + if basis_gates is not None: + basis_gates = list(basis_gates) return basis_gates -def _parse_inst_map(inst_map, backend, num_circuits): +def _parse_inst_map(inst_map, backend): # try getting inst_map from user, else backend if inst_map is None: backend_version = getattr(backend, "version", 0) @@ -696,7 +687,7 @@ def _parse_inst_map(inst_map, backend, num_circuits): return inst_map -def _parse_coupling_map(coupling_map, backend, num_circuits): +def _parse_coupling_map(coupling_map, backend): # try getting coupling_map from user, else backend if coupling_map is None: backend_version = getattr(backend, "version", 0) @@ -820,7 +811,7 @@ def _target_to_backend_properties(target: Target): return None -def _parse_backend_properties(backend_properties, backend, num_circuits): +def _parse_backend_properties(backend_properties, backend): # try getting backend_properties from user, else backend if backend_properties is None: backend_version = getattr(backend, "version", None) @@ -916,22 +907,6 @@ def _layout_from_raw(initial_layout, circuit): return initial_layout -def _parse_layout_method(layout_method, num_circuits): - return layout_method - - -def _parse_routing_method(routing_method, num_circuits): - return routing_method - - -def _parse_translation_method(translation_method, num_circuits): - return translation_method - - -def _parse_scheduling_method(scheduling_method, num_circuits): - return scheduling_method - - def _parse_instruction_durations(backend, inst_durations, dt, circuits): """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided, the backend will be ignored, otherwise, the durations will be populated from the @@ -965,7 +940,7 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits): return durations -def _parse_approximation_degree(approximation_degree, num_circuits): +def _parse_approximation_degree(approximation_degree): if approximation_degree is None: return None if not isinstance(approximation_degree, list): @@ -977,29 +952,13 @@ def _parse_approximation_degree(approximation_degree, num_circuits): return approximation_degree -def _parse_unitary_synthesis_method(unitary_synthesis_method, num_circuits): - return unitary_synthesis_method - - -def _parse_unitary_plugin_config(unitary_synthesis_plugin_config, num_circuits): - return unitary_synthesis_plugin_config - - -def _parse_target(backend, target, num_circuits): +def _parse_target(backend, target): backend_target = getattr(backend, "target", None) if target is None: target = backend_target return target -def _parse_seed_transpiler(seed_transpiler, num_circuits): - return seed_transpiler - - -def _parse_optimization_level(optimization_level, num_circuits): - return optimization_level - - def _parse_callback(callback, num_circuits): if not isinstance(callback, list): callback = [callback] * num_circuits diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 21c3c1813598..c826b156d3cd 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -170,5 +170,5 @@ def test_transpile_mumbai_target(self): def test_transpile_parse_inst_map(self): """Test that transpiler._parse_inst_map() supports BackendV2.""" - inst_map = _parse_inst_map(inst_map=None, backend=self.backend, num_circuits=1) + inst_map = _parse_inst_map(inst_map=None, backend=self.backend) self.assertIsInstance(inst_map, InstructionScheduleMap) From 2bf132fb40c38e165e96ef47ca2cafa7f52d0162 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 24 Mar 2022 10:06:59 -0400 Subject: [PATCH 07/17] Only use shared memory when running in parallel The serialization overhead of using shared memory is only necessary when we're running in parallel. If we're running serially there is no need to serialize the transpiler arguments and write them to shared memory since serially we are running the same memory space. This commit splits the code path between serial and parallel and only uses shared memory when we are running in parallel. --- qiskit/compiler/transpiler.py | 185 ++++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 67 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 107aa851be69..5abe3d005e0a 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -17,6 +17,7 @@ import io from itertools import cycle import logging +import os import pickle import sys from time import time @@ -284,48 +285,72 @@ def callback_func(**kwargs): UserWarning, ) - with SharedMemoryManager() as smm: - with io.BytesIO() as buf: - # Get transpile_args to configure the circuit transpilation job(s) - unique_transpile_args, shared_args = _parse_transpile_args( - circuits, - backend, - basis_gates, - inst_map, - coupling_map, - backend_properties, - initial_layout, - layout_method, - routing_method, - translation_method, - scheduling_method, - instruction_durations, - dt, - approximation_degree, - seed_transpiler, - optimization_level, - callback, - output_name, - timing_constraints, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - target, + unique_transpile_args, shared_args = _parse_transpile_args( + circuits, + backend, + basis_gates, + inst_map, + coupling_map, + backend_properties, + initial_layout, + layout_method, + routing_method, + translation_method, + scheduling_method, + instruction_durations, + dt, + approximation_degree, + seed_transpiler, + optimization_level, + callback, + output_name, + timing_constraints, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + target, + ) + # Get transpile_args to configure the circuit transpilation job(s) + if coupling_map in unique_transpile_args: + cmap_conf = unique_transpile_args["coupling_map"] + else: + cmap_conf = [shared_args["coupling_map"]] * len(circuits) + _check_circuits_coupling_map(circuits, cmap_conf, backend) + if ( + len(circuits) > 1 + and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE" + and parallel.PARALLEL_DEFAULT + ): + try: + os.environ["QISKIT_IN_PARALLEL"] = "TRUE" + with SharedMemoryManager() as smm: + with io.BytesIO() as buf: + pickle.dump(shared_args, buf) + data = buf.getvalue() + smb = smm.SharedMemory(size=len(data)) + smb.buf[:] = data[:] + # Transpile circuits in parallel + circuits = parallel.parallel_map( + _transpile_circuit, + list(zip(circuits, cycle([smb.name]), unique_transpile_args)), + ) + finally: + os.environ["QISKIT_IN_PARALLEL"] = "FALSE" + else: + output_circuits = [] + for circuit, unique_args in zip(circuits, unique_transpile_args): + transpile_config, pass_manager = _combine_args(shared_args, unique_args) + output_circuits.append( + _serial_transpile_circuit( + circuit, + pass_manager, + transpile_config["callback"], + transpile_config["output_name"], + transpile_config["backend_num_qubits"], + transpile_config["faulty_qubits_map"], + transpile_config["pass_manager_config"].backend_properties, + ) ) - if coupling_map in unique_transpile_args: - cmap_conf = unique_transpile_args["coupling_map"] - else: - cmap_conf = [shared_args["coupling_map"]] * len(circuits) - _check_circuits_coupling_map(circuits, cmap_conf, backend) - pickle.dump(shared_args, buf) - data = buf.getvalue() - smb = smm.SharedMemory(size=len(data)) - smb.buf[:] = data[:] - # Transpile circuits in parallel - circuits = parallel.parallel_map( - _transpile_circuit, - list(zip(circuits, cycle([smb.name]), unique_transpile_args)), - ) - + circuits = output_circuits end_time = time() _log_transpile_time(start_time, end_time) @@ -368,32 +393,8 @@ def _log_transpile_time(start_time, end_time): logger.info(log_msg) -def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> QuantumCircuit: - """Select a PassManager and run a single circuit through it. - Args: - circuit_config_tuple (tuple): - circuit (QuantumCircuit): circuit to transpile - transpile_config (dict): configuration dictating how to transpile. The - dictionary has the following format: - {'optimization_level': int, - 'output_name': string, - 'callback': callable, - 'pass_manager_config': PassManagerConfig} - Returns: - The transpiled circuit - Raises: - TranspilerError: if transpile_config is not valid or transpilation incurs error - """ - circuit, name, unique_config = circuit_config_tuple - existing_shm = SharedMemory(name=name) - try: - with io.BytesIO(existing_shm.buf) as buf: - shared_transpiler_args = pickle.load(buf) - finally: - existing_shm.close() - +def _combine_args(shared_transpiler_args, unique_config): optimization_level = shared_transpiler_args.pop("optimization_level") - # backend_num_qubits = shared_transpiler_args.pop("backend_num_qubits") pass_manager_config = shared_transpiler_args pass_manager_config.update(unique_config.pop("pass_manager_config")) pass_manager_config = PassManagerConfig(**pass_manager_config) @@ -401,7 +402,6 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua transpile_config = unique_config transpile_config["pass_manager_config"] = pass_manager_config transpile_config["optimization_level"] = optimization_level - # transpile_config["backend_num_qubits"] = backend_num_qubits if transpile_config["faulty_qubits_map"]: pass_manager_config.initial_layout = _remap_layout_faulty_backend( @@ -410,6 +410,7 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua # we choose an appropriate one based on desired optimization level level = transpile_config["optimization_level"] + shared_transpiler_args["optimization_level"] = optimization_level if level == 0: pass_manager = level_0_pass_manager(pass_manager_config) @@ -421,6 +422,56 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua pass_manager = level_3_pass_manager(pass_manager_config) else: raise TranspilerError("optimization_level can range from 0 to 3.") + return transpile_config, pass_manager + + +def _serial_transpile_circuit( + circuit, + pass_manager, + callback, + output_name, + num_qubits, + faulty_qubits_map=None, + backend_prop=None, +): + result = pass_manager.run(circuit, callback=callback, output_name=output_name) + if faulty_qubits_map: + return _remap_circuit_faulty_backend( + result, + num_qubits, + backend_prop, + faulty_qubits_map, + ) + + return result + + +def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> QuantumCircuit: + """Select a PassManager and run a single circuit through it. + Args: + circuit_config_tuple (tuple): + circuit (QuantumCircuit): circuit to transpile + transpile_config (dict): configuration dictating how to transpile. The + dictionary has the following format: + {'optimization_level': int, + 'output_name': string, + 'callback': callable, + 'pass_manager_config': PassManagerConfig} + Returns: + The transpiled circuit + Raises: + TranspilerError: if transpile_config is not valid or transpilation incurs error + """ + circuit, name, unique_config = circuit_config_tuple + existing_shm = SharedMemory(name=name) + try: + with io.BytesIO(existing_shm.buf) as buf: + shared_transpiler_args = pickle.load(buf) + finally: + existing_shm.close() + + transpile_config, pass_manager = _combine_args(shared_transpiler_args, unique_config) + pass_manager_config = transpile_config["pass_manager_config"] result = pass_manager.run( circuit, callback=transpile_config["callback"], output_name=transpile_config["output_name"] From eafbf8bfbb74d6033065da9fbb5a5da996e6f734 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 3 Jun 2022 07:54:05 -0400 Subject: [PATCH 08/17] Fix handling of ideal gates in UnitarySynthesis The move to only having one copy of shared arguments fixed an issue in the handling of basis gates in some cases where a list input was not properly wrapped in a list. This was then causing UnitarySynthesis pass to be skipped in some cases because the target basis was incomplete (and was only a single gate) which wouldn't match any of the basis the synthesis pass could work in. By fixing this issue with shared memory a bug in UnitarySynthesis was exposed when trying to run the pass with no coupling map but a Target (i.e. an ideal simulator) the pass would fail because it assumed if there was a Target there was a coupling map and would try to use the coupling map to find the natural direction to synthesize the 2q gates into. This commit fixes the unitary synthesis pass to ignore the natural direction if no coupling map is preset even if there is a target. The test triggering this condition is then updated to update the result to include the output of the unitary synthesis pass. --- .../passes/synthesis/unitary_synthesis.py | 24 ++++++++++++------- test/python/compiler/test_transpiler.py | 18 +++++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a979812cd894..752515c7bd77 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -569,15 +569,21 @@ def _synth_natural_direction( if natural_direction in {None, True} and ( coupling_map or (target is not None and decomposer2q and not preferred_direction) ): - cmap = coupling_map - neighbors0 = cmap.neighbors(qubits[0]) - zero_one = qubits[1] in neighbors0 - neighbors1 = cmap.neighbors(qubits[1]) - one_zero = qubits[0] in neighbors1 - if zero_one and not one_zero: - preferred_direction = [0, 1] - if one_zero and not zero_one: - preferred_direction = [1, 0] + if coupling_map is not None: + cmap = coupling_map + else: + cmap = target.build_coupling_map() + # If we don't have a defined coupling map (either from the input) + # or from the target we can't check for a natural direction + if cmap is not None: + neighbors0 = cmap.neighbors(qubits[0]) + zero_one = qubits[1] in neighbors0 + neighbors1 = cmap.neighbors(qubits[1]) + one_zero = qubits[0] in neighbors1 + if zero_one and not one_zero: + preferred_direction = [0, 1] + if one_zero and not zero_one: + preferred_direction = [1, 0] if ( natural_direction in {None, True} and preferred_direction is None diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 0c60a1def5d2..7f75bc5456b5 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1449,9 +1449,21 @@ def test_target_ideal_gates(self, opt_level): qc.measure(qubit_reg, clbit_reg) result = transpile(qc, target=target, optimization_level=opt_level) expected = QuantumCircuit(qubit_reg, clbit_reg) - expected.u(np.pi / 2, 0, np.pi, qubit_reg[0]) - expected.cx(qubit_reg[0], qubit_reg[1]) - expected.measure(qubit_reg, clbit_reg) + # The Unitary synthesis optimization pass results for optimization level 3 + # results in a different output than the other optimization levels + if opt_level == 3: + expected.u(np.pi / 2, -2.573974639041118, -np.pi, qubit_reg[0]) + expected.u(2.856600312467528, -np.pi / 2, -np.pi / 2, qubit_reg[1]) + expected.cx(qubit_reg[0], qubit_reg[1]) + expected.u( + 3.0323422787222316e-16, 0.7742010131357828, -1.3418190276844602, qubit_reg[0] + ) + expected.u(2.8566003124675303, -np.pi / 2, -np.pi / 2, qubit_reg[1]) + expected.measure(qubit_reg, clbit_reg) + else: + expected.u(np.pi / 2, 0, np.pi, qubit_reg[0]) + expected.cx(qubit_reg[0], qubit_reg[1]) + expected.measure(qubit_reg, clbit_reg) self.assertEqual(result, expected) From fe87445eb475814a4b84d3452f92d4eafae47d69 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 3 Jun 2022 09:41:46 -0400 Subject: [PATCH 09/17] Fix lint failure on python 3.7 This commit fixes a lint failure on Python 3.7 by disabling a module name check for the shared memory attributes in the multiprocessing module. These were added in Python 3.8 and don't exist in older python versions. For python 3.7 we use a backport library to make up for this, but pylint doesn't know to conditionally check the stdlib import statement as it is only used on Python >= 3.8. To avoid the failure we just disable checking the imports from stdlib. --- qiskit/compiler/transpiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 5753e559bdfb..5b0d9d002d0a 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -49,8 +49,8 @@ from qiskit.transpiler.target import Target, target_to_backend_properties if sys.version_info >= (3, 8): - from multiprocessing.shared_memory import SharedMemory - from multiprocessing.managers import SharedMemoryManager + from multiprocessing.shared_memory import SharedMemory # pylint: disable=no-name-in-module + from multiprocessing.managers import SharedMemoryManager # pylint: disable=no-name-in-module else: from shared_memory import SharedMemory, SharedMemoryManager From 4ffe9b8b93df8173c68e2d91fe4036a0446df0ef Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 3 Jun 2022 10:41:41 -0400 Subject: [PATCH 10/17] Add release notes --- ...ed-memory-dependency-1e32e1c55902216f.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 releasenotes/notes/shared-memory-dependency-1e32e1c55902216f.yaml diff --git a/releasenotes/notes/shared-memory-dependency-1e32e1c55902216f.yaml b/releasenotes/notes/shared-memory-dependency-1e32e1c55902216f.yaml new file mode 100644 index 000000000000..ce92dd95b2e1 --- /dev/null +++ b/releasenotes/notes/shared-memory-dependency-1e32e1c55902216f.yaml @@ -0,0 +1,21 @@ +--- +upgrade: + - | + For Python 3.7 `shared-memory38 `__ + is now a dependency. This was added as a dependency for Python 3.7 to enable + leveraging the shared memory constructs in the standard library of newer + versions of Python. If you're running on Python >= 3.8 there is no extra + dependency required. +fixes: + - | + Fixed an issue with :func:`~.transpile` where in some cases providing a + list of basis gate strings with the ``basis_gates`` keyword argument or + implicitly via a :class:`~.Target` input via the ``target`` keyword + argument would not be interpreted correctly and result in a subset of the + listed gates being used for each circuit. + - | + Fixed an issue in the :class:`~.UnitarySynthesis` transpiler pass which + would result in an error when a :class:`~.Target` that didn't have any + qubit restrictions on the operations (e.g. in the case of an ideal + simulator target) was specified with the ``target`` keyword argument for the + constructor. From a0bf2b2c77c6e74e9ddc3f86da55bc8f53a7268e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 7 Jun 2022 08:59:07 -0400 Subject: [PATCH 11/17] Relax equality check on test_target_ideal_gates opt_level 3 This commit relaxes the output circuit equality check on the optimization level 3 variant of the test_target_ideal_gates() test. Since this branch fixes the basis gate handling so that the UnitarySynthesis pass is actually running now the exact output is dependent on the 2q synthesis routine which on different platforms has subtlely different but equally correct results based on differences in the the floating point implementation. To avoid spurious CI failures this commit changes the test just check the output unitary of the circuit is equivalent to the input for optimization level 3. --- test/python/compiler/test_transpiler.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 7f75bc5456b5..165461eb0bf8 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1446,25 +1446,23 @@ def test_target_ideal_gates(self, opt_level): qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") qc.h(qubit_reg[0]) qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) + if opt_level != 3: + qc.measure(qubit_reg, clbit_reg) result = transpile(qc, target=target, optimization_level=opt_level) - expected = QuantumCircuit(qubit_reg, clbit_reg) # The Unitary synthesis optimization pass results for optimization level 3 # results in a different output than the other optimization levels + # and can differ based on fp precision. To avoid relying on a hard match + # do a unitary equiv + if opt_level == 3: - expected.u(np.pi / 2, -2.573974639041118, -np.pi, qubit_reg[0]) - expected.u(2.856600312467528, -np.pi / 2, -np.pi / 2, qubit_reg[1]) - expected.cx(qubit_reg[0], qubit_reg[1]) - expected.u( - 3.0323422787222316e-16, 0.7742010131357828, -1.3418190276844602, qubit_reg[0] - ) - expected.u(2.8566003124675303, -np.pi / 2, -np.pi / 2, qubit_reg[1]) - expected.measure(qubit_reg, clbit_reg) + result_op = Operator.from_circuit(result) + self.assertTrue(result_op.equiv(qc)) else: + expected = QuantumCircuit(qubit_reg, clbit_reg) expected.u(np.pi / 2, 0, np.pi, qubit_reg[0]) expected.cx(qubit_reg[0], qubit_reg[1]) expected.measure(qubit_reg, clbit_reg) - self.assertEqual(result, expected) + self.assertEqual(result, expected) class StreamHandlerRaiseException(StreamHandler): From 14e734babf6453779648852d1bb38c100a4972fc Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 8 Jun 2022 08:14:19 -0400 Subject: [PATCH 12/17] Only try to write pickle buffer length to shared memory In CI we were seeing failures on the macOS Python 3.7 environment around writing the pickle bytes data to the memory view of the shared memory object that we allocated. This failure was being caused by the macOS shared memory backport library over allocating the shared memory object and the size of the memory view buffer being larger than the pickle data. This caused the assignment of the pickle data to the memory view to fail because the lengths didn't match. This commit fixes this by explicitly only writing the bytes equal to the pickle length into memory. This ensures no matter how big the allocated memory view is we are always writing the correct size. Co-Authored-By: Jake Lishman --- qiskit/compiler/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 5b0d9d002d0a..45ff42d1ff7e 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -327,7 +327,7 @@ def callback_func(**kwargs): pickle.dump(shared_args, buf) data = buf.getvalue() smb = smm.SharedMemory(size=len(data)) - smb.buf[:] = data[:] + smb.buf[:len(data)] = data[:] # Transpile circuits in parallel circuits = parallel.parallel_map( _transpile_circuit, From d879984288c02284b03407723c01316809eb2a35 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 8 Jun 2022 08:48:03 -0400 Subject: [PATCH 13/17] Actually commit black changes --- qiskit/compiler/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 45ff42d1ff7e..9c45ca48df53 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -327,7 +327,7 @@ def callback_func(**kwargs): pickle.dump(shared_args, buf) data = buf.getvalue() smb = smm.SharedMemory(size=len(data)) - smb.buf[:len(data)] = data[:] + smb.buf[: len(data)] = data[:] # Transpile circuits in parallel circuits = parallel.parallel_map( _transpile_circuit, From 0ecb81d8134e5194b1f450bcd05ba43f357b0c2e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 8 Jun 2022 08:55:29 -0400 Subject: [PATCH 14/17] Remove manual manipulation of QISKIT_IN_PARALLEL This commit removes a leftover from earlier iterations of the PR that were manually manipulation of QISKIT_IN_PARALLEL. This was done earlier in the PR branch because parallel_map was manually replaced with multiprocessing directly. However, after refining the use of shared memory this wasn't needed and parallel_map was used instead. --- qiskit/compiler/transpiler.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 9c45ca48df53..6e091c68535a 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -320,21 +320,17 @@ def callback_func(**kwargs): and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE" and parallel.PARALLEL_DEFAULT ): - try: - os.environ["QISKIT_IN_PARALLEL"] = "TRUE" - with SharedMemoryManager() as smm: - with io.BytesIO() as buf: - pickle.dump(shared_args, buf) - data = buf.getvalue() - smb = smm.SharedMemory(size=len(data)) - smb.buf[: len(data)] = data[:] - # Transpile circuits in parallel - circuits = parallel.parallel_map( - _transpile_circuit, - list(zip(circuits, cycle([smb.name]), unique_transpile_args)), - ) - finally: - os.environ["QISKIT_IN_PARALLEL"] = "FALSE" + with SharedMemoryManager() as smm: + with io.BytesIO() as buf: + pickle.dump(shared_args, buf) + data = buf.getvalue() + smb = smm.SharedMemory(size=len(data)) + smb.buf[: len(data)] = data[:] + # Transpile circuits in parallel + circuits = parallel.parallel_map( + _transpile_circuit, + list(zip(circuits, cycle([smb.name]), unique_transpile_args)), + ) else: output_circuits = [] for circuit, unique_args in zip(circuits, unique_transpile_args): From bb2f8e223139f5eea49cfa022f92518a76ca56de Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Jun 2022 15:57:33 -0400 Subject: [PATCH 15/17] Remove unecessary optimization_level storage --- qiskit/compiler/transpiler.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index df51ad71ed92..c8bd08aca1f2 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -390,14 +390,18 @@ def _log_transpile_time(start_time, end_time): def _combine_args(shared_transpiler_args, unique_config): - optimization_level = shared_transpiler_args.pop("optimization_level") + # Pop optimization_level to exclude it from the kwargs when building a + # PassManagerConfig + level = shared_transpiler_args.pop("optimization_level") pass_manager_config = shared_transpiler_args pass_manager_config.update(unique_config.pop("pass_manager_config")) pass_manager_config = PassManagerConfig(**pass_manager_config) + # restore optimization_level in the input shared dict in case it's used again + # in the same process + shared_transpiler_args["optimization_level"] = level transpile_config = unique_config transpile_config["pass_manager_config"] = pass_manager_config - transpile_config["optimization_level"] = optimization_level if transpile_config["faulty_qubits_map"]: pass_manager_config.initial_layout = _remap_layout_faulty_backend( @@ -405,9 +409,6 @@ def _combine_args(shared_transpiler_args, unique_config): ) # we choose an appropriate one based on desired optimization level - level = transpile_config["optimization_level"] - shared_transpiler_args["optimization_level"] = optimization_level - if level == 0: pass_manager = level_0_pass_manager(pass_manager_config) elif level == 1: From 67f204918172d92d2fbe9942c1e757509e0b5b55 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Jun 2022 16:08:33 -0400 Subject: [PATCH 16/17] Update docs and type hints --- qiskit/compiler/transpiler.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index c8bd08aca1f2..d1c349969d7f 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -443,17 +443,14 @@ def _serial_transpile_circuit( return result -def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> QuantumCircuit: +def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, str, Dict]) -> QuantumCircuit: """Select a PassManager and run a single circuit through it. Args: circuit_config_tuple (tuple): circuit (QuantumCircuit): circuit to transpile - transpile_config (dict): configuration dictating how to transpile. The - dictionary has the following format: - {'optimization_level': int, - 'output_name': string, - 'callback': callable, - 'pass_manager_config': PassManagerConfig} + name (str): The name of the shared memory object containing a pickled dict of shared + arguments between parallel works + unique_config (dict): configuration dictating unique arguments for transpile. Returns: The transpiled circuit Raises: @@ -563,7 +560,7 @@ def _parse_transpile_args( unitary_synthesis_method, unitary_synthesis_plugin_config, target, -) -> List[Dict]: +) -> Tuple[List[Dict], Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on what types of inputs are allowed. @@ -573,7 +570,8 @@ def _parse_transpile_args( arg has more priority than the arg set by backend). Returns: - list[dicts]: a list of transpile parameters. + Tuple[list[dict], dict]: a tuple contain a list of unique transpile parameter dicts and + the second element contains a dict of shared transpiler argument across all circuits. Raises: TranspilerError: If instruction_durations are required but not supplied or found. From 86448759b42491269c2320f7b9d4e6a126f200d8 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Jun 2022 16:18:26 -0400 Subject: [PATCH 17/17] Remove unnecessary list cast for basis gates --- qiskit/compiler/transpiler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index d1c349969d7f..ac9ccc7c0db4 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -710,8 +710,6 @@ def _parse_basis_gates(basis_gates, backend): basis_gates = getattr(backend.configuration(), "basis_gates", None) else: basis_gates = backend.operation_names - if basis_gates is not None: - basis_gates = list(basis_gates) return basis_gates