-
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
Fix potential infinite loop in SabreSwap #7970
Conversation
This adds a "release valve" mechanism to `SabreSwap`, so that it will always eventually make forwards progress and terminate the run. Before this commit, it was possible for certain pathological circuits (see `looping_circuit` in `test/python/transpiler/test_sabre_swap.py` for an example) to get stuck in a stable local minimum of the 'lookahead' or 'decay' heuristics (no matter the weightings). The release mechanism is done with very small per-loop overhead for the vastly more common good paths; we simply count how many iterations we have been through since we made progress, and the minimum weight in the coupling map to be overcome to make progress again. Once the number of iterations without progress exceeds some value, we backtrack (inefficiently, because this is the bad path) to the last time we made progress, and forcibly insert swaps to bring the nearest gate together. We then continue like normal. There are also a few minor optimisations in this commit that prevent recalculating sets that we already know; `extended_set` is fixed by knowledge of the `front_layer` if the DAG isn't (meaningfully) changed, so there's no need to recalculate it on each loop.
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:
|
Pull Request Test Coverage Report for Build 2202565163
💛 - 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.
nice job with debugging this!
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.
Overall this looks good, thanks for diving in and fixing this! I just have a few comments inline all pretty minor
Co-authored-by: Matthew Treinish <[email protected]>
The `max_iterations_without_progress` value was originally exposed to users via the constructor. It was added because I thought I'd need a way to force the backtracking algorithm to occur to test the behaviour. When I wrote the tests, I ended up using Aer to find the keys, which is sufficiently fast that we can verify validity using the regular infinite loop circuit, and the argument is unnecessary. It was highly unlikely to actually be useful in real applications, so it does not need to be added.
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 now, thanks for the updates.
split = len(path) // 2 | ||
forwards, backwards = path[1:split], reversed(path[split:-1]) | ||
for swap in forwards: | ||
yield v_start, layout._p2v[swap] | ||
for swap in backwards: | ||
yield v_goal, layout._p2v[swap] |
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.
Just a comment for the record this hurt my head for a little while until I realized it's intended to work on virtual bits and that the layout is updated on each yield. So it's not the sequence of physical bits to add swaps on
* Fix potential infinite loop in SabreSwap This adds a "release valve" mechanism to `SabreSwap`, so that it will always eventually make forwards progress and terminate the run. Before this commit, it was possible for certain pathological circuits (see `looping_circuit` in `test/python/transpiler/test_sabre_swap.py` for an example) to get stuck in a stable local minimum of the 'lookahead' or 'decay' heuristics (no matter the weightings). The release mechanism is done with very small per-loop overhead for the vastly more common good paths; we simply count how many iterations we have been through since we made progress, and the minimum weight in the coupling map to be overcome to make progress again. Once the number of iterations without progress exceeds some value, we backtrack (inefficiently, because this is the bad path) to the last time we made progress, and forcibly insert swaps to bring the nearest gate together. We then continue like normal. There are also a few minor optimisations in this commit that prevent recalculating sets that we already know; `extended_set` is fixed by knowledge of the `front_layer` if the DAG isn't (meaningfully) changed, so there's no need to recalculate it on each loop. * Reduce depth of greedy swap insertion * Add link in release note Co-authored-by: Matthew Treinish <[email protected]> * Remove extra keyword argument in SabreSwap constructor The `max_iterations_without_progress` value was originally exposed to users via the constructor. It was added because I thought I'd need a way to force the backtracking algorithm to occur to test the behaviour. When I wrote the tests, I ended up using Aer to find the keys, which is sufficiently fast that we can verify validity using the regular infinite loop circuit, and the argument is unnecessary. It was highly unlikely to actually be useful in real applications, so it does not need to be added. * Remove useless return from _add_greedy_swaps Co-authored-by: Matthew Treinish <[email protected]> (cherry picked from commit f414273)
* Fix potential infinite loop in SabreSwap This adds a "release valve" mechanism to `SabreSwap`, so that it will always eventually make forwards progress and terminate the run. Before this commit, it was possible for certain pathological circuits (see `looping_circuit` in `test/python/transpiler/test_sabre_swap.py` for an example) to get stuck in a stable local minimum of the 'lookahead' or 'decay' heuristics (no matter the weightings). The release mechanism is done with very small per-loop overhead for the vastly more common good paths; we simply count how many iterations we have been through since we made progress, and the minimum weight in the coupling map to be overcome to make progress again. Once the number of iterations without progress exceeds some value, we backtrack (inefficiently, because this is the bad path) to the last time we made progress, and forcibly insert swaps to bring the nearest gate together. We then continue like normal. There are also a few minor optimisations in this commit that prevent recalculating sets that we already know; `extended_set` is fixed by knowledge of the `front_layer` if the DAG isn't (meaningfully) changed, so there's no need to recalculate it on each loop. * Reduce depth of greedy swap insertion * Add link in release note Co-authored-by: Matthew Treinish <[email protected]> * Remove extra keyword argument in SabreSwap constructor The `max_iterations_without_progress` value was originally exposed to users via the constructor. It was added because I thought I'd need a way to force the backtracking algorithm to occur to test the behaviour. When I wrote the tests, I ended up using Aer to find the keys, which is sufficiently fast that we can verify validity using the regular infinite loop circuit, and the argument is unnecessary. It was highly unlikely to actually be useful in real applications, so it does not need to be added. * Remove useless return from _add_greedy_swaps Co-authored-by: Matthew Treinish <[email protected]> (cherry picked from commit f414273) Co-authored-by: Jake Lishman <[email protected]>
Summary
This adds a "release valve" mechanism to
SabreSwap
, so that it willalways eventually make forwards progress and terminate the run. Before
this commit, it was possible for certain pathological circuits
(see
looping_circuit
intest/python/transpiler/test_sabre_swap.py
for an example) to get stuck in a stable local minimum of the
'lookahead' or 'decay' heuristics (no matter the weightings).
The release mechanism is done with very small per-loop overhead for the
vastly more common good paths; we simply count how many iterations we
have been through since we made progress, and the minimum weight in the
coupling map to be overcome to make progress again. Once the
number of iterations without progress exceeds some value, we backtrack
(inefficiently, because this is the bad path) to the last time we made
progress, and forcibly insert swaps to bring the nearest gate together.
We then continue like normal.
There are also a few minor optimisations in this commit that prevent
recalculating sets that we already know;
extended_set
is fixed byknowledge of the
front_layer
if the DAG isn't (meaningfully) changed,so there's no need to recalculate it on each loop.
Details and comments
Fix #7707.
This commit also knocks off some of the low-hanging fruit in the efficiency of the
SabreSwap
implementation, since I had to touch it anyway; we don't recalculate theextended_set
on each iteration if no progress on the first layer has been made, because it shouldn't have changed. We just store it and move on. This is good for a 25-30% speedup inSabreSwap
for 20 qubits at a depth of 1024, and it passes some savings on to a generaltranspile(optimization_level=3)
, which is most notable for QV circuits.