Ringkasan
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 pipa masukan.
Untuk memulai, kumpulkan profil pekerjaan TensorFlow Anda. Petunjuk tentang cara melakukannya tersedia untuk CPU/GPU dan Cloud TPU .
Alur kerja analisis yang diperinci di bawah berfokus pada alat penampil jejak di Profiler. Alat ini menampilkan garis waktu yang menunjukkan durasi operasi yang dijalankan oleh program TensorFlow Anda dan memungkinkan Anda mengidentifikasi operasi mana yang membutuhkan waktu paling lama untuk dieksekusi. Untuk informasi selengkapnya tentang penampil jejak, lihat bagian panduan TF Profiler ini . Secara umum, event tf.data
akan muncul di timeline CPU host.
Alur Kerja Analisis
Silakan ikuti alur kerja di bawah ini. Jika Anda memiliki umpan balik untuk membantu kami memperbaikinya, harap buat masalah github dengan label "comp:data".
1. Apakah pipeline tf.data
Anda menghasilkan data dengan cukup cepat?
Mulailah dengan memastikan apakah pipeline input merupakan hambatan untuk program TensorFlow Anda.
Untuk melakukannya, cari operasi IteratorGetNext::DoCompute
di trace viewer. Secara umum, Anda berharap untuk melihatnya di awal langkah. Irisan ini mewakili waktu yang dibutuhkan pipa input Anda untuk menghasilkan kumpulan elemen saat diminta. Jika Anda menggunakan keras atau mengulangi dataset Anda di tf.function
, ini harus ditemukan di utas tf_data_iterator_get_next
.
Perhatikan bahwa jika Anda menggunakan strategi distribusi , Anda mungkin melihat peristiwa IteratorGetNextAsOptional::DoCompute
alih-alih IteratorGetNext::DoCompute
(pada TF 2.3).
Jika panggilan kembali dengan cepat (<= 50 us), ini berarti data Anda tersedia saat diminta. Pipa input bukanlah hambatan Anda; lihat panduan Profiler untuk tips analisis kinerja yang lebih umum.
Jika panggilan kembali dengan lambat, tf.data
tidak dapat memenuhi permintaan konsumen. Lanjutkan ke bagian berikutnya.
2. Apakah Anda melakukan prefetching data?
Praktik terbaik untuk performa pipeline input adalah menyisipkan transformasi tf.data.Dataset.prefetch
di akhir pipeline tf.data
Anda. Transformasi ini tumpang tindih dengan komputasi prapemrosesan pipeline input dengan langkah komputasi model berikutnya dan diperlukan untuk performa pipeline input yang optimal saat melatih model Anda. Jika Anda melakukan prefetching data, Anda akan melihat Iterator::Prefetch
slice pada thread yang sama dengan IteratorGetNext::DoCompute
op.
Jika Anda tidak memiliki prefetch
di akhir pipeline , Anda harus menambahkannya. Untuk informasi selengkapnya tentang rekomendasi performa tf.data
, lihat panduan performa tf.data .
Jika Anda sudah melakukan prapengambilan data , dan saluran masukan masih menjadi penghambat, lanjutkan ke bagian berikutnya untuk menganalisis kinerja lebih lanjut.
3. Apakah Anda mencapai utilisasi CPU yang tinggi?
tf.data
mencapai throughput tinggi dengan mencoba memanfaatkan sumber daya yang tersedia sebaik mungkin. Secara umum, bahkan saat menjalankan model Anda di akselerator seperti GPU atau TPU, pipeline tf.data
dijalankan di CPU. Anda dapat memeriksa penggunaan Anda dengan fitur seperti sar dan htop , atau di konsol pemantauan cloud jika Anda menggunakan GCP.
Jika pemanfaatan Anda rendah, ini menunjukkan bahwa pipa input Anda mungkin tidak memanfaatkan CPU host sepenuhnya. Anda harus berkonsultasi dengan panduan kinerja tf.data untuk praktik terbaik. Jika Anda telah menerapkan praktik terbaik dan utilisasi serta 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 masukan (misalnya, menghindari perhitungan yang tidak perlu) atau perhitungan offload.
Anda dapat meningkatkan efisiensi saluran masukan dengan menghindari perhitungan yang tidak perlu di tf.data
. Salah satu cara untuk melakukan ini adalah menyisipkan transformasi tf.data.Dataset.cache
setelah pekerjaan intensif komputasi jika data Anda masuk ke dalam memori; ini mengurangi perhitungan dengan biaya penggunaan memori yang meningkat. Selain itu, menonaktifkan paralelisme intra-op di tf.data
berpotensi meningkatkan efisiensi > 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 hambatannya dan kemungkinan strategi mitigasi.
Memahami peristiwa tf.data
di Profiler
Setiap kejadian tf.data
di Profiler memiliki nama Iterator::<Dataset>
, di mana <Dataset>
adalah nama sumber atau transformasi kumpulan data. Setiap event juga memiliki nama panjang Iterator::<Dataset_1>::...::<Dataset_n>
, yang dapat Anda lihat dengan mengklik event tf.data
. Dalam nama panjang, <Dataset_n>
cocok dengan <Dataset>
dari nama (pendek), dan kumpulan data lain dalam nama panjang mewakili transformasi hilir.
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 API python (misalnya, FiniteRepeat alih-alih Ulangi), tetapi harus cukup intuitif untuk diuraikan.
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 bersifat sinkron, semua kejadian 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 mana dalam alur yang terkait dengan peristiwa.
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 masukannya ( BatchV2
) akan berada di utas yang berbeda, dan dapat ditemukan dengan mencari nama panjang Iterator::Prefetch::BatchV2
. Dalam hal ini, mereka ada di utas tf_data_iterator_resource
. Dari namanya yang panjang, Anda dapat menyimpulkan bahwa BatchV2
adalah upstream dari Prefetch
. Selanjutnya, parent_id
dari event BatchV2
akan cocok dengan ID dari event Prefetch
.
Mengidentifikasi kemacetan
Secara umum, untuk mengidentifikasi bottleneck dalam pipa input Anda, atur pipa input dari transformasi terluar sampai ke sumbernya. Mulai dari transformasi terakhir di pipeline Anda, berulang ke transformasi upstream hingga Anda menemukan transformasi yang lambat atau mencapai kumpulan data sumber, seperti TFRecord
. Pada contoh di atas, Anda akan mulai dari Prefetch
, lalu berjalan ke upstream ke BatchV2
, FiniteRepeat
, Map
, dan terakhir Range
.
Secara umum, transformasi lambat berhubungan dengan transformasi yang peristiwanya panjang, tetapi peristiwa masukannya singkat. Beberapa contoh ikuti di bawah ini.
Perhatikan bahwa transformasi terakhir (terluar) di sebagian besar saluran input host adalah peristiwa Iterator::Model
. Transformasi Model diperkenalkan secara otomatis oleh runtime tf.data
dan digunakan untuk menginstrumentasi dan menyetel otomatis performa pipeline input.
Jika tugas Anda menggunakan strategi distribusi , penampil jejak akan berisi peristiwa tambahan yang sesuai dengan pipa masukan perangkat. Transformasi terluar dari pipeline perangkat (bersarang di bawah IteratorGetNextOp::DoCompute
atau IteratorGetNextAsOptionalOp::DoCompute
) akan menjadi peristiwa Iterator::Prefetch
dengan peristiwa Upstream Iterator::Generator
. Anda dapat menemukan pipa host yang sesuai dengan mencari peristiwa Iterator::Model
.
Contoh 1
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()
Di tangkapan layar, amati bahwa (1) Iterator::Map
peristiwa panjang, tetapi (2) peristiwa masukannya ( Iterator::FlatMap
) kembali dengan cepat. Ini menunjukkan bahwa transformasi Peta berurutan adalah hambatannya.
Perhatikan bahwa di tangkapan layar, acara InstantiatedCapturedFunction::Run
sesuai dengan waktu yang diperlukan untuk menjalankan fungsi peta.
Contoh 2
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 Peta. Kami perhatikan di sini bahwa (1) acara Iterator::ParallelMap
panjang, tetapi (2) acara masukannya Iterator::FlatMap
(yang ada di utas berbeda, karena ParallelMap asinkron) pendek. Ini menunjukkan bahwa transformasi ParallelMap adalah hambatannya.
Mengatasi kemacetan
Kumpulan data sumber
Jika Anda telah mengidentifikasi sumber kumpulan data sebagai hambatan, seperti membaca dari file TFRecord, Anda dapat meningkatkan kinerja dengan memparalelkan ekstraksi data. Untuk melakukannya, pastikan data Anda di-sharding di beberapa file dan gunakan tf.data.Dataset.interleave
dengan parameter num_parallel_calls
disetel ke tf.data.AUTOTUNE
. Jika determinisme tidak penting untuk program Anda, Anda dapat lebih meningkatkan kinerja dengan menyetel 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 sharded harus berukuran cukup besar untuk mengamortisasi biaya pembukaan file. Untuk detail lebih lanjut tentang ekstraksi data paralel, lihat bagian panduan performa tf.data
ini.
Kumpulan data transformasi
Jika Anda telah mengidentifikasi transformasi tf.data
perantara sebagai hambatan, Anda dapat mengatasinya dengan memparalelkan transformasi atau menyimpan komputasi ke dalam cache jika data Anda cocok dengan memori dan sesuai. Beberapa transformasi seperti Map
memiliki pasangan 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, misalkan pipa input Anda awalnya terlihat seperti berikut, dengan Batch
sebagai hambatannya:
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 banyak salinan pipa input melalui input yang dipecah 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
- panduan performa tf.data tentang cara menulis pipeline input
tf.data
performa - Di dalam video TensorFlow: praktik terbaik
tf.data
- Panduan profiler
- Tutorial profiler dengan colab