GPU 시스템 16 - Vectorized Load/Store와 Alignment
vectorized memory access와 alignment가 bandwidth 활용에 어떤 차이를 만드는지
메모리 접근 폭도 성능에 영향을 준다
coalescing을 이해한 뒤에는 한 단계 더 나아가 vectorized load/store를 보게 된다. 이는 thread 하나가 scalar 값 하나만 읽는 대신, 여러 값을 묶어 더 넓은 폭으로 읽고 쓰는 방식이다.
이 접근이 중요한 이유는 memory transaction을 더 효율적으로 맞추고 instruction 수도 줄일 수 있기 때문이다.
언제 효과가 있을까
입력 데이터가 적절히 정렬되어 있고, thread마다 연속적인 여러 값을 처리하는 구조라면 vectorized load/store는 꽤 큰 도움이 될 수 있다.
예를 들어 float4처럼 묶어서 읽으면:
- load instruction 수를 줄일 수 있고
- alignment가 맞을 경우 bandwidth 활용이 좋아질 수 있다
물론 모든 상황에서 좋은 것은 아니다.
alignment가 왜 중요할까
vectorized access는 데이터 정렬이 맞지 않으면 기대한 이득이 깨질 수 있다. 즉, 단순히 타입만 바꾼다고 해결되는 것이 아니라 실제 메모리 주소와 layout이 적절해야 한다.
이 때문에 vectorization은 커널 자체뿐 아니라 입력 tensor layout과 padding까지 함께 봐야 하는 문제다.
예를 들어 float4 기반 load를 하려면 보통 16-byte alignment가 맞아야 한다. 그런데 입력 tensor가 slice나 transpose 결과라면 겉으로는 같은 shape처럼 보여도 실제 메모리 시작 주소와 stride가 기대와 다를 수 있다. 이 경우 vectorized path를 쓰기 애매해지거나, 앞단에서 contiguous 변환이 추가로 필요해질 수 있다.
즉, vectorization은 단순한 kernel 내부 기법이 아니라 tensor layout 계약과도 연결된다.
instruction 수를 줄이는 효과도 있다
vectorized load/store의 장점은 bandwidth 활용뿐 아니라 instruction count 감소에도 있다. thread 하나가 scalar 네 개를 따로 읽는 대신 한 번에 읽으면 load instruction 수를 줄일 수 있고, 그 다음 계산에서도 묶음 단위 처리가 가능해진다.
물론 이것도 조건부다. tail 처리와 unpack 비용이 커지면 기대만큼 단순하지 않을 수 있다. 그래서 vectorization은 "항상 더 적은 instruction"이라기보다 "적절한 layout일 때 instruction 구조를 더 깔끔하게 만들 수 있는 방법"으로 보는 편이 정확하다.
tail 처리는 생각보다 중요한 디테일이다
vector width가 4인데 입력 길이가 항상 4의 배수인 것은 아니다. 이때 마지막 몇 개 원소를 어떻게 처리할지가 구현의 중요한 디테일이 된다.
보통은 다음 중 하나를 택한다.
- main path는 vectorized로 가고 tail은 scalar로 처리한다
- 입력 shape를 padding해서 tail 문제를 줄인다
- 작은 tail 오버헤드를 감수하되 main path를 최대한 빠르게 유지한다
언제 조심해야 하나
- input size가 vector width와 잘 안 맞을 때
- tail 처리 분기가 늘어날 때
- alignment가 보장되지 않을 때
- register usage가 늘어날 때
이런 상황에서는 vectorized access가 오히려 구현 복잡도만 높이고 기대보다 큰 이득이 없을 수도 있다.
여기에 하나를 더 붙이면, global memory 쪽에서 vectorized access가 좋아 보여도 shared memory에 풀어놓는 과정이나 register 사용량까지 보면 오히려 다른 제약이 생길 수 있다. 즉, vectorization은 독립된 최적화가 아니라 전체 memory path 안에서 봐야 한다.
실무에서는 어디서 자주 보이나
vectorized access는 아래 같은 kernel에서 자주 등장한다.
- normalization kernel
- elementwise fused op
- matmul 전후 packing/unpacking 성격 연산
- Triton kernel의 block load
특히 arithmetic보다 memory movement가 중요한 kernel에서는 vectorized load/store가 생각보다 실질적인 차이를 만든다.
정리
vectorized load/store는 GPU 메모리 최적화의 다음 단계다. coalescing이 기본 정돈이라면, vectorization은 그 정돈 위에서 접근 폭까지 더 잘 맞추는 작업에 가깝다.
중요한 것은 단순히 float4를 쓰는 것이 아니라:
- alignment가 맞는가
- tail 처리가 과하지 않은가
- layout이 이를 허용하는가
- register와 shared memory 구조까지 함께 괜찮은가
를 같이 보는 것이다.
다음 글에서는 register pressure와 spill을 보면서, 계산 재사용과 occupancy 사이의 긴장을 더 자세히 본다.