주 콘텐츠로 건너뛰기
이 프로젝트는 베타 단계입니다. 버그를 발견하셨나요? GitHub 이슈 만들기. 질문이나 아이디어가 있으신가요? Discussions에서 질문하기 — 피드백을 기다립니다!
이 페이지의 번역 품질은 어떻습니까?

Executor 입력 및 출력

패키지 버전

이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 이 버전 이상을 사용하는 것을 권장합니다.

qiskit[all]~=2.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0

Executor 프리미티브는 오류 완화 워크플로를 사용자 정의할 때 더 많은 유연성을 제공하는 지정 실행 모델의 일부입니다.

Executor Primitive의 입력과 출력은 Sampler 및 Estimator Primitive의 입력과 출력과 매우 다릅니다. 예를 들어, PUB 목록을 입력으로 받는 대신 Executor는 QuantumProgramItem 객체 목록을 포함하는 QuantumProgram을 받습니다. 이 컨테이너 클래스는 단순한 튜플 데이터 구조인 PUB보다 더 많은 유연성을 제공합니다.

Executor의 출력은 QuantumProgramResult로, 반복 가능하며 각 입력 QuantumProgramItem에 대해 하나의 요소를 포함합니다.

입력: 양자 프로그램​

앞서 언급했듯이, Executor Primitive에 대한 입력은 QuantumProgram으로, QuantumProgramItem 객체의 반복 가능한 컬렉션입니다. 이 객체들은 두 가지 유형이 될 수 있습니다:

  • CircuitItem: 일반적으로 Circuit과 해당 매개변수 값(있는 경우)을 저장합니다.
  • SamplexItem: 일반적으로 다음을 저장합니다:
    • 템플릿 Circuit
    • 런타임에 무작위화된 매개변수 집합을 생성하는 데 사용되는 samplex 객체(예: 트월링 수행 또는 노이즈 주입)
    • 원래 Circuit에 대한 매개변수 값을 포함할 수 있는 samplex에 대한 인수

이 각 항목은 Executor가 수행할 다른 작업을 나타냅니다.

시작하기 전에​

이 페이지의 일부 코드 예제는 Samplomatic 패키지의 일부인 samplex를 사용합니다. 따라서 해당 코드 블록을 실행하기 전에 다음 코드 블록에 표시된 것처럼 Samplomatic을 설치해야 합니다. 자세한 내용은 Samplomatic 문서를 참조하세요.

pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]

예제: 두 가지 다른 작업으로 QuantumProgram 생성​

먼저 양자 프로그램을 초기화한 다음, 다음 예제에 표시된 것처럼 append_circuit_item 또는 append_samplex_item(samplex가 있는 경우)를 사용하여 프로그램 항목을 추가합니다.

다음 셀은 QuantumProgram을 초기화하고 프로그램의 각 항목의 모든 구성에 대해 1024번의 샷을 실행하도록 지정합니다.

참고

Sampler와 달리, QuantumProgram은 단일 샷 값만 받습니다. 다른 샷 값을 원하는 경우 별도의 QuantumProgram이 필요하며 이는 별도의 Job이 됩니다.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Initialize an empty program
program = QuantumProgram(shots=1024)

# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)

# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()

# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)

# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)

CircuitItem 추가​

다음으로, 백엔드의 명령 집합 아키텍처(ISA)에 따라 트랜스파일된 대상 Circuit을 QuantumProgram에 추가해요. 이 Circuit에는 두 개의 매개변수가 있으므로 매개변수 값도 제공해야 해요(이 예제에서는 10개 집합). 이 CircuitItem을 실행하는 것이 프로그램이 수행할 첫 번째 작업이에요.

# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(
10, 2
), # 10 sets of parameter values and 2 parameters
)

SamplexItem 추가​

Circuit 항목은 어떤 종류의 무작위화도 없이 실행됩니다. 반면에 samplex 항목을 사용하면 내용을 무작위화하는 방법을 지정할 수 있습니다. 다음 셀은 generate_boxing_pass_manager() 함수를 사용하여 Circuit의 게이트와 측정을 박스로 그룹화하고 각 박스에 트월링 주석을 추가합니다. 그런 다음 build() 함수를 사용하여 템플릿 Circuit과 samplex 쌍을 생성합니다.

이 SamplexItem을 실행하는 것이 프로그램이 수행할 두 번째 작업입니다.

samplex와 그 인수에 대한 전체 세부 사항은 Samplomatic API 문서를 참조하세요. generate_boxing_pass_manager() 함수 사용에 대한 정보는 Samplomatic 트랜스파일러 가이드를 참조하세요.

# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)

# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)

# Build the template circuit and the samplex. The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)

# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())
TensorInterface(<
- 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
# the arguments required by the samplex.sample method
"parameter_values": np.random.rand(10, 2),
},
shape=(28, 10), # 28 randomizations and 10 sets of parameter values
)
# Initialize an Executor with the default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()

출력​

Executor의 출력은 반복 가능한 QuantumProgramResult예요. 입력 항목과 같은 순서로 입력 QuantumProgramItem당 하나의 항목을 포함해요. 이 출력 항목 각각은 딕셔너리로, 키는 (다른 키들과 함께) 입력 Circuit의 고전 레지스터 이름에 해당하는 문자열이므로, Sampler 출력에서와 달리 이 이름들을 따로 기억해 둘 필요가 없어요. 딕셔너리 값은 np.ndarray 유형이에요.

이전 예제의 결과에는 다음 항목들이 포함돼요:

CircuitItem 결과​

첫 번째 항목은 프로그램에서 첫 번째 작업(CircuitItem)을 실행한 결과를 포함해요. 여기에는 입력 Circuit의 고전 레지스터 이름인 meas라는 단일 키가 포함돼요. 이 키의 값은 (parameter sets, shots, register bits) 형태의 np.ndarray에 매핑되며, 위 예제에서는 (10, 1024, 3)이에요.

다음 코드는 이 정보에 접근하는 방법을 보여줍니다:

# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")
Result shape: (10, 1024, 3)

result_0의 가장 오른쪽 축은 고전 비트를 리틀 엔디언 순서로 따라가요. 따라서 인덱스 0에는 고전 비트 0, 즉 최하위 비트의 결과가 들어 있어요.

SamplexItem 결과​

두 번째 항목은 프로그램에서 두 번째 작업(SamplexItem)을 실행한 결과를 포함해요. 이 항목에는 여러 키가 포함돼요. 입력 Circuit의 고전 레지스터 이름인 meas 키는 해당 레지스터의 결과 배열에 매핑돼요. 이 배열은 (randomizations, parameter sets, shots, classical bits) 형태를 가지며, 이 예제에서는 (28, 10, 1024, 3)이에요.

또한, 출력에는 meas 레지스터에 대한 측정 트월링을 원상복구하기 위한 비트 플립 보정인 measurement_flips.meas 키가 포함돼요. 비트 플립을 수행하는 데 단 하나의 샷만 필요하기 때문에 이 예제에서 이 출력 형태는 (28, 10, 1, 3)이 돼요.

result_1의 가장 오른쪽 축도 고전 비트를 리틀 엔디언 순서로 따라간다는 점에 유의하세요.

# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")

# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")

# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)

실행 구간 보기​

단일 Executor.run() 호출은 하나의 작업을 제출합니다. 그러나 서버 측에서는 서비스가 양자 프로그램의 모든 구성을 반드시 동시에 실행하는 것은 아닙니다. 대신 프로그램 항목의 바인딩된 Circuit을 _청크_로 묶어, 한 번에 하나의 청크를 QPU에서 실행합니다. 프로그램을 분할하거나 추가 작업을 제출할 필요는 없으며, 청킹은 하나의 작업 안에서 자동으로 이루어집니다. 결과의 metadata 속성에는 result.metadata.chunk_timing에 이러한 청크에 대한 타이밍 정보가 들어 있습니다. 이 타임스탬프는 QPU에서 각 청크가 실제로 시작하고 종료된 시각입니다. 이 정보를 사용하여 특정 데이터가 QPU에서 수집된 시점을 확인하거나, Executor가 작업을 어떻게 분배했는지 이해하거나, 성능 문제를 진단할 수 있습니다.

chunk_timing은 실행된 청크마다 하나의 ChunkSpan을 담은 리스트입니다. 각 ChunkSpan에는 다음 속성이 있습니다.

  • start 및 stop: 청크가 QPU에서 실행을 시작하고 종료한 UTC 타임스탬프입니다.

  • parts: 어떤 프로그램 항목이 청크에 기여했는지 설명하는 ChunkPart 객체의 리스트입니다. 각 ChunkPart에는 다음이 포함됩니다.

    • idx_item: QuantumProgram에 항목이 추가된 순서에 따른 프로그램 항목의 인덱스입니다.

    • size: 해당 항목에서 이 청크에 실행된 구성(항목 shape의 요소)의 수입니다. 예를 들어 이전 예제의 SamplexItem은 shape이 (28, 10)이므로 총 280개의 구성이 있습니다.

다음 코드는 이전에 제출한 작업에 대해 각 청크의 소요 시간과 각 청크의 내용을 출력합니다.

for idx, span in enumerate(result.metadata.chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" Item {part.idx_item}: {part.size} configurations")
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
Item 0: 10 configurations
Item 1: 280 configurations

QuantumProgramResult.timing 속성은 같은 리스트를 ChunkTiming 객체로 감싸며, 이 객체는 청크 모음에 대한 전역 정보도 보고합니다.

timing = result.timing

print("Number of chunks:", len(timing))
print(" Start of the first chunk:", timing.start)
print(" End of the last chunk:", timing.stop)
print(" Total duration (s):", timing.duration)
Number of chunks: 1
Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
End of the last chunk: 2026-11-06 01:49:05.577720+00:00
Total duration (s): 79.568002

실행 구간 시각화​

draw_chunk_timings 함수를 사용하여 청크 타이밍을 대화형 막대 플롯으로 시각화하세요. x축은 시간이며, 각 청크는 시작 및 종료 시각에 걸친 가로 구간으로 그려집니다. y축은 완료된 구성의 누적 개수(또는 normalize_y=True로 설정한 경우 전체 워크로드에 대한 백분율)를 나타냅니다. 구간 위에 마우스를 올리면 해당 청크의 타임스탬프, 소요 시간, parts를 볼 수 있습니다.

이 함수는 plotly 그림을 반환하며, 이를 직접 표시하거나 파일로 저장하거나 둘 다 할 수 있습니다. chunk_timing 리스트 또는 ChunkTiming 객체를 받을 수 있습니다. 예를 들어 서로 다른 작업의 여러 객체를 전달하여 같은 플롯에 그릴 수도 있습니다. 비교를 위해 공통 시작 시각에 맞추려면 common_start=True로 설정하세요.

Plotly 필요

draw_chunk_timings에는 plotly가 필요합니다. pip install plotly를 실행하여 설치할 수 있습니다.

청크 사이의 간격

시각화에서 청크 실행 사이에 간격이 나타날 수 있습니다. 실제 QPU에 제출되는 모든 작업은 하드웨어로 전송할 수 있는 펄스로의 변환과 같은 고전 전처리를 거칩니다. 이 전처리는 병렬로 수행될 수 있으므로 서비스는 여러 작업을 동시에 대기열에서 꺼내 시작합니다. 그러나 QPU는 한 번에 하나의 작업 단위만 처리할 수 있습니다. 따라서 작업이 여러 청크를 생성하면 해당 청크가 다른 사용자의 작업 청크와 교차 실행될 수 있으며, 각 청크는 QPU에서 자기 차례를 기다려야 할 수 있습니다. 이것이 청크 사이에 간격이 나타날 수 있는 이유입니다.

from qiskit_ibm_runtime.visualization import draw_chunk_timings

fig = draw_chunk_timings(result.timing, names="Executor job")

# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")

다음 단계​

권장 사항