GPU의 특성
- vectorized 연산을 하는 것이 아니라, 동일한 연산을 개별적으로 동시에 수행하는 구조다.
- SM(Streaming Multiprocessor)에는 계산을 담당하는 Core, 각 Core가 사용하는 Register, Core들 간에 공유할 수 있는 Shared Memory 및 L1 Cache가 있다.
- Global memory는 GPU device마다 하나씩 있으며, device 내의 SM들이 여기로부터 데이터를 받아간다.
몇가지 최적화 아이디어들
Tiling
- 흔히 알고 있는 2D block tiling뿐만 아니라, 하나의 스레드가 더 많은 일을 하게 하는 2D Thread tiling도 고려할 수 있다.
Occupancy: Thread 및 Thread Block
- Grid Size를 충분히 크게 해주어야(즉 많은 스레드 블록) 한다. 하나의 SM이 여러 개의 스레드 블록을 받아가서 이를 warp로 나누어 실행해야 병렬화에 유리하기 때문이다.
- 하나의 스레드 블록의 크기(즉 스레드의 개수)는 32의 배수여야 한다(Warp size가 32일 때). 그래야 '덜 사용'하는 워프 없이 꽉 채워서 실행된다.
- 32의 배수일 뿐만 아니라 적어도 128개 이상 정도는 되어야 좋다. 스레드 개수가 적으면 워프 개수도 적어진다. 그런데 만약 스레드 블록이 너무 크면(블록 당 스레드 개수가 너무 많으면) 스레드가 쓸 수 있는 자원이 부족해진다. SM은 스레드 블록 하나가 완전히 종료되어야 다음 스레드 블록을 받아와서 계산할 수 있는데, 만약 블록이 너무 크다면 종료에 시간이 오래 걸려서 granularity가 떨어질 수 있다.
Global Memory
- global memory에 대한 접근은 Aligned & Coalesced되어야 한다.
- 만약 연속적이지 않은 위치에 대한 접근을 하고자 한다면 각각 접근이 이루어져야 하지만, 예를 들어서 연속적인 128바이트를 읽어올 때는 한 번에 가져올 수 있다.
Shared Memory
- global memory의 내용을 shared memory로 가져와서 사용하면 유용한데, 이 때는 bank conflict가 생길 수 있음에 유의한다.(bank는 global memory와는 크게 관련 없는 내용이다) bank는 말하자면 32개의 통로가 있다고 생각하면 된다. 그래서 0번, 32번, 64번 등 위치의 자료는 동일한 0번 통로를, 1번, 33번, 65번 등 위치의 자료는 동일한 1번 통로를 이용하는 식이다. 만약에 하나의 워프 내에서 접근하려는 뱅크가 겹칠 경우 기다려서 가져와야 하므로 시간이 오래 걸린다.
Branch Divergence
- GPU는 기본적으로 하나의 명령을 모두가 동시에 실행하기 때문에, 하나의 워프 내에 있는 32개의 스레드 중에 일부가 브랜치될 경우(e.g. if문에서 일부만 조건 만족...) 각각 알아서 브랜치를 동시에 실행하는 게 아니라, 브랜치되지 않은 것들은 대기하여야 한다. 즉 유휴 스레드가 생길 수밖에 없어서, 브랜치를 최대한 줄여야 한다.
- 다만 32개의 스레드 전체가 브랜치되는 경우에는 이러한 문제가 없다. 만약 전부 다 해당 줄을 실행하지 않아도 되는 상황이면 아예 뛰어넘어버리기 때문이다.
Concurrency
- GPU device 여러개, 그리고 CPU를 동시에 실행함으로써 더욱 throughput을 높일 수 있다.
- 이를 위해서 stream을 이용할 수 있다. 하나의 스트림 내에서는 순서대로 synchronous하게 이루어지지만, 서로 다른 stream끼리는 연산이 겹쳐서 동시에 이루어질 수 있기 때문이다.
- 그리고 메모리를 옮기는 작업과 계산을 하는 작업도 동시에 이루어질 수 있어서, 일종의 파이프라이닝도 가능하다. Asynchronous하게 메모리를 복사하는 API도 제공된다.
'컴퓨터' 카테고리의 다른 글
[TIL] 반복문의 인덱스 정하기는 의미있는 이름으로 한다(i, j, k 지양) (0) | 2023.10.17 |
---|
댓글