서울대학교 컴퓨터공학과 이재진 교수님의 "확장형 고성능 컴퓨팅" 강의를 필기한 내용입니다.
Performance factors for HW
- Key performance factors
- Parallelism: 한번에 몇개의 연산을 같이 하느냐
- Frequency: 1 clock 이 얼마나 빠른지 (speed)
- Memory bandwidth: 한번에 데이터를 얼마나 가져올 수 있는지
- Memory latency: Memory bandwidth 에서의 “한번” 이 얼마나 빠른지
- 이 factor 들 중에서 집중하는 factor 들은 workload 에 따라 달라진다.
- 각 factor 들을 향상시키는 방법은 architecture 에 따라 달라질 수 있다.
- CPU 한테 저 factor 들을 적용해 보자.
- Parallelism: CPU 에는 core 가 여러개고 (multicore) 각각 ILP 를 지원한다.
- Frequency: 오래걸리는 Instruction 을 기준으로 frequency 가 설정되니까 이 오래걸리는 instruction 을 multi-step 으로 나눠서 frequency 를 올리는 방법을 해왔으나,
- Power consumption (발열) 등의 문제로 이것을 올리는 데는 한계가 있다.
- Memory bandwidth: DDR 은 bandwidth 가 한정되어 있다.
- Memory latency: DDR 은 latency 가 다소 길다.
- Memory 쪽이 좀 부실해 보이긴 하지만,
- 어차피 CPU 의 freqency 를 올리는데는 한계가 있어서 CPU 의 속도 발전은 정체되어 있고 따라서 memory 와의 성능차이는 (비록 mem 발전속도가 느리긴 하지만) 점차 줄어들고 있다고 한다.
- 그리고 CPU 에는 cache 가 있으니까 이 부실함이 그렇게까지 critical 하지는 않다.
GPU overview
- 일단 Graphic Processing Unit (GPU) 는 단순 연산을 아주 빠르게 하는 core 가 엄청 많이 (많게는 몇천개까지) 들어있는 장치이다.
- 생각해 보면 이미지는 하나의 큰 2차원 배열이고, 영상은 이것의 모음이기 때문에 이 배열을 계산하는 것이 graphic 처리의 시작과 끝일 것이다.
- 특정 작업만 아주 빠르게 하는 HW 장치를 Accelerator 라고 하는데, 단순 연산이 아주 빠르다는 점에서 GPU 도 accelerator 에 속한다.
- GPU 한테도 위에서의 performance factor 들을 적용해보면
- Parallelism: 단순한 작업을 하는 core 가 매우 많게 구성된다.
- Frequency: CPU 보다는 frequency 가 좀 낮긴 하다.
- 근데 parallelism 이 매우 커서 그다지 흠이 아니다.
- Memory bandwidth: HBM (High-bandwidth Memory) 를 사용해 아주 bandwidth 가 크다.
- Memory latency: 이건 DDR 과 비슷하다고 하네
- 근데 HW multithreading 으로 이러한 latency 를 가린다고 한다 (Latency hiding)
- 여담으로 GPU 는 원래 게이밍 분야에서 발전해 왔지만, NVIDIA 를 필두로 복잡한 계산을 수행하는데 사용할 수 있도록 HPC 시장으로 확대되어 왔다고 한다.
Rendering
- Rendering: 3D model 을 구성해 놓고, 그것을 2차원 이미지에 보는 방향에 따라 투사하는 것
- 여기서 3D Model 은 3차원의 객체의 표면을 수학적으로 표현하는 것이다.
- 그리고 이건 2차원 삼각형의 조합으로서 표현된다.
Shader
- 아래는 Rendering 의 과정 (pipeline) 이다.
- 3d rendering 하는 과정은 위와 같은데, 원래는 이 작업들이 각각의 accelerator 를 이용했다면
- 지금은 많은 작업들이 programmable 하다는 것이 알려저 general purpose computation unit (CPU) 로 수행해도 비슷한 성능을 낸다.
- 이때 Shader 라는 것은 GPU 에서 작동하며 위 pipeline 중 하나를 처리하는 프로그램을 일컫는다.
Architecture for general purpose CPU
- CPU 의 구조를 간단하게 보면 위와 같다.
- IF 와 ID 를 담당하는 Fetch, Decode logic
- ALU 들
- Context 는 CPU 에서 사용할 저장공간 register (Multithreading 라면 이것도 여러개)
- OOO controller 는 OoO execution 를 위한 reservation station, reorder buffer 등
- 그리고 나머지 Branch predictor 나 Prefetch logic, Big data cache 는 뭐 이름이 시사하는 바 그대로임
- 참고로 어차피 transistor 밀도는 정해져 있기 때문에 (몇 나노 공정 등) CPU 크기가 크면 그만큼 복잡도도 커진다고 한다.
Shader core
- 근데 shader 를 생각해 보면 triangle 의 pixel 을 계산하는데 이 계산이 복잡한 것도 아니고 단순히 많은 것 뿐이다.
- Triangle 간의 data dependency 도 없다고 한다.
- 그래서 위처럼 (1) Fetch, Decode logic (2) ALU, (3) Execution context 만 남기고 다 쳐내게 된다.
- 여기서 share 할 수 있는 것들을 보아보면 위처럼 되고, 이것이 GPU 의 기본적인 구조
- 하나의 instruction stream 을 빠르게 해주는 요사스러운 것은 다 빼고
- 무적권 parallel processing 으로만 승부보는 것.
- 여기서 Fetch, Decode logic 이 1개라는 것은 PC 가 1개고, 이 instruction 를 모든 ALU 가 실행한다는 것이다.
- 여기에서 rendering pipeline 을 하나하나 처리하게 되는데, 따라서 pipeline 마다 GPU 를 방문하게 된다.
- 이런 것을 Logical graphics pipeline 이라고 한다.
Thread, Warp
- 약간 NVIDIA-specific 용어인거같은데
- Thread 는 하나의 IF-ID-EX 단위로 GPU 내의 ALU 하나하나라고 생각하면 된다.
- IF-ID-EX 가 하나의 “실행 흐름” 이기 때문에 Thread 라고 이름붙여진 것.
- 그리고 이놈을 묶어서 하나의 Fetch-Decode logic 에 따라 움직이는 Thread 들을 Warp 라고 한다.
Predicated execution
- Predicated execution 은 GPU 에서 조건문 처리하는 방법인데, 무식하게 한다: 모든 조건들을 다 계산하고, 맞는 조건만 살리는 것.
- Branch divergence 라고도 부른다.
- 즉, if-else 에서 if 인 경우와 else 인 경우를 모두 parallel 하게 계산하고, 나중에 if branch 가 맞다는 것이 밣혀지만 else 인 경우를 버린다
- 근데 딱 봐도 overhead 가 커보인다: 그래서 GPU 를 쓸 때는 branch 를 줄이는 것이 좋다
- 다만 무조건 한쪽으로만 branch 되는 경우에는 예외.
Context switch
- CPU 에서는 context switch 를 할 때는 register-memory 간의 저장-복구 작업이 수행되는데
- 근데 gpu 에서는 warp 가 사용할 execution context 를 여러벌 만들어서 이 overhead 를 줄인다.
- 즉, context + shared context 를 여러벌 만들고 (= context pool) context switch 할 때 그냥 다른 context 를 사용하기만 하면 되는 방식
- 위 경우에는 이 context 가 4벌있는 것
- Warp 가 실행되다가 stall 이 되면 다른 warp 로 switch 되고, 이때는 data 이동 없이 그냥 contex pool 의 다른 context 를 사용하기만 하면 된다.
- GPU 에서는 이런 구조로 인해 stall 이 되면 그냥 다른 context 로 갈아껴서 계속 연산을 진행한다.
- 따라서 context switch 를 해줄 OS 가 따로 필요 없고, time shared 도 아니다.
- 따라서 지원하는 warp 의 개수는 HW 적으로 fixed 되어 있다.
- 참고로 필요한 register 사이즈에 따라 올리는 warp 는 달라질 수 있다고 한다.
Streaming Multiprocessor, SM
- 저런 (Fetch-Decode logic, ALU 들, context 들) 을 묶어서 (NVIDIA 에서는) Streaming Multiprocessor (SM) 라고 하고, 위 그림처럼 이 SM 을 여러개 사용하는 방식을 사용
- 참고) cosine, sine 계산은 수학적으로는 Taylor series 으로 할 수 있고, GPU 에서는 이것들을 미리 pre-calculation 해놓고 그냥 table lookup 을 한다고 한다.
- 그래서 현대의 GPU 들은 위와 같은 형상을 띈다고 한다.
- 저기서 SP 는 Scalar Processor 로, 그냥 ALU 의 묶음이라고 생각하면 된다.
- 그리고 LD-ST 는 Load-Store unit 이다. 말 그대로 memory 접근하는 놈
- 마지막으로 SFU 는 Special Function Unit 이다. 뭐 특별한 일 하나봄.
GPU Summary
- 하나의 instruction 을 많은 ALU 가 동시에 실행하고 있다는 점에서 SIMD 와 유사하다.
- Thread 간 sync 를 맞추는 기능은 (거의) 제공하지 않음: 왜냐면 shading 연산에서는 그럴 일이 별로 없었기 떄문에: 따라서 이런 sync 를 맞춰야 하는 코드를 작성하지 않는 것이 좋다.
Direct Memory Access (DMA)
- DMA 가 뭔지는 알제? Memory access 해주는 HW
- 뭐 IO 이후에 메모리에 올리는 것을 cpu 가 하는게 아니고 dma 가 하며 이것이 끝난 다음에 interrupt 거는 시스템
- 대충 작동 과정은 다음과 같다:
- 일단 CPU 가 memory 의 한 곳에 DMA command 를 적는다.
- 여기에는 뭐 memory 의 어느 주소부터 몇 byte 를 적어라 그런게 담긴다.
- 그리고 DMA 에다가 적은 DMA command 의 memory 주소를 보낸다.
- 그럼 DMA controller 가 여기로 가 command 를 읽고, 처리한다.
- 처리가 완료되면 DMA controller 는 CPU 에 다 됐다고 interrupt 를 건다.
- 일단 CPU 가 memory 의 한 곳에 DMA command 를 적는다.
DMA cache coherence
- 위에서 알 수 있다 시피, memory 에 적힌 내용은 DMA 가 볼 수 있다. 근데 문제는 CPU 는 memory 에 바로 적지 않을 수도 있다는 것.
- 즉, processor 가 cache 로 가져간 값이랑 dma 가 올린 값이랑 타이밍이 안맞으면 이들이 차이가 생기게 된다.
- 가령 cache 로 올린 다음에 memory 에 dma 한 경우
- 이러한 문제는 memory (DMA) 와 cache 간에 sync 가 맞지 않는 경우이므로 DMA cache coherence 라고 부른다.
- 이걸 해결하는 것은
- cache flush 를 하거나
- caching 을 비활성화하던가 (C 에서
volatile
)
Remote Direct Memory Access (RDMA)
- RDMA Network 를 통해 remote node 의 memory 에 바로 접근하겠다는 아이디어이다.
- Host A 의 NIC 는 요청을 host B 의 NIC 로 보내고
- Host B 의 NIC 는 host B 의 NIC 를 통해 host B 의 DMA 에 접근해 그쪽의 memory 에 있는 데이터를 읽거나 쓴 후
- 그 결과를 host B 의 NIC 에서 host A 의 NIC 로 받아, host A 의 DMA 에게 알려 host A 의 memory 에 쓰게 한다.
- 이렇게 하면 remote 의 OS 개입을 최소화하게 된다.
- 그리고 data copy 도 줄일 수 있다.
- 이게 뭔소리냐: 원래 remote node 의 memory 에 접근하려면 다음과 같은 stack 을 탔어야 했는데
- 이제는 이렇게 할 수 있는 것.
- 이것은 single side communication 이다: 즉, local 에서 remote 로 일방적으로 보내는 거고 remote 의 OS 는 이것을 인지하지 못한다.
- 이 기술은 보통 infiniband 와 같은 아주 빠른 네트워크 환경에서 사용할 수 있다고 한다.
GPU Direct
- GPU 가 누군가와 통신하려면 원래는 CPU 의 memory 를 통해야 했는데 (즉, main memory 를 bounce buffer 로 삼는 것) 이것을 GPU 에서 바로 접근하게 해주는 것이 GPU Direct 이다.
- 즉, DMA 를 통해서 주변장치끼리 통신하는 것을 통칭하는 것.
- 가령 GPU 끼리 서로의 메모리에 접근한다던가
- 위 그림처럼 DMA 가 storage 가 보낸 데이터를 main memory 가 아닌 GPU memory 로 보내도록 한다던가 (GPU Direct Storage)
- 아니면 다른 node 의 main memory 및 GPU memory 에 RDMA 로 접근한다던가 (GPU Direct RDMA)
- … 할 수 있다.
Collective Communication Library (CCL)
여기부터는
2024-10-09
강의임
- 통신을 할 때, 딱 누구를 지정해서 통신 (point-to-point) 할 수도 있지만
- Broadcast 를 해야 할 일도 있을 것이다.
- GPU 에서 이런 기능을 제공하는 library 가 Collective Communication Library (CCL) 이다.
- 물론 point-to-point 로도 구현이 가능하지만, 너무 비효율적이어서 새로 만들어 library 로 만들었다고 한다.
- 기존에 CPU 간의 통신을 위해 만든 것이 MPI (Message Passing Interface) CCL 이었고, 이것을 GPU 에서 사용하기 위해 바꾼 것이 NVCCL (NVIDIA), MSCCL (Microsoft), RCCL (AMD) 등이다.
- 여기에는 Broadcast, Scatter, Gather, All-Gather, All-to-All, Reduce, All-Reduce 가 있다고 한다.
- GPU Direct 를 사용하여 구현되어 있고, remote node 일 시에는 RDMA 를 사용한다고 한다.
깨알 홍보
- 이재진 교수님 랩이 만든 Thunder CCL (TCCL) 도 있다고 한다.
- NVCCL 에서는 NVIDIA 에 특화된 NVLink 라는 통신을 사용하는데,
- 보통은 PCIe 를 사용하니까 이것을 위해 커스터마이징한 것이 TCCL 이랜다.
Broadcast, Scatter, Gather
- Broadcast 는 동일한 값을 다 뿌리는 것
- Scatter 는 여러곳에서 데이터를 받아 한곳으로 모으는 것
- Gather 는 반대로 데이터를 쪼개 여러곳으로 뿌리는 것
All-Gather
- 는 Gather 를 하되 그 결과를 모든 놈이 동일한 copy 를 갖게 하는 것
All-to-All
- 는 transpose 와 같이 행렬의 X, Y 축을 반전시키는 것
Reduce, All-Reduce
- Reduce 는 여러 놈의 데이터를 Reduce 해서 한 놈한테 모으는 것
- All-Reduce 는 Reduce 한 것을 모든 놈이 동일한 copy 를 갖게 하는 것이다.