שלבי ה-Transpiler
גרסאות חבילות
הקוד בדף זה פותח עם הדרישות הבאות. אנחנו ממליצים להשתמש בגרסאות אלו או בגרסאות חדשות יותר.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
דף זה מתאר את השלבים של צינור הטרנספילציה המובנה ב-Qiskit SDK. ישנם שישה שלבים:
initlayoutroutingtranslationoptimizationscheduling
הפונקציה generate_preset_pass_manager יוצרת staged pass manager מובנה המורכב מהשלבים הללו. ה-passes הספציפיים המרכיבים כל שלב תלויים בארגומנטים שמועברים ל-generate_preset_pass_manager. ה-optimization_level הוא ארגומנט מיקומי שחובה לציין; הוא מספר שלם שיכול להיות 0, 1, 2 או 3. ערכים גבוהים יותר מעידים על אופטימיזציה כבדה יותר אך גם יקרה יותר (ראה ברירות מחדל וגדרות תצורה לטרנספילציה).
הדרך המומלצת לטרנספל מעגל היא ליצור staged pass manager מובנה ואז להריץ אותו על המעגל, כמתואר ב-Transpile with pass managers. עם זאת, חלופה פשוטה יותר אך פחות ניתנת להתאמה אישית היא להשתמש בפונקציה transpile. פונקציה זו מקבלת את המעגל ישירות כארגומנט. כמו עם generate_preset_pass_manager, ה-passes הספציפיים המשמשים תלויים בארגומנטים, כגון optimization_level, שמועברים ל-transpile. למעשה, פנימית פונקציית transpile קוראת ל-generate_preset_pass_manager כדי ליצור staged pass manager מובנה ומריצה אותו על המעגל.
שלב ה-Init
שלב ראשון זה עושה מעט מאוד כברירת מחדל והוא שימושי בעיקר אם רוצים לכלול אופטימיזציות ראשוניות משלך. מכיוון שרוב אלגוריתמי הפריסה והניתוב מתוכננים לעבוד רק עם שערים של Qubit יחיד ושני Qubits, שלב זה משמש גם לתרגום שערים שפועלים על יותר משני Qubits לשערים שפועלים רק על אחד או שניים.
למידע נוסף על יישום אופטימיזציות ראשוניות משלך לשלב זה, ראה את הסעיף על plugins והתאמה אישית של pass managers.
שלב ה-Layout
השלב הבא עוסק בפריסה או בקישוריות של ה-Backend שאליו ישלח מעגל. באופן כללי, מעגלים קוונטיים הם ישויות מופשטות שה-Qubits שלהן הם ייצוגים "וירטואליים" או "לוגיים" של Qubits פיזיים בפועל המשמשים בחישובים. כדי לבצע רצף של שערים, יש צורך במיפוי אחד-לאחד מ-Qubits "וירטואליים" ל-Qubits "פיזיים" במכשיר קוונטי ממשי. מיפוי זה מאוחסן כאובייקט Layout והוא חלק מהמגבלות המוגדרות בתוך ארכיטקטורת מערכת ההוראות (ISA) של Backend.

בחירת המיפוי חשובה ביותר למזעור מספר פעולות ה-SWAP הנדרשות למיפוי מעגל הקלט על טופולוגיית המכשיר ולהבטחת השימוש ב-Qubits המכוילים ביותר. בשל חשיבות שלב זה, ה-preset pass managers מנסים מספר שיטות שונות למציאת הפריסה הטובה ביותר. בדרך כלל זה כולל שני שלבים: ראשית, ניסיון למצוא פריסה "מושלמת" (פריסה שאינה דורשת פעולות SWAP), ולאחר מכן, pass היוריסטי שמנסה למצוא את הפריסה הטובה ביותר לשימוש אם לא ניתן למצוא פריסה מושלמת. ישנם שני Passes המשמשים בדרך כלל לשלב ראשון זה:
TrivialLayout: ממפה בצורה פשוטה כל Qubit וירטואלי ל-Qubit פיזי עם אותו מספר במכשיר (כלומר, [0,1,1,3] -> [0,1,1,3]). זוהי התנהגות היסטורית המשמשת רק ב-optimzation_level=1לניסיון למצוא פריסה מושלמת. אם הדבר נכשל,VF2Layoutינסה הבא.VF2Layout: זהוAnalysisPassשבוחר פריסה אידיאלית על ידי טיפול בשלב זה כבעיית איזומורפיזם תת-גרף, הנפתרת על ידי אלגוריתם VF2++. אם נמצאת יותר מפריסה אחת, מופעל היוריסטי ניקוד לבחירת המיפוי עם שגיאה ממוצעת נמוכה יותר.
לאחר מכן לשלב ההיוריסטי, משתמשים בשני passes כברירת מחדל:
DenseLayout: מוצא את תת-הגרף של המכשיר עם הקישוריות הגבוהה ביותר ושיש לו אותו מספר Qubits כמו המעגל (משמש לרמת אופטימיזציה 1 אם יש פעולות בקרת זרימה כגון IfElseOp הקיימות במעגל).SabreLayout: pass זה בוחר פריסה על ידי התחלה מפריסה אקראית ראשונית והפעלה חוזרת של אלגוריתםSabreSwap. pass זה משמש רק ברמות אופטימיזציה 1, 2 ו-3 אם לא נמצאת פריסה מושלמת דרך ה-VF2Layoutpass. לפרטים נוספים על אלגוריתם זה, עיין בעיתון arXiv:1809.02573.
שלב הניתוב
כדי לממש שער שני-Qubits בין Qubits שאינם מחוברים ישירות במכשיר קוונטי, יש להכניס שער SWAP אחד או יותר למעגל כדי להזיז את מצבי ה-Qubit עד שהם סמוכים במפת השערים של המכשיר. כל שער SWAP מייצג פעולה יקרה ורועשת לביצוע. לפיכך, מציאת מספר מינימלי של שערי SWAP הנדרשים למיפוי מעגל על מכשיר נתון היא שלב חשוב בתהליך הטרנספילציה. ליעילות, שלב זה מחושב בדרך כלל יחד עם שלב ה-Layout כברירת מחדל, אך הם נבדלים לוגית זה מזה. שלב ה-Layout בוחר את Qubits החומרה שישמשו, בעוד שלב ה-Routing מכניס את מספר שערי ה-SWAP המתאים כדי לבצע את המעגלים באמצעות הפריסה שנבחרה.
עם זאת, מציאת מיפוי ה-SWAP האופטימלי קשה. למעשה, זוהי בעיה NP-hard, ולכן יקרה מדי לחישוב עבור כל המכשירים הקוונטיים הקטנים ביותר ומעגלי הקלט. כדי לעקוף זאת, Qiskit משתמש באלגוריתם היוריסטי סטוכסטי הנקרא SabreSwap לחישוב מיפוי SWAP טוב, אך לא בהכרח אופטימלי. השימוש בשיטה סטוכסטית פירושו שהמעגלים שנוצרים אינם מובטחים להיות זהים בריצות חוזרות. אכן, הרצת אותו מעגל שוב ושוב מייצרת התפלגות של עומקי מעגל וספירות שערים בפלט. זו הסיבה שמשתמשים רבים בוחרים להריץ את פונקציית הניתוב (או את ה-StagedPassManager כולו) פעמים רבות ולבחור את המעגלים בעומק הנמוך ביותר מהתפלגות הפלטים.
למשל, נקח מעגל GHZ בן 15 Qubits המבוצע 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')
התפלגות רחבה זו מדגימה עד כמה קשה למיירט ה-SWAP לחשב את המיפוי הטוב ביותר. כדי לקבל תובנה, נסתכל גם על המעגל המבוצע וגם על ה-Qubits שנבחרו בחומרה.
ghz.draw("mpl", idle_wires=False)
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)
כפי שניתן לראות, מעגל זה צריך לבצע שער שני-Qubits בין Qubits 0 ו-14, שהם רחוקים מאוד זה מזה על גרף הקישוריות. הפעלת מעגל זה מצריכה לכן הכנסת שערי SWAP לביצוע כל שערי שני-ה-Qubits באמצעות ה-SabreSwap pass.
שים לב גם שאלגוריתם SabreSwap שונה משיטת SabreLayout הגדולה יותר בשלב הקודם. כברירת מחדל, SabreLayout מריץ גם פריסה וגם ניתוב, ומחזיר את המעגל המומר. הדבר נעשה מכמה סיבות טכניות מיוחדות המפורטות ב-דף הפניית ה-API של ה-pass.
שלב התרגום
בעת כתיבת מעגל קוונטי, אפשר להשתמ ש בכל שער קוונטי (פעולה יוניטרית) שרוצים, יחד עם אוסף פעולות שאינן שערים כגון מדידות Qubit או הוראות איפוס. עם זאת, רוב המכשירים הקוונטיים תומכים באופן ילידי רק בקומץ פעולות שערים קוונטיים ולא-שערים. שערים ילידיים אלה הם חלק מהגדרת ISA של יעד, ושלב זה של ה-PassManagers המובנים מתרגם (או פורס) את השערים שצוינו במעגל לשערי הבסיס הילידיים של Backend מוגדר. זהו שלב חשוב, שכן הוא מאפשר לבצע את המעגל על ידי ה-Backend, אך בדרך כלל מוביל לעלייה בעומק ובמספר השערים.
שני מקרים מיוחדים חשובים במיוחד להדגיש, ועוזרים להמחיש מה שלב זה עושה.
- אם שער SWAP אינו שער ילידי ל-Backend היעד, הדבר דורש שלושה שערי 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']
כמכפלה של שלושה שערי CNOT, SWAP הוא פעולה יקרה לביצוע על מכשירים קוונטיים רועשים. עם זאת, פעולות כאלה הכרחיות בדרך כלל לשילוב מעגל בתוך קישוריות שערים מוגבלת של מכשירים רבים. לפיכך, מזעור מספר שערי ה-SWAP במעגל הוא יעד ראשוני בתהליך הטרנספילציה.
- שער Toffoli, או שער controlled-controlled-not (
ccx), הוא שער של שלושה Qubits. בהתחשב שמערכת השערים הבסיסית שלנו כוללת רק שערים של Qubit יחיד ושני Qubits, פעולה זו חייבת להיות מפורקת. עם זאת, היא יקרה למדי:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")
עבור כל שער Toffoli במעגל קוונטי, החומרה עשויה לבצע עד שישה שערי CNOT וקומץ שערים חד-Qubit. דוגמה זו מדגימה שכל אלגוריתם המשתמש במספר שערי Toffoli יסתיים כמעגל בעומק גדול ולכן יושפע בצורה ניכרת מרעש.
שלב האופטימיזציה
שלב זה מתרכז בפירוק מעגלים קוונטיים לסט שערי הבסיס של המכשיר היעד, ועליו להתמודד עם העומק המוגבר משלבי הפריסה והניתוב. למרבה המזל, קיימות רבות שגרות לאופטימיזציה של מעגלים על ידי שילוב או ביטול שערים. במקרים מסוימים, שיטות אלו כה אפקטיביות עד שמעגלי הפלט בעומק נמוך מהקלט, גם לאחר פריסה וניתוב לטופולוגיית החומרה. במקרים אחרים, לא ניתן לעשות הרבה, והחישוב עשוי להיות קשה לביצוע על מכשירים רועשים. שלב זה הוא המקום שבו רמות האופטימיזציה השונות מתחילות להשתנות.
- עבור
optimization_level=1, שלב זה מכיןOptimize1qGatesDecompositionו-CXCancellation, המשלבים שרשרות של שערים חד-Qubit ומבטלים שערי CNOT גב-אל-גב. - עבור
optimization_level=2, שלב זה משתמש ב-passCommutativeCancellationבמקוםCXCancellation, שמסיר שערים מיותרים על ידי ניצול יחסי קומוטציה. - עבור
optimization_level=3, שלב זה מכין את ה-passes הבאים:
בנוסף, שלב זה גם מבצע מספר בדיקות סופיות כדי לוודא שכל ההוראות במעגל מורכבות מ-Gates הבסיסיים הזמינים ב-Backend היעד.
הדוגמה הבאה המשתמשת במצב GHZ מדגימה את ההשפעות של הגדרות רמת אופטימיזציה שונות על עומק המעגל וספירת השערים.
פלט הטרנספילציה משתנה בשל מיפוי SWAP הסטוכסטי. לפיכך, המספרים שלהלן ככל הנראה ישתנו בכל פעם שמריצים את הקוד.
הקוד הבא בונה מצב GHZ בן 15 Qubits ומשווה את optimization_levels של הטרנספילציה מבחינת עומק מעגל תוצאתי, ספירות שערים וספירות שערים רב-Qubit.
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()
תזמון
שלב אחרון זה מופעל רק אם הוא נדרש במפורש (בדומה לשלב ה-Init) ואינו פועל כברירת מחדל (אם כי ניתן לציין שיטה על ידי הגדרת הארגומנט scheduling_method בעת קריאה ל-generate_preset_pass_manager). שלב התזמון משמש בדרך כלל לאחר שהמעגל תורגם לבסיס היעד, מופה למכשיר, ואופטימז. ה-passes הללו מתמקדות בחשבון כל זמן הסרק במעגל. ברמה גבוהה, ניתן לחשוב על ה-scheduling pass כהכנסה מפורשת של הוראות עיכוב כדי לחשב את זמן הסרק בין ביצועי שערים ולבדוק כמה זמן המעגל יפעל על ה-Backend.
הנה דוגמה:
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)
ה-Transpiler הכניס הוראות Delay כדי לחשב את זמן הסרק על כל Qubit. כדי לקבל מושג טוב יותר על תזמון המעגל, ניתן גם להסתכל עליו עם הפונקציה timeline.draw():
תזמון מעגל כולל שני חלקים: ניתוח ומיפוי אילוצים, ואחריהם padding pass. החלק הראשון דורש הפעלת scheduling analysis pass (כברירת מחדל זהו
ALAPSchedulingAnalysis), שמנתח את המעגל ורושם את זמן ההתחלה של כל הוראה במעגל לתוך לוח זמנים. לאחר שלמעגל יש לוח זמנים ראשוני, ניתן להריץ passes נוספים כדי לחשב כל אילוצי תזמון ב-Backend היעד. לבסוף, ניתן לבצע padding pass כגון PadDelay או PadDynamicalDecoupling.
השלבים הבאים
- כדי ללמוד כיצד להשתמש בפונקציה
generate_preset_passmanager, התחל עם נושא הגדרות ברירת מחדל ואפשרויות תצורה לטרנספילציה. - המשך ללמוד על טרנספילציה עם הנושא Transpiler with pass managers.
- נסה את המדריך Compare transpiler settings.
- ראה את תיעוד ה-API של Transpile.