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-aer에AerSampler구현이 있는 Sampler와 달리, 현재 Executor용 시뮬레이터 백엔드는 없습니다. 시뮬레이터 지원은 곧 제공될 예정입니다. 그때까지는 로컬에서 템플릿 회로를 검사하고 샘플링하여 하드웨어에 제출하기 전에 워크플로를 검증할 수 있습니다. -
이 가이드는 Sampler만 다루며 Estimator는 다루지 않습니다. Estimator는 원시 샘플을 반환하는 대신 기댓값을 계산하기 때문에 Estimator에서 Executor로 마이그레이션하는 것은 Sampler에서 마이그레이션하는 것보다 훨씬 더 복잡합니다. Executor로 Estimator의 동작을 재현하려면 추가적인 후처리가 필요합니다. Estimator에서 Executor로 마이그레이션하는 데 도움이 되는 유틸리티 함수는 아직 개발 중이므로, 이 가이드는 의도적으로 Sampler 워크플로만 설명합니다.
Executor와 Sampler의 주요 차이점
Sampler와 Executor는 모두 양자 회로의 출력 레지스터를 샘플링하지만, 대상으로 하는 사용자는 다릅니다:
-
Sampler는 고수준 추상화입니다. 다음과 같은 특징이 있습니다:
-
동적 디커플링과 트월링 등 내장 오류 억제 기능이 있습니다.
-
사용자를 대신하여 암묵적인 결정을 내립니다.
-
알고리즘 개발자가 데이터 변환보다는 혁신에 집중할 수 있도록 설계되었습니다.
-
-
Executor는 directed execution model의 일부입니다. Sampler와 여러 면에서 다르며 다음과 같은 특징이 있습니다:
-
내장 오류 억제나 완화 기능이 없습니다. 대신 (회로 어노테이션과 샘플렉스를 사용하여) 클라이언트 측에서 설계 의도를 포착하며, 비용이 많이 드는 회로 변형 생성은 서버 측으로 이전됩니다.
-
암묵적인 결정을 내리지 않습니다. 지시한 대로 정확히 따르므로 완전한 제어와 투명성을 제공합니다.
-
Executor와 Samplomatic은 함께 Sampler가 제공하지 않는 다음과 같은(단, 이에 국한되지 않는) 추가 기능을 제공합니다:
- 더 많은 트월링 그룹: Samplomatic을 사용하면 Sampler가 대신 적용하는 단일 전략에 국한되지 않고 박스별로 적용할 트월링 그룹을 선택할 수 있습니다. 또한
"local_c1"트월링 그룹과 같이 Pauli 외의 트월링 그룹도 지원합니다. - 커널화된 측정과 분류된 측정을 함께 사용:
QuantumProgram.meas_level = "both"(qiskit-ibm-runtimev0.48.0에서 추가됨)를 설정하면 작업당 단일 측정 유형을 선택하는 대신 분류된 측정과 커널화된 측정이 모두 결과에 포함되도록 요청합니다. - 분수 게이트가 있는 회로에 대한 트월링: Executor는 분수 게이트를 포함하는 회로에 트월링을 적용할 수 있습니다.
- 세밀하고 조합 가능한 오류 완화: 예를 들어 완화할 회로 레이어를 선택하고 회로에 주입되는 노이즈율을 조정할 수 있습니다.
참고- 향후 새로운 기능은 Executor에 먼저 릴리스될 것으로 예상되며 Sampler로 이식되지 않을 수 있습니다. 최신 기능에 대한 접근이 필요하다면 Executor가 더 미래 지향적인 선택입니다.
- 기본 Qiskit 패키지는 아직 Executor 프리미티브를 위한 기본 클래스를 제공하지 않습니다(
SamplerV2의 경우는 제공합니다).
- 더 많은 트월링 그룹: Samplomatic을 사용하면 Sampler가 대신 적용하는 단일 전략에 국한되지 않고 박스별로 적용할 트월링 그룹을 선택할 수 있습니다. 또한
-
개념 매핑
다음 표는 Sampler 개념이 Executor에 어떻게 매핑되는지를 보여줍니다.
| Concept | Sampler | Executor |
|---|---|---|
| Import | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Input | PUB 목록(튜플) | QuantumProgramItem 객체의 QuantumProgram |
| Circuit and parameters | (circuit, params, shots) 튜플 | program.append_circuit_item(circuit, circuit_arguments=...) |
| Twirling | TwirlingOptions | 어노테이션이 달린 박스와 샘플렉스를 통해 명시적으로 지정(append_samplex_item) |
| Run call | sampler.run([pub, ...]) | executor.run(program) |
| Result type | SamplerPubResult의 PrimitiveResult | QuantumProgramResult(반복 가능) |
| Access data | result[0].data.<register>(BitArray) | result[0]["<register>"](np.ndarray) |
| Manage noise | 내장 옵션 | 수동으로 구성해야 함(어노테이션, 샘플렉스, NoiseLearnerV3) |
마이그레이션 단계 개요
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-runtimev0.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)_을 추가합니다.
QuantumProgram은 circuit 항목과 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_arguments로 append_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:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions는 반환된 데이터의 구조를 바꾸지 않는 더 낮은 수준의 실행 및 환경
설정만 보유합니다. 최상위 그룹은 세 가지입니다.
-
environment(EnvironmentOptions) -
execution(ExecutionOptions): Sampler보다 옵션이 적습니다. 예를 들어 Executor 옵션에는meas_type이 없습니다.
특히, twirling 및 dynamical_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 연산으로 다시 작성하세요.
| 작업 | Sampler | Executor |
|---|---|---|
| Get register data | result[0].data.meas | result[0]["meas"] |
| Data type | BitArray | np.ndarray |
| Counts dictionary | result[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"]