-
GPU는 연산 속도가 메모리 접근 속도보다 월등히 빨라서, 메모리 계층 구조가 성능의 병목을 일으킴
-
연산 집약도(Arithmetic Intensity, AI) 에 따라 연산이 메모리 바운드, 계산 바운드 상태로 구분되며, A100 GPU의 임계점은 약 13 FLOPs/Byte임
-
성능 최적화 주요 전략으로 연산 합치기(Fusion)와 타일링(Tiling)이 있음; Fusion은 불필요한 메모리 왕복을 줄이고, Tiling은 데이터 재사용을 극대화함
-
동기화, Coalesced Load, 뱅크 충돌 해결 등 GPU 하드웨어의 구조적 특성을 이해하는 것이 고성능 커널 작성에 중요함
-
점유율(Occupancy), 스레드 분기 최소화, 양자화(Quantization) 등 추가적인 고려사항이 실제 성능에 중요한 영향을 미침
GPU의 컴퓨트 및 메모리 계층 구조
-
GPU는 일반적으로 메모리 대역폭보다 산술 연산 처리 속도가 훨씬 높음
- 예를 들어, NVIDIA A100은 약 19.5 TFLOPS(32비트 부동소수점) 성능을 내지만, 메모리 대역폭은 약 1.5TB/s 수준임
- 4바이트 데이터를 읽는 동안 수십 개의 계산을 처리할 수 있으므로, 데이터 이동이 성능의 병목임
-
글로벌 메모리(VRAM) 는 모든 데이터가 존재하는 느린 오프칩 메모리이며, Streaming Multiprocessor(SM) 는 컴퓨팅을 담당함
- 각 SM에는 고속 온칩 Shared Memory(SRAM) 가 있고, 여기서 프로그램이 직접 관리하는 캐시로 활용 가능함
-
스레드는 가장 작은 실행 단위이며, 각 스레드는 개별 레지스터 집합을 갖고 있음
-
32개의 스레드가 Warp를 이루며, Block은 동일 SM에서 실행될 스레드 그리드임
성능 구간: 메모리 바운드 vs 컴퓨트 바운드
-
커널의 성능은 메모리 바운드(데이터 이동 속도에 의해 제한) 또는 컴퓨트 바운드(SM 연산 능력에 의해 제한) 중 하나임
-
연산 집약도(AI) 는 Total FLOPs / Total Bytes Accessed로 정의되며, 이 값이 중요 지표가 됨
-
Roofline 모델: x축이 AI, y축이 FLOPS/s인 그래프에서 커널의 실현 성능을 나타냄
- AI가 낮아 메모리 바운드이면 대각선(메모리 대역폭 계단)
- AI가 높아 계산 바운드이면 수평선(최고 연산 성능 계단)
-
A100의 Ridge Point는 19.5 TFLOPS / 1.5 TB/s ≈ 13 FLOPs/Byte임
- AI를 높이면 성능이 향상되며, 커널이 컴퓨트 바운드에 도달할 수 있음
연산 집약도 높이기 전략
-
간단한 모델: 스레드 1개가 C[i,j] 1개 계산 → AI = 0.25 (매우 낮음, 메모리 바운드)
- 스레드가 2x2 타일 계산 시에도 AI = 0.5 (여전히 낮음)
- AI를 높이기 위해서는 여러 스레드가 블록 단위로 Shared Memory에 대형 타일을 적재해 데이터 재사용을 극대화해야 함
- Block 내 스레드 협력을 통해 AI > 13로 증가시켜 컴퓨트 바운드에 진입 가능함
오버헤드 바운드 상태
- CPU(호스트)가 GPU에 작업을 할당하는 과정에서 오버헤드 발생 가능
- GPU 커널이 너무 작거나 많으면, GPU는 작업을 기다리며 대기하는 상황 발생
- 현대 프레임워크는 비동기 실행을 도입해, 커맨드 스트림을 미리 큐잉하여 오버헤드를 최소화함
성능 증진을 위한 두 가지 핵심 전략: Fusion과 Tiling
Operator Fusion (연산 합치기)
- 단순 연산(chain), 예: y = relu(x + 1)에서는 각 연산이 별도 커널로 동작하면 데이터가 글로벌 메모리를 왕복함
-
Fusion은 여러 연산을 하나의 커널로 합쳐 중간 값을 글로벌 메모리에 저장하지 않고, 레지스터 내에서 연산 처리 후 최종 결과만 기록함
- 예시: Triton, torch.compile Inductor 등 JIT 컴파일러가 자동화 처리
Tiling (타일링)
-
행렬곱 등 복잡한 연산에서, 단일 스레드 모델로는 AI가 낮음
- 블록 단위로 타일을 나눈 후, 블록 내 모든 스레드가 협력해 데이터 타일을 Shared Memory에 적재, 대규모 데이터 재사용 구현
- 연산은 "Load(글로벌 -> Shared Memory) - Synchronize(동기화) - Compute(연산)" 3단계 패턴을 따름
Coalesced Load와 벡터화
- 글로벌 메모리에서 Shared Memory로 데이터를 옮길 땐, Coalesced Access(워프 32개 스레드가 연속된 128바이트 구간 접근)가 중요
- 벡터화(예: float4) 로 한 번에 여러 데이터 로드 시, 하드웨어 리소스 절약 및 메모리 대역폭 활용 극대화
- 데이터 정렬(alignment)이 필수이며, 행렬 내 바이트 수 K값이 4의 배수여야 효율적임
Shared Memory 뱅크와 뱅크 충돌
- Shared Memory는 32개의 독립된 뱅크로 구성되어, 워프 32개 스레드가 각기 다른 뱅크에 접근해야 충돌 없이 동작
-
행 단위 접근은 충돌 없음, 열 단위 접근은 충돌(같은 뱅크 접근) 발생
- B 타일은 "로드 및 전치" 전략으로 Shared Memory에 전치로 저장해, 연산 시 행 접근 중심으로 뱅크 충돌을 피함
고속 온칩 연산 패턴
기본 전략 1: 한 스레드가 한 output 계산
- BLOCK_DIM=32 제한 하에 AI 최대 8로, 컴퓨트 바운드 진입 불가
전략 2: 한 스레드가 여러 output 계산
- BLOCK_DIM=16, TILE_DIM=64로 설정 시, 한 스레드가 4x4 출력 계산 → AI=16
- AI>13이므로 A100 기준으로 compute-bound 성능 달성 가능
- Shared Memory에서 float4 등 벡터화 로드로 효율적 연산 가능
타일화의 실제 한계: 타일 양자화
- 행렬 크기가 타일 크기의 배수가 아니면, 경계 블록이 실제보다 큰 영역을 계산(불필요한 연산)하며 패딩 처리됨
- 경계의 스레드는 guard 조건으로 불필요한 메모리 접근은 막지만, 연산 루프는 동일하게 돌려 쓰레기 계산(예: C += A * 0) 발생
추가 성능 튜닝 요소
점유율(Occupancy)와 대기(Latency) 숨기기
- 워프가 메모리 읽기 등 장시간 대기 시, SM은 다른 워프로 즉각 전환해 유휴 시간을 줄임(대기 숨기기, latency hiding)
- 여러 Thread Block을 동시에 할당하면, 높은 점유율로 대기 시간 최소화
- Block이나 타일 크기가 너무 커지면 resident block 수가 줄고, 점유율 저하로 성능 저하 발생
스레드 분기 최소화
- 워프 내 if-else 조건 분기가 발생하면, 두 경로를 순차적으로 실행해 효과적인 성능 절반 수준으로 감소함
- min, max 등 branchless 코드로 분기 최소화 필요
양자화(Quantization)
- FP32 → FP16/BFP16 등 정밀도 축소 시, 메모리 이동량 및 처리 가능 데이터 수가 각각 2배로 증가함
- A100 기준 FP16 연산은 312 TFLOPS(상대적으로 FP32의 19.5 TFLOPS 대비 최대 16배 성능) 도달 가능
- 양자화로 Roofline상의 AI 오른쪽(메모리 효율)과 위쪽(최고 연산 성능) 동시 달성 가능
전체 요약
- GPU 성능의 본질적 한계는 메모리 대역폭과 온칩 연산 능력의 불균형에 기인함
- 성능 향상은 데이터 재사용 극대화(타일링) 및 중간 메모리 트래픽 최소화(Fusion) 로 달성
- 하드웨어 구조(워프, 뱅크, 코얼레스드 액세스, 동기화) 특성을 이해해야 고성능 커널 작성 및 최적화가 가능함
- 실전에서는 점유율, 분기 최소화, 양자화 등 추가적인 요소가 실질적 속도에 직접적인 영향 미침
- 고성능 GPU 연산 설계는 이론적 AI 향상, 하드웨어 특성 활용, 실제 데이터 배치 및 사이즈 대응 등 복합적인 고려가 필요함