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
modeundoptions. -
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
| Änderung | Migrationsmaß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
| Änderung | Migrationsmaß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.
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]beiprecision=0.01und[B]beiprecision=0.05. Das Einreichen vonAundCals 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 vonNoiseLearnerV3Result-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
Batchein, und sammle dann ihre Ergebnisse. DerBatch-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 vonBatchzunichtemacht. 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 EingabeExecutorOption. -
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.
-
NoiseLearnerV3hat keinen lokalen Testmodus: Seinmodeakzeptiert nur ein echtesBackend, eineSessionoder einBatch, sodass du den Rauschlernschritt nicht gegen ein Fake- Backend ausführen kannst. Überprüfe diesen Teil deines Codes stattdessen anhand derNoiseLearnerV3-API-Referenz. Bestätige, dass der Konstruktor, die Eingabeform vonrun(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
.layoutwird verworfen. Der cliffordisierte Circuit behält dieselbe Qubit- Anzahl, aberclifford.layoutistNone, sodassobservable.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_parameterszu0wird. 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.