Sari la conținutul principal

Etapele Transpiler-ului

Versiuni de pachete

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.

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

Această pagină descrie etapele pipeline-ului de transpilare preconfigurate din Qiskit SDK. Există șase etape:

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

Funcția generate_preset_pass_manager creează un staged pass manager preconfigurate, compus din aceste etape. Pasele specifice care alcătuiesc fiecare etapă depind de argumentele transmise lui generate_preset_pass_manager. optimization_level este un argument pozițional care trebuie specificat; este un număr întreg care poate fi 0, 1, 2 sau 3. Valorile mai mari indică o optimizare mai intensă, dar și mai costisitoare (vezi Valorile implicite ale Transpiler-ului și opțiunile de configurare).

Modalitatea recomandată de a transpila un Circuit este să creezi un staged pass manager preconfigurate și apoi să rulezi acel pass manager pe Circuit, după cum este descris în Transpilare cu pass managere. Totuși, o alternativă mai simplă, dar mai puțin personalizabilă, este să folosești funcția transpile. Această funcție acceptă direct Circuit-ul ca argument. La fel ca în cazul generate_preset_pass_manager, pasele specifice de transpilare utilizate depind de argumentele, precum optimization_level, transmise lui transpile. De fapt, intern, funcția transpile apelează generate_preset_pass_manager pentru a crea un staged pass manager preconfigurate și îl rulează pe Circuit.

Etapa Init

Această primă etapă face foarte puțin în mod implicit și este utilă în principal dacă vrei să incluzi propriile tale optimizări inițiale. Deoarece majoritatea algoritmilor de layout și routing sunt proiectați să lucreze doar cu Gate-uri pe unul sau doi qubiți, această etapă este folosită și pentru a traduce orice Gate-uri care operează pe mai mult de doi Qubiți, în Gate-uri care operează doar pe unul sau doi Qubiți.

Pentru mai multe informații despre implementarea propriilor optimizări inițiale pentru această etapă, consultă secțiunea despre plugin-uri și personalizarea pass managerelor.

Etapa Layout

Următoarea etapă implică layout-ul sau conectivitatea Backend-ului căruia îi va fi trimis un Circuit. În general, circuitele cuantice sunt entități abstracte ale căror Qubiți sunt reprezentări „virtuale" sau „logice" ale Qubiților reali utilizați în calcule. Pentru a executa o secvență de Gate-uri, este necesară o mapare unu-la-unu de la Qubiții „virtuali" la Qubiții „fizici" dintr-un dispozitiv cuantic real. Această mapare este stocată ca un obiect Layout și face parte din constrângerile definite în cadrul arhitecturii setului de instrucțiuni (ISA) a unui Backend.

Această imagine ilustrează maparea Qubiților din reprezentarea de tip fir către o diagramă care reprezintă modul în care Qubiții sunt conectați pe QPU.

Alegerea mapării este extrem de importantă pentru minimizarea numărului de operații SWAP necesare pentru a mapa Circuit-ul de intrare pe topologia dispozitivului și pentru a asigura utilizarea celor mai bine calibrați Qubiți. Datorită importanței acestei etape, pass managerele preconfigurate încearcă câteva metode diferite pentru a găsi cel mai bun layout. De obicei, acest lucru implică doi pași: mai întâi, se încearcă găsirea unui layout „perfect" (un layout care nu necesită operații SWAP), iar apoi, un pas euristic care încearcă să găsească cel mai bun layout de utilizat dacă un layout perfect nu poate fi găsit. Există două Pase utilizate de obicei pentru acest prim pas:

  • TrivialLayout: Mapează naiv fiecare Qubit virtual la același Qubit fizic numerotat pe dispozitiv (adică, [0,1,1,3] -> [0,1,1,3]). Acesta este comportamentul istoric folosit doar în optimzation_level=1 pentru a încerca să găsească un layout perfect. Dacă eșuează, VF2Layout este încercat în continuare.
  • VF2Layout: Acesta este un AnalysisPass care selectează un layout ideal tratând această etapă ca o problemă de izomorfism de subgraf, rezolvată de algoritmul VF2++. Dacă se găsesc mai multe layout-uri, se rulează o euristică de punctare pentru a selecta maparea cu cea mai mică eroare medie.

Apoi, pentru etapa euristică, două pase sunt utilizate în mod implicit:

  • DenseLayout: Găsește sub-graful dispozitivului cu cea mai mare conectivitate și care are același număr de Qubiți ca și Circuit-ul (utilizat pentru nivelul de optimizare 1 dacă există operații de flux de control (cum ar fi IfElseOp) prezente în Circuit).
  • SabreLayout: Această pasă selectează un layout pornind de la un layout inițial aleatoriu și rulând în mod repetat algoritmul SabreSwap. Această pasă este folosită doar la nivelurile de optimizare 1, 2 și 3, dacă un layout perfect nu este găsit prin pasa VF2Layout. Pentru mai multe detalii despre acest algoritm, consultă lucrarea arXiv:1809.02573.

Etapa Routing

Pentru a implementa un Gate cu doi Qubiți între Qubiți care nu sunt conectați direct pe un dispozitiv cuantic, unul sau mai multe Gate-uri SWAP trebuie inserate în Circuit pentru a muta stările Qubiților până când acestea sunt adiacente pe harta Gate-urilor dispozitivului. Fiecare Gate SWAP reprezintă o operație costisitoare și zgomotoasă de efectuat. Astfel, găsirea numărului minim de Gate-uri SWAP necesare pentru a mapa un Circuit pe un dispozitiv dat este un pas important în procesul de transpilare. Din motive de eficiență, această etapă este de obicei calculată împreună cu etapa Layout în mod implicit, dar ele sunt distincte logic una față de cealaltă. Etapa Layout selectează Qubiții hardware care urmează să fie utilizați, în timp ce etapa Routing inserează numărul adecvat de Gate-uri SWAP pentru a executa circuitele folosind layout-ul selectat.

Totuși, găsirea mapării SWAP optime este dificilă. De fapt, este o problemă NP-hard și, prin urmare, este prohibitiv de costisitoare de calculat pentru toate dispozitivele cuantice, cu excepția celor mai mici, și a circuitelor de intrare. Pentru a depăși acest lucru, Qiskit folosește un algoritm euristic stocastic numit SabreSwap pentru a calcula o mapare SWAP bună, dar nu neapărat optimă. Utilizarea unei metode stochastice înseamnă că circuitele generate nu sunt garantate să fie aceleași la rulări repetate. Într-adevăr, rularea aceluiași Circuit în mod repetat duce la o distribuție a adâncimilor circuitului și a numărului de Gate-uri la ieșire. Din acest motiv, mulți utilizatori aleg să ruleze funcția de routing (sau întregul StagedPassManager) de mai multe ori și să selecteze circuitele cu cea mai mică adâncime din distribuția ieșirilor.

De exemplu, să luăm un Circuit GHZ de 15 Qubiți executat de 100 de ori, folosind un initial_layout „prost" (deconectat).

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

Ieșirea celulei de cod anterioare

Această distribuție largă demonstrează cât de dificil este pentru mapper-ul SWAP să calculeze cea mai bună mapare. Pentru a obține ceva informații, să analizăm atât Circuit-ul executat, cât și Qubiții care au fost aleși pe hardware.

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

Ieșirea celulei de cod anterioare

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)

Ieșirea celulei de cod anterioare

După cum poți vedea, acest Circuit trebuie să execute un Gate cu doi Qubiți între Qubiții 0 și 14, care sunt foarte depărtați în graful de conectivitate. Rularea acestui Circuit necesită astfel inserarea de Gate-uri SWAP pentru a executa toate Gate-urile cu doi Qubiți folosind pasa SabreSwap.

Notează, de asemenea, că algoritmul SabreSwap este diferit de metoda mai mare SabreLayout din etapa anterioară. În mod implicit, SabreLayout rulează atât layout-ul cât și routing-ul, și returnează Circuit-ul transformat. Acest lucru se face din câteva motive tehnice particulare specificate pe pagina de referință API a pasei.

Etapa de traducere

Când scrii un Circuit cuantic, ești liber să folosești orice poartă cuantică (operație unitară) dorești, împreună cu o colecție de operații care nu sunt porți, cum ar fi instrucțiunile de măsurare sau resetare a qubiților. Totuși, majoritatea dispozitivelor cuantice acceptă nativ doar un număr mic de operații Gate cuantice și non-Gate. Aceste Gate-uri native fac parte din definiția ISA a unei ținte (ISA), iar această etapă a PassManagers-elor preset traduce (sau desfășoară) Gate-urile specificate într-un Circuit în Gate-urile de bază native ale unui Backend specificat. Aceasta este un pas important, deoarece permite executarea circuitului de către Backend, dar în general duce la o creștere a adâncimii și a numărului de Gate-uri.

Două cazuri speciale sunt deosebit de importante de subliniat și ajută la ilustrarea a ceea ce face această etapă.

  1. Dacă un Gate SWAP nu este un Gate nativ al Backend-ului țintă, acesta necesită trei Gate-uri 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

Ca produs a trei Gate-uri CNOT, un SWAP este o operație costisitoare de efectuat pe dispozitivele cuantice cu zgomot. Cu toate acestea, astfel de operații sunt de obicei necesare pentru a încorpora un Circuit în conectivitățile limitate de Gate ale multor dispozitive. Prin urmare, minimizarea numărului de Gate-uri SWAP dintr-un Circuit este un obiectiv principal în procesul de transpilare.

  1. Un Gate Toffoli, sau controlled-controlled-not (ccx), este un Gate cu trei qubiți. Deoarece setul nostru de Gate-uri de bază include doar Gate-uri cu unul sau doi qubiți, această operație trebuie descompusă. Cu toate acestea, este destul de costisitoare:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output of the previous code cell

Pentru fiecare Gate Toffoli dintr-un Circuit cuantic, hardware-ul poate executa până la șase Gate-uri CNOT și câteva Gate-uri cu un singur qubit. Acest exemplu demonstrează că orice algoritm care utilizează mai multe Gate-uri Toffoli va rezulta într-un Circuit cu adâncime mare și va fi, prin urmare, afectat considerabil de zgomot.

Etapa de optimizare

Această etapă se concentrează pe descompunerea circuitelor cuantice în setul de Gate-uri de bază al dispozitivului țintă și trebuie să combată creșterea adâncimii din etapele de layout și rutare. Din fericire, există multe rutine pentru optimizarea circuitelor fie prin combinarea, fie prin eliminarea Gate-urilor. În unele cazuri, aceste metode sunt atât de eficiente încât circuitele de ieșire au adâncime mai mică decât cele de intrare, chiar și după maparea pe topologia hardware prin layout și rutare. În alte cazuri, nu se poate face prea mult, iar calculul poate fi dificil de efectuat pe dispozitivele cu zgomot. Această etapă este locul unde diferitele niveluri de optimizare încep să difere.

În plus, această etapă execută și câteva verificări finale pentru a se asigura că toate instrucțiunile din Circuit sunt compuse din Gate-urile de bază disponibile pe Backend-ul țintă.

Exemplul de mai jos, folosind o stare GHZ, demonstrează efectele diferitelor setări ale nivelului de optimizare asupra adâncimii circuitului și a numărului de Gate-uri.

notă

Ieșirea transpilării variază din cauza mapperului SWAP stocastic. Prin urmare, numerele de mai jos se vor schimba cel mai probabil de fiecare dată când rulezi codul.

15-qubit GHZ state

Următorul cod construiește o stare GHZ cu 15 qubiți și compară optimization_levels ale transpilării în termeni de adâncime a circuitului rezultat, număr de Gate-uri și număr de Gate-uri cu mai mulți qubiți.

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

Această ultimă etapă este executată doar dacă este solicitată explicit (similar cu etapa Init) și nu rulează implicit (deși o metodă poate fi specificată prin setarea argumentului scheduling_method la apelarea funcției generate_preset_pass_manager). Etapa de scheduling este folosită de obicei după ce Circuit-ul a fost tradus în baza target, mapat pe dispozitiv și optimizat. Aceste pasări se concentrează pe contabilizarea întregului timp inactiv dintr-un Circuit. La un nivel înalt, pasarea de scheduling poate fi privită ca inserarea explicită a instrucțiunilor de întârziere pentru a contabiliza timpul inactiv dintre execuțiile de Gate-uri și pentru a inspecta cât timp va rula Circuit-ul pe Backend.

Iată un exemplu:

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)

Rezultatul celulei de cod anterioare

Circuit cu instrucțiuni de întârziere

Transpiler-ul a inserat instrucțiuni Delay pentru a contabiliza timpul inactiv pe fiecare Qubit. Pentru a obține o idee mai bună despre cronologia Circuit-ului, putem să îl vizualizăm și cu funcția timeline.draw():

Vizualizarea timeline.draw() a aceluiași Circuit Programarea unui Circuit implică două părți: analiza și maparea constrângerilor, urmată de o pasare de umplere. Prima parte necesită rularea unei pasări de analiză a scheduling-ului (implicit aceasta este ALAPSchedulingAnalysis), care analizează Circuit-ul și înregistrează timpul de început al fiecărei instrucțiuni din Circuit într-un program. Odată ce Circuit-ul are un program inițial, pot fi rulate pasări suplimentare pentru a ține cont de orice constrângeri de sincronizare ale Backend-ului target. În final, poate fi executată o pasare de umplere, cum ar fi PadDelay sau PadDynamicalDecoupling.

Pași următori

Recomandări