คู่มือเริ่มต้นฉบับสมบูรณ์: การทำความเข้าใจและใช้ torch.profiler เพื่อเพิ่มประสิทธิภาพ PyTorch 🚀

"สิ่งใดที่เราวัดผลไม่ได้ สิ่งนั้นเราก็ปรับปรุงให้ดีขึ้นไม่ได้" คำกล่าวนี้เป็นจริงเสมอ ไม่ว่าคุณจะต้องการดึงประสิทธิภาพสูงสุดจากโมเดลภาษาขนาดใหญ่ (LLM) เพื่อให้ได้โทเค็นต่อวินาทีมากขึ้น ลดเวลาในการอนุมาน (inference) หรือเพียงแค่อยากรู้ว่าทำไมลูปการฝึก (training loop) ของคุณจึงทำงานช้ากว่าที่ควรจะเป็น การทำความเข้าใจและใช้งานเครื่องมือ Profiling คือกุญแจสำคัญ

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

โพสต์นี้จะเป็นจุดเริ่มต้นของซีรีส์ "Profiling in PyTorch" ที่จะช่วยลดอุปสรรคในการเรียนรู้การอ่าน Profiler traces และนำไปใช้ในการปรับปรุงประสิทธิภาพโมเดลของคุณอย่างเป็นขั้นตอน

สิ่งที่คุณจะได้เรียนรู้จากซีรีส์นี้

  • วิธีการตั้งค่า torch.profiler และทำความเข้าใจผลลัพธ์ที่ได้
  • วิธีอ่านตารางและ Trace ของ Profiler (ทั้งฝั่ง CPU, GPU และช่องว่างที่น่าสงสัยระหว่างนั้น)
  • ลำดับเหตุการณ์ตั้งแต่การเรียกใช้ Python ไปจนถึงการทำงานของ CUDA kernel
  • การเปลี่ยนแปลง (และสิ่งที่น่าสนใจคือสิ่งที่ไม่เปลี่ยนแปลง) เมื่อนำ torch.compile มาใช้

ก่อนอื่น มาทำความเข้าใจคำศัพท์พื้นฐานสองคำที่จะช่วยให้การอ่านเนื้อหาต่อไปนี้ง่ายขึ้น

  • GPU Kernel: คือโปรแกรมขนาดเล็กที่ทำงานแบบขนานบนเธรด (thread) จำนวนมากของ GPU
  • CPU: ทำหน้าที่จัดตารางเวลาและสั่งงานให้ GPU kernel ทำงาน

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

การเตรียมโค้ดและเครื่องมือ

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

การดำเนินการคูณเมทริกซ์และการบวก (Matrix Multiplication and Addition)

อย่างที่ Dr. Sara Hooker ได้กล่าวไว้ "เช่นเดียวกับที่ร่างกายมนุษย์ส่วนใหญ่ประกอบด้วยน้ำ โครงข่ายประสาทเทียมเชิงลึก (Deep Neural Networks) ส่วนใหญ่ก็ประกอบด้วยการคูณเมทริกซ์" การดำเนินการพื้นฐานนี้มีความสำคัญมาก และเป็นจุดเริ่มต้นที่ดีในการทำความเข้าใจการทำงานของ Profiler

การบวกเมทริกซ์เข้ากับการคูณเมทริกซ์จะเลียนแบบการทำงานของน้ำหนัก (weights) และไบแอส (biases) ในเซลล์ประสาท (neuron) ซึ่งจะช่วยให้เราเข้าใจกระบวนการที่ปูทางไปสู่การคอมไพล์ (compilation) ในภายหลัง

ขั้นตอนการทำ Profiling ด้วย torch.profiler

  1. เตรียมโค้ดที่ต้องการ Profile: ในที่นี้คือฟังก์ชัน def fn ซึ่งห่อหุ้มการคูณเมทริกซ์และการบวก
  2. การใส่คำอธิบาย (Annotation): แม้จะไม่จำเป็น แต่แนะนำให้ใช้ recordfunction เพื่อระบุชื่อฟังก์ชัน เช่น matmuladd ซึ่งจะช่วยให้การนำทางใน trace ง่ายขึ้น
  3. ครอบโค้ดด้วย torch.profiler.profile: ใช้ context manager เพื่อเริ่มและหยุดการเก็บข้อมูล

ผลลัพธ์จาก Profiler: สองส่วนสำคัญ

torch.profiler จะสร้างผลลัพธ์หลักสองประเภท:

  1. Profiler Table (ตารางสถิติ): ให้ข้อมูลสรุปเชิงสถิติของอัลกอริทึม ตอบคำถามว่า "อะไรคือส่วนที่ใช้เวลามากที่สุด" (Hotspots) ซึ่งอาจเป็นคอขวด (bottleneck) หรือเหตุการณ์ที่ถูกเรียกใช้บ่อยครั้ง
  2. Profiler Trace (การแสดงผลแบบเวลา): แสดงภาพรวมการทำงานตามเวลาจริง ตอบคำถามว่า "เมื่อไหร่และทำไมเหตุการณ์จึงเกิดขึ้น" โดยแสดงกิจกรรมบน CPU และ GPU ช่วยให้เราตรวจสอบ kernel ที่ถูกเรียกใช้, ความล่าช้าในการเรียกใช้, หรือการทำงานที่เหลื่อมกันระหว่าง CPU และ GPU

ทดลองใช้งาน Profiler

เมื่อรันสคริปต์ (บนเครื่องที่มี GPU) คุณจะพบโฟลเดอร์ traces/01matmuladd ที่มีไฟล์ผลลัพธ์สองประเภท:

1. ไฟล์ .txt: Profiler Table

ไฟล์นี้จะแสดงตารางขนาดใหญ่ โดยคอลัมน์แรกคือชื่อเหตุการณ์ที่เกิดขึ้นภายใน profile scope

  • คอลัมน์อื่นๆ: เกี่ยวข้องกับเวลาที่แต่ละเหตุการณ์ใช้บน CPU, GPU หรืออุปกรณ์อื่นๆ ที่ระบุไว้ใน activities ของ torch.profiler.profile
  • สิ่งที่ต้องสังเกต:
  • เวลาที่ใช้: เหตุการณ์ใดที่ใช้เวลานานที่สุด
  • **

of Calls**: จำนวนครั้งที่เหตุการณ์นั้นถูกเรียกใช้

ความแตกต่างระหว่าง "Self CPU/CUDA" กับ "CPU/CUDA total"

  • "Self" columns: วัดเวลาที่ใช้เฉพาะภายในเหตุการณ์นั้นๆ โดยไม่รวมการทำงานของส่วนย่อย (children events)
  • "Total" columns: รวมเวลาของเหตุการณ์นั้นๆ และส่วนย่อยทั้งหมด

การวิเคราะห์ผลลัพธ์เบื้องต้น

จากตารางผลลัพธ์ (เช่น Figure 1, 2) คุณอาจสังเกตได้ว่า:

  • หน่วยเวลา: CPU time แสดงเป็น ms (มิลลิวินาที) ในขณะที่ GPU time แสดงเป็น us (ไมโครวินาที)
  • GPU Idle Time: เวลาที่ใช้บน GPU (เช่น kernel amperebf16s16816gemm...) อาจน้อยกว่า 1% ของเวลาที่ใช้บน CPU (เช่น การทำงาน matmul_add ทั้งหมด) GPU มักจะว่างอยู่ส่วนใหญ่

สาเหตุ: GPU สามารถประมวลผลการคูณเมทริกซ์ขนาดเล็กได้อย่างรวดเร็วมาก ทำให้โค้ดส่วนใหญ่หมดไปกับการเตรียมการ, การสั่งงาน kernel, การส่งข้อมูล และการรวบรวมผลลัพธ์ นี่คือสิ่งที่เรียกว่า Overhead-bound algorithm

วิธีแก้ไขเบื้องต้น: ใช้การคูณเมทริกซ์ที่มีขนาดใหญ่ขึ้น

เมื่อลองรันด้วยเมทริกซ์ขนาดใหญ่ขึ้น (เช่น Figure 2) คุณจะเห็นว่า:

  • GPU time มีสัดส่วนเพิ่มขึ้นอย่างมาก
  • เวลาส่วนใหญ่บน CUDA ถูกใช้โดย GPU kernel เอง แทนที่จะเป็นการทำงานบน CPU ที่สั่งงาน

นี่แสดงว่าเราสามารถเปลี่ยนจากสภาวะ Overhead-bound ไปสู่ Compute-bound ได้สำเร็จ

2. ไฟล์ .json: Profiler Trace (สำหรับการแสดงผลด้วยภาพ)

ไฟล์ .json นี้สามารถนำไปอัปโหลดเพื่อดูใน Perfetto UI หรือใช้เครื่องมือ uvx trace-util เพื่อสร้างลิงก์ Perfetto ได้โดยตรง

การทำความเข้าใจ Perfetto Trace

  • ความกว้างของแถบ (Bar Width): แสดงระยะเวลาของเหตุการณ์
  • การซ้อนกันในแนวตั้ง (Vertical Nesting): แสดงลำดับชั้นของการเรียก (call hierarchy)
  • CPU Lane: แสดงเหตุการณ์ที่เกิดขึ้นบน CPU
  • GPU Lane: แสดงการทำงานของ CUDA kernel จริงๆ
  • ช่องว่างสีขาว: คือเวลาที่ระบบกำลังรอ (waiting) หรือไม่ได้ทำงาน (idle)

การตั้งค่าเริ่มต้นของสคริปต์

  • size 64: ขนาดของ input, weights, biases คือ (64, 64)
  • dtype bf16: ชนิดข้อมูลคือ bfloat16
  • no compile: ไม่ได้ใช้ torch.compile
  • no warmup: ไม่ได้มีการวอร์มอัพ (warmup) GPU ก่อนเก็บข้อมูล

การสำรวจ Trace ด้วย Perfetto

  • การนำทาง: ใช้ปุ่ม "W A S D" เพื่อเลื่อนดู trace
  • CPU Lane: สังเกต ProfilerStep#2 ซึ่งใช้เวลามากกว่าขั้นตอนอื่น ๆ
  • "Dead Window": คุณอาจสังเกตเห็นช่องว่างประมาณ 228 µs ระหว่างการเข้า recordfunction("matmuladd") และการที่ PyTorch เริ่มส่งงาน aten::matmul ช่องว่างนี้อาจเกิดจากการจัดสรรพื้นที่ทำงาน (workspace allocations), การทำงานของ cuBLAS หรือการโหลดโมดูลแบบ Lazy

การใช้ Warmup เพื่อลด Overhead

  • Warmup คืออะไร: คือการรันโค้ดหรือคำสั่งต่างๆ สักสองสามครั้งก่อนเริ่มเก็บข้อมูลจริง เพื่อให้ GPU ทำงานเตรียมการเบื้องต้นที่จำเป็นเสร็จสิ้นไปก่อน
  • วิธีการ:
  1. วนลูปเรียกฟังก์ชันก่อนเข้า profiler
  2. ใช้ argument warmup ใน torch.profiler.profile

เมื่อใช้ Warmup (Figure 7) จะเห็นว่าแต่ละ ProfilerStep ใช้เวลาใกล้เคียงกันมากขึ้น ซึ่งหมายความว่า overhead ที่เกิดขึ้นครั้งเดียวถูกย้ายออกไปนอกช่วงการวัดผลแล้ว

ช่องว่างเวลา (Offset) ระหว่าง CPU และ GPU

คุณอาจสังเกตเห็นความแตกต่างของเวลาประมาณ 2.5 ms ระหว่าง CPU Lane และ GPU Lane นี่คือช่วงเวลาหน่วง (delay) ตั้งแต่ CPU ส่ง CUDA kernel ไปจนถึง kernel เริ่มทำงานจริง

การสืบค้นสาเหตุ:

หากเราปรับเปลี่ยน schedule เล็กน้อย (Figure 9) เราจะเห็น Activity Buffer Request ใน GPU Lane ก่อนการทำงานจริง

เมื่อซูมเข้าไปใน GPU trace (Figure 10) คุณจะเห็นว่า kernel ของ ProfileStep#0 ทำงานต่อเนื่องกัน แต่ ProfileStep#1 มีช่องว่างระหว่างการทำงาน คำอธิบายที่เป็นไปได้มากที่สุด คือเกิด Buffer Overflow ทำให้ต้องมีการขอจัดสรรหน่วยความจำบน GPU VRAM ใหม่ในระหว่างการทำงานของ kernel

การตรวจสอบเพิ่มเติม: รัน profiling ด้วยจำนวน iteration ที่มากขึ้น (เช่น active=20) เพื่อดูว่าช่องว่างลักษณะนี้เกิดขึ้นอีกหรือไม่ (Figure 11)

การทำความเข้าใจ Dispatch Chain

Figure 12 แสดงภาพการเรียกใช้ซ้อนกัน (nested CPU calls) ซึ่งช่วยให้เห็นภาพรวมของ Dispatch Chain ได้อย่างชัดเจน:

  • เริ่มต้นด้วย ProfileStep#
  • ภายในคือ matmul_add (เนื่องจากการ annotate ของเรา)
  • matmul_add ประกอบด้วยการเรียก aten สองครั้ง: สำหรับ matrix multiplication และ matrix addition
  • aten::matmul คือจุดที่การเรียกใช้ matmul จากผู้ใช้ปลายทาง (user-facing PyTorch calls) มาถึง
  • aten::mm คือ backend สำหรับการคูณเมทริกซ์แบบ 2 มิติ

เป็นเรื่องน่าสนใจที่จะสังเกตว่า PyTorch จะเรียกใช้ aten::bmm (batched matrix multiplication) หากเราเพิ่มมิติ... (เนื้อหาต่อเนื่องในส่วนถัดไป)


คำถามที่พบบ่อย (FAQ)

Q1: torch.profiler เหมาะสำหรับใครบ้าง?

A1: เหมาะสำหรับนักพัฒนา PyTorch ทุกระดับที่ต้องการทำความเข้าใจว่าโค้ดของตนเองใช้ทรัพยากร (CPU/GPU) อย่างไร และต้องการหาจุดที่สามารถปรับปรุงประสิทธิภาพได้

Q2: ต้องมีความรู้เรื่อง CUDA มากแค่ไหนถึงจะใช้ torch.profiler ได้?

A2: ไม่จำเป็นต้องเป็นผู้เชี่ยวชาญด้าน CUDA แต่การเข้าใจแนวคิดพื้นฐานเกี่ยวกับ GPU kernel และการทำงานร่วมกันระหว่าง CPU/GPU จะช่วยให้ตีความผลลัพธ์ได้ดียิ่งขึ้น

Q3: torch.compile เกี่ยวข้องกับการ Profiling อย่างไร?

A3: torch.compile เป็นเทคนิคที่ช่วยเพิ่มความเร็วในการประมวลผลโดยการคอมไพล์โค้ด PyTorch ให้เป็นรูปแบบที่ทำงานได้เร็วกว่าเดิม การทำ profiling สามารถช่วยให้เห็นว่า torch.compile มีผลต่อการใช้ทรัพยากรอย่างไรบ้าง และช่วยระบุส่วนที่อาจยังไม่ได้รับการปรับปรุงอย่างเต็มที่

Q4: ผลลัพธ์จาก Profiler Table และ Profiler Trace ต่างกันอย่างไร?

A4: Profiler Table จะให้ภาพรวมเชิงสถิติว่าส่วนไหนใช้เวลามากที่สุด ในขณะที่ Profiler Trace จะแสดงลำดับเหตุการณ์และกิจกรรมที่เกิดขึ้นตามเวลาจริง ทำให้เห็นภาพการทำงานตั้งแต่ต้นจนจบและสาเหตุของความล่าช้าได้ดีกว่า

Q5: ทำไม CPU ถึงใช้เวลานานกว่า GPU มากในการคำนวณเล็กๆ?

A5: เป็นเพราะ overhead ในการเตรียมการ, การส่งคำสั่ง, การจัดการข้อมูล และการเริ่มต้น GPU kernel ซึ่งมักจะใช้เวลานานกว่าการคำนวณจริงบน GPU สำหรับงานขนาดเล็ก การเพิ่มขนาดการคำนวณจะช่วยลดผลกระทบของ overhead นี้ได้

#PyTorch #Profiling #DeepLearning #AI #Optimization

ขอบคุณ แหล่งข้อมูล
https://huggingface.co/blog/torch-profiler

คู่มือเริ่มต้นฉบับสมบูรณ์: การทำความเข้าใจและใช้ torch.profiler เพื่อเพิ่มประสิทธิภาพ PyTorch 🚀"สิ่งใดที่เราวัดผลไม่ได้ สิ่งนั้นเราก็ปรับปรุงให้ดีขึ้นไม่ได้" คำกล่าวนี้เป็นจริงเสมอ ไม่ว่าคุณจะต้องการดึงประสิทธิภาพสูงสุดจากโมเดลภาษาขนาดใหญ่ (LLM) เพื่อให้ได้โทเค็นต่อวินาทีมากขึ้น ลดเวลาในการอนุมาน (inference) หรือเพียงแค่อยากรู้ว่าทำไมลูปการฝึก (training loop) ของคุณจึงทำงานช้ากว่าที่ควรจะเป็น การทำความเข้าใจและใช้งานเครื่องมือ Profiling คือกุญแจสำคัญแต่บ่อยครั้งที่การเริ่มต้นใช้งาน Profiler อาจดูน่ากลัว เพราะข้อมูลที่ได้มักจะเต็มไปด้วยกราฟิกที่ซับซ้อน ชื่อเหตุการณ์ที่ดูเข้าใจยาก และบทแนะนำส่วนใหญ่ก็มักจะสมมติว่าคุณมีความรู้พื้นฐานอยู่แล้ว ทำให้หลายคนเลื่อนการทำ Profiling ออกไป หรือมอบหมายให้คนอื่นทำแทนโพสต์นี้จะเป็นจุดเริ่มต้นของซีรีส์ "Profiling in PyTorch" ที่จะช่วยลดอุปสรรคในการเรียนรู้การอ่าน Profiler traces และนำไปใช้ในการปรับปรุงประสิทธิภาพโมเดลของคุณอย่างเป็นขั้นตอนสิ่งที่คุณจะได้เรียนรู้จากซีรีส์นี้วิธีการตั้งค่า torch.profiler และทำความเข้าใจผลลัพธ์ที่ได้วิธีอ่านตารางและ Trace ของ Profiler (ทั้งฝั่ง CPU, GPU และช่องว่างที่น่าสงสัยระหว่างนั้น)ลำดับเหตุการณ์ตั้งแต่การเรียกใช้ Python ไปจนถึงการทำงานของ CUDA kernelการเปลี่ยนแปลง (และสิ่งที่น่าสนใจคือสิ่งที่ไม่เปลี่ยนแปลง) เมื่อนำ torch.compile มาใช้ก่อนอื่น มาทำความเข้าใจคำศัพท์พื้นฐานสองคำที่จะช่วยให้การอ่านเนื้อหาต่อไปนี้ง่ายขึ้นGPU Kernel: คือโปรแกรมขนาดเล็กที่ทำงานแบบขนานบนเธรด (thread) จำนวนมากของ GPUCPU: ทำหน้าที่จัดตารางเวลาและสั่งงานให้ GPU kernel ทำงานโดยปกติแล้ว คุณไม่จำเป็นต้องเขียน GPU kernel เอง เมื่อคุณใช้คำสั่ง PyTorch ใดๆ มันจะถูกแปลงเป็น GPU kernel หนึ่งตัวหรือมากกว่านั้นเพื่อทำงานบน GPUการเตรียมโค้ดและเครื่องมือเราจะใช้สคริปต์ 01matmuladd.py เป็นตัวอย่างในการเริ่มต้น ซึ่งคุณสามารถเปิดดูควบคู่ไปกับเนื้อหานี้ได้ แนะนำให้รันสคริปต์นี้บนเครื่องที่มี GPU เพื่อให้เห็นผลลัพธ์ได้ชัดเจนการดำเนินการคูณเมทริกซ์และการบวก (Matrix Multiplication and Addition)อย่างที่ Dr. Sara Hooker ได้กล่าวไว้ "เช่นเดียวกับที่ร่างกายมนุษย์ส่วนใหญ่ประกอบด้วยน้ำ โครงข่ายประสาทเทียมเชิงลึก (Deep Neural Networks) ส่วนใหญ่ก็ประกอบด้วยการคูณเมทริกซ์" การดำเนินการพื้นฐานนี้มีความสำคัญมาก และเป็นจุดเริ่มต้นที่ดีในการทำความเข้าใจการทำงานของ Profilerการบวกเมทริกซ์เข้ากับการคูณเมทริกซ์จะเลียนแบบการทำงานของน้ำหนัก (weights) และไบแอส (biases) ในเซลล์ประสาท (neuron) ซึ่งจะช่วยให้เราเข้าใจกระบวนการที่ปูทางไปสู่การคอมไพล์ (compilation) ในภายหลังขั้นตอนการทำ Profiling ด้วย torch.profilerเตรียมโค้ดที่ต้องการ Profile: ในที่นี้คือฟังก์ชัน def fn ซึ่งห่อหุ้มการคูณเมทริกซ์และการบวกการใส่คำอธิบาย (Annotation): แม้จะไม่จำเป็น แต่แนะนำให้ใช้ recordfunction เพื่อระบุชื่อฟังก์ชัน เช่น matmuladd ซึ่งจะช่วยให้การนำทางใน trace ง่ายขึ้นครอบโค้ดด้วย torch.profiler.profile: ใช้ context manager เพื่อเริ่มและหยุดการเก็บข้อมูลผลลัพธ์จาก Profiler: สองส่วนสำคัญtorch.profiler จะสร้างผลลัพธ์หลักสองประเภท:Profiler Table (ตารางสถิติ): ให้ข้อมูลสรุปเชิงสถิติของอัลกอริทึม ตอบคำถามว่า "อะไรคือส่วนที่ใช้เวลามากที่สุด" (Hotspots) ซึ่งอาจเป็นคอขวด (bottleneck) หรือเหตุการณ์ที่ถูกเรียกใช้บ่อยครั้งProfiler Trace (การแสดงผลแบบเวลา): แสดงภาพรวมการทำงานตามเวลาจริง ตอบคำถามว่า "เมื่อไหร่และทำไมเหตุการณ์จึงเกิดขึ้น" โดยแสดงกิจกรรมบน CPU และ GPU ช่วยให้เราตรวจสอบ kernel ที่ถูกเรียกใช้, ความล่าช้าในการเรียกใช้, หรือการทำงานที่เหลื่อมกันระหว่าง CPU และ GPUทดลองใช้งาน Profilerเมื่อรันสคริปต์ (บนเครื่องที่มี GPU) คุณจะพบโฟลเดอร์ traces/01matmuladd ที่มีไฟล์ผลลัพธ์สองประเภท:1. ไฟล์ .txt: Profiler Tableไฟล์นี้จะแสดงตารางขนาดใหญ่ โดยคอลัมน์แรกคือชื่อเหตุการณ์ที่เกิดขึ้นภายใน profile scopeคอลัมน์อื่นๆ: เกี่ยวข้องกับเวลาที่แต่ละเหตุการณ์ใช้บน CPU, GPU หรืออุปกรณ์อื่นๆ ที่ระบุไว้ใน activities ของ torch.profiler.profileสิ่งที่ต้องสังเกต:เวลาที่ใช้: เหตุการณ์ใดที่ใช้เวลานานที่สุด**of Calls**: จำนวนครั้งที่เหตุการณ์นั้นถูกเรียกใช้ความแตกต่างระหว่าง "Self CPU/CUDA" กับ "CPU/CUDA total""Self" columns: วัดเวลาที่ใช้เฉพาะภายในเหตุการณ์นั้นๆ โดยไม่รวมการทำงานของส่วนย่อย (children events)"Total" columns: รวมเวลาของเหตุการณ์นั้นๆ และส่วนย่อยทั้งหมดการวิเคราะห์ผลลัพธ์เบื้องต้นจากตารางผลลัพธ์ (เช่น Figure 1, 2) คุณอาจสังเกตได้ว่า:หน่วยเวลา: CPU time แสดงเป็น ms (มิลลิวินาที) ในขณะที่ GPU time แสดงเป็น us (ไมโครวินาที)GPU Idle Time: เวลาที่ใช้บน GPU (เช่น kernel amperebf16s16816gemm...) อาจน้อยกว่า 1% ของเวลาที่ใช้บน CPU (เช่น การทำงาน matmul_add ทั้งหมด) GPU มักจะว่างอยู่ส่วนใหญ่สาเหตุ: GPU สามารถประมวลผลการคูณเมทริกซ์ขนาดเล็กได้อย่างรวดเร็วมาก ทำให้โค้ดส่วนใหญ่หมดไปกับการเตรียมการ, การสั่งงาน kernel, การส่งข้อมูล และการรวบรวมผลลัพธ์ นี่คือสิ่งที่เรียกว่า Overhead-bound algorithmวิธีแก้ไขเบื้องต้น: ใช้การคูณเมทริกซ์ที่มีขนาดใหญ่ขึ้นเมื่อลองรันด้วยเมทริกซ์ขนาดใหญ่ขึ้น (เช่น Figure 2) คุณจะเห็นว่า:GPU time มีสัดส่วนเพิ่มขึ้นอย่างมากเวลาส่วนใหญ่บน CUDA ถูกใช้โดย GPU kernel เอง แทนที่จะเป็นการทำงานบน CPU ที่สั่งงานนี่แสดงว่าเราสามารถเปลี่ยนจากสภาวะ Overhead-bound ไปสู่ Compute-bound ได้สำเร็จ2. ไฟล์ .json: Profiler Trace (สำหรับการแสดงผลด้วยภาพ)ไฟล์ .json นี้สามารถนำไปอัปโหลดเพื่อดูใน Perfetto UI หรือใช้เครื่องมือ uvx trace-util เพื่อสร้างลิงก์ Perfetto ได้โดยตรงการทำความเข้าใจ Perfetto Traceความกว้างของแถบ (Bar Width): แสดงระยะเวลาของเหตุการณ์การซ้อนกันในแนวตั้ง (Vertical Nesting): แสดงลำดับชั้นของการเรียก (call hierarchy)CPU Lane: แสดงเหตุการณ์ที่เกิดขึ้นบน CPUGPU Lane: แสดงการทำงานของ CUDA kernel จริงๆช่องว่างสีขาว: คือเวลาที่ระบบกำลังรอ (waiting) หรือไม่ได้ทำงาน (idle)การตั้งค่าเริ่มต้นของสคริปต์size 64: ขนาดของ input, weights, biases คือ (64, 64)dtype bf16: ชนิดข้อมูลคือ bfloat16no compile: ไม่ได้ใช้ torch.compileno warmup: ไม่ได้มีการวอร์มอัพ (warmup) GPU ก่อนเก็บข้อมูลการสำรวจ Trace ด้วย Perfettoการนำทาง: ใช้ปุ่ม "W A S D" เพื่อเลื่อนดู traceCPU Lane: สังเกต ProfilerStep#2 ซึ่งใช้เวลามากกว่าขั้นตอนอื่น ๆ"Dead Window": คุณอาจสังเกตเห็นช่องว่างประมาณ 228 µs ระหว่างการเข้า recordfunction("matmuladd") และการที่ PyTorch เริ่มส่งงาน aten::matmul ช่องว่างนี้อาจเกิดจากการจัดสรรพื้นที่ทำงาน (workspace allocations), การทำงานของ cuBLAS หรือการโหลดโมดูลแบบ Lazyการใช้ Warmup เพื่อลด OverheadWarmup คืออะไร: คือการรันโค้ดหรือคำสั่งต่างๆ สักสองสามครั้งก่อนเริ่มเก็บข้อมูลจริง เพื่อให้ GPU ทำงานเตรียมการเบื้องต้นที่จำเป็นเสร็จสิ้นไปก่อนวิธีการ:วนลูปเรียกฟังก์ชันก่อนเข้า profilerใช้ argument warmup ใน torch.profiler.profileเมื่อใช้ Warmup (Figure 7) จะเห็นว่าแต่ละ ProfilerStep ใช้เวลาใกล้เคียงกันมากขึ้น ซึ่งหมายความว่า overhead ที่เกิดขึ้นครั้งเดียวถูกย้ายออกไปนอกช่วงการวัดผลแล้วช่องว่างเวลา (Offset) ระหว่าง CPU และ GPUคุณอาจสังเกตเห็นความแตกต่างของเวลาประมาณ 2.5 ms ระหว่าง CPU Lane และ GPU Lane นี่คือช่วงเวลาหน่วง (delay) ตั้งแต่ CPU ส่ง CUDA kernel ไปจนถึง kernel เริ่มทำงานจริงการสืบค้นสาเหตุ:หากเราปรับเปลี่ยน schedule เล็กน้อย (Figure 9) เราจะเห็น Activity Buffer Request ใน GPU Lane ก่อนการทำงานจริงเมื่อซูมเข้าไปใน GPU trace (Figure 10) คุณจะเห็นว่า kernel ของ ProfileStep#0 ทำงานต่อเนื่องกัน แต่ ProfileStep#1 มีช่องว่างระหว่างการทำงาน คำอธิบายที่เป็นไปได้มากที่สุด คือเกิด Buffer Overflow ทำให้ต้องมีการขอจัดสรรหน่วยความจำบน GPU VRAM ใหม่ในระหว่างการทำงานของ kernelการตรวจสอบเพิ่มเติม: รัน profiling ด้วยจำนวน iteration ที่มากขึ้น (เช่น active=20) เพื่อดูว่าช่องว่างลักษณะนี้เกิดขึ้นอีกหรือไม่ (Figure 11)การทำความเข้าใจ Dispatch ChainFigure 12 แสดงภาพการเรียกใช้ซ้อนกัน (nested CPU calls) ซึ่งช่วยให้เห็นภาพรวมของ Dispatch Chain ได้อย่างชัดเจน:เริ่มต้นด้วย ProfileStep#ภายในคือ matmul_add (เนื่องจากการ annotate ของเรา)matmul_add ประกอบด้วยการเรียก aten สองครั้ง: สำหรับ matrix multiplication และ matrix additionaten::matmul คือจุดที่การเรียกใช้ matmul จากผู้ใช้ปลายทาง (user-facing PyTorch calls) มาถึงaten::mm คือ backend สำหรับการคูณเมทริกซ์แบบ 2 มิติเป็นเรื่องน่าสนใจที่จะสังเกตว่า PyTorch จะเรียกใช้ aten::bmm (batched matrix multiplication) หากเราเพิ่มมิติ... (เนื้อหาต่อเนื่องในส่วนถัดไป)คำถามที่พบบ่อย (FAQ)Q1: torch.profiler เหมาะสำหรับใครบ้าง?A1: เหมาะสำหรับนักพัฒนา PyTorch ทุกระดับที่ต้องการทำความเข้าใจว่าโค้ดของตนเองใช้ทรัพยากร (CPU/GPU) อย่างไร และต้องการหาจุดที่สามารถปรับปรุงประสิทธิภาพได้Q2: ต้องมีความรู้เรื่อง CUDA มากแค่ไหนถึงจะใช้ torch.profiler ได้?A2: ไม่จำเป็นต้องเป็นผู้เชี่ยวชาญด้าน CUDA แต่การเข้าใจแนวคิดพื้นฐานเกี่ยวกับ GPU kernel และการทำงานร่วมกันระหว่าง CPU/GPU จะช่วยให้ตีความผลลัพธ์ได้ดียิ่งขึ้นQ3: torch.compile เกี่ยวข้องกับการ Profiling อย่างไร?A3: torch.compile เป็นเทคนิคที่ช่วยเพิ่มความเร็วในการประมวลผลโดยการคอมไพล์โค้ด PyTorch ให้เป็นรูปแบบที่ทำงานได้เร็วกว่าเดิม การทำ profiling สามารถช่วยให้เห็นว่า torch.compile มีผลต่อการใช้ทรัพยากรอย่างไรบ้าง และช่วยระบุส่วนที่อาจยังไม่ได้รับการปรับปรุงอย่างเต็มที่Q4: ผลลัพธ์จาก Profiler Table และ Profiler Trace ต่างกันอย่างไร?A4: Profiler Table จะให้ภาพรวมเชิงสถิติว่าส่วนไหนใช้เวลามากที่สุด ในขณะที่ Profiler Trace จะแสดงลำดับเหตุการณ์และกิจกรรมที่เกิดขึ้นตามเวลาจริง ทำให้เห็นภาพการทำงานตั้งแต่ต้นจนจบและสาเหตุของความล่าช้าได้ดีกว่าQ5: ทำไม CPU ถึงใช้เวลานานกว่า GPU มากในการคำนวณเล็กๆ?A5: เป็นเพราะ overhead ในการเตรียมการ, การส่งคำสั่ง, การจัดการข้อมูล และการเริ่มต้น GPU kernel ซึ่งมักจะใช้เวลานานกว่าการคำนวณจริงบน GPU สำหรับงานขนาดเล็ก การเพิ่มขนาดการคำนวณจะช่วยลดผลกระทบของ overhead นี้ได้#PyTorch #Profiling #DeepLearning #AI #Optimizationhttps://huggingface.co/blog/torch-profiler
Shared content
HUGGINGFACE.CO
Profiling in PyTorch (Part 1): A Beginner's Guide to torch.profiler
We’re on a journey to advance and democratize artificial intelligence through open source and open science.
5 Σχόλια 0 Μοιράστηκε 322 Views 0 Προεπισκόπηση