-
-
Notifications
You must be signed in to change notification settings - Fork 48
✨ Add Rotation Gate Merging using Quaternions #1407
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
base: main
Are you sure you want to change the base?
✨ Add Rotation Gate Merging using Quaternions #1407
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Hey @J4MMlE 👋🏻 How much of an ask would it be to directly base this pass on the QCO dialect and its infrastructure? |
106575c to
7528b05
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 a lot @J4MMlE for the effort! The code already looks super clean in my opinion and I like the way you have implemented everything.
I do have quite a few minor comments, but they are largely just related to comments in the code (docstrings/typos).
What I did not look at in much detail is the CMake setup. I guess it would make sense if @burgholzer (after your vacation, of course) could look into that - although probably it would be most efficient to do that only once the tests are ready, too (btw, I think the point from my top-level comment below might also be interesting to consider for you).
Anyways, @J4MMlE, once you have addressed the comments and added the tests, feel free to re-request my review and I will look at the code again. Thanks a lot in the meantime!
| areUsersUnique(const mlir::ResultRange::user_range& users) { | ||
| return llvm::none_of(users, | ||
| [&](auto* user) { return user != *users.begin(); }); | ||
| } |
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 know we have had similar logic in some old passes, but could you please refresh my memory:
Is this check not the same as checking whether the number of users is equal to one? Or in other words, can op->getUsers() ever contain the same element twice?
If this is the same as the number check, then we could replace it by that, as that would reduce lines 95-100 (since the users.empty() check would also be covered by a function/operation that checks whether the number of users is exactly 1.
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 also stumbled across this while trying to come up with some test case for these checks. The QCOProgramBuilder enforces linearity by only allowing a value to be consumed once. However, the current compiler pipeline does not verify linearity like QCOProgramBuilder, so you could theoretically parse non-linear code like this:
module {
func.func @multipleUsers() {
%0 = qco.alloc : !qco.qubit
%cst = arith.constant 1.000000e+00 : f64
%1 = qco.ry(%cst) %0 : !qco.qubit -> !qco.qubit
// %1 is used by BOTH operations
%2 = qco.rz(%cst) %1 : !qco.qubit -> !qco.qubit
%3 = qco.rz(%cst) %1 : !qco.qubit -> !qco.qubit
qco.dealloc %2 : !qco.qubit
qco.dealloc %3 : !qco.qubit
return
}
}I guess code like that should be rejected? The intention here would be to clone state %1 which is not possible in the real world. Can we assume that initial mlir code is in a linear form (where each value is consumed only once)?
The resulting code is linear but %0 is deallocated twice:
module {
func.func @multipleUsers() {
%cst = arith.constant 1.000000e+00 : f64
%0 = qc.alloc : !qc.qubit
qc.ry(%cst) %0 : !qc.qubit
qc.rz(%cst) %0 : !qc.qubit
qc.rz(%cst) %0 : !qc.qubit
qc.dealloc %0 : !qc.qubit
qc.dealloc %0 : !qc.qubit
return
}
}Given the assumption that input code is linear, no checks whatsoever would actually be needed (by definition every operation would have one and only one use).
And yes, I also think that a user can only be included once in the users. For now the best solution would be to get rid of users.empty() and areUsersUnique() and replace them by a single op->hasOneUse() to ensure op is used only once. What do you think @DRovara?
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, in theory we expect linearity, as you say. We don't have a verifier because we assume this to hold by construction, no need to waste resources on verifying it. That being said, there is one situation where we just didn't find a way to enforce linearity without larger re-development efforts:
... // some classical value in %c, some qubit in %q_0
%q_1 = scf.if (%c) {
%q_0_then = qco.x %q_0 : !qco.qubit
scf.yield %q_0_then : !qco.qubit
} else {
%q_0_else = qco.y %q_0 : !qco.qubit
scf.yield %q_0_else : !qco.qubit
}Here, %q_0 is used twice, however we know that no cloning can happen because only one of the two branches will be taken.
This should be the only case where "re-using" a qubit is valid, but that's already enough to require explicit checks in a lot of passes, unfortunately.
mlir/lib/Dialect/QCO/Transforms/QuaternionMergeRotationGates.cpp
Outdated
Show resolved
Hide resolved
mlir/lib/Dialect/QCO/Transforms/QuaternionMergeRotationGates.cpp
Outdated
Show resolved
Hide resolved
|
I just remembered one more comment I wanted to make that I forgot: You talk about how you no longer have to explicitly filter for control qubits due to the new dialect structure. Imagine the following pseudo-code: Here, the first so in theory, this can definitely be merged. Now, I don't know if this is a flaw of the pass (maybe this situation should be checked explicitly), a flaw of the dialect implementation (maybe My personal gut feeling is that it would be a nice helper method to implement for the |
aaa7096 to
94ea576
Compare
f0989ad to
045857a
Compare
Description
This PR extends the rotation merging pass in the MQTOpt dialect to support quaternion-based gate fusion. This is the first step toward closing #1029.
The existing rotation merge pass only merges consecutive rotation gates of the same type (e.g.,
rx + rxorry + ry) by adding their angles.This PR introduces quaternion-based merging, which can merge rotation gates of different types (currently only single qubit gates
rx,ry,rz,u).Quaternions are widely used to represent rotations in three-dimensional space and naturally map to qubit gate rotations around the Bloch sphere. The implementation:
ugate. (This could also be done differently in the future, and directly decompose to the correct base gates by using the decomposition from ✨ Implement single-qubit gate decomposition pass #1182)Since this optimization may only be beneficial on certain quantum architectures, it is disabled by default. It can be invoked using:This implementation currently targets the legacy MQTOpt dialect. In the future, this PR will port this optimization to the new QCO dialect.For future implementation in QCO, a separate pass will be added, since simple same-type rotation gate merging is implemented as a canonicalization there but it only makes sense to have quaternion merging as a separate pass.
Checklist: