GPU 시스템 12 - Warp Shuffle과 Warp-Level Primitive
warp 내부 데이터 교환을 shared memory 없이 처리하는 warp-level primitive의 의미
warp 내부 협업을 다시 생각해보기
지금까지 block 협업은 주로 shared memory와 synchronization을 통해 설명했다. 그런데 warp 단위로 보면 더 가벼운 협업 방법이 있다. 바로 warp-level primitive, 그중에서도 warp shuffle 계열이다.
이 기능이 중요한 이유는, 32개 thread로 이루어진 warp 내부에서는 shared memory를 거치지 않고도 값을 주고받을 수 있는 경우가 있기 때문이다.
shuffle은 무엇을 가능하게 하나
warp shuffle은 warp 내부 thread의 register 값을 다른 lane으로 전달하는 식의 연산을 가능하게 한다. 쉽게 말하면 같은 warp 안에서 "내 값 하나를 옆 thread와 직접 교환하거나 참조한다"는 흐름을 더 가볍게 만들 수 있다.
이 방식은 reduction, scan, 일부 communication pattern에서 매우 유용하다.
왜 shared memory보다 나을 수 있을까
shared memory를 쓰면 보통:
- 값을 shared memory에 저장하고
- synchronization을 하고
- 다시 읽어와야 한다
반면 warp 내부에서 해결 가능한 문제라면 shuffle 기반 접근은 이 경로를 줄일 수 있다. 즉, 더 적은 메모리 왕복과 더 적은 synchronization으로 협업할 수 있다.
reduction에서 특히 자주 보이는 이유
warp-level reduction은 shuffle의 대표 사용처다. 예를 들어 32개 thread가 각각 partial sum을 하나씩 들고 있다면, 이를 단계적으로 합쳐 warp 하나의 합으로 줄일 수 있다.
이 과정은 shared memory 기반 reduction보다 더 가볍고 간결한 경우가 많다. 그래서 modern CUDA kernel에서는 block-level reduction 안에서도 마지막 단계는 warp primitive로 처리하는 경우가 흔하다.
모든 것을 shuffle로 해결할 수는 없다
물론 warp-level primitive는 warp 범위 안에서만 자연스럽다. block 전체 협업이나 더 큰 범위의 조정은 여전히 shared memory나 더 높은 수준의 synchronization이 필요하다.
즉, 좋은 접근은 보통 이렇게 된다.
- warp 내부는 shuffle로 최대한 가볍게
- block 전체는 shared memory와 적절한 synchronization으로
이런 layered approach가 reduction이나 softmax 같은 커널에서 자주 보인다.
왜 이 개념이 중요한가
warp shuffle을 이해하면 "협업 = shared memory"라는 단순한 그림에서 벗어날 수 있다. GPU 최적화는 종종 같은 목적을 더 낮은 오버헤드로 달성하는 방법을 찾는 작업인데, shuffle은 그 대표적인 예다.
정리
warp-level primitive는 GPU 최적화에서 중요한 중간 계층이다.
- thread 하나보다 큰 협업을 제공하고
- shared memory보다 가볍게 동작할 수 있으며
- reduction 같은 핵심 패턴에서 큰 의미가 있다
다음 글에서는 reduction kernel 자체를 조금 더 깊게 보면서 block-level reduction과 warp-level reduction이 어떻게 결합되는지 본다.