ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

تحليل أداء tf.data باستخدام ملف تعريف TF

نظرة عامة

يفترض هذا الدليل الإلمام بملف تعريف tf.data و tf.data . يهدف إلى تقديم إرشادات خطوة بخطوة مع أمثلة لمساعدة المستخدمين على تشخيص وإصلاح مشكلات أداء خط أنابيب الإدخال.

للبدء ، قم بتجميع ملف تعريف لوظيفة TensorFlow الخاصة بك. تتوفر تعليمات حول كيفية القيام بذلك لوحدات المعالجة المركزية / وحدات معالجة الرسومات و Cloud TPU .

TensorFlow Trace Viewer

سير عمل التحليل المفصل أدناه يركز على أداة عارض التتبع في ملف التعريف. تعرض هذه الأداة مخططًا زمنيًا يوضح مدة العمليات التي ينفذها برنامج TensorFlow الخاص بك وتتيح لك تحديد العمليات التي تستغرق وقتًا أطول في التنفيذ. لمزيد من المعلومات حول عارض التتبع ، راجع هذا القسم من دليل TF Profiler. بشكل عام ، tf.data أحداث 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).

image

إذا عادت المكالمات بسرعة (<= 50 لنا) ، فهذا يعني أن بياناتك متاحة عند طلبها. خط أنابيب الإدخال ليس عنق الزجاجة الخاص بك ؛ راجع دليل منشئ ملفات التعريف للحصول على مزيد من النصائح العامة لتحليل الأداء.

image

إذا عادت المكالمات ببطء ، tf.data يتمكن tf.data مواكبة طلبات المستهلك. تابع إلى القسم التالي.

2. هل تقوم بالجلب المسبق للبيانات؟

أفضل ممارسة لأداء خط أنابيب الإدخال هي إدراج تحويل tf.data.Dataset.prefetch في نهاية خط أنابيب tf.data . يتداخل هذا التحويل مع حساب المعالجة المسبقة لخط أنابيب الإدخال مع الخطوة التالية من حساب النموذج وهو مطلوب للحصول على الأداء الأمثل لخط أنابيب الإدخال عند تدريب النموذج الخاص بك. إذا كنت تقوم بالجلب المسبق للبيانات ، فيجب أن ترى شريحة Iterator::Prefetch على نفس مؤشر الترابط مثل IteratorGetNext::DoCompute op.

image

إذا لم يكن لديك 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 في 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 في منشئ ملفات التعريف

كل حدث tf.data في منشئ ملفات التعريف له اسم 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 . لاحظ أن اسم مجموعات البيانات قد يختلف قليلاً عن Python API (على سبيل المثال ، FiniteRepeat بدلاً من Repeat) ، ولكن يجب أن يكون بديهيًا بما يكفي للتحليل.

التحولات المتزامنة وغير المتزامنة

بالنسبة إلى تحويلات tf.data المتزامنة (مثل Batch and Map ) ، سترى أحداثًا من تحويلات المنبع على نفس tf.data . في المثال أعلاه ، نظرًا لأن جميع التحويلات المستخدمة متزامنة ، تظهر جميع الأحداث في نفس السلسلة.

بالنسبة MapAndBatch غير المتزامنة (مثل Prefetch و ParallelMap و ParallelInterleave و MapAndBatch ) ، MapAndBatch الأحداث من عمليات التحويل 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 ) الخاصة به على سلسلة BatchV2 مختلفة ، ويمكن تحديد موقعها من خلال البحث عن الاسم الطويل Iterator::Prefetch::BatchV2 . في هذه الحالة ، يكونون على tf_data_iterator_resource الموضوع. من اسمها الطويل ، يمكنك استنتاج أن BatchV2 هو منبع Prefetch . علاوة على ذلك ، فإن parent_id لحدث BatchV2 سوف يتطابق مع معرف حدث Prefetch .

تحديد عنق الزجاجة

بشكل عام ، لتحديد الاختناق في خط أنابيب الإدخال ، اسلك خط أنابيب الإدخال من التحويل الأبعد وصولاً إلى المصدر. بدءًا من التحول النهائي في خط الأنابيب الخاص بك ، كرر إلى عمليات التحويل الأولية حتى تجد تحولًا بطيئًا أو تصل إلى مجموعة بيانات المصدر ، مثل TFRecord . في المثال أعلاه ، ستبدأ من Prefetch ، ثم BatchV2 نحو المنبع BatchV2 ، و FiniteRepeat ، و Map ، وأخيراً Range .

بشكل عام ، يقابل التحول البطيء الشخص الذي تكون أحداثه طويلة ، ولكن أحداث الإدخال قصيرة. فيما يلي بعض الأمثلة.

لاحظ أن التحويل النهائي (الخارجي) في معظم خطوط أنابيب إدخال المضيف هو حدث Iterator::Model . يتم تقديم تحويل النموذج تلقائيًا بواسطة وقت تشغيل tf.data ويتم استخدامه tf.data التلقائي لأداء خط أنابيب الإدخال.

إذا كانت وظيفتك تستخدم إستراتيجية توزيع ، فسيحتوي عارض التتبع على أحداث إضافية تتوافق مع خط أنابيب إدخال الجهاز. سيكون التحويل الخارجي لخط أنابيب الجهاز (المتداخل ضمن 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 بدلاً من Map. نلاحظ هنا أن (1) أحداث Iterator::ParallelMap طويلة ، ولكن (2) أحداث الإدخال Iterator::FlatMap (التي توجد على سلسلة Iterator::FlatMap مختلفة ، نظرًا لأن ParallelMap غير متزامن) قصيرة. هذا يشير إلى أن تحويل ParallelMap هو عنق الزجاجة.

معالجة عنق الزجاجة

مجموعات البيانات المصدر

إذا حددت مصدر مجموعة بيانات على أنه عنق الزجاجة ، مثل القراءة من ملفات TFRecord ، فيمكنك تحسين الأداء عن طريق موازنة استخراج البيانات. للقيام بذلك، تأكد من sharded البيانات عبر ملفات متعددة واستخدام tf.data.Dataset.interleave مع num_parallel_calls المعلمة مجموعة إلى tf.data.experimental.AUTOTUNE . إذا لم تكن الحتمية مهمة tf.data.Dataset.interleave ، فيمكنك تحسين الأداء بشكل أكبر عن طريق تعيين علامة 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)

لاحظ أن الملفات المُقسمة يجب أن تكون كبيرة بشكل معقول لاستهلاك النفقات العامة لفتح ملف. لمزيد من التفاصيل حول استخراج البيانات المتوازي ، راجع هذا القسم من دليل أداء tf.data .

مجموعات بيانات التحول

إذا حددت 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.experimental.AUTOTUNE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

مصادر إضافية