Aller au contenu principal

Exécuter des jobs en lot

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

Utilise le mode batch pour soumettre plusieurs jobs primitifs simultanément. Voici des exemples d'utilisation des batches.

Configurer l'utilisation des batches

Avant de démarrer un batch, tu dois configurer Qiskit Runtime et l'initialiser en tant que service :

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
EstimatorV2 as Estimator,
)

service = QiskitRuntimeService()

Ouvrir un batch

Tu peux ouvrir un batch Runtime en utilisant le gestionnaire de contexte with Batch(...) ou en instanciant la classe Batch. Lorsque tu démarres un batch, tu dois spécifier un QPU en passant un objet backend. Le batch démarre quand son premier job commence à s'exécuter.

Classe Batch

backend = service.least_busy(operational=True, simulator=False)
batch = Batch(backend=backend)
estimator = Estimator(mode=batch)
sampler = Sampler(mode=batch)
# Close the batch because no context manager was used.
batch.close()

Gestionnaire de contexte

Le gestionnaire de contexte ouvre et ferme automatiquement le batch.

from qiskit_ibm_runtime import (
Batch,
SamplerV2 as Sampler,
EstimatorV2 as Estimator,
)

backend = service.least_busy(operational=True, simulator=False)
with Batch(backend=backend):
estimator = Estimator()
sampler = Sampler()

Durée de vie d'un batch

Tu peux définir la durée de vie maximale (TTL) d'un batch avec le paramètre max_time. Cette valeur doit dépasser le temps d'exécution du job le plus long. Ce minuteur démarre quand le batch commence. Lorsque la valeur est atteinte, le batch est fermé. Les jobs en cours d'exécution se terminent, mais les jobs encore en file d'attente échouent.

with Batch(backend=backend, max_time="25m"):
...

Il existe également une valeur de durée de vie interactive (TTL interactif) qui n'est pas configurable (1 minute pour tous les plans). Si aucun job de batch n'est mis en file d'attente dans cette fenêtre, le batch est temporairement désactivé.

Valeurs TTL maximales par défaut :

Type d'instanceTTL maximum par défaut
Tous les plans payants8 heures
Open10 minutes

Pour déterminer le TTL maximum ou le TTL interactif d'un batch, suis les instructions dans Déterminer les détails d'un batch et recherche les valeurs max_time ou interactive_timeout respectivement.

Fermer un batch

Un batch se ferme automatiquement lorsqu'il sort du gestionnaire de contexte. Quand le gestionnaire de contexte du batch est quitté, le batch passe en état « En cours, n'acceptant plus de nouveaux jobs ». Cela signifie que le batch termine de traiter tous les jobs en cours d'exécution ou en file d'attente jusqu'à ce que la valeur TTL maximale soit atteinte. Une fois tous les jobs terminés, le batch est immédiatement fermé. Tu ne peux pas soumettre de jobs à un batch fermé.

from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np

# This cell is hidden from users
service = QiskitRuntimeService()
backend = service.least_busy()

# Define two circuits, each with one parameter with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
transpiled_circuit_sampler = transpiled_circuit
transpiled_circuit_sampler.measure_all()

params = np.random.uniform(size=(2, 3)).T
observables = [
[
SparsePauliOp(["XX", "IY"], [0.5, 0.5]).apply_layout(
transpiled_circuit.layout
)
],
[SparsePauliOp("XX").apply_layout(transpiled_circuit.layout)],
[SparsePauliOp("IY").apply_layout(transpiled_circuit.layout)],
]

sampler_pub = (transpiled_circuit_sampler, params)
estimator_pub = (transpiled_circuit_sampler, observables, params)
with Batch(backend=backend) as batch:
estimator = Estimator()
sampler = Sampler()
job1 = estimator.run([estimator_pub])
job2 = sampler.run([sampler_pub])

# The batch is no longer accepting jobs but the submitted job will run to completion.
result = job1.result()
result2 = job2.result()
conseil

Si tu n'utilises pas de gestionnaire de contexte, ferme le batch manuellement. Si tu laisses le batch ouvert et soumets d'autres jobs plus tard, il est possible que le TTL maximum soit atteint avant que les jobs suivants commencent à s'exécuter, ce qui provoquerait leur annulation. Tu peux fermer un batch dès que tu as terminé de soumettre des jobs. Lorsqu'un batch est fermé avec batch.close(), il n'accepte plus de nouveaux jobs, mais les jobs déjà soumis continueront de s'exécuter jusqu'à leur achèvement et leurs résultats pourront être récupérés.

batch = Batch(backend=backend)

# If using qiskit-ibm-runtime earlier than 0.24.0, change `mode=` to `batch=`
estimator = Estimator(mode=batch)
sampler = Sampler(mode=batch)
job1 = estimator.run([estimator_pub])
job2 = sampler.run([sampler_pub])
print(f"Result1: {job1.result()}")
print(f"Result2: {job2.result()}")

# Manually close the batch. Running and queued jobs will run to completion.
batch.close()
Result1: PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 2), dtype=float64>), stds=np.ndarray(<shape=(3, 2), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 2), dtype=float64>), shape=(3, 2)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
Result2: PrimitiveResult([SamplerPubResult(data=DataBin(meas=BitArray(<shape=(3, 2), num_shots=4096, num_bits=2>), meas0=BitArray(<shape=(3, 2), num_shots=4096, num_bits=133>), shape=(3, 2)), metadata={'circuit_metadata': {}})], metadata={'execution': {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-01-15 07:47:58', stop='2026-01-15 07:48:05', size=24576>)])}, 'version': 2})

Déterminer les détails d'un batch

Pour un aperçu complet de la configuration et de l'état d'un batch, incluant ses TTL interactif et maximum, utilise la méthode batch.details().

from qiskit_ibm_runtime import (
QiskitRuntimeService,
batch,
SamplerV2 as Sampler,
)

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

with Batch(backend=backend) as batch:
print(batch.details())
{'id': 'ce8cf08d-b18e-4d56-ab51-eaff0b8190f4', 'backend_name': 'ibm_torino', 'interactive_timeout': 1, 'max_time': 28800, 'active_timeout': 28800, 'state': 'open', 'accepting_jobs': True, 'last_job_started': None, 'last_job_completed': None, 'started_at': None, 'closed_at': None, 'activated_at': None, 'mode': 'batch', 'usage_time': None}

Reconfigurer les jobs pour un traitement parallèle

Il existe plusieurs façons de reconfigurer tes jobs pour tirer parti du traitement parallèle offert par le batching. L'exemple suivant montre comment tu peux partitionner une longue liste de circuits en plusieurs jobs et les exécuter en batch pour bénéficier du traitement parallèle.

from qiskit_ibm_runtime import SamplerV2 as Sampler, Batch
from qiskit.circuit.random import random_circuit

max_circuits = 100
circuits = [pm.run(random_circuit(5, 5)) for _ in range(5 * max_circuits)]
for circuit in circuits:
circuit.measure_active()
all_partitioned_circuits = []
for i in range(0, len(circuits), max_circuits):
all_partitioned_circuits.append(circuits[i : i + max_circuits])
jobs = []
start_idx = 0

with Batch(backend=backend):
sampler = Sampler()
for partitioned_circuits in all_partitioned_circuits:
job = sampler.run(partitioned_circuits)
jobs.append(job)
attention

Si tu définis backend=backend dans une primitive, le programme s'exécute en mode job, même s'il se trouve à l'intérieur d'un contexte batch ou session. La définition de backend=backend est dépréciée depuis Qiskit Runtime v0.24.0. Utilise plutôt le paramètre mode.

Prochaines étapes

Recommandations