Zum Hauptinhalt springen

Migration von serverseitigen zu clientseitigen Sampler und Estimator

Dieser Leitfaden beschreibt, wie du von den serverseitigen Implementierungen von IBM Quantum® Sampler und Estimator zu ihren neuen clientseitigen Implementierungen in qiskit-ibm-runtime migrierst. Die Schnittstellen und Optionen sind größtenteils unverändert, sodass die meisten Codes unverändert funktionieren, es gibt jedoch einige Verhaltensunterschiede, die du verstehen solltest.

Hintergrund​

Sampler und Estimator sind primitive Schnittstellen, die in Qiskit definiert sind. Der IBM Quantum Compute Service (früher Qiskit Runtime) hat historisch die Implementierung dieser Primitives innerhalb seiner Runtime-Umgebung bereitgestellt. Wenn du sampler.run() oder estimator.run() aufrufst, wird die Anfrage an den Service gesendet, und die gesamte Berechnung — einschließlich Fehlerunterdrückung und -minderung — findet auf der Serverseite statt.

Diese Black-Box-Erfahrung ist praktisch: Du musst dich nicht um Implementierungsdetails kümmern. Aber sie macht die Primitives auch schwer zu debuggen, anzupassen oder daraus zu lernen, da du nicht sehen kannst, was während der Verarbeitung passiert.

Das neu eingeführte direkte Ausführungsmodell verfolgt den entgegengesetzten Ansatz und bietet eine White-Box-Erfahrung. Alle Designabsichten werden auf der Client-Seite erfasst, und ein einzelnes serverseitiges Primitive Executor verarbeitet diese Eingaben genau so, wie angewiesen — es trifft keine impliziten Entscheidungen in deinem Namen.

Ab qiskit-ibm-runtime v0.50.0 werden Sampler und Estimator auf der Client-Seite auf Basis von Executor neu implementiert. Sie bieten dieselbe Bequemlichkeit und Abstraktion wie zuvor, und jetzt kannst du die Implementierungsdetails bei Bedarf einsehen. Da die Schnittstellen und Optionen größtenteils gleich bleiben, sollte die Migration nahtlos verlaufen.

Hinweis: IBM Quantum unterstützt nur Version 2 der Sampler- und Estimator-Schnittstellen (BaseSamplerV2 und BaseEstimatorV2). Daher werden sie in diesem Leitfaden einfach als Sampler und Estimator bezeichnet.

Aktualisiere die Imports​

Heute musst du die neuen Implementierungen explizit aus ihren dedizierten Modulen importieren:

from qiskit_ibm_runtime.executor_sampler import Sampler
from qiskit_ibm_runtime.executor_estimator import Estimator

In naher Zukunft werden die Top-Level-Imports zu den neuen clientseitigen Implementierungen aufgelöst, und es ist keine Codeänderung erforderlich:

# Coming soon — the following code will import the new client-side implementations.
from qiskit_ibm_runtime import Sampler, Estimator

Ebenso musst du, wenn du typisierte Options-Objekte erstellst, diese stattdessen aus qiskit_ibm_runtime.options_models importieren, oder einfach ein einfaches verschachteltes Dict übergeben:

from qiskit_ibm_runtime.options_models import SamplerOptions, EstimatorOptions

Was gleich bleibt​

  • Primitive-Konstruktion mit einem mode und options.

  • Die run()-Signatur und das PUB-Format.

  • Der Options-Baum (options.twirling, options.resilience, options.default_shots, und so weiter).

  • Die von job.result() zurückgegebene Ergebnisdatenstruktur.

Inkompatible Änderungen im neuen Sampler​

ÄnderungMigrationsmaßnahme
Das zugrunde liegende Primitive ist jetzt Executor. Sowohl die IBM Quantum Platform-Benutzeroberfläche als auch job.primitive_id zeigen executor anstelle von sampler an.Aktualisiere jeden Code, der job.primitive_id referenziert.
Die neue Implementierung bildet Sampler-Eingaben auf Executor-Eingaben ab, sodass job.inputs Executor-Eingaben zurückgibt.Aktualisiere jeden Code, der job.inputs referenziert. Siehe Job-Eingaben.
Mehr Vor- und Nachverarbeitung findet jetzt auf der Client-Seite statt, sodass sampler.run() und job.result() länger dauern können als zuvor.Aktiviere INFO-Logging, um den Fortschritt der clientseitigen Verarbeitung zu verfolgen. Siehe INFO-Logging aktivieren.
Circuit-Metadaten werden in die Ergebnismetadaten kopiert. Die in den Ergebnismetadaten zulässigen Datentypen sind jetzt auf str, float, int, bool sowie Listen oder Dictionaries dieser Typen beschränkt.Wenn du andere Datentypen benötigst, kodiere sie zuerst als String (zum Beispiel mit base64).
Options-Klassen (options_models.SamplerOptions und so weiter) sind jetzt Pydantic-Modelle statt Dataclasses, sodass sie nicht mehr mit asdict() in Python-Dictionaries umgewandelt werden können.Verwende stattdessen options.model_dump().
Options-Klassen, die zuvor das V2-Suffix hatten (ExecutionOptionsV2 und so weiter), haben dieses nicht mehr, da V1-Primitives nicht mehr unterstützt werden.Entferne das V2-Suffix dieser Options-Klassen: Ersetze ExecutionOptionsV2 durch ExecutionOptions, ResilienceOptionsV2 durch ResilienceOptions und SamplerExecutionOptionsV2 durch SamplerExecutionOptions.
Wenn twirling aktiviert ist und shots (in den PUBs oder in run()), shots_per_randomization und num_randomizations alle angegeben sind, hat num_randomizations * shots_per_randomization Vorrang vor shots.Lasse num_randomizations und shots_per_randomization weg, wenn der Wert von shots verwendet werden soll.
Einige Eingabevalidierungen wurden auf die Serverseite verlagert und lösen jetzt RuntimeError anstelle von IBMInputValueError aus.Aktualisiere die Exception-Typen, die dein Code abfängt.
Gemischte Shot-Werte in einem einzigen Job werden nicht mehr unterstützt.Reiche für jeden Shot-Wert einen separaten Job ein. Siehe Job-Aufteilung für Überlegungen.

Inkompatible Änderungen im neuen Estimator​

ÄnderungMigrationsmaßnahme
Das zugrunde liegende Primitive ist jetzt Executor. Sowohl die IBM Quantum Platform-Benutzeroberfläche als auch job.primitive_id zeigen executor anstelle von estimator an.Aktualisiere jeden Code, der job.primitive_id referenziert.
Die neue Implementierung bildet Estimator-Eingaben auf Executor-Eingaben ab, sodass job.inputs Executor-Eingaben zurückgibt.Aktualisiere jeden Code, der job.inputs referenziert. Siehe Job-Eingaben.
Mehr Vor- und Nachverarbeitung findet jetzt auf der Client-Seite statt, sodass estimator.run() und job.result() länger dauern können als zuvor.Aktiviere INFO-Logging, um den Fortschritt der clientseitigen Verarbeitung zu verfolgen. Siehe INFO-Logging aktivieren.
Circuit-Metadaten werden in die Ergebnismetadaten kopiert. Die in den Ergebnismetadaten zulässigen Datentypen sind jetzt auf str, float, int, bool sowie Listen oder Dictionaries dieser Typen beschränkt.Wenn du andere Datentypen benötigst, kodiere sie zuerst als String (zum Beispiel mit base64).
Options-Klassen (options_models.EstimatorOptions und so weiter) sind jetzt Pydantic-Modelle statt Dataclasses, sodass sie nicht mehr mit asdict() in Python-Dictionaries umgewandelt werden können.Verwende stattdessen options.model_dump().
Options-Klassen, die zuvor das V2-Suffix hatten (ExecutionOptionsV2 und so weiter), haben dieses nicht mehr, da V1-Primitives nicht mehr unterstützt werden.Entferne das V2-Suffix dieser Options-Klassen: Ersetze ExecutionOptionsV2 durch ExecutionOptions und ResilienceOptionsV2 durch ResilienceOptions.
Alle Eingabeoptionen werden in den Ergebnismetadaten zurückgegeben, statt einer ausgewählten Teilmenge.Keine — dies ist informativ.
Einige Eingabevalidierungen wurden auf die Serverseite verlagert und lösen jetzt RuntimeError anstelle von IBMInputValueError aus.Aktualisiere die Exception-Typen, die dein Code abfängt.
Kein implizites Rauschlernen mehr für PEA und PEC. Das Messrauschlernen für TREX wird weiterhin unterstützt.Lerne die Rauschmodelle separat und übergib sie an Estimator. Siehe Explizites Rauschlernen für PEA und PEC durchführen.
Der Eingabetyp von ResilienceOptions.layer_noise_model ist anders und kann aus NoiseLearnerV3-Ergebnissen erstellt werden.Siehe Explizites Rauschlernen für PEA und PEC durchführen, um zu erfahren, wie du die Rauschmodelle mit NoiseLearnerV3 lernst und an Estimator übergibst.
MeasureNoiseLearningOptions.shots_per_randomization wird nicht mehr unterstützt.Für alle Circuits im Job wird ein einzelner Shot-Wert verwendet, einschließlich der Circuits für das Messrauschlernen. Wenn du einen anderen Shot-Wert verwenden musst, wende TREX mit qiskit-mitigation außerhalb von Estimator an.
Gemischte Präzisionswerte in einem einzigen Job werden nicht mehr unterstützt.Reiche für jede gewünschte Präzision einen separaten Job ein. Siehe Job-Aufteilung für Überlegungen.
Die Option seed_estimator wird nicht mehr unterstützt.Entferne jede Zuweisung von options.seed_estimator (das Setzen löst einen ValidationError aus). Es gibt kein clientseitiges Äquivalent, sodass Ergebnisse über diesen Seed nicht mehr reproduzierbar sind.

INFO-Logging aktivieren​

Da jetzt mehr Arbeit auf der Client-Seite stattfindet, ist es hilfreich, den Fortschritt dieser Verarbeitung zu sehen. Aktiviere Logging auf INFO-Ebene für den qiskit_ibm_runtime-Logger:

import logging

logger = logging.getLogger("qiskit_ibm_runtime")
logger.setLevel(logging.INFO)

Explizites Rauschlernen für PEA und PEC durchführen​

Der neue Estimator führt kein implizites Rauschlernen mehr durch, wenn die Fehlerminderungsmethode PEA oder PEC ausgewählt ist. Du musst die Rauschmodelle explizit lernen und übergeben. Verwende den neuen NoiseLearnerV3, um zu steuern, wie Circuits in Layer aufgeteilt werden. Er nimmt eine Liste von geboxten Circuit-Anweisungen (zum Beispiel den eindeutigen Layern) als Eingabe.

Wichtig

PEA und PEC erfordern jetzt dieses explizite Muster. Überspringe den Rauschlernschritt nicht, sonst schlägt dein Code fehl. Das Messrauschlernen für TREX ist davon nicht betroffen und funktioniert weiterhin wie zuvor.

Ebenso musst du, wenn dein Code NoiseLearner verwendet und das resultierende Rauschmodell an den serverseitigen Estimator übergibt, zu NoiseLearnerV3 migrieren. Verwende NICHT den älteren NoiseLearner, der mit dem neuen Estimator inkompatibel ist.

Alle Rauschlernoptionen im serverseitigen Estimator (LayerNoiseLearningOptions) werden direkt auf die NoiseLearnerV3-Option (NoiseLearnerV3Options) abgebildet, mit Ausnahme von max_layers_to_learn. Die Anzahl der zu lernenden Layer basiert stattdessen auf der Anzahl der an NoiseLearnerV3 übergebenen Layer.

Zum Beispiel:

Serverseitiger Estimator (mit aktiviertem PEC):

from qiskit_ibm_runtime import Estimator

pubs = [...] # Your PUBs
estimator = Estimator(mode, options)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier
estimator.options.resilience.layer_noise_learning.num_randomizations = 64

job = estimator.run(pubs)

Clientseitiger Estimator (mit aktiviertem PEC):

from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import NoiseLearnerV3

pubs = [...] # Your PUBs
estimator = Estimator(mode, options)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier

# Identify the unique layers to learn.
layers = estimator.find_unique_layers(pubs)

# Learn the noise model for those layers (runs as a separate job).
learner = NoiseLearnerV3(mode)
learner.options.num_randomizations = 64 # Same as layer_noise_learning.num_randomizations
learner_job = learner.run(layers)
learner_result = learner_job.result()

# Convert results to Pauli-Lindblad noise maps.
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps so PEA/PEC uses them.
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

# Now execute the target PUBs.
job = estimator.run(pubs)

Migration von NoiseLearner zu NoiseLearnerV3​

NoiseLearner funktioniert nur mit der serverseitigen Implementierung von Estimator. Wenn dein Code also NoiseLearner verwendet, um das Rauschmodell zu lernen und an Estimator zu übergeben, musst du deinen Code aktualisieren, um NoiseLearnerV3 zu verwenden.

Weitere Details findest du im Leitfaden Migration von NoiseLearner zu NoiseLearnerV3.

Job-Aufteilung​

Wenn du einen Job in mehrere aufteilen musst, weil gemischte Shot- oder Präzisionswerte in einem einzigen Job nicht mehr unterstützt werden, beachte Folgendes:

  • Gruppiere die PUBs nach ihrem Zielwert — ein Job pro eindeutigem Wert, nicht ein Job pro PUB. Die Aufteilung ist eine Umgruppierung, sodass sich die Gesamtzahl der eingereichten PUBs nicht ändert. Gegeben zum Beispiel [A@0.01, B@0.05, C@0.01], reiche zwei Jobs ein: [A, C] bei precision=0.01 und [B] bei precision=0.05. Das Einreichen von A und C als separate Jobs ist weniger effizient, da jeder Job mit einem festen Overhead verbunden ist.

  • Lerne einmal und verwende die Rauschmodelle in allen aufgeteilten Jobs. Es ist effizienter, einen einzigen NoiseLearnerV3-Job über die Vereinigung aller Layer auszuführen. Das Ergebnis eines Rauschlern-Jobs enthält eine Liste von NoiseLearnerV3Result-Objekten, eines für jede Eingabeanweisung, in derselben Reihenfolge wie die Eingabeliste. Du kannst die Ausgabe dieses Rauschlern-Jobs in allen aufgeteilten (Estimator-)Jobs verwenden, und Rausch- modelle für Layer, die nicht in den PUBs eines aufgeteilten Jobs enthalten sind, werden ignoriert.

  • Reiche zuerst alle aufgeteilten Jobs in einem Batch ein, und sammle dann ihre Ergebnisse. Der Batch-Ausführungsmodus bietet effiziente parallele Ausführung, wenn mehrere Jobs vorhanden sind. job.result() blockiert jedoch, sodass der Aufruf innerhalb der Einreichungsschleife die Jobs serialisiert und die Vorteile von Batch zunichtemacht. Stelle sicher, dass du das Muster "Alle einreichen, dann sammeln" verwendest (unten gezeigt).

Im folgenden Beispiel benötigen pub1 und pub2 precision=0.5, während pub3 precision=0.1 benötigt:

group1_pubs = [pub1, pub2]
group2_pubs = [pub3]

with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.resilience.pec_mitigation = True

# Learn once, over the union of every job's layers.
all_layers = estimator.find_unique_layers(group1_pubs + group2_pubs)
learner_job = NoiseLearnerV3(mode=batch).run(all_layers)
learner_result = learner_job.result()
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps. Any layers not found in the input PUBs are ignored.
estimator.options.resilience.layer_noise_model = zip(all_layers, pauli_lindblad_maps)

# Submit every split job with different precision values.
jobs = []
jobs.append(estimator.run(group1_pubs, precision=0.5))
jobs.append(estimator.run(group2_pubs, precision=0.1))

# Block once, at the end — the jobs run in parallel.
results = [job.result() for job in jobs]

Job-Eingabenstruktur​

Die neue Implementierung bildet Sampler- oder Estimator-Eingaben auf Executor-Eingaben ab, sodass job.inputs ein Dictionary zurückgibt, das Executor-Eingaben enthält. Dieses Dictionary hat die folgenden Schlüssel:

  • options: Die Eingabe ExecutorOption.

  • quantum_program: Das Eingabe-QuantumProgram

  • schema_version: Die verwendete serverseitige Schemaversion.

Wenn dein Code job.inputs['options'] verwendet hat, um die für den Job angegebenen Optionen zu finden, kannst du jetzt stattdessen job.result().metadata['options'] verwenden.

Lokal mit einem Fake-Backend testen​

Bevor du an die Hardware übermittelst, kannst du den migrierten Code gegen ein Fake*- Backend validieren, um Syntaxfehler frühzeitig zu erkennen. Beachte die folgenden Details zum lokalen Testmodus:

  • Es reproduziert keine Hardware-Ergebnisse. Die lokale verrauschte Simulation repliziert das Rauschen echter Geräte nicht perfekt, daher können die Ausgaben abweichen. Die Ausführung validiert jedoch, dass die Optionspfade und Werttypen korrekt sind.

  • NoiseLearnerV3 hat keinen lokalen Testmodus: Sein mode akzeptiert nur ein echtes Backend, eine Session oder ein Batch, sodass du den Rauschlernschritt nicht gegen ein Fake- Backend ausführen kannst. Überprüfe diesen Teil deines Codes stattdessen anhand der NoiseLearnerV3-API-Referenz. Bestätige, dass der Konstruktor, die Eingabeform von run(instructions) und jeder Helfer (wie der Unique-Layer-Helfer) wie dokumentiert verwendet werden.

Cliffordisiere den Circuit für effiziente lokale Simulation​

Ein Fake-Backend verwendet einen (verrauschten) Statevector-Simulator, dessen Kosten exponentiell mit der Qubit-Anzahl und der Tiefe wachsen. Daher kann ein realistischer Workload-Circuit hängen bleiben oder den Speicher erschöpfen. Da lokales Testen nur die Optionspfade ausüben muss (nicht physikalische Ergebnisse reproduzieren muss), reduziere den Circuit zunächst mit ConvertISAToClifford auf einen Clifford-Circuit, was jeden RZ/RZZ/RX-Winkel auf das nächste Vielfache von π/2 rundet. Clifford-Circuits simulieren unabhängig von ihrer Größe effizient (Stabilizer-Simulation).

from qiskit.transpiler import PassManager
from qiskit_ibm_runtime.transpiler.passes import ConvertISAToClifford

clifford = PassManager([ConvertISAToClifford()]).run(isa_circuit)
# run `clifford` (not the original) through the fake-backend primitive

ConvertISAToClifford benötigt einen ISA-Circuit als Eingabe (die Ausgabe von generate_preset_pass_manager(...).run(...), das auf das Backend abzielt). Du musst beim Erstellen des lokalen PUB die folgenden Konsequenzen berücksichtigen:

  • Das Attribut .layout wird verworfen. Der cliffordisierte Circuit behält dieselbe Qubit- Anzahl, aber clifford.layout ist None, sodass observable.apply_layout(clifford.layout) fehlschlägt. Lege das Observable stattdessen anhand des Pre-Clifford-ISA-Circuits fest: isa_obs = observable.apply_layout(isa_circuit.layout), führe dann (clifford, isa_obs) aus.

  • Parameter werden weggebunden. Das Runden der Rotationswinkel verwandelt einen parametrischen ISA-Circuit in einen konkreten Clifford-Circuit, sodass clifford.num_parameters zu 0 wird. Ein PUB, das noch ein Parameterwerte-Array trägt, schlägt bei der Coercion fehl. Für den lokalen Lauf entferne das Parameter- Array aus dem PUB; der Hardware-Lauf behält den ursprünglichen parametrischen Circuit und seine Werte bei.

Nächste Schritte​