Лекция 9. Программирование GPU

Preview:

DESCRIPTION

Программирование GPU (NVIDIA CUDA)

Citation preview

Курносов Михаил Георгиевич

E-mail: mkurnosov@gmail.comWWW: www.mkurnosov.net

Курс “Высокопроизводительные вычислительные системы”Сибирский государственный университет телекоммуникаций и информатики (Новосибирск)Осенний семестр, 2014

Лекция 9Программирование GPU

Graphics Processing Unit (GPU) – графический процессор, специализированный многопроцессорный ускоритель с общей памятью

Большая часть площади чипа занята элементарными ALU/FPU/Load/Store модулями

Устройство управления (Control unit) относительно простое по сравнению с CPU

NVIDIA GeForce GTX 780 (Kepler, 2304 cores, GDDR5 3 GB)

AMD Radeon HD 8970(2048 cores, GDDR5 3 GB)

GPU – Graphics Processing Unit

Гибридные вычислительные узлы

Несколько GPU

CPU управляет GPU через драйвер

Узкое место –передача данных между памятью CPU и GPU

GPU не является bootable-устройством

74 системы из Top500 (Nov. 2014) оснащены ускорителями (NVIDIA, ATI, Intel Xeon Phi, PEZY SC )

CPU1

Core1 Core2 Core3 Core4

GPU1

Cores

GPU Memory

Memory (DDR3) Memory (DDR3)

QPI

I/O Hub

QPI QPI

PCI Express Gen. 2

CPU1

Core1 Core2 Core3 Core4

GPU1

Cores

GPU Memory

Кластер Jet

Три узла с GPU (CUDA, MPI + CUDA):

cngpu1 – NVIDIA GeForce GTX 680

cngpu2 – 2 x NVIDIA GeForce 210

cngpu3 – NVIDIA GeForce GT 630

http://cpct.sibsutis.ru/index.php/Main/CUDAGTX680

NVIDIA CUDA

55

NVIDIA CUDA – программно-аппаратная

платформа для организации параллельных

вычислений на графических процессорах

(Graphic processing unit – GPU)

http://developer.nvidia.com/cuda

NVIDIA CUDA SDK:

o архитектура виртуальной машины CUDA

o компилятор C/C++

o драйвер GPU

ОС: GNU/Linux, Apple Mac OS X, Microsoft Windows

2014 – CUDA 6.5 (+ 64-bit ARM, Unified memory, Multi-GPU)

2006 – CUDA 1.0

Архитектура NVIDIA CUDA

66

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)

GPU binary code

Host (CPU)

Device (GPU)

CPU Code

Архитектура NVIDIA CUDA

77

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), PTX ISA 4.1 (2014)

Архитектуры NVIDIA CUDA

o NVIDIA Tesla (2007)

o NVIDIA Fermi (GeForce 400 Series, 2010)

o NVIDIA Kepler (GeForce 600 Series, 2012)

o NVIDIA Maxwell (GeForce 700 Series, 2014)

http://docs.nvidia.com/cuda/parallel-thread-execution/index.html

Гетерогенные вычислительные узлы

88

CPU1

Core1 Core2 Core3 Core4

GPU1

Cores

GPU Memory

Memory (DDR3) Memory (DDR3)

QPI

I/O Hub

QPI QPI

PCI Express Gen. 2

CPU1

Core1 Core2 Core3 Core4

GPU1

Cores

GPU Memory

Гетерогенные вычислительные узлы

99

NVIDIA Tesla K10 (2 GPU)

Процессорные ядра: 2 GPU NVIDIA GK104 (2 x 1536 ядер)

Архитектура: NVIDIA Kepler

RAM: 8GB GDDR5

PCI Express 3.0

Архитектура NVIDIA Kepler (GK110)

1010

15 SMX – streaming multiprocessor

(возможны конфигурации с 13 и 14 SMX)

6 контроллеров памяти (64-бит)

Интерфейс подключения к хосту PCI Express 3.0

Архитектура NVIDIA Kepler (GK110)

1111

Архитектура NVIDIA Kepler (GK110)

1212

SMX –streaming

multiprocessor

Архитектура SMX (GK110)

1313

192 ядра для выполнения операций с одинарной

точностью (single precision float, integer)

64 модуля двойной точности (double precision, FMA)

32 модуля специальных функций (SFU)

32 модуля чтения/записи (LD/ST)

4 планировщика потоков (warp schedulers)

Архитектура SMX (GK110)

1414

Warp scheduler (GK110)

1515

Планировщик организует потоки в группы по 32 (warps)

Потоки группы выполняются одновременно

Каждый такт потоки группы (warp) выполняют две независимые инструкции (допустимо совмещение инструкций double и float)

Warp scheduler (GK110)

1616

Организация памяти Kepler (GK110)

1717

Каждый SMX имеет 64KB памяти:

o 48KB shared + 16KB L1 cacheo 16KB shared + 48KB L1 cache

L2 Cache 1536KB –общий для всех SMX

Global Memory 8GB

Архитектура NVIDIA Kepler (GK110)

1818

FERMIGF100

FERMIGF104

KEPLERGK104

KEPLERGK110

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

NVIDIA Maxwell (2014)

1919

NVIDIA Maxwell = GPU Cores + ARM Core

Интегрированное ядро ARM (64 бит, проект Denver)

o возможность загрузки операционной системы на GPU

o поддержка унифицированной виртуальной памяти (device ←→ host)

Основные понятия CUDA

2020

Хост (host) – узел с CPU и его память

Устройство (device) – графический процессор

и его память

Ядро (kernel) – это фрагмент программы,

предназначенный для выполнения на GPU

Пользователь самостоятельно запускает с CPU

ядра на GPU

Перед выполнением ядра пользователь копирует данные

из памяти хоста в память GPU

После выполнения ядра пользователь копирует данные

из памяти GPU в память хоста

Основные понятия CUDA

2121

CPU (Host) GPU (Device)

void kernelA(){

/* Code */}

void kernelB(){

/* Code */}

/* Serial code */

/* Serial code */

/* Serial code */

kernelA()

kernelB()

Выполнение CUDA-программы

2222

CPU (Host)

CPU

Memory

PCI Express GPU (Device)

Memory

Копирование данных из памяти хоста в память GPU

Выполнение CUDA-программы

2323

CPU (Host)

CPU

Memory

PCI Express GPU (Device)

Memory

Копирование данных из памяти хоста в память GPU

Загрузка и выполнение ядра (kernel) в GPU

Выполнение CUDA-программы

2424

CPU (Host)

CPU

Memory

PCI Express GPU (Device)

Memory

Копирование данных из памяти хоста в память GPU

Загрузка и выполнение ядра (kernel) в GPU

Копирование данных из памяти GPU в память хоста

CUDA HelloWorld!

2525

#include <stdio.h>

__global__ void mykernel() {

/* Parallel GPU code (kernel) */ }

int main() {

mykernel<<<1, 1>>>();

return 0;}

CUDA HelloWorld!

2626

$ nvcc -c -o prog.o ./prog.cu

$ g++ ./prog.o –o prog -L/opt/cuda-5.5/lib64 \

-lcudart

Вычислительные ядра (kernels)

2727

Спецификатор __global__ сообщает компилятору,

что функция предназначена для выполнения на GPU

Компилятор nvcc разделяет исходный код –

ядра компилируются nvcc, а остальной код системным

компилятором (gcc, cl, …)

Тройные угловые скобки “<<< >>>” сообщают о вызове

ядра на GPU и количестве требуемых потоков

Вызов ядра (kernel) не блокирует выполнение потока

на CPU

Функция cudaThreadSynchronize() позволяет реализовать

ожидание завершения ядра

Вычислительные потоки (threads)

2828

Номер потока (thread index) – это трехкомпонентный

вектор (координаты потока)

Потоки логически сгруппированы в одномерный,

двухмерный или трёхмерный блок (thread block)

Количество потоков в блоке ограничено (в Kepler 1024)

Блоки распределяются по потоковым

мультипроцессорам SMX

Предопределенные переменные

o threadIdx.{x, y, z} – номер потока

o blockDim.{x, y, z} – размерность блока

Thread Block

Thread Thread Thread

Thread Thread Thread

Вычислительные потоки (threads)

2929

Блоки группируются одно- двух- и трехмерную

сетку (grid)

Блоки распределяются по потоковым мультипроцессорам

SMX (в Kepler 15 SMX)

Предопределенные переменные

o blockIdx.{x, y, z} – номер блока потока

o gridDim.{x, y, z} – размерность сетки

Вычислительные потоки (threads)

3030

Grid of thread blocks

Thread Block

Thread Thread Thread

Thread Thread Thread

Thread Block

Thread Thread Thread

Thread Thread Thread

Thread Block

Thread Thread Thread

Thread Thread Thread

Thread Block

Thread Thread Thread

Thread Thread Thread

Thread Block

Thread Thread Thread

Thread Thread Thread

Thread Block

Thread Thread Thread

Thread Thread Thread

gridDim.y

gridDim.x

blockDim.xblockDim.z

blockDim.y

CUDA Program

Выполнение CUDA-программы

3131

Block 0 Block 1 Block 2

Block 3 Block 4 Block 5

GPU 1

Block 0 Block 1

Block 2 Block 3

Block 4 Block 5

SMX 1 SMX 2

GPU 2

Block 0 Block 1 Block 2

Block 3 Block 4 Block 5

SMX 1 SMX 2 SMX 3

Пример: сложение векторов

3232

void vecadd(float *a, float *b, float *c, int n){

int i;

for (i = 0; i < n; i++) {c[i] = a[i] + b[i];

}}

Пример: сложение векторов

3333

int main() {

int i, n = 100;float *a, *b, *c;float *deva, *devb, *devc;

a = (float *)malloc(sizeof(float) * n);b = (float *)malloc(sizeof(float) * n);c = (float *)malloc(sizeof(float) * n);for (i = 0; i < n; i++) {

a[i] = 2.0;b[i] = 4.0;

}

Пример: сложение векторов

34

// Выделяем память на GPUcudaMalloc((void **)&deva, sizeof(float) * n);cudaMalloc((void **)&devb, sizeof(float) * n);cudaMalloc((void **)&devc, sizeof(float) * n);

// Копируем из памяти узла в память GPUcudaMemcpy(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;} 34

Пример: сложение векторов

35

__global__ void vecadd_gpu(float *a, float *b, float *c) {

// Каждый поток обрабатывает один элементint i = threadIdx.x;c[i] = a[i] + b[i];

}

35

Запускается один блок из n потоков (n <= 1024)

vecadd_gpu<<<1, n>>>(deva, devb, devc);

Каждый поток вычисляет один элемент массива c

Thread Block

Thread 0 Thread 1 Thread n - 1…

Пример: сложение векторов

3636

Сложение векторов с количеством элементов > 256

int threadsPerBlock = 256; /* Device specific */

int blocksPerGrid = (n + threadsPerBlock - 1) /

threadsPerBlock;

vecadd_gpu<<<blocksPerGrid, threadsPerBlock>>>(deva,

devb, devc, n);

Будет запущена группа блоков, в каждом блоке по фиксированному количеству потоков

Потоков может быть больше чем элементов в массиве

Пример: сложение векторов

3737

__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 ……

Thread Block 1

Thread 0 Thread 1 Thread ……

Thread Block 2

Thread 0 Thread 1 Thread ……

Thread Block 3

Thread 0 Thread 1 Thread ……

Двухмерный блок потоков

3838

dim3 threadsPerBlock(N, N);

matrix<<<1, threadsPerBlock>>>(A, B, C);

Двухмерный блок потоков (threadIdx.x, threadIdx.y)

Информация о GPU

3939

cudaSetDevice(0);

cudaDeviceProp deviceProp;

cudaGetDeviceProperties(&deviceProp, 0);

/*

* deviceProp.maxThreadsPerBlock

* deviceProp.warpSize

* devProp.totalGlobalMem

* ...

*/

NVIDIA GeForce GTS 250

4040

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

Иерархия памяти

41

NVidia GeForce GTS 250

Global memory: 1GB

Shared mem. per block: 16KB

Constant memory: 64KB

Registers per block: 8192

41

Data race

4242

__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;}

CUDA Atomics

4343

CUDA предоставляет API атомарных операций:

atomicAdd, atomicSub, atomicInc, atomicDec

atomicMax, atomicMin

atomicExch

atomicCAS

atomicAnd, atomicOr, atomicXor

atomicOP(a,b){

t1 = *a; // readt2 = t1 OP b; // modify*a = t2; // writereturn t;

}

CUDA Atomics

4444

__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;}

Умножение матриц

4545

C = A * B

Результирующая матрица C разбивается на подматрицы

размером 16x16 элементов

Подматрицы параллельно вычисляются блоками потоков

Каждый элемент подматрицы вычисляется отдельным

потоком (в блоке 16x16 = 256 потоков)

Количество потоков = количеству элементов в матрице C

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);

Умножение матриц

4646

Умножение матриц

4747

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);

Умножение матриц

4848

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);

Умножение матриц

4949

free(h_A);

free(h_B);

free(h_C);

cudaFree(d_A);

cudaFree(d_B);

cudaFree(d_C);

return 0;

} /* main */

Умножение матриц

5050

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 = wA * BLOCK_SIZE * by;int aEnd = aBegin + wA - 1;int aStep = BLOCK_SIZE;int bBegin = BLOCK_SIZE * bx;int bStep = BLOCK_SIZE * wB;float Csub = 0;

Умножение матриц

5151

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 memoryAs[ty][tx] = A[a + wA * ty + tx];Bs[ty][tx] = B[b + wB * ty + tx];

// Synchronize (wait for loading matrices)__syncthreads();

Умножение матриц

5252

// 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;

}

Reduction

5353

O(log2n)

Mark Harris. Optimizing Parallel Reduction in CUDA // http://www.cuvilib.com/Reduction.pdf

Reduction v1

5454

Условный оператор ifвнутри цикла приводит к сильному ветвлению

Можно перераспределить данные и операции по нитям

Reduction v2

5555

Количество ветвлений сокращено

Большое число конфликтов банков при обращении к разделяемой памяти

Dynamic parallelism (CUDA 5.0)

5656

__global__ ChildKernel(void* data)

{

// Operate on data

}

__global__ ParentKernel(void *data)

{

ChildKernel<<<16, 1>>>(data);

}

// In Host Code

ParentKernel<<<256, 64>>(data);

Dynamic parallelism (CUDA 5.0)

5757

__global__ RecursiveKernel(void* data)

{

if (continueRecursion == true)

RecursiveKernel<<<64, 16>>>(data);

}

Dynamic parallelism (CUDA 5.0)

5858

Литература

5959

CUDA by Example // http://developer.download.nvidia.com/books/cuda-by-

example/cuda-by-example-sample.pdf

Джейсон Сандерс, Эдвард Кэндрот. Технология CUDA в

примерах. ДМК Пресс, 2011 г.

А. В. Боресков, А. А. Харламов. Основы работы с

технологией CUDA. М.:ДМК, 2010 г.

http://www.nvidia.ru/object/cuda-parallel-computing-books-ru.html

Recommended