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

Migrate layout stage to plugins #10622

Merged
merged 6 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 278 additions & 0 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@
from qiskit.transpiler.passes import StochasticSwap
from qiskit.transpiler.passes import SabreSwap
from qiskit.transpiler.passes import Error
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import VF2Layout
from qiskit.transpiler.passes import SabreLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import CheckMap
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason


class BasisTranslatorPassManager(PassManagerStagePlugin):
Expand Down Expand Up @@ -418,3 +427,272 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
return common.generate_scheduling(
instruction_durations, scheduling_method, timing_constraints, inst_map, target
)


class DefaultLayoutPassManager(PassManagerStagePlugin):
"""Plugin class for default layout stage."""

def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager:
_given_layout = SetLayout(pass_manager_config.initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

def _layout_not_perfect(property_set):
"""Return ``True`` if the first attempt at layout has been checked and found to be
imperfect. In this case, perfection means "does not require any swap routing"."""
return property_set["is_swap_mapped"] is not None and not property_set["is_swap_mapped"]
Comment on lines +435 to +444
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any concerns about pickle-ability of the resulting pass manager now we're retuning method-local functions as part of the conditions?

(also, lol at using explicitly private names when defining method-internal functions - a clear copy-paste sign haha)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be as it's passing the parallel transpiler tests which should be testing this.


def _vf2_match_not_found(property_set):
# If a layout hasn't been set by the time we run vf2 layout we need to
# run layout
if property_set["layout"] is None:
return True
# if VF2 layout stopped for any reason other than solution found we need
# to run layout since VF2 didn't converge.
return (
property_set["VF2Layout_stop_reason"] is not None
and property_set["VF2Layout_stop_reason"] is not VF2LayoutStopReason.SOLUTION_FOUND
)

def _swap_mapped(property_set):
return property_set["final_layout"] is None

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
else:
coupling_map = pass_manager_config.target

layout = PassManager()
layout.append(_given_layout)
if optimization_level == 0:
layout.append(TrivialLayout(coupling_map), condition=_choose_layout_condition)
layout += common.generate_embed_passmanager(coupling_map)
return layout
elif optimization_level == 1:
layout.append(
[TrivialLayout(coupling_map), CheckMap(coupling_map)],
condition=_choose_layout_condition,
)
choose_layout_1 = VF2Layout(
coupling_map=pass_manager_config.coupling_map,
seed=pass_manager_config.seed_transpiler,
call_limit=int(5e4), # Set call limit to ~100ms with rustworkx 0.10.2
properties=pass_manager_config.backend_properties,
target=pass_manager_config.target,
max_trials=2500, # Limits layout scoring to < 600ms on ~400 qubit devices
)
layout.append(choose_layout_1, condition=_layout_not_perfect)
choose_layout_2 = SabreLayout(
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=5,
layout_trials=5,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_2], condition=_vf2_match_not_found
)
elif optimization_level == 2:
choose_layout_0 = VF2Layout(
coupling_map=pass_manager_config.coupling_map,
seed=pass_manager_config.seed_transpiler,
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
)
layout.append(choose_layout_0, condition=_choose_layout_condition)
choose_layout_1 = SabreLayout(
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=10,
layout_trials=10,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found
)
elif optimization_level == 3:
choose_layout_0 = VF2Layout(
coupling_map=pass_manager_config.coupling_map,
seed=pass_manager_config.seed_transpiler,
call_limit=int(3e7), # Set call limit to ~60s with rustworkx 0.10.2
properties=pass_manager_config.backend_properties,
target=pass_manager_config.target,
max_trials=250000, # Limits layout scoring to < 60s on ~400 qubit devices
)
layout.append(choose_layout_0, condition=_choose_layout_condition)
choose_layout_1 = SabreLayout(
coupling_map,
max_iterations=4,
seed=pass_manager_config.seed_transpiler,
swap_trials=20,
layout_trials=20,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found
)
else:
raise TranspilerError(f"Invalid optimization level: {optimization_level}")

embed = common.generate_embed_passmanager(coupling_map)
layout.append(
[pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped
)
return layout


class TrivialLayoutPassManager(PassManagerStagePlugin):
"""Plugin class for trivial layout stage."""

def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager:
_given_layout = SetLayout(pass_manager_config.initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
else:
coupling_map = pass_manager_config.target

layout = PassManager()
layout.append(_given_layout)
layout.append(TrivialLayout(coupling_map), condition=_choose_layout_condition)
layout += common.generate_embed_passmanager(coupling_map)
return layout


class DenseLayoutPassManager(PassManagerStagePlugin):
"""Plugin class for dense layout stage."""

def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager:
_given_layout = SetLayout(pass_manager_config.initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
else:
coupling_map = pass_manager_config.target

layout = PassManager()
layout.append(_given_layout)
layout.append(
DenseLayout(
coupling_map=pass_manager_config.coupling_map,
backend_prop=pass_manager_config.backend_properties,
target=pass_manager_config.target,
),
condition=_choose_layout_condition,
)
layout += common.generate_embed_passmanager(coupling_map)
return layout


class NoiseAdaptiveLayoutPassManager(PassManagerStagePlugin):
"""Plugin class for noise adaptive layout stage."""

def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager:
_given_layout = SetLayout(pass_manager_config.initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
else:
coupling_map = pass_manager_config.target

layout = PassManager()
layout.append(_given_layout)
if pass_manager_config.target is None:
layout.append(
NoiseAdaptiveLayout(pass_manager_config.backend_properties),
condition=_choose_layout_condition,
)
else:
layout.append(
NoiseAdaptiveLayout(pass_manager_config.target), condition=_choose_layout_condition
)
layout += common.generate_embed_passmanager(coupling_map)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With how we use this, I wonder a little if embed should be a separate stage in the transpiler pipeline. There's only ever a single version of it that would make sense, I guess, which reduces its importance, but it's a common usage pitfall to forget to run an embed step after what most people consider to be a complete "layout" stage, and it might be a clearer separation of concerns if "embed" is a separate stage.

That said, it'd probably play even weirder with the hybrid layout+routing manager we do in the defaults.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it potentially would have made sense when we originally did the stages. I was thinking also as a post-layout stage it would work instead of doing an explicit embed stage. But that layout ensure it is embedded is baked into the plugin interface now: https://qiskit.org/documentation/apidoc/transpiler_plugins.html#plugin-stages (under expectations for layouts). If we wanted to split it out now we'd have to condition the stage to only run if it's not already been embedded.

return layout


class SabreLayoutPassManager(PassManagerStagePlugin):
"""Plugin class for sabre layout stage."""

def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager:
_given_layout = SetLayout(pass_manager_config.initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

def _swap_mapped(property_set):
return property_set["final_layout"] is None

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
else:
coupling_map = pass_manager_config.target

layout = PassManager()
layout.append(_given_layout)
if optimization_level == 0:
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
layout_pass = SabreLayout(
coupling_map,
max_iterations=1,
seed=pass_manager_config.seed_transpiler,
swap_trials=5,
layout_trials=5,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
elif optimization_level == 1:
layout_pass = SabreLayout(
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=5,
layout_trials=5,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
elif optimization_level == 2:
layout_pass = SabreLayout(
coupling_map,
max_iterations=2,
seed=pass_manager_config.seed_transpiler,
swap_trials=10,
layout_trials=10,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
elif optimization_level == 3:
layout_pass = SabreLayout(
coupling_map,
max_iterations=4,
seed=pass_manager_config.seed_transpiler,
swap_trials=20,
layout_trials=20,
skip_routing=pass_manager_config.routing_method is not None
and pass_manager_config.routing_method != "sabre",
)
else:
raise TranspilerError(f"Invalid optimization level: {optimization_level}")
layout.append(
[BarrierBeforeFinalMeasurements(), layout_pass], condition=_choose_layout_condition
)
embed = common.generate_embed_passmanager(coupling_map)
layout.append(
[pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped
)
return layout
62 changes: 4 additions & 58 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,9 @@

Level 0 pass manager: no explicit optimization other than mapping to backend.
"""
from qiskit.transpiler.basepasses import BasePass

from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.passmanager import PassManager
from qiskit.transpiler.passmanager import StagedPassManager

from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import SabreLayout
from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.preset_passmanagers.plugin import (
PassManagerStagePluginManager,
Expand Down Expand Up @@ -55,50 +47,17 @@ def level_0_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 "trivial"
layout_method = pass_manager_config.layout_method or "default"
routing_method = pass_manager_config.routing_method or "stochastic"
translation_method = pass_manager_config.translation_method or "translator"
optimization_method = pass_manager_config.optimization_method
scheduling_method = pass_manager_config.scheduling_method or "default"
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
hls_config = pass_manager_config.hls_config

# Choose an initial layout if not set by user (default: trivial layout)
_given_layout = SetLayout(initial_layout)

def _choose_layout_condition(property_set):
return not property_set["layout"]

if target is None:
coupling_map_layout = coupling_map
else:
coupling_map_layout = target

if layout_method == "trivial":
_choose_layout: BasePass = TrivialLayout(coupling_map_layout)
elif layout_method == "dense":
_choose_layout = DenseLayout(coupling_map, backend_properties, target=target)
elif layout_method == "noise_adaptive":
if target is None:
_choose_layout = NoiseAdaptiveLayout(backend_properties)
else:
_choose_layout = NoiseAdaptiveLayout(target)
elif layout_method == "sabre":
skip_routing = pass_manager_config.routing_method is not None and routing_method != "sabre"
_choose_layout = SabreLayout(
coupling_map_layout,
max_iterations=1,
seed=seed_transpiler,
swap_trials=5,
layout_trials=5,
skip_routing=skip_routing,
)

# Choose routing pass
routing_pm = plugin_manager.get_passmanager_stage(
"routing", routing_method, pass_manager_config, optimization_level=0
Expand All @@ -115,22 +74,9 @@ def _choose_layout_condition(property_set):
unitary_synthesis_plugin_config,
hls_config,
)
if layout_method not in {"trivial", "dense", "noise_adaptive", "sabre"}:
layout = plugin_manager.get_passmanager_stage(
"layout", layout_method, pass_manager_config, optimization_level=0
)
else:

def _swap_mapped(property_set):
return property_set["final_layout"] is None

layout = PassManager()
layout.append(_given_layout)
layout.append(_choose_layout, condition=_choose_layout_condition)
embed = common.generate_embed_passmanager(coupling_map_layout)
layout.append(
[pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped
)
layout = plugin_manager.get_passmanager_stage(
"layout", layout_method, pass_manager_config, optimization_level=0
)
routing = routing_pm
else:
layout = None
Expand Down
Loading