Skip to content

Commit

Permalink
Add support for custom backend transpiler stages (#8648)
Browse files Browse the repository at this point in the history
* Add support for custom backend transpiler stages

This commit adds initial support to the transpiler and backend
interfaces for backends to specify custom transpiler stages. There are
often specific hardware compilation requirements that a general purpose
transpiler's preset pass manager can't account for. While we strive to
provide interfaces to outline all the hardware constraints via the
Target class and general purposes passes to fit and optimize an input
circuit to the target backend there are some constraints that aren't
easily addressed in a general purpose way. For such cases having an
interface for a specific backend which has the necessary context of its
own constraints to provide custom compilation steps to integrate into
the pipeline is a necessary feature. The two initial examples of this
are custom basis gate translation and custom scheduling. This commit
adds two new hook point methods for BackendV2 objects,
get_post_translation_stage() and get_scheduling_stage(). These allow
for backends to specify custom PassManager objects that will run after
the translation stage and for the scheduling stage by default when
compilation is targetting the backend. This should enable backends with
custom hardware specific requirements to influence the compilation
process so that any required custom steps to ensure the output circuit
is executable. In the future we may add additional hook points in a
similar manner to enable backends to assert more hardware-specific
compilation where the need arises.

Closes #8329

* Remove stray optimization level args from pm config

* Fix tests

* Apply suggestions from code review

Co-authored-by: Jake Lishman <jake@binhbar.com>

* Pivot backend transpiler hook points to leverage plugins

Since in qiskit-terra 0.22.0 we're adding a plugin interface for
transpiler stages already, the more natural fit for enabling backends to
inject custom passes into a pass maanger is via that plugin interface.
This commit updates the hook points to return a plugin method name that
a backend should use by default. This will provide the same level of
flexibility but also export any custom stages as standalone methods that
users can call into if they need to and also advertise the custom stage
methods along with all other installed methods.

* Apply suggestions from code review

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Update qiskit/providers/__init__.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Update release note

* Update qiskit/providers/backend.py

Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>

* Rename transpile() option and reword doc

* Rename hook methods backend_

* Update qiskit/compiler/transpiler.py

Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>

Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 4, 2022
1 parent 53e215c commit 86fb555
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 22 deletions.
16 changes: 15 additions & 1 deletion qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def transpile(
hls_config: Optional[HLSConfig] = None,
init_method: str = None,
optimization_method: str = None,
ignore_backend_supplied_default_methods: bool = False,
) -> Union[QuantumCircuit, List[QuantumCircuit]]:
"""Transpile one or more circuits, according to some desired transpilation targets.
Expand Down Expand Up @@ -276,6 +277,11 @@ def callback_func(**kwargs):
plugin is not used. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"optimization"`` for the
``stage_name`` argument.
ignore_backend_supplied_default_methods: If set to ``True`` any default methods specified by
a backend will be ignored. Some backends specify alternative default methods
to support custom compilation target-specific passes/plugins which support
backend-specific compilation techniques. If you'd prefer that these defaults were
not used this option is used to disable those backend-specific defaults.
Returns:
The transpiled circuit(s).
Expand Down Expand Up @@ -344,6 +350,7 @@ def callback_func(**kwargs):
hls_config,
init_method,
optimization_method,
ignore_backend_supplied_default_methods,
)
# Get transpile_args to configure the circuit transpilation job(s)
if coupling_map in unique_transpile_args:
Expand Down Expand Up @@ -426,7 +433,7 @@ def _log_transpile_time(start_time, end_time):
def _combine_args(shared_transpiler_args, unique_config):
# Pop optimization_level to exclude it from the kwargs when building a
# PassManagerConfig
level = shared_transpiler_args.get("optimization_level")
level = shared_transpiler_args.pop("optimization_level")
pass_manager_config = shared_transpiler_args
pass_manager_config.update(unique_config.pop("pass_manager_config"))
pass_manager_config = PassManagerConfig(**pass_manager_config)
Expand Down Expand Up @@ -597,6 +604,7 @@ def _parse_transpile_args(
hls_config,
init_method,
optimization_method,
ignore_backend_supplied_default_methods,
) -> Tuple[List[Dict], Dict]:
"""Resolve the various types of args allowed to the transpile() function through
duck typing, overriding args, etc. Refer to the transpile() docstring for details on
Expand Down Expand Up @@ -669,6 +677,12 @@ def _parse_transpile_args(
}

list_transpile_args = []
if not ignore_backend_supplied_default_methods:
if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
scheduling_method = backend.get_scheduling_stage_plugin()
if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
translation_method = backend.get_translation_stage_plugin()

for key, value in {
"inst_map": inst_map,
"coupling_map": coupling_map,
Expand Down
47 changes: 47 additions & 0 deletions qiskit/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,53 @@ def _define(self):
transpiler will ensure that it continues to be well supported by Qiskit
moving forward.
.. _custom_transpiler_backend:
Custom Transpiler Passes
^^^^^^^^^^^^^^^^^^^^^^^^
The transpiler supports the ability for backends to provide custom transpiler
stage implementations to facilitate hardware specific optimizations and
circuit transformations. Currently there are two stages supported,
``get_translation_stage_plugin()`` and ``get_scheduling_stage_plugin()``
which allow a backend to specify string plugin names to be used as the default
translation and scheduling stages, respectively. These
hook points in a :class:`~.BackendV2` class can be used if your
backend has requirements for compilation that are not met by the
current backend/:class:`~.Target` interface. Please also consider
submitting a Github issue describing your use case as there is interest
in improving these interfaces to be able to describe more hardware
architectures in greater depth.
To leverage these hook points you just need to add the methods to your
:class:`~.BackendV2` implementation and have them return a string plugin name.
For example::
class Mybackend(BackendV2):
def get_scheduling_stage_plugin(self):
return "SpecialDD"
def get_translation_stage_plugin(self):
return "BasisTranslatorWithCustom1qOptimization"
This snippet of a backend implementation will now have the :func:`~.transpile`
function use the ``SpecialDD`` plugin for the scheduling stage and
the ``BasisTranslatorWithCustom1qOptimization`` plugin for the translation
stage by default when the target is set to ``Mybackend``. Note that users may override these choices
by explicitly selecting a different plugin name. For this interface to work though transpiler
stage plugins must be implemented for the returned plugin name. You can refer
to :mod:`qiskit.transpiler.preset_passmanagers.plugin` module documentation for
details on how to implement plugins. The typical expectation is that if your backend
requires custom passes as part of a compilation stage the provider package will
include the transpiler stage plugins that use those passes. However, this is not
required and any valid method (from a built-in method or external plugin) can
be used.
This way if these two compilation steps are **required** for running or providing
efficient output on ``Mybackend`` the transpiler will be able to perform these
custom steps without any manual user input.
Run Method
----------
Expand Down
20 changes: 20 additions & 0 deletions qiskit/providers/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,26 @@ class BackendV2(Backend, ABC):
will build a :class:`~qiskit.providers.models.BackendConfiguration` object
and :class:`~qiskit.providers.models.BackendProperties` from the attributes
defined in this class for backwards compatibility.
A backend object can optionally contain methods named
``get_translation_stage_plugin`` and ``get_scheduling_stage_plugin``. If these
methods are present on a backend object and this object is used for
:func:`~.transpile` or :func:`~.generate_preset_pass_manager` the
transpilation process will default to using the output from those methods
as the scheduling stage and the translation compilation stage. This
enables a backend which has custom requirements for compilation to specify a
stage plugin for these stages to enable custom transformation of
the circuit to ensure it is runnable on the backend. These hooks are enabled
by default and should only be used to enable extra compilation steps
if they are **required** to ensure a circuit is executable on the backend or
have the expected level of performance. These methods are passed no input
arguments and are expected to return a ``str`` representing the method name
which should be a stage plugin (see: :mod:`qiskit.transpiler.preset_passmanagers.plugin`
for more details on plugins). The typical expected use case is for a backend
provider to implement a stage plugin for ``translation`` or ``scheduling``
that contains the custom compilation passes and then for the hook methods on
the backend object to return the plugin name so that :func:`~.transpile` will
use it by default when targetting the backend.
"""

version = 2
Expand Down
7 changes: 4 additions & 3 deletions qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def __init__(
hls_config=None,
init_method=None,
optimization_method=None,
optimization_level=None,
):
"""Initialize a PassManagerConfig object
Expand Down Expand Up @@ -84,7 +83,6 @@ def __init__(
init_method (str): The plugin name for the init stage plugin to use
optimization_method (str): The plugin name for the optimization stage plugin
to use.
optimization_level (int): The optimization level being used for compilation.
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
Expand All @@ -105,7 +103,6 @@ def __init__(
self.unitary_synthesis_plugin_config = unitary_synthesis_plugin_config
self.target = target
self.hls_config = hls_config
self.optimization_level = optimization_level

@classmethod
def from_backend(cls, backend, **pass_manager_options):
Expand Down Expand Up @@ -157,6 +154,10 @@ def from_backend(cls, backend, **pass_manager_options):
if res.target is None:
if backend_version >= 2:
res.target = backend.target
if res.scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
res.scheduling_method = backend.get_scheduling_stage_plugin()
if res.translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
res.translation_method = backend.get_translation_stage_plugin()
return res

def __str__(self):
Expand Down
1 change: 0 additions & 1 deletion qiskit/transpiler/preset_passmanagers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ def generate_preset_pass_manager(
hls_config=hls_config,
init_method=init_method,
optimization_method=optimization_method,
optimization_level=optimization_level,
)

if backend is not None:
Expand Down
10 changes: 2 additions & 8 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
hls_config = pass_manager_config.hls_config
# Override an unset optimization_level for stage plugin use.
# it will be restored to None before this is returned
optimization_level = pass_manager_config.optimization_level
if optimization_level is None:
pass_manager_config.optimization_level = 3

# Layout on good qubits if calibration info available, otherwise on dense links
_given_layout = SetLayout(initial_layout)
Expand Down Expand Up @@ -314,14 +309,13 @@ def _unroll_condition(property_set):
sched = common.generate_scheduling(
instruction_durations, scheduling_method, timing_constraints, inst_map
)
elif isinstance(scheduling_method, PassManager):
sched = scheduling_method
else:
sched = plugin_manager.get_passmanager_stage(
"scheduling", scheduling_method, pass_manager_config, optimization_level=3
)

# Restore PassManagerConfig optimization_level override
pass_manager_config.optimization_level = optimization_level

return StagedPassManager(
init=init,
layout=layout,
Expand Down
18 changes: 18 additions & 0 deletions releasenotes/notes/add-backend-custom-passes-cddfd05c8704a4b1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
features:
- |
The :class:`~.BackendV2` class now has support for two new optional hook
points enabling backends to inject custom compilation steps as part of
:func:`~.transpile` and :func:`~.generate_preset_pass_manager`. If a
:class:`~.BackendV2` implementation includes the methods
``get_scheduling_stage_plugin()`` or ``get_translation_stage_plugin()`` the
transpiler will use the returned string as the default value for
the ``scheduling_method`` and ``translation_method`` arguments. This enables
backends to run additional custom transpiler passes when targetting that
backend by leveraging the transpiler stage
:mod:`~qiskit.transpiler.preset_passmanagers.plugin` interface.
For more details on how to use this see :ref:`custom_transpiler_backend`.
- |
Added a new keyword argument, ``ignore_backend_supplied_default_methods``, to the
:func:`~.transpile` function can be used to disable a backend's custom
default method if the target backend has one set.
13 changes: 5 additions & 8 deletions releasenotes/notes/stage-plugin-interface-47daae40f7d0ad3c.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ features:
``optimization_method`` which are used to specify alternative plugins to
use for the ``init`` stage and ``optimization`` stages respectively.
- |
The :class:`~.PassManagerConfig` class has 3 new attributes,
:attr:`~.PassManagerConfig.init_method`,
:attr:`~.PassManagerConfig.optimization_method`, and
:attr:`~.PassManagerConfig.optimization_level` along with matching keyword
arguments on the constructor methods. The first two attributes represent
The :class:`~.PassManagerConfig` class has 2 new attributes,
:attr:`~.PassManagerConfig.init_method` and
:attr:`~.PassManagerConfig.optimization_method`
along with matching keyword arguments on the constructor methods. These represent
the user specified ``init`` and ``optimization`` plugins to use for
compilation. The :attr:`~.PassManagerConfig.optimization_level` attribute
represents the compilations optimization level if specified which can
be used to inform stage plugin behavior.
compilation.
Loading

0 comments on commit 86fb555

Please sign in to comment.