เพิ่มประสิทธิภาพ GPU ของ TensorFlow ด้วย 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

คู่มือนี้จะถือว่าคุณใช้ tf.function อยู่แล้วเพื่อเป็นพื้นฐานในการรับโค้ดที่มีประสิทธิภาพบน GPU 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 และส่วน การดำเนินการของ TensorFlow คุณสามารถระบุส่วนต่างๆ ของโมเดลได้ เช่น การส่งต่อ ฟังก์ชันการสูญเสีย การคำนวณการส่งผ่านย้อนกลับ/การไล่ระดับสี และการอัปเดตน้ำหนักของเครื่องมือเพิ่มประสิทธิภาพ คุณยังสามารถให้การดำเนินการทำงานบน GPU ถัดจากแต่ละ Stream ซึ่งอ้างอิงถึงสตรีม CUDA แต่ละสตรีมใช้สำหรับงานเฉพาะ ในการติดตามนี้ Stream#118 ใช้เพื่อเรียกใช้เคอร์เนลประมวลผลและสำเนาระหว่างอุปกรณ์ Stream#119 ใช้สำหรับการคัดลอกจากโฮสต์ไปยังอุปกรณ์ และ Stream#120 สำหรับการคัดลอกจากอุปกรณ์ไปยังโฮสต์

การติดตามด้านล่างแสดงคุณลักษณะทั่วไปของแบบจำลองที่มีประสิทธิภาพ

image

ตัวอย่างเช่น ไทม์ไลน์การประมวลผล GPU ( Stream#118 ) ดูเหมือน "ยุ่ง" และมีช่องว่างน้อยมาก มีสำเนาน้อยที่สุดจากโฮสต์ไปยังอุปกรณ์ ( Stream #119 ) และจากอุปกรณ์ไปยังโฮสต์ ( Stream #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 ที่ทำงานบนโฮสต์ การดำเนินการเหล่านี้ไม่มีประสิทธิภาพที่ดีเท่ากับการดำเนินการในกราฟ TensorFlow นอกจากนี้ การดำเนินการบางส่วนยังทำงานบน CPU และคัดลอกเทนเซอร์ไปมาจาก GPU

หากหลังจากเพิ่มประสิทธิภาพไปป์ไลน์อินพุตของคุณแล้ว หากคุณยังคงสังเกตเห็นช่องว่างระหว่างขั้นตอนในโปรแกรมดูการติดตาม คุณควรดูโค้ดโมเดลระหว่างขั้นตอนต่างๆ และตรวจสอบว่าการปิดใช้งานการเรียกกลับ/ตัววัดช่วยปรับปรุงประสิทธิภาพหรือไม่ รายละเอียดบางส่วนของการดำเนินการเหล่านี้ยังอยู่ในโปรแกรมดูการติดตาม (ทั้งด้านอุปกรณ์และฝั่งโฮสต์) คำแนะนำในสถานการณ์นี้คือการตัดจำหน่ายค่าใช้จ่ายของการดำเนินการเหล่านี้โดยดำเนินการตามจำนวนขั้นตอนที่กำหนดแทนทุกขั้นตอน เมื่อใช้เมธอด Model.compile ใน tf.keras API การตั้งค่าแฟล็ก steps_per_execution จะเป็นการดำเนินการนี้โดยอัตโนมัติ สำหรับลูปการฝึกแบบกำหนดเอง ให้ใช้ tf.while_loop . 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 สำหรับโปรไฟล์เดียวกันแสดงการดำเนินการ Mul 126,224 รายการใช้เวลา 2.77 วินาที ดังนั้นแต่ละเคอร์เนลจึงอยู่ที่ประมาณ 21.9 μs ซึ่งมีขนาดเล็กมาก (ประมาณเวลาเดียวกับเวลาแฝงในการเปิด) และอาจส่งผลให้เกิดความล่าช้าในการเปิดเคอร์เนลของโฮสต์

image

หาก โปรแกรมดูการติดตาม ของคุณแสดงช่องว่างเล็กๆ มากมายระหว่างการดำเนินการบน 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

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

image

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

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

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

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

image

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

3. เคอร์เนลที่มีประสิทธิภาพมากขึ้นบน GPU

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

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

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

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

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

2. การทำงานของฟิวส์

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

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

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

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

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

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

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

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

1. ใช้เคอร์เนล fp16 ที่เหมาะสมที่สุด

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

image

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

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

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

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

2. เปิดใช้งาน XLA ด้วย tf.function(jit_compile=True) หรือการจัดกลุ่มอัตโนมัติ

ในขั้นตอนสุดท้ายในการได้รับประสิทธิภาพสูงสุดด้วย GPU ตัวเดียว คุณสามารถทดลองเปิดใช้งาน XLA ซึ่งจะหลอมรวมการทำงานและนำไปสู่การใช้อุปกรณ์ที่ดีขึ้นและลดขนาดหน่วยความจำลง สำหรับรายละเอียดเกี่ยวกับวิธีเปิดใช้งาน 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 GPU เป็น 2 ตัว เป็นต้น

มุมมองการติดตามด้านล่างแสดงตัวอย่างค่าใช้จ่ายในการสื่อสารเพิ่มเติมเมื่อฝึกฝนบน 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 ไม่ได้ใช้งานแล้วเริ่มทำงานหลังจาก GPU2 เริ่มทำงานแล้ว

image

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

image

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

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