From 1814a5aea32d3ccebd7b0a9d630b417927163a27 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 1 May 2023 15:32:25 -0400 Subject: [PATCH 1/6] Set max_trials for VF2Layout in preset pass managers. By setting max_trials, we limit the number of layouts enumerated and scored when iterating through vf2_mapping(). This is necessary for scoring to complete in a reasonable amount of time for circuits with many connected components on larger (e.g. 400 qubit) devices. These limits were chosen using a fake 400 qubit device, using 200 connected components, where each component is a single CX gate. Because layout scoring scales linearly with the number of qubits in the circuit, 250,000 (O3) takes abount a minute, 25,000 (O2) takes about 6 seconds, and 2,500 (O1) takes less than a second. --- .../transpiler/preset_passmanagers/level1.py | 1 + .../transpiler/preset_passmanagers/level2.py | 1 + .../transpiler/preset_passmanagers/level3.py | 1 + ...set-pm-vf2-max-trials-958bb8a36fff472f.yaml | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index bb4413b03219..6f57e00a5d58 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -133,6 +133,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e4), # Set call limit to ~100ms with rustworkx 0.10.2 properties=backend_properties, target=target, + max_trials=int(2500), # Limits layout scoring to < 600ms on ~400 qubit devices ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index c7313cf4475d..00bf89d9e455 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -118,6 +118,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e6), # Set call limit to ~10 sec with rustworkx 0.10.2 properties=backend_properties, target=target, + max_trials=int(25000), # Limits layout scoring to < 6 sec on ~400 qubit devices ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index cc20771d9e54..bcdf92614e62 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -124,6 +124,7 @@ def _vf2_match_not_found(property_set): call_limit=int(3e7), # Set call limit to ~60 sec with rustworkx 0.10.2 properties=backend_properties, target=target, + max_trials=int(250000), # Limits layout scoring to < 60 sec on ~400 qubit devices ) ) diff --git a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml new file mode 100644 index 000000000000..331c29192c21 --- /dev/null +++ b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml @@ -0,0 +1,18 @@ +--- +upgrade: + - | + The maximum number of trials evaluated when searching for the best + layout using :class:`.VF2Layout` is now limited in + :func:`qiskit.transpiler.preset_passmanagers.level_1_pass_manager`, + :func:`qiskit.transpiler.preset_passmanagers.level_2_pass_manager`, + and + :func:`qiskit.transpiler.preset_passmanagers.level_3_pass_manager` + to ``2,500``, ``25,000``, and ``250,000``, respectively. Previously, + all possible layouts were evaluated. To perform a full search as + before, manually run :class:`.VF2PostLayout` over the transpiled circuit, + in strict mode, specifying ``0`` for ``max_trials``. +fixes: + - | + Fixed a potential performance scaling issue with layout scoring in preset + pass managers, which could occur when transpiling circuits with many + connected components on large devices. From 9c0e5e8ba6aba09d914b3754ede47f0f4bd41b6b Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 1 May 2023 22:55:25 -0400 Subject: [PATCH 2/6] Address review comments. --- .../preset_passmanagers/builtin_plugins.py | 19 +++++-- .../transpiler/preset_passmanagers/common.py | 50 ++++++++++++++++++- .../transpiler/preset_passmanagers/level1.py | 2 +- .../transpiler/preset_passmanagers/level2.py | 2 +- .../transpiler/preset_passmanagers/level3.py | 2 +- ...et-pm-vf2-max-trials-958bb8a36fff472f.yaml | 11 ++-- 6 files changed, 74 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ca4c54494d34..8a44950d4367 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -37,7 +37,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana else: routing_pass = BasicSwap(target) - vf2_call_limit = common.get_vf2_call_limit( + vf2_call_limit, vf2_max_trials = common.get_vf2_limits( optimization_level, pass_manager_config.layout_method, pass_manager_config.initial_layout, @@ -56,6 +56,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, check_trivial=True, @@ -67,6 +68,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -77,6 +79,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -96,7 +99,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana if coupling_map_routing is None: coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties - vf2_call_limit = common.get_vf2_call_limit( + vf2_call_limit, vf2_max_trials = common.get_vf2_limits( optimization_level, pass_manager_config.layout_method, pass_manager_config.initial_layout, @@ -120,6 +123,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, check_trivial=True, @@ -131,6 +135,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -150,7 +155,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana if coupling_map_routing is None: coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties - vf2_call_limit = common.get_vf2_call_limit( + vf2_call_limit, vf2_max_trials = common.get_vf2_limits( optimization_level, pass_manager_config.layout_method, pass_manager_config.initial_layout, @@ -171,6 +176,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, check_trivial=True, @@ -183,6 +189,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -194,6 +201,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -213,7 +221,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana if coupling_map_routing is None: coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties - vf2_call_limit = common.get_vf2_call_limit( + vf2_call_limit, vf2_max_trials = common.get_vf2_limits( optimization_level, pass_manager_config.layout_method, pass_manager_config.initial_layout, @@ -244,6 +252,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, check_trivial=True, @@ -261,6 +270,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, @@ -277,6 +287,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target, coupling_map=coupling_map, vf2_call_limit=vf2_call_limit, + vf2_max_trials=vf2_max_trials, backend_properties=backend_properties, seed_transpiler=seed_transpiler, use_barrier_before_measurement=True, diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index d8d3bc400e50..02e73a3c91e2 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -51,6 +51,7 @@ from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout +from qiskit.utils.deprecation import deprecate_func _CONTROL_FLOW_OP_NAMES = {"for_loop", "if_else", "while_loop", "switch_case"} @@ -263,6 +264,7 @@ def generate_routing_passmanager( seed_transpiler=None, check_trivial=False, use_barrier_before_measurement=True, + vf2_max_trials=None, ): """Generate a routing :class:`~qiskit.transpiler.PassManager` @@ -273,7 +275,8 @@ def generate_routing_passmanager( coupling_map (CouplingMap): The coupling map of the backend to route for vf2_call_limit (int): The internal call limit for the vf2 post layout - pass. If this is ``None`` the vf2 post layout will not be run. + pass. If this is ``None`` or ``0`` the vf2 post layout will not be + run. backend_properties (BackendProperties): Properties of a backend to synthesize for (e.g. gate fidelities). seed_transpiler (int): Sets random seed for the stochastic parts of @@ -287,6 +290,9 @@ def generate_routing_passmanager( use_barrier_before_measurement (bool): If true (the default) the :class:`~.BarrierBeforeFinalMeasurements` transpiler pass will be run prior to the specified pass in the ``routing_pass`` argument. + vf2_max_trials (int): The maximum number of trials to run VF2 when + evaluating the vf2 post layout + pass. If this is ``None`` or ``0`` the vf2 post layout will not be run. Returns: PassManager: The routing pass manager """ @@ -314,7 +320,8 @@ def _swap_condition(property_set): else: routing.append([routing_pass], condition=_swap_condition) - if (target is not None or backend_properties is not None) and vf2_call_limit is not None: + is_vf2_fully_bounded = vf2_call_limit and vf2_max_trials + if (target is not None or backend_properties is not None) and is_vf2_fully_bounded: routing.append( VF2PostLayout( target, @@ -322,6 +329,7 @@ def _swap_condition(property_set): backend_properties, seed_transpiler, call_limit=vf2_call_limit, + max_trials=vf2_max_trials, strict_direction=False, ), condition=_run_post_layout_condition, @@ -539,6 +547,10 @@ def _require_alignment(property_set): return scheduling +@deprecate_func( + additional_msg="Instead, use ``get_vf2_limits``.", + since="0.24.0", +) def get_vf2_call_limit( optimization_level: int, layout_method: Optional[str] = None, @@ -554,3 +566,37 @@ def get_vf2_call_limit( elif optimization_level == 3: vf2_call_limit = int(3e7) # Set call limit to ~60 sec with rustworkx 0.10.2 return vf2_call_limit + + +VF2Limits = collections.namedtuple("VF2Limits", ("call_limit", "max_trials")) + + +def get_vf2_limits( + optimization_level: int, + layout_method: Optional[str] = None, + initial_layout: Optional[Layout] = None, +) -> Optional[VF2Limits]: + """Get the VF2 limits for VF2-based layout passes. + + Returns: + Optional[VF2Limits]: An optional namedtuple with elements + ``call_limit`` and ``max_trials``. + """ + limits = None + if layout_method is None and initial_layout is None: + if optimization_level == 1: + limits = VF2Limits( + int(5e4), # Set call limit to ~100ms with rustworkx 0.10.2 + 2500, # Limits layout scoring to < 600ms on ~400 qubit devices + ) + elif optimization_level == 2: + limits = VF2Limits( + int(5e6), # Set call limit to ~10 sec with rustworkx 0.10.2 + 25000, # Limits layout scoring to < 6 sec on ~400 qubit devices + ) + elif optimization_level == 3: + limits = VF2Limits( + int(3e7), # Set call limit to ~60 sec with rustworkx 0.10.2 + 250000, # Limits layout scoring to < 60 sec on ~400 qubit devices + ) + return limits diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 6f57e00a5d58..46045b54ef54 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -133,7 +133,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e4), # Set call limit to ~100ms with rustworkx 0.10.2 properties=backend_properties, target=target, - max_trials=int(2500), # Limits layout scoring to < 600ms on ~400 qubit devices + max_trials=2500, # Limits layout scoring to < 600ms on ~400 qubit devices ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 00bf89d9e455..900cb2668e93 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -118,7 +118,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e6), # Set call limit to ~10 sec with rustworkx 0.10.2 properties=backend_properties, target=target, - max_trials=int(25000), # Limits layout scoring to < 6 sec on ~400 qubit devices + max_trials=25000, # Limits layout scoring to < 6 sec on ~400 qubit devices ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index bcdf92614e62..57cfa5d06201 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -124,7 +124,7 @@ def _vf2_match_not_found(property_set): call_limit=int(3e7), # Set call limit to ~60 sec with rustworkx 0.10.2 properties=backend_properties, target=target, - max_trials=int(250000), # Limits layout scoring to < 60 sec on ~400 qubit devices + max_trials=250000, # Limits layout scoring to < 60 sec on ~400 qubit devices ) ) diff --git a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml index 331c29192c21..f28aaf325056 100644 --- a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml +++ b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml @@ -2,14 +2,19 @@ upgrade: - | The maximum number of trials evaluated when searching for the best - layout using :class:`.VF2Layout` is now limited in + layout using :class:`.VF2Layout` and :class:`.VF2PostLayout` is now + limited in :func:`qiskit.transpiler.preset_passmanagers.level_1_pass_manager`, :func:`qiskit.transpiler.preset_passmanagers.level_2_pass_manager`, and :func:`qiskit.transpiler.preset_passmanagers.level_3_pass_manager` to ``2,500``, ``25,000``, and ``250,000``, respectively. Previously, - all possible layouts were evaluated. To perform a full search as - before, manually run :class:`.VF2PostLayout` over the transpiled circuit, + all possible layouts were evaluated. This change was made to prevent + transpilation from hanging during layout scoring for circuits with many + connected components on larger devices, which scales combinatorially + since each connected component must be evaluated in all possible + positions on the device. To perform a full search as + before, manually run :class:`.VF2PostLayout` over the transpiled circuit in strict mode, specifying ``0`` for ``max_trials``. fixes: - | From 0e315eba39c4af62541c678acfae4587293f3e91 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 2 May 2023 01:05:14 -0400 Subject: [PATCH 3/6] Return tuple of None instead for finer control and a better interface. --- qiskit/transpiler/preset_passmanagers/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 02e73a3c91e2..3d09e7dfb7d2 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -575,14 +575,14 @@ def get_vf2_limits( optimization_level: int, layout_method: Optional[str] = None, initial_layout: Optional[Layout] = None, -) -> Optional[VF2Limits]: +) -> VF2Limits: """Get the VF2 limits for VF2-based layout passes. Returns: - Optional[VF2Limits]: An optional namedtuple with elements + VF2Limits: An namedtuple with optional elements ``call_limit`` and ``max_trials``. """ - limits = None + limits = VF2Limits(None, None) if layout_method is None and initial_layout is None: if optimization_level == 1: limits = VF2Limits( From 9a6a4ca28fcef33c66e5f4168a19c04be0d7da89 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 2 May 2023 01:05:29 -0400 Subject: [PATCH 4/6] Add deprecation notice to release note. --- .../notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml index f28aaf325056..4d6d3ba13573 100644 --- a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml +++ b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml @@ -16,6 +16,13 @@ upgrade: positions on the device. To perform a full search as before, manually run :class:`.VF2PostLayout` over the transpiled circuit in strict mode, specifying ``0`` for ``max_trials``. +deprecations: + - | + The method + :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_call_limit` + is deprecated. Instead, use replacement method + :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_limits` and + access the ``call_limit`` field of the returned namedtuple. fixes: - | Fixed a potential performance scaling issue with layout scoring in preset From 5ef7a9435a0ff0d226f19051c6132ebbcba13b52 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 2 May 2023 10:13:54 -0400 Subject: [PATCH 5/6] Remove deprecation until 0.25.0. --- qiskit/transpiler/preset_passmanagers/common.py | 4 ---- .../notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml | 7 ------- 2 files changed, 11 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 3d09e7dfb7d2..00f9f4bcee8e 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -547,10 +547,6 @@ def _require_alignment(property_set): return scheduling -@deprecate_func( - additional_msg="Instead, use ``get_vf2_limits``.", - since="0.24.0", -) def get_vf2_call_limit( optimization_level: int, layout_method: Optional[str] = None, diff --git a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml index 4d6d3ba13573..f28aaf325056 100644 --- a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml +++ b/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml @@ -16,13 +16,6 @@ upgrade: positions on the device. To perform a full search as before, manually run :class:`.VF2PostLayout` over the transpiled circuit in strict mode, specifying ``0`` for ``max_trials``. -deprecations: - - | - The method - :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_call_limit` - is deprecated. Instead, use replacement method - :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_limits` and - access the ``call_limit`` field of the returned namedtuple. fixes: - | Fixed a potential performance scaling issue with layout scoring in preset From 89148c91957d3e34e8eaaf217b92b6351a3b3429 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 2 May 2023 10:56:17 -0400 Subject: [PATCH 6/6] Remove unused import. --- qiskit/transpiler/preset_passmanagers/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 00f9f4bcee8e..e801d04500fc 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -51,7 +51,6 @@ from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout -from qiskit.utils.deprecation import deprecate_func _CONTROL_FLOW_OP_NAMES = {"for_loop", "if_else", "while_loop", "switch_case"}