주 콘텐츠로 건너뛰기

Sampler에서 Executor로 마이그레이션

이 가이드는 IBM Quantum® Sampler 프리미티브에서 Executor 프리미티브로 양자 샘플링 워크로드를 이전하는 방법을 설명합니다.

베타 릴리스

Executor 프리미티브는 directed execution model의 일부입니다. directed execution model의 모든 구성 요소는 현재 베타 버전이며 안정적이지 않을 수 있습니다. Samplomatic 또는 qiskit-ibm-runtime GitHub 저장소에서 이슈를 열어 테스트하고 피드백을 제공해 주세요.

마이그레이션해야 할까요?

모든 사용자가 Sampler에서 Executor로 마이그레이션해야 하는 것은 아닙니다. 두 프리미티브 사이에는 많은 차이점이 있지만 다음 가이드라인이 마이그레이션 여부를 결정하는 데 도움이 될 수 있습니다:

유틸리티 규모 실험을 실행하며 Pauli 트월링, 노이즈 모델 학습 및 주입, 기저 변환 등의 기법에 대해 세밀하고 재현 가능한 제어가 필요한 양자 정보 과학자이거나, Executor가 제공하는 추가 기능 중 하나가 필요한 경우 Executor로 마이그레이션하세요.

단순하고 고수준의 인터페이스를 원하고 프리미티브가 오류 억제 및 완화를 대신 관리해주기를 원한다면 계속 Sampler를 사용하세요.

제한 사항 및 주의 사항

Executor와 directed execution model은 베타 버전이므로 마이그레이션을 결정하기 전에 다음 사항에 유의하세요:

  • 아직 시뮬레이터 지원 없음: 로컬 시뮬레이션을 위해 qiskit-aerAerSampler 구현이 있는 Sampler와 달리, 현재 Executor용 시뮬레이터 백엔드는 없습니다. 시뮬레이터 지원은 곧 제공될 예정입니다. 그때까지는 로컬에서 템플릿 회로를 검사하고 샘플링하여 하드웨어에 제출하기 전에 워크플로를 검증할 수 있습니다.

  • 이 가이드는 Sampler만 다루며 Estimator는 다루지 않습니다. Estimator는 원시 샘플을 반환하는 대신 기댓값을 계산하기 때문에 Estimator에서 Executor로 마이그레이션하는 것은 Sampler에서 마이그레이션하는 것보다 훨씬 더 복잡합니다. Executor로 Estimator의 동작을 재현하려면 추가적인 후처리가 필요합니다. Estimator에서 Executor로 마이그레이션하는 데 도움이 되는 유틸리티 함수는 아직 개발 중이므로, 이 가이드는 의도적으로 Sampler 워크플로만 설명합니다.

Executor와 Sampler의 주요 차이점

Sampler와 Executor는 모두 양자 회로의 출력 레지스터를 샘플링하지만, 대상으로 하는 사용자는 다릅니다:

  • Sampler는 고수준 추상화입니다. 다음과 같은 특징이 있습니다:

    • 동적 디커플링과 트월링 등 내장 오류 억제 기능이 있습니다.

    • 사용자를 대신하여 암묵적인 결정을 내립니다.

    • 알고리즘 개발자가 데이터 변환보다는 혁신에 집중할 수 있도록 설계되었습니다.

  • Executordirected execution model의 일부입니다. Sampler와 여러 면에서 다르며 다음과 같은 특징이 있습니다:

    • 내장 오류 억제나 완화 기능이 없습니다. 대신 (회로 어노테이션과 샘플렉스를 사용하여) 클라이언트 측에서 설계 의도를 포착하며, 비용이 많이 드는 회로 변형 생성은 서버 측으로 이전됩니다.

    • 암묵적인 결정을 내리지 않습니다. 지시한 대로 정확히 따르므로 완전한 제어와 투명성을 제공합니다.

    • Executor와 Samplomatic은 함께 Sampler가 제공하지 않는 다음과 같은(단, 이에 국한되지 않는) 추가 기능을 제공합니다:

      • 더 많은 트월링 그룹: Samplomatic을 사용하면 Sampler가 대신 적용하는 단일 전략에 국한되지 않고 박스별로 적용할 트월링 그룹을 선택할 수 있습니다. 또한 "local_c1" 트월링 그룹과 같이 Pauli 외의 트월링 그룹도 지원합니다.
      • 커널화된 측정과 분류된 측정을 함께 사용: QuantumProgram.meas_level = "both"(qiskit-ibm-runtime v0.48.0에서 추가됨)를 설정하면 작업당 단일 측정 유형을 선택하는 대신 분류된 측정과 커널화된 측정이 모두 결과에 포함되도록 요청합니다.
      • 분수 게이트가 있는 회로에 대한 트월링: Executor는 분수 게이트를 포함하는 회로에 트월링을 적용할 수 있습니다.
      • 세밀하고 조합 가능한 오류 완화: 예를 들어 완화할 회로 레이어를 선택하고 회로에 주입되는 노이즈율을 조정할 수 있습니다.
      참고
      • 향후 새로운 기능은 Executor에 먼저 릴리스될 것으로 예상되며 Sampler로 이식되지 않을 수 있습니다. 최신 기능에 대한 접근이 필요하다면 Executor가 더 미래 지향적인 선택입니다.
      • 기본 Qiskit 패키지는 아직 Executor 프리미티브를 위한 기본 클래스를 제공하지 않습니다(SamplerV2의 경우는 제공합니다).

개념 매핑

다음 표는 Sampler 개념이 Executor에 어떻게 매핑되는지를 보여줍니다.

ConceptSamplerExecutor
Importfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
InputPUB 목록(튜플)QuantumProgramItem 객체의 QuantumProgram
Circuit and parameters(circuit, params, shots) 튜플program.append_circuit_item(circuit, circuit_arguments=...)
TwirlingTwirlingOptions어노테이션이 달린 박스와 샘플렉스를 통해 명시적으로 지정(append_samplex_item)
Run callsampler.run([pub, ...])executor.run(program)
Result typeSamplerPubResultPrimitiveResultQuantumProgramResult(반복 가능)
Access dataresult[0].data.<register>(BitArray)result[0]["<register>"](np.ndarray)
Manage noise내장 옵션수동으로 구성해야 함(어노테이션, 샘플렉스, NoiseLearnerV3)

마이그레이션 단계 개요

  1. Samplomatic 설치하기.

  2. Import 변경하기.

  3. PUB 튜플 대체하기.

  4. 샷을 표현하는 방식 변경하기.

  5. 필요에 따라 다른 옵션 업데이트하기.

  6. run 명령 업데이트하기.

  7. 결과 파싱 업데이트하기.

  8. 트월링 되돌리기.

1단계. 필요한 패키지 설치하기

Executor와 directed execution model에는 samplomatic 패키지가 필요합니다:

pip install qiskit qiskit-ibm-runtime samplomatic

# For visualization support:
# pip install samplomatic[vis]
버전 참고 사항
  • meas_level = "both" 옵션과 local_c1 트월링 그룹을 추가하므로 qiskit-ibm-runtime v0.48.0을 권장합니다.
  • qiskit >= 2.3.0이 필요합니다.
  • samplomatic >= 0.18.0이 필요합니다.

2단계. Import 변경하기

Sampler:

from qiskit_ibm_runtime import SamplerV2 as Sampler

Executor:

from qiskit_ibm_runtime import Executor, QuantumProgram

3단계. PUB 튜플을 QuantumProgram으로 대체하기

Executor를 사용할 때는 튜플(PUB) 목록을 전달하는 대신 QuantumProgram을 만들고 여기에 _항목(item)_을 추가합니다.

QuantumProgramcircuit 항목과 samplex 항목을 받습니다:

  • append_circuit_item: CircuitItem을 추가합니다. 이는 회로와 (선택적으로) 그 매개변수 값입니다. 어떠한 랜덤화 없이 그대로 실행됩니다.

    트월링이 없는 PUB로 Sampler가 하는 것처럼 단순히 회로를 샘플링하고 싶을 때 사용하세요. 예를 들어 일반 샘플링 작업을 제출하거나 원하는 변형을 이미 수동으로 포함시킨 경우입니다.

  • append_samplex_item: samplexItem을 추가합니다. 이는 템플릿 회로와 서버 측에서 랜덤화된 매개변수 세트를 생성하는 샘플렉스입니다.

    회로의 내용을 랜덤화하고 싶을 때 이 방법을 사용하세요. 주된 사용 사례는 트월링(게이트 또는 측정) 또는 노이즈 주입입니다. 이 기능은 Sampler의 내장 트월링을 대체합니다.

단일 QuantumProgram은 두 항목 유형을 모두 허용할 수 있으며, 추가된 각 항목은 독립적인 작업으로 실행되어 결과에 자체 항목을 생성합니다. 일반적으로 회로를 랜덤화할 필요가 없는 경우 append_circuit_item을 사용합니다. 그렇지 않으면 append_samplex_item을 사용합니다.

다음 섹션에서는 append_circuit_item을 사용하는 매개변수화된 회로와, append_samplex_item을 사용하여 트월링을 마이그레이션하는 방법을 각각 보여줍니다.

다음 코드 예제에서 isa_circuit은 대상 백엔드의 Instruction Set Architecture(ISA)를 준수하도록 트랜스파일된 회로를 나타냅니다. 이 isa_circuit에는 두 개의 매개변수가 포함되어 있습니다.

3a단계. 매개변수화된 회로 마이그레이션

Sampler에서는 매개변수 값이 PUB 튜플의 두 번째 요소입니다. Executor에서는 이를 circuit_argumentsappend_circuit_item에 전달합니다.

Sampler:

params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)

Executor

program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)

# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]

3b단계. 내장된 트월링을 명시적 어노테이션으로 마이그레이션

이것이 가장 중요한 변경 사항입니다. Sampler는 옵션을 사용하여 트월링을 자동으로 적용합니다. Executor에서는 어노테이션이 지정된 박스와 샘플렉스(Samplomatic 참조)를 사용하여 그 의도를 명시적으로 선언합니다.

Sampler(옵션을 사용한 트월링):

sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True

Executor(박스와 샘플렉스를 사용한 트월링):

from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)

# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)

# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)

템플릿 회로와 샘플렉스는 클라이언트 측에서 빌드되므로, 하드웨어로 전송하기 전에 로컬에서 이를 검사하고 샘플링하여 출력을 확인할 수 있습니다.

확인: 템플릿 회로를 로컬에서 샘플링하기

샘플렉스에서 랜덤화를 추출하여 템플릿 회로에 바인딩함으로써 샘플렉스가 예상한 매개변수 값을 생성하는지 확인할 수 있습니다. samplex.sample에서 반환된 매개변수 값은 템플릿 회로의 매개변수와 직접 호환됩니다.

# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())

# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)

# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)

더 나아가, 예를 들어 두 회로를 모두 Operator 객체로 변환하여 유니터리 구현을 비교하거나(측정 트월링을 취소하는 outputs["measurement_flips.<register>"] 보정을 고려한 후), 로컬 StatevectorSampler 또는 StatevectorEstimator 실행의 기댓값을 비교하여 각 랜덤화가 원래 회로와 논리적으로 동일한지 확인할 수 있습니다. 전체 안내는 Samplomatic의 Samplex inputs and outputs 가이드를 참조하세요.

4단계. 샷 요청 방식 변경하기

샷을 PUB에서 QuantumProgram(shots=...)으로 옮기세요. Executor에서 shots는 전체 작업에 적용됩니다. 서로 다른 샷 수가 필요한 경우 여러 작업을 제출하세요.

Sampler:

# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])

Executor:

# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

5단계. 필요에 따라 옵션 업데이트하기

오류 완화 선택 사항이 이제 옵션 대신 어노테이션과 샘플렉스에 있기 때문에, Executor에서 사용할 수 있는 옵션은 Sampler보다 적습니다.

설정이 위치하는 방식에도 구조적인 차이가 있습니다.

  • Sampler에서는 결과 후처리에 영향을 미치는 선택 사항을 포함한 모든 것이 프리미티브의 옵션이나 PUB에서 구성됩니다.

  • Executor에서는 작업 결과의 형태와 후처리 방식에 영향을 미치는 선택 사항이 ExecutorOptions가 아니라 QuantumProgram에서 설정됩니다.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(meas_level=...)

ExecutorOptions는 반환된 데이터의 구조를 바꾸지 않는 더 낮은 수준의 실행 및 환경 설정만 보유합니다. 최상위 그룹은 세 가지입니다.

특히, twirlingdynamical_decoupling 옵션은 Sampler에는 있지만 Executor에는 없습니다. 대신 이러한 옵션 값은 directed execution model을 통해 표현됩니다.

Example:

from qiskit_ibm_runtime import Executor, ExecutorOptions

options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True

executor = Executor(mode=backend, options=options)

6단계. run 명령 업데이트하기

Executor 작업의 입력은 PUB가 아니라 프로그램입니다.

Sampler:

# Submit a job
sampler.run([(isa_circuit, parameter_values)])

Executor:

# Submit a job
executor.run(program)

7단계. 결과 접근 방식 변경하기

Executor에서 결과는 BitArray 객체가 아니라 NumPy 배열입니다. 이름 문자열을 인덱스로 사용하면(result[0]["meas"]) np.ndarray가 반환됩니다. .data.<register> 속성 경로를 기억할 필요가 없습니다.

Sampler에서 Executor로 업데이트하려면, result[i].data.<reg> (BitArray)result[i]["<reg>"] (np.ndarray)로 변경한 다음, get_counts 기반 후처리를 NumPy 연산으로 다시 작성하세요.

작업SamplerExecutor
Get register dataresult[0].data.measresult[0]["meas"]
Data typeBitArraynp.ndarray
Counts dictionaryresult[0].data.meas.get_counts()배열을 수동으로 후처리
Multiple registers레지스터별 result[0].data.<name>레지스터별 result[0]["<name>"]
CircuitItem array shape-(parameter_sets, shots, register_bits)
SamplexItem array shape-(randomizations, parameter_sets, shots, register_bits)
Undo measurement twirling자동result[i]["measurement_flips.<name>"] + XOR
참고

Sampler의 BitArray는 헬퍼(get_counts, slice_bits, slice_shots, expectation_values, 그리고 post-selection 마스크)를 제공합니다. Executor는 원시 NumPy 배열을 반환하므로 표준 NumPy 연산을 사용하여 이 후처리를 수행할 수 있습니다.

8단계. 트월링된 결과 처리하기(비트 플립 보정)

SamplexItem을 통해 측정 트월링을 적용하면 Executor는 원본 (트월링된) 측정값과 트월링을 취소하는 데 필요한 비트 플립 보정을 함께 반환합니다. 이를 수동으로 적용해야 하며, 암묵적으로 보정되는 것은 없습니다.

Executor를 사용할 때는 다음 예제와 같이 measurement_flips.<reg> 보정과 XOR을 사용하여 명시적으로 트월링을 취소하세요.

# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)

# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)

# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1

Sampler는 트월링을 자동으로 취소해 주므로 이에 해당하는 단계가 없습니다.

전체 예제: 기본 샘플링 작업 마이그레이션하기

Sampler

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()

# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()

Executor

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram

# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA (unchanged)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()

# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]

다음 단계