본문 바로가기

GPU-KERNEL

Occupancy / Register Pressure

Occupancy

한 SM 위에 동시에 올려놓을 수 있는 warp 수 / 이론적으로 가능한 최대 warp 수 

어떤 아키텍처에서 SM 당 최대 warp 수가 64라 치고,

지금 커널이 SM 하나에 평균 32 warp 까지만 올라간다면,

Occupancy = 32 / 64 = 50%

글로벌 메모리에서 데이터를 가져오는 데 수백 사이클

연재 warp 가 id.global 걸어놓거 기다리는 동안, SM 은 다른 warp 로 context switch 해서 연산을 계속 수행

즉, warp 가 많을수록 쉼 없는 연산 수행

때문에

메모리 레이턴시가 큰 커널에서는 어느 정도 이상의 occupancy 가 있어야 버틴다.

 

Occupancy 를 제한하는 요소들

한 SM 에 몇 개의 block/warp 를 올릴 수 있느냐는 여러 리소스 제한으로 결정된다. 

최대 thread/SM

최대 block/SM

레지스터 수/SM

Shared memory 용량/SM

각 block 이 요구하는 thread 수, 레지스터 수, shared memory 사용량

 

예를 들어

SM 에 레지스터가 총 65536 개 있고,

커널에서 스레드당 128개 레지스터를 쓰고,

block 크기가 256 threads 라면

그 block 하나가 먹는 레지스터 수 = 128 * 256 = 32768

SM 전체가 block 2 개 까지만 동시에 올릴 수 있음, 그이상은 레지스터 부족으로 더 못 올림

이것이 register pressure 가 occupancy 를 깎아먹음

 

Register Pressure 란 

스레드 하나가 필요로 하는 레지스터 수가너무 많다는 상황

컴파일러는 가능한 한 많은 값을 레지스터에 들고 있으려고 함

하지만 너무 많으면, 

SM 전체 레지스터를 금방 다 써버림, occupancy 하락

더 심하면 spill 발생, 레지스터에 못 넣어준 값을 local memory 로 밀어냄, 이건 추가 글로벌 메모리 로드/스토어

 

trade-off

레지스터를 많이 쓰면 - 한 스레드 입장에선 중간값들을 캐싱해서 연산은 빠른데, 전체 SM 입장에서는 올릴 수 있는 warp 수가 줄어든다.

레지스터를 아끼면 - 더 많은 warp 를 올릴 수 있음 ( 높은 occupancy ) 대신 컴파일러가 중간 계산을 다시 한다거나, 덜 최적인 코드를 만들 수 있음

 

실전 에서는

메모리 bound 커널 (간단한 elementwise, 메모리를 많이 읽는 커널 ) : 레지스터를 너무 많이 쓰게 만들지 말고, occupany 를 적당량 확보

강한 연산 / 타일링된 GEMM 같은 compute bound 커널 : 한 warp 의 효율을 높이는, 레지스터 타일을 크게 잡는다.