오늘 끝나면
GPU 메모리와 coalescing
- ✓GPU 메모리와 coalescing의 핵심 문제를 한 문장으로 설명한다
- ✓오른쪽 실습에서 GPU이 어떻게 움직이는지 관찰한다
- ✓다음 강의와 이어지는 한계를 말할 수 있다
실습 미션
인접 접근을 한 transaction으로 — bank conflict·tiling 이 문장이 실제로 무슨 뜻인지 실습에서 한 번 손으로 확인한다.
성공 조건
- □실습의 기본값을 먼저 관찰
- □입력값이나 모드를 한 번 이상 바꿔 결과 비교
- □왜 결과가 바뀌었는지 한 문장으로 설명
반도체 설계 · 30
GPU 메모리와
coalescing
GPU의 진짜 병목은 연산이 아니라 메모리다.
워프 한 무리가 한꺼번에 주소를 읽는다.
그 주소가 붙어 있느냐 흩어졌느냐가 속도를 가른다.
global은 멀고, shared는 가깝다
GPU 메모리는 한 덩어리가 아니다. 멀고 큰 global과 가깝고 작은 shared로 나뉜다.
global은 칩 밖 DRAM이다. 수 GB로 크지만 멀어서 한 번 다녀오는 데 수백 사이클이 든다.
모든 thread가 같은 global을 본다. 대신 갈 때마다 비싸다.
shared는 SM 안에 박힌 작은 메모리다. scratchpad라고도 부른다.
블록 안 thread들이 같이 쓰는 손바닥만 한 칠판이다. 빠르지만 수십 KB뿐이다.
global에서 한 번 끌어온 데이터를 shared에 올려 재사용하는 것이 GPU 최적화의 절반이다. 이 scratchpad가 그대로 최종 MyChip에 들어간다.
수 GB · 모든 thread 공유 · 갈 때마다 비싸다
global에서 끌어와 올려두고 재사용하는 것이 scratchpad다
coalescing, 인접 주소를 한 번에 묶기
메모리는 한 바이트씩 못 준다. 항상 128바이트 정렬 구간을 통째로 실어 온다.
워프가 한 명령에서 16(실제론 32)개 thread로 동시에 주소를 읽는다.
그 주소들이 같은 128B 구간에 떨어지면 하드웨어가 한 번의 전송으로 묶는다. 이것이 coalescing이다.
thread tid가 base + tid*4를 읽으면 16개가 64B 안에 다 들어가고 transaction은 1개가 된다.
주소가 흩어지면 구간마다 따로 실어 와야 하고, transaction이 최대 thread 수만큼 늘어난다.
같은 16개 값을 읽는데 메모리 전송이 16배가 된다. 이것이 GPU에서 느려지는 첫 번째 이유다.
bank conflict, shared도 공짜가 아니다
shared로 옮겼다고 끝이 아니다. shared에도 동시 접근의 함정이 있다.
shared 메모리는 여러 bank로 잘게 인터리브돼 있다. 연속한 word가 bank 0, 1, 2로 번갈아 들어간다.
한 사이클에 bank마다 1개 word를 내보내므로, thread들이 서로 다른 bank를 두드리면 16개가 동시에 읽힌다.
그런데 여러 thread가 같은 bank를 동시에 두드리면 어떻게 될까. bank는 한 번에 하나만 줄 수 있어 순서대로 나눠 처리한다. 이것이 bank conflict다.
N개가 한 bank에 몰리면 N-way conflict가 되고, N step으로 직렬화돼 N배 느려진다.
그래서 stride를 bank 수의 약수로 두지 않거나, 배열에 padding 한 칸을 끼워 충돌을 비켜간다.
직접 주소를 깔아 transaction을 센다
16-thread 워프의 접근 패턴을 연속, stride, 흩어짐으로 바꿔본다.
연속이면 transaction 1개로 묶여 효율이 가장 높고, 흩어지면 16개까지 늘어 효율이 바닥을 친다. 효율은 쓸모 바이트를 전송 바이트로 나눈 값이다.
shared 모드에서는 stride에 따라 같은 bank로 몰리는 thread가 막대로 쌓이며 N-way conflict가 보인다. stride를 1로 두면 충돌이 풀린다.
여기서 고른 접근 방식과 scratchpad 사용이 그대로 MyChip의 메모리 경로가 된다.
실행을 누르면 16개 thread 주소가 128B segment로 묶여 transaction 수와 효율이 뜬다.
여기서 본 scratchpad(shared), 재사용할 타일을 bank 충돌 없이 깔아두는 칸이 최종 MyChip에 들어간다.
tiling, 한 번 끌어와 여러 번 쓴다
coalescing과 bank를 다 챙겨도 같은 데이터를 매번 global에서 다시 끌어오면 헛수고다.
핵심은 재사용이다. 큰 배열을 shared에 들어갈 만한 작은 타일로 쪼갠다.
타일 하나를 coalesced하게 global에서 한 번 읽어 shared에 올려두고, 블록 안 thread들이 그 타일을 여러 번 우려먹는다.
행렬 곱이 대표 예다. 타일을 shared에 올리면 한 번 읽은 값을 행과 열이 공유해 global 접근이 수십 배 줄어든다.
coalescing으로 잘 읽고, shared 재사용으로 덜 읽고, bank 회피로 빨리 읽는다. 이 셋이 GPU 메모리 최적화의 핵심이다.
Q. global 메모리에서 coalescing이 잘 되는 접근 패턴은?
정답은 인접한(연속된) 주소 접근이다.워프의 thread들이 base + tid*4처럼 나란히 붙은 주소를 읽으면 16개가 같은 128B 구간에 떨어져 transaction 한 번으로 묶인다.
주소가 흩어지면 구간마다 따로 실어 와야 해 transaction이 thread 수만큼 늘고 효율이 떨어진다.