ملخص
يفترض هذا الدليل الإلمام بملف تعريف TensorFlow و tf.data
. يهدف إلى تقديم إرشادات خطوة بخطوة مع أمثلة لمساعدة المستخدمين على تشخيص وإصلاح مشكلات أداء خط أنابيب الإدخال.
للبدء ، قم بتجميع ملف تعريف لوظيفة TensorFlow الخاصة بك. تتوفر تعليمات حول كيفية القيام بذلك لوحدات المعالجة المركزية / وحدات معالجة الرسومات ووحدات المعالجة المركزية السحابية .
سير عمل التحليل المفصل أدناه يركز على أداة عارض التتبع في ملف التعريف. تعرض هذه الأداة مخططًا زمنيًا يوضح مدة العمليات التي ينفذها برنامج TensorFlow الخاص بك وتتيح لك تحديد العمليات التي تستغرق وقتًا أطول في التنفيذ. لمزيد من المعلومات حول عارض التتبع ، راجع هذا القسم من دليل TF Profiler. بشكل عام ، ستظهر أحداث tf.data
في المخطط الزمني لوحدة المعالجة المركزية المضيفة.
تحليل سير العمل
يرجى اتباع سير العمل أدناه. إذا كانت لديك ملاحظات لمساعدتنا على تحسينها ، فالرجاء إنشاء مشكلة في github بعنوان "comp: data".
1. هل ينتج خط أنابيب tf.data
البيانات بسرعة كافية؟
ابدأ بالتحقق مما إذا كان خط أنابيب الإدخال هو عنق الزجاجة لبرنامج TensorFlow الخاص بك.
للقيام بذلك ، ابحث عن عمليات IteratorGetNext::DoCompute
في عارض التتبع. بشكل عام ، تتوقع أن ترى هذه في بداية الخطوة. تمثل هذه الشرائح الوقت الذي يستغرقه خط أنابيب الإدخال الخاص بك لإنتاج مجموعة من العناصر عند طلبها. إذا كنت تستخدم 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
على وحدة المعالجة المركزية. يمكنك التحقق من استخدامك باستخدام أدوات مثل sar و htop ، أو في وحدة التحكم السحابية إذا كنت تعمل على GCP.
إذا كان استخدامك منخفضًا ، فهذا يشير إلى أن خط أنابيب الإدخال الخاص بك قد لا يستفيد استفادة كاملة من وحدة المعالجة المركزية المضيفة. يجب عليك الرجوع إلى دليل أداء 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
في منشئ ملفات التعريف
كل حدث tf.data
في منشئ ملفات التعريف له اسم 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
. لاحظ أن اسم مجموعة البيانات قد يختلف قليلاً عن Python API (على سبيل المثال ، FiniteRepeat بدلاً من Repeat) ، ولكن يجب أن يكون بديهيًا بما يكفي للتحليل.
التحولات المتزامنة وغير المتزامنة
بالنسبة إلى تحويلات tf.data
المتزامنة (مثل Batch
and 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
thread. من اسمها الطويل ، يمكنك استنتاج أن 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
.
مثال 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 بدلاً من Map. نلاحظ هنا أن (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
- فيديو Inside TensorFlow: أفضل ممارسات
tf.data
- دليل ملف التعريف
- برنامج تعليمي لملف التعريف مع colab