Zum Hauptinhalt springen

Transpiler-Phasen

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese oder neuere Versionen zu verwenden.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

Diese Seite beschreibt die Phasen der vordefinierten Transpilierungspipeline im Qiskit SDK. Es gibt sechs Phasen:

  1. init
  2. layout
  3. routing
  4. translation
  5. optimization
  6. scheduling

Die Funktion generate_preset_pass_manager erstellt einen vordefinierten stufenweisen Pass-Manager, der aus diesen Phasen besteht. Welche Passes die einzelnen Phasen umfassen, hängt von den Argumenten ab, die an generate_preset_pass_manager übergeben werden. optimization_level ist ein Pflichtargument, das angegeben werden muss; es ist eine ganze Zahl, die 0, 1, 2 oder 3 sein kann. Höhere Werte stehen für eine aufwändigere, aber auch rechenintensivere Optimierung (siehe Transpilierungsstandards und Konfigurationsoptionen).

Die empfohlene Vorgehensweise zum Transpilieren eines Circuits besteht darin, einen vordefinierten stufenweisen Pass-Manager zu erstellen und diesen dann auf den Circuit anzuwenden, wie in Transpilieren mit Pass-Managern beschrieben. Eine einfachere, aber weniger anpassbare Alternative ist die Verwendung der Funktion transpile. Diese Funktion nimmt den Circuit direkt als Argument entgegen. Wie bei generate_preset_pass_manager hängen die verwendeten Transpiler-Passes von den Argumenten ab, etwa optimization_level, die an transpile übergeben werden. Intern ruft die Funktion transpile tatsächlich generate_preset_pass_manager auf, um einen vordefinierten stufenweisen Pass-Manager zu erstellen und diesen auf den Circuit anzuwenden.

Init-Phase

Diese erste Phase tut standardmäßig sehr wenig und ist vor allem nützlich, wenn du eigene anfängliche Optimierungen einbinden möchtest. Da die meisten Layout- und Routing-Algorithmen nur für Ein- und Zwei-Qubit-Gates ausgelegt sind, wird diese Phase auch genutzt, um Gates, die auf mehr als zwei Qubits wirken, in Gates umzuwandeln, die nur auf einem oder zwei Qubits wirken.

Weitere Informationen zur Implementierung eigener anfänglicher Optimierungen für diese Phase findest du im Abschnitt über Plugins und die Anpassung von Pass-Managern.

Layout-Phase

Die nächste Phase betrifft das Layout bzw. die Konnektivität des Backends, an das ein Circuit gesendet wird. Quantenschaltkreise sind im Allgemeinen abstrakte Entitäten, deren Qubits „virtuelle" oder „logische" Darstellungen der tatsächlich bei Berechnungen verwendeten Qubits sind. Um eine Folge von Gates auszuführen, ist eine Eins-zu-eins-Zuordnung der „virtuellen" Qubits zu den „physischen" Qubits eines echten Quantengeräts erforderlich. Diese Zuordnung wird als Layout-Objekt gespeichert und ist Teil der Einschränkungen, die in der Instruction Set Architecture (ISA) eines Backends definiert sind.

Dieses Bild zeigt, wie Qubits von der Drahtdarstellung in ein Diagramm überführt werden, das die Verbindungen der Qubits auf dem QPU abbildet.

Die Wahl der Zuordnung ist äußerst wichtig, um die Anzahl der benötigten SWAP-Operationen beim Einbetten des Eingangs-Circuits in die Gerätetopologie zu minimieren und sicherzustellen, dass die am besten kalibrierten Qubits genutzt werden. Aufgrund der Bedeutung dieser Phase probieren die vordefinierten Pass-Manager verschiedene Methoden aus, um das beste Layout zu finden. In der Regel umfasst dies zwei Schritte: Zunächst wird versucht, ein „perfektes" Layout zu finden (ein Layout, das keine SWAP-Operationen erfordert), und dann wird ein heuristischer Pass eingesetzt, der das beste Layout ermittelt, falls kein perfektes Layout gefunden werden kann. Für diesen ersten Schritt werden typischerweise zwei Passes verwendet:

  • TrivialLayout: Ordnet jeden virtuellen Qubit naiv dem physischen Qubit mit der gleichen Nummer auf dem Gerät zu (d. h. [0,1,1,3] -> [0,1,1,3]). Dies ist ein historisches Verhalten, das nur bei optimzation_level=1 verwendet wird, um ein perfektes Layout zu finden. Schlägt dies fehl, wird als nächstes VF2Layout versucht.
  • VF2Layout: Dies ist ein AnalysisPass, der ein ideales Layout auswählt, indem diese Phase als Teilgraph-Isomorphieproblem behandelt wird, das durch den VF2++-Algorithmus gelöst wird. Wenn mehr als ein Layout gefunden wird, wird eine bewertungsbasierte Heuristik ausgeführt, um die Zuordnung mit dem niedrigsten Durchschnittsfehler auszuwählen.

Für die heuristische Phase werden standardmäßig zwei Passes verwendet:

  • DenseLayout: Findet den Teilgraphen des Geräts mit der größten Konnektivität und der gleichen Qubit-Anzahl wie der Circuit (wird bei Optimierungslevel 1 verwendet, wenn Control-Flow-Operationen wie IfElseOp im Circuit vorhanden sind).
  • SabreLayout: Dieser Pass wählt ein Layout aus, indem er von einem zufälligen Anfangslayout startet und den SabreSwap-Algorithmus wiederholt ausführt. Dieser Pass wird nur bei den Optimierungslevels 1, 2 und 3 verwendet, wenn kein perfektes Layout über den VF2Layout-Pass gefunden wurde. Weitere Details zu diesem Algorithmus findest du im Artikel arXiv:1809.02573.

Routing-Phase

Um ein Zwei-Qubit-Gate zwischen Qubits auszuführen, die auf einem Quantengerät nicht direkt miteinander verbunden sind, müssen ein oder mehrere SWAP-Gates in den Circuit eingefügt werden, um die Qubit-Zustände so zu verschieben, bis sie auf der Gerätegate-Map benachbart sind. Jedes SWAP-Gate stellt eine aufwändige und fehleranfällige Operation dar. Daher ist es ein wichtiger Schritt im Transpilierungsprozess, die minimale Anzahl benötigter SWAP-Gates zu finden, um einen Circuit auf ein bestimmtes Gerät abzubilden. Der Effizienz halber wird diese Phase standardmäßig zusammen mit der Layout-Phase berechnet, sie sind aber logisch voneinander getrennt. Die Layout-Phase wählt die zu verwendenden Hardware-Qubits aus, während die Routing-Phase die geeignete Anzahl von SWAP-Gates einfügt, um die Circuits mit dem gewählten Layout auszuführen.

Die Suche nach der optimalen SWAP-Zuordnung ist jedoch schwierig. Es handelt sich tatsächlich um ein NP-schweres Problem, und es ist daher für alle außer den kleinsten Quantengeräten und Eingangs-Circuits zu rechenintensiv, um es exakt zu lösen. Um dies zu umgehen, verwendet Qiskit einen stochastischen heuristischen Algorithmus namens SabreSwap, um eine gute, wenn auch nicht unbedingt optimale SWAP-Zuordnung zu berechnen. Der Einsatz einer stochastischen Methode bedeutet, dass die erzeugten Circuits bei wiederholten Durchläufen nicht garantiert identisch sind. Tatsächlich ergibt das wiederholte Ausführen desselben Circuits eine Verteilung von Circuit-Tiefen und Gate-Anzahlen in der Ausgabe. Aus diesem Grund führen viele Nutzer die Routing-Funktion (oder den gesamten StagedPassManager) mehrmals aus und wählen die Circuits mit der geringsten Tiefe aus der Ausgabeverteilung aus.

Nehmen wir zum Beispiel einen 15-Qubit-GHZ-Circuit, der 100 Mal mit einem „schlechten" (unverbundenen) initial_layout ausgeführt wird.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeAuckland, FakeWashingtonV2
from qiskit.transpiler import generate_preset_pass_manager

backend = FakeAuckland()

ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
for seed in range(100):
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial", # Fixed layout mapped in circuit order
seed_transpiler=seed, # For reproducible results
)
depths.append(pass_manager.run(ghz).depth())

plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)
Text(0, 0.5, 'Counts')

Ausgabe der vorherigen Code-Zelle

Diese breite Verteilung zeigt, wie schwierig es für den SWAP-Mapper ist, die beste Zuordnung zu berechnen. Um einen besseren Einblick zu gewinnen, schauen wir uns sowohl den ausgeführten Circuit als auch die auf der Hardware gewählten Qubits an.

ghz.draw("mpl", idle_wires=False)

Ausgabe der vorherigen Code-Zelle

from qiskit.visualization import plot_circuit_layout

# Plot the hardware graph and indicate which hardware qubits were chosen to run the circuit
transpiled_circ = pass_manager.run(ghz)
plot_circuit_layout(transpiled_circ, backend)

Ausgabe der vorherigen Code-Zelle

Wie du siehst, muss dieser Circuit ein Zwei-Qubit-Gate zwischen den Qubits 0 und 14 ausführen, die im Konnektivitätsgraphen sehr weit voneinander entfernt sind. Das Ausführen dieses Circuits erfordert daher das Einfügen von SWAP-Gates, um alle Zwei-Qubit-Gates mit dem SabreSwap-Pass auszuführen.

Beachte auch, dass der SabreSwap-Algorithmus sich von der übergeordneten SabreLayout-Methode in der vorherigen Phase unterscheidet. Standardmäßig führt SabreLayout sowohl Layout als auch Routing durch und gibt den transformierten Circuit zurück. Dies geschieht aus einigen speziellen technischen Gründen, die auf der API-Referenzseite des Passes beschrieben sind.

Übersetzungsphase

Beim Schreiben eines Quantum-Circuits kannst du beliebige Quantum-Gates (unitäre Operationen) verwenden, zusammen mit einer Reihe von Nicht-Gate-Operationen wie Qubit-Messung oder Reset-Anweisungen. Die meisten Quantengeräte unterstützen jedoch nur eine Handvoll Quantum-Gates und Nicht-Gate-Operationen nativ. Diese nativen Gates sind Teil der Definition der ISA eines Targets, und diese Phase der vordefinierten PassManagers übersetzt (oder entrollt) die im Circuit angegebenen Gates in die nativen Basis-Gates eines bestimmten Backends. Dies ist ein wichtiger Schritt, da er dem Backend die Ausführung des Circuits ermöglicht, aber typischerweise zu einer Zunahme der Tiefe und Anzahl der Gates führt.

Zwei besondere Fälle sind besonders wichtig hervorzuheben und helfen zu veranschaulichen, was diese Phase bewirkt.

  1. Wenn ein SWAP-Gate kein natives Gate des Ziel-Backends ist, werden dafür drei CNOT-Gates benötigt:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")
native gates:['cx', 'delay', 'for_loop', 'id', 'if_else', 'measure', 'reset', 'rz', 'switch_case', 'sx', 'x']

Ausgabe der vorherigen Code-Zelle

Als Produkt dreier CNOT-Gates ist ein SWAP eine aufwändige Operation auf verrauschten Quantengeräten. Solche Operationen sind jedoch meist notwendig, um einen Circuit in die begrenzte Gate-Konnektivität vieler Geräte einzubetten. Daher ist die Minimierung der Anzahl von SWAP-Gates in einem Circuit ein primäres Ziel im Transpilierungsprozess.

  1. Ein Toffoli- oder kontrolliert-kontrolliert-nicht-Gate (ccx) ist ein Drei-Qubit-Gate. Da unser Basis-Gate-Set nur Ein- und Zwei-Qubit-Gates umfasst, muss diese Operation zerlegt werden. Dies ist jedoch recht kostspielig:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Ausgabe der vorherigen Code-Zelle

Für jedes Toffoli-Gate in einem Quantenschaltkreis kann die Hardware bis zu sechs CNOT-Gates und eine Reihe von Ein-Qubit-Gates ausführen. Dieses Beispiel zeigt, dass jeder Algorithmus, der mehrere Toffoli-Gates verwendet, zu einem Circuit mit großer Tiefe wird und daher erheblich durch Rauschen beeinträchtigt wird.

Optimierungsphase

Diese Phase dreht sich darum, Quantenschaltkreise in das Basis-Gate-Set des Zielgeräts zu zerlegen, und muss der durch die Layout- und Routing-Phasen erhöhten Tiefe entgegenwirken. Glücklicherweise gibt es viele Routinen zur Optimierung von Circuits, indem Gates kombiniert oder eliminiert werden. In einigen Fällen sind diese Methoden so effektiv, dass die Ausgangs-Circuits eine geringere Tiefe haben als die Eingangs-Circuits, selbst nach Layout und Routing auf die Hardware-Topologie. In anderen Fällen ist nicht viel möglich, und die Berechnung kann auf verrauschten Geräten schwierig sein. In dieser Phase beginnen sich die verschiedenen Optimierungslevel zu unterscheiden.

Darüber hinaus führt diese Phase auch einige abschließende Prüfungen durch, um sicherzustellen, dass alle Anweisungen im Circuit aus den auf dem Ziel-Backend verfügbaren Basis-Gates bestehen.

Das folgende Beispiel mit einem GHZ-Zustand zeigt die Auswirkungen verschiedener Optimierungslevel-Einstellungen auf Circuit-Tiefe und Gate-Anzahl.

hinweis

Die Transpilierungsausgabe variiert aufgrund des stochastischen SWAP-Mappers. Daher werden sich die unten angegebenen Zahlen wahrscheinlich bei jedem Ausführen des Codes ändern.

15-Qubit-GHZ-Zustand

Der folgende Code erstellt einen 15-Qubit-GHZ-Zustand und vergleicht die optimization_levels der Transpilierung hinsichtlich der resultierenden Circuit-Tiefe, Gate-Anzahl und Mehrzbit-Gate-Anzahl.

ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
gate_counts = []
multiqubit_gate_counts = []
levels = [str(x) for x in range(4)]
for level in range(4):
pass_manager = generate_preset_pass_manager(
optimization_level=level,
backend=backend,
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
depths.append(circ.depth())
gate_counts.append(sum(circ.count_ops().values()))
multiqubit_gate_counts.append(circ.count_ops()["cx"])

fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.bar(levels, depths, label="Depth")
ax1.set_xlabel("Optimization Level")
ax1.set_ylabel("Depth")
ax1.set_title("Output Circuit Depth")
ax2.bar(levels, gate_counts, label="Number of Circuit Operations")
ax2.bar(levels, multiqubit_gate_counts, label="Number of CX gates")
ax2.set_xlabel("Optimization Level")
ax2.set_ylabel("Number of gates")
ax2.legend()
ax2.set_title("Number of output circuit gates")
fig.tight_layout()
plt.show()

Ausgabe der vorherigen Code-Zelle

Scheduling

Diese letzte Phase wird nur ausgeführt, wenn sie explizit angefordert wird (ähnlich wie die Init-Phase) und läuft standardmäßig nicht (obwohl eine Methode durch Setzen des Arguments scheduling_method beim Aufruf von generate_preset_pass_manager angegeben werden kann). Die Scheduling-Phase wird typischerweise verwendet, nachdem der Circuit in die Ziel-Basis übersetzt, auf das Gerät abgebildet und optimiert wurde. Diese Passes konzentrieren sich darauf, alle Leerlaufzeiten in einem Circuit zu berücksichtigen. Auf hoher Ebene kann der Scheduling-Pass als das explizite Einfügen von Delay-Anweisungen betrachtet werden, um die Leerlaufzeit zwischen Gate-Ausführungen zu berücksichtigen und zu überprüfen, wie lange der Circuit auf dem Backend laufen wird.

Hier ist ein Beispiel:

ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))

# Use fake backend
backend = FakeWashingtonV2()

# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
scheduling_method="asap",
seed_transpiler=1234,
)

circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)

Ausgabe der vorherigen Code-Zelle

Circuit mit Delay-Anweisungen

Der Transpiler hat Delay-Anweisungen eingefügt, um die Leerlaufzeit auf jedem Qubit zu berücksichtigen. Um einen besseren Eindruck vom Timing des Circuits zu bekommen, können wir ihn auch mit der Funktion timeline.draw() betrachten:

timeline.draw()-Ansicht desselben Circuits Das Scheduling eines Circuits umfasst zwei Teile: Analyse und Constraint-Mapping, gefolgt von einem Padding-Pass. Der erste Teil erfordert die Ausführung eines Scheduling-Analyse-Passes (standardmäßig ALAPSchedulingAnalysis), der den Circuit analysiert und die Startzeit jeder Anweisung im Circuit in einem Schedule erfasst. Sobald der Circuit einen initialen Schedule hat, können weitere Passes ausgeführt werden, um Timing-Einschränkungen des Ziel-Backends zu berücksichtigen. Abschließend kann ein Padding-Pass wie PadDelay oder PadDynamicalDecoupling ausgeführt werden.

Nächste Schritte

Empfehlungen