Aller au contenu principal

Étapes du transpileur

Versions des packages

Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

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

Cette page décrit les étapes du pipeline de transpilation préconstruit du SDK Qiskit. Il y a six étapes :

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

La fonction generate_preset_pass_manager crée un gestionnaire de passes par étapes préconfiguré, composé de ces étapes. Les passes spécifiques qui constituent chaque étape dépendent des arguments transmis à generate_preset_pass_manager. optimization_level est un argument positionnel obligatoire ; c'est un entier qui peut valoir 0, 1, 2 ou 3. Des valeurs plus élevées indiquent une optimisation plus poussée, mais aussi plus coûteuse en calcul (voir Paramètres par défaut et options de configuration du transpileur).

La méthode recommandée pour transpiler un circuit consiste à créer un gestionnaire de passes par étapes préconfiguré, puis à l'exécuter sur le circuit, comme décrit dans Transpiler avec des gestionnaires de passes. Il existe toutefois une alternative plus simple, mais moins personnalisable : la fonction transpile. Cette fonction accepte directement le circuit en argument. Comme pour generate_preset_pass_manager, les passes de transpilation utilisées dépendent des arguments, tels que optimization_level, transmis à transpile. En interne, la fonction transpile appelle generate_preset_pass_manager pour créer un gestionnaire de passes préconfiguré et l'exécute sur le circuit.

Étape init

Cette première étape fait très peu de choses par défaut et s'avère surtout utile si tu souhaites inclure tes propres optimisations initiales. Étant donné que la plupart des algorithmes de placement et de routage sont conçus pour fonctionner uniquement avec des portes à un ou deux qubits, cette étape sert aussi à traduire toute porte opérant sur plus de deux qubits en portes n'opérant que sur un ou deux qubits.

Pour plus d'informations sur la façon d'implémenter tes propres optimisations initiales pour cette étape, consulte la section sur les plugins et la personnalisation des gestionnaires de passes.

Étape layout

L'étape suivante concerne le placement ou la connectivité du backend vers lequel un circuit sera envoyé. En général, les circuits quantiques sont des entités abstraites dont les qubits sont des représentations « virtuelles » ou « logiques » des qubits réels utilisés dans les calculs. Pour exécuter une séquence de portes, il est nécessaire d'établir une correspondance biunivoque entre les qubits « virtuels » et les qubits « physiques » d'un appareil quantique réel. Cette correspondance est stockée dans un objet Layout et fait partie des contraintes définies dans l'architecture d'ensemble d'instructions (ISA) d'un backend.

Cette image illustre la correspondance des qubits depuis la représentation en fils jusqu'à un diagramme représentant la façon dont les qubits sont connectés sur le QPU.

Le choix de la correspondance est extrêmement important pour minimiser le nombre d'opérations SWAP nécessaires à la projection du circuit d'entrée sur la topologie de l'appareil, et pour s'assurer que les qubits les mieux calibrés sont utilisés. En raison de l'importance de cette étape, les gestionnaires de passes préconfigurés essaient plusieurs méthodes différentes pour trouver le meilleur placement. Cela implique généralement deux étapes : d'abord, tenter de trouver un placement « parfait » (un placement qui ne nécessite aucune opération SWAP), puis exécuter une passe heuristique qui tente de trouver le meilleur placement si un placement parfait est impossible. Deux Passes sont généralement utilisées pour cette première étape :

  • TrivialLayout : Fait correspondre naïvement chaque qubit virtuel au qubit physique de même numéro sur l'appareil (p. ex. [0,1,2,3] -> [0,1,2,3]). Il s'agit d'un comportement historique utilisé uniquement dans optimization_level=1 pour tenter de trouver un placement parfait. En cas d'échec, VF2Layout est essayé ensuite.
  • VF2Layout : Il s'agit d'une AnalysisPass qui sélectionne un placement idéal en traitant cette étape comme un problème d'isomorphisme de sous-graphe, résolu par l'algorithme VF2++. Si plusieurs placements sont trouvés, une heuristique de score est exécutée pour sélectionner la correspondance avec l'erreur moyenne la plus faible.

Ensuite, pour l'étape heuristique, deux passes sont utilisées par défaut :

  • DenseLayout : Trouve le sous-graphe de l'appareil présentant la plus grande connectivité et ayant le même nombre de qubits que le circuit (utilisé pour le niveau d'optimisation 1 lorsque des opérations de flux de contrôle, telles que IfElseOp, sont présentes dans le circuit).
  • SabreLayout : Cette passe sélectionne un placement en partant d'un placement aléatoire initial et en exécutant répétitivement l'algorithme SabreSwap. Cette passe n'est utilisée qu'aux niveaux d'optimisation 1, 2 et 3 si aucun placement parfait n'est trouvé via la passe VF2Layout. Pour plus de détails sur cet algorithme, consulte l'article arXiv:1809.02573.

Étape routing

Pour implémenter une porte à deux qubits entre des qubits qui ne sont pas directement connectés sur un appareil quantique, il faut insérer une ou plusieurs portes SWAP dans le circuit afin de déplacer les états de qubits jusqu'à ce qu'ils soient adjacents sur la carte de portes de l'appareil. Chaque porte SWAP représente une opération coûteuse et bruyante à effectuer. Ainsi, trouver le nombre minimal de portes SWAP nécessaires pour projeter un circuit sur un appareil donné est une étape cruciale du processus de transpilation. Pour des raisons d'efficacité, cette étape est généralement calculée en même temps que l'étape Layout par défaut, mais elles sont logiquement distinctes l'une de l'autre. L'étape Layout sélectionne les qubits matériels à utiliser, tandis que l'étape Routing insère le nombre approprié de portes SWAP afin d'exécuter les circuits avec le placement sélectionné.

Cependant, trouver la correspondance SWAP optimale est difficile. En fait, il s'agit d'un problème NP-difficile, et son calcul est donc prohibitivement coûteux pour tout sauf les plus petits appareils quantiques et circuits d'entrée. Pour contourner ce problème, Qiskit utilise un algorithme heuristique stochastique appelé SabreSwap pour calculer une correspondance SWAP bonne, mais pas nécessairement optimale. L'utilisation d'une méthode stochastique signifie que les circuits générés ne sont pas garantis d'être identiques d'une exécution à l'autre. En effet, l'exécution répétée d'un même circuit produit une distribution de profondeurs de circuit et de nombres de portes en sortie. C'est pour cette raison que de nombreux utilisateurs choisissent d'exécuter la fonction de routage (ou l'intégralité du StagedPassManager) plusieurs fois et de sélectionner les circuits de plus faible profondeur parmi la distribution des sorties.

Par exemple, prenons un circuit GHZ à 15 qubits exécuté 100 fois, en utilisant un initial_layout « mauvais » (déconnecté).

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

Sortie de la cellule de code précédente

Cette large distribution illustre la difficulté pour le mappeur SWAP de calculer la meilleure correspondance. Pour mieux comprendre, examinons à la fois le circuit exécuté et les qubits sélectionnés sur le matériel.

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

Sortie de la cellule de code précédente

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)

Sortie de la cellule de code précédente

Comme tu peux le voir, ce circuit doit exécuter une porte à deux qubits entre les qubits 0 et 14, qui sont très éloignés sur le graphe de connectivité. L'exécution de ce circuit nécessite donc l'insertion de portes SWAP pour exécuter toutes les portes à deux qubits via la passe SabreSwap.

Note également que l'algorithme SabreSwap est différent de la méthode SabreLayout plus large de l'étape précédente. Par défaut, SabreLayout exécute à la fois le placement et le routage, et retourne le circuit transformé. Ceci est fait pour quelques raisons techniques particulières précisées dans la page de référence API de la passe.

Étape translation

Lors de l'écriture d'un circuit quantique, tu es libre d'utiliser n'importe quelle porte quantique (opération unitaire) de ton choix, ainsi qu'un ensemble d'opérations non-porte telles que les instructions de mesure ou de réinitialisation de qubit. Cependant, la plupart des appareils quantiques ne prennent en charge nativement qu'un petit nombre de portes quantiques et d'opérations non-porte. Ces portes natives font partie de la définition de l'ISA d'une cible, et cette étape des PassManagers préconfigurés traduit (ou déroule) les portes spécifiées dans un circuit vers les portes de base natives d'un backend spécifié. Il s'agit d'une étape importante, car elle permet l'exécution du circuit par le backend, mais entraîne généralement une augmentation de la profondeur et du nombre de portes.

Deux cas particuliers méritent d'être soulignés et illustrent bien ce que fait cette étape.

  1. Si une porte SWAP n'est pas une porte native du backend cible, elle nécessite trois portes 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']

Sortie de la cellule de code précédente

En tant que produit de trois portes CNOT, un SWAP est une opération coûteuse à effectuer sur des appareils quantiques bruités. Cependant, ces opérations sont généralement nécessaires pour intégrer un circuit dans les connectivités de portes limitées de nombreux appareils. Ainsi, minimiser le nombre de portes SWAP dans un circuit est un objectif primordial du processus de transpilation.

  1. Une porte Toffoli, ou porte contrôlée-contrôlée-NON (ccx), est une porte à trois qubits. Étant donné que notre ensemble de portes de base n'inclut que des portes à un et deux qubits, cette opération doit être décomposée. Cependant, le coût est élevé :
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Sortie de la cellule de code précédente

Pour chaque porte Toffoli dans un circuit quantique, le matériel peut exécuter jusqu'à six portes CNOT et quelques portes à un qubit. Cet exemple montre que tout algorithme utilisant plusieurs portes Toffoli aboutira à un circuit de grande profondeur et sera donc considérablement affecté par le bruit.

Étape optimization

Cette étape est centrée sur la décomposition des circuits quantiques vers l'ensemble de portes de base de l'appareil cible, et doit lutter contre l'augmentation de profondeur due aux étapes de placement et de routage. Heureusement, il existe de nombreuses routines pour optimiser les circuits en combinant ou en éliminant des portes. Dans certains cas, ces méthodes sont si efficaces que les circuits en sortie ont une profondeur inférieure aux circuits en entrée, même après le placement et le routage vers la topologie matérielle. Dans d'autres cas, il n'y a pas grand-chose à faire et le calcul peut être difficile à réaliser sur des appareils bruités. C'est lors de cette étape que les différents niveaux d'optimisation commencent à se distinguer.

De plus, cette étape exécute également quelques vérifications finales pour s'assurer que toutes les instructions du circuit sont composées des portes de base disponibles sur le backend cible.

L'exemple ci-dessous utilisant un état GHZ illustre les effets des différents niveaux d'optimisation sur la profondeur du circuit et le nombre de portes.

remarque

La sortie de transpilation varie en raison du mappeur SWAP stochastique. Par conséquent, les valeurs ci-dessous changeront probablement à chaque exécution du code.

État GHZ à 15 qubits

Le code suivant construit un état GHZ à 15 qubits et compare les optimization_levels de transpilation en termes de profondeur de circuit résultante, de nombres de portes et de nombres de portes multi-qubits.

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

Sortie de la cellule de code précédente

Scheduling

Cette dernière étape n'est exécutée que si elle est explicitement demandée (de la même façon que l'étape Init) et ne s'exécute pas par défaut (bien qu'une méthode puisse être spécifiée en définissant l'argument scheduling_method lors de l'appel à generate_preset_pass_manager). L'étape de scheduling est généralement utilisée une fois que le circuit a été traduit vers la base cible, projeté sur l'appareil et optimisé. Ces passes se concentrent sur la prise en compte de tout le temps d'inactivité dans un circuit. À haut niveau, la passe de scheduling peut être vue comme l'insertion explicite d'instructions de délai pour tenir compte du temps d'inactivité entre les exécutions de portes et pour inspecter la durée d'exécution du circuit sur le backend.

Voici un exemple :

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)

Sortie de la cellule de code précédente

Circuit avec des instructions de délai

Le transpileur a inséré des instructions Delay pour tenir compte du temps d'inactivité sur chaque qubit. Pour mieux comprendre le timing du circuit, on peut également l'examiner avec la fonction timeline.draw() :

Vue timeline.draw() du même circuit

Le scheduling d'un circuit implique deux parties : l'analyse et la projection des contraintes, suivies d'une passe de rembourrage. La première partie nécessite l'exécution d'une passe d'analyse de scheduling (par défaut ALAPSchedulingAnalysis), qui analyse le circuit et enregistre l'heure de début de chaque instruction dans un calendrier. Une fois que le circuit dispose d'un calendrier initial, des passes supplémentaires peuvent être exécutées pour tenir compte des contraintes de timing sur le backend cible. Enfin, une passe de rembourrage telle que PadDelay ou PadDynamicalDecoupling peut être exécutée.

Étapes suivantes

Recommandations