Wat is CUDA?

Programmeermodel voor algemene berekeningen op NVIDIA-GPU's. Kernels worden geschreven in een uitbreiding van C++ en parallellisme wordt uitgedrukt in de hiërarchie grid, block en thread.

Japanse benaming: CUDA

“CUDA” komt voor in 18 oefenvragen uit 1 onderwerpen, bijv. GPU-programmeren.

Thread-hiërarchie van een GPU en toewijzing aan SM's Grid B0 B1 B2 B3 B4 B5 Blok Warp 0 Warp 1 Warp 2 Warp (32 threads) Zelfde instructie tegelijk Threads gebundeld per 32 Elk blok toegewezen aan een SM SM0 B0 B3 Gedeeld geheugen SM1 B1 B4 Gedeeld geheugen SM2 B2 B5 Gedeeld geheugen Globaal geheugen (lezen/schrijven door alle blokken) Een blok draait op één SM en werkt samen via gedeeld geheugen
Thread-hiërarchie van een GPU en toewijzing aan SM's · Bekijken in het figurenoverzicht

Vakgebied: GPU-programmeren

Kernels en de threadhiërarchie

In CUDA start de CPU (host) een kernel, een functie die door veel GPU-threads tegelijk wordt uitgevoerd. Eén launch maakt een grid van blocks, en elk block bevat veel threads. Elke thread berekent een unieke globale index, meestal i = blockIdx.x × blockDim.x + threadIdx.x, om zijn data-element te kiezen: thread 5 van block 2 met 256 threads per block verwerkt element 517. Omdat threads in hele blocks worden gestart, heeft het laatste block meestal overtollige threads, dus kernels beveiligen geheugentoegangen met if (i < n) om niet voorbij het einde van de array te schrijven.

Hoe blocks op de GPU worden afgebeeld

Elk block wordt toegewezen aan één streaming multiprocessor (SM) en draait daar tot het klaar is, en een SM kan meerdere blocks tegelijk bevatten. Binnen een block worden threads uitgevoerd in warps van 32. De occupancy, de verhouding tussen het aantal aanwezige warps en het maximum van de SM, wordt beperkt door het aantal registers per thread, het shared memory per block en de blockgrootte; bij een lage occupancy zijn er te weinig warps om de geheugenlatentie te verbergen. Blocks draaien onafhankelijk en in willekeurige volgorde, dus binnen een kernel wachten op alle andere blocks kan tot een deadlock leiden; globale synchronisatie wordt normaal verkregen door het werk te verdelen over twee kernellaunches.

Geheugenruimten

Global memory is het DRAM van de GPU: hoge bandbreedte, hoge latentie, en het snelst wanneer naburige threads naburige adressen benaderen. Shared memory is een klein on-chip geheugen per block dat expliciet door het programma wordt beheerd. Het is verdeeld in 32 banks, en threads van een warp die verschillende adressen in dezelfde bank benaderen, worden na elkaar afgehandeld. __syncthreads() laat elke thread van een block wachten tot alle threads zijn aangekomen; als je het in een branch plaatst die sommige threads overslaan, leidt dat tot vastlopers of ongedefinieerde resultaten. Unified memory laat host en device één pointer delen, waarbij pagina's op aanvraag worden gemigreerd.

De GPU bezig houden

Kopieën van host naar device gaan over PCI Express en zijn vaak de bottleneck. Streams, geordende wachtrijen van transfers en kernels, laten werk in verschillende streams overlappen, zodat het volgende stuk data kan worden gekopieerd terwijl aan het huidige wordt gerekend. Elke kernellaunch kost ook een paar microseconden, dus programma's die duizenden kleine kernels starten, hebben er baat bij die samen te voegen (fusing), wat ook voorkomt dat tussenresultaten naar het geheugen worden geschreven, of opgenomen launchreeksen in één keer in te dienen met CUDA Graphs. CUDA zelf draait alleen op NVIDIA-GPU's; leveranciersonafhankelijke alternatieven zoals OpenCL volgen een vergelijkbaar model van kernels en work-groups.

Nu oefenen: vragen over CUDA

Welke uitspraak over wat in GPU-programmering zoals CUDA een 'kernel' wordt genoemd, is juist?

  1. AHet centrale deel van het besturingssysteem van de GPU
  2. BEen functie die door veel threads tegelijk op de GPU wordt uitgevoerd
  3. CEen component die de GPU-chip koelt
Antwoord en toelichting tonen

Antwoord: B. Een functie die door veel threads tegelijk op de GPU wordt uitgevoerd

Een kernel in GPU-programmering is een functie die vanuit het programma aan de CPU-zijde wordt gestart en waarbij veel threads op de GPU dezelfde code uitvoeren. Elke thread kiest met zijn eigen nummer het data-element waarvoor hij verantwoordelijk is. Bij het optellen van arrays schrijft men de kernel bijvoorbeeld zo dat één thread één element optelt, en start men zoveel threads als er elementen zijn. Het woord is hetzelfde als de kernel van een besturingssysteem, maar de betekenis is anders.

Welke combinatie van de hiërarchie van CUDA-threads is juist (van groot naar klein)?

  1. AGrid → block → thread
  2. BThread → block → grid
  3. CBlock → thread → grid
Antwoord en toelichting tonen

Antwoord: A. Grid → block → thread

Eén keer een kernel starten vormt het gehele grid, het grid is verdeeld in veel blocks en een block in veel threads. De threads van één block worden uitgevoerd op dezelfde SM (een groep rekeneenheden), kunnen via shared memory samenwerken en kunnen synchroniseren. Verschillende blocks worden onafhankelijk uitgevoerd, dus het aantal blocks dat tegelijk draait verandert met de omvang van de GPU, en hetzelfde programma draait op kleine en grote GPU's.

Veelgestelde vragen

Wat is het verschil tussen een CUDA-block en een warp?

Een block is een door de programmeur gekozen groep threads die op één SM draait en geheugen kan delen en kan synchroniseren. Een warp is de hardwaregroep van 32 threads binnen een block die instructies samen in lockstep uitvoeren.

Hoe bereken je de globale threadindex in CUDA?

Gebruik voor een eendimensionale launch i = blockIdx.x * blockDim.x + threadIdx.x. Controleer i < n voordat je arrays benadert, omdat het laatste block vaak meer threads bevat dan er nog elementen over zijn.

Waarvoor wordt shared memory in CUDA gebruikt?

Het is snel on-chip geheugen dat wordt gedeeld door de threads van één block, gebruikt om data uit global memory klaar te zetten voor hergebruik en om tussenresultaten uit te wisselen. Anders dan bij een cache bepaalt het programma wat erin komt, en __syncthreads() brengt volgorde aan in de toegangen.

“CUDA” in de toelichting

Vragen met “CUDA” (18)

  1. Welke uitspraak over wat in GPU-programmering zoals CUDA een 'kernel' wordt genoemd, is juist?
  2. Welke combinatie van de hiërarchie van CUDA-threads is juist (van groot naar klein)?
  3. In een CUDA-kernel wordt met bovenstaande formule het nummer i van elke thread bepaald. Wat is i voor de thread met blockIdx.x=2, blockDim.x=256 en threadIdx.x=5?
  4. Waarom wordt in de bovenstaande CUDA-kernel 'if (i < n)' opgenomen?
  5. Welke uitspraak over de kenmerken van CUDA shared memory is juist?
  6. Welke uitspraak over de werking van __syncthreads() in CUDA is juist?
  7. In CUDA wordt __syncthreads() binnen 'if (threadIdx.x < 16)' geplaatst, zodat alleen een deel van de threads er doorheen komt. Wat kan er juist gebeuren?
  8. Welke uitspraak over bank conflicts van CUDA shared memory is juist?
  9. Welke uitspraak over de relatie tussen blocks en SM's (streaming multiprocessors) in CUDA is juist?
  10. In CUDA wil men halverwege een kernel wachten tot de berekeningen van alle andere blocks klaar zijn voordat men naar de volgende fase gaat. Welke methode is gebruikelijk?
  11. Wat is het belangrijkste doel van het gebruik van meerdere CUDA-streams?
  12. Welke uitspraak over CUDA unified memory is juist?
  13. Welke algemene richtlijn voor het bepalen van het aantal threads per block in CUDA is juist?
  14. Bij welke toegang werkt CUDA constant memory bijzonder snel?
  15. Welke uitspraak is in het algemeen juist over wat er in CUDA gebeurt direct nadat het programma aan de CPU-zijde een kernel start?

Verwante termen

GPU Shared memory Streaming multiprocessor (SM) Occupancy Via Bank Unified memory Constant memory OpenCL FPGA

Naar de woordenlijst