การทำความเข้าใจมิติกริดของ CUDA ขนาดบล็อกและการจัดระเบียบเธรด (คำอธิบายง่ายๆ) [ปิด]


161

เธรดมีการจัดการที่จะดำเนินการโดย GPU อย่างไร?


คู่มือการเขียนโปรแกรม CUDA ควรเป็นจุดเริ่มต้นที่ดี ฉันยังอยากจะขอแนะนำให้ตรวจสอบจากการแนะนำ CUDA จากที่นี่
Tom

คำตอบ:


287

ฮาร์ดแวร์

ตัวอย่างเช่นหากอุปกรณ์ GPU มีหน่วยประมวลผลหลายตัว 4 หน่วยและพวกเขาสามารถเรียกใช้ 768 เธรดแต่ละรายการ: จากนั้นในช่วงเวลาที่กำหนดไม่เกิน 4 * 768 เธรดจะทำงานแบบขนานจริง ๆ (หากคุณวางแผนเธรดเพิ่มเติมพวกเขาจะรอ ตาของพวกเขา)

ซอฟต์แวร์

กระทู้ถูกจัดระเบียบในบล็อก บล็อกถูกดำเนินการโดยหน่วยประมวลผลหลายตัว กระทู้ของบล็อกสามารถระบุ (ดัชนี) โดยใช้ 1Dimension (x), 2 มิติ (x, y) หรือดัชนี 3Dim (x, y, z) แต่ในทุกกรณี x y z <= 768 สำหรับตัวอย่างของเรา (ข้อ จำกัด อื่น ๆ ใช้ ถึง x, y, z, ดูคำแนะนำและความสามารถของอุปกรณ์ของคุณ)

เห็นได้ชัดว่าถ้าคุณต้องการมากกว่า 4 * 768 เธรดที่คุณต้องการมากกว่า 4 บล็อก บล็อกอาจถูกจัดทำดัชนีด้วย 1D, 2D หรือ 3D มีคิวของบล็อกรอการเข้าสู่ GPU (เนื่องจากในตัวอย่างของเรา GPU มีมัลติโปรเซสเซอร์ 4 ตัวและดำเนินการพร้อมกัน 4 บล็อกเท่านั้น)

ตอนนี้เป็นกรณีง่าย: การประมวลผลภาพ 512x512

สมมติว่าเราต้องการเธรดหนึ่งเธรดเพื่อประมวลผลหนึ่งพิกเซล (i, j)

เราสามารถใช้บล็อกจำนวน 64 เธรด จากนั้นเราต้องการบล็อก 512 * 512/64 = 4096 (เพื่อให้มีหัวข้อ 512x512 = 4096 * 64)

เป็นเรื่องปกติที่จะจัดระเบียบ (เพื่อให้การจัดทำดัชนีรูปภาพง่ายขึ้น) เธรดในบล็อก 2 มิติที่มี blockDim = 8 x 8 (64 เธรดต่อบล็อก) ฉันชอบที่จะเรียกมันว่ากระทู้ Perlock

dim3 threadsPerBlock(8, 8);  // 64 threads

และ 2D gridDim = 64 x 64 บล็อก (จำเป็นต้องมี 4096 บล็อก) ฉันชอบเรียกมันว่า numBlocks

dim3 numBlocks(imageWidth/threadsPerBlock.x,  /* for instance 512/8 = 64*/
              imageHeight/threadsPerBlock.y); 

เคอร์เนลถูกเปิดใช้งานเช่นนี้:

myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );       

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

ในเคอร์เนลมีการคำนวณพิกเซล (i, j) โดยเธรดด้วยวิธีนี้:

uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;

11
หากแต่ละบล็อกสามารถรัน 768 เธรดทำไมต้องใช้ 64 เท่านั้น หากคุณใช้ขีด จำกัด สูงสุดที่ 768 คุณจะมีบล็อกน้อยลงและมีประสิทธิภาพที่ดีขึ้น
Aliza

10
@Aliza: บล็อกเป็นลอจิคัลขีด จำกัด ของเธรด 768 สำหรับแต่ละหน่วยประมวลผลทางกายภาพ คุณใช้บล็อกตามข้อกำหนดของปัญหาของคุณเพื่อแจกจ่ายงานไปยังเธรด ไม่น่าเป็นไปได้ที่คุณจะสามารถใช้บล็อกของ 768 เธรดสำหรับทุกปัญหาที่คุณมี ลองนึกภาพคุณต้องประมวลผลภาพขนาด 64x64 (4096 พิกเซล) 4096/768 = 5.333333 บล็อก?
cibercitizen1

1
บล็อกเป็นตรรกะ แต่แต่ละบล็อกถูกกำหนดให้กับแกน หากมีบล็อกมากกว่าแกนหลักบล็อกนั้นจะเข้าคิวจนกว่าแกนจะว่าง ในตัวอย่างของคุณคุณสามารถใช้ 6 บล็อกและให้เธรดพิเศษไม่ทำอะไรเลย (2/3 ของเธรดในบล็อกที่ 6)
Aliza

3
@ cibercitizen1 - ฉันคิดว่าจุดของ Aliza นั้นดี: ถ้าเป็นไปได้เราต้องการใช้เธรดจำนวนมากต่อบล็อกเท่าที่จะทำได้ หากมีข้อ จำกัด ที่ต้องใช้เธรดน้อยกว่าให้อธิบายสาเหตุที่อาจเป็นไปได้ในตัวอย่างที่สอง (แต่ยังคงอธิบายกรณีที่ง่ายและเป็นที่ต้องการมากกว่าเดิม)

6
@ พวกเขาใช่บางที แต่ในกรณีที่ปริมาณหน่วยความจำที่จำเป็นสำหรับแต่ละเธรดนั้นขึ้นอยู่กับแอปพลิเคชัน ตัวอย่างเช่นในโปรแกรมสุดท้ายของฉันแต่ละเธรดจะเรียกใช้ฟังก์ชั่นการปรับให้เหมาะสมน้อยที่สุดซึ่งต้องการหน่วยความจำ "มาก" บล็อกนั้นไม่สามารถมีขนาดใหญ่กว่าเธรด 4x4 ได้ ถึงกระนั้นการเร่งความเร็วที่ได้รับก็น่าทึ่งเทียบกับเวอร์ชั่นต่อเนื่อง
cibercitizen1

9

สมมติว่า 9800GT GPU:

  • มันมี 14 มัลติโปรเซสเซอร์ (SM)
  • SM แต่ละตัวมี 8 ตัวประมวลผลเธรด (AKA สตรีมโปรเซสเซอร์, SP หรือแกนประมวลผล)
  • อนุญาตสูงสุด 512 เธรดต่อบล็อก
  • warpsize คือ 32 (ซึ่งหมายความว่าแต่ละ 14x8 = 112 ตัวประมวลผลเธรดสามารถกำหนดเวลาได้สูงสุด 32 เธรด)

https://www.tutorialspoint.com/cuda/cuda_threads.htm

บล็อกไม่สามารถมีเธรดที่ใช้งานได้มากกว่า 512 ดังนั้น__syncthreadsสามารถซิงโครไนซ์จำนวนเธรดที่ จำกัด ได้เท่านั้น เช่นถ้าคุณดำเนินการต่อไปนี้ด้วย 600 กระทู้:

func1();
__syncthreads();
func2();
__syncthreads();

เคอร์เนลจะต้องรันสองครั้งและลำดับการดำเนินการจะเป็น:

  1. func1 ถูกดำเนินการสำหรับ 512 เธรดแรก
  2. func2 ถูกดำเนินการสำหรับ 512 เธรดแรก
  3. func1 ถูกดำเนินการสำหรับเธรดที่เหลืออยู่
  4. func2 ถูกดำเนินการสำหรับเธรดที่เหลืออยู่

บันทึก:

จุดหลักคือ__syncthreadsการดำเนินการทั้งบล็อกและไม่ซิงโครไนซ์เธรดทั้งหมด


ฉันไม่แน่ใจเกี่ยวกับจำนวนเธรดที่แน่นอนที่__syncthreadsสามารถซิงโครไนซ์ได้เนื่องจากคุณสามารถสร้างบล็อกที่มีเธรดมากกว่า 512 เธรดและให้ warp จัดการการกำหนดเวลา เพื่อความเข้าใจของฉันมันแม่นยำมากขึ้นที่จะพูดว่า: func1 ดำเนินการอย่างน้อยสำหรับ 512 กระทู้แรก

ก่อนที่ฉันจะแก้ไขคำตอบนี้ (ย้อนกลับไปในปี 2010) ฉันวัดว่ามีการซิงโครไนซ์ 14x8x32 เธรดโดยใช้ __syncthreadsหัวข้อที่ถูกใช้ทำข้อมูลให้ตรงกัน

ฉันจะขอบคุณมากถ้ามีคนทดสอบอีกครั้งเพื่อหาข้อมูลที่แม่นยำยิ่งขึ้น


จะเกิดอะไรขึ้นหาก func2 () ขึ้นอยู่กับผลลัพธ์ของ func1 () ฉันคิดว่ามันผิด
Chris

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

ขออภัยฉันคิดว่านี่เป็นสิ่งที่ผิดนอกจากนี้ GPU นั้นสามารถรันเธรด 112 พร้อมกันได้เท่านั้น
Steven Lu

@StevenLu คุณได้ลองแล้วหรือยัง ฉันยังไม่คิดว่า 112 กระทู้ที่ทำงานพร้อมกันเหมาะสมสำหรับ GPU 112 คือจำนวนสตรีมโปรเซสเซอร์ ฉันจำ CUDA ได้ยากแล้วตอนนี้ :)
Bizhan

1
@StevenLu จำนวนเธรดสูงสุดไม่ใช่ปัญหาที่นี่__syncthreadsคือการดำเนินการทั้งบล็อกและความจริงที่ว่าไม่ได้ซิงโครไนซ์เธรดทั้งหมดจริง ๆ เป็นสิ่งที่สร้างความรำคาญให้กับผู้เรียน CUDA ดังนั้นฉันจึงอัปเดตคำตอบตามข้อมูลที่คุณให้ไว้ ฉันซาบซึ้งจริงๆ
Bizhan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.