Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Лекция 11: Программирование графических процессоров на NVIDIA CUDA
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
12. Warp scheduler (GK110)
Планировщик организует потоки в группы по 32 (warps)
Потоки группы выполняются одновременно
Каждый такт потоки группы (warp) выполняют две
независимые инструкции (допустимо совмещение
инструкций double и float)
12
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
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
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
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
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
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
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
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