Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable multiple parallel seed trials for SabreSwap #8572

Merged
merged 12 commits into from
Sep 1, 2022
Merged
28 changes: 26 additions & 2 deletions qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,42 @@ class SabreLayout(AnalysisPass):
`arXiv:1809.02573 <https://arxiv.org/pdf/1809.02573.pdf>`_
"""

def __init__(self, coupling_map, routing_pass=None, seed=None, max_iterations=3):
def __init__(
self, coupling_map, routing_pass=None, seed=None, max_iterations=3, swap_trials=None
):
"""SabreLayout initializer.

Args:
coupling_map (Coupling): directed graph representing a coupling map.
routing_pass (BasePass): the routing pass to use while iterating.
This is mutually exclusive with the ``swap_trials`` argument and
if both are set an error will be raised.
seed (int): seed for setting a random first trial layout.
max_iterations (int): number of forward-backward iterations.
swap_trials (int): The number of trials to run of
:class:`~.SabreSwap` for each iteration. This is equivalent to
the ``trials`` argument on :class:`~.SabreSwap`. If this is not
specified (and ``routing_pass`` isn't set) by default the number
of physical CPUs on your local system will be used. For
reproducibility between environments it is best to set this
to an explicit number because the output will potentially depend
on the number of trials run. This option is mutually exclusive
with the ``routing_pass`` argument and an error will be raised
if both are used.

Raises:
TranspilerError: If both ``routing_pass`` and ``swap_trials`` are
specified
"""
super().__init__()
self.coupling_map = coupling_map
if routing_pass is not None and swap_trials is not None:
raise TranspilerError("Both routing_pass and swap_trials can't be set at the same time")
self.routing_pass = routing_pass
self.seed = seed
self.max_iterations = max_iterations
self.trials = swap_trials
self.swap_trials = swap_trials

def run(self, dag):
"""Run the SabreLayout pass on `dag`.
Expand All @@ -86,7 +108,9 @@ def run(self, dag):
initial_layout = Layout({q: dag.qubits[i] for i, q in enumerate(physical_qubits)})

if self.routing_pass is None:
self.routing_pass = SabreSwap(self.coupling_map, "decay", seed=self.seed, fake_run=True)
self.routing_pass = SabreSwap(
self.coupling_map, "decay", seed=self.seed, fake_run=True, trials=self.swap_trials
)
else:
self.routing_pass.fake_run = True

Expand Down
26 changes: 19 additions & 7 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout
from qiskit.dagcircuit import DAGOpNode
from qiskit.tools.parallel import CPU_COUNT

# pylint: disable=import-error
from qiskit._accelerate.sabre_swap import (
Expand Down Expand Up @@ -61,20 +62,19 @@ class SabreSwap(TransformationPass):
scored according to some heuristic cost function. The best SWAP is
implemented and ``current_layout`` updated.

This transpiler pass adds onto the SABRE algorithm in that it will run
multiple trials of the algorithm with different seeds. The best output,
deteremined by the trial with the least amount of SWAPed inserted, will
be selected from the random trials.

**References:**

[1] Li, Gushu, Yufei Ding, and Yuan Xie. "Tackling the qubit mapping problem
for NISQ-era quantum devices." ASPLOS 2019.
`arXiv:1809.02573 <https://arxiv.org/pdf/1809.02573.pdf>`_
"""

def __init__(
self,
coupling_map,
heuristic="basic",
seed=None,
fake_run=False,
):
def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, trials=None):
r"""SabreSwap initializer.

Args:
Expand All @@ -84,6 +84,12 @@ def __init__(
seed (int): random seed used to tie-break among candidate swaps.
fake_run (bool): if true, it only pretend to do routing, i.e., no
swap is effectively added.
trials (int): The number of seed trials to run sabre with. These will
be run in parallel (unless the PassManager is already running in
parallel). If not specified this defaults to the number of physical
CPUs on the local system. For reproducible results it is recommended
that you set this explicitly, as the output will be deterministic for
a fixed number of trials.

Raises:
TranspilerError: If the specified heuristic is not valid.
Expand Down Expand Up @@ -158,6 +164,11 @@ def __init__(
self.seed = np.random.default_rng(None).integers(0, ii32.max, dtype=int)
else:
self.seed = seed
if trials is None:
self.trials = CPU_COUNT
else:
self.trials = trials

self.fake_run = fake_run
self._qubit_indices = None
self._clbit_indices = None
Expand Down Expand Up @@ -216,6 +227,7 @@ def run(self, dag):
self.heuristic,
self.seed,
layout,
self.trials,
)

layout_mapping = layout.layout_mapping()
Expand Down
16 changes: 12 additions & 4 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.initial_layout,
)
if optimization_level == 0:
routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)
routing_pass = SabreSwap(
coupling_map, heuristic="basic", seed=seed_transpiler, trials=5
)
return common.generate_routing_passmanager(
routing_pass,
target,
Expand All @@ -215,7 +217,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
use_barrier_before_measurement=True,
)
if optimization_level == 1:
routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)
routing_pass = SabreSwap(
coupling_map, heuristic="lookahead", seed=seed_transpiler, trials=5
)
return common.generate_routing_passmanager(
routing_pass,
target,
Expand All @@ -227,7 +231,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
use_barrier_before_measurement=True,
)
if optimization_level == 2:
routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)
routing_pass = SabreSwap(
coupling_map, heuristic="decay", seed=seed_transpiler, trials=10
)
return common.generate_routing_passmanager(
routing_pass,
target,
Expand All @@ -238,7 +244,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
use_barrier_before_measurement=True,
)
if optimization_level == 3:
routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)
routing_pass = SabreSwap(
coupling_map, heuristic="decay", seed=seed_transpiler, trials=20
)
return common.generate_routing_passmanager(
routing_pass,
target,
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def _choose_layout_condition(property_set):
elif layout_method == "noise_adaptive":
_choose_layout = NoiseAdaptiveLayout(backend_properties)
elif layout_method == "sabre":
_choose_layout = SabreLayout(coupling_map, max_iterations=1, seed=seed_transpiler)
_choose_layout = SabreLayout(
coupling_map, max_iterations=1, seed=seed_transpiler, swap_trials=5
)

toqm_pass = False
# Choose routing pass
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ def _vf2_match_not_found(property_set):
elif layout_method == "noise_adaptive":
_improve_layout = NoiseAdaptiveLayout(backend_properties)
elif layout_method == "sabre":
_improve_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler)
_improve_layout = SabreLayout(
coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=5
)

toqm_pass = False
routing_pm = None
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ def _vf2_match_not_found(property_set):
elif layout_method == "noise_adaptive":
_choose_layout_1 = NoiseAdaptiveLayout(backend_properties)
elif layout_method == "sabre":
_choose_layout_1 = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler)
_choose_layout_1 = SabreLayout(
coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=10
)

toqm_pass = False
routing_pm = None
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ def _vf2_match_not_found(property_set):
elif layout_method == "noise_adaptive":
_choose_layout_1 = NoiseAdaptiveLayout(backend_properties)
elif layout_method == "sabre":
_choose_layout_1 = SabreLayout(coupling_map, max_iterations=4, seed=seed_transpiler)
_choose_layout_1 = SabreLayout(
coupling_map, max_iterations=4, seed=seed_transpiler, swap_trials=20
)

toqm_pass = False
# TODO: Remove when qiskit-toqm has it's own plugin and we can rely on just the plugin interface
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
features:
- |
The :class:`~.SabreSwap` transpiler pass has a new keyword argument on its
constructor, ``trials``. The ``trials`` argument is used to specify the
number of random seed trials to attempt. The output from the
`SABRE algorithm <https://arxiv.org/abs/1809.02573>`__ can differ greatly
based on the seed used for the random number. :class:`~.SabreSwap` will
now run the algorithm with ``trials`` number of random seeds and pick the
best (with the fewest swaps inserted). If ``trials`` is not specified the
pass will default to use the number of physical CPUs on the local system.
- |
The :class:`~.SabreLayout` transpiler pass has a new keyword argument on
its constructor, ``swap_trials``. The ``swap_trials`` argument is used
to specify how many random seed trials to run on the :class:`~.SabreSwap`
pass internally. It corresponds to the ``trials`` arugment on the
:class:`~.SabreSwap` pass. When set, each iteration of
:class:`~.SabreSwap` will be run internally ``swap_trials`` times.
If ``swap_trials`` is not specified the will default to use
the number of physical CPUs on the local system.
Loading