주 콘텐츠로 건너뛰기

Transpiler 최적화 레벨 설정

패키지 버전

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

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

실제 양자 장치는 노이즈와 Gate 오류의 영향을 받으므로, Circuit의 깊이와 Gate 수를 줄이도록 최적화하면 해당 Circuit 실행 결과를 크게 향상시킬 수 있습니다. generate_preset_pass_manager 함수에는 필수 위치 인수인 optimization_level이 있으며, 이 인수는 Transpiler가 Circuit 최적화에 얼마나 많은 노력을 기울이는지 제어합니다. 이 인수는 0, 1, 2, 3 중 하나의 값을 가지는 정수입니다. 최적화 레벨이 높을수록 더 최적화된 Circuit이 생성되지만, 컴파일 시간이 더 오래 걸립니다. 다음 표는 각 설정에서 수행되는 최적화를 설명합니다.

최적화 레벨설명
0

최적화 없음: 주로 하드웨어 특성 분석에 사용됩니다.

  • 기본 변환
  • 레이아웃/라우팅: TrivialLayout 사용. 가상 Qubit과 동일한 물리적 Qubit 번호를 선택하고 SWAP을 삽입하여 작동시킵니다 (SabreSwap 사용).
1

경량 최적화:

  • 레이아웃/라우팅: 먼저 TrivialLayout으로 레이아웃을 시도합니다. 추가 SWAP이 필요한 경우 SabreSwap을 사용하여 최소 SWAP 수의 레이아웃을 찾고, 이후 VF2LayoutPostLayout을 사용하여 그래프에서 최적의 Qubit을 선택하려고 시도합니다.
  • InverseCancellation
  • 1Q Gate 최적화
2

중간 최적화:

  • 레이아웃/라우팅: 최적화 레벨 1 (trivial 제외) + 더 큰 탐색 깊이와 최적화 함수 시도 횟수로 경험적 최적화 수행. TrivialLayout을 사용하지 않으므로 물리적 Qubit 번호와 가상 Qubit 번호를 동일하게 사용하려는 시도가 없습니다.
  • CommutativeCancellation
3

고수준 최적화:

  • 최적화 레벨 2 + 더 많은 노력/시도로 레이아웃/라우팅에 대한 경험적 최적화 추가 수행
  • Cartan의 KAK 분해를 사용하여 2-Qubit 블록 재합성
  • 유니타리성을 깨는 패스:
    • OptimizeSwapBeforeMeasure: SWAP을 피하기 위해 측정 위치를 이동합니다.
    • RemoveDiagonalGatesBeforeMeasure: 측정에 영향을 미치지 않는 측정 전 Gate를 제거합니다.

실제 최적화 레벨 동작

2-Qubit Gate는 일반적으로 오류의 가장 중요한 원인이므로, 결과 Circuit에서 2-Qubit Gate의 수를 세어 트랜스파일의 "하드웨어 효율성"을 대략적으로 정량화할 수 있습니다. 여기서는 무작위 유니타리에 SWAP Gate가 이어지는 입력 Circuit에 대해 다양한 최적화 레벨을 시도해 보겠습니다.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
from qiskit import QuantumCircuit
from qiskit.circuit.library import UnitaryGate
from qiskit.quantum_info import Operator, random_unitary

UU = random_unitary(4, seed=12345)
rand_U = UnitaryGate(UU)

qc = QuantumCircuit(2)
qc.append(rand_U, range(2))
qc.swap(0, 1)
qc.draw("mpl", style="iqp")

이전 코드 셀의 출력

예제에서는 FakeSherbrooke 모의 Backend를 사용하겠습니다. 먼저 최적화 레벨 0으로 트랜스파일해 보겠습니다.

from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke

backend = FakeSherbrooke()

pass_manager = generate_preset_pass_manager(
optimization_level=0, backend=backend, seed_transpiler=12345
)
qc_t1_exact = pass_manager.run(qc)
qc_t1_exact.draw("mpl", idle_wires=False)

이전 코드 셀의 출력

트랜스파일된 Circuit에는 6개의 2-Qubit ECR Gate가 있습니다.

최적화 레벨 1로 반복합니다:

from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke

backend = FakeSherbrooke()

pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend, seed_transpiler=12345
)
qc_t1_exact = pass_manager.run(qc)
qc_t1_exact.draw("mpl", idle_wires=False)

이전 코드 셀의 출력

트랜스파일된 Circuit에는 여전히 6개의 ECR Gate가 있지만, 단일 Qubit Gate의 수는 감소했습니다.

최적화 레벨 2로 반복합니다:

pass_manager = generate_preset_pass_manager(
optimization_level=2, backend=backend, seed_transpiler=12345
)
qc_t2_exact = pass_manager.run(qc)
qc_t2_exact.draw("mpl", idle_wires=False)

이전 코드 셀의 출력

이는 최적화 레벨 1과 동일한 결과를 제공합니다. 최적화 레벨을 높인다고 해서 항상 차이가 생기는 것은 아님을 주의하세요.

최적화 레벨 3으로 다시 반복합니다:

pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=12345
)
qc_t3_exact = pass_manager.run(qc)
qc_t3_exact.draw("mpl", idle_wires=False)

이전 코드 셀의 출력

이제 ECR Gate가 3개밖에 없습니다. 최적화 레벨 3에서 Qiskit은 2-Qubit Gate 블록을 재합성하려고 시도하며, 임의의 2-Qubit Gate는 최대 3개의 ECR Gate로 구현할 수 있기 때문에 이 결과를 얻게 됩니다. approximation_degree를 1보다 작은 값으로 설정하면 ECR Gate를 더욱 줄일 수 있으며, 이를 통해 Transpiler가 Gate 분해에 약간의 오류를 도입할 수 있는 근사를 허용합니다 (트랜스파일에 자주 사용되는 매개변수 참조):

pass_manager = generate_preset_pass_manager(
optimization_level=3,
approximation_degree=0.99,
backend=backend,
seed_transpiler=12345,
)
qc_t3_approx = pass_manager.run(qc)
qc_t3_approx.draw("mpl", idle_wires=False)

이전 코드 셀의 출력

이 Circuit에는 ECR Gate가 2개만 있지만 근사 Circuit입니다. 이 Circuit의 효과가 정확한 Circuit과 어떻게 다른지 이해하기 위해, 이 Circuit이 구현하는 유니타리 연산자와 정확한 유니타리 사이의 충실도(fidelity)를 계산할 수 있습니다. 계산을 수행하기 전에, 먼저 127개의 Qubit을 포함하는 트랜스파일된 Circuit을 활성 Qubit만 포함하는 Circuit(총 2개)으로 축소합니다.

import numpy as np

def trace_to_fidelity_2q(trace: float) -> float:
return (4.0 + trace * trace.conjugate()) / 20.0

# Reduce circuits down to 2 qubits so they are easy to simulate
qc_t3_exact_small = QuantumCircuit.from_instructions(qc_t3_exact)
qc_t3_approx_small = QuantumCircuit.from_instructions(qc_t3_approx)

# Compute the fidelity
exact_fid = trace_to_fidelity_2q(
np.trace(np.dot(Operator(qc_t3_exact_small).adjoint().data, UU))
)
approx_fid = trace_to_fidelity_2q(
np.trace(np.dot(Operator(qc_t3_approx_small).adjoint().data, UU))
)
print(
f"Synthesis fidelity\nExact: {exact_fid:.3f}\nApproximate: {approx_fid:.3f}"
)
Synthesis fidelity
Exact: 1.000+0.000j
Approximate: 0.992+0.000j

최적화 레벨을 조정하면 ECR Gate 수뿐만 아니라 Circuit의 다른 측면도 변경될 수 있습니다. 최적화 레벨 설정이 레이아웃을 어떻게 변경하는지에 대한 예시는 양자 컴퓨터 표현을 참조하세요.

다음 단계

권장 사항