نمای کلی
این راهنما آشنایی با TensorFlow Profiler و tf.data
را فرض می کند. هدف آن ارائه دستورالعمل های گام به گام همراه با مثال هایی برای کمک به کاربران در تشخیص و رفع مشکلات عملکرد خط لوله ورودی است.
برای شروع، یک نمایه از کار TensorFlow خود جمع آوری کنید. دستورالعملهای نحوه انجام این کار برای CPU/GPU و Cloud TPU موجود است.
گردش کار تجزیه و تحلیل که در زیر به تفصیل شرح داده شده است، بر ابزار ردیابی نمایشگر در Profiler متمرکز است. این ابزار یک جدول زمانی نمایش می دهد که مدت عملیات اجرا شده توسط برنامه TensorFlow شما را نشان می دهد و به شما امکان می دهد تشخیص دهید کدام عملیات طولانی ترین زمان را برای اجرا می برد. برای اطلاعات بیشتر در مورد نمایشگر ردیابی، این بخش از راهنمای TF Profiler را بررسی کنید. به طور کلی، رویدادهای tf.data
در جدول زمانی CPU میزبان ظاهر می شود.
گردش کار تجزیه و تحلیل
لطفا روند کار زیر را دنبال کنید. اگر بازخوردی برای کمک به ما در بهبود آن دارید، لطفاً یک مشکل 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. آیا به استفاده از CPU بالا رسیده اید؟
tf.data
با تلاش برای استفاده بهینه از منابع موجود به توان عملیاتی بالایی دست می یابد. به طور کلی، حتی زمانی که مدل خود را روی یک شتاب دهنده مانند GPU یا TPU اجرا می کنید، خطوط لوله tf.data
روی CPU اجرا می شوند. میتوانید میزان استفاده خود را با ابزارهایی مانند sar و htop یا در کنسول مانیتورینگ ابری اگر روی GCP کار میکنید، بررسی کنید.
اگر استفاده شما کم است، این نشان می دهد که خط لوله ورودی شما ممکن است از 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
در 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 پایتون متفاوت باشد (به عنوان مثال، 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
رویدادها می توانید خط لوله میزبان مربوطه را پیدا کنید.
مثال 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()
این مثال مشابه مثال بالا است، اما به جای Map از 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
- ویدیوی Inside TensorFlow: بهترین شیوه های
tf.data
- راهنمای پروفایل
- آموزش پروفایلر با کولب