Saltar al contenido principal

Migrar de Sampler y Estimator del lado del servidor a los del lado del cliente

Esta guía describe cómo migrar desde las implementaciones del lado del servidor de IBM Quantum® Sampler y Estimator a sus nuevas implementaciones del lado del cliente en qiskit-ibm-runtime. Las interfaces y opciones son en gran medida las mismas, por lo que la mayoría del código se ejecuta tal cual, pero hay algunas diferencias de comportamiento que debes entender.

Antecedentes​

Sampler y Estimator son interfaces primitivas definidas en Qiskit. IBM Quantum Compute Service (anteriormente Qiskit Runtime) históricamente ha proporcionado la implementación de estas primitivas dentro de su entorno de ejecución. Cuando llamas a sampler.run() o estimator.run(), la solicitud se envía al servicio, y todo el cómputo — incluyendo la supresión y mitigación de errores — ocurre en el lado del servidor.

Esta experiencia de caja negra es conveniente: no tienes que preocuparte por los detalles de implementación. Pero también hace que las primitivas sean difíciles de depurar, personalizar o aprender de ellas, porque no puedes ver qué sucede durante el procesamiento.

El modelo de ejecución dirigida recién introducido adopta el enfoque opuesto y proporciona una experiencia de caja blanca. Todas las intenciones de diseño se capturan en el lado del cliente, y una única primitiva del lado del servidor, Executor, procesa esas entradas exactamente como se indica — no toma decisiones implícitas en tu nombre.

A partir de qiskit-ibm-runtime v0.50.0, Sampler y Estimator se reimplementan en el lado del cliente sobre Executor. Proporcionan la misma comodidad y abstracción que antes, y ahora puedes inspeccionar los detalles de implementación cuando lo necesites. Debido a que las interfaces y opciones se mantienen en gran medida iguales, la migración debería ser fluida.

Nota: IBM Quantum solo admite la versión 2 de las interfaces Sampler y Estimator (BaseSamplerV2 y BaseEstimatorV2). Por lo tanto, en esta guía se les denomina simplemente Sampler y Estimator.

Actualizar las importaciones​

Actualmente, debes importar explícitamente las nuevas implementaciones desde sus módulos dedicados:

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

En un futuro cercano, las importaciones de nivel superior se resolverán a las nuevas implementaciones del lado del cliente, y no se requerirá ningún cambio de código:

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

Del mismo modo, si construyes objetos de opciones tipados, debes importarlos desde qiskit_ibm_runtime.options_models en su lugar, o simplemente pasar un diccionario anidado simple:

from qiskit_ibm_runtime.options_models import SamplerOptions, EstimatorOptions

Qué se mantiene igual​

  • Construcción de primitivas con un mode y options.

  • La firma de run() y el formato de PUB.

  • El árbol de opciones (options.twirling, options.resilience, options.default_shots, entre otros).

  • La estructura de datos de resultado devuelta por job.result().

Cambios incompatibles en el nuevo Sampler​

CambioAcción de migración
La primitiva subyacente ahora es Executor. Tanto la interfaz de usuario de IBM Quantum Platform como job.primitive_id mostrarán executor en lugar de sampler.Actualiza cualquier código que haga referencia a job.primitive_id.
La nueva implementación asigna las entradas de Sampler a entradas de Executor, por lo que job.inputs devuelve entradas de Executor.Actualiza cualquier código que haga referencia a job.inputs. Consulta Job inputs.
Ahora se realiza más preprocesamiento y posprocesamiento en el lado del cliente, por lo que sampler.run() y job.result() podrían tardar más que antes.Habilita el registro INFO para seguir el progreso del procesamiento del lado del cliente. Consulta Enable INFO logging.
Los metadatos del circuito se copian en los metadatos del resultado. Los tipos de datos permitidos en los metadatos del resultado ahora están limitados a str, float, int, bool, y listas o diccionarios de esos tipos.Si necesitas otros tipos de datos, codifícalos primero como una cadena (por ejemplo, con base64).
Las clases de opciones (options_models.SamplerOptions, entre otras) ahora son modelos Pydantic en lugar de dataclasses, por lo que ya no se pueden convertir a diccionarios de Python usando asdict().Usa options.model_dump() en su lugar.
Las clases de opciones que antes tenían el sufijo V2 (ExecutionOptionsV2, entre otras) ya no lo tienen, ya que las primitivas V1 ya no son compatibles.Elimina el sufijo V2 de estas clases de opciones: reemplaza ExecutionOptionsV2 por ExecutionOptions, ResilienceOptionsV2 por ResilienceOptions, y SamplerExecutionOptionsV2 por SamplerExecutionOptions.
Si twirling está habilitado y se especifican todos estos valores: shots (en los PUB o en run()), shots_per_randomization y num_randomizations, entonces num_randomizations * shots_per_randomization tiene prioridad sobre shots.Omite num_randomizations y shots_per_randomization si quieres que se use el valor de shots.
Parte de la validación de entradas se ha trasladado al lado del servidor y ahora genera RuntimeError en lugar de IBMInputValueError.Actualiza los tipos de excepción que captura tu código.
Los valores de shots mixtos en un solo job ya no son compatibles.Envía un job separado para cada valor de shots. Consulta Job splitting para más consideraciones.

Cambios incompatibles en el nuevo Estimator​

CambioAcción de migración
La primitiva subyacente ahora es Executor. Tanto la interfaz de usuario de IBM Quantum Platform como job.primitive_id mostrarán executor en lugar de estimator.Actualiza cualquier código que haga referencia a job.primitive_id.
La nueva implementación asigna las entradas de Estimator a entradas de Executor, por lo que job.inputs devuelve entradas de Executor.Actualiza cualquier código que haga referencia a job.inputs. Consulta Job inputs.
Ahora ocurre más pre y post procesamiento en el lado del cliente, por lo que estimator.run() y job.result() podrían tardar más que antes.Habilita el registro INFO para seguir el progreso del procesamiento del lado del cliente. Consulta Enable INFO logging.
Los metadatos del circuito se copian en los metadatos del resultado. Los tipos de datos permitidos en los metadatos del resultado ahora están limitados a str, float, int, bool, y listas o diccionarios de esos tipos.Si necesitas otros tipos de datos, codifícalos primero como una cadena (por ejemplo, con base64).
Las clases de opciones (options_models.EstimatorOptions, entre otras) ahora son modelos Pydantic en lugar de dataclasses, por lo que ya no se pueden convertir a diccionarios de Python usando asdict().Usa options.model_dump() en su lugar.
Las clases de opciones que antes tenían el sufijo V2 (ExecutionOptionsV2, entre otras) ya no lo tienen, ya que las primitivas V1 ya no son compatibles.Elimina el sufijo V2 de estas clases de opciones: reemplaza ExecutionOptionsV2 por ExecutionOptions y ResilienceOptionsV2 por ResilienceOptions.
Todas las opciones de entrada se devuelven en los metadatos del resultado, en lugar de un subconjunto seleccionado.Ninguna — esto es informativo.
Parte de la validación de entradas se ha trasladado al lado del servidor y ahora genera RuntimeError en lugar de IBMInputValueError.Actualiza los tipos de excepción que captura tu código.
Ya no hay aprendizaje de ruido implícito para PEA y PEC. El aprendizaje de ruido de medición para TREX aún es compatible.Aprende los modelos de ruido por separado y pásalos a Estimator. Consulta Perform explicit noise learning for PEA and PEC.
El tipo de entrada de ResilienceOptions.layer_noise_model es diferente y se puede construir a partir de los resultados de NoiseLearnerV3.Consulta Perform explicit noise learning for PEA and PEC para saber cómo aprender los modelos de ruido usando NoiseLearnerV3 y pasarlos a Estimator.
MeasureNoiseLearningOptions.shots_per_randomization ya no es compatible.Se usa un único valor de shots para todos los circuitos del job, incluidos los circuitos de aprendizaje de ruido de medición. Si debes usar un valor de shots diferente, aplica TREX con qiskit-mitigation fuera de Estimator.
Los valores de precisión mixtos en un solo job ya no son compatibles.Envía un job separado para cada precisión deseada. Consulta Job splitting para más consideraciones.
La opción seed_estimator ya no es compatible.Elimina cualquier asignación de options.seed_estimator (establecerla genera un ValidationError). No existe un equivalente del lado del cliente, por lo que los resultados ya no son reproducibles mediante esta semilla.

Habilitar el registro INFO​

Debido a que ahora ocurre más trabajo en el lado del cliente, es útil ver el progreso de ese procesamiento. Habilita el registro de nivel INFO para el logger qiskit_ibm_runtime:

import logging

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

Realizar aprendizaje de ruido explícito para PEA y PEC​

El nuevo Estimator ya no realiza aprendizaje de ruido implícito cuando se selecciona el método de mitigación de errores PEA o PEC. Debes aprender los modelos de ruido de forma explícita y pasarlos. Usa el nuevo NoiseLearnerV3 para controlar cómo se estratifican los circuitos en capas. Toma como entrada una lista de instrucciones de circuito en cajas (por ejemplo, las capas únicas).

Importante

PEA y PEC ahora requieren este patrón explícito. No omitas el paso de aprendizaje de ruido o tu código fallará. El aprendizaje de ruido de medición para TREX no se ve afectado y sigue funcionando como antes.

Del mismo modo, si tu código usa NoiseLearner y pasa el modelo de ruido resultante al Estimator del lado del servidor, necesitas migrar a NoiseLearnerV3. NO uses el NoiseLearner anterior, que es incompatible con el nuevo Estimator.

Todas las opciones de aprendizaje de ruido en el Estimator del lado del servidor (LayerNoiseLearningOptions) se corresponden directamente con la opción de NoiseLearnerV3 (NoiseLearnerV3Options), con la excepción de max_layers_to_learn. El número de capas a aprender se basa, en cambio, en el número de capas pasadas a NoiseLearnerV3.

Por ejemplo:

Estimator del lado del servidor (con PEC habilitado):

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)

Estimator del lado del cliente (con PEC habilitado):

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)

Migrar de NoiseLearner a NoiseLearnerV3​

NoiseLearner solo funciona con la implementación del lado del servidor de Estimator. Por lo tanto, si tu código usa NoiseLearner para aprender el modelo de ruido y pasarlo a Estimator, necesitas actualizar tu código para usar NoiseLearnerV3.

Consulta la guía Migrar de NoiseLearner a NoiseLearnerV3 para más detalles.

División de jobs​

Cuando debas dividir un job en varios porque los valores mixtos de shots o precisión en un solo job ya no son compatibles, ten en cuenta lo siguiente:

  • Agrupa los PUB por su valor objetivo — un job por cada valor distinto, no un job por PUB. Dividir es una reagrupación, por lo que el número total de PUB que envías no cambia. Por ejemplo, dado [A@0.01, B@0.05, C@0.01], envía dos jobs: [A, C] con precision=0.01 y [B] con precision=0.05. Enviar A y C como jobs separados es menos eficiente, ya que cada job conlleva una sobrecarga fija.

  • Aprende una vez y usa los modelos de ruido en todos los jobs divididos. Es más eficiente ejecutar un único job de NoiseLearnerV3 sobre la unión de todas las capas. El resultado de un job de aprendizaje de ruido contiene una lista de objetos NoiseLearnerV3Result, uno por cada instrucción de entrada, y está en el mismo orden que la lista de entrada. Puedes usar la salida de este job de aprendizaje de ruido en todos los jobs (Estimator) divididos, y los modelos de ruido para capas que no están en los PUB de un job dividido se ignoran.

  • Envía primero todos los jobs divididos en un Batch, y luego recopila sus resultados. El modo de ejecución Batch proporciona una ejecución paralela eficiente cuando hay varios jobs. Sin embargo, job.result() es bloqueante, por lo que llamarlo dentro del bucle de envío serializa los jobs y anula los beneficios de usar Batch. Asegúrate de usar el patrón de enviar-todo-y-luego-recopilar (que se muestra a continuación).

En el siguiente ejemplo, pub1 y pub2 requieren precision=0.5, mientras que pub3 requiere precision=0.1:

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]

Estructura de las entradas del job​

La nueva implementación asigna las entradas de Sampler o Estimator a entradas de Executor, por lo que job.inputs devuelve un diccionario que contiene entradas de Executor. Este diccionario tiene las siguientes claves:

  • options: La entrada ExecutorOption.

  • quantum_program: La entrada QuantumProgram

  • schema_version: La versión del esquema del lado del servidor utilizada.

Si tu código usaba job.inputs['options'] para encontrar las opciones especificadas para el job, ahora puedes usar job.result().metadata['options'] en su lugar.

Probar localmente con un backend simulado​

Antes de enviar al hardware, puedes validar el código migrado contra un backend Fake* para detectar errores de sintaxis a tiempo. Ten en cuenta los siguientes detalles sobre el modo de prueba local:

  • No reproduce los resultados del hardware. La simulación ruidosa local no replica perfectamente el ruido de un dispositivo real, por lo que las salidas pueden diferir. La ejecución sí valida que las rutas de las opciones y los tipos de valores sean correctos.

  • NoiseLearnerV3 no tiene modo de prueba local: su mode solo acepta un Backend, Session, o Batch reales, por lo que no puedes ejercitar el paso de aprendizaje de ruido contra un backend simulado. En su lugar, verifica esa parte de tu código contra la referencia de API de NoiseLearnerV3. Confirma que el constructor, la forma de entrada de run(instructions), y cualquier ayudante (como el ayudante de capas únicas) se usen como está documentado.

Cliffordizar el circuito para una simulación local eficiente​

Un backend simulado usa un simulador de vector de estado (ruidoso), cuyo costo crece exponencialmente con el número de qubits y la profundidad. Por lo tanto, un circuito de carga de trabajo realista puede colgarse o agotar la memoria. Dado que la prueba local solo necesita probar las rutas de las opciones (no reproducir resultados físicos), reduce primero el circuito a uno de Clifford con ConvertISAToClifford, que redondea cada ángulo RZ/RZZ/RX al múltiplo más cercano de π/2. Los circuitos de Clifford se simulan de manera eficiente (simulación de estabilizadores) independientemente del tamaño.

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 requiere un circuito ISA como entrada (la salida de generate_preset_pass_manager(...).run(...) dirigido al backend). Debes tener en cuenta las siguientes consecuencias al construir el PUB local:

  • Se elimina el atributo .layout. El circuito Cliffordizado mantiene el mismo número de qubits, pero clifford.layout es None, por lo que observable.apply_layout(clifford.layout) falla. En su lugar, dispón el observable a partir del circuito ISA previo a Clifford: isa_obs = observable.apply_layout(isa_circuit.layout), y luego ejecuta (clifford, isa_obs).

  • Los parámetros se vinculan y desaparecen. Redondear los ángulos de rotación convierte un circuito ISA paramétrico en uno de Clifford concreto, por lo que clifford.num_parameters se vuelve 0. Un PUB que aún lleva un arreglo de valores de parámetros falla en la coerción. Para la ejecución local, elimina el arreglo de parámetros del PUB; la ejecución en hardware conserva el circuito paramétrico original y sus valores.

Próximos pasos​