GPU 시스템 19 - Asynchronous Copy와 Pipelining
memory load와 compute를 더 겹치게 만드는 asynchronous copy와 double buffering의 감각
더 높은 수준의 최적화는 겹치기에서 나온다
기본적인 coalescing, shared memory, tiling을 이해한 뒤에는 계산과 memory load를 얼마나 겹칠 수 있는지가 중요해진다. 이 지점에서 asynchronous copy와 pipelining 개념이 등장한다.
아이디어는 단순하다
현재 tile을 계산하는 동안 다음 tile을 미리 가져오면 대기 시간을 줄일 수 있다. 즉, load와 compute를 순차적으로 하지 않고 겹치는 것이다.
이 구조는 흔히 double buffering 같은 방식으로 구현된다. 하나의 buffer는 현재 계산에 쓰고, 다른 buffer는 다음 데이터를 준비하는 식이다.
이 관점은 GPU 최적화를 한 단계 더 깊게 만든다. 이제는 "얼마나 적게 읽는가"뿐 아니라 "읽는 시간을 얼마나 숨기는가"를 같이 보게 되기 때문이다.
왜 asynchronous copy가 필요한가
shared memory tile 기반 kernel에서도 여전히 load 단계와 compute 단계가 분리되어 있으면 중간중간 대기가 생길 수 있다. asynchronous copy는 이 둘을 더 자연스럽게 겹칠 수 있게 도와준다.
즉, 다음 tile을 가져오는 작업이 현재 tile 계산과 병행되면 pipeline이 더 매끄러워진다.
double buffering은 어떤 감각으로 이해하면 좋을까
double buffering은 이름 그대로 buffer 두 개를 번갈아 쓰는 구조다.
- buffer A로 현재 tile 계산
- buffer B에는 다음 tile 로드
- 다음 라운드에서 역할 교체
이 방식은 hardware pipeline을 더 잘 활용하게 해준다. 중요한 것은 버퍼를 두 개 둔다는 사실보다, load와 compute를 겹쳐 stall 구간을 줄인다는 점이다.
언제 효과가 커질까
- tile 기반 matmul 계열 kernel
- memory latency가 무시되지 않는 workload
- compute가 충분히 있어 overlap 이득이 생기는 경우
반대로 workload가 너무 작거나 구조가 단순하면 이 기법의 복잡도만 늘고 실익이 크지 않을 수도 있다.
왜 고급 최적화처럼 느껴질까
asynchronous copy와 pipelining은 이제 커널을 단순한 수식 구현이 아니라 시간 흐름이 있는 시스템처럼 다루기 시작한다는 점에서 중요하다. 메모리, shared memory, register, compute pipeline이 시간 축에서 어떻게 겹치는지를 설계해야 하기 때문이다.
왜 중요한가
GPU 최적화가 깊어질수록 문제는 단순한 arithmetic가 아니라 "유닛을 얼마나 놀리지 않는가"로 바뀐다. asynchronous copy와 pipelining은 바로 이 목표를 위해 등장한다.
이 단계에서는 profiling 없이 판단하기도 더 어려워진다. overlap이 잘 되고 있는지, stall reason이 실제로 줄었는지, register/shared memory 부담이 지나치게 늘지 않았는지 확인해야 하기 때문이다.
정리
이 단계는 GPU 최적화가 점점 micro-architecture 친화적인 방향으로 간다는 것을 보여준다. 좋은 kernel은 단지 메모리를 덜 읽는 것이 아니라, 읽는 시간과 계산 시간을 더 잘 겹치게 만든다.
즉, 고급 kernel 최적화는 데이터 이동량을 줄이는 것에서 끝나지 않고, 남아 있는 이동과 계산을 얼마나 잘 orchestration하느냐로 확장된다.
다음 글에서는 Nsight Compute를 통해 이런 최적화가 실제로 어떤 지표 차이로 보이는지 확인한다.