Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

Analisis kinerja tf.data dengan TF Profiler

Gambaran

Panduan ini mengasumsikan keakraban dengan TensorFlow Profiler dan tf.data . Ini bertujuan untuk memberikan petunjuk langkah demi langkah dengan contoh-contoh untuk membantu pengguna mendiagnosis dan memperbaiki masalah kinerja pipa input.

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

TensorFlow Trace Viewer

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

Alur Kerja Analisis

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

1. Apakah saluran pipa tf.data Anda menghasilkan data dengan cukup cepat?

Mulailah dengan memastikan apakah pipa input adalah hambatan untuk program TensorFlow Anda.

Untuk melakukannya, cari IteratorGetNext::DoCompute ops di penampil jejak. Secara umum, Anda berharap melihat ini di awal langkah. Irisan ini menunjukkan waktu yang dibutuhkan pipa input Anda untuk menghasilkan sejumlah elemen saat diminta. Jika Anda menggunakan keras atau mengulangi dataset Anda di fungsi tf.function , ini harus ditemukan di utas tf_data_iterator_get_next .

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

image

Jika panggilan kembali dengan cepat (<= 50 kami), ini berarti bahwa data Anda tersedia saat diminta. Pipa input bukan hambatan Anda; lihat panduan Profiler untuk tips analisis kinerja yang lebih umum.

image

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

2. Apakah Anda mengambil data sebelumnya?

Praktik terbaik untuk kinerja saluran input adalah dengan memasukkan transformasi tf.data.Dataset.prefetch di akhir pipa tf.data Anda. Transformasi ini tumpang tindih dengan perhitungan preprocessing pipa input dengan langkah selanjutnya dari perhitungan model dan diperlukan untuk kinerja pipa input yang optimal saat melatih model Anda. Jika Anda mengambil data sebelumnya, Anda akan melihat Iterator::Prefetch slice pada utas yang sama dengan IteratorGetNext::DoCompute op.

image

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

Jika Anda sudah mengambil data sebelumnya , dan pipa input masih menjadi hambatan Anda, lanjutkan ke bagian selanjutnya untuk menganalisis kinerja lebih lanjut.

3. Apakah Anda mencapai pemanfaatan CPU yang tinggi?

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

Jika utilisasi Anda rendah, ini menunjukkan bahwa pipa 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 utilisasi dan throughput tetap rendah, lanjutkan ke analisis Bottleneck di bawah ini.

Jika pemanfaatan Anda mendekati batas sumber daya , untuk meningkatkan kinerja lebih lanjut, Anda perlu meningkatkan efisiensi pipa input Anda (misalnya, menghindari perhitungan yang tidak perlu) atau perhitungan pembongkaran.

Anda dapat meningkatkan efisiensi pipa input Anda dengan menghindari perhitungan yang tidak perlu di tf.data . Salah satu cara untuk melakukan ini adalah memasukkan transformasi tf.data.Dataset.cache setelah pekerjaan intensif komputasi jika data Anda sesuai dengan memori; ini mengurangi perhitungan dengan biaya peningkatan penggunaan memori. Selain itu, menonaktifkan paralelisme intra-op di tf.data memiliki potensi untuk meningkatkan efisiensi hingga> 10%, dan dapat dilakukan dengan menetapkan opsi berikut pada pipa 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 membahas cara membaca peristiwa tf.data di penampil jejak untuk memahami di mana hambatannya dan kemungkinan strategi mitigasi.

Memahami peristiwa tf.data di Profiler

Setiap acara tf.data di Profiler memiliki nama Iterator::<Dataset> , di mana <Dataset> adalah nama sumber dataset atau transformasi. Setiap acara juga memiliki nama panjang Iterator::<Dataset_1>::...::<Dataset_n> , yang dapat Anda lihat dengan mengklik acara tf.data . Dalam nama panjang, <Dataset_n> cocok dengan <Dataset> dari nama (pendek), dan kumpulan 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 dataset mungkin sedikit berbeda dari python API (misalnya, FiniteRepeat, bukan Ulangi), 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 hulu pada utas yang sama. Dalam contoh di atas, karena semua transformasi yang digunakan adalah sinkron, semua peristiwa muncul di utas yang sama.

Untuk transformasi asinkron (seperti Prefetch , ParallelMap , ParallelInterleave dan MapAndBatch ) peristiwa dari transformasi hulu akan berada di utas yang berbeda. Dalam kasus seperti itu, "nama panjang" dapat membantu Anda mengidentifikasi transformasi dalam pipa yang sesuai 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, acara Iterator::Prefetch ada di utas tf_data_iterator_get_next . Karena Prefetch asinkron, peristiwa inputnya ( BatchV2 ) akan berada di utas yang berbeda, dan dapat ditemukan dengan mencari nama panjang Iterator::Prefetch::BatchV2 . Dalam hal ini, mereka berada di utas tf_data_iterator_resource . Dari namanya yang panjang, Anda dapat menyimpulkan bahwa BatchV2 adalah hulu dari Prefetch . Selanjutnya, parent_id dari acara BatchV2 akan cocok dengan ID dari acara Prefetch .

Mengidentifikasi kemacetan

Secara umum, untuk mengidentifikasi hambatan dalam pipa input Anda, jalankan pipa input dari transformasi terluar sampai ke sumbernya. Mulai dari transformasi akhir di pipeline Anda, rekur ulang menjadi transformasi hulu hingga Anda menemukan transformasi lambat atau mencapai dataset sumber, seperti TFRecord . Pada contoh di atas, Anda akan mulai dari Prefetch , lalu berjalan ke hulu ke BatchV2 , FiniteRepeat , Map , dan akhirnya Range .

Secara umum, transformasi lambat sesuai dengan yang peristiwanya panjang, tetapi yang inputnya pendek. Beberapa contoh mengikuti di bawah ini.

Perhatikan bahwa transformasi terakhir (paling luar) di sebagian besar pipa input host adalah peristiwa Iterator::Model . Transformasi Model diperkenalkan secara otomatis oleh runtime tf.data dan digunakan untuk menginstruksikan dan autotunisasi kinerja pipa input.

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

Contoh 1

image

Tangkapan layar di atas dihasilkan dari pipa input berikut:

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

Pada tangkapan layar, amati bahwa (1) Iterator::Map Acara Iterator::Map panjang, tetapi (2) acara masukannya ( Iterator::FlatMap ) kembali dengan cepat. Ini menunjukkan bahwa transformasi Peta sekuensial adalah hambatannya.

Perhatikan bahwa dalam tangkapan layar, acara InstantiatedCapturedFunction::Run sesuai dengan waktu yang diperlukan untuk menjalankan fungsi peta.

Contoh 2

image

Tangkapan layar di atas dihasilkan dari pipa input 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 yang di atas, tetapi menggunakan ParallelMap, bukan Map. Kami perhatikan di sini bahwa (1) Peristiwa Iterator::ParallelMap panjang, tetapi (2) peristiwa masukannya Iterator::FlatMap (yang berada di utas berbeda, karena ParallelMap asinkron) pendek. Ini menunjukkan bahwa transformasi ParallelMap adalah hambatan.

Mengatasi kemacetan

Kumpulan data sumber

Jika Anda telah mengidentifikasi sumber dataset sebagai hambatan, seperti membaca dari file TFRecord, Anda dapat meningkatkan kinerja dengan memaralelkan ekstraksi data. Untuk melakukannya, pastikan bahwa data Anda terbagi di beberapa file dan gunakan tf.data.Dataset.interleave dengan parameter num_parallel_calls diatur ke tf.data.experimental.AUTOTUNE . Jika determinisme tidak penting bagi program Anda, Anda dapat lebih meningkatkan kinerja dengan menetapkan deterministic=False tf.data.Dataset.interleave 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.experimental.AUTOTUNE,
  deterministic=False)
 

Perhatikan bahwa file yang diarsir harus cukup besar untuk mengamortisasi overhead pembukaan file. Untuk detail lebih lanjut tentang ekstraksi data paralel, lihat bagian panduan kinerja tf.data .

Kumpulan data transformasi

Jika Anda telah mengidentifikasi transformasi intermediate tf.data sebagai bottleneck, Anda dapat mengatasinya dengan memparalelkan transformasi atau caching perhitungan jika data Anda sesuai dengan memori dan itu sesuai. Beberapa transformasi seperti Map memiliki mitra paralel; panduan kinerja tf.data menunjukkan cara memparalelkannya. Transformasi lain, seperti Filter , Unbatch , dan Batch secara inheren berurutan; Anda dapat memparalelkannya dengan memperkenalkan "paralelisme luar". Misalnya, anggap pipa input Anda awalnya terlihat seperti berikut, dengan Batch sebagai hambatan:

 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 dari pipa input melalui input yang di-shard 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.experimental.AUTOTUNE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
 

Sumber daya tambahan