Skip to content

Conversation

jakelishman
Copy link
Member

The previous Sabre extended set was just the "next N" 2q gates topologically on from the front layer, where Qiskit reliably used N = 20 ever since its introduction. For small-width circuits (as were common when the original Sabre paper was written, and when it was first implemented in Qiskit), this could mean the extended set was reliably several layers deep. This could also be the case for star-like circuits. For the wider circuits in use now, at the 100q order of magnitude, the 20-gate limit reliably means that denser circuits cannot have their entire next layer considered by the lookahead set.

This commit modifies the lookahead heuristic to be based specifically on layers. This regularises much of the structure of the heuristic with respect to circuit and target topology; we reliably "look ahead" by the same "distance" as far as routing is concerned. It comes with the additional benefits:

  • we can use the same Layer structure for both the front layer and the lookahead layers, which reduces the amount of scoring code

  • the lookahead score of a swap can now affect at most two gates per layer, just like the front-layer scoring, and we can do this statically without loops

  • we no longer risk "biasing" the lookahead heuristic in either case of long chains of dependent gates (e.g. a gate that has 10 predecessors weights the score the same as a gate with only 1) or wide circuits (some qubits have their next layer counted in the score, but others don't because the extended set reached capacity).

  • applying a swap to the lookahead now has a time complexity that is constant per layer, regardless of the number of gates stored in it, whereas previously it was proportional to the number of gates stored (and the implementation in the parent of this commit is proportional to the number of qubits in the circuit).

This change alone is mostly a set up, which enables further computational complexity improvements by modifying the lookahead layers in place after a gate routes, rather than rebuilding them from scratch, and subsequently only updating swap scores based on routing changes, rather than recalculating all from scratch.

This allows `NLayout` to be indexed by `VirtualQubit` and
`PhysicalQubit`, which is a shorter way of writing
`VirtualQubit::to_phys` and `PhysicalQubit::to_virt`.
This commit separates out the three logical components of the Sabre
routing tracking into three separate structs, where each struct groups
objects that have the same mutation tendency.

The previous Sabre state stored its problem description, internal
tracking, and output tracking altogether in the same flat structure.
Those three components have different tendencies to mutate: the problem
description never mutates, the internal tracking frequently does, and
the output tracking only occasionally does and has a lifetime validity
tied to that of the problem description.

Putting them together made it impossible to call methods that mutated
the state while passing an object that borrowed from the problem
description, such as when recursing into control-flow operations,
because the borrow checker could not validate it.  This applied
interface pressure to inline more into the same method, which made
code-reuse of separate concerns harder.
Sabre uses several objects that are logically maps from an index-like
newtype (like `NodeIndex` or `PhysicalQubit`) to some value, and are
implemented as fixed-slice `Vec`s for lookup efficiency.    The newtype
provides type safety while it's in use, but we have to cast it away to
index, which makes it easy to index slices with the wrong object.

This introduces a `VecMap` object, which provides a (minimal) slice-like
interface, but indexes using the relevant newtype.

The current implementation of Sabre does not use this _too_ much, but a
refactoring of the layer structures will have them store one slice
indexed by `PhysicalQubit` and one by `VirtualQubit`, which are
trivially easy to get switched (a frequent mistake that is the base
reason those new types were introduced in the first place).
The previous Sabre extended set was just the "next N" 2q gates
topologically on from the front layer, where Qiskit reliably used
`N = 20` ever since its introduction.  For small-width circuits (as were
common when the original Sabre paper was written, and when it was first
implemented in Qiskit), this could mean the extended set was reliably
several layers deep.  This could also be the case for star-like
circuits.  For the wider circuits in use now, at the 100q order of
magnitude, the 20-gate limit reliably means that denser circuits cannot
have their entire next layer considered by the lookahead set.

This commit modifies the lookahead heuristic to be based specifically on
layers.  This regularises much of the structure of the heuristic with
respect to circuit and target topology; we reliably "look ahead" by the
same "distance" as far as routing is concerned.  It comes with the
additional benefits:

- we can use the same `Layer` structure for both the front layer and the
  lookahead layers, which reduces the amount of scoring code

- the lookahead score of a swap can now affect at most two gates per
  layer, just like the front-layer scoring, and we can do this
  statically without loops

- we no longer risk "biasing" the lookahead heuristic in either case of
  long chains of dependent gates (e.g. a gate that has 10 predecessors
  weights the score the same as a gate with only 1) or wide circuits
  (some qubits have their next layer counted in the score, but others
  don't because the extended set reached capacity).

- applying a swap to the lookahead now has a time complexity that is
  constant per layer, regardless of the number of gates stored in it,
  whereas previously it was proportional to the number of gates stored
  (and the implementation in the parent of this commit is proportional
  to the number of qubits in the circuit).

This change alone is mostly a set up, which enables further
computational complexity improvements by modifying the lookahead layers
in place after a gate routes, rather than rebuilding them from scratch,
and subsequently only updating _swap scores_ based on routing changes,
rather than recalculating all from scratch.
@jakelishman jakelishman requested a review from a team as a code owner August 14, 2025 15:10
@jakelishman jakelishman added this to the 2.2.0 milestone Aug 14, 2025
@jakelishman jakelishman added the mod: transpiler Issues and PRs related to Transpiler label Aug 14, 2025
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@coveralls
Copy link

Pull Request Test Coverage Report for Build 16969144205

Details

  • 220 of 236 (93.22%) changed or added relevant lines in 7 files are covered.
  • 20 unchanged lines in 7 files lost coverage.
  • Overall coverage increased (+0.007%) to 88.265%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/circuit/src/nlayout.rs 3 6 50.0%
crates/transpiler/src/passes/sabre/route.rs 153 156 98.08%
crates/transpiler/src/passes/sabre/heuristic.rs 15 25 60.0%
Files with Coverage Reduction New Missed Lines %
crates/circuit/src/parameter/parameter_expression.rs 1 83.39%
crates/qasm2/src/expr.rs 1 93.63%
crates/transpiler/src/passes/sabre/dag.rs 1 96.97%
crates/transpiler/src/passes/sabre/heuristic.rs 2 51.37%
crates/transpiler/src/passes/sabre/route.rs 3 94.3%
crates/qasm2/src/lex.rs 6 91.75%
crates/qasm2/src/parse.rs 6 97.56%
Totals Coverage Status
Change from base Build 16967470304: 0.007%
Covered Lines: 87840
Relevant Lines: 99519

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: transpiler Issues and PRs related to Transpiler on hold Can not fix yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants