5 คะแนน โดย GN⁺ 2023-10-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • GPU มีสถาปัตยกรรมที่ให้ความสำคัญกับ throughput จากการประมวลผลแบบขนานขนาดใหญ่ มากกว่า latency ต่ำของคำสั่งเดี่ยว จึงแข็งแกร่งกับงานที่ต้องทำการคำนวณชนิดเดียวกันจำนวนมาก เช่น deep learning, กราฟิก และการคำนวณเชิงตัวเลข
  • CPU ลด latency ของการทำงานแบบลำดับ ด้วย pipelining, out-of-order execution, speculative execution และ cache หลายระดับ ขณะที่ GPU ใช้ ALU และ thread จำนวนมากเพื่อซ่อน latency และเพิ่ม throughput
  • เมื่อวัดที่ความแม่นยำ 32 บิต Nvidia Ampere A100 ให้สมรรถนะ 19.5 TFLOPS ส่วนโปรเซสเซอร์ Intel 24 คอร์ในปี 2021 อยู่ที่ 0.66 TFLOPS ทำให้ช่องว่างด้าน throughput ของการคำนวณเชิงตัวเลขยังคงกว้างขึ้นเรื่อย ๆ
  • CUDA kernel มี host code บน CPU ทำหน้าที่เตรียมการรัน และ device code บน GPU รันด้วยโครงสร้าง grid·block·thread โดย thread จะถูกจัดกลุ่มเป็น warp หน่วยละ 32 ตัว และประมวลผลแบบ SIMT
  • ประสิทธิภาพจริงขึ้นอยู่กับการแบ่ง register, shared memory, block slot และ thread slot ของ SM อย่างมาก หาก occupancy ต่ำ ก็จะซ่อน latency ได้ยากและอาจไม่ถึง throughput สูงสุด

ความแตกต่างของเป้าหมายการออกแบบระหว่าง CPU และ GPU

  • CPU ถูกออกแบบมาเพื่อประมวลผล คำสั่งแบบลำดับ ให้เร็วเป็นหลัก
    • ใช้ความสามารถอย่าง instruction pipelining, out-of-order execution, speculative execution และ multilevel cache เพื่อลด latency ของการรันคำสั่ง
    • สำหรับ operation เดี่ยว เช่น การบวกตัวเลขสองตัว หรือ flow การคำนวณสั้น ๆ CPU สามารถประมวลผลด้วย latency ต่ำกว่า GPU ได้
  • GPU ถูกออกแบบโดยเน้น parallelism ขนาดใหญ่ และ throughput สูง
    • งานที่ต้องทำ linear algebra และการคำนวณเชิงตัวเลขจำนวนมากอย่างรวดเร็ว เช่น วิดีโอเกม, กราฟิก, การคำนวณเชิงตัวเลข และ deep learning เข้ากับสถาปัตยกรรมนี้ได้ดี
    • ในการคำนวณชนิดเดียวกันระดับหลายล้านหรือหลายพันล้าน operation GPU สามารถประมวลผลได้เร็วกว่า CPU มากด้วย parallelism ขนาดใหญ่
  • ประสิทธิภาพการคำนวณเชิงตัวเลขวัดด้วย FLOPS หรือจำนวน floating-point operations ต่อวินาที
    • Nvidia Ampere A100 ให้ throughput 19.5 TFLOPS ที่ความแม่นยำ 32 บิต
    • โปรเซสเซอร์ Intel 24 คอร์ในปี 2021 อยู่ที่ระดับ 0.66 TFLOPS ที่ความแม่นยำ 32 บิต
    • ช่องว่างด้าน throughput ระหว่าง GPU และ CPU กว้างขึ้นทุกปี

วิธีที่ GPU ซ่อน latency

  • แม้ latency ของคำสั่งแต่ละคำสั่งจะสูง GPU ก็ใช้ thread และทรัพยากรคำนวณจำนวนมากเพื่อให้ได้ latency tolerance
  • ระหว่างที่ thread หนึ่งรอผลลัพธ์ของคำสั่ง GPU จะรัน thread อื่นที่ไม่ต้องรอ
  • ด้วย scheduling แบบนี้ หน่วยคำนวณจึงทำงานต่อเนื่องได้มากที่สุดและรักษา throughput สูง ไว้ได้

สถาปัตยกรรม GPU compute

  • GPU ประกอบด้วย array ของ Streaming Multiprocessor (SM) หลายตัว
  • SM แต่ละตัวมี streaming processor, core และ thread หลายตัว
    • Nvidia H100 มี SM 132 ตัว และแต่ละ SM มี 64 คอร์ รวมเป็น 8,448 คอร์
  • แต่ละ SM มีหน่วยความจำบนชิปขนาดจำกัดที่ทุกคอร์ใช้ร่วมกัน
    • หน่วยความจำนี้เรียกว่า shared memory หรือ scratchpad
  • ทรัพยากร control unit ของ SM ก็ถูกใช้ร่วมกันโดยคอร์เช่นกัน
  • SM แต่ละตัวมี thread scheduler แบบฮาร์ดแวร์สำหรับรัน thread
  • อาจมีหน่วยฟังก์ชันพิเศษหรือหน่วยคำนวณเร่งความเร็ว เช่น tensor core, ray tracing unit รวมอยู่ด้วย ขึ้นอยู่กับ workload

ลำดับชั้นหน่วยความจำของ GPU

  • Register

    • แต่ละ SM มี register จำนวนมาก
    • Nvidia A100 และ H100 มี register 65,536 ตัวต่อ SM
    • register ถูกใช้ร่วมกันโดยคอร์ และจัดสรรแบบ dynamic ตามความต้องการของ thread
    • register ที่ถูกจัดสรรให้ thread ใด thread หนึ่งระหว่างการรัน จะเป็นของ thread นั้นโดยเฉพาะ ทำให้ thread อื่นอ่านหรือเขียนไม่ได้
  • constant cache

    • cache ข้อมูลคงที่ที่โค้ดซึ่งรันบน SM ใช้งาน
    • โปรแกรมเมอร์ต้องประกาศ object ในโค้ดให้เป็นค่าคงที่อย่างชัดเจน GPU จึงจะเก็บไว้ใน constant cache ได้
  • shared memory

    • เป็น SRAM บนชิปขนาดเล็ก เร็ว latency ต่ำ และ programmable ที่อยู่ในแต่ละ SM
    • ใช้ร่วมกันโดย thread block ที่รันอยู่บน SM เดียวกัน
    • เมื่อ thread หลายตัวใช้ชิ้นข้อมูลเดียวกัน ให้ thread เดียวอ่านจาก global memory แล้วแชร์ให้ที่เหลือใช้ได้ เพื่อลดการโหลดซ้ำ
    • ยังใช้เป็นกลไก synchronization ระหว่าง thread ภายใน thread block ได้ด้วย
  • L1 cache และ L2 cache

    • แต่ละ SM มี L1 cache สำหรับ cache ข้อมูลที่เข้าถึงบ่อยจาก L2 cache
    • L2 cache ถูกใช้ร่วมกันโดยทุก SM และ cache ข้อมูลที่เข้าถึงบ่อยจาก global memory เพื่อลด latency
    • L1 และ L2 cache ทำงานแบบโปร่งใสต่อ SM ดังนั้นจากมุมมองของ SM จึงดูเหมือนรับข้อมูลจาก global memory
  • global memory

    • GPU มี global memory นอกชิป ซึ่งเป็น DRAM ขนาดใหญ่และ bandwidth สูง
    • Nvidia H100 มี HBM 80GB และ bandwidth 3000GB/s
    • global memory อยู่ไกลจาก SM จึงมี latency สูง แต่ลำดับชั้นหน่วยความจำบนชิปและหน่วยคำนวณจำนวนมากช่วยซ่อน latency นี้

CUDA kernel และโครงสร้าง thread

  • CUDA คือ programming interface สำหรับเขียนโปรแกรมบน Nvidia GPU
  • การคำนวณที่จะรันบน GPU แสดงในรูปของ kernel ที่คล้ายฟังก์ชัน C/C++
    • ตัวอย่างคือ kernel บวก vector ที่รับ vector สองตัวเป็น input บวกกันแบบราย element แล้วเขียนผลลัพธ์ลงใน vector ตัวที่สาม
  • เมื่อรัน kernel จะเริ่ม thread หลายตัว และกลุ่มทั้งหมดนี้เรียกว่า grid
    • grid ประกอบด้วย thread block ตั้งแต่หนึ่งตัวขึ้นไป
    • thread block แต่ละตัวประกอบด้วย thread ตั้งแต่หนึ่งตัวขึ้นไป
  • จำนวน block และจำนวน thread ขึ้นอยู่กับขนาดข้อมูลและ parallelism ที่ต้องการ
    • ในการบวก vector ขนาด 256 มิติ สามารถสร้าง block เดียวที่มี 256 thread เพื่อให้แต่ละ thread ประมวลผลหนึ่ง element ของ vector ได้
    • ในปัญหาที่ใหญ่กว่า จำนวน thread ที่ GPU ใช้งานได้อาจไม่เพียงพอ จึงอาจให้แต่ละ thread ประมวลผลข้อมูลหลายจุด
  • การ implement CUDA แบ่งเป็นสองส่วน
    • host code รันบน CPU และรับผิดชอบการโหลดข้อมูล, จัดสรรหน่วยความจำ GPU และสั่งรัน kernel ด้วย thread grid ที่กำหนดไว้
    • device code รันบน GPU และนิยามฟังก์ชัน kernel จริง

ขั้นตอนที่ kernel รันบน GPU

  • คัดลอกข้อมูลจาก host ไปยัง device

    • ก่อนรัน kernel ต้องคัดลอกข้อมูลที่จำเป็นจากหน่วยความจำ CPU ไปยัง GPU global memory
    • ในฮาร์ดแวร์ GPU รุ่นใหม่ สามารถใช้ unified virtual memory เพื่ออ่านจาก host memory ได้โดยตรงเช่นกัน
  • scheduling thread block ไปยัง SM

    • เมื่อข้อมูลที่จำเป็นพร้อมในหน่วยความจำ GPU แล้ว จะกำหนด thread block ให้กับ SM
    • thread ทั้งหมดใน block เดียวกันจะถูกประมวลผลพร้อมกันบน SM เดียวกัน
    • ก่อนรัน GPU ต้องจัดหาทรัพยากร SM ที่จำเป็นสำหรับ thread เหล่านั้น
    • ในทางปฏิบัติ thread block หลายตัวสามารถถูกกำหนดให้ SM เดียวกันพร้อมกันได้
    • เนื่องจากจำนวน SM มีจำกัด และ kernel ขนาดใหญ่อาจมี block จำนวนมาก บาง block จึงไม่ได้รันทันที
    • GPU จะเก็บรายการ block ที่รออยู่ และเมื่อ block ใดทำงานเสร็จ ก็จะกำหนด block ที่รออยู่หนึ่งตัวให้รัน
  • SIMT และ warp

    • thread ที่ถูกกำหนดให้ SM จะถูกจัดกลุ่มอีกครั้งเป็นหน่วยละ 32 ตัว และกลุ่มนี้เรียกว่า warp
    • ขนาด warp ของ Nvidia GPU รุ่นปัจจุบันคือ 32 แต่อาจเปลี่ยนได้ในฮาร์ดแวร์อนาคต
    • SM จะ fetch และ issue คำสั่งเดียวกันให้ thread ทั้งหมดใน warp
    • thread เหล่านี้รันคำสั่งเดียวกันพร้อมกัน แต่ประมวลผลส่วนข้อมูลที่ต่างกัน
    • โมเดลนี้เรียกว่า single instruction multiple threads (SIMT) และคล้ายกับคำสั่ง SIMD ของ CPU
    • GPU รุ่นใหม่ตั้งแต่ Volta เป็นต้นมายังมี independent thread scheduling ที่อนุญาตให้ thread ทำงานพร้อมกันทั้งหมดได้โดยไม่ขึ้นกับ warp
  • warp scheduling และ latency tolerance

    • แม้ processing block ทั้งหมดใน SM จะประมวลผล warp แต่ในช่วงเวลาหนึ่ง warp ที่กำลังรันคำสั่งจริงมีเพียงบางส่วนเท่านั้น
    • เหตุผลคือจำนวน execution unit ใน SM มีจำกัด
    • หาก warp ใดรอผลลัพธ์ของคำสั่งที่ใช้เวลานาน SM จะปล่อย warp นั้นไว้ในสถานะรอ แล้วรัน warp อื่นที่ไม่จำเป็นต้องรอ
    • เนื่องจากแต่ละ thread ในแต่ละ warp มีชุด register ของตัวเอง การสลับระหว่าง warp จึงไม่มี overhead เพิ่มเติม
    • process context switching ของ CPU มีค่าใช้จ่ายสูง เพราะต้องบันทึก register ลง main memory และ restore สถานะของ process อื่น
  • คัดลอกข้อมูลผลลัพธ์จาก device กลับไปยัง host

    • เมื่อ thread ทั้งหมดของ kernel รันเสร็จแล้ว จะคัดลอกผลลัพธ์กลับไปยัง host memory

การแบ่งทรัพยากรและ occupancy

  • การใช้ทรัพยากร GPU วัดด้วยตัวชี้วัดที่เรียกว่า occupancy
    • occupancy คืออัตราส่วนระหว่างจำนวน warp ที่ถูกกำหนดให้ SM กับจำนวน warp สูงสุดที่ SM นั้นรองรับได้
    • เพื่อให้ได้ throughput สูงสุด occupancy 100% เป็นสิ่งที่พึงประสงค์ แต่ทำไม่ได้เสมอไปเพราะข้อจำกัดหลายอย่าง
  • SM มีทรัพยากรสำหรับรันที่คงที่ เช่น register, shared memory, thread block slot และ thread slot
    • ทรัพยากรเหล่านี้ถูกแบ่งแบบ dynamic ตามความต้องการของ thread และข้อจำกัดของ GPU
  • ตัวอย่างของ Nvidia H100
    • SM แต่ละตัวรองรับ block ได้ 32 ตัว, warp 64 ตัว หรือ thread 2048 ตัว
    • รองรับ thread สูงสุด 1024 ตัวต่อ block
    • หากรันด้วยขนาด block 1024 thread จะทำให้ thread slot 2048 ตัวถูกแบ่งเป็น 2 block
  • การแบ่งแบบ dynamic สามารถใช้ทรัพยากรคำนวณได้มีประสิทธิภาพกว่าการแบ่งแบบคงที่
    • ในการแบ่งแบบคงที่ thread block แต่ละตัวจะได้รับทรัพยากรสำหรับรันในปริมาณคงที่
    • ในบางกรณี thread อาจได้รับทรัพยากรมากกว่าที่จำเป็น ทำให้เกิดการสิ้นเปลืองทรัพยากรและ throughput ลดลง
  • ตัวอย่างที่ทำให้ occupancy ต่ำลง
    • หากตั้งขนาด block เป็น 32 thread และต้องการทั้งหมด 2048 thread จะเกิด 64 block
    • แต่ SM แต่ละตัวประมวลผลได้ครั้งละ 32 block เท่านั้น จึงมี thread ที่รันจริงเพียง 1024 ตัว และ occupancy กลายเป็น 50%
    • เมื่อมี register 65,536 ตัวต่อ SM หากต้องการรัน 2048 thread พร้อมกัน จะใช้ register ได้สูงสุดเพียง 32 ตัวต่อ thread
    • หาก kernel ต้องการ register 64 ตัวต่อ thread จะรันได้เพียง 1024 thread ต่อ SM ทำให้ occupancy กลับมาเป็น 50% อีกครั้ง
  • occupancy ต่ำทำให้ซ่อน latency ได้ไม่เพียงพอ และอาจลด throughput ด้านการคำนวณที่จำเป็นต่อการไปถึง throughput สูงสุดของฮาร์ดแวร์
  • การเขียน GPU kernel ที่มีประสิทธิภาพต้องจัดสรรทรัพยากรอย่างระมัดระวังเพื่อรักษา occupancy สูงไว้พร้อมกับลด latency
    • การใช้ register มากอาจทำให้โค้ดเองเร็วขึ้น แต่ก็อาจทำให้ occupancy ต่ำลง จึงต้องบาลานซ์การ optimize ให้ดี

แหล่งข้อมูลเพิ่มเติม

1 ความคิดเห็น

 
GN⁺ 2023-10-22
ความคิดเห็นจาก Hacker News
  • มีคนส่งอีเมลร้องเรียนเกี่ยวกับบทความนี้: https://twitter.com/abhi9u/status/1715753871564476597
    นี่เป็น การละเมิดกฎของ HN จริง ๆ แล้วเป็นข้อเดียวที่สำคัญพอจะอยู่ทั้งในแนวทางของไซต์และ FAQ และผู้ใช้ HN ก็อ่อนไหวกับเรื่องนี้มาก
    Q: ขอให้คนช่วยโหวตบทความของฉันได้ไหม?
    A: ไม่ได้ ผู้ใช้ควรโหวตเมื่อพวกเขารู้สึกว่าสิ่งนั้นน่าสนใจทางปัญญาด้วยตัวเอง ไม่ใช่เพราะมีใครบางคนมีคอนเทนต์ที่อยากโปรโมต หากฝ่าฝืนกฎนี้ เราอาจลงโทษหรือบล็อกบทความ บัญชี หรือไซต์ ดังนั้นอย่าทำ
    https://news.ycombinator.com/newsfaq.html
    อย่าขอให้มีการโหวต แสดงความคิดเห็น หรือส่งโพสต์ ผู้ใช้ควรโหวตและแสดงความคิดเห็นเมื่อพบเจอบางสิ่งด้วยตนเองแล้วรู้สึกว่าน่าสนใจเป็นการส่วนตัว ไม่ใช่เพื่อจุดประสงค์ในการโปรโมต
    https://news.ycombinator.com/newsguidelines.html

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

    • น่าจะใช้เลขทศนิยม 64 บิต (double) อยู่ ถ้าเป็นแบบนั้น GPU ทุกตัวก็ไม่ได้ช่วยได้มาก โดยเฉพาะเมื่อเทียบกับ CPU ที่แรง
      แต่ถ้าใช้ GPU ที่มี FP64 unit จำนวนมาก ก็อาจเร็วขึ้นมากได้ ปกติพวกนี้ไม่ใช่ GPU สำหรับเล่นเกม แต่ถ้ามี 4060 อยู่เฉย ๆ ประสิทธิภาพ FP64 น่าจะราว 300 GFLOPS ซึ่งมีโอกาสสูงกว่าจะมากกว่า CPU CPU สมัยใหม่ก็แรงในด้านนี้เช่นกัน เพราะแต่ละคอร์สามารถออกคำสั่ง FP64 ได้หลายรายการต่อหนึ่งคล็อก
  • ประโยคแรกที่ว่า “โปรแกรมเมอร์ส่วนใหญ่เข้าใจ CPU อย่างลึกซึ้ง” มันไม่จริงอย่างโจ่งแจ้งเกินไป จนแม้บทความอาจจะยอดเยี่ยม แต่ก็ทำให้ยากที่จะรับส่วนที่เหลืออย่างจริงจัง

    • ลองเปลี่ยนเป็นแบบนี้ดีไหม: “นักวิทยาการคอมพิวเตอร์ วิศวกรคอมพิวเตอร์ วิศวกรไฟฟ้า และนักพัฒนาสายงานอดิเรกจำนวนไม่น้อย …”
      ตอนมหาวิทยาลัยเคยลงเรียนวิชาปรัชญาเล่น ๆ และที่นั่นได้ฝึกทักษะในการไม่ทิ้งประโยคหนึ่งทันที แต่ปรับให้อ่านในรูปแบบที่ดีกว่า ตอนนี้สมองแปลการเหมารวมเกินจริงหรือคำกล่าวที่เท็จชัด ๆ ให้เป็นข้อเสนอที่ใกล้เคียงความจริงอย่างมีเหตุผลโดยอัตโนมัติ เมื่อประเด็นถูกพัฒนาต่อไป ก็จะประกอบไอเดียเหล่านั้นขึ้นใหม่ และประเมินบทความทั้งชิ้นว่าเป็นสิ่งที่สอดคล้องกันเชิงตรรกะได้
      ด้วยเหตุนี้ แม้อ่านงานเขียนแย่ ๆ ก็ยังเหลือสมมติฐานและข้อโต้แย้งใหม่ ๆ ทั้งจริงและเท็จเกี่ยวกับหัวข้อที่สนใจ และโลกในความคิดของฉันก็ขยายขึ้นเท่านั้น
    • สำหรับโปรแกรมเมอร์ส่วนใหญ่แล้วไม่จริงแน่นอน แต่ผู้เขียนอาจหมายถึง วิศวกรที่ได้รับการศึกษาด้าน CS ก็ได้ ถ้าผ่านหลักสูตรวิทยาการคอมพิวเตอร์อย่างเป็นทางการ มักจะเข้าใจ CPU ค่อนข้างลึก และ GPU มักถูกสอนแบบตื้นกว่ามาก
    • ไม่เข้าใจว่าทำไมบทความบนอินเทอร์เน็ตทุกชิ้นต้องมีคอมเมนต์แนว “อ่านถึง X แล้วก็หยุด” อยู่เสมอ คำพูดแบบนั้นไม่ได้เพิ่มอะไรเลย
    • คิดว่าข้อถกเถียงนี้มากกว่าครึ่งขึ้นอยู่กับว่าจะนิยามคำว่า เข้าใจอย่างลึกซึ้ง อย่างไร
      ตอนมหาวิทยาลัยได้เรียนข้อเท็จจริงพื้นฐานของสถาปัตยกรรม CPU รู้ภาพรวมของภูมิทัศน์นี้ในระดับพื้นฐานมาก ๆ และบางครั้งก็ได้อัปเดตความรู้แบบจำกัด แต่จะไม่เรียกสิ่งนั้นว่า “ความเข้าใจอย่างลึกซึ้ง” น่าจะเป็น “ความเข้าใจพื้นฐานว่า CPU ทำงาน ออกแบบ และถูกใช้งานอย่างไร” มากกว่า
      ถ้าเชี่ยวชาญ assembly ก็อาจพูดได้ว่า “เข้าใจอย่างลึกซึ้ง” ว่าจะใช้ CPU ในระดับต่ำอย่างไร แต่ก็ยังฟังดูเกินจริงอยู่ดี และนั่นก็ไม่ใช่สิ่งเดียวกับการเป็นผู้เชี่ยวชาญด้านการออกแบบ CPU/GPU
      ดังนั้นเห็นด้วย แต่บทความก็ยังน่าสนใจ โดยเฉพาะไดอะแกรมที่ดี
    • ได้เรียนทั้งในหลักสูตรปริญญาและในวิชา Structure and Interpretation of Computer Programs และขอแนะนำวิชานั้นให้กับคนที่สนใจคอมพิวติ้งระดับต่ำ
  • ส่วนที่ว่า “รีจิสเตอร์ที่จัดสรรให้เธรดระหว่างการรันเป็นของเธรดนั้นโดยเฉพาะ เธรดอื่นจึงอ่านหรือเขียนไม่ได้” มีข้อยกเว้นอยู่
    wave intrinsic ของ HLSL และฟีเจอร์คล้ายกันของ CUDA สามารถอ่านรีจิสเตอร์ของเธรดอื่นใน wavefront ปัจจุบันได้ นอกจากนี้ในย่อหน้าสถาปัตยกรรมหน่วยความจำ ก็น่าจะเพิ่มว่าถึงแม้แคชจะไม่รับประกันความสอดคล้องระหว่างเธรดใน dispatch/grid เดียวกัน แต่มีบล็อกฟังก์ชันพิเศษที่มีอยู่แบบโกลบอลทั่วทั้งชิปซึ่งทำหน้าที่ implement atomic operation บนหน่วยความจำโกลบอล

  • การเขียนโปรแกรม SIMD นี่โหดจริง ๆ
    อยากคำนวณกับทุกพิกเซลบนหน้าจอ? ไม่มีปัญหา
    อยากใส่เงื่อนไข branch? โอ๊ย

    • อยากใส่ eval? ทุกอย่างหยุดหมด
    • ถ้าพูดอย่างเป็นธรรม เรื่องนี้ก็สมเหตุสมผล งานตัดสินใจอย่างชาญฉลาดนั้น “ยากกว่า” การขยายการคำนวณง่าย ๆ ไปยัง worker จำนวนมาก
  • ทำไมยังเรียกว่า GPU กันอยู่อีก? PPU (หน่วยประมวลผลแบบขนาน) ฟังดูเป็นชื่อที่ดีกว่า

    • เพราะนอกจากความสามารถ GPU แบบทั่วไปแล้ว ยังมี ซิลิคอนเฉพาะด้านกราฟิกส์ อยู่ด้วย
    • เพราะถ้าพูดว่า GPU ทุกคนก็เข้าใจว่าหมายถึงอะไร
      ความสัมพันธ์ระหว่าง drone กับ quad-copter ก็คล้ายกัน
    • หน่วยประมวลผลเวกเตอร์ น่าจะเหมาะกว่า
    • CPU ก็เป็น PPU เหมือนกัน
    • General Processing Unit
  • เป็นบทความที่ยอดเยี่ยม และ GPU ก็พัฒนาไปไกลและมีประสิทธิภาพดีกว่าสิ่งใด ๆ ที่ผมนึกออกในงานที่มันทำอยู่
    แต่ถ้าได้เรียนรู้พาราไดม์อื่นที่ยืดหยุ่นกว่าแล้ว ผมอยากจัด SIMD ไว้ในหมวดที่ไม่จำเป็นนัก ผมชอบ MIMD กับคลัสเตอร์/ทรานส์พิวเตอร์มากกว่า ซึ่งดูเหมือนจะหายไปแถว ๆ ยุค 2000 สถานะในตอนนี้คือมันบังคับให้นักพัฒนาต้องย้ายข้อมูลเอง เขียนเชดเดอร์ภายใต้ข้อจำกัดตามอำเภอใจเรื่องจำนวนตำแหน่งหน่วยความจำที่เข้าถึงพร้อมกันได้ เขียนภาษาแยกกันสำหรับ GPU/CPU จนงานซ้ำซ้อน ต้องรู้ว่ามีฮาร์ดแวร์อะไรสำหรับฟีเจอร์อย่าง ray tracing และถูกผูกกับเฟรมเวิร์กที่มีแนวทางตายตัวอย่าง OpenGL/Metal/Vulkan สำหรับผม GPU เป็นทางแยกที่ไม่มีวันพาไปถึงที่ที่อยากไปได้เลย ช่วง 25 ปีที่ผ่านมาจึงเหมือนประสบการณ์ของคนที่อยู่ในไทม์ไลน์ผิด
    พูดแบบคร่าว ๆ ภายใต้ข้อจำกัดหลังการสิ้นสุดของกฎของมัวร์ CPU อเนกประสงค์ที่ขยายได้ควรเป็นมัลติคอร์ที่มีหน่วยความจำแบบ local แชร์ข้อมูลด้วยหน่วยความจำแบบ content-addressable ที่คัดลอกเมื่อเขียน หรือวิธีแคชแบบอื่น และมีพื้นที่แอดเดรสเดียวที่รวมเป็นหนึ่ง เพื่อให้ผู้ใช้สำรวจวิธีคำนวณทุกรูปแบบได้อย่างอิสระในสภาพแวดล้อมคอมพิวติ้งเดสก์ท็อป ใช้ภาษาแอสเซมบลีมาตรฐาน แต่โดยปกติโปรแกรมด้วยภาษาการเขียนโปรแกรมเชิงฟังก์ชันอย่าง Erlang/Go, Octave/MATLAB และในอุดมคติคือ Julia ไลบรารี 3D rendering กับ AI เป็นชั้นที่วางอยู่ด้านบน ไม่ใช่องค์ประกอบพื้นฐาน
    ที่น่าสนใจคือ GPU ไปถึงโครงสร้างแบบมัลติคอร์ที่ผมพูดถึงโดยประมาณแล้ว แต่ไดรเวอร์กลับแยกผู้ใช้ออกจากการเข้าถึงแบบ bare metal ที่จำเป็นต่อ MIMD อเนกประสงค์ ผมเคยคิดว่าวิธีเดียวที่จะโค่นความได้เปรียบของ GPU ได้คือ FPGA แต่บางทีอาจมีโอกาสเขียนไดรเวอร์ที่ทำให้ฮาร์ดแวร์ GPU ดูเหมือน MIMD ที่มีหน่วยความจำรวมก็ได้ ผมไม่รู้ว่าแกน GPU จัดการการคำนวณจำนวนเต็มได้ดีแค่ไหน แต่ดูเหมือนจะประมาณได้ด้วยส่วนจำนวนเต็ม 32 บิตของ floating point 64 บิต ด้วยการประนีประนอมแบบนั้น เครื่อง MIMD อาจช้ากว่า GPU 10–100 เท่า แต่ก็อาจเร็วกว่า CPU 10–100 เท่าได้ ในขณะเดียวกันก็ยังขยายได้โดยไม่ต้องพึ่งพาแคชขนาดใหญ่และบัสที่เร็วมากเกินไป ซึ่งทำให้ CPU ชะงักมาตั้งแต่ราวปี 2007 หลังจากตลาดมือถือเข้ามานำทิศทางและให้ความสำคัญกับราคาและประสิทธิภาพพลังงานมากกว่าประสิทธิภาพ เครื่อง MIMD สามารถทำคลัสเตอร์เพื่อสร้างเครือข่ายคำนวณแบบกระจายอย่าง SETI@home ได้โดยไม่ต้องแก้โค้ดด้วย ถ้าจะให้เห็นภาพว่ามันจะเพิ่มพลังให้ผู้ใช้ทั่วไปได้แค่ไหน ก็คล้ายกับการเปรียบเทียบ BitTorrent ของการคำนวณ กับ FTP ไม่ใช่ของข้อมูล

  • ผมยังไม่ค่อยเข้าใจว่า สถาปัตยกรรม Apple Silicon ต่างจาก NVIDIA อย่างไร
    ถ้าดูประโยคว่า “Nvidia H100 GPU มี SM 132 ตัว แต่ละ SM มีคอร์ 64 คอร์ รวมทั้งหมด 8448 คอร์” ตัวเลข 8448 คอร์น่าประทับใจแน่นอน แต่ Apple M2 Ultra มีแค่ 76 คอร์เองหรือ?
    NVIDIA H100 GPU มีคอร์มากกว่าได้เกิน 110 เท่าได้อย่างไร? แน่นอนว่าประสิทธิภาพไม่ได้สูงกว่า M2 Ultra 110 เท่า แล้วตรงนี้เกิดอะไรขึ้นกันแน่?

    • โดยทั่วไปแล้ว SM ของ NVIDIA ใกล้เคียงที่สุดกับ CU ของ AMD GPU หรือคอร์ของ Apple GPU ส่วน “คอร์” ในที่นี้ให้จำว่าเป็นองค์ประกอบย่อยของ SM ที่ทำการคำนวณแต่ละรายการ
      ดูไดอะแกรมนี้จากบล็อกของ NVIDIA: https://developer-blogs.nvidia.com/wp-content/uploads/2021/g...
      (https://developer.nvidia.com/blog/nvidia-ampere-architecture...)
    • การที่ NVIDIA เรียกสิ่งที่โดยแท้จริงแล้วคือ vector lane ว่า “คอร์” และใช้คำว่า “เธรด” ใน SIMT ให้หมายถึงการรันหนึ่งครั้งบน vector lane แบบนั้น เป็นการจงใจทำให้คลุมเครือ และพูดตรง ๆ คือไม่ซื่อตรง
      แน่นอนว่าอาจรู้สึกว่ามีเหตุผลพอที่จะเรียกมันว่า “เธรด” เพราะแต่ละ lane รองรับ program counter แยกกัน แต่สุดท้ายสิ่งที่สำคัญคือความเร็วและ throughput ของ ALU
    • H100 ใช้ทำให้ห้องหนึ่งอุ่นขึ้นได้เลย มันกินไฟมากกว่า M2 Ultra เกิน 10 เท่า
  • ตอนนี้เข้าใจแล้วว่าทำไมแมชชีนเลิร์นนิงถึงใช้ floating point สำหรับความละเอียด ไม่ใช่เพราะเป็นทางเลือก แต่เพราะโค้ดกราฟิกส์ใช้กันแบบนั้นอยู่แล้ว
    นี่เป็นชิ้นส่วนอีกชิ้นหนึ่งของปริศนา “ทำไมแมชชีนเลิร์นนิงถึงไม่มีประสิทธิภาพขนาดนี้”
    สงสัยว่าในสภาพแวดล้อมจริง overhead จากการคัดลอกหน่วยความจำจะมีมากแค่ไหน ถ้ามันทำงานเหมือนงานทั่วไปก็คงโหดมาก ถึงขั้นต้องส่งการประมวลผล TCP ไปให้ฮาร์ดแวร์ทำเพื่อหลีกเลี่ยงสิ่งนั้นเลย ที่นี่ข้อมูลมีมากกว่ามาก แต่ก็ถูกประมวลผลเป็นก้อนใหญ่กว่าเช่นกัน

    • ในเครือข่ายขนาดใหญ่ยุคใหม่จำนวนมาก เวลาในการคำนวณ gradient และ backpropagation บน GPU นั้นช้ามากอยู่แล้ว จนการคัดลอกข้อมูล floating point ผ่านบัส PCIe ไม่ได้เป็นคอขวด
      กล่าวคือ การคัดลอก mini-batch ของภาพแบบ floating point ยังเร็วพออยู่ดี เพราะการวนซ้ำของ gradient/SGD ช้าและมีปริมาณการคำนวณสูงมาก แม้จะใช้ mixed precision ก็ยังเป็นแบบนั้น
      ในเครือข่ายตื้น ๆ อาจมีข้อดีจากการคัดลอกเฉพาะข้อมูลที่ถูกบีบอัดเดิมไปยังหน่วยความจำ GPU แล้วทำการคลายบีบอัด ฯลฯ บน GPU แต่เหตุผลที่ GPU สมัยใหม่ยังไม่รองรับ PCIe 5 ก็เพราะประสิทธิภาพการคำนวณดิบสำคัญกว่า
      สุดท้าย Tensor Core ก็มีผลมากเช่นกัน และขึ้นอยู่กับเครือข่าย บางครั้งมันอาจเร็วเกินไปจน utilization ต่ำมากได้
    • ไม่ได้มองว่าการเลือกใช้ตัวเลข floating point นั้นไม่มีประสิทธิภาพเป็นพิเศษ ถ้าเฟรมเวิร์กใช้ fixed point เป็นค่าเริ่มต้น การปรับ dynamic range ให้เข้ากันทั่วทั้งเครือข่ายคงยุ่งยากมาก
      คณิตศาสตร์ของการฝึกก็สมมติว่าตัวเลขเป็นแบบต่อเนื่องด้วย
    • floating point มีขนาดใหญ่กว่า และการดำเนินการกับมันก็ยากกว่า
      แต่สงสัยว่าทำไม LLM ที่รันบน CPU ถึงทำ quantization ตามที่เข้าใจคือเป็นกระบวนการลดความละเอียดของ weight เพื่อใช้หน่วยความจำน้อยลง
      ยังไม่ชัดเจนว่าความละเอียดที่ไม่พอทำให้เกิดความแตกต่างหรือไม่ ถ้าอย่างนั้นทำไมถึงใช้ floating point ตั้งแต่แรก? ถ้าความละเอียดไม่สำคัญ ความละเอียดที่เพิ่มขึ้นก็แค่ทำให้ใช้ทรัพยากรมากขึ้นโดยไม่มีเหตุผลจริง ๆ และอาจใช้ทรัพยากรมากกว่าที่จำเป็นไปหลายหลักด้วยซ้ำ
      วงการนี้ไม่ได้เริ่มต้นโดยคนที่เข้าใจ performance พวกเขาใช้เครื่องมือสร้างอะไรบางอย่าง แต่ไม่มี “ทำไม” เครื่องมือทำแบบนั้น ก็เลยทำตามนั้น
      เหตุผลที่เรื่องนี้สำคัญคือแบบนี้ แม้แต่บน CPU ทั่วไป วิธีหนึ่งในการเข้าถึงข้อมูลก็อาจเร็วกว่าวิธีอื่นหลายหลักได้ แต่ต้องรู้เรื่องนั้นก่อน ไม่อยากลดต้นทุน LLM ลงหลายหลักหรือ?
    • floating point ไม่มีประสิทธิภาพตรงไหน? ดูเหมือนว่าแมชชีนเลิร์นนิงจะได้ประโยชน์มากจากการเข้าถึง dynamic range ในระดับหลายหลัก
  • สไลด์และการนำเสนอนี้เมื่อไม่กี่ปีก่อนที่พูดถึง ส่วนที่ยุ่งยากของ CPU และ GPU ก็น่าดูเช่นกัน
    Alexander Titov — Know your hardware: CPU memory hierarchy https://youtu.be/QOJ2hsop6hM
    https://github.com/alexander-titov/public/blob/master/confer...
    Know Your Hardware - CPU Memory Hierarchy -- Alexander Titov -- C%2B%2B Moscow Meetup March 2019.pdf
    https://github.com/alexander-titov/public/blob/master/confer...
    GPGPU - what it is and why you should care -- Alexander Titov -- CoreHard 2019.pdf