Przejdź do głównej treści

Etapy Transpilera

Wersje pakietów

Kod na tej stronie został opracowany przy użyciu poniższych wymagań. Zalecamy korzystanie z tych lub nowszych wersji.

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

Ta strona opisuje etapy gotowego potoku transpilacji w Qiskit SDK. Wyróżniamy sześć etapów:

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

Funkcja generate_preset_pass_manager tworzy gotowy staged pass manager złożony z tych etapów. Konkretne przejścia składające się na każdy etap zależą od argumentów przekazanych do generate_preset_pass_manager. Argument optimization_level jest argumentem pozycyjnym, który musi być podany; jest to liczba całkowita, która może przyjmować wartość 0, 1, 2 lub 3. Wyższe wartości oznaczają bardziej kosztowną, lecz intensywniejszą optymalizację (zob. Domyślne ustawienia transpilacji i opcje konfiguracji).

Zalecanym sposobem transpilacji Circuit jest stworzenie gotowego staged pass managera, a następnie uruchomienie go na Circuit, zgodnie z opisem w Transpilacja z pass managerami. Prostszą, choć mniej konfigurowalną alternatywą jest użycie funkcji transpile. Funkcja ta przyjmuje Circuit bezpośrednio jako argument. Podobnie jak w przypadku generate_preset_pass_manager, konkretne przejścia Transpilera zależą od argumentów, takich jak optimization_level, przekazanych do transpile. W rzeczywistości wewnętrznie funkcja transpile wywołuje generate_preset_pass_manager, aby stworzyć gotowy staged pass manager, i uruchamia go na Circuit.

Etap Init

Ten pierwszy etap domyślnie robi bardzo niewiele i jest przydatny głównie wtedy, gdy chcesz uwzględnić własne wstępne optymalizacje. Ponieważ większość algorytmów układu i routingu jest zaprojektowana do pracy wyłącznie z bramkami jednoQubitowymi i dwuQubitowymi, ten etap służy również do przekształcania wszelkich Gate działających na więcej niż dwóch Qubitach w Gate działające tylko na jednym lub dwóch Qubitach.

Więcej informacji na temat implementacji własnych wstępnych optymalizacji dla tego etapu znajdziesz w sekcji dotyczącej wtyczek i dostosowywania pass managerów.

Etap Layout

Kolejny etap dotyczy układu lub łączności Backendu, do którego Circuit zostanie wysłany. Ogólnie rzecz biorąc, Circuit kwantowe są abstrakcyjnymi bytami, których Qubity są „wirtualnymi" lub „logicznymi" reprezentacjami rzeczywistych Qubitów używanych w obliczeniach. Aby wykonać sekwencję Gate, niezbędne jest odwzorowanie jeden do jednego „wirtualnych" Qubitów na „fizyczne" Qubity rzeczywistego urządzenia kwantowego. To odwzorowanie jest przechowywane jako obiekt Layout i stanowi część ograniczeń zdefiniowanych w architekturze zestawu instrukcji (ISA) Backendu.

Ten obraz ilustruje mapowanie Qubitów z reprezentacji przewodów na diagram przedstawiający sposób połączenia Qubitów w QPU.

Wybór odwzorowania ma ogromne znaczenie dla zminimalizowania liczby operacji SWAP potrzebnych do zmapowania wejściowego Circuit na topologię urządzenia oraz zapewnienia, że używane są najlepiej skalibrowane Qubity. Ze względu na wagę tego etapu, gotowe pass managery wypróbowują kilka różnych metod, aby znaleźć najlepszy layout. Zazwyczaj obejmuje to dwa kroki: najpierw próba znalezienia „idealnego" layoutu (layoutu niewymagającego żadnych operacji SWAP), a następnie heurystyczne przejście próbujące znaleźć najlepszy layout do użycia, gdy idealny layout nie może zostać znaleziony. Na tym pierwszym etapie typowo używane są dwa Passes:

  • TrivialLayout: Naiwnie odwzorowuje każdy wirtualny Qubit na fizyczny Qubit o tym samym numerze w urządzeniu (tzn. [0,1,1,3] -> [0,1,1,3]). Jest to historyczne zachowanie używane wyłącznie w optimization_level=1 w celu znalezienia idealnego layoutu. Jeśli się to nie powiedzie, następnie próbowany jest VF2Layout.
  • VF2Layout: Jest to AnalysisPass, który wybiera idealny layout, traktując ten etap jako problem izomorfizmu podgrafów, rozwiązywany przez algorytm VF2++. Jeśli zostanie znaleziony więcej niż jeden layout, uruchamiana jest heurystyczna funkcja oceniająca, aby wybrać odwzorowanie z najniższym średnim błędem.

Następnie w etapie heurystycznym domyślnie używane są dwa przejścia:

  • DenseLayout: Znajduje podgraf urządzenia o największej łączności, posiadający tę samą liczbę Qubitów co Circuit (używany dla poziomu optymalizacji 1, jeśli w Circuit są obecne operacje przepływu sterowania, takie jak IfElseOp).
  • SabreLayout: To przejście wybiera layout, zaczynając od losowego układu początkowego i wielokrotnie uruchamiając algorytm SabreSwap. Przejście to jest używane tylko na poziomach optymalizacji 1, 2 i 3, gdy za pomocą przejścia VF2Layout nie zostanie znaleziony idealny layout. Więcej szczegółów dotyczących tego algorytmu znajdziesz w artykule arXiv:1809.02573.

Etap Routing

Aby zaimplementować dwuQubitową Gate między Qubitami, które nie są bezpośrednio połączone w urządzeniu kwantowym, do Circuit należy wstawić jedną lub więcej Gate SWAP w celu przemieszczenia stanów Qubitów, aż znajdą się sąsiadujące na mapie Gate urządzenia. Każda Gate SWAP oznacza kosztowną i podatną na szumy operację do wykonania. Dlatego znalezienie minimalnej liczby Gate SWAP potrzebnych do zmapowania Circuit na dane urządzenie jest ważnym krokiem w procesie transpilacji. Dla wydajności ten etap jest domyślnie zazwyczaj obliczany wspólnie z etapem Layout, choć są one od siebie logicznie odrębne. Etap Layout wybiera fizyczne Qubity do użycia, natomiast etap Routing wstawia odpowiednią liczbę Gate SWAP w celu wykonania Circuit przy użyciu wybranego layoutu.

Znalezienie optymalnego mapowania SWAP jest jednak trudne. Jest to w rzeczywistości problem NP-trudny, przez co jego obliczanie jest kosztowne praktycznie dla wszystkich poza najmniejszymi urządzeniami kwantowymi i wejściowymi Circuit. Aby to obejść, Qiskit używa stochastycznego algorytmu heurystycznego o nazwie SabreSwap do obliczania dobrego, choć niekoniecznie optymalnego mapowania SWAP. Użycie metody stochastycznej oznacza, że generowane Circuit nie muszą być takie same przy powtarzanych uruchomieniach. Rzeczywiście, wielokrotne uruchamianie tego samego Circuit prowadzi do rozkładu głębokości Circuit i liczby Gate na wyjściu. Z tego powodu wielu użytkowników decyduje się na wielokrotne uruchamianie funkcji routingu (lub całego StagedPassManager) i wybieranie Circuit o najmniejszej głębokości z rozkładu wyników.

Na przykład przyjrzyjmy się 15-Qubitowemu obwodowi GHZ wykonanemu 100 razy, korzystając ze „złego" (rozłączonego) initial_layout.

# 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')

Output of the previous code cell

Ten szeroki rozkład pokazuje, jak trudne jest dla mappera SWAP obliczenie najlepszego odwzorowania. Aby uzyskać pewien wgląd w temat, przyjrzyjmy się zarówno wykonywanemu Circuit, jak i wybranym Qubitom sprzętowym.

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

Output of the previous code cell

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)

Output of the previous code cell

Jak widać, ten Circuit musi wykonać dwuQubitową Gate między Qubitami 0 i 14, które są od siebie bardzo oddalone na grafie łączności. Uruchomienie tego Circuit wymaga zatem wstawiania Gate SWAP w celu wykonania wszystkich dwuQubitowych Gate przy użyciu przejścia SabreSwap.

Zauważ również, że algorytm SabreSwap różni się od szerszej metody SabreLayout z poprzedniego etapu. Domyślnie SabreLayout wykonuje zarówno layout, jak i routing, i zwraca przekształcony Circuit. Jest to spowodowane kilkoma szczególnymi względami technicznymi opisanymi na stronie dokumentacji API przejścia.

Etap Translation

Pisząc Circuit kwantowy, możesz swobodnie używać dowolnej kwantowej Gate (operacji unitarnej), a także zbioru operacji innych niż Gate, takich jak instrukcje pomiaru lub resetowania Qubitu. Jednak większość urządzeń kwantowych natywnie obsługuje tylko niewielką liczbę kwantowych Gate i operacji innych niż Gate. Te natywne Gate są częścią definicji ISA docelowego urządzenia i ten etap gotowych PassManagerów przekształca (lub rozwijania) Gate określone w Circuit na natywne Gate bazowe określonego Backendu. Jest to ważny krok, ponieważ umożliwia wykonanie Circuit przez Backend, ale zazwyczaj prowadzi do zwiększenia głębokości i liczby Gate.

Warto podkreślić dwa szczególne przypadki, które dobrze ilustrują, co robi ten etap.

  1. Jeśli Gate SWAP nie jest natywną Gate docelowego Backendu, wymaga to trzech Gate CNOT:
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']

Output of the previous code cell

Jako iloczyn trzech Gate CNOT, SWAP jest kosztowną operacją do wykonania na szumiących urządzeniach kwantowych. Takie operacje są jednak zazwyczaj niezbędne do osadzenia Circuit w ograniczonych łącznościach Gate wielu urządzeń. Minimalizacja liczby Gate SWAP w Circuit jest zatem głównym celem procesu transpilacji.

  1. Toffoli, czyli bramka controlled-controlled-not (ccx), jest trzuQubitową Gate. Biorąc pod uwagę, że nasz bazowy zestaw Gate zawiera tylko Gate jednoQubitowe i dwuQubitowe, operacja ta musi zostać zdekomponowana. Jest to jednak dość kosztowne:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output of the previous code cell

Na każdą Gate Toffoli w Circuit kwantowym sprzęt może wykonać do sześciu Gate CNOT i kilka Gate jednoQubitowych. Ten przykład pokazuje, że każdy algorytm korzystający z wielu Gate Toffoli stanie się Circuit o dużej głębokości i będzie zatem w znacznym stopniu dotknięty przez szumy.

Etap Optimization

Ten etap skupia się na dekompozycji Circuit kwantowych do zestawu Gate bazowych docelowego urządzenia i musi zwalczać zwiększoną głębokość wynikającą z etapów layout i routing. Na szczęście istnieje wiele procedur optymalizacji Circuit poprzez łączenie lub eliminowanie Gate. W niektórych przypadkach metody te są tak skuteczne, że wyjściowe Circuit mają mniejszą głębokość niż wejściowe, nawet po layoutcie i routingu do topologii sprzętowej. W innych przypadkach nie wiele można zrobić, a obliczenia mogą być trudne do wykonania na szumiących urządzeniach. Na tym etapie poszczególne poziomy optymalizacji zaczynają się od siebie różnić.

Ponadto ten etap wykonuje również kilka końcowych sprawdzeń, aby upewnić się, że wszystkie instrukcje w Circuit są złożone z Gate bazowych dostępnych w docelowym Backendzie.

Poniższy przykład ze stanem GHZ demonstruje efekty różnych ustawień poziomu optymalizacji na głębokość Circuit i liczbę Gate.

uwaga

Wynik transpilacji różni się ze względu na stochastyczny mapper SWAP. Dlatego poniższe liczby będą się prawdopodobnie zmieniać przy każdym uruchomieniu kodu.

15-Qubitowy stan GHZ

Poniższy kod konstruuje 15-Qubitowy stan GHZ i porównuje optimization_levels transpilacji pod względem wynikowej głębokości Circuit, liczby Gate i liczby wieloQubitowych Gate.

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()

Output of the previous code cell

Scheduling

Ten ostatni etap jest uruchamiany tylko wtedy, gdy jest jawnie wywoływany (podobnie jak etap Init) i domyślnie nie jest uruchamiany (choć metodę można określić, ustawiając argument scheduling_method podczas wywoływania generate_preset_pass_manager). Etap Scheduling jest zazwyczaj używany po przetłumaczeniu Circuit na docelową bazę, zmapowaniu na urządzenie i optymalizacji. Przejścia te skupiają się na uwzględnieniu całego czasu bezczynności w Circuit. Na wysokim poziomie abstrakcji przejście Scheduling można traktować jako jawne wstawianie instrukcji opóźnienia w celu uwzględnienia czasu bezczynności między wykonaniami Gate oraz inspekcji czasu działania Circuit na Backendzie.

Oto przykład:

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)

Output of the previous code cell

Circuit z instrukcjami opóźnienia

Transpiler wstawił instrukcje Delay w celu uwzględnienia czasu bezczynności na każdym Qubicie. Aby lepiej zrozumieć harmonogram Circuit, możemy również spojrzeć na niego za pomocą funkcji timeline.draw():

Widok timeline.draw() tego samego Circuit

Planowanie Circuit obejmuje dwie części: analizę i mapowanie ograniczeń, a następnie przejście wypełniające. Pierwsza część wymaga uruchomienia przejścia analizy harmonogramu (domyślnie jest to ALAPSchedulingAnalysis), które analizuje Circuit i zapisuje czas rozpoczęcia każdej instrukcji w Circuit w harmonogramie. Po utworzeniu wstępnego harmonogramu Circuit można uruchomić dodatkowe przejścia w celu uwzględnienia wszelkich ograniczeń czasowych docelowego Backendu. Na koniec można wykonać przejście wypełniające, takie jak PadDelay lub PadDynamicalDecoupling.

Następne kroki

Zalecenia