Google I / O kembali hadir pada 18-20 Mei! Pesan tempat dan buat jadwal Anda Daftar sekarang

Analisis kinerja tf.data dengan TF Profiler

Gambaran

Panduan ini mengasumsikan pemahaman tentang TensorFlow Profiler dan tf.data . Ini bertujuan untuk memberikan petunjuk langkah demi langkah dengan contoh untuk membantu pengguna mendiagnosis dan memperbaiki masalah kinerja pipeline masukan.

Untuk memulai, kumpulkan profil tugas TensorFlow Anda. Petunjuk tentang cara melakukannya tersedia untuk CPU / GPU dan Cloud TPU .

TensorFlow Trace Viewer

Alur kerja analisis yang dijelaskan di bawah ini berfokus pada alat penampil jejak di Profiler. Alat ini menampilkan timeline yang menunjukkan durasi operasi yang dijalankan oleh program TensorFlow Anda dan memungkinkan Anda mengidentifikasi operasi mana yang membutuhkan waktu paling lama untuk dijalankan. Untuk informasi lebih lanjut tentang trace viewer, lihat bagian panduan TF Profiler ini. Secara umum, peristiwa tf.data akan muncul di timeline CPU host.

Alur Kerja Analisis

Harap ikuti alur kerja di bawah ini. Jika Anda memiliki umpan balik untuk membantu kami memperbaikinya, buat masalah github dengan label "comp: data".

1. Apakah pipeline tf.data Anda menghasilkan data dengan cukup cepat?

Mulailah dengan memastikan apakah pipeline input adalah penghambat untuk program TensorFlow Anda.

Untuk melakukannya, cari operasi IteratorGetNext::DoCompute di penampil jejak. Secara umum, Anda berharap melihat ini di awal langkah. Irisan ini mewakili waktu yang dibutuhkan pipeline input Anda untuk menghasilkan sekumpulan elemen saat diminta. Jika Anda menggunakan keras atau melakukan iterasi atas kumpulan data Anda dalam fungsi tf.function , ini harus ditemukan di utas tf_data_iterator_get_next .

Perhatikan bahwa jika Anda menggunakan strategi distribusi , Anda mungkin melihat kejadian IteratorGetNextAsOptional::DoCompute alih-alih IteratorGetNext::DoCompute (pada TF 2.3).

image

Jika panggilan kembali dengan cepat (<= 50 us), ini berarti data Anda tersedia saat diminta. Pipa masukan bukanlah penghambat Anda; lihat panduan Profiler untuk tip analisis performa yang lebih umum.

image

Jika panggilan kembali lambat, tf.data tidak dapat memenuhi permintaan konsumen. Lanjutkan ke bagian selanjutnya.

2. Apakah Anda mengambil data lebih dulu?

Praktik terbaik untuk performa pipeline input adalah dengan menyisipkan transformasi tf.data.Dataset.prefetch di akhir pipeline tf.data Anda. Transformasi ini tumpang tindih dengan komputasi praproses pipeline masukan dengan langkah komputasi model berikutnya dan diperlukan untuk performa pipeline masukan yang optimal saat melatih model Anda. Jika Anda mengambil data lebih dulu, Anda akan melihat irisan Iterator::Prefetch pada utas yang sama dengan operasi IteratorGetNext::DoCompute .

image

Jika Anda tidak memiliki prefetch di akhir pipeline , Anda harus menambahkannya. Untuk informasi lebih lanjut tentang rekomendasi kinerja tf.data , lihat panduan kinerja tf.data .

Jika Anda sudah mengambil data lebih dulu , dan pipeline input masih menjadi penghambat Anda, lanjutkan ke bagian berikutnya untuk menganalisis performa lebih lanjut.

3. Apakah Anda mencapai penggunaan CPU yang tinggi?

tf.data mencapai throughput yang tinggi dengan mencoba memanfaatkan sebaik mungkin sumber daya yang tersedia. Secara umum, meski menjalankan model Anda pada akselerator seperti GPU atau TPU, pipeline tf.data dijalankan pada CPU. Anda dapat memeriksa pemanfaatannya dengan alat seperti sar dan htop , atau di konsol pemantauan cloud jika Anda menjalankan di GCP.

Jika pemanfaatan Anda rendah, ini menunjukkan bahwa pipeline input Anda mungkin tidak memanfaatkan CPU host sepenuhnya. Anda harus membaca panduan kinerja tf.data untuk praktik terbaik. Jika Anda telah menerapkan praktik terbaik dan pemanfaatan serta hasil tetap rendah, lanjutkan ke analisis Kemacetan di bawah ini.

Jika pemanfaatan Anda mendekati batas resource , untuk meningkatkan performa lebih lanjut, Anda perlu meningkatkan efisiensi pipeline input Anda (misalnya, menghindari komputasi yang tidak perlu) atau offload komputasi.

Anda dapat meningkatkan efisiensi pipeline input Anda dengan menghindari komputasi yang tidak perlu di tf.data . Salah satu cara untuk melakukannya adalah menyisipkan transformasi tf.data.Dataset.cache setelah pekerjaan intensif komputasi jika data Anda cocok dengan memori; ini mengurangi komputasi dengan biaya peningkatan penggunaan memori. Selain itu, menonaktifkan paralelisme intra-op di tf.data berpotensi meningkatkan efisiensi hingga> 10%, dan dapat dilakukan dengan menyetel opsi berikut pada pipeline input Anda:

dataset = ...
options = tf.data.Options()
options.experimental_threading.max_intra_op_parallelism = 1
dataset = dataset.with_options(options)

4. Analisis Kemacetan

Bagian berikut menjelaskan cara membaca peristiwa tf.data di penampil jejak untuk memahami di mana letak hambatan dan kemungkinan strategi mitigasi.

Memahami peristiwa tf.data di Profiler

Setiap peristiwa tf.data di Profiler memiliki nama Iterator::<Dataset> , di mana <Dataset> adalah nama sumber atau transformasi tf.data data. Setiap peristiwa juga memiliki nama panjang Iterator::<Dataset_1>::...::<Dataset_n> , yang dapat Anda lihat dengan mengklik peristiwa tf.data . Dalam nama panjang, <Dataset_n> cocok dengan <Dataset> dari nama (pendek), dan <Dataset_n> data lain dalam nama panjang mewakili transformasi hilir.

image

Misalnya, tangkapan layar di atas dihasilkan dari kode berikut:

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)

Di sini, acara Iterator::Map memiliki nama panjang Iterator::BatchV2::FiniteRepeat::Map . Perhatikan bahwa nama kumpulan data mungkin sedikit berbeda dari python API (misalnya, FiniteRepeat daripada Repeat), tetapi harus cukup intuitif untuk diurai.

Transformasi sinkron dan asinkron

Untuk transformasi tf.data sinkron (seperti Batch dan Map ), Anda akan melihat peristiwa dari transformasi upstream di utas yang sama. Dalam contoh di atas, karena semua transformasi yang digunakan sinkron, semua peristiwa muncul di utas yang sama.

Untuk transformasi asynchronous (seperti Prefetch , ParallelMap , ParallelInterleave dan MapAndBatch ) peristiwa dari transformasi hulu akan berada di thread yang berbeda. Dalam kasus seperti itu, "nama panjang" dapat membantu Anda mengidentifikasi transformasi dalam pipeline yang terkait dengan peristiwa.

image

Misalnya, tangkapan layar di atas dihasilkan dari kode berikut:

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
dataset = dataset.prefetch(1)

Di sini, kejadian Iterator::Prefetch ada di utas tf_data_iterator_get_next . Karena Prefetch tidak sinkron, kejadian masukannya ( BatchV2 ) akan berada di utas yang berbeda, dan dapat ditemukan dengan mencari nama panjang Iterator::Prefetch::BatchV2 . Dalam kasus ini, mereka berada di utas tf_data_iterator_resource . Dari namanya yang panjang, Anda dapat menyimpulkan bahwa BatchV2 adalah upstream dari Prefetch . Selanjutnya, parent_id dari acara BatchV2 akan cocok dengan ID acara Prefetch .

Mengidentifikasi kemacetan

Secara umum, untuk mengidentifikasi hambatan dalam pipeline masukan Anda, jalankan pipeline masukan dari transformasi terluar sampai ke sumbernya. Mulai dari transformasi terakhir di pipeline Anda, lakukan kembali transformasi upstream hingga Anda menemukan transformasi yang lambat atau mencapai TFRecord data sumber, seperti TFRecord . Pada contoh di atas, Anda akan mulai dari Prefetch , lalu berjalan ke hulu ke BatchV2 , FiniteRepeat , Map , dan terakhir Range .

Secara umum, transformasi lambat sesuai dengan transformasi yang kejadiannya panjang, tetapi kejadian masukannya pendek. Beberapa contoh mengikuti di bawah ini.

Perhatikan bahwa transformasi terakhir (terluar) di sebagian besar pipeline input host adalah peristiwa Iterator::Model . Transformasi Model diperkenalkan secara otomatis oleh runtime tf.data dan digunakan untuk tf.data dan tf.data otomatis performa pipeline input.

Jika tugas Anda menggunakan strategi distribusi , penampil jejak akan berisi peristiwa tambahan yang sesuai dengan pipeline masukan perangkat. Transformasi terluar dari pipeline perangkat (bersarang di bawah IteratorGetNextOp::DoCompute atau IteratorGetNextAsOptionalOp::DoCompute ) akan menjadi peristiwa Iterator::Prefetch dengan peristiwa Iterator::Generator upstream. Anda dapat menemukan pipeline host yang sesuai dengan mencari kejadian Iterator::Model .

Contoh 1

image

Tangkapan layar di atas dihasilkan dari pipa masukan berikut:

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

Pada tangkapan layar, amati bahwa (1) Peristiwa Iterator::Map panjang, tetapi (2) peristiwa masukannya ( Iterator::FlatMap ) kembali dengan cepat. Ini menunjukkan bahwa transformasi Map sekuensial adalah penghambat.

Perhatikan bahwa di tangkapan layar, kejadian InstantiatedCapturedFunction::Run sesuai dengan waktu yang dibutuhkan untuk menjalankan fungsi peta.

Contoh 2

image

Tangkapan layar di atas dihasilkan dari pipa masukan berikut:

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

Contoh ini mirip dengan di atas, tetapi menggunakan ParallelMap, bukan Map. Kita perhatikan di sini bahwa (1) peristiwa Iterator::ParallelMap panjang, tetapi (2) peristiwa masukannya Iterator::FlatMap (yang berada di utas berbeda, karena ParallelMap tidak sinkron) pendek. Ini menunjukkan bahwa transformasi ParallelMap adalah penghambat.

Mengatasi kemacetan

Set data sumber

Jika Anda telah mengidentifikasi sumber set data sebagai penghambat, seperti membaca dari file TFRecord, Anda dapat meningkatkan kinerja dengan memparalelkan ekstraksi data. Untuk melakukannya, pastikan bahwa data Anda dibagi di beberapa file dan gunakan tf.data.Dataset.interleave dengan parameter num_parallel_calls disetel ke tf.data.AUTOTUNE . Jika determinisme tidak penting bagi program Anda, Anda dapat meningkatkan kinerja lebih jauh dengan menetapkan flag deterministic=False pada tf.data.Dataset.interleave pada TF 2.2. Misalnya, jika Anda membaca dari TFRecords, Anda dapat melakukan hal berikut:

dataset = tf.data.Dataset.from_tensor_slices(filenames)
dataset = dataset.interleave(tf.data.TFRecordDataset,
  num_parallel_calls=tf.data.AUTOTUNE,
  deterministic=False)

Perhatikan bahwa file yang dipecah harus cukup besar untuk mengurangi biaya overhead pembukaan file. Untuk detail selengkapnya tentang ekstraksi data paralel, lihat bagian panduan kinerja tf.data .

Kumpulan data transformasi

Jika Anda telah mengidentifikasi transformasi tf.data perantara sebagai penghambat, Anda dapat mengatasinya dengan memparalelkan transformasi atau meng-cache komputasi jika data Anda cocok dengan memori dan sudah sesuai. Beberapa transformasi seperti Map memiliki rekan paralel; panduan kinerja tf.data mendemonstrasikan cara memparalelkan ini. Transformasi lain, seperti Filter , Unbatch , dan Batch secara inheren berurutan; Anda dapat memparalelkannya dengan memperkenalkan "paralelisme luar". Misalnya, anggap pipeline input Anda awalnya terlihat seperti berikut ini, dengan Batch sebagai penghambat:

filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
dataset = filenames_to_dataset(filenames)
dataset = dataset.batch(batch_size)

Anda dapat memperkenalkan "paralelisme luar" dengan menjalankan beberapa salinan pipeline masukan di atas masukan dengan sharded dan menggabungkan hasilnya:

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)

Sumber daya tambahan