Skip to content

Expand backend specific default methods to cover all stages#16119

Open
mtreinish wants to merge 1 commit intoQiskit:mainfrom
mtreinish:expand-backend-plugins
Open

Expand backend specific default methods to cover all stages#16119
mtreinish wants to merge 1 commit intoQiskit:mainfrom
mtreinish:expand-backend-plugins

Conversation

@mtreinish
Copy link
Copy Markdown
Member

This commit expands the optional backend interface methods for specifying an alternative default plugin for a transpiler stage to cover all the stages in the preset pass manager. Originally this interface was introduced in #8648 for the translation and scheduling stages. This was just a starting point as these were two stages most likely to require backend specific compilation. However, since then this interface has proven a useful tool for backend implementations but it's utility has been limited to just translation and scheduling. There is a potential role for a backend to provide an alternative default for the other stages of the preset pass manager. This finishes what #8648 starts by expanding the interface to cover all the stages.

AI/LLM disclosure

  • I didn't use LLM tooling, or only used it privately.
  • I used the following tool to help write this PR description:
  • I used the following tool to generate or modify code:

This commit expands the optional backend interface methods for
specifying an alternative default plugin for a transpiler stage to cover
all the stages in the preset pass manager. Originally this interface was
introduced in Qiskit#8648 for the translation and scheduling stages. This was
just a starting point as these were two stages most likely to require
backend specific compilation. However, since then this interface has
proven a useful tool for backend implementations but it's utility has
been limited to just translation and scheduling. There is a potential
role for a backend to provide an alternative default for the other
stages of the preset pass manager. This finishes what Qiskit#8648 starts by
expanding the interface to cover all the stages.
@mtreinish mtreinish added this to the 2.5.0 milestone Apr 30, 2026
@mtreinish mtreinish requested a review from a team as a code owner April 30, 2026 20:06
@mtreinish mtreinish added the Changelog: Added Add an "Added" entry in the GitHub Release changelog. label Apr 30, 2026
@mtreinish mtreinish requested a review from Cryoris April 30, 2026 20:06
@mtreinish mtreinish added the mod: providers Related to the backend and job abstractions label Apr 30, 2026
@qiskit-bot
Copy link
Copy Markdown
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 25186773179

Coverage increased (+0.01%) to 87.583%

Details

  • Coverage increased (+0.01%) from the base build.
  • Patch coverage: 4 uncovered changes across 1 file (9 of 13 lines covered, 69.23%).
  • 11 coverage regressions across 3 files.

Uncovered Changes

File Changed Covered %
qiskit/compiler/transpiler.py 8 4 50.0%

Coverage Regressions

11 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
crates/qasm2/src/parse.rs 6 97.63%
crates/qasm2/src/lex.rs 4 91.52%
crates/circuit/src/parameter/parameter_expression.rs 1 90.53%

Coverage Stats

Coverage Status
Relevant Lines: 121358
Covered Lines: 106289
Line Coverage: 87.58%
Coverage Strength: 968109.86 hits per line

💛 - Coveralls

Comment on lines 275 to +287
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()
if init_method is None and hasattr(backend, "get_init_stage_plugin"):
init_method = backend.get_init_stage_plugin()
if layout_method is None and hasattr(backend, "get_layout_stage_plugin"):
layout_method = backend.get_layout_stage_plugin()
if routing_method is None and hasattr(backend, "get_routing_stage_plugin"):
routing_method = backend.get_routing_stage_plugin()
if optimization_method is None and hasattr(backend, "get_optimization_stage_plugin"):
optimization_method = backend.get_optimization_stage_plugin()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given we unconditionally call the method if it exists and expect a certain behaviour, I think this is functionally no different to adding

class Backend:
    def get_optimization_stage_plugin(self) -> None | str:
        return None
    # ...

with all the not-entirely-stable implications that comes with. Because this is called unconditionally and transpile is typed as taking Backend, I want to point out this has some implications for BackendV3 too as transpile is currently written. The documentation is only in BackendV2, but the only actual behaviour of transpile has been to do it for BackendV1 as well.

This was of course true in #8648 too and it's somewhat regrettable we didn't talk about it then (also wow, it's fairly clear how our attitudes to stability where different back then).

Anyway, my point:

  • the potential for breakage here is certainly unlikely, but technically entirely possible, so I think at a minimum we need an upgrade note
  • can we / should make these proper methods on the Backend base class? I think we should at least consider this for BackendV1 and BackendV2 objects, and also fix the type hinting of transpile to be in terms only of those two concrete types - I think we should really not have this implied behaviour for BackendV3 too (and I don't think it's guaranteed the same hardcoded stage names will make sense there).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Well BackendV1 we removed in 2.0 to get rid of the dead weight model objects, so there is no concern there anymore. I was thinking for backendv3 we'd keep this same interface because I do think having a way for a backend to define their own compilation routines by default is important. I agree it's not the prettiest interface but we're kind of stuck with it now for backendv2 at least. We have the opportunity to improve on this for V3 if you have suggestions on how we should handle backend specific custom compilation. It's very easy to update the logic in this PR to wrap everything in a if backend.version == 2 to make it more future facing.

I agree there is some concern around backwards compatibility but it really depends on how we want to handle our stability contract around out of tree backend objects. Like on it's own this won't change anything unless a backend opts-in to using the new methods. That being said I can add an upgrade note about it to just warn users that it might change compilation for backends in the future and they should refer to the backend providers for changes.

As for proper methods yeah we can do that, I just was mirroring the existing pattern we had from #8648 but having explicit methods would probably be better just from a documentation PoV if nothing else.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In general I'm ok expanding the interface to include the extra stages here, it's mostly the documentation I'm concerned with.

The reason I say it's unstable is that an existing BackendV2 could technically have defined a get_optimization_stage_plugin method of its own with an arbitrary signature, and until this PR, it would have been fine. Following this PR, it could suddenly be invalid to pass that object to transpile.

I think we definitely should type-check on ifinstance(BackendV2) before doing these checks, since they're only documented as being valid on BackendV2, which would solve some of my concern. I think we'd need to have a clearer view on the compilation pipeline for QuantumProgram/BackendV3 before we start making decisions about that.

The (not especially strong) concern about the instability of assigning new meaning to existing get_optimization_stage_plugin methods isn't solvable by an isinstance check; prior to Qiskit 2.5, it was valid to have a BackendV2 with such a method and a totally different signature / semantics. If we were going to do that, we'd need some sort of separate versioning semantics on the subclass interface, which don't exist - something like a backend_feature_level() hook that returns a version of the BackendV2 spec, and we only attempt to access the new-in-Qiskit-2.5 methods for backends that set that feature level high enough. Given we'd have to break semver compatibility in the same way to patch that method/hook onto BackendV2, I don't think it's worth doing, but I think that kind of version negotiation is probably what we want in the future if we're intending to define forwards-compatible subclass interfaces.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh you meant on that front, I agree it is a breaking change since someone could have been using the name before. I just assumed in practice it is extremely unlikely especially given the existing get_translation_stage_plugin and get_scheduling_stage_plugin methods and we could slip it in without any complaints. One option is I can put protections in place to catch any exceptions caused by trying to call the methods, and also gracefully handling the case where the method exists already and has the same signature but doesn't return a valid plugin name. The only case we can't handle is if a backend was previously using this method name with the same signature and the returned string is a valid plugin name, but the intent of the pre-existing method was not to return a default plugin name. It did seem a very unlikely scenario in practice.

The other two options are we can just save this for BackendV3 and not bother with it on BackendV2, which seems reasonable to me. The other option is wait and indeterminately long period of time until Qiskit 3.0 for adding it on BackendV2. Actually talking this through I'm thinking doing both is sufficient. I didn't have a strong use case in mind for this. I was just musing with the idea that exposing this interface would be a nice avenue to investigate adding backend specific optimizations, like sabre prelayout hints and similar in the future.

Comment on lines +55 to +59
* ``get_init_stage_plugin``
* ``get_layout_stage_plugin``
* ``get_routing_stage_plugin``
* ``get_translation_stage_plugin``
* ``get_scheduling_stage_plugin``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing optimization?

Comment on lines +72 to 75
The typical expected use case is for a backend provider to implement a stage
plugin 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 targeting the backend.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This isn't "the typical expected use case", I don't think? It's a description of the actual calling signature. It might be clearer by writing out the actual signature (although see the other review comment about just making them overrideable methods).

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``
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This new text adds in the bit about "bespoke methods" in as an additional the "custom requirements" reasons, but that then sounds weird
with the later sentence

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.

  1. they're "enabled by default" but we don't have a comment on how to disable them (it's a user setting of transpile, not a BackendV2-author setting)
  2. I think the whole rest of the sentence about "should only be used ..." is kind of unnecessary? It boils down to "if you believe Qiskit's defaults are not optimal for your backend", which might be a cleaner way of phrasing more of this text (since the context of the text has changed a little now it's every stage, and not just translation/scheduling).

Comment on lines +54 to +59
A backend object can optionally contain methods named:
* ``get_init_stage_plugin``
* ``get_layout_stage_plugin``
* ``get_routing_stage_plugin``
* ``get_translation_stage_plugin``
* ``get_scheduling_stage_plugin``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also the docs-build error is a true error and it's complaining that these bullet points are indented by a space, when they shouldn't be indented at all (but you might need a blank line before and after them?).

@jakelishman
Copy link
Copy Markdown
Member

Oh yeah, I forgot to write in the top review comment: given we already did this once in #8648, I think I'm roughly on board with doing it again now, even though I don't think it's technically semver stable to add extra methods to a base class in a minor release.

I would like to see if we can design BackendV3 with more forwards-compatibility / extensibility safeguards, though, so we don't end up in this situation again when we want to extend a base class.

@mtreinish
Copy link
Copy Markdown
Member Author

mtreinish commented Apr 30, 2026

I would like to see if we can design BackendV3 with more forwards-compatibility / extensibility safeguards, though, so we don't end up in this situation again when we want to extend a base class.

The simplest thing we could probably do is for any defined transpiler stage plugin interface have the name get_{stage}_stage_plugin be a reserved method name on BackendV3 and have the semantics explicitly outlined in the class definition and documentation so it's future facing. That way when we add new stage plugins we have it defined already.

@jakelishman
Copy link
Copy Markdown
Member

If we're going to do future compatibility by reserved names, I think we need to reserve a way larger namespace of methods, like hook_* or something like that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Added Add an "Added" entry in the GitHub Release changelog. mod: providers Related to the backend and job abstractions

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

4 participants