Skip to content

Commit

Permalink
Increase heuristic effort for optimization level 2 (#12149)
Browse files Browse the repository at this point in the history
* Increase heuristic effort for optimization level 2

This commit tweaks the heuristic effort in optimization level 2 to be
more of a middle ground between level 1 and 3; with a better balance
between output quality and runtime. This places it to be a better
default for a pass manager we use if one isn't specified. The
tradeoff here is that the vf2layout and vf2postlayout search space is
reduced to be the same as level 1. There are diminishing margins of
return on the vf2 layout search especially for cases when there are a
large number of qubit permutations for the mapping found. Then the
number of sabre trials is brought up to the same level as optimization
level 3. As this can have a significant impact on output and the extra
runtime cost is minimal. The larger change is that the optimization
passes from level 3. This ends up mainly being 2q peephole optimization.
With the performance improvements from #12010 and #11946 and all the
follow-on PRs this is now fast enough to rely on in optimization level
2.

* Add test workaround from level 3 to level 2 too

* Expand vf2 call limit on VF2Layout

For the initial VF2Layout call this commit expands the vf2 call limit
back to the previous level instead of reducing it to the same as level 1.
The idea behind making this change is that spending up to 10s to find a
perfect layout is a worthwhile tradeoff as that will greatly improve the
result from execution. But scoring multiple layouts to find the lowest
error rate subgraph has a diminishing margin of return in most cases as
there typically aren't thousands of unique subgraphs and often when we
hit the scoring limit it's just permuting the qubits inside a subgraph
which doesn't provide the most value.

For VF2PostLayout the lower call limits from level 1 is still used. This
is because both the search for isomorphic subgraphs is typically much
shorter with the vf2++ node ordering heuristic so we don't need to spend
as much time looking for alternative subgraphs.

* Move 2q peephole outside of optimization loop in O2

Due to potential instability in the 2q peephole optimization we run we
were using the `MinimumPoint` pass to provide backtracking when we reach
a local minimum. However, this pass adds a significant amount of
overhead because it deep copies the circuit at every iteration of the
optimization loop that improves the output quality. This commit tweaks
the O2 pass manager construction to only run 2q peephole once, and then
updates the optimization loop to be what the previous O2 optimization
loop was.
  • Loading branch information
mtreinish authored Apr 23, 2024
1 parent b970348 commit 40ac274
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 22 deletions.
44 changes: 30 additions & 14 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.unitary_synthesis_plugin_config,
pass_manager_config.hls_config,
)
elif optimization_level in {1, 2}:
elif optimization_level == 1:
init = PassManager()
if (
pass_manager_config.initial_layout
Expand Down Expand Up @@ -123,10 +123,8 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
]
)
)
if optimization_level == 2:
init.append(CommutativeCancellation())

elif optimization_level == 3:
elif optimization_level in {2, 3}:
init = common.generate_unroll_3q(
pass_manager_config.target,
pass_manager_config.basis_gates,
Expand Down Expand Up @@ -543,16 +541,13 @@ def _opt_control(property_set):
]
),
]

elif optimization_level == 2:
# Steps for optimization level 2
_opt = [
Optimize1qGatesDecomposition(
basis=pass_manager_config.basis_gates, target=pass_manager_config.target
),
CommutativeCancellation(
basis_gates=pass_manager_config.basis_gates,
target=pass_manager_config.target,
),
CommutativeCancellation(target=pass_manager_config.target),
]
elif optimization_level == 3:
# Steps for optimization level 3
Expand Down Expand Up @@ -598,6 +593,27 @@ def _unroll_condition(property_set):

if optimization_level == 3:
optimization.append(_minimum_point_check)
elif optimization_level == 2:
optimization.append(
[
Collect2qBlocks(),
ConsolidateBlocks(
basis_gates=pass_manager_config.basis_gates,
target=pass_manager_config.target,
approximation_degree=pass_manager_config.approximation_degree,
),
UnitarySynthesis(
pass_manager_config.basis_gates,
approximation_degree=pass_manager_config.approximation_degree,
coupling_map=pass_manager_config.coupling_map,
backend_props=pass_manager_config.backend_properties,
method=pass_manager_config.unitary_synthesis_method,
plugin_config=pass_manager_config.unitary_synthesis_plugin_config,
target=pass_manager_config.target,
),
]
)
optimization.append(_depth_check + _size_check)
else:
optimization.append(_depth_check + _size_check)
opt_loop = (
Expand Down Expand Up @@ -749,7 +765,7 @@ def _swap_mapped(property_set):
call_limit=int(5e6), # Set call limit to ~10s with rustworkx 0.10.2
properties=pass_manager_config.backend_properties,
target=pass_manager_config.target,
max_trials=25000, # Limits layout scoring to < 10s on ~400 qubit devices
max_trials=2500, # Limits layout scoring to < 600ms on ~400 qubit devices
)
layout.append(
ConditionalController(choose_layout_0, condition=_choose_layout_condition)
Expand All @@ -758,8 +774,8 @@ def _swap_mapped(property_set):
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=10,
layout_trials=10,
swap_trials=20,
layout_trials=20,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
Expand Down Expand Up @@ -911,8 +927,8 @@ def _swap_mapped(property_set):
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=10,
layout_trials=10,
swap_trials=20,
layout_trials=20,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
Expand Down
7 changes: 1 addition & 6 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,16 +627,11 @@ def get_vf2_limits(
"""
limits = VF2Limits(None, None)
if layout_method is None and initial_layout is None:
if optimization_level == 1:
if optimization_level in {1, 2}:
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
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
upgrade_transpiler:
- |
The preset :class:`.StagedPassManager` returned for optimization level 2 by
:func:`.generate_preset_pass_manager` and :func:`.level_2_pass_manager` have
been reworked to provide a better balance between runtime and optimization.
This means the output circuits will change compared to earlier releases. If
you need an exact pass manager from level 2 in earlier releases you can
either build it manually or use it from an earlier release and save the
circuits with :mod:`~qiskit.qpy` to load with a newer release.
5 changes: 3 additions & 2 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1831,11 +1831,12 @@ def test_synthesis_translation_method_with_gates_outside_basis(self, optimizatio
optimization_level=optimization_level,
seed_transpiler=42,
)
if optimization_level != 3:
if optimization_level not in {2, 3}:
self.assertTrue(Operator(qc).equiv(res))
self.assertNotIn("swap", res.count_ops())
else:
# Optimization level 3 eliminates the pointless swap
# Optimization level 2 and 3 eliminates the swap by permuting the
# qubits
self.assertEqual(res, QuantumCircuit(2))

@data(0, 1, 2, 3)
Expand Down

0 comments on commit 40ac274

Please sign in to comment.