“บั๊กเดียวเปลี่ยนชีวิต! เมื่อ PyTorch สอนมากกว่าหลายปีที่ใช้งาน”

ลองจินตนาการว่าคุณกำลังเทรนโมเดล deep learning บน MacBook ด้วย PyTorch แล้วอยู่ดีๆ loss ก็หยุดนิ่งไม่ขยับ ทั้งที่คุณมั่นใจว่าโค้ดไม่มีปัญหา… นี่คือจุดเริ่มต้นของการผจญภัยของ Elana Simon นักวิจัยจาก Stanford ที่ค้นพบว่าไม่ใช่แค่ hyperparameter ที่ผิด แต่เป็นบั๊กลึกใน PyTorch ที่ซ่อนอยู่ในระดับ kernel บน Apple Silicon GPU!

เธอเริ่มจากการสงสัยตัวเอง ลองปรับทุกอย่างที่คิดได้ ตั้งแต่ learning rate ไปจนถึง auxiliary loss แต่ encoder weights กลับไม่ขยับเลยแม้แต่นิดเดียว ทั้งที่ decoder weights กลับอัปเดตตามปกติ สุดท้ายเธอพบว่า optimizer Adam บน MPS (Metal Performance Shaders) มีปัญหากับ tensor ที่ไม่ contiguous ซึ่งทำให้บาง operation อย่าง addcmul_() และ addcdiv_() ไม่อัปเดตค่าเลยแม้จะคำนวณเสร็จแล้ว!

การแก้ปัญหานี้ไม่ใช่แค่การเรียก .contiguous() แต่ยังต้องเข้าใจการทำงานของ kernel, memory layout, และ dispatch system ของ PyTorch อย่างลึกซึ้ง ซึ่งเธอได้เรียนรู้ทั้งหมดจากการไล่ debug ทีละขั้นตอน และสุดท้ายก็สามารถแก้บั๊กได้เอง พร้อมส่ง pull request ไปยัง PyTorch repo!

นอกจากนั้น เธอยังพบว่า operation อื่นๆ เช่น random_() ก็มีบั๊กแบบเดียวกัน และทั้งหมดนี้เกิดจาก abstraction ที่ “รั่ว” ของ Placeholder ที่ไม่รู้ว่าตัวเองกำลังจัดการกับ output tensor หรือ input tensor

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

ปัญหาเริ่มต้นจาก loss ที่ไม่ลดลง
เกิดขึ้นกับ encoder weights ที่ไม่อัปเดตเลย

การตรวจสอบพบว่า gradients มีอยู่จริง
encoder มี gradient ขนาดใหญ่และไม่เป็น NaN

Optimizer Adam เป็นต้นเหตุ
encoder weights ไม่อัปเดตเมื่อใช้ Adam แต่ทำงานปกติเมื่อใช้ SGD

การตรวจสอบ state ของ Adam พบว่า exp_avg_sq เป็นศูนย์
ทั้งที่ควรมีค่าเพราะ gradients ไม่เป็นศูนย์

การเปลี่ยนไปใช้ float64 ทำให้ปัญหาหายไป
แต่จริงๆ แล้วเป็นเพราะเปลี่ยนจาก MPS ไปใช้ CPU โดยไม่ตั้งใจ

ปัญหาเกิดจาก device-specific kernel บน MPS
บาง operation ไม่สามารถเขียนค่าลงใน non-contiguous tensor ได้

การแก้ไขคือการทำ tensor ให้ contiguous ก่อนใช้งาน
โดยเรียก .contiguous() ก่อน optimizer step

การตรวจสอบ source code พบว่า operation บางตัวไม่เช็ค contiguity
เช่น addcmul_() และ addcdiv_() บน MPS ไม่ทำ copy-back

การแก้ไขใน PyTorch v2.4 ได้แก้ปัญหานี้แล้ว
โดยเพิ่มขั้นตอน copy-back สำหรับ non-contiguous output

macOS 15 รองรับ non-contiguous tensor โดยตรง
ลดความจำเป็นในการ workaround ด้วยการ copy

https://elanapearl.github.io/blog/2025/the-bug-that-taught-me-pytorch/
🧠 “บั๊กเดียวเปลี่ยนชีวิต! เมื่อ PyTorch สอนมากกว่าหลายปีที่ใช้งาน” ลองจินตนาการว่าคุณกำลังเทรนโมเดล deep learning บน MacBook ด้วย PyTorch แล้วอยู่ดีๆ loss ก็หยุดนิ่งไม่ขยับ ทั้งที่คุณมั่นใจว่าโค้ดไม่มีปัญหา… นี่คือจุดเริ่มต้นของการผจญภัยของ Elana Simon นักวิจัยจาก Stanford ที่ค้นพบว่าไม่ใช่แค่ hyperparameter ที่ผิด แต่เป็นบั๊กลึกใน PyTorch ที่ซ่อนอยู่ในระดับ kernel บน Apple Silicon GPU! เธอเริ่มจากการสงสัยตัวเอง ลองปรับทุกอย่างที่คิดได้ ตั้งแต่ learning rate ไปจนถึง auxiliary loss แต่ encoder weights กลับไม่ขยับเลยแม้แต่นิดเดียว ทั้งที่ decoder weights กลับอัปเดตตามปกติ สุดท้ายเธอพบว่า optimizer Adam บน MPS (Metal Performance Shaders) มีปัญหากับ tensor ที่ไม่ contiguous ซึ่งทำให้บาง operation อย่าง addcmul_() และ addcdiv_() ไม่อัปเดตค่าเลยแม้จะคำนวณเสร็จแล้ว! การแก้ปัญหานี้ไม่ใช่แค่การเรียก .contiguous() แต่ยังต้องเข้าใจการทำงานของ kernel, memory layout, และ dispatch system ของ PyTorch อย่างลึกซึ้ง ซึ่งเธอได้เรียนรู้ทั้งหมดจากการไล่ debug ทีละขั้นตอน และสุดท้ายก็สามารถแก้บั๊กได้เอง พร้อมส่ง pull request ไปยัง PyTorch repo! นอกจากนั้น เธอยังพบว่า operation อื่นๆ เช่น random_() ก็มีบั๊กแบบเดียวกัน และทั้งหมดนี้เกิดจาก abstraction ที่ “รั่ว” ของ Placeholder ที่ไม่รู้ว่าตัวเองกำลังจัดการกับ output tensor หรือ input tensor เรื่องนี้ไม่ใช่แค่การแก้บั๊ก แต่เป็นบทเรียนสำคัญในการเข้าใจระบบที่เราใช้งานอยู่ และเป็นแรงบันดาลใจให้ใครหลายคนกล้าลงลึกเพื่อเข้าใจสิ่งที่อยู่เบื้องหลัง framework ที่เราใช้ทุกวัน ✅ ปัญหาเริ่มต้นจาก loss ที่ไม่ลดลง ➡️ เกิดขึ้นกับ encoder weights ที่ไม่อัปเดตเลย ✅ การตรวจสอบพบว่า gradients มีอยู่จริง ➡️ encoder มี gradient ขนาดใหญ่และไม่เป็น NaN ✅ Optimizer Adam เป็นต้นเหตุ ➡️ encoder weights ไม่อัปเดตเมื่อใช้ Adam แต่ทำงานปกติเมื่อใช้ SGD ✅ การตรวจสอบ state ของ Adam พบว่า exp_avg_sq เป็นศูนย์ ➡️ ทั้งที่ควรมีค่าเพราะ gradients ไม่เป็นศูนย์ ✅ การเปลี่ยนไปใช้ float64 ทำให้ปัญหาหายไป ➡️ แต่จริงๆ แล้วเป็นเพราะเปลี่ยนจาก MPS ไปใช้ CPU โดยไม่ตั้งใจ ✅ ปัญหาเกิดจาก device-specific kernel บน MPS ➡️ บาง operation ไม่สามารถเขียนค่าลงใน non-contiguous tensor ได้ ✅ การแก้ไขคือการทำ tensor ให้ contiguous ก่อนใช้งาน ➡️ โดยเรียก .contiguous() ก่อน optimizer step ✅ การตรวจสอบ source code พบว่า operation บางตัวไม่เช็ค contiguity ➡️ เช่น addcmul_() และ addcdiv_() บน MPS ไม่ทำ copy-back ✅ การแก้ไขใน PyTorch v2.4 ได้แก้ปัญหานี้แล้ว ➡️ โดยเพิ่มขั้นตอน copy-back สำหรับ non-contiguous output ✅ macOS 15 รองรับ non-contiguous tensor โดยตรง ➡️ ลดความจำเป็นในการ workaround ด้วยการ copy https://elanapearl.github.io/blog/2025/the-bug-that-taught-me-pytorch/
ELANAPEARL.GITHUB.IO
the bug that taught me more about PyTorch than years of using it
a loss plateau that looked like my mistake turned out to be a PyTorch bug. tracking it down meant peeling back every layer of abstraction, from optimizer internals to GPU kernels.
0 Comments 0 Shares 27 Views 0 Reviews