NVIDIA 사의 공식 튜토리얼 Fundamentals of Accelerated Computing C/C++
을 학습하며 정리한 내용.
Accelerating Applications with CUDA C/C++
GPU-accelerated Vs. CPU-only Applications
Writing Application Code for the GPU
1 |
|
__global__ void GPUFunction()
__global__
이라는 키워드가 GPU에서 돌아간다는 사실을 명시해준다.
-
GPUFunction<<1,1>>();
- GPU에서 작동하는 이러한 함수를 kernel이라 부르며, thread hierarchy를 명시해준다고 한다.
- 인자 중 앞의
1
은 실행될 쓰레드 그룹의 개수를 명시하며,block
이라고 부른다. - 인자 중 뒤의
1
은 각block
내에 몇 개의 쓰레드가 실행될 것인지를 명시한다.
좀 더 자세한 내용은 뒤에서..
-
cudaDeviceSynchronize();
- 이후 계산된 값을 CPU와 synchronize하여 작동하게 하기 위해서는, 이 함수를 사용하여야 한다.
Compiling and Running Accelerated CUDA Code
.c
파일을 gcc
로 컴파일하는 것처럼, .cu
파일은 nvcc
라는 NVIDIA CUDA Compiler로 컴파일한다. 다음과 같이 쓸 수 있다.
nvcc -arch=sm_70 -o out some-CUDA.cu -run
옵션은 다음과 같다.
-arch
: 컴파일되는 환경의 GPU 아키텍쳐를 명시해준다.sm_70
의 경우 Volta 아키텍쳐를 명시해준다.-o
: 아웃풋 파일의 이름을 명시해준다.-run
: 편의를 위한 옵션. 이 옵션을 쓰면 컴파일한 바이너리 파일을 실행해준다.
CUDA Thread Hierarchy
CUDA Thread Hierarchy에서의 용어 좀 더 자세히 설명
kernel
: GPU function을 부르는 용어.kernel
은execution configuration
에 따라 실행된다.thread
: GPU 작업의 기본 단위. 여러thread
가 병렬적으로 작동한다.block
:thread
의 모임을block
이라 한다.grid
: 주어진kernel
의execution configuration
에서block
들의 모임, 그러니까 전체를grid
라 부른다.
CUDA-Provided Thread Hierarchy Variables
CUDA Thread Hierarchy에서는 미리 정해진 변수를 통해 각 block
과 thread
에 접근할 수 있다.
gridDim.x
:grid
내에 있는block
의 개수. performWork«<2,4»>()와 같은kernel
의 경우 2가 된다.blockIdx.x
:grid
내에 있는block
들 중 해당block
의 위치 인덱스. performWork«<2,4»>()와 같은kernel
을 실행한다면0
,1
이 될 수 있다.blockDim.x
:block
내에 있는thread
의 개수. performWork«<2,4»>()와 같은kernel
의 경우4
가 된다. 한grid
내에 있는 모든block
은 같은 수의thread
를 가진다.threadIdx.x
:block
내에 있는thread
중 해당thread
의 위치 인덱스. performWork«<2,4»>()와 같은kernel
의 경우0
,1
,2
,3
중 하나가 된다.
Accelerating For Loops
반복문을 가속화하는 법.
Exercise: Accelerating a For Loop with a Single Block of Threads
1 |
|
이런 반복문을 어떻게 GPU로 가속할 수 있을까? 병렬화를 하기 위해서는 2가지 단계를 꼭 거쳐야 한다:
kernel
은 해당 반복문에서 딱 한 번의 반복 작업만 하도록 설계되어야 한다.kernel
이 다른kernel
에 대해서 알지 못하기 때문에,execution configuration
이 해당 반복문에서 반복되는 작업의 수에 맞춰 선언되어야 한다.
우리가 위에서 배운 Thread Hierarchy Variable을 활용하면 이를 달성할 수 있다.
Solution)
1 |
|
Coordinating Parallel Threads
각 block
에 존재할 수 있는 thread
의 개수는 최대 1024개로 한계가 있다. 따라서 병렬처리의 효과를 더 크게 누리기 위해서는 여러 block
들 간의 coordinate를 잘 해야 한다.
GPU thread
에 data를 할당하기 위해, 각 thread
의 인덱스를 활용한 데이터 분배 접근 전략을 활용한다.
각 block
의 사이즈는 blockDim.x
로 알 수 있고, 인덱스는 blockIdx.x
로 접근할 수 있다. 또 각 thread
의 인덱스는 threadIdx.x
로 접근할 수 있다.
따라서 threadIdx.x
+ blockIdx.x
* blockDim.x
라는 공식을 활용하여 데이터를 thread
에 매핑할 수 있다.
Exercise: Accelerating a For Loop with Multiple Blocks of Threads
아까 위에서 병렬화 한 반복문을, 이번에는 최소 2개 이상의 block
을 활용하여 병렬화시켜보자.
Allocating Memory to be accessed on the GPU and the CPU
CPU-only application에서는 C가 malloc
과 free
를 사용해 메모리를 할당하고 해제하지만, GPU 가속을 할 때는 대신 cudaMallocManaged
와 cudaFree
를 사용한다. 사용 예시는 다음과 같으니 비교해보자.
1 |
|
Exercise: Array Manipulation on both the Host and Device
1 |
|
위 코드를 배열 포인터 a
가 CPU와 GPU 코드에서 모두 쓰일 수 있게, 또 a
를 정확히 메모리 해제해야 한다는 점에 유의해서 고쳐보자.
Solution)
1 |
|
Grid Size Work Amount Mismatch
우리가 사용하려는 데이터가 grid
사이즈에 딱 맞으면 상관 없지만, 만약 그것보다 부족한 경우 사이즈가 맞지 않는다는 문제가 발생한다. e.g.) grid
내에 thread
개수가 8개인데 사용할 데이터는 5개밖에 없으면 threadIdx.x
+ blockIdx.x
* blockDim.x
공식으로 할당할 때 5,6,7번은 문제가 생긴다.
1 |
|
그래서 위와 같이 some_kernel
함수의 if
문처럼 인덱스가 데이터의 크기보다 작을 때만 특정 기능을 실행하도록 설정해주면 된다.
Exercise: Accelerating a For Loop with a Mismatched Execution Configuration
1 |
|
이 코드는 1000개의 integer를 cudaMallocManaged
를 활용해 메모리 할당하고 있고, thread_per_blocks
라는 이름의 변수로 block
당 최대 thread
개수를 정의하고 있다. 이에 따라 number_of_blocks
변수에 필요한 block
의 개수를 구해 할당해주고, initializeElementsTo
함수에다 데이터 수보다 인덱스가 넘치는 경우 예외를 처리해주는 내용의 코드를 추가해주자.
Solution)
1 |
|
Grid-Stride Loops
이번에는 데이터의 개수가 grid
내에 존재하는 thread
의 개수보다 많은 경우이다. 이 경우 남은 데이터들을 처리해줄 수 없기 때문에 grid-stride loops
라는 방법을 사용한다.
이 방법은 kernel
내에서 반복문을 사용하는 것인데, 매 반복마다 grid
내의 thread
의 개수인 gridDim.x
* blockDim.x
만큼 인덱스에 더해주면서 모든 데이터를 처리하는 방법이다.
Exercise: Use a Grid-Stride Loop to Manipulate an Array Larger than the Grid
1 |
|
grid
내의 thread
개수는 256*32 = 8192개지만 전체 데이터는 10000개로 이를 초과한다. doubleElements
커널을 grid-stride loops
방식으로 수정하여 모든 데이터를 연산할 수 있게 해보자.
Solution)
1 |
|
Error Handling
어느 어플리케이션에서나 Error Handling
은 중요하다. 대부분의 CUDA 함수는 cudaError_t
라는 자료형으로 결과값을 반환하는데, 이걸 확인하면 에러가 발생했는지 아닌지를 확인할 수 있다. cudaMallocManaged
함수의 경우 다음과 같다.
1 |
|
그런데 kernel
함수는 void 함수로 짜서 cudaError_t
를 반환하지 않는데, 이 경우 CUDA는 cudaGetLastError
함수를 제공한다.
1 |
|
만약 asynchronous하게 발생하는 에러를 잡고 싶다면(예를 들면 asynchronous하게 작동하는 kernel
) synchronize하게 하는 CUDA runtime API call (예를 들면 cudaDeviceSynchronize
)을 잘 체크해서 잡아내야 한다.
Exercise: Add Error Handling
1 |
|
Solution)
1 |
|
CUDA Error Handling Function
다음은 에러를 확인하기 편하게 해주는 매크로 함수다. 다른 exercise를 풀 때 편하게 사용하면 된다.
1 |
|
요약
Accelerating Applications with CUDA C/C++에서 배운 내용 요약.
- Write, compile, and run C/C++ programs that both call CPU functions and launch GPU kernels.
- Control parallel thread hierarchy using execution configuration.
- Refactor serial loops to execute their iterations in parallel on a GPU.
- Allocate and free memory available to both CPUs and GPUs.
- Handle errors generated by CUDA code.
Final Exercise: Accelerate Vector Addition Application
1 |
|
위 코드는 CPU로만 작동하는 벡터 더하기 어플리케이션이다. 여기 있는 addVectorsInto
함수를 CUDA kernel
로 만들어 GPU 병렬 연산을 할 수 있게 만들어보자. 다음을 유의해서 코드를 짜보자.
addVectorsInto
를 CUDAkernel
로 만들기addVectorsInto
가 CUDAkernel
로 작동하는 적절한 execution configuration을 찾고 실행하기- 메모리 할당과 해제를 적절히 해서
a
,b
,result
벡터가 CPU/GPU에서 모두 접근 가능하도록 하기 addVectorsInto
를 리팩토링하자: it will be launched inside of a single thread, and only needs to do one thread’s worth of work on the input vectors. Be certain the thread will never try to access elements outside the range of the input vectors, and take care to note whether or not the thread needs to do work on more than one element of the input vectors.- CUDA 코드가 잘못될 수 있는 부분에 적절히 error handling을 하자.
Solution)
1 |
|
Advanced Content
The following exercises provide additional challenge for those with time and interest. They require the use of more advanced techniques, and provide less scaffolding. They are difficult and excellent for your development.
Grids and Blocks of 2 and 3 Dimensions
Grids and blocks can be defined to have up to 3 dimensions. Defining them with multiple dimensions does not impact their performance in any way, but can be very helpful when dealing with data that has multiple dimensions, for example, 2d matrices. To define either grids or blocks with two or 3 dimensions, use CUDA’s dim3
type as such:
1 |
|
Given the example just above, the variables gridDim.x
, gridDim.y
, blockDim.x
, and blockDim.y
inside of someKernel
, would all be equal to 16
.
Exercise: Accelerate 2D Matrix Multiply Application
The file 01-matrix-multiply-2d.cu
contains a host function matrixMulCPU
which is fully functional. Your task is to build out the matrixMulGPU
CUDA kernel. The source code will execute the matrix multiplication with both functions, and compare their answers to verify the correctness of the CUDA kernel you will be writing. Use the following guidelines to support your work and refer to the solution if you get stuck:
- You will need to create an execution configuration whose arguments are both
dim3
values with thex
andy
dimensions set to greater than1
. - Inside the body of the kernel, you will need to establish the running thread’s unique index within the grid per usual, but you should establish two indices for the thread: one for the x axis of the grid, and one for the y axis of the grid.
1 |
|
Solution)
1 |
|
Exercise: Accelerate A Thermal Conductivity Application
In the following exercise, you will be accelerating an application that simulates the thermal conduction of silver in 2 dimensional space.
Convert the step_kernel_mod
function inside 01-heat-conduction.cu
to execute on the GPU, and modify the main
function to properly allocate data for use on CPU and GPU. The step_kernel_ref
function executes on the CPU and is used for error checking. Because this code involves floating point calculations, different processors, or even simply reording operations on the same processor, can result in slightly different results. For this reason the error checking code uses an error threshold, instead of looking for an exact match. Refer to the solution if you get stuck.
1 |
|
Solution)
1 |
|