Skip to main content
IBM Quantum Platform

트랜스파일러 단계

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

    qiskit[all]~=2.5.2
    

이 페이지는 키스킷 SDK에서 미리 빌드된 트랜슬레이션 파이프라인의 단계를 설명합니다. 6단계가 있습니다:

  1. init
  2. layout
  3. routing
  4. translation
  5. optimization
  6. scheduling

함수는 generate_preset_pass_manager 함수는 이러한 단계로 구성된 사전 설정된 스테이지 패스 관리자를 생성합니다. 각 단계를 구성하는 구체적인 패스는 generate_preset_pass_manager 에 전달되는 인수에 따라 달라집니다. optimization_level 은 반드시 지정해야 하는 위치 인자로, 0, 1, 2 또는 3이 될 수 있는 정수입니다. 값이 클수록 최적화가 더 무겁지만 비용이 많이 든다는 의미입니다( 번역 기본값 및 구성 옵션 참조).

서킷을 트랜스파일하는 권장 방법은 패스 관리자를 사용한 트랜스파일에 설명된 대로 미리 설정된 스테이지 패스 관리자를 생성한 다음 해당 패스 관리자를 서킷에서 실행하는 것입니다. 그러나 더 간단하지만 사용자 지정이 어려운 대안은 transpile 함수를 사용하는 것입니다. 이 함수는 회로를 직접 인수로 받습니다. generate_preset_pass_manager 에서와 마찬가지로 사용되는 특정 트랜스파일러 패스는 transpile 에 전달된 optimization_level 과 같은 인수에 따라 달라집니다. 실제로 내부적으로 transpile 함수는 generate_preset_pass_manager 을 호출하여 사전 설정된 스테이지 패스 관리자를 생성하고 이를 회로에서 실행합니다.


초기화 단계

이 첫 번째 단계는 기본적으로 거의 수행하지 않으며 주로 자체 초기 최적화를 포함하려는 경우에 유용합니다. 대부분의 레이아웃 및 라우팅 알고리즘은 단일 및 2큐비트 게이트에서만 작동하도록 설계되었기 때문에 이 단계는 2큐비트 이상에서 작동하는 모든 게이트를 1 또는 2큐비트에서만 작동하는 게이트로 변환하는 데에도 사용됩니다.

이 단계의 초기 최적화를 직접 구현하는 방법에 대한 자세한 내용은 플러그인 및 패스 관리자 사용자 지정 섹션을 참조하세요.


레이아웃 단계

다음 단계에서는 신호가 전송될 백엔드의 레이아웃 또는 연결 방식을 결정합니다. 일반적으로 양자 회로는 추상적인 개념으로, 그 큐비트들은 계산에 사용되는 실제 큐비트를 “가상” 또는 “논리적”으로 표현한 것이다. 일련의 게이트 연산을 수행하려면, “가상” 큐비트와 실제 양자 장치 내의 “물리적” 큐비트 간에 일대일 대응 관계가 필요합니다. 이 매핑은 객체로 Layout 저장되며, 백엔드의 명령어 집합 아키텍처(ISA) 내에서 정의된 제약 조건의 일부입니다.

이 이미지는 큐비트가 와이어 표현에서 QPU 내 큐비트 연결 방식을 나타내는 다이어그램으로 매핑되는 과정을 보여줍니다.
큐비트 매핑

매핑 선택은 입력 회로를 디바이스 토폴로지에 매핑하고 가장 잘 보정된 큐비트가 사용되도록 하는 데 필요한 SWAP 연산 횟수를 최소화하는 데 매우 중요합니다. 이 단계의 중요성 때문에 프리셋 패스 관리자는 최적의 레이아웃을 찾기 위해 몇 가지 다른 방법을 시도합니다. 일반적으로 이 작업에는 두 단계가 포함됩니다. 먼저 '완벽한' 레이아웃(스왑 작업이 필요 없는 레이아웃)을 찾은 다음, 완벽한 레이아웃을 찾을 수 없는 경우 사용할 최적의 레이아웃을 찾는 휴리스틱 패스를 시도합니다. 이 첫 번째 단계에는 일반적으로 Passes 두 개가 사용됩니다:

  • TrivialLayout: 각 가상 큐비트를 장치의 동일한 번호가 매겨진 물리적 큐비트에 순진하게 매핑합니다(예: [0,1,1,3] -> [0,1,1,3] ). 이것은 단지 사용된 역사적 동작입니다. optimzation_level=1 완벽한 레이아웃을 찾으려고 노력합니다. 실패하면 VF2Layout 이 다음에 시도됩니다.
  • VF2Layout: 이 단계를 하위 그래프 동형 문제( VF2++ 알고리즘으로 해결)로 처리하여 이상적인 레이아웃을 선택하는 AnalysisPass 입니다. 레이아웃이 두 개 이상 발견되면 채점 휴리스틱이 실행되어 평균 오류가 가장 낮은 매핑을 선택합니다.

그런 다음 휴리스틱 단계에서는 기본적으로 두 개의 패스가 사용됩니다:

  • DenseLayout: 연결성이 가장 높고 회로와 동일한 수의 큐비트를 가진 장치의 하위 그래프를 찾습니다(회로에 제어 흐름 연산(예: IfElseOp )이 있는 경우 최적화 레벨 1에 사용됨).
  • SabreLayout이 패스는 초기 무작위 레이아웃에서 시작하여 알고리즘을 SabreSwap 반복적으로 실행함으로써 레이아웃을 선택합니다. 이 패스는 최적화 레벨 1, 2, 3에서만 사용되며, 해당 VF2Layout 패스를 통해 완벽한 레이아웃이 발견되지 않을 경우에만 적용됩니다. 이 알고리즘에 대한 자세한 내용은 논문 ' arXiv:1809.02573 '를 참조하십시오.

라우팅 단계

양자 디바이스에서 직접 연결되지 않은 큐비트 사이에 2큐비트 게이트를 구현하려면 하나 이상의 SWAP 게이트를 회로에 삽입하여 디바이스 게이트 맵에서 인접할 때까지 큐비트 상태를 이동시켜야 합니다. 각 스왑 게이트는 수행 비용이 많이 들고 소음이 많은 연산을 나타냅니다. 따라서 회로를 주어진 디바이스에 매핑하는 데 필요한 최소 SWAP 게이트 수를 찾는 것은 트랜스필레이션 프로세스에서 중요한 단계입니다. 효율성을 위해 이 단계는 일반적으로 기본적으로 레이아웃 단계와 함께 계산되지만 논리적으로 서로 구분됩니다. 레이아웃 단계에서는 사용할 하드웨어 큐비트를 선택하고, 라우팅 단계에서는 선택한 레이아웃을 사용하여 회로를 실행하기 위해 적절한 양의 스왑 게이트를 삽입합니다.

하지만 최적의 SWAP 매핑을 찾는 것은 어려운 일입니다. 사실 이 문제는 NP-하드 문제이므로 가장 작은 양자 장치와 입력 회로를 제외하고는 모두 계산하는 데 엄청난 비용이 듭니다. 이 문제를 해결하기 위해 키스킷은 SabreSwap 이라는 확률적 휴리스틱 알고리즘을 사용하여 최적은 아니지만 좋은 스왑 매핑을 계산합니다. 확률적 방법을 사용한다는 것은 반복 실행 시 생성되는 회로가 동일하다는 보장이 없다는 것을 의미합니다. 실제로 동일한 회로를 반복적으로 실행하면 출력에서 회로 깊이와 게이트 수의 분포가 달라집니다. 이러한 이유로 많은 사용자가 라우팅 기능(또는 전체 StagedPassManager)을 여러 번 실행하고 출력 분포에서 가장 깊이가 낮은 회로를 선택합니다.

예를 들어 "나쁜"(연결이 끊어진) initial_layout 을 사용하여 100회 실행된 15-큐비트 GHZ 회로를 예로 들어 보겠습니다.

import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.providers.fake_provider import GenericBackendV2

backend = GenericBackendV2(15)


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)

Output:

Text(0, 0.5, 'Counts')
Output of the previous code cell

이 넓은 분포는 SWAP 매퍼가 최적의 매핑을 계산하는 것이 얼마나 어려운지를 보여줍니다. 인사이트를 얻기 위해 실행 중인 회로와 하드웨어에서 선택된 큐비트를 모두 살펴봅시다.

ghz.draw("mpl", idle_wires=False)

Output:

Output of the previous code cell
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)

Output:

Output of the previous code cell

보시다시피, 이 회로는 연결 그래프에서 매우 멀리 떨어져 있는 큐비트 0과 14 사이에서 2큐비트 게이트를 실행해야 합니다. 따라서 이 회로를 실행하려면 SabreSwap 패스를 사용하여 모든 2쿼비트 게이트를 실행하기 위해 스왑 게이트를 삽입해야 합니다.

SabreSwap 알고리즘은 이전 단계의 더 큰 SabreLayout 방법과 다르다는 점도 참고하세요. 기본적으로 SabreLayout 은 레이아웃과 라우팅을 모두 실행하고 변환된 회로를 반환합니다. 이는 패스의 API 참조 페이지에 명시된 몇 가지 특정 기술적인 이유로 인해 수행됩니다.


번역 단계

양자 회로를 작성할 때는 원하는 양자 게이트(유니터리 연산)를 자유롭게 사용할 수 있으며, 큐비트 측정이나 리셋 명령어와 같은 게이트가 아닌 연산들도 함께 활용할 수 있습니다. 그러나 대부분의 양자 장치는 기본적으로 소수의 양자 게이트 및 비게이트 연산만 지원한다. 이러한 네이티브 게이트는 대상의 ISA 정의의 일부이며, 프리셋의 이 단계에서는 회로에 지정된 게이트를 지정된 PassManagers 백엔드의 네이티브 기준 게이트로 변환(또는 언롤 )합니다. 이는 회로를 백엔드에서 실행할 수 있게 해주기 때문에 중요한 단계이지만, 일반적으로 회로의 깊이와 게이트 수가 증가하게 됩니다.

이 단계의 기능을 설명하는 데 도움이 되는 두 가지 특별한 경우를 특히 강조할 필요가 있습니다.

  1. SWAP 게이트가 타겟 백엔드의 기본 게이트가 아닌 경우 세 개의 CNOT 게이트가 필요합니다:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")

Output:

native gates:['cx', 'delay', 'id', 'measure', 'reset', 'rz', 'sx', 'x']
Output of the previous code cell

3개의 CNOT 게이트의 산물인 스왑은 잡음이 많은 양자 디바이스에서 수행하기에는 비용이 많이 드는 작업입니다. 그러나 이러한 작업은 일반적으로 많은 디바이스의 제한된 게이트 연결에 회로를 내장하는 데 필요합니다. 따라서 트랜스파일레이션 프로세스의 주요 목표는 회로에서 SWAP 게이트 수를 최소화하는 것입니다.

  1. 토폴리 또는 제어-제어-제어-낫 게이트(ccx)는 3비트 게이트입니다. 기본 게이트 세트에는 단일 및 2큐비트 게이트만 포함되어 있으므로 이 연산을 분해해야 합니다. 하지만 비용이 상당히 많이 듭니다:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output:

Output of the previous code cell

양자 회로의 모든 토폴리 게이트에 대해 하드웨어는 최대 6개의 CNOT 게이트와 소수의 단일 큐비트 게이트를 실행할 수 있습니다. 이 예는 여러 토폴리 게이트를 사용하는 모든 알고리즘이 결국 큰 깊이를 가진 회로가 되어 노이즈의 영향을 크게 받는다는 것을 보여줍니다.


최적화 단계

이 단계에서는 양자 회로를 대상 디바이스의 기본 게이트 세트로 분해하는 데 중점을 두며, 레이아웃 및 라우팅 단계의 증가된 깊이와 싸워야 합니다. 다행히도 게이트를 결합하거나 제거하여 회로를 최적화하는 많은 루틴이 있습니다. 경우에 따라 이러한 방법은 매우 효과적이어서 하드웨어 토폴로지로 레이아웃 및 라우팅한 후에도 출력 회로가 입력보다 깊이가 낮은 경우도 있습니다. 다른 경우에는 많은 작업을 수행할 수 없으며 소음이 심한 장치에서는 계산을 수행하기 어려울 수 있습니다. 이 단계에서는 다양한 최적화 수준이 달라지기 시작합니다.

또한 이 단계에서는 회로의 모든 명령이 대상 백엔드에서 사용 가능한 기본 게이트로 구성되었는지 확인하기 위해 몇 가지 최종 검사를 실행합니다.

GHZ 상태를 사용한 아래 예는 다양한 최적화 수준 설정이 회로 깊이와 게이트 수에 미치는 영향을 보여줍니다.

Note

트랜스필레이션 출력은 확률론적 SWAP 매퍼에 따라 달라집니다. 따라서 아래 숫자는 코드를 실행할 때마다 변경될 가능성이 높습니다.

15-쿼비트 GHZ 상태
트랜스퓔레이션 전 15-쿼비트 GHZ 상태

다음 코드는 15-큐비트 GHZ 상태를 구성하고 결과 회로 깊이, 게이트 수 및 멀티-큐비트 게이트 수 측면에서 트랜스퓔레이션의 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()

Output:

Output of the previous code cell

스케줄링

이 마지막 단계는 명시적으로 호출된 경우에만 실행되며(Init 단계와 유사) 기본적으로 실행되지 않습니다( generate_preset_pass_manager 호출 시 scheduling_method 인수를 설정하여 메서드를 지정할 수 있음). 스케줄링 단계는 일반적으로 회로를 대상 기준으로 변환하고, 디바이스에 매핑하고, 최적화한 후에 사용됩니다. 이 패스는 서킷의 모든 유휴 시간을 고려하는 데 중점을 둡니다. 높은 수준에서 스케줄링 패스는 게이트 실행 사이의 유휴 시간을 고려하고 백엔드에서 회로가 얼마나 오래 실행될지 검사하기 위해 지연 명령을 명시적으로 삽입하는 것으로 생각할 수 있습니다.

예는 다음과 같습니다.

ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))


# Use fake backend
backend = GenericBackendV2(5)

# 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)

Output:

Output of the previous code cell
지연 지침이 있는 회로

트랜스파일러는 각 큐비트에서 유휴 시간을 고려하기 위해 Delay 명령어를 삽입했습니다. 회로의 타이밍을 더 잘 파악하려면 timeline.draw() 함수를 사용하여 살펴볼 수도 있습니다:

timeline.draw ()에서 본 동일한 회로의 모습

회로 스케줄링은 분석과 제약 조건 매핑, 그리고 패딩 패스의 두 부분으로 구성됩니다. 첫 번째 부분은 스케줄링 분석 패스를 실행해야 합니다(기본값은 ALAPSchedulingAnalysis)를 실행하여 회로를 분석하고 회로 내 각 명령어의 시작 시간을 스케줄에 기록합니다. 회로에 초기 일정이 설정되면 대상 백엔드의 타이밍 제약을 고려하여 추가 패스를 실행할 수 있습니다. 마지막으로, 패딩 패스는 PadDelay 또는 PadDynamicalDecoupling 와 같은 패딩 패스를 실행할 수 있습니다.


다음 단계

권장사항
이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.