cudaक्यूडा के साथ शुरू हो रहा है


टिप्पणियों

CUDA उनके GPU के लिए एक मालिकाना NVIDIA समानांतर कंप्यूटिंग तकनीक और प्रोग्रामिंग भाषा है।

जीपीयू समानांतर समानांतर मशीनें हैं जो समानांतर में हजारों हल्के धागे चलाने में सक्षम हैं। प्रत्येक GPU धागा आमतौर पर निष्पादन में धीमा होता है और उनका संदर्भ छोटा होता है। दूसरी ओर, GPU समानांतर में कई हजारों धागे चलाने में सक्षम है और समवर्ती (सटीक संख्या वास्तविक GPU पर निर्भर करता है)। CUDA एक C ++ बोली विशेष रूप से NVIDIA GPU वास्तुकला के लिए डिज़ाइन किया गया है। हालांकि, वास्तुकला के अंतर के कारण, अधिकांश एल्गोरिदम सादे सी ++ से केवल कॉपी-पेस्ट नहीं किए जा सकते हैं - वे चलेंगे, लेकिन बहुत धीमी गति से होंगे।

शब्दावली

  • होस्ट - सामान्य सीपीयू-आधारित हार्डवेयर और उस वातावरण में चलने वाले सामान्य कार्यक्रमों को संदर्भित करता है
  • डिवाइस - एक विशिष्ट GPU को संदर्भित करता है जो CUDA प्रोग्राम में चलता है। एक एकल होस्ट कई उपकरणों का समर्थन कर सकता है।
  • कर्नेल - एक फ़ंक्शन जो उस डिवाइस पर रहता है जिसे होस्ट कोड से मंगवाया जा सकता है।

शारीरिक प्रोसेसर संरचना

CUDA- सक्षम GPU प्रोसेसर में निम्नलिखित भौतिक संरचना होती है:

  • चिप - GPU के पूरे प्रोसेसर। कुछ GPU में दो हैं।
  • मल्टीप्रोसेसर (एसएम) को स्ट्रीम करना - प्रत्येक चिप में एक मॉडल के आधार पर ~ 100 एसएम तक होते हैं। प्रत्येक एसएम एक दूसरे से स्वतंत्र रूप से संचालित होता है, एक दूसरे से संवाद करने के लिए केवल वैश्विक मेमोरी का उपयोग करता है।
  • सीयूडीए कोर - एक एसएम की एकल स्केलर कम्प्यूट यूनिट। उनकी सटीक संख्या वास्तुकला पर निर्भर करती है। प्रत्येक कोर एक त्वरित उत्तराधिकार (सीपीयू में हाइपरथ्रेडिंग के समान) में समवर्ती रूप से निष्पादित कुछ धागे को संभाल सकता है।

इसके अलावा, प्रत्येक एसएम में एक या अधिक ताना शेड्यूलर होते हैं । प्रत्येक अनुसूचक कई CUDA कोर के लिए एक निर्देश भेजता है। यह प्रभावी रूप से एसएम को 32-वाइड सिमड मोड में संचालित करने का कारण बनता है।

CUDA निष्पादन मॉडल

GPU की भौतिक संरचना पर सीधा प्रभाव पड़ता है कि डिवाइस पर गुठली को कैसे निष्पादित किया जाता है, और CUDA में उन्हें कैसे प्रोग्राम करता है। कर्नेल को कॉल कॉन्फ़िगरेशन के साथ लागू किया जाता है जो निर्दिष्ट करता है कि कितने समानांतर थ्रेड्स को जन्म दिया गया है।

  • ग्रिड - उन सभी थ्रेड्स का प्रतिनिधित्व करता है जो कर्नेल कॉल पर स्पॉन किए जाते हैं। यह एक या दो खंडों के सेट के रूप में निर्दिष्ट है
  • ब्लॉक - धागे का एक अर्ध-स्वतंत्र सेट है। प्रत्येक ब्लॉक एक एकल एसएम को सौंपा गया है। जैसे, ब्लॉक केवल वैश्विक मेमोरी के माध्यम से संवाद कर सकते हैं। ब्लॉक किसी भी तरह से सिंक्रनाइज़ नहीं हैं। यदि बहुत अधिक ब्लॉक हैं, तो कुछ अन्य के बाद क्रमिक रूप से निष्पादित हो सकते हैं। दूसरी ओर, यदि संसाधन अनुमति देते हैं, तो एक ही एसएम पर एक से अधिक ब्लॉक चल सकते हैं, लेकिन प्रोग्रामर को ऐसा होने (स्पष्ट प्रदर्शन को बढ़ावा देने के लिए छोड़कर) से कोई लाभ नहीं हो सकता है।
  • धागा - एक एकल CUDA कोर द्वारा निष्पादित निर्देशों का एक स्केलर अनुक्रम। थ्रेड्स न्यूनतम संदर्भ के साथ 'हल्के' हैं, जिससे हार्डवेयर जल्दी से उन्हें अंदर और बाहर स्वैप कर सकते हैं। उनकी संख्या के कारण, CUDA थ्रेड्स उन्हें सौंपे गए कुछ रजिस्टरों के साथ काम करते हैं, और बहुत कम स्टैक (अधिमानतः कोई नहीं!)। उस कारण से, CUDA संकलक कर्नेल को समतल करने के लिए सभी फ़ंक्शन कॉल को इनलाइन करना पसंद करता है ताकि इसमें केवल स्थिर कूद और लूप हो। सबसे नए उपकरणों में समर्थित फंक्शन पॉंटर कॉल और वर्चुअल मेथड कॉल, आमतौर पर एक प्रमुख प्रदर्शन तपस्या होती है।

प्रत्येक धागा एक ब्लॉक सूचकांक द्वारा पहचाना जाता है blockIdx और ब्लॉक के भीतर धागा सूचकांक threadIdx । इन नंबरों को किसी भी चल रहे धागे से किसी भी समय चेक किया जा सकता है और एक धागे को दूसरे से अलग करने का एकमात्र तरीका है।

इसके अलावा, थ्रेड्स को युद्ध में व्यवस्थित किया जाता है, प्रत्येक में बिल्कुल 32 धागे होते हैं। एक एकल ताना के भीतर थ्रेड्स SIMD फ़ेसन में एक सही सिंक में निष्पादित होते हैं। विभिन्न ताना से धागे, लेकिन एक ही ब्लॉक के भीतर किसी भी क्रम में निष्पादित कर सकते हैं, लेकिन प्रोग्रामर द्वारा सिंक्रनाइज़ करने के लिए मजबूर किया जा सकता है। विभिन्न ब्लॉकों के थ्रेड्स को किसी भी तरह से सीधे सिंक्रनाइज़ या इंटरैक्ट नहीं किया जा सकता है।

मेमोरी संगठन

सामान्य सीपीयू प्रोग्रामिंग में मेमोरी संगठन आमतौर पर प्रोग्रामर से छिपा होता है। विशिष्ट कार्यक्रम ऐसे कार्य करते हैं जैसे कि सिर्फ RAM हो। सभी मेमोरी ऑपरेशंस, जैसे कि रजिस्टरों को प्रबंधित करना, L1- L2- L3- कैशिंग, डिस्क की अदला-बदली, आदि का उपयोग कंपाइलर, ऑपरेटिंग सिस्टम या हार्डवेयर द्वारा स्वयं किया जाता है।

CUDA के साथ ऐसा नहीं है। जबकि नए GPU मॉडल आंशिक रूप से बोझ को छिपाते हैं, उदाहरण के लिए CUDA 6 में यूनिफाइड मेमोरी के माध्यम से, यह अभी भी प्रदर्शन कारणों के लिए संगठन को समझने के लायक है। मूल CUDA मेमोरी संरचना इस प्रकार है:

  • होस्ट मेमोरी - नियमित रैम। ज्यादातर होस्ट कोड द्वारा उपयोग किया जाता है, लेकिन नए GPU मॉडल भी इसे एक्सेस कर सकते हैं। जब कोई कर्नेल होस्ट मेमोरी तक पहुंचता है, तो GPU को मदरबोर्ड के साथ आमतौर पर PCIe कनेक्टर के माध्यम से संवाद करना चाहिए और जैसे कि यह अपेक्षाकृत धीमा है।
  • डिवाइस मेमोरी / ग्लोबल मेमोरी - GPU की मुख्य ऑफ-चिप मेमोरी, सभी थ्रेड्स के लिए उपलब्ध है।
  • साझा की गई मेमोरी - प्रत्येक SM में स्थित वैश्विक की तुलना में अधिक त्वरित पहुँच की अनुमति देती है। साझा मेमोरी प्रत्येक ब्लॉक के लिए निजी है। एक ब्लॉक के भीतर थ्रेड्स इसे संचार के लिए उपयोग कर सकते हैं।
  • रजिस्टर - प्रत्येक धागे की सबसे तेज, निजी, अनजानी स्मृति। सामान्य तौर पर इनका उपयोग संचार के लिए नहीं किया जा सकता है, लेकिन कुछ आंतरिक कार्य एक ताना के भीतर अपनी सामग्री को फेरबदल करने की अनुमति देते हैं।
  • स्थानीय स्मृति - प्रत्येक थ्रेड कि पता है की निजी स्मृति। इसका उपयोग रजिस्टर स्पिल के लिए किया जाता है, और वैरिएबल इंडेक्सिंग के साथ स्थानीय एरेज़। शारीरिक रूप से, वे वैश्विक स्मृति में रहते हैं।
  • बनावट स्मृति, निरंतर स्मृति - वैश्विक स्मृति का एक हिस्सा जो कर्नेल के लिए अपरिवर्तनीय के रूप में चिह्नित है। यह GPU को विशेष प्रयोजन के कैश का उपयोग करने की अनुमति देता है।
  • L2 कैश - ऑन-चिप, सभी थ्रेड्स के लिए उपलब्ध। थ्रेड्स की मात्रा को देखते हुए, प्रत्येक कैश लाइन का अपेक्षित जीवनकाल सीपीयू की तुलना में बहुत कम है। इसका उपयोग ज्यादातर सहायता प्राप्त मिसलीड और आंशिक रूप से रैंडम मेमोरी एक्सेस पैटर्न में किया जाता है।
  • L1 कैश - साझा मेमोरी के समान स्थान पर स्थित है। फिर, राशि बल्कि छोटी है, इसका उपयोग करते हुए थ्रेड्स की संख्या को देखते हुए, इसलिए डेटा को लंबे समय तक वहां रहने की उम्मीद न करें। L1 कैशिंग अक्षम किया जा सकता है।

संस्करण

संगणना क्षमता आर्किटेक्चर GPU कोडनाम रिलीज़ की तारीख
1.0 टेस्ला G80 2006/11/08
1.1 टेस्ला G84, G86, G92, G94, G96, G98, 2007-04-17
1.2 टेस्ला GT218, GT216, GT215 2009-04-01
1.3 टेस्ला GT200, GT200b 2009-04-09
2.0 फर्मी GF100, GF110 2010-03-26
2.1 फर्मी GF104, GF106 GF108, GF114, GF116, GF117, GF119 2010-07-12
3.0 केपलर GK104, GK106, GK107 2012-03-22
3.2 केपलर GK20A 2014-04-01
3.5 केपलर GK110, GK208 2013-02-19
3.7 केपलर GK210 2014-11-17
5.0 मैक्सवेल GM107, GM108 2014-02-18
5.2 मैक्सवेल GM200, GM204, GM206 2014-09-18
5.3 मैक्सवेल GM20B 2015/04/01
6.0 पास्कल GP100 2016/10/01
6.1 पास्कल GP102, GP104, GP106 2016/05/27

रिलीज की तारीख गणना क्षमता प्रदान करने वाले पहले GPU के समर्थन को जारी करती है। कुछ तिथियां अनुमानित हैं, उदाहरण के लिए Q2 कार्ड को Q2 2014 में जारी किया गया था।

नमूना कार्यक्रमों को संकलित करना और चलाना

CUDA टूलकिट की आपकी स्थापना को सत्यापित करने के लिए नमूना प्रोग्राम चलाने के साथ NVIDIA इंस्टॉलेशन गाइड समाप्त होता है, लेकिन यह स्पष्ट रूप से नहीं बताता है कि कैसे। पहले सभी आवश्यक शर्तें जांचें। नमूना कार्यक्रमों के लिए डिफ़ॉल्ट CUDA निर्देशिका की जाँच करें। यदि यह मौजूद नहीं है, तो इसे आधिकारिक CUDA वेबसाइट से डाउनलोड किया जा सकता है। उस निर्देशिका पर नेविगेट करें जहां उदाहरण मौजूद हैं।

$ cd /path/to/samples/
$ ls
 

आपको इसके समान आउटपुट देखना चाहिए:

0_Simple     2_Graphics  4_Finance      6_Advanced       bin     EULA.txt
1_Utilities  3_Imaging   5_Simulations  7_CUDALibraries  common  Makefile
 

सुनिश्चित करें कि Makefile इस निर्देशिका में मौजूद है। UNIX आधारित सिस्टम में make कमांड सभी सैंपल प्रोग्राम का निर्माण करेगा। वैकल्पिक रूप से, एक उपनिर्देशिका पर नेविगेट करें जहां एक और Makefile मौजूद है और केवल उस नमूने के निर्माण के लिए वहां से make कमांड चलाएं।

दो सुझाए गए नमूना कार्यक्रम deviceQuery - deviceQuery और bandwidthTest :

$ cd 1_Utilities/deviceQuery/
$ ./deviceQuery 
 

आउटपुट नीचे दिखाए गए के समान होगा:

./deviceQuery Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

Detected 1 CUDA Capable device(s)

Device 0: "GeForce GTX 950M"
  CUDA Driver Version / Runtime Version          7.5 / 7.5
  CUDA Capability Major/Minor version number:    5.0
  Total amount of global memory:                 4096 MBytes (4294836224 bytes)
  ( 5) Multiprocessors, (128) CUDA Cores/MP:     640 CUDA Cores
  GPU Max Clock rate:                            1124 MHz (1.12 GHz)
  Memory Clock rate:                             900 Mhz
  Memory Bus Width:                              128-bit
  L2 Cache Size:                                 2097152 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096)
  Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  2048
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 1 copy engine(s)
  Run time limit on kernels:                     Yes
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  Device supports Unified Addressing (UVA):      Yes
  Device PCI Domain ID / Bus ID / location ID:   0 / 1 / 0
  Compute Mode:
     < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 7.5, CUDA Runtime Version = 7.5, NumDevs = 1, Device0 = GeForce GTX 950M
Result = PASS
 

बयान Result = PASS अंत में Result = PASS इंगित करता है कि सब कुछ सही ढंग से काम कर रहा है। अब, अन्य सुझाए गए नमूना प्रोग्राम bandwidthTest को एक समान तरीके से चलाएं। आउटपुट के समान होगा:

[CUDA Bandwidth Test] - Starting...
Running on...

 Device 0: GeForce GTX 950M
 Quick Mode

 Host to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            10604.5

 Device to Host Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            10202.0

 Device to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            23389.7

Result = PASS

NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.
 

फिर से, Result = PASS स्टेटमेंट बताता है कि सब कुछ ठीक से निष्पादित किया गया था। अन्य सभी नमूना कार्यक्रम समान रूप से चलाए जा सकते हैं।

चलो नमस्ते कहने के लिए एक एकल CUDA धागा लॉन्च करें

यह सरल CUDA कार्यक्रम दर्शाता है कि एक फ़ंक्शन कैसे लिखना है जो GPU (उर्फ "डिवाइस") पर निष्पादित होगा। सीपीयू, या "होस्ट", विशेष कार्यों को "कर्नेल" कहकर CUDA थ्रेड बनाता है। CUDA प्रोग्राम अतिरिक्त वाक्य रचना के साथ C ++ प्रोग्राम हैं।

यह कैसे काम करता है, यह देखने के लिए, hello.cu नामक फ़ाइल में निम्न कोड hello.cu :

#include <stdio.h>

// __global__ functions, or "kernels", execute on the device
__global__ void hello_kernel(void)
{
  printf("Hello, world from the device!\n");
}

int main(void)
{
  // greet from the host
  printf("Hello, world from the host!\n");

  // launch a kernel with a single thread to greet from the device
  hello_kernel<<<1,1>>>();

  // wait for the device to finish so that we see the message
  cudaDeviceSynchronize();

  return 0;
}
 

(ध्यान दें कि डिवाइस पर printf फ़ंक्शन का उपयोग करने के लिए, आपको एक ऐसे डिवाइस की आवश्यकता है जिसमें कम से कम 2.0 की क्षमता हो। विवरण के लिए संस्करण अवलोकन देखें।)

अब NVIDIA कंपाइलर का उपयोग करके प्रोग्राम को संकलित करें और इसे चलाएं:

$ nvcc hello.cu -o hello
$ ./hello
Hello, world from the host!
Hello, world from the device!
 

उपरोक्त उदाहरण के बारे में कुछ अतिरिक्त जानकारी:

  • nvcc अर्थ "NVIDIA CUDA कंपाइलर" है। यह स्रोत कोड को होस्ट और डिवाइस घटकों में अलग करता है।
  • __global__ एक CUDA कीवर्ड है जिसका उपयोग फ़ंक्शन घोषणाओं में किया जाता है जो दर्शाता है कि फ़ंक्शन GPU डिवाइस पर चलता है और होस्ट से कहा जाता है।
  • ट्रिपल एंगल ब्रैकेट्स ( <<< , >>> ) होस्ट कोड से डिवाइस कोड पर कॉल करते हैं (जिसे "कर्नेल लॉन्च" भी कहा जाता है)। इन ट्रिपल कोष्ठकों के भीतर की संख्या समानांतर में निष्पादित करने के लिए और थ्रेड्स की संख्या को इंगित करती है।

आवश्यक शर्तें

CUDA के साथ प्रोग्रामिंग शुरू करने के लिए, CUDA टूलकिट और डेवलपर ड्राइवर को डाउनलोड और इंस्टॉल करें। टूलकिट में nvcc , NVIDIA CUDA कंपाइलर और CUDA अनुप्रयोगों को विकसित करने के लिए आवश्यक अन्य सॉफ़्टवेयर शामिल हैं। ड्राइवर यह सुनिश्चित करता है कि GPU प्रोग्राम CUDA- सक्षम हार्डवेयर पर सही ढंग से चले, जिसकी आपको आवश्यकता होगी।

आप पुष्टि कर सकते हैं कि कमांड लाइन से nvcc --version चलाकर CUDA टूलकिट आपकी मशीन पर सही ढंग से स्थापित है। उदाहरण के लिए, लिनक्स मशीन पर,

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jul_12_18:28:38_CDT_2016
Cuda compilation tools, release 8.0, V8.0.32
 

संकलक जानकारी को आउटपुट करता है। यदि पिछली कमांड सफल नहीं थी, तो CUDA टूलकिट स्थापित होने की संभावना नहीं है, या Windows मशीनों पर nvcc ( C:\CUDA\bin ), या POSIX OS पर /usr/local/cuda/bin का पथ) आपका हिस्सा नहीं है PATH पर्यावरण चर।

इसके अतिरिक्त, आपको एक होस्ट कंपाइलर की भी आवश्यकता होगी जो CUDA प्रोग्राम को संकलित करने और बनाने के लिए nvcc साथ काम करता है। Windows पर, यह cl.exe , Microsoft कंपाइलर, जो Microsoft Visual Studio के साथ शिप करता है। POSIX OSes पर, gcc या g++ सहित अन्य संकलक उपलब्ध हैं। आधिकारिक CUDA क्विक स्टार्ट गाइड आपको बता सकता है कि आपके विशेष प्लेटफॉर्म पर कौन से संकलक संस्करण समर्थित हैं।

यह सुनिश्चित करने के लिए कि सब कुछ सही ढंग से सेट किया गया है, आइए सभी टूल को एक साथ सही ढंग से काम करने के लिए एक तुच्छ CUDA प्रोग्राम संकलित करें और चलाएं।

__global__ void foo() {}

int main()
{
  foo<<<1,1>>>();

  cudaDeviceSynchronize();
  printf("CUDA error: %s\n", cudaGetErrorString(cudaGetLastError()));

  return 0;
}
 

इस प्रोग्राम को संकलित करने के लिए, इसे test.cu नामक फ़ाइल में कॉपी करें और कमांड लाइन से संकलित करें। उदाहरण के लिए, लिनक्स सिस्टम पर, निम्नलिखित कार्य करना चाहिए:

$ nvcc test.cu -o test
$ ./test
CUDA error: no error
 

यदि कार्यक्रम त्रुटि के बिना सफल होता है, तो चलो कोडिंग शुरू करें!

CUDA के साथ दो सरणियाँ

यह उदाहरण दिखाता है कि एक साधारण प्रोग्राम कैसे बनाया जाए जो CUDA के साथ दो int सरणियों का योग करेगा।

एक CUDA कार्यक्रम विषम है और भागों में CPU और GPU दोनों शामिल हैं।

CUDA का उपयोग करने वाले प्रोग्राम के मुख्य भाग सीपीयू प्रोग्राम के समान होते हैं और इसमें शामिल होते हैं

  • GPU के लिए उपयोग किए जाने वाले डेटा के लिए मेमोरी आवंटन
  • होस्ट मेमोरी से जीपीयू मेमोरी में डेटा कॉपी करना
  • डेटा को संसाधित करने के लिए कर्नेल फ़ंक्शन को आमंत्रित करना
  • सीपीयू मेमोरी में परिणाम की प्रतिलिपि बनाएँ

डिवाइस मेमोरी को आवंटित करने के लिए हम cudaMalloc फ़ंक्शन का उपयोग cudaMalloc हैं। डिवाइस और होस्ट cudaMemcpy फ़ंक्शन के बीच डेटा की प्रतिलिपि बनाने के लिए उपयोग किया जा सकता है। cudaMemcpy का अंतिम तर्क प्रतिलिपि कार्रवाई की दिशा निर्दिष्ट करता है। 5 संभावित प्रकार हैं:

  • cudaMemcpyHostToHost - होस्ट -> होस्ट
  • cudaMemcpyHostToDevice - होस्ट -> डिवाइस
  • cudaMemcpyDeviceToHost - उपकरण -> होस्ट
  • cudaMemcpyDeviceToDevice - डिवाइस -> डिवाइस
  • cudaMemcpyDefault - डिफ़ॉल्ट आधारित एकीकृत वर्चुअल एड्रेस स्पेस

अगले कर्नेल फ़ंक्शन को लागू किया जाता है। ट्रिपल शेवरॉन के बीच की जानकारी निष्पादन कॉन्फ़िगरेशन है, जो बताता है कि कितने डिवाइस थ्रेड समानांतर में कर्नेल को निष्पादित करते हैं। पहला नंबर (उदाहरण में 2 ) ब्लॉक की संख्या और दूसरा (size + 1) / 2 उदाहरण में निर्दिष्ट करता है - एक ब्लॉक में धागे की संख्या। ध्यान दें कि इस उदाहरण में हम आकार में 1 जोड़ते हैं, ताकि हम दो तत्वों के लिए जिम्मेदार एक धागे के बजाय एक अतिरिक्त धागे का अनुरोध करें।

चूंकि कर्नेल आह्वान एक अतुल्यकालिक फ़ंक्शन है cudaDeviceSynchronize को निष्पादन पूरा होने तक प्रतीक्षा करने के लिए कहा जाता है। परिणाम सरणियों को होस्ट मेमोरी में कॉपी किया जाता है और डिवाइस पर आवंटित सभी मेमोरी को cudaFree से मुक्त किया cudaFree

फ़ंक्शन को कर्नेल के रूप में परिभाषित करने के लिए __global__ डिक्लेरेशन __global__ का उपयोग किया जाता है। यह फ़ंक्शन प्रत्येक थ्रेड द्वारा लागू किया जाएगा। यदि हम परिणामी सरणी के एक तत्व को संसाधित करने के लिए प्रत्येक थ्रेड को चाहते हैं, तो हमें प्रत्येक थ्रेड को पहचानने और पहचानने का एक साधन चाहिए। CUDA वैरिएबल blockDim , blockIdx और threadIdx को परिभाषित करता है। पूर्वनिर्धारित चर blockDim में कर्नेल लॉन्च के लिए दूसरे निष्पादन कॉन्फ़िगरेशन पैरामीटर में निर्दिष्ट प्रत्येक थ्रेड ब्लॉक के आयाम शामिल हैं। पूर्वनिर्धारित चर threadIdx और blockIdx में क्रमशः थ्रेड ब्लॉक के भीतर और ग्रिड के भीतर धागा ब्लॉक के सूचकांक होते हैं। ध्यान दें कि चूंकि हम सरणियों में तत्वों की तुलना में संभावित रूप से एक और थ्रेड का अनुरोध करते हैं, इसलिए हमें यह सुनिश्चित करने के लिए size में पास होने की size है कि हम सरणी के अंत तक नहीं पहुंचें।

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>

__global__ void addKernel(int* c, const int* a, const int* b, int size) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < size) {
        c[i] = a[i] + b[i];
    }
}

// Helper function for using CUDA to add vectors in parallel.
void addWithCuda(int* c, const int* a, const int* b, int size) {
    int* dev_a = nullptr;
    int* dev_b = nullptr;
    int* dev_c = nullptr;

    // Allocate GPU buffers for three vectors (two input, one output)
    cudaMalloc((void**)&dev_c, size * sizeof(int));
    cudaMalloc((void**)&dev_a, size * sizeof(int));
    cudaMalloc((void**)&dev_b, size * sizeof(int));

    // Copy input vectors from host memory to GPU buffers.
    cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);

    // Launch a kernel on the GPU with one thread for each element.
    // 2 is number of computational blocks and (size + 1) / 2 is a number of threads in a block
    addKernel<<<2, (size + 1) / 2>>>(dev_c, dev_a, dev_b, size);
    
    // cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaDeviceSynchronize();

    // Copy output vector from GPU buffer to host memory.
    cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);

    cudaFree(dev_c);
    cudaFree(dev_a);
    cudaFree(dev_b);
}

int main(int argc, char** argv) {
    const int arraySize = 5;
    const int a[arraySize] = {  1,  2,  3,  4,  5 };
    const int b[arraySize] = { 10, 20, 30, 40, 50 };
    int c[arraySize] = { 0 };

    addWithCuda(c, a, b, arraySize);

    printf("{1, 2, 3, 4, 5} + {10, 20, 30, 40, 50} = {%d, %d, %d, %d, %d}\n", c[0], c[1], c[2], c[3], c[4]);

    cudaDeviceReset();

    return 0;
}