روز جامعه ML 9 نوامبر است! برای به روز رسانی از TensorFlow، JAX به ما بپیوندید، و بیشتر بیشتر بدانید

عملکرد tf.data را با TF Profiler تجزیه و تحلیل کنید

بررسی اجمالی

این راهنما با tf.data Profiler و tf.data . هدف آن ارائه دستورالعمل های گام به گام با مثالهایی برای کمک به کاربران در تشخیص و رفع مشکلات عملکرد خط لوله ورودی است.

برای شروع ، نمایه ای از کار TensorFlow خود را جمع آوری کنید. دستورالعمل نحوه انجام این کار برای CPU / GPU و Cloud TPU موجود است .

TensorFlow Trace Viewer

گردش کار تجزیه و تحلیل دقیق در زیر بر روی ابزار مشاهده کننده ردیابی در Profiler تمرکز دارد. این ابزار یک جدول زمانی را نشان می دهد که مدت زمان انجام عملیات توسط برنامه TensorFlow شما را نشان می دهد و به شما اجازه می دهد شناسایی کنید که کدام عملیات طولانی ترین زمان را دارند برای کسب اطلاعات بیشتر در مورد مشاهده کننده ردیابی ، این بخش از راهنمای TF Profiler را بررسی کنید. به طور کلی ، رویدادهای tf.data در جدول زمانی CPU میزبان ظاهر می شوند.

گردش کار تحلیل

لطفاً روند کار زیر را دنبال کنید. اگر بازخوردی برای کمک به ما در بهبود آن دارید ، لطفاً یک مشکل github با برچسب "comp: data" ایجاد کنید.

1. آیا خط تولید tf.data شما به سرعت کافی تولید می کند؟

با اطمینان از اینکه خط لوله ورودی گلوگاه برنامه TensorFlow شما است ، شروع کنید.

برای انجام این کار ، به IteratorGetNext::DoCompute در IteratorGetNext::DoCompute ردیابی بپردازید. به طور کلی ، شما انتظار دارید که این موارد را در آغاز یک مرحله مشاهده کنید. این برش ها نشان دهنده زمانی است که خط لوله ورودی شما برای درخواست دسته ای از عناصر لازم دارد. اگر از keras استفاده می کنید یا روی مجموعه داده خود را در یک 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. آیا به استفاده بالای CPU رسیده اید؟

tf.data با تلاش برای استفاده tf.data از منابع موجود ، به توان عملیاتی بالایی دست می یابد. به طور کلی ، حتی هنگام اجرای مدل خود روی شتاب دهنده ای مانند GPU یا TPU ، خطوط لوله tf.data روی CPU اجرا می شوند. اگر از GCP استفاده می کنید می توانید میزان استفاده خود را با ابزارهایی مانند sar و htop یا در کنسول نظارت بر ابر بررسی کنید.

اگر میزان استفاده شما کم است ، این نشان می دهد که خط لوله ورودی شما از CPU میزبان نهایت استفاده را نمی کند. برای بهترین روش ها باید با راهنمای عملکرد tf.data مشورت کنید. اگر بهترین روش ها را به کار گرفته اید و میزان بهره وری و توان مصرفی پایین است ، به تجزیه و تحلیل Bottleneck در زیر ادامه دهید.

اگر میزان استفاده شما به محدودیت منابع نزدیک می شود ، برای بهبود عملکرد بیشتر ، باید کارایی خط لوله ورودی خود را بهبود بخشید (به عنوان مثال از محاسبات غیرضروری جلوگیری کنید) یا محاسبه را بارگیری کنید.

با جلوگیری از محاسبات غیرضروری در 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 در Profiler

هر رویداد tf.data در Profiler دارای نام Iterator::<Dataset> ، در آنجا <Dataset> نام منبع داده یا تحول است. همچنین هر رویداد دارای نام طولانی Iterator::<Dataset_1>::...::<Dataset_n> که با کلیک روی رویداد tf.data می توانید آن را مشاهده کنید. در نام طولانی ، <Dataset_n> با نام (کوتاه) <Dataset> <Dataset_n> مطابقت دارد و سایر مجموعه های داده در نام طولانی ، نشان دهنده تحولات پایین دستی است.

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 و 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 که BatchV2 بالادست Prefetch . علاوه بر این، parent_id از BatchV2 رویداد شناسه مطابقت Prefetch رویداد.

شناسایی گلوگاه

به طور کلی ، برای شناسایی گلوگاه در خط لوله ورودی خود ، خط لوله ورودی را از بیرونی ترین تغییر شکل تا مبدا طی کنید. با شروع از تحول نهایی در خط لوله خود ، به تحولات بالادستی TFRecord شوید تا زمانی که تحولی کند پیدا کنید یا به یک مجموعه داده منبع مانند TFRecord . در مثال بالا ، شما از Prefetch شروع می کنید ، سپس در بالادست به سمت BatchV2 ، FiniteRepeat ، Map و در نهایت Range FiniteRepeat .

به طور کلی ، تحول آهسته مربوط به تحولی است که وقایع آن طولانی است ، اما رویدادهای ورودی آن کوتاه است. چند نمونه در زیر دنبال می شود.

توجه داشته باشید که آخرین (بیرونی ترین) تغییر شکل در اکثر خطوط لوله ورودی میزبان ، رویداد Iterator::Model است. تغییر شکل مدل به طور خودکار توسط زمان اجرای tf.data معرفی می شود و برای ابزار دقیق و تنظیم خودکار عملکرد خط لوله ورودی استفاده می شود.

اگر شغل شما از یک استراتژی توزیع استفاده می کند ، مشاهده کننده ردیابی شامل موارد دیگری خواهد بود که مربوط به خط لوله ورودی دستگاه است. بیرونی ترین تغییر شکل خط لوله دستگاه (در زیر IteratorGetNextOp::DoCompute یا IteratorGetNextAsOptionalOp::DoCompute ) یک رویداد Iterator::Prefetch با یک رویداد بالادست Iterator::Generator بود. با جستجوی Iterator::Model رویدادهای Iterator::Model می توانید خط لوله میزبان مربوطه را پیدا کنید.

مثال 1

image

تصویر بالا از خط لوله ورودی زیر تولید می شود:

dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record)
dataset = dataset.batch(32)
dataset = dataset.repeat()

در تصویر ، مشاهده کنید که (1) Iterator::Map وقایع 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 (که در یک موضوع متفاوت قرار دارند ، زیرا 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 را به عنوان گلوگاه شناسایی کرده اید ، اگر داده های شما در حافظه مناسب است و می توانید محاسبات را به صورت موازی در tf.data ، می توانید آن را برطرف کنید. برخی از دگرگونی ها مانند Map مشابه های موازی دارند. راهنمای عملکرد tf.data نحوه موازی سازی این موارد را نشان می دهد . تحولات دیگر مانند Filter ، Unbatch و Batch ذاتاً دنباله 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)

منابع اضافی