Your SlideShare is downloading. ×
Лекция 11: Программирование графических процессоров на NVIDIA CUDA
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Лекция 11: Программирование графических процессоров на NVIDIA CUDA

870
views

Published on


0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
870
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
15
Comments
0
Likes
1
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Лекция 11: NVIDIA CUDA Курносов Михаил Георгиевич к.т.н. доцент Кафедры вычислительных систем Сибирский государственный университет телекоммуникаций и информатики http://www.mkurnosov.net
  • 2. NVIDIA CUDA  NVIDIA CUDA – программно-аппаратная платформа для организации параллельных вычислений на графических процессорах (Graphic processing unit – GPU)  NVIDIA CUDA SDK: o архитектура виртуальной машины CUDA o компилятор C/C++ o драйвер GPU  ОС: GNU/Linux, Apple Mac OS X, Microsoft Windows  2013 – CUDA 5.5 (CUDA 6.0 announced)  2006 – CUDA 1.0 http://developer.nvidia.com/cuda 2
  • 3. Архитектура NVIDIA CUDA Host (CPU) CPU Code CUDA C/C++/Fortran Source CUDA C/C++/Fortran Compiler (NVIDIA nvcc, PGI pgfortran) PTX (Parallel Thread Execution) (NVIDA GPU assembly language) GPU Driver (JIT compiler) Device (GPU) GPU binary code 3
  • 4. Архитектура NVIDIA CUDA  NVIDIA C/C++ Compiler (nvcc) – компилятор c расширений языков C/C++ (основан на LLVM), генерирует код для CPU и GPU  NVIDA PTX Assembler (ptxas)  Набор команд PTX развивается: PTX ISA 3.2 (2013), PTX ISA 3.1 (2012), …  Архитектуры NVIDIA CUDA o NVIDIA Tesla (2007) o NVIDIA Fermi (GeForce 400 Series, 2010) o NVIDIA Kepler (GeForce 600 Series, 2012) o NVIDIA Maxwell (2014) http://docs.nvidia.com/cuda/parallel-thread-execution/index.html 4
  • 5. Гетерогенные вычислительные узлы Memory (DDR3) CPU1 Core1 Core2 Core3 Memory (DDR3) CPU1 QPI Core4 Core1 Core2 Core3 Core4 QPI QPI I/O Hub PCI Express Gen. 2 GPU1 GPU1 GPU Memory GPU Memory Cores Cores 5
  • 6. Гетерогенные вычислительные узлы PCI Express 3.0 NVIDIA Tesla K10 (2 GPU)  Процессорные ядра: 2 GPU NVIDIA GK104 (2 x 1536 ядер)  Архитектура: NVIDIA Kepler  RAM: 8GB GDDR5 6
  • 7. Архитектура NVIDIA Kepler (GK110)  15 SMX – streaming multiprocessor (возможны конфигурации с 13 и 14 SMX)  6 контроллеров памяти (64-бит)  Интерфейс подключения к хосту PCI Express 3.0 7
  • 8. Архитектура NVIDIA Kepler (GK110) 8
  • 9. Архитектура NVIDIA Kepler (GK110) SMX – streaming multiprocessor 9
  • 10. Архитектура SMX (GK110)  192 ядра для выполнения операций с одинарной точностью (single precision float, integer)  64 модуля двойной точности (double precision, FMA)  32 модуля специальных функций (SFU)  32 модуля чтения/записи (LD/ST)  4 планировщика потоков (warp schedulers) 10
  • 11. Архитектура SMX (GK110) 11
  • 12. Warp scheduler (GK110)  Планировщик организует потоки в группы по 32 (warps)  Потоки группы выполняются одновременно  Каждый такт потоки группы (warp) выполняют две независимые инструкции (допустимо совмещение инструкций double и float) 12
  • 13. Warp scheduler (GK110) 13
  • 14. Организация памяти Kepler (GK110)  Каждый SMX имеет 64KB памяти: o 48KB shared + 16KB L1 cache o 16KB shared + 48KB L1 cache  L2 Cache 1536KB – общий для всех SMX  Global Memory 8GB 14
  • 15. Архитектура NVIDIA Kepler (GK110) FERMI GF100 FERMI GF104 KEPLER KEPLER GK104 GK110 Compute Capability 2.0 2.1 3.0 3.5 Threads / Warp 32 32 32 32 Max Warps / Multiprocessor 48 48 64 64 Max Thread Blocks / Multiprocessor 8 8 16 16 Max Threads / Thread Block 1024 1024 1024 1024 32‐bit Registers / Multiprocessor 32768 32768 65536 65536 Max Registers / Thread 63 63 63 255 Max X Grid Dimension 2^16‐1 2^16‐1 2^32‐1 2^32‐1 Hyper‐Q No No No Yes Dynamic Parallelism No No No Yes 15
  • 16. NVIDIA Maxwell (2014)  NVIDIA Maxwell = GPU Cores + ARM Core  Интегрированное ядро ARM (64 бит, проект Denver) o возможность загрузки операционной системы на GPU o поддержка унифицированной виртуальной памяти (device ←→ host) 16
  • 17. Основные понятия CUDA  Хост (host) – узел с CPU и его память  Устройство (device) – графический процессор и его память  Ядро (kernel) – это фрагмент программы, предназначенный для выполнения на GPU  Пользователь самостоятельно запускает с CPU ядра на GPU  Перед выполнением ядра пользователь копирует данные из памяти хоста в память GPU  После выполнения ядра пользователь копирует данные из памяти GPU в память хоста 17
  • 18. Основные понятия CUDA CPU (Host) /* Serial code GPU (Device) */ kernelA() /* Serial code */ void kernelA() { /* Code */ } kernelB() void kernelB() { /* Code */ } /* Serial code */ 18
  • 19. Выполнение CUDA-программы CPU (Host) PCI Express GPU (Device) Memory CPU  Копирование данных из памяти хоста в память GPU Memory 19
  • 20. Выполнение CUDA-программы CPU (Host) PCI Express GPU (Device) Memory CPU  Копирование данных из памяти хоста в память GPU  Загрузка и выполнение ядра (kernel) в GPU Memory 20
  • 21. Выполнение CUDA-программы CPU (Host) PCI Express GPU (Device) Memory CPU  Копирование данных из памяти хоста в память GPU  Загрузка и выполнение ядра (kernel) в GPU  Копирование данных из памяти GPU в память хоста Memory 21
  • 22. CUDA HelloWorld! #include <stdio.h> __global__ void mykernel() { /* Parallel GPU code (kernel) */ } int main() { mykernel<<<1, 1>>>(); return 0; } 22
  • 23. CUDA HelloWorld! $ nvcc -c -o prog.o ./prog.cu $ g++ ./prog.o –o prog -L/opt/cuda-5.5/lib64 -lcudart 23
  • 24. Вычислительные ядра (kernels)  Спецификатор __global__ сообщает компилятору, что функция предназначена для выполнения на GPU  Компилятор nvcc разделяет исходный код – ядра компилируются nvcc, а остальной код системным компилятором (gcc, cl, …)  Тройные угловые скобки “<<< >>>” сообщают о вызове ядра на GPU и количестве требуемых потоков  Вызов ядра (kernel) не блокирует выполнение потока на CPU  Функция cudaThreadSynchronize() позволяет реализовать ожидание завершения ядра 24
  • 25. Вычислительные потоки (threads)  Номер потока (thread index) – это трехкомпонентный вектор (координаты потока)  Потоки логически сгруппированы в одномерный, двухмерный или трёхмерный блок (thread block)  Количество потоков в блоке ограничено (в Kepler 1024)  Блоки распределяются по потоковым мультипроцессорам SMX  Предопределенные переменные Thread Block Thread Thread Thread Thread Thread Thread o threadIdx.{x, y, z} – номер потока o blockDim.{x, y, z} – размерность блока 25
  • 26. Вычислительные потоки (threads)  Блоки группируются одно- двух- и трехмерную сетку (grid)  Блоки распределяются по потоковым мультипроцессорам SMX (в Kepler 15 SMX)  Предопределенные переменные o blockIdx.{x, y, z} – номер блока потока o gridDim.{x, y, z} – размерность сетки 26
  • 27. Вычислительные потоки (threads) Grid of thread blocks Thread Block Thread Block Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Block gridDim.y Thread Block Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Block Thread Block Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread blockDim.y Thread blockDim.x gridDim.x blockDim.z 27
  • 28. Выполнение CUDA-программы CUDA Program Block 0 Block 1 Block 2 Block 3 Block 4 Block 5 GPU 1 GPU 2 SMX 1 SMX 2 SMX 1 SMX 2 SMX 3 Block 0 Block 1 Block 0 Block 1 Block 2 Block 2 Block 3 Block 3 Block 4 Block 5 Block 4 Block 5 28
  • 29. Пример: сложение векторов void vecadd(float *a, float *b, float *c, int n) { int i; for (i = 0; i < n; i++) { c[i] = a[i] + b[i]; } } 29
  • 30. Пример: сложение векторов int main() { int i, n = 100; float *a, *b, *c; float *deva, *devb, *devc; a = b = c = for (float *)malloc(sizeof(float) * n); (float *)malloc(sizeof(float) * n); (float *)malloc(sizeof(float) * n); (i = 0; i < n; i++) { a[i] = 2.0; b[i] = 4.0; } 30
  • 31. Пример: сложение векторов // Выделяем память на GPU cudaMalloc((void **)&deva, sizeof(float) * n); cudaMalloc((void **)&devb, sizeof(float) * n); cudaMalloc((void **)&devc, sizeof(float) * n); // Копируем из памяти узла в память GPU cudaMemcpy(deva, a, sizeof(float) * n, cudaMemcpyHostToDevice); cudaMemcpy(devb, b, sizeof(float) * n, cudaMemcpyHostToDevice); vecadd_gpu<<<1, n>>>(deva, devb, devc); cudaMemcpy(c, devc, sizeof(float) * n, cudaMemcpyDeviceToHost); cudaFree(deva); cudaFree(devb); cudaFree(devc); free(a); free(b); free(c); return 0; } 31
  • 32. Пример: сложение векторов __global__ void vecadd_gpu(float *a, float *b, float *c) { // Каждый поток обрабатывает один элемент int i = threadIdx.x; c[i] = a[i] + b[i]; }  Запускается один блок из n потоков (n <= 1024) vecadd_gpu<<<1, n>>>(deva, devb, devc);  Каждый поток вычисляет один элемент массива c Thread Block Thread 0 Thread 1 … Thread n - 1 32
  • 33. Пример: сложение векторов  Сложение векторов с количеством элементов > 256 int threadsPerBlock = 256; /* Device specific */ int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock; vecadd_gpu<<<blocksPerGrid, threadsPerBlock>>>(deva, devb, devc, n);  Будет запущена группа блоков, в каждом блоке по фиксированному количеству потоков  Потоков может быть больше чем элементов в массиве 33
  • 34. Пример: сложение векторов __global__ void vecadd_gpu(float *a, float *b, float *c, int n) { int i = threadIdx.x + blockIdx.x * blockDim.x; if (i < n) c[i] = a[i] + b[i]; } Thread Block 0 Thread 0 Thread 1 … Thread Block 1 Thread … Thread 0 Thread Block 2 Thread 0 Thread 1 … Thread 1 … Thread … Thread Block 3 Thread … Thread 0 Thread 1 … Thread … 34
  • 35. Двухмерный блок потоков  Двухмерный блок потоков (threadIdx.x, threadIdx.y) dim3 threadsPerBlock(N, N); matrix<<<1, threadsPerBlock>>>(A, B, C); 35
  • 36. Информация о GPU cudaSetDevice(0); cudaDeviceProp deviceProp; cudaGetDeviceProperties(&deviceProp, 0); /* * deviceProp.maxThreadsPerBlock * deviceProp.warpSize * devProp.totalGlobalMem * ... */ 36
  • 37. NVIDIA GeForce GTS 250 CUDA Device Query (Runtime API) version (CUDART static linking) Device 0: "GeForce GTS 250" CUDA Driver Version: 3.20 CUDA Runtime Version: 3.20 CUDA Capability Major/Minor version number: 1.1 Total amount of global memory: 1073020928 bytes Multiprocessors x Cores/MP = Cores: 16 (MP) x 8 (Cores/MP) = 128 (Cores) Total amount of constant memory: 65536 bytes Total amount of shared memory per block: 16384 bytes Total number of registers available per block: 8192 Warp size: 32 Maximum number of threads per block: 512 Maximum sizes of each dimension of a block: 512 x 512 x 64 Maximum sizes of each dimension of a grid: 65535 x 65535 x 1 Maximum memory pitch: 2147483647 bytes Texture alignment: 256 bytes Clock rate: 1.91 GHz Concurrent copy and execution: Yes Run time limit on kernels: Yes Support host page-locked memory mapping: Yes Compute mode: Default (multiple host threads can use this device simultaneously) Concurrent kernel execution: No Device has ECC support enabled: No 37
  • 38. Иерархия памяти NVidia GeForce GTS 250  Global memory: 1GB  Shared mem. per block: 16KB  Constant memory: 64KB  Registers per block: 8192 38
  • 39. Data race __global__ void race(int* x) { int i = threadIdx.x + blockDim.x * blockIdx.x; *x = *x + 1; // Data race } int main() { int x; // ... race<<<1, 128>>>(d_x); // ... return 0; } 39
  • 40. CUDA Atomics  CUDA предоставляет API атомарных операций:      atomicAdd, atomicSub, atomicInc, atomicDec atomicMax, atomicMin atomicExch atomicCAS atomicAnd, atomicOr, atomicXor atomicOP(a,b) { t1 = *a; // read t2 = t1 OP b; // modify *a = t2; // write return t; } 40
  • 41. CUDA Atomics __global__ void race(int* x) { int i = threadIdx.x + blockDim.x * blockIdx.x; int j = atomicAdd(x, 1); // j = *x; *x = j + i; } int main() { int x; // ... race<<<1, 128>>>(d_x); // ... return 0; } 41
  • 42. Умножение матриц C=A*B  Результирующая матрица C разбивается на подматрицы размером 16x16 элементов  Подматрицы параллельно вычисляются блоками потоков  Каждый элемент подматрицы вычисляется отдельным потоком (в блоке 16x16 = 256 потоков)  Количество потоков = количеству элементов в матрице C 42
  • 43. Умножение матриц int main() { int block_size = 16; dim3 dimsA(10 * block_size, 10 * block_size, 1); dim3 dimsB(20 * block_size, 10 * block_size, 1); printf("A(%d,%d), B(%d,%d)n", dimsA.x, dimsA.y, dimsB.x, dimsB.y); unsigned int size_A = dimsA.x * dimsA.y; unsigned int mem_size_A = sizeof(float) * size_A; float *h_A = (float *)malloc(mem_size_A); unsigned int size_B = dimsB.x * dimsB.y; unsigned int mem_size_B = sizeof(float) * size_B; float *h_B = (float *)malloc(mem_size_B); dim3 dimsC(dimsB.x, dimsA.y, 1); unsigned int mem_size_C = dimsC.x * dimsC.y * sizeof(float); float *h_C = (float *) malloc(mem_size_C); 43
  • 44. Умножение матриц const float valB = 0.01f; constantInit(h_A, size_A, 1.0f); constantInit(h_B, size_B, 0.01f); float *d_A, *d_B, *d_C; cudaMalloc((void **) &d_A, mem_size_A); cudaMalloc((void **) &d_B, mem_size_B); cudaMalloc((void **) &d_C, mem_size_C); cudaMemcpy(d_A, h_A, mem_size_A, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, mem_size_B, cudaMemcpyHostToDevice); 44
  • 45. Умножение матриц dim3 threads(block_size, block_size); dim3 grid(dimsB.x / threads.x, dimsA.y / threads.y); matmul_gpu<16><<<grid, threads>>>(d_C, d_A, d_B, dimsA.x, dimsB.x); cudaDeviceSynchronize(); cudaMemcpy(h_C, d_C, mem_size_C, cudaMemcpyDeviceToHost); 45
  • 46. Умножение матриц free(h_A); free(h_B); free(h_C); cudaFree(d_A); cudaFree(d_B); cudaFree(d_C); return 0; } /* main */ 46
  • 47. Умножение матриц template <int BLOCK_SIZE> __global__ void matmul_gpu(float *C, float *A, float *B, int wA, int wB) { int bx = blockIdx.x; int by = blockIdx.y; int tx = threadIdx.x; int ty = threadIdx.y; int aBegin int aEnd = int aStep int bBegin int bStep float Csub = wA * BLOCK_SIZE * by; aBegin + wA - 1; = BLOCK_SIZE; = BLOCK_SIZE * bx; = BLOCK_SIZE * wB; = 0; 47
  • 48. Умножение матриц for (int a = aBegin, b = bBegin; a <= aEnd; a += aStep, b += bStep) { // sub-matrix of A __shared__ float As[BLOCK_SIZE][BLOCK_SIZE]; // sub-matrix of B __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE]; // Load from device memory to shared memory As[ty][tx] = A[a + wA * ty + tx]; Bs[ty][tx] = B[b + wB * ty + tx]; // Synchronize (wait for loading matrices) __syncthreads(); 48
  • 49. Умножение матриц // Multiply the two matrices #pragma unroll for (int k = 0; k < BLOCK_SIZE; ++k) { Csub += As[ty][k] * Bs[k][tx]; } __syncthreads(); } /* for aBegin ... */ // Write the block sub-matrix to device memory; int c = wB * BLOCK_SIZE * by + BLOCK_SIZE * bx; C[c + wB * ty + tx] = Csub; } 49
  • 50. Reduction O(log2n) Mark Harris. Optimizing Parallel Reduction in CUDA // http://www.cuvilib.com/Reduction.pdf 50
  • 51. Reduction v1  Условный оператор if внутри цикла приводит к сильному ветвлению  Можно перераспределить данные и операции по нитям 51
  • 52. Reduction v2  Количество ветвлений сокращено  Большое число конфликтов банков при обращении к разделяемой памяти 52
  • 53. Dynamic parallelism (CUDA 5.0) __global__ ChildKernel(void* data) { // Operate on data } __global__ ParentKernel(void *data) { ChildKernel<<<16, 1>>>(data); } // In Host Code ParentKernel<<<256, 64>>(data); 53
  • 54. Dynamic parallelism (CUDA 5.0) __global__ RecursiveKernel(void* data) { if (continueRecursion == true) RecursiveKernel<<<64, 16>>>(data); } 54
  • 55. Dynamic parallelism (CUDA 5.0) 55
  • 56. Литература  CUDA by Example // http://developer.download.nvidia.com/books/cuda-byexample/cuda-by-example-sample.pdf  Джейсон Сандерс, Эдвард Кэндрот. Технология CUDA в примерах. ДМК Пресс, 2011 г.  А. В. Боресков, А. А. Харламов. Основы работы с технологией CUDA. М.:ДМК, 2010 г. http://www.nvidia.ru/object/cuda-parallel-computing-books-ru.html 56