Migrar do Sampler e Estimator do lado do servidor para o lado do cliente
Este guia descreve como migrar das implementações do lado do servidor do IBM Quantum®
Sampler e Estimator para suas novas implementações do lado do cliente no
qiskit-ibm-runtime. As interfaces e opções permanecem, em grande parte, inalteradas, então a maior parte do código
funciona como está, mas há algumas diferenças de comportamento a entender.
Contexto
Sampler e Estimator são interfaces primitivas definidas no Qiskit. O IBM Quantum
Compute Service (anteriormente Qiskit Runtime) historicamente forneceu a implementação
dessas primitivas dentro do seu ambiente de runtime. Quando você chama sampler.run() ou
estimator.run(), a solicitação é enviada ao serviço, e toda a computação — incluindo
supressão e mitigação de erros — acontece no lado do servidor.
Essa experiência de caixa-preta é conveniente: você não precisa se preocupar com detalhes de implementação. Mas isso também torna as primitivas difíceis de depurar, personalizar ou aprender, porque você não consegue ver o que acontece durante o processamento.
O modelo de execução direcionada recém-introduzido adota a abordagem oposta e oferece uma experiência de caixa-branca. Todas as intenções de design são capturadas no lado do cliente, e uma única primitiva do lado do servidor, o Executor, processa essas entradas exatamente como direcionado — ela não toma decisões implícitas em seu nome.
A partir do qiskit-ibm-runtime v0.50.0, Sampler e Estimator são reimplementados
no lado do cliente, sobre o Executor. Eles oferecem a mesma conveniência e
abstração de antes, e agora você pode inspecionar os detalhes de implementação quando
precisar. Como as interfaces e opções permanecem, em grande parte, as mesmas, a migração deve ser
tranquila.
Nota: o IBM Quantum suporta apenas a versão 2 das interfaces Sampler e Estimator (BaseSamplerV2 e BaseEstimatorV2). Portanto, elas são simplesmente chamadas de Sampler e Estimator neste guia.
Atualizar as importações
Atualmente, você deve importar explicitamente as novas implementações de seus módulos dedicados:
from qiskit_ibm_runtime.executor_sampler import Sampler
from qiskit_ibm_runtime.executor_estimator import Estimator
Em um futuro próximo, as importações de nível superior serão resolvidas para as novas implementações do lado do cliente, e nenhuma alteração de código será necessária:
# Coming soon — the following code will import the new client-side implementations.
from qiskit_ibm_runtime import Sampler, Estimator
Da mesma forma, se você construir objetos de opções tipados, deve importá-los de
qiskit_ibm_runtime.options_models, ou apenas passar um dicionário aninhado simples:
from qiskit_ibm_runtime.options_models import SamplerOptions, EstimatorOptions
O que permanece o mesmo
-
Construção de primitiva com
modeeoptions. -
A assinatura de
run()e o formato PUB. -
A árvore de opções (
options.twirling,options.resilience,options.default_shots, e assim por diante). -
A estrutura de dados de resultado retornada por
job.result().
Mudanças incompatíveis no novo Sampler
| Mudança | Ação de migração |
|---|---|
A primitiva subjacente agora é Executor. Tanto a interface do usuário do IBM Quantum Platform quanto job.primitive_id mostrarão executor em vez de sampler. | Atualize qualquer código que faça referência a job.primitive_id. |
A nova implementação mapeia entradas do Sampler para entradas do Executor, então job.inputs retorna entradas do Executor. | Atualize qualquer código que faça referência a job.inputs. Veja Estrutura de entradas do job. |
Mais pré e pós-processamento agora acontece no lado do cliente, então sampler.run() e job.result() podem levar mais tempo do que antes. | Habilite o log em nível INFO para acompanhar o progresso do processamento no lado do cliente. Veja Habilitar log em nível INFO. |
Os metadados do circuito são copiados para os metadados do resultado. Os tipos de dados permitidos nos metadados do resultado agora são limitados a str, float, int, bool, e listas ou dicionários desses tipos. | Se você precisar de outros tipos de dados, codifique-os primeiro como uma string (por exemplo, com base64). |
As classes de opções (options_models.SamplerOptions e assim por diante) agora são modelos Pydantic em vez de dataclasses, então não podem mais ser convertidas em dicionários Python usando asdict(). | Use options.model_dump() em vez disso. |
As classes de opções que antes tinham o sufixo V2 (ExecutionOptionsV2 e assim por diante) não têm mais, já que as primitivas V1 não são mais suportadas. | Remova o sufixo V2 dessas classes de opções: substitua ExecutionOptionsV2 por ExecutionOptions, ResilienceOptionsV2 por ResilienceOptions, e SamplerExecutionOptionsV2 por SamplerExecutionOptions. |
Se twirling estiver habilitado e shots (nos PUBs ou em run()), shots_per_randomization e num_randomizations estiverem todos especificados, então num_randomizations * shots_per_randomization tem precedência sobre shots. | Omita num_randomizations e shots_per_randomization se quiser que o valor de shots seja usado. |
Algumas validações de entrada foram movidas para o lado do servidor e agora geram RuntimeError em vez de IBMInputValueError. | Atualize os tipos de exceção que seu código captura. |
| Valores de shots mistos em um único job não são mais suportados. | Envie um job separado para cada valor de shots. Veja Divisão de jobs para considerações. |
Mudanças incompatíveis no novo Estimator
| Mudança | Ação de migração |
|---|---|
A primitiva subjacente agora é Executor. Tanto a interface do usuário do IBM Quantum Platform quanto job.primitive_id mostrarão executor em vez de estimator. | Atualize qualquer código que faça referência a job.primitive_id. |
A nova implementação mapeia entradas do Estimator para entradas do Executor, então job.inputs retorna entradas do Executor. | Atualize qualquer código que faça referência a job.inputs. Veja Estrutura de entradas do job. |
Mais pré e pós-processamento agora acontece no lado do cliente, então estimator.run() e job.result() podem levar mais tempo do que antes. | Habilite o log em nível INFO para acompanhar o progresso do processamento no lado do cliente. Veja Habilitar log em nível INFO. |
Os metadados do circuito são copiados para os metadados do resultado. Os tipos de dados permitidos nos metadados do resultado agora são limitados a str, float, int, bool, e listas ou dicionários desses tipos. | Se você precisar de outros tipos de dados, codifique-os primeiro como uma string (por exemplo, com base64). |
As classes de opções (options_models.EstimatorOptions e assim por diante) agora são modelos Pydantic em vez de dataclasses, então não podem mais ser convertidas em dicionários Python usando asdict(). | Use options.model_dump() em vez disso. |
As classes de opções que antes tinham o sufixo V2 (ExecutionOptionsV2 e assim por diante) não têm mais, já que as primitivas V1 não são mais suportadas. | Remova o sufixo V2 dessas classes de opções: substitua ExecutionOptionsV2 por ExecutionOptions e ResilienceOptionsV2 por ResilienceOptions. |
| Todas as opções de entrada são retornadas nos metadados do resultado, em vez de um subconjunto selecionado. | Nenhuma — isso é apenas informativo. |
Algumas validações de entrada foram movidas para o lado do servidor e agora geram RuntimeError em vez de IBMInputValueError. | Atualize os tipos de exceção que seu código captura. |
| Não há mais aprendizado de ruído implícito para PEA e PEC. O aprendizado de ruído de medição para TREX ainda é suportado. | Aprenda os modelos de ruído separadamente e passe-os ao Estimator. Veja Realizar aprendizado explícito de ruído para PEA e PEC. |
O tipo de entrada de ResilienceOptions.layer_noise_model é diferente e pode ser construído a partir dos resultados de NoiseLearnerV3. | Veja Realizar aprendizado explícito de ruído para PEA e PEC sobre como aprender os modelos de ruído usando NoiseLearnerV3 e passá-los ao Estimator. |
MeasureNoiseLearningOptions.shots_per_randomization não é mais suportado. | Um único valor de shots é usado para todos os circuitos no job, incluindo os circuitos de aprendizado de ruído de medição. Se você precisar usar um valor de shots diferente, aplique TREX com qiskit-mitigation fora do Estimator. |
| Valores de precisão mistos em um único job não são mais suportados. | Envie um job separado para cada precisão desejada. Veja Divisão de jobs para considerações. |
A opção seed_estimator não é mais suportada. | Remova qualquer atribuição de options.seed_estimator (defini-la gera um ValidationError). Não há equivalente do lado do cliente, então os resultados não são mais reprodutíveis por meio desse seed. |
Habilitar log em nível INFO
Como mais trabalho agora acontece no lado do cliente, é útil ver o progresso desse
processamento. Habilite o log em nível INFO para o logger qiskit_ibm_runtime:
import logging
logger = logging.getLogger("qiskit_ibm_runtime")
logger.setLevel(logging.INFO)
Realizar aprendizado explícito de ruído para PEA e PEC
O novo Estimator não realiza mais aprendizado de ruído implícito quando o método de mitigação de erros PEA ou PEC
é selecionado. Você deve aprender os modelos de ruído explicitamente e passá-los.
Use o novo NoiseLearnerV3 para controlar como os circuitos
são estratificados em camadas. Ele recebe como entrada uma lista de instruções de circuito em caixas (por exemplo,
as camadas distintas).
PEA e PEC agora exigem esse padrão explícito. Não pule a etapa de aprendizado de ruído ou seu código falhará. O aprendizado de ruído de medição para TREX não é afetado e continua funcionando como antes.
Da mesma forma, se seu código usa NoiseLearner e passa o modelo de ruído resultante para o Estimator do lado do servidor, você precisa migrar para NoiseLearnerV3. NÃO use o NoiseLearner mais antigo, que é incompatível com o novo Estimator.
Todas as opções de aprendizado de ruído no Estimator do lado do servidor (LayerNoiseLearningOptions) mapeiam diretamente para a opção do NoiseLearnerV3 (NoiseLearnerV3Options), com exceção de max_layers_to_learn. O número de camadas a aprender é, em vez disso, baseado no número de camadas passadas ao NoiseLearnerV3.
Por exemplo:
Estimator do lado do servidor (com 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 do lado do cliente (com 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 para NoiseLearnerV3
NoiseLearner só funciona com a implementação do lado do servidor do Estimator. Portanto, se seu código usa NoiseLearner para aprender o modelo de ruído e passá-lo ao Estimator, você precisa atualizar seu código para usar NoiseLearnerV3.
Veja o guia Migrar de NoiseLearner para NoiseLearnerV3 para detalhes.
Divisão de jobs
Quando você precisa dividir um job em vários porque valores mistos de shots ou precisão em um único job não são mais suportados, considere o seguinte:
-
Agrupe os PUBs pelo valor alvo — um job por valor distinto, não um job por PUB. A divisão é um reagrupamento, então o número total de PUBs que você envia não muda. Por exemplo, dado
[A@0.01, B@0.05, C@0.01], envie dois jobs:[A, C]comprecision=0.01e[B]comprecision=0.05. EnviarAeCcomo jobs separados é menos eficiente, já que cada job vem com um overhead fixo. -
Aprenda uma vez e use os modelos de ruído em todos os jobs divididos. É mais eficiente executar um único job
NoiseLearnerV3sobre a união de todas as camadas. O resultado de um job de aprendizado de ruído contém uma lista de objetosNoiseLearnerV3Result, um para cada instrução de entrada, na mesma ordem da lista de entrada. Você pode usar a saída desse job de aprendizado de ruído em todos os jobs divididos (do Estimator), e os modelos de ruído para camadas que não estão nos PUBs de um job dividido são ignorados. -
Envie todos os jobs divididos em um
Batchprimeiro e depois colete seus resultados. O modo de execuçãoBatchoferece execução paralela eficiente quando há vários jobs. No entanto,job.result()é bloqueante, então chamá-lo dentro do loop de envio serializa os jobs e anula os benefícios de usarBatch. Certifique-se de usar o padrão de enviar-tudo-depois-coletar (mostrado abaixo).
No exemplo a seguir, pub1 e pub2 requerem precision=0.5, enquanto pub3 requer 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]
Estrutura de entradas do job
A nova implementação mapeia entradas do Sampler ou Estimator para entradas do Executor, então job.inputs retorna um dicionário que contém entradas do Executor. Esse dicionário tem as seguintes chaves:
-
options: AExecutorOptionde entrada. -
quantum_program: OQuantumProgramde entrada -
schema_version: A versão do schema do lado do servidor usada.
Se seu código usava job.inputs['options'] para encontrar as opções especificadas para o job, agora você pode usar job.result().metadata['options'] em vez disso.
Testar localmente com um backend falso
Antes de enviar para o hardware, você pode validar o código migrado contra um backend Fake*
para detectar erros de sintaxe antecipadamente. Observe os seguintes detalhes sobre o modo de teste local:
-
Não reproduz os resultados do hardware. A simulação local com ruído não replica perfeitamente o ruído de um dispositivo real, portanto, as saídas podem diferir. A execução valida que os caminhos de opções e os tipos de valores estão corretos.
-
NoiseLearnerV3não tem modo de teste local: seumodeaceita apenas umBackend,SessionouBatchreais, então você não pode exercitar a etapa de aprendizado de ruído contra um backend falso. Em vez disso, verifique essa parte do seu código com a referência de API doNoiseLearnerV3. Confirme que o construtor, o formato de entrada derun(instructions), e qualquer helper (como o helper de camadas distintas) sejam usados conforme documentado.
Cliffordizar o circuito para simulação local eficiente
Um backend falso usa um simulador de vetor de estado (com ruído), cujo custo cresce exponencialmente com
o número de qubits e a profundidade. Assim, um circuito de carga de trabalho realista pode travar ou esgotar a memória. Como
o teste local só precisa testar os caminhos de opções (e não reproduzir resultados físicos),
reduza o circuito para um circuito de Clifford primeiro com
ConvertISAToClifford,
que arredonda cada ângulo RZ/RZZ/RX para o múltiplo mais próximo de π/2. Circuitos de Clifford
simulam de forma eficiente (simulação de estabilizador) independentemente do tamanho.
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 requer um circuito ISA como entrada (a saída de
generate_preset_pass_manager(...).run(...) direcionada ao backend). Você deve levar em conta as seguintes consequências
ao construir o PUB local:
-
O atributo
.layouté descartado. O circuito Cliffordizado mantém o mesmo número de qubits, masclifford.layoutéNone, entãoobservable.apply_layout(clifford.layout)falha. Em vez disso, disponha o observável a partir do circuito ISA pré-Clifford:isa_obs = observable.apply_layout(isa_circuit.layout), e então execute(clifford, isa_obs). -
Os parâmetros são vinculados e removidos. Arredondar os ângulos de rotação transforma um circuito ISA paramétrico em um circuito de Clifford concreto, então
clifford.num_parametersse torna0. Um PUB que ainda carrega um array de valores de parâmetros falha na coerção. Para a execução local, remova o array de parâmetros do PUB; a execução no hardware mantém o circuito paramétrico original e seus valores.
Próximos passos
- Modelo de execução direcionada
- Entradas e saídas do Estimator
- Especificar opções do Estimator
- Entradas e saídas do Sampler
- Especificar opções do Sampler
- Helper de aprendizado de ruído (NoiseLearnerV3)
- Referência de API do NoiseLearnerV3
- Passe do transpilador
ConvertISAToClifford - Técnicas de mitigação e supressão de erros