Starting layout analysis pass for sabre#10829
Conversation
|
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 6534170170
💛 - Coveralls |
mtreinish
left a comment
There was a problem hiding this comment.
Thanks for making the updates, I just had a couple of other comments inline after taking another pass at review.
| target=None, | ||
| coupling_map=None, |
There was a problem hiding this comment.
Could we combine these args (and just use target). We did something similar in SabreLayout:
qiskit/qiskit/transpiler/passes/layout/sabre_layout.py
Lines 164 to 169 in 18a9a2b
(but used coupling_map as the name for backwards compatibility).
There was a problem hiding this comment.
I guess we could do this, but this feels like this is somewhat abusing notation. I don't feel very strongly one way or another.
There was a problem hiding this comment.
There was a problem hiding this comment.
Sure, it's better to be consistent across different passes, done in 96b1056.
| d = self.coupling_map.distance(x, y) | ||
| if 1 < d <= distance: | ||
| augmented_coupling_map.add_edge(x, y) | ||
| augmented_error_map.add_error((x, y), self.error_rate) |
There was a problem hiding this comment.
I know we were talking offline about scaling the error rate based on the edge distance. Do you think doing something like:
| augmented_error_map.add_error((x, y), self.error_rate) | |
| augmented_error_map.add_error((x, y), self.error_rate * distance) |
Or were you thinking of something more complicated?
There was a problem hiding this comment.
I think something like this is a good idea, except that if error_rate * distance becomes larger than 1, then the final fidelity will be 0, regardless of the layout (because the scoring code multiplies all the fidelities). So either we can live with this (in the worst case we will get a crappy starting layout that Sabre will eventually not pick), or to automatically decrease error_rate to something like 1 / (2 * distance), or to consider some arctan-like function f(distance) such that f(1) = 0 (real edges have no error) and f(infinity)=1 (100% error), and everything else would be in-between.
There was a problem hiding this comment.
I like the asymptotic error rate approach that seems like the right fit for this use case. Although, I'd probably just be lazy and do something like min(0.9999, self.error_rate * distance) to approximate it :)
There was a problem hiding this comment.
This actually works well in practice, though at the end I have gone with an also simple asymptotic approach which resembles score calculations in sabre error_rate(d) = 1 - ( (1 - self.error_rate)^d ) (but interestingly gives the same results as this "lazy" heuristic).
I didn't know this existed :) Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
|
I think this is basically ready to go when resolve the question on the heuristic error for the added edges. |
…qiskit-terra into initial-layout-for-sabre
|
I experimented quite a bit with mapping the BV circuit over 16 qubits to the heavy-hex(n) coupling for increasing values of n, and saw that (i) this PreSabreLayout helps even if the smallest d for which VF2 finds the layout is 4 (i.e. we need to add distance-4 edges to the coupling graph to find an embedding), (ii) additionally increasing error rates for longer-distance edges helps for larger values of n. In the end I have went with the heuristic that closely resembles computation of errors in sabre scoring: |
…y with other passes
|
|
||
| if self.coupling_map is None: | ||
| raise TranspilerError( | ||
| "SabrePreLayout requires either target or coupling_map to be provided." |
There was a problem hiding this comment.
| "SabrePreLayout requires either target or coupling_map to be provided." | |
| "SabrePreLayout requires coupling_map to be used with either a " | |
| "CouplingMap or Target." |
| target (Target): A target representing the backend device. If specified, it will | ||
| supersede a set value for ``coupling_map``. |
There was a problem hiding this comment.
| target (Target): A target representing the backend device. If specified, it will | |
| supersede a set value for ``coupling_map``. |
| coupling_map (Union[CouplingMap, Target]): directed graph representing the | ||
| original coupling map. |
There was a problem hiding this comment.
| coupling_map (Union[CouplingMap, Target]): directed graph representing the | |
| original coupling map. | |
| coupling_map (Union[CouplingMap, Target]): directed graph representing the | |
| original coupling map or a target modelling the backend (including its | |
| connectivity). |
| def _find_layout(self, dag, edges): | ||
| """Checks if there is a layout for a given set of edges.""" | ||
| cm = CouplingMap(edges) | ||
| pass_ = VF2Layout(cm, seed=0, max_trials=1, call_limit=self.call_limit_vf2) |
There was a problem hiding this comment.
I feel like this is a bit heavy weight we're basically we just need to call rustworkx.is_subgraph_isomorphic(cm_graph, im_graph, id_order=True, induced=False, call_limit=self.call_limit_vf2) here without all the pass machinery to determine if a reduced edge list is valid or not.
For a first implementation I think this is fine, because there is probably some larger refinement we'll want to do since we do need a layout with the minimized edge list.
There was a problem hiding this comment.
I am leaving this as is for now, but sure this is something to rethink in the future.
| extra_edges_unprocessed_set = self._get_extra_edges_used(dag, layout).difference( | ||
| set(extra_edges_necessary) | ||
| ) | ||
| best_layout = layout |
There was a problem hiding this comment.
If we minimize the edge list we don't take into account noise really and just return the first edge found? Since _find_layout sets max_trials=1. Is there value in running multiple trials on the output of minimization to take into account error rates?
There was a problem hiding this comment.
This is a good question. On the one hand, I believe that some part of the effort of looking for extra solutions at this stage would be wasted, as the top-level algorithm would proceed to removing more edges and calling this function again. On the other hand, it does seem a good idea to take the noise into account here as well. However, I do think that the effort here should be smaller than the effort in the main call. A possible solution would be to add yet another argument to the run function, something like improve_layout_max_trials_vf2 (and, while we are at this, also improve_layout_call_limit_vf2). Please tell me if you are agree with this.
There was a problem hiding this comment.
Yeah, I'm not sure either way. In general for sabre I don't think noise awareness buys us very much because we have VF2PostLayout which factors in the whole circuit after routing. The place where this is different is to try and avoid the extra edges we've added to the coupling map. But I think for right now this is probably fine, we can always refine the pass more and add extra options in a follow up.
mtreinish
left a comment
There was a problem hiding this comment.
This LGTM, just one tiny inline nit (sorry I didn't catch it before) and then I think this is good to merge
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
|
Test failure in the merge queue was a network timeout with installing the dependencies - not a true failure, and hopefully just ephemeral. |
Building on the work done in Qiskit#10829, Qiskit#10721, and Qiskit#12104 this commit adds a new trial to all runs of `SabreLayout` that runs the dense layout pass. In general the sabre layout algorithm starts from a random layout and then runs a routing algorithm to permute that layout virtually where swaps would be inserted to select a layout that would result in fewer swaps. As was discussed in Qiskit#10721 and Qiskit#10829 that random starting point is often not ideal especially for larger targets where the distance between qubits can be quite far. Especially when the circuit qubit count is low relative to the target's qubit count this can result it poor layouts as the distance between the qubits is too large. In qiskit we have an existing pass, `DenseLayout`, which tries to find the most densely connected n qubit subgraph of a connectivity graph. This algorithm necessarily will select a starting layout where the qubits are near each other and for those large backends where the random starting layout doesn't work well this can improve the output quality. As the overhead of `DenseLayout` is quite low and the core algorithm is written in rust already this commit adds a default trial that uses DenseLayout as a starting point on top of the random trials (and any input starting points). For example if the user specifies to run SabreLayout with 20 layout trials this will run 20 random trials and one trial with `DenseLayout` as the starting point. This is all done directly in the sabre layout rust code for efficiency. The other difference between the standalone `DenseLayout` pass is that in the standalone pass a sparse matrix is built and a reverse Cuthill-McKee permutation is run on the densest subgraph qubits to pick the final layout. This permutation is skipped because in the case of Sabre's use of dense layout we're relying on the sabre algorithm to perform the permutation. Depends on: Qiskit#12104
Building on the work done in Qiskit#10829, Qiskit#10721, and Qiskit#12104 this commit adds a new trial to all runs of `SabreLayout` that runs the dense layout pass. In general the sabre layout algorithm starts from a random layout and then runs a routing algorithm to permute that layout virtually where swaps would be inserted to select a layout that would result in fewer swaps. As was discussed in Qiskit#10721 and Qiskit#10829 that random starting point is often not ideal especially for larger targets where the distance between qubits can be quite far. Especially when the circuit qubit count is low relative to the target's qubit count this can result it poor layouts as the distance between the qubits is too large. In qiskit we have an existing pass, `DenseLayout`, which tries to find the most densely connected n qubit subgraph of a connectivity graph. This algorithm necessarily will select a starting layout where the qubits are near each other and for those large backends where the random starting layout doesn't work well this can improve the output quality. As the overhead of `DenseLayout` is quite low and the core algorithm is written in rust already this commit adds a default trial that uses DenseLayout as a starting point on top of the random trials (and any input starting points). For example if the user specifies to run SabreLayout with 20 layout trials this will run 20 random trials and one trial with `DenseLayout` as the starting point. This is all done directly in the sabre layout rust code for efficiency. The other difference between the standalone `DenseLayout` pass is that in the standalone pass a sparse matrix is built and a reverse Cuthill-McKee permutation is run on the densest subgraph qubits to pick the final layout. This permutation is skipped because in the case of Sabre's use of dense layout we're relying on the sabre algorithm to perform the permutation. Depends on: Qiskit#12104
Building on the work done in #10829, #10721, and #12104 this commit adds a new trial to all runs of `SabreLayout` that runs the dense layout pass. In general the sabre layout algorithm starts from a random layout and then runs a routing algorithm to permute that layout virtually where swaps would be inserted to select a layout that would result in fewer swaps. As was discussed in #10721 and #10829 that random starting point is often not ideal especially for larger targets where the distance between qubits can be quite far. Especially when the circuit qubit count is low relative to the target's qubit count this can result it poor layouts as the distance between the qubits is too large. In qiskit we have an existing pass, `DenseLayout`, which tries to find the most densely connected n qubit subgraph of a connectivity graph. This algorithm necessarily will select a starting layout where the qubits are near each other and for those large backends where the random starting layout doesn't work well this can improve the output quality. As the overhead of `DenseLayout` is quite low and the core algorithm is written in rust already this commit adds a default trial that uses DenseLayout as a starting point on top of the random trials (and any input starting points). For example if the user specifies to run SabreLayout with 20 layout trials this will run 20 random trials and one trial with `DenseLayout` as the starting point. This is all done directly in the sabre layout rust code for efficiency. The other difference between the standalone `DenseLayout` pass is that in the standalone pass a sparse matrix is built and a reverse Cuthill-McKee permutation is run on the densest subgraph qubits to pick the final layout. This permutation is skipped because in the case of Sabre's use of dense layout we're relying on the sabre algorithm to perform the permutation. Depends on: #12104
Building on the work done in Qiskit#10829, Qiskit#10721, and Qiskit#12104 this commit adds a new trial to all runs of `SabreLayout` that runs the dense layout pass. In general the sabre layout algorithm starts from a random layout and then runs a routing algorithm to permute that layout virtually where swaps would be inserted to select a layout that would result in fewer swaps. As was discussed in Qiskit#10721 and Qiskit#10829 that random starting point is often not ideal especially for larger targets where the distance between qubits can be quite far. Especially when the circuit qubit count is low relative to the target's qubit count this can result it poor layouts as the distance between the qubits is too large. In qiskit we have an existing pass, `DenseLayout`, which tries to find the most densely connected n qubit subgraph of a connectivity graph. This algorithm necessarily will select a starting layout where the qubits are near each other and for those large backends where the random starting layout doesn't work well this can improve the output quality. As the overhead of `DenseLayout` is quite low and the core algorithm is written in rust already this commit adds a default trial that uses DenseLayout as a starting point on top of the random trials (and any input starting points). For example if the user specifies to run SabreLayout with 20 layout trials this will run 20 random trials and one trial with `DenseLayout` as the starting point. This is all done directly in the sabre layout rust code for efficiency. The other difference between the standalone `DenseLayout` pass is that in the standalone pass a sparse matrix is built and a reverse Cuthill-McKee permutation is run on the densest subgraph qubits to pick the final layout. This permutation is skipped because in the case of Sabre's use of dense layout we're relying on the sabre algorithm to perform the permutation. Depends on: Qiskit#12104
Summary
This PR augments #10721 with a specific strategy to create a starting sabre layout, implemented as
SabrePreLayoutanalysis pass.The pass works by augmenting the coupling map with more and more "extra" edges until VF2 succeeds to find a perfect graph isomorphism. More precisely, the augmented coupling map contains edges between nodes that are within a given distance
din the original coupling map, and the value ofdis increased until an isomorphism is found.Intuitively, a better layout involves fewer extra edges. The pass also optionally minimizes the number of extra edges involved in the layout until a local minimum is found. This involves removing extra edges and running VF2 to see if an isomorphism still exists.
Details and comments
Matthew (@mtreinish) had the idea to mitigate the problem of the transpiler yielding less optimal circuits on larger topologies (described in #10160) by creating "better" starting layouts for Sabre and using these in addition to the fully random layouts (see #10169 and #10721). This particular strategy of creating a "better" layout is heavily inspired by #10169.
Here are a few experiments on the
EfficientSU2example with circular entanglement from #10160. The quantum circuit isEfficientSU2(num_qubits=16, entanglement='circular', reps=6, flatten=True)and the coupling map isCouplingMap.from_heavy_hex(15).Running Sabre yields the layout on the left. Note that certain pairs of qubits that are connected in the abstract circuit get quite separated in the physical circuit (shown using black arrows). Using SabrePreLayout before Sabre yields the layout in the middle. In this case there is a way to embed the abstract circuit such that all connected nodes will be at most distance-2 apart in the physical map, and Sabre ends up choosing this starting layout over the random layouts. Note however that this layout is still visually non-optimal, containing both a qubit away from the "figure-8" (circled pair of red qubits) and gaps in the "figure-8" (circles blue qubits). Using the additional optional minimization in SabrePreLayout yields the layout on the right, which is the best out of the 3.
Note that heavy-hex architectures a bit funny, in that they also contain heavy pentagons in addition to heavy hexagons (the pentagons are at the border of the coupling map). An even better layout would be a "figure-8" around a heavy-hexagon and a heavy-pentagon, but the proposed method is not guaranteed to find it.
Limitations of the approach
The proposed method is expected to work only for a niche set of applications, where a perfect layout does not exist, but a "close-to-perfect" layout does. For example, it would not be useful for abstract circuits where every pair of qubits is connected. However, the way we have organized the transpiler flow is that
SabrePreLayoutis just an analysis pass that creates additional layouts for Sabre, if these turn out to be not useful, Sabre will end up simply ignoring these.