ภาพรวม
คู่มือนี้จะถือว่าคุณคุ้นเคยกับ TensorFlow Profiler และ tf.data
มีจุดมุ่งหมายเพื่อให้คำแนะนำทีละขั้นตอนพร้อมตัวอย่างเพื่อช่วยผู้ใช้ในการวินิจฉัยและแก้ไขปัญหาประสิทธิภาพของไปป์ไลน์อินพุต
ในการเริ่มต้น ให้รวบรวมโปรไฟล์ของงาน TensorFlow ของคุณ คำแนะนำเกี่ยวกับวิธีดังกล่าวมีให้สำหรับ CPU/GPU และ Cloud TPU
เวิร์กโฟลว์การวิเคราะห์ที่มีรายละเอียดด้านล่างมุ่งเน้นไปที่เครื่องมือตัวแสดงการติดตามใน 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)
หากโทรกลับอย่างรวดเร็ว (<= 50 us) หมายความว่าข้อมูลของคุณจะพร้อมใช้งานเมื่อมีการร้องขอ ไปป์ไลน์อินพุตไม่ใช่คอขวดของคุณ ดู คู่มือ Profiler สำหรับเคล็ดลับการวิเคราะห์ประสิทธิภาพทั่วไปเพิ่มเติม
หากการโทรกลับช้า tf.data
จะไม่สามารถดำเนินการตามคำขอของผู้ใช้ได้ ดำเนินการต่อไปยังส่วนถัดไป
2. คุณกำลังดึงข้อมูลล่วงหน้าหรือไม่
แนวทางปฏิบัติที่ดีที่สุดสำหรับประสิทธิภาพของไปป์ไลน์อินพุตคือการแทรกการแปลง tf.data.Dataset.prefetch
ที่ส่วนท้ายของไปป์ไลน์ tf.data
ของคุณ การแปลงนี้จะซ้อนทับการคำนวณการประมวลผลล่วงหน้าของไปป์ไลน์อินพุตกับขั้นตอนถัดไปของการคำนวณแบบจำลอง และจำเป็นสำหรับประสิทธิภาพของไปป์ไลน์อินพุตที่เหมาะสมที่สุดเมื่อฝึกโมเดลของคุณ หากคุณกำลังดึงข้อมูลล่วงหน้า คุณควรเห็นชิ้นส่วน Iterator::Prefetch
บนเธรดเดียวกันกับคำสั่ง IteratorGetNext::DoCompute
หากคุณไม่มีการ prefetch
ที่ส่วนท้ายของไปป์ไลน์ คุณควรเพิ่มการดึงข้อมูลล่วงหน้า สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำแนะนำประสิทธิภาพ tf.data
โปรดดู คำแนะนำประสิทธิภาพ tf.data
หากคุณดึงข้อมูลล่วงหน้าแล้ว และไปป์ไลน์อินพุตยังคงเป็นคอขวด ให้ดำเนินการต่อในส่วนถัดไปเพื่อวิเคราะห์ประสิทธิภาพเพิ่มเติม
3. คุณมีการใช้งาน CPU สูงหรือไม่?
tf.data
รับปริมาณงานสูงโดยพยายามใช้ทรัพยากรที่มีอยู่ให้เกิดประโยชน์สูงสุด โดยทั่วไป แม้ในขณะที่เรียกใช้โมเดลของคุณบนตัวเร่งความเร็วเช่น GPU หรือ TPU ไปป์ไลน์ tf.data
จะทำงานบน CPU คุณสามารถตรวจสอบการใช้งานของคุณด้วยเครื่องมือต่างๆ เช่น sar และ htop หรือใน คอนโซลการตรวจสอบระบบคลาวด์ หากคุณใช้งาน GCP
หากการใช้งานของคุณต่ำ แสดงว่าอินพุตไปป์ไลน์ของคุณอาจไม่ได้ใช้ประโยชน์จาก CPU ของโฮสต์อย่างเต็มที่ คุณควรศึกษาแนวทางปฏิบัติที่ดีที่สุดจาก คู่มือประสิทธิภาพ tf.data หากคุณใช้แนวทางปฏิบัติที่ดีที่สุดและการใช้งานและปริมาณงานยังคงต่ำ ให้ไปที่ การวิเคราะห์คอขวด ด้านล่าง
หากการใช้งานของคุณใกล้ถึงขีดจำกัดของทรัพยากร เพื่อปรับปรุงประสิทธิภาพให้ดียิ่งขึ้น คุณต้องปรับปรุงประสิทธิภาพของไปป์ไลน์อินพุตของคุณ (เช่น หลีกเลี่ยงการคำนวณที่ไม่จำเป็น) หรือลดภาระการคำนวณ
คุณสามารถปรับปรุงประสิทธิภาพของไปป์ไลน์อินพุตของคุณได้โดยหลีกเลี่ยงการคำนวณที่ไม่จำเป็นใน 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
โปรดทราบว่าชื่อชุดข้อมูลอาจแตกต่างจาก python 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
จะตรงกับ ID ของเหตุการณ์ Prefetch
การระบุคอขวด
โดยทั่วไป ในการระบุคอขวดในไปป์ไลน์อินพุตของคุณ ให้เดินท่ออินพุตจากการแปลงด้านนอกสุดไปจนถึงต้นทาง เริ่มต้นจากการแปลงขั้นสุดท้ายในไปป์ไลน์ของคุณ วนซ้ำเป็นการแปลงต้นน้ำจนกว่าคุณจะพบการแปลงที่ช้าหรือเข้าถึงชุดข้อมูลต้นทาง เช่น TFRecord
ในตัวอย่างด้านบน คุณจะเริ่มจาก Prefetch
จากนั้นเดินอัปสตรีมไปที่ BatchV2
, FiniteRepeat
, Map
และสุดท้ายคือ Range
โดยทั่วไป การแปลงช้าจะสอดคล้องกับเหตุการณ์ที่มีความยาว แต่เหตุการณ์อินพุตสั้น ตัวอย่างบางส่วนดังต่อไปนี้
โปรดทราบว่าการแปลงขั้นสุดท้าย (นอกสุด) ในไปป์ไลน์อินพุตของโฮสต์ส่วนใหญ่เป็นเหตุการณ์ Iterator::Model
การแปลงแบบจำลองถูกนำมาใช้โดยอัตโนมัติโดยรันไทม์ tf.data
และใช้สำหรับการวัดและปรับแต่งประสิทธิภาพของไปป์ไลน์อินพุตโดยอัตโนมัติ
หากงานของคุณใช้ กลยุทธ์การกระจาย ตัว แสดงการติดตามจะมีเหตุการณ์เพิ่มเติมที่สอดคล้องกับไปป์ไลน์อินพุตของอุปกรณ์ การเปลี่ยนแปลงภายนอกสุดของไปป์ไลน์อุปกรณ์ (ซ้อนอยู่ภายใต้ IteratorGetNextOp::DoCompute
หรือ IteratorGetNextAsOptionalOp::DoCompute
) จะเป็นเหตุการณ์ Iterator::Prefetch
ที่มีเหตุการณ์ Upstream Iterator::Generator
คุณสามารถค้นหาไปป์ไลน์โฮสต์ที่เกี่ยวข้องได้โดยค้นหา Iterator::Model
events
ตัวอย่างที่ 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()
ตัวอย่างนี้คล้ายกับด้านบน แต่ใช้ ParallelMap แทน Map เราสังเกตว่า (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
- คู่มือผู้สร้างโปรไฟล์
- บทช่วยสอนการสร้างโปรไฟล์ด้วย colab