-
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
Optimise SparsePauliOp.from_operator
#11557
Optimise SparsePauliOp.from_operator
#11557
Conversation
One or more of the the following people are requested to review this:
|
e13384d
to
3d477f6
Compare
Pull Request Test Coverage Report for Build 11597689008Warning: 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 |
f0a95f1
to
2bc120c
Compare
22f7962
to
589137b
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.
There's also a merge conflict now that #11388 merged
releasenotes/notes/fast-sparse-pauli-operator-b41cacf11e8c4e0e.yaml
Outdated
Show resolved
Hide resolved
589137b
to
06121b9
Compare
Now rebased over |
58caacb
to
9ad36a3
Compare
Before looking into this very smart, efficient and complicated code, I looked into the tests, and found only one very simple test for the
Since this PR (as well the one before it #11133) replaced a simple code of about ~10 lines in Python by a quite complicated and much longer code in Rust, I would suggest to enhance the test code by some tests of the following form:
There are also several methods that tests the
|
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 a very optimized version of the original code (~10 lines in Python) as well as the code in the paper of HBG (<100 lines in Python).
The code is of high quality, and so my main concern is that these optimizations should be better explained and documented, and some further tests should be added (see my previous comment).
out_list.push(PauliLocation::begin(num_qubits)); | ||
} | ||
_ => { | ||
unsafe { scratch.set_len(side * side) }; |
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 there a way to test that there is no memory leakage?
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 can write a Rust-space test, since that'll cause all this code to be run under Miri, which checks this kind of thing. In the mean time I made this scratch.set_len(scratch.capacity())
so it's clear that it's not setting it wider than is allocated.
// This set of `extend` calls is effectively an 8-fold unrolling of the "natural" loop through | ||
// each bit, where the initial `if` statements are handling the remainder (the up-to 7 | ||
// least-significant bits). In practice, it's probably unlikely that people are decomposing | ||
// 16q+ operators, since that's a pretty huge matrix already. |
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 it possible that an operator will have many qubits but with a few Pauli terms (sparse)?
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.
Yeah, very much so. Everything here should handle those cases fine, though.
This rewrites the `from_operator` handling (again!) from the initial Rust implementation of the recursive matrix-addition form into an iterative approach that re-uses the same scratch memory all the way down. This is significantly faster, and allocates far less often, although in practice the peak heap memory usage will be not dissimilar. The algorithm is rewritten to be a manual stack-based iteration, rather than a functional recursion. The size of a single stack entry in the iteration is one `usize`, which is drastically smaller than whatever per-function-call stack will have been used before.
9ad36a3
to
e2a97e4
Compare
I've updated all the documentation - I'll add some more tests tomorrow. |
Ok, I've pushed up a handful more tests in 80a3e87, which should substantially increase the mathematical coverage, and refactored a small amount to make it possible to write a couple of simple Rust-space tests so all the |
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.
LGTM, thanks!
Summary
This rewrites the
from_operator
handling (again!) from the initial Rust implementation of the recursive matrix-addition form into an iterative approach that re-uses the same scratch memory all the way down. This is significantly faster, and allocates far less often, although in practice the peak heap memory usage will be not dissimilar.The algorithm is rewritten to be a manual stack-based iteration, rather than a functional recursion. The size of a single stack entry in the iteration is one
usize
, which is drastically smaller than whatever per-function-call stack will have been used before.Details and comments
This is the rewrite alluded to in #11133. Using the same timing script, the new graph looks like:
where the absolute timing of the the 10q operator is ~40ms compared to ~227ms, so it's a 5-6x speedup at that scale (for a fully dense operator). Decompositions of up to 4q operators are now entirely lost in the noise of the construction time of
SparsePauliOp
(which tbh probably says more about the overheads of thequantum_info
module than anything else...).Still no parallelism here, but honestly, I'm not even sure it's worth putting in the time to do that other than casual interest.