csce 5160 parallel processing · 2019. 4. 17. · 4/17/19 1 csce5160 april 17, 2019 1 csce 5160...

12
4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final Exam: Monday May 6, 2019: 1:30-3:30, F280 Complete Course Evaluations using the new SPOT (Student Perception Of Teaching) Review Sparse matrix representations Coordinate form Compressed Sparse Row (CSR method) Some sparse matrix algorithms Matrix * Vector product Different ways of distributing sparse matrix and vector Basic introduction to GPUs and Cuda Logical organization of threads Working group or Block of threads All threads execute the same instructions Several blocks in a grid Several grids 3D stricture of threads and grids CSCE5160 April 17, 2019 2 CSCE 5160 Parallel Processing Threads are grouped into blocks (possibly 16 or 32 or 64 threads per block) Blocks are grouped into Grids (can be 8, 16 or 32 blocks per grid)] Grids can be organized in 3D grids Each thread, block can be 1, 2 3 Dimensional

Upload: others

Post on 20-Feb-2021

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

1

CSCE5160 April 17, 2019 1

CSCE 5160 Parallel ProcessingProject Reports Due on the last day of classes: May 1, 2019

Final Exam: Monday May 6, 2019: 1:30-3:30, F280Complete Course Evaluations using the new SPOT (Student Perception Of Teaching)

ReviewSparse matrix representations

Coordinate formCompressed Sparse Row (CSR method)

Some sparse matrix algorithmsMatrix * Vector product

Different ways of distributing sparse matrix and vector

Basic introduction to GPUs and Cuda

Logical organization of threads Working group or Block of threads

All threads execute the same instructionsSeveral blocks in a gridSeveral grids

3D stricture of threads and grids

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

CSCE5160 April 17, 2019 2

CSCE 5160 Parallel Processing

Threads are grouped into blocks(possibly 16 or 32 or 64 threads per block)

Blocks are grouped into Grids(can be 8, 16 or 32 blocks per grid)]

Grids can be organized in 3D grids

Each thread, block can be 1, 2 3 Dimensional

Page 2: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

2

CSCE5160 April 17, 2019 3

CSCE 5160 Parallel ProcessingPhysical organization

A processor = Compute Unit = CU

Contains 32 threads, share instruction unit

Each thread operates on a different index

A streaming multiprocessor (SM) contains several CUs à equivalent to a grid

Threads in a CU share registers and local memory

Threads in a SM share local (shared) memory

Global/Device memory à used to send and receive data between CPU and CPU

Other types of memory

Constant cache: remains between kernel invocations

CSCE5160 April 17, 2019 4

CSCE 5160 Parallel Processing

When a kernel (the thing you define in.cu files) is called, the task is divided up into threads◦ Each thread handles a small portion of

the given task

The threads are divided into a Grid of Blocks◦ Both Grids and Blocks are 3 dimensional◦ e.g.dim3 dimBlock(8, 8, 8);dim3 dimGrid(100, 100, 1);

Kernel<<<dimGrid, dimBlock>>>(…);◦ However, we'll often only work with 1

dimensional grids and blocks◦ e.g. Kernel<<<block_count, block_size>>>(…);

Page 3: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

3

CSCE5160 April 17, 2019 5

CSCE 5160 Parallel Processing

Maximum number of threads per block is usually restricted (512 or 1024 max) depending on the machine

Maximum number of blocks per grid isusually 65535◦ If you go over either of these numbers

your GPU will just give up or outputgarbage data

◦ Much of GPU programming is dealingwith this kind of hardware limitations!

◦ This limitation also means that yourKernel must compensate for the fact thatyou may not have enough threads toindividually allocate to your data points

CSCE5160 April 17, 2019 6

CSCE 5160 Parallel ProcessingWe need to define number of blocks per grid, number of threads per block.

Simple example: Add elements of two vectors

for (i =0; i<N; i++) {C[i] = A[i]+B[i];}

// Kernel definition__global__ void VecAdd(float* A, float* B, float* C){ int i = threadIdx.x;C[i] = A[i] + B[i];}

Each thread performs one addition (one iteration)The index used by the thread is equal to the thread ID. (Id can be obtained using threadIdx

or thradIdy (if a block is defined with 2 dimensions)

The main is invoking N threads, one block per grid and N threads per block

Compile using nvcc filename.cu –o outputfile

Page 4: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

4

CSCE5160 April 17, 2019 7

CSCE 5160 Parallel ProcessingContinuing with vector add example

for (i=0; i<N; i++) C[i] = A[i]+B[i];

int main(){

int N = ...;size_t size = N * sizeof(float);// Allocate input vectors h_A and h_B in host memoryfloat* h_A = (float*)malloc(size);float* h_B = (float*)malloc(size);// Initialize input vectors

. ..

// Allocate vectors in device memoryfloat* d_A;cudaMalloc(&d_A, size);float* d_B;cudaMalloc(&d_B, size);float* d_C;cudaMalloc(&d_C, size);

// Copy vectors from host memory to device memorycudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);

CSCE5160 April 17, 2019 8

CSCE 5160 Parallel ProcessingContinuing with Vector Add example (still part of main running on CPU(

//invoke kernelint threadsPerBlock = 256;int blocksPerGrid = (N + threadsPerBlock – 1) / threadsPerBlock;

VecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);// Copy result from device memory to host memory// h_C contains the result in host memory

cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);// Free device memory

cudaFree(d_A);cudaFree(d_B);cudaFree(d_C);

// Free host memory...} Sample programs on arch are at /usr/local/cuda-10.1/samples/

Test at least the vectorAdd application

Page 5: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

5

CSCE5160 April 17, 2019 9

CSCE 5160 Parallel ProcessingConsider addition elements of two matrices

for (i =0; i<N; i++) {for (j=0; j<N; j++) C[i][j] = A[i][j]+B[i][j];}

int main(){...// Kernel invocation with one block of N * N * 1 threadsint numBlocks = 1;dim3 threadsPerBlock(N, N);MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);...}

// Kernel definition__global__ void MatAdd(float A[N][N], float B[N][N],float C[N][N]){int i = threadIdx.x;int j = threadIdx.y;C[i][j] = A[i][j] + B[i][j];}

Here also we are using one block but the number of threads per block are N*N à 2D block

In this case, each thread will be identified by x and y IDs

CSCE5160 April 17, 2019 10

CSCE 5160 Parallel ProcessingWe can extend this to matrix multiplication

int main(){...// Kernel invocation with one block of N * N * 1 threadsint numBlocks = 1;dim3 threadsPerBlock(N, N);MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C, N);...}

// Kernel definition__global__ void MatAdd(float A[N][N], float B[N][N],float C[N][N], int size){int i = threadIdx.x;int j = threadIdx.y;C[i][j] = 0.0for (int k =0; k<size; k++

C[i][j] = C[i][j] +A[i][k] B[k][j];}

Page 6: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

6

CSCE5160 April 17, 2019 11

CSCE 5160 Parallel ProcessingSame example but different thread configuration (can be extended to matrix multiplication)

// Kernel definition__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N])

{int i = blockIdx.x * blockDim.x + threadIdx.x;int j = blockIdx.y * blockDim.y + threadIdx.y;if (i < N && j < N) C[i][j] = A[i][j] + B[i][j];}

int main(){...// Kernel invocationdim3 threadsPerBlock(16, 16);dim3 numBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);...}

Number of threads per block is limited (512 or 1024)Here we limit to 256 but use more blocks

CSCE5160 April 17, 2019 12

CSCE 5160 Parallel ProcessingAllocating memory for 2D and 3D arrays. Cuda recommends that we use special Malloc functions that align allocation to meet memory accesses in terms of accessing on 4, 8, 16 byte boundaries

// Host codeint width = 64, height = 64;float* devPtr;size_t pitch;cudaMallocPitch(&devPtr, &pitch, width * sizeof(float), height);MyKernel<<<100, 512>>>(devPtr, pitch, width, height);

// Device code__global__ void MyKernel(float* devPtr,size_t pitch, int width, int height){

for (int r = 0; r < height; ++r) {float* row = (float*)((char*)devPtr + r * pitch);for (int c = 0; c < width; ++c) {

float element = row[c]; }}

}

Pitch = stride =how many elements per column (second dimension)

We are accessing elements of a rowFirst get starting address of the rowThen access each element of the row

Page 7: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

7

CSCE5160 April 17, 2019 13

CSCE 5160 Parallel ProcessingThread configuration should take advantage of how GPU memory is organized

Threads in a block share memory (and registers)

Blocks share local memory

All blocks have access to global memory

We need to send the data from CPU memory to GPU memory

GPU (Device) memory is accessible to both CPU and GPU

So, we need to first allocate space in Device memory for kernel data

Then move the data from CPU memory to GPU/Device memory

And when kernel completes, move the data from Device memory to CPU memory

CSCE5160 April 17, 2019 14

CSCE 5160 Parallel ProcessingConsider the following variation of matrix multiplication, where threads in a block move their A and B matrix portions to shared memory.

Note if a thread is computing C[i][j], it needs ith row of A and jth column of B.

So, we will group all the row of A needed by threads in a block and columns of B and move them to shared memory

Each block is responsible for computing a portion of C, a submatrix of C.

The rows of A and columns of B are moved to shared memory.

Chapter 3. Programming Interface

28 CUDA C Programming Guide Version 4.2

Figure 3-2. Matrix Multiplication with Shared Memory

3.2.4 Page-Locked Host Memory The runtime provides functions to allow the use of page-locked (also known as pinned) host memory (as opposed to regular pageable host memory allocated by malloc()):

� cudaHostAlloc() and cudaFreeHost() allocate and free page-locked host memory;

� cudaHostRegister() page-locks a range of memory allocated by malloc() (see reference manual for limitations).

Using page-locked host memory has several benefits:

� Copies between page-locked host memory and device memory can be performed concurrently with kernel execution for some devices as mentioned in Section 3.2.5;

� On some devices, page-locked host memory can be mapped into the address space of the device, eliminating the need to copy it to or from device memory as detailed in Section 3.2.4.3;

A

B

C

Csub

BLOCK_SIZE

B.width A.width

BLOCK_SIZE BLOCK_SIZE

BLO

CK

_SIZ

E B

LOC

K_S

IZE

BLO

CK

_SIZ

E

bloc

kRow

row

0

BLOCK_SIZE-1

BLO

CK

_SIZ

E-1

0 col

blockCol

A.h

eigh

t B

.hei

ght

Page 8: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

8

15

CSCE 5160 Parallel Processing

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Chapter 3. Programming Interface

CUDA C Programming Guide Version 4.2 27

// Each thread computes one element of Csub // by accumulating results into Cvalue float Cvalue = 0; // Thread row and column within Csub int row = threadIdx.y; int col = threadIdx.x; // Loop over all the sub-matrices of A and B that are // required to compute Csub // Multiply each pair of sub-matrices together // and accumulate the results for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) { // Get sub-matrix Asub of A Matrix Asub = GetSubMatrix(A, blockRow, m); // Get sub-matrix Bsub of B Matrix Bsub = GetSubMatrix(B, m, blockCol); // Shared memory used to store Asub and Bsub respectively __shared__ float As[BLOCK_SIZE][BLOCK_SIZE]; __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE]; // Load Asub and Bsub from device memory to shared memory // Each thread loads one element of each sub-matrix As[row][col] = GetElement(Asub, row, col); Bs[row][col] = GetElement(Bsub, row, col); // Synchronize to make sure the sub-matrices are loaded // before starting the computation __syncthreads(); // Multiply Asub and Bsub together for (int e = 0; e < BLOCK_SIZE; ++e) Cvalue += As[row][e] * Bs[e][col]; // Synchronize to make sure that the preceding // computation is done before loading two new // sub-matrices of A and B in the next iteration __syncthreads(); } // Write Csub to device memory // Each thread writes one element SetElement(Csub, row, col, Cvalue); }

GetSubMatrix are functions that copies the appropriate rows of A or columns of B

Shared memory declaration here allows threads to share the memory

These functions copy specific element of A and Bà Each thread copies one element of A and one

element of Bà So, all the necessary row and column elements

Write values to result matrix C

16

CSCE 5160 Parallel ProcessingThe rest of the code

Chapter 3. Programming Interface

CUDA C Programming Guide Version 4.2 25

the block is responsible for computing one element of Csub. As illustrated in Figure 3-2, Csub is equal to the product of two rectangular matrices: the sub-matrix of A of dimension (A.width, block_size) that has the same row indices as Csub, and the sub-matrix of B of dimension (block_size, A.width) that has the same column indices as Csub. In order to fit into the device’s resources, these two rectangular matrices are divided into as many square matrices of dimension block_size as necessary and Csub is computed as the sum of the products of these square matrices. Each of these products is performed by first loading the two corresponding square matrices from global memory to shared memory with one thread loading one element of each matrix, and then by having each thread compute one element of the product. Each thread accumulates the result of each of these products into a register and once done writes the result to global memory.

By blocking the computation this way, we take advantage of fast shared memory and save a lot of global memory bandwidth since A is only read (B.width / block_size) times from global memory and B is read (A.height / block_size) times.

The Matrix type from the previous code sample is augmented with a stride field, so that sub-matrices can be efficiently represented with the same type. __device__ functions (see Section B.1.1) are used to get and set elements and build any sub-matrix from a matrix. // Matrices are stored in row-major order: // M(row, col) = *(M.elements + row * M.stride + col) typedef struct { int width; int height; int stride; float* elements; } Matrix; // Get a matrix element __device__ float GetElement(const Matrix A, int row, int col) { return A.elements[row * A.stride + col]; } // Set a matrix element __device__ void SetElement(Matrix A, int row, int col, float value) { A.elements[row * A.stride + col] = value; } // Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is // located col sub-matrices to the right and row sub-matrices down // from the upper-left corner of A __device__ Matrix GetSubMatrix(Matrix A, int row, int col) { Matrix Asub; Asub.width = BLOCK_SIZE; Asub.height = BLOCK_SIZE; Asub.stride = A.stride; Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row + BLOCK_SIZE * col]; return Asub;

This is the routine to get an element of a matrix we used in the previous slide

This stores a value in a matrix

This identifies the rows of A neededAnd declares a submatrix of that size

Page 9: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

9

17

CSCE 5160 Parallel ProcessingContinuing

Chapter 3. Programming Interface

26 CUDA C Programming Guide Version 4.2

} // Thread block size #define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel __global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code // Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C) { // Load A and B to device memory Matrix d_A; d_A.width = d_A.stride = A.width; d_A.height = A.height; size_t size = A.width * A.height * sizeof(float); cudaMalloc(&d_A.elements, size); cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B; d_B.width = d_B.stride = B.width; d_B.height = B.height; size = B.width * B.height * sizeof(float); cudaMalloc(&d_B.elements, size); cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory Matrix d_C; d_C.width = d_C.stride = C.width; d_C.height = C.height; size = C.width * C.height * sizeof(float); cudaMalloc(&d_C.elements, size); // Invoke kernel dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE); dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y); MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory cudaFree(d_A.elements); cudaFree(d_B.elements); cudaFree(d_C.elements); } // Matrix multiplication kernel called by MatMul() __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockRow = blockIdx.y; int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

Declaring data as “const” allows compiler move the data into “constant cache” on GPUTypically, they are read onlyYou can copy the data and then modify

CSCE5160 April 17, 2019 18

CSCE 5160 Parallel Processing_synchthreads() is a barrier – all the threads of a block will have to complete before moving forward

GPU’s don’t have locks but they do have atomic instructions

atomicAdd()int atomicAdd(int* address, int val);

unsigned int atomicAdd(unsigned int* address, unsigned int val);float atomicAdd(float* address, float val);

These functions proceed sequentially. For example, each thread computes one multiplication

value =A[i][j]*B[i][j]; We can do

atomicAdd(&C[i][j], value)

All threads add their values, but the addition proceeds sequentially

Cuda also has atomic subtract, multiply, min, max, etc

Page 10: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

10

CSCE5160 April 17, 2019 19

CSCE 5160 Parallel ProcessingThere are many other mechanisms and debugging tools provided by NVIDIAto optimize programs

Note since GPUs rely on SIMD parallelism à all threads execute the same instruction,Algorithms with variance for thread code do not perform wellThread Divergence.

Consider for example, Gaussian Elimination

Remember: Divide and Elimination StepsNot all threads execute these steps in each iteration

CSCE5160 April 17, 2019 20

CSCE 5160 Parallel ProcessingDiscrete Optimizations (or search solutions space) – Chapter 11

In general an optimization attempts to satisfy some constraints while minimizing or maximizing some objectives. We can describe the problem as

Maximize (or minimize) (cT )* x subject to A *x >= b

Here A is a n*n matrix; x is n*1 vector of unknowns; b and c are n*1 vectors.

Consider the example on page 471. We are given the following problem

Minimize 2x1 +x2 - x3 - 2x4 subject to

5x1 + 2x2 + x3 +2x4 >= 8x1 - x2 - x3 +2x4 >= 2

3x1 + x2 + x3 +3x4 >= 5

We need to try to first solve for the unknowns; but we have only 3 equations with 4 unknowns.We need to try to find all possible values that meet the constraints; and then find one set of values that minimizes the cost function

Page 11: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

11

CSCE5160 April 17, 2019 21

CSCE 5160 Parallel ProcessingWe can describe this problem as a graph algorithm also. For now let us assume that each xi can only take values of 0 and 1.

Under this assumption we refer to problems of this type as “satisfiability”

Let us generate a tree with different values assigned to the various xi variables

Initial state

x1 = 0 x1 = 1

Not feasible since the first constraint will not be satisfied

5x1 + 2x2 + x3 +2x4 >= 8

Initial state

x1 = 0 x1 = 1

x2 = 0 x2 = 1

x3 = 0 x3 = 1

Not feasible first constraint will not be met5x1 + 2x2 + x3 +2x4 >= 8

CSCE5160 April 17, 2019 22

CSCE 5160 Parallel Processing

Continuing:

Initial state

x1 = 0 x1 = 1

x2 = 0 x2 = 1

x3 = 0

x3 = 1

x3 = 0 x3 = 1

cost = 0 cost = 2

Two feasible solutions: x1 =1; x2 =0; x3=1; x4= 1 but the constraint equation becomes 0x1 =1; x2 =1; x3=0; x4= 1; the constraint equation equals 2

Constraint Equation: 2x1 +x2 - x3 - 2x4

Page 12: CSCE 5160 Parallel Processing · 2019. 4. 17. · 4/17/19 1 CSCE5160 April 17, 2019 1 CSCE 5160 Parallel Processing Project Reports Due on the last day of classes: May 1, 2019 Final

4/17/19

12

CSCE5160 April 17, 2019 23

CSCE 5160 Parallel ProcessingConsider another example. See page 470 which describes the 8 puzzle problem

CSCE5160 April 17, 2019 24

CSCE 5160 Parallel Processing

The goal is to start with the initial configuration and by moving the blank tile, rearrange the remaining tile in numerical order.

At each position, we have up to 4 possible new configurations by moving the blank tileleft, right, up or down.

One of these 4 moves can be discarded since it results in the same configuration as the one we started with.

The cost function for such problems is often the number of moves needed.We need to have a way of estimating this cost

Once again we can think of constructing a graph to describe the possible moves that lead us to a feasible solutions.

In general, we are trying to explore the space of all feasible solutions, evaluate each solutionfor its cost (or goal).