סקירה כללית
מדריך זה מניח היכרות עם TensorFlow Profiler ו- tf.data
. מטרתו היא לספק הוראות שלב אחר שלב עם דוגמאות כדי לעזור למשתמשים לאבחן ולתקן בעיות בביצועי צינור הקלט.
כדי להתחיל, אסוף פרופיל של עבודת TensorFlow שלך. הוראות כיצד לעשות זאת זמינות עבור CPUs/GPUs ו- Cloud TPUs .
זרימת העבודה של הניתוח המפורטת להלן מתמקדת בכלי מציג המעקב ב-Profiler. כלי זה מציג ציר זמן המציג את משך הפעולות שבוצעו על ידי תוכנית TensorFlow שלך ומאפשר לך לזהות אילו פעולות לוקח הכי הרבה זמן לביצוע. למידע נוסף על מציג המעקב, עיין בסעיף זה במדריך TF Profiler. באופן כללי, אירועי tf.data
יופיעו בציר הזמן של המעבד המארח.
זרימת עבודה של ניתוח
אנא עקוב אחר זרימת העבודה למטה. אם יש לך משוב שיעזור לנו לשפר אותו, אנא צור בעיית github עם התווית "comp:data".
1. האם צינור tf.data
שלך מייצר נתונים מהיר מספיק?
התחל על ידי בירור אם צינור הקלט הוא צוואר הבקבוק עבור תוכנית TensorFlow שלך.
כדי לעשות זאת, חפש את IteratorGetNext::DoCompute
ops במציג המעקב. באופן כללי, אתה מצפה לראות אותם בתחילת שלב. פרוסות אלה מייצגות את הזמן שלוקח לצינור הקלט שלך להניב אצווה של אלמנטים כאשר היא מתבקשת. אם אתה משתמש ב-keras או חוזר על מערך הנתונים שלך ב- tf.function
, אלה אמורים להימצא בשרשורים של tf_data_iterator_get_next
.
שים לב שאם אתה משתמש באסטרטגיית הפצה , ייתכן שתראה אירועי IteratorGetNextAsOptional::DoCompute
במקום IteratorGetNext::DoCompute
(נכון ל-TF 2.3).
אם השיחות חוזרות במהירות (<= 50 לנו), זה אומר שהנתונים שלך זמינים כאשר הם מתבקשים. צינור הקלט אינו צוואר הבקבוק שלך; עיין במדריך Profiler לקבלת עצות כלליות יותר לניתוח ביצועים.
אם השיחות חוזרות לאט, tf.data
לא מסוגלת לעמוד בקצב הבקשות של הצרכן. המשך לסעיף הבא.
2. האם אתה שולף נתונים מראש?
השיטה הטובה ביותר לביצועי צינור קלט היא להוסיף טרנספורמציה של tf.data.Dataset.prefetch
בסוף צינור ה- tf.data
שלך. טרנספורמציה זו חופפת את חישוב העיבוד המקדים של צינור הקלט עם השלב הבא של חישוב המודל ונדרשת לביצועים מיטביים של צינור הקלט בעת אימון המודל שלך. אם אתה שולף נתונים מראש, אתה אמור לראות פרוסת Iterator::Prefetch
באותו שרשור כמו ה- IteratorGetNext::DoCompute
op.
אם אין לך prefetch
בסוף הצינור שלך , עליך להוסיף אחד. למידע נוסף על המלצות ביצועים tf.data
, עיין במדריך הביצועים של tf.data .
אם אתה כבר שולף נתונים מראש , וצינור הקלט הוא עדיין צוואר הבקבוק שלך, המשך לסעיף הבא לניתוח ביצועים נוסף.
3. האם אתה מגיע לניצול מעבד גבוה?
tf.data
משיגה תפוקה גבוהה על ידי ניסיון לעשות את השימוש הטוב ביותר במשאבים הזמינים. באופן כללי, גם כאשר מריצים את הדגם שלך על מאיץ כמו GPU או TPU, צינורות tf.data
מופעלים על ה-CPU. אתה יכול לבדוק את השימוש שלך עם כלים כמו sar ו- htop , או במסוף הניטור בענן אם אתה פועל על GCP.
אם הניצול שלך נמוך, זה מצביע על כך שצינור הקלט שלך עשוי לא לנצל את מלוא ה-CPU המארח. עליך לעיין במדריך הביצועים של tf.data לקבלת שיטות עבודה מומלצות. אם יישמת את השיטות המומלצות והניצול והתפוקה נשארים נמוכים, המשך לניתוח צוואר הבקבוק להלן.
אם הניצול שלך מתקרב למגבלת המשאבים , כדי לשפר את הביצועים עוד יותר, עליך לשפר את היעילות של צינור הקלט שלך (לדוגמה, הימנעות מחישוב מיותר) או חישוב הורדה.
אתה יכול לשפר את היעילות של צינור הקלט שלך על ידי הימנעות מחישוב מיותר ב- tf.data
. אחת הדרכים לעשות זאת היא הכנסת טרנספורמציה tf.data.Dataset.cache
לאחר עבודה עתירת חישוב אם הנתונים שלך מתאימים לזיכרון; זה מפחית את החישוב במחיר של שימוש מוגבר בזיכרון. בנוסף, לנטרול מקביליות תוך-אופ ב- tf.data
יש פוטנציאל להגדיל את היעילות ב->10%, וניתן לעשות זאת על ידי הגדרת האפשרות הבאה בצינור הקלט שלך:
dataset = ...
options = tf.data.Options()
options.experimental_threading.max_intra_op_parallelism = 1
dataset = dataset.with_options(options)
4. ניתוח צוואר בקבוק
הסעיף הבא יסביר כיצד לקרוא אירועי tf.data
במציג המעקב כדי להבין היכן צוואר הבקבוק ואסטרטגיות הפחתה אפשריות.
הבנת אירועי tf.data
ב-Profiler
לכל אירוע tf.data
ב-Profiler יש את השם Iterator::<Dataset>
, כאשר <Dataset>
הוא השם של מקור הנתונים או הטרנספורמציה. לכל אירוע יש גם את השם הארוך Iterator::<Dataset_1>::...::<Dataset_n>
, אותו תוכלו לראות על ידי לחיצה על האירוע tf.data
. בשם הארוך, <Dataset_n>
תואם את <Dataset>
מהשם (הקצר), ושאר מערכי הנתונים בשם הארוך מייצגים טרנספורמציות במורד הזרם.
לדוגמה, צילום המסך לעיל נוצר מהקוד הבא:
dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
כאן, לאירוע Iterator::Map
יש את השם הארוך Iterator::BatchV2::FiniteRepeat::Map
. שים לב ששם מערכי הנתונים עשוי להיות שונה במקצת מה-API של python (לדוגמה, FiniteRepeat במקום Repeat), אבל צריך להיות מספיק אינטואיטיבי כדי לנתח.
טרנספורמציות סינכרוניות וא-סינכרוניות
עבור טרנספורמציות tf.data
סינכרוניות (כגון Batch
Map
), תראה אירועים מתמורות במעלה הזרם באותו שרשור. בדוגמה שלמעלה, מכיוון שכל הטרנספורמציות בהן נעשה שימוש הן סינכרוניות, כל האירועים מופיעים באותו שרשור.
עבור טרנספורמציות אסינכרוניות (כגון Prefetch
, ParallelMap
, ParallelInterleave
ו- MapAndBatch
) אירועים מהטרנספורמציות במעלה הזרם יהיו בשרשור אחר. במקרים כאלה, ה"שם הארוך" יכול לעזור לך לזהות לאיזו טרנספורמציה בצנרת אירוע מתאים.
לדוגמה, צילום המסך לעיל נוצר מהקוד הבא:
dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
dataset = dataset.prefetch(1)
כאן, אירועי Iterator::Prefetch
נמצאים באשכולות tf_data_iterator_get_next
. מכיוון ש- Prefetch
הוא אסינכרוני, אירועי הקלט שלו ( BatchV2
) יהיו בשרשור אחר, וניתן לאתר אותם על ידי חיפוש השם הארוך Iterator::Prefetch::BatchV2
. במקרה זה, הם נמצאים בשרשור tf_data_iterator_resource
. מהשם הארוך שלו, אתה יכול להסיק ש- BatchV2
נמצא במעלה הזרם של Prefetch
. יתר על כן, ה- parent_id
של אירוע BatchV2
יתאים לזיהוי של אירוע Prefetch
.
זיהוי צוואר הבקבוק
באופן כללי, כדי לזהות את צוואר הבקבוק בצינור הקלט שלך, צעד בצינור הקלט מהטרנספורמציה החיצונית ביותר ועד למקור. החל מהטרנספורמציה הסופית בצינור שלך, חזור על טרנספורמציות במעלה הזרם עד שתמצא טרנספורמציה איטית או תגיע למערך נתונים מקור, כגון TFRecord
. בדוגמה שלמעלה, תתחיל מ- Prefetch
, ואז תלך במעלה הזרם אל BatchV2
, FiniteRepeat
, Map
, ולבסוף Range
.
באופן כללי, טרנספורמציה איטית מתאימה למי שהאירועים שלו ארוכים, אבל אירועי הקלט שלו קצרים. להלן כמה דוגמאות.
שימו לב שהטרנספורמציה הסופית (החיצונית) ברוב קווי הקלט המארח היא Iterator::Model
. הטרנספורמציה של המודל מוצגת באופן אוטומטי על ידי זמן הריצה tf.data
ומשמשת למכשיר וכיוונון אוטומטי של ביצועי צינור הקלט.
אם העבודה שלך משתמשת באסטרטגיית הפצה , מציג המעקב יכיל אירועים נוספים התואמים לצינור הקלט של המכשיר. הטרנספורמציה החיצונית ביותר של צינור המכשיר (המקוננת תחת IteratorGetNextOp::DoCompute
או IteratorGetNextAsOptionalOp::DoCompute
) תהיה אירוע Iterator::Prefetch
עם אירוע Iterator::Generator
במעלה הזרם. אתה יכול למצוא את צינור המארח המתאים על ידי חיפוש אחר Iterator::Model
events.
דוגמה 1
צילום המסך לעיל נוצר מצינור הקלט הבא:
dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record)
dataset = dataset.batch(32)
dataset = dataset.repeat()
בצילום המסך, שים לב ש-(1) אירועי Iterator::Map
הם ארוכים, אך (2) אירועי הקלט שלו ( Iterator::FlatMap
) חוזרים במהירות. זה מצביע על כך שהשינוי ברצף של המפה הוא צוואר הבקבוק.
שימו לב שבצילום המסך, אירוע InstantiatedCapturedFunction::Run
מתאים לזמן שלוקח לביצוע פונקציית המפה.
דוגמה 2
צילום המסך לעיל נוצר מצינור הקלט הבא:
dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record, num_parallel_calls=2)
dataset = dataset.batch(32)
dataset = dataset.repeat()
דוגמה זו דומה לאמור לעיל, אך משתמשת ב-ParallelMap במקום במפה. אנו מבחינים כאן כי (1) אירועי Iterator::ParallelMap
הם ארוכים, אך (2) אירועי הקלט שלו Iterator::FlatMap
(שנמצאים בשרשור אחר, מכיוון ParallelMap הוא אסינכרוני) הם קצרים. זה מצביע על כך שהטרנספורמציה של ParallelMap היא צוואר הבקבוק.
טיפול בצוואר הבקבוק
מערכי נתונים של מקור
אם זיהית מקור מערך נתונים כצוואר הבקבוק, כגון קריאה מקובצי TFRecord, תוכל לשפר את הביצועים על ידי הפקת נתונים במקביל. לשם כך, ודא שהנתונים שלך מחולקים על פני מספר קבצים והשתמש ב- tf.data.Dataset.interleave
עם הפרמטר num_parallel_calls
מוגדר ל- tf.data.AUTOTUNE
. אם הדטרמיניזם אינו חשוב לתוכנית שלך, תוכל לשפר עוד יותר את הביצועים על ידי הגדרת הדגל deterministic=False
ב- tf.data.Dataset.interleave
החל מ-TF 2.2. לדוגמה, אם אתה קורא מ-TFRecords, אתה יכול לעשות את הפעולות הבאות:
dataset = tf.data.Dataset.from_tensor_slices(filenames)
dataset = dataset.interleave(tf.data.TFRecordDataset,
num_parallel_calls=tf.data.AUTOTUNE,
deterministic=False)
שים לב שקבצים מרוסקים צריכים להיות גדולים למדי כדי להפחית את התקורה של פתיחת קובץ. לפרטים נוספים על מיצוי נתונים מקבילים, עיין בסעיף זה של מדריך הביצועים tf.data
.
מערכי נתונים של טרנספורמציה
אם זיהית טרנספורמציה ביניים של tf.data
כצוואר הבקבוק, תוכל לטפל בה על ידי הקבילה של הטרנספורמציה או שמירה במטמון של החישוב אם הנתונים שלך מתאימים לזיכרון וזה מתאים. לחלק מהטרנספורמציות כמו Map
יש מקבילות; מדריך הביצועים tf.data
מדגים כיצד להקביל אותם. טרנספורמציות אחרות, כגון Filter
, Unbatch
ו- Batch
הן מטבען עוקבות; אתה יכול להקביל אותם על ידי הצגת "מקביליות חיצונית". לדוגמה, נניח שצינור הקלט שלך נראה בהתחלה כמו הבא, עם Batch
כצוואר הבקבוק:
filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
dataset = filenames_to_dataset(filenames)
dataset = dataset.batch(batch_size)
אתה יכול להציג "מקביליות חיצונית" על ידי הפעלת עותקים מרובים של צינור הקלט על פני קלטות מרוסקות ושילוב התוצאות:
filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
def make_dataset(shard_index):
filenames = filenames.shard(NUM_SHARDS, shard_index)
dataset = filenames_to_dataset(filenames)
Return dataset.batch(batch_size)
indices = tf.data.Dataset.range(NUM_SHARDS)
dataset = indices.interleave(make_dataset,
num_parallel_calls=tf.data.AUTOTUNE)
dataset = dataset.prefetch(tf.data.AUTOTUNE)
משאבים נוספים
- מדריך ביצועים tf.data כיצד לכתוב צינורות קלט
tf.data
ביצועים - סרטון בתוך TensorFlow: שיטות עבודה מומלצות
tf.data
- מדריך לפרופילים
- הדרכה לפרופילים עם colab