דף זה תורגם על ידי Cloud Translation API.
Switch to English

לנתח tf.data ביצועים עם Profiler TF

סקירה כללית

מדריך זה מניח היכרות עם TensorFlow Profiler ו tf.data . מטרתו לספק הוראות צעד אחר צעד עם דוגמאות לאבחן משתמשים עזרה בבעיות ביצועים צינור הזנה לתקן.

כדי להתחיל, לאסוף פרופיל של עבודת TensorFlow שלך. הנחיות כיצד לעשות זאת זמינות עבור מעבדים / GPUs ו ענן TPUs .

TensorFlow Trace Viewer

זרימת העבודה ניתוח כמפורט להלן מתמקד כלי הצופה עקבות של Profiler. מציג כלי זה ציר זמן המציג את משך ops להורג על ידי תוכנית TensorFlow שלך מאפשר לך לזהות אילו ops שלוקח הכי הרבה זמן לבצע. לקבלת מידע נוסף על הצופה עקבות, לבדוק סעיף זה של המדריך Profiler TF. באופן כללי, tf.data אירועים יופיעו על ציר זמן מעבד מארח.

ניתוח Workflow

בצע את העבודה מתחת. אם יש לך משוב כדי לסייע לנו לשפר את זה, בבקשה ליצור נושא GitHub עם התווית "חינמון: נתונים".

1. האם שלך tf.data נתונים והפקת צינור מספיק מהר?

בראש וראשונה לוודא אם צינור הזנה הוא צוואר בקבוק עבור תוכנית TensorFlow שלך.

לשם כך, לחפש IteratorGetNext::DoCompute ops במציג עקבות. באופן כללי, אתה מצפה לראות הללו בתחילת צעד. פרוסות אלה מייצגים את הזמן שלוקח צינור ההזנה שלך להניב אצווה של אלמנטים כשזה מתבקש. אם אתה משתמש keras או iterating מעל הנתונים שלך tf.function , צריך להימצא אלה tf_data_iterator_get_next האשכולות.

שים לב שאם אתה משתמש אסטרטגיית ההפצה , ייתכן שיופיע IteratorGetNextAsOptional::DoCompute אירועים במקום IteratorGetNext::DoCompute (כמו של TF 2.3).

image

אם השיחות לחזור במהירות (<= 50 אותנו), אמצעי זה שנתון שלך זמין כשזה מתבקש. צינור ההזנה הוא לא צוואר הבקבוק שלך; לראות את מדריך Profiler עבור טיפים לניתוח ביצועים כלליים יותר.

image

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

2. האם אתה הכנה מקדים של נתון?

השיטה המומלצת ביצועי צינור הזנה היא להכניס tf.data.Dataset.prefetch טרנספורמציה בסוף שלך tf.data הצינור. שינוי זה חופף את החישוב והעיבוד המקדים של קלט הצינור עם הצעד הבא של חישוב מודל נדרש לביצועי צינור הזנה אופטימליים כאשר מתאמן המודל שלך. אם אתה הכנה מקדים של נתון, אתה צריך לראות Iterator::Prefetch פרוס על אותו החוט כמו IteratorGetNext::DoCompute אופ.

image

אם אין לך prefetch בסוף הצינור שלך, אתה צריך להוסיף אחד. לקבלת מידע נוסף אודות tf.data המלצות ביצועים, לראות את מדריך ביצועי tf.data .

אם אתה כבר הכנה מקדים של נתון, ואת צינור ההזנה עדיין צוואר הבקבוק שלך, המשך לסעיף הבא כדי לנתח נוסף בביצועים.

3. האם אתה מגיע לניצול גבוה של המעבד?

tf.data משיגה תפוקה גבוהה על ידי מנסה לעשות את השימוש הטוב ביותר של משאבים זמינים. באופן כללי, גם כאשר פועל המודל שלך על מאיץ כמו GPU או TPU, את tf.data צינורות מנוהלים על המעבד. אתה יכול לבדוק הניצול שלך עם כלים כמו SAR ו htop , או ענן ניטור קונסולה אם אתה מפעיל על GCP.

אם הניצול שלך נמוך, זה מרמז כי צינור ההזנה שלך לא יכול להיות ניצול מלא של מעבד המארח. אתה צריך להתייעץ עם מדריך ביצועי tf.data עבור מומלצות. אם יישמו את שיטות העבודה המומלצות ואת ניצול התפוקה להישאר נמוכה, ממשיכים ניתוח צוואר בקבוק מתחת.

אם הניצול שלך מתקרב לגבול המשאב, כדי לשפר את הביצועים עוד יותר, אתה צריך גם לשפר את היעילות של צינור ההזנה שלך (למשל, הימנעות חישוב מיותר) או חישוב Offload.

אתה יכול לשפר את היעילות של צינור ההזנה שלך על ידי הימנעות חישוב מיותר tf.data . אחת דרכים לעשות זאת היא החדרת tf.data.Dataset.cache טרנספורמציה אחרי עבודת חישוב עתיר אם מתאים לנתונים שלך לתוך זיכרון; זה מפחית חישוב במחיר של שימוש בזיכרון מוגבר. בנוסף, השבתת הקבלת התוך-op ב 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> משם (הקצר), ואת מערכי הנתונים האחרים בשם הארוך מייצגים טרנספורמציות במורד זרם.

image

לדוגמה, בצילום המסך למעלה נוצר מן הקוד הבא:

 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 פייתון (למשל, FiniteRepeat במקום חזור), אבל צריך להיות מספיק אינטואיטיבי לנתח.

טרנספורמציות סינכרוני אסינכרוני

עבור סינכרוני tf.data טרנספורמציות (כגון Batch ו Map ), תוכלו לראות אירועים טרנספורמציות במעלה הזרם על אותו חוט. בדוגמה לעיל, מאז כל התמורות בהם נעשה שימוש הם סינכרוני, כל האירועים יופיעו באותו פתיל.

עבור טרנספורמציות אסינכרוני (כגון Prefetch , ParallelMap , ParallelInterleave ו MapAndBatch ) אירועים מן התמורות במעלה זרם יהיו על חוט שונה. במקרים כאלה, "שם ארוכים" יכול לעזור לך לזהות אילו טרנספורמציה צינור תואם אירוע.

image

לדוגמא, בצילום המסך למעלה נוצר מן הקוד הבא:

 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 אירוע יתאים ID של Prefetch האירוע.

זיהוי צוואר הבקבוק

באופן כללי, כדי לזהות את צוואר בקבוק צינור ההזנה שלך, ללכת בצנרת הקלט מן השינוי החיצוני כל הדרך אל המקור. החל השינוי האחרון בצנרת שלך, רקורסיבית לתוך טרנספורמציות במעלה הזרם עד שתמצא טרנספורמציה איטית או להגיע במערך המקור, כגון TFRecord . בדוגמה לעיל, היית להתחיל Prefetch , ואז ללכת נגד הזרם כדי BatchV2 , FiniteRepeat , Map , ולבסוף Range .

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

הערה כי הטרנספורמציה (חיצוני) הסופית ברוב צינורות מארח קלט היא Iterator::Model האירוע. שינוי הדגם מוצג באופן אוטומטי על ידי tf.data הריצה והוא משמש instrumenting ו AutoTuning ביצועי צינור הזנה.

אם העבודה שלך היא באמצעות אסטרטגיית הפצה , הצופה העקבות יכיל אירועים נוספים שמתאימים בצנרת קלט התקן. השינוי החיצוני של הצינור למכשיר (מקוננת תחת IteratorGetNextOp::DoCompute או IteratorGetNextAsOptionalOp::DoCompute ) יהיה Iterator::Prefetch אירוע עם הזרם Iterator::Generator האירוע. אתה יכול למצוא את הצינור למארח תואם ידי חיפוש Iterator::Model אירועים.

דוגמא 1

image

המסך שלמעלה מופק בצנרת הקלט הבא:

 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

image

המסך שלמעלה מופק בצנרת הקלט הבא:

 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, אתה יכול לשפר את ביצועים על ידי parallelizing חילוץ נתונים. לשם כך, להבטיח כי הנתונים שלך sharded ברחבי קבצים ולהשתמש tf.data.Dataset.interleave עם num_parallel_calls פרמטר מוכן tf.data.experimental.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.experimental.AUTOTUNE,
  deterministic=False)
 

הערה שקבצי sharded צריכים להיות גדולים באופן סביר amortize את התקורה של פתיחת תיק. לפרטים נוספים על מיצוי נתונים מקביל, רואה בסעיף הזה של tf.data מדריך ביצועים.

מערכי נתונים טרנספורמציה

אם זיהית ביניים tf.data טרנספורמציה כמו צוואר בקבוק, אתה יכול לטפל בה על ידי parallelizing הטרנספורמציה או במטמון בחישוב אם מתאים לנתונים שלך לתוך זיכרון ראוי. כמה טרנספורמציות כגון 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)
 

אתה יכול להציג את "הקבלה חיצונית" על ידי הפעלת מספר עותקים של צינור ההזנה מעל תשומות sharded ושילוב התוצאות:

 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.experimental.AUTOTUNE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
 

משאבים נוספים