Shared Memory

Shared Memory를 사용하기 위해서는

__shared__  (DataType) Name(Size)

위와 같이 선언을 해주고 메모리 공간을 사용해주면 됩니다.

※ 메모리에 접근하는 횟수가 많은 데이터일수록 Shared Memory에 복사해주는것이 좋습니다.

 

__syncthreads(): sA와 sB에 데이터들을 옮겨주는 작업이 동시에 끝난다고 장담할 수 없습니다.

                      그래서 __syncthreads()를 이용하여 모든 작업이 끝나고 수행될 수 있게 선언해줍니다.

 

 

더보기

연습용으로 작성한 것이라 작동이 안되는 코드입니다.

 

 


Shared Memory 실제로 사용하기!

 

 

 

 

 

 

출처:

https://www.youtube.com/watch?v=6X1tMw1F0iw&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=23

'Cuda' 카테고리의 다른 글

13_Shared_Memory_Maximizing_CUDA  (0) 2021.09.07
12_Global_Memory_Maximizing_CUDA  (0) 2021.09.07
10_Using Shared Memory_Example_Quiz_1_CUDA  (0) 2021.09.01
09_Memory_Active_Warp_CUDA  (0) 2021.09.01
08_Memory_Architecture_CUDA  (0) 2021.08.30

Shared Memory를 사용하기 위한 방법은 두가지 있습니다.

  1. User-managed cache ★ 중요
    • 유저가 관리하는 cache
    • 읽는 데이터 Block을 cache에 가져다 놓음으로써 Memory Access 시간을 줄입니다.
  2. L1 cache
    • 하드웨어에 맡기는 경우.

 

 


User-Managed Cache

※ Device Memory = Global Memory

 

Shared Memory에 자주 사용하는 Data를 올려놓음으로써 Global Memory의 Access를 줄여주는것입니다.

 


 

Example

행렬 A(row x k)와 B(k x col) 의 곱을 예로 들어봅니다.

여기서 Thread Layout을 잡을 때는 출력을 기준으로 합니다.

즉, 행렬 C를 기준으로 계산을 해줍니다.

C는 32 by 32 행렬입니다.

32 x 32 = 1024이므로 하나의 Block으로 처리가 가능합니다.

 

row = threadIdx.y → y축으로 행이 몇개 이동했는지

col = threadIdx.x  → x축으로 열이 몇개 이동했는지

 

행렬 A안에 하나의 행에 있는 인자값들의 수는 k

행렬 B안에 하나의 열에 있는 인자값들의 수는 k

즉, k로 for문을 만들면 됩니다.

 

 

HOST(CPU)에서의 로직, Kernel(GPU)에서의 로직을 비교했습니다.

여기서 한줄 한줄 자세히 살펴보겠습니다.


행렬 C의 계산될 row, col 위치 구하기! (9~10번째 line)

row는 행이고 행렬 C의 왼쪽 맨위를 기준으로 세로방향 즉, y축으로 움직입니다.

그러므로 row = threadIdx.y 가 되고

col은 열이고 행렬 C의 왼쪽 맨위를 기준으로 가로방향 즉, x축으로 움직입니다.

그러므로 col = threadIdx.x 가 됩니다.

 

 

 

행렬 C의 index 구하기! (11번째 line)

하나의 행의 사이즈는 blockDim.x이고,

우리가 원하는 행의 위치는 threadIdx.y 입니다.

즉, blockDim.x * threadIdx.y 행에 우리가 원하는 thread가 있습니다.

그리고 여기서 원하는 열의 위치는 threadIdx.x 이므로

blockDim.x * threadIdx.y + threadIdx.x 가 최종적으로 우리가 원하는 thread의 위치입니다.

※ row = threadIdx.y, col = threadIdx.x

 

 

 

13번째 line

여기는 행렬 C의 index 부분을 초기화 해주는 부분입니다.

 

 

 

14번째 line

행렬 C는 k by k 사이즈의 행렬입니다.

그래서 k를 기준으로 for문을 실행해줍니다.

 

 

 

행렬 C의 index값 구하기! (15번째 line)

일단 행렬 곱을 알고 있다는 전제하에 시작하겠습니다.

행렬 곱을 구하기 위해서는

행렬 A의 k번째 index 값들과

행렬 B의 k번째 index 값들의 합입니다.

여기서 K_SIZE와 COL_SIZE는 사용자가 주는 값입니다.

 

행렬 A

일단 행렬 A의 k번째 index 값을 얻기 위한 식을 먼저 세워줍니다.

 

 

행렬 B

다음으로 행렬 B의 k번째 index를 구해줍니다.

 

여기서 k값이 0~K_SIZE로 변화함에 따라 원하는 행렬 C의 index값을 얻을 수 있습니다.

 

 

행렬 곱을 해보면서 가장 중요한 점은

최종 OutPut으로 나오는 행렬 C를 기준으로

k 와 threadIdx.x 와 threadIdx.y 를 나타내주는 겁니다.

 


 

Shared Memory 사용하기!

행렬 A,B,C는 Global Memory에 있는 상태입니다.

 

행렬 C의 원하는 연산을 하기 위해서는

 

행렬 A에 접근하는 횟수: row(계산될 A의 행의 개수) * col(계산될 B의 열의 개수) * k

행렬 B에 접근하는 횟수: col(계산될 B의 열의 개수) * row(계산될 A의 행의 개수) * k

최종적으로 row * col * k + col * row * k 가 됩니다.

 

여기서 행렬 C의 원하는 index 하나를 연산하기 위해서는

행렬 A,B에 각각 k번씩 반복적으로 접근을 하게 됩니다.

 

k는 임의의 수인 32로

행렬 A의 col 사이즈는 128로

행렬 B의 row 사이즈는 128로

데이터의 크기는 4byte로 정의한 후 진행하겠습니다.

 

그렇다면

행렬 A의 크기는 4byte * (32 * 128)

행렬 B의 크기는 4byte * (128 * 32)

총 32KB 입니다.

Shared Memory는 보통 64KB이므로 Shared Memory에 넣을 수 있습니다.

 

'Cuda' 카테고리의 다른 글

12_Global_Memory_Maximizing_CUDA  (0) 2021.09.07
11_Using Shared Memory_Example_Quiz_2_CUDA  (0) 2021.09.02
09_Memory_Active_Warp_CUDA  (0) 2021.09.01
08_Memory_Architecture_CUDA  (0) 2021.08.30
07_GPU_HardWare_CUDA  (0) 2021.08.30

적절한 곳에 적당한 메모리를 사용하자!

ex) block-local data는 shared memory

ex) read only -> constant memory

 

Design your kernel and block

memory 사용량을 고려해서 병렬처리의 효율을 높이자!

 

 

Active Warp

 

Warp는 32개의 Thread를 묶어둔것이고 하나의 Instruction에 의해 동작합니다.

Warp에 있는 모든 Thread들이 Register Memory 공간을 할당 받은 상태를 Active Warp라고 합니다.

 

ex) Block 안에 Register 용량이 64KB이고 1024개의 Thread들이 있습니다.

하나의 Thread가 필요한 memory는 128이라면

64KB(64 * 1024) / 128 = 512

즉, 1024개중 512개의 Thread만 사용하고 나머지 512개의 Thread는 놀고 있다는 뜻입니다.

여기서 사용되고 있는 512개 즉, 16개의 Warp를 Active Warp라고 합니다.

 

 


Active Block

자신이 필요한 Shared Memory 공간을 다 할당받은 Block.

 

 


Occupancy

실제로 수행할 수 있는 Active Warp 수의 비율 즉, Occupancy가 높으면 Maximize parallelism

※ Active Warps / Maximum Warps

 

  • Thread당 Register 수
  • Block 당 Thread 수
  • Block이 사용하는 Shared Memory 수

 

Occupancy Calculator

Occupancy를 계산하기 위한 툴!

cuda 설치된 위치 → NVIDIA GPU Computing Toolkit → tools →CUDA_Occupancy_Calculator.xls

 

여기서 주황색 영역의 맨 오른쪽 3개를 조정하며 최적의 값들을 찾으면 됩니다.

 

 


Register 수 지정하기

Default 값은 0이며, 자동으로 잡아준다.

 

 

출처:

https://www.youtube.com/watch?v=PzV7h4v9ANs&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=21

'Cuda' 카테고리의 다른 글

11_Using Shared Memory_Example_Quiz_2_CUDA  (0) 2021.09.02
10_Using Shared Memory_Example_Quiz_1_CUDA  (0) 2021.09.01
08_Memory_Architecture_CUDA  (0) 2021.08.30
07_GPU_HardWare_CUDA  (0) 2021.08.30
06_Where_is_Thread_CUDA  (0) 2021.08.30

CPU Memory

GPU Memory도 CPU Memory와 같이 계층이 나뉜다고 보면 됩니다.

 

 

 


GPU Memory

지금까지 Thread, Block, Grid를 알아봤습니다.

여기서 Thread, Block, Grid가 사용하는 Memory가 각각 다릅니다.

 

 

 

 


Thread가 사용하는 Memory

 

Thread가 사용하는 Memory는 Register, Local Momory가 있습니다.

  • 가장 빠르지만 가장 작은 Memory 입니다.
  • GPU마다 다르지만 하나의 Block 마다 32개의 Register가 존재합니다.
  • Register는 8k~64k의 크기입니다.
  • Thread마다 최대 255개의 Register를 사용할 수 있습니다.
    • ex) 64k의 Register 공간이 있고 1024의 Thread가 존재한다면 하나의 Thread는 최대 62의 Register 공간을 사용할 수 있습니다.
  • In-core memory (SM 안에 있는 Memory)

 

  • Off-chip Memory - SM 밖에 있는 Memory
  • Register보다 느리지만 큰 용량을 가집니다.
  • Register에 다 못올리는 경우 Local Memory에 올라갑니다.

 

 

 


Block이 사용하는 Memory

 

  • Block 안에 있는 Thread들이 공유하는 Memory 공간
  • on-chip (in-core-memory)이므로 속도가 빠릅니다.
  • SM안에 Block들이 Shared Memory 공간을 분할해서 사용합니다.

 

 

 

 


Grid가 사용하는 Memory

 

  • off-chip memory
  • 매우 느리지만 용량이 굉장히 큽니다.
  • Grid안에 있는 모든 Thread들이 접근 가능합니다.
  • HOST(CPU)가 접근 가능합니다. - 즉, 통신 메모리입니다.

  • GPU에서 읽을 수만 있는 Memory 입니다.
  • HOST(CPU) 쪽에서 선언합니다.
  • 굉장히 작고 빠릅니다. (64KB per SM)

  • GPU에서 읽을 수만 있는 Memory
  • Graphic 관련 Memory
  • 그냥 있다는 것만 알아두자!

 

 

 


Caches

  • L1 cahce: SM 안의 Shared memory를 L1 cache로 바꿀 수 있습니다.
  • L2 cache: SM 밖에 위치해있고 Global memory, Local memory... 등이 접근 할때 L2 cache를 통해 접근

 

 

출처:

https://www.youtube.com/watch?v=ipARGT0HfBM&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=20

'Cuda' 카테고리의 다른 글

10_Using Shared Memory_Example_Quiz_1_CUDA  (0) 2021.09.01
09_Memory_Active_Warp_CUDA  (0) 2021.09.01
07_GPU_HardWare_CUDA  (0) 2021.08.30
06_Where_is_Thread_CUDA  (0) 2021.08.30
05_How_Kernel_Works_CUDA  (0) 2021.08.30

HardWare

GPU Architecture (Fermi), SM (Streaming Multiprocessor)

  • CUDA core
    • Basic processing unit
    • 하나의 thread를 처리
    • Register, Local memory
  • Streaming Multiprocessor (SM)
    • block들을 처리
    • Shared memory
    • Texture/ Cache

Grid, Block

  • Grid / Kernel
    • Kernel launch 시 생성
    • GPU를 사용 하는 단위
  • Block
    • 각각의 SM에 block들이 배정되어 처리됨
    • Active block
      • 현재 SM에게 배정된 block
      • 개수는 block 당 자원 사용량에 의해 결정됨 - 성능에 영향을 줌

Warp

  • Warp
    • 각 block은 warp 단위로 분할
    • 자신의 execution context를 가짐 - 자기만의 실행 문맥을 가진다.
      • Program counters, registers, Etc.
      • SM의 register(Register File)를 warp(32개의 thread)가 나누어 가짐
    • Warp divergence
      • 한 Warp 안의 threads들이 다른 instruction을 진행 하는 경우, 분기 별로 serial 하게 수행됨
    • Zero context switching overhead
      • 많은 수의 thread를 사용하여, memory access latency를 감출 수 있음
      • context가 바뀔때 register에 저장하여 비용이 많이 들지 않는다.

 


Massive Parallelism for Latency Hiding

 

출처:

https://www.youtube.com/watch?v=MM04LrNlq2I&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=19

'Cuda' 카테고리의 다른 글

09_Memory_Active_Warp_CUDA  (0) 2021.09.01
08_Memory_Architecture_CUDA  (0) 2021.08.30
06_Where_is_Thread_CUDA  (0) 2021.08.30
05_How_Kernel_Works_CUDA  (0) 2021.08.30
04_Vector_Sum_CUDA  (0) 2021.08.27

Vector Sum for a Large Vector

하나의 Block에는 1024개의 threads를 사용할 수 있습니다.

그래서 1024보다 큰 수를 계산할때는 Block을 여러개 생성해서 진행해주면 됩니다.

여기에서 thread의 위치를 찾기 위해서는

blockDim.x * blockIdx.x + threadIdx.x;

위와 같이 위치를 찾을 수 있습니다.

blockDim.x: Block 하나안에 있는 thread의 수

blockIdx.x: 몇번째 Block인지 나타내는 수

threadIdx.x: Block안의 몇번째 thread인지 나타내는 수

 

 


Thread 위치 찾기 연습

  • 1D block: threadIdx.x
  • 2D block: blockDim.x * threadIdx.y + threadIdx.x
  • 3D block: blockDim.x * blockDim.y * threadIdx.z + blockDim.x * threadIdx.y + threadIdx.x
  • Grid내 global thread ID: blockIdx.x * blockDim.x * blockDim.y * blockDim.z + TID_IN_BLOCK

 

 

  • 2D grid: blockIdx.y * (gridDim.x * NUM_THREAD_IN_BLOCK) + 1D_grid_TID
  • 3D grid: blockIdx.z * (gridDim.y * gridDim.x * NUM_THREAD_IN_BLOCK) + 2D_grid_TID

※ NUM_THREAD_IN_BLOCK: block.x * blockDim.y * blockDim.z

 


 

 

출처:

https://www.youtube.com/watch?v=KLPHCovPnfg&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=18

'Cuda' 카테고리의 다른 글

08_Memory_Architecture_CUDA  (0) 2021.08.30
07_GPU_HardWare_CUDA  (0) 2021.08.30
05_How_Kernel_Works_CUDA  (0) 2021.08.30
04_Vector_Sum_CUDA  (0) 2021.08.27
03_Hello_CUDA_(Colab에서 CUDA 설정하기)  (0) 2021.08.26

CUDA 작동원리 알아보기

  • 하나의 Instruction이 여러개의 Threads들을 관리합니다.
  • Threads들이 공유하는 코드를 Kernel이라고 합니다. ex)__global__ 함수
  • Threads들은 각각의 메모리 공간을 가집니다.

 


Kernel 함수

Kernel

모든 스레드가 이 코드를 똑같이 실행합니다.

각각 자기만의 데이터를 다루기 위해 자신의 아이디=tID (위치)를 가져와서 실행합니다.


Thread의 계층구조

  • Thread: 기본적으로 연산을 수행하는 기본 단위
  • Warp:
    • 32개의 Threads들의 집합
    • 하나의 Instruction에 의해 작동
  • Block: 
    • Warp들의 집합
    • 하나의 블럭안에 있는 Thread들은 자신 고유의 ID값을 갖습니다. ex) threadidx
    • 1D, 2D, 3D 형태로 만들 수 있습니다.
  • Grid:
    • Block들의 집합
    • 하나의 Grid안에서는 Block들이 서로 다른 ID를 갖는다. ex_blockidx
    • 1D, 2D, 3D 형태로 만들 수 있습니다.

 


Grid, block

CUDA에 기본적으로 내장되어 있는 변수.

  • gridDim: 
    • Grid안의 block 수 결정
  • blockIdx:
    • Block ID of current thread

 

  • blockDim:
    • block 안의 thread 수 결정
  • threadIdx:
    • block 안에서 자신의 thread ID 값

 


Dimension 설정해주기

  • dimGrid(4, 1, 1): x축 - 4, y축 - 1, z축 - 1 즉, 4개의 1D형태의 Grid
  • dimBlock(8, 1, 1): x축 - 8, y축 - 1, z축 - 1 즉, 8개의 1D형태의 block

 


코드로 사이즈 알아보기

 

위의 dim3 block(), dim3 grid() 는 CUDA에서 제공해주는 기능입니다.

dim3 block(3), dim3 grid(2) 이건 아래의 그림과 같은 형태를 띄게 됩니다.

※ block(3), grid(2)와 같이 정수 하나면 입력할 경우 (3) -> (3, 1, 1), (2) -> (2, 1, 1) 이 됩니다.

즉, Grid 안에는 2개의 block이 존재하고 block 안에는 3개의 thread가 존재합니다.

 

 

이제 Kernel을 정의해 줍니다.

  • threadIdx: block안의 thread의 위치
  • blockIdx: grid안의 block의 위치
  • blockDim: block의 사이즈
  • gridDim: grid의 사이즈

출력의 결과입니다.

 

 

출처:

https://www.youtube.com/watch?v=my1U4QY59Bg&list=PLBrGAFAIyf5pp3QNigbh2hRU5EUD0crgI&index=17

'Cuda' 카테고리의 다른 글

07_GPU_HardWare_CUDA  (0) 2021.08.30
06_Where_is_Thread_CUDA  (0) 2021.08.30
04_Vector_Sum_CUDA  (0) 2021.08.27
03_Hello_CUDA_(Colab에서 CUDA 설정하기)  (0) 2021.08.26
02_용어정리_CUDA  (0) 2021.08.26

글을 작성하기 전에 알고가야 하는 것을 적어보겠습니다.

  • Host(CPU)와 Device(GPU)는 서로 다른 독립적인 메모리 영역입니다.
  • 서로 비동기적으로 동시에 실행 가능합니다. - 즉, 동시에 동작할 수 있습니다.

CUDA Programming Structure

  1. CPU -> GPU로 데이터를 옮겨줍니다.
  2. GPU에서 작업을 진행합니다.
  3. 결과값을 GPU에서 CPU로 옮겨줍니다.

Deivce memory allocation / release

  • cudaError_t cudaMalloc(void** ptr, size_t size): GPU를 사용하기 위한 공간을 확보
  • cudaError_t cudaFree(void* ptr): 사용된 GPU 공간을 해제
  • cudaMemcpy(void* dst, const void* src, size_t size, kind): 확보한 GPU공간에 Data를 복사
    • dst: 복사할 곳
    • src: 어떤것을 복사할지
    • size: 어떤것의 크기
    • kind: 어디서 어디로 복사할지(방향성)
      1. cudaMemcpyHostToHost: CPU -> CPU
      2. cudaMemcpyHostToDevice: CPU -> GPU
      3. cudaMemcpyDeivceToHost: GPU -> CPU
      4. cudaMemcpyDevicetoDevice: GPU -> GPU

※ HOST와 DEVICE에서의 명령어 비교

 


직접 작성해보기

GPU에서 실행될 __global__ 함수를 정의해줍니다.

 

각종 변수들을 선언 및 초기화를 진행해줍니다.

여기서는 int * 512만큼의 공간을 사용하겠습니다.

 

위에서 선언한 d_a, d_b, d_c를 이용하여 GPU에 공간을 할당해줍니다.

그리고 위에서 선언한 __global__함수를 호출하여 실행해줍니다.

 

결과값을 비교해보면 잘 작동되는 것을 알 수 있습니다.

 


 

처리시간 재기

시간을 측정하기 위해서는 vecAdd<<<1, NUM_DATA>>>... 밑에

cudaDeviceSynchronize(); 라는 함수를 사용하면 됩니다.

위에서 말했다시피 CPU와 GPU는 서로 독립적으로 작동하기 때문데

CPU는 vecAdd를 실행시키고 끝날때까지 기다리는것이 아니고 바로 다음줄을 실행해버립니다.

그래서 vecAdd 밑에 cudaDeviceSynchronize();를 사용하게 되면

GPU연산이 끝날때까지 작동을 멈춥니다.

 

cudaDeviceSynchronize(); 를 사용해라!

'Cuda' 카테고리의 다른 글

06_Where_is_Thread_CUDA  (0) 2021.08.30
05_How_Kernel_Works_CUDA  (0) 2021.08.30
03_Hello_CUDA_(Colab에서 CUDA 설정하기)  (0) 2021.08.26
02_용어정리_CUDA  (0) 2021.08.26
01_Parallel_Computing_CUDA  (0) 2021.08.25

+ Recent posts