דלג לתוכן הראשי

מעבר מ-Sampler ו-Estimator בצד השרת לצד הלקוח

מדריך זה מתאר כיצד לעבור מהמימושים בצד השרת של IBM Quantum® Sampler ו-Estimator למימושים החדשים שלהם בצד הלקוח ב- qiskit-ibm-runtime. הממשקים והאפשרויות נשארים ברובם ללא שינוי, כך שרוב הקוד רץ כמות שהוא, אך יש כמה הבדלים התנהגותיים שכדאי להבין.

רקע​

Sampler ו-Estimator הם ממשקי primitive שמוגדרים ב-Qiskit. IBM Quantum Compute Service (בעבר Qiskit Runtime) סיפק היסטורית את המימוש של primitives אלה בתוך סביבת זמן הריצה שלו. כאשר אתה קורא ל-sampler.run() או ל-estimator.run(), הבקשה נשלחת לשירות, וכל החישוב — כולל דיכוי שגיאות והפחתת שגיאות — מתבצע בצד השרת.

חוויית קופסה שחורה זו נוחה: אינך צריך לדאוג לפרטי המימוש. אבל היא גם מקשה על debug, התאמה אישית, או למידה מה-primitives, מכיוון שאינך יכול לראות מה קורה במהלך העיבוד.

מודל הביצוע המכוון שהוצג לאחרונה נוקט בגישה הפוכה ו מספק חוויית קופסה שקופה. כל כוונות העיצוב נלכדות בצד הלקוח, ו-primitive יחיד בצד השרת, Executor, מעבד את הקלטים האלה בדיוק כפי שהונחה — הוא אינו מקבל החלטות מרומזות מטעמך.

החל מ-qiskit-ibm-runtime v0.50.0, Sampler ו-Estimator ממומשים מחדש בצד הלקוח מעל Executor. הם מספקים את אותה נוחות והפשטה כמו קודם, ועכשיו אתה יכול לבדוק את פרטי המימוש כשאתה צריך זאת. מכיוון שהממשקים והאפשרויות נשארים ברובם זהים, המעבר אמור להיות חלק.

הערה: IBM Quantum תומך רק בגרסה 2 של ממשקי Sampler ו-Estimator (BaseSamplerV2 ו-BaseEstimatorV2). לכן, במדריך זה הם מכונים פשוט Sampler ו-Estimator.

עדכן את ה-imports​

כיום, עליך לייבא באופן מפורש את המימושים החדשים מהמודולים הייעודיים שלהם:

from qiskit_ibm_runtime.executor_sampler import Sampler
from qiskit_ibm_runtime.executor_estimator import Estimator

בעתיד הקרוב, ה-imports ברמה העליונה יפנו למימושים החדשים בצד הלקוח, ולא יידרש שינוי קוד:

# Coming soon — the following code will import the new client-side implementations.
from qiskit_ibm_runtime import Sampler, Estimator

באופן דומה, אם אתה בונה אובייקטי options מסוג מוקלד, עליך לייבא אותם מ- qiskit_ibm_runtime.options_models במקום זאת, או פשוט להעביר dict מקונן פשוט:

from qiskit_ibm_runtime.options_models import SamplerOptions, EstimatorOptions

מה נשאר זהה​

  • בניית primitive עם mode ו-options.

  • החתימה של run() ותבנית PUB.

  • עץ האפשרויות (options.twirling, options.resilience, options.default_shots, וכן הלאה).

  • מבנה נתוני התוצאה שמוחזר על ידי job.result().

שינויים לא תואמים ב-Sampler החדש​

שינויפעולת מעבר
ה-primitive הבסיסי הוא כעת Executor. גם ממשק המשתמש של IBM Quantum Platform וגם job.primitive_id יציגו executor במקום sampler.עדכן כל קוד שמפנה ל-job.primitive_id.
המימוש החדש ממפה קלטי Sampler לקלטי Executor, כך ש-job.inputs מחזיר קלטי Executor.עדכן כל קוד שמפנה ל-job.inputs. ראה קלטי job.
יותר עיבוד מקדים ומאוחר מתבצע כעת בצד הלקוח, כך ש-sampler.run() ו-job.result() עשויים לקחת יותר זמן מבעבר.הפעל רישום ברמת INFO כדי לעקוב אחר ההתקדמות של העיבוד בצד הלקוח. ראה הפעלת רישום INFO.
מטא-נתוני מעגל מועתקים למטא-נתוני התוצאה. סוגי הנתונים המותרים במטא-נתוני התוצאה מוגבלים כעת ל-str, float, int, bool, ורשימות או מילונים של סוגים אלה.אם אתה זקוק לסוגי נתונים אחרים, קודד אותם כמחרוזת תחילה (לדוגמה, עם base64).
מחלקות אפשרויות (options_models.SamplerOptions וכן הלאה) הן כעת מודלים של Pydantic במקום dataclasses, כך שכבר לא ניתן להמיר אותן למילוני Python באמצעות asdict().השתמש ב-options.model_dump() במקום זאת.
מחלקות אפשרויות שבעבר היה להן הסיומת V2 (ExecutionOptionsV2 וכן הלאה) כבר לא, מכיוון ש-primitives מגרסה V1 כבר לא נתמכים.הסר את הסיומת V2 ממחלקות אפשרויות אלה: החלף את ExecutionOptionsV2 ב-ExecutionOptions, את ResilienceOptionsV2 ב-ResilienceOptions, ואת SamplerExecutionOptionsV2 ב-SamplerExecutionOptions.
אם twirling מופעל ו-shots (ב-PUBs או ב-run()), shots_per_randomization, ו-num_randomizations כולם מוגדרים, אז num_randomizations * shots_per_randomization גובר על shots.השמט את num_randomizations ו-shots_per_randomization אם ברצונך שערך shots ישמש.
חלק מאימות הקלט עבר לצד השרת וכעת מעלה RuntimeError במקום IBMInputValueError.עדכן את סוגי החריגות שהקוד שלך תופס.
ערכי shot מעורבים ב-job בודד כבר לא נתמכים.הגש job נפרד עבור כל ערך shot. ראה פיצול job לשיקולים.

שינויים לא תואמים ב-Estimator החדש​

שינויפעולת מעבר
ה-primitive הבסיסי הוא כעת Executor. גם ממשק המשתמש של IBM Quantum Platform וגם job.primitive_id יציגו executor במקום estimator.עדכן כל קוד שמפנה ל-job.primitive_id.
המימוש החדש ממפה קלטי Estimator לקלטי Executor, כך ש-job.inputs מחזיר קלטי Executor.עדכן כל קוד שמפנה ל-job.inputs. ראה קלטי job.
יותר עיבוד מקדים ומאוחר מתבצע כעת בצד הלקוח, כך ש-estimator.run() ו-job.result() עשויים לקחת יותר זמן מבעבר.הפעל רישום ברמת INFO כדי לעקוב אחר ההתקדמות של העיבוד בצד הלקוח. ראה הפעלת רישום INFO.
מטא-נתוני מעגל מועתקים למטא-נתוני התוצאה. סוגי הנתונים המותרים במטא-נתוני התוצאה מוגבלים כעת ל-str, float, int, bool, ורשימות או מילונים של סוגים אלה.אם אתה זקוק לסוגי נתונים אחרים, קודד אותם כמחרוזת תחילה (לדוגמה, עם base64).
מחלקות אפשרויות (options_models.EstimatorOptions וכן הלאה) הן כעת מודלים של Pydantic במקום dataclasses, כך שכבר לא ניתן להמיר אותן למילוני Python באמצעות asdict().השתמש ב-options.model_dump() במקום זאת.
מחלקות אפשרויות שבעבר היה להן הסיומת V2 (ExecutionOptionsV2 וכן הלאה) כבר לא, מכיוון ש-primitives מגרסה V1 כבר לא נתמכים.הסר את הסיומת V2 ממחלקות אפשרויות אלה: החלף את ExecutionOptionsV2 ב-ExecutionOptions ואת ResilienceOptionsV2 ב-ResilienceOptions.
כל אפשרויות הקלט מוחזרות במטא-נתוני התוצאה, במקום תת-קבוצה נבחרת.אין — זהו מידע בלבד.
חלק מאימות הקלט עבר לצד השרת וכעת מעלה RuntimeError במקום IBMInputValueError.עדכן את סוגי החריגות שהקוד שלך תופס.
אין יותר למידת רעש מרומזת עבור PEA ו-PEC. למידת רעש מדידה עבור TREX עדיין נתמכת.למד את מודלי הרעש בנפרד והעבר אותם ל-Estimator. ראה ביצוע למידת רעש מפורשת עבור PEA ו-PEC.
סוג הקלט של ResilienceOptions.layer_noise_model שונה וניתן לבנות אותו מתוצאות NoiseLearnerV3.ראה ביצוע למידת רעש מפורשת עבור PEA ו-PEC כיצד ללמוד את מודלי הרעש באמצעות NoiseLearnerV3 ולהעביר אותם ל-Estimator.
MeasureNoiseLearningOptions.shots_per_randomization כבר לא נתמך.ערך shot יחיד משמש עבור כל המעגלים ב-job, כולל מעגלי למידת רעש מדידה. אם עליך להשתמש בערך shot שונה, החל TREX עם qiskit-mitigation מחוץ ל-Estimator.
ערכי דיוק מעורבים ב-job בודד כבר לא נתמכים.הגש job נפרד עבור כל דיוק רצוי. ראה פיצול job לשיקולים.
האפשרות seed_estimator כבר לא נתמכת.הסר כל הקצאה של options.seed_estimator (הגדרתה מעלה ValidationError). אין מקבילה בצד הלקוח, כך שהתוצאות כבר לא ניתנות לשחזור באמצעות seed זה.

הפעלת רישום INFO​

מכיוון שיותר עבודה מתבצעת כעת בצד הלקוח, שימושי לראות את ההתקדמות של עיבוד זה. הפעל רישום ברמת INFO עבור ה-logger qiskit_ibm_runtime:

import logging

logger = logging.getLogger("qiskit_ibm_runtime")
logger.setLevel(logging.INFO)

ביצוע למידת רעש מפורשת עבור PEA ו-PEC​

ה-Estimator החדש כבר לא מבצע למידת רעש מרומזת כאשר שיטת הפחתת השגיאות PEA או PEC נבחרת. עליך ללמוד את מודלי הרעש באופן מפורש ולהעביר אותם פנימה. השתמש ב-NoiseLearnerV3 החדש כדי לשלוט כיצד מעגלים מחולקים לשכבות. הוא מקבל רשימה של הוראות מעגל ממוסגרות (לדוגמה, השכבות הייחודיות) כקלט.

חשוב

PEA ו-PEC כעת דורשים את התבנית המפורשת הזו. אל תדלג על שלב למידת הרעש אחרת הקוד שלך ייכשל. למידת רעש מדידה עבור TREX אינה מושפעת וממשיכה לעבוד כרגיל.

באופן דומה, אם הקוד שלך משתמש ב-NoiseLearner ומעביר את מודל הרעש המתקבל ל-Estimator בצד השרת, עליך לעבור ל-NoiseLearnerV3. אל תשתמש ב-NoiseLearner הישן יותר, שאינו תואם ל-Estimator החדש.

כל אפשרויות למידת הרעש ב-Estimator בצד השרת (LayerNoiseLearningOptions) ממופות ישירות לאפשרות NoiseLearnerV3 (NoiseLearnerV3Options), למעט max_layers_to_learn. מספר השכבות שיש ללמוד מבוסס במקום זאת על מספר השכבות שהועברו ל-NoiseLearnerV3.

לדוגמה:

Estimator בצד השרת (עם PEC מופעל):

from qiskit_ibm_runtime import Estimator

pubs = [...] # Your PUBs
estimator = Estimator(mode, options)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier
estimator.options.resilience.layer_noise_learning.num_randomizations = 64

job = estimator.run(pubs)

Estimator בצד הלקוח (עם PEC מופעל):

from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import NoiseLearnerV3

pubs = [...] # Your PUBs
estimator = Estimator(mode, options)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier

# Identify the unique layers to learn.
layers = estimator.find_unique_layers(pubs)

# Learn the noise model for those layers (runs as a separate job).
learner = NoiseLearnerV3(mode)
learner.options.num_randomizations = 64 # Same as layer_noise_learning.num_randomizations
learner_job = learner.run(layers)
learner_result = learner_job.result()

# Convert results to Pauli-Lindblad noise maps.
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps so PEA/PEC uses them.
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

# Now execute the target PUBs.
job = estimator.run(pubs)

מעבר מ-NoiseLearner ל-NoiseLearnerV3​

NoiseLearner עובד רק עם המימוש בצד השרת של Estimator. לכן, אם הקוד שלך משתמש ב-NoiseLearner כדי ללמוד את מודל הרעש ולהעביר אותו ל-Estimator, עליך לעדכן את הקוד שלך כדי להשתמש ב-NoiseLearnerV3.

ראה את המדריך מעבר מ-NoiseLearner ל-NoiseLearnerV3 לפרטים.

פיצול job​

כאשר עליך לפצל job אחד למספר jobs מכיוון שערכי shot או דיוק מעורבים ב-job בודד כבר לא נתמכים, שקול את הדברים הבאים:

  • קבץ את ה-PUBs לפי ערך היעד שלהם — job אחד לכל ערך ייחודי, לא job אחד לכל PUB. פיצול הוא קיבוץ מחדש, כך שמספר ה-PUBs הכולל שאתה מגיש לא משתנה. לדוגמה, בהינתן [A@0.01, B@0.05, C@0.01], הגש שני jobs: [A, C] ב-precision=0.01 ו-[B] ב-precision=0.05. הגשת A ו-C כ-jobs נפרדים פחות יעילה, מכיוון שכל job מגיע עם עלות תקורה קבועה.

  • למד פעם אחת והשתמש במודלי הרעש בכל ה-jobs המפוצלים. יעיל יותר להריץ job אחד של NoiseLearnerV3 על איחוד כל השכבות. התוצאה של job למידת רעש מכילה רשימה של אובייקטי NoiseLearnerV3Result, אחד עבור כל הוראת קלט, ובאותו סדר כמו רשימת הקלט. אתה יכול להשתמש בפלט של job למידת הרעש הזה בכל ה-jobs המפוצלים (Estimator), ומודלי רעש עבור שכבות שאינן ב-PUBs של job מפוצל מתעלמים מהם.

  • הגש קודם את כל ה-jobs המפוצלים ב-Batch, ורק אז אסוף את תוצאותיהם. מצב הביצוע Batch מספק ביצוע מקבילי יעיל כאשר יש מספר jobs. עם זאת, job.result() חוסם, כך שקריאה לו בתוך לולאת ההגשה הופכת את ה-jobs לסדרתיים ומבטלת את היתרונות של שימוש ב-Batch. וודא שאתה משתמש בתבנית של הגשת-הכל-ואז-איסוף (מוצגת למטה).

בדוגמה הבאה, pub1 ו-pub2 דורשים precision=0.5, בעוד ש-pub3 דורש precision=0.1:

group1_pubs = [pub1, pub2]
group2_pubs = [pub3]

with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.resilience.pec_mitigation = True

# Learn once, over the union of every job's layers.
all_layers = estimator.find_unique_layers(group1_pubs + group2_pubs)
learner_job = NoiseLearnerV3(mode=batch).run(all_layers)
learner_result = learner_job.result()
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps. Any layers not found in the input PUBs are ignored.
estimator.options.resilience.layer_noise_model = zip(all_layers, pauli_lindblad_maps)

# Submit every split job with different precision values.
jobs = []
jobs.append(estimator.run(group1_pubs, precision=0.5))
jobs.append(estimator.run(group2_pubs, precision=0.1))

# Block once, at the end — the jobs run in parallel.
results = [job.result() for job in jobs]

מבנה קלטי job​

המימוש החדש ממפה קלטי Sampler או Estimator לקלטי Executor, כך ש-job.inputs מחזיר מילון שמכיל קלטי Executor. למילון זה יש את המפתחות הבאים:

  • options: קלט ה-ExecutorOption.

  • quantum_program: קלט ה-QuantumProgram

  • schema_version: גרסת הסכימה בצד השרת שבה נעשה שימוש.

אם הקוד שלך השתמש ב-job.inputs['options'] כדי למצוא אפשרויות שצוינו עבור ה-job, אתה יכול כעת להשתמש ב-job.result().metadata['options'] במקום זאת.

בדיקה מקומית עם backend מדומה​

לפני הגשה לחומרה, אתה יכול לאמת את הקוד שעבר מעבר מול backend Fake* כדי לתפוס שגיאות תחביר מוקדם. שים לב לפרטים הבאים לגבי מצב בדיקה מקומי:

  • זה לא משחזר תוצאות חומרה. סימולציה רועשת מקומית לא משחזרת באופן מושלם את הרעש של המכשיר האמיתי, ולכן הפלטים עשויים להיות שונים. ההרצה כן מאמתת שנתיבי האפשרויות וסוגי הערכים נכונים.

  • ל-NoiseLearnerV3 אין מצב בדיקה מקומי: ה-mode שלו מקבל רק Backend אמיתי, Session, או Batch, כך שאתה לא יכול להפעיל את שלב למידת הרעש מול backend מדומה. אמת את החלק הזה של הקוד שלך מול מסמכי ה-API של NoiseLearnerV3 במקום זאת. ודא שהבנאי, צורת הקלט של run(instructions), וכל helper (כגון ה-helper של שכבה ייחודית) משמשים כפי שמתועד.

הפוך את המעגל ל-Clifford לסימולציה מקומית יעילה​

backend מדומה משתמש בסימולטור statevector (רועש), שהעלות שלו גדלה באופן אקספוננציאלי עם מספר הקיובטים והעומק. לכן, מעגל עומס עבודה ריאליסטי עלול להיתקע או לגמור זיכרון. מכיוון שבדיקה מקומית צריכה רק להפעיל את נתיבי האפשרויות (לא לשחזר תוצאות פיזיקליות), צמצם את המעגל למעגל Clifford תחילה עם ConvertISAToClifford, שמעגל כל זווית RZ/RZZ/RX לכפולה הקרובה ביותר של π/2. מעגלי Clifford מבצעים סימולציה ביעילות (סימולציית stabilizer) ללא קשר לגודל.

from qiskit.transpiler import PassManager
from qiskit_ibm_runtime.transpiler.passes import ConvertISAToClifford

clifford = PassManager([ConvertISAToClifford()]).run(isa_circuit)
# run `clifford` (not the original) through the fake-backend primitive

ConvertISAToClifford דורש מעגל ISA כקלט (הפלט של generate_preset_pass_manager(...).run(...) שמכוון ל-backend). עליך לקחת בחשבון את ההשלכות הבאות כאשר אתה בונה את ה-PUB המקומי:

  • התכונה .layout נשמטת. המעגל שהפך ל-Clifford שומר על אותו מספר קיובטים, אך clifford.layout הוא None, כך ש-observable.apply_layout(clifford.layout) נכשל. פרוס את ה-observable מהמעגל ISA שלפני ה-Clifford במקום זאת: isa_obs = observable.apply_layout(isa_circuit.layout), ואז הרץ (clifford, isa_obs).

  • פרמטרים נקשרים ומוסרים. עיגול זוויות הסיבוב הופך מעגל ISA פרמטרי למעגל Clifford קונקרטי, כך ש-clifford.num_parameters הופך ל-0. PUB שעדיין נושא מערך ערכי פרמטרים נכשל בהמרה. עבור ההרצה המקומית, הסר את מערך הפרמטרים מה-PUB; ההרצה בחומרה שומרת על המעגל הפרמטרי המקורי וערכיו.

הצעדים הבאים​