From 0cd7dba18408e7ffdbaa2923115c2220a239c37f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 28 Sep 2022 23:43:28 -0400 Subject: [PATCH] Use Sabre by default for optimization levels 1 and 2 (#8552) * Use Sabre by default for optimization levels 1 and 2 This commit updates the preset pass manager construction to use the SabreLayout and SabreSwap passes by default for optimization level 1 and level 2. With the recently merged #7977 the performance of the sabre swap pass has improved significantly enough to be considered for use by default with optimization levels 1 and 2. While for small numbers of target device qubits (< 30) the SabreLayout/SabreSwap pass doesn't quite match the runtime performance of DenseLayout/StochasticSwap it typically has better runtime performance for larger target devices. Additionally, the runtime performance of Sabre should also improve further after #8388 is finished. However, the output quality from the sabre passes is typically better resulting in fewer swap gates being inserted. With the combination of better quality and comparable runtime performance it makes sense to use sabre as the default for optimization levels 1 and 2. For optimization level 0 stochastic swap is still used there because we want to continue to leverage TrivialLayout for that level and to get the full quality advantages SabreSwap and SabreLayout should be used together. * Fix pickling of SabreSwap object In #7977 we moved to use compiled objects for part of the SabreSwap compiler pass. However an unintended side effect of that PR was the use of Rust objects stored in instance level variables which weren't pickleable. This breaks multiprocessing at the PassManager level which expects to be able to pickle and send a SabreSwap object to the subprocess running on a circuit. This commit fixes this by making the Rust NeighborTable object pickleable and switching to storing the heuristic string at the instance level instead of the heuristic enum. * Update layout tests to match new default This commit updates a failing layout test which was assuming that level 1 and level 2 where still running DenseLayout. The test has been updated to reflect the new default of SabreLayout. * Fix stochastic swap specific test to use that routing method Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../transpiler/passes/routing/sabre_swap.py | 20 +++--- .../preset_passmanagers/builtin_plugins.py | 2 +- .../transpiler/preset_passmanagers/level1.py | 4 +- .../transpiler/preset_passmanagers/level2.py | 4 +- .../sabres-for-everyone-3148ccf2064ccb0d.yaml | 17 +++++ src/sabre_swap/neighbor_table.rs | 72 +++++++++++-------- test/python/compiler/test_transpiler.py | 2 +- .../transpiler/test_preset_passmanagers.py | 27 +------ 8 files changed, 77 insertions(+), 71 deletions(-) create mode 100644 releasenotes/notes/sabres-for-everyone-3148ccf2064ccb0d.yaml diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 114f6d577898..a2d749b69b9c 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -150,14 +150,7 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t if coupling_map is not None: self._neighbor_table = NeighborTable(retworkx.adjacency_matrix(self.coupling_map.graph)) - if heuristic == "basic": - self.heuristic = Heuristic.Basic - elif heuristic == "lookahead": - self.heuristic = Heuristic.Lookahead - elif heuristic == "decay": - self.heuristic = Heuristic.Decay - else: - raise TranspilerError("Heuristic %s not recognized." % heuristic) + self.heuristic = heuristic if seed is None: ii32 = np.iinfo(np.int32) @@ -191,6 +184,15 @@ def run(self, dag): if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError("More virtual qubits exist than physical.") + if self.heuristic == "basic": + heuristic = Heuristic.Basic + elif self.heuristic == "lookahead": + heuristic = Heuristic.Lookahead + elif self.heuristic == "decay": + heuristic = Heuristic.Decay + else: + raise TranspilerError("Heuristic %s not recognized." % self.heuristic) + self.dist_matrix = self.coupling_map.distance_matrix # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. @@ -229,7 +231,7 @@ def run(self, dag): sabre_dag, self._neighbor_table, self.dist_matrix, - self.heuristic, + heuristic, self.seed, layout, self.trials, diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index d3bce75e11bb..ba4c1266ce68 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -218,7 +218,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 1: routing_pass = SabreSwap( - coupling_map, heuristic="lookahead", seed=seed_transpiler, trials=5 + coupling_map, heuristic="decay", seed=seed_transpiler, trials=5 ) return common.generate_routing_passmanager( routing_pass, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 48a798b62827..8d1f44bbca99 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -71,9 +71,9 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa inst_map = pass_manager_config.inst_map coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout - layout_method = pass_manager_config.layout_method or "dense" init_method = pass_manager_config.init_method - routing_method = pass_manager_config.routing_method or "stochastic" + layout_method = pass_manager_config.layout_method or "sabre" + routing_method = pass_manager_config.routing_method or "sabre" translation_method = pass_manager_config.translation_method or "translator" optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d1599fd0bc9f..a8f193883c7a 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -74,8 +74,8 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout init_method = pass_manager_config.init_method - layout_method = pass_manager_config.layout_method or "dense" - routing_method = pass_manager_config.routing_method or "stochastic" + layout_method = pass_manager_config.layout_method or "sabre" + routing_method = pass_manager_config.routing_method or "sabre" translation_method = pass_manager_config.translation_method or "translator" optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method diff --git a/releasenotes/notes/sabres-for-everyone-3148ccf2064ccb0d.yaml b/releasenotes/notes/sabres-for-everyone-3148ccf2064ccb0d.yaml new file mode 100644 index 000000000000..2be30c7a290e --- /dev/null +++ b/releasenotes/notes/sabres-for-everyone-3148ccf2064ccb0d.yaml @@ -0,0 +1,17 @@ +--- +upgrade: + - | + The preset pass managers for levels 1 and 2, which will be used when + ``optimization_level=1`` or ``optimization_level=2`` with + :func:`~.transpile` or :func:`~.generate_preset_pass_manager` and output + from :func:`~.level_1_pass_manager` and :func:`~.level_2_pass_manager`, + will now use :class:`~.SabreLayout` and :func:`~SabreSwap` by default + instead of the previous defaults :class:`~.DenseLayout` and + :class:`~.StochasticSwap`. This change was made to improve the output + quality of the transpiler, the :class:`~.SabreLayout` and + :func:`~SabreSwap` combination typically results in fewer + :class:`~.SwapGate` objects being inserted into the output circuit. + If you would like to use the previous default passes you can set + ``layout_method='dense'`` and ``routing_method='stochastic'`` on + :func:`~.transpile` or :func:`~.generate_preset_pass_manager to + leverage :class:`~.DenseLayout` and :class:`~.StochasticSwap` respectively. diff --git a/src/sabre_swap/neighbor_table.rs b/src/sabre_swap/neighbor_table.rs index d59700ed6352..195875330cf6 100644 --- a/src/sabre_swap/neighbor_table.rs +++ b/src/sabre_swap/neighbor_table.rs @@ -36,38 +36,48 @@ pub struct NeighborTable { #[pymethods] impl NeighborTable { #[new] - pub fn new(adjacency_matrix: PyReadonlyArray2) -> Self { - let adj_mat = adjacency_matrix.as_array(); + pub fn new(adjacency_matrix: Option>) -> Self { let run_in_parallel = getenv_use_multiple_threads(); - let build_neighbors = |row: ArrayView1| -> Vec { - row.iter() - .enumerate() - .filter_map( - |(row_index, value)| { - if *value == 0. { - None - } else { - Some(row_index) - } - }, - ) - .collect() - }; - if run_in_parallel { - NeighborTable { - neighbors: adj_mat - .axis_iter(Axis(0)) - .into_par_iter() - .map(|row| build_neighbors(row)) - .collect(), - } - } else { - NeighborTable { - neighbors: adj_mat - .axis_iter(Axis(0)) - .map(|row| build_neighbors(row)) - .collect(), + let neighbors = match adjacency_matrix { + Some(adjacency_matrix) => { + let adj_mat = adjacency_matrix.as_array(); + let build_neighbors = |row: ArrayView1| -> Vec { + row.iter() + .enumerate() + .filter_map( + |(row_index, value)| { + if *value == 0. { + None + } else { + Some(row_index) + } + }, + ) + .collect() + }; + if run_in_parallel { + adj_mat + .axis_iter(Axis(0)) + .into_par_iter() + .map(|row| build_neighbors(row)) + .collect() + } else { + adj_mat + .axis_iter(Axis(0)) + .map(|row| build_neighbors(row)) + .collect() + } } - } + None => Vec::new(), + }; + NeighborTable { neighbors } + } + + fn __getstate__(&self) -> Vec> { + self.neighbors.clone() + } + + fn __setstate__(&mut self, state: Vec>) { + self.neighbors = state } } diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 40f5d59113dd..cae17c2227f4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -716,7 +716,7 @@ def test_move_measurements(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] - out = transpile(circ, initial_layout=lay, coupling_map=cmap) + out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic") out_dag = circuit_to_dag(out) meas_nodes = out_dag.named_nodes("measure") for meas_node in meas_nodes: diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index e954932e2f1c..eadd82edb38a 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -701,29 +701,6 @@ def test_layout_tokyo_fully_connected_cx(self, level): 19: ancilla[14], } - dense_layout = { - 11: qr[0], - 6: qr[1], - 5: qr[2], - 10: qr[3], - 15: qr[4], - 0: ancilla[0], - 1: ancilla[1], - 2: ancilla[2], - 3: ancilla[3], - 4: ancilla[4], - 7: ancilla[5], - 8: ancilla[6], - 9: ancilla[7], - 12: ancilla[8], - 13: ancilla[9], - 14: ancilla[10], - 16: ancilla[11], - 17: ancilla[12], - 18: ancilla[13], - 19: ancilla[14], - } - sabre_layout = { 11: qr[0], 17: qr[1], @@ -748,8 +725,8 @@ def test_layout_tokyo_fully_connected_cx(self, level): } expected_layout_level0 = trivial_layout - expected_layout_level1 = dense_layout - expected_layout_level2 = dense_layout + expected_layout_level1 = sabre_layout + expected_layout_level2 = sabre_layout expected_layout_level3 = sabre_layout expected_layouts = [