หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

วิเคราะห์ประสิทธิภาพ tf.data ด้วย TF Profiler

ภาพรวม

คำแนะนำนี้ถือว่าคุ้นเคยกับ TensorFlow Profiler และ tf.data มันมีจุดมุ่งหมายเพื่อให้คำแนะนำทีละขั้นตอนพร้อมตัวอย่างเพื่อช่วยให้ผู้ใช้วินิจฉัยและแก้ไขปัญหาประสิทธิภาพการทำงานของไปป์ไลน์อินพุต

ในการเริ่มต้นรวบรวมโปรไฟล์ของงาน TensorFlow ของคุณ คำแนะนำเกี่ยวกับวิธีการทำเช่นนั้นพร้อมใช้งานสำหรับ CPUs / GPUs และ Cloud TPU

TensorFlow Trace Viewer

เวิร์กโฟลว์การวิเคราะห์ที่มีรายละเอียดด้านล่างมุ่งเน้นไปที่เครื่องมือเครื่องมือดูข้อมูลการติดตามใน Profiler เครื่องมือนี้แสดงไทม์ไลน์ที่แสดงระยะเวลาของ ops ที่ดำเนินการโดยโปรแกรม TensorFlow ของคุณและช่วยให้คุณระบุ ops ที่ใช้เวลาในการประมวลผลนานที่สุด สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวแสดงการติดตามตรวจสอบ ส่วนนี้ ของคำแนะนำ TF Profiler โดยทั่วไปเหตุการณ์ tf.data จะปรากฏบนไทม์ไลน์ของโฮสต์ CPU

ขั้นตอนการวิเคราะห์

โปรดทำตามขั้นตอนการทำงานด้านล่าง หากคุณมีข้อเสนอแนะเพื่อช่วยเราปรับปรุงโปรด สร้างปัญหา GitHub พร้อมกับฉลาก“ comp: data”

1. ท่อส่ง tf.data ของคุณสร้างข้อมูลเร็วพอหรือไม่?

เริ่มต้นด้วยการตรวจสอบว่าอินพุตไลน์เป็นคอขวดสำหรับโปรแกรม TensorFlow ของคุณหรือไม่

เมื่อต้องการทำเช่นนั้นค้นหา IteratorGetNext::DoCompute ops ในตัวแสดงการติดตาม โดยทั่วไปคุณคาดหวังที่จะเห็นสิ่งเหล่านี้ในช่วงเริ่มต้นของขั้นตอน ชิ้นส่วนเหล่านี้เป็นตัวแทนของเวลาที่ใช้ในการป้อนข้อมูลของคุณเพื่อให้ได้ชุดองค์ประกอบเมื่อมีการร้องขอ หากคุณกำลังใช้ keras หรือวนซ้ำชุดข้อมูลของคุณในรูปแบบ tf.function คุณควรพบสิ่งเหล่านี้ในหัวข้อ tf_data_iterator_get_next

โปรดทราบว่าหากคุณใช้ กลยุทธ์การกระจาย คุณอาจเห็นเหตุการณ์ IteratorGetNextAsOptional::DoCompute แทน IteratorGetNext::DoCompute (เหมือน TF 2.3)

image

หากการโทรกลับมาอย่างรวดเร็ว (<= 50 us) แสดง ว่าข้อมูลของคุณสามารถใช้ได้เมื่อมีการร้องขอ ไพพ์ไลน์อินพุตไม่ใช่คอขวดของคุณ ดู คู่มือ Profiler สำหรับเคล็ดลับการวิเคราะห์ประสิทธิภาพทั่วไปเพิ่มเติม

image

หากการโทรกลับมาช้า tf.data จะไม่สามารถตอบสนองคำขอของผู้บริโภคได้ ดำเนินการต่อในส่วนถัดไป

2. คุณกำลังดึงข้อมูลล่วงหน้าหรือไม่

แนวทางปฏิบัติที่ดีที่สุดสำหรับประสิทธิภาพของ tf.data ไลน์คือการแทรกการแปลง tf.data.Dataset.prefetch ที่ส่วนท้ายของ tf.data ไลน์ tf.data ของคุณ การแปลงนี้จะทับซ้อนการคำนวณการประมวลผลล่วงหน้าของอินพุตไพพ์ด้วยขั้นตอนถัดไปของการคำนวณแบบจำลองและจำเป็นสำหรับประสิทธิภาพของไพพ์ไลน์อินพุตที่ดีที่สุดเมื่อฝึกฝนโมเดลของคุณ หากคุณกำลังดึงข้อมูลล่วงหน้าคุณควรเห็น Iterator::Prefetch IteratorGetNext::DoCompute บนเธรดเดียวกับ IteratorGetNext::DoCompute op

image

หากคุณไม่มี prefetch ที่ส่วนท้ายของ prefetch ไลน์ของ คุณคุณควรเพิ่มอันใหม่ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำแนะนำด้านประสิทธิภาพ tf.data ให้ดูที่ คู่มือประสิทธิภาพ tf.data

หากคุณกำลังดึงข้อมูลล่วงหน้าอยู่แล้ว และขั้นตอนการป้อนข้อมูลยังคงเป็นปัญหาคอขวดของคุณให้ไปที่หัวข้อถัดไปเพื่อวิเคราะห์ประสิทธิภาพเพิ่มเติม

3. คุณใช้งาน CPU สูงหรือไม่

tf.data รับปริมาณงานสูงโดยพยายามใช้ประโยชน์จากทรัพยากรที่มีให้ได้ดีที่สุด โดยทั่วไปแม้ว่าจะใช้โมเดลของคุณบนตัวเร่งเช่น GPU หรือ TPU tf.data ท่อ tf.data ก็ยังทำงานบนซีพียู คุณสามารถตรวจสอบการใช้งานของคุณด้วยเครื่องมือเช่น sar และ htop หรือใน คอนโซลการตรวจสอบระบบคลาวด์ หากคุณใช้งาน GCP

หากการใช้งานของคุณต่ำ แสดงว่าการป้อนข้อมูลของคุณอาจไม่ได้รับประโยชน์เต็มที่จาก CPU โฮสต์ คุณควร ศึกษาคู่มือประสิทธิภาพ tf.data สำหรับแนวทางปฏิบัติที่ดีที่สุด หากคุณใช้แนวทางปฏิบัติที่ดีที่สุดและการใช้ประโยชน์และปริมาณงานยังคงอยู่ในระดับต่ำให้ดำเนินการ วิเคราะห์คอขวด ด้านล่าง

หากการใช้งานของคุณใกล้ถึงขีด จำกัด ของทรัพยากร เพื่อปรับปรุงประสิทธิภาพเพิ่มเติมคุณต้องปรับปรุงประสิทธิภาพของขั้นตอนการป้อนข้อมูลของคุณ (เช่นหลีกเลี่ยงการคำนวณที่ไม่จำเป็น) หรือการคำนวณออฟโหลด

คุณสามารถปรับปรุงประสิทธิภาพของขั้นตอนการป้อนข้อมูลของคุณโดยหลีกเลี่ยงการคำนวณที่ไม่จำเป็นใน tf.data วิธีหนึ่งในการทำเช่นนี้คือการแทรกการแปลง tf.data.Dataset.cache หลังจากทำงานอย่างหนักในการคำนวณถ้าข้อมูลของคุณเหมาะกับหน่วยความจำ ซึ่งจะช่วยลดการคำนวณที่ต้นทุนของการใช้หน่วยความจำเพิ่มขึ้น นอกจากนี้การปิดใช้งานความขนานภายใน intra-op ใน tf.data มีศักยภาพในการเพิ่มประสิทธิภาพโดย> 10% และสามารถทำได้โดยการตั้งค่าตัวเลือกต่อไปนี้บน tf.data ไลน์อินพุตของคุณ:

 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> ชื่อยาว Iterator::<Dataset_1>::...::<Dataset_n> ซึ่งคุณสามารถดูได้โดยคลิกที่เหตุการณ์ tf.data ในชื่อแบบยาว <Dataset_n> จับคู่ <Dataset> จากชื่อ (สั้น) และชุดข้อมูลอื่นในชื่อแบบยาวแสดงถึงการแปลงแบบดาวน์สตรีม

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 ) เหตุการณ์จากการแปลง upstream จะอยู่บนเธรดอื่น ในกรณีเช่นนี้“ ชื่อยาว” สามารถช่วยคุณระบุการเปลี่ยนแปลงในท่อที่เหตุการณ์สอดคล้องกัน

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 จะตรงกับ ID ของเหตุการณ์ Prefetch

การระบุคอขวด

โดยทั่วไปหากต้องการระบุคอขวดในอินพุตไพพ์ของคุณให้เดินไปป์ไลน์อินพุตจากการแปลงภายนอกสุดสุดไปจนถึงต้นทาง เริ่มต้นจากการแปลงขั้นสุดท้ายในไปป์ไลน์ของคุณหักเงินเข้าสู่การแปลงต้นน้ำจนกว่าคุณจะพบการเปลี่ยนแปลงที่ช้าหรือเข้าถึงชุดข้อมูลต้นทางเช่น TFRecord ในตัวอย่างข้างต้นคุณจะเริ่มต้นจากการ Prefetch แล้วเดินขึ้นไปที่ BatchV2 , FiniteRepeat , Map และ Range สุดท้าย

โดยทั่วไปการแปลงที่ช้าจะสอดคล้องกับเหตุการณ์ที่มีความยาว แต่เหตุการณ์อินพุตสั้น ตัวอย่างบางส่วนปฏิบัติตามด้านล่าง

โปรดทราบว่าการแปลงสุดท้าย (นอกสุด) ใน Iterator::Model ไลน์อินพุตโฮสต์ส่วนใหญ่คือเหตุการณ์ Iterator::Model การแปลงแบบจำลองถูกนำมาใช้โดยอัตโนมัติโดยรันไทม์ tf.data และใช้สำหรับการใช้เครื่องมือและการปรับอัตโนมัติประสิทธิภาพของขั้นตอนการป้อนข้อมูล

หากงานของคุณกำลังใช้ กลยุทธ์การกระจาย ตัวดูการติดตามจะมีเหตุการณ์เพิ่มเติมที่สอดคล้องกับไปป์ไลน์อินพุตอุปกรณ์ การแปลงด้านนอกสุดของ IteratorGetNextOp::DoCompute ไลน์ของอุปกรณ์ (ซ้อนภายใต้ 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 events ยาว แต่ (2) เหตุการณ์ input ( 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 เหตุการณ์ Iterator::ParallelMap นั้นมีความยาว แต่ (2) เหตุการณ์อินพุตของ Iterator::FlatMap (ซึ่งอยู่ในเธรดที่แตกต่างกันเนื่องจาก ParallelMap เป็นแบบอะซิงโครนัส) สั้น สิ่งนี้ชี้ให้เห็นว่าการแปลง ParallelMap เป็นคอขวด

ที่อยู่คอขวด

ชุดข้อมูลต้นฉบับ

หากคุณระบุแหล่งข้อมูลชุดข้อมูลเป็นคอขวดเช่นการอ่านจากไฟล์ TFRecord คุณสามารถปรับปรุงประสิทธิภาพได้โดยการแยกข้อมูลเป็นคู่ขนาน ในการทำเช่นนั้นตรวจสอบให้แน่ใจว่าข้อมูลของคุณถูกแบ่งออกเป็นหลายไฟล์และใช้ tf.data.Dataset.interleave ด้วยพารามิเตอร์ num_parallel_calls ตั้งค่าเป็น tf.data.experimental.AUTOTUNE ถ้าระดับความสำคัญไม่สำคัญกับโปรแกรมของคุณคุณสามารถปรับปรุงประสิทธิภาพโดยการตั้งค่า deterministic=False flag ใน 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.experimental.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)
 

คุณสามารถแนะนำ“ outer parallelism” โดยการรันหลาย ๆ ไพพ์ของอินพุตไพพ์ผ่านอินพุตที่ถูกแบ่งส่วนและรวมผลลัพธ์:

 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)
 

แหล่งข้อมูลเพิ่มเติม