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 SabreSwap with classically conditioned gates #8041

Merged
merged 6 commits into from
May 12, 2022

Conversation

jakelishman
Copy link
Member

Summary

The calculation of the expected number of predecessors for a gate to
be considered "resolved" in SabreSwap was not accounting for wires
stemming from classical conditions. The tracking of the actual number
of predecessor requirements satisfied did correctly account for this, so
in certain circumstances the actual count could jump from "too low" to
"too high" without passing through "just right", and the gate would
never get added to the circuit.

Details and comments

Fix #8040.

The calculation of the expected number of predecessors for a gate to
be considered "resolved" in `SabreSwap` was not accounting for wires
stemming from classical conditions.  The tracking of the _actual_ number
of predecessor requirements satisfied did correctly account for this, so
in certain circumstances the actual count could jump from "too low" to
"too high" without passing through "just right", and the gate would
never get added to the circuit.
@jakelishman jakelishman added stable backport potential The bug might be minimal and/or import enough to be port to stable Changelog: Bugfix Include in the "Fixed" section of the changelog labels May 10, 2022
@jakelishman jakelishman added this to the 0.20.2 milestone May 10, 2022
@jakelishman jakelishman requested a review from a team as a code owner May 10, 2022 18:05
@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 May 10, 2022

Pull Request Test Coverage Report for Build 2315102409

  • 15 of 15 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.005%) to 84.38%

Totals Coverage Status
Change from base Build 2309830910: 0.005%
Covered Lines: 54505
Relevant Lines: 64595

💛 - Coveralls

@jakelishman
Copy link
Member Author

Oh wait, I didn't test my method with more than one bit in the condition register - it might not be fully correct yet. I'll look again tomorrow.

@jakelishman jakelishman added the on hold Can not fix yet label May 10, 2022
This unifies the calculation of what is considered a required
predecessor by using the same `SabreSwap._successors` function for both
aspects of the comparison: counting the required predecessors and
counting the actual number of applied predecessors.  This simplifies the
logic, since now an update in one place is sufficient, and the wires are
read directly from the DAG, rather than using assumptions about the
nodes.
@jakelishman
Copy link
Member Author

I've fixed up the pass so it now uses the same SabreSwap._successors function to calculate both the required number of predecessors and the actual number of predecessors. Now, instead of counting up to the required number, we count down to zero. This means that a change to SabreSwap._successors is sufficient to change all the assumptions about the input wires, and those wires are read from the DAG directly, rather than being inferred (incorrectly) from a DAGOpNode.

@jakelishman jakelishman removed the on hold Can not fix yet label May 11, 2022
@jakelishman jakelishman force-pushed the fix-sabreswap-classical-condition branch from 470ac7b to ab5d539 Compare May 11, 2022 13:44
@mtreinish mtreinish self-assigned this May 12, 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 LGTM, thanks for fixing this. Just a typo fix inline and a small question (not critical).

qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/routing/sabre_swap.py Outdated Show resolved Hide resolved
Comment on lines +318 to +320
for node in dag.op_nodes():
for successor in self._successors(node, dag):
out[successor] += 1
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if doing something like:

Suggested change
for node in dag.op_nodes():
for successor in self._successors(node, dag):
out[successor] += 1
for node in dag.op_nodes():
out_successor[node] += dag._multi_graph.in_degree(node)

would work and be faster. Not that I expect this to be a bottleneck. It just took me a sec to grok this.
I guess there might be an issue because of the input nodes as we'd be including the first layer nodes and they'd have a required predecessor count for the inputs. That's not too different than what this outputs now because we'll end up with output nodes in the out dict but that should be ok because we'll never do a lookup on an output wire.

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 think this will count DAGInNodes too? We ought to discount those to do things properly, but we could just loop through the original front_layer and automatically set those to zero.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I clearly didn't read your comment properly sorry!

Copy link
Member

Choose a reason for hiding this comment

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

Lets just leave this as is. We can revisit this after #7977 and see if we're actually seeing this in profiles or not.

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
@mergify mergify bot merged commit 4410a9b into Qiskit:main May 12, 2022
mergify bot pushed a commit that referenced this pull request May 12, 2022
* Fix SabreSwap with classically conditioned gates

The calculation of the expected number of predecessors for a gate to
be considered "resolved" in `SabreSwap` was not accounting for wires
stemming from classical conditions.  The tracking of the _actual_ number
of predecessor requirements satisfied did correctly account for this, so
in certain circumstances the actual count could jump from "too low" to
"too high" without passing through "just right", and the gate would
never get added to the circuit.

* Use `successors` to calculate required predecessors

This unifies the calculation of what is considered a required
predecessor by using the same `SabreSwap._successors` function for both
aspects of the comparison: counting the required predecessors and
counting the actual number of applied predecessors.  This simplifies the
logic, since now an update in one place is sufficient, and the wires are
read directly from the DAG, rather than using assumptions about the
nodes.

* Fix incorrect import

* Fix decremented typo

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit 4410a9b)
@jakelishman jakelishman deleted the fix-sabreswap-classical-condition branch May 12, 2022 18:59
mergify bot added a commit that referenced this pull request May 12, 2022
* Fix SabreSwap with classically conditioned gates

The calculation of the expected number of predecessors for a gate to
be considered "resolved" in `SabreSwap` was not accounting for wires
stemming from classical conditions.  The tracking of the _actual_ number
of predecessor requirements satisfied did correctly account for this, so
in certain circumstances the actual count could jump from "too low" to
"too high" without passing through "just right", and the gate would
never get added to the circuit.

* Use `successors` to calculate required predecessors

This unifies the calculation of what is considered a required
predecessor by using the same `SabreSwap._successors` function for both
aspects of the comparison: counting the required predecessors and
counting the actual number of applied predecessors.  This simplifies the
logic, since now an update in one place is sufficient, and the wires are
read directly from the DAG, rather than using assumptions about the
nodes.

* Fix incorrect import

* Fix decremented typo

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit 4410a9b)

Co-authored-by: Jake Lishman <jake@binhbar.com>
@jakelishman
Copy link
Member Author

jakelishman commented May 13, 2022

Our asv benchmarks claim that this PR introduced a severe performance regression for large circuits. This is a false positive - the benchmarks were actually being completely invalidated by the bug this PR fixes. For example, constructing the exact 14-qubit 1024-depth circuit used in the benchmark suite, before this PR we saw:

In [1]: cmap = ...
   ...: dag_14q_1024 = ...
   ...: swap = SabreSwap(cmap)
   ...: ops_before = dag_to_circuit(dag_14q_1024).count_ops()
   ...: ops_after = dag_to_circuit(swap.run(dag_14q_1024)).count_ops()
   ...:
   ...: # Should be all zeros, except for swap which should be positive.
   ...: {k: ops_after[k] - ops_before[k] for k in ops_before}
Out[1]:
{'cy': -886,
 'cx': -484,
 'ch': -456,
 'rzz': -454,
 'cu3': -459,
 'cz': -436,
 'swap': 474,
 'crz': -399,
 'u1': -260,
 'tdg': -248,
 'sdg': -245,
 'rz': -241,
 'u3': -236,
 'x': -257,
 'u2': -251,
 'z': -232,
 'ry': -228,
 's': -231,
 'rx': -244,
 'y': -249,
 'reset': -232,
 't': -223,
 'id': -230,
 'h': -224,
 'measure': -13}

Ignoring swap gates (hard to count the true difference), the pass was throwing away literally 80% of the instructions in the input. After this patch, the dictionary is correctly:

{'cy': 0,
 'cx': 0,
 'ch': 0,
 'rzz': 0,
 'cu3': 0,
 'cz': 0,
 'swap': 6329,
 'crz': 0,
 'u1': 0,
 'tdg': 0,
 'sdg': 0,
 'rz': 0,
 'u3': 0,
 'x': 0,
 'u2': 0,
 'z': 0,
 'ry': 0,
 's': 0,
 'rx': 0,
 'y': 0,
 'reset': 0,
 't': 0,
 'id': 0,
 'h': 0,
 'measure': 0}

Since we're now (correctly) doing 5x the amount of work, the 400% increase in run-time matches up with what is expected.

ajavadia pushed a commit to ajavadia/qiskit that referenced this pull request May 31, 2022
* Fix SabreSwap with classically conditioned gates

The calculation of the expected number of predecessors for a gate to
be considered "resolved" in `SabreSwap` was not accounting for wires
stemming from classical conditions.  The tracking of the _actual_ number
of predecessor requirements satisfied did correctly account for this, so
in certain circumstances the actual count could jump from "too low" to
"too high" without passing through "just right", and the gate would
never get added to the circuit.

* Use `successors` to calculate required predecessors

This unifies the calculation of what is considered a required
predecessor by using the same `SabreSwap._successors` function for both
aspects of the comparison: counting the required predecessors and
counting the actual number of applied predecessors.  This simplifies the
logic, since now an update in one place is sufficient, and the wires are
read directly from the DAG, rather than using assumptions about the
nodes.

* Fix incorrect import

* Fix decremented typo

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.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.

SabreSwap can throw away classically conditioned gates and all successors
4 participants