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

Fix potential infinite loop in SabreSwap #7970

Merged
merged 5 commits into from Apr 21, 2022

Conversation

jakelishman
Copy link
Member

@jakelishman jakelishman commented Apr 20, 2022

Summary

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.

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 the extended_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 in SabreSwap for 20 qubits at a depth of 1024, and it passes some savings on to a general transpile(optimization_level=3), which is most notable for QV circuits.

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.
@jakelishman jakelishman added the Changelog: Bugfix Include in the "Fixed" section of the changelog label Apr 20, 2022
@jakelishman jakelishman requested a review from a team as a code owner April 20, 2022 22:54
@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 Apr 20, 2022

Pull Request Test Coverage Report for Build 2202565163

  • 52 of 61 (85.25%) changed or added relevant lines in 1 file are covered.
  • 3 unchanged lines in 1 file lost coverage.
  • Overall coverage decreased (-0.009%) to 84.229%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/routing/sabre_swap.py 52 61 85.25%
Files with Coverage Reduction New Missed Lines %
qiskit/pulse/library/waveform.py 3 89.36%
Totals Coverage Status
Change from base Build 2198724829: -0.009%
Covered Lines: 54026
Relevant Lines: 64142

💛 - Coveralls

Copy link
Member

@ajavadia ajavadia left a 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!

@mtreinish mtreinish added the stable backport potential The bug might be minimal and/or import enough to be port to stable label Apr 21, 2022
Copy link
Member

@mtreinish mtreinish left a 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

qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
releasenotes/notes/sabreswap-loop-230ef99e61358105.yaml Outdated Show resolved Hide resolved
qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
jakelishman and others added 3 commits April 21, 2022 09:29
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
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.
Copy link
Member

@mtreinish mtreinish left a 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.

Comment on lines +452 to +457
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]
Copy link
Member

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

@mergify mergify bot merged commit f414273 into Qiskit:main Apr 21, 2022
mergify bot pushed a commit that referenced this pull request Apr 21, 2022
* 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 <mtreinish@kortar.org>

* 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 <mtreinish@kortar.org>
(cherry picked from commit f414273)
@jakelishman jakelishman deleted the fix/sabreswap-loop branch April 21, 2022 16:05
mergify bot added a commit that referenced this pull request Apr 21, 2022
* 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 <mtreinish@kortar.org>

* 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 <mtreinish@kortar.org>
(cherry picked from commit f414273)

Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: Bugfix Include in the "Fixed" section of the changelog stable backport potential The bug might be minimal and/or import enough to be port to stable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SabreLayout does not terminate in some cases
5 participants