-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Port InstructionDurationCheck
to Rust
#14700
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
Port InstructionDurationCheck
to Rust
#14700
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 following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 16472913654Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
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 getting this started, this has the potential to speed up the pass quite a bit. I left a few inline comments on how to improve it a bit, I also think there is a bug in the pass that predates this PR, but we should fix it while we're porting this, it should be simple to do.
|
||
//Check delay durations | ||
for (_, packed_op) in dag.op_nodes(false) { | ||
if packed_op.op.name() == "delay" { |
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.
It would be more efficient to do this check using the rust types directly rather than a string comparison. This should be something like:
if packed_op.op.name() == "delay" { | |
if let OperationRef::StandardInstruction(StandardInstruction::Delay(_unit)) = packed_op.op.view() { |
I haven't tested this code exactly so it's worth validating the exact syntax locally. But this is the basic idea, is use the rust type instead of the string, as this ends up be 2 integer comparisons vs a string comparison and will be faster, especially since this is being run for ever node in the dag.
for (_, packed_op) in dag.op_nodes(false) { | ||
if packed_op.op.name() == "delay" { | ||
let params = packed_op.params_view(); | ||
if let Some(param) = params.first() { |
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 this ever be None
? If not I think we should probably error because the DAGCircuit
is invalid.
_ => continue, | ||
}; | ||
|
||
if !(duration % acquire_align == 0 || duration % pulse_align == 0) { |
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.
FWIW %
in rust isn't the same as %
in python. In this case I don't think it matters but whenever I see this I always want to double check whether %
is correct or whether I should be using rem_euclid
instead. It's probably worth double checking.
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 letting me know. I did check, both acquire_align
and pulse_align
seem to accept negative integer values (though I don't know if negative alignment values make sense here) so it is probably best to use rem_euclid
instead of %
.
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.
Update: Unlike what I said earlier, I think we can stick to using u32
types for the alignments and the %
operator for alignment checks. While Python technically allows passing negative values for acquire_align
and pulse_align
, it won't be meaningful. Target
's implementation too always returns unsigned integers for both acquire_align and pulse_align, so using u32 in Rust will continue to enforce this constraint.
let duration = match param { | ||
Param::Float(val) => *val as u32, | ||
_ => continue, | ||
}; |
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 correct? I would expect Param::Obj
when the unit is dt
because we're storing an integer. This will skip that case and we won't validate the alignment.
Also, shouldn't we be validating the unit here, like if the delay is in units of ns
the alignment in terms of dt
being checked below doesn't work. I realize you faithfully ported this from Python, but it doesn't seem like the python logic was correct. If the unit is not in terms of dt
we can't check the alignment and this pass won't be able to handle that delay.
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 see, I will make the changes regarding Param::Obj
. Just to clarify, should we skip on checking the alignment if the delay unit is anything other than dt
, or should we convert the unit from seconds to dt
first and then check the alignment?
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 pass doesn't do unit conversion. There is a TimeUnitConversion
transpiler pass that runs which is supposed to handle that: https://quantum.cloud.ibm.com/docs/en/api/qiskit/1.2/qiskit.transpiler.passes.TimeUnitConversion if it's not in units of dt
either skipping or erroring is a appropriate. I might lean toward returning an error because the assumptions this pass works under aren't valid. But there might be backwards compatibility concerns with that, it's worth figuring out how the current python implementation behaves and work against 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.
Good progress! I just have one comment (the rest are nitpicks)
) -> PyResult<bool> { | ||
let num_stretches = dag.num_stretches(); | ||
|
||
//Rescheduling is not necessary |
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.
Minor nitpick: Add a whitespace after the //
in the comment.
//Rescheduling is not necessary | |
// Rescheduling is not necessary |
return Ok(false); | ||
} | ||
|
||
//Check delay durations |
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.
See https://github.com/Qiskit/qiskit/pull/14700/files#r2193260197
//Check delay durations | |
// Check delay durations |
Param::Float(val) => *val as u32, | ||
_ => continue, |
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 the correct way of handling this? IIRC a duration can either be a number or an Expr
. So we know that brings the question of whether we can keep this rust native? I know Expr already exists in Rust but we don't yet use it within instructions.
Thank you @mtreinish and @raynelfss for your comments! I have made the suggested changes. I think it is ready for another review, please let me know if there is anything else that I could improve upon. |
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.
Most of it looks good but I have some concerns about the dt
check.
return Err(TranspilerError::new_err( | ||
"Delay duration must have dt unit for checking alignment.", | ||
)); | ||
} |
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 correct? I know Matthew mentioned that throwing an error here might be the best option given how the pass works. Since the error didn't exist in Python, does it seem to break any of the existing pipelines? (Judging from the CI runs everything seems to run as expected, but just out of curiosity).
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.
So far it doesn't break any of the pipelines. The TimeUnitConversion
pass takes care of unifying the units before sending it to this pass. Since the alignment constraints are only meaningful when defined in dt
and checking the alignment would only make sense if the unit of durations is also dt
. If we are getting unit as seconds, I believe it is safe to return the error.
let duration = match param { | ||
Param::Float(val) => Some(*val as u32), | ||
Param::Obj(val) | Param::ParameterExpression(val) => { | ||
val.bind(py).extract::<u32>().ok() | ||
} |
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.
There are two things you can do about this match arm here depending on what you do about the comment above:
- If what was mentioned above is true, and we don't operate on anything that's not on terms of dt, we should drop the
Float
andParameterExpression
arms sincedt
will always useint
as a type. - If we will be removing the DT check and will continue either way instead, we should drop only the
ParameterExpression
arm.
If any of the dropped cases is found, either erroring or skipping should be expected. I'm leaning more towards erroring so the user knows something is wrong.
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 f364f35!
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 is about ready to merge. Just one comment about the new error message.
let duration = match param { | ||
Param::Obj(val) => val.bind(py).extract::<u32>(), | ||
_ => Err(TranspilerError::new_err( | ||
"Delay instruction parameter is not an object.", |
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.
"Delay instruction parameter is not an object.", | |
"The provided Delay duration is not in terms of dt.", |
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!
* Add rust-native instruction duration check in transpiler * Make num_stretches method public in DAGCircuit implementation * Improve error handling and parameter validation * Enforce dt unit for delay durations and update alignment parameters to accept unsigned integers * Modify match arm to return error when instruction paramter is not an object * Correct the error message for when duration is not in dt
* Add rust-native instruction duration check in transpiler * Make num_stretches method public in DAGCircuit implementation * Improve error handling and parameter validation * Enforce dt unit for delay durations and update alignment parameters to accept unsigned integers * Modify match arm to return error when instruction paramter is not an object * Correct the error message for when duration is not in dt
Summary
This PR ports the
InstructionDurationCheck
analysis pass from Python to Rust for improved performance and integration with the Qiskit transpiler. No changes to pass logic or user-facing behavior are introduced.Details and comments
InstructionDurationCheck
to Rust #12267num_stretches
method inDAGCircuit
public to allow access from the new Rust pass.