ข้ามไปยังเนื้อหาหลัก

การปรับแต่ง Transpilation ด้วย SABRE

ประมาณเวลาการใช้งาน: ไม่ถึงหนึ่งนาทีบนโปรเซสเซอร์ Heron r2 (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น เวลาจริงอาจแตกต่างกันได้)

พื้นหลัง

Transpilation เป็นขั้นตอนสำคัญใน Qiskit ที่แปลง quantum circuit ให้เข้ากันได้กับฮาร์ดแวร์ควอนตัมที่กำหนด โดยประกอบด้วยสองขั้นตอนหลัก คือ qubit layout (การแมป logical qubit ไปยัง physical qubit บนอุปกรณ์) และ gate routing (การทำให้ multi-qubit gate เป็นไปตามข้อจำกัดการเชื่อมต่อของอุปกรณ์โดยการแทรก SWAP gate ตามที่จำเป็น)

SABRE (SWAP-Based Bidirectional heuristic search algorithm) เป็นเครื่องมือปรับแต่งที่ทรงพลังทั้งสำหรับ layout และ routing เหมาะเป็นพิเศษกับ วงจรขนาดใหญ่ (100+ qubit) และอุปกรณ์ที่มี coupling map ซับซ้อน เช่น IBM® Heron ซึ่งการเติบโตแบบ exponential ในการแมป qubit ที่เป็นไปได้ต้องการวิธีแก้ปัญหาที่มีประสิทธิภาพ

ทำไมต้องใช้ SABRE?

SABRE ช่วยลดจำนวน SWAP gate และลดความลึกของวงจร ซึ่งช่วยปรับปรุงประสิทธิภาพของวงจรบนฮาร์ดแวร์จริง การใช้ heuristic เป็นพื้นฐานทำให้เหมาะสำหรับฮาร์ดแวร์ขั้นสูงและวงจรที่ซับซ้อนขนาดใหญ่ การปรับปรุงล่าสุดที่นำมาใช้ใน LightSABRE ช่วยเพิ่มประสิทธิภาพของ SABRE อีกต่อ โดยให้รันไทม์เร็วขึ้นและ SWAP gate น้อยลง การเพิ่มประสิทธิภาพเหล่านี้ทำให้มีประสิทธิผลมากขึ้นสำหรับวงจรขนาดใหญ่

สิ่งที่จะได้เรียนรู้

บทเรียนนี้แบ่งออกเป็นสองส่วน:

  1. เรียนรู้การใช้ SABRE กับ Qiskit patterns เพื่อปรับแต่งวงจรขนาดใหญ่ขั้นสูง
  2. ใช้ประโยชน์จาก qiskit_serverless เพื่อเพิ่มศักยภาพสูงสุดของ SABRE สำหรับ transpilation ที่ scale ได้และมีประสิทธิภาพ

คุณจะได้:

  • ปรับแต่ง SABRE สำหรับวงจรที่มี qubit มากกว่า 100 ตัว ซึ่งเกินกว่าการตั้งค่า transpilation เริ่มต้นอย่าง optimization_level=3
  • สำรวจ การปรับปรุง LightSABRE ที่ช่วยให้รันไทม์ดีขึ้นและลดจำนวน gate
  • ปรับแต่งพารามิเตอร์สำคัญของ SABRE (swap_trials, layout_trials, max_iterations, heuristic) เพื่อสร้างสมดุลระหว่าง คุณภาพวงจร และ รันไทม์ของ transpilation

ข้อกำหนดเบื้องต้น

ก่อนเริ่มบทเรียนนี้ ต้องติดตั้งสิ่งต่อไปนี้:

  • Qiskit SDK v1.0 หรือใหม่กว่า พร้อมรองรับ visualization
  • 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

ส่วนที่ 1 การใช้ SABRE กับ Qiskit patterns

SABRE สามารถใช้ใน Qiskit เพื่อปรับแต่ง quantum circuit โดยจัดการทั้งขั้นตอน qubit layout และ gate routing ในส่วนนี้จะแนะนำ ตัวอย่างพื้นฐาน ของการใช้ SABRE กับ Qiskit patterns โดยเน้นที่ขั้นตอนที่ 2 ของการปรับแต่งเป็นหลัก

ในการรัน SABRE คุณต้องมี:

  • การแทนค่า DAG (Directed Acyclic Graph) ของ quantum circuit
  • coupling map จาก Backend ซึ่งระบุวิธีที่ qubit เชื่อมต่อกันทางกายภาพ
  • SABRE pass ซึ่งใช้ algorithm เพื่อปรับแต่ง layout และ routing

สำหรับส่วนนี้จะเน้นที่ SabreLayout pass โดยจะทำการทดสอบ layout และ routing เพื่อหา initial layout ที่มีประสิทธิภาพสูงสุดขณะลดจำนวน SWAP gate ที่ต้องการ สำคัญคือ SabreLayout เพียงอย่างเดียวจะปรับแต่งทั้ง layout และ routing ภายในโดยเก็บวิธีแก้ปัญหาที่เพิ่ม SWAP gate น้อยที่สุด โปรดทราบว่าเมื่อใช้แค่ SabreLayout เราไม่สามารถเปลี่ยน heuristic ของ SABRE ได้ แต่สามารถปรับแต่งจำนวน layout_trials ได้

ขั้นตอนที่ 1: แมป input แบบ classical ไปยังปัญหาควอนตัม

GHZ (Greenberger-Horne-Zeilinger) circuit เป็น quantum circuit ที่เตรียมสถานะ entangled ที่ qubit ทั้งหมดอยู่ในสถานะ |0...0⟩ หรือ |1...1⟩ สถานะ GHZ สำหรับ nn qubit แสดงทางคณิตศาสตร์ได้เป็น: GHZ=12(0n+1n)|\text{GHZ}\rangle = \frac{1}{\sqrt{2}} \left( |0\rangle^{\otimes n} + |1\rangle^{\otimes n} \right)

สร้างโดยการใช้:

  1. Hadamard gate กับ qubit แรกเพื่อสร้าง superposition
  2. ชุดของ CNOT gate เพื่อ entangle qubit ที่เหลือกับ qubit แรก

สำหรับตัวอย่างนี้ เราจงใจสร้าง star-topology GHZ circuit แทนที่จะเป็น linear-topology ใน star topology qubit แรกทำหน้าที่เป็น "hub" และ qubit อื่นทั้งหมด entangle โดยตรงกับมันด้วย CNOT gate การเลือกนี้เป็นเจตนา เพราะแม้ว่า linear topology GHZ state จะสามารถ implement ได้ในทางทฤษฎีที่ความลึก O(N)O(N) บน linear coupling map โดยไม่ต้องใช้ SWAP gate แต่ SABRE จะหาวิธีแก้ที่เหมาะสมได้ง่ายมากโดยแมป GHZ circuit 100 qubit ไปยัง subgraph ของ heavy-hex coupling map ของ Backend

star topology GHZ circuit สร้างปัญหาที่ท้าทายกว่ามาก แม้ว่าทางทฤษฎียังสามารถ execute ได้ที่ความลึก O(N)O(N) โดยไม่ต้องใช้ SWAP gate แต่การหาวิธีแก้ปัญหานี้ต้องการการระบุ initial layout ที่เหมาะสม ซึ่งยากกว่ามากเนื่องจากการเชื่อมต่อที่ไม่ใช่เชิงเส้นของวงจร topology นี้เป็น test case ที่ดีกว่าสำหรับประเมิน SABRE เพราะแสดงให้เห็นว่า configuration parameter ส่งผลต่อประสิทธิภาพ layout และ routing อย่างไรในเงื่อนไขที่ซับซ้อนกว่า

ghz_star_topology.png

สิ่งที่น่าสังเกต:

  • เครื่องมือ HighLevelSynthesis สามารถสร้างวิธีแก้ที่เหมาะสม O(N)O(N) สำหรับ star topology GHZ circuit โดยไม่แนะนำ SWAP gate ดังที่แสดงในภาพข้างต้น
  • อีกทางหนึ่ง StarPrerouting pass สามารถลดความลึกลงอีกโดยชี้นำการตัดสินใจ routing ของ SABRE แม้ว่าอาจยังคง introduce SWAP gate บ้าง อย่างไรก็ตาม StarPrerouting เพิ่ม runtime และต้องการการรวมเข้ากับกระบวนการ transpilation เริ่มต้น

สำหรับวัตถุประสงค์ของบทเรียนนี้ เราไม่รวม HighLevelSynthesis และ StarPrerouting เพื่อแยกและเน้น impact โดยตรงของ SABRE configuration ต่อ runtime และความลึกของวงจร โดยการวัดค่าความคาดหวัง Z0Zi\langle Z_0 Z_i \rangle สำหรับแต่ละคู่ qubit เราวิเคราะห์:

  • SABRE ลด SWAP gate และความลึกของวงจรได้ดีแค่ไหน
  • ผลของการปรับแต่งเหล่านี้ต่อ fidelity ของวงจรที่ execute โดยค่าที่เบี่ยงเบนจาก Z0Zi=1\langle Z_0 Z_i \rangle = 1 บ่งชี้ถึงการสูญเสีย entanglement!
# 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()

ต่อไปเราจะแมป operator ที่สนใจเพื่อประเมินพฤติกรรมของระบบ โดยเฉพาะจะใช้ ZZ operator ระหว่าง qubit เพื่อตรวจสอบว่า entanglement เสื่อมลงอย่างไรเมื่อ qubit อยู่ห่างกันมากขึ้น การวิเคราะห์นี้สำคัญมากเพราะความไม่แม่นยำในค่าความคาดหวัง Z0Zi\langle Z_0 Z_i \rangle สำหรับ qubit ที่อยู่ห่างไกลสามารถเผยให้เห็น impact ของ noise และ error ในการ execute วงจร การศึกษาค่าเบี่ยงเบนเหล่านี้ทำให้เราเข้าใจว่าวงจรรักษา entanglement ได้ดีแค่ไหนภายใต้ SABRE configuration ต่างๆ และ SABRE ลด impact ของข้อจำกัดของฮาร์ดแวร์ได้มีประสิทธิภาพแค่ไหน

# 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

ขั้นตอนที่ 2: ปรับแต่งปัญหาสำหรับการ execute บนฮาร์ดแวร์ควอนตัม

ในขั้นตอนนี้เราเน้นการปรับแต่ง circuit layout สำหรับการ execute บนอุปกรณ์ฮาร์ดแวร์ควอนตัมที่มี qubit 127 ตัว นี่คือจุดสำคัญหลักของบทเรียน เนื่องจากเราทำ การปรับแต่ง SABRE และ transpilation เพื่อให้ได้ประสิทธิภาพวงจรดีที่สุด โดยใช้ SabreLayout pass เพื่อกำหนด initial qubit mapping ที่ลดความจำเป็นใน SWAP gate ระหว่าง routing การส่ง coupling_map ของ Backend เป้าหมายทำให้ SabreLayout ปรับ layout ตามข้อจำกัดการเชื่อมต่อของอุปกรณ์

เราจะใช้ generate_preset_pass_manager กับ optimization_level=3 สำหรับกระบวนการ transpilation และปรับแต่ง SabreLayout pass ด้วย configuration ต่างๆ เป้าหมายคือหา setup ที่สร้างวงจร transpiled ที่มี size และ/หรือ depth ต่ำที่สุด เพื่อแสดงให้เห็น impact ของการปรับแต่ง SABRE

ทำไม Circuit Size และ Depth จึงสำคัญ?

  • Size ต่ำกว่า (จำนวน gate): ลดจำนวน operation ลดโอกาสที่ error จะสะสม
  • Depth ต่ำกว่า: ลดเวลา execute โดยรวม ซึ่งสำคัญมากสำหรับการหลีกเลี่ยง decoherence และรักษา fidelity ของ quantum state

การปรับแต่ง metric เหล่านี้ช่วยปรับปรุงความน่าเชื่อถือและความแม่นยำในการ execute บนฮาร์ดแวร์ควอนตัมที่มี noise เลือก 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

เพื่อประเมิน impact ของ configuration ต่างๆ ต่อการปรับแต่งวงจร เราจะสร้าง pass manager สามตัว แต่ละตัวมีการตั้งค่าเฉพาะสำหรับ SabreLayout pass configuration เหล่านี้ช่วยวิเคราะห์การแลกเปลี่ยนระหว่างคุณภาพวงจรและเวลา transpilation

พารามิเตอร์สำคัญ

  • max_iterations: จำนวน iteration routing ไปข้างหน้า-ถอยหลังเพื่อปรับแต่ง layout และลด routing cost
  • layout_trials: จำนวน initial layout แบบ random ที่ทดสอบ โดยเลือกอันที่ลด SWAP gate น้อยที่สุด
  • swap_trials: จำนวน routing trial สำหรับแต่ละ layout เพื่อปรับแต่งการจัด gate เพื่อ routing ที่ดีขึ้น

เพิ่ม layout_trials และ swap_trials เพื่อทำการปรับแต่งที่ละเอียดยิ่งขึ้น แต่แลกกับเวลา transpilation ที่เพิ่มขึ้น

Configuration ในบทเรียนนี้

  1. pm_1: การตั้งค่าเริ่มต้นกับ optimization_level=3

    • max_iterations=4
    • layout_trials=20
    • swap_trials=20
  2. pm_2: เพิ่มจำนวน trial เพื่อการสำรวจที่ดีขึ้น

    • max_iterations=4
    • layout_trials=200
    • swap_trials=200
  3. pm_3: ขยายจาก pm_2 โดยเพิ่มจำนวน iteration เพื่อการปรับแต่งเพิ่มเติม

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

โดยการเปรียบเทียบผลลัพธ์ของ configuration เหล่านี้ เราต้องการหาว่า configuration ไหนบรรลุความสมดุลที่ดีที่สุดระหว่างคุณภาพวงจร (เช่น size และ depth) และ computational cost

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

ตอนนี้เราสามารถ configure SabreLayout pass ใน custom pass manager ได้แล้ว สำหรับการทำเช่นนี้ เราทราบว่าสำหรับ generate_preset_pass_manager เริ่มต้นบน optimization_level=3 SabreLayout pass อยู่ที่ index 2 เนื่องจาก SabreLayout เกิดขึ้นหลัง SetLayout และ VF2Layout pass เราสามารถเข้าถึง pass นี้และแก้ไขพารามิเตอร์ได้

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

เมื่อ configure pass manager แต่ละตัวแล้ว เราจะ execute กระบวนการ transpilation สำหรับแต่ละตัว เพื่อเปรียบเทียบผลลัพธ์ เราจะติดตาม metric สำคัญ รวมถึงเวลา transpilation ความลึกของวงจร (วัดเป็น two-qubit gate depth) และจำนวน gate ทั้งหมดใน circuit ที่ transpiled

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

ผลลัพธ์แสดงให้เห็นว่าการเพิ่มจำนวน trial (layout_trials และ swap_trials) สามารถปรับปรุงคุณภาพวงจรได้อย่างมีนัยสำคัญโดยลดทั้ง depth และ size อย่างไรก็ตาม การปรับปรุงนี้มักมาพร้อมกับ runtime ที่เพิ่มขึ้นเนื่องจากการคำนวณเพิ่มเติมที่จำเป็นในการสำรวจ layout ที่เป็นไปได้และ routing path มากขึ้น

การเพิ่ม max_iterations สามารถเพิ่มประสิทธิภาพการปรับแต่งได้อีกโดยการปรับแต่ง layout ผ่าน routing cycle ไปข้างหน้า-ถอยหลังมากขึ้น ในกรณีนี้ การเพิ่ม max_iterations ส่งผลให้ circuit depth และ size ลดลงอย่างมีนัยสำคัญที่สุด แม้แต่ยังลด runtime เมื่อเปรียบเทียบกับ pm_2 ซึ่งน่าจะเกิดจากการทำให้ขั้นตอน optimization ถัดไปมีประสิทธิภาพมากขึ้น อย่างไรก็ตามสำคัญต้องทราบว่าประสิทธิผลของการเพิ่ม max_iterations สามารถแตกต่างกันอย่างมีนัยสำคัญขึ้นอยู่กับวงจร แม้ว่า iteration มากขึ้นอาจให้การเลือก layout และ routing ที่ดีขึ้น แต่ไม่ได้รับประกันและขึ้นอยู่กับโครงสร้างของวงจรและความซับซ้อนของข้อจำกัดการเชื่อมต่อเป็นอย่างมาก

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

ในขั้นตอนนี้ เราใช้ primitive Estimator เพื่อคำนวณค่าความคาดหวัง Z0Zi\langle Z_0 Z_i \rangle ของ operator ZZ ซึ่งช่วยประเมินคุณภาพของความพัวพันและการรันของ Circuit ที่ผ่าน transpile แล้ว เพื่อให้สอดคล้องกับ workflow ที่ผู้ใช้ทั่วไปใช้งาน เราส่ง job เพื่อรันและใช้การลด error ด้วย dynamical decoupling ซึ่งเป็นเทคนิคที่แทรก sequence ของ Gate เพื่อรักษาสภาวะของ Qubit และบรรเทา decoherence นอกจากนี้เรายังกำหนด resilience level เพื่อต้านทานสัญญาณรบกวน โดย level ที่สูงขึ้นจะให้ผลลัพธ์ที่แม่นยำกว่าแต่ใช้เวลาประมวลผลนานขึ้น วิธีนี้ช่วยประเมินประสิทธิภาพของแต่ละการตั้งค่า pass manager ภายใต้สภาวะการรันจริง

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: ประมวลผลผลลัพธ์และแสดงในรูปแบบ classical ที่ต้องการ

เมื่อ job เสร็จสมบูรณ์ เราจะวิเคราะห์ผลลัพธ์โดยพล็อตค่าความคาดหวัง Z0Zi\langle Z_0 Z_i \rangle สำหรับแต่ละ Qubit ในการจำลองที่สมบูรณ์แบบ ค่า Z0Zi\langle Z_0 Z_i \rangle ทั้งหมดควรเท่ากับ 1 สะท้อนถึงความพัวพันที่สมบูรณ์ทั่วทุก Qubit อย่างไรก็ตาม เนื่องจากสัญญาณรบกวนและข้อจำกัดของฮาร์ดแวร์ ค่าความคาดหวังมักจะลดลงเมื่อ i เพิ่มขึ้น แสดงให้เห็นว่าความพัวพันเสื่อมสลายตามระยะทางอย่างไร

ในขั้นตอนนี้ เราเปรียบเทียบผลลัพธ์จากแต่ละการตั้งค่า pass manager กับการจำลองที่สมบูรณ์แบบ การตรวจสอบความเบี่ยงเบนของ Z0Zi\langle Z_0 Z_i \rangle จาก 1 ในแต่ละการตั้งค่าช่วยให้เราวัดได้ว่า pass manager แต่ละตัวรักษาความพัวพันและบรรเทาผลกระทบจากสัญญาณรบกวนได้ดีเพียงใด การวิเคราะห์นี้ประเมินผลกระทบของการปรับแต่ง SABRE ต่อ execution fidelity โดยตรง และแสดงให้เห็นว่าการตั้งค่าใดสร้างความสมดุลระหว่างคุณภาพการปรับแต่งและประสิทธิภาพการรันได้ดีที่สุด

ผลลัพธ์จะถูกแสดงเป็นภาพเพื่อเน้นความแตกต่างระหว่าง pass manager แต่ละตัว แสดงให้เห็นว่าการปรับปรุงใน layout และ routing มีผลต่อการรัน Circuit บนฮาร์ดแวร์ quantum ที่มีสัญญาณรบกวนอย่างไร

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

การวิเคราะห์ผลลัพธ์

กราฟแสดงค่าความคาดหวัง Z0Zi/Z0Z0\langle Z_0 Z_i \rangle / \langle Z_0 Z_0 \rangle เป็นฟังก์ชันของระยะทางระหว่าง Qubit สำหรับการตั้งค่า pass manager สามแบบที่มีระดับการปรับแต่งเพิ่มขึ้นเรื่อยๆ ในกรณีที่สมบูรณ์แบบ ค่าเหล่านี้จะอยู่ใกล้ 1 บ่งชี้ถึงความสัมพันธ์ที่แข็งแกร่งทั่ว Circuit เมื่อระยะทางเพิ่มขึ้น สัญญาณรบกวนและ error ที่สะสมทำให้ความสัมพันธ์ลดลง เผยให้เห็นว่าแต่ละกลยุทธ์การ transpile รักษาโครงสร้างพื้นฐานของสภาวะได้ดีเพียงใด

ในบรรดาการตั้งค่าทั้งสาม pm_1 แสดงประสิทธิภาพที่แย่ที่สุดอย่างชัดเจน ค่าความสัมพันธ์ของมันลดลงอย่างรวดเร็วเมื่อระยะทางเพิ่มขึ้นและเข้าใกล้ศูนย์เร็วกว่าอีกสองการตั้งค่ามาก พฤติกรรมนี้สอดคล้องกับความลึกของ Circuit และจำนวน Gate ที่มากกว่า ซึ่งทำให้สัญญาณรบกวนที่สะสมทำลายความสัมพันธ์ระยะไกลอย่างรวดเร็ว

ทั้ง pm_2 และ pm_3 แสดงการปรับปรุงที่สำคัญเมื่อเทียบกับ pm_1 ในแทบทุกระยะทาง โดยเฉลี่ยแล้ว pm_3 แสดงประสิทธิภาพโดยรวมที่ดีที่สุด รักษาค่าความสัมพันธ์ที่สูงกว่าในระยะทางที่ไกลขึ้นและแสดงการลดลงที่ช้ากว่า ซึ่งสอดคล้องกับการปรับแต่งที่เข้มข้นกว่าของมัน ซึ่งสร้าง Circuit ที่ตื้นกว่าและโดยทั่วไปทนทานต่อการสะสมสัญญาณรบกวนได้ดีกว่า

อย่างไรก็ตาม pm_2 แสดงความแม่นยำที่ดีกว่าอย่างเห็นได้ชัดในระยะทางสั้นเมื่อเทียบกับ pm_3 แม้จะมีความลึกและจำนวน Gate ที่มากกว่าเล็กน้อย สิ่งนี้บ่งชี้ว่าความลึกของ Circuit เพียงอย่างเดียวไม่ได้กำหนดประสิทธิภาพทั้งหมด โครงสร้างเฉพาะที่สร้างโดยการ transpile รวมถึงวิธีจัดเรียง Gate ที่สร้างความพัวพันและวิธีที่ error แพร่กระจายผ่าน Circuit ก็มีบทบาทสำคัญเช่นกัน ในบางกรณี การแปลงที่ pm_2 ใช้ดูเหมือนจะรักษาความสัมพันธ์ระยะใกล้ได้ดีกว่า แม้ว่าจะไม่ scale ได้ดีเท่าในระยะทางที่ไกลขึ้น

โดยรวมแล้ว ผลลัพธ์เหล่านี้เน้นให้เห็นถึง trade-off ระหว่างความกระชับของ Circuit และโครงสร้างของ Circuit แม้ว่าการปรับแต่งที่เพิ่มขึ้นโดยทั่วไปจะปรับปรุงเสถียรภาพระยะไกล แต่ประสิทธิภาพที่ดีที่สุดสำหรับ observable ที่กำหนดขึ้นอยู่กับทั้งการลดความลึกของ Circuit และการสร้างโครงสร้างที่เข้ากันได้ดีกับลักษณะสัญญาณรบกวนของฮาร์ดแวร์

Part II. การปรับแต่ง heuristic ใน SABRE และการใช้ Serverless

นอกจากการปรับจำนวน trial แล้ว SABRE ยังรองรับการปรับแต่ง routing heuristic ที่ใช้ระหว่างการ transpile ด้วย โดยค่าเริ่มต้น SabreLayout จะใช้ decay heuristic ซึ่งกำหนดน้ำหนักให้ qubit แบบ dynamic ตามความน่าจะเป็นที่จะถูก swap หากต้องการใช้ heuristic อื่น (เช่น lookahead heuristic) สามารถสร้าง pass SabreSwap แบบกำหนดเองแล้วเชื่อมต่อกับ SabreLayout โดยรัน PassManager ร่วมกับ FullAncillaAllocation, EnlargeWithAncilla, และ ApplyLayout เมื่อใช้ SabreSwap เป็น parameter ของ SabreLayout จะมีการทำ layout trial เพียง 1 ครั้งโดยค่าเริ่มต้น เพื่อรัน layout trial หลายครั้งได้อย่างมีประสิทธิภาพ เราจึงใช้ประโยชน์จาก serverless runtime สำหรับการ parallelize สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ serverless ดูที่ เอกสาร Serverless

วิธีเปลี่ยน Routing Heuristic

  1. สร้าง pass SabreSwap แบบกำหนดเองพร้อม heuristic ที่ต้องการ
  2. ใช้ SabreSwap แบบกำหนดเองนี้เป็น routing method สำหรับ pass SabreLayout

แม้จะสามารถรัน layout trial หลายครั้งโดยใช้ loop ได้ แต่ serverless runtime เป็นตัวเลือกที่ดีกว่าสำหรับการทดลองขนาดใหญ่และเข้มข้นกว่า Serverless รองรับการรัน layout trial แบบขนาน ซึ่งช่วยเร่งการ optimize circuit ขนาดใหญ่และการ sweep การทดลองขนาดใหญ่ได้อย่างมีนัยสำคัญ ทำให้มีคุณค่าเป็นพิเศษเมื่อทำงานกับ task ที่ใช้ทรัพยากรมากหรือเมื่อประสิทธิภาพด้านเวลาเป็นสิ่งสำคัญ

ส่วนนี้เน้นเฉพาะขั้นตอนที่ 2 ของการ optimize เท่านั้น ได้แก่ การลดขนาดและความลึกของ Circuit เพื่อให้ได้ circuit ที่ transpile แล้วดีที่สุดเท่าที่เป็นไปได้ โดยต่อยอดจากผลลัพธ์ก่อนหน้า เราจะสำรวจว่าการปรับแต่ง heuristic และการ parallelize ด้วย serverless สามารถยกระดับประสิทธิภาพการ optimize ได้อีกแค่ไหน ทำให้เหมาะสมกับการ transpile quantum circuit ขนาดใหญ่

ผลลัพธ์โดยไม่ใช้ serverless runtime (1 layout trial):

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 heuristic มีประสิทธิภาพดีกว่า decay heuristic ในแง่ของความลึกของ Circuit ขนาด และเวลา การปรับปรุงนี้แสดงให้เห็นว่าเราสามารถพัฒนา SABRE ได้นอกเหนือจากการปรับแค่ trial และ iteration สำหรับ circuit และข้อจำกัดของฮาร์ดแวร์ที่เฉพาะเจาะจง โปรดทราบว่าผลลัพธ์เหล่านี้อ้างอิงจาก layout trial เพียง 1 ครั้ง เพื่อให้ได้ผลลัพธ์ที่แม่นยำกว่านี้ แนะนำให้รัน layout trial หลายครั้ง ซึ่งสามารถทำได้อย่างมีประสิทธิภาพโดยใช้ serverless runtime

ผลลัพธ์ด้วย serverless runtime (หลาย layout trial)

Qiskit Serverless ต้องการการตั้งค่าไฟล์ .py ของ workload ไว้ในไดเรกทอรีเฉพาะ code cell ต่อไปนี้คือไฟล์ Python ในไดเรกทอรี source_files ชื่อ transpile_remote.py ไฟล์นี้ประกอบด้วยฟังก์ชันที่รัน process การ transpile

# 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

cell ต่อไปนี้อัปโหลดไฟล์ transpile_remote.py เป็น Qiskit Serverless program ภายใต้ชื่อ transpile_remote_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")

สร้าง seed ที่แตกต่างกัน 20 ตัวเพื่อแทน layout trial ที่แตกต่างกัน 20 ครั้ง

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

รัน program ที่อัปโหลดและส่ง input สำหรับ lookahead heuristic

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'

รับ log และผลลัพธ์จาก serverless runtime

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

เมื่อ program มีสถานะ 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 heuristic

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%

ผลลัพธ์เหล่านี้แสดงให้เห็นถึงประสิทธิภาพที่เพิ่มขึ้นอย่างมีนัยสำคัญจากการใช้ serverless execution สำหรับการ transpile quantum circuit เมื่อเทียบกับการรันแบบ serial การรันแบบ serverless ลดเวลาโดยรวมลงได้อย่างมากสำหรับทั้ง decay และ lookahead heuristic โดยการ parallelize transpilation trial ที่เป็นอิสระจากกัน ในขณะที่การรันแบบ serial สะท้อนต้นทุนสะสมทั้งหมดของการสำรวจ layout trial หลายครั้ง เวลาของ serverless job แสดงให้เห็นว่าการรันแบบขนานช่วยลดต้นทุนนี้ให้เหลือเวลา wall-clock ที่สั้นกว่ามาก ส่งผลให้เวลาเฉลี่ยต่อการ transpile หนึ่งครั้งลดลงเหลือเพียงเศษเสี้ยวหนึ่งของที่ต้องการในการรันแบบ serial โดยแทบไม่ขึ้นอยู่กับ heuristic ที่ใช้ ความสามารถนี้มีความสำคัญเป็นพิเศษสำหรับการ optimize SABRE ให้ถึงศักยภาพสูงสุด ประสิทธิภาพที่แข็งแกร่งที่สุดของ SABRE หลายอย่างมาจากการเพิ่มจำนวน layout และ routing trial ซึ่งอาจมีต้นทุนสูงเกินไปหากรันแบบ sequential Serverless execution ขจัดคอขวดนี้ ทำให้การ sweep parameter ขนาดใหญ่และการสำรวจการกำหนดค่า heuristic เชิงลึกเป็นไปได้ด้วยค่าใช้จ่ายที่ต่ำมาก

โดยรวมแล้ว การค้นพบเหล่านี้แสดงให้เห็นว่า serverless execution เป็นกุญแจสำคัญในการ scale การ optimize SABRE ทำให้การทดลองและการปรับแต่งแบบเข้มข้นเป็นเรื่องที่ทำได้จริงเมื่อเทียบกับการรันแบบ serial นำผลลัพธ์จาก serverless runtime มาเปรียบเทียบผลลัพธ์ของ lookahead และ decay heuristic โดยจะเปรียบเทียบขนาดและความลึก

# 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

แต่ละจุดในกราฟ scatter ด้านบนแทน layout trial หนึ่งครั้ง โดยแกน x แสดงความลึกของ Circuit และแกน y แสดงขนาดของ Circuit ผลลัพธ์แสดงให้เห็นว่า lookahead heuristic โดยทั่วไปมีประสิทธิภาพดีกว่า decay heuristic ในการลดความลึกและขนาดของ Circuit ในการใช้งานจริง เป้าหมายคือการหา layout trial ที่ดีที่สุดสำหรับ heuristic ที่เลือก ไม่ว่าจะให้ความสำคัญกับความลึกหรือขนาด ซึ่งสามารถทำได้โดยเลือก trial ที่มีค่าต่ำที่สุดสำหรับ metric ที่ต้องการ สิ่งสำคัญคือการเพิ่มจำนวน layout trial ช่วยเพิ่มโอกาสในการได้ผลลัพธ์ที่ดีกว่าในแง่ของขนาดหรือความลึก แต่ก็มีต้นทุนด้าน overhead ในการคำนวณที่สูงขึ้น

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

ในการเปรียบเทียบเบื้องต้นโดยใช้ layout trial เพียง 1 ครั้ง lookahead heuristic แสดงประสิทธิภาพที่ดีกว่าเล็กน้อยทั้งในด้านความลึกและขนาดของ Circuit เมื่อขยายการศึกษานี้ไปยัง layout trial หลายครั้งโดยใช้ QiskitServerless เราสามารถสำรวจพื้นที่ของ SABRE initialization ที่กว้างขึ้นมาก ทำให้เปรียบเทียบ heuristic ได้ครอบคลุมยิ่งขึ้น

จากกราฟ scatter และผลลัพธ์ที่ดีที่สุดที่สังเกตได้ เห็นได้ชัดว่าประสิทธิภาพแตกต่างกันอย่างมีนัยสำคัญตาม random seed ที่ SABRE ใช้ ทั้งสอง heuristic แสดงการกระจายตัวที่กว้างในความลึกและขนาดของ Circuit ตาม seed ต่างๆ บ่งชี้ว่าการรันครั้งเดียวมักไม่เพียงพอในการหาผลลัพธ์ที่ใกล้เคียงกับค่าที่ดีที่สุด ความแปรปรวนนี้เน้นย้ำถึงความสำคัญของการรัน trial หลายครั้งด้วย seed ต่างๆ เมื่อต้องการลดความลึกและ/หรือจำนวน gate ตลอดชุด trial ทั้งหมด ทั้ง lookahead และ decay heuristic สามารถให้ผลลัพธ์ที่แข่งขันได้ ในบางกรณี decay heuristic สามารถทัดเทียมหรือแม้แต่เหนือกว่า lookahead สำหรับ seed บางตัว อย่างไรก็ตาม สำหรับ circuit นี้โดยเฉพาะ ผลลัพธ์โดยรวมที่ดีที่สุดได้รับจาก lookahead heuristic แม้จะเป็นส่วนต่างเพียงเล็กน้อย ซึ่งบ่งชี้ว่าแม้ lookahead จะให้ผลลัพธ์ที่แข็งแกร่งที่สุดในที่นี้ แต่ข้อได้เปรียบเหนือ decay ก็ไม่ใช่สิ่งที่แน่นอนเสมอไป

โดยรวมแล้ว ผลลัพธ์เหล่านี้เสริมความมั่นใจในสองประเด็นหลัก ประการแรก การใช้ประโยชน์จาก seed จำนวนมากเป็นสิ่งสำคัญสำหรับการดึงประสิทธิภาพที่ดีที่สุดออกจาก SABRE โดยไม่คำนึงถึง heuristic ที่ใช้ ประการที่สอง แม้ heuristic จะมีความสำคัญ แต่โครงสร้างของ Circuit มีบทบาทหลัก และประสิทธิภาพสัมพัทธ์ของ lookahead และ decay อาจแตกต่างกันสำหรับ circuit อื่นๆ ดังนั้น การทดลองขนาดใหญ่แบบ multi-seed จึงมีความสำคัญอย่างยิ่งสำหรับการ transpile quantum 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()

สรุป

ในบทแนะนำนี้ เราได้สำรวจวิธีการ optimize circuit ขนาดใหญ่โดยใช้ SABRE ใน Qiskit เราได้แสดงให้เห็นถึงวิธีการกำหนดค่า pass SabreLayout ด้วย parameter ต่างๆ เพื่อสร้างสมดุลระหว่างคุณภาพของ Circuit และเวลาในการ transpile เราได้แสดงให้เห็นด้วยวิธีการปรับแต่ง routing heuristic ใน SABRE และการใช้ QiskitServerless runtime เพื่อ parallelize layout trial อย่างมีประสิทธิภาพสำหรับกรณีที่ SabreSwap เข้ามาเกี่ยวข้อง การปรับ parameter และ heuristic เหล่านี้ช่วยให้คุณ optimize layout และ routing ของ circuit ขนาดใหญ่ได้ เพื่อให้แน่ใจว่าจะรันบนฮาร์ดแวร์ควอนตัมได้อย่างมีประสิทธิภาพ

แบบสำรวจ tutorial

Link to survey

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.

กรุณาทำแบบสำรวจสั้นๆ นี้เพื่อให้ข้อเสนอแนะเกี่ยวกับ tutorial นี้ ข้อมูลเชิงลึกของคุณจะช่วยให้เราปรับปรุงเนื้อหาและประสบการณ์ผู้ใช้

Source: IBM Quantum docs — updated 23 ม.ค. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of 9 เม.ย. 2569