Transpiler 단계
패키지 버전
이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 이 버전 이상을 사용하는 것을 권장합니다.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
이 페이지에서는 Qiskit SDK의 사전 구축된 트랜스파일레이션 파이프라인 단계를 설명합니다. 6개의 단계가 있습니다:
initlayoutroutingtranslationoptimizationscheduling
generate_preset_pass_manager 함수는 이러한 단계로 구성된 사전 설정된 단계별 패스 매니저를 생성합니다. 각 단계를 구성하는 특정 패스는 generate_preset_pass_manager에 전달되는 인수에 따라 달라집니다. optimization_level은 반드시 지정해야 하는 위치 인수이며, 0, 1, 2, 3 중 하나의 정수입니다. 값이 높을수록 더 강력하지만 비용이 많이 드는 최적화를 나타냅니다(트랜스파일레이션 기본값 및 구성 옵션 참조).
Circuit을 트랜스파일하는 권장 방법은 사전 설정된 단계별 패스 매니저를 생성한 후 해당 패스 매니저를 Circuit에서 실행하는 것입니다(패스 매니저로 트랜스파일 참조). 그러나 더 간단하지만 사용자 정의 가능성이 적은 대안으로 transpile 함수를 사용할 수 있습니다. 이 함수는 Circuit을 직접 인수로 받습니다. generate_preset_pass_manager와 마찬가지로 사용되는 특정 Transpiler 패스는 transpile에 전달되는 optimization_level과 같은 인수에 따라 달라집니다. 사실 내부적으로 transpile 함수 는 generate_preset_pass_manager를 호출하여 사전 설정된 단계별 패스 매니저를 생성하고 Circuit에서 실행합니다.
Init 단계
이 첫 번째 단계는 기본적으로 거의 아무것도 하지 않으며, 주로 자신만의 초기 최적화를 포함하려는 경우에 유용합니다. 대부분의 레이아웃 및 라우팅 알고리즘은 1-Qubit 및 2-Qubit Gate에서만 작동하도록 설계되어 있으므로, 이 단계는 2개 이상의 Qubit에서 작동하는 Gate를 1개 또는 2개의 Qubit에서만 작동하는 Gate로 변환하는 데에도 사용됩니다.
이 단계에 대한 자체 초기 최적화 구현에 대한 자세한 내용은 플러그인 및 패스 매니저 사용자 정의 섹션을 참조하세요.
Layout 단계
다음 단계는 Circuit이 전송될 Backend의 레이아웃 또는 연결성과 관련됩니다. 일반적으로 양자 Circuit은 계산에 사용되는 실제 Qubit의 "가상" 또는 "논리적" 표현인 Qubit를 가진 추상 엔티티입니다. Gate 시퀀스를 실행하려면 "가상" Qubit에서 실제 양자 장치의 "물리적" Qubit으로의 일대일 매핑이 필요합니다. 이 매핑은 Layout 객체에 저장되며 Backend의 명령어 세트 아키텍처(ISA) 내에 정의된 제약 조건의 일부입니다.

매핑의 선택은 입력 Circuit을 장치 토폴로지에 매핑하는 데 필요한 SWAP 연산 수를 최소화하고 가장 잘 교정된 Qubit를 사용하는 데 매우 중요합니다. 이 단계의 중요성 때문에 사전 설정된 패스 매니저는 최적의 레이아웃을 찾기 위해 여러 방법을 시도합니다. 일반적으로 두 단계로 이루어집니다: 먼저 "완벽한" 레이아웃(SWAP 연산이 필요 없는 레이아웃)을 찾으려 시도하고, 완벽한 레이아웃을 찾을 수 없는 경우 최적의 레이아웃을 찾는 휴리스틱 패스를 시도합니다. 이 첫 번째 단계에서 일반적으로 사용되는 두 가지 Passes가 있습니다:
TrivialLayout: 각 가상 Qubit를 장치의 동일 번호 물리적 Qubit에 단순하게 매핑합니다(예: [0,1,1,3] -> [0,1,1,3]). 이는 완벽한 레이아웃을 찾기 위해optimzation_level=1에서만 사용되는 과거 동작입니다. 실패하면 다음으로VF2Layout이 시도됩니다.VF2Layout: 이 단계를 VF2++ 알고리즘으로 해결되는 부분 그래프 동형 문제로 처리하여 이상적인 레이아웃을 선택하는AnalysisPass입니다. 둘 이상의 레이아웃이 발견되면 평균 오류가 가장 낮은 매핑을 선택하기 위해 점수 휴리스틱이 실행됩니다.
그런 다음 휴리스틱 단계에서는 기본적으로 두 가지 패스가 사용됩니다:
DenseLayout: 가장 높은 연결성을 가지고 Circuit과 동일한 수의 Qubit를 가진 장치의 하위 그래프를 찾습니다(Circuit에 제어 흐 름 연산(예: IfElseOp)이 있는 경우 최적화 레벨 1에서 사용됨).SabreLayout: 초기 랜덤 레이아웃에서 시작하여SabreSwap알고리즘을 반복적으로 실행하여 레이아웃을 선택하는 패스입니다.VF2Layout패스를 통해 완벽한 레이아웃을 찾지 못한 경우 최적화 레벨 1, 2, 3에서만 사용됩니다. 이 알고리즘에 대한 자세한 내용은 논문 arXiv:1809.02573을 참조하세요.
Routing 단계
양자 장치에서 직접 연결되지 않은 Qubit 사이에 2-Qubit Gate를 구현하려면, 장치 Gate 맵에서 인접하게 될 때까지 Qubit 상태를 이동시키기 위해 하나 이상의 SWAP Gate를 Circuit에 삽입해야 합니다. 각 SWAP Gate는 비용이 많이 들고 노이즈가 있는 연산입니다. 따라서 Circuit을 주어진 장치에 매핑하는 데 필요한 최소 SWAP Gate 수를 찾는 것은 트랜스파일레이션 프로세스의 중요한 단계입니다. 효율성을 위해 이 단계는 기본적으로 Layout 단계와 함께 계산되지만, 논리적으로는 서로 구별됩니다. Layout 단계는 사용할 하드웨어 Qubit를 선택하고, Routing 단계는 선택된 레이아웃을 사용하여 Circuit을 실행하기 위해 적절한 양의 SWAP Gate를 삽입합니다.
그러나 최적의 SWAP 매핑을 찾는 것은 어렵습니다. 사실 이것은 NP-hard 문제이며, 따라서 가장 작은 양자 장치와 입력 Circuit을 제외하고는 계산 비용이 엄청나게 높습니다. 이를 해결하기 위해 Qiskit은 SabreSwap이라는 확률적 휴리스틱 알고리 즘을 사용하여 좋지만 반드시 최적은 아닌 SWAP 매핑을 계산합니다. 확률적 방법의 사용은 반복 실행에서 생성된 Circuit이 동일하다는 것을 보장하지 않음을 의미합니다. 실제로 동일한 Circuit을 반복적으로 실행하면 출력에서 Circuit 깊이와 Gate 수의 분포가 생성됩니다. 이러한 이유로 많은 사용자가 라우팅 함수(또는 전체 StagedPassManager)를 여러 번 실행하고 출력 분포에서 가장 낮은 깊이의 Circuit을 선택합니다.
예를 들어, “나쁜”(연결되지 않은) initial_layout을 사용하여 100번 실행된 15-Qubit GHZ Circuit을 살펴보겠습니다.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeAuckland, FakeWashingtonV2
from qiskit.transpiler import generate_preset_pass_manager
backend = FakeAuckland()
ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))
depths = []
for seed in range(100):
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial", # Fixed layout mapped in circuit order
seed_transpiler=seed, # For reproducible results
)
depths.append(pass_manager.run(ghz).depth())
plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)
Text(0, 0.5, 'Counts')
이 넓은 분포는 SWAP 매퍼가 최적의 매핑을 계산하는 것이 얼마나 어려운지를 보여줍니다. 통찰력을 얻기 위해 실행되는 Circuit과 하드웨어에서 선택된 Qubit를 모두 살펴보겠습니다.
ghz.draw("mpl", idle_wires=False)
from qiskit.visualization import plot_circuit_layout
# Plot the hardware graph and indicate which hardware qubits were chosen to run the circuit
transpiled_circ = pass_manager.run(ghz)
plot_circuit_layout(transpiled_circ, backend)
보면 알 수 있듯이 이 Circuit은 연결성 그래프에서 매우 멀리 떨어진 Qubit 0과 14 사이에 2-Qubit Gate를 실행해야 합니다. 따라서 이 Circuit을 실행하려면 SabreSwap 패스를 사용하여 모든 2-Qubit Gate를 실행하기 위해 SWAP Gate를 삽입해야 합니다.
또한 SabreSwap 알고리즘은 이전 단계의 더 큰 SabreLayout 메서드와 다릅니다. 기본적으로 SabreLayout은 레이아웃과 라우팅을 모두 실행하고 변환된 Circuit을 반환합니다. 이는 패스의 API 참조 페이지에 명시된 몇 가지 특정 기술적 이유 때문입니다.
Translation 단계
양자 Circuit을 작성할 때, 원하는 양자 Gate(유니터리 연산)와 Qubit 측정이나 리셋 명령과 같은 비-Gate 연산의 집합을 자유롭게 사용할 수 있습니다. 그러나 대부분의 양자 장치는 소수의 양자 Gate 및 비-Gate 연산만 기본적으로 지원합니다. 이러한 기본 Gate는 대상의 ISA 정의의 일부이며, 사전 설정된 PassManagers의 이 단계에서는 Circuit에 지정된 Gate를 지정된 Backend의 기본 기저 Gate로 변환(또는 언롤)합니다. 이는 Backend에서 Circuit을 실행할 수 있게 하는 중요한 단계이지만, 일반적으로 깊이와 Gate 수의 증가로 이어집니다.
두 가지 특수한 경우가 특히 중요하며, 이 단계가 수행하는 작업을 설명하는 데 도움이 됩니다.
- SWAP Gate가 대상 Backend의 기본 Gate가 아닌 경우, 3개의 CNOT Gate가 필요합니다:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")
native gates:['cx', 'delay', 'for_loop', 'id', 'if_else', 'measure', 'reset', 'rz', 'switch_case', 'sx', 'x']
3개의 CNOT Gate의 곱으로서, SWAP은 노이즈가 있는 양자 장치에서 수행하기에 비용이 많이 드는 연산입니다. 그러나 이러한 연산은 일반적으로 많은 장치의 제한된 Gate 연결성에 Circuit을 임베딩하는 데 필요합니다. 따라서 Circuit에서 SWAP Gate의 수를 최소화하는 것이 트랜스파일레이션 프로세스의 주요 목표입니다.
- Toffoli 또는 제어-제어-not Gate(
ccx)는 3-Qubit Gate입니다. 기저 Gate 세트가 1-Qubit 및 2-Qubit Gate만 포함하므로 이 연산은 분해되어야 합니다. 그러나 비용이 꽤 높습니다:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")
양자 Circuit의 모든 Toffoli Gate에 대해 하드웨어는 최대 6개의 CNOT Gate와 소수의 1-Qubit Gate를 실행할 수 있습니다. 이 예제는 여러 Toffoli Gate를 사용하는 모든 알고리즘이 큰 깊이의 Circuit이 되어 노이즈의 영향을 상당히 받게 됨을 보여줍니다.
Optimization 단계
이 단계는 양자 Circuit을 대상 장치의 기저 Gate 세트로 분해하는 것을 중심으로 하며, Layout 및 Routing 단계에서 증가된 깊이에 대응해야 합니다. 다행히 Gate를 결합하거나 제거하여 Circuit을 최적화하는 많은 루틴이 있습니다. 일부 경우에 이러한 방법은 너무 효과적이어서 하드웨어 토폴로지에 대한 레이아웃 및 라우팅 후에도 출력 Circuit이 입력보다 더 낮은 깊이를 가집니다. 다른 경우에는 할 수 있는 것이 많지 않으며, 노이즈가 있는 장치에서 계산을 수행하기 어려울 수 있습니다. 이 단계에서 다양한 최적화 레벨이 차이를 보이기 시작합니다.
optimization_level=1의 경우, 이 단계는 1-Qubit Gate 체인을 결합하고 연속된 CNOT Gate를 취소하는Optimize1qGatesDecomposition과CXCancellation을 준비합니다.optimization_level=2의 경우, 이 단계는CXCancellation대신 교환 관계를 활용하여 중복 Gate를 제거하는CommutativeCancellation패스를 사용합니다.optimization_level=3의 경우, 이 단계는 다음 패스를 준비합니다:
또한 이 단계는 Circuit의 모든 명령이 대상 Backend에서 사용 가능한 기저 Gate로 구성되어 있는지 확인하기 위한 몇 가지 최종 검사도 실행합니다.
아래 GHZ 상태를 사용한 예제는 다양한 최적화 레벨 설정이 Circuit 깊이와 Gate 수에 미치는 영향을 보여줍니다.
확률적 SWAP 매퍼로 인해 트랜스파일레이션 출력이 달라집니다. 따라서 아래 숫자는 코드를 실행할 때마다 변경될 수 있습니다.
다음 코드는 15-Qubit GHZ 상태를 구성하고 결과 Circuit 깊이, Gate 수, 멀티-Qubit Gate 수 측면에서 트랜스파일레이션의 optimization_levels를 비교합니다.
ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))
depths = []
gate_counts = []
multiqubit_gate_counts = []
levels = [str(x) for x in range(4)]
for level in range(4):
pass_manager = generate_preset_pass_manager(
optimization_level=level,
backend=backend,
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
depths.append(circ.depth())
gate_counts.append(sum(circ.count_ops().values()))
multiqubit_gate_counts.append(circ.count_ops()["cx"])
fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.bar(levels, depths, label="Depth")
ax1.set_xlabel("Optimization Level")
ax1.set_ylabel("Depth")
ax1.set_title("Output Circuit Depth")
ax2.bar(levels, gate_counts, label="Number of Circuit Operations")
ax2.bar(levels, multiqubit_gate_counts, label="Number of CX gates")
ax2.set_xlabel("Optimization Level")
ax2.set_ylabel("Number of gates")
ax2.legend()
ax2.set_title("Number of output circuit gates")
fig.tight_layout()
plt.show()
스케줄링
이 마지막 단계는 명시적으로 호출된 경우에만 실행되며(Init 단계와 유사) 기본적으로 실행되지 않습니다(generate_preset_pass_manager 호출 시 scheduling_method 인수를 설정하여 메서드를 지정할 수 있음). 스케줄링 단계는 일반적으로 Circuit이 대상 기저로 변환되고, 장치에 매핑되고, 최적화된 후에 사용됩니다. 이러한 패스는 Circuit의 모든 유휴 시간을 처리하는 데 중점을 둡니다. 높은 수준에서 스케줄링 패스는 Gate 실행 사이의 유휴 시간을 처리하고 Circuit이 Backend에서 얼마나 오래 실행될지 검사하기 위해 지연 명령을 명시적으로 삽입하는 것으로 생각할 수 있습니다.
다음은 예제입니다:
ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))
# Use fake backend
backend = FakeWashingtonV2()
# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
scheduling_method="asap",
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)
Transpiler가 각 Qubit의 유휴 시간을 처리하기 위해 Delay 명령을 삽입했습니다. Circuit의 타이밍을 더 잘 파악하기 위해 timeline.draw() 함수로도 볼 수 있습니다:
Circuit의 스케줄링은 두 부분으로 구성됩니다: 분석 및 제약 매핑, 그리고 패딩 패스. 첫 번째 부분은 스케줄링 분석 패스(기본적으로 ALAPSchedulingAnalysis)를 실행해야 하며, 이는 Circuit을 분석하고 Circuit의 각 명령 시작 시간을 스케줄에 기록합니다. Circuit에 초기 스케줄이 있으면 대상 Backend의 타이밍 제약을 처리하기 위해 추가 패스를 실행할 수 있습니다. 마지막으로 PadDelay 또는 PadDynamicalDecoupling과 같은 패딩 패스를 실행할 수 있습니다.
다음 단계
generate_preset_passmanager함수 사용법을 배우려면 트랜스파일레이션 기본 설정 및 구성 옵션 주제부터 시작하세요.- 패스 매니저로 트랜스파일 주제로 트랜스파일레이션에 대해 계속 학습하세요.
- Transpiler 설정 비교 가이드를 시도해 보세요.
- Transpile API 문서를 참조하세요.