Skip to content

Conversation

mtreinish
Copy link
Member

@mtreinish mtreinish commented Nov 4, 2021

Summary

In #5885 we added the next version of the abstract Backend interface
BackendV2 which added the concept of a Target which represents a
compiler target for the transpiler. It contains all the information
about the constraints of a backend for the compiler to use and replaces
the combination of basis_gates, coupling_map, etc and expands the
representation to model more complex devices. However, in #5885 we only
introduced the interface and didn't modify the transpiler to use the
Target natively or any of the extra information it contains. This commit
is the start of the process of updated the transpiler to work with a
target natively. To start if a backend has a target that is now passed
through from transpile to the passmanager_config so we can start passing
it directly to passes as we enable it. Then the basis translator is
updated to work natively with a target instead of the basis gates list
it used before. In addition to using a target directly support is added
for heterogeneous gate sets so that target instructions can work on only
a subset of qargs.

Additionally, to ensure that we're respecting direcitonality of non-symmetric
gates after translating the GateDirection and CheckDirection passes are
updated to work with a target too so they can adapt to strict direction constraints
defined in the target.

Building off this in the future There are additional features in target
that we might want to expand support for in the BasisTranslator in the
future, such as supporting custom variants of the same gate, or handling
fixed angle rotation gate variants, etc.

Details and comments

@mtreinish mtreinish added on hold Can not fix yet priority: high Changelog: None Do not include in changelog labels Nov 4, 2021
@mtreinish mtreinish added this to the 0.19 milestone Nov 4, 2021
@mtreinish mtreinish requested review from chriseclectic, jyu00 and a team as code owners November 4, 2021 18:19
@mtreinish
Copy link
Member Author

This is tagged as on hold because it's on top of #5885 and will need to be rebased after #5885 merges. To see the contents of just this PR you can look at the last commit: 84361f2 which contains the diff for this PR

@coveralls
Copy link

coveralls commented Nov 4, 2021

Pull Request Test Coverage Report for Build 1537465186

  • 308 of 350 (88.0%) changed or added relevant lines in 14 files are covered.
  • 1 unchanged line in 1 file lost coverage.
  • Overall coverage increased (+0.02%) to 83.099%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/basis/basis_translator.py 50 51 98.04%
qiskit/transpiler/target.py 30 31 96.77%
qiskit/transpiler/passmanager_config.py 5 7 71.43%
qiskit/test/mock/fake_backend_v2.py 34 39 87.18%
qiskit/test/mock/fake_mumbai_v2.py 46 51 90.2%
qiskit/transpiler/passes/utils/gate_direction.py 35 45 77.78%
qiskit/compiler/transpiler.py 66 84 78.57%
Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/passes/basis/basis_translator.py 1 98.44%
Totals Coverage Status
Change from base Build 1532852473: 0.02%
Covered Lines: 51664
Relevant Lines: 62172

💛 - Coveralls

@mtreinish mtreinish mentioned this pull request Nov 5, 2021
8 tasks
@mtreinish mtreinish force-pushed the target-basis-passes branch 3 times, most recently from 756c8d3 to ca5e714 Compare November 17, 2021 12:23
@ajavadia
Copy link
Member

can you add a test to transpile a circuit containing ccx to FakeMumbaiV2? It should utilize rzx(pi/2) and rzx(pi/4) if you base this branch off of #7279

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.

Some comments - mostly it looks like it works right to me, but I had some questions about potential scaling issues (which I suppose are more closely linked to Target than this PR)

return dag

qarg_indices = {qubit: index for index, qubit in enumerate(dag.qubits)}
Copy link
Member

Choose a reason for hiding this comment

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

And again it appears... If only there were a built-in collections.OrderedSet type.

Copy link
Member Author

Choose a reason for hiding this comment

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

Heh, yeah. We really need the find_bits api for the dag class. I think we're well past the point where the majority of the passes do this.

Comment on lines 451 to 458
if size not in all_permutations_all_size:
all_permutations_all_size[size] = set(permutations(range(target.num_qubits), size))
if qargs != all_permutations_all_size[size]:
incomplete_basis_gates.append(inst)
Copy link
Member

Choose a reason for hiding this comment

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

This is O(n^d) for a target with n qubits and a maximum gate arity of d. I'm assuming that once we get to large backends, then they just won't be able to specify all properties for every pair (or even triple!) of qubits - even Eagle's going to be a bit of problem with ~16,000 elements necessary in these sets. Are we allowed to assume that a target is well-formed? If so, can we just skip creating our own permutation, and test the size of the set?

Also, are the qubits guaranteed to be ordered from 0 to n - 1? I feel like I remember the qubit properties data structure being a dictionary, not an array.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think I'm going to need to change to search for each edge (or multiqubit gate) in the target instead of trying to compute all the valid permutations and figure out if we need this or not. This is still WIP and something I realized a while ago (I should have left a comment) because also we're almost always going to be returning something for size >=2 here unless they have complete connectivity (which ions do at least). Since the full permutation map is all to all. I'm planning to update the logic here, I've just been concentrating on #5885 because that really needs to merge soon so we can unblock this and other things

Copy link
Member

Choose a reason for hiding this comment

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

Sure, no problem - apologies if this review was a bit premature.

Copy link
Member Author

Choose a reason for hiding this comment

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

Take a look at the current version (I force pushed it because I've been rebasing on #5885 as that changes) I think this should be more efficient than before. It still scales with the number of edges in the coupling map but not the full permutation. I did try adding a search for each edge and the performance was horrific on that.

Comment on lines 103 to 105
for gate in self._incomplete_basis:
for qarg in self._target[gate]:
qarg_with_incomplete[qarg].add(gate)
Copy link
Member

Choose a reason for hiding this comment

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

I may well be wrong, but this feels like it's doing some extra iteration - it feels like there'll be a way to combine it into _compute_incomplete_basis and reuse the set differences we're already calculating in there, rather than needing to duplicate it.

@mtreinish mtreinish force-pushed the target-basis-passes branch 2 times, most recently from 1e85dd1 to 2546ab9 Compare November 22, 2021 15:08
Comment on lines 457 to 458
if len(qargs) != size_dict[len(qarg_sample)]:
incomplete_basis_gates.append(inst)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this actually tests if the gate applies to all qubits, right? If I have a target with no coupling at all between qubits 0 and 2 and a single 2q gate, then this test will appear true, because the qargs will be exactly the set of operands to this gate, so size_dict[2] will be the same as len(qargs), even though there's not full connectivity (which I what I think the previous iteration of this function was testing).

Also, minor sidenote: do targets need to specify symmetric gates like cz with qargs (a, b) and (b, a), or only one of the pair?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, yeah my bad I made a copy paste error this should be len(qargs) not len(qarg_sample).

Right the earlier iteration was doing the wrong thing. We want this to return a list of instructions which do not apply globally in the target, for 1q this is on all qubits, for 2q this is on all edges in the coupling graph, >3q on all valid qargs in the target of that size. The previous iteration was incorrect because it was always checking the full permutation which was only true on an all to all coupling map (it was only correct in the 1q case).

Also, minor sidenote: do targets need to specify symmetric gates like cz with qargs (a, b) and (b, a), or only one of the pair?

They need to be specified bidirectionally (so (a, b) and (b, a)) the way the target is implemented now. Otherwise the target will treat it like you can only run cz on (a, b) or (b, a) (whichever you put in the target).

Copy link
Member

Choose a reason for hiding this comment

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

With the new explanation, I think what you've got written here is correct. That also makes way more sense to me - I was stuck still thinking about the old version of the function as what we were trying to achieve.

Copy link
Member Author

@mtreinish mtreinish Nov 23, 2021

Choose a reason for hiding this comment

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

Taking another look at it I actually I think I did this correctly, I'm trying to get the size of the qargs tuple to look up the number of valid options in the target. So for example if we have a 3q bidirectional linear coupling map: [(0, 1), (1, 0), (1, 2), (2, 1)] and have cx defined on all edges, but ecr only on reverse (so (1, 0) and (2, 1)). The check here should be:

For ecr:

size_dict = {1: 3, 2: 4}

len([(1, 0), (2, 1)]) != len(size_dict[2])

which will add ecr to the incomplete list.

While for cx it'll be:

len([(0, 1), (1, 0), (1, 2), (2, 1)] != size_dict[2]

which means we don't add cx to the incomplete list because it's on all edges in the coupling graph.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I agree - I thought it was wrong because I thought we were shooting for the old, incorrect behaviour.

Copy link
Member

Choose a reason for hiding this comment

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

This is an interesting question. Do we want ecr to show up as incomplete here given that this case (a gate is available on an edge, but in the reverse of the direction required) is usually handled by GateDirection?

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 we do at least until we can make gate direction target aware. The other thing is if we wanted to rely on gate direction for this we'd need to differentiate between unidirectional gates vs bidirectional gates in the target (at least for the compute_non_global_operation_names() method which it doesn't do right now.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think gate direction needs to be target aware (yet), but I do think the BasisTranslator should not (maybe not yet) be gate direction aware. Otherwise, e.g. if we have the equivalent of a non-symmetric coupling map (with some non-global gates), the BasisTranslator would raise before we get a chance to run GateDirection.

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem here is that in the case where you have a target that's like what's in FakeBackendV2 where cx is on (0, 1) and ecr is on (1, 0) if you treat each 2q gate as symmetric (or a weak edge in a directed graph) if you have a circuit with a 2q gate on (1, 0) outside the basis you'll almost always end up with cx(1, 0) in the output circuit. This is also why gate direction being target aware is a requirement because the coupling map object that gets used is of the global edges independent of which gates so gate direction will think that's valid unless it's made target aware. That being said even if you do this and make gate direction target aware (I have a commit on this PR branch locally doing it) this still ends up with a less than ideal result because you'll end up with cx(0, 1) with the h gates around it where ecr(1, 0) is a valid and less gates

@mtreinish mtreinish force-pushed the target-basis-passes branch 3 times, most recently from c17ab6b to 54a609f Compare November 30, 2021 15:23
@mtreinish
Copy link
Member Author

can you add a test to transpile a circuit containing ccx to FakeMumbaiV2? It should utilize rzx(pi/2) and rzx(pi/4) if you base this branch off of #7279

I have tested this and it doesn't pick rzx right now, for two reasons I think. The first is that the path to cx is likely shorter and will be favored by the basis translator. The second is this PR doesn't handled tuned variants correctly, it only adds support for heterogeneous gate sets (so different gates on different qubits). So right now even where there is a path from ccx -> rzx the basis translator won't think this is valid because rzx_30, rzx_45, and rzx_90 are the targets it considers. The first half should be easily fixable with #7211 where we can change the search based on error rates. The second half though we probably will need to rework how the basis translator finds matches to consider the gate instance and not just the name (although the name will be needed still for mapping).

In Qiskit#5885 we added the next version of the abstract Backend interface
BackendV2 which added the concept of a Target which represents a
compiler target for the transpiler. It contains all the information
about the constraints of a backend for the compiler to use and replaces
the combination of basis_gates, coupling_map, etc and expands the
representation to model more complex devices. However, in Qiskit#5885 we only
introduced the interface and didn't modify the transpiler to use the
Target natively or any of the extra information it contains. This commit
is the start of the process of updated the transpiler to work with a
target natively. To start if a backend has a target that is now passed
through from transpile to the passmanager_config so we can start passing
it directly to passes as we enable it. Then the basis translator is
updated to work natively with a target instead of the basis gates list
it used before. In addition to using a target directly support is added
for heterogeneous gate sets so that target instructions can work on only
a subset of qargs.

Building off this in the future There are additional features in target
that we might want to expand support for in the BasisTranslator in the
future, such as supporting custom variants of the same gate, or handling
fixed angle rotation gate variants, etc.
@mtreinish mtreinish force-pushed the target-basis-passes branch from 54a609f to 070db99 Compare December 1, 2021 14:39
@mtreinish mtreinish removed the on hold Can not fix yet label Dec 1, 2021
@mtreinish mtreinish changed the title [WIP] Start making the BasisTranslator Target/BackendV2 aware Start making the BasisTranslator Target/BackendV2 aware Dec 1, 2021
@mtreinish
Copy link
Member Author

Ok now that #5885 has merged I've rebased this on main and it's unblocked now

mtreinish and others added 2 commits December 3, 2021 08:58
Co-authored-by: georgios-ts <45130028+georgios-ts@users.noreply.github.com>
Copy link
Member

@kdk kdk left a comment

Choose a reason for hiding this comment

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

Thanks for the updates here. Few additional small questions.

Also, can you update the BasisTranslator docstring to include how it should handle non-global operations? I think it would also be good to add a few additional test cases for the new behavior. Maybe different variants of cx, cz and ecr on an input circuit targeting a 4q device that has q0 ->(cx) q1 ->(ecr) q2 ->(cx) -> q3 ->(ecr) q0 and the bidirectional version of the same would give some additional coverage.

if len(qarg) > 1:
qubit_set = frozenset(qarg)
for non_local_qarg, local_basis in qarg_with_incomplete.items():
if qubit_set.issuperset(non_local_qarg):
Copy link
Member

Choose a reason for hiding this comment

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

Minor, but at some point, it might be faster to look for all the unique subsets of qarg in qarg_with_incomplete. (I'm assuming the max(len(qargs)) will be stay relatively small, and the size of qarg_with_incomplete will grow, but maybe that's also a concern better left for #7346.)

@georgios-ts
Copy link
Contributor

Another unexpected behavior that will not come up in preset pass managers since BasisTranslator runs post-routing and the circuit will already be adapted in hardware connectivity but if run as a standalone pass, the following will raise or will not raise if you uncomment the commented line.
(IMO it should raise in both cases)

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.circuit.library import UGate, CXGate, ECRGate
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel

from qiskit.transpiler import PassManager, Target, InstructionProperties
from qiskit.transpiler.passes import BasisTranslator

gmap = Target()

# U gate in qubit 0.
theta = Parameter('theta')
phi = Parameter('phi')
lam = Parameter('lambda')
u_props = {
    (0,): InstructionProperties(duration=5.23e-8, error=0.00038115),
    (1,): InstructionProperties(duration=5.23e-8, error=0.00038115),
    (2,): InstructionProperties(duration=5.23e-8, error=0.00038115),
}
gmap.add_instruction(UGate(theta, phi, lam), u_props)

cx_props = {
    (0, 1): InstructionProperties(duration=5.23e-7, error=0.00098115),
    # (1, 2): InstructionProperties(duration=5.23e-7, error=0.00098115),
}
gmap.add_instruction(CXGate(), cx_props)

ecr_props = {
    (1, 2): InstructionProperties(duration=5.23e-7, error=0.00098115),
}
gmap.add_instruction(ECRGate(), ecr_props)

qc = QuantumCircuit(3)
qc.cx(0, 2)

pm = PassManager([
    BasisTranslator(sel, target_basis=None, target=gmap)
])

circ = pm.run(qc)

This fixes 2 issues found in review with the the transpile() functions
handling of target. First if a target kwarg is passed by a user that
target will be used instead of the backend for not just the target but
also all derived quantities (such as coupling_map and basis_gates).
Also previously the backend_properties field was not being correctly
built from a target (both for a backendv2 or a standalone target). So a
converter helper function was added to go from a target and build a
BackendPropeties object from the data contained in the target. This will
enable noise aware transpilation until the transpiler passes are all
target aware.
Optimization level 3 is different from the other lower optimization
levels because it uses unitary synthesis by default. The unitary
synthesis pass is basis and coupling map to optimize the synthesized
circuit for a unitary to be hardware efficient. However when using a
target the basis gates and coupling map don't give a complete picture of
the device constraints (mainly the non-global gates if any). To account
for this we need to run the gate direction pass after unitary synthesis
to correct an incorrect decision the unitary synthesis pass might make
based on it's incomplete data. Once UnitarySynthesis is target aware we
probably do not require this anymore.
@mtreinish mtreinish requested a review from kdk December 3, 2021 21:54
Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
Copy link
Member

@kdk kdk left a comment

Choose a reason for hiding this comment

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

Thanks for the updates here, this LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog priority: high
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants