เพิ่มประสิทธิภาพ TensorFlow GPU ด้วย TensorFlow Profiler

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

ภาพรวม

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

หากคุณยังใหม่ต่อ Profiler:

พึงระลึกไว้เสมอว่าการนำการคำนวณออกไปยัง GPU อาจไม่เป็นประโยชน์เสมอไป โดยเฉพาะอย่างยิ่งสำหรับรุ่นขนาดเล็ก อาจมีค่าใช้จ่ายเนื่องจาก:

  • การถ่ายโอนข้อมูลระหว่างโฮสต์ (CPU) และอุปกรณ์ (GPU) และ
  • เนื่องจากเวลาแฝงที่เกี่ยวข้องเมื่อโฮสต์เปิดตัวเคอร์เนล GPU

เวิร์กโฟลว์การเพิ่มประสิทธิภาพประสิทธิภาพ

คู่มือนี้สรุปวิธีการดีบักปัญหาด้านประสิทธิภาพโดยเริ่มจาก GPU ตัวเดียว จากนั้นจึงย้ายไปยังโฮสต์เดียวที่มี GPU หลายตัว

ขอแนะนำให้ดีบักปัญหาประสิทธิภาพการทำงานตามลำดับต่อไปนี้:

  1. เพิ่มประสิทธิภาพและดีบักประสิทธิภาพใน GPU เดียว:
    1. ตรวจสอบว่าไปป์ไลน์อินพุตเป็นคอขวดหรือไม่
    2. ดีบักประสิทธิภาพของ GPU ตัวเดียว
    3. เปิดใช้งานความแม่นยำแบบผสม (ด้วย fp16 (float16)) และเปิดใช้งาน XLA หรือไม่ก็ได้
  2. เพิ่มประสิทธิภาพและดีบักประสิทธิภาพการทำงานบนโฮสต์เดียวที่มี GPU หลายตัว

ตัวอย่างเช่น หากคุณใช้ กลยุทธ์การกระจาย TensorFlow เพื่อฝึกโมเดลบนโฮสต์เดียวที่มี GPU หลายตัว และสังเกตเห็นการใช้งาน GPU ที่ไม่เหมาะสม คุณควรเพิ่มประสิทธิภาพและดีบักประสิทธิภาพของ GPU หนึ่งตัวก่อนจะทำการดีบักระบบ multi-GPU

เพื่อเป็นพื้นฐานในการรับโค้ดสำหรับประสิทธิภาพบน GPU คู่มือนี้จะถือว่าคุณใช้ tf.function อยู่แล้ว Keras Model.compile และ Model.fit API จะใช้ tf.function โดยอัตโนมัติภายใต้ประทุน เมื่อเขียนลูปการฝึกแบบกำหนดเองด้วย tf.GradientTape ให้อ้างอิงถึง ประสิทธิภาพที่ ดีขึ้นด้วย tf.function เกี่ยวกับวิธีการเปิดใช้ tf.function s

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

1. เพิ่มประสิทธิภาพบน GPU ตัวเดียว

ในกรณีที่เหมาะสม โปรแกรมของคุณควรมีการใช้ GPU สูง การสื่อสาร CPU (โฮสต์) กับ GPU (อุปกรณ์) น้อยที่สุด และไม่มีโอเวอร์เฮดจากไปป์ไลน์อินพุต

ขั้นตอนแรกในการวิเคราะห์ประสิทธิภาพคือการรับโปรไฟล์สำหรับรุ่นที่ใช้ GPU ตัวเดียว

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

TensorFlow Profiler Overview Page

ตัวเลขสำคัญที่ต้องให้ความสนใจในหน้าภาพรวมคือ:

  1. ระยะเวลาของขั้นตอนมาจากการทำงานของอุปกรณ์จริง
  2. เปอร์เซ็นต์ของการดำเนินการบนอุปกรณ์เทียบกับโฮสต์
  3. จำนวนเมล็ดที่ใช้ fp16

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

ด้านล่างเป็นรูปภาพของมุมมองการติดตามแบบจำลองที่ทำงานบน GPU ตัวเดียว จากส่วน TensorFlow Name Scope และส่วน TensorFlow Ops คุณสามารถระบุส่วนต่างๆ ของโมเดลได้ เช่น การส่งต่อ ฟังก์ชันการสูญเสีย การคำนวณการส่งต่อ/การไล่ระดับย้อนหลัง และการอัปเดตน้ำหนักของเครื่องมือเพิ่มประสิทธิภาพ คุณยังสามารถให้ ops ทำงานบน GPU ข้าง Stream แต่ละรายการ ซึ่งอ้างถึงสตรีม CUDA แต่ละสตรีมจะใช้สำหรับงานเฉพาะ ในการติดตามนี้ Stream#118 ใช้เพื่อเปิดใช้เคอร์เนลประมวลผลและสำเนาแบบอุปกรณ์ต่ออุปกรณ์ Stream#119 ใช้สำหรับคัดลอกจากโฮสต์ไปยังอุปกรณ์ และ Stream#120 สำหรับการคัดลอกจากอุปกรณ์ไปยังโฮสต์

การติดตามด้านล่างแสดงลักษณะทั่วไปของโมเดลนักแสดง

image

ตัวอย่างเช่น ไทม์ไลน์การประมวลผล GPU ( Stream#118 ) ดู "ไม่ว่าง" โดยมีช่องว่างน้อยมาก มีสำเนาน้อยที่สุดจากโฮสต์ไปยังอุปกรณ์ ( สตรีม #119 ) และจากอุปกรณ์หนึ่งไปยังอีกโฮสต์ ( สตรีม #120 ) รวมถึงช่องว่างระหว่างขั้นตอนน้อยที่สุด เมื่อคุณรัน Profiler สำหรับโปรแกรมของคุณ คุณอาจไม่สามารถระบุคุณลักษณะในอุดมคติเหล่านี้ในมุมมองการติดตามของคุณ ส่วนที่เหลือของคู่มือนี้ครอบคลุมสถานการณ์ทั่วไปและวิธีแก้ไข

1. ดีบักไปป์ไลน์อินพุต

ขั้นตอนแรกในการดีบักประสิทธิภาพของ GPU คือการพิจารณาว่าโปรแกรมของคุณถูกผูกไว้กับอินพุตหรือไม่ วิธีที่ง่ายที่สุดในการค้นหาสิ่งนี้คือการใช้ ตัววิเคราะห์ไปป์ไลน์อินพุต ของ Profiler บน TensorBoard ซึ่งให้ภาพรวมของเวลาที่ใช้ในไปป์ไลน์อินพุต

image

คุณสามารถดำเนินการต่อไปนี้ได้หากไปป์ไลน์อินพุตของคุณมีส่วนอย่างมากต่อเวลาของขั้นตอน:

  • คุณสามารถใช้ คำแนะนำ เฉพาะของ tf.data เพื่อเรียนรู้วิธีดีบักไปป์ไลน์อินพุตของคุณ
  • อีกวิธีที่รวดเร็วในการตรวจสอบว่าไพพ์ไลน์อินพุตเป็นคอขวดหรือไม่คือการใช้ข้อมูลอินพุตที่สร้างแบบสุ่มซึ่งไม่ต้องการการประมวลผลล่วงหน้าใดๆ นี่คือตัวอย่าง การใช้เทคนิคนี้สำหรับโมเดล ResNet หากไปป์ไลน์อินพุตเหมาะสมที่สุด คุณควรพบกับประสิทธิภาพที่คล้ายคลึงกันกับข้อมูลจริงและด้วยข้อมูลสุ่ม/สังเคราะห์ที่สร้างขึ้น ค่าใช้จ่ายเพียงอย่างเดียวในกรณีข้อมูลสังเคราะห์จะเกิดจากการคัดลอกข้อมูลอินพุตซึ่งสามารถดึงข้อมูลล่วงหน้าและปรับให้เหมาะสมได้อีกครั้ง

นอกจากนี้ โปรดดู แนวทางปฏิบัติที่ดีที่สุดสำหรับการเพิ่มประสิทธิภาพไปป์ไลน์ข้อมูลอินพุต

2. ดีบักประสิทธิภาพของหนึ่ง GPU

มีหลายปัจจัยที่อาจส่งผลให้มีการใช้ GPU ต่ำ ด้านล่างนี้คือบางสถานการณ์ที่สังเกตได้ทั่วไปเมื่อดูที่ตัวแสดงการ ติดตาม และวิธีแก้ปัญหาที่เป็นไปได้

1. วิเคราะห์ช่องว่างระหว่างขั้นตอน

การสังเกตทั่วไปเมื่อโปรแกรมของคุณไม่ทำงานอย่างเหมาะสมคือช่องว่างระหว่างขั้นตอนการฝึก ในภาพมุมมองการติดตามด้านล่าง มีช่องว่างขนาดใหญ่ระหว่างขั้นตอนที่ 8 ถึง 9 ซึ่งหมายความว่า GPU ไม่ได้ใช้งานในช่วงเวลานั้น

image

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

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

หากคุณสังเกตเห็นช่องว่างขนาดใหญ่ที่ฝั่งโฮสต์ ซึ่งกำหนดเวลาการดำเนินการเหล่านี้บน GPU คุณสามารถตั้งค่าตัวแปรสภาพแวดล้อม TF_GPU_THREAD_MODE=gpu_private เพื่อให้แน่ใจว่ามีการเปิดใช้เคอร์เนล GPU จากเธรดเฉพาะของตนเอง และไม่ต้องเข้าคิวหลังงาน tf.data

ช่องว่างระหว่างขั้นตอนอาจเกิดจากการคำนวณเมตริก การเรียกกลับของ Keras หรือการดำเนินการนอก tf.function ที่ทำงานบนโฮสต์ ops เหล่านี้ไม่มีประสิทธิภาพที่ดีเท่ากับ ops ในกราฟ TensorFlow นอกจากนี้ ops เหล่านี้บางส่วนทำงานบน CPU และคัดลอกเทนเซอร์ไปมาจาก GPU

หากหลังจากเพิ่มประสิทธิภาพไพพ์ไลน์อินพุตของคุณแล้ว คุณยังคงสังเกตเห็นช่องว่างระหว่างขั้นตอนในโปรแกรมดูการติดตาม คุณควรดูโค้ดโมเดลระหว่างขั้นตอนต่างๆ และตรวจสอบว่าการปิดใช้งานการเรียกกลับ/เมตริกช่วยเพิ่มประสิทธิภาพหรือไม่ รายละเอียดบางอย่างของ ops เหล่านี้ยังอยู่ในโปรแกรมดูการติดตาม (ทั้งด้านอุปกรณ์และโฮสต์) คำแนะนำในสถานการณ์นี้คือการตัดจำหน่ายค่าใช้จ่ายของ ops เหล่านี้โดยดำเนินการตามจำนวนขั้นตอนคงที่แทนที่จะเป็นทุกขั้นตอน เมื่อใช้วิธี Model.compile ใน tf.keras API การตั้งค่าแฟ steps_per_execution จะทำสิ่งนี้โดยอัตโนมัติ สำหรับลูปการฝึกแบบกำหนดเอง ให้ใช้ tf.while_loop

2. บรรลุการใช้งานอุปกรณ์ที่สูงขึ้น

1. เคอร์เนล GPU ขนาดเล็กและโฮสต์เคอร์เนลเริ่มต้นล่าช้า

โฮสต์จัดคิวเมล็ดให้ทำงานบน GPU แต่มีเวลาแฝง (ประมาณ 20-40 μs) ที่เกี่ยวข้องก่อนที่เมล็ดจะถูกดำเนินการบน GPU ในกรณีที่เหมาะสม โฮสต์จะจัดคิวเคอร์เนลบน GPU ให้เพียงพอเพื่อให้ GPU ใช้เวลาส่วนใหญ่ในการดำเนินการ แทนที่จะรอให้โฮสต์จัดคิวเคอร์เนลเพิ่มเติม

หน้าภาพรวม ของ Profiler บน TensorBoard จะแสดงเวลาที่ GPU ไม่ได้ใช้งานเนื่องจากการรอให้โฮสต์เปิดเคอร์เนล ในภาพด้านล่าง GPU ไม่ได้ใช้งานประมาณ 10% ของเวลาขั้นตอนที่รอการเปิดเมล็ด

image

โปรแกรมดูการติดตาม สำหรับโปรแกรมเดียวกันนี้แสดงช่องว่างเล็ก ๆ ระหว่างเมล็ดที่โฮสต์กำลังยุ่งกับการเปิดเมล็ดบน GPU

image

ด้วยการเปิดใช้ตัวเลือกเล็กๆ จำนวนมากบน GPU (เช่น การเพิ่มสเกลาร์) โฮสต์อาจไม่สามารถตาม GPU ได้ เครื่องมือ TensorFlow Stats ใน TensorBoard สำหรับโปรไฟล์เดียวกันแสดงการดำเนินการ 126,224 Mul โดยใช้เวลา 2.77 วินาที ดังนั้น เคอร์เนลแต่ละตัวจะมีขนาดประมาณ 21.9 μs ซึ่งมีขนาดเล็กมาก (ในช่วงเวลาเดียวกับเวลาแฝงในการเรียกใช้) และอาจส่งผลให้เกิดความล่าช้าในการเรียกใช้เคอร์เนลของโฮสต์

image

หาก โปรแกรมดูการติดตาม ของคุณแสดงช่องว่างเล็กๆ จำนวนมากระหว่าง ops บน GPU เช่นเดียวกับในภาพด้านบน คุณสามารถ:

  • เชื่อมต่อเมตริกซ์ขนาดเล็กและใช้ ops แบบเวกเตอร์หรือใช้ขนาดแบทช์ที่ใหญ่ขึ้นเพื่อให้เคอร์เนลที่เปิดใช้งานแต่ละตัวทำงานมากขึ้น ซึ่งจะทำให้ GPU ยุ่งนานขึ้น
  • ตรวจสอบให้แน่ใจว่าคุณกำลังใช้ tf.function เพื่อสร้างกราฟ TensorFlow เพื่อไม่ให้คุณเรียกใช้ ops ในโหมดกระตือรือร้นอย่างแท้จริง หากคุณกำลังใช้ Model.fit (ตรงข้ามกับลูปการฝึกแบบกำหนดเองด้วย tf.GradientTape ) ดังนั้น tf.keras.Model.compile จะทำสิ่งนี้ให้คุณโดยอัตโนมัติ
  • ฟิวส์เมล็ดโดยใช้ XLA กับ tf.function(jit_compile=True) หรือการจัดกลุ่มอัตโนมัติ สำหรับรายละเอียดเพิ่มเติม ไปที่ส่วน เปิดใช้งานความแม่นยำแบบผสมและ XLA ด้านล่างเพื่อเรียนรู้วิธีเปิดใช้งาน XLA เพื่อให้ได้ประสิทธิภาพที่สูงขึ้น คุณลักษณะนี้สามารถนำไปสู่การใช้อุปกรณ์สูง
2. TensorFlow op ตำแหน่ง

หน้าภาพรวม ของ Profiler จะแสดงเปอร์เซ็นต์ของ ops ที่วางบนโฮสต์เทียบกับอุปกรณ์ (คุณยังสามารถตรวจสอบตำแหน่งของ ops เฉพาะได้ด้วยการดูที่ตัวแสดงการ ติดตาม เช่นเดียวกับในภาพด้านล่าง คุณต้องการเปอร์เซ็นต์ของ ops บนโฮสต์ จะเล็กมากเมื่อเทียบกับตัวเครื่อง

image

ตามหลักการแล้ว ops ที่เน้นการประมวลผลส่วนใหญ่ควรวางบน GPU

ในการค้นหาว่าอุปกรณ์ใดที่การทำงานและเทนเซอร์ในโมเดลของคุณถูกกำหนดให้กับอุปกรณ์ ให้ตั้งค่า tf.debugging.set_log_device_placement(True) เป็นคำสั่งแรกของโปรแกรมของคุณ

โปรดทราบว่าในบางกรณี แม้ว่าคุณจะระบุตัวเลือกที่จะวางบนอุปกรณ์เฉพาะ การใช้งานอาจแทนที่เงื่อนไขนี้ (ตัวอย่าง: tf.unique ) แม้แต่สำหรับการฝึกอบรม GPU ตัวเดียว การระบุกลยุทธ์การจัดจำหน่าย เช่น tf.distribute.OneDeviceStrategy อาจส่งผลให้วาง ops ที่เจาะจงมากขึ้นบนอุปกรณ์ของคุณ

เหตุผลหนึ่งที่ทำให้ ops ส่วนใหญ่วางบน GPU คือการป้องกันไม่ให้มีการคัดลอกหน่วยความจำมากเกินไประหว่างโฮสต์และอุปกรณ์ (คาดว่าจะมีการคัดลอกหน่วยความจำสำหรับข้อมูลอินพุต/เอาต์พุตรุ่นระหว่างโฮสต์และอุปกรณ์) ตัวอย่างของการคัดลอกมากเกินไปจะแสดงในมุมมองการติดตามด้านล่างบนสตรีม GPU #167 , #168 และ #169

image

สำเนาเหล่านี้บางครั้งอาจส่งผลเสียต่อประสิทธิภาพหากบล็อกไม่ให้เคอร์เนล GPU ดำเนินการ การดำเนินการคัดลอกหน่วยความจำใน โปรแกรมดูการติดตาม มีข้อมูลเพิ่มเติมเกี่ยวกับ ops ที่เป็นแหล่งที่มาของเทนเซอร์ที่คัดลอกเหล่านี้ แต่อาจไม่ใช่เรื่องง่ายที่จะเชื่อมโยง memCopy กับ op ในกรณีเหล่านี้ คุณควรตรวจสอบการดำเนินการที่อยู่ใกล้เคียงเพื่อตรวจสอบว่าสำเนาหน่วยความจำเกิดขึ้นที่ตำแหน่งเดียวกันในทุกขั้นตอนหรือไม่

3. เมล็ดที่มีประสิทธิภาพมากขึ้นบน GPUs

เมื่อยอมรับการใช้งาน GPU ของโปรแกรมแล้ว ขั้นตอนต่อไปคือพิจารณาการเพิ่มประสิทธิภาพของเคอร์เนล GPU โดยใช้ Tensor Cores หรือการรวม ops

1. ใช้เทนเซอร์คอร์

GPU NVIDIA® สมัยใหม่มี Tensor Cores เฉพาะที่สามารถปรับปรุงประสิทธิภาพของเคอร์เนลที่เข้าเกณฑ์ได้อย่างมาก

คุณสามารถใช้ สถิติเคอร์เนล GPU ของ TensorBoard เพื่อดูว่าเคอร์เนลใดของ GPU ที่มีสิทธิ์ใช้ Tensor Core และเคอร์เนลใดใช้ Tensor Cores การเปิดใช้งาน fp16 (ดูหัวข้อการเปิดใช้งาน Mixed Precision ด้านล่าง) เป็นวิธีหนึ่งในการทำให้เคอร์เนล General Matrix Multiply (GEMM) ของโปรแกรมของคุณ (matmul ops) ใช้ Tensor Core เคอร์เนล GPU ใช้ Tensor Cores อย่างมีประสิทธิภาพเมื่อความแม่นยำคือ fp16 และมิติเทนเซอร์อินพุต/เอาท์พุตหารด้วย 8 หรือ 16 (สำหรับ int8 )

สำหรับคำแนะนำโดยละเอียดอื่นๆ เกี่ยวกับวิธีทำให้เคอร์เนลมีประสิทธิภาพสำหรับ GPU โปรดดูคู่มือ ประสิทธิภาพการเรียนรู้เชิงลึกของ NVIDIA®

2. ฟิวส์ ops

ใช้ tf.function(jit_compile=True) เพื่อรวม ops ที่มีขนาดเล็กลงเพื่อสร้างเคอร์เนลที่ใหญ่ขึ้นซึ่งนำไปสู่ประสิทธิภาพที่เพิ่มขึ้นอย่างมาก หากต้องการเรียนรู้เพิ่มเติม โปรดดูคู่มือ XLA

3. เปิดใช้งานความแม่นยำแบบผสมและ XLA

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

1. เปิดใช้งานความแม่นยำแบบผสม

คู่มือ ความแม่นยำ TensorFlow Mixed จะแสดงวิธีเปิดใช้งานความแม่นยำ fp16 บน GPU เปิดใช้งาน AMP บน NVIDIA® GPUs เพื่อใช้ Tensor Cores และรับรู้ถึงความเร็วที่เพิ่มขึ้นโดยรวมสูงสุด 3 เท่า เมื่อเทียบกับการใช้ความแม่นยำเพียง fp32 (float32) บน Volta และสถาปัตยกรรม GPU ที่ใหม่กว่า

ตรวจสอบให้แน่ใจว่ามิติเมทริกซ์/เทนเซอร์ตรงตามข้อกำหนดสำหรับการเรียกเคอร์เนลที่ใช้เทนเซอร์คอร์ เคอร์เนล GPU ใช้ Tensor Cores อย่างมีประสิทธิภาพเมื่อความแม่นยำเป็น fp16 และขนาดอินพุต/เอาต์พุตหารด้วย 8 หรือ 16 ลงตัว (สำหรับ int8)

โปรดทราบว่าด้วย cuDNN v7.6.3 และใหม่กว่า มิติของการบิดจะถูกเสริมโดยอัตโนมัติเมื่อจำเป็นเพื่อใช้ประโยชน์จาก Tensor Cores

ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดด้านล่างเพื่อเพิ่มประโยชน์ด้านประสิทธิภาพของความแม่นยำ fp16 สูงสุด

1. ใช้เมล็ด fp16 ที่เหมาะสมที่สุด

เมื่อเปิดใช้งาน fp16 การคูณเมทริกซ์ (GEMM) ของโปรแกรมควรใช้เวอร์ชัน fp16 ที่สอดคล้องกันซึ่งใช้ Tensor Cores อย่างไรก็ตาม ในบางกรณี สิ่งนี้จะไม่เกิดขึ้น และคุณไม่พบการเร่งความเร็วที่คาดไว้จากการเปิดใช้งาน fp16 เนื่องจากโปรแกรมของคุณกลับไปใช้การใช้งานที่ไม่มีประสิทธิภาพแทน

image

หน้าสถิติ เคอร์เนลของ GPU แสดงว่า ops ใดที่ Tensor Core มีสิทธิ์และเคอร์เนลใดกำลังใช้ Tensor Core ที่มีประสิทธิภาพ คู่มือ NVIDIA® เกี่ยวกับประสิทธิภาพการเรียนรู้เชิงลึก ประกอบด้วยคำแนะนำเพิ่มเติมเกี่ยวกับวิธีการใช้ประโยชน์จาก Tensor Cores นอกจากนี้ ประโยชน์ของการใช้ fp16 จะแสดงในเมล็ดที่ถูกผูกไว้กับหน่วยความจำก่อนหน้านี้ด้วย เนื่องจากตอนนี้ ops จะใช้เวลาเพียงครึ่งเดียว

2. การปรับขนาดการสูญเสียแบบไดนามิกเทียบกับแบบคงที่

จำเป็นต้องปรับขนาดความสูญเสียเมื่อใช้ fp16 เพื่อป้องกันการไหลล้นอันเนื่องมาจากความเที่ยงตรงต่ำ การปรับขนาดการสูญเสียมีสองประเภท ได้แก่ ไดนามิกและสแตติก ซึ่งทั้งสองประเภทมีคำอธิบายโดยละเอียดมากขึ้นใน คู่มือความแม่นยำผสม คุณสามารถใช้นโยบาย mixed_float16 เพื่อเปิดใช้งานการปรับขนาดการสูญเสียภายใน Keras Optimizer โดยอัตโนมัติ

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

2. เปิดใช้งาน XLA ด้วย tf.function(jit_compile=True) หรือ auto-clustering

เป็นขั้นตอนสุดท้ายในการรับประสิทธิภาพที่ดีที่สุดด้วย GPU ตัวเดียว คุณสามารถทดลองเปิดใช้งาน XLA ซึ่งจะหลอมรวม ops และนำไปสู่การใช้อุปกรณ์ที่ดีขึ้นและลดพื้นที่หน่วยความจำ สำหรับรายละเอียดเกี่ยวกับวิธีการเปิดใช้งาน XLA ในโปรแกรมของคุณด้วย tf.function(jit_compile=True) หรือการจัดกลุ่มอัตโนมัติ โปรดดูคู่มือ XLA

คุณสามารถตั้งค่าระดับ JIT ส่วนกลางเป็น -1 (ปิด), 1 หรือ 2 ระดับที่สูงขึ้นจะก้าวร้าวมากขึ้นและอาจลดความขนานและใช้หน่วยความจำมากขึ้น ตั้งค่าเป็น 1 หากคุณมีข้อจำกัดด้านหน่วยความจำ โปรดทราบว่า XLA ทำงานได้ไม่ดีสำหรับรุ่นที่มีรูปร่างเทนเซอร์อินพุตแบบแปรผัน เนื่องจากคอมไพเลอร์ XLA จะต้องคอยรวบรวมเมล็ดทุกครั้งที่พบรูปร่างใหม่

2. เพิ่มประสิทธิภาพการทำงานบนโฮสต์เดียวที่มี GPU หลายตัว

tf.distribute.MirroredStrategy API สามารถใช้เพื่อปรับขนาดการฝึกโมเดลจาก GPU ตัวเดียวเป็น GPU หลายตัวบนโฮสต์เดียว (หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีการทำการฝึกอบรมแบบกระจายด้วย TensorFlow โปรดดูที่การ ฝึกอบรมแบบกระจายด้วย TensorFlow ใช้ GPU และคำแนะนำการใช้ TPU และการ ฝึกอบรมแบบกระจายด้วยบทช่วยสอน Keras )

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

เมื่อเปลี่ยนจากการฝึกด้วย GPU ตัวเดียวไปเป็น GPU หลายตัวบนโฮสต์เดียวกัน คุณควรสัมผัสประสบการณ์การปรับขนาดประสิทธิภาพโดยมีค่าใช้จ่ายเพิ่มเติมในการสื่อสารแบบเกรเดียนต์และการใช้เธรดของโฮสต์ที่เพิ่มขึ้น ด้วยเหตุนี้ คุณจะไม่มีการเพิ่มความเร็ว 2x ที่แน่นอนหากคุณย้ายจาก 1 เป็น 2 GPU เป็นต้น

มุมมองการติดตามด้านล่างแสดงตัวอย่างค่าใช้จ่ายเพิ่มเติมในการสื่อสารเมื่อฝึกกับ GPU หลายตัว มีค่าใช้จ่ายบางอย่างในการเชื่อมโยงการไล่ระดับสี สื่อสารข้ามแบบจำลอง และแยกออกก่อนที่จะทำการอัปเดตน้ำหนัก

image

รายการตรวจสอบต่อไปนี้จะช่วยให้คุณได้รับประสิทธิภาพที่ดีขึ้นเมื่อปรับประสิทธิภาพให้เหมาะสมในสถานการณ์ที่มี GPU หลายตัว:

  1. พยายามเพิ่มขนาดแบตช์ให้สูงสุด ซึ่งจะนำไปสู่การใช้อุปกรณ์ที่สูงขึ้นและลดต้นทุนการสื่อสารระหว่าง GPU หลายตัว การใช้ตัวสร้าง โปรไฟล์หน่วยความจำ ช่วยให้เข้าใจว่าโปรแกรมของคุณใกล้ถึงขีดสุดของการใช้หน่วยความจำมากเพียงใด โปรดทราบว่าแม้ว่าขนาดแบทช์ที่สูงขึ้นอาจส่งผลต่อการบรรจบกัน แต่มักจะถูกมองข้ามโดยข้อดีด้านประสิทธิภาพ
  2. เมื่อย้ายจาก GPU ตัวเดียวไปเป็น GPU หลายตัว โฮสต์เดียวกันจะต้องประมวลผลข้อมูลที่ป้อนมากขึ้น ดังนั้น หลังจาก (1) ขอแนะนำให้ตรวจสอบประสิทธิภาพของไปป์ไลน์อินพุตอีกครั้ง และตรวจสอบให้แน่ใจว่าไม่ใช่คอขวด
  3. ตรวจสอบไทม์ไลน์ของ GPU ในมุมมองการติดตามของโปรแกรมของคุณสำหรับการโทร AllReduce ที่ไม่จำเป็น ซึ่งจะทำให้เกิดการซิงโครไนซ์กับอุปกรณ์ทั้งหมด ในมุมมองการติดตามที่แสดงด้านบน AllReduce ดำเนินการผ่านเคอร์เนล NCCL และมีการเรียก NCCL เพียงครั้งเดียวในแต่ละ GPU สำหรับการไล่ระดับสีในแต่ละขั้นตอน
  4. ตรวจสอบการทำสำเนา D2H, H2D และ D2D ที่ไม่จำเป็นซึ่งสามารถย่อให้เล็กสุดได้
  5. ตรวจสอบเวลาของขั้นตอนเพื่อให้แน่ใจว่าแบบจำลองแต่ละตัวทำงานเหมือนกัน ตัวอย่างเช่น อาจเกิดขึ้นได้ว่า GPU หนึ่งตัว (โดยปกติคือ GPU0 ) ถูกจองเกินเพราะโฮสต์ผิดพลาดทำให้ต้องทำงานหนักขึ้น
  6. สุดท้าย ตรวจสอบขั้นตอนการฝึกอบรมสำหรับ GPU ทั้งหมดในมุมมองการติดตามของคุณสำหรับการดำเนินการใดๆ ที่ดำเนินการตามลำดับ ซึ่งมักจะเกิดขึ้นเมื่อโปรแกรมของคุณมีการควบคุมการขึ้นต่อกันจาก GPU หนึ่งไปยังอีกตัวหนึ่ง ในอดีต การดีบักประสิทธิภาพในสถานการณ์นี้ได้รับการแก้ไขเป็นกรณีไป หากคุณสังเกตเห็นพฤติกรรมนี้ในโปรแกรมของคุณ ให้ ยื่นปัญหา GitHub พร้อมรูปภาพของมุมมองการติดตามของคุณ

1. ปรับการไล่ระดับสีให้เหมาะสม AllReduce

เมื่อฝึกด้วยกลยุทธ์แบบซิงโครนัส อุปกรณ์แต่ละเครื่องจะได้รับส่วนหนึ่งของข้อมูลที่ป้อนเข้า

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

GPU แต่ละตัวจะเชื่อมโยงการไล่ระดับสีข้ามเลเยอร์ของโมเดลก่อน สื่อสารข้าม GPU โดยใช้ tf.distribute.CrossDeviceOps ( tf.distribute.NcclAllReduce เป็นค่าเริ่มต้น) จากนั้นคืนค่าการไล่ระดับสีหลังจากลดขนาดต่อเลเยอร์

เครื่องมือเพิ่มประสิทธิภาพจะใช้การไล่ระดับสีที่ลดลงเหล่านี้เพื่ออัปเดตน้ำหนักของแบบจำลองของคุณ ตามหลักการแล้ว กระบวนการนี้ควรเกิดขึ้นพร้อมกันใน GPU ทั้งหมด เพื่อป้องกันโอเวอร์เฮด

เวลาในการ AllReduce ควรใกล้เคียงกับ:

(number of parameters * 4bytes)/ (communication bandwidth)

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

โปรดทราบว่าพารามิเตอร์แต่ละรุ่นมีขนาด 4 ไบต์ เนื่องจาก TensorFlow ใช้ fp32 (float32) เพื่อสื่อสารการไล่ระดับสี แม้ว่าคุณจะเปิดใช้งาน fp16 ไว้ NCCL AllReduce ยังใช้พารามิเตอร์ fp32

เพื่อให้ได้รับประโยชน์จากการปรับขนาด ขั้นตอนเวลาต้องสูงกว่ามากเมื่อเทียบกับค่าโสหุ้ยเหล่านี้ วิธีหนึ่งในการบรรลุเป้าหมายนี้คือการใช้ขนาดแบทช์ที่สูงขึ้น เนื่องจากขนาดแบทช์ส่งผลต่อเวลาของขั้นตอน แต่ไม่ส่งผลต่อโอเวอร์เฮดในการสื่อสาร

2. ความขัดแย้งเธรดโฮสต์ GPU

เมื่อใช้งาน GPU หลายตัว หน้าที่ของ CPU คือทำให้อุปกรณ์ทั้งหมดไม่ว่างโดยเปิดใช้เคอร์เนล GPU ทั่วทั้งอุปกรณ์อย่างมีประสิทธิภาพ

อย่างไรก็ตาม เมื่อมีการดำเนินการอิสระจำนวนมากที่ CPU สามารถกำหนดเวลาบน GPU ตัวหนึ่งได้ CPU สามารถตัดสินใจใช้เธรดโฮสต์จำนวนมากเพื่อให้ GPU ตัวหนึ่งไม่ว่าง จากนั้นจึงเปิดเคอร์เนลบน GPU อื่นในลำดับที่ไม่ได้กำหนด . ซึ่งอาจทำให้เกิดการปรับขนาดแบบเบ้หรือเชิงลบ ซึ่งอาจส่งผลเสียต่อประสิทธิภาพการทำงาน

โปรแกรมดูการติดตาม ด้านล่างแสดงโอเวอร์เฮดเมื่อ CPU ทำงานช้าเคอร์เนลของ GPU เปิดตัวอย่างไม่มีประสิทธิภาพ เนื่องจาก GPU1 ไม่ได้ใช้งานและเริ่มทำงาน ops หลังจาก GPU2 เริ่มทำงาน

image

มุมมองการติดตามสำหรับโฮสต์แสดงว่าโฮสต์กำลังเรียกใช้เคอร์เนลบน GPU2 ก่อนเปิดใช้งานบน GPU1 (โปรดทราบว่า tf_Compute* ops ด้านล่างไม่ได้บ่งบอกถึงเธรดของ CPU)

image

หากคุณประสบปัญหาการส่ายของเคอร์เนล GPU ในมุมมองการติดตามของโปรแกรม การดำเนินการที่แนะนำคือ:

  • ตั้งค่าตัวแปรสภาพแวดล้อม TF_GPU_THREAD_MODE เป็น gpu_private ตัวแปรสภาพแวดล้อมนี้จะบอกโฮสต์ให้เก็บเธรดสำหรับ GPU ส่วนตัว
  • โดยค่าเริ่มต้น TF_GPU_THREAD_MODE=gpu_private จะตั้งค่าจำนวนเธรดเป็น 2 ซึ่งเพียงพอในกรณีส่วนใหญ่ อย่างไรก็ตาม จำนวนนั้นสามารถเปลี่ยนแปลงได้โดยการตั้งค่าตัวแปรสภาพแวดล้อม TF_GPU_THREAD_COUNT เป็นจำนวนเธรดที่ต้องการ