Перейти до основного вмісту

Етапи транспілятора

Package versions

The code on this page was developed using the following requirements. We recommend using these versions or newer.

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

На цій сторінці описані етапи вбудованого конвеєра транспіляції в Qiskit SDK. Існує шість етапів:

  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, конкретні проходи транспілятора залежать від аргументів, таких як optimization_level, переданих у transpile. Фактично, внутрішньо функція transpile викликає generate_preset_pass_manager для створення наперед визначеного поетапного менеджера проходів і запускає його на схемі.

Етап init

Цей перший етап за замовчуванням робить дуже мало і насамперед корисний, якщо ти хочеш включити власні початкові оптимізації. Оскільки більшість алгоритмів розмітки та маршрутизації розроблені для роботи лише з однокубітними та двокубітними вентилями, цей етап також використовується для перетворення будь-яких вентилів, що діють на більше двох кубітів, у вентилі, що діють лише на одному або двох кубітах.

Більше інформації про реалізацію власних початкових оптимізацій для цього етапу дивись у розділі про плагіни та налаштування менеджерів проходів.

Етап layout

Наступний етап пов'язаний з розміткою або зв'язністю бекенду, на який буде надіслано схему. Загалом квантові схеми — це абстрактні сутності, кубіти яких є "віртуальними" або "логічними" представленнями реальних кубітів, що використовуються в обчисленнях. Для виконання послідовності вентилів необхідне взаємно однозначне відображення "віртуальних" кубітів на "фізичні" кубіти реального квантового пристрою. Це відображення зберігається у вигляді об'єкта Layout і є частиною обмежень, визначених в архітектурі набору інструкцій (ISA) бекенду.

Це зображення ілюструє відображення кубітів від дротової схеми до діаграми, що показує, як кубіти підключені на QPU.

Вибір відображення є надзвичайно важливим для мінімізації кількості операцій SWAP, необхідних для відображення вхідної схеми на топологію пристрою, і для забезпечення використання найкраще скаліброваних кубітів. Зважаючи на важливість цього етапу, наперед визначені менеджери проходів пробують кілька різних методів для пошуку найкращої розмітки. Як правило, це передбачає два кроки: спочатку спробувати знайти "ідеальну" розмітку (розмітку, що не вимагає жодних операцій SWAP), а потім евристичний прохід, який намагається знайти найкращу розмітку, якщо ідеальну знайти не вдається. Для першого кроку зазвичай використовуються два проходи Passes:

  • TrivialLayout: Наївно відображає кожен віртуальний кубіт на фізичний кубіт з тим самим номером на пристрої (тобто [0,1,1,3] -> [0,1,1,3]). Це поведінка, що збереглась з попередніх версій і використовується лише в optimization_level=1 для спроби знайти ідеальну розмітку. Якщо це не вдається, наступним пробується VF2Layout.
  • VF2Layout: Це AnalysisPass, що вибирає ідеальну розмітку, трактуючи цей етап як задачу ізоморфізму підграфів, розв'язану алгоритмом VF2++. Якщо знайдено більше однієї розмітки, запускається евристична оцінка для вибору відображення з найнижчою середньою похибкою.

Потім для евристичного етапу за замовчуванням використовуються два проходи:

  • DenseLayout: Знаходить підграф пристрою з найбільшою зв'язністю, що має таку саму кількість кубітів, як і схема (використовується для рівня оптимізації 1, якщо в схемі є операції управляючого потоку, наприклад IfElseOp).
  • SabreLayout: Цей прохід вибирає розмітку, починаючи з початкової випадкової розмітки та багаторазово запускаючи алгоритм SabreSwap. Цей прохід використовується лише на рівнях оптимізації 1, 2 та 3, якщо ідеальна розмітка не знайдена за допомогою проходу VF2Layout. Детальніше про цей алгоритм дивись у статті arXiv:1809.02573.

Етап routing

Щоб реалізувати двокубітний вентиль між кубітами, які безпосередньо не з'єднані на квантовому пристрої, до схеми необхідно вставити один або кілька вентилів SWAP для переміщення станів кубітів до тих пір, поки вони не стануть суміжними на карті вентилів пристрою. Кожен вентиль SWAP є дорогою та шумною операцією для виконання. Тому пошук мінімальної кількості вентилів SWAP, необхідних для відображення схеми на даний пристрій, є важливим кроком у процесі транспіляції. З міркувань ефективності цей етап зазвичай за замовчуванням обчислюється разом з етапом Layout, але вони логічно відрізняються один від одного. Етап Layout вибирає апаратні кубіти для використання, тоді як етап Routing вставляє відповідну кількість вентилів SWAP для виконання схем з вибраною розміткою.

Однак пошук оптимального відображення SWAP є складним завданням. Фактично це NP-важка задача, і тому її обчислення є надто дорогим для всіх, крім найменших квантових пристроїв та вхідних схем. Щоб обійти це, Qiskit використовує стохастичний евристичний алгоритм SabreSwap для обчислення хорошого, але не обов'язково оптимального, відображення SWAP. Використання стохастичного методу означає, що генеровані схеми не гарантовано будуть однаковими при повторних запусках. Справді, повторне виконання однієї й тієї ж схеми дає розподіл глибин схем і кількості вентилів на виході. Саме тому багато користувачів вирішують запускати функцію маршрутизації (або весь StagedPassManager) багато разів і вибирати схеми з найменшою глибиною з розподілу результатів.

Наприклад, розглянемо 15-кубітну схему GHZ, виконану 100 разів із "поганою" (незв'язаною) initial_layout.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeAuckland, FakeWashingtonV2
from qiskit.transpiler import generate_preset_pass_manager

backend = FakeAuckland()

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

depths = []
for seed in range(100):
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial", # Fixed layout mapped in circuit order
seed_transpiler=seed, # For reproducible results
)
depths.append(pass_manager.run(ghz).depth())

plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)
Text(0, 0.5, 'Counts')

Output of the previous code cell

Цей широкий розподіл демонструє, наскільки важко маперу SWAP обчислити найкраще відображення. Щоб отримати деяке розуміння, розглянемо як схему, що виконується, так і кубіти, вибрані на апаратному забезпеченні.

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

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

Як видно, ця схема повинна виконати двокубітний вентиль між кубітами 0 та 14, які розташовані дуже далеко один від одного на графі зв'язності. Тому виконання цієї схеми вимагає вставки вентилів SWAP для виконання всіх двокубітних вентилів за допомогою проходу SabreSwap.

Зауваж також, що алгоритм SabreSwap відрізняється від ширшого методу SabreLayout на попередньому етапі. За замовчуванням SabreLayout виконує як розмітку, так і маршрутизацію, і повертає трансформовану схему. Це робиться з кількох конкретних технічних причин, зазначених на сторінці API-довідника проходу.

Етап translation

При написанні квантової схеми ти можеш використовувати будь-який квантовий вентиль (унітарну операцію), а також колекцію невентильних операцій, таких як інструкції вимірювання або скидання кубітів. Однак більшість квантових пристроїв нативно підтримують лише невелику кількість квантових вентильних і невентильних операцій. Ці нативні вентилі є частиною визначення ISA цілі, і цей етап наперед визначених PassManagers перекладає (або розгортає) вентилі, задані в схемі, до нативних базових вентилів вказаного бекенду. Це важливий крок, оскільки він дозволяє схемі виконуватися бекендом, але зазвичай призводить до збільшення глибини та кількості вентилів.

Два особливих випадки заслуговують на особливу увагу і допомагають проілюструвати, що робить цей етап.

  1. Якщо вентиль SWAP не є нативним вентилем цільового бекенду, для його реалізації потрібні три вентилі CNOT:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")
native gates:['cx', 'delay', 'for_loop', 'id', 'if_else', 'measure', 'reset', 'rz', 'switch_case', 'sx', 'x']

Output of the previous code cell

Будучи добутком трьох вентилів CNOT, SWAP є дорогою операцією для виконання на шумних квантових пристроях. Однак такі операції зазвичай необхідні для вбудовування схеми в обмежену зв'язність вентилів багатьох пристроїв. Тому мінімізація кількості вентилів SWAP у схемі є основною метою процесу транспіляції.

  1. Вентиль Тоффолі, або вентиль controlled-controlled-not (ccx), є трикубітним вентилем. Враховуючи, що наш базовий набір вентилів включає лише однокубітні та двокубітні вентилі, ця операція повинна бути розкладена. Однак це досить затратно:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output of the previous code cell

Для кожного вентиля Тоффолі в квантовій схемі апаратне забезпечення може виконувати до шести вентилів CNOT і кілька однокубітних вентилів. Цей приклад демонструє, що будь-який алгоритм, що використовує кілька вентилів Тоффолі, матиме велику глибину схеми і тому суттєво постраждає від шуму.

Етап optimization

Цей етап зосереджений на розкладенні квантових схем до базового набору вентилів цільового пристрою і повинен протистояти збільшенню глибини від етапів розмітки та маршрутизації. На щастя, існує багато підпрограм для оптимізації схем шляхом комбінування або видалення вентилів. У деяких випадках ці методи настільки ефективні, що вихідні схеми мають меншу глибину, ніж вхідні, навіть після розмітки та маршрутизації до топології апаратного забезпечення. В інших випадках мало що можна зробити, і обчислення може бути важким для виконання на шумних пристроях. Саме на цьому етапі різні рівні оптимізації починають відрізнятися.

Крім того, цей етап також виконує кілька фінальних перевірок, щоб переконатися, що всі інструкції в схемі складаються з базових вентилів, доступних на цільовому бекенді.

Наведений нижче приклад зі станом GHZ демонструє ефекти різних рівнів оптимізації на глибину схеми та кількість вентилів.

примітка

Результат транспіляції варіюється через стохастичний SWAP-мапер. Тому наведені нижче числа, ймовірно, зміняться кожного разу при запуску коду.

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

Планування

Цей останній етап виконується лише якщо він явно викликаний (аналогічно до етапу Init) і не запускається за замовчуванням (хоча метод можна задати, встановивши аргумент scheduling_method при виклику generate_preset_pass_manager). Етап планування зазвичай використовується після того, як схема перекладена до цільового базису, відображена на пристрій і оптимізована. Ці проходи зосереджені на врахуванні всього часу простою в схемі. На високому рівні прохід планування можна розглядати як явну вставку інструкцій затримки для обліку часу простою між виконанням вентилів та для перевірки тривалості виконання схеми на бекенді.

Ось приклад:

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

# Use fake backend
backend = FakeWashingtonV2()

# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
scheduling_method="asap",
seed_transpiler=1234,
)

circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)

Output of the previous code cell

Схема з інструкціями затримки

Транспілятор вставив інструкції Delay для обліку часу простою на кожному кубіті. Щоб краще зрозуміти часову структуру схеми, ми також можемо розглянути її за допомогою функції timeline.draw():

Вигляд тієї ж схеми через timeline.draw() Планування схеми складається з двох частин: аналізу та відображення обмежень, за яким слідує прохід заповнення. Перша частина вимагає виконання проходу аналізу планування (за замовчуванням це ALAPSchedulingAnalysis), який аналізує схему та записує час початку кожної інструкції в схемі в розклад. Після того як у схеми є початковий розклад, можна запустити додаткові проходи для врахування будь-яких часових обмежень на цільовому бекенді. Нарешті, може бути виконаний прохід заповнення, такий як PadDelay або PadDynamicalDecoupling.

Наступні кроки

Рекомендації