-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Update pulse gate transpiler pass to use target. #9587
Update pulse gate transpiler pass to use target. #9587
Conversation
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the the following people are requested to review this:
|
releasenotes/notes/update-pulse-gate-pass-for-target-ebfb0ec9571f058e.yaml
Outdated
Show resolved
Hide resolved
@@ -64,6 +64,15 @@ def get_schedule(self, *args, **kwargs) -> Union[Schedule, ScheduleBlock]: | |||
""" | |||
pass | |||
|
|||
@abstractmethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you do this without breaking compatibility? Wouldn't adding a new required abstract method break any downstream usage of CalibrationEntry
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 33b76b3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this has been fixed (or if it was the fix got undone at some point), because this is still a new abstractmethod
which will cause a hard failure for any existing implementations of this interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously I implemented an abstractmethod to return schedule duration. Note that schedule duration is computed here for added schedule from the inst map, but this requires Qobj parsing that is the biggest performance bottleneck for non-pulse users. So I have implemented a custom (complicated) method that computes the duration without parsing Qobj.
https://github.com/Qiskit/qiskit-terra/blob/bf3bb96cebc141f79ce46b5938724eef30778c8e/qiskit/transpiler/target.py#L449-L457
New abstract method just returns whether it is provided by backend or by end-user. If your schedule is provided from backend, you should know duration of it and we don't need to update duration in the first place. This avoids unnecessary Qobj parsing for duration. If it is user-provided one, usually they don't define schedule with JSON format, and no parsing overhead.
48e6724
to
c78ead8
Compare
f692207 and bbc0a0e are from #9597 so I'll rebase this PR after it is merged. Currently I am struggling with handling of synthesis and decomposition passes (see failed test). In main branch, these passes are executed to get optimal gate decomposition based on Unfortunately, I am not familiar with these optimization passes. What would be the correct behavior of these passes when gate fidelity is not available? |
Thanks Matthew. As far as I saw, The getattr default value only works when instruction property itself is (1q gate bug is probably resolved by #9578?) |
For 1q yeah I think #9578 should fix it. For the general question though the pass should be robust enough to handle no error definition or no instruction properties definition for it's scoring heuristics. What the passes are trying to do is after synthesizing circuits in all the target bases which the Edit: This internal implementation details of the target are tricky, I've had a PR up to add helper methods to hide these details #9158 (I need to circle back to it for 0.24). #9617 should fix the issue |
c78ead8
to
8529043
Compare
Rebased. Thanks @mtreinish. I reviewed #9158 |
When inst_map is provided, it copies schedules there into target instance. This fixes a bug that custom schedules in the inst_map are ignored when transpiling circuit with V2 backend. To support this behavior, internal machinery of Target is updated so that a target instance can update itself only with inst_map without raising any error. Also InstructionProperties.calibration now only stores CalibrationEntry instances. When Schedule or ScheduleBlock are provided as a calibration, it converts schedule into CalibrationEntry instance.
IBM backend still provide ugate calibrations in CmdDef and they are loaded in the instmap. If we update target with the instmap, these gates are accidentally registered in the target, and they may be used in the following 1q decomposition. To prevent this, update_from_instruction_schedule_map method is updated.
…g-instmap-v2-backend
57ea4e2
to
066d9d8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this and ensuring the pulse gates path works as expected with a target. I'm looking forward to having this better handling of calibrations and InstructionScheduleMap
compatibility in place. I left a few comments inline, but my biggest concern right now is around api compatibility both for CalibrationEntry
as a whole (I'm not sure what the downstream upgrade path looks like because I'm pretty sure any downstream users will break with these changes) and also to a lesser degree the behavior around setting InstructionProperties.calibration
(I'm a bit worried that this will subtly change things causing the PulseGates
pass to run when it doesn't need to.
The other question that I have, which is kind of future facing, is in a world with only a Target
what does the replacement for this line look like: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/preset_passmanagers/common.py#L449
We shouldn't change this here, but I expect we'll need another API added to the target for this, which we can do in a follow up PR.
@@ -34,11 +34,12 @@ class CalibrationEntry(metaclass=ABCMeta): | |||
"""A metaclass of a calibration entry.""" | |||
|
|||
@abstractmethod | |||
def define(self, definition: Any): | |||
def define(self, definition: Any, user_provided: bool): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be a breaking API change for any downstream usage of CalibrationEntry
right? Like if you have an existing CalibrationEntry
subclass in 0.23.x then when upgrading to 0.24.0 that class's define
method signature will no long match the abstract interface definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, all subclasses provide default value for user_provided
to conform to previous default behavior. Currently in 0.23.x, this flag is implicitly determined by type information and only PulseQobjDef
is recognized as a backend provided entry. However one may want to provide backend calibration as ScheduleDef
, to bypass cumbersome JSON data generation. This could be a separate PR though.
@@ -64,6 +64,15 @@ def get_schedule(self, *args, **kwargs) -> Union[Schedule, ScheduleBlock]: | |||
""" | |||
pass | |||
|
|||
@abstractmethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this has been fixed (or if it was the fix got undone at some point), because this is still a new abstractmethod
which will cause a hard failure for any existing implementations of this interface.
@@ -89,7 +92,7 @@ def calibration(self): | |||
def calibration(self, calibration: Union[Schedule, ScheduleBlock, CalibrationEntry]): | |||
if isinstance(calibration, (Schedule, ScheduleBlock)): | |||
new_entry = ScheduleDef() | |||
new_entry.define(calibration) | |||
new_entry.define(calibration, user_provided=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually going to be the case? Like can we safely assume that no backend is going to set a calibration with the setter? If we need to make this assumption we should at least be sure that it's documented clearly somewhere because this is new behavior. The other option I'm wondering if it would be better is to add a dedicated method like set_custom_calibration
which has a kwarg to set this (and defaults to True
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. If a provider defines a backend schedule, one can directly create CalibrationEntry
instance with user_provided=False
. This code block is invoked only when .calibration
setter is called with raw Schedule
or ScheduleBlock
, which is likely the workflow of the end users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then can we document this behavior? I think it's a bit non-obvious without reading the code. I agree the typical backend construction should be populating the calibration via the InstructionProperties
constructor but we should make it clear that if a provider were to add calibrations after initial construction via the setter it will be treated as a custom calibration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in f203f59. I think setter docs is not rendered in Sphinx build, so added detailed note section to the property method.
releasenotes/notes/update-pulse-gate-pass-for-target-ebfb0ec9571f058e.yaml
Outdated
Show resolved
Hide resolved
Thanks Matthew for the review. Regarding the backward compatibility
I didn't check these pass factory functions. We should update them to cover the situation that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, if CalibrationEntry
is an internal interface (I was initially confused because of the EXPERIMENT_SERVICE
in the publisher enum and assumed something external might be using it) then this mostly LGTM now. Just two small doc things, the first is an inline, but the other is to add something to the class docstring for CalibrationEntry
saying it's private without a stable user facing API. I realize now it's in the module docstring but having it explicitly in the class would be good so we make it clear if someone was randomly using it outside of terra.
@@ -89,7 +92,7 @@ def calibration(self): | |||
def calibration(self, calibration: Union[Schedule, ScheduleBlock, CalibrationEntry]): | |||
if isinstance(calibration, (Schedule, ScheduleBlock)): | |||
new_entry = ScheduleDef() | |||
new_entry.define(calibration) | |||
new_entry.define(calibration, user_provided=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then can we document this behavior? I think it's a bit non-obvious without reading the code. I agree the typical backend construction should be populating the calibration via the InstructionProperties
constructor but we should make it clear that if a provider were to add calibrations after initial construction via the setter it will be treated as a custom calibration.
Another change in f217d01 is necessary to prevent a bug but it might be slight performance regression with deepcopy. This is only triggered when the instmap is provided and thus no impact for the workflow of standard applications. We should cleanup the handling of transpiler arguments maybe in next release. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM now. Thanks for the updates and adding the note sections to the docs. I think that makes it much more explicit how this works now.
* Update PulseGate pass to use Target internally. When inst_map is provided, it copies schedules there into target instance. This fixes a bug that custom schedules in the inst_map are ignored when transpiling circuit with V2 backend. To support this behavior, internal machinery of Target is updated so that a target instance can update itself only with inst_map without raising any error. Also InstructionProperties.calibration now only stores CalibrationEntry instances. When Schedule or ScheduleBlock are provided as a calibration, it converts schedule into CalibrationEntry instance. * Remove fix note * Remove get_duration * Update the logic to get instruction object * Update target immediately when inst map is available * Update tests * Fix edge case. IBM backend still provide ugate calibrations in CmdDef and they are loaded in the instmap. If we update target with the instmap, these gates are accidentally registered in the target, and they may be used in the following 1q decomposition. To prevent this, update_from_instruction_schedule_map method is updated. * cleanup release note * Minor review suggestions * More strict gate uniformity check when create from schedules. * Added note for calibration behavior * More documentation for CalibrationEntry * Add logic to prevent unintentional backend mutation with instmap. * fix lint
* Update PulseGate pass to use Target internally. When inst_map is provided, it copies schedules there into target instance. This fixes a bug that custom schedules in the inst_map are ignored when transpiling circuit with V2 backend. To support this behavior, internal machinery of Target is updated so that a target instance can update itself only with inst_map without raising any error. Also InstructionProperties.calibration now only stores CalibrationEntry instances. When Schedule or ScheduleBlock are provided as a calibration, it converts schedule into CalibrationEntry instance. * Remove fix note * Remove get_duration * Update the logic to get instruction object * Update target immediately when inst map is available * Update tests * Fix edge case. IBM backend still provide ugate calibrations in CmdDef and they are loaded in the instmap. If we update target with the instmap, these gates are accidentally registered in the target, and they may be used in the following 1q decomposition. To prevent this, update_from_instruction_schedule_map method is updated. * cleanup release note * Minor review suggestions * More strict gate uniformity check when create from schedules. * Added note for calibration behavior * More documentation for CalibrationEntry * Add logic to prevent unintentional backend mutation with instmap. * fix lint
* Update PulseGate pass to use Target internally. When inst_map is provided, it copies schedules there into target instance. This fixes a bug that custom schedules in the inst_map are ignored when transpiling circuit with V2 backend. To support this behavior, internal machinery of Target is updated so that a target instance can update itself only with inst_map without raising any error. Also InstructionProperties.calibration now only stores CalibrationEntry instances. When Schedule or ScheduleBlock are provided as a calibration, it converts schedule into CalibrationEntry instance. * Remove fix note * Remove get_duration * Update the logic to get instruction object * Update target immediately when inst map is available * Update tests * Fix edge case. IBM backend still provide ugate calibrations in CmdDef and they are loaded in the instmap. If we update target with the instmap, these gates are accidentally registered in the target, and they may be used in the following 1q decomposition. To prevent this, update_from_instruction_schedule_map method is updated. * cleanup release note * Minor review suggestions * More strict gate uniformity check when create from schedules. * Added note for calibration behavior * More documentation for CalibrationEntry * Add logic to prevent unintentional backend mutation with instmap. * fix lint
Summary
PulseGate pass is updated to internally use Target, rather than InstructionScheduleMap. This is to fix a bug that custom gate definition is ignored when circuit is transpiled with v2 backend. In the previous implementation, target is always provided when the backend argument is V2, and target has higher priority than the inst_map in the PulseGate pass. After this update, custom gates in the inst_map are copied into target before transform.
Fix #9489
Details and comments
There are several updates to realize above behavior. First,
Target.update_from_instruction_schedule_map
has been updated not to raise an error when the method is called without inst_name_map.InstructionProperties._calibration
now only takesCalibrationEntry
so that we can always getSignature
object from there to parse parameter value-object mapping to lower the OpNode with assigned parameters (i.e. CircuitInstruction doesn't preserve parameter name after assignment).CalibrationEntry
now providesget_duration
method to efficiently compute schedule duration without parsing PulseQobj JSON.