주 콘텐츠로 건너뛰기

SABRE를 활용한 트랜스파일 최적화

사용 시간 예상: Heron r2 프로세서에서 1분 미만 (참고: 이는 예상치이며 실제 런타임은 다를 수 있습니다.)

배경

트랜스파일은 양자 회로를 특정 양자 하드웨어와 호환되는 형태로 변환하는 Qiskit의 중요한 단계입니다. 이 과정에는 두 가지 핵심 단계가 포함됩니다: qubit 레이아웃 (논리적 qubit을 장치의 물리적 qubit에 매핑)과 gate 라우팅 (SWAP gate를 필요에 따라 삽입하여 다중 qubit gate가 장치 연결성을 따르도록 보장).

SABRE (SWAP 기반 양방향 휴리스틱 탐색 알고리즘)는 레이아웃과 라우팅 모두에 강력한 최적화 도구입니다. 대규모 회로 (100개 이상의 qubit)와 가능한 qubit 매핑의 지수적 증가로 인해 효율적인 해결책이 필요한 IBM® Heron 같은 복잡한 커플링 맵을 갖는 장치에 특히 효과적입니다.

SABRE를 사용하는 이유

SABRE는 SWAP gate 수를 최소화하고 회로 깊이를 줄여 실제 하드웨어에서의 회로 성능을 향상시킵니다. 휴리스틱 기반 접근 방식은 고급 하드웨어와 크고 복잡한 회로에 이상적입니다. LightSABRE 알고리즘에서 도입된 최근 개선 사항은 더 빠른 런타임과 더 적은 SWAP gate를 제공하며 SABRE의 성능을 더욱 최적화합니다. 이러한 향상으로 대규모 회로에서 더욱 효과적입니다.

학습 내용

이 튜토리얼은 두 부분으로 나뉩니다:

  1. 대규모 회로의 고급 최적화를 위해 Qiskit 패턴과 함께 SABRE 사용법 익히기.
  2. 확장 가능하고 효율적인 트랜스파일을 위해 qiskit_serverless를 활용하여 SABRE의 잠재력 극대화.

다음을 배우게 됩니다:

  • 100개 이상의 qubit를 가진 회로에 대한 SABRE 최적화를 통해 optimization_level=3 같은 기본 트랜스파일 설정 초과 달성.
  • 런타임을 개선하고 gate 수를 줄이는 LightSABRE 개선 사항 탐색.
  • 회로 품질트랜스파일 런타임의 균형을 위해 SABRE 핵심 파라미터 (swap_trials, layout_trials, max_iterations, heuristic) 커스터마이징.

요구 사항

이 튜토리얼을 시작하기 전에 다음 항목이 설치되어 있는지 확인하세요:

  • Qiskit SDK v1.0 이상, 시각화 지원 포함
  • Qiskit Runtime v0.28 이상 (pip install qiskit-ibm-runtime)
  • Serverless (pip install qiskit-ibm-catalog qiskit_serverless)

설정

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-catalog qiskit-ibm-runtime qiskit-serverless
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_catalog import QiskitServerless, QiskitFunction
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorOptions
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import SabreLayout, SabreSwap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import matplotlib.pyplot as plt
import numpy as np
import time

Part I. Qiskit 패턴과 함께 SABRE 사용하기

SABRE는 Qiskit에서 qubit 레이아웃과 gate 라우팅 단계를 모두 처리하여 양자 회로를 최적화하는 데 사용할 수 있습니다. 이 섹션에서는 최적화 단계 2에 주로 초점을 맞추어 Qiskit 패턴과 함께 SABRE를 사용하는 최소 예제를 안내합니다.

SABRE를 실행하려면 다음이 필요합니다:

  • 양자 회로의 DAG (방향성 비순환 그래프) 표현.
  • qubit이 물리적으로 어떻게 연결되어 있는지 지정하는 Backend의 커플링 맵.
  • 레이아웃과 라우팅을 최적화하기 위해 알고리즘을 적용하는 SABRE 패스.

이 부분에서는 SabreLayout 패스에 집중합니다. 이 패스는 SWAP gate 수를 최소화하면서 가장 효율적인 초기 레이아웃을 찾기 위해 레이아웃과 라우팅 시도를 모두 수행합니다. 중요하게도, SabreLayout은 단독으로도 SWAP gate를 가장 적게 추가하는 해결책을 저장함으로써 내부적으로 레이아웃과 라우팅을 모두 최적화합니다. 단, SabreLayout만 사용할 때는 SABRE의 휴리스틱을 변경할 수 없지만 layout_trials 수는 커스터마이징할 수 있습니다.

Step 1: 고전적 입력을 양자 문제로 매핑

GHZ (Greenberger-Horne-Zeilinger) 회로는 모든 qubit이 |0...0⟩ 또는 |1...1⟩ 상태에 있는 얽힘 상태를 준비하는 양자 회로입니다. nn qubit에 대한 GHZ 상태는 수학적으로 다음과 같이 표현됩니다: GHZ=12(0n+1n)|\text{GHZ}\rangle = \frac{1}{\sqrt{2}} \left( |0\rangle^{\otimes n} + |1\rangle^{\otimes n} \right)

다음을 적용하여 구성됩니다:

  1. 첫 번째 qubit에 Hadamard gate를 적용하여 중첩 상태 생성.
  2. 첫 번째 qubit과 나머지 qubit을 얽히게 하는 일련의 CNOT gate 적용.

이 예제에서는 선형 토폴로지 대신 의도적으로 별형 토폴로지 GHZ 회로를 구성합니다. 별형 토폴로지에서는 첫 번째 qubit이 "허브" 역할을 하며 다른 모든 qubit은 CNOT gate를 사용하여 직접 연결됩니다. 이 선택은 의도적인 것입니다: 선형 토폴로지 GHZ 상태는 이론적으로 SWAP gate 없이 선형 커플링 맵에서 O(N)O(N) 깊이로 구현될 수 있지만, SABRE는 100-qubit GHZ 회로를 Backend의 heavy-hex 커플링 맵의 하위 그래프에 매핑함으로써 최적 해결책을 쉽게 찾을 수 있습니다.

별형 토폴로지 GHZ 회로는 훨씬 더 어려운 문제를 제시합니다. SWAP gate 없이 여전히 이론적으로 O(N)O(N) 깊이로 실행될 수 있지만, 이 해결책을 찾으려면 회로의 비선형 연결성으로 인해 훨씬 어려운 최적 초기 레이아웃을 식별해야 합니다. 이 토폴로지는 보다 복잡한 조건에서 레이아웃과 라우팅 성능에 대한 구성 파라미터의 영향을 보여주기 때문에 SABRE를 평가하기 위한 더 나은 테스트 케이스로 사용됩니다.

ghz_star_topology.png

주목할 사항:

  • HighLevelSynthesis 도구는 위 이미지에 표시된 것처럼 SWAP gate를 도입하지 않고 별형 토폴로지 GHZ 회로에 대한 최적 O(N)O(N) 깊이 해결책을 생성할 수 있습니다.
  • 대안적으로, StarPrerouting 패스는 SABRE의 라우팅 결정을 안내함으로써 깊이를 더 줄일 수 있지만 여전히 일부 SWAP gate를 도입할 수 있습니다. 그러나 StarPrerouting은 런타임을 증가시키고 초기 트랜스파일 프로세스에 통합이 필요합니다.

이 튜토리얼의 목적상, 런타임과 회로 깊이에 대한 SABRE 구성의 직접적인 영향을 고립시켜 강조하기 위해 HighLevelSynthesis와 StarPrerouting을 모두 제외합니다. 각 qubit 쌍에 대해 기댓값 Z0Zi\langle Z_0 Z_i \rangle를 측정함으로써 다음을 분석합니다:

  • SABRE가 SWAP gate와 회로 깊이를 얼마나 잘 줄이는지.
  • 이러한 최적화가 실행된 회로의 충실도에 미치는 영향 (Z0Zi=1\langle Z_0 Z_i \rangle = 1에서의 편차는 얽힘 손실을 나타냄).!
# set seed for reproducibility
seed = 42
num_qubits = 110

# Create GHZ circuit
qc = QuantumCircuit(num_qubits)
qc.h(0)
for i in range(1, num_qubits):
qc.cx(0, i)

qc.measure_all()

다음으로, 시스템의 동작을 평가하기 위한 관심 연산자를 매핑합니다. 특히, ZZ 연산자를 사용하여 qubit이 서로 멀어질수록 얽힘이 어떻게 저하되는지 조사합니다. 이 분석은 매우 중요한데, 멀리 있는 qubit에 대한 기댓값 Z0Zi\langle Z_0 Z_i \rangle의 부정확성이 회로 실행에서 노이즈와 오류의 영향을 드러낼 수 있기 때문입니다. 이러한 편차를 연구함으로써 다양한 SABRE 구성에서 회로가 얽힘을 얼마나 잘 보존하는지, 그리고 SABRE가 하드웨어 제약의 영향을 얼마나 효과적으로 최소화하는지에 대한 통찰을 얻을 수 있습니다.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + "I" * i + "Z" + "I" * (num_qubits - 2 - i)
for i in range(num_qubits - 1)
]
print(operator_strings)
print(len(operator_strings))

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZI', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZ']
109

Step 2: 양자 하드웨어 실행을 위한 문제 최적화

이 단계에서는 127개의 qubit를 가진 특정 양자 하드웨어 장치에서의 실행을 위해 회로 레이아웃을 최적화하는 데 집중합니다. 이것이 튜토리얼의 주요 초점으로, SABRE 최적화와 트랜스파일을 수행하여 최상의 회로 성능을 달성합니다. SabreLayout 패스를 사용하여 라우팅 중 SWAP gate의 필요성을 최소화하는 초기 qubit 매핑을 결정합니다. coupling_map을 대상 Backend에 전달함으로써 SabreLayout은 장치의 연결성 제약에 레이아웃을 적응시킵니다.

트랜스파일 프로세스에는 optimization_level=3으로 generate_preset_pass_manager를 사용하고 SabreLayout 패스를 다양한 구성으로 커스터마이징합니다. 목표는 가장 낮은 크기 및/또는 깊이를 가진 트랜스파일된 회로를 생성하는 설정을 찾아 SABRE 최적화의 영향을 입증하는 것입니다.

회로 크기와 깊이가 중요한 이유

  • 낮은 크기 (gate 수): 연산 수를 줄여 오류가 누적될 기회를 최소화합니다.
  • 낮은 깊이: 전체 실행 시간을 단축하여 디코히어런스를 방지하고 양자 상태 충실도를 유지하는 데 중요합니다.

이러한 지표를 최적화함으로써 노이즈가 있는 양자 하드웨어에서 회로의 신뢰성과 실행 정확도를 향상시킵니다. Backend를 선택합니다.

service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=127
# )
backend = service.backend("ibm_boston")
print(f"Using backend: {backend.name}")
Using backend: ibm_boston

다양한 구성이 회로 최적화에 미치는 영향을 평가하기 위해 SabreLayout 패스에 대한 고유한 설정을 가진 세 가지 패스 관리자를 만들 것입니다. 이러한 구성은 회로 품질과 트랜스파일 시간 사이의 절충점을 분석하는 데 도움이 됩니다.

핵심 파라미터

  • max_iterations: 레이아웃을 정제하고 라우팅 비용을 줄이기 위한 전방-후방 라우팅 반복 횟수.
  • layout_trials: SWAP gate를 최소화하는 것을 선택하여 테스트된 임의 초기 레이아웃의 수.
  • swap_trials: 각 레이아웃에 대한 라우팅 시도 횟수로, 더 나은 라우팅을 위해 gate 배치를 정제합니다.

layout_trialsswap_trials를 늘리면 더 철저한 최적화를 수행할 수 있지만 트랜스파일 시간이 증가합니다.

이 튜토리얼의 구성

  1. pm_1: optimization_level=3의 기본 설정.

    • max_iterations=4
    • layout_trials=20
    • swap_trials=20
  2. pm_2: 더 나은 탐색을 위해 시도 횟수 증가.

    • max_iterations=4
    • layout_trials=200
    • swap_trials=200
  3. pm_3: 추가 정제를 위해 반복 횟수를 늘려 pm_2를 확장.

    • max_iterations=8
    • layout_trials=200
    • swap_trials=200

이러한 구성의 결과를 비교함으로써 회로 품질 (예: 크기와 깊이)과 계산 비용 사이에서 최상의 균형을 달성하는 것이 어느 것인지 결정하려 합니다.

# Get the coupling map from the backend
cmap = CouplingMap(backend().configuration().coupling_map)

# Create the SabreLayout passes for the custom configurations
sl_2 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=4,
layout_trials=200,
swap_trials=200,
)
sl_3 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=8,
layout_trials=200,
swap_trials=200,
)

# Create the pass managers, need to first create then configure the SabreLayout passes
pm_1 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_2 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_3 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)

이제 커스텀 패스 관리자에서 SabreLayout 패스를 구성할 수 있습니다. 이를 위해 optimization_level=3에서 기본 generate_preset_pass_manager의 경우 SabreLayout 패스가 인덱스 2에 있음을 알고 있습니다. SabreLayoutSetLayoutVF2Layout 패스 다음에 발생하기 때문입니다. 이 패스에 접근하여 파라미터를 수정할 수 있습니다.

pm_2.layout.replace(index=2, passes=sl_2)
pm_3.layout.replace(index=2, passes=sl_3)

각 패스 관리자가 구성되면 각각에 대한 트랜스파일 프로세스를 실행합니다. 결과를 비교하기 위해 트랜스파일 시간, 회로 깊이 (2-qubit gate 깊이로 측정), 트랜스파일된 회로의 총 gate 수 등 핵심 지표를 추적합니다.

# Transpile the circuit with each pass manager and measure the time
t0 = time.time()
tqc_1 = pm_1.run(qc)
t1 = time.time() - t0
t0 = time.time()
tqc_2 = pm_2.run(qc)
t2 = time.time() - t0
t0 = time.time()
tqc_3 = pm_3.run(qc)
t3 = time.time() - t0

# Obtain the depths and the total number of gates (circuit size)
depth_1 = tqc_1.depth(lambda x: x.operation.num_qubits == 2)
depth_2 = tqc_2.depth(lambda x: x.operation.num_qubits == 2)
depth_3 = tqc_3.depth(lambda x: x.operation.num_qubits == 2)
size_1 = tqc_1.size()
size_2 = tqc_2.size()
size_3 = tqc_3.size()

# Transform the observables to match the backend's ISA
operators_list_1 = [op.apply_layout(tqc_1.layout) for op in operators]
operators_list_2 = [op.apply_layout(tqc_2.layout) for op in operators]
operators_list_3 = [op.apply_layout(tqc_3.layout) for op in operators]

# Compute improvements compared to pass manager 1 (default)
depth_improvement_2 = ((depth_1 - depth_2) / depth_1) * 100
depth_improvement_3 = ((depth_1 - depth_3) / depth_1) * 100
size_improvement_2 = ((size_1 - size_2) / size_1) * 100
size_improvement_3 = ((size_1 - size_3) / size_1) * 100
time_increase_2 = ((t2 - t1) / t1) * 100
time_increase_3 = ((t3 - t1) / t1) * 100

print(
f"Pass manager 1 (4,20,20) : Depth {depth_1}, Size {size_1}, Time {t1:.4f} s"
)
print(
f"Pass manager 2 (4,200,200): Depth {depth_2}, Size {size_2}, Time {t2:.4f} s"
)
print(f" - Depth improvement: {depth_improvement_2:.2f}%")
print(f" - Size improvement: {size_improvement_2:.2f}%")
print(f" - Time increase: {time_increase_2:.2f}%")
print(
f"Pass manager 3 (8,200,200): Depth {depth_3}, Size {size_3}, Time {t3:.4f} s"
)
print(f" - Depth improvement: {depth_improvement_3:.2f}%")
print(f" - Size improvement: {size_improvement_3:.2f}%")
print(f" - Time increase: {time_increase_3:.2f}%")
Pass manager 1 (4,20,20)  : Depth 439, Size 2346, Time 0.5775 s
Pass manager 2 (4,200,200): Depth 395, Size 2070, Time 3.9927 s
- Depth improvement: 10.02%
- Size improvement: 11.76%
- Time increase: 591.43%
Pass manager 3 (8,200,200): Depth 375, Size 1873, Time 2.3079 s
- Depth improvement: 14.58%
- Size improvement: 20.16%
- Time increase: 299.67%

결과는 시도 횟수 (layout_trialsswap_trials)를 늘리면 깊이와 크기를 모두 줄여 회로 품질을 크게 향상시킬 수 있음을 보여줍니다. 그러나 이러한 개선은 더 많은 잠재적 레이아웃과 라우팅 경로를 탐색하는 데 필요한 추가 계산으로 인해 증가된 런타임 비용을 수반하는 경우가 많습니다.

max_iterations를 늘리면 더 많은 전방-후방 라우팅 사이클을 통해 레이아웃을 정제하여 최적화를 더욱 향상시킬 수 있습니다. 이 경우, max_iterations를 늘린 것이 회로 깊이와 크기에서 가장 큰 감소를 가져왔으며, 후속 최적화 단계를 간소화함으로써 pm_2와 비교하여 런타임을 줄이기도 했습니다. 그러나 max_iterations를 늘리는 것의 효과는 회로에 따라 크게 달라질 수 있습니다. 더 많은 반복이 더 나은 레이아웃과 라우팅 선택을 가져올 수 있지만, 보장은 없으며 회로의 구조와 연결성 제약의 복잡성에 크게 의존합니다.

# Plot the results of the metrics
times = [t1, t2, t3]
depths = [depth_1, depth_2, depth_3]
sizes = [size_1, size_2, size_3]
pm_names = [
"pm_1 (4 iter, 20 trials)",
"pm_2 (4 iter, 200 trials)",
"pm_3 (8 iter, 200 trials)",
]
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(pm_names)))

# Create a figure with three subplots
fig, axs = plt.subplots(3, 1, figsize=(6, 9), sharex=True)
axs[0].bar(pm_names, times, color=colors)
axs[0].set_ylabel("Time (s)", fontsize=12)
axs[0].set_title("Transpilation Time", fontsize=14)
axs[0].grid(axis="y", linestyle="--", alpha=0.7)
axs[1].bar(pm_names, depths, color=colors)
axs[1].set_ylabel("Depth", fontsize=12)
axs[1].set_title("Circuit Depth", fontsize=14)
axs[1].grid(axis="y", linestyle="--", alpha=0.7)
axs[2].bar(pm_names, sizes, color=colors)
axs[2].set_ylabel("Size", fontsize=12)
axs[2].set_title("Circuit Size", fontsize=14)
axs[2].set_xticks(range(len(pm_names)))
axs[2].set_xticklabels(pm_names, fontsize=10, rotation=15)
axs[2].grid(axis="y", linestyle="--", alpha=0.7)

# Add some spacing between subplots
plt.tight_layout()
plt.show()

Output of the previous code cell

3단계: Qiskit 프리미티브를 사용한 실행

이 단계에서는 Estimator 프리미티브를 사용하여 ZZ 연산자에 대한 기댓값 Z0Zi\langle Z_0 Z_i \rangle를 계산하고, 트랜스파일된 Circuit의 얽힘 품질과 실행 품질을 평가합니다. 일반적인 사용자 워크플로우에 맞추어, 작업을 실행을 위해 제출하고 **동적 디커플링(dynamical decoupling)**을 사용하여 오류 억제를 적용합니다. 이 기법은 게이트 시퀀스를 삽입하여 Qubit 상태를 보존함으로써 디코히어런스를 완화합니다. 또한 노이즈에 대응하기 위해 복원력 수준(resilience level)을 지정하며, 수준이 높을수록 처리 시간이 늘어나는 대신 더 정확한 결과를 얻을 수 있습니다. 이 방식은 현실적인 실행 조건에서 각 패스 매니저 구성의 성능을 평가합니다.

options = EstimatorOptions()
options.resilience_level = 2
options.dynamical_decoupling.enable = True
options.dynamical_decoupling.sequence_type = "XY4"

# Create an Estimator object
estimator = Estimator(backend, options=options)
# Submit the circuit to Estimator
job_1 = estimator.run([(tqc_1, operators_list_1)])
job_1_id = job_1.job_id()
print(job_1_id)

job_2 = estimator.run([(tqc_2, operators_list_2)])
job_2_id = job_2.job_id()
print(job_2_id)

job_3 = estimator.run([(tqc_3, operators_list_3)])
job_3_id = job_3.job_id()
print(job_3_id)
d5k0qs7853es738dab6g
d5k0qsf853es738dab70
d5k0qsf853es738dab7g
# Run the jobs
result_1 = job_1.result()[0]
print("Job 1 done")
result_2 = job_2.result()[0]
print("Job 2 done")
result_3 = job_3.result()[0]
print("Job 3 done")
Job 1 done
Job 2 done
Job 3 done

4단계: 후처리 및 원하는 고전적 형식으로 결과 반환

작업이 완료되면 각 Qubit에 대한 기댓값 Z0Zi\langle Z_0 Z_i \rangle를 플롯하여 결과를 분석합니다. 이상적인 시뮬레이션에서는 모든 Z0Zi\langle Z_0 Z_i \rangle 값이 1에 가까워야 하며, 이는 Qubit 전체에 걸쳐 완벽한 얽힘이 이루어졌음을 나타냅니다. 그러나 노이즈와 하드웨어 제약으로 인해, i가 증가함에 따라 기댓값이 일반적으로 감소하며, 이는 거리에 따라 얽힘이 어떻게 저하되는지를 보여 줍니다.

이 단계에서는 각 패스 매니저 구성의 결과를 이상적인 시뮬레이션과 비교합니다. 각 구성에서 Z0Zi\langle Z_0 Z_i \rangle가 1로부터 얼마나 벗어나는지를 살펴봄으로써, 각 패스 매니저가 얽힘을 얼마나 잘 보존하고 노이즈 영향을 얼마나 잘 완화하는지를 정량화할 수 있습니다. 이 분석은 SABRE 최적화가 실행 충실도에 미치는 영향을 직접적으로 평가하며, 최적화 품질과 실행 성능 사이의 균형을 가장 잘 맞추는 구성이 어떤 것인지 부각시킵니다.

결과는 패스 매니저 간의 차이를 강조하기 위해 시각화되며, 레이아웃 및 라우팅의 개선이 노이즈가 있는 양자 하드웨어에서의 최종 Circuit 실행에 어떤 영향을 미치는지를 보여 줍니다.

data = list(range(1, len(operators) + 1))  # Distance between the Z operators

values_1 = list(result_1.data.evs)
values_2 = list(result_2.data.evs)
values_3 = list(result_3.data.evs)

plt.plot(
data,
values_1,
marker="o",
label="pm_1 (iters=4, swap_trials=20, layout_trials=20)",
)
plt.plot(
data,
values_2,
marker="s",
label="pm_2 (iters=4, swap_trials=200, layout_trials=200)",
)
plt.plot(
data,
values_3,
marker="^",
label="pm_3 (iters=8, swap_trials=200, layout_trials=200)",
)
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Output of the previous code cell

결과 분석

이 플롯은 최적화 수준이 점점 높아지는 세 가지 패스 매니저 구성에 대해 Qubit 간 거리의 함수로 나타낸 기댓값 Z0Zi/Z0Z0\langle Z_0 Z_i \rangle / \langle Z_0 Z_0 \rangle를 보여 줍니다. 이상적인 경우에는 이 값들이 1에 가깝게 유지되어 Circuit 전반에 걸쳐 강한 상관관계를 나타내야 합니다. 거리가 증가함에 따라 노이즈와 누적 오류로 인해 상관관계가 감소하며, 이는 각 트랜스파일 전략이 상태의 기본 구조를 얼마나 잘 보존하는지를 드러냅니다.

세 구성 중 pm_1은 분명히 가장 낮은 성능을 보입니다. 거리가 증가함에 따라 상관관계 값이 급격히 감소하며, 다른 두 구성보다 훨씬 이른 시점에 0에 근접합니다. 이 동작은 더 큰 Circuit 깊이와 게이트 수에 기인하며, 누적된 노이즈가 장거리 상관관계를 빠르게 저하시킵니다.

pm_2pm_3 모두 거의 모든 거리에서 pm_1에 비해 크게 향상된 성능을 보입니다. 평균적으로 pm_3가 전반적으로 가장 강한 성능을 나타내며, 더 긴 거리에서도 높은 상관관계 값을 유지하고 더 완만한 감소를 보입니다. 이는 더 적극적인 최적화로 더 얕은 Circuit을 생성하여 일반적으로 노이즈 누적에 더 강인하다는 점과 일치합니다.

그렇지만 pm_2는 Circuit 깊이와 게이트 수가 약간 더 많음에도 불구하고 짧은 거리에서 pm_3보다 눈에 띄게 더 좋은 정확도를 보입니다. 이는 Circuit 깊이만으로는 성능을 완전히 결정할 수 없음을 시사합니다. 트랜스파일에 의해 생성된 특정 구조, 즉 얽힘 게이트의 배열 방식과 오류가 Circuit을 통해 전파되는 방식도 중요한 역할을 합니다. 경우에 따라 pm_2가 적용하는 변환이 더 긴 거리로 확장되지 않더라도 국소 상관관계를 더 잘 보존하는 것처럼 보입니다.

이 결과들을 종합하면, Circuit 압축성과 Circuit 구조 사이의 트레이드오프가 부각됩니다. 최적화 수준을 높이면 일반적으로 장거리 안정성이 향상되지만, 특정 관측량에 대한 최상의 성능은 Circuit 깊이를 줄이는 것과 하드웨어의 노이즈 특성에 잘 맞는 구조를 생성하는 것 모두에 달려 있습니다.

파트 II. SABRE의 휴리스틱 구성 및 Serverless 사용

트라이얼 수 조정 외에도, SABRE는 트랜스파일 중 사용되는 라우팅 휴리스틱의 사용자 지정을 지원합니다. 기본적으로 SabreLayout은 스왑될 가능성에 따라 Qubit에 동적으로 가중치를 부여하는 decay 휴리스틱을 사용합니다. 다른 휴리스틱(예: lookahead 휴리스틱)을 사용하려면 사용자 정의 SabreSwap 패스를 만들고, FullAncillaAllocation, EnlargeWithAncilla, ApplyLayout이 포함된 PassManager를 실행하여 SabreLayout에 연결할 수 있습니다. SabreSwapSabreLayout의 파라미터로 사용할 경우, 기본적으로 레이아웃 트라이얼이 하나만 수행됩니다. 여러 레이아웃 트라이얼을 효율적으로 실행하려면 병렬화를 위해 serverless 런타임을 활용합니다. Serverless에 대한 자세한 내용은 Serverless 문서를 참조하세요.

라우팅 휴리스틱 변경 방법

  1. 원하는 휴리스틱을 사용하는 사용자 정의 SabreSwap 패스를 만듭니다.
  2. 이 사용자 정의 SabreSwapSabreLayout 패스의 라우팅 방법으로 사용합니다.

루프를 사용하여 여러 레이아웃 트라이얼을 실행하는 것도 가능하지만, 대규모의 엄밀한 실험에는 serverless 런타임이 더 나은 선택입니다. Serverless는 레이아웃 트라이얼의 병렬 실행을 지원하여 더 큰 Circuit과 대규모 실험 스윕의 최적화를 크게 가속합니다. 따라서 리소스 집약적인 작업을 처리하거나 시간 효율성이 중요한 경우에 특히 유용합니다.

이 섹션은 최적화의 2단계, 즉 Circuit 크기와 깊이를 최소화하여 가능한 최상의 트랜스파일된 Circuit을 달성하는 데만 집중합니다. 이전 결과를 바탕으로, 이제 휴리스틱 사용자 지정과 serverless 병렬화가 최적화 성능을 어떻게 더욱 향상시킬 수 있는지, 그리고 이를 대규모 양자 Circuit 트랜스파일에 적합하게 만드는지를 살펴봅니다.

Serverless 런타임 없이 실행한 결과 (레이아웃 트라이얼 1회):

swap_trials = 1000

# Default PassManager with `SabreLayout` and `SabreSwap`, using heuristic "decay"
sr_default = SabreSwap(
coupling_map=cmap, heuristic="decay", trials=swap_trials, seed=seed
)
sl_default = SabreLayout(
coupling_map=cmap, routing_pass=sr_default, seed=seed
)
pm_default = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_default.layout.replace(index=2, passes=sl_default)
pm_default.routing.replace(index=1, passes=sr_default)

t0 = time.time()
tqc_default = pm_default.run(qc)
t_default = time.time() - t0
size_default = tqc_default.size()
depth_default = tqc_default.depth(lambda x: x.operation.num_qubits == 2)

# Custom PassManager with `SabreLayout` and `SabreSwap`, using heuristic "lookahead"
sr_custom = SabreSwap(
coupling_map=cmap, heuristic="lookahead", trials=swap_trials, seed=seed
)
sl_custom = SabreLayout(coupling_map=cmap, routing_pass=sr_custom, seed=seed)
pm_custom = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_custom.layout.replace(index=2, passes=sl_custom)
pm_custom.routing.replace(index=1, passes=sr_custom)

t0 = time.time()
tqc_custom = pm_custom.run(qc)
t_custom = time.time() - t0
size_custom = tqc_custom.size()
depth_custom = tqc_custom.depth(lambda x: x.operation.num_qubits == 2)

print(
f"Default (heuristic='decay') : Depth {depth_default}, Size {size_default}, Time {t_default}"
)
print(
f"Custom (heuristic='lookahead'): Depth {depth_custom}, Size {size_custom}, Time {t_custom}"
)
Default (heuristic='decay')    : Depth 443, Size 3115, Time 1.034372091293335
Custom (heuristic='lookahead'): Depth 432, Size 2856, Time 0.6669301986694336

여기서 lookahead 휴리스틱이 Circuit 깊이, 크기, 시간 측면에서 decay 휴리스틱보다 더 나은 성능을 보임을 알 수 있습니다. 이 개선은 트라이얼 및 반복 횟수를 늘리는 것 외에도 특정 Circuit과 하드웨어 제약에 맞게 SABRE를 어떻게 개선할 수 있는지를 보여 줍니다. 이 결과는 단일 레이아웃 트라이얼을 기반으로 합니다. 더 정확한 결과를 얻으려면 여러 레이아웃 트라이얼을 실행하는 것을 권장하며, 이는 serverless 런타임을 사용하여 효율적으로 수행할 수 있습니다.

Serverless 런타임을 사용한 결과 (여러 레이아웃 트라이얼)

Qiskit Serverless를 사용하려면 워크로드의 .py 파일을 전용 디렉터리에 설정해야 합니다. 다음 코드 셀은 source_files 디렉터리에 있는 transpile_remote.py라는 Python 파일입니다. 이 파일에는 트랜스파일 프로세스를 실행하는 함수가 포함되어 있습니다.

# This cell is hidden from users, it makes sure the `source_files` directory exists
from pathlib import Path

Path("source_files").mkdir(exist_ok=True)
%%writefile source_files/transpile_remote.py
import time
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import SabreLayout, SabreSwap
from qiskit.transpiler import CouplingMap
from qiskit_serverless import get_arguments, save_result, distribute_task, get
from qiskit_ibm_runtime import QiskitRuntimeService

@distribute_task(target={
"cpu": 1,
"mem": 1024 * 1024 * 1024
})
def transpile_remote(qc, optimization_level, backend_name, seed, swap_trials, heuristic):
"""Transpiles an abstract circuit into an ISA circuit for a given backend."""

service = QiskitRuntimeService()
backend = service.backend(backend_name)

pm = generate_preset_pass_manager(
optimization_level=optimization_level,
backend=backend,
seed_transpiler=seed
)

# Changing the `SabreLayout` and `SabreSwap` passes to use the custom configurations
cmap = CouplingMap(backend().configuration().coupling_map)
sr = SabreSwap(coupling_map=cmap, heuristic=heuristic, trials=swap_trials, seed=seed)
sl = SabreLayout(coupling_map=cmap, routing_pass=sr, seed=seed)
pm.layout.replace(index=2, passes=sl)
pm.routing.replace(index=1, passes=sr)

# Measure the transpile time
start_time = time.time() # Start timer
tqc = pm.run(qc) # Transpile the circuit
end_time = time.time() # End timer

transpile_time = end_time - start_time # Calculate the elapsed time
return tqc, transpile_time # Return both the transpiled circuit and the transpile time

# Get program arguments
arguments = get_arguments()
circuit = arguments.get("circuit")
backend_name = arguments.get("backend_name")
optimization_level = arguments.get("optimization_level")
seed_list = arguments.get("seed_list")
swap_trials = arguments.get("swap_trials")
heuristic = arguments.get("heuristic")

# Transpile the circuits
transpile_worker_references = [
transpile_remote(circuit, optimization_level, backend_name, seed, swap_trials, heuristic)
for seed in seed_list
]

results_with_times = get(transpile_worker_references)

# Separate the transpiled circuits and their transpile times
transpiled_circuits = [result[0] for result in results_with_times]
transpile_times = [result[1] for result in results_with_times]

# Save both results and transpile times
save_result({"transpiled_circuits": transpiled_circuits, "transpile_times": transpile_times})
Overwriting source_files/transpile_remote.py

다음 셀은 transpile_remote.py 파일을 transpile_remote_serverless라는 이름의 Qiskit Serverless 프로그램으로 업로드해요.

serverless = QiskitServerless()

transpile_remote_demo = QiskitFunction(
title="transpile_remote_serverless",
entrypoint="transpile_remote.py",
working_dir="./source_files/",
)
serverless.upload(transpile_remote_demo)
transpile_remote_serverless = serverless.load("transpile_remote_serverless")

서로 다른 20번의 레이아웃 시도를 나타내는 20개의 서로 다른 시드를 생성해요.

num_seeds = 20  # represents the different layout trials
seed_list = [seed + i for i in range(num_seeds)]

업로드한 프로그램을 실행하고 lookahead 휴리스틱을 위한 입력값을 전달해요.

job_lookahead = transpile_remote_serverless.run(
circuit=qc,
backend_name=backend.name,
optimization_level=3,
seed_list=seed_list,
swap_trials=swap_trials,
heuristic="lookahead",
)
job_lookahead.job_id
'15767dfc-e71d-4720-94d6-9212f72334c2'
job_lookahead.status()
'QUEUED'

서버리스 런타임에서 로그와 결과를 받아와요.

logs_lookahead = job_lookahead.logs()
print(logs_lookahead)
No logs yet.

프로그램이 DONE 상태가 되면 job.results()save_result()에 저장된 결과를 가져올 수 있어요.

# Run the job with lookahead heuristic
start_time = time.time()
results_lookahead = job_lookahead.result()
end_time = time.time()

job_lookahead_time = end_time - start_time

이제 decay 휴리스틱에 대해서도 동일하게 진행해요.

job_decay = transpile_remote_serverless.run(
circuit=qc,
backend_name=backend.name,
optimization_level=3,
seed_list=seed_list,
swap_trials=swap_trials,
heuristic="decay",
)
job_decay.job_id
'00418c76-d6ec-4bd8-9f70-05d0fa14d4eb'
logs_decay = job_decay.logs()
print(logs_decay)
No logs yet.
# Run the job with the decay heuristic
start_time = time.time()
results_decay = job_decay.result()
end_time = time.time()

job_decay_time = end_time - start_time
# Extract transpilation times
transpile_times_decay = results_decay["transpile_times"]
transpile_times_lookahead = results_lookahead["transpile_times"]

# Calculate total transpilation time for serial execution
total_transpile_time_decay = sum(transpile_times_decay)
total_transpile_time_lookahead = sum(transpile_times_lookahead)

# Print total transpilation time
print("=== Total Transpilation Time (Serial Execution) ===")
print(f"Decay Heuristic : {total_transpile_time_decay:.2f} seconds")
print(f"Lookahead Heuristic: {total_transpile_time_lookahead:.2f} seconds")

# Print serverless job time (parallel execution)
print("\n=== Serverless Job Time (Parallel Execution) ===")
print(f"Decay Heuristic : {job_decay_time:.2f} seconds")
print(f"Lookahead Heuristic: {job_lookahead_time:.2f} seconds")

# Calculate and print average runtime per transpilation
avg_transpile_time_decay = total_transpile_time_decay / num_seeds
avg_transpile_time_lookahead = total_transpile_time_lookahead / num_seeds
avg_job_time_decay = job_decay_time / num_seeds
avg_job_time_lookahead = job_lookahead_time / num_seeds

print("\n=== Average Time Per Transpilation ===")
print(f"Decay Heuristic (Serial) : {avg_transpile_time_decay:.2f} seconds")
print(f"Decay Heuristic (Serverless): {avg_job_time_decay:.2f} seconds")
print(
f"Lookahead Heuristic (Serial) : {avg_transpile_time_lookahead:.2f} seconds"
)
print(
f"Lookahead Heuristic (Serverless): {avg_job_time_lookahead:.2f} seconds"
)

# Calculate and print serverless improvement percentage
decay_improvement_percentage = (
(total_transpile_time_decay - job_decay_time) / total_transpile_time_decay
) * 100
lookahead_improvement_percentage = (
(total_transpile_time_lookahead - job_lookahead_time)
/ total_transpile_time_lookahead
) * 100

print("\n=== Serverless Improvement ===")
print(f"Decay Heuristic : {decay_improvement_percentage:.2f}%")
print(f"Lookahead Heuristic: {lookahead_improvement_percentage:.2f}%")
=== Total Transpilation Time (Serial Execution) ===
Decay Heuristic : 112.37 seconds
Lookahead Heuristic: 85.37 seconds

=== Serverless Job Time (Parallel Execution) ===
Decay Heuristic : 5.72 seconds
Lookahead Heuristic: 5.85 seconds

=== Average Time Per Transpilation ===
Decay Heuristic (Serial) : 5.62 seconds
Decay Heuristic (Serverless): 0.29 seconds
Lookahead Heuristic (Serial) : 4.27 seconds
Lookahead Heuristic (Serverless): 0.29 seconds

=== Serverless Improvement ===
Decay Heuristic : 94.91%
Lookahead Heuristic: 93.14%

이 결과들은 양자 Circuit 트랜스파일에 서버리스 실행을 사용할 때 얻을 수 있는 효율성 향상이 상당하다는 점을 보여줘요. 직렬 실행과 비교하면, 서버리스 실행은 독립적인 트랜스파일 시도를 병렬화함으로써 decaylookahead 휴리스틱 모두에서 전체 런타임을 극적으로 줄여줘요. 직렬 실행은 여러 레이아웃 시도를 탐색하는 데 드는 누적 비용 전체를 반영하는 반면, 서버리스 작업 시간은 병렬 실행이 이 비용을 훨씬 짧은 실제 경과 시간으로 압축한다는 점을 잘 보여줘요. 그 결과, 트랜스파일 한 번당 실효 시간은 사용된 휴리스틱과 거의 무관하게 직렬 환경에서 필요한 시간의 일부분으로 줄어들어요. 이 기능은 SABRE의 잠재력을 최대한 끌어올리는 데 특히 중요해요. SABRE의 가장 강력한 성능 향상은 레이아웃과 라우팅 시도 횟수를 늘리는 데서 오는 경우가 많은데, 이를 순차적으로 실행하면 비용이 너무 커질 수 있어요. 서버리스 실행은 이 병목을 제거해서, 대규모 매개변수 스윕과 휴리스틱 설정에 대한 더 깊은 탐색을 최소한의 오버헤드로 가능하게 해줘요.

전반적으로 이러한 결과는 SABRE 최적화를 확장하는 데 서버리스 실행이 핵심이며, 직렬 실행에 비해 적극적인 실험과 개선을 실용적으로 만들어준다는 점을 보여줘요. 서버리스 런타임에서 결과를 가져와 lookahead와 decay 휴리스틱의 결과를 비교해요. 크기와 깊이를 비교할 거예요.

# Extract sizes and depths
sizes_lookahead = [
circuit.size() for circuit in results_lookahead["transpiled_circuits"]
]
depths_lookahead = [
circuit.depth(lambda x: x.operation.num_qubits == 2)
for circuit in results_lookahead["transpiled_circuits"]
]
sizes_decay = [
circuit.size() for circuit in results_decay["transpiled_circuits"]
]
depths_decay = [
circuit.depth(lambda x: x.operation.num_qubits == 2)
for circuit in results_decay["transpiled_circuits"]
]

def create_scatterplot(x, y1, y2, xlabel, ylabel, title, labels, colors):
plt.figure(figsize=(8, 5))
plt.scatter(
x, y1, label=labels[0], color=colors[0], alpha=0.8, edgecolor="k"
)
plt.scatter(
x, y2, label=labels[1], color=colors[1], alpha=0.8, edgecolor="k"
)
plt.xlabel(xlabel, fontsize=12)
plt.ylabel(ylabel, fontsize=12)
plt.title(title, fontsize=14)
plt.legend(fontsize=10)
plt.grid(axis="y", linestyle="--", alpha=0.7)
plt.tight_layout()
plt.show()

create_scatterplot(
seed_list,
sizes_lookahead,
sizes_decay,
"Seed",
"Size",
"Circuit Size",
["lookahead", "Decay"],
["blue", "red"],
)
create_scatterplot(
seed_list,
depths_lookahead,
depths_decay,
"Seed",
"Depth",
"Circuit Depth",
["lookahead", "Decay"],
["blue", "red"],
)

Output of the previous code cell

Output of the previous code cell

위 산점도의 각 점은 하나의 레이아웃 시도를 나타내며, x축은 Circuit 깊이를, y축은 Circuit 크기를 의미해요. 결과를 보면, lookahead 휴리스틱이 Circuit 깊이와 크기를 줄이는 면에서 대체로 decay 휴리스틱보다 더 나은 성능을 보여줘요. 실제 응용에서는 깊이를 우선시하든 크기를 우선시하든, 선택한 휴리스틱에서 최적의 레이아웃 시도를 찾는 것이 목표예요. 원하는 지표에서 가장 낮은 값을 가진 시도를 선택하면 이를 달성할 수 있어요. 중요한 점은, 레이아웃 시도 횟수를 늘리면 크기나 깊이 면에서 더 나은 결과를 얻을 가능성이 높아지지만, 그만큼 계산 비용도 커진다는 거예요.

min_depth_lookahead = min(depths_lookahead)
min_depth_decay = min(depths_decay)
min_size_lookahead = min(sizes_lookahead)
min_size_decay = min(sizes_decay)
print(
"Lookahead: Min Depth",
min_depth_lookahead,
"Min Size",
min_size_lookahead,
)
print("Decay: Min Depth", min_depth_decay, "Min Size", min_size_decay)
Lookahead: Min Depth 399 Min Size 2452
Decay: Min Depth 415 Min Size 2611

단일 레이아웃 시도를 사용한 초기 비교에서, lookahead 휴리스틱은 Circuit 깊이와 크기 모두에서 약간 더 나은 성능을 보였습니다. QiskitServerless를 활용해 여러 레이아웃 시도로 이 연구를 확장함으로써, 훨씬 더 넓은 범위의 SABRE 초기화 공간을 탐색할 수 있었고, 두 휴리스틱 간의 보다 대표적인 비교가 가능해졌습니다.

산점도와 관측된 최적 결과를 보면, SABRE가 사용하는 무작위 시드에 따라 성능이 크게 달라짐을 알 수 있습니다. 두 휴리스틱 모두 시드에 따라 Circuit 깊이와 크기가 넓게 분포하며, 이는 단일 실행만으로는 최적에 가까운 결과를 얻기에 충분하지 않은 경우가 많음을 의미합니다. 이러한 변동성은 깊이 및/또는 Gate 수를 최소화하려 할 때 서로 다른 시드로 많은 시도를 실행하는 것이 중요함을 강조합니다. 전체 시도 세트에 걸쳐, lookaheaddecay 휴리스틱 모두 경쟁력 있는 결과를 낼 수 있었습니다. 일부 경우에는 decay 휴리스틱이 특정 시드에서 lookahead와 동등하거나 심지어 더 나은 결과를 보이기도 했습니다. 그러나 이 특정 Circuit에서는 전체적으로 lookahead 휴리스틱을 사용했을 때 가장 좋은 결과가 도출되었으며, 그 차이는 크지 않습니다. 이는 lookahead가 여기서 가장 강력한 결과를 제공했지만, decay에 대한 우위가 절대적이지 않음을 시사합니다.

전반적으로, 이러한 결과는 두 가지 핵심 사항을 강조합니다. 첫째, 사용하는 휴리스틱에 관계없이 SABRE에서 최상의 성능을 끌어내기 위해서는 많은 시드를 활용하는 것이 필수적입니다. 둘째, 휴리스틱 선택도 중요하지만, Circuit 구조가 지배적인 역할을 하며 lookaheaddecay의 상대적 성능은 다른 Circuit에서는 달라질 수 있습니다. 따라서 강력하고 효과적인 양자 Circuit 트랜스파일을 위해서는 대규모 다중 시드 실험이 필수적입니다.

# This cell is hidden from users, it cleans up the `source_files` directory
from pathlib import Path

Path("source_files/transpile_remote.py").unlink()
Path("source_files").rmdir()

결론

이 튜토리얼에서는 Qiskit에서 SABRE를 사용하여 대형 Circuit을 최적화하는 방법을 살펴보았습니다. Circuit 품질과 트랜스파일 런타임 간의 균형을 맞추기 위해 SabreLayout 패스를 다양한 매개변수로 구성하는 방법을 시연했습니다. 또한 SABRE에서 라우팅 휴리스틱을 커스터마이즈하고, SabreSwap이 관여할 때 레이아웃 시도를 효율적으로 병렬화하기 위해 QiskitServerless 런타임을 활용하는 방법도 소개했습니다. 이러한 매개변수와 휴리스틱을 조정함으로써, 대형 Circuit의 레이아웃과 라우팅을 최적화하여 양자 하드웨어에서 효율적으로 실행될 수 있도록 할 수 있습니다.

튜토리얼 설문

이 짧은 설문에 참여하여 튜토리얼에 대한 피드백을 제공해 주세요. 여러분의 의견은 콘텐츠와 사용자 경험을 개선하는 데 도움이 됩니다.

설문 링크

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.