Skip to content
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

new unroll for-loops transpilation pass #9670

Merged
merged 24 commits into from
Apr 13, 2023

Conversation

1ucian0
Copy link
Member

@1ucian0 1ucian0 commented Feb 26, 2023

Summary

This transpilation pass unrolls for loops when possible. Things like for x in {0, 3, 4} {rx(x) qr[1];} will turn into rx(0) qr[1]; rx(3) qr[1]; rx(4) qr[1];.

Details and comments

Loop unrolling is a common optimization which is particularly easy in Qiskit for-loops. There are some exceptions though: when there is a ContinueLoopOp or a BreakLoopOp, which they might be inside a IfElseOp.

@qiskit-bot
Copy link
Collaborator

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:

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Feb 26, 2023

Pull Request Test Coverage Report for Build 4693010276

  • 35 of 36 (97.22%) changed or added relevant lines in 3 files are covered.
  • 151 unchanged lines in 14 files lost coverage.
  • Overall coverage increased (+0.3%) to 85.767%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/utils/unroll_forloops.py 33 34 97.06%
Files with Coverage Reduction New Missed Lines %
qiskit/primitives/backend_sampler.py 1 98.81%
qiskit/transpiler/passes/optimization/template_matching/template_substitution.py 1 93.2%
qiskit/primitives/sampler.py 2 97.1%
qiskit/circuit/quantumcircuit.py 3 93.98%
qiskit/primitives/base/base_sampler.py 3 92.68%
qiskit/primitives/estimator.py 3 96.05%
qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py 5 86.32%
qiskit/primitives/base/base_estimator.py 5 91.38%
qiskit/primitives/primitive_job.py 5 82.5%
qiskit/primitives/backend_estimator.py 7 95.77%
Totals Coverage Status
Change from base Build 4678110396: 0.3%
Covered Lines: 70319
Relevant Lines: 81988

💛 - Coveralls

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

This starts getting into questions that I'm not sure we need to be answering at the Qiskit level; it's a purely classical action, and we really don't have the context to know whether it's "better" at this level (given it's been put in optimisations). Unrolling loops might be better if it avoids a tight classical jump from needing to be synced, but equally it could easily blow out program memory on certain controllers. I'm not really sure it's something we should be trying to do at the Qiskit level in the guise of optimisation.

That said, it might not hurt for us to include it as a simple pass for backends that don't support for_loop; we can use it as part of the translation process for those, or even just expose it like we do ConvertCifToIfElse (or whatever it's called) to allow backends an easier upgrade route in their custom stages.

qiskit/transpiler/passes/optimization/unroll_forloops.py Outdated Show resolved Hide resolved
@1ucian0
Copy link
Member Author

1ucian0 commented Feb 27, 2023

I'm not really sure it's something we should be trying to do at the Qiskit level in the guise of "optimisation".

I agree. I'm not advocating for having it in any default pipeline.

@1ucian0
Copy link
Member Author

1ucian0 commented Feb 28, 2023

but equally it could easily blow out program memory on certain controllers.

Maybe the pass can be configurable for the amount of maximum repetitions that is allow to unroll given the size of the indexset to be known)?

@1ucian0
Copy link
Member Author

1ucian0 commented Feb 28, 2023

but equally it could easily blow out program memory on certain controllers.

Maybe the pass can be configurable for the amount of maximum repetitions that is allow to unroll given the size of the indexset to be known)?

done, with max_target_depth parameter.

@1ucian0 1ucian0 marked this pull request as ready for review February 28, 2023 20:50
@1ucian0 1ucian0 requested a review from a team as a code owner February 28, 2023 20:50
@1ucian0 1ucian0 added the Changelog: New Feature Include in the "Added" section of the changelog label Feb 28, 2023
@1ucian0 1ucian0 added this to the 0.24.0 milestone Feb 28, 2023
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Naming nitpicks: can we place this pass in passes.utils instead (next to convert_conditions_to_if_ops.py)? It feels wrong to call it an "optimisation" to me in Qiskit land - we're not a classical compiler nor should we be right now (if ever?), and I think this pass's place is more of a translation utility.

qiskit/transpiler/passes/optimization/unroll_forloops.py Outdated Show resolved Hide resolved
Comment on lines 65 to 67

@classmethod
def body_contains_continue_or_break(cls, circuit):
Copy link
Member

Choose a reason for hiding this comment

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

There's no real reason for this to be a class method (doesn't need to access cls, and this transpiler pass isn't designed/safe for subclassing) - it could just be a stand alone function, but it's not a big deal.

Copy link
Member Author

@1ucian0 1ucian0 Mar 3, 2023

Choose a reason for hiding this comment

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

done in b33e1fa

Comment on lines 22 to 28
"""UnrollForLoops transpilation pass unrolls for-loops when possible. Things like
`for x in {0, 3, 4} {rx(x) qr[1];}` will turn into `rx(0) qr[1]; rx(3) qr[1]; rx(4) qr[1];`.
"""

def __init__(self, max_target_depth=-1):
"""UnrollForLoops transpilation pass unrolls for-loops when possible. Things like
`for x in {0, 3, 4} {rx(x) qr[1];}` will turn into `rx(0) qr[1]; rx(3) qr[1]; rx(4) qr[1];`.
Copy link
Member

Choose a reason for hiding this comment

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

No need to repeat documentation between the class docstring and __init__ - both get concatenated into the docs output. That said, we need to ensure that qiskit.transpiler.passes.<whichever category> and qiskit.transpiler.passes both import this into their namespace, and the qiskit.transpiler.passes docstring initiates documentation of the class.

Copy link
Member Author

@1ucian0 1ucian0 Mar 3, 2023

Choose a reason for hiding this comment

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

Indeed! fixed in fbbcdb7

qiskit/transpiler/passes/optimization/unroll_forloops.py Outdated Show resolved Hide resolved
test/python/transpiler/test_unroll_forloops.py Outdated Show resolved Hide resolved
Comment on lines 50 to 53
# skip unrolling if it results in bigger than max_target_depth
if self.max_target_depth > 0 and len(indexset) * body.depth() > self.max_target_depth:
continue

Copy link
Member

Choose a reason for hiding this comment

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

QuantumCircuit.depth doesn't treat control-flow specially, i.e. a single containing for loop looks like it contributes just 1 to the depth.

I think that's probably actually the right choice here, but I think it's worth mentioning: if an inner for loop is not unrolled because of the max-depth constraint (during the depth-first nature of control_flow.trivial_recurse), then a containing for loop is still eligible for unrolling using this metric. I think this behaviour is the right choice, I just think it's worth pointing out. We may even want a test to enforce it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I extended the docstrings with a ..note:: and added a test in c1966fe and 8039977

@1ucian0
Copy link
Member Author

1ucian0 commented Mar 3, 2023

oh! while showing this to my team, I noticed that parameterless loops (circuit.for_loop(indexset, None, body, [1, 2, 3], [1]) ) do not work. So, on hold. It should be an easy fix.

@1ucian0 1ucian0 added the on hold Can not fix yet label Mar 3, 2023
@1ucian0
Copy link
Member Author

1ucian0 commented Mar 3, 2023

can we place this pass in passes.utils instead (next to convert_conditions_to_if_ops.py)?

Makes sense to me, done in 41312ad

@1ucian0 1ucian0 removed the on hold Can not fix yet label Mar 3, 2023
@1ucian0 1ucian0 changed the title dynamic circuit optimization: unroll forloops new unroll for-loops transpilation pass Mar 6, 2023
@1ucian0
Copy link
Member Author

1ucian0 commented Mar 10, 2023

while showing this to my team, I noticed that parameterless loops (circuit.for_loop(indexset, None, body, [1, 2, 3], [1]) ) do not work. So, on hold. It should be an easy fix.

fixed in 9422e4f and on hold removed

It should be ready to go!

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

The code here looks good to me (minor debugging print aside). I think we agreed to add this pass but just not include it in default pass managers - it'd be good to get @1ucian0 and @kdk to confirm that I'm remembering that correctly.

test/python/transpiler/test_unroll_forloops.py Outdated Show resolved Hide resolved
@mtreinish
Copy link
Member

I think we agreed to add this pass but just not include it in default pass managers

This matches my recollection as well.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Ok, that's good enough for me.

@jakelishman jakelishman added this pull request to the merge queue Apr 13, 2023
Merged via the queue into Qiskit:main with commit 7b147a0 Apr 13, 2023
giacomoRanieri pushed a commit to giacomoRanieri/qiskit-terra that referenced this pull request Apr 16, 2023
* dynamic circuit optimization: unroll for loops

* exceptions

* check inside conditional blocks

* docs

* reno

* Update qiskit/transpiler/passes/optimization/unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* parameterless support

* moved to utils

* no classmethod, but function

* docstring and __init__

* Update qiskit/transpiler/passes/optimization/unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* Update test/python/transpiler/test_unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* nested for-loops test

* docstring note

* new test with c_if

* Remove commented-out code

---------

Co-authored-by: Jake Lishman <jake@binhbar.com>
king-p3nguin pushed a commit to king-p3nguin/qiskit-terra that referenced this pull request May 22, 2023
* dynamic circuit optimization: unroll for loops

* exceptions

* check inside conditional blocks

* docs

* reno

* Update qiskit/transpiler/passes/optimization/unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* parameterless support

* moved to utils

* no classmethod, but function

* docstring and __init__

* Update qiskit/transpiler/passes/optimization/unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* Update test/python/transpiler/test_unroll_forloops.py

Co-authored-by: Jake Lishman <jake@binhbar.com>

* nested for-loops test

* docstring note

* new test with c_if

* Remove commented-out code

---------

Co-authored-by: Jake Lishman <jake@binhbar.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants