[알쓸G잡] GPU 메모리 및 쓰레드 구조
1. Nvidia GPU 의 종류
출처 : https://en.wikipedia.org/wiki/CUDA
용어 설명
- Compute capability (이하 CC): 말그대로 연산 능력이다. GPU 코어가 동시에 몇개의 쓰레드를 수행 할 수 있는지 등이 다르다.
- Micro-architecture : GPU의 근간이 되는 아키텍쳐 이름이다. 같은 아키텍쳐에서 여러 CC 가 있을 수 있다.
- Ampere 아키텍쳐에서 A100은 CC가 8.0이고 A40은 8.6이다.
- 일반적으로 CC가 높으면 지원하는 기능들이 더 많다.
- GPUs : 코드 네임. 뒤에 나오는 GeForce, Quadro, Tesla 등의 기반이 되는 GPU 구조이다.
- 예를 들어 똑같은 에스프레소샷에 따듯한 물타면 뜨아, 얼음타면 아아 되듯이, 같은 GPU 아키텍쳐를 이용해서 그래픽 관련 모듈 설치하고 RT 코어 달고 하면 게임 용 GeFore, 그래픽 포트 없애고 Tensor 코어를 달고, DRAM 더욱 집적해서서 과학연산 용으로 만들면 Tesla, 이런 식으로 생각하면 된다.
- Quadro는 GeForce와 Tesla를 섞은 느낌이다. 화면 출력도 되고 메모리 집적도도 어느 정도 높다. 보통 (24시간 실험하는) 대학원생이나 개인 장비를 소유하는 연구자들이 출력 및 관리가 용이해 많이 쓴다. Tesla 는 서버렉에 설치하는 형태가 일반적이고 Quadro는 데스크탑 형태이기 때문이다.
- Jetson 라인은 자율주행 자동차에 들어가는 라인이다.
- 본 글에서는 Tesla, IDC 용 GPU 를 타겟으로 서술 하겠다.
2. GPU Architecture
CPU vs. GPU 구조 비교
출처 : https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
- 위는 CPU와 GPU의 구조를 비교한 그림이다. 개론적인 그림이지만 얼핏 봐도 CPU 대비 GPU 코어의 갯수가 압도적으로 많다.
- GPU 는 CPU 대비 코어 각각의 성능은 낮다. 코어당 컨트롤러의 크기도 작고 L1 cache의 크기도 작아서 recursion 같은 연산에도 적합하지 않다.
- 초기 GPU는 초당 수십번 모니터 화면에 랜더링 값을 연산해서 뿌려주는 용도 였기 때문에, 정확한 값을 뿌려주는 것보다 처리량이 중요했다.
초기 GPU는 CPU와 부동 소수점 연산 명령어도 달랐고, 오차가 꽤 컸다. 위에서 언급했듯이 초당 최소 30번씩 값을 뿌려 주었기 때문에, 값이 조금 틀려서 색이 다르게 표현되도 사람이 알아차리긴 어려웠기 때문이다. 하지만 과학 연산 용으로 사용되는 요즘 GPU는 연산 정확도 및 다른 CPU/GPU 와의 호환성 (재구현성)에 신경을 많이 쓰고 있다.
GPU 내부 구조
- 아래 그림은 H100 백서에 나오는 GH100(H100, H200의 베이스) 의 구조이다.
- 가운데에 GPU 칩셋들(이하 SM)과 L2 캐시가 있으며, 주위를 GPU 메모리와 PCIe, NVLink 컨트롤러가 둘러싸고 있다.
- 여기서 우리가 주목해야 할 것은 SM (GPU 코어칩셋)이다. 하나의 GPU는 여러개의 SM으로 구성되어 있으며 하나의 SM 안에는 여러 종류의 GPU 코어와 메모리로 구성되어있다.
- H100 SXM5(NVSwitch) 는 132 개의 SM 을 갖고 있으며, PCIe 버전은 114개의 SM 을 갖고 있다.
- NVSwitch 와 PCIe 버전이 다른 수의 SM을 갖고 있는 이유는 공간 문제로 추측 해 볼 수 있다. 개인적인 생각이지만, NVSwitch의 경우 GPU 메인보드에 직접 설치를 하므로, 조금 더 공간을 효율적으로 가져가서 SM 을 설치할 공간을 더 확보 한 것 같다.
- 참고로 A100의 SM의 갯수는 108개 이다.
TMI : 예전에 Nvidia 관계자에게 왜 SM 내부 메모리 사이즈 늘리지 않느냐고 물어본 적이 있었는데, 메모리 늘릴 공간 있으면 코어 하나라도 더 설치한다고 했었다.
출처 : nvidia-h100-tensor-core-hopper-whitepaper.pdf
Streaming Multiprocessor (SM) 구조
- SM은 또 여러개의 SP로 구성이 되어있는데, 이는 아키텍쳐마다, GPU 모델마다 다르다.
- 초기 모델은 1 SM = 1 SP 였다.
- H100의 경우 하나의 SM이 4개의 SP로 구성되어 있다.
- SM내 SP들은 L1 캐쉬와 Shared Memory를 공유한다.
- 같은 SM안에 있지만 다른 SP 안에 있는 코어들끼리 데이터를 교환하기 위해 Shared memory를 사용한다.
- 하지만, 다른 SM에 있는 코어들과 데이터를 교환하고 싶을때는 SM 외부에 있는 GPU 메모리를 사용해야 해서 성능 저하가 있었는데, 이번 Hopper 아키텍쳐에선 해당 부분을 해소하기 위한 방안을 마련했다. (조금 복잡한 내용이라 4장에서 설명하겠다)
- 일반적으로 On-chip 메모리라고 하면 SM 안에 있는 Shared Memory를 뜻하고 Off-ship 메모리라고 하면 SM 밖에 있는 (Global/Device) Memory 를 뜻한다.
- GPU 코어에서 on-chip 대비 off-chip 메모리의 접근속도는 약 7~8배 정도 느리다.
출처 : nvidia-h100-tensor-core-hopper-whitepaper.pdf
Streaming Processor (SP) 구조
출처 : nvidia-h100-tensor-core-hopper-whitepaper.pdf
- 각각의 SP에는 여러 종류의 코어와 컨트롤 유닛이 있다.
- Tesla 모델등 딥러닝 학습을 위한 모델에는 텐서코어가 내장되어 있고, GeForce 같은 게이밍 GPU에는 레이트레이싱을 위한 RT 코어가 탑재 되어 있다.
- 텐서코어는 행렬곱을 가속화한 장치로써 일반적으로 GPU 코어는 1 clock 동안 하나의 부동소수점 연산을 하는데 반해, 텐서코어는 4x4 행렬곱을 수행한다.
- 이때 입력은 FP16, 출력은 FP32 이라 mixed precision 이라고 부르기도 한다.
GPU 프로파일화면 캡쳐. 텐서코어 사용률을 확인 할 수 있다.
- FlashAttention 및 여러 GPU-aware 한 논문들의 주요 가속화 방법 중 하나가 연산 수를 늘리더라도 off-chip 메모리 접근을 최소화 하는 것이다.
- 메모리 접근 최소화를 위해 사용하는 가장 흔한 기법이 타일링 이다.
- 타일링이란 딥러닝의 data parallel 처럼, 서로 독립적으로 수행해도 되는 것들을 분할, 정복하는 기법이다. 이때 분할하는 사이즈, 타일 사이즈를 GPU의 on-chip 메모리 사이즈에 맞추는 것이 일반적이다.
앞서 말했듯이 GPU의 경우 연산 수를 늘리더라도 오히려 속도가 빨라지는 경우가 꽤 있다. 자세한 건 후에 FlashAttention 논문 설명할때 다루도록 하겠다. 참고로 여기서 말하는 연산은 컨트롤 유닛 연산이 아닌, 산술연산을 뜻한다. (컨트롤 유닛 연산 관련해선 4장에서 다루도록 하겠다)
3. GPU Thread Hierarchy
- CPU 는 쓰레드가 1차원인데 반해, GPU 여러 계층 및 차원으로 이루어져 있다.
- Grid with Clusters → Thread Block Cluster → Thread Block → Warp → Thread
- Warp 는 아래 그림에는 안 나와있지만 매우 중요한 개념인데, CUDA 에선 쓰레드가 32개씩 그루핑이 되어있고 이걸 Warp 라고 부른다. 하나의 Warp 는 하나의 명령어를 수행한다. (Volta 부터는 각 쓰레드에 PC가 생겼지만 너무 복잡해지니 여기선 다루지 않겠다.)
출처 : https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
- CUDA 프로그래밍을 하려고 한다면 다 알아야 되지만, 너무 복잡하다. 우리는 두 가지만 생각하자. ‘블럭’, 그리고 ‘쓰레드’
- 예를 들어 블럭 2개, 쓰레드 3개를 생성한다고 치면, GPU 블럭2개가 만들어지고 각각 블럭 안에 쓰레드가 3개씩 생성되는 식이다.
- 각각의 블럭은 동시에 하나의 SM에서만 수행 된다.
- 즉, 아래 그림처럼 블럭을 총 8개를 생성하면, SM 이 두 개인 GPU 는 왼쪽과 같이, 4개인 GPU는 오른쪽 같이 실행 된다.
- 물론 SM의 상태에 따라 블럭이 다이나믹하게 할당 된다.
- H100의 경우 하나의 SM이 32개의 블럭을 처리 할 수 있다. 참고로 A40, A100은 각각 16개, 32개이다.
- 물론 SM의 상태에 따라 블럭이 다이나믹하게 할당 된다.
출처 : https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
- CUDA 프로그래밍에서는 생각보다 블럭 숫자가 매우 중요하다. SM이 많더라도 적절하게 블럭을 생성해주지 않으면 SM이 놀게 되기 때문이다.
- 아래 화면은 GPU 프로파일링 툴 내에서 확인 할 수 있는 SM 사용률이다.
- 적절한 수의 블럭을 생성하기 위해 예전에는(
라떼는) Occupancy Calculator 라는 엑셀 파일이 있었다.
- 최근에는 GPU 프로파일링 툴 안에 내장되어 있다. 링크
댓글남기기