Mojo ๐ฅ GPU Puzzles, Edition 1
โ์ฐ๋ฆฌ๊ฐ ํ ์ ์๊ธฐ ์ ์ ๋ฐฐ์์ผ ํ๋ ๊ฒ๋ค์, ํ๋ฉด์ ๋ฐฐ์ด๋ค.โ ์๋ฆฌ์คํ ํ ๋ ์ค (๋์ฝ๋ง์ฝ์ค ์ค๋ฆฌํ)
Mojo ๐ฅ๋ฅผ ์ฌ์ฉํ GPU ํ๋ก๊ทธ๋๋ฐ ์ค์ต ๊ฐ์ด๋์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค. Mojo๋ ํ์ด์ฌ ๋ฌธ๋ฒ๊ณผ ์์คํ ์์ค์ ์ฑ๋ฅ์ ๊ฒฐํฉํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋๋ค.
์๋ ๊ฐ์ ์์์ ๋จผ์ ์์ฒญํ๊ฑฐ๋, ๊ณ์ ์ฝ์ด์ฃผ์ธ์.
์ GPU ํ๋ก๊ทธ๋๋ฐ์ธ๊ฐ?
GPU ํ๋ก๊ทธ๋๋ฐ์ ์ ๋ฌธ ๊ธฐ์ ์์ ํ๋ ์ปดํจํ ์ ํต์ฌ ์ธํ๋ผ๋ก ๋ฐ์ ํ์ต๋๋ค. ์์ญ์ต ๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ๋ถํฐ ์ค์๊ฐ ์์ ์คํธ๋ฆผ์ ๋ถ์ํ๋ ์ปดํจํฐ ๋น์ ์์คํ ๊น์ง, GPU ๊ฐ์์ด ์ค๋๋ ์ ์ฐ์ฐ ํ์ ์ ์ด๋๊ณ ์์ต๋๋ค. ๊ธฐํ ๋ชจ๋ธ๋ง, ์ ์ฝ ๋ฐ๊ฒฌ, ์์ ์๋ฎฌ๋ ์ด์ ๋ฑ ๊ณผํ์ ๋ฐ์ ์ GPU๋ง์ด ์ ๊ณตํ๋ ๋๊ท๋ชจ ๋ณ๋ ฌ ์ฒ๋ฆฌ ๋ฅ๋ ฅ์ ์์กดํ๊ณ ์์ต๋๋ค. ๊ธ์ต ๊ธฐ๊ด์ ์ค์๊ฐ ๋ฆฌ์คํฌ ๋ถ์๊ณผ ์๊ณ ๋ฆฌ์ฆ ํธ๋ ์ด๋ฉ์ GPU ์ปดํจํ ์ ํ์ฉํ๋ฉฐ, ์์จ์ฃผํ ์ฐจ๋์ GPU ๊ฐ์ ์ ๊ฒฝ๋ง์ ํตํด ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ์ฌ ์ค์ํ ์์ฌ๊ฒฐ์ ์ ๋ด๋ฆฝ๋๋ค.
๊ฒฝ์ ์ ํ๊ธ๋ ฅ๋ ์๋นํฉ๋๋ค. GPU ์ปดํจํ ์ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์กฐ์ง์ ๊ฐ๋ฐ ์ฃผ๊ธฐ ๋จ์ถ, ์ฐ์ฐ ๋น์ฉ ์ ๊ฐ, ๊ทธ๋ฆฌ๊ณ ์ด์ ์๋ ํ๊ธฐ ์ด๋ ค์ ๋ ๊ณ์ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฅ๋ ฅ ๋ฑ ์๋นํ ๊ฒฝ์ ์ฐ์๋ฅผ ํ๋ณดํฉ๋๋ค. ๊ณ์ฐ ๋ฅ๋ ฅ์ด ๋น์ฆ๋์ค ๊ฐ์น์ ์ง๊ฒฐ๋๋ ์๋์, GPU ํ๋ก๊ทธ๋๋ฐ ์ญ๋์ ์์ง๋์ด, ์ฐ๊ตฌ์, ์กฐ์ง์๊ฒ ์ ๋ต์ ์ฐจ๋ณํ ์์์ ๋๋ค.
์ GPU ํ๋ก๊ทธ๋๋ฐ์ Mojo๐ฅ๋ฅผ ์ฌ์ฉํ๋๊ฐ?
์ปดํจํ ์ฐ์ ์ ์ค๋ํ ์ ํ์ ์ ๋๋ฌํ์ต๋๋ค. CPU ์ฑ๋ฅ์ ์ ๋ ฅ๊ณผ ๋ฐ์ด ์ ์ฝ์ผ๋ก ์ธํด ํด๋ญ ์๋ ํฅ์๋ง์ผ๋ก๋ ํ๊ณ์ ์ด๋ฅด๋ ์ต๋๋ค. ์ด์ ๋ฐ๋ผ ํ๋์จ์ด ์ ์กฐ์ฌ๋ค์ ๋ฌผ๋ฆฌ์ ์ฝ์ด ์๋ฅผ ๋๋ฆฌ๋ ๋ฐฉํฅ์ผ๋ก ๋์๊ฐ๊ณ , ์ด๋ฌํ ๋ฉํฐ์ฝ์ด ์ ๊ทผ ๋ฐฉ์์ ์ ์ ์ด ๋ฐ๋ก ์์ฒ ๊ฐ์ ์ฝ์ด๊ฐ ๋ณ๋ ฌ๋ก ๋์ํ๋ ํ๋ GPU์ ๋๋ค. NVIDIA H100์ ์๋ก ๋ค๋ฉด, ๋จ์ผ ํด๋ญ ์ฌ์ดํด์ 16,896๊ฐ์ ์ค๋ ๋๋ฅผ ๋์์ ์คํํ๋ฉด์ 270,000๊ฐ ์ด์์ ์ค๋ ๋๋ฅผ ๋๊ธฐ์ํฌ ์ ์์ต๋๋ค.
Mojo๋ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ํ ์ค์ฉ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํ์ฌ, ์ด๋ฌํ ๋ณ๋ ฌ์ฑ์ ๋ ์ฝ๊ฒ ํ์ฉํ ์ ์๊ฒ ํฉ๋๋ค:
- ํ์ด์ฌ ์คํ์ผ ๋ฌธ๋ฒ์ผ๋ก ์์คํ ํ๋ก๊ทธ๋๋ฐ๊น์ง
- ์ถ์ํํด๋ ์ฑ๋ฅ ์์ค ์์ด ๋จธ์ ์ฝ๋๋ก ์ปดํ์ผ๋๋ ์ ๋ก ์ฝ์คํธ ์ถ์ํ
- ์ปดํ์ผ ํ์์ ์ค๋ฅ๋ฅผ ์ก๋ ๊ฐ๋ ฅํ ํ์ ์์คํ
- ํ๋์จ์ด ์ต์ ํ๋ฅผ ๊ณ ๋ คํ ํ ์ ๊ธฐ๋ณธ ์ง์
- CPUยทGPU ๋ด์ฅ ํจ์๋ฅผ ์ง์ ํธ์ถํ ์ ์๋ ํ๋์จ์ด ์ง์ ์ ์ด
- CPU์ GPU ๋ชจ๋์์ ๋์ํ๋ ํฌ๋ก์ค ํ๋์จ์ด ์ด์์ฑ
- C/C++ ๋๋น ํฅ์๋ ์์ ์ฑ
- ๋ฎ์ ์ง์ ์ฅ๋ฒฝ์ผ๋ก ๋ ๋ง์ ํ๋ก๊ทธ๋๋จธ๊ฐ GPU ์ฑ๋ฅ์ ํ์ฉ
Mojo๐ฅ๋ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋๊ตฌ๋ ํ ์ ์๋๋ก ๋ง๋ค์ด ํ์ ์ ์ด๋๊ณ ์ ํฉ๋๋ค. >์ต์ํ ํ์ด์ฌ ๋ฌธ๋ฒ์ ๋ฐํ์ผ๋ก GPU์ ์ง์ ์ ๊ทผํ ์ ์์ด, ๊น์ ์ ๋ฌธ ์ง์ ์์ด๋ CPU์ GPU๋ฅผ ํจ๊ป ํ์ฉํ๋ ๊ณ ์ฑ๋ฅ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ต๋๋ค.
์ ํผ์ฆ๋ก ๋ฐฐ์ฐ๋๊ฐ?
๋๋ถ๋ถ์ GPU ํ๋ก๊ทธ๋๋ฐ ์๋ฃ๋ ์ค์ต์ ์์ ๋ฐฉ๋ํ ์ด๋ก ์ ๋จผ์ ๋ค๋ฃน๋๋ค. ํ์ง๋ง ์ง์ ํด๋ด์ผ ์ดํด๋๋ ์ถ์์ ๊ฐ๋ ๋ค์ ์ ๋ฌธ์์๊ฒ ๋ถ๋ด์ด ๋ ์ ์์ต๋๋ค.
์ด ์ฑ ์ ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ํํฉ๋๋ค. ์ค์ ๋ฌธ์ ์ ๋ฐ๋ก ๋ฐ์ด๋ค์ด, ๋จ๊ณ์ ์ผ๋ก ๊ฐ๋ ์ ๋ฐ๊ฒฌํด ๋๊ฐ๋๋ค.
ํผ์ฆ ๊ธฐ๋ฐ ํ์ต์ ์ฅ์ :
- ์ง์ ์ฒดํ: GPU์์ ๋ฐ๋ก ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค
- ์ ์ง์ ๋ณต์ก๋: ๊ฐ ํผ์ฆ์ด ์ด์ ์ ๋ฐฐ์ด ๊ฐ๋ ์์ ์์ฌ๊ฐ๋๋ค
- ์ค์ฉ์ ์ด์ : ์ค์ ๊ณ์ฐ ๋ฌธ์ ๋ฅผ ๋ฐ์ํ ํผ์ฆ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค
- ๋๋ฒ๊น ๋ฅ๋ ฅ: ์ฒด๊ณ์ ์ธ ๋๋ฒ๊น ์ฐ์ต์ ํตํด ๋ฌธ์ ํด๊ฒฐ ๊ฐ๊ฐ์ ํค์๋๋ค
- ์ง์ ์ ์ฐฉ: ์ง์ ํ์ด๋ณด๋ ๊ฒ์ด ์ฝ๊ธฐ๋ง ํ๋ ๊ฒ๋ณด๋ค ์ดํด๊ฐ ๋ ๊น์ด์ง๋๋ค
์๊ธฐ๊ฐ ์๋ ๋ฐ๊ฒฌ์ ์ค์ ์ ๋ก๋๋ค. ์ง์ ์คํํ๋ฉด์ ๊ฐ๋ ์ ์์ฐ์ค๋ฝ๊ฒ ์ตํ๊ณ , ๊น์ ์ดํด์ ์ค์ ์ญ๋์ ํจ๊ป ์์๊ฐ ์ ์์ต๋๋ค.
๊ฐ์ฌ์ ๋ง: ์ด ์ฑ ์ Part I๊ณผ III์ ์ธํฐ๋ํฐ๋ธ NVIDIA GPU ํ์ต ํ๋ก์ ํธ์ธ GPU Puzzles์์ ํฐ ์๊ฐ์ ๋ฐ์์ต๋๋ค. ์ด ์ฑ ์ ํด๋น ๊ฐ๋ ๋ค์ Mojo์ ์ถ์ํ์ ์ฑ๋ฅ์ ํ์ฉํ์ฌ ์ฌ๊ตฌํํ๊ณ , Mojo์ ํนํ๋ ์ต์ ํ๋ก ๊ณ ๊ธ ์ฃผ์ ๋ฅผ ๋ ๋๊ฒ ๋ค๋ฃน๋๋ค.
GPU ํ๋ก๊ทธ๋๋ฐ ์ฌ๊ณ ๋ฐฉ์
ํจ๊ณผ์ ์ธ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ํด์๋ ๊ณ์ฐ์ ๋ฐ๋ผ๋ณด๋ ๋ฐฉ์ ์์ฒด๋ฅผ ๋ฐ๊ฟ์ผ ํฉ๋๋ค. ์์ผ๋ก์ ํ์ต์ ๊ธธ์ก์ด๊ฐ ๋ ํต์ฌ ์ฌ๊ณ ๋ชจ๋ธ์ ์๊ฐํฉ๋๋ค:
์์ฐจ์์ ๋ณ๋ ฌ๋ก: ๋ฐ๋ณต๋ฌธ์ ์ค๋ ๋๋ก ๋์ฒด
๊ธฐ์กด CPU ํ๋ก๊ทธ๋๋ฐ์์๋ ๋ฐ๋ณต๋ฌธ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ์์๋๋ก ์ฒ๋ฆฌํฉ๋๋ค:
# CPU ๋ฐฉ์
for i in range(data_size):
result[i] = process(data[i])
GPU ํ๋ก๊ทธ๋๋ฐ์ ์ด ๋ฐฉ์์ ์์ ํ ๋ค์ง์ต๋๋ค. ๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ์ํํ๋ ๋์ , ์์ฒ ๊ฐ์ ์ค๋ ๋๋ฅผ ํ ๋นํ์ฌ ๋ฐ์ดํฐ ์์๋ฅผ ๋์์ ์ฒ๋ฆฌํฉ๋๋ค:
# GPU ๋ฐฉ์ (๊ฐ๋
์ )
thread_id = get_global_id()
if thread_id < data_size:
result[thread_id] = process(data[thread_id])
๊ฐ ์ค๋ ๋๊ฐ ํ๋์ ๋ฐ์ดํฐ ์์๋ฅผ ๋งก์ ์ฒ๋ฆฌํ๋ฏ๋ก, ๋ช ์์ ์ธ ๋ฐ๋ณต๋ฌธ์ด ๋๊ท๋ชจ ๋ณ๋ ฌ ์คํ์ผ๋ก ๋ฐ๋๋๋ค. ์์ฐจ ์ฒ๋ฆฌ์์ ๋์ ์คํ์ผ๋ก์ ์ ํ์ด GPU ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ ๊ฐ๋ ์ ๋๋ค.
๋ฐ์ดํฐ ์์ ์ฐ์ฐ ๊ทธ๋ฆฌ๋ ๋ง์ถ๊ธฐ
๋ฐ์ดํฐ๋ฅผ ๊ตฌ์กฐํ๋ ๊ทธ๋ฆฌ๋๋ก, GPU ์ค๋ ๋๊ฐ ์ด์ ๋์ํ๋ ์ฐ์ฐ ๊ทธ๋ฆฌ๋๋ฅผ ํ์ฑํ๋ค๊ณ ์๊ฐํด ๋ณด์ธ์. ํจ๊ณผ์ ์ธ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ด ์ค๋ ๋ ๊ตฌ์ฑ์ ์ ์ค๊ณํ์ฌ ๋ฐ์ดํฐ ๊ณต๊ฐ์ ์ต์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋๋ค:
- ์ค๋ ๋: ๊ฐ๊ฐ ํน์ ๋ฐ์ดํฐ ์์๋ฅผ ๋ด๋นํ๋ ๊ฐ๋ณ ์ฒ๋ฆฌ ๋จ์
- ๋ธ๋ก: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๊ณผ ๋๊ธฐํ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์ค๋ ๋ ๊ทธ๋ฃน
- ๊ทธ๋ฆฌ๋: ์ ์ฒด ๊ณ์ฐ ๋ฌธ์ ๋ฅผ ์์ฐ๋ฅด๋ ์ค๋ ๋ ๊ณ์ธต ๊ตฌ์กฐ
GPU ํ๋ก๊ทธ๋๋ฐ์ ์ํ๋ ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ๋๊ธฐํ ์๊ตฌ์ฌํญ์ ๊ด๋ฆฌํ๋ฉด์ ๋ณ๋ ฌ ํจ์จ์ ์ต๋ํ ๋์ด์ฌ๋ฆฌ๋๋ก ์ด ์ค๋ ๋ ๊ตฌ์ฑ์ ๊ท ํ์ ์ก์์ผ ํฉ๋๋ค.
๋ฐ์ดํฐ ์ด๋ vs. ์ฐ์ฐ
GPU ํ๋ก๊ทธ๋๋ฐ์์๋ ์ฐ์ฐ ์์ฒด๋ณด๋ค ๋ฐ์ดํฐ๋ฅผ ์ฎ๊ธฐ๋ ๋น์ฉ์ด ๋ ํด ๋๊ฐ ๋ง์ต๋๋ค:
- CPU์ GPU ๊ฐ ๋ฐ์ดํฐ ์ด๋์ ๋๋ฆฝ๋๋ค
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก์ ์ด๋์ ๊ทธ๋ณด๋ค ๋น ๋ฆ ๋๋ค
- ๋ ์ง์คํฐ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ด๋ฏธ ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ๊ฒ์ ๋งค์ฐ ๋น ๋ฆ ๋๋ค
์ด๋ ํ๋ก๊ทธ๋๋ฐ์์ ํํ ๊ฐ์ง๋ ๊ฐ์ ์ ๋ค์ง์ต๋๋ค. ๋ณ๋ชฉ์ ์ฐ์ฐ์ด ์๋๋ผ ๋ฐ์ดํฐ ์ด๋์ ๋๋ค.
์ด ์ฑ ์ ํผ์ฆ๋ค์ ํ์ด๊ฐ๋ฉด์ ์ด๋ฌํ ์์น์ ์ง๊ด์ ์ผ๋ก ์ฒด๋ํ๊ณ , ๊ณ์ฐ ๋ฌธ์ ์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๋ฐ๊ฟ ๋๊ฐ ์ ์์ต๋๋ค.
ํ์ต ๋ด์ฉ
์ด ์ฑ ์ ๊ธฐ์ด ์๋ฆฌ๋ถํฐ ๊ณ ๊ธ GPU ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ๊น์ง ๋ค๋ฃน๋๋ค. GPU๋ฅผ ์ ์ ์๋ ๋ธ๋๋ฐ์ค๋ก ๋์ง ์๊ณ , ๊ฐ๋ณ ์ค๋ ๋์ ๋์๋ถํฐ ์์ํ์ฌ ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ๊น์ง ๋จ๊ณ๋ณ๋ก ์ดํด๋ฅผ ์์๊ฐ๋๋ค. ์ ์์ค ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๊ณ ์์ค ํ ์ ์ถ์ํ๋ฅผ ๋ชจ๋ ๋ฐฐ์์ผ๋ก์จ, ์ด๋ค GPU ํ๋ก๊ทธ๋๋ฐ ๊ณผ์ ์๋ ์ ์ฐํ๊ฒ ๋์ํ ์ ์๊ฒ ๋ฉ๋๋ค.
ํ์ฌ ํ์ต ๊ณผ์
| ํต์ฌ ๊ธฐ์ | ์ํ | ํผ์ฆ |
|---|---|---|
| ์ค๋ ๋/๋ธ๋ก ๊ธฐ์ด | โ ์ ๊ณต ์ค | Part I (1-8) |
| GPU ํ๋ก๊ทธ๋จ ๋๋ฒ๊น | โ ์ ๊ณต ์ค | Part II (9-10) |
| ํต์ฌ ์๊ณ ๋ฆฌ์ฆ | โ ์ ๊ณต ์ค | Part III (11-16) |
| MAX ๊ทธ๋ํ ํตํฉ | โ ์ ๊ณต ์ค | Part IV (17-19) |
| PyTorch ํตํฉ | โ ์ ๊ณต ์ค | Part V (20-22) |
| ํจ์ํ ํจํด ๋ฐ ๋ฒค์น๋งํน | โ ์ ๊ณต ์ค | Part VI (23) |
| ์ํ ํ๋ก๊ทธ๋๋ฐ | โ ์ ๊ณต ์ค | Part VII (24-26) |
| ๋ธ๋ก ์์ค ํ๋ก๊ทธ๋๋ฐ | โ ์ ๊ณต ์ค | Part VIII (27) |
| ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ | โ ์ ๊ณต ์ค | Part IX (28-29) |
| ์ฑ๋ฅ ๋ถ์ | โ ์ ๊ณต ์ค | Part X (30-32) |
| ์ต์ GPU ๊ธฐ๋ฅ | โ ์ ๊ณต ์ค | Part XI (33-34) |
์์ธ ํ์ต ๋ชฉํ
Part I: GPU ๊ธฐ์ด (ํผ์ฆ 1-8) โ
- ์ค๋ ๋ ์ธ๋ฑ์ฑ๊ณผ ๋ธ๋ก ๊ตฌ์ฑ ๋ฐฐ์ฐ๊ธฐ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ๊ฐ๋ ์ดํดํ๊ธฐ
- ์์ ํฌ์ธํฐ์ TileTensor ์ถ์ํ ๋ชจ๋ ๋ค๋ค๋ณด๊ธฐ
- ์ค๋ ๋ ๊ฐ ํต์ ์ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ์ด ์ตํ๊ธฐ
Part II: GPU ํ๋ก๊ทธ๋จ ๋๋ฒ๊น (ํผ์ฆ 9-10) โ
- GPU ๋๋ฒ๊ฑฐ์ ๋๋ฒ๊น ๊ธฐ๋ฒ ๋ฐฐ์ฐ๊ธฐ
- ์๋ํ์ด์ ๋ก ๋ฉ๋ชจ๋ฆฌ ์ค๋ฅ์ ๊ฒฝ์ ์ํ ์ฐพ๊ธฐ
- GPU ๋ฒ๊ทธ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์๋ณํ๊ณ ์์ ํ๊ธฐ
- ๋ณต์กํ GPU ํ๋ก๊ทธ๋๋ฐ ๊ณผ์ ์ ๋์ ํ ์์ ๊ฐ ์๊ธฐ
์ฐธ๊ณ : ๋๋ฒ๊น ํผ์ฆ์ ์คํํ๋ ค๋ฉด NVIDIA GPU ๋๋ฒ๊น ๋๊ตฌ ์ ๊ทผ์ ์ํ
pixi๊ฐ ํ์ํฉ๋๋ค. CUDA๋ฅผ ์ง์ํ๋ NVIDIA GPU์์๋ง ์๋ํฉ๋๋ค.
Part III: GPU ์๊ณ ๋ฆฌ์ฆ (ํผ์ฆ 11-16) โ
- ๋ณ๋ ฌ ๋ฆฌ๋์ ๊ณผ ํ๋ง ์ฐ์ฐ ๊ตฌํํ๊ธฐ
- ํจ์จ์ ์ธ ํฉ์ฑ๊ณฑ ์ปค๋ ๋ง๋ค๊ธฐ
- ๋์ ํฉ(์ค์บ) ์๊ณ ๋ฆฌ์ฆ ๋ฐฐ์ฐ๊ธฐ
- ํ์ผ๋ง ์ ๋ต์ผ๋ก ํ๋ ฌ ๊ณฑ์ ์ต์ ํํ๊ธฐ
Part IV: MAX ๊ทธ๋ํ ํตํฉ (ํผ์ฆ 17-19) โ
- ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ ๋ง๋ค๊ธฐ
- GPU ์ปค๋๊ณผ ํ์ด์ฌ ์ฝ๋ ์ฐ๊ฒฐํ๊ธฐ
- ์ํํธ๋งฅ์ค, ์ดํ ์ ๊ฐ์ ํ๋ก๋์ ์์ค์ ์ฐ์ฐ ๊ตฌํํ๊ธฐ
Part V: PyTorch ํตํฉ (ํผ์ฆ 20-22) โ
- Mojo GPU ์ปค๋๊ณผ PyTorch ํ ์ ์ฐ๊ฒฐํ๊ธฐ
- CustomOpLibrary๋ก ํ ์ ๋ง์ฌ๋ง์ ๋งค๋๋ฝ๊ฒ ์ฒ๋ฆฌํ๊ธฐ
- torch.compile๊ณผ ํตํฉํ์ฌ ์คํ ์ต์ ํํ๊ธฐ
- ์ปค๋ ํจ์ ๊ณผ ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค ๋ฐฐ์ฐ๊ธฐ
Part VI: Mojo ํจ์ํ ํจํด ๋ฐ ๋ฒค์น๋งํน (ํผ์ฆ 23) โ
- ํจ์ํ ํจํด ๋ฐฐ์ฐ๊ธฐ: elementwise, tiled ์ฒ๋ฆฌ, vectorization
- ์ฒด๊ณ์ ์ธ ์ฑ๋ฅ ์ต์ ํ์ ํธ๋ ์ด๋์คํ ์ตํ๊ธฐ
- ์ ๋์ ๋ฒค์น๋งํน์ผ๋ก ์ฑ๋ฅ ๋ถ์ํ๊ธฐ
- GPU ์ค๋ ๋ฉ vs SIMD ์คํ ๊ณ์ธต ๊ตฌ์กฐ ์ดํดํ๊ธฐ
Part VII: ์ํ ์์ค ํ๋ก๊ทธ๋๋ฐ (ํผ์ฆ 24-26) โ
- ์ํ ๊ธฐ์ด์ SIMT ์คํ ๋ชจ๋ธ ๋ฐฐ์ฐ๊ธฐ
- ํต์ฌ ์ํ ์ฐ์ฐ ์ตํ๊ธฐ: sum, shuffle_down, broadcast
- shuffle_xor์ prefix_sum์ผ๋ก ๊ณ ๊ธ ํจํด ๊ตฌํํ๊ธฐ
- ์ํ ํ๋ก๊ทธ๋๋ฐ๊ณผ ํจ์ํ ํจํด์ ํจ๊ณผ์ ์ผ๋ก ๊ฒฐํฉํ๊ธฐ
Part VIII: ๋ธ๋ก ์์ค ํ๋ก๊ทธ๋๋ฐ (ํผ์ฆ 27) โ
block.sum()๊ณผblock.max()๋ก ๋ธ๋ก ๋จ์ ๋ฆฌ๋์ ๋ฐฐ์ฐ๊ธฐ- ๋ธ๋ก ์์ค ๋์ ํฉ ํจํด๊ณผ ํต์ ์ตํ๊ธฐ
block.broadcast()๋ก ๋ธ๋ก ๋ด ์กฐ์จ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๊ธฐ
Part IX: ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์์คํ (ํผ์ฆ 28-29) โ
- ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ํจํด ๊ตฌํํ๊ธฐ
- ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ผ๋ก ์ฐ์ฐ๊ณผ ์ ์ก์ ๊ฒน์ณ ์ง์ฐ ์๊ฐ ์จ๊ธฐ๊ธฐ
- ๋ฉ๋ชจ๋ฆฌ ํ์ค์ ๋๊ธฐํ ๊ธฐ๋ณธ ์์ ๋ฐฐ์ฐ๊ธฐ
- ํ๋ฆฌํ์นญ๊ณผ ์บ์ ์ต์ ํ ์ ๋ต ์ตํ๊ธฐ
Part X: ์ฑ๋ฅ ๋ถ์ ๋ฐ ์ต์ ํ (ํผ์ฆ 30-32) โ
- GPU ์ปค๋ ํ๋กํ์ผ๋ง์ผ๋ก ๋ณ๋ชฉ ์ง์ ์ฐพ๊ธฐ
- ์ ์ ์จ๊ณผ ๋ฆฌ์์ค ํ์ฉ๋ ์ต์ ํํ๊ธฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋ ์ ๊ฑฐํ๊ธฐ
Part XI: ๊ณ ๊ธ GPU ๊ธฐ๋ฅ (ํผ์ฆ 33-34) โ
- AI ์ํฌ๋ก๋๋ฅผ ์ํ ํ ์ ์ฝ์ด ํ๋ก๊ทธ๋๋ฐ ๋ฐฐ์ฐ๊ธฐ
- ํ๋ GPU์ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๋ฐฐ์ฐ๊ธฐ
์ด ์ฑ ์ ๊ธฐ์กด ๋ฐฉ์๊ณผ ๋ฌ๋ฆฌ, ๋จผ์ ์ ์์ค ๋ฉ๋ชจ๋ฆฌ ์กฐ์์ผ๋ก ์ดํด๋ฅผ ์์ ๋ค ์ ์ง์ ์ผ๋ก Mojo์ TileTensor ์ถ์ํ๋ก ์ ํํฉ๋๋ค. ์ด๋ฅผ ํตํด GPU ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๋ํ ๊น์ ์ดํด์ ํ๋์ ํ ์ ๊ธฐ๋ฐ ์ ๊ทผ๋ฒ์ ์ค์ฉ์ ์ง์์ ๋ชจ๋ ์ป์ ์ ์์ต๋๋ค.
์์ํ ์ค๋น๊ฐ ๋์ จ๋์?
GPU ํ๋ก๊ทธ๋๋ฐ์ด ์ ์ค์ํ์ง, Mojo๊ฐ ์ ์ ํฉํ์ง, ๊ทธ๋ฆฌ๊ณ ํผ์ฆ๋ก ์ด๋ป๊ฒ ๋ฐฐ์ฐ๋์ง ์ดํด๋ณด์์ต๋๋ค. ์ด์ ์์ํด ๋ด ์๋ค.
๋ค์ ๋จ๊ณ: ํผ์ฆ ์ฌ์ฉ ๊ฐ์ด๋์์ ํ๊ฒฝ ์ค์ , ์์คํ ์๊ตฌ์ฌํญ, ์ฒซ ๋ฒ์งธ ํผ์ฆ ์คํ ๋ฐฉ๋ฒ์ ํ์ธํ์ธ์.
ํผ์ฆ ์ฌ์ฉ ๊ฐ์ด๋
๊ฐ ํผ์ฆ์ ๋จ๊ณ์ ์ผ๋ก ์ค๋ ฅ์ ์์ ์ ์๋๋ก ๋ค์๊ณผ ๊ฐ์ ์ผ๊ด๋ ๊ตฌ์กฐ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค:
- ๊ฐ์: ๋ฌธ์ ์ ์์ ํต์ฌ ๊ฐ๋ ์๊ฐ
- ๊ตฌ์ฑ: ๊ธฐ์ ์ ์ค์ ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ ์ค๋ช
- ์์ฑํ ์ฝ๋:
problems/pXX/์ ์ฑ์์ผ ํ ๋ถ๋ถ์ด ํ์๋ ๊ตฌํ ํ ํ๋ฆฟ - ํํธ: ํ์ํ ๋ ์ฐธ๊ณ ํ ์ ์๋ ์ ๋ต์ ํํธ๋ก, ์ ๋ต์ ์ง์ ์๋ ค์ฃผ์ง ์์ต๋๋ค
- ํ์ด: ์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ๊ณผ ๊ฐ๋ ์ค๋ช ์ ํฌํจํ ์ข ํฉ ๋ถ์
ํผ์ฆ์ ์ด์ ์ ๋ฐฐ์ด ๊ฐ๋ ์์ ์๋ก์ด ๊ฐ๋ ์ ์์๊ฐ๋ฉฐ ์ ์ฐจ ๋ณต์กํด์ง๋๋ค. ๊ณ ๊ธ ํผ์ฆ์ ์์ ํผ์ฆ์ ๊ฐ๋ ์ ์๊ณ ์๋ค๊ณ ๊ฐ์ ํ๋ฏ๋ก, ์์๋๋ก ํ์ด๋๊ฐ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์ฝ๋ ์คํํ๊ธฐ
๋ชจ๋ ํผ์ฆ์๋ ๊ตฌํ ๊ฒฐ๊ณผ๋ฅผ ์์ ๊ฒฐ๊ณผ์ ๋น๊ตํด์ฃผ๋ ํ ์คํธ ํ๋ ์์ํฌ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค. ๊ฐ ํผ์ฆ๋ณ๋ก ์คํ ๋ฐฉ๋ฒ๊ณผ ๊ฒ์ฆ ์ ์ฐจ๊ฐ ์๋ด๋ฉ๋๋ค.
์ฌ์ ์ค๋น
์์คํ ์๊ตฌ์ฌํญ
๋จผ์ ์์คํ ์ด ์์คํ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ๋์ง ํ์ธํ์ธ์.
์ง์๋๋ GPU
ํผ์ฆ์ ์คํํ๋ ค๋ฉด
์ง์๋๋ GPU๊ฐ ํ์ํฉ๋๋ค.
ํ๊ฒฝ ์ค์ ์ ๋ง์น ๋ค ์๋ ํ๊ฒฝ ์ค์ ์ gpu-specs ๋ช
๋ น์ด๋ก GPU ํธํ์ฑ์ ํ์ธํ ์
์์ต๋๋ค.
์ด์์ฒด์
[!NOTE] ์ด์์ฒด์ ๋ณ GPU ์ง์ ์ค์ ๋ฐฉ๋ฒ์ ์๋ดํฉ๋๋ค.
Windows WSL2 for Linux with NVIDIA
Windows Subsystem for Linux(WSL2, ์: Ubuntu)์์ NVIDIA GPU๋ฅผ ์ค์ ํ๋ ค๋ฉด NVIDIA CUDA on WSL ๊ฐ์ด๋๋ฅผ ์ฐธ๊ณ ํ์ธ์.
ํต์ฌ์ Windows์ฉ NVIDIA CUDA ๋๋ผ์ด๋ฒ๋ฅผ ์ค์นํ๋ ๊ฒ์ ๋๋ค. ์ด ๋๋ผ์ด๋ฒ๊ฐ WSL2๋ฅผ ์๋ฒฝํ ์ง์ํฉ๋๋ค. Windows์ NVIDIA GPU ๋๋ผ์ด๋ฒ๋ฅผ ์ค์นํ๋ฉด WSL 2 ์์์ CUDA๋ฅผ ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. Windows ํธ์คํธ์ CUDA ๋๋ผ์ด๋ฒ๊ฐ WSL 2 ๋ด๋ถ์์ libcuda.so๋ก ์คํ (stub) ์ฒ๋ฆฌ๋๋ฏ๋ก, WSL 2 ์์ ๋ณ๋์ NVIDIA GPU Linux ๋๋ผ์ด๋ฒ๋ฅผ ์ค์นํด์๋ ์ ๋ฉ๋๋ค.
๋๋ผ์ด๋ฒ ์ค์น ํ ์ ์ ๋์์ ํ์ธํฉ๋๋ค.
Windows์์ ํ์ธ: PowerShell์ ์ฝ๋๋ค (WSL์ด ์๋๋๋ค)
nvidia-smi
WSL ๋ด๋ถ์์ ํ์ธ: (๋จผ์ WSL์ ์์ํฉ๋๋ค. ์: wsl -d Ubuntu)
ls -l /usr/lib/wsl/lib/nvidia-smi
/usr/lib/wsl/lib/nvidia-smi
Pixi์์ ์ค์ ์ ํ์ธํ๊ณ , ํ์์ ๋๋ฝ๋ ์๊ตฌ์ฌํญ์ ์ค์นํฉ๋๋ค (์: cuda-gdb ๋๋ฒ๊น ์ฉ)
pixi run nvidia-smi
pixi run setup-cuda-gdb
pixi run mojo debug --help
pixi run cuda-gdb --version
WSL์์๋ VS Code๋ฅผ ์๋ํฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
- Windows์์ https://code.visualstudio.com/์ ํตํด VS Code๋ฅผ ์ค์นํฉ๋๋ค.
- ๊ทธ๋ฐ ๋ค์ Remote - WSL ํ์ฅ์ ์ค์นํฉ๋๋ค.
[!NOTE] ํผ์ฆ 1-15๋ ๋ชจ๋ WSL๊ณผ Linux์์ ์๋ํฉ๋๋ค.
Linux native with NVIDIA
๋จผ์ GPU์ Ubuntu ๋ฒ์ ์ ํ์ธํฉ๋๋ค (์ง์๋๋ Ubuntu LTS: 20.04, 22.04, 24.04)
lspci | grep -i nvidia
lsb_release -a
NVIDIA ๋๋ผ์ด๋ฒ๋ฅผ ์ค์นํฉ๋๋ค (ํ์)
sudo ubuntu-drivers devices
sudo ubuntu-drivers autoinstall
sudo reboot
Linux์์๋ VS Code๋ฅผ ์๋ํฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. VS Code APT ์ ์ฅ์๋ฅผ ํตํด ์ค์นํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Microsoft GPG ํค ๊ฐ์ ธ์ค๊ธฐ
wget -qO- https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor \
| sudo tee /usr/share/keyrings/packages.microsoft.gpg > /dev/null
VS Code APT ์ ์ฅ์ ์ถ๊ฐ
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] \
https://packages.microsoft.com/repos/code stable main" \
| sudo tee /etc/apt/sources.list.d/vscode.list
VS Code ์ค์น ๋ฐ ํ์ธ
sudo apt update
sudo apt install code
code --version
[!NOTE] ํผ์ฆ 1-15๋ ๋ชจ๋ Linux์์ ์๋ํฉ๋๋ค.
macOS Apple Silicon
osx-arm64 ์ฌ์ฉ์๋ ๋ค์์ด ํ์ํฉ๋๋ค:
- macOS 15.0 ์ด์ โ ์ต์ ํธํ์ฑ์ ์ํด ๊ถ์ฅ๋ฉ๋๋ค.
pixi run check-macos๋ก ํ์ธํ๊ณ , ์คํจํ๋ฉด ์ ๊ทธ๋ ์ด๋ํ์ธ์. - Xcode 16 ์ด์ โ ์ต์ ์๊ตฌ์ฌํญ์
๋๋ค.
xcodebuild -version์ผ๋ก ํ์ธํฉ๋๋ค.
xcrun -sdk macosx metal ์คํ ์
cannot execute tool 'metal' due to missing Metal toolchain ์ค๋ฅ๊ฐ ๋ํ๋๋ฉด
๋ค์์ ์คํํฉ๋๋ค.
xcodebuild -downloadComponent MetalToolchain
์ดํ xcrun -sdk macosx metal์ ๋ค์ ์คํํ๋ฉด no input files error๊ฐ ๋ํ๋์ผ
์ ์์
๋๋ค.
[!NOTE] ํ์ฌ ํผ์ฆ 1-8๊ณผ 11-15๊ฐ macOS์์ ์๋ํฉ๋๋ค. ๋ ๋ง์ ํผ์ฆ ์ง์์ ์ค๋นํ๊ณ ์์ต๋๋ค!
ํ๋ก๊ทธ๋๋ฐ ์ง์
๋ค์์ ๋ํ ๊ธฐ๋ณธ์ ์ธ ์ดํด๊ฐ ์์ผ๋ฉด ์ข์ต๋๋ค:
- ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด (๋ณ์, ๋ฐ๋ณต๋ฌธ, ์กฐ๊ฑด๋ฌธ, ํจ์)
- ๋ณ๋ ฌ ์ปดํจํ ๊ฐ๋ (์ค๋ ๋, ๋๊ธฐํ, ๊ฒฝ์ ์ํ)
- Mojo ๊ธฐ๋ณธ ๋ฌธ๋ฒ (ํฌ์ธํฐ ์ ๋ฌธ ์น์ ํฌํจ)
- GPU ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด๋ฅผ ๋ฏธ๋ฆฌ ์ฝ์ด๋๋ฉด ๋์์ด ๋ฉ๋๋ค!
GPU ํ๋ก๊ทธ๋๋ฐ ๊ฒฝํ์ด ์์ด๋ ๊ด์ฐฎ์ต๋๋ค! ํผ์ฆ์ ํ์ด๊ฐ๋ฉฐ ์์ฐ์ค๋ฝ๊ฒ ์ตํ ์ ์์ต๋๋ค.
Mojo๐ฅ์ ํจ๊ป GPU ์ปดํจํ ์ ์ธ๊ณ๋ก ๋ ๋๋ด ์๋ค!
ํ๊ฒฝ ์ค์ ํ๊ธฐ
-
GitHub ์ ์ฅ์๋ฅผ ํด๋ก ํ๊ณ ํด๋น ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํฉ๋๋ค:
# ์ ์ฅ์ ํด๋ก git clone https://github.com/modular/mojo-gpu-puzzles cd mojo-gpu-puzzles -
Mojo๐ฅ ํ๋ก๊ทธ๋จ์ ์คํํ๊ธฐ ์ํ ํจํค์ง ๋งค๋์ ๋ฅผ ์ค์นํฉ๋๋ค:
์ต์ 1 (๊ฐ๋ ฅ ์ถ์ฒ): pixi
์ด ํ๋ก์ ํธ์์ `pixi`๋ฅผ **๊ถ์ฅํ๋ ์ด์ **๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- Modular์ MAX/Mojo ํจํค์ง์ ์ฝ๊ฒ ์ ๊ทผ ๊ฐ๋ฅ
- GPU ์์กด์ฑ์ ์๋์ผ๋ก ์ฒ๋ฆฌ
- conda + PyPI ์ํ๊ณ๋ฅผ ๋ชจ๋ ์ง์
> **์ฐธ๊ณ : ์ผ๋ถ ํผ์ฆ์ `pixi`์์๋ง ์๋ํฉ๋๋ค**
**์ค์น:**
```bash
curl -fsSL https://pixi.sh/install.sh | sh
```
**์
๋ฐ์ดํธ:**
```bash
pixi self-update
```
์ต์
2: uv
**์ค์น:**
```bash
curl -fsSL https://astral.sh/uv/install.sh | sh
```
**์
๋ฐ์ดํธ:**
```bash
uv self update
```
**๊ฐ์ ํ๊ฒฝ ์์ฑ:**
```bash
uv venv && source .venv/bin/activate
```
- ์ค์ ์ ํ์ธํ๊ณ ์ฒซ ๋ฒ์งธ ํผ์ฆ์ ์คํํฉ๋๋ค:
# GPU ์ฌ์ ํ์ธ
pixi run gpu-specs
# ์ฒซ ๋ฒ์งธ ํผ์ฆ ์คํ
# ์์ง ๊ตฌํ ์ ์ด๋ฏ๋ก ์คํจํฉ๋๋ค! ๋ณธ๋ฌธ์ ๋ฐ๋ผ ๊ตฌํํด ๋ณด์ธ์
pixi run p01
# GPU ์ฌ์ ํ์ธ
pixi run gpu-specs
# ์ฒซ ๋ฒ์งธ ํผ์ฆ ์คํ
# ์์ง ๊ตฌํ ์ ์ด๋ฏ๋ก ์คํจํฉ๋๋ค! ๋ณธ๋ฌธ์ ๋ฐ๋ผ ๊ตฌํํด ๋ณด์ธ์
pixi run -e amd p01
# GPU ์ฌ์ ํ์ธ
pixi run gpu-specs
# ์ฒซ ๋ฒ์งธ ํผ์ฆ ์คํ
# ์์ง ๊ตฌํ ์ ์ด๋ฏ๋ก ์คํจํฉ๋๋ค! ๋ณธ๋ฌธ์ ๋ฐ๋ผ ๊ตฌํํด ๋ณด์ธ์
pixi run -e apple p01
# GPU๋ณ ์์กด์ฑ ์ค์น
uv pip install -e ".[nvidia]" # NVIDIA GPU์ฉ
# ๋๋
uv pip install -e ".[amd]" # AMD GPU์ฉ
# GPU ์ฌ์ ํ์ธ
uv run poe gpu-specs
# ์ฒซ ๋ฒ์งธ ํผ์ฆ ์คํ
# ์์ง ๊ตฌํ ์ ์ด๋ฏ๋ก ์คํจํฉ๋๋ค! ๋ณธ๋ฌธ์ ๋ฐ๋ผ ๊ตฌํํด ๋ณด์ธ์
uv run poe p01
ํผ์ฆ ํ๊ธฐ
ํ๋ก์ ํธ ๊ตฌ์กฐ
problems/: ํ์ด๋ฅผ ์ง์ ๊ตฌํํ๋ ๊ณณ์ ๋๋ค (์ฌ๊ธฐ์ ์์ ํฉ๋๋ค!)solutions/: ๋น๊ต์ ํ์ต์ ์ํ ์ฐธ๊ณ ํ์ด์ ๋๋ค. ์ฑ ์ ๋ฐ์ ๊ฑธ์ณ ํ์ฉ๋ฉ๋๋ค
์์ ํ๋ฆ
problems/pXX/์์ ํผ์ฆ ํ ํ๋ฆฟ์ ์ฝ๋๋ค- ์ ๊ณต๋ ํ๋ ์์ํฌ ์์ ํ์ด๋ฅผ ์์ฑํฉ๋๋ค
- ๊ตฌํ์ ํ
์คํธํฉ๋๋ค:
pixi run pXX๋๋uv run poe pXX(ํ๋ซํผ์ ๋ฐ๋ผ-e platform์ ์ถ๊ฐํฉ๋๋ค. ์:-e amd) solutions/pXX/์ ์ฐธ๊ณ ํ์ด์ ๋น๊ตํ๋ฉฐ ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ๋ฐฐ์๋๋ค
์ฃผ์ ๋ช ๋ น์ด
# ํผ์ฆ ์คํ (ํ์์ -e๋ก ํ๋ซํผ ์ง์ )
pixi run pXX # NVIDIA (๊ธฐ๋ณธ๊ฐ) `pixi run -e nvidia pXX`์ ๋์ผ
pixi run -e amd pXX # AMD GPU
pixi run -e apple pXX # Apple GPU
# ํ์ด ํ
์คํธ
pixi run tests # ๋ชจ๋ ํ์ด ํ
์คํธ
pixi run tests pXX # ํน์ ํผ์ฆ ํ
์คํธ
# ์๋ ์คํ
pixi run mojo problems/pXX/pXX.mojo # ๋ด ๊ตฌํ
pixi run mojo solutions/pXX/pXX.mojo # ์ฐธ๊ณ ํ์ด
# ์ธํฐ๋ํฐ๋ธ ์
ธ
pixi shell # ํ๊ฒฝ ์ง์
mojo problems/p01/p01.mojo # ์ง์ ์คํ
exit # ์
ธ ์ข
๋ฃ
# ๊ฐ๋ฐ
pixi run format # ์ฝ๋ ํฌ๋งทํ
pixi task list # ์ฌ์ฉ ๊ฐ๋ฅํ ๋ช
๋ น์ด
# ์ฐธ๊ณ : uv๋ ์ ํ์ ์ด๋ฉฐ ์ผ๋ถ ์ฑํฐ๋ pixi๊ฐ ํ์ํฉ๋๋ค
# GPU๋ณ ์์กด์ฑ ์ค์น:
uv pip install -e ".[nvidia]" # NVIDIA GPU์ฉ
uv pip install -e ".[amd]" # AMD GPU์ฉ
# ํ์ด ํ
์คํธ
uv run poe tests # ๋ชจ๋ ํ์ด ํ
์คํธ
uv run poe tests pXX # ํน์ ํผ์ฆ ํ
์คํธ
# ์๋ ์คํ
uv run mojo problems/pXX/pXX.mojo # ๋ด ๊ตฌํ
uv run mojo solutions/pXX/pXX.mojo # ์ฐธ๊ณ ํ์ด
GPU ์ง์ ํํฉ
์๋ ํ๋ ํผ์ฆ๋ณ GPU ํ๋ซํผ ํธํ์ฑ์ ์ ๋ฆฌํ ๊ฒ์ ๋๋ค. ํผ์ฆ์ ๋ฐ๋ผ ํ์ํ GPU ๊ธฐ๋ฅ๊ณผ ๋ฒค๋๋ณ ๋๊ตฌ๊ฐ ๋ค๋ฆ ๋๋ค.
| ํผ์ฆ | NVIDIA GPU | AMD GPU | Apple GPU | ๋น๊ณ |
|---|---|---|---|---|
| Part I: GPU ๊ธฐ์ด | ||||
| 1 - Map | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 2 - Zip | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 3 - ๊ฐ๋ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 4 - Map 2D | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 5 - ๋ธ๋ก๋์บ์คํธ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 6 - ๋ธ๋ก | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 7 - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 8 - ์คํ ์ค | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| Part II: ๋๋ฒ๊น | ||||
| 9 - GPU ๋๋ฒ๊ฑฐ | โ | โ | โ | NVIDIA ์ ์ฉ ๋๋ฒ๊น ๋๊ตฌ |
| 10 - ์๋ํ์ด์ | โ | โ | โ | NVIDIA ์ ์ฉ ๋๋ฒ๊น ๋๊ตฌ |
| Part III: GPU ์๊ณ ๋ฆฌ์ฆ | ||||
| 11 - ๋ฆฌ๋์ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 12 - ์ค์บ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 13 - ํ๋ง | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 14 - ํฉ์ฑ๊ณฑ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 15 - ํ๋ ฌ ๊ณฑ์ | โ | โ | โ | ๊ธฐ๋ณธ GPU ์ปค๋ |
| 16 - Flashdot | โ | โ | โ | ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ํจํด |
| Part IV: MAX ๊ทธ๋ํ | ||||
| 17 - ์ปค์คํ Op | โ | โ | โ | MAX ๊ทธ๋ํ ํตํฉ |
| 18 - ์ํํธ๋งฅ์ค | โ | โ | โ | MAX ๊ทธ๋ํ ํตํฉ |
| 19 - ์ดํ ์ | โ | โ | โ | MAX ๊ทธ๋ํ ํตํฉ |
| Part V: PyTorch ํตํฉ | ||||
| 20 - Torch ๋ธ๋ฆฟ์ง | โ | โ | โ | PyTorch ํตํฉ |
| 21 - ์คํ ๊ทธ๋๋ | โ | โ | โ | PyTorch ํตํฉ |
| 22 - ํจ์ | โ | โ | โ | PyTorch ํตํฉ |
| Part VI: ํจ์ํ ํจํด | ||||
| 23 - ํจ์ํ | โ | โ | โ | ๊ณ ๊ธ Mojo ํจํด |
| Part VII: ์ํ ํ๋ก๊ทธ๋๋ฐ | ||||
| 24 - ์ํ ํฉ๊ณ | โ | โ | โ | ์ํ ์์ค ์ฐ์ฐ |
| 25 - ์ํ ํต์ | โ | โ | โ | ์ํ ์์ค ์ฐ์ฐ |
| 26 - ๊ณ ๊ธ ์ํ | โ | โ | โ | ์ํ ์์ค ์ฐ์ฐ |
| Part VIII: ๋ธ๋ก ํ๋ก๊ทธ๋๋ฐ | ||||
| 27 - ๋ธ๋ก ์ฐ์ฐ | โ | โ | โ | ๋ธ๋ก ๋จ์ ํ๋ก๊ทธ๋๋ฐ ํจํด |
| Part IX: ๋ฉ๋ชจ๋ฆฌ ์์คํ | ||||
| 28 - ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ | โ | โ | โ | ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ |
| 29 - ๋ฐฐ๋ฆฌ์ด | โ | โ | โ | NVIDIA ์ ์ฉ ๊ณ ๊ธ ๋๊ธฐํ |
| Part X: ์ฑ๋ฅ ๋ถ์ | ||||
| 30 - ํ๋กํ์ผ๋ง | โ | โ | โ | NVIDIA ํ๋กํ์ผ๋ง ๋๊ตฌ (NSight) |
| 31 - ์ ์ ์จ | โ | โ | โ | NVIDIA ํ๋กํ์ผ๋ง ๋๊ตฌ |
| 32 - ๋ฑ ํฌ ์ถฉ๋ | โ | โ | โ | NVIDIA ํ๋กํ์ผ๋ง ๋๊ตฌ |
| Part XI: ์ต์ GPU ๊ธฐ๋ฅ | ||||
| 33 - ํ ์ ์ฝ์ด | โ | โ | โ | NVIDIA ํ ์ ์ฝ์ด ์ ์ฉ |
| 34 - ํด๋ฌ์คํฐ | โ | โ | โ | NVIDIA ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ |
๋ฒ๋ก
- โ ์ง์: ํด๋น ํ๋ซํผ์์ ํผ์ฆ์ด ์๋ํฉ๋๋ค
- โ ๋ฏธ์ง์: ํ๋ซํผ๋ณ ๊ณ ์ ๊ธฐ๋ฅ์ด ํ์ํฉ๋๋ค
ํ๋ซํผ๋ณ ์ฐธ๊ณ ์ฌํญ
NVIDIA GPU (์ ์ฒด ์ง์)
- ๋ชจ๋ ํผ์ฆ(1-34)์ด CUDA๋ฅผ ์ง์ํ๋ NVIDIA GPU์์ ์๋ํฉ๋๋ค
- CUDA ํดํท๊ณผ ํธํ ๋๋ผ์ด๋ฒ๊ฐ ํ์ํฉ๋๋ค
- ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ด ๊ฐ์ฅ ์์ ํ ํ์ต ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค
AMD GPU (ํญ๋์ ์ง์)
- ๋๋ถ๋ถ์ ํผ์ฆ(1-8, 11-29)์ด ROCm์ ํตํด ์๋ํฉ๋๋ค
- ๋ฏธ์ง์: ๋๋ฒ๊น ๋๊ตฌ(9-10), ํ๋กํ์ผ๋ง(30-32), ํ ์ ์ฝ์ด(33-34)
- ๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ฉ๋ชจ๋ฆฌ ํจํด๊น์ง ํฌํจํ์ฌ GPU ํ๋ก๊ทธ๋๋ฐ์ ํญ๋๊ฒ ํ์ตํ ์ ์์ต๋๋ค
Apple GPU (๊ธฐ๋ณธ ์ง์)
- ๊ธฐ์ด(1-8, 11-18) ๋ฐ ๊ณ ๊ธ(23-27) ํผ์ฆ ์ผ๋ถ๋ฅผ ์ง์ํฉ๋๋ค
- ๋ฏธ์ง์: ๊ณ ๊ธ ๊ธฐ๋ฅ ์ ๋ฐ, ๋๋ฒ๊น , ํ๋กํ์ผ๋ง ๋๊ตฌ
- GPU ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ๋ณธ ํจํด์ ์ตํ๊ธฐ์ ์ ํฉํฉ๋๋ค
ํฅํ ์ง์ ๊ณํ: AMD ๋ฐ Apple GPU์ ๋ํ ๋๊ตฌ์ ํ๋ซํผ ์ง์์ ๊พธ์คํ ํ๋ํ๊ณ ์์ต๋๋ค. ๋๋ฒ๊น ๋๊ตฌ, ํ๋กํ์ผ๋ง ๊ธฐ๋ฅ, ๊ณ ๊ธ GPU ์ฐ์ฐ ๋ฑ ์์ง ์ง์๋์ง ์๋ ๊ธฐ๋ฅ์ ํฅํ ๋ฆด๋ฆฌ์ค์ ํฌํจ๋ ์์ ์ ๋๋ค. ํฌ๋ก์ค ํ๋ซํผ ํธํ์ฑ์ ๊ณ์ ๊ฐ์ ํ๊ณ ์์ผ๋ ์ ๋ฐ์ดํธ๋ฅผ ํ์ธํด ์ฃผ์ธ์.
GPU ๋ฆฌ์์ค
๋ฌด๋ฃ ํด๋ผ์ฐ๋ GPU ํ๋ซํผ
๋ก์ปฌ GPU๊ฐ ์๋ค๋ฉด, ๋ฌด๋ฃ๋ก GPU๋ฅผ ์ฌ์ฉํ ์ ์๋ ํด๋ผ์ฐ๋ ํ๋ซํผ์ ํ์ฉํ ์ ์์ต๋๋ค:
Google Colab
Google Colab์ ๋ฌด๋ฃ GPU ์ ๊ทผ์ ์ ๊ณตํ์ง๋ง, Mojo GPU ํ๋ก๊ทธ๋๋ฐ์๋ ์ผ๋ถ ์ ํ์ด ์์ต๋๋ค:
์ฌ์ฉ ๊ฐ๋ฅํ GPU:
- Tesla T4 (๊ตฌ์ธ๋ Turing ์ํคํ ์ฒ)
- Tesla V100 (์ ํ์ ๊ฐ์ฉ)
Mojo GPU Puzzles ์ฌ์ฉ ์ ์ ํ์ฌํญ:
- ๊ตฌ์ธ๋ GPU ์ํคํ ์ฒ: T4 GPU๋ ๊ณ ๊ธ Mojo GPU ๊ธฐ๋ฅ๊ณผ ํธํ๋์ง ์์ ์ ์์ต๋๋ค
- ์ธ์ ์๊ฐ ์ ํ: ์ต๋ 12์๊ฐ ์คํ ํ ์๋์ผ๋ก ์ฐ๊ฒฐ์ด ๋๊น๋๋ค
- ์ ํ์ ๋๋ฒ๊น ์ง์: NVIDIA ๋๋ฒ๊น ๋๊ตฌ(ํผ์ฆ 9-10)๋ฅผ ์์ ํ ์ฌ์ฉํ์ง ๋ชปํ ์ ์์ต๋๋ค
- ํจํค์ง ์ค์น ์ ํ: Mojo/MAX ์ค์น ์ ์ฐํ ๋ฐฉ๋ฒ์ด ํ์ํ ์ ์์ต๋๋ค
- ์ฑ๋ฅ ์ ํ: ๊ณต์ ์ธํ๋ผ ํน์ฑ์ ์ผ๊ด๋ ๋ฒค์น๋งํน์ด ์ด๋ ต์ต๋๋ค
์ถ์ฒ ์ฉ๋: ๊ธฐ๋ณธ GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ (ํผ์ฆ 1-8, 11-15)๊ณผ ๊ธฐ์ด ํจํด ํ์ต.
Kaggle Notebooks
Kaggle์ Colab๋ณด๋ค ๋๋ํ ๋ฌด๋ฃ GPU ์ฌ์ฉ ์๊ฐ์ ์ ๊ณตํฉ๋๋ค:
์ฌ์ฉ ๊ฐ๋ฅํ GPU:
- Tesla T4 (์ฃผ๋น 30์๊ฐ ๋ฌด๋ฃ)
- P100 (์ ํ์ ๊ฐ์ฉ)
Colab ๋๋น ์ฅ์ :
- ๋๋ํ ์๊ฐ: Colab์ ์ผ์ผ ์ธ์ ์ ํ๊ณผ ๋ฌ๋ฆฌ ์ฃผ๋น 30์๊ฐ ์ฌ์ฉ ๊ฐ๋ฅ
- ์๋ ์ ์ฅ: ๋ ธํธ๋ถ์ด ์๋์ผ๋ก ์ ์ฅ๋ฉ๋๋ค
- ์์ ์ ์ธ ํ๊ฒฝ: ํจํค์ง ์ค์น๊ฐ ๋ ์์ ์ ์ ๋๋ค
Mojo GPU Puzzles ์ฌ์ฉ ์ ์ ํ์ฌํญ:
- GPU ์ํคํ ์ฒ ์ ์ฝ: T4์ ๊ณ ๊ธ ๊ธฐ๋ฅ ํธํ์ฑ ๋ฌธ์ ๋ Colab๊ณผ ๋์ผ
- ์ ํ์ ๋๋ฒ๊น ๋๊ตฌ: NVIDIA ํ๋กํ์ผ๋ง ๋ฐ ๋๋ฒ๊น ๋๊ตฌ(ํผ์ฆ 9-10, 30-32) ์ฌ์ฉ ๋ถ๊ฐ
- Mojo ์ค์น ๋ณต์ก์ฑ: Mojo ํ๊ฒฝ์ ์๋์ผ๋ก ์ค์ ํด์ผ ํฉ๋๋ค
- ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๋ฏธ์ง์: ๊ณ ๊ธ ํผ์ฆ(33-34) ์๋ ๋ถ๊ฐ
์ถ์ฒ ์ฉ๋: ๊ธฐ๋ณธ GPU ํ๋ก๊ทธ๋๋ฐ(ํผ์ฆ 1-16)์ ์ฅ์๊ฐ์ ๊ฑธ์ณ ํ์ตํ ๋ ์ ํฉํฉ๋๋ค.
๊ถ์ฅ ์ฌํญ
- ์ ์ฒด ํ์ต ๊ณผ์ : NVIDIA GPU๊ฐ ์์ผ๋ฉด ๋ชจ๋ ํผ์ฆ์ ํ์ตํ ์ ์์ต๋๋ค (์ ์ฒด 34๊ฐ)
- ํญ๋์ ํ์ต: AMD GPU๋ก๋ ๋๋ถ๋ถ์ ๋ด์ฉ์ ๋ค๋ฃฐ ์ ์์ต๋๋ค (34๊ฐ ์ค 27๊ฐ)
- ๊ธฐ์ด ํ์ต: Apple GPU๋ก ๊ธฐ๋ณธ ๊ฐ๋ ์ ์ตํ ์ ์์ต๋๋ค (34๊ฐ ์ค 13๊ฐ)
- ๋ฌด๋ฃ ํ๋ซํผ ํ์ต: Google Colab/Kaggle๋ก ๊ธฐ์ด~์ค๊ธ ๊ฐ๋ ๊น์ง ํ์ต ๊ฐ๋ฅํฉ๋๋ค (ํผ์ฆ 1-16)
- ๋๋ฒ๊น ๋ฐ ํ๋กํ์ผ๋ง: ๋๋ฒ๊น ๋๊ตฌ์ ์ฑ๋ฅ ๋ถ์์๋ NVIDIA GPU๊ฐ ํ์ํฉ๋๋ค
- ์ต์ GPU ๊ธฐ๋ฅ: ํ ์ ์ฝ์ด์ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ์๋ NVIDIA GPU๊ฐ ํ์ํฉ๋๋ค
๊ฐ๋ฐ
์์ธํ ๋ด์ฉ์ README๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ปค๋ฎค๋ํฐ ์ฐธ์ฌํ๊ธฐ
์ปค๋ฎค๋ํฐ์์ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ํด ์ด์ผ๊ธฐํ๊ณ , ํ์ด๋ฅผ ๊ณต์ ํ๊ณ , ์๋ก ๋์์ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค.
๐ ๋ณด์์ ๋ฐ์๊ฐ์ธ์
ํผ์ฆ์ ๋ชจ๋ ํ์ด๋ณด์ จ๋์? ์ฌ๋ฌ๋ถ์ ๋์ ์ ์ถํํ๋ฉฐ ๋ฌด๋ฃ ์คํฐ์ปค ํฉ์ ์ ๋ฌผ๋ก ๋๋ ค์!
๋ฌด๋ฃ ์คํฐ์ปค๋ฅผ ๋ฐ๋ ๋ฐฉ๋ฒ:
- GitHub ์ ์ฅ์ https://github.com/modular/mojo-gpu-puzzles๋ฅผ Forkํฉ๋๋ค
- ํผ์ฆ ์๋ฃจ์ ์ ์์ฑํด์ ์ถ๊ฐํฉ๋๋ค
- ์ด ์์์ผ๋ก ์ ์ถํ๋ฉด Modular ํ์ ์คํฐ์ปค๋ฅผ ๋ณด๋ด๋๋ ค์!
ํ์ฌ๋ ๋ถ๋ฏธ ์ง์ญ์ผ๋ก๋ง ๋ฐฐ์ก์ด ๊ฐ๋ฅํฉ๋๋ค. ๋ค๋ฅธ ์ง์ญ์ ๊ณ์ ๋ถ๋ค๋ ์๋ฃจ์ ์ ์ ์ถํด ์ฃผ์ธ์ โ ๋ฐฐ์ก ๋ฒ์๋ฅผ ๋ํ๊ฐ๊ณ ์์ผ๋, ๊ฐ๋ฅํด์ง๋ฉด ๊ผญ ๋ณด์์ ๋ณด๋ด๋๋ฆด๊ฒ์.
Puzzle 1: Map
๊ฐ์
์ด ํผ์ฆ์์๋ GPU ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ๊ธฐ๋ณธ ๊ฐ๋
์ ๋ค๋ฃน๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ ์์
ํ๋๋ฅผ ๋งก์ ๋์์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋ฐฐ์๋๋ค. ๋ฒกํฐ a์ ๊ฐ ์์์ 10์ ๋ํด
output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ๊ฐ ์์น๋ง๋ค ์ค๋ ๋ 1๊ฐ๊ฐ ๋ฐฐ์ ๋ฉ๋๋ค.
ํต์ฌ ๊ฐ๋
- GPU ์ปค๋์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
- ์ค๋ ๋์ ๋ฐ์ดํฐ ๊ฐ ์ผ๋์ผ ๋งคํ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- GPU์์์ ๋ฐฐ์ด ์ฐ์ฐ
๊ฐ ์์น \(i\)์ ๋ํด: \[\Large output[i] = a[i] + 10\]
๋ค๋ฃจ๋ ๋ด์ฉ
๐ฐ ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์
์ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ค๋ฃจ๋ฉฐ GPU์ ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ์ตํ๋๋ค.
๐ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ: TileTensor๋ฅผ ํ์ฉํ ํ๋์ ๋ฐฉ์
TileTensor๊ฐ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ด๋ป๊ฒ ๋จ์ํํ๋์ง ์ดํด๋ด ๋๋ค. ๋ ์์ ํ๊ณ ๊น๋ํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
๐ก ํ: ๋ ๋ฐฉ์์ ๋ชจ๋ ์ตํ๋ฉด ํ๋์ ์ธ GPU ํ๋ก๊ทธ๋๋ฐ ํจํด์ ๋ ๊น์ด ์ดํดํ ์ ์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ฐ๋ ๋ด์ฉ:
-
๊ธฐ๋ณธ GPU ์ปค๋ ๊ตฌ์กฐ
-
thread_idx.x๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ์ธ๋ฑ์ฑ -
๊ฐ๋จํ ๋ณ๋ ฌ ์ฐ์ฐ
-
๋ณ๋ ฌ์ฑ: ๊ฐ ์ค๋ ๋๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋ฉ๋๋ค
-
์ค๋ ๋ ์ธ๋ฑ์ฑ:
i = thread_idx.x์์น์ ์์์ ์ ๊ทผํฉ๋๋ค -
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
a[i]์์ ์ฝ๊ณoutput[i]์ ์๋๋ค -
๋ฐ์ดํฐ ๋ ๋ฆฝ์ฑ: ๊ฐ ์ถ๋ ฅ์ ํด๋น ์ ๋ ฅ์๋ง ์์กดํฉ๋๋ค
์์ฑํ ์ฝ๋
comptime SIZE = 4
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = SIZE
comptime dtype = DType.float32
def add_10(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
):
var i = thread_idx.x
# FILL ME IN (roughly 1 line)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p01/p01.mojo
ํ
thread_idx.x๋ฅผi์ ์ ์ฅํฉ๋๋คa[i]์ 10์ ๋ํฉ๋๋ค- ๊ฒฐ๊ณผ๋ฅผ
output[i]์ ์ ์ฅํฉ๋๋ค
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p01
pixi run -e amd p01
pixi run -e apple p01
uv run poe p01
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
์๋ฃจ์
def add_10(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
):
var i = thread_idx.x
output[i] = a[i] + 10.0
์ด ์๋ฃจ์ ์:
i = thread_idx.x๋ก ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ ธ์ต๋๋ค- ์
๋ ฅ๊ฐ์ 10์ ๋ํฉ๋๋ค:
output[i] = a[i] + 10.0
์ TileTensor๋ฅผ ๊ณ ๋ คํด์ผ ํ ๊น์?
์๋ ๊ธฐ์กด ๊ตฌํ์ ๋ณด๋ฉด ๋ช ๊ฐ์ง ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ ์ ์์ต๋๋ค:
ํ์ฌ ๋ฐฉ์
i = thread_idx.x
output[i] = a[i] + 10.0
1D ๋ฐฐ์ด์์๋ ์ ์๋ํ์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์๋ ์ด๋จ๊น์?
- 2D๋ 3D ๋ฐ์ดํฐ๋ฅผ ๋ค๋ค์ผ ํ ๋
- ๋ค์ํ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ ์ฒ๋ฆฌํด์ผ ํ ๋
- ๋ณํฉ(coalesced) ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๋ณด์ฅํด์ผ ํ ๋
์์ผ๋ก์ ๋์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
ํผ์ฆ์ ์งํํ๋ฉด์ ๋ฐฐ์ด ์ธ๋ฑ์ฑ์ ์ ์ ๋ณต์กํด์ง๋๋ค:
# ์ดํ ํผ์ฆ์์ ๋ค๋ฃฐ 2D ์ธ๋ฑ์ฑ
idx = row * WIDTH + col
# 3D ์ธ๋ฑ์ฑ
idx = (batch * HEIGHT + row) * WIDTH + col
# ํจ๋ฉ์ด ์๋ ๊ฒฝ์ฐ
idx = (batch * padded_height + row) * padded_width + col
TileTensor ๋ฏธ๋ฆฌ๋ณด๊ธฐ
TileTensor๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ํจ์ฌ ๊น๋ํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค:
# ๋ฏธ๋ฆฌ๋ณด๊ธฐ - ์ง๊ธ์ ์ด ๋ฌธ๋ฒ์ ๋ชฐ๋ผ๋ ๊ด์ฐฎ์ต๋๋ค!
output[i, j] = a[i, j] + 10.0 # 2D ์ธ๋ฑ์ฑ
output[b, i, j] = a[b, i, j] + 10.0 # 3D ์ธ๋ฑ์ฑ
Puzzle 4์์ TileTensor๋ฅผ ์์ธํ ๋ฐฐ์ธ ์์ ์ ๋๋ค. ๊ทธ๋ ์ด ๊ฐ๋ ๋ค์ด ํ์๊ฐ ๋ฉ๋๋ค. ์ง๊ธ์ ๋ค์ ๋ด์ฉ์ ์ดํดํ๋ ๋ฐ ์ง์คํ์ธ์:
- ๊ธฐ๋ณธ ์ค๋ ๋ ์ธ๋ฑ์ฑ
- ๊ฐ๋จํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ค๋ ๋์ ๋ฐ์ดํฐ์ ์ผ๋์ผ ๋งคํ
๐ก ํต์ฌ ํฌ์ธํธ: ์ง์ ์ธ๋ฑ์ฑ์ ๊ฐ๋จํ ๊ฒฝ์ฐ์ ์ ์๋ํ์ง๋ง, ๋ณต์กํ GPU ํ๋ก๊ทธ๋๋ฐ ํจํด์์๋ ๊ณง ๋ ์ ๊ตํ ๋๊ตฌ๊ฐ ํ์ํด์ง๋๋ค.
Puzzle 2: Zip
๊ฐ์
๋ฒกํฐ a์ ๋ฒกํฐ b์ ๊ฐ ์์น๋ฅผ ๋ํด output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ๊ฐ ์์น๋ง๋ค ์ค๋ ๋ 1๊ฐ๊ฐ ๋ฐฐ์ ๋ฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ฐ๋ ๋ด์ฉ:
- ์ฌ๋ฌ ์ ๋ ฅ ๋ฐฐ์ด์ ๋ณ๋ ฌ ์ฒ๋ฆฌ
- ์ฌ๋ฌ ์ ๋ ฅ์ ๋ํ ์์๋ณ ์ฐ์ฐ
- ๋ฐฐ์ด ๊ฐ ์ค๋ ๋-๋ฐ์ดํฐ ๋งคํ
- ์ฌ๋ฌ ๋ฐฐ์ด์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
๊ฐ ์ค๋ ๋ \(i\)์ ๋ํด: \[\Large output[i] = a[i] + b[i]\]
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
Thread 0: a[0] + b[0] โ output[0]
Thread 1: a[1] + b[1] โ output[1]
Thread 2: a[2] + b[2] โ output[2]
...
๐ก ์ฐธ๊ณ : ์ด์ ์ปค๋์์ ์ธ ๊ฐ์ ๋ฐฐ์ด(a, b, output)์ ๋ค๋ฃจ๊ณ ์์ต๋๋ค.
์ฐ์ฐ์ด ๋ณต์กํด์ง์๋ก ์ฌ๋ฌ ๋ฐฐ์ด์ ๋ํ ์ ๊ทผ์ ๊ด๋ฆฌํ๊ธฐ๊ฐ ์ ์ ์ด๋ ค์์ง๋๋ค.
์์ฑํ ์ฝ๋
comptime SIZE = 4
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = SIZE
comptime dtype = DType.float32
def add(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
b: UnsafePointer[Scalar[dtype], MutAnyOrigin],
):
var i = thread_idx.x
# FILL ME IN (roughly 1 line)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p02/p02.mojo
ํ
thread_idx.x๋ฅผi์ ์ ์ฅํฉ๋๋คa[i]์b[i]๋ฅผ ๋ํฉ๋๋ค- ๊ฒฐ๊ณผ๋ฅผ
output[i]์ ์ ์ฅํฉ๋๋ค
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p02
pixi run -e amd p02
pixi run -e apple p02
uv run poe p02
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([0.0, 2.0, 4.0, 6.0])
์๋ฃจ์
def add(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
b: UnsafePointer[Scalar[dtype], MutAnyOrigin],
):
var i = thread_idx.x
output[i] = a[i] + b[i]
์ด ์๋ฃจ์ ์:
i = thread_idx.x๋ก ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ ธ์ต๋๋ค- ๋ ๋ฐฐ์ด์ ๊ฐ์ ๋ํฉ๋๋ค:
output[i] = a[i] + b[i]
์์ผ๋ก ๋ค๋ฃฐ ๋ด์ฉ
์ง์ ์ธ๋ฑ์ฑ์ ๊ฐ๋จํ ์์๋ณ ์ฐ์ฐ์์ ์ ์๋ํ์ง๋ง, ๋ค์ ์ํฉ์ ์๊ฐํด ๋ณด์ธ์:
- ๋ฐฐ์ด์ ๋ ์ด์์์ด ์๋ก ๋ค๋ฅด๋ค๋ฉด?
- ํ ๋ฐฐ์ด์ ๋ค๋ฅธ ๋ฐฐ์ด์ ๋ธ๋ก๋์บ์คํธํด์ผ ํ๋ค๋ฉด?
- ์ฌ๋ฌ ๋ฐฐ์ด์์ ๋ณํฉ(coalesced) ์ ๊ทผ์ ์ด๋ป๊ฒ ๋ณด์ฅํ ์ ์์๊น?
์ด๋ฌํ ์ง๋ฌธ๋ค์ Puzzle 4์ TileTensor ์์๋ณด๊ธฐ์์ ๋ค๋ฃน๋๋ค.
Puzzle 3: ๊ฐ๋
๊ฐ์
๋ฒกํฐ a์ ๊ฐ ์์น์ 10์ ๋ํด output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ์ค๋ ๋ ์๊ฐ ๋ฐ์ดํฐ ๊ฐ์๋ณด๋ค ๋ง์์, ์ผ๋ถ ์ค๋ ๋๋ ์ฒ๋ฆฌํ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค. ์ด๋ฐ ์ค๋ ๋๊ฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ์ง ์๋๋ก ๋ฐฉ์งํด์ผ ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- ์ค๋ ๋ ์์ ๋ฐ์ดํฐ ํฌ๊ธฐ ๋ถ์ผ์น ์ฒ๋ฆฌ
- ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ฐฉ์ง
- GPU ์ปค๋์์ ์กฐ๊ฑด๋ถ ์คํ ์ฌ์ฉ
- ์์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
์ํ์ ํํ
๊ฐ ์ค๋ ๋ \(i\)์ ๋ํด: \[\Large \text{if}\ i < \text{size}: output[i] = a[i] + 10\]
๋ฉ๋ชจ๋ฆฌ ์์ ํจํด
Thread 0 (i=0): if 0 < size: output[0] = a[0] + 10 โ Valid
Thread 1 (i=1): if 1 < size: output[1] = a[1] + 10 โ Valid
Thread 2 (i=2): if 2 < size: output[2] = a[2] + 10 โ Valid
Thread 3 (i=3): if 3 < size: output[3] = a[3] + 10 โ Valid
Thread 4 (i=4): if 4 < size: โ Skip (out of bounds)
Thread 5 (i=5): if 5 < size: โ Skip (out of bounds)
๐ก ์ฐธ๊ณ : ๋ค์ ์ํฉ์์ ๊ฒฝ๊ณ(boundary) ๊ฒ์ฌ๋ ์ ์ ๋ณต์กํด์ง๋๋ค:
- ๋ค์ฐจ์ ๋ฐฐ์ด
- ๋ค์ํ ๋ฐฐ์ด ํํ
- ๋ณต์กํ ์ ๊ทผ ํจํด
์์ฑํ ์ฝ๋
comptime SIZE = 4
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = 8
comptime dtype = DType.float32
def add_10_guard(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var i = thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p03/p03.mojo
ํ
thread_idx.x๋ฅผi์ ์ ์ฅํฉ๋๋ค- ๊ฐ๋ ์ถ๊ฐ:
if i < size - ๊ฐ๋ ๋ด๋ถ:
output[i] = a[i] + 10.0
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p03
pixi run -e amd p03
pixi run -e apple p03
uv run poe p03
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
์๋ฃจ์
def add_10_guard(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var i = thread_idx.x
if i < size:
output[i] = a[i] + 10.0
์ด ์๋ฃจ์ ์:
i = thread_idx.x๋ก ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ ธ์ต๋๋คif i < size๋ก ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ์ ๋ฐฉ์งํฉ๋๋ค- ๊ฐ๋ ๋ด๋ถ: ์ ๋ ฅ๊ฐ์ 10์ ๋ํฉ๋๋ค
๊ฒฝ๊ณ ๊ฒ์ฌ ์์ด๋ ํ ์คํธ๊ฐ ํต๊ณผ๋๋ ์ด์ ๊ฐ ๊ถ๊ธํ ์ ์์ต๋๋ค! ํ ์คํธ ํต๊ณผ๊ฐ ์ฝ๋์ ์์ ์ฑ์ด๋ ๋ฏธ์ ์ ๋์(Undefined Behavior) ๋ถ์ฌ๋ฅผ ๋ณด์ฅํ์ง๋ ์๋๋ค๋ ์ ์ ํญ์ ๊ธฐ์ตํ์ธ์. Puzzle 10์์ ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ์ดํด๋ณด๊ณ , ์์ ์ฑ ๋ฒ๊ทธ๋ฅผ ์ก๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํด ๋ด ๋๋ค.
์์ผ๋ก ๋ค๋ฃฐ ๋ด์ฉ
๊ฐ๋จํ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ ์ฌ๊ธฐ์ ์ ์๋ํ์ง๋ง, ๋ค์ ์ํฉ์ ์๊ฐํด ๋ณด์ธ์:
- 2D/3D ๋ฐฐ์ด์ ๊ฒฝ๊ณ๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น?
- ๋ค์ํ ํํ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ค๋ฉด?
- ํจ๋ฉ์ด๋ ๊ฐ์ฅ์๋ฆฌ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค๋ฉด?
๋ณต์ก๋๊ฐ ์ฆ๊ฐํ๋ ์์:
# ํ์ฌ: 1D ๊ฒฝ๊ณ ๊ฒ์ฌ
if i < size: ...
# ๊ณง ๋ค๋ฃฐ ๋ด์ฉ: 2D ๊ฒฝ๊ณ ๊ฒ์ฌ
if i < height and j < width: ...
# ์ดํ: ํจ๋ฉ์ด ์๋ 3D
if i < height and j < width and k < depth and
i >= padding and j >= padding: ...
์ด๋ฐ ๊ฒฝ๊ณ ์ฒ๋ฆฌ ํจํด์ Puzzle 4์ TileTensor ์์๋ณด๊ธฐ์์ ๋ฐฐ์ฐ๋ฉด ํจ์ฌ ๊น๋ํด์ง๋๋ค. TileTensor๋ ํํ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
Puzzle 4: 2D Map
๊ฐ์
2D ์ ์ฌ๊ฐ ํ๋ ฌ a์ ๊ฐ ์์น์ 10์ ๋ํด 2D ์ ์ฌ๊ฐ ํ๋ ฌ output์ ์ ์ฅํ๋
์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ์ค๋ ๋ ์๊ฐ ํ๋ ฌ์ ์์น ์๋ณด๋ค ๋ง์ต๋๋ค.
ํต์ฌ ๊ฐ๋
- 2D ์ค๋ ๋ ์ธ๋ฑ์ฑ
- GPU์์์ ํ๋ ฌ ์ฐ์ฐ
- ์ด๊ณผ ์ค๋ ๋ ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ํจํด
๊ฐ ์์น \((i,j)\)์ ๋ํด: \[\Large output[i,j] = a[i,j] + 10\]
์ค๋ ๋ ์ธ๋ฑ์ฑ ๊ท์น
GPU ํ๋ก๊ทธ๋๋ฐ์์ 2D ํ๋ ฌ์ ๋ค๋ฃฐ ๋๋ ์ค๋ ๋ ์ธ๋ฑ์ค์ ํ๋ ฌ ์ขํ ์ฌ์ด์ ์์ฐ์ค๋ฌ์ด ๋งคํ์ ๋ฐ๋ฆ ๋๋ค:
thread_idx.y๋ ํ(row) ์ธ๋ฑ์คthread_idx.x๋ ์ด(column) ์ธ๋ฑ์ค![]()
![]()
์ด ๊ท์น์ ๋ค์๊ณผ ์ ๋ง์ต๋๋ค:
- ํ๋ ฌ ์์น๋ฅผ (row, column)์ผ๋ก ์ฐ๋ ํ์ค ์ํ ํ๊ธฐ๋ฒ
- ํ์ ์์์ ์๋๋ก(y์ถ), ์ด์ ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก(x์ถ) ๊ฐ๋ ํ๋ ฌ์ ์๊ฐ์ ๊ตฌ์กฐ
- ์ค๋ ๋ ๋ธ๋ก์ ํ๋ ฌ ๊ตฌ์กฐ์ ๋ง์ถฐ 2D ๊ทธ๋ฆฌ๋๋ก ๊ตฌ์ฑํ๋ ์ผ๋ฐ์ ์ธ GPU ํ๋ก๊ทธ๋๋ฐ ํจํด
์ญ์ฌ์ ๋ฐฐ๊ฒฝ
๊ทธ๋ํฝ์ด๋ ์ด๋ฏธ์ง ์ฒ๋ฆฌ์์๋ ๋ณดํต \((x,y)\) ์ขํ๋ฅผ ์ฐ์ง๋ง, ํ๋ ฌ ์ฐ์ฐ์์๋ ์ ํต์ ์ผ๋ก (row, column) ์ธ๋ฑ์ฑ์ ์จ์์ต๋๋ค. ์ด๊ธฐ ์ปดํจํฐ๊ฐ 2D ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์์ ๋น๋กฏ๋ ๊ฒ์ ๋๋ค: ์์์ ์๋๋ก ํ ์ค์ฉ, ๊ฐ ์ค์ ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ์ฝ์์ฃ . ์ด๋ฐ ํ ์ฐ์ (row-major) ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์์ฐจ์ ์ผ๋ก ์ ๊ทผํ๋ ๋ฐฉ์๊ณผ ๋ง์์ CPU์ GPU ๋ชจ๋์์ ํจ์จ์ ์์ด ์ ์ฆ๋์์ต๋๋ค. GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ฉ ์ค๋ ๋ ๋ธ๋ก์ด ๋์ ๋์ ๋,
thread_idx.y๋ฅผ ํ์,thread_idx.x๋ฅผ ์ด์ ๋งคํํ ๊ฑด ๊ธฐ์กด์ ํ๋ฆฝ๋ ํ๋ ฌ ์ธ๋ฑ์ฑ ๊ท์น๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ๋ ค๋ ์์ฐ์ค๋ฌ์ด ์ ํ์ด์์ต๋๋ค.
๊ตฌํ ๋ฐฉ์
๐ฐ ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์
์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ด๋ฆฌํ๋ฉด์ 2D ์ธ๋ฑ์ฑ์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ์์๋ด ๋๋ค.
๐ TileTensor ์์๋ณด๊ธฐ
GPU์์ ๋ค์ฐจ์ ๋ฐฐ์ด ์ฐ์ฐ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๊ฐํธํ๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ์ถ์ํ๋ฅผ ์๊ฐํฉ๋๋ค.
๐ ํ๋์ 2D ์ฐ์ฐ
์์ฐ์ค๋ฌ์ด 2D ์ธ๋ฑ์ฑ๊ณผ ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ๊ฐ์ถ TileTensor๋ฅผ ์ง์ ์จ๋ด ๋๋ค.
๐ก ์ฐธ๊ณ : ์ด ํผ์ฆ๋ถํฐ๋ ๋ ๊น๋ํ๊ณ ์์ ํ GPU ์ฝ๋๋ฅผ ์ํด TileTensor๋ฅผ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค.
๊ฐ์
2D ์ ์ฌ๊ฐ ํ๋ ฌ a์ ๊ฐ ์์น์ 10์ ๋ํด 2D ์ ์ฌ๊ฐ ํ๋ ฌ output์ ์ ์ฅํ๋
์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ์ค๋ ๋ ์๊ฐ ํ๋ ฌ์ ์์น ์๋ณด๋ค ๋ง์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- 2D ์ค๋ ๋ ์ธ๋ฑ์ค ๋ค๋ฃจ๊ธฐ (
thread_idx.x,thread_idx.y) - 2D ์ขํ๋ฅผ 1D ๋ฉ๋ชจ๋ฆฌ ์ธ๋ฑ์ค๋ก ๋ณํํ๊ธฐ
- 2์ฐจ์์์ ๊ฒฝ๊ณ ๊ฒ์ฌ ์ฒ๋ฆฌํ๊ธฐ
ํต์ฌ์ 2D ์ค๋ ๋ ์ขํ \((i,j)\)๋ฅผ ํฌ๊ธฐ \(n \times n\)์ธ ํ ์ฐ์ ํ๋ ฌ์ ์์๋ก ๋งคํํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค. ๋์์ ์ค๋ ๋ ์ธ๋ฑ์ค๊ฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋์ง ์๋์ง๋ ํ์ธํด์ผ ํฉ๋๋ค.
- 2D ์ธ๋ฑ์ฑ: ๊ฐ ์ค๋ ๋๊ฐ ๊ณ ์ ํ \((i,j)\) ์์น๋ฅผ ๊ฐ์ง
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ํ ์ฐ์ ์์๋ก 2D๋ฅผ 1D ๋ฉ๋ชจ๋ฆฌ์ ๋งคํ
- ๊ฐ๋ ์กฐ๊ฑด: ๋ ์ฐจ์ ๋ชจ๋ ๊ฒฝ๊ณ ๊ฒ์ฌ ํ์
- ์ค๋ ๋ ๋ฒ์: ์ค๋ ๋ \((3 \times 3)\)๊ฐ ํ๋ ฌ ์์ \((2 \times 2)\)๋ณด๋ค ๋ง์
์์ฑํ ์ฝ๋
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
def add_10_2d(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p04/p04.mojo
ํ
- 2D ์ธ๋ฑ์ค ๊ฐ์ ธ์ค๊ธฐ:
row = thread_idx.y,col = thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if row < size and col < size - ๊ฐ๋ ๋ด๋ถ์์ ํ ์ฐ์ ๋ฐฉ์์ผ๋ก 10 ๋ํ๊ธฐ!
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p04
pixi run -e amd p04
pixi run -e apple p04
uv run poe p04
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
์๋ฃจ์
def add_10_2d(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
if row < size and col < size:
output[row * size + col] = a[row * size + col] + 10.0
์ด ์๋ฃจ์ ์:
- 2D ์ธ๋ฑ์ค ๊ฐ์ ธ์ค๊ธฐ:
row = thread_idx.y,col = thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if row < size and col < size - ๊ฐ๋ ๋ด๋ถ:
output[row * size + col] = a[row * size + col] + 10.0
TileTensor ์์๋ณด๊ธฐ
ํผ์ฆ ํ์ด๋ฅผ ์ ์ ๋ฉ์ถ๊ณ , GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ ์ฆ๊ฒ๊ฒ ๋ง๋ค์ด์ค ๊ฐ๋ ฅํ ์ถ์ํ๋ฅผ ๋ฏธ๋ฆฌ ์ดํด๋ด ์๋ค: ๐ฅโฆ ๋ฐ๋ก TileTensor ์ ๋๋ค.
๐ก TileTensor๊ฐ ์ด๋ค ์ผ์ ํ ์ ์๋์ง ๋ง๋ณด๊ธฐ๋ก ์ดํด๋ด ๋๋ค. ์ง๊ธ ๋ชจ๋ ๊ฑธ ์ดํดํ ํ์๋ ์์ด์ - ํผ์ฆ์ ์งํํ๋ฉด์ ๊ฐ ๊ธฐ๋ฅ์ ์์ธํ ์์๋ณผ ๊ฒ๋๋ค.
๋ฌธ์ : ์ ์ ๋ณต์กํด์ง๋ ์ฝ๋
์ง๊ธ๊น์ง ๊ฒช์ ์ด๋ ค์์ ์ดํด๋ด ์๋ค:
# Puzzle 1: ๋จ์ ์ธ๋ฑ์ฑ
output[i] = a[i] + 10.0
# Puzzle 2: ์ฌ๋ฌ ๋ฐฐ์ด ๊ด๋ฆฌ
output[i] = a[i] + b[i]
# Puzzle 3: ๊ฒฝ๊ณ ๊ฒ์ฌ
if i < size:
output[i] = a[i] + 10.0
์ฐจ์์ด ๋์ด๋๋ฉด ์ฝ๋๋ ๋ ๋ณต์กํด์ง๋๋ค:
# ์ ํต์ ์ธ 2D ์ธ๋ฑ์ฑ (ํ ์ฐ์ 2D ํ๋ ฌ)
idx = row * WIDTH + col
if row < height and col < width:
output[idx] = a[idx] + 10.0
ํด๊ฒฐ์ฑ : TileTensor ๋ฏธ๋ฆฌ๋ณด๊ธฐ
TileTensor๋ ์ด๋ฐ ๋ฌธ์ ๋ค์ ๊น๋ํ๊ฒ ํด๊ฒฐํด์ค๋๋ค. ์์ผ๋ก ๋ฐฐ์ธ ๋ด์ฉ์ ์ด์ง ์ฟ๋ณด๋ฉด:
- ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ: ์๋ ์คํ์
๊ณ์ฐ ๋์
tensor[i, j]์ฌ์ฉ - ์ ์ฐํ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ํ ์ฐ์ , ์ด ์ฐ์ , ํ์ผ ๊ตฌ์ฑ ์ง์
- ์ฑ๋ฅ ์ต์ ํ: GPU์ ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
์์ผ๋ก ๋ฐฐ์ธ ๋ด์ฉ ๋ง๋ณด๊ธฐ
TileTensor๊ฐ ํ ์ ์๋ ์ผ์ ๋ช ๊ฐ์ง ์์๋ก ์ดํด๋ด ์๋ค. ์ง๊ธ ๋ชจ๋ ์ธ๋ถ ์ฌํญ์ ์ดํดํ ํ์๋ ์์ต๋๋ค - ์์ผ๋ก ๋์ฌ ํผ์ฆ์์ ๊ฐ ๊ธฐ๋ฅ์ ๊ผผ๊ผผํ ๋ค๋ฃฐ ๊ฑฐ์์.
๊ธฐ๋ณธ ์ฌ์ฉ ์์
from layout import TileTensor
from layout.tile_layout import row_major
# ๋ ์ด์์ ์ ์
comptime HEIGHT = 2
comptime WIDTH = 3
comptime layout = row_major[HEIGHT, WIDTH]()
comptime LayoutType = type_of(layout)
# ํ
์ ์์ฑ
tensor = TileTensor(buffer, layout)
# ์์ฐ์ค๋ฝ๊ฒ ์์ ์ ๊ทผ
tensor[0, 0] = 1.0 # ์ฒซ ๋ฒ์งธ ์์
tensor[1, 2] = 2.0 # ๋ง์ง๋ง ์์
Layout๊ณผ TileTensor์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด
Mojo ๋งค๋ด์ผ์ ๊ฐ์ด๋๋ฅผ ์ฐธ๊ณ ํ์ธ์:
๊ฐ๋จํ ์์
TileTensor์ ๊ธฐ๋ณธ์ ๋ณด์ฌ์ฃผ๋ ๊ฐ๋จํ ์์ ๋ก ๋ชจ๋ ๊ฒ์ ์ ๋ฆฌํด๋ด ์๋ค:
# ===----------------------------------------------------------------------=== #
#
# This file is Modular Inc proprietary.
#
# ===----------------------------------------------------------------------=== #
from std.gpu.host import DeviceContext
from layout import TileTensor
from layout.tile_layout import row_major
comptime HEIGHT = 2
comptime WIDTH = 3
comptime dtype = DType.float32
comptime layout = row_major[HEIGHT, WIDTH]()
comptime LayoutType = type_of(layout)
def kernel(
tensor: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
):
print("Before:")
print(tensor)
tensor[0, 0] += 1
print("After:")
print(tensor)
def main() raises:
ctx = DeviceContext()
a = ctx.enqueue_create_buffer[dtype](HEIGHT * WIDTH)
a.enqueue_fill(0)
tensor = TileTensor(a, layout)
# Note: since `tensor` is a device tensor we can't print it without the kernel wrapper
ctx.enqueue_function[kernel, kernel](tensor, grid_dim=1, block_dim=1)
ctx.synchronize()
๋ค์ ๋ช ๋ น์ด๋ก ์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด:
pixi run tile_tensor_intro
pixi run -e amd tile_tensor_intro
pixi run -e apple tile_tensor_intro
uv run poe tile_tensor_intro
Before:
0.0 0.0 0.0
0.0 0.0 0.0
After:
1.0 0.0 0.0
0.0 0.0 0.0
๋ฌด์จ ์ผ์ด ์ผ์ด๋๋์ง ์ดํด๋ด ์๋ค:
- ํ ์ฐ์ ๋ ์ด์์์ผ๋ก
2 x 3ํ ์๋ฅผ ์์ฑํฉ๋๋ค - ์ฒ์์๋ ๋ชจ๋ ์์๊ฐ 0์ ๋๋ค
- ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ์ผ๋ก ํ๋์ ์์๋ฅผ ์์ ํฉ๋๋ค
- ๋ณ๊ฒฝ ์ฌํญ์ด ์ถ๋ ฅ์ ๋ฐ์๋ฉ๋๋ค
์ด ๊ฐ๋จํ ์์ ๋ TileTensor์ ํต์ฌ ์ฅ์ ์ ๋ณด์ฌ์ค๋๋ค:
- ํ ์ ์์ฑ๊ณผ ์ ๊ทผ์ ์ํ ๊น๋ํ ๋ฌธ๋ฒ
- ์๋ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ฒ๋ฆฌ
- ์์ฐ์ค๋ฌ์ด ๋ค์ฐจ์ ์ธ๋ฑ์ฑ
์ด ์์ ๋ ๊ฐ๋จํ์ง๋ง, ๊ฐ์ ํจํด์ด ์์ผ๋ก ๋์ฌ ํผ์ฆ์ ๋ณต์กํ GPU ์ฐ์ฐ์๋ ๊ทธ๋๋ก ์ ์ฉ๋ฉ๋๋ค. ์ด๋ฐ ๊ธฐ๋ณธ ๊ฐ๋ ์ด ๋ค์์ผ๋ก ์ด๋ป๊ฒ ํ์ฅ๋๋์ง ๋ณด๊ฒ ๋ ๊ฑฐ์์:
- ๋ฉํฐ ์ค๋ ๋ GPU ์ฐ์ฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
- ๋ณต์กํ ํ์ผ๋ง ์ ๋ต
- ํ๋์จ์ด ๊ฐ์ ์ฐ์ฐ
TileTensor์ ํจ๊ป GPU ํ๋ก๊ทธ๋๋ฐ ์ฌ์ ์ ์์ํ ์ค๋น๊ฐ ๋๋์? ํผ์ฆ๋ก ๋ค์ด๊ฐ๋ด ์๋ค!
๐ก ํ: ์งํํ๋ฉด์ ์ด ์์ ๋ฅผ ๊ธฐ์ตํด๋์ธ์ - ์ด ๊ธฐ๋ณธ ๊ฐ๋ ์ ๋ฐํ์ผ๋ก ์ ์ ๋ ์ ๊ตํ GPU ํ๋ก๊ทธ๋จ์ ๋ง๋ค์ด๊ฐ ๊ฒ๋๋ค.
TileTensor ๋ฒ์
๊ฐ์
2D TileTensor a์ ๊ฐ ์์น์ 10์ ๋ํด 2D TileTensor output์ ์ ์ฅํ๋
์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ์ค๋ ๋ ์๊ฐ ํ๋ ฌ์ ์์น ์๋ณด๋ค ๋ง์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- 2D ๋ฐฐ์ด ์ ๊ทผ์
TileTensor์ฌ์ฉํ๊ธฐ tensor[i, j]๋ก ์ง์ 2D ์ธ๋ฑ์ฑํ๊ธฐTileTensor์์ ๊ฒฝ๊ณ ๊ฒ์ฌ ์ฒ๋ฆฌํ๊ธฐ
ํต์ฌ์ TileTensor๊ฐ ์์ฐ์ค๋ฌ์ด 2D ์ธ๋ฑ์ฑ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ์ฌ ๋ด๋ถ ๋ฉ๋ชจ๋ฆฌ
๋ ์ด์์์ ์ถ์ํํ๋ค๋ ์ ์
๋๋ค. ๊ทธ๋ฌ๋ฉด์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ ์ฌ์ ํ ํ์ํฉ๋๋ค.
- 2D ์ ๊ทผ:
TileTensor๋ก ์์ฐ์ค๋ฌ์ด \((i,j)\) ์ธ๋ฑ์ฑ - ๋ฉ๋ชจ๋ฆฌ ์ถ์ํ: ์๋ ํ ์ฐ์ ๊ณ์ฐ ๋ถํ์
- ๊ฐ๋ ์กฐ๊ฑด: ๋ ์ฐจ์ ๋ชจ๋ ๊ฒฝ๊ณ ๊ฒ์ฌ ํ์
- ์ค๋ ๋ ๋ฒ์: ์ค๋ ๋ \((3 \times 3)\)๊ฐ ํ ์ ์์ \((2 \times 2)\)๋ณด๋ค ๋ง์
์์ฑํ ์ฝ๋
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
comptime layout = row_major[SIZE, SIZE]()
comptime LayoutType = type_of(layout)
def add_10_2d(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p04/p04_tile_tensor.mojo
ํ
- 2D ์ธ๋ฑ์ค ๊ฐ์ ธ์ค๊ธฐ:
row = thread_idx.y,col = thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if row < size and col < size - ๊ฐ๋ ๋ด๋ถ์์
a[row, col]์ 10 ๋ํ๊ธฐ
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p04_tile_tensor
pixi run -e amd p04_tile_tensor
pixi run -e apple p04_tile_tensor
uv run poe p04_tile_tensor
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
์๋ฃจ์
def add_10_2d(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
if col < size and row < size:
output[row, col] = a[row, col] + 10.0
์ด ์๋ฃจ์ ์:
row = thread_idx.y,col = thread_idx.x๋ก 2D ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ ธ์ดif row < size and col < size๋ก ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ ๋ฐฉ์งTileTensor์ 2D ์ธ๋ฑ์ฑ ์ฌ์ฉ:output[row, col] = a[row, col] + 10.0
Puzzle 5: ๋ธ๋ก๋์บ์คํธ
๊ฐ์
1D TileTensor a์ b๋ฅผ ๋ธ๋ก๋์บ์คํธ๋ก ๋ํด 2D TileTensor output์ ์ ์ฅํ๋
์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ์ค๋ ๋ ์๊ฐ ํ๋ ฌ์ ์์น ์๋ณด๋ค ๋ง์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ์
TileTensor์ฌ์ฉํ๊ธฐ - ์๋ก ๋ค๋ฅธ ํ ์ ํฌ๊ธฐ ๋ค๋ฃจ๊ธฐ
TileTensor๋ก 2D ์ธ๋ฑ์ฑ ์ฒ๋ฆฌํ๊ธฐ
ํต์ฌ์ TileTensor๊ฐ ์๋ก ๋ค๋ฅธ ํ
์ ํฌ๊ธฐ \((1, n)\)์ \((n, 1)\)์
\((n,n)\)์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ๋ธ๋ก๋์บ์คํธํ ์ ์๋ค๋ ์ ์
๋๋ค. ๊ทธ๋ฌ๋ฉด์๋ ๊ฒฝ๊ณ
๊ฒ์ฌ๋ ์ฌ์ ํ ํ์ํฉ๋๋ค.
- ํ ์ ํฌ๊ธฐ: ์ ๋ ฅ ๋ฒกํฐ์ ํฌ๊ธฐ๋ \((1, n)\)๊ณผ \((n, 1)\)
- ๋ธ๋ก๋์บ์คํธ: ๋ ์ฐจ์์ ๊ฒฐํฉํด \((n,n)\) ์ถ๋ ฅ ์์ฑ
- ๊ฐ๋ ์กฐ๊ฑด: ์ถ๋ ฅ ํฌ๊ธฐ์ ๋ํ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ ์ฌ์ ํ ํ์
- ์ค๋ ๋ ๋ฒ์: ํ ์ ์์ \((2 \times 2)\)๋ณด๋ค ์ค๋ ๋ \((3 \times 3)\)๊ฐ ๋ง์
์์ฑํ ์ฝ๋
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
comptime out_layout = row_major[SIZE, SIZE]()
comptime a_layout = row_major[1, SIZE]()
comptime b_layout = row_major[SIZE, 1]()
comptime OutLayout = type_of(out_layout)
comptime ALayout = type_of(a_layout)
comptime BLayout = type_of(b_layout)
def broadcast_add(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, ALayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, BLayout, ImmutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p05/p05.mojo
ํ
- 2D ์ธ๋ฑ์ค ๊ฐ์ ธ์ค๊ธฐ:
row = thread_idx.y,col = thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if row < size and col < size - ๊ฐ๋ ๋ด๋ถ: TileTensor๋ก
a์b๊ฐ์ ์ด๋ป๊ฒ ๋ธ๋ก๋์บ์คํธํ ์ง ์๊ฐํด ๋ณด์ธ์
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p05
pixi run -e amd p05
pixi run -e apple p05
uv run poe p05
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([1.0, 2.0, 11.0, 12.0])
์๋ฃจ์
def broadcast_add(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, ALayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, BLayout, ImmutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
if row < size and col < size:
output[row, col] = a[0, col] + b[row, 0]
TileTensor ๋ธ๋ก๋์บ์คํธ์ GPU ์ค๋ ๋ ๋งคํ์ ํต์ฌ ๊ฐ๋ ์ ๋ณด์ฌ์ฃผ๋ ์๋ฃจ์ ์ ๋๋ค:
-
์ค๋ ๋์์ ํ๋ ฌ๋ก ๋งคํ
thread_idx.y๋ก ํ,thread_idx.x๋ก ์ด์ ์ ๊ทผ- ์์ฐ์ค๋ฌ์ด 2D ์ธ๋ฑ์ฑ์ด ์ถ๋ ฅ ํ๋ ฌ ๊ตฌ์กฐ์ ์ผ์น
- ์ด๊ณผ ์ค๋ ๋(3ร3 ๊ทธ๋ฆฌ๋)๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ก ์ฒ๋ฆฌ
-
๋ธ๋ก๋์บ์คํธ ์๋ ๋ฐฉ์
- ์
๋ ฅ
a์ ํฌ๊ธฐ๋(1,n):a[0,col]์ด ํ์ ๊ฐ๋ก์ง๋ฌ ๋ธ๋ก๋์บ์คํธ - ์
๋ ฅ
b์ ํฌ๊ธฐ๋(n,1):b[row,0]์ด ์ด์ ๊ฐ๋ก์ง๋ฌ ๋ธ๋ก๋์บ์คํธ - ์ถ๋ ฅ์ ํฌ๊ธฐ๋
(n,n): ๊ฐ ์์๋ ํด๋น ๋ธ๋ก๋์บ์คํธ ๊ฐ๋ค์ ํฉ
[ a0 a1 ] + [ b0 ] = [ a0+b0 a1+b0 ] [ b1 ] [ a0+b1 a1+b1 ] - ์
๋ ฅ
-
๊ฒฝ๊ณ ๊ฒ์ฌ
- ๊ฐ๋ ์กฐ๊ฑด
row < size and col < size๋ก ๋ฒ์ ์ด๊ณผ ์ ๊ทผ ๋ฐฉ์ง - ํ๋ ฌ ๋ฒ์์ ์ด๊ณผ ์ค๋ ๋๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ
- ๋ธ๋ก๋์บ์คํธ ๋๋ถ์
a์b์ ๋ํ ๋ณ๋ ๊ฒ์ฌ ๋ถํ์
- ๊ฐ๋ ์กฐ๊ฑด
์ด ํจํด์ ์ดํ ํผ์ฆ์์ ๋ค๋ฃฐ ๋ ๋ณต์กํ ํ ์ ์ฐ์ฐ์ ๊ธฐ์ด๊ฐ ๋ฉ๋๋ค.
Puzzle 6: ๋ธ๋ก
๊ฐ์
๋ฒกํฐ a์ ๊ฐ ์์น์ 10์ ๋ํด output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ๋ธ๋ก๋น ์ค๋ ๋ ์๊ฐ a์ ํฌ๊ธฐ๋ณด๋ค ์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- ์ค๋ ๋ ๋ธ๋ก ํฌ๊ธฐ๋ณด๋ค ํฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- ์ฌ๋ฌ ๋ธ๋ก์ ์ค๋ ๋ ์กฐ์จ
- ์ ์ญ ์ค๋ ๋ ์์น ๊ณ์ฐ
์ฌ๊ธฐ์ ํต์ฌ์ ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ด ํ๋ ฅํ์ฌ ๋จ์ผ ๋ธ๋ก ์ฉ๋๋ณด๋ค ํฐ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ฉด์๋, ์์์ ์ค๋ ๋ ๊ฐ ์ฌ๋ฐ๋ฅธ ๋งคํ์ ์ ์งํ๋ ์๋ฆฌ๋ฅผ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
์์ฑํ ์ฝ๋
comptime SIZE = 9
comptime BLOCKS_PER_GRID = (3, 1)
comptime THREADS_PER_BLOCK = (4, 1)
comptime dtype = DType.float32
def add_10_blocks(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var i = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p06/p06.mojo
์ฐธ๊ณ : ์ด ํผ์ฆ์
TileTensor๋ฒ์ ์ ๊ฑฐ์ ๋์ผํ๋ฏ๋ก ๋ ์์๊ฒ ๋งก๊น๋๋ค.
ํ
- ์ ์ญ ์ธ๋ฑ์ค ๊ณ์ฐ:
i = block_dim.x * block_idx.x + thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if i < size - ๊ฐ๋ ๋ด๋ถ:
output[i] = a[i] + 10.0
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p06
pixi run -e amd p06
pixi run -e apple p06
uv run poe p06
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0])
์๋ฃจ์
def add_10_blocks(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
size: Int,
):
var i = block_dim.x * block_idx.x + thread_idx.x
if i < size:
output[i] = a[i] + 10.0
์ด ์๋ฃจ์ ์ ๋ธ๋ก ๊ธฐ๋ฐ GPU ์ฒ๋ฆฌ์ ํต์ฌ ๊ฐ๋ ์ ๋ค๋ฃน๋๋ค:
-
์ ์ญ ์ค๋ ๋ ์ธ๋ฑ์ฑ
-
๋ธ๋ก ์ธ๋ฑ์ค์ ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฒฐํฉ:
block_dim.x * block_idx.x + thread_idx.x -
๊ฐ ์ค๋ ๋๋ฅผ ๊ณ ์ ํ ์ ์ญ ์์น์ ๋งคํ
-
๋ธ๋ก๋น 3๊ฐ ์ค๋ ๋ ์์:
Block 0: [0 1 2] Block 1: [3 4 5] Block 2: [6 7 8]
-
-
๋ธ๋ก ์กฐ์จ
-
๊ฐ ๋ธ๋ก์ ์ฐ์๋ ๋ฐ์ดํฐ ์ฒญํฌ๋ฅผ ์ฒ๋ฆฌ
-
๋ธ๋ก ํฌ๊ธฐ(3) < ๋ฐ์ดํฐ ํฌ๊ธฐ(9)์ด๋ฏ๋ก ์ฌ๋ฌ ๋ธ๋ก ํ์
-
๋ธ๋ก ๊ฐ ์๋ ์์ ๋ถ๋ฐฐ:
Data: [0 1 2 3 4 5 6 7 8] Block 0: [0 1 2] Block 1: [3 4 5] Block 2: [6 7 8]
-
-
๊ฒฝ๊ณ ๊ฒ์ฌ
- ๊ฐ๋ ์กฐ๊ฑด
i < size๋ก ๊ฒฝ๊ณ ์ผ์ด์ค ์ฒ๋ฆฌ - ๋ฐ์ดํฐ ํฌ๊ธฐ๊ฐ ๋ธ๋ก ํฌ๊ธฐ๋ก ๋๋์ด ๋จ์ด์ง์ง ์์ ๋ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ ๋ฐฉ์ง
- ๋ฐ์ดํฐ ๋๋ถ๋ถ์ ๋ถ์์ ํ ๋ธ๋ก ์ฒ๋ฆฌ์ ํ์
- ๊ฐ๋ ์กฐ๊ฑด
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ณํฉ(coalesced) ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ๋ธ๋ก ๋ด ์ค๋ ๋๋ค์ด ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผ
- ๊ฐ ์ค๋ ๋๊ฐ ํ๋์ ์์ ์ฒ๋ฆฌ:
output[i] = a[i] + 10.0 - ๋ธ๋ก ์์ค ๋ณ๋ ฌ์ฑ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ํจ์จ์ ์ผ๋ก ํ์ฉ
์ด ํจํด์ ๋จ์ผ ์ค๋ ๋ ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ์ด๊ณผํ๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ฒ๋ฆฌ์ ๊ธฐ์ด๊ฐ ๋ฉ๋๋ค.
Puzzle 7: 2D ๋ธ๋ก
๊ฐ์
2D TileTensor a์ ๊ฐ ์์น์ 10์ ๋ํด 2D TileTensor output์ ์ ์ฅํ๋ ์ปค๋์
๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ๋ธ๋ก๋น ์ค๋ ๋ ์๊ฐ a์ ํ๊ณผ ์ด ํฌ๊ธฐ๋ณด๋ค ๋ชจ๋ ์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ์ฌ๋ฌ ๋ธ๋ก๊ณผ ํจ๊ป
TileTensor์ฌ์ฉํ๊ธฐ - 2D ๋ธ๋ก ๊ตฌ์ฑ์ผ๋ก ํฐ ํ๋ ฌ ์ฒ๋ฆฌํ๊ธฐ
- ๋ธ๋ก ์ธ๋ฑ์ฑ๊ณผ
TileTensor์ ๊ทผ ๊ฒฐํฉํ๊ธฐ
ํต์ฌ์ TileTensor๊ฐ 2D ์ธ๋ฑ์ฑ์ ๋จ์ํํด ์ฃผ์ง๋ง, ํฐ ํ๋ ฌ์์๋ ์ฌ์ ํ ๋ธ๋ก ๊ฐ
์กฐ์จ์ด ํ์ํ๋ค๋ ์ ์
๋๋ค.
๐ 2D ์ค๋ ๋ ์ธ๋ฑ์ฑ ๋ฐฉ์
Puzzle 4: 2D Map์ ๋ธ๋ก ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ์ 2D๋ก ํ์ฅํฉ๋๋ค:
์ ์ญ ์์น ๊ณ์ฐ: row = block_dim.y * block_idx.y + thread_idx.y col = block_dim.x * block_idx.x + thread_idx.x์๋ฅผ ๋ค์ด, 4ร4 ๊ทธ๋ฆฌ๋์์ 2ร2 ๋ธ๋ก์ ์ฌ์ฉํ๋ฉด:
Block (0,0): Block (1,0): [0,0 0,1] [0,2 0,3] [1,0 1,1] [1,2 1,3] Block (0,1): Block (1,1): [2,0 2,1] [2,2 2,3] [3,0 3,1] [3,2 3,3]๊ฐ ์์น๋ ํด๋น ์ค๋ ๋์ ์ ์ญ ์ธ๋ฑ์ค (row, col)๋ฅผ ๋ํ๋ ๋๋ค. ๋ธ๋ก ์ฐจ์๊ณผ ์ธ๋ฑ์ค๊ฐ ํจ๊ป ์๋ํ์ฌ ๋ค์์ ๋ณด์ฅํฉ๋๋ค:
- 2D ๊ณต๊ฐ ์ ์ฒด๋ฅผ ๋นํ์์ด ์ฒ๋ฆฌ
- ๋ธ๋ก ๊ฐ ๊ฒน์นจ ์์
- ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
๊ตฌ์ฑ
- ํ๋ ฌ ํฌ๊ธฐ: \(5 \times 5\) ์์
- ๋ ์ด์์ ์ฒ๋ฆฌ:
TileTensor๊ฐ ํ ์ฐ์ ๊ตฌ์ฑ ๊ด๋ฆฌ - ๋ธ๋ก ์กฐ์จ: ์ฌ๋ฌ ๋ธ๋ก์ผ๋ก ์ ์ฒด ํ๋ ฌ ์ปค๋ฒ
- 2D ์ธ๋ฑ์ฑ: ๊ฒฝ๊ณ ๊ฒ์ฌ์ ํจ๊ป ์์ฐ์ค๋ฌ์ด \((i,j)\) ์ ๊ทผ
- ์ด ์ค๋ ๋ ์: \(25\)๊ฐ ์์์ ๋ํด \(36\)๊ฐ
- ์ค๋ ๋ ๋งคํ: ๊ฐ ์ค๋ ๋๊ฐ ํ๋ ฌ ์์ ํ๋์ฉ ์ฒ๋ฆฌ
์์ฑํ ์ฝ๋
comptime SIZE = 5
comptime BLOCKS_PER_GRID = (2, 2)
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
comptime out_layout = row_major[SIZE, SIZE]()
comptime a_layout = row_major[SIZE, SIZE]()
comptime OutLayout = type_of(out_layout)
comptime ALayout = type_of(a_layout)
def add_10_blocks_2d(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, ALayout, ImmutAnyOrigin],
size: Int,
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p07/p07.mojo
ํ
- ์ ์ญ ์ธ๋ฑ์ค ๊ณ์ฐ:
row = block_dim.y * block_idx.y + thread_idx.y,col = block_dim.x * block_idx.x + thread_idx.x - ๊ฐ๋ ์ถ๊ฐ:
if row < size and col < size - ๊ฐ๋ ๋ด๋ถ: 2D TileTensor์ 10์ ๋ํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋ณด์ธ์
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p07
pixi run -e amd p07
pixi run -e apple p07
uv run poe p07
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, ... , 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, ... , 34.0])
์๋ฃจ์
def add_10_blocks_2d(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, ALayout, ImmutAnyOrigin],
size: Int,
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
if row < size and col < size:
output[row, col] = a[row, col] + 10.0
TileTensor๊ฐ 2D ๋ธ๋ก ๊ธฐ๋ฐ ์ฒ๋ฆฌ๋ฅผ ์ผ๋ง๋ ๊ฐ์ํํ๋์ง ๋ณด์ฌ์ฃผ๋ ์๋ฃจ์ ์ ๋๋ค:
-
2D ์ค๋ ๋ ์ธ๋ฑ์ฑ
-
์ ์ญ ํ(row):
block_dim.y * block_idx.y + thread_idx.y -
์ ์ญ ์ด(col):
block_dim.x * block_idx.x + thread_idx.x -
์ค๋ ๋ ๊ทธ๋ฆฌ๋๋ฅผ ํ ์ ์์์ ๋งคํ:
3ร3 ๋ธ๋ก์ผ๋ก ๊ตฌ์ฑ๋ 5ร5 ํ ์: Block (0,0) Block (1,0) [(0,0) (0,1) (0,2)] [(0,3) (0,4) * ] [(1,0) (1,1) (1,2)] [(1,3) (1,4) * ] [(2,0) (2,1) (2,2)] [(2,3) (2,4) * ] Block (0,1) Block (1,1) [(3,0) (3,1) (3,2)] [(3,3) (3,4) * ] [(4,0) (4,1) (4,2)] [(4,3) (4,4) * ] [ * * * ] [ * * * ](* = ์ค๋ ๋๋ ์กด์ฌํ์ง๋ง ํ ์ ๊ฒฝ๊ณ ๋ฐ)
-
-
TileTensor์ ์ฅ์
-
์์ฐ์ค๋ฌ์ด 2D ์ธ๋ฑ์ฑ: ์๋ ์คํ์ ๊ณ์ฐ ๋์
tensor[row, col]์ฌ์ฉ -
์๋ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ต์ ํ
-
์ ๊ทผ ํจํด ์์:
์์ ๋ฉ๋ชจ๋ฆฌ: TileTensor: row * size + col tensor[row, col] (2,1) -> 11 (2,1) -> ๊ฐ์ ์์
-
-
๊ฒฝ๊ณ ๊ฒ์ฌ
- ๊ฐ๋
row < size and col < size๊ฐ ์ฒ๋ฆฌํ๋ ์ํฉ:- ๋ถ๋ถ ๋ธ๋ก์์ ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ ์ค๋ ๋
- ํ ์ ๊ฒฝ๊ณ์ ์ฃ์ง ์ผ์ด์ค
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ TileTensor๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌ
- 25๊ฐ ์์๋ฅผ 36๊ฐ ์ค๋ ๋๋ก ์ฒ๋ฆฌ (3ร3 ๋ธ๋ก์ 2ร2 ๊ทธ๋ฆฌ๋)
- ๊ฐ๋
-
๋ธ๋ก ์กฐ์จ
- ๊ฐ 3ร3 ๋ธ๋ก์ด 5ร5 ํ ์์ ์ผ๋ถ๋ถ์ ๋ด๋น
- TileTensor๊ฐ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ:
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ต์ ํ
- ํจ์จ์ ์ธ ์ ๊ทผ ํจํด
- ๋ธ๋ก ๊ฒฝ๊ณ ๊ฐ ์กฐ์จ
- ์บ์ ์นํ์ ๋ฐ์ดํฐ ์ ๊ทผ
์ด ํจํด์ TileTensor๊ฐ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ์ค๋ ๋ ์กฐ์จ์ ์ ์งํ๋ฉด์๋ 2D ๋ธ๋ก ์ฒ๋ฆฌ๋ฅผ ์ผ๋ง๋ ๊ฐ์ํํ๋์ง ๋ณด์ฌ์ค๋๋ค.
Puzzle 8: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ
๊ฐ์
1D TileTensor a์ ๊ฐ ์์น์ 10์ ๋ํด 1D TileTensor output์ ์ ์ฅํ๋ ์ปค๋์
๊ตฌํํด ๋ณด์ธ์.
์ฐธ๊ณ : ๋ธ๋ก๋น ์ค๋ ๋ ์๊ฐ a์ ํฌ๊ธฐ๋ณด๋ค ์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- address_space๋ฅผ ํ์ฉํ TileTensor์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฅ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋์ ์ค๋ ๋ ๋๊ธฐํ
- TileTensor๋ก ๋ธ๋ก ๋ก์ปฌ ๋ฐ์ดํฐ ๊ด๋ฆฌํ๊ธฐ
ํต์ฌ์ TileTensor๊ฐ ๋ธ๋ก ๋ก์ปฌ ์ ์ฅ์์ ์ฑ๋ฅ์ ๊ทธ๋๋ก ์ ์งํ๋ฉด์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ผ๋ง๋ ๊ฐ์ํํ๋์ง ์ดํดํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE = 8์์ - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 4 - ๋ธ๋ก ์: 2
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก๋น
TPB๊ฐ ์์
๊ฒฝ๊ณ : ๊ฐ ๋ธ๋ก์๋ ํด๋น ๋ธ๋ก์ ์ค๋ ๋๋ค์ด ์ฝ๊ณ ์ธ ์ ์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์์ด _์์_๋ก ๊ณ ์ ๋์ด ์์ต๋๋ค. ์ด ๊ฐ์ ํ์ด์ฌ ๋ฆฌํฐ๋ด ์์์ฌ์ผ ํ๋ฉฐ ๋ณ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ด ํ์๋ barrier๋ฅผ ํธ์ถํด ์ค๋ ๋๋ค์ด ๊ต์ฐจํ์ง ์๋๋ก ํด์ผ ํฉ๋๋ค.
ํ์ต ์ฐธ๊ณ : ์ด ํผ์ฆ์์๋ ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น์๋ง ์ ๊ทผํ๋ฏ๋ก
barrier()๊ฐ ์๋ฐํ ํ์ํ์ง ์์ต๋๋ค. ํ์ง๋ง ๋ ๋ณต์กํ ์ํฉ์์ ํ์ํ ์ฌ๋ฐ๋ฅธ
๋๊ธฐํ ํจํด์ ์ตํ๊ธฐ ์ํด ํฌํจ๋์ด ์์ต๋๋ค.
์์ฑํ ์ฝ๋
comptime TPB = 4
comptime SIZE = 8
comptime BLOCKS_PER_GRID = (2, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
def add_10_shared(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
# Allocate shared memory using stack_allocation
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
if global_i < size:
shared[local_i] = a[global_i]
barrier()
# FILL ME IN (roughly 2 lines)
์ ์ฒด ์ฝ๋ ๋ณด๊ธฐ: problems/p08/p08.mojo
ํ
- address_space ํ๋ผ๋ฏธํฐ๋ก TileTensor ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ฑ
- ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ์ผ๋ก ๋ฐ์ดํฐ ๋ก๋:
shared[local_i] = a[global_i] barrier()๋ก ๋๊ธฐํ (ํ์ต์ฉ - ์ฌ๊ธฐ์๋ ์๋ฐํ ํ์ํ์ง ์์)- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ธ๋ฑ์ค๋ก ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ์ ๋ฐฉ์งํ๋ ๊ฐ๋
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p08
pixi run -e amd p08
pixi run -e apple p08
uv run poe p08
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([11.0, 11.0, 11.0, 11.0, 11.0, 11.0, 11.0, 11.0])
์๋ฃจ์
def add_10_shared_tile_tensor(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
# Allocate shared memory using stack_allocation
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
if global_i < size:
shared[local_i] = a[global_i]
# Note: barrier is not strictly needed here since each thread only accesses
# its own shared memory location. However, it's included to teach proper
# shared memory synchronization patterns for more complex scenarios where
# threads need to coordinate access to shared data.
barrier()
if global_i < size:
output[global_i] = shared[local_i] + 10
TileTensor๊ฐ ์ฑ๋ฅ์ ์ ์งํ๋ฉด์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ ์ผ๋ง๋ ๊ฐ์ํํ๋์ง ๋ณด์ฌ์ฃผ๋ ์๋ฃจ์ ์ ๋๋ค:
-
TileTensor๋ฅผ ์ฌ์ฉํ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ
-
์ ์ญ ํ ์:
a์output(๋๋ฆผ, ๋ชจ๋ ๋ธ๋ก์์ ๋ณด์) -
๊ณต์ ํ ์:
shared(๋น ๋ฆ, ์ค๋ ๋ ๋ธ๋ก ๋ก์ปฌ) -
๋ธ๋ก๋น 4๊ฐ ์ค๋ ๋๋ก 8๊ฐ ์์๋ฅผ ์ฒ๋ฆฌํ๋ ์์:
์ ์ญ ํ ์ a: [1 1 1 1 | 1 1 1 1] # ์ ๋ ฅ: ๋ชจ๋ 1 Block (0): Block (1): shared[0..3] shared[0..3] [1 1 1 1] [1 1 1 1]
-
-
์ค๋ ๋ ์กฐ์จ
-
๋ก๋ ๋จ๊ณ (์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ ์ฌ์ฉ):
Thread 0: shared[0] = a[0]=1 Thread 2: shared[2] = a[2]=1 Thread 1: shared[1] = a[1]=1 Thread 3: shared[3] = a[3]=1 barrier() โ โ โ โ # ๋ชจ๋ ๋ก๋ ์๋ฃ ๋๊ธฐ -
์ฒ๋ฆฌ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ๊ณต์ ํ ์ ๊ฐ์ 10์ ๋ํจ
-
๊ฒฐ๊ณผ:
output[global_i] = shared[local_i] + 10 = 11
-
์ฐธ๊ณ : ์ด ๊ฒฝ์ฐ์๋ ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น(shared[local_i])์๋ง
์ฐ๊ณ ์ฝ์ผ๋ฏ๋ก barrier()๊ฐ ์๋ฐํ ํ์ํ์ง ์์ต๋๋ค. ํ์ง๋ง ์ค๋ ๋๋ค์ด ์๋ก์
๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ์ํฉ์์ ํ์์ ์ธ ๋๊ธฐํ ํจํด์ ์ตํ๊ธฐ ์ํด ํฌํจ๋์ด ์์ต๋๋ค.
-
TileTensor์ ์ฅ์
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น:
# address_space๋ฅผ ์ฌ์ฉํ ๊น๋ํ TileTensor API shared = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB]()) -
์ ์ญ๊ณผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ:
Block 0 ์ถ๋ ฅ: [11 11 11 11] Block 1 ์ถ๋ ฅ: [11 11 11 11] -
๋ด์ฅ๋ ๋ ์ด์์ ๊ด๋ฆฌ์ ํ์ ์์ ์ฑ
-
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ก๋: ์ ์ญ ํ ์ โ ๊ณต์ ํ ์ (์ต์ ํ๋จ)
- ๋๊ธฐํ: ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์ ๊ณผ ๋์ผํ
barrier()ํ์ - ์ฒ๋ฆฌ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ฐ์ 10 ๋ํ๊ธฐ
- ์ ์ฅ: ๊ฒฐ๊ณผ(11)๋ฅผ ์ ์ญ ํ ์์ ์ฐ๊ธฐ
์ด ํจํด์ TileTensor๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ฑ๋ฅ ์ด์ ์ ์ ์งํ๋ฉด์ ๋ ํธ๋ฆฌํ API์ ๋ด์ฅ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
Puzzle 9: GPU ๋๋ฒ๊น ์ํฌํ๋ก์ฐ
โ ๏ธ ์ด ํผ์ฆ์ ํธํ๋๋ NVIDIA GPU์์๋ง ์๋ํฉ๋๋ค. ๋ค๋ฅธ GPU ๋ฒค๋ ์ง์์ ์ํ ๋๊ตฌ ๊ฐ๋ฐ์ด ์งํ ์ค์ ๋๋ค.
GPU ํ๋ก๊ทธ๋จ์ด ์คํจํ ๋
์ง๊ธ๊น์ง GPU ์ปค๋์ ์์ฑํ๊ณ , ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ค๋ฃจ๊ณ , ์์ฒ ๊ฐ์ ๋ณ๋ ฌ ์ค๋ ๋๋ฅผ ์กฐ์จํด ์์ต๋๋ค. ์ฝ๋๊ฐ ์ปดํ์ผ๋ฉ๋๋ค. ์ฌ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ํ๋ฉฐ ์คํํ๋ฉด:
- ํฌ๋์
- ์๋ชป๋ ๊ฒฐ๊ณผ
- ๋ฌดํ ์ ์ง
GPU ํ๋ก๊ทธ๋๋ฐ์ ํ์ค์ด ๋ฐ๋ก ์ด๊ฒ์ ๋๋ค. ์์ฒ ๊ฐ์ ์ค๋ ๋์์ ๋์์ ์คํ๋๋ ๋ณ๋ ฌ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํด์ผ ํ์ฃ . ์ด๋ก ๊ณผ ์ค์ ์ด ๋ง๋๊ณ , ์๊ณ ๋ฆฌ์ฆ ์ง์๊ณผ ์กฐ์ฌ ๋ฅ๋ ฅ์ด ๊ต์ฐจํ๋ ์์ญ์ ๋๋ค.
GPU ๋๋ฒ๊น ์ด ์ด๋ ค์ด ์ด์
๋จ์ผ ์ค๋ ๋์ ์์ฐจ ์คํ์ ๋ฐ๋ผ๊ฐ๋ ์ ํต์ ์ธ CPU ๋๋ฒ๊น ๊ณผ ๋ฌ๋ฆฌ, GPU ๋๋ฒ๊น ์ ๋ค์์ ์๊ตฌํฉ๋๋ค:
- ๋ณ๋ ฌ๋ก ์ฌ๊ณ ํ๊ธฐ: ์์ฒ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์์ ์คํ๋๋ฉฐ, ๊ฐ๊ฐ ๋ค๋ฅธ ์์ ์ ์ํํ ์ ์์
- ์ฌ๋ฌ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ ํ์: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ ์ง์คํฐ, ์์ ๋ฉ๋ชจ๋ฆฌ
- ์กฐ์จ ์คํจ ์ฒ๋ฆฌ: ๊ฒฝ์ ์ํ, ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ, ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์๋ฐ
- ์ต์ ํ๋ ์ฝ๋ ๋๋ฒ๊น : JIT ์ปดํ์ผ, ๋ณ์ ์ต์ ํ, ์ ํ๋ ์ฌ๋ณผ ์ ๋ณด
- ์ ๋ฌธ ๋๊ตฌ ์ฌ์ฉ: ์ปค๋ ๊ฒ์ฌ, ์ค๋ ๋ ํ์, ๋ณ๋ ฌ ์ํ ๋ถ์์ ์ํ CUDA-GDB
GPU ๋๋ฒ๊น ์ ์ตํ๋ฉด ๋ณ๋ ฌ ์ปดํจํ ์ ๊ธฐ์ด๋ฅผ ๊น์ด ์ดํดํ๊ฒ ๋ฉ๋๋ค.
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ
์ด ํผ์ฆ์์๋ GPU ์ฝ๋๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๋๋ฒ๊น ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. GPU ๊ฐ๋ฐ์๋ค์ด ๋ณต์กํ ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋งค์ผ ์ฌ์ฉํ๋ ์ ๊ทผ๋ฒ, ๋๊ตฌ, ๊ธฐ๋ฒ์ ์ตํ๊ฒ ๋ฉ๋๋ค.
์ตํ๊ฒ ๋ ํต์ฌ ๊ธฐ์
- ์ ๋ฌธ์ ์ธ ๋๋ฒ๊น ์ํฌํ๋ก์ฐ - ์ ๋ฌธ๊ฐ๋ค์ด ์ฌ์ฉํ๋ ์ฒด๊ณ์ ์ธ ์ ๊ทผ๋ฒ
- ๋๊ตฌ ์๋ จ๋ - ํธ์คํธ ์ฝ๋์ฉ LLDB, GPU ์ปค๋์ฉ CUDA-GDB
- ํจํด ์ธ์ - ํํ GPU ๋ฒ๊ทธ ์ ํ๊ณผ ์ฆ์
- ์กฐ์ฌ ๊ธฐ๋ฒ - ๋ณ์๊ฐ ์ต์ ํ๋ก ์ ๊ฑฐ๋์์ ๋ ๊ทผ๋ณธ ์์ธ ์ฐพ๊ธฐ
- ์ค๋ ๋ ์กฐ์จ ๋๋ฒ๊น - ๊ณ ๊ธ GPU ๋๋ฒ๊น ๊ธฐ์
์ค์ ๋๋ฒ๊น ์๋๋ฆฌ์ค
๊ฐ์ฅ ํํ ์ธ ๊ฐ์ง GPU ํ๋ก๊ทธ๋๋ฐ ์คํจ ์ํฉ์ ๋ค๋ฃน๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ํฌ๋์ - Null ํฌ์ธํฐ, ์๋ชป๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ, ์ธ๊ทธ๋ฉํ ์ด์ ํดํธ
- ๋ก์ง ๋ฒ๊ทธ - ์ ์ ์คํ๋์ง๋ง ๊ฒฐ๊ณผ๊ฐ ํ๋ฆผ, ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ
- ์กฐ์จ ๊ต์ฐฉ ์ํ - ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ ์คํจ, ๋ฌดํ ์ ์ง
๊ฐ ์๋๋ฆฌ์ค๋ ์๋ก ๋ค๋ฅธ ์กฐ์ฌ ๊ธฐ๋ฒ์ ๊ฐ๋ฅด์น๊ณ ๋๋ฒ๊น ๊ฐ๊ฐ์ ๊ธธ๋ฌ์ค๋๋ค.
๋๋ฒ๊น ์ฌ์
์ด ํผ์ฆ์ ๊ธฐ๋ณธ ๋๋ฒ๊น ๊ฐ๋ ๋ถํฐ ๊ณ ๊ธ ๋ณ๋ ฌ ์กฐ์จ ์คํจ๊น์ง, ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ๋ ๊ณผ์ ์ ์๋ดํฉ๋๋ค:
๐ Step 1: Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ
๊ธฐ์ด ๋ค์ง๊ธฐ - ๋๊ตฌ์ ์ํฌํ๋ก์ฐ ๋ฐฐ์ฐ๊ธฐ
pixi์ CUDA-GDB๋ก ๋๋ฒ๊น ํ๊ฒฝ ์ค์ - ๋ค ๊ฐ์ง ๋๋ฒ๊น ์ ๊ทผ๋ฒ ๋ฐฐ์ฐ๊ธฐ: JIT vs ๋ฐ์ด๋๋ฆฌ, CPU vs GPU
- GPU ์ปค๋ ๊ฒ์ฌ๋ฅผ ์ํ ํ์ CUDA-GDB ๋ช ๋ น์ด ํ์ต
- ์ด์ ํผ์ฆ์ ์ต์ํ ์ฝ๋๋ก ์ค์ต
- ๊ฐ ๋๋ฒ๊น ์ ๊ทผ๋ฒ์ ์ธ์ ์ฌ์ฉํด์ผ ํ๋์ง ์ดํด
๋ชฉํ: ์ ๋ฌธ์ ์ธ ๋๋ฒ๊น ์ํฌํ๋ก์ฐ์ ๋๊ตฌ ์๋ จ๋
๐ง Step 2: ํ์ ์์ฌ: ์ฒซ ๋ฒ์งธ ์ฌ๋ก
๋ฉ๋ชจ๋ฆฌ ํฌ๋์ ์กฐ์ฌ - ํฌ๋์๊ฐ ๋ฐ์ํ๋ GPU ํ๋ก๊ทธ๋จ ๋๋ฒ๊น
CUDA_ERROR_ILLEGAL_ADDRESSํฌ๋์ ์กฐ์ฌ- ์ฒด๊ณ์ ์ธ ํฌ์ธํฐ ๊ฒ์ฌ ๊ธฐ๋ฒ ํ์ต
- Null ํฌ์ธํฐ ํ์ง ๋ฐ ๊ฒ์ฆ ํ์ต
- ์ ๋ฌธ์ ์ธ ํฌ๋์ ๋ถ์ ์ํฌํ๋ก์ฐ ์ค์ต
- GPU ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์คํจ ์ดํด
๋ชฉํ: GPU ๋ฉ๋ชจ๋ฆฌ ํฌ๋์์ ํฌ์ธํฐ ๋ฌธ์ ๋๋ฒ๊น ๋ฅ๋ ฅ
๐ Step 3: ํ์ ์์ฌ: ๋ ๋ฒ์งธ ์ฌ๋ก
๋ก์ง ๋ฒ๊ทธ ์กฐ์ฌ - ๊ฒฐ๊ณผ๊ฐ ํ๋ฆฐ ํ๋ก๊ทธ๋จ ๋๋ฒ๊น
- TileTensor ๊ธฐ๋ฐ์ ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ ์กฐ์ฌ
- ์ต์ ํ๋ก ๋ณ์๊ฐ ์ฌ๋ผ์ก์ ๋ ์คํ ํ๋ฆ ๋ถ์ํ๊ธฐ
- ๋ฐ๋ณต๋ฌธ ๊ฒฝ๊ณ์ ๋ฐ๋ณต ํ์ ๋ถ์ํ๊ธฐ
- ํ๋ฆฐ ๊ฒฐ๊ณผ์์ ํจํด ์ฐพ์๋ด๊ธฐ
- ๋ณ์๋ฅผ ์ง์ ํ์ธํ์ง ์๊ณ ๋๋ฒ๊น ํ๊ธฐ
๋ชฉํ: GPU ์ปค๋์ ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ์ ๋ก์ง ๋ฒ๊ทธ ๋๋ฒ๊น ๋ฅ๋ ฅ
๐ต๏ธ Step 4: ํ์ ์์ฌ: ์ธ ๋ฒ์งธ ์ฌ๋ก
๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ ์กฐ์ฌ - ์์ํ ๋ฉ์ถ๋ ํ๋ก๊ทธ๋จ ๋๋ฒ๊น
- ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ ์คํจ ์กฐ์ฌ
- ๋ณ๋ ฌ ์คํ ์ ๋ฐ์ ๋ฉํฐ ์ค๋ ๋ ์ํ ๋ถ์ ํ์ต
- ์กฐ๊ฑด๋ถ ์คํ ๊ฒฝ๋ก ์ถ์ ํ์ต
- ์ค๋ ๋ ์กฐ์จ ๋๋ฒ๊น ์ค์ต
- ๊ฐ์ฅ ์ด๋ ค์ด GPU ๋๋ฒ๊น ์๋๋ฆฌ์ค ์ดํด
๋ชฉํ: ๊ณ ๊ธ ์ค๋ ๋ ์กฐ์จ ๋๋ฒ๊น - GPU ๋๋ฒ๊น ๊ธฐ์ ์ ์ ์
ํ์ ์ ๋ง์ธ๋์
GPU ๋๋ฒ๊น ์ ์ผ๋ฐ์ ์ธ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๋ค๋ฅธ ์ฌ๊ณ ๋ฐฉ์์ ์๊ตฌํฉ๋๋ค. ์ฌ๋ฌ๋ถ์ ๋ฒ์ฃ ํ์ฅ์ ์กฐ์ฌํ๋ ํ์ ์ด ๋ฉ๋๋ค:
- ๋จ์๊ฐ ๋ถ์กฑํจ - ๋ณ์๋ ์ต์ ํ๋ก ์ฌ๋ผ์ง๊ณ , ์ฌ๋ณผ๋ช ์ ์์๋ณด๊ธฐ ์ด๋ ค์
- ์ฉ์์๊ฐ ๋์นจ - ์์ฒ ๊ฐ์ ์ค๋ ๋, ๋๊ตฌ๋ ๋ฒ์ธ์ผ ์ ์์
- ํ์๋ผ์ธ์ด ๋ณต์กํจ - ๋ณ๋ ฌ ์คํ, ๊ฒฝ์ ์ํ, ํ์ด๋ฐ ์์กด์ฑ
- ์ ๋ฌธ ๋๊ตฌ๊ฐ ํ์ํจ - CUDA-GDB, ์ค๋ ๋ ํ์, GPU ๋ฉ๋ชจ๋ฆฌ ๊ฒ์ฌ
ํ์ง๋ง ํ๋ฅญํ ํ์ ์ด ๊ทธ๋ ๋ฏ, ์ฌ๋ฌ๋ถ๋ ๋ค์์ ๋ฐฐ์ฐ๊ฒ ๋ฉ๋๋ค:
- ๋จ์๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ถ์ - ์๋ฌ ๋ฉ์์ง, ํฌ๋์ ํจํด, ์ค๋ ๋ ์ํ
- ๊ฐ์ค ์๋ฆฝ - ์ด ๋์์ ์ผ์ผํฌ ์ ์๋ ์์ธ์ ๋ฌด์์ผ๊น?
- ์ด๋ก ๊ฒ์ฆ - ๋๋ฒ๊น ๋ช ๋ น์ด๋ก ์์ด๋์ด๋ฅผ ํ์ธํ๊ฑฐ๋ ๋ฐ์ฆ
- ๊ทผ๋ณธ ์์ธ ์ถ์ - ์ฆ์์์ ์ค์ ๋ฌธ์ ์ ์์ธ๊น์ง
์์ํ๊ธฐ ์ ์
์์์ผ ํ ๊ฒ:
- Puzzle 1-8์์ ๋ค๋ฃฌ GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ (์ค๋ ๋ ์ธ๋ฑ์ฑ, ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ๋ฐฐ๋ฆฌ์ด)
- ๊ธฐ๋ณธ์ ์ธ ๋ช ๋ น์ค ์ฌ์ฉ์ ์ต์ํจ (ํฐ๋ฏธ๋ ๊ธฐ๋ฐ ๋๋ฒ๊น ๋๊ตฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค)
- ์ธ๋ด์ฌ๊ณผ ์ฒด๊ณ์ ์ฌ๊ณ (GPU ๋๋ฒ๊น ์ ๊ผผ๊ผผํ ์กฐ์ฌ๊ฐ ํ์ํฉ๋๋ค)
๋ชฉํ:
- GPU ๊ฐ๋ฐํ์์ ์ฌ์ฉํ๋ ์ ๋ฌธ ๋๋ฒ๊น ๊ธฐ์
- ์ค๋ ๋ ์์ค์ ์คํ์ ๊ด์ฐฐํ๋ฉฐ ์ป๋ ๋ณ๋ ฌ ์ปดํจํ ์ ๋ํ ๊น์ ์ดํด
- ๊ฐ์ฅ ๊น๋ค๋ก์ด GPU ํ๋ก๊ทธ๋๋ฐ ์ํฉ์์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค๋ ์์ ๊ฐ
- GPU ํ๋ก๊ทธ๋๋ฐ ์ปค๋ฆฌ์ด ์ ๋ฐ์ ๋์์ด ๋ ๋๊ตฌ ์๋ จ๋
์์ํ ์ค๋น๊ฐ ๋์ จ๋์?
GPU ๋๋ฒ๊น ์ GPU ํ๋ก๊ทธ๋จ์ ์์ฑํ๋ ๊ฒ์์ ๊น์ด ์ดํดํ๋ ๊ฒ์ผ๋ก ๋์๊ฐ๋ ๊ณผ์ ์ ๋๋ค. ์ ๋ฌธ GPU ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ๋ณ๋ ฌ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ๊ณ , ์์ฒ ๊ฐ์ ์ค๋ ๋๋ก ๋์์ ์ฌ๊ณ ํ๋ ๋ฒ์ ์ตํ๊ณ , ๋ณต์กํ ์กฐ์จ ์คํจ๋ฅผ ๋๊ธฐ ์๊ฒ ์กฐ์ฌํ๋ฉฐ ์๋ง์ ์๊ฐ์ ๋ณด๋์ต๋๋ค.
์ง๊ธ์ด ๋ฐ๋ก ๊ทธ ์ ๋ฌธ๊ฐ ๊ทธ๋ฃน์ ํฉ๋ฅํ ๊ธฐํ์ ๋๋ค.
๋๋ฒ๊น ์ฌ์ ์์ํ๊ธฐ: Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ
โ๋๋ฒ๊น ์ ์ฝ๋ ์์ฑ๋ณด๋ค ๋ ๋ฐฐ๋ ์ด๋ ต๋ค. ๋ฐ๋ผ์ ์ต๋ํ ์๋ฆฌํ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ค๋ฉด, ์ ์์ ๊ทธ๊ฒ์ ๋๋ฒ๊น ํ ๋งํผ ๋๋ํ์ง ์๋ค๋ ๋ป์ด๋ค.โ - Brian Kernighan
GPU ํ๋ก๊ทธ๋๋ฐ์์๋ ์ด ๋ง์ด ์์ฒ ๋ฐฐ๋ก ์๋ฟ์ต๋๋ค. ๋์์ ๋๋ฒ๊น ํด์ผ ํ ๋ณ๋ ฌ ์ค๋ ๋ ์๋งํผ์.
๐ Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ
GPU ๋๋ฒ๊น ์ ์ธ๊ณ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! Puzzle 1-8์ ํตํด GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ์ ๋ฐฐ์ ์ผ๋, ์ด์ ๋ชจ๋ GPU ํ๋ก๊ทธ๋๋จธ์๊ฒ ๊ฐ์ฅ ์ค์ํ ๊ธฐ์ ์ ๋ฐฐ์ธ ์ค๋น๊ฐ ๋์์ต๋๋ค: ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๋ ๋๋ฒ๊น ํ๋ ๋ฐฉ๋ฒ.
GPU ๋๋ฒ๊น ์ ์ฒ์์๋ ์ด๋ ค์ ๋ณด์ผ ์ ์์ต๋๋ค. ์์ฒ ๊ฐ์ ์ค๋ ๋๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๊ณ , ๋ค์ํ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ด ์์ผ๋ฉฐ, ํ๋์จ์ด๋ณ ๋์๋ ๋ค๋ฃจ์ด์ผ ํฉ๋๋ค. ํ์ง๋ง ์ ์ ํ ๋๊ตฌ์ ์ํฌํ๋ก์ฐ๋ง ์์ผ๋ฉด GPU ์ฝ๋ ๋๋ฒ๊น ๋ ์ฒด๊ณ์ ์ผ๋ก ๋ค๋ฃฐ ์ ์์ต๋๋ค.
์ด ๊ฐ์ด๋์์๋ CPU ํธ์คํธ ์ฝ๋(GPU ์์ ์ ์ค์ ํ๋ ๋ถ๋ถ)์ GPU ์ปค๋ ์ฝ๋(๋ณ๋ ฌ ์ฐ์ฐ์ด ์คํ๋๋ ๋ถ๋ถ) ๋ชจ๋๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. ์ค์ ์์ , ์ค์ ๋๋ฒ๊ฑฐ ์ถ๋ ฅ, ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๋จ๊ณ๋ณ ์ํฌํ๋ก์ฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ฐธ๊ณ : ๋ค์ ๋ด์ฉ์ ๋ฒ์ฉ IDE ํธํ์ฑ์ ์ํด ๋ช ๋ น์ค ๋๋ฒ๊น ์ ์ด์ ์ ๋ง์ถฅ๋๋ค. VS Code ๋๋ฒ๊น ์ ์ ํธํ๋ค๋ฉด Mojo ๋๋ฒ๊น ๋ฌธ์์์ VS Code ์ ์ฉ ์ค์ ๊ณผ ์ํฌํ๋ก์ฐ๋ฅผ ์ฐธ์กฐํ์ธ์.
GPU ๋๋ฒ๊น ์ด ๋ค๋ฅธ ์ด์
๋๊ตฌ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์, GPU ๋๋ฒ๊น ์ด ํน๋ณํ ์ด์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
- ์ ํต์ ์ธ CPU ๋๋ฒ๊น : ๋จ์ผ ์ค๋ ๋, ์์ฐจ ์คํ, ๋จ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ
- GPU ๋๋ฒ๊น : ์์ฒ ๊ฐ์ ์ค๋ ๋, ๋ณ๋ ฌ ์คํ, ์ฌ๋ฌ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ, ๊ฒฝ์ ์ํ
์ด๋ ๋ค์์ ํ ์ ์๋ ์ ๋ฌธ ๋๊ตฌ๊ฐ ํ์ํ๋ค๋ ์๋ฏธ์ ๋๋ค:
- ์๋ก ๋ค๋ฅธ GPU ์ค๋ ๋ ๊ฐ ์ ํ
- ์ค๋ ๋๋ณ ๋ณ์์ ๋ฉ๋ชจ๋ฆฌ ๊ฒ์ฌ
- ๋ณ๋ ฌ ์คํ์ ๋ณต์ก์ฑ ์ฒ๋ฆฌ
- CPU ์ค์ ์ฝ๋์ GPU ์ปค๋ ์ฝ๋ ๋ชจ๋ ๋๋ฒ๊น
๋๋ฒ๊น ๋๊ตฌ ๋ชจ์
Mojo์ GPU ๋๋ฒ๊น ๊ธฐ๋ฅ์ ํ์ฌ NVIDIA GPU๋ก ์ ํ๋ฉ๋๋ค. Mojo ๋๋ฒ๊น ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด Mojo ํจํค์ง์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค:
- CPU ์ธก ๋๋ฒ๊น ์ ์ํ Mojo ํ๋ฌ๊ทธ์ธ์ด ํฌํจ๋ LLDB ๋๋ฒ๊ฑฐ
- GPU ์ปค๋ ๋๋ฒ๊น ์ ์ํ CUDA-GDB ํตํฉ
- ๋ฒ์ฉ IDE ํธํ์ฑ์ ์ํ
mojo debug๋ฅผ ํตํ ๋ช ๋ น์ค ์ธํฐํ์ด์ค
GPU ์ ์ฉ ๋๋ฒ๊น ์ ๋ํด์๋ Mojo GPU ๋๋ฒ๊น ๊ฐ์ด๋์์ ์ถ๊ฐ ๊ธฐ์ ์ธ๋ถ ์ฌํญ์ ์ ๊ณตํฉ๋๋ค.
์ด ์ํคํ ์ฒ๋ ์ต์ํ ๋๋ฒ๊น ๋ช ๋ น์ด์ GPU ์ ์ฉ ๊ธฐ๋ฅ, ๋ ๊ฐ์ง ์ฅ์ ์ ๋ชจ๋ ์ ๊ณตํฉ๋๋ค.
๋๋ฒ๊น ์ํฌํ๋ก์ฐ: ๋ฌธ์ ์์ ํด๊ฒฐ๊น์ง
GPU ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ๊ฑฐ๋, ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ๋ด๊ฑฐ๋, ์์์น ๋ชปํ ๋์์ ํ ๋ ๋ค์์ ์ฒด๊ณ์ ์ธ ์ ๊ทผ๋ฒ์ ๋ฐ๋ฅด์ธ์:
- ๋๋ฒ๊น ์ ์ํ ์ฝ๋ ์ค๋น (์ต์ ํ ๋นํ์ฑํ, ๋๋ฒ๊ทธ ์ฌ๋ณผ ์ถ๊ฐ)
- ์ ์ ํ ๋๋ฒ๊ฑฐ ์ ํ (CPU ํธ์คํธ ์ฝ๋ vs GPU ์ปค๋ ๋๋ฒ๊น )
- ์ ๋ต์ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์ (๋ฌธ์ ๊ฐ ์์ฌ๋๋ ์์น์)
- ์คํ ๋ฐ ๊ฒ์ฌ (์ฝ๋๋ฅผ ๋จ๊ณ๋ณ๋ก ์คํํ๋ฉฐ ๋ณ์ ๊ฒ์ฌ)
- ํจํด ๋ถ์ (๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ, ์ค๋ ๋ ๋์, ๊ฒฝ์ ์ํ)
์ด ์ํฌํ๋ก์ฐ๋ Puzzle 01์ ๊ฐ๋จํ ๋ฐฐ์ด ์ฐ์ฐ์ด๋ Puzzle 08์ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๋๋ ์๊ด์์ด ์๋ํฉ๋๋ค.
Step 1: ๋๋ฒ๊น ์ ์ํ ์ฝ๋ ์ค๋น
๐ฅ ์ฒ ์น: ์ต์ ํ๋ ์ฝ๋๋ ์ ๋ ๋๋ฒ๊น ํ์ง ๋ง์ธ์. ์ต์ ํ๋ ๋ช ๋ น์ด ์์๋ฅผ ๋ฐ๊พธ๊ณ , ๋ณ์๋ฅผ ์ ๊ฑฐํ๊ณ , ํจ์๋ฅผ ์ธ๋ผ์ธํํ์ฌ ๋๋ฒ๊น ์ ๊ฑฐ์ ๋ถ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค.
๋๋ฒ๊ทธ ์ ๋ณด๋ก ๋น๋ํ๊ธฐ
๋๋ฒ๊น ์ฉ Mojo ํ๋ก๊ทธ๋จ์ ๋น๋ํ ๋๋ ํญ์ ๋๋ฒ๊ทธ ์ฌ๋ณผ์ ํฌํจํ์ธ์:
# ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด๋ก ๋น๋
mojo build -O0 -g your_program.mojo -o your_program_debug
์ด ํ๋๊ทธ๋ค์ด ํ๋ ์ผ:
-O0: ๋ชจ๋ ์ต์ ํ๋ฅผ ๋นํ์ฑํํ์ฌ ์๋ ์ฝ๋ ๊ตฌ์กฐ๋ฅผ ๋ณด์กด-g: ๋๋ฒ๊ฑฐ๊ฐ ๋จธ์ ์ฝ๋๋ฅผ Mojo ์์ค์ ๋งคํํ ์ ์๋๋ก ๋๋ฒ๊ทธ ์ฌ๋ณผ ํฌํจ-o: ์ฌ์ด ์๋ณ์ ์ํด ๋ช ๋ช ๋ ์ถ๋ ฅ ํ์ผ ์์ฑ
์ด๊ฒ์ด ์ค์ํ ์ด์
๋๋ฒ๊ทธ ์ฌ๋ณผ ์์ด๋ ๋๋ฒ๊น ์ธ์ ์ด ์ด๋ ๊ฒ ๋ณด์ ๋๋ค:
(lldb) print my_variable
error: use of undeclared identifier 'my_variable'
๋๋ฒ๊ทธ ์ฌ๋ณผ์ด ์์ผ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ฉ๋๋ค:
(lldb) print my_variable
(int) $0 = 42
Step 2: ๋๋ฒ๊น ์ ๊ทผ๋ฒ ์ ํ
์ฌ๊ธฐ์ GPU ๋๋ฒ๊น ์ด ํฅ๋ฏธ๋ก์์ง๋๋ค. ๋ค ๊ฐ์ง ๋ค๋ฅธ ์กฐํฉ ์ค์์ ์ ํํ ์ ์์ผ๋ฉฐ, ์ ์ ํ ๊ฒ์ ๊ณ ๋ฅด๋ฉด ์๊ฐ์ ์ ์ฝํ ์ ์์ต๋๋ค:
๋ค ๊ฐ์ง ๋๋ฒ๊น ์กฐํฉ
๋น ๋ฅธ ์ฐธ์กฐ:
# 1. JIT + LLDB: ์์ค์์ ์ง์ CPU ํธ์คํธ ์ฝ๋ ๋๋ฒ๊น
pixi run mojo debug your_gpu_program.mojo
# 2. JIT + CUDA-GDB: ์์ค์์ ์ง์ GPU ์ปค๋ ๋๋ฒ๊น
pixi run mojo debug --cuda-gdb --break-on-launch your_gpu_program.mojo
# 3. ๋ฐ์ด๋๋ฆฌ + LLDB: ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ์์ CPU ํธ์คํธ ์ฝ๋ ๋๋ฒ๊น
pixi run mojo build -O0 -g your_gpu_program.mojo -o your_program_debug
pixi run mojo debug your_program_debug
# 4. ๋ฐ์ด๋๋ฆฌ + CUDA-GDB: ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ์์ GPU ์ปค๋ ๋๋ฒ๊น
pixi run mojo debug --cuda-gdb --break-on-launch your_program_debug
๊ฐ ์ ๊ทผ๋ฒ์ ์ธ์ ์ฌ์ฉํ ๊น
ํ์ต๊ณผ ๋น ๋ฅธ ์คํ์ฉ:
- JIT ๋๋ฒ๊น ์ฌ์ฉ - ๋น๋ ๋จ๊ณ๊ฐ ํ์ ์์ด ๋ ๋น ๋ฅด๊ฒ ๋ฐ๋ณต ๊ฐ๋ฅ
๋ณธ๊ฒฉ์ ์ธ ๋๋ฒ๊น ์ธ์ ์ฉ:
- ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น ์ฌ์ฉ - ๋ ์์ธก ๊ฐ๋ฅํ๊ณ ๊น๋ํ ๋๋ฒ๊ฑฐ ์ถ๋ ฅ
CPU ์ธก ๋ฌธ์ ์ฉ (๋ฒํผ ํ ๋น, ํธ์คํธ ๋ฉ๋ชจ๋ฆฌ, ํ๋ก๊ทธ๋จ ๋ก์ง):
- LLDB ๋ชจ๋ ์ฌ์ฉ -
main()ํจ์์ ์ค์ ์ฝ๋ ๋๋ฒ๊น ์ ์ ํฉ
GPU ์ปค๋ ๋ฌธ์ ์ฉ (์ค๋ ๋ ๋์, GPU ๋ฉ๋ชจ๋ฆฌ, ์ปค๋ ํฌ๋์):
- CUDA-GDB ๋ชจ๋ ์ฌ์ฉ - ๊ฐ๋ณ GPU ์ค๋ ๋๋ฅผ ๊ฒ์ฌํ๋ ์ ์ผํ ๋ฐฉ๋ฒ
์ฅ์ ์ ๋ค์ํ๊ฒ ์กฐํฉํด์ ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ ๋๋ค. JIT + LLDB๋ก ์ค์ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ ๋ค์, JIT + CUDA-GDB๋ก ์ ํํด์ ์ค์ ์ปค๋์ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
CUDA-GDB๋ก GPU ์ปค๋ ๋๋ฒ๊น ์ดํดํ๊ธฐ
์ด์ GPU ์ปค๋ ๋๋ฒ๊น ์ ๋๋ค - ๋๋ฒ๊น ๋๊ตฌ ๋ชจ์์์ ๊ฐ์ฅ ๊ฐ๋ ฅํ๋ฉด์๋ ๋ณต์กํ ๋ถ๋ถ์ ๋๋ค.
--cuda-gdb๋ฅผ ์ฌ์ฉํ๋ฉด Mojo๋ NVIDIA์
CUDA-GDB ๋๋ฒ๊ฑฐ์
ํตํฉ๋ฉ๋๋ค. ์ด๊ฒ์ ๋จ์ํ ๋๋ฒ๊ฑฐ๊ฐ ์๋๋๋ค - GPU ์ปดํจํ
์ ๋ณ๋ ฌ ๋ฉํฐ์ค๋ ๋
์ธ๊ณ๋ฅผ ์ํด ํน๋ณํ ์ค๊ณ๋์์ต๋๋ค.
CUDA-GDB๊ฐ ํน๋ณํ ์ด์
์ผ๋ฐ GDB๋ ํ ๋ฒ์ ํ๋์ ์ค๋ ๋๋ฅผ ๋๋ฒ๊น ํ๋ฉฐ ์์ฐจ ์ฝ๋๋ฅผ ๋จ๊ณ๋ณ๋ก ์คํํฉ๋๋ค. CUDA-GDB๋ ์์ฒ ๊ฐ์ GPU ์ค๋ ๋๋ฅผ ๋์์ ๋๋ฒ๊น ํ๋ฉฐ, ๊ฐ๊ฐ์ด ์๋ก ๋ค๋ฅธ ๋ช ๋ น์ด๋ฅผ ์คํํ ์ ์์ต๋๋ค.
์ด๋ ๋ค์์ ํ ์ ์๋ค๋ ์๋ฏธ์ ๋๋ค:
- GPU ์ปค๋ ๋ด๋ถ์ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์ - ์ด๋ค ์ค๋ ๋๋ ๋ธ๋ ์ดํฌํฌ์ธํธ์ ๋๋ฌํ๋ฉด ์คํ์ ์ผ์ ์ ์ง
- GPU ์ค๋ ๋ ๊ฐ ์ ํ - ๊ฐ์ ์๊ฐ์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋ฌด์์ ํ๋์ง ๊ฒ์ฌ
- ์ค๋ ๋๋ณ ๋ฐ์ดํฐ ๊ฒ์ฌ - ๊ฐ์ ๋ณ์๊ฐ ์ค๋ ๋๋ง๋ค ๋ค๋ฅธ ๊ฐ์ ๊ฐ์ง๋ ๊ฒ์ ํ์ธ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋๋ฒ๊น - ๋ฒ์ ์ด๊ณผ ์ ๊ทผ, ๊ฒฝ์ ์ํ, ๋ฉ๋ชจ๋ฆฌ ์์ ํฌ์ฐฉ (์ด๋ฐ ๋ฌธ์ ๊ฐ์ง์ ๋ํด์๋ Puzzle 10์์ ๋ ์์ธํ)
- ๋ณ๋ ฌ ์คํ ๋ถ์ - ์ค๋ ๋๋ค์ด ์ด๋ป๊ฒ ์ํธ์์ฉํ๊ณ ๋๊ธฐํํ๋์ง ์ดํด
์ด์ ํผ์ฆ์ ๊ฐ๋ ๊ณผ ์ฐ๊ฒฐ
Puzzle 1-8์์ ๋ฐฐ์ด GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ์ ๊ธฐ์ตํ์๋์? CUDA-GDB๋ก ๋ฐํ์์ ๋ชจ๋ ๊ฒ์ ๊ฒ์ฌํ ์ ์์ต๋๋ค:
์ค๋ ๋ ๊ณ์ธต ๊ตฌ์กฐ ๋๋ฒ๊น
Puzzle 1-8์์ ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ต๋๋ค:
# Puzzle 1์์: ๊ธฐ๋ณธ ์ค๋ ๋ ์ธ๋ฑ์ฑ
i = thread_idx.x # ๊ฐ ์ค๋ ๋๊ฐ ๊ณ ์ ํ ์ธ๋ฑ์ค๋ฅผ ์ป์
# Puzzle 7์์: 2D ์ค๋ ๋ ์ธ๋ฑ์ฑ
row = thread_idx.y # 2D ์ค๋ ๋ ๊ทธ๋ฆฌ๋
col = thread_idx.x
CUDA-GDB๋ก ์ด ์ค๋ ๋ ์ขํ๋ค์ด ์ค์ ๋ก ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค:
(cuda-gdb) info cuda threads
์ถ๋ ฅ:
BlockIdx ThreadIdx To BlockIdx To ThreadIdx Count PC Filename Line
Kernel 0
* (0,0,0) (0,0,0) (0,0,0) (3,0,0) 4 0x00007fffcf26fed0 /home/ubuntu/workspace/mojo-gpu-puzzles/solutions/p01/p01.mojo 13
๊ทธ๋ฆฌ๊ณ ํน์ ์ค๋ ๋๋ก ์ด๋ํด์ ๋ฌด์์ ํ๋์ง ๋ณผ ์ ์์ต๋๋ค:
(cuda-gdb) cuda thread (1,0,0)
์ถ๋ ฅ:
[Switching to CUDA thread (1,0,0)]
์ ๋ง ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ๋๋ค - ๋ง ๊ทธ๋๋ก ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ด ์ฌ๋ฌ ์ค๋ ๋์์ ์คํ๋๋ ๊ฒ์ ์ง์ ์ง์ผ๋ณผ ์ ์์ต๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ ๋๋ฒ๊น
๋ค์ํ ์ ํ์ GPU ๋ฉ๋ชจ๋ฆฌ์ ๋ํด ๋ฐฐ์ด Puzzle 8์ ๊ธฐ์ตํ์๋์? CUDA-GDB๋ก ๋ชจ๋ ๊ฒ์ ๊ฒ์ฌํ ์ ์์ต๋๋ค:
# ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๊ฒ์ฌ (Puzzle 1-5์ ๋ฐฐ์ด๋ค)
(cuda-gdb) print input_array[0]@4
$1 = {{1}, {2}, {3}, {4}} # Mojo ์ค์นผ๋ผ ํ์
# ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ฒ์ฌ (thread_idx.x๋ ์๋ํ์ง ์์)
(cuda-gdb) print shared_data[i] # thread_idx.x ๋์ ๋ก์ปฌ ๋ณ์ 'i' ์ฌ์ฉ
$2 = {42}
๋๋ฒ๊ฑฐ๋ ๊ฐ ์ค๋ ๋๊ฐ ๋ฉ๋ชจ๋ฆฌ์์ ์ ํํ ๋ฌด์์ ๋ณด๋์ง ๋ณด์ฌ์ค๋๋ค. ์ด๋ ๊ฒฝ์ ์ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ฒ๊ทธ๋ฅผ ์ก๊ธฐ์ ์๋ฒฝํฉ๋๋ค.
์ ๋ต์ ๋ธ๋ ์ดํฌํฌ์ธํธ ๋ฐฐ์น
CUDA-GDB ๋ธ๋ ์ดํฌํฌ์ธํธ๋ ๋ณ๋ ฌ ์คํ๊ณผ ํจ๊ป ์๋ํ๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ ๋ธ๋ ์ดํฌํฌ์ธํธ๋ณด๋ค ํจ์ฌ ๊ฐ๋ ฅํฉ๋๋ค:
# ์ด๋ค ์ค๋ ๋๋ ์ปค๋์ ์ง์
ํ ๋ ์ค๋จ
(cuda-gdb) break add_kernel
# ํน์ ์ค๋ ๋์ ๋ํด์๋ง ์ค๋จ (๋ฌธ์ ๊ฒฉ๋ฆฌ์ ์ข์)
(cuda-gdb) break add_kernel if thread_idx.x == 0
# ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์๋ฐ ์ ์ค๋จ
(cuda-gdb) watch input_array[thread_idx.x]
# ํน์ ๋ฐ์ดํฐ ์กฐ๊ฑด์์ ์ค๋จ
(cuda-gdb) break add_kernel if input_array[thread_idx.x] > 100.0
์ด๋ฅผ ํตํด ์์ฒ ๊ฐ ์ค๋ ๋์ ์ถ๋ ฅ์ ํ๋ฌปํ์ง ์๊ณ ์ ํํ ๊ด์ฌ ์๋ ์ค๋ ๋์ ์กฐ๊ฑด์ ์ง์คํ ์ ์์ต๋๋ค.
ํ๊ฒฝ ์ค๋นํ๊ธฐ
๋๋ฒ๊น ์ ์์ํ๊ธฐ ์ ์ ๊ฐ๋ฐ ํ๊ฒฝ์ด ์ ๋๋ก ๊ตฌ์ฑ๋์ด ์๋์ง ํ์ธํ์ธ์. ์ด์ ํผ์ฆ๋ค์ ์งํํด์๋ค๋ฉด ๋๋ถ๋ถ ์ด๋ฏธ ์ค์ ๋์ด ์์ ๊ฒ์ ๋๋ค!
์ฐธ๊ณ : pixi ์์ด๋
NVIDIA ๊ณต์ ๋ฆฌ์์ค์์ CUDA
Toolkit์ ์๋์ผ๋ก ์ค์นํ๊ณ , ๋๋ผ์ด๋ฒ ํธํ์ฑ์ ๊ด๋ฆฌํ๊ณ , ํ๊ฒฝ ๋ณ์๋ฅผ ๊ตฌ์ฑํ๊ณ ,
์ปดํฌ๋ํธ ๊ฐ ๋ฒ์ ์ถฉ๋์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. pixi๋ ๋ชจ๋ CUDA ์์กด์ฑ, ๋ฒ์ , ํ๊ฒฝ
๊ตฌ์ฑ์ ์๋์ผ๋ก ๊ด๋ฆฌํ์ฌ ์ด ๋ณต์ก์ฑ์ ์ ๊ฑฐํฉ๋๋ค.
pixi๊ฐ ๋๋ฒ๊น
์ ์ค์ํ ์ด์
๋ฌธ์ ์ : GPU ๋๋ฒ๊น ์ CUDA ํดํท, GPU ๋๋ผ์ด๋ฒ, Mojo ์ปดํ์ผ๋ฌ, ๋๋ฒ๊ฑฐ ์ปดํฌ๋ํธ ๊ฐ์ ์ ๋ฐํ ์กฐ์จ์ด ํ์ํฉ๋๋ค. ๋ฒ์ ๋ถ์ผ์น๋ โ๋๋ฒ๊ฑฐ๋ฅผ ์ฐพ์ ์ ์์โ ์ค๋ฅ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
ํด๊ฒฐ์ฑ
: pixi๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ์กฐํ๋กญ๊ฒ ์๋ํฉ๋๋ค.
pixi run mojo debug --cuda-gdb๋ฅผ ์คํํ๋ฉด pixi๊ฐ ์๋์ผ๋ก:
- CUDA ํดํท ๊ฒฝ๋ก ์ค์
- ์ฌ๋ฐ๋ฅธ GPU ๋๋ผ์ด๋ฒ ๋ก๋
- Mojo ๋๋ฒ๊น ํ๋ฌ๊ทธ์ธ ๊ตฌ์ฑ
- ํ๊ฒฝ ๋ณ์๋ฅผ ์ผ๊ด๋๊ฒ ๊ด๋ฆฌ
์ค์ ํ์ธ
๋ชจ๋ ๊ฒ์ด ์๋ํ๋์ง ํ์ธํด ๋ด ์๋ค:
# 1. GPU ํ๋์จ์ด ์ ๊ทผ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
pixi run nvidia-smi
# GPU์ ๋๋ผ์ด๋ฒ ๋ฒ์ ์ด ํ์๋์ด์ผ ํจ
# 2. CUDA-GDB ํตํฉ ์ค์ (GPU ๋๋ฒ๊น
์ ํ์)
pixi run setup-cuda-gdb
# ์์คํ
CUDA-GDB ๋ฐ์ด๋๋ฆฌ๋ฅผ conda ํ๊ฒฝ์ ๋งํฌ
# 3. Mojo ๋๋ฒ๊ฑฐ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
pixi run mojo debug --help
# --cuda-gdb๋ฅผ ํฌํจํ ๋๋ฒ๊น
์ต์
์ด ํ์๋์ด์ผ ํจ
# 4. CUDA-GDB ํตํฉ ํ
์คํธ
pixi run cuda-gdb --version
# NVIDIA CUDA-GDB ๋ฒ์ ์ ๋ณด๊ฐ ํ์๋์ด์ผ ํจ
์ด ๋ช
๋ น์ด ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด pixi.toml ๊ตฌ์ฑ์ ๋ค์ ํ์ธํ๊ณ CUDA ํดํท ๊ธฐ๋ฅ์ด
ํ์ฑํ๋์ด ์๋์ง ํ์ธํ์ธ์.
์ค์: conda์ cuda-gdb ํจํค์ง๋ ๋ํผ ์คํฌ๋ฆฝํธ๋ง ์ ๊ณตํ๊ธฐ ๋๋ฌธ์
pixi run setup-cuda-gdb ๋ช
๋ น์ด ํ์ํฉ๋๋ค. ์ด ๋ช
๋ น์ ์์คํ
CUDA ์ค์น์์ ์ค์
CUDA-GDB ๋ฐ์ด๋๋ฆฌ๋ฅผ ์๋ ๊ฐ์งํ๊ณ conda ํ๊ฒฝ์ ๋งํฌํ์ฌ ์ ์ฒด GPU ๋๋ฒ๊น
๊ธฐ๋ฅ์
ํ์ฑํํฉ๋๋ค.
์ด ๋ช ๋ น์ด ํ๋ ์ผ:
์คํฌ๋ฆฝํธ๋ ์ฌ๋ฌ ์ผ๋ฐ์ ์ธ ์์น์์ CUDA๋ฅผ ์๋ ๊ฐ์งํฉ๋๋ค:
$CUDA_HOMEํ๊ฒฝ ๋ณ์/usr/local/cuda(Ubuntu/Debian ๊ธฐ๋ณธ๊ฐ)/opt/cuda(ArchLinux ๋ฐ ๊ธฐํ ๋ฐฐํฌํ)- ์์คํ
PATH (
which cuda-gdbํตํด)
๊ตฌํ ์ธ๋ถ ์ฌํญ์
scripts/setup-cuda-gdb.sh๋ฅผ
์ฐธ์กฐํ์ธ์.
WSL ์ฌ์ฉ์๋ฅผ ์ํ ํน๋ณ ์ฐธ๊ณ ์ฌํญ: Part II์์ ์ฌ์ฉํ ๋ ๊ฐ์ง ๋๋ฒ๊ทธ
๋๊ตฌ(cuda-gdb์ compute-sanitizer)๋ WSL์์ CUDA ์ ํ๋ฆฌ์ผ์ด์
๋๋ฒ๊น
์
์ง์ํ์ง๋ง, ๋ ์ง์คํธ๋ฆฌ ํค
HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation\GPUDebugger\EnableInterface๋ฅผ
์ถ๊ฐํ๊ณ (DWORD) 1๋ก ์ค์ ํด์ผ ํฉ๋๋ค. ์ง์๋๋ ํ๋ซํผ๊ณผ OS๋ณ ๋์์ ๋ํ
์์ธํ ๋ด์ฉ์
cuda-gdb์
compute-sanitizer๋ฅผ
์ฐธ์กฐํ์ธ์.
์ค์ต ํํ ๋ฆฌ์ผ: ์ฒซ GPU ๋๋ฒ๊น ์ธ์
์ด๋ก ๋ ์ข์ง๋ง ์ง์ ๊ฒฝํํ๋ ๊ฒ๋ง ํ ๊ฒ ์์ต๋๋ค. Puzzle 01 - ์ฌ๋ฌ๋ถ์ด ์ ์๋ ๊ฐ๋จํ โ๋ฐฐ์ด ๊ฐ ์์์ 10 ๋ํ๊ธฐโ ์ปค๋์ ์ฌ์ฉํด์ ์ค์ ํ๋ก๊ทธ๋จ์ ๋๋ฒ๊น ํด ๋ด ์๋ค.
์ Puzzle 01์ธ๊ฐ? ๋ค์ ์ด์ ๋ก ์๋ฒฝํ ๋๋ฒ๊น ํํ ๋ฆฌ์ผ์ ๋๋ค:
- ์ถฉ๋ถํ ๋จ์ํด์ ๋ฌด์์ด ์ผ์ด๋์ผ ํ๋์ง ์ดํดํ ์ ์์
- ์ค์ ์ปค๋ ์คํ์ด ์๋ ์ง์ง GPU ์ฝ๋
- CPU ์ค์ ์ฝ๋์ GPU ์ปค๋ ์ฝ๋ ๋ชจ๋ ํฌํจ
- ์งง์ ์คํ ์๊ฐ์ผ๋ก ๋น ๋ฅธ ๋ฐ๋ณต ๊ฐ๋ฅ
์ด ํํ ๋ฆฌ์ผ์ด ๋๋๋ฉด ๋ค ๊ฐ์ง ๋๋ฒ๊น ์ ๊ทผ๋ฒ ๋ชจ๋๋ก ๊ฐ์ ํ๋ก๊ทธ๋จ์ ๋๋ฒ๊น ํ๊ณ , ์ค์ ๋๋ฒ๊ฑฐ ์ถ๋ ฅ์ ๋ณด๊ณ , ๋งค์ผ ์ฌ์ฉํ ํ์ ๋๋ฒ๊น ๋ช ๋ น์ด๋ฅผ ๋ฐฐ์ฐ๊ฒ ๋ฉ๋๋ค.
๋๋ฒ๊น ์ ๊ทผ๋ฒ ํ์ต ๊ฒฝ๋ก
Puzzle 01์ ์์ ๋ก ๋ค ๊ฐ์ง ๋๋ฒ๊น ์กฐํฉ์ ํ์ํฉ๋๋ค. ํ์ต ๊ฒฝ๋ก: JIT + LLDB(๊ฐ์ฅ ์ฌ์)๋ก ์์ํด์ CUDA-GDB(๊ฐ์ฅ ๊ฐ๋ ฅํจ)๋ก ์งํํฉ๋๋ค.
โ ๏ธ GPU ๋๋ฒ๊น ์ ์ค์์ฌํญ:
--break-on-launchํ๋๊ทธ๋ CUDA-GDB ์ ๊ทผ๋ฒ์์ ํ์- ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ (์ ๊ทผ๋ฒ 3 & 4)๋ ๋๋ฒ๊น
์ ์ํด
i๊ฐ์ ๋ก์ปฌ ๋ณ์๋ฅผ ๋ณด์กด - JIT ์ปดํ์ผ (์ ๊ทผ๋ฒ 1 & 2)์ ๋๋ถ๋ถ์ ๋ก์ปฌ ๋ณ์๋ฅผ ์ต์ ํ๋ก ์ ๊ฑฐ
- ๋ณธ๊ฒฉ์ ์ธ GPU ๋๋ฒ๊น ์๋ ์ ๊ทผ๋ฒ 4 (๋ฐ์ด๋๋ฆฌ + CUDA-GDB) ์ฌ์ฉ
ํํ ๋ฆฌ์ผ Step 1: LLDB๋ก CPU ๋๋ฒ๊น
๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋๋ฒ๊น
์๋๋ฆฌ์ค๋ก ์์ํฉ์๋ค: ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ๊ฑฐ๋ ์์์น
๋ชปํ ๋์์ ํด์ main() ํจ์์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๋์ง ๋ด์ผ ํ ๋.
๋ฏธ์ : Puzzle 01์ CPU ์ธก ์ค์ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ์ฌ Mojo๊ฐ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ด๊ธฐํํ๊ณ ์ปค๋์ ์คํํ๋ ๋ฐฉ๋ฒ์ ํ์ ํฉ๋๋ค.
๋๋ฒ๊ฑฐ ์คํ
JIT ์ปดํ์ผ๋ก LLDB ๋๋ฒ๊ฑฐ๋ฅผ ์์ํฉ๋๋ค:
# ํ ๋จ๊ณ๋ก p01.mojo๋ฅผ ์ปดํ์ผํ๊ณ ๋๋ฒ๊น
pixi run mojo debug solutions/p01/p01.mojo
LLDB ํ๋กฌํํธ๊ฐ ๋ณด์
๋๋ค: (lldb). ์ด์ ๋๋ฒ๊ฑฐ ์์์ ํ๋ก๊ทธ๋จ ์คํ์ ๊ฒ์ฌํ
์ค๋น๊ฐ ๋์์ต๋๋ค!
์ฒซ ๋๋ฒ๊น ๋ช ๋ น์ด๋ค
Puzzle 01์ด ์คํ๋ ๋ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๋์ง ์ถ์ ํด ๋ด ์๋ค. ๋ณด์ฌ๋๋ฆฐ ๋๋ก ์ ํํ ์ด ๋ช ๋ น์ด๋ค์ ์ ๋ ฅํ๊ณ ์ถ๋ ฅ์ ๊ด์ฐฐํ์ธ์:
Step 1: main ํจ์์ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์
(lldb) br set -n main
์ถ๋ ฅ:
Breakpoint 1: where = mojo`main, address = 0x00000000027d7530
๋๋ฒ๊ฑฐ๊ฐ main ํจ์๋ฅผ ์ฐพ์๊ณ ๊ฑฐ๊ธฐ์ ์คํ์ ์ผ์ ์ ์งํฉ๋๋ค.
Step 2: ํ๋ก๊ทธ๋จ ์์
(lldb) run
์ถ๋ ฅ:
Process 186951 launched: '/home/ubuntu/workspace/mojo-gpu-puzzles/.pixi/envs/default/bin/mojo' (x86_64)
Process 186951 stopped
* thread #1, name = 'mojo', stop reason = breakpoint 1.1
frame #0: 0x0000555557d2b530 mojo`main
mojo`main:
-> 0x555557d2b530 <+0>: pushq %rbp
0x555557d2b531 <+1>: movq %rsp, %rbp
...
ํ๋ก๊ทธ๋จ์ด ๋ธ๋ ์ดํฌํฌ์ธํธ์์ ๋ฉ์ท์ต๋๋ค. ํ์ฌ ์ด์ ๋ธ๋ฆฌ ์ฝ๋๋ฅผ ๋ณด๊ณ ์๋๋ฐ ์ด๋ ์ ์์ ๋๋ค - ๋๋ฒ๊ฑฐ๊ฐ ๊ณ ์์ค Mojo ์์ค์ ๋๋ฌํ๊ธฐ ์ ์ ์ ์์ค ๋จธ์ ์ฝ๋์์ ์์ํฉ๋๋ค.
Step 3: ์์ ๊ณผ์ ํ์
# ๋ช
๋ น์ด ํ๋๋ฅผ ๋จ๊ณ๋ณ ์คํ ์๋
(lldb) next
์ถ๋ ฅ:
Process 186951 stopped
* thread #1, name = 'mojo', stop reason = instruction step over
frame #0: 0x0000555557d2b531 mojo`main + 1
mojo`main:
-> 0x555557d2b531 <+1>: movq %rsp, %rbp
0x555557d2b534 <+4>: pushq %r15
...
์ด์ ๋ธ๋ฆฌ๋ฅผ ๋จ๊ณ๋ณ๋ก ์คํํ๋ ๊ฒ์ ์ง๋ฃจํ ์ ์์ต๋๋ค. ๋ ๊ด๋ จ ์๋ ๋ถ๋ถ์ผ๋ก ์งํํฉ์๋ค.
Step 4: Mojo ์์ค ์ฝ๋์ ๋๋ฌํ๊ธฐ ์ํด ๊ณ์
# ์์ ์ด์
๋ธ๋ฆฌ๋ฅผ ๊ฑด๋๋ฐ์ด ์ค์ ์ฝ๋๋ก ์ด๋
(lldb) continue
์ถ๋ ฅ:
Process 186951 resuming
Process 186951 stopped and restarted: thread 1 received signal: SIGCHLD
2 locations added to breakpoint 1
Process 186951 stopped
* thread #1, name = 'mojo', stop reason = breakpoint 1.3
frame #0: 0x00007fff5c01e841 JIT(0x7fff5c075000)`stdlib::builtin::_startup::__mojo_main_prototype(argc=([0] = 1), argv=0x00007fffffffa858) at _startup.mojo:95:4
Mojo์ ๋ฐํ์์ด ์ด๊ธฐํ ์ค์
๋๋ค. _startup.mojo๋ Mojo์ ๋ด๋ถ ์์ ์ฝ๋๋ฅผ
๋ํ๋
๋๋ค. SIGCHLD ์๊ทธ๋์ ์ ์์
๋๋ค - Mojo๊ฐ ๋ด๋ถ ํ๋ก์ธ์ค๋ฅผ ๊ด๋ฆฌํ๋
๋ฐฉ์์
๋๋ค.
Step 5: ์ค์ ์ฝ๋๋ก ๊ณ์
# ํ ๋ฒ ๋ continueํด์ p01.mojo ์ฝ๋์ ๋๋ฌ!
(lldb) continue
์ถ๋ ฅ:
Process 186951 resuming
Process 186951 stopped
* thread #1, name = 'mojo', stop reason = breakpoint 1.2
frame #0: 0x00007fff5c014040 JIT(0x7fff5c075000)`p01::main(__error__=<unavailable>) at p01.mojo:24:23
21
22
23 def main():
-> 24 with DeviceContext() as ctx:
25 out = ctx.enqueue_create_buffer[dtype](SIZE)
26 out.enqueue_fill(0)
27 a = ctx.enqueue_create_buffer[dtype](SIZE)
์ด์ ์ค์ Mojo ์์ค ์ฝ๋๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ฃผ๋ชฉํ ์ :
- p01.mojo ํ์ผ์ 21-27๋ฒ ์ค
- ํ์ฌ ์ค 24:
with DeviceContext() as ctx: - JIT ์ปดํ์ผ:
JIT(0x7fff5c075000)์ Mojo๊ฐ ์ฝ๋๋ฅผ ์ฆ์์์ ์ปดํ์ผํ์์ ๋ํ๋
Step 6: ํ๋ก๊ทธ๋จ ์๋ฃ
# ํ๋ก๊ทธ๋จ์ ์๋ฃ๊น์ง ์คํ
(lldb) continue
์ถ๋ ฅ:
Process 186951 resuming
out: HostBuffer([10.0, 11.0, 12.0, 13.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
Process 186951 exited with status = 0 (0x00000000)
๋ฐฐ์ด ๋ด์ฉ
๐ ์ถํํฉ๋๋ค! ์ฒซ GPU ํ๋ก๊ทธ๋จ ๋๋ฒ๊น ์ธ์ ์ ์๋ฃํ์ต๋๋ค. ๋ฌด์จ ์ผ์ด ์์๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค:
๊ฑฐ์ณ์จ ๋๋ฒ๊น ์ฌ์ :
- ์ด์ ๋ธ๋ฆฌ๋ก ์์ - ์ ์์ค ๋๋ฒ๊น ์์๋ ์ ์์ ์ธ ํ์์ด๋ฉฐ, ๋๋ฒ๊ฑฐ๊ฐ ๋จธ์ ์์ค์์ ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ณด์ฌ์ค
- Mojo ์์ ๊ณผ์ ํ์ - Mojo์ ๋ด๋ถ ์ด๊ธฐํ ์ฝ๋๊ฐ ์์์ ํ์ต
- ์์ค ์ฝ๋ ๋๋ฌ - ๊ตฌ๋ฌธ ๊ฐ์กฐ๊ฐ ๋ ์ค์ p01.mojo 21-27๋ฒ ์ค ํ์ธ
- JIT ์ปดํ์ผ ๊ด์ฐฐ - Mojo๊ฐ ์ฝ๋๋ฅผ ์ฆ์์์ ์ปดํ์ผํ๋ ๊ฒ์ ๊ด์ฐฐ
- ์ฑ๊ณต์ ์ธ ์คํ ํ์ธ - ํ๋ก๊ทธ๋จ์ด ์์๋ ์ถ๋ ฅ์ ์์ฑํจ์ ํ์ธ
LLDB ๋๋ฒ๊น ์ด ์ ๊ณตํ๋ ๊ฒ:
- โ
CPU ์ธก ๊ฐ์์ฑ:
main()ํจ์, ๋ฒํผ ํ ๋น, ๋ฉ๋ชจ๋ฆฌ ์ค์ ํ์ธ - โ ์์ค ์ฝ๋ ๊ฒ์ฌ: ์ค ๋ฒํธ๊ฐ ์๋ ์ค์ Mojo ์ฝ๋ ๋ณด๊ธฐ
- โ ๋ณ์ ๊ฒ์ฌ: ํธ์คํธ ์ธก ๋ณ์(CPU ๋ฉ๋ชจ๋ฆฌ) ๊ฐ ํ์ธ
- โ ํ๋ก๊ทธ๋จ ํ๋ฆ ์ ์ด: ์ค์ ๋ก์ง์ ์ค ๋จ์๋ก ๋จ๊ณ๋ณ ์คํ
- โ ์ค๋ฅ ์กฐ์ฌ: ์ฅ์น ์ค์ , ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ฑ์ ํฌ๋์ ๋๋ฒ๊น
LLDB๊ฐ ํ ์ ์๋ ๊ฒ:
- โ GPU ์ปค๋ ๊ฒ์ฌ:
add_10ํจ์ ์คํ ๋ด๋ถ๋ก ์ง์ ๋ถ๊ฐ๋ฅ - โ ์ค๋ ๋ ์์ค ๋๋ฒ๊น : ๊ฐ๋ณ GPU ์ค๋ ๋ ๋์ ํ์ธ ๋ถ๊ฐ
- โ GPU ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: GPU ์ค๋ ๋๊ฐ ๋ณด๋ ๋ฐ์ดํฐ ๊ฒ์ฌ ๋ถ๊ฐ
- โ ๋ณ๋ ฌ ์คํ ๋ถ์: ๊ฒฝ์ ์ํ๋ ๋๊ธฐํ ๋๋ฒ๊น ๋ถ๊ฐ
LLDB ๋๋ฒ๊น ์ ์ฌ์ฉํ ๋:
- GPU ์ฝ๋๊ฐ ์คํ๋๊ธฐ ์ ์ ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ ๋
- ๋ฒํผ ํ ๋น์ด๋ ๋ฉ๋ชจ๋ฆฌ ์ค์ ๋ฌธ์
- ํ๋ก๊ทธ๋จ ์ด๊ธฐํ์ ํ๋ฆ ์ดํด
- Mojo ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๋ป๊ฒ ์์๋๋์ง ํ์ต
- ๋น ๋ฅธ ํ๋กํ ํ์ดํ๊ณผ ์ฝ๋ ๋ณ๊ฒฝ ์คํ
ํต์ฌ ํต์ฐฐ: LLDB๋ ํธ์คํธ ์ธก ๋๋ฒ๊น ์ ์๋ฒฝํฉ๋๋ค - GPU ์คํ ์ ํ์ CPU์์ ์ผ์ด๋๋ ๋ชจ๋ ๊ฒ. ์ค์ GPU ์ปค๋ ๋๋ฒ๊น ์๋ ๋ค์ ์ ๊ทผ๋ฒ์ด ํ์ํฉ๋๋คโฆ
ํํ ๋ฆฌ์ผ Step 2: ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น
JIT ๋๋ฒ๊น ์ ๋ฐฐ์ ์ผ๋ ์ด์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ์ฉํ๋ ์ ๋ฌธ์ ์ธ ์ ๊ทผ๋ฒ์ ํ์ํฉ์๋ค.
์๋๋ฆฌ์ค: ์ฌ๋ฌ ํ์ผ์ด ์๋ ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฒ๊น ํ๊ฑฐ๋ ๊ฐ์ ํ๋ก๊ทธ๋จ์ ๋ฐ๋ณต์ ์ผ๋ก ๋๋ฒ๊น ํด์ผ ํฉ๋๋ค. ๋จผ์ ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋น๋ํ๋ฉด ๋ ๋ง์ ์ ์ด์ ๋น ๋ฅธ ๋๋ฒ๊น ๋ฐ๋ณต์ด ๊ฐ๋ฅํฉ๋๋ค.
๋๋ฒ๊ทธ ๋ฐ์ด๋๋ฆฌ ๋น๋
Step 1: ๋๋ฒ๊ทธ ์ ๋ณด๋ก ์ปดํ์ผ
# ๋๋ฒ๊ทธ ๋น๋ ์์ฑ (๋ช
ํํ ๋ช
๋ช
์ ์ฃผ๋ชฉ)
pixi run mojo build -O0 -g solutions/p01/p01.mojo -o solutions/p01/p01_debug
์ฌ๊ธฐ์ ์ผ์ด๋๋ ์ผ:
- ๐ง
-O0: ์ต์ ํ ๋นํ์ฑํ (์ ํํ ๋๋ฒ๊น ์ ๋ฐ๋์ ํ์) - ๐
-g: ๋จธ์ ์ฝ๋๋ฅผ ์์ค ์ฝ๋์ ๋งคํํ๋ ๋๋ฒ๊ทธ ์ฌ๋ณผ ํฌํจ - ๐
-o p01_debug: ๋ช ํํ๊ฒ ์ด๋ฆ ์ง์ ๋๋ฒ๊ทธ ๋ฐ์ด๋๋ฆฌ ์์ฑ
Step 2: ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น
# ๋ฏธ๋ฆฌ ๋น๋๋ ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น
pixi run mojo debug solutions/p01/p01_debug
๋ฌด์์ด ๋ค๋ฅธ๊ฐ (๊ทธ๋ฆฌ๊ณ ๋ ๋์๊ฐ)
์์ ๋น๊ต:
| JIT ๋๋ฒ๊น | ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น |
|---|---|
| ํ ๋จ๊ณ๋ก ์ปดํ์ผ + ๋๋ฒ๊น | ํ ๋ฒ ๋น๋, ์ฌ๋ฌ ๋ฒ ๋๋ฒ๊น |
| ๋๋ฆฐ ์์ (์ปดํ์ผ ์ค๋ฒํค๋) | ๋น ๋ฅธ ์์ |
| ์ปดํ์ผ ๋ฉ์์ง๊ฐ ๋๋ฒ๊ทธ ์ถ๋ ฅ๊ณผ ์์ | ๊น๋ํ ๋๋ฒ๊ฑฐ ์ถ๋ ฅ |
| ๋๋ฒ๊น ์ค ์์ฑ๋๋ ๋๋ฒ๊ทธ ์ฌ๋ณผ | ๊ณ ์ ๋ ๋๋ฒ๊ทธ ์ฌ๋ณผ |
๊ฐ์ LLDB ๋ช
๋ น์ด(br set -n main, run, continue)๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์
์ฐจ์ด๋ฅผ ๋๋ ์ ์์ต๋๋ค:
- ๋น ๋ฅธ ์์ - ์ปดํ์ผ ์ง์ฐ ์์
- ๊น๋ํ ์ถ๋ ฅ - JIT ์ปดํ์ผ ๋ฉ์์ง ์์
- ๋ ์์ธก ๊ฐ๋ฅ - ๋๋ฒ๊ทธ ์ฌ๋ณผ์ด ์คํ ๊ฐ์ ๋ณํ์ง ์์
- ์ ๋ฌธ์ ์ธ ์ํฌํ๋ก์ฐ - ํ๋ก๋์ ๋๋ฒ๊น ์ด ์ด๋ ๊ฒ ์๋ํจ
ํํ ๋ฆฌ์ผ Step 3: GPU ์ปค๋ ๋๋ฒ๊น
์ง๊ธ๊น์ง๋ CPU ํธ์คํธ ์ฝ๋ - ์ค์ , ๋ฉ๋ชจ๋ฆฌ ํ ๋น, ์ด๊ธฐํ๋ฅผ ๋๋ฒ๊น ํ์ต๋๋ค. ํ์ง๋ง ๋ณ๋ ฌ ์ฐ์ฐ์ด ์ผ์ด๋๋ ์ค์ GPU ์ปค๋์ ์ด๋จ๊น์?
๋ฌธ์ ์ : add_10 ์ปค๋์ ์ ์ฌ์ ์ผ๋ก ์์ฒ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์์ ์คํ๋๋
GPU์์ ์คํ๋ฉ๋๋ค. LLDB๋ GPU์ ๋ณ๋ ฌ ์คํ ํ๊ฒฝ์ ์ ๊ทผํ ์ ์์ต๋๋ค.
ํด๊ฒฐ์ฑ : CUDA-GDB - GPU ์ค๋ ๋, GPU ๋ฉ๋ชจ๋ฆฌ, ๋ณ๋ ฌ ์คํ์ ์ดํดํ๋ ์ ๋ฌธ ๋๋ฒ๊ฑฐ์ ๋๋ค.
CUDA-GDB๊ฐ ํ์ํ ์ด์
GPU ๋๋ฒ๊น ์ด ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์ด์ ๋ฅผ ์ดํดํฉ์๋ค:
CPU ๋๋ฒ๊น (LLDB):
- ์์ฐจ์ ์ผ๋ก ์คํ๋๋ ๋จ์ผ ์ค๋ ๋
- ์ถ์ ํ ์ฝ ์คํ์ด ํ๋๋ฟ
- ๋จ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ
- ๋ณ์๊ฐ ๋จ์ผ ๊ฐ์ ๊ฐ์ง
GPU ๋๋ฒ๊น (CUDA-GDB):
- ๋ณ๋ ฌ๋ก ์คํ๋๋ ์์ฒ ๊ฐ์ ์ค๋ ๋
- ์ฌ๋ฌ ์ฝ ์คํ (์ค๋ ๋๋น ํ๋)
- ๋ณต์กํ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ (์ ์ญ, ๊ณต์ , ๋ก์ปฌ, ๋ ์ง์คํฐ)
- ๊ฐ์ ๋ณ์๊ฐ ์ค๋ ๋๋ง๋ค ๋ค๋ฅธ ๊ฐ์ ๊ฐ์ง
์ค์ ์: add_10 ์ปค๋์์ thread_idx.x ๋ณ์๋ ๊ฐ ์ค๋ ๋๋ง๋ค ๋ค๋ฅธ ๊ฐ์
๊ฐ์ง๋๋ค - ์ค๋ ๋ 0์ 0์, ์ค๋ ๋ 1์ 1์ ๋ณด๋ ์์
๋๋ค. CUDA-GDB๋ง์ด ์ด
๋ณ๋ ฌ ํ์ค์ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
CUDA-GDB ๋๋ฒ๊ฑฐ ์คํ
Step 1: GPU ์ปค๋ ๋๋ฒ๊น ์์
์ ๊ทผ๋ฒ์ ์ ํํ์ธ์:
# ์ด๋ฏธ ์คํํ๋์ง ํ์ธ (ํ ๋ฒ์ด๋ฉด ์ถฉ๋ถ)
pixi run setup-cuda-gdb
# JIT + CUDA-GDB ์ฌ์ฉ (์์ ์ ๊ทผ๋ฒ 2)
pixi run mojo debug --cuda-gdb --break-on-launch solutions/p01/p01.mojo
ํ์ต๊ณผ ๋น ๋ฅธ ๋ฐ๋ณต์ ์ ํฉํ JIT + CUDA-GDB ์ ๊ทผ๋ฒ์ ์ฌ์ฉํฉ๋๋ค.
Step 2: ์คํํ๊ณ GPU ์ปค๋ ์ง์ ์ ์๋ ์ ์ง
CUDA-GDB ํ๋กฌํํธ๋ ์ด๋ ๊ฒ ๋ณด์
๋๋ค: (cuda-gdb). ํ๋ก๊ทธ๋จ์ ์์ํฉ๋๋ค:
# ํ๋ก๊ทธ๋จ ์คํ - GPU ์ปค๋์ด ์คํ๋ ๋ ์๋์ผ๋ก ์ ์ง
(cuda-gdb) run
์ถ๋ ฅ:
Starting program: /home/ubuntu/workspace/mojo-gpu-puzzles/.pixi/envs/default/bin/mojo...
[Thread debugging using libthread_db enabled]
...
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (0,0,0)]
CUDA thread hit application kernel entry function breakpoint, p01_add_10_UnsafePointer...
<<<(1,1,1),(4,1,1)>>> (output=0x302000000, a=0x302000200) at p01.mojo:16
16 i = thread_idx.x
์ฑ๊ณต! GPU ์ปค๋ ๋ด๋ถ์์ ์๋์ผ๋ก ์ ์งํ์ต๋๋ค! --break-on-launch ํ๋๊ทธ๊ฐ
์ปค๋ ์คํ์ ๊ฐ์งํ๊ณ ์ด์ i = thread_idx.x๊ฐ ์คํ๋๋ 16๋ฒ ์ค์ ์์ต๋๋ค.
์ค์: break add_10์ฒ๋ผ ์๋์ผ๋ก ๋ธ๋ ์ดํฌํฌ์ธํธ๋ฅผ ์ค์ ํ ํ์ ์์ต๋๋ค
- ์ปค๋ ์ง์
๋ธ๋ ์ดํฌํฌ์ธํธ๋ ์๋์
๋๋ค. GPU ์ปค๋ ํจ์๋ CUDA-GDB์์ ๋งน๊ธ๋ง๋
์ด๋ฆ(
p01_add_10_UnsafePointer...๊ฐ์)์ ๊ฐ์ง์ง๋ง, ์ด๋ฏธ ์ปค๋ ์์ ์์ผ๋ฏ๋ก ๋ฐ๋ก ๋๋ฒ๊น ์ ์์ํ ์ ์์ต๋๋ค.
Step 3: ๋ณ๋ ฌ ์คํ ํ์
# ๋ธ๋ ์ดํฌํฌ์ธํธ์์ ์ผ์ ์ ์ง๋ ๋ชจ๋ GPU ์ค๋ ๋ ๋ณด๊ธฐ
(cuda-gdb) info cuda threads
์ถ๋ ฅ:
BlockIdx ThreadIdx To BlockIdx To ThreadIdx Count PC Filename Line
Kernel 0
* (0,0,0) (0,0,0) (0,0,0) (3,0,0) 4 0x00007fffd326fb70 /home/ubuntu/workspace/mojo-gpu-puzzles/solutions/p01/p01.mojo 16
์๋ฒฝํฉ๋๋ค! Puzzle 01์ ๋ชจ๋ 4๊ฐ ๋ณ๋ ฌ GPU ์ค๋ ๋๋ฅผ ๋ณด์ฌ์ค๋๋ค:
*๊ฐ ํ์ฌ ์ค๋ ๋ ํ์:(0,0,0)- ๋๋ฒ๊น ์ค์ธ ์ค๋ ๋- ์ค๋ ๋ ๋ฒ์:
(0,0,0)์์(3,0,0)๊น์ง - ๋ธ๋ก์ ๋ชจ๋ 4๊ฐ ์ค๋ ๋ - Count:
4- ์ฝ๋์THREADS_PER_BLOCK = 4์ ์ผ์น - ๊ฐ์ ์์น: ๋ชจ๋ ์ค๋ ๋๊ฐ
p01.mojo์ 16๋ฒ ์ค์์ ์ผ์ ์ ์ง
Step 4: ์ปค๋์ ๋จ๊ณ๋ณ ์คํํ๊ณ ๋ณ์ ๊ฒ์ฌ
# 'next'๋ก ์ฝ๋ ๋จ๊ณ๋ณ ์คํ ('step'์ ๋ด๋ถ๋ก ๋ค์ด๊ฐ)
(cuda-gdb) next
์ถ๋ ฅ:
p01_add_10_UnsafePointer... at p01.mojo:17
17 output[i] = a[i] + 10.0
# ๋ก์ปฌ ๋ณ์๋ ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ์์ ์๋!
(cuda-gdb) print i
์ถ๋ ฅ:
$1 = 0 # ์ด ์ค๋ ๋์ ์ธ๋ฑ์ค (thread_idx.x ๊ฐ ์บก์ฒ)
# GPU ๋ด์ฅ ๋ณ์๋ ์๋ํ์ง ์์ง๋ง ํ์ ์์
(cuda-gdb) print thread_idx.x
์ถ๋ ฅ:
No symbol "thread_idx" in current context.
# ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํด ์ค๋ ๋๋ณ ๋ฐ์ดํฐ ์ ๊ทผ
(cuda-gdb) print a[i] # ์ด ์ค๋ ๋์ ์
๋ ฅ: a[0]
์ถ๋ ฅ:
$2 = {0} # ์
๋ ฅ ๊ฐ (Mojo ์ค์นผ๋ผ ํ์)
(cuda-gdb) print output[i] # ์ฐ์ฐ ์ ์ด ์ค๋ ๋์ ์ถ๋ ฅ
์ถ๋ ฅ:
$3 = {0} # ์์ง 0 - ์ฐ์ฐ์ด ์์ง ์คํ๋์ง ์์!
# ์ฐ์ฐ ์ค ์คํ
(cuda-gdb) next
์ถ๋ ฅ:
13 fn add_10( # ์ฐ์ฐ ํ ํจ์ ์๊ทธ๋์ฒ ์ค๋ก ์ด๋
# ์ด์ ๊ฒฐ๊ณผ ํ์ธ
(cuda-gdb) print output[i]
์ถ๋ ฅ:
$4 = {10} # ์ด์ ๊ณ์ฐ๋ ๊ฒฐ๊ณผ ํ์: 0 + 10 = 10
# ํจ์ ํ๋ผ๋ฏธํฐ๋ ์ฌ์ ํ ์ฌ์ฉ ๊ฐ๋ฅ
(cuda-gdb) print a
์ถ๋ ฅ:
$5 = (!pop.scalar<f32> * @register) 0x302000200
Step 5: ๋ณ๋ ฌ ์ค๋ ๋ ๊ฐ ์ด๋
# ๋ค๋ฅธ ์ค๋ ๋๋ก ์ ํํด์ ์คํ ํ์ธ
(cuda-gdb) cuda thread (1,0,0)
์ถ๋ ฅ:
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (1,0,0), device 0, sm 0, warp 0, lane 1]
13 fn add_10( # ์ค๋ ๋ 1๋ ํจ์ ์๊ทธ๋์ฒ์ ์์
# ์ค๋ ๋์ ๋ก์ปฌ ๋ณ์ ํ์ธ
(cuda-gdb) print i
์ถ๋ ฅ:
$5 = 1 # ์ค๋ ๋ 1์ ์ธ๋ฑ์ค (์ค๋ ๋ 0๊ณผ ๋ค๋ฆ!)
# ์ด ์ค๋ ๋๊ฐ ์ฒ๋ฆฌํ๋ ๊ฒ ๊ฒ์ฌ
(cuda-gdb) print a[i] # ์ด ์ค๋ ๋์ ์
๋ ฅ: a[1]
์ถ๋ ฅ:
$6 = {1} # ์ค๋ ๋ 1์ ์
๋ ฅ ๊ฐ
# ์ค๋ ๋ 1์ ์ฐ์ฐ์ ์ด๋ฏธ ์๋ฃ (๋ณ๋ ฌ ์คํ!)
(cuda-gdb) print output[i] # ์ด ์ค๋ ๋์ ์ถ๋ ฅ: output[1]
์ถ๋ ฅ:
$7 = {11} # 1 + 10 = 11 (์ด๋ฏธ ๊ณ์ฐ๋จ)
# ์ต๊ณ ์ ๊ธฐ๋ฒ: ๋ชจ๋ ์ค๋ ๋ ๊ฒฐ๊ณผ๋ฅผ ํ ๋ฒ์ ๋ณด๊ธฐ
(cuda-gdb) print output[0]@4
์ถ๋ ฅ:
$8 = {{10}, {11}, {12}, {13}} # ๋ชจ๋ 4๊ฐ ์ค๋ ๋์ ๊ฒฐ๊ณผ๋ฅผ ํ ๋ช
๋ น์ด๋ก!
(cuda-gdb) print a[0]@4
์ถ๋ ฅ:
$9 = {{0}, {1}, {2}, {3}} # ๋น๊ต๋ฅผ ์ํ ๋ชจ๋ ์
๋ ฅ ๊ฐ
# ๋๋ฌด ๋ฉ๋ฆฌ ์งํํ๋ฉด CUDA ์ปจํ
์คํธ๋ฅผ ์์ต๋๋ค
(cuda-gdb) next
์ถ๋ ฅ:
[Switching to Thread 0x7ffff7e25840 (LWP 306942)] # ํธ์คํธ ์ค๋ ๋๋ก ๋ณต๊ท
0x00007fffeca3f831 in ?? () from /lib/x86_64-linux-gnu/libcuda.so.1
(cuda-gdb) print output[i]
์ถ๋ ฅ:
No symbol "output" in current context. # GPU ์ปจํ
์คํธ๋ฅผ ์์!
์ด ๋๋ฒ๊น ์ธ์ ์ ํต์ฌ ํต์ฐฐ:
- ๐คฏ ๋ณ๋ ฌ ์คํ์ ์ง์ง์ ๋๋ค - ์ค๋ ๋ (1,0,0)์ผ๋ก ์ ํํ๋ฉด ์ด๋ฏธ ์ฐ์ฐ์ด ์๋ฃ๋์ด ์์ต๋๋ค!
- ๊ฐ ์ค๋ ๋๋ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋ด
๋๋ค -
i=0vsi=1,a[i]={0}vsa[i]={1},output[i]={10}vsoutput[i]={11} - ๋ฐฐ์ด ๊ฒ์ฌ๊ฐ ๊ฐ๋ ฅํฉ๋๋ค -
print output[0]@4๋ก ๋ชจ๋ ์ค๋ ๋์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:{{10}, {11}, {12}, {13}} - GPU ์ปจํ ์คํธ๋ ๊นจ์ง๊ธฐ ์ฝ์ต๋๋ค - ๋๋ฌด ๋ฉ๋ฆฌ ์งํํ๋ฉด ํธ์คํธ ์ค๋ ๋๋ก ๋์๊ฐ GPU ๋ณ์์ ์ ๊ทผํ ์ ์๊ฒ ๋ฉ๋๋ค
์ด๊ฒ์ด ๋ฐ๋ก ๋ณ๋ ฌ ์ปดํจํ ์ ๋ณธ์ง์ ๋๋ค: ๊ฐ์ ์ฝ๋, ์ค๋ ๋๋ง๋ค ๋ค๋ฅธ ๋ฐ์ดํฐ, ๋์ ์คํ.
CUDA-GDB๋ก ๋ฐฐ์ด ๋ด์ฉ
๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ๋ก GPU ์ปค๋ ์คํ ๋๋ฒ๊น ์ ์๋ฃํ์ต๋๋ค. ๋ค์์ ์ค์ ๋ก ์๋ํ๋ ๊ธฐ๋ฅ๋ค์ ๋๋ค:
์ต๋ํ GPU ๋๋ฒ๊น ๋ฅ๋ ฅ:
- โ
GPU ์ปค๋ ์๋ ๋๋ฒ๊น
-
--break-on-launch๊ฐ ์ปค๋ ์ง์ ์์ ์์ ์ ์งํฉ๋๋ค - โ
GPU ์ค๋ ๋ ๊ฐ ์ด๋ -
cuda thread๋ก ์ปจํ ์คํธ๋ฅผ ์ ํํฉ๋๋ค - โ
๋ก์ปฌ ๋ณ์ ์ ๊ทผ -
-O0 -g๋ก ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ์์print i๊ฐ ์๋ํฉ๋๋ค - โ
์ค๋ ๋๋ณ ๋ฐ์ดํฐ ๊ฒ์ฌ - ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ
i,a[i],output[i]๊ฐ์ ๋ณด์ฌ์ค๋๋ค - โ
๋ชจ๋ ์ค๋ ๋ ๊ฒฐ๊ณผ ๋ณด๊ธฐ -
print output[0]@4๋ก{{10}, {11}, {12}, {13}}์ ํ ๋ฒ์ ํ์ํฉ๋๋ค - โ
GPU ์ฝ๋ ๋จ๊ณ๋ณ ์คํ -
next๊ฐ ์ฐ์ฐ์ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค - โ ๋ณ๋ ฌ ์คํ ํ์ธ - ์ค๋ ๋๊ฐ ๋์์ ์คํ๋ฉ๋๋ค (์ ํํ๋ฉด ๋ค๋ฅธ ์ค๋ ๋๋ ์ด๋ฏธ ๊ณ์ฐ ์๋ฃ)
- โ
ํจ์ ํ๋ผ๋ฏธํฐ ์ ๊ทผ -
output๊ณผaํฌ์ธํฐ๋ฅผ ๊ฒ์ฌํ ์ ์์ต๋๋ค - โ GPU ๋ด์ฅ ๋ณ์ ์ฌ์ฉ ๋ถ๊ฐ -
thread_idx.x,blockIdx.x๋ฑ์ ์๋ํ์ง ์์ต๋๋ค (ํ์ง๋ง ๋ก์ปฌ ๋ณ์๋ ์๋ํฉ๋๋ค!) - ๐ Mojo ์ค์นผ๋ผ ํ์ - ๊ฐ์ด
10.0๋์{10}์ผ๋ก ํ์๋ฉ๋๋ค - โ ๏ธ ๊นจ์ง๊ธฐ ์ฌ์ด GPU ์ปจํ ์คํธ - ๋๋ฌด ๋ฉ๋ฆฌ ์งํํ๋ฉด GPU ๋ณ์์ ์ ๊ทผํ ์ ์๊ฒ ๋ฉ๋๋ค
ํต์ฌ ํต์ฐฐ:
- ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ ๋ฐ์ด๋๋ฆฌ (
mojo build -O0 -g)๋ ํ์์ ๋๋ค - ๋ก์ปฌ ๋ณ์๊ฐ ๋ณด์กด๋ฉ๋๋ค @N์ ์ฌ์ฉํ ๋ฐฐ์ด ๊ฒ์ฌ - ๋ชจ๋ ๋ณ๋ ฌ ๊ฒฐ๊ณผ๋ฅผ ํ ๋ฒ์ ๋ณด๋ ๊ฐ์ฅ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ๋๋ค- GPU ๋ด์ฅ ๋ณ์๋ ์์ต๋๋ค - ํ์ง๋ง
i๊ฐ์ ๋ก์ปฌ ๋ณ์๊ฐ ํ์ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ต๋๋ค - Mojo๋
{value}ํ์์ ์ฌ์ฉํฉ๋๋ค - ์ค์นผ๋ผ๊ฐ10.0๋์{10}์ผ๋ก ํ์๋ฉ๋๋ค - ๋จ๊ณ๋ณ ์คํ์ ์ฃผ์ํ์ธ์ - GPU ์ปจํ ์คํธ๋ฅผ ์๊ณ ํธ์คํธ ์ค๋ ๋๋ก ๋์๊ฐ๊ธฐ ์ฝ์ต๋๋ค
์ค์ ๋๋ฒ๊น ๊ธฐ๋ฒ๋ค
์ด์ ์ค์ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋ง์ฃผ์น๊ฒ ๋ ์ค์ฉ์ ์ธ ๋๋ฒ๊น ์๋๋ฆฌ์ค๋ฅผ ์ดํด๋ด ์๋ค:
๊ธฐ๋ฒ 1: ์ค๋ ๋ ๊ฒฝ๊ณ ํ์ธ
# ๋ชจ๋ 4๊ฐ ์ค๋ ๋๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ณ์ฐํ๋์ง ํ์ธ
(cuda-gdb) print output[0]@4
์ถ๋ ฅ:
$8 = {{10}, {11}, {12}, {13}} # ๋ชจ๋ 4๊ฐ ์ค๋ ๋๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ณ์ฐ
# ์ ํจ ๋ฒ์๋ฅผ ๋์ด ํ์ธํ์ฌ ๋ฒ์ ์ด๊ณผ ๋ฌธ์ ๊ฐ์ง
(cuda-gdb) print output[0]@5
์ถ๋ ฅ:
$9 = {{10}, {11}, {12}, {13}, {0}} # ์์ 4๋ ์ด๊ธฐํ๋์ง ์์ (์ข์!)
# ์
๋ ฅ๊ณผ ๋น๊ตํ์ฌ ์ฐ์ฐ ๊ฒ์ฆ
(cuda-gdb) print a[0]@4
์ถ๋ ฅ:
$10 = {{0}, {1}, {2}, {3}} # ์
๋ ฅ ๊ฐ: 0+10=10, 1+10=11 ๋ฑ
์ด๊ฒ์ด ์ค์ํ ์ด์ : ๋ฒ์ ์ด๊ณผ ์ ๊ทผ์ GPU ํฌ๋์์ ๊ฐ์ฅ ํํ ์์ธ์ ๋๋ค. ์ด๋ฐ ๋๋ฒ๊น ๋จ๊ณ๋ก ์ผ์ฐ ๋ฐ๊ฒฌํ ์ ์์ต๋๋ค.
๊ธฐ๋ฒ 2: ์ค๋ ๋ ๊ตฌ์ฑ ์ดํด
# ์ค๋ ๋๊ฐ ๋ธ๋ก์ผ๋ก ์ด๋ป๊ฒ ๊ตฌ์ฑ๋๋์ง ๋ณด๊ธฐ
(cuda-gdb) info cuda blocks
์ถ๋ ฅ:
BlockIdx To BlockIdx Count State
Kernel 0
* (0,0,0) (0,0,0) 1 running
# ํ์ฌ ๋ธ๋ก์ ๋ชจ๋ ์ค๋ ๋ ๋ณด๊ธฐ
(cuda-gdb) info cuda threads
์ถ๋ ฅ์ ์ด๋ค ์ค๋ ๋๊ฐ ํ์ฑ ์ํ์ธ์ง, ์ ์ง๋์๋์ง, ์ค๋ฅ๊ฐ ์๋์ง ๋ณด์ฌ์ค๋๋ค.
์ด๊ฒ์ด ์ค์ํ ์ด์ : ์ค๋ ๋ ๋ธ๋ก ๊ตฌ์ฑ์ ์ดํดํ๋ฉด ๋๊ธฐํ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
๊ธฐ๋ฒ 3: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ถ์
# GPU ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ํ์ธ:
(cuda-gdb) print a # ์
๋ ฅ ๋ฐฐ์ด GPU ํฌ์ธํฐ
์ถ๋ ฅ:
$9 = (!pop.scalar<f32> * @register) 0x302000200
(cuda-gdb) print output # ์ถ๋ ฅ ๋ฐฐ์ด GPU ํฌ์ธํฐ
์ถ๋ ฅ:
$10 = (!pop.scalar<f32> * @register) 0x302000000
# ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ์ธ:
(cuda-gdb) print a[i] # ๊ฐ ์ค๋ ๋๊ฐ 'i'๋ฅผ ์ฌ์ฉํด ์์ ์ ์์์ ์ ๊ทผ
์ถ๋ ฅ:
$11 = {0} # ์ค๋ ๋์ ์
๋ ฅ ๋ฐ์ดํฐ
์ด๊ฒ์ด ์ค์ํ ์ด์ : ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ฑ๋ฅ๊ณผ ์ ํ์ฑ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ์๋ชป๋ ํจํด์ ๊ฒฝ์ ์ํ๋ ํฌ๋์๋ฅผ ์ด๋ํฉ๋๋ค.
๊ธฐ๋ฒ 4: ๊ฒฐ๊ณผ ๊ฒ์ฆ ๋ฐ ์๋ฃ
# ์ปค๋ ์คํ์ ๋จ๊ณ๋ณ๋ก ์คํํ ํ ์ต์ข
๊ฒฐ๊ณผ ํ์ธ
(cuda-gdb) print output[0]@4
์ถ๋ ฅ:
$11 = {10.0, 11.0, 12.0, 13.0} # ์๋ฒฝ! ๊ฐ ์์๊ฐ 10 ์ฆ๊ฐ
# ํ๋ก๊ทธ๋จ์ ์ ์์ ์ผ๋ก ์๋ฃ
(cuda-gdb) continue
์ถ๋ ฅ:
...ํ๋ก๊ทธ๋จ ์ถ๋ ฅ์ด ์ฑ๊ณต ํ์...
# ๋๋ฒ๊ฑฐ ์ข
๋ฃ
(cuda-gdb) exit
์ค์ ๋ถํฐ ๊ฒฐ๊ณผ๊น์ง GPU ์ปค๋ ์คํ ๋๋ฒ๊น ์ ์๋ฃํ์ต๋๋ค.
GPU ๋๋ฒ๊น ์ฌ์ : ํต์ฌ ํต์ฐฐ
ํฌ๊ด์ ์ธ GPU ๋๋ฒ๊น ํํ ๋ฆฌ์ผ์ ์๋ฃํ์ต๋๋ค. ๋ณ๋ ฌ ์ปดํจํ ์ ๋ํด ๋ฐ๊ฒฌํ ๋ด์ฉ์ ๋๋ค:
๋ณ๋ ฌ ์คํ์ ๋ํ ๊น์ ํต์ฐฐ
-
์ค๋ ๋ ์ธ๋ฑ์ฑ์ ์ค์ :
thread_idx.x๊ฐ ๋ณ๋ ฌ ์ค๋ ๋๋ง๋ค ๋ค๋ฅธ ๊ฐ(0, 1, 2, 3โฆ)์ ๊ฐ๋ ๊ฒ์ ์ด๋ก ์ด ์๋ ์ง์ ํ์ธํ์ต๋๋ค -
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ์ : ๊ฐ ์ค๋ ๋๊ฐ
a[thread_idx.x]์์ ์ฝ๊ณoutput[thread_idx.x]์ ์ฐ๋ฉฐ, ์ถฉ๋ ์์ด ์๋ฒฝํ ๋ฐ์ดํฐ ๋ณ๋ ฌ์ฑ์ ๋ง๋ค์ด๋ ๋๋ค -
๋ณ๋ ฌ ์คํ์ ์ดํด: ์์ฒ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์ผํ ์ปค๋ ์ฝ๋๋ฅผ ๋์์ ์คํํ๋ฉด์ ๊ฐ๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ ์์๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
-
GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ: ๋ฐฐ์ด์ ์ ์ญ GPU ๋ฉ๋ชจ๋ฆฌ์ ์์ด ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ๊ทผํ ์ ์์ง๋ง, ์ค๋ ๋๋ณ ์ธ๋ฑ์ฑ์ ์ฌ์ฉํฉ๋๋ค
๋ชจ๋ ํผ์ฆ์ ์ ์ฉ๋๋ ๋๋ฒ๊น ๊ธฐ๋ฒ
Puzzle 01๋ถํฐ Puzzle 08, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ดํ๊น์ง ๋ณดํธ์ ์ผ๋ก ์ ์ฉ๋๋ ๊ธฐ๋ฒ์ ์ต๋ํ์ต๋๋ค:
- CPU ์ธก ๋ฌธ์ (์ฅ์น ์ค์ , ๋ฉ๋ชจ๋ฆฌ ํ ๋น)๋ LLDB๋ก ์์ํฉ๋๋ค
- GPU ์ปค๋ ๋ฌธ์ (์ค๋ ๋ ๋์, ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ)๋ CUDA-GDB๋ก ์ ํํฉ๋๋ค
- ํน์ ์ค๋ ๋๋ ๋ฐ์ดํฐ ์กฐ๊ฑด์ ์ง์คํ๋ ค๋ฉด ์กฐ๊ฑด๋ถ ๋ธ๋ ์ดํฌํฌ์ธํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค
- ๋ณ๋ ฌ ์คํ ํจํด์ ์ดํดํ๋ ค๋ฉด ์ค๋ ๋ ๊ฐ ์ด๋์ ํ์ฉํฉ๋๋ค
- ๊ฒฝ์ ์ํ์ ๋ฒ์ ์ด๊ณผ ์ค๋ฅ๋ฅผ ์ก์ผ๋ ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ํ์ธํฉ๋๋ค
ํ์ฅ์ฑ: ์ด ๊ธฐ๋ฒ๋ค์ ๋ค์ ๋ชจ๋ ์ํฉ์์ ๋์ผํ๊ฒ ์๋ํฉ๋๋ค:
- Puzzle 01: ๊ฐ๋จํ ๋ง์ ์ ํ๋ 4๊ฐ ์์ ๋ฐฐ์ด
- Puzzle 08: ์ค๋ ๋ ๋๊ธฐํ๊ฐ ํ์ํ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ
- ํ๋ก๋์ ์ฝ๋: ์ ๊ตํ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋ ๋ฐฑ๋ง ๊ฐ ์์ ๋ฐฐ์ด
ํ์ ๋๋ฒ๊น ๋ช ๋ น์ด ์ฐธ์กฐ
๋๋ฒ๊น ์ํฌํ๋ก์ฐ๋ฅผ ๋ฐฐ์ ์ผ๋, ์ผ์์ ์ธ ๋๋ฒ๊น ์ธ์ ์์ ์ธ ๋น ๋ฅธ ์ฐธ์กฐ ๊ฐ์ด๋๋ฅผ ๋๋ฆฝ๋๋ค. ์ด ์น์ ์ ๋ถ๋งํฌํ์ธ์!
GDB ๋ช ๋ น์ด ์ฝ์ด (์๊ฐ ์ ์ฝ!)
๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ๋จ์ถํค๋ก ๋ ๋น ๋ฅธ ๋๋ฒ๊น :
| ์ฝ์ด | ์ ์ฒด ๋ช ๋ น์ด | ๊ธฐ๋ฅ |
|---|---|---|
r | run | ํ๋ก๊ทธ๋จ ์์/์คํ |
c | continue | ์คํ ์ฌ๊ฐ |
n | next | ์คํ ์ค๋ฒ (๊ฐ์ ๋ ๋ฒจ) |
s | step | ํจ์ ๋ด๋ถ๋ก ์ง์ |
b | break | ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์ |
p | print | ๋ณ์ ๊ฐ ์ถ๋ ฅ |
l | list | ์์ค ์ฝ๋ ํ์ |
q | quit | ๋๋ฒ๊ฑฐ ์ข ๋ฃ |
์์:
(cuda-gdb) r # 'run' ๋์
(cuda-gdb) b 39 # 'break 39' ๋์
(cuda-gdb) p thread_id # 'print thread_id' ๋์
(cuda-gdb) n # 'next' ๋์
(cuda-gdb) c # 'continue' ๋์
โก Pro ํ: ์ฝ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ฒ๊น ์๋๊ฐ 3-5๋ฐฐ ๋นจ๋ผ์ง๋๋ค!
LLDB ๋ช ๋ น์ด (CPU ํธ์คํธ ์ฝ๋ ๋๋ฒ๊น )
์ธ์ ์ฌ์ฉ: ์ฅ์น ์ค์ , ๋ฉ๋ชจ๋ฆฌ ํ ๋น, ํ๋ก๊ทธ๋จ ํ๋ฆ, ํธ์คํธ ์ธก ํฌ๋์ ๋๋ฒ๊น
์คํ ์ ์ด
(lldb) run # ํ๋ก๊ทธ๋จ ์คํ
(lldb) continue # ์คํ ์ฌ๊ฐ (๋ณ์นญ: c)
(lldb) step # ํจ์ ๋ด๋ถ๋ก ์ง์
(์์ค ๋ ๋ฒจ)
(lldb) next # ํจ์ ๊ฑด๋๋ฐ๊ธฐ (์์ค ๋ ๋ฒจ)
(lldb) finish # ํ์ฌ ํจ์์์ ๋๊ฐ๊ธฐ
๋ธ๋ ์ดํฌํฌ์ธํธ ๊ด๋ฆฌ
(lldb) br set -n main # main ํจ์์ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์
(lldb) br set -n function_name # ์ด๋ค ํจ์์๋ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์
(lldb) br list # ๋ชจ๋ ๋ธ๋ ์ดํฌํฌ์ธํธ ํ์
(lldb) br delete 1 # ๋ธ๋ ์ดํฌํฌ์ธํธ #1 ์ญ์
(lldb) br disable 1 # ๋ธ๋ ์ดํฌํฌ์ธํธ #1 ์์ ๋นํ์ฑํ
๋ณ์ ๊ฒ์ฌ
(lldb) print variable_name # ๋ณ์ ๊ฐ ํ์
(lldb) print pointer[offset] # ํฌ์ธํฐ ์ญ์ฐธ์กฐ
(lldb) print array[0]@4 # ์ฒซ 4๊ฐ ๋ฐฐ์ด ์์ ํ์
CUDA-GDB ๋ช ๋ น์ด (GPU ์ปค๋ ๋๋ฒ๊น )
์ธ์ ์ฌ์ฉ: GPU ์ปค๋, ์ค๋ ๋ ๋์, ๋ณ๋ ฌ ์คํ, GPU ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋๋ฒ๊น
GPU ์ํ ๊ฒ์ฌ
(cuda-gdb) info cuda threads # ๋ชจ๋ GPU ์ค๋ ๋์ ์ํ ํ์
(cuda-gdb) info cuda blocks # ๋ชจ๋ ์ค๋ ๋ ๋ธ๋ก ํ์
(cuda-gdb) cuda kernel # ํ์ฑ GPU ์ปค๋ ๋์ด
์ค๋ ๋ ํ์ (๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ!)
(cuda-gdb) cuda thread (0,0,0) # ํน์ ์ค๋ ๋ ์ขํ๋ก ์ ํ
(cuda-gdb) cuda block (0,0) # ํน์ ๋ธ๋ก์ผ๋ก ์ ํ
(cuda-gdb) cuda thread # ํ์ฌ ์ค๋ ๋ ์ขํ ํ์
์ค๋ ๋๋ณ ๋ณ์ ๊ฒ์ฌ
# ๋ก์ปฌ ๋ณ์์ ํจ์ ํ๋ผ๋ฏธํฐ:
(cuda-gdb) print i # ๋ก์ปฌ ์ค๋ ๋ ์ธ๋ฑ์ค ๋ณ์
(cuda-gdb) print output # ํจ์ ํ๋ผ๋ฏธํฐ ํฌ์ธํฐ
(cuda-gdb) print a # ํจ์ ํ๋ผ๋ฏธํฐ ํฌ์ธํฐ
GPU ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
# ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํ ๋ฐฐ์ด ๊ฒ์ฌ (์ค์ ๋ก ์๋ํ๋ ๊ฒ):
(cuda-gdb) print array[i] # ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํ ์ค๋ ๋๋ณ ๋ฐฐ์ด ์ ๊ทผ
(cuda-gdb) print array[0]@4 # ์ฌ๋ฌ ์์ ๋ณด๊ธฐ: {{val1}, {val2}, {val3}, {val4}}
๊ณ ๊ธ GPU ๋๋ฒ๊น
# ๋ฉ๋ชจ๋ฆฌ ๊ฐ์
(cuda-gdb) watch array[i] # ๋ฉ๋ชจ๋ฆฌ ๋ณ๊ฒฝ ์ ์ค๋จ
(cuda-gdb) rwatch array[i] # ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ ์ ์ค๋จ
๋น ๋ฅธ ์ฐธ์กฐ: ๋๋ฒ๊น ๊ฒฐ์ ํธ๋ฆฌ
๐ค ์ด๋ค ์ ํ์ ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๊ณ ์๋์?
GPU ์ฝ๋ ์คํ ์ ์ ํ๋ก๊ทธ๋จ์ด ํฌ๋์
โ LLDB ๋๋ฒ๊น ์ฌ์ฉ
pixi run mojo debug your_program.mojo
GPU ์ปค๋์ด ์๋ชป๋ ๊ฒฐ๊ณผ ์์ฑ
โ ์กฐ๊ฑด๋ถ ๋ธ๋ ์ดํฌํฌ์ธํธ์ ํจ๊ป CUDA-GDB ์ฌ์ฉ
pixi run mojo debug --cuda-gdb --break-on-launch your_program.mojo
์ฑ๋ฅ ๋ฌธ์ ๋ ๊ฒฝ์ ์ํ
โ ์ฌํ์ฑ์ ์ํด ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น ์ฌ์ฉ
pixi run mojo build -O0 -g your_program.mojo -o debug_binary
pixi run mojo debug --cuda-gdb --break-on-launch debug_binary
GPU ๋๋ฒ๊น ์ ํต์ฌ์ ๋ฐฐ์ ์ต๋๋ค
GPU ๋๋ฒ๊น ๊ธฐ์ด์ ๋ํ ํฌ๊ด์ ์ธ ํํ ๋ฆฌ์ผ์ ์๋ฃํ์ต๋๋ค. ๋ค์์ ๋ฌ์ฑํ ๋ด์ฉ์ ๋๋ค:
์ต๋ํ ๊ธฐ์
๋ค์ค ๋ ๋ฒจ ๋๋ฒ๊น ์ง์:
- โ LLDB๋ก CPU ํธ์คํธ ๋๋ฒ๊น - ์ฅ์น ์ค์ , ๋ฉ๋ชจ๋ฆฌ ํ ๋น, ํ๋ก๊ทธ๋จ ํ๋ฆ ๋๋ฒ๊น
- โ CUDA-GDB๋ก GPU ์ปค๋ ๋๋ฒ๊น - ๋ณ๋ ฌ ์ค๋ ๋, GPU ๋ฉ๋ชจ๋ฆฌ, ๊ฒฝ์ ์ํ ๋๋ฒ๊น
- โ JIT vs ๋ฐ์ด๋๋ฆฌ ๋๋ฒ๊น - ์ํฉ์ ๋ง๋ ์ ๊ทผ๋ฒ ์ ํ
- โ pixi๋ก ํ๊ฒฝ ๊ด๋ฆฌ - ์ผ๊ด๋๊ณ ์ ๋ขฐํ ์ ์๋ ๋๋ฒ๊น ์ค์ ๋ณด์ฅ
์ค์ ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ ํต์ฐฐ:
- ์ค๋ ๋์ ์ค์ ๋์ ํ์ธ - ๋ณ๋ ฌ ์ค๋ ๋๋ง๋ค
thread_idx.x๊ฐ ๋ค๋ฅธ ๊ฐ์ ๊ฐ๋ ๊ฒ์ ์ง์ ๋ชฉ๊ฒฉํ์ต๋๋ค - ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ดํด - ์ ์ญ GPU ๋ฉ๋ชจ๋ฆฌ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๋ฅผ ๋๋ฒ๊น ํ์ต๋๋ค
- ์ค๋ ๋ ํ์ ํ์ต - ์์ฒ ๊ฐ์ ๋ณ๋ ฌ ์ค๋ ๋ ์ฌ์ด๋ฅผ ํจ์จ์ ์ผ๋ก ์ด๋ํ์ต๋๋ค
์ด๋ก ์์ ์ค์ ์ผ๋ก
GPU ๋๋ฒ๊น ์ ๋ํด ์ฝ๊ธฐ๋ง ํ ๊ฒ์ด ์๋๋ผ ๊ฒฝํํ์ต๋๋ค:
- ์ค์ ์ฝ๋ ๋๋ฒ๊น
: ์ค์ GPU ์คํ์ผ๋ก Puzzle 01์
add_10์ปค๋์ ๋๋ฒ๊น ํ์ต๋๋ค - ์ค์ ๋๋ฒ๊ฑฐ ์ถ๋ ฅ ํ์ธ: LLDB ์ด์ ๋ธ๋ฆฌ, CUDA-GDB ์ค๋ ๋ ์ํ, ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ง์ ํ์ธํ์ต๋๋ค
- ์ ๋ฌธ ๋๊ตฌ ์ฌ์ฉ: ํ๋ก๋์ GPU ๊ฐ๋ฐ์์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ CUDA-GDB๋ฅผ ์ฌ์ฉํ์ต๋๋ค
- ์ค์ ์๋๋ฆฌ์ค ํด๊ฒฐ: ๋ฒ์ ์ด๊ณผ ์ ๊ทผ, ๊ฒฝ์ ์ํ, ์ปค๋ ์คํ ์คํจ ๋ฌธ์ ๋ฅผ ๋ค๋ค์ต๋๋ค
๋๋ฒ๊น ๋๊ตฌ ๋ชจ์
๋น ๋ฅธ ๊ฒฐ์ ๊ฐ์ด๋ (ํญ์ ๊ฐ๊น์ด ๋์ธ์!):
| ๋ฌธ์ ์ ํ | ๋๊ตฌ | ๋ช ๋ น์ด |
|---|---|---|
| GPU ์ ์ ํ๋ก๊ทธ๋จ ํฌ๋์ | LLDB | pixi run mojo debug program.mojo |
| GPU ์ปค๋ ๋ฌธ์ | CUDA-GDB | pixi run mojo debug --cuda-gdb --break-on-launch program.mojo |
| ๊ฒฝ์ ์ํ | CUDA-GDB + ์ค๋ ๋ ํ์ | (cuda-gdb) cuda thread (0,0,0) |
ํ์ ๋ช ๋ น์ด (์ผ์ ๋๋ฒ๊น ์ฉ):
# GPU ์ค๋ ๋ ๊ฒ์ฌ
(cuda-gdb) info cuda threads # ๋ชจ๋ ์ค๋ ๋ ๋ณด๊ธฐ
(cuda-gdb) cuda thread (0,0,0) # ์ค๋ ๋ ์ ํ
(cuda-gdb) print i # ๋ก์ปฌ ์ค๋ ๋ ์ธ๋ฑ์ค (thread_idx.x ๋ฑ๊ฐ)
# ์ค๋งํธ ๋ธ๋ ์ดํฌํฌ์ธํธ (GPU ๋ด์ฅ ๋ณ์๊ฐ ์๋ํ์ง ์์ผ๋ฏ๋ก ๋ก์ปฌ ๋ณ์ ์ฌ์ฉ)
(cuda-gdb) break kernel if i == 0 # ์ค๋ ๋ 0์ ์ง์ค
(cuda-gdb) break kernel if array[i] > 100 # ๋ฐ์ดํฐ ์กฐ๊ฑด์ ์ง์ค
# ๋ฉ๋ชจ๋ฆฌ ๋๋ฒ๊น
(cuda-gdb) print array[i] # ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํ ์ค๋ ๋๋ณ ๋ฐ์ดํฐ
(cuda-gdb) print array[0]@4 # ๋ฐฐ์ด ์ธ๊ทธ๋จผํธ: {{val1}, {val2}, {val3}, {val4}}
์์ฝ
GPU ๋๋ฒ๊น ์๋ ์์ฒ ๊ฐ์ ๋ณ๋ ฌ ์ค๋ ๋, ๋ณต์กํ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ, ์ ๋ฌธ ๋๊ตฌ๊ฐ ๊ด์ฌํฉ๋๋ค. ์ด์ ๋ค์์ ๊ฐ์ถ๊ฒ ๋์์ต๋๋ค:
- ์ด๋ค GPU ํ๋ก๊ทธ๋จ์๋ ์ ์ฉํ ์ ์๋ ์ฒด๊ณ์ ์ธ ์ํฌํ๋ก์ฐ
- LLDB์ CUDA-GDB ์ ๋ฌธ ๋๊ตฌ์ ๋ํ ์น์ํจ
- ์ค์ ๋ณ๋ ฌ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ ์ค์ ๊ฒฝํ
- ๋ณต์กํ ์ํฉ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ค์ฉ์ ์ธ ์ ๋ต
- GPU ๋๋ฒ๊น ๊ณผ์ ๋ฅผ ํด๊ฒฐํ ๊ธฐ์ด
์ถ๊ฐ ์๋ฃ
- Mojo ๋๋ฒ๊น ๋ฌธ์
- Mojo GPU ๋๋ฒ๊น ๊ฐ์ด๋
- NVIDIA CUDA-GDB ์ฌ์ฉ์ ๊ฐ์ด๋
- CUDA-GDB ๋ช ๋ น์ด ์ฐธ์กฐ
์ฐธ๊ณ : GPU ๋๋ฒ๊น ์๋ ์ธ๋ด์ฌ๊ณผ ์ฒด๊ณ์ ์ธ ์กฐ์ฌ๊ฐ ํ์ํฉ๋๋ค. ์ด ํผ์ฆ์์ ๋ค๋ฃฌ ์ํฌํ๋ก์ฐ์ ๋ช ๋ น์ด๋ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ง์ฃผ์น๊ฒ ๋ ๋ณต์กํ GPU ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๋ ๊ธฐ์ด๊ฐ ๋ฉ๋๋ค.
๐ง ํ์ ์์ฌ: ์ฒซ ๋ฒ์งธ ์ฌ๋ก
๊ฐ์
์ด๋ฒ ํผ์ฆ์์๋ ํฌ๋์๊ฐ ๋ฐ์ํ๋ GPU ํ๋ก๊ทธ๋จ์ด ์ฃผ์ด์ง๋๋ค. ์์ค ์ฝ๋๋ฅผ ๋ณด์ง
์๊ณ (cuda-gdb) ๋๋ฒ๊น
๋๊ตฌ๋ง์ผ๋ก ๋ฌธ์ ๋ฅผ ์ฐพ์๋ด์ผ ํฉ๋๋ค. ๋๋ฒ๊น
์คํฌ์ ๋ฐํํด
๋ฏธ์คํฐ๋ฆฌ๋ฅผ ํ์ด๋ณด์ธ์!
์ฌ์ ์ค๋น: Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ์ ๋จผ์ ์๋ฃํด์ CUDA-GDB ์ค์ ๊ณผ ๊ธฐ๋ณธ ๋๋ฒ๊น ๋ช ๋ น์ด๋ฅผ ์ตํ๋์ธ์. ์๋ ๋ช ๋ น์ ์คํํ๋์ง ํ์ธํ์ธ์:
pixi run -e nvidia setup-cuda-gdb
์ด ๋ช ๋ น์ ์์คํ ์ CUDA ์ค์น๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ๊ณ GPU ๋๋ฒ๊น ์ ํ์ํ ๋งํฌ๋ฅผ ์ค์ ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด๋ฒ ๋๋ฒ๊น ์ฑ๋ฆฐ์ง์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ์ฒด๊ณ์ ์ธ ๋๋ฒ๊น : ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋จ์ ์ผ์ ๊ทผ๋ณธ ์์ธ ์ฐพ๊ธฐ
- ์ค๋ฅ ๋ถ์: ํฌ๋์ ๋ฉ์์ง์ ์คํ ์ถ์ (stack trace) ํด์ํ๊ธฐ
- ๊ฐ์ค ์๋ฆฝ: ๋ฌธ์ ์ ๋ํ ํฉ๋ฆฌ์ ์ธ ์ถ์ธก ์ธ์ฐ๊ธฐ
- ๋๋ฒ๊น ์ํฌํ๋ก์ฐ: ๋จ๊ณ๋ณ ์กฐ์ฌ ๊ณผ์ ์ตํ๊ธฐ
์ฝ๋ ์คํ
๋จผ์ ์ ์ฒด ์ฝ๋๋ฅผ ๋ณด์ง ์๊ณ ์ปค๋๋ง ์ดํด๋ด ์๋ค:
def add_10(
output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
):
var i = thread_idx.x
output[i] = a[i] + 10.0
๋ฒ๊ทธ๋ฅผ ์ง์ ๊ฒฝํํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช
๋ น์ ์คํํ์ธ์ (pixi ์ ์ฉ):
pixi run -e nvidia p09 --first-case
ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ถ๋ ฅ์ด ๋ํ๋ฉ๋๋ค:
First Case: Try to identify what's wrong without looking at the code!
stack trace was not collected. Enable stack trace collection with environment variable `MOJO_ENABLE_STACK_TRACE_ON_ERROR`
Unhandled exception caught during execution: At open-source/max/mojo/stdlib/stdlib/gpu/host/device_context.mojo:2082:17: CUDA call failed: CUDA_ERROR_INVALID_IMAGE (device kernel image is invalid)
To get more accurate error information, set MODULAR_DEVICE_CONTEXT_SYNC_MODE=true.
/home/ubuntu/workspace/mojo-gpu-puzzles/.pixi/envs/nvidia/bin/mojo: error: execution exited with a non-zero result: 1
๊ณผ์ : ํ์ ์์ฌ
๋์ : ์ฝ๋๋ฅผ ๋ณด์ง ์์ ์ํ์์, ์ด ํฌ๋์๋ฅผ ์กฐ์ฌํ๊ธฐ ์ํ ๋๋ฒ๊น ์ ๋ต์ ๋ฌด์์ผ๊น์?
๋ค์ ๋ช ๋ น์ผ๋ก ์์ํด ๋ณด์ธ์:
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --first-case
ํ
- ํฌ๋์ ๋ฉ์์ง๋ฅผ ๊ผผ๊ผผํ ์ฝ๊ธฐ -
CUDA_ERROR_ILLEGAL_ADDRESS๋ GPU๊ฐ ์๋ชป๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ค ํ๋ค๋ ๋ป์ ๋๋ค - ๋ธ๋ ์ดํฌํฌ์ธํธ ์ ๋ณด ํ์ธ - CUDA-GDB๊ฐ ๋ฉ์ถ ๋ ํ์๋๋ ํจ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ดํด๋ณด์ธ์
- ๋ชจ๋ ํฌ์ธํฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๊ฒ์ฌ -
print๋ก ๊ฐ ํฌ์ธํฐ ํ๋ผ๋ฏธํฐ๋ฅผ ํ์ธํ์ธ์ - ์์ํ ์ฃผ์ ์ฐพ๊ธฐ - ์ ํจํ GPU ์ฃผ์๋ ๋ณดํต ํฐ 16์ง์์
๋๋ค (
0x0์ ๋ฌด์์ ์๋ฏธํ ๊น์?) - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํ ์คํธ - ๊ฐ ํฌ์ธํฐ๋ก ๋ฐ์ดํฐ์ ์ ๊ทผํด์ ์ด๋ ๊ฒ์ด ์คํจํ๋์ง ํ์ธํ์ธ์
- ์ฒด๊ณ์ ์ผ๋ก ์ ๊ทผ - ํ์ ์ฒ๋ผ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉฐ ์ฆ์์์ ๊ทผ๋ณธ ์์ธ๊น์ง ์ถ์ ํ์ธ์
- ์ ํจํ ํจํด๊ณผ ๊ทธ๋ ์ง ์์ ํจํด ๋น๊ต - ํ ํฌ์ธํฐ๊ฐ ์๋ํ๊ณ ๋ค๋ฅธ ๊ฑด ์ ๋๋ค๋ฉด, ๋ฌธ์ ๊ฐ ์๋ ์ชฝ์ ์ง์คํ์ธ์
๐ก ์กฐ์ฌ ๊ณผ์ ๊ณผ ํด๊ฒฐ์ฑ
CUDA-GDB๋ก ๋จ๊ณ๋ณ ์กฐ์ฌ
๋๋ฒ๊ฑฐ ์คํ
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --first-case
๋ธ๋ ์ดํฌํฌ์ธํธ ์ ๋ณด ํ์ธ
CUDA-GDB๊ฐ ๋ฉ์ถ๋ฉด ๋ฐ๋ก ์ ์ฉํ ๋จ์๊ฐ ๋ํ๋ฉ๋๋ค:
(cuda-gdb) run
CUDA thread hit breakpoint, p09_add_10_... (output=0x302000000, a=0x0)
at /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo:31
31 i = thread_idx.x
๐ ์ฒซ ๋ฒ์งธ ๋จ์: ํจ์ ์๊ทธ๋์ฒ์ (output=0x302000000, a=0x0)์ด ๋ณด์
๋๋ค
output์ ์ ํจํ GPU ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋คa๋0x0- null ํฌ์ธํฐ์ ๋๋ค!
์ฒด๊ณ์ ์ธ ๋ณ์ ๊ฒ์ฌ
(cuda-gdb) next
32 output[i] = a[i] + 10.0
(cuda-gdb) print i
$1 = 0
(cuda-gdb) print output
$2 = (!pop.scalar<f32> * @register) 0x302000000
(cuda-gdb) print a
$3 = (!pop.scalar<f32> * @register) 0x0
์ฆ๊ฑฐ ์์ง:
- โ
์ค๋ ๋ ์ธ๋ฑ์ค
i=0์ ์ ํจํฉ๋๋ค - โ
๊ฒฐ๊ณผ ํฌ์ธํฐ
0x302000000์ ์ฌ๋ฐ๋ฅธ GPU ์ฃผ์์ ๋๋ค - โ ์
๋ ฅ ํฌ์ธํฐ
0x0์ null์ ๋๋ค
๋ฌธ์ ํ์ธ
(cuda-gdb) print a[i]
Cannot access memory at address 0x0
๊ฒฐ์ ์ ์ฆ๊ฑฐ: null ์ฃผ์์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ ์ ์์ต๋๋ค - ๋ฐ๋ก ์ด๊ฒ์ด ํฌ๋์์ ์์ธ์ ๋๋ค!
๊ทผ๋ณธ ์์ธ ๋ถ์
๋ฌธ์ ์ : ์ด์ --first-crash์
์ฝ๋๋ฅผ ๋ณด๋ฉด, ํธ์คํธ ์ฝ๋๊ฐ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ
์ ๋๋ก ํ ๋นํ์ง ์๊ณ null ํฌ์ธํฐ๋ฅผ ๋ง๋ค๊ณ ์์ต๋๋ค:
input_buf = ctx.enqueue_create_buffer[dtype](0) # 0๊ฐ์ ์์๋ฅผ ๊ฐ์ง `DeviceBuffer`๋ฅผ ์์ฑํฉ๋๋ค. ์์๊ฐ 0๊ฐ์ด๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ ๋น๋์ง ์์ NULL ํฌ์ธํฐ๊ฐ ๋ฉ๋๋ค!
์ ํฌ๋์๊ฐ ๋ฐ์ํ๋๊ฐ:
ctx.enqueue_create_buffer[dtype](0)์ 0๊ฐ ์์๋ฅผ ๊ฐ์งDeviceBuffer๋ฅผ ์์ฑํฉ๋๋ค.- ํ ๋นํ ์์๊ฐ ์์ผ๋ null ํฌ์ธํฐ๋ฅผ ๋ฐํํฉ๋๋ค.
- ์ด null ํฌ์ธํฐ๊ฐ GPU ์ปค๋๋ก ์ ๋ฌ๋ฉ๋๋ค.
- ์ปค๋์ด
a[i]์ ์ ๊ทผํ๋ ค ํ ๋ null์ ์ญ์ฐธ์กฐ โCUDA_ERROR_ILLEGAL_ADDRESS
์์ ๋ฐฉ๋ฒ
Null ํฌ์ธํฐ ์์ฑ์ ์ ์ ํ ๋ฒํผ ํ ๋น์ผ๋ก ๊ต์ฒดํฉ๋๋ค:
# ์๋ชป๋ ๋ฐฉ๋ฒ: Null ํฌ์ธํฐ ์์ฑ
input_buf = ctx.enqueue_create_buffer[dtype](0)
# ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์์ ํ ์ฒ๋ฆฌ๋ฅผ ์ํด ์ค์ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ณ ์ด๊ธฐํ
input_buf = ctx.enqueue_create_buffer[dtype](SIZE)
input_buf.enqueue_fill(0)
ํต์ฌ ๋๋ฒ๊น ๊ตํ
ํจํด ์ธ์:
0x0์ฃผ์๋ ํญ์ null ํฌ์ธํฐ์ ๋๋ค- ์ ํจํ GPU ์ฃผ์๋ ํฐ 16์ง์์
๋๋ค (์:
0x302000000)
๋๋ฒ๊น ์ ๋ต:
- ํฌ๋์ ๋ฉ์์ง ์ฝ๊ธฐ - ๋์ฒด๋ก ๋ฌธ์ ์ ํ์ ๋ํ ํํธ๋ฅผ ์ค๋๋ค
- ํจ์ ํ๋ผ๋ฏธํฐ ํ์ธ - CUDA-GDB๊ฐ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ง์ ์ ๋ณด์ฌ์ค๋๋ค
- ๋ชจ๋ ํฌ์ธํฐ ๊ฒ์ฌ - ์ฃผ์๋ฅผ ๋น๊ตํด์ null์ด๋ ์๋ชป๋ ๊ฒ์ ์ฐพ์ต๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํ ์คํธ - ์์ํ ํฌ์ธํฐ๋ฅผ ์ญ์ฐธ์กฐํด ๋ด ๋๋ค
- ํ ๋น ์ง์ ๊น์ง ์ถ์ - ๋ฌธ์ ์ ํฌ์ธํฐ๊ฐ ์ด๋์ ์์ฑ๋์๋์ง ์ฐพ์ต๋๋ค
๐ก ํต์ฌ ํต์ฐฐ: ์ด๋ฐ ์ ํ์ null ํฌ์ธํฐ ๋ฒ๊ทธ๋ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋งค์ฐ ํํฉ๋๋ค. ์ฌ๊ธฐ์ ๋ฐฐ์ด ์ฒด๊ณ์ ์ธ CUDA-GDB ์กฐ์ฌ ๋ฐฉ๋ฒ์ ๋ค๋ฅธ ๋ง์ GPU ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ , ๊ฒฝ์ ์ํ, ์ปค๋ ํฌ๋์๋ฅผ ๋๋ฒ๊น ํ ๋๋ ๊ทธ๋๋ก ์ ์ฉ๋ฉ๋๋ค.
๋ค์ ๋จ๊ณ: ํฌ๋์์์ ์กฐ์ฉํ ๋ฒ๊ทธ๋ก
ํฌ๋์ ๋๋ฒ๊น ์ ์ตํ์ต๋๋ค! ์ด์ ํ ์ ์์ต๋๋ค:
- ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋จ์๋ก GPU ํฌ๋์๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์กฐ์ฌ
- ํฌ์ธํฐ ์ฃผ์ ๊ฒ์ฌ๋ฅผ ํตํด null ํฌ์ธํฐ ๋ฒ๊ทธ ์๋ณ
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ๋๋ฒ๊น ์ CUDA-GDB๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉ
๋ค์ ๋์ : ํ์ ์์ฌ: ๋ ๋ฒ์งธ ์ฌ๋ก
๊ทธ๋ฐ๋ฐ ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ์ง ์๋๋ค๋ฉด์? ์๋ฒฝํ๊ฒ ์คํ๋์ง๋ง ์๋ชป๋ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค๋ฉด?
๋ ๋ฒ์งธ ์ฌ๋ก๋ ์ ํ ๋ค๋ฅธ ์ ํ์ ๋๋ฒ๊น ๋์ ์ ๋๋ค:
- ๊ธธ์ก์ด๊ฐ ๋์ด์ค ํฌ๋์ ๋ฉ์์ง๊ฐ ์์ต๋๋ค
- ์กฐ์ฌํ ๋๋ ทํ ํฌ์ธํฐ ๋ฌธ์ ๋ ์์ต๋๋ค
- ๋ฌธ์ ๋ฅผ ๊ฐ๋ฆฌํค๋ ์คํ ์ถ์ ๋ ์์ต๋๋ค
- ์ฒด๊ณ์ ์ธ ์กฐ์ฌ๊ฐ ํ์ํ ์๋ชป๋ ๊ฒฐ๊ณผ๋ง ์์ต๋๋ค
์๋กญ๊ฒ ์ตํ๊ฒ ๋ ์คํฌ:
- ๋ก์ง ๋ฒ๊ทธ ํ์ง - ํฌ๋์ ์์ด ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ ์ฐพ๊ธฐ
- ํจํด ๋ถ์ - ์๋ชป๋ ์ถ๋ ฅ์์ ๊ทผ๋ณธ ์์ธ๊น์ง ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๊ธฐ
- ์คํ ํ๋ฆ ๋๋ฒ๊น - ์ต์ ํ ๋๋ฌธ์ ๋ณ์ ๊ฒ์ฌ๊ฐ ์ ๋ ๋ ๋์ฒํ๊ธฐ
์ฌ๊ธฐ์ ๋ฐฐ์ด ์ฒด๊ณ์ ์ธ ์กฐ์ฌ ๋ฐฉ๋ฒ - ๋จ์ ์ฝ๊ธฐ, ๊ฐ์ค ์ธ์ฐ๊ธฐ, ์ฒด๊ณ์ ์ผ๋ก ํ ์คํธํ๊ธฐ - ์ ์์ผ๋ก ๋ง์ฃผํ ๋ ๋ฏธ๋ฌํ ๋ก์ง ์ค๋ฅ๋ฅผ ๋๋ฒ๊น ํ๋ ๊ธฐ์ด๊ฐ ๋ฉ๋๋ค.
๐ ํ์ ์์ฌ: ๋ ๋ฒ์งธ ์ฌ๋ก
๊ฐ์
์ฒซ ๋ฒ์งธ ์ฌ๋ก์์ ์ตํ ํฌ๋์ ๋๋ฒ๊น ์คํฌ์ ๋ฐํ์ผ๋ก, ์ด๋ฒ์๋ ์ ํ ๋ค๋ฅธ ์ ํ์ ๋์ ์ ๋ง์ฃผํฉ๋๋ค: ํฌ๋์ ์์ด ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ ๋ก์ง ๋ฒ๊ทธ์ ๋๋ค.
๋๋ฒ๊น ๊ด์ ์ ์ ํ:
- ์ฒซ ๋ฒ์งธ ์ฌ๋ก: ๋ช
ํํ ํฌ๋์
์ ํธ(
CUDA_ERROR_ILLEGAL_ADDRESS)๊ฐ ์กฐ์ฌ๋ฅผ ์๋ดํจ - ๋ ๋ฒ์งธ ์ฌ๋ก: ํฌ๋์๋ ์๊ณ ์๋ฌ ๋ฉ์์ง๋ ์์ - ํ์ ์ฒ๋ผ ํํค์ณ์ผ ํ๋ ๋ฏธ๋ฌํ๊ฒ ์๋ชป๋ ๊ฒฐ๊ณผ๋ง ์์
์ด๋ฒ ์ค๊ธ ๋๋ฒ๊น
์ฑ๋ฆฐ์ง์์๋ TileTensor ์ฐ์ฐ์ ์ฌ์ฉํ๋ ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ๋ฅผ
์กฐ์ฌํฉ๋๋ค. ํ๋ก๊ทธ๋จ์ ์ฑ๊ณต์ ์ผ๋ก ์คํ๋์ง๋ง ์๋ชป๋ ์ถ๋ ฅ์ ๋ด๋๋ฐ, ์ค์ ๊ฐ๋ฐ์์
ํจ์ฌ ํํ๋ฉด์๋ ๊น๋ค๋ก์ด ๋๋ฒ๊น
์๋๋ฆฌ์ค์
๋๋ค.
์ฌ์ ์ค๋น: Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ๊ณผ ํ์ ์์ฌ: ์ฒซ ๋ฒ์งธ ์ฌ๋ก๋ฅผ ๋จผ์ ์๋ฃํด์ CUDA-GDB ์ํฌํ๋ก์ฐ์ ์ฒด๊ณ์ ์ธ ๋๋ฒ๊น ๊ธฐ๋ฒ์ ์ตํ๋์ธ์. ์๋ ๋ช ๋ น์ ์คํํ๋์ง ํ์ธํ์ธ์:
pixi run -e nvidia setup-cuda-gdb
ํต์ฌ ๊ฐ๋
์ด๋ฒ ๋๋ฒ๊น ์ฑ๋ฆฐ์ง์์ ๋ฐฐ์ธ ๋ด์ฉ:
- TileTensor ๋๋ฒ๊น : ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ ์ ๊ทผ ํจํด ์กฐ์ฌํ๊ธฐ
- ๋ก์ง ๋ฒ๊ทธ ํ์ง: ํฌ๋์ํ์ง ์๋ ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ ์ฐพ๊ธฐ
- ๋ฐ๋ณต๋ฌธ ๊ฒฝ๊ณ ๋ถ์: ๋ฐ๋ณต ํ์ ๋ฌธ์ ์ดํดํ๊ธฐ
- ๊ฒฐ๊ณผ ํจํด ๋ถ์: ์ถ๋ ฅ ๋ฐ์ดํฐ๋ก ๊ทผ๋ณธ ์์ธ๊น์ง ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๊ธฐ
์ฝ๋ ์คํ
๋จผ์ ์ ์ฒด ์ฝ๋๋ฅผ ๋ณด์ง ์๊ณ ์ปค๋๋ง ์ดํด๋ด ์๋ค:
def process_sliding_window(
output: TileTensor[mut=True, dtype, VectorLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, VectorLayout, ImmutAnyOrigin],
):
var thread_id = thread_idx.x
# Each thread processes a sliding window of 3 elements
var window_sum = Scalar[dtype](0.0)
# Sum elements in sliding window: [i-1, i, i+1]
for offset in range(ITER):
var idx = Int(thread_id) + offset - 1
if 0 <= idx < SIZE:
var value = rebind[Scalar[dtype]](a[idx])
window_sum += value
output[thread_id] = window_sum
๋ฒ๊ทธ๋ฅผ ์ง์ ๊ฒฝํํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช
๋ น์ ์คํํ์ธ์ (pixi ์ ์ฉ):
pixi run -e nvidia p09 --second-case
๋ค์๊ณผ ๊ฐ์ ์ถ๋ ฅ์ด ๋ํ๋ฉ๋๋ค - ํฌ๋์ ์์ด ์๋ชป๋ ๊ฒฐ๊ณผ:
This program computes sliding window sums for each position...
Input array: [0, 1, 2, 3]
Computing sliding window sums (window size = 3)...
Each position should sum its neighbors: [left + center + right]
stack trace was not collected. Enable stack trace collection with environment variable `MOJO_ENABLE_STACK_TRACE_ON_ERROR`
Unhandled exception caught during execution: At open-source/max/mojo/stdlib/stdlib/gpu/host/device_context.mojo:2082:17: CUDA call failed: CUDA_ERROR_INVALID_IMAGE (device kernel image is invalid)
To get more accurate error information, set MODULAR_DEVICE_CONTEXT_SYNC_MODE=true.
/home/ubuntu/workspace/mojo-gpu-puzzles/.pixi/envs/nvidia/bin/mojo: error: execution exited with a non-zero result: 1
๊ณผ์ : ํ์ ์์ฌ
๋์ : ํ๋ก๊ทธ๋จ์ ํฌ๋์ ์์ด ์คํ๋์ง๋ง ์ผ์ ํ ํจํด์ผ๋ก ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ๋ ๋๋ค. ์ฝ๋๋ฅผ ๋ณด์ง ์์ ์ํ์์, ์ด ๋ก์ง ๋ฒ๊ทธ๋ฅผ ์กฐ์ฌํ๊ธฐ ์ํ ์ฒด๊ณ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ๋ฌด์์ผ๊น์?
์๊ฐํด ๋ณผ ์ :
- ์๋ชป๋ ๊ฒฐ๊ณผ์์ ์ด๋ค ํจํด์ด ๋ณด์ด๋์?
- ์ ๋๋ก ๋์ง ์๋ ๊ฒ ๊ฐ์ ๋ฐ๋ณต๋ฌธ์ ์ด๋ป๊ฒ ์กฐ์ฌํ ๊ฑด๊ฐ์?
- ๋ณ์๋ฅผ ์ง์ ๊ฒ์ฌํ ์ ์์ ๋ ์ด๋ค ๋๋ฒ๊น ์ ๋ต์ด ํจ๊ณผ์ ์ผ๊น์?
- ์กฐ์ฌ๋ฅผ ์๋ดํด ์ค ํฌ๋์ ์ ํธ๊ฐ ์์ ๋, ์ฒซ ๋ฒ์งธ ์ฌ๋ก์ ์ฒด๊ณ์ ์ธ ์กฐ์ฌ ๋ฐฉ๋ฒ์ ์ด๋ป๊ฒ ์ ์ฉํ ์ ์์๊น์?
๋ค์ ๋ช ๋ น์ผ๋ก ์์ํด ๋ณด์ธ์:
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --second-case
GDB ๋ช ๋ น์ด ๋จ์ถํค (๋น ๋ฅธ ๋๋ฒ๊น )
์ด ๋จ์ถํค๋ค์ ์ฌ์ฉํ๋ฉด ๋๋ฒ๊น ์ธ์ ์๋๋ฅผ ๋์ผ ์ ์์ต๋๋ค:
| ๋จ์ถ | ์ ์ฒด | ์ฌ์ฉ ์์ |
|---|---|---|
r | run | (cuda-gdb) r |
n | next | (cuda-gdb) n |
c | continue | (cuda-gdb) c |
b | break | (cuda-gdb) b 39 |
p | print | (cuda-gdb) p thread_id |
q | quit | (cuda-gdb) q |
์๋ ๋ชจ๋ ๋๋ฒ๊น ๋ช ๋ น์ด๋ ํจ์จ์ ์ํด ์ด ๋จ์ถํค๋ฅผ ์ฌ์ฉํฉ๋๋ค!
ํ
- ํจํด ๋ถ์๋ถํฐ - ๊ธฐ๋๊ฐ๊ณผ ์ค์ ๊ฒฐ๊ณผ์ ๊ด๊ณ๋ฅผ ์ดํด๋ณด์ธ์ (์ฐจ์ด์ ์ด๋ค ์ํ์ ํจํด์ด ์๋์?)
- ์คํ ํ๋ฆ์ ์ง์ค - ๋ณ์์ ์ ๊ทผํ ์ ์์ผ๋ฉด ๋ฐ๋ณต ํ์๋ฅผ ์ธ์ด๋ณด์ธ์
- ๋จ์ํ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ฌ์ฉ - ์ต์ ํ๋ ์ฝ๋์์๋ ๋ณต์กํ ๋๋ฒ๊น ๋ช ๋ น์ด ์คํจํ๊ธฐ ์ฝ์ต๋๋ค
- ์ํ์ ์ถ๋ก - ๊ฐ ์ค๋ ๋๊ฐ ์ ๊ทผํด์ผ ํ๋ ๊ฒ๊ณผ ์ค์ ๋ก ์ ๊ทผํ๋ ๊ฒ์ ๋ฐ์ ธ๋ณด์ธ์
- ๋๋ฝ๋ ๋ฐ์ดํฐ ์กฐ์ฌ - ๊ฒฐ๊ณผ๊ฐ ์ผ๊ด๋๊ฒ ๊ธฐ๋๋ณด๋ค ์๋ค๋ฉด, ๋ฌด์์ด ๋น ์ก์๊น์?
- ํธ์คํธ ์ถ๋ ฅ ๊ฒ์ฆ - ์ต์ข ๊ฒฐ๊ณผ์์ ๋ฒ๊ทธ์ ํจํด์ด ๋๋ฌ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค
- ์๊ณ ๋ฆฌ์ฆ ๊ฒฝ๊ณ ๋ถ์ - ๋ฐ๋ณต๋ฌธ์ด ์ฌ๋ฐ๋ฅธ ๊ฐ์์ ์์๋ฅผ ์ฒ๋ฆฌํ๋์ง ํ์ธํ์ธ์
- ์๋ํ๋ ์ผ์ด์ค์ ๊ต์ฐจ ๊ฒ์ฆ - ์ค๋ ๋ 3์ ์ ํํ๊ฒ ์๋ํ๋๋ฐ ๋ค๋ฅธ ๊ฒ๋ค์ ์ ์ ๋ ๊น์?
๐ก ์กฐ์ฌ ๊ณผ์ ๊ณผ ํด๊ฒฐ์ฑ
CUDA-GDB๋ก ๋จ๊ณ๋ณ ์กฐ์ฌ
1๋จ๊ณ: ์คํ๊ณผ ์ด๊ธฐ ๋ถ์
Step 1: ๋๋ฒ๊ฑฐ ์คํ
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --second-case
Step 2: ์ฆ์๋ถํฐ ๋ถ์
๋๋ฒ๊ฑฐ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์, ์ด๋ฏธ ์๊ณ ์๋ ๊ฒ์ ์ ๋ฆฌํฉ๋๋ค:
์ค์ ๊ฒฐ๊ณผ: [0.0, 1.0, 3.0, 5.0]
๊ธฐ๋๊ฐ: [1.0, 3.0, 6.0, 5.0]
๐ ํจํด ์ธ์:
- ์ค๋ ๋ 0: 0.0 ์ป์, ๊ธฐ๋๊ฐ 1.0 โ 1.0 ๋๋ฝ
- ์ค๋ ๋ 1: 1.0 ์ป์, ๊ธฐ๋๊ฐ 3.0 โ 2.0 ๋๋ฝ
- ์ค๋ ๋ 2: 3.0 ์ป์, ๊ธฐ๋๊ฐ 6.0 โ 3.0 ๋๋ฝ
- ์ค๋ ๋ 3: 5.0 ์ป์, ๊ธฐ๋๊ฐ 5.0 โ โ ์ ํ
์ด๊ธฐ ๊ฐ์ค: ๊ฐ ์ค๋ ๋๊ฐ ์ผ๋ถ ๋ฐ์ดํฐ๋ฅผ ๋๋ฝํ๊ณ ์๋๋ฐ, ์ค๋ ๋ 3๋ง ์ ํํ๊ฒ ์๋ํฉ๋๋ค.
2๋จ๊ณ: ์ปค๋ ์ง์
Step 3: ๋ธ๋ ์ดํฌํฌ์ธํธ ์ง์ ํ์ธ
์ค์ ๋๋ฒ๊น ์ธ์ ์์๋ ๋ค์๊ณผ ๊ฐ์ด ์งํ๋ฉ๋๋ค:
(cuda-gdb) r
Starting program: .../mojo run problems/p09/p09.mojo --second-case
This program computes sliding window sums for each position...
Input array: [0, 1, 2, 3]
Computing sliding window sums (window size = 3)...
Each position should sum its neighbors: [left + center + right]
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (0,0,0), device 0, sm 0, warp 0, lane 0]
CUDA thread hit application kernel entry function breakpoint, p09_process_sliding_window_...
<<<(1,1,1),(4,1,1)>>> (output=..., input=...)
at /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo:30
30 input: TileTensor[mut=False, dtype, vector_layout],
Step 4: ๋ฉ์ธ ๋ก์ง์ผ๋ก ์ด๋
(cuda-gdb) n
29 output: TileTensor[mut=True, dtype, vector_layout],
(cuda-gdb) n
32 thread_id = thread_idx.x
(cuda-gdb) n
38 for offset in range(ITER):
Step 5: ๋ณ์ ์ ๊ทผ์ฑ ํ ์คํธ - ์ค์ํ ๋ฐ๊ฒฌ
(cuda-gdb) p thread_id
$1 = 0
โ ์ข์: Thread ID์ ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค.
(cuda-gdb) p window_sum
Cannot access memory at address 0x0
โ ๋ฌธ์ : window_sum์ ์ ๊ทผํ ์ ์์ต๋๋ค.
(cuda-gdb) p a[0]
Attempt to take address of value not located in memory.
โ ๋ฌธ์ : TileTensor ์ง์ ์ธ๋ฑ์ฑ์ด ์๋ํ์ง ์์ต๋๋ค.
(cuda-gdb) p a.ptr[0]
$2 = {0}
(cuda-gdb) p a.ptr[0]@4
$3 = {{0}, {1}, {2}, {3}}
๐ฏ ๋ํ๊ตฌ: a.ptr[0]@4๋ก ์ ์ฒด ์
๋ ฅ ๋ฐฐ์ด์ ๋ณผ ์ ์์ต๋๋ค! ์ด๊ฒ์ด TileTensor
๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฌํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
3๋จ๊ณ: ํต์ฌ ๋ฐ๋ณต๋ฌธ ์กฐ์ฌ
Step 6: ๋ฐ๋ณต๋ฌธ ๋ชจ๋ํฐ๋ง ์ค์
(cuda-gdb) b 42
Breakpoint 1 at 0x7fffd326ffd0: file problems/p09/p09.mojo, line 42.
(cuda-gdb) c
Continuing.
CUDA thread hit Breakpoint 1, p09_process_sliding_window_...
<<<(1,1,1),(4,1,1)>>> (output=..., input=...)
at /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo:42
42 idx = thread_id + offset - 1
๐ ์ด์ ๋ฐ๋ณต๋ฌธ ๋ณธ๋ฌธ ์์ ์์ต๋๋ค. ์ง์ ๋ฐ๋ณต ํ์๋ฅผ ์ธ์ด๋ด ์๋ค.
Step 7: ์ฒซ ๋ฒ์งธ ๋ฐ๋ณต (offset = 0)
(cuda-gdb) n
43 if 0 <= idx < SIZE:
(cuda-gdb) n
41 for offset in range(ITER):
์ฒซ ๋ฒ์งธ ๋ฐ๋ณต ์๋ฃ: ๋ฐ๋ณต๋ฌธ์ด 42๋ฒ ์ค โ 43๋ฒ ์ค โ 41๋ฒ ์ค๋ก ๋์์์ต๋๋ค. ๋ฐ๋ณต๋ฌธ์ด ๊ณ์๋ฉ๋๋ค.
Step 8: ๋ ๋ฒ์งธ ๋ฐ๋ณต (offset = 1)
(cuda-gdb) n
CUDA thread hit Breakpoint 1, p09_process_sliding_window_...
42 idx = thread_id + offset - 1
(cuda-gdb) n
43 if 0 <= idx < SIZE:
(cuda-gdb) n
44 value = rebind[Scalar[dtype]](input[idx])
(cuda-gdb) n
45 window_sum += value
(cuda-gdb) n
43 if 0 <= idx < SIZE:
(cuda-gdb) n
41 for offset in range(ITER):
๋ ๋ฒ์งธ ๋ฐ๋ณต ์๋ฃ: ์ด๋ฒ์๋ if ๋ธ๋ก(44-45๋ฒ ์ค)์ ํต๊ณผํ์ต๋๋ค.
Step 9: ์ธ ๋ฒ์งธ ๋ฐ๋ณต ํ ์คํธ
(cuda-gdb) n
47 output[thread_id] = window_sum
๊ฒฐ์ ์ ๋ฐ๊ฒฌ: ๋ฐ๋ณต๋ฌธ์ด 2๋ฒ๋ง ๋๊ณ ์ข ๋ฃ๋์์ต๋๋ค! 42๋ฒ ์ค์ ๋ธ๋ ์ดํฌํฌ์ธํธ์ ๋ค์ ๊ฑธ๋ฆฌ์ง ์๊ณ 47๋ฒ ์ค๋ก ๋ฐ๋ก ๋์ด๊ฐ์ต๋๋ค.
๊ฒฐ๋ก : ๋ฐ๋ณต๋ฌธ์ด ์ ํํ 2๋ฒ ๋๊ณ ์ข ๋ฃ๋์์ต๋๋ค.
Step 10: ์ปค๋ ์คํ ์๋ฃ์ ์ปจํ ์คํธ ์์ค
(cuda-gdb) n
31 fn process_sliding_window(
(cuda-gdb) n
[Switching to Thread 0x7ffff7cc0e00 (LWP 110927)]
0x00007ffff064f84a in ?? () from /lib/x86_64-linux-gnu/libcuda.so.1
(cuda-gdb) p output.ptr[0]@4
No symbol "output" in current context.
(cuda-gdb) p offset
No symbol "offset" in current context.
๐ ์ปจํ ์คํธ ์์ค: ์ปค๋ ์คํ์ด ๋๋๋ฉด ์ปค๋ ๋ณ์์ ๋ ์ด์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ ์์ ์ธ ๋์์ ๋๋ค.
4๋จ๊ณ: ๊ทผ๋ณธ ์์ธ ๋ถ์
Step 11: ๊ด์ฐฐ๋ ์คํ์์ ์๊ณ ๋ฆฌ์ฆ ๋ถ์
๋๋ฒ๊น ์ธ์ ์์ ๊ด์ฐฐํ ๊ฒ:
- ๋ฐ๋ณต ํ์: 2๋ฒ๋ง ๋ฐ๋ณต (offset = 0, offset = 1)
- ๊ธฐ๋๊ฐ: ํฌ๊ธฐ 3์ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ๋ 3๋ฒ ๋ฐ๋ณตํด์ผ ํจ (offset = 0, 1, 2)
- ๋๋ฝ: ์ธ ๋ฒ์งธ ๋ฐ๋ณต (offset = 2)
๊ฐ ์ค๋ ๋๊ฐ ๊ณ์ฐํด์ผ ํ ๊ฒ:
- ์ค๋ ๋ 0: window_sum = input[-1] + input[0] + input[1] = (๊ฒฝ๊ณ) + 0 + 1 = 1.0
- ์ค๋ ๋ 1: window_sum = input[0] + input[1] + input[2] = 0 + 1 + 2 = 3.0
- ์ค๋ ๋ 2: window_sum = input[1] + input[2] + input[3] = 1 + 2 + 3 = 6.0
- ์ค๋ ๋ 3: window_sum = input[2] + input[3] + input[4] = 2 + 3 + (๊ฒฝ๊ณ) = 5.0
Step 12: ์ค๋ ๋ 0์ ์ค์ ์คํ ์ถ์
2๋ฒ๋ง ๋ฐ๋ณตํ ๊ฒฝ์ฐ (offset = 0, 1):
๋ฐ๋ณต 1 (offset = 0):
idx = thread_id + offset - 1 = 0 + 0 - 1 = -1if 0 <= idx < SIZE:โif 0 <= -1 < 4:โ False- ํฉ์ฐ ์ฐ์ฐ ๊ฑด๋๋
๋ฐ๋ณต 2 (offset = 1):
idx = thread_id + offset - 1 = 0 + 1 - 1 = 0if 0 <= idx < SIZE:โif 0 <= 0 < 4:โ Truewindow_sum += input[0]โwindow_sum += 0
๋๋ฝ๋ ๋ฐ๋ณต 3 (offset = 2):
idx = thread_id + offset - 1 = 0 + 2 - 1 = 1if 0 <= idx < SIZE:โif 0 <= 1 < 4:โ Truewindow_sum += input[1]โwindow_sum += 1โ ์ด ์ฐ์ฐ์ด ์คํ๋์ง ์์
๊ฒฐ๊ณผ: ์ค๋ ๋ 0์ window_sum = 0 + 1 = 1 ๋์ window_sum = 0์ ์ป์ต๋๋ค
5๋จ๊ณ: ๋ฒ๊ทธ ํ์ธ
๋ฌธ์ ์ฝ๋๋ฅผ ๋ณด๋ฉด:
comptime ITER = 2 # โ ๋ฒ๊ทธ: 3์ด์ด์ผ ํจ!
for offset in range(ITER): # โ 2๋ฒ๋ง ๋ฐ๋ณต: [0, 1]
idx = Int(thread_id) + offset - 1 # โ offset = 2 ๋๋ฝ
if 0 <= idx < SIZE:
value = rebind[Scalar[dtype]](a[idx])
window_sum += value
๐ฏ ๊ทผ๋ณธ ์์ธ ํ์ธ: ํฌ๊ธฐ 3์ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ๋ฅผ ์ํด ITER = 2๊ฐ
ITER = 3์ด์ด์ผ ํฉ๋๋ค.
์์ ๋ฐฉ๋ฒ: ์์ค ์ฝ๋์์ comptime ITER = 2๋ฅผ comptime ITER = 3์ผ๋ก
๋ณ๊ฒฝํฉ๋๋ค.
ํต์ฌ ๋๋ฒ๊น ๊ตํ
๋ณ์์ ์ ๊ทผํ ์ ์์ ๋:
- ์คํ ํ๋ฆ์ ์ง์ค - ๋ธ๋ ์ดํฌํฌ์ธํธ๊ฐ ๋ช ๋ฒ ๊ฑธ๋ฆฌ๋์ง, ๋ฐ๋ณต์ด ๋ช ๋ฒ ๋๋์ง ์ธ์ด๋ณด์ธ์
- ์ํ์ ์ถ๋ก ์ฌ์ฉ - ์ผ์ด๋์ผ ํ ์ผ๊ณผ ์ค์ ๋ก ์ผ์ด๋๋ ์ผ์ ๋ฐ์ ธ๋ณด์ธ์
- ํจํด ๋ถ์ - ์๋ชป๋ ๊ฒฐ๊ณผ๊ฐ ์กฐ์ฌ๋ฅผ ์ด๋๋๋ก ํ์ธ์
- ๊ต์ฐจ ๊ฒ์ฆ - ์ฌ๋ฌ ๋ฐ์ดํฐ ํฌ์ธํธ์ ๋ํด ๊ฐ์ค์ ํ ์คํธํ์ธ์
์ ๋ฌธ์ ์ธ GPU ๋๋ฒ๊น ์ ํ์ค:
- ์ปดํ์ผ๋ฌ ์ต์ ํ ๋๋ฌธ์ ๋ณ์ ๊ฒ์ฌ๊ฐ ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค
- ์คํ ํ๋ฆ ๋ถ์์ด ๋ฐ์ดํฐ ๊ฒ์ฌ๋ณด๋ค ๋ ์ ๋ขฐํ ์ ์์ต๋๋ค
- ํธ์คํธ ์ถ๋ ฅ ํจํด์ด ์ค์ํ ๋๋ฒ๊น ๋จ์๋ฅผ ์ ๊ณตํฉ๋๋ค
- ์์ค ์ฝ๋ ์ถ๋ก ์ด ์ ํ๋ ๋๋ฒ๊ฑฐ ๊ธฐ๋ฅ์ ๋ณด์ํฉ๋๋ค
TileTensor ๋๋ฒ๊น :
- TileTensor ์ถ์ํ๋ฅผ ์ฌ์ฉํด๋ ๊ทผ๋ณธ์ ์ธ ์๊ณ ๋ฆฌ์ฆ ๋ฒ๊ทธ๋ ๊ทธ๋๋ก ๋๋ฌ๋ฉ๋๋ค
- ํ ์ ๋ด์ฉ์ ๊ฒ์ฌํ๋ ค ํ๊ธฐ๋ณด๋ค ์๊ณ ๋ฆฌ์ฆ ๋ก์ง์ ์ง์คํ์ธ์
- ์ฒด๊ณ์ ์ธ ์ถ๋ก ์ผ๋ก ๊ฐ ์ค๋ ๋๊ฐ ์ ๊ทผํด์ผ ํ๋ ๊ฒ๊ณผ ์ค์ ๋ก ์ ๊ทผํ๋ ๊ฒ์ ์ถ์ ํ์ธ์
๐ก ํต์ฌ ํต์ฐฐ: ์ด๋ฐ ์ ํ์ off-by-one (์ญ์ฃผ: ๊ฒฝ๊ณ๊ฐ์ด 1๋งํผ ์ด๊ธ๋๋ ์ค๋ฅ) ๋ฐ๋ณต๋ฌธ ๋ฒ๊ทธ๋ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋งค์ฐ ํํฉ๋๋ค. ์ฌ๊ธฐ์ ๋ฐฐ์ด ์ฒด๊ณ์ ์ธ ์ ๊ทผ๋ฒ - ์ ํ๋ ๋๋ฒ๊ฑฐ ์ ๋ณด์ ์ํ์ ๋ถ์๊ณผ ํจํด ์ธ์์ ๊ฒฐํฉํ๋ ๊ฒ - ์ ๋๊ตฌ์ ํ๊ณ๊ฐ ์์ ๋ ์ ๋ฌธ GPU ๊ฐ๋ฐ์๋ค์ด ๋๋ฒ๊น ํ๋ ๋ฐฉ์ ๊ทธ๋๋ก์ ๋๋ค.
๋ค์ ๋จ๊ณ: ๋ก์ง ๋ฒ๊ทธ์์ ๊ต์ฐฉ ์ํ๋ก
๋ก์ง ๋ฒ๊ทธ ๋๋ฒ๊น ์ ์ตํ์ต๋๋ค! ์ด์ ํ ์ ์์ต๋๋ค:
- โ ํฌ๋์๋ ๋๋ ทํ ์ฆ์ ์์ด๋ ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ ์กฐ์ฌ
- โ ํจํด ๋ถ์์ผ๋ก ์๋ชป๋ ๊ฒฐ๊ณผ์์ ๊ทผ๋ณธ ์์ธ๊น์ง ์ถ์
- โ ์คํ ํ๋ฆ ๋ถ์์ผ๋ก ๋ณ์ ์ ๊ทผ์ด ์ ํ๋ ์ํฉ์์ ๋๋ฒ๊น
- โ ๋๋ฒ๊ฑฐ ๋๊ตฌ์ ํ๊ณ๊ฐ ์์ ๋ ์ํ์ ์ถ๋ก ์ ์ฉ
๋ง์ง๋ง ๋์ : ํ์ ์์ฌ: ์ธ ๋ฒ์งธ ์ฌ๋ก
๊ทธ๋ฐ๋ฐ ํ๋ก๊ทธ๋จ์ด ํฌ๋์ํ์ง๋ ์๊ณ ๋๋์ง๋ ์๋๋ค๋ฉด์? ๊ทธ๋ฅ ์์ํ ๋ฉ์ถฐ๋ฒ๋ฆฐ๋ค๋ฉด์?
์ธ ๋ฒ์งธ ์ฌ๋ก๋ ๊ถ๊ทน์ ๋๋ฒ๊น ๋์ ์ ์ ์ํฉ๋๋ค:
- โ ํฌ๋์ ๋ฉ์์ง ์์ (์ฒซ ๋ฒ์งธ ์ฌ๋ก์ฒ๋ผ)
- โ ์๋ชป๋ ๊ฒฐ๊ณผ ์์ (๋ ๋ฒ์งธ ์ฌ๋ก์ฒ๋ผ)
- โ ์๋ฃ ์์ฒด๊ฐ ์์ - ๊ทธ๋ฅ ๋ฌดํํ ๋ฉ์ถค
- โ ๊ณ ๊ธ ์ค๋ ๋ ์กฐ์จ ๋ถ์์ด ํ์ํ ์กฐ์ฉํ ๊ต์ฐฉ ์ํ
์๋กญ๊ฒ ์ตํ๊ฒ ๋ ์คํฌ:
- ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ ํ์ง - ๋ณ๋ ฌ ์ค๋ ๋์์ ์กฐ์จ ์คํจ ์ฐพ๊ธฐ
- ๋ฉํฐ ์ค๋ ๋ ์ํ ๋ถ์ - ๋ชจ๋ ์ค๋ ๋๋ฅผ ๋์์ ๊ฒ์ฌํ๊ธฐ
- ๋๊ธฐํ ๋๋ฒ๊น - ์ค๋ ๋ ํ๋ ฅ ์คํจ ์ดํดํ๊ธฐ
๋๋ฒ๊น ์งํ:
- ์ฒซ ๋ฒ์งธ ์ฌ๋ก: ํฌ๋์ ์ ํธ ๋ฐ๋ผ๊ฐ๊ธฐ โ ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ ์ฐพ๊ธฐ
- ๋ ๋ฒ์งธ ์ฌ๋ก: ๊ฒฐ๊ณผ ํจํด ๋ถ์ํ๊ธฐ โ ๋ก์ง ๋ฒ๊ทธ ์ฐพ๊ธฐ
- ์ธ ๋ฒ์งธ ์ฌ๋ก: ์ค๋ ๋ ์ํ ์กฐ์ฌํ๊ธฐ โ ์กฐ์จ ๋ฒ๊ทธ ์ฐพ๊ธฐ
์ด์ ๋ ์ฌ๋ก์์ ๋ฐฐ์ด ์ฒด๊ณ์ ์ธ ์กฐ์ฌ ์คํฌ - ๊ฐ์ค ์๋ฆฝ, ์ฆ๊ฑฐ ์์ง, ํจํด ๋ถ์ - ์ ๊ฐ์ฅ ์ด๋ ค์ด GPU ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ ๋ ํต์ฌ์ด ๋ฉ๋๋ค: ์กฐ์จ์ด ์ด๊ธ๋ ์์ํ ์๋ก๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ค๋ ๋๋ค.
๐ต ํ์ ์์ฌ: ์ธ ๋ฒ์งธ ์ฌ๋ก
๊ฐ์
๋ฉ๋ชจ๋ฆฌ ํฌ๋์์ ๋ก์ง ๋ฒ๊ทธ ๋๋ฒ๊น ์ ์ตํ์ต๋๋ค. ์ด์ GPU ๋๋ฒ๊น ์ ์ต์ข ๋ณด์ค์ ๋์ ํฉ๋๋ค: ํ๋ก๊ทธ๋จ์ด ๋ฌดํ์ ๋ฉ์ถฐ๋ฒ๋ฆฌ๋ ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ. ์ค๋ฅ ๋ฉ์์ง๋, ์๋ชป๋ ๊ฒฐ๊ณผ๋ ์์ด - ๊ทธ์ ๋์๋ ์นจ๋ฌต๋ง ์์ต๋๋ค.
๋๋ฒ๊น ์ฌ์ ์ ์๊ฒฐ:
- ์ฒซ ๋ฒ์งธ ์ฌ๋ก: ํ๋ก๊ทธ๋จ ํฌ๋์ โ ์ค๋ฅ ์ ํธ ์ถ์ โ ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ ๋ฐ๊ฒฌ
- ๋ ๋ฒ์งธ ์ฌ๋ก: ์๋ชป๋ ๊ฒฐ๊ณผ ์ถ๋ ฅ โ ํจํด ๋ถ์ โ ๋ก์ง ๋ฒ๊ทธ ๋ฐ๊ฒฌ
- ์ธ ๋ฒ์งธ ์ฌ๋ก: ํ๋ก๊ทธ๋จ ๋ฌดํ ์ ์ง โ ์ค๋ ๋ ์ํ ์กฐ์ฌ โ ์กฐ์จ ๋ฒ๊ทธ ๋ฐ๊ฒฌ
์ด ๊ณ ๊ธ ๋๋ฒ๊น ์ฑ๋ฆฐ์ง์์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, TileTensor ์ฐ์ฐ, ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ๊ฐ ์ฝํ ์ค๋ ๋ ์กฐ์จ ์คํจ๋ฅผ ์กฐ์ฌํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค - ์ด์ ์ฌ๋ก๋ค์์ ์ตํ ์ฒด๊ณ์ ์ธ ์กฐ์ฌ ๊ธฐ์ ์ ์ด๋์ํฉ๋๋ค.
์ฌ์ ์ค๋น: Mojo GPU ๋๋ฒ๊น ์ ํต์ฌ, ํ์ ์์ฌ: ์ฒซ ๋ฒ์งธ ์ฌ๋ก, ํ์ ์์ฌ: ๋ ๋ฒ์งธ ์ฌ๋ก๋ฅผ ๋จผ์ ์๋ฃํด์ CUDA-GDB ์ํฌํ๋ก์ฐ, ๋ณ์ ๊ฒ์ฌ์ ํ๊ณ, ์ฒด๊ณ์ ์ธ ๋๋ฒ๊น ์ ๊ทผ๋ฒ์ ์ดํดํ์ธ์. ์๋ ์ค์ ๋ช ๋ น์ ์คํํ๋์ง ํ์ธํ์ธ์:
pixi run -e nvidia setup-cuda-gdb
ํต์ฌ ๊ฐ๋
์ด๋ฒ ๋๋ฒ๊น ์ฑ๋ฆฐ์ง์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ ํ์ง: ์ค๋ ๋๋ค์ด ๋๊ธฐํ ์ง์ ์์ ์์ํ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋๋ ์ํฉ ์๋ณํ๊ธฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์จ: TileTensor๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ํ๋ ฅ ํจํด ์ดํดํ๊ธฐ
- ์กฐ๊ฑด๋ถ ์คํ ๋ถ์: ์ผ๋ถ ์ค๋ ๋๊ฐ ๋ค๋ฅธ ์ฝ๋ ๊ฒฝ๋ก๋ฅผ ํ ๋ ๋๋ฒ๊น ํ๊ธฐ
- ์ค๋ ๋ ์กฐ์จ ๋๋ฒ๊น : CUDA-GDB๋ก ๋ค์ค ์ค๋ ๋ ๋๊ธฐํ ์คํจ ๋ถ์ํ๊ธฐ
์ฝ๋ ์คํ
๋จผ์ ์ ์ฒด ์ฝ๋๋ฅผ ๋ณด์ง ์๊ณ ์ปค๋๋ง ์ดํด๋ด ์๋ค:
def collaborative_filter(
output: TileTensor[mut=True, dtype, VectorLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, VectorLayout, ImmutAnyOrigin],
):
var thread_id = thread_idx.x
# Shared memory workspace for collaborative processing
var shared_workspace = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[SIZE - 1]())
# Phase 1: Initialize shared workspace (all threads participate)
if thread_id < SIZE - 1:
shared_workspace[thread_id] = rebind[Scalar[dtype]](a[thread_id])
barrier()
# Phase 2: Collaborative processing
if thread_id < SIZE - 1:
# Apply collaborative filter with neighbors
if thread_id > 0:
shared_workspace[thread_id] += shared_workspace[thread_id - 1] * 0.5
barrier()
# Phase 3: Final synchronization and output
barrier()
# Write filtered results back to output
if thread_id < SIZE - 1:
output[thread_id] = shared_workspace[thread_id]
else:
output[thread_id] = rebind[Scalar[dtype]](a[thread_id])
๋ฒ๊ทธ๋ฅผ ์ง์ ๊ฒฝํํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช
๋ น์ ์คํํ์ธ์ (pixi ์ ์ฉ):
pixi run -e nvidia p09 --third-case
๋ค์๊ณผ ๊ฐ์ ์ถ๋ ฅ์ด ๋ํ๋ฉ๋๋ค - ํ๋ก๊ทธ๋จ์ด ๋ฌดํ์ ๋ฉ์ถฅ๋๋ค:
Third Case: Advanced collaborative filtering with shared memory...
WARNING: This may hang - use Ctrl+C to stop if needed
Input array: [1, 2, 3, 4]
Applying collaborative filter using shared memory...
Each thread cooperates with neighbors for smoothing...
Waiting for GPU computation to complete...
[HANGS FOREVER - Use Ctrl+C to stop]
โ ๏ธ ๊ฒฝ๊ณ : ์ด ํ๋ก๊ทธ๋จ์ ๋ฉ์ถฐ์ ์๋ฃ๋์ง ์์ต๋๋ค. Ctrl+C๋ก ์ค๋จํ์ธ์.
๊ณผ์ : ํ์ ์์ฌ
๋์ : ํ๋ก๊ทธ๋จ์ด ์ ์์ ์ผ๋ก ์์๋์ง๋ง GPU ์ฐ์ฐ ์ค์ ๋ฉ์ถฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ์ง ์์ต๋๋ค. ์ฝ๋๋ฅผ ๋ณด์ง ์์ ์ํ์์, ์ด ๊ต์ฐฉ ์ํ๋ฅผ ์กฐ์ฌํ๊ธฐ ์ํ ์ฒด๊ณ์ ์ธ ์ ๊ทผ๋ฒ์ ๋ฌด์์ผ๊น์?
์๊ฐํด๋ณผ ์ :
- GPU ์ปค๋์ด ์์ ์๋ฃ๋์ง ์๊ฒ ๋ง๋๋ ์์ธ์ ๋ฌด์์ผ๊น์?
- ์ค๋ ๋ ์กฐ์จ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ์กฐ์ฌํ์๊ฒ ์ต๋๊น?
- ์ค๋ฅ ๋ฉ์์ง ์์ด ํ๋ก๊ทธ๋จ์ด ๊ทธ๋ฅ โ๋ฉ์ถฐ๋ฒ๋ฆดโ ๋ ์ด๋ค ๋๋ฒ๊น ์ ๋ต์ด ํตํ ๊น์?
- ์ค๋ ๋๋ค์ด ์ ๋๋ก ํ๋ ฅํ์ง ์์ ์๋ ์๋ค๋ฉด ์ด๋ป๊ฒ ๋๋ฒ๊น ํ ๊น์?
- ์ฒด๊ณ์ ์กฐ์ฌ(์ฒซ ๋ฒ์งธ ์ฌ๋ก)์ ์คํ ํ๋ฆ ๋ถ์(๋ ๋ฒ์งธ ์ฌ๋ก)์ ๊ฒฐํฉํด์ ์กฐ์จ ์คํจ๋ฅผ ์ด๋ป๊ฒ ๋๋ฒ๊น ํ ์ ์์๊น์?
๋ค์ ๋ช ๋ น์ผ๋ก ์์ํด ๋ณด์ธ์:
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --third-case
GDB ๋ช ๋ น์ด ๋จ์ถํค (๋น ๋ฅธ ๋๋ฒ๊น )
์ด ๋จ์ถํค๋ค์ ์ฌ์ฉํ๋ฉด ๋๋ฒ๊น ์ธ์ ์๋๋ฅผ ๋์ผ ์ ์์ต๋๋ค:
| ๋จ์ถ | ์ ์ฒด | ์ฌ์ฉ ์์ |
|---|---|---|
r | run | (cuda-gdb) r |
n | next | (cuda-gdb) n |
c | continue | (cuda-gdb) c |
b | break | (cuda-gdb) b 62 |
p | print | (cuda-gdb) p thread_id |
q | quit | (cuda-gdb) q |
์๋ ๋ชจ๋ ๋๋ฒ๊น ๋ช ๋ น์ ํจ์จ์ฑ์ ์ํด ๋จ์ถํค๋ฅผ ์ฌ์ฉํฉ๋๋ค!
ํ
- ์๋ฆฌ ์๋ ๋ฉ์ถค ์กฐ์ฌ - ์ค๋ฅ ๋ฉ์์ง ์์ด ํ๋ก๊ทธ๋จ์ด ๋ฉ์ถฐ๋ฒ๋ฆด ๋, GPU์ ์ด๋ค ๊ธฐ๋ณธ ์์๊ฐ ๋ฌดํ ๋๊ธฐ๋ฅผ ์ผ์ผํฌ ์ ์์๊น์?
- ์ค๋ ๋ ์ํ ๊ฒ์ฌ -
info cuda threads๋ก ์๋ก ๋ค๋ฅธ ์ค๋ ๋๋ค์ด ์ด๋์ ๋ฉ์ท๋์ง ํ์ธํ์ธ์ - ์กฐ๊ฑด๋ถ ์คํ ๋ถ์ - ์ด๋ค ์ค๋ ๋๊ฐ ์ด๋ค ์ฝ๋ ๊ฒฝ๋ก๋ฅผ ์คํํ๋์ง ํ์ธํ์ธ์ (๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ฅด๋์?)
- ๋๊ธฐํ ์ง์ ์กฐ์ฌ - ์ค๋ ๋๋ค์ด ์กฐ์จํด์ผ ํ ์๋ ์๋ ์ง์ ์ ์ฐพ์ผ์ธ์
- ์ค๋ ๋ ๋ถ๊ธฐ ํ์ง - ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ํ๋ก๊ทธ๋จ ์์น์ ์๋์, ์๋๋ฉด ์ผ๋ถ๋ ๋ค๋ฅธ ๊ณณ์ ์๋์?
- ์กฐ์จ ๊ธฐ๋ณธ ์์ ๋ถ์ - ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋๊ธฐํ ์ฐ์ฐ์ ์ฐธ์ฌํ์ง ์์ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?
- ์คํ ํ๋ฆ ์ถ์ - ๊ฐ ์ค๋ ๋๊ฐ ์กฐ๊ฑด๋ฌธ์ ํตํด ์ด๋ค ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ผ๊ฐ๋์ง ์ถ์ ํ์ธ์
- ์ค๋ ๋ ID ์ํฅ ๋ถ์ - ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ID๊ฐ ์ด๋ค ์ฝ๋ ๊ฒฝ๋ก๋ฅผ ์คํํ ์ง ์ด๋ป๊ฒ ์ํฅ์ ๋ฏธ์น๋์?
๐ก ์กฐ์ฌ ๊ณผ์ ๊ณผ ํด๊ฒฐ์ฑ
CUDA-GDB๋ก ๋จ๊ณ๋ณ ์กฐ์ฌ
1๋จ๊ณ: ์คํ๊ณผ ์ด๊ธฐ ์ค์
Step 1: ๋๋ฒ๊ฑฐ ์คํ
pixi run -e nvidia mojo debug --cuda-gdb --break-on-launch problems/p09/p09.mojo --third-case
Step 2: ์ ์ง ํ์ ๋ถ์
๋๋ฒ๊น ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ์๊ณ ์๋ ์ ๋ณด๋ฅผ ์ ๋ฆฌํฉ๋๋ค:
๊ธฐ๋๊ฐ: ํ๋ก๊ทธ๋จ์ด ์๋ฃ๋๊ณ ํํฐ๋ง๋ ๊ฒฐ๊ณผ ํ์
์ค์ : "Waiting for GPU computation to complete..."์์ ๋ฉ์ถค
๐ ์ด๊ธฐ ๊ฐ์ค: GPU ์ปค๋์ด ๊ต์ฐฉ ์ํ์ ๋น ์ง - ์ด๋ค ๋๊ธฐํ ๊ธฐ๋ณธ ์์๊ฐ ์ค๋ ๋๋ค์ ์์ํ ๋๊ธฐ์ํค๊ณ ์์ต๋๋ค.
2๋จ๊ณ: ์ปค๋ ์ง์
Step 3: ์คํ ๋ฐ ์ปค๋ ์ง์ ๊ด์ฐฐ
(cuda-gdb) r
Starting program: .../mojo run problems/p09/p09.mojo --third-case
Third Case: Advanced collaborative filtering with shared memory...
WARNING: This may hang - use Ctrl+C to stop if needed
Input array: [1, 2, 3, 4]
Applying collaborative filter using shared memory...
Each thread cooperates with neighbors for smoothing...
Waiting for GPU computation to complete...
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (0,0,0), device 0, sm 0, warp 0, lane 0]
CUDA thread hit application kernel entry function breakpoint, p09_collaborative_filter_Orig6A6AcB6A6A_1882ca334fc2d34b2b9c4fa338df6c07<<<(1,1,1),(4,1,1)>>> (
output=..., a=...)
at /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo:56
56 a: TileTensor[mut=False, dtype, vector_layout],
๐ ์ฃผ์ ๊ด์ฐฐ:
- Grid: (1,1,1) - ๋จ์ผ ๋ธ๋ก
- Block: (4,1,1) - ์ด 4๊ฐ ์ค๋ ๋ (0, 1, 2, 3)
- ํ์ฌ ์ค๋ ๋: (0,0,0) - ์ค๋ ๋ 0 ๋๋ฒ๊น ์ค
- ํจ์: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ์ฌ์ฉํ๋ collaborative_filter
Step 4: ์ด๊ธฐํ ๊ณผ์ ํ์
(cuda-gdb) n
55 output: TileTensor[mut=True, dtype, vector_layout],
(cuda-gdb) n
58 thread_id = thread_idx.x
(cuda-gdb) n
66 ].stack_allocation()
(cuda-gdb) n
69 if thread_id < SIZE - 1:
(cuda-gdb) p thread_id
$1 = 0
โ
์ค๋ ๋ 0 ์ํ: thread_id = 0, ์กฐ๊ฑด 0 < 3 ๊ฒ์ฌ ์ง์ โ True
Step 5: 1๋จ๊ณ ์ถ์
(cuda-gdb) n
70 shared_workspace[thread_id] = rebind[Scalar[dtype]](a[thread_id])
(cuda-gdb) n
69 if thread_id < SIZE - 1:
(cuda-gdb) n
71 barrier()
1๋จ๊ณ ์๋ฃ: ์ค๋ ๋ 0์ด ์ด๊ธฐํ๋ฅผ ์คํํ๊ณ ์ฒซ ๋ฒ์งธ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํ์ต๋๋ค.
3๋จ๊ณ: ๊ฒฐ์ ์ ์ธ ๋ฐฐ๋ฆฌ์ด ์กฐ์ฌ
Step 6: ์ฒซ ๋ฒ์งธ ๋ฐฐ๋ฆฌ์ด ๊ฒ์ฌ
(cuda-gdb) n
74 if thread_id < SIZE - 1:
(cuda-gdb) info cuda threads
BlockIdx ThreadIdx To BlockIdx To ThreadIdx Count PC Filename Line
Kernel 0
* (0,0,0) (0,0,0) (0,0,0) (3,0,0) 4 0x00007fffd3272180 /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo 74
โ ์ ์: 4๊ฐ ์ค๋ ๋ ๋ชจ๋ 74๋ฒ ์ค(์ฒซ ๋ฒ์งธ ๋ฐฐ๋ฆฌ์ด ํต๊ณผ ํ)์ ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋ฐฐ๋ฆฌ์ด๋ ์ ์ ์๋ํ์ต๋๋ค.
๐ ๊ฒฐ์ ์ ์ง์ : ์ด์ ๋ ๋ค๋ฅธ ์กฐ๊ฑด๋ฌธ์ด ์๋ 2๋จ๊ณ์ ์ง์ ํฉ๋๋ค.
Step 7: 2๋จ๊ณ ์ถ์ - ์ค๋ ๋ 0 ๊ด์
(cuda-gdb) n
76 if thread_id > 0:
์ค๋ ๋ 0 ๋ถ์: 0 < 3 โ True โ ์ค๋ ๋ 0์ด 2๋จ๊ณ ๋ธ๋ก์ ์ง์
(cuda-gdb) n
78 barrier()
์ค๋ ๋ 0 ๊ฒฝ๋ก: 0 > 0 โ False โ ์ค๋ ๋ 0์ด ๋ด๋ถ ์ฐ์ฐ์ ๊ฑด๋๋ฐ์ง๋ง 78๋ฒ
์ค์ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌ
๊ฒฐ์ ์ ์๊ฐ: ์ค๋ ๋ 0์ด ์ด์ 78๋ฒ ์ค์ ๋ฐฐ๋ฆฌ์ด์์ ๋๊ธฐ ์ค์ ๋๋ค.
(cuda-gdb) n # <-- ์คํํ๋ฉด ํ๋ก๊ทธ๋จ์ด ๋ฉ์ถฅ๋๋ค!
[HANGS HERE - ํ๋ก๊ทธ๋จ์ด ์ด ์ง์ ์ ๋์ด๊ฐ์ง ๋ชปํจ]
Step 8: ๋ค๋ฅธ ์ค๋ ๋ ์กฐ์ฌ
(cuda-gdb) cuda thread (1,0,0)
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (1,0,0), device 0, sm 0, warp 0, lane 1]
78 barrier()
(cuda-gdb) p thread_id
$2 = 1
(cuda-gdb) info cuda threads
BlockIdx ThreadIdx To BlockIdx To ThreadIdx Count PC Filename Line
Kernel 0
* (0,0,0) (0,0,0) (0,0,0) (2,0,0) 3 0x00007fffd3273aa0 /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo 78
(0,0,0) (3,0,0) (0,0,0) (3,0,0) 1 0x00007fffd3273b10 /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p09/p09.mojo 81
๊ฒฐ์ ์ ์ฆ๊ฑฐ ๋ฐ๊ฒฌ:
- ์ค๋ ๋ 0, 1, 2: 78๋ฒ ์ค์์ ๋ชจ๋ ๋๊ธฐ ์ค (์กฐ๊ฑด ๋ธ๋ก ์์ ๋ฐฐ๋ฆฌ์ด)
- ์ค๋ ๋ 3: 81๋ฒ ์ค์ ์์ (์กฐ๊ฑด ๋ธ๋ก์ ์ง๋์ณค๊ณ , ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํ ์ ์์!)
Step 9: ์ค๋ ๋ 3์ ์คํ ๊ฒฝ๋ก ๋ถ์
๐ info ์ถ๋ ฅ์ผ๋ก ๋ณธ ์ค๋ ๋ 3 ๋ถ์:
- ์ค๋ ๋ 3: 81๋ฒ ์ค์ ์์น (PC: 0x00007fffd3273b10)
- 2๋จ๊ณ ์กฐ๊ฑด:
thread_id < SIZE - 1โ3 < 3โ False - ๊ฒฐ๊ณผ: ์ค๋ ๋ 3์ 2๋จ๊ณ ๋ธ๋ก(74-78๋ฒ ์ค)์ ์ง์ ํ์ง ์์
- ๊ฒฐ๊ณผ: ์ค๋ ๋ 3์ 78๋ฒ ์ค์ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํ ์ ์์
- ํ์ฌ ์ํ: ์ค๋ ๋ 3์ 81๋ฒ ์ค(๋ง์ง๋ง ๋ฐฐ๋ฆฌ์ด)์ ์๊ณ , ์ค๋ ๋ 0,1,2๋ 78๋ฒ ์ค์์ ๊ฐํ ์์
4๋จ๊ณ: ๊ทผ๋ณธ ์์ธ ๋ถ์
Step 10: ๊ต์ฐฉ ์ํ ๋ฉ์ปค๋์ฆ ์๋ณ
# 2๋จ๊ณ: ํ๋ ฅ์ ์ฒ๋ฆฌ
if thread_id < SIZE - 1: # โ ์ค๋ ๋ 0, 1, 2๋ง ์ด ๋ธ๋ก์ ์ง์
# ์ด์๊ณผ ํ๋ ฅ ํํฐ ์ ์ฉ
if thread_id > 0:
shared_workspace[thread_id] += shared_workspace[thread_id - 1] * 0.5
barrier() # โ ๊ต์ฐฉ ์ํ: 4๊ฐ ์ค 3๊ฐ ์ค๋ ๋๋ง ์ฌ๊ธฐ์ ๋๋ฌ!
๐ ๊ต์ฐฉ ์ํ ๋ฉ์ปค๋์ฆ:
- ์ค๋ ๋ 0:
0 < 3โ True โ ๋ธ๋ก ์ง์ โ ๋ฐฐ๋ฆฌ์ด์์ ๋๊ธฐ (69๋ฒ ์ค) - ์ค๋ ๋ 1:
1 < 3โ True โ ๋ธ๋ก ์ง์ โ ๋ฐฐ๋ฆฌ์ด์์ ๋๊ธฐ (69๋ฒ ์ค) - ์ค๋ ๋ 2:
2 < 3โ True โ ๋ธ๋ก ์ง์ โ ๋ฐฐ๋ฆฌ์ด์์ ๋๊ธฐ (69๋ฒ ์ค) - ์ค๋ ๋ 3:
3 < 3โ False โ ๋ธ๋ก์ ์ง์ ์ ํจ โ 72๋ฒ ์ค๋ก ๊ณ์ ์งํ
๊ฒฐ๊ณผ: 3๊ฐ ์ค๋ ๋๊ฐ 4๋ฒ์งธ ์ค๋ ๋๋ฅผ ์์ํ ๊ธฐ๋ค๋ฆฌ์ง๋ง, ์ค๋ ๋ 3์ ๊ทธ ๋ฐฐ๋ฆฌ์ด์ ์ ๋ ๋์ฐฉํ์ง ์์ต๋๋ค.
5๋จ๊ณ: ๋ฒ๊ทธ ํ์ธ๊ณผ ํด๊ฒฐ์ฑ
Step 11: ๊ทผ๋ณธ์ ์ธ ๋ฐฐ๋ฆฌ์ด ๊ท์น ์๋ฐ
GPU ๋ฐฐ๋ฆฌ์ด ๊ท์น: ๋๊ธฐํ๊ฐ ์๋ฃ๋๋ ค๋ฉด ์ค๋ ๋ ๋ธ๋ก์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํด์ผ ํฉ๋๋ค.
๋ฌด์์ด ์๋ชป๋์๋:
# โ ์๋ชป๋ ๋ฐฉ๋ฒ: ์กฐ๊ฑด๋ฌธ ์์ ๋ฐฐ๋ฆฌ์ด
if thread_id < SIZE - 1: # ๋ชจ๋ ์ค๋ ๋๊ฐ ์ง์
ํ์ง ์์
# ... ์ฐ์ฐ ...
barrier() # ์ผ๋ถ ์ค๋ ๋๋ง ์ฌ๊ธฐ์ ๋๋ฌ
# โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์กฐ๊ฑด๋ฌธ ๋ฐ์ ๋ฐฐ๋ฆฌ์ด
if thread_id < SIZE - 1: # ๋ชจ๋ ์ค๋ ๋๊ฐ ์ง์
ํ์ง ์์
# ... ์ฐ์ฐ ...
barrier() # ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฌ๊ธฐ์ ๋๋ฌ
์์ ๋ฐฉ๋ฒ: ๋ฐฐ๋ฆฌ์ด๋ฅผ ์กฐ๊ฑด ๋ธ๋ก ๋ฐ์ผ๋ก ์ด๋:
def collaborative_filter(
output: TileTensor[mut=True, dtype, vector_layout],
a: TileTensor[mut=False, dtype, vector_layout],
):
thread_id = thread_idx.x
shared_workspace = TileTensor[
dtype,
row_major[SIZE-1](),
MutAnyOrigin,
address_space = AddressSpace.SHARED,
].stack_allocation()
# 1๋จ๊ณ: ๊ณต์ ์์
๊ณต๊ฐ ์ด๊ธฐํ (๋ชจ๋ ์ค๋ ๋ ์ฐธ์ฌ)
if thread_id < SIZE - 1:
shared_workspace[thread_id] = rebind[Scalar[dtype]](a[thread_id])
barrier()
# 2๋จ๊ณ: ํ๋ ฅ์ ์ฒ๋ฆฌ
if thread_id < SIZE - 1:
if thread_id > 0:
shared_workspace[thread_id] += shared_workspace[thread_id - 1] * 0.5
# โ
์์ : ๋ฐฐ๋ฆฌ์ด๋ฅผ ์กฐ๊ฑด๋ฌธ ๋ฐ์ผ๋ก ์ด๋ํด์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๋๋ฌํ๋๋ก
barrier()
# 3๋จ๊ณ: ์ต์ข
๋๊ธฐํ์ ์ถ๋ ฅ
barrier()
if thread_id < SIZE - 1:
output[thread_id] = shared_workspace[thread_id]
else:
output[thread_id] = rebind[Scalar[dtype]](a[thread_id])
ํต์ฌ ๋๋ฒ๊น ๊ตํ
๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ ํ์ง:
info cuda threads์ฌ์ฉ - ์ด๋ค ์ค๋ ๋๊ฐ ์ด๋ ์ค์ ์๋์ง ๋ณด์ฌ์ค- ์ค๋ ๋ ์ํ ๋ถ๊ธฐ ์ฐพ๊ธฐ - ์ผ๋ถ ์ค๋ ๋๊ฐ ๋ค๋ฅธ ํ๋ก๊ทธ๋จ ์์น์ ์์
- ์กฐ๊ฑด๋ถ ์คํ ๊ฒฝ๋ก ์ถ์ - ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํ๋์ง ํ์ธ
- ๋ฐฐ๋ฆฌ์ด ๋๋ฌ ๊ฐ๋ฅ์ฑ ๊ฒ์ฆ - ๋ค๋ฅธ ์ค๋ ๋๋ค์ด ๋๋ฌํ๋ ๋ฐฐ๋ฆฌ์ด๋ฅผ ๊ฑด๋๋ฐ๋ ์ค๋ ๋๊ฐ ์๋์ง ํ์ธ
์ค๋ฌด GPU ๋๋ฒ๊น ์ ํ์ค:
- ๊ต์ฐฉ ์ํ๋ ์๋ฆฌ ์๋ ์ด์ธ์ - ์ค๋ฅ ๋ฉ์์ง ์์ด ํ๋ก๊ทธ๋จ์ด ๊ทธ๋ฅ ๋ฉ์ถค
- ์ค๋ ๋ ์กฐ์จ ๋๋ฒ๊น ์ ์ธ๋ด๊ฐ ํ์ - ๊ฐ ์ค๋ ๋ ๊ฒฝ๋ก๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๋ถ์ํด์ผ ํจ
- ์กฐ๊ฑด๋ถ ๋ฐฐ๋ฆฌ์ด๊ฐ ๊ต์ฐฉ ์ํ์ 1์์ ์์ธ - ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋๊ธฐํ ์ง์ ์ ๋๋ฌํ๋์ง ํญ์ ํ์ธ
- CUDA-GDB ์ค๋ ๋ ๊ฒ์ฌ๊ฐ ํ์ - ์ค๋ ๋ ์กฐ์จ ์คํจ๋ฅผ ๋ณผ ์ ์๋ ์ ์ผํ ๋ฐฉ๋ฒ
๊ณ ๊ธ GPU ๋๊ธฐํ:
- ๋ฐฐ๋ฆฌ์ด ๊ท์น: ๋ธ๋ก์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฐฐ๋ฆฌ์ด์ ๋๋ฌํด์ผ ํจ
- ์กฐ๊ฑด๋ถ ์คํ์ ํจ์ : ์ด๋ค if๋ฌธ์ด๋ ์ค๋ ๋ ๋ถ๊ธฐ๋ฅผ ์ผ์ผํฌ ์ ์์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์จ: ์ฌ๋ฐ๋ฅธ ๋๊ธฐํ๋ฅผ ์ํด ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น์ ์ฃผ์ ํ์
- TileTensor๊ฐ ๊ต์ฐฉ ์ํ๋ฅผ ๋ง์์ฃผ์ง ์์: ๊ณ ์์ค ์ถ์ํ๋ผ๋ ์ฌ๋ฐ๋ฅธ ๋๊ธฐํ๋ ์ฌ์ ํ ํ์
๐ก ํต์ฌ ํต์ฐฐ: ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ๋ GPU ๋ฒ๊ทธ ์ค ๋๋ฒ๊น ํ๊ธฐ ๊ฐ์ฅ ์ด๋ ค์ด ์ ํ์ ์ํฉ๋๋ค:
- ์ค๋ฅ๊ฐ ๋ณด์ด์ง ์์ - ๊ทธ์ ๋ฌดํ ๋๊ธฐ
- ๋ค์ค ์ค๋ ๋ ๋ถ์ ํ์ - ์ค๋ ๋ ํ๋๋ง ๋ด์๋ ๋๋ฒ๊น ํ ์ ์์
- ์กฐ์ฉํ ์คํจ ๋ชจ๋ - ์ ํ์ฑ ๋ฒ๊ทธ๊ฐ ์๋ ์ฑ๋ฅ ๋ฌธ์ ์ฒ๋ผ ๋ณด์
- ๋ณต์กํ ์ค๋ ๋ ์กฐ์จ - ๋ชจ๋ ์ค๋ ๋์ ๊ฑธ์ณ ์คํ ๊ฒฝ๋ก๋ฅผ ์ถ์ ํด์ผ ํจ
CUDA-GDB๋ก ์ค๋ ๋ ์ํ๋ฅผ ๋ถ์ํ๊ณ , ๋ถ๊ธฐ๋ ์คํ ๊ฒฝ๋ก๋ฅผ ์๋ณํ๊ณ , ๋ฐฐ๋ฆฌ์ด ๋๋ฌ ๊ฐ๋ฅ์ฑ์ ๊ฒ์ฆํ๋ ์ด ๋๋ฒ๊น ๋ฐฉ์์ ์ค๋ฌด GPU ๊ฐ๋ฐ์๋ค์ด ์ด์ ์์คํ ์์ ๊ต์ฐฉ ์ํ ๋ฌธ์ ์ ๋ง๋ฅ๋จ๋ ธ์ ๋ ์ฐ๋ ๋ฐฉ๋ฒ๊ณผ ์ ํํ ๊ฐ์ต๋๋ค.
๋ค์ ๋จ๊ณ: GPU ๋๋ฒ๊น ์คํฌ ์์ฑ
GPU ๋๋ฒ๊น ์ผ๋ถ์์ ์๋ฃํ์ต๋๋ค!
์์ฑ๋ GPU ๋๋ฒ๊น ๋ฌด๊ธฐ๊ณ
์ฒซ ๋ฒ์งธ ์ฌ๋ก์์ - ํฌ๋์ ๋๋ฒ๊น :
- โ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๊ฐ์ด๋ ์ผ์ ์ฒด๊ณ์ ์ธ ํฌ๋์ ์กฐ์ฌ
- โ ํฌ์ธํฐ ์ฃผ์ ๊ฒ์ฌ๋ฅผ ํตํ ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ ํ์ง
- โ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์ํ CUDA-GDB ๊ธฐ์ด
๋ ๋ฒ์งธ ์ฌ๋ก์์ - ๋ก์ง ๋ฒ๊ทธ ๋๋ฒ๊น :
- โ ๋๋ ทํ ์ฆ์ ์์ด ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ ์กฐ์ฌ
- โ ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ๊ทผ๋ณธ ์์ธ๊น์ง ์ถ์ ํ๋ ํจํด ๋ถ์ ๊ธฐ๋ฒ
- โ ๋ณ์ ๊ฒ์ฌ๊ฐ ์ ๋ ๋ ์คํ ํ๋ฆ ๋๋ฒ๊น
์ธ ๋ฒ์งธ ์ฌ๋ก์์ - ์กฐ์จ ๋๋ฒ๊น :
- โ ์ค๋ ๋ ์กฐ์จ ์คํจ๋ฅผ ์ํ ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ ์กฐ์ฌ
- โ ๊ณ ๊ธ CUDA-GDB ๊ธฐ๋ฒ์ ์ฌ์ฉํ ๋ค์ค ์ค๋ ๋ ์ํ ๋ถ์
- โ ๋ณต์กํ ๋ณ๋ ฌ ํ๋ก๊ทธ๋จ์ ์ํ ๋๊ธฐํ ๊ฒ์ฆ
์ ๋ฌธ๊ฐ์ GPU ๋๋ฒ๊น ๋ฐฉ๋ฒ๋ก
์ค๋ฌด GPU ๊ฐ๋ฐ์๋ค์ด ์ฌ์ฉํ๋ ์ฒด๊ณ์ ์ธ ์ ๊ทผ๋ฒ์ ์ตํ์ต๋๋ค:
- ์ฆ์ ์ฝ๊ธฐ - ํฌ๋์์ธ๊ฐ? ์๋ชป๋ ๊ฒฐ๊ณผ์ธ๊ฐ? ๋ฌดํ ์ ์ง์ธ๊ฐ?
- ๊ฐ์ค ์๋ฆฝ - ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ? ๋ก์ง ์ค๋ฅ? ์กฐ์จ ๋ฌธ์ ?
- ์ฆ๊ฑฐ ์์ง - ๋ฒ๊ทธ ์ ํ์ ๋ง์ถฐ CUDA-GDB๋ฅผ ์ ๋ต์ ์ผ๋ก ํ์ฉ
- ์ฒด๊ณ์ ์ผ๋ก ํ ์คํธ - ๋ชฉํ ์งํฅ์ ์กฐ์ฌ๋ฅผ ํตํด ๊ฐ ๊ฐ์ค ๊ฒ์ฆ
- ๊ทผ๋ณธ ์์ธ ์ถ์ - ์ฆ๊ฑฐ์ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ๋ฅผ ๋ฐ๋ผ ์์ฒ๊น์ง
์ ์ ๋ฌ์ฑ: ์ด์ ๊ฐ์ฅ ํํ ์ธ ๊ฐ์ง GPU ํ๋ก๊ทธ๋๋ฐ ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ํฌ๋์ (์ฒซ ๋ฒ์งธ ์ฌ๋ก) - null ํฌ์ธํฐ, ๋ฒ์ ๋ฐ ์ ๊ทผ
- ๋ก์ง ๋ฒ๊ทธ (๋ ๋ฒ์งธ ์ฌ๋ก) - ์๊ณ ๋ฆฌ์ฆ ์ค๋ฅ, ์๋ชป๋ ๊ฒฐ๊ณผ
- ์กฐ์จ ๊ต์ฐฉ ์ํ (์ธ ๋ฒ์งธ ์ฌ๋ก) - ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ ์คํจ
Puzzle 10: ์๋ํ์ด์ ๋ก ๋ฉ๋ชจ๋ฆฌ ์ค๋ฅ์ ๊ฒฝ์ ์ํ ์ฐพ๊ธฐ
โ ๏ธ ์ด ํผ์ฆ์ ํธํ๋๋ NVIDIA GPU์์๋ง ์๋ํฉ๋๋ค. ๋ค๋ฅธ GPU ๋ฒค๋ ์ง์์ ์ํ ๋๊ตฌ ๊ฐ๋ฐ์ด ์งํ ์ค์ ๋๋ค.
๋ชจ๋ GPU ๊ฐ๋ฐ์๊ฐ ๋๋ ค์ํ๋ ์๊ฐ
์๋ฒฝํด ๋ณด์ด๋ GPU ์ฝ๋๋ฅผ ์์ฑํ์ต๋๋ค. ์๊ณ ๋ฆฌ์ฆ์ ์ ํํ๊ณ , ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ์ฌ๋ฐ๋ฅธ ๊ฒ ๊ฐ๊ณ , ์ค๋ ๋ ์กฐ์จ๋ ํ ์ก์ ๋ฐ ์์ด ๋ณด์ ๋๋ค. ์์ ์๊ฒ ํ ์คํธ๋ฅผ ์คํํ๋ฉดโฆ
- โ ๋ชจ๋ ํ ์คํธ ํต๊ณผ
- โ ์ฑ๋ฅ๋ ํ๋ฅญํจ
- โ ์ถ๋ ฅ์ด ์์ ๊ฒฐ๊ณผ์ ์ผ์น
๋ฟ๋ฏํ๊ฒ ์ฝ๋๋ฅผ ํ๋ก๋์ ์ ๋ฐฐํฌํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ช ์ฃผ ํ, ์ฐ๋ฝ์ด ์ต๋๋ค:
- โํ๋ก๋์ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ํฌ๋์๋์ด์โ
- โ์คํํ ๋๋ง๋ค ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์โ
- โ๋ฉ๋ชจ๋ฆฌ ์์์ด ๊ฐ์ง๋์ด์โ
์กฐ์ฉํ ์จ์ด๋๋ GPU ๋ฒ๊ทธ์ ์ธ๊ณ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค. ๋๊ท๋ชจ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ๊ทธ๋์ ์จ์ด ์๋ค๊ฐ ๊ฐ์ฅ ์์์น ๋ชปํ ์๊ฐ์ ํ์ด๋์ค๋ ์ค๋ฅ๋ค์ด์ฃ . ์ด๋ฐ ๋ฒ๊ทธ๋ค์ ๋ชจ๋ ํ ์คํธ๋ฅผ ํต๊ณผํ๊ณ , 99%์ ๊ฒฝ์ฐ ์ฌ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ค๊ฐ, ๊ฐ์ฅ ์ค์ํ ์๊ฐ์ ์น๋ช ์ ์ผ๋ก ์คํจํฉ๋๋ค.
์ค์: ์ด ํผ์ฆ์ NVIDIA GPU ํ๋์จ์ด๊ฐ ํ์ํ๋ฉฐ, compute-sanitizer๊ฐ NVIDIA
CUDA toolkit์ ํฌํจ๋์ด ์์ด pixi๋ฅผ ํตํด์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
GPU ๋ฒ๊ทธ๊ฐ ์ ๋ํ ๊ตํํ ์ด์
CPU ํ๋ก๊ทธ๋จ์์๋ ๋ฒ๊ทธ๊ฐ ๋ณดํต ์ฆ๊ฐ์ ์ธ ํฌ๋์๋ ์๋ชป๋ ๊ฒฐ๊ณผ๋ก ์์ ์ ์กด์ฌ๋ฅผ ์๋ฆฝ๋๋ค. ํ์ง๋ง GPU ๋ฒ๊ทธ๋ ์จ๊ธฐ์ ๋ฌ์ธ์ ๋๋ค:
์กฐ์ฉํ ๋ฐ์ดํฐ๋ฅผ ์์์ํค๋ ํจํด:
- ํฌ๋์ ์๋ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ: ์ฐ์ฐํ ์ ํจํ ๋ฉ๋ชจ๋ฆฌ ์์น๋ฅผ ๊ฑด๋๋ฆฌ๋ ๋ฒ์ ์ด๊ณผ ์ ๊ทผ
- โ๋๋ถ๋ถ์ ์ ๋์ํ๋โ ๊ฒฝ์ ์ํ: ํ์ด๋ฐ์ ๋ฐ๋ผ ๋ฌด์์์ฒ๋ผ ๋ํ๋๋ ๋ฒ๊ทธ
- ์ค๋ ๋ ์กฐ์จ ์คํจ: ํน์ ๋ถํ ์กฐ๊ฑด์์๋ง ๋ฐ์ํ๋ ๊ต์ฐฉ ์ํ
๋๊ท๋ชจ ๋ณ๋ ฌ ์ฒ๋ฆฌ์์ ์ฆํญ๋๋ ๋ฌธ์ :
- ํ ์ค๋ ๋์ ๋ฒ๊ทธ๊ฐ ์์ฒ ๊ฐ์ ์ํฅ: ๋ฉ๋ชจ๋ฆฌ ์๋ฐ ํ๋๊ฐ ์ ์ฒด ์ํ๋ฅผ ์์์ํฌ ์ ์์
- ๊ฒฝ์ ์ํ์ ๊ธฐํ๊ธ์์ ์ฆ๊ฐ: ์ค๋ ๋๊ฐ ๋ง์์๋ก ์์ ๊ฐ๋ฅ์ฑ๋ ์ปค์ง
- ํ๋์จ์ด ์ฐจ์ด๊ฐ ๋ฌธ์ ๋ฅผ ์ํ: ๊ฐ์ ๋ฒ๊ทธ๊ฐ GPU ์ํคํ ์ฒ๋ง๋ค ๋ค๋ฅด๊ฒ ๋์
ํ์ง๋ง ํฌ์์์ด ์์ต๋๋ค: GPU ๊ฒ์ฌ ๋๊ตฌ๋ฅผ ์ตํ๋ฉด, ์ด๋ ๊ฒ ์ฐพ๊ธฐ ์ด๋ ค์ด ๋ฒ๊ทธ๋ค์ ํ๋ก๋์ ์ ๋๋ฌํ๊ธฐ ์ ์ ์ก์๋ผ ์ ์์ต๋๋ค.
์๋ํ์ด์ ๋๊ตฌ ๋ชจ์: NVIDIA compute-sanitizer
NVIDIA compute-sanitizer๋ GPU ๋ฒ๊ทธ์ ๋ง์ ์ธ์ฐ๋ ์ฌ๋ฌ๋ถ์ ๋น๋ฐ ๋ฌด๊ธฐ์ ๋๋ค. ๋ค์์ ํ์งํ ์ ์์ต๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ์๋ฐ: ๋ฒ์ ์ด๊ณผ ์ ๊ทผ, ์๋ชป๋ ํฌ์ธํฐ, ๋ฉ๋ชจ๋ฆฌ ๋์
- ๊ฒฝ์ ์ํ: ์ค๋ ๋ ๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ hazard
- ๋๊ธฐํ ๋ฒ๊ทธ: ๊ต์ฐฉ ์ํ, barrier ์ค์ฉ, ๋ถ์ ์ ํ ์ค๋ ๋ ์กฐ์จ
- ๊ทธ ์ธ:
pixi run compute-sanitizer --help๋ก ํ์ธ
๐ ๊ณต์ ๋ฌธ์: NVIDIA Compute Sanitizer User Guide
GPU ํ๋ก๊ทธ๋จ์ X-ray๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค. ์ผ๋ฐ ํ ์คํธ๋ก๋ ๋ณผ ์ ์๋ ์จ๊ฒจ์ง ๋ฌธ์ ๊น์ง ๋๋ฌ๋ด ์ค๋๋ค.
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ
์ด ํผ์ฆ์์๋ ๊ฐ์ฅ ์ฐพ๊ธฐ ์ด๋ ค์ด GPU ๋ฒ๊ทธ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ฐพ์ ์์ ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. ์ ๋ฅํ GPU ๊ฐ๋ฐ์์ ๋ฐ์ด๋ ๊ฐ๋ฐ์๋ฅผ ๊ตฌ๋ถ ์ง๋ ํ์ ๊ธฐ์ ์ ์ตํ๊ฒ ๋ฉ๋๋ค.
์ตํ๊ฒ ๋ ํต์ฌ ๊ธฐ์
- ์จ์ ๋ฒ๊ทธ ์ฐพ๊ธฐ - ํ ์คํธ๋ก๋ ์กํ์ง ์๋ ๋ฌธ์ ๋ฐ๊ฒฌ
- ๋ฉ๋ชจ๋ฆฌ ์์ ์กฐ์ฌ - ํผํด๊ฐ ๋ฐ์ํ๊ธฐ ์ ์ ๋ฏธ์ ์ ๋์ ์ถ์
- ๊ฒฝ์ ์ํ ํ์ง - ๋์์ฑ ์ํ ์์๋ฅผ ์ฐพ์๋ด๊ณ ์ ๊ฑฐ
- ๋๊ตฌ ์ ํ ๋ฅ๋ ฅ - ์ํฉ์ ๋ง๋ ์๋ํ์ด์ ์ ํ
- ํ๋ก๋์ ๋๋ฒ๊น ์์ ๊ฐ - ์ฌ์ฉ์์๊ฒ ๋๋ฌํ๊ธฐ ์ ์ ๋ฒ๊ทธ ํฌ์ฐฉ
์ค์ ๋ฒ๊ทธ ์ฌ๋ฅ ์๋๋ฆฌ์ค
๊ฐ์ฅ ์ํํ ๋ ์ข ๋ฅ์ GPU ๋ฒ๊ทธ๋ฅผ ์กฐ์ฌํฉ๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ์๋ฐ - ๊ฒฝ๊ณ ์์ด ๋ฐ์ดํฐ๋ฅผ ๋ง๊ฐ๋จ๋ฆฌ๋ ์กฐ์ฉํ ์์ด์
- ๊ฒฝ์ ์ํ - ๊ฒฐ๊ณผ๋ฅผ ์์ธก ๋ถ๊ฐ๋ฅํ๊ฒ ๋ง๋๋ ํผ๋์ ์จ์
๊ฐ ์๋๋ฆฌ์ค์์ ์ผ๋ฐ ํ ์คํธ๋ก๋ ๋ณด์ด์ง ์๋ ๋จ์๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉฐ, GPU ๋ฒ๊ทธ ํ์ ์ฒ๋ผ ์ฌ๊ณ ํ๋ ๋ฒ์ ๋ฐฐ์๋๋ค.
๋ฒ๊ทธ ์ฌ๋ฅ ์ฌ์
์ด ํผ์ฆ์ ์กฐ์ฉํ ์์์ ๋ฐ๊ฒฌํ๋ ๊ฒ๋ถํฐ ๋ณ๋ ฌ ๋๋ฒ๊น ์ ๋ฐฐ์ฐ๋ ๊ฒ๊น์ง, ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ๋ ๊ณผ์ ์ ์๋ดํฉ๋๋ค:
๐ฎ๐ผโโ๏ธ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ ํ์ง
๋ฉ๋ชจ๋ฆฌ ์๋ฐ ์กฐ์ฌ - ํ ์คํธ๋ ํต๊ณผํด๋ ๋ฉ๋ชจ๋ฆฌ๋ ๊ฑฐ์ง๋ง์ ํ ๋
- ํ ์คํธ๋ฅผ ํต๊ณผํ๋ฉด์๋ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์ฃ๋ฅผ ์ ์ง๋ฅด๋ ํ๋ก๊ทธ๋จ ์กฐ์ฌ
- ๋ฏธ์ ์ ๋์(UB)์ ์งํ๋ฅผ ์์๋ณด๋ ๋ฒ ์ตํ๊ธฐ
memcheckํ์ต - ๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ ์ก์๋ด๋ ํ์ง๊ธฐ- GPU ํ๋์จ์ด๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฅ๋ฅผ ์จ๊ธฐ๋ ์ด์ ์ดํด
- ์ฒด๊ณ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๊ฒ์ฆ ์ค์ต
๋ชฉํ: ๋ฐฉ์นํ๋ฉด ํ๋ก๋์ ๊น์ง ๋ฐ๊ฒฌ๋์ง ์์์ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ ํ์ง ๋ฅ๋ ฅ
๐ ๊ฒฝ์ ์ํ ๋๋ฒ๊น
๋์์ฑ ๋ฒ๊ทธ ์กฐ์ฌ - ์ค๋ ๋๋ค์ด ์๋ก ๋ฐ๋ชฉ์ ์ก์ ๋
- ์ค๋ ๋ ํ์ด๋ฐ ๋๋ฌธ์ ๋ฌด์์๋ก ์คํจํ๋ ํ๋ก๊ทธ๋จ ์กฐ์ฌ
- ๋ฐ์ดํฐ๊ฐ ์์๋๊ธฐ ์ ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํ ์์ ์๋ณ๋ฒ ์ตํ๊ธฐ
racecheckํ์ต - ๊ฒฝ์ ์ํ๋ฅผ ์ก์๋ด๋ ํ์ง๊ธฐ- ๋ค์ํ ๋์์ฑ ๋ฒ๊ทธ์ ๋ํด
racecheckvssynccheck๋น๊ต - ์ค๋ ๋ ๋๊ธฐํ ์ ๋ต ์ค์ต
๋ชฉํ: ๊ณ ๊ธ ๋์์ฑ ๋๋ฒ๊น - ์์ฒ ๊ฐ์ ๋ณ๋ ฌ ์ค๋ ๋๋ฅผ ๊ธธ๋ค์ด๋ ๋ฅ๋ ฅ
GPU ํ์ ๋ง์ธ๋์
GPU ๊ฒ์ฌ๋ฅผ ํ๋ ค๋ฉด ๋ณ๋ ฌ ํ๋ก๊ทธ๋จ ํ์ ์ด ๋์ด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์ฌ๊ฑด์ ์กฐ์ฌํ๊ฒ ๋ฉ๋๋ค:
- ์ฆ๊ฑฐ๊ฐ ์จ๊ฒจ์ ธ ์๋ค - ์ง์ ๊ด์ฐฐํ ์ ์๋ ๋ณ๋ ฌ ์คํ ์์์ ๋ฒ๊ทธ๊ฐ ๋ฐ์
- ์ฉ์์๊ฐ ์์์ด ๋ง๋ค - ์์ฒ ๊ฐ์ ์ค๋ ๋ ์ค ์ด๋ค ์กฐํฉ์ด๋ ๋ฒ์ธ์ผ ์ ์์
- ๋ฒํ์ด ๊ฐํ์ ์ด๋ค - ๊ฒฝ์ ์ํ์ ํ์ด๋ฐ์ ๋ฐ๋ฅธ ์คํจ
- ์ ๋ฌธ ๋๊ตฌ๊ฐ ํ์ํ๋ค - ์ผ๋ฐ ๋๋ฒ๊น ์ผ๋ก๋ ๋ณผ ์ ์๋ ๊ฒ์ ์๋ํ์ด์ ๊ฐ ๋ณด์ฌ์ค
ํ์ง๋ง ํ๋ฅญํ ํ์ ์ฒ๋ผ, ์ฌ๋ฌ๋ถ๋ ๋ค์์ ๋ฐฐ์ฐ๊ฒ ๋ฉ๋๋ค:
- ๋ณด์ด์ง ์๋ ๋จ์ ๋ฐ๋ผ๊ฐ๊ธฐ - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด, ์ค๋ ๋ ํ์ด๋ฐ, ๋๊ธฐํ ์ง์
- ๋ณ๋ ฌ์ ์ผ๋ก ์ฌ๊ณ ํ๊ธฐ - ์์ฒ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์์ ์ด๋ป๊ฒ ์ํธ์์ฉํ๋์ง ๊ณ ๋ ค
- ๋ฏธ๋์ ๋ฒ์ฃ ์๋ฐฉํ๊ธฐ - ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ๊ฒ์ฌ ๋๊ตฌ ํตํฉ
- ๋๊ตฌ ๋ฏฟ๊ธฐ - ์๋ ํ ์คํธ๋ก๋ ๋๋ฌ๋ผ ์ ์๋ ๊ฒ์ ์๋ํ์ด์ ์ ๋งก๊ธฐ๊ธฐ
์์ํ๊ธฐ ์ ์
์์์ผ ํ ๊ฒ:
- Puzzle 1-8์์ ๋ค๋ฃฌ GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ (๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ์ค๋ ๋ ์กฐ์จ, ๋ฐฐ๋ฆฌ์ด)
- ํธํ NVIDIA GPU ํ๋์จ์ด
compute-sanitizer์ ๊ทผ์ ์ํpixiํจํค์ง ๋งค๋์ ํ๊ฒฝ ์ค์ - ์ ํ ํผ์ฆ: Puzzle 4์ Puzzle 8 ์์ง ๊ถ์ฅ
๋ชฉํ:
- ์ ๋ฌธ GPU ๊ฐ๋ฐํ์์ ์ฌ์ฉํ๋ ํ๋ก๋์ ๊ธ ๋๋ฒ๊น ๊ธฐ์
- ๋น์ฉ์ด ํฐ ํ๋ก๋์ ์ฅ์ ๋ฅผ ์๋ฐฉํ๋ ์จ์ ๋ฒ๊ทธ ํ์ง ๊ธฐ์
- ๊ฐ์ฅ ๊น๋ค๋ก์ด ๋์์ฑ ์๋๋ฆฌ์ค์์๋ ๋ณ๋ ฌ ๋๋ฒ๊น ์์ ๊ฐ
- GPU ํ๋ก๊ทธ๋๋ฐ ์ปค๋ฆฌ์ด ์ ๋ฐ์ ๋์์ด ๋ ๋๊ตฌ ์ ๋ฌธ์ฑ
๐ฎ๐ผโโ๏ธ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ ํ์ง
๊ฐ์
ํ
์คํธ๊ฐ ํต๊ณผํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ฌ๋ GPU ํ๋ก๊ทธ๋จ์ ์กฐ์ฉํ ์์์ํฌ ์ ์๋ ๋ฉ๋ชจ๋ฆฌ
์๋ฐ์ ํ์งํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. NVIDIA์ compute-sanitizer(pixi๋ฅผ ํตํด ์ฌ์ฉ
๊ฐ๋ฅ)์ memcheck ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ, GPU ์ฝ๋์์ ์์ธก ๋ถ๊ฐ๋ฅํ ๋์์ ์ผ์ผํฌ ์
์๋ ์จ์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ๋ฅผ ๋ฐ๊ฒฌํ๊ฒ ๋ฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ํ๋ก๊ทธ๋จ์ ๋ถ๋ฒ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ํํ๋ฉด์๋ ๋์์ โ์ฌ๋ฐ๋ฅธโ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์ด๋ผ ์ ์์ต๋๋ค.
์ ํ ํ์ต: Puzzle 4 TileTensor์ ๊ธฐ๋ณธ์ ์ธ GPU ๋ฉ๋ชจ๋ฆฌ ๊ฐ๋ ์ ๋ํ ์ดํด๊ฐ ํ์ํฉ๋๋ค.
์กฐ์ฉํ ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ์ ๋ฐ๊ฒฌ
ํ ์คํธ๋ ํต๊ณผํ์ง๋ง, ์ฝ๋๊ฐ ์ ๋ง ์ฌ๋ฐ๋ฅธ ๊ฑธ๊น?
์ผํ ๋ฌดํดํด ๋ณด์ด๊ณ ์๋ฒฝํ๊ฒ ๋์ํ๋ ๋ฏํ ํ๋ก๊ทธ๋จ์ผ๋ก ์์ํด ๋ด ์๋ค (๊ฐ๋๊ฐ ์๋ Puzzle 04์ ๋๋ค):
def add_10_2d(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
output[row, col] = a[row, col] + 10.0
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p10/p10.mojo
์ด ํ๋ก๊ทธ๋จ์ ์ผ๋ฐ์ ์ผ๋ก ์คํํ๋ฉด, ๋ชจ๋ ๊ฒ์ด ์ ์์ผ๋ก ๋ณด์ ๋๋ค:
pixi run p10 --memory-bug
out shape: 2 x 2
Running memory bug example (bounds checking issue)...
out: HostBuffer([10.0, 11.0, 12.0, 13.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
โ
Memory test PASSED! (memcheck may find bounds violations)
โ ํ ์คํธ ํต๊ณผ! ์ถ๋ ฅ์ด ์์ ๊ฒฐ๊ณผ์ ์๋ฒฝํ๊ฒ ์ผ์นํฉ๋๋ค. ์ฌ๊ฑด ์ข ๊ฒฐ, ๋ง์ฃ ?
์๋๋๋ค! compute-sanitizer๊ฐ ๋ฌด์์ ๋ณด์ฌ์ฃผ๋์ง ๋ด
์๋ค:
MODULAR_DEVICE_CONTEXT_MEMORY_MANAGER_SIZE_PERCENT=0 pixi run compute-sanitizer --tool memcheck mojo problems/p10/p10.mojo --memory-bug
์ฐธ๊ณ : MODULAR_DEVICE_CONTEXT_MEMORY_MANAGER_SIZE_PERCENT=0์ ๋๋ฐ์ด์ค
์ปจํ
์คํธ์ ๋ฒํผ ์บ์๋ฅผ ๋นํ์ฑํํ๋ ๋ช
๋ น์ค ํ๊ฒฝ ๋ณ์ ์ค์ ์
๋๋ค. ์ด ์ค์ ์
์ผ๋ฐ์ ์ธ ์บ์ฑ ๋์์ ์ํด ์จ๊ฒจ์ง๋ ๊ฒฝ๊ณ ์๋ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ๋๋ฌ๋ผ ์
์์ต๋๋ค. (์ญ์ฃผ: ๋ฒํผ ์บ์๊ฐ ํ์ฑํ๋๋ฉด ํด์ ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฆ์ ๋ฐํํ์ง ์๊ณ
์ฌ์ฌ์ฉ์ ์ํด ๋ณด๊ดํฉ๋๋ค. ์ด ๋๋ฌธ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ์ด ์์ง ์ ํจํ ์บ์ ์์ญ์
๋ฟ์ ์ค๋ฅ๊ฐ ๋๋ฌ๋์ง ์์ ์ ์์ต๋๋ค. ๋นํ์ฑํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฆ์ ๋ฐํ๋์ด
์๋ฐ์ด ๊ฐ์ง๋ฉ๋๋ค.)
========= COMPUTE-SANITIZER
out shape: 2 x 2
Running memory bug example (bounds checking issue)...
========= Invalid __global__ read of size 4 bytes
========= at p10_add_10_2d_...+0x80
========= by thread (2,1,0) in block (0,0,0)
========= Access at 0xe0c000210 is out of bounds
========= and is 513 bytes after the nearest allocation at 0xe0c000000 of size 16 bytes
========= Invalid __global__ read of size 4 bytes
========= at p10_add_10_2d_...+0x80
========= by thread (0,2,0) in block (0,0,0)
========= Access at 0xe0c000210 is out of bounds
========= and is 513 bytes after the nearest allocation at 0xe0c000000 of size 16 bytes
========= Invalid __global__ read of size 4 bytes
========= at p10_add_10_2d_...+0x80
========= by thread (1,2,0) in block (0,0,0)
========= Access at 0xe0c000214 is out of bounds
========= and is 517 bytes after the nearest allocation at 0xe0c000000 of size 16 bytes
========= Invalid __global__ read of size 4 bytes
========= at p10_add_10_2d_...+0x80
========= by thread (2,2,0) in block (0,0,0)
========= Access at 0xe0c000218 is out of bounds
========= and is 521 bytes after the nearest allocation at 0xe0c000000 of size 16 bytes
========= Program hit CUDA_ERROR_LAUNCH_FAILED (error 719) due to "unspecified launch failure" on CUDA API call to cuStreamSynchronize.
========= Program hit CUDA_ERROR_LAUNCH_FAILED (error 719) due to "unspecified launch failure" on CUDA API call to cuEventCreate.
========= Program hit CUDA_ERROR_LAUNCH_FAILED (error 719) due to "unspecified launch failure" on CUDA API call to cuMemFreeAsync.
========= ERROR SUMMARY: 7 errors
๋ชจ๋ ํ ์คํธ๋ฅผ ํต๊ณผํ์์๋ ํ๋ก๊ทธ๋จ์๋ ์ด 7๊ฐ์ ์ค๋ฅ๊ฐ ์์ต๋๋ค:
- 4๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ (
Invalid __global__ read) - 3๊ฐ์ ๋ฐํ์ ์ค๋ฅ (๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ผ๋ก ์ธํด ๋ฐ์)
์จ๊ฒจ์ง ๋ฒ๊ทธ ์ดํดํ๊ธฐ
๊ทผ๋ณธ ์์ธ ๋ถ์
๋ฌธ์ :
- ํ ์ ํฌ๊ธฐ: 2ร2 (์ ํจํ ์ธ๋ฑ์ค: 0, 1)
- ์ค๋ ๋ ๊ทธ๋ฆฌ๋: 3ร3 (์ค๋ ๋ ์ธ๋ฑ์ค: 0, 1, 2)
- ๋ฒ์ ์ด๊ณผ ์ค๋ ๋:
(2,1),(0,2),(1,2),(2,2)๊ฐ ์๋ชป๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผ - ๊ฒฝ๊ณ ๊ฒ์ฌ ๋๋ฝ: ํ
์ ์ฐจ์์ ๋ํ
thread_idx๊ฒ์ฆ์ด ์์
7๊ฐ ์ค๋ฅ ์ ์ฒด ์ดํดํ๊ธฐ
4๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ:
- ๊ฐ ๋ฒ์ ์ด๊ณผ ์ค๋ ๋
(2,1),(0,2),(1,2),(2,2)๊ฐInvalid __global__ read๋ฅผ ๋ฐ์์ํด
3๊ฐ์ CUDA ๋ฐํ์ ์ค๋ฅ:
- ์ปค๋ ์คํ ์คํจ๋ก ์ธํด
cuStreamSynchronize์คํจ - ์ ๋ฆฌ ๊ณผ์ ์์
cuEventCreate์คํจ - ๋ฉ๋ชจ๋ฆฌ ํด์ ๊ณผ์ ์์
cuMemFreeAsync์คํจ
ํต์ฌ ํต์ฐฐ: ๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ ์ฐ์ ํจ๊ณผ๋ฅผ ์ผ์ผํต๋๋ค - ํ๋์ ์๋ชป๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ฌ๋ฌ ํ์ CUDA API ์คํจ๋ฅผ ์ผ๊ธฐํฉ๋๋ค.
๊ทธ๋ผ์๋ ํ ์คํธ๊ฐ ํต๊ณผํ ์ด์ :
- ์ ํจํ ์ค๋ ๋
(0,0),(0,1),(1,0),(1,1)์ด ์ฌ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํจ - ํ ์คํธ๊ฐ ์ ํจํ ์ถ๋ ฅ ์์น๋ง ๊ฒ์ฌํจ
- ๋ฒ์ ์ด๊ณผ ์ ๊ทผ์ด ํ๋ก๊ทธ๋จ์ ์ฆ์ ํฌ๋์์ํค์ง ์์
๋ฏธ์ ์ ๋์ ์ดํดํ๊ธฐ
๋ฏธ์ ์ ๋์์ด๋?
๋ฏธ์ ์ ๋์(Undefined Behavior, UB) ์ ํ๋ก๊ทธ๋จ์ด ์ธ์ด ๋ช ์ธ์ ์ ์๋์ง ์์ ์ฐ์ฐ์ ์ํํ ๋ ๋ฐ์ํฉ๋๋ค. ๋ฒ์ ์ด๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ๋ํ์ ์ธ ์์ ๋๋ค.
๋ฏธ์ ์ ๋์์ ์ฃผ์ ํน์ฑ:
- ํ๋ก๊ทธ๋จ์ด ๋ง ๊ทธ๋๋ก ๋ฌด์จ ์ง์ด๋ ํ ์ ์์: ํฌ๋์, ์๋ชป๋ ๊ฒฐ๊ณผ, ์ ์ ๋์ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ธฐ, ๋ฉ๋ชจ๋ฆฌ ์์
- ์ด๋ค ๋ณด์ฅ๋ ์์: ์ปดํ์ผ๋ฌ, ํ๋์จ์ด, ๋๋ผ์ด๋ฒ, ์ฌ์ง์ด ์คํํ ๋๋ง๋ค ๋์์ด ๋ฌ๋ผ์ง ์ ์์
๋ฏธ์ ์ ๋์์ด ํนํ ์ํํ ์ด์
์ ํ์ฑ ๋ฌธ์ :
- ์์ธก ๋ถ๊ฐ๋ฅํ ๊ฒฐ๊ณผ: ํ ์คํธ ์ค์๋ ๋์ํ๋ค๊ฐ ํ๋ก๋์ ์์ ์คํจํ ์ ์์
- ๋น๊ฒฐ์ ์ ๋์: ๊ฐ์ ์ฝ๋๊ฐ ๋ค๋ฅธ ์คํ์์ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ผ ์ ์์
- ์กฐ์ฉํ ์์: ๋ฏธ์ ์ ๋์์ ๊ฐ์์ ์ธ ์ค๋ฅ ์์ด ๋ฐ์ดํฐ๋ฅผ ์์์ํฌ ์ ์์
- ์ปดํ์ผ๋ฌ ์ต์ ํ: ์ปดํ์ผ๋ฌ๋ ๋ฏธ์ ์ ๋์์ด ์๋ค๊ณ ๊ฐ์ ํ๊ณ ์์์น ๋ชปํ ๋ฐฉ์์ผ๋ก ์ต์ ํํ ์ ์์
๋ณด์ ์ทจ์ฝ์ :
- ๋ฒํผ ์ค๋ฒํ๋ก์ฐ: ์์คํ ํ๋ก๊ทธ๋๋ฐ์์ ๋ณด์ ๊ณต๊ฒฉ์ ๊ณ ์ ์ ์ธ ์์ธ
- ๋ฉ๋ชจ๋ฆฌ ์์: ๊ถํ ์์น์ด๋ ์ฝ๋ ์ธ์ ์ ๊ณต๊ฒฉ์ผ๋ก ์ด์ด์ง ์ ์์
- ์ ๋ณด ์ ์ถ: ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ฝ๊ธฐ๋ก ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๊ฐ ๋ ธ์ถ๋ ์ ์์
- ์ ์ด ํ๋ฆ ํ์ด์ฌํน: ๋ฏธ์ ์ ๋์์ ์ ์ฉํด ํ๋ก๊ทธ๋จ ์คํ ํ๋ฆ์ ํ์ทจํ ์ ์์
GPU ํน์ ์ ๋ฏธ์ ์ ๋์ ์ํ์ฑ
๋๊ท๋ชจ ์ํฅ:
- ์ค๋ ๋ ๋ถ๊ธฐ: ํ ์ค๋ ๋์ ๋ฏธ์ ์ ๋์์ด ์ ์ฒด ์ํ(32๊ฐ ์ค๋ ๋)์ ์ํฅ์ ์ค ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ๋ฒ์ ์ด๊ณผ ์ ๊ทผ์ด ์ธ์ ์ค๋ ๋์ ๋ฐ์ดํฐ๋ฅผ ์์์ํฌ ์ ์์
- ์ปค๋ ์คํจ: ๋ฏธ์ ์ ๋์์ด GPU ์ปค๋ ์ ์ฒด๋ฅผ ์์ ํ ๋ง๊ฐ๋จ๋ฆด ์ ์์
ํ๋์จ์ด ์ฐจ์ด:
- ๋ค๋ฅธ GPU ์ํคํ ์ฒ: ๋ฏธ์ ์ ๋์์ด ๋ค๋ฅธ GPU ๋ชจ๋ธ์์ ๋ค๋ฅด๊ฒ ๋ํ๋ ์ ์์
- ๋๋ผ์ด๋ฒ ์ฐจ์ด: ๊ฐ์ ๋ฏธ์ ์ ๋์์ด ๋๋ผ์ด๋ฒ ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์ํ ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ๋ณ๊ฒฝ: GPU ๋ฉ๋ชจ๋ฆฌ ํ ๋น ํจํด์ ๋ฐ๋ผ ๋ฏธ์ ์ ๋์์ด ๋ค๋ฅด๊ฒ ๋ํ๋ ์ ์์
๋ฉ๋ชจ๋ฆฌ ์๋ฐ ์์ ํ๊ธฐ
ํด๊ฒฐ์ฑ
Puzzle 04์์ ๋ณธ ๊ฒ์ฒ๋ผ, ๋ค์๊ณผ ๊ฐ์ด ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํด์ผ ํฉ๋๋ค:
def add_10_2d(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
if col < size and row < size:
output[row, col] = a[row, col] + 10.0
ํด๊ฒฐ์ฑ ์ ๊ฐ๋จํฉ๋๋ค: ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๊ธฐ ์ ์ ํญ์ ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๋ฐ์ดํฐ ์ฐจ์์ ๋ํด ๊ฒ์ฆํ์ธ์.
compute-sanitizer๋ก ๊ฒ์ฆ
# p10.mojo ๋ณต์ฌ๋ณธ์์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์์ ํ ํ ์คํ:
MODULAR_DEVICE_CONTEXT_MEMORY_MANAGER_SIZE_PERCENT=0 pixi run compute-sanitizer --tool memcheck mojo problems/p10/p10.mojo --memory-bug
========= COMPUTE-SANITIZER
out shape: 2 x 2
Running memory bug example (bounds checking issue)...
out: HostBuffer([10.0, 11.0, 12.0, 13.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])
โ
Memory test PASSED! (memcheck may find bounds violations)
========= ERROR SUMMARY: 0 errors
โ ์ฑ๊ณต: ๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ด ํ์ง๋์ง ์์์ต๋๋ค!
ํต์ฌ ํ์ต ํฌ์ธํธ
์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ์ค์ํ ์ด์
- ๋ช ํ์ฑ: ์ฝ๋์์ ์์ ์๊ตฌ์ฌํญ์ ๋ช ์์ ์ผ๋ก ํํ
- ์ ์ด: ๋ฒ์ ์ด๊ณผ ์ผ์ด์ค์์ ์ ํํ ์ด๋ค ์ผ์ด ์ผ์ด๋ ์ง ์ง์ ๊ฒฐ์
- ๋๋ฒ๊น : ๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ด ๋ฐ์ํ ๋ ์ถ๋ก ํ๊ธฐ ์ฌ์
GPU ๋ฉ๋ชจ๋ฆฌ ์์ ๊ท์น
- ํญ์ ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๊ฒ์ฆํ์ฌ ๋ฐ์ดํฐ ์ฐจ์๊ณผ ๋น๊ต
- ๋ฏธ์ ์ ๋์์ ์ด๋ค ๋๊ฐ๋ฅผ ์น๋ฅด๋๋ผ๋ ํผํ๊ธฐ - ๋ฒ์ ์ด๊ณผ ์ ๊ทผ์ ๋ฏธ์ ์ ๋์์ด๋ฉฐ ๋ชจ๋ ๊ฒ์ ๋ง๊ฐ๋จ๋ฆด ์ ์์
- ๊ฐ๋ฐ๊ณผ ํ ์คํธ ์ค compute-sanitizer ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ๊ฒ์ฌ ์์ด โ๋์ํ๋คโ๊ณ ์ ๋ ๊ฐ์ ํ์ง ์๊ธฐ
- ๋ค์ํ ๊ทธ๋ฆฌ๋/๋ธ๋ก ๊ตฌ์ฑ์ผ๋ก ํ ์คํธํ์ฌ ์ผ๊ด์ฑ ์์ด ๋ํ๋๋ ๋ฏธ์ ์ ๋์ ํฌ์ฐฉ
compute-sanitizer ๋ชจ๋ฒ ์ฌ๋ก
MODULAR_DEVICE_CONTEXT_MEMORY_MANAGER_SIZE_PERCENT=0 pixi run compute-sanitizer --tool memcheck mojo your_code.mojo
์ฐธ๊ณ : ์๋ํ์ด์ ์ถ๋ ฅ์์ Mojo ๋ฐํ์ ๊ฒฝ๊ณ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ค์ ๋ฉ๋ชจ๋ฆฌ
์๋ฐ์ ํ์ธํ๋ ค๋ฉด ========= Invalid์ ========= ERROR SUMMARY ๋ผ์ธ์
์ง์คํ์ธ์.
๐ ๊ฒฝ์ ์ํ ๋๋ฒ๊น
๊ฐ์
NVIDIA compute-sanitizer๋ฅผ ์ฌ์ฉํด ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ์ผ์ผํค๋ ๊ฒฝ์ ์ํ๋ฅผ
์๋ณํ๋ฉด์ ์คํจํ๋ GPU ํ๋ก๊ทธ๋จ์ ๋๋ฒ๊น
ํฉ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์์ ๋์์ฑ
๋ฒ๊ทธ๋ฅผ ์ฐพ๋ racecheck ๋๊ตฌ ์ฌ์ฉ๋ฒ์ ๋ฐฐ์๋๋ค.
๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ์ฌ๋ฌ ์ค๋ ๋์ ๊ฐ์ ๋์ ํด์ผ ํ๋ GPU ์ปค๋์ด ์์ต๋๋ค. ํ ์คํธ๋ ์คํจํ๋๋ฐ, ๋ก์ง์ ์ฌ๋ฐ๋ฅธ ๊ฒ ๊ฐ์ต๋๋ค. ๋น์ ์ ๊ณผ์ ๋ ์คํจ๋ฅผ ์ผ์ผํค๋ ๊ฒฝ์ ์ํ๋ฅผ ์ฐพ์ ์์ ํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3) # 9๊ฐ ์ค๋ ๋ ์ค 4๊ฐ๋ง ํ์ฑํ
comptime dtype = DType.float32
์คํจํ๋ ์ปค๋
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
comptime layout = row_major[SIZE, SIZE]()
comptime LayoutType = type_of(layout)
def shared_memory_race(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var row = thread_idx.y
var col = thread_idx.x
var shared_sum = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[1]())
if row < size and col < size:
shared_sum[0] += a[row, col]
barrier()
if row < size and col < size:
output[row, col] = shared_sum[0]
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p10/p10.mojo
์ฝ๋ ์คํ
pixi run p10 --race-condition
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
out shape: 2 x 2
Running race condition example...
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([6.0, 6.0, 6.0, 6.0])
stack trace was not collected. Enable stack trace collection with environment variable `MOJO_ENABLE_STACK_TRACE_ON_ERROR`
Unhandled exception caught during execution: At /home/ubuntu/workspace/mojo-gpu-puzzles/problems/p10/p10.mojo:122:33: AssertionError: `left == right` comparison failed:
left: 0.0
right: 6.0
compute-sanitizer๊ฐ GPU ์ฝ๋์ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ์ฐพ์๋ด๋์ง ์ดํด๋ด
์๋ค.
compute-sanitizer๋ก ๋๋ฒ๊น
ํ๊ธฐ
1๋จ๊ณ: racecheck๋ก ๊ฒฝ์ ์ํ ์๋ณ
compute-sanitizer์ racecheck ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒฝ์ ์ํ๋ฅผ ์๋ณํฉ๋๋ค:
pixi run compute-sanitizer --tool racecheck mojo problems/p10/p10.mojo --race-condition
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
========= COMPUTE-SANITIZER
out shape: 2 x 2
Running race condition example...
========= Error: Race reported between Write access at p10_shared_memory_race_...+0x140
========= and Read access at p10_shared_memory_race_...+0xe0 [4 hazards]
========= and Write access at p10_shared_memory_race_...+0x140 [5 hazards]
=========
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([6.0, 6.0, 6.0, 6.0])
AssertionError: `left == right` comparison failed:
left: 0.0
right: 6.0
========= RACECHECK SUMMARY: 1 hazard displayed (1 error, 0 warnings)
๋ถ์: ํ๋ก๊ทธ๋จ์ 1๊ฐ์ ๊ฒฝ์ ์ํ์ 9๊ฐ์ ๊ฐ๋ณ ์ํ ์์๊ฐ ์์ต๋๋ค:
- 4๊ฐ์ read-after-write ์ํ ์์ (๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ฐ๋ ๋์ ์ฝ๊ธฐ)
- 5๊ฐ์ write-after-write ์ํ ์์ (์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ฐ๊ธฐ)
2๋จ๊ณ: synccheck์ ๋น๊ต
๋๊ธฐํ ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ ์ํ์ธ์ง ํ์ธํฉ๋๋ค:
pixi run compute-sanitizer --tool synccheck mojo problems/p10/p10.mojo --race-condition
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
========= COMPUTE-SANITIZER
out shape: 2 x 2
Running race condition example...
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([6.0, 6.0, 6.0, 6.0])
AssertionError: `left == right` comparison failed:
left: 0.0
right: 6.0
========= ERROR SUMMARY: 0 errors
ํต์ฌ ํต์ฐฐ: synccheck๊ฐ 0๊ฐ์ ์ค๋ฅ๋ฅผ ์ฐพ์์ต๋๋ค - ๊ต์ฐฉ ์ํ ๊ฐ์ ๋๊ธฐํ
๋ฌธ์ ๋ ์์ต๋๋ค. ๋ฌธ์ ๋ ๋๊ธฐํ ๋ฒ๊ทธ๊ฐ ์๋ ๊ฒฝ์ ์ํ์
๋๋ค.
๊ต์ฐฉ ์ํ vs ๊ฒฝ์ ์ํ: ์ฐจ์ด์ ์ดํดํ๊ธฐ
| ์ธก๋ฉด | ๊ต์ฐฉ ์ํ | ๊ฒฝ์ ์ํ |
|---|---|---|
| ์ฆ์ | ํ๋ก๊ทธ๋จ์ด ์์ํ ๋ฉ์ถค | ํ๋ก๊ทธ๋จ์ด ์๋ชป๋ ๊ฒฐ๊ณผ ์์ฑ |
| ์คํ | ์๋ฃ๋์ง ์์ | ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋จ |
| ํ์ด๋ฐ | ๊ฒฐ์ ์ ์ผ๋ก ๋ฉ์ถค | ๋น๊ฒฐ์ ์ ๊ฒฐ๊ณผ |
| ๊ทผ๋ณธ ์์ธ | ๋๊ธฐํ ๋ก์ง ์ค๋ฅ | ๋๊ธฐํ๋์ง ์์ ๋ฐ์ดํฐ ์ ๊ทผ |
| ํ์ง ๋๊ตฌ | synccheck | racecheck |
| ์์ | Puzzle 09: ์ธ ๋ฒ์งธ ์ฌ๋ก ๋ฐฐ๋ฆฌ์ด ๊ต์ฐฉ ์ํ | ๊ณต์ ๋ฉ๋ชจ๋ฆฌ += ์ฐ์ฐ |
์ฐ๋ฆฌ ์ฌ๋ก์์:
- ํ๋ก๊ทธ๋จ ์๋ฃ๋จ โ ๊ต์ฐฉ ์ํ ์์ (์ค๋ ๋๊ฐ ๋ฉ์ถ์ง ์์)
- ์๋ชป๋ ๊ฒฐ๊ณผ โ ๊ฒฝ์ ์ํ (์ค๋ ๋๋ค์ด ์๋ก์ ๋ฐ์ดํฐ๋ฅผ ์์)
- ๋๊ตฌ ํ์ธ โ
synccheck๋ 0๊ฐ ์ค๋ฅ,racecheck๋ 9๊ฐ ์ํ ์์ ๋ณด๊ณ
๋๋ฒ๊น ์์ ์ด ๊ตฌ๋ถ์ด ์ค์ํ ์ด์ :
- ๊ต์ฐฉ ์ํ ๋๋ฒ๊น : ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น, ์กฐ๊ฑด๋ถ ๋๊ธฐํ, ์ค๋ ๋ ์กฐ์จ์ ์ง์ค
- ๊ฒฝ์ ์ํ ๋๋ฒ๊น : ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด, ์์์ ์ฐ์ฐ (์ญ์ฃผ: ์ค๊ฐ ์ํ ์์ด ์์ ํ ์คํ๋๊ฑฐ๋ ์ ํ ์คํ๋์ง ์๋ ์ฐ์ฐ), ๋ฐ์ดํฐ ์์กด์ฑ์ ์ง์ค
๋์ ๊ณผ์
์ด ๋๊ตฌ๋ค์ ํ์ฉํ์ฌ ์คํจํ๋ ์ปค๋์ ์์ ํ์ธ์.
ํ
์ํ ์์ ๋ถ์
shared_sum[0] += a[row, col] ์ฐ์ฐ์ด ์ํํ ์ด์ ๋ ์ค์ ๋ก
์ธ ๊ฐ์ ๋ณ๋ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ด๊ธฐ ๋๋ฌธ์
๋๋ค:
shared_sum[0]์ฝ๊ธฐ- ์ฝ์ ๊ฐ์
a[row, col]๋ํ๊ธฐ - ๊ฒฐ๊ณผ๋ฅผ
shared_sum[0]์ ๋ค์ ์ฐ๊ธฐ
4๊ฐ์ ํ์ฑ ์ค๋ ๋(์์น (0,0), (0,1), (1,0), (1,1))์์ ์ด ์ฐ์ฐ๋ค์ด ๊ฒน์น ์ ์์ต๋๋ค:
- ์ค๋ ๋ ํ์ด๋ฐ ์ค์ฒฉ โ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ์ด๊ธฐ๊ฐ(0.0)์ ์ฝ์
- ์
๋ฐ์ดํธ ์์ค โ ๊ฐ ์ค๋ ๋๊ฐ
0.0 + ์์ ์_๊ฐ์ ์จ์ ๋ค๋ฅธ ์ค๋ ๋์ ์์ ์ ๋ฎ์ด์ - ๋น์์์ ์ฐ์ฐ โ
+=๋ณตํฉ ๋์ ์ GPU ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์์์ ์ด์ง ์์ (์ญ์ฃผ: ์คํ ๋์ค ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋ผ์ด๋ค ์ ์์ด ์ค๊ฐ ์ํ๊ฐ ๋ ธ์ถ๋จ)
์ ํํ 9๊ฐ์ ์ํ ์์๊ฐ ๋์ค๋ ์ด์ :
- ๊ฐ ์ค๋ ๋๊ฐ read-modify-write๋ฅผ ์๋
- 4๊ฐ ์ค๋ ๋ ร ์ค๋ ๋๋น 2-3๊ฐ ์ํ ์์ = ์ด 9๊ฐ ์ํ ์์
compute-sanitizer๊ฐ ๋ชจ๋ ์ถฉ๋ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์์ ์ถ์
๊ฒฝ์ ์ํ ๋๋ฒ๊น ํ
- ๋ฐ์ดํฐ ๊ฒฝ์์๋ racecheck ์ฌ์ฉ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํ ์์์ ๋ฐ์ดํฐ ์์ ํ์ง
- ๊ต์ฐฉ ์ํ์๋ synccheck ์ฌ์ฉ: ๋๊ธฐํ ๋ฒ๊ทธ(๋ฐฐ๋ฆฌ์ด ๋ฌธ์ , ๊ต์ฐฉ ์ํ) ํ์ง
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ง์ค: ๊ณต์ ๋ณ์์ ๋ํ ๋๊ธฐํ๋์ง ์์
+=,=์ฐ์ฐ ์ฐพ๊ธฐ - ํจํด ์๋ณ: read-modify-write ์ฐ์ฐ์ด ํํ ๊ฒฝ์ ์ํ ์์ธ
- ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น ํ์ธ: ๋ฐฐ๋ฆฌ์ด๋ ์ถฉ๋ ์ฐ์ฐ ์ด์ ์ ๋ฐฐ์นํด์ผ ํจ, ์ดํ๊ฐ ์๋
๋๋ฒ๊น ์์ ์ด ๊ตฌ๋ถ์ด ์ค์ํ ์ด์ :
- ๊ต์ฐฉ ์ํ ๋๋ฒ๊น : ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น, ์กฐ๊ฑด๋ถ ๋๊ธฐํ, ์ค๋ ๋ ์กฐ์จ์ ์ง์ค
- ๊ฒฝ์ ์ํ ๋๋ฒ๊น : ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด, ์์์ ์ฐ์ฐ, ๋ฐ์ดํฐ ์์กด์ฑ์ ์ง์ค
ํผํด์ผ ํ ํํ ๊ฒฝ์ ์ํ ํจํด:
- ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ฐ๊ธฐ
- ๋๊ธฐํ๋์ง ์์ read-modify-write ์ฐ์ฐ (
+=,++๋ฑ) - ๊ฒฝ์ ์ํ ์ด์ ์ด ์๋ ์ดํ์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น
์๋ฃจ์
comptime SIZE = 2
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = (3, 3)
comptime dtype = DType.float32
comptime layout = row_major[SIZE, SIZE]()
comptime LayoutType = type_of(layout)
def shared_memory_race(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
"""Fixed: sequential access with barriers eliminates race conditions."""
var row = thread_idx.y
var col = thread_idx.x
var shared_sum = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[1]())
# Only thread 0 does all the accumulation work to prevent races
if row == 0 and col == 0:
# Use local accumulation first, then single write to shared memory
var local_sum = Scalar[dtype](0.0)
for r in range(size):
for c in range(size):
local_sum += rebind[Scalar[dtype]](a[r, c])
shared_sum[0] = local_sum # Single write operation
barrier() # Ensure thread 0 completes before others read
# All threads read the safely accumulated result after synchronization
if row < size and col < size:
output[row, col] = shared_sum[0]
๋ฌด์์ด ์๋ชป๋์๋์ง ์ดํดํ๊ธฐ
๊ฒฝ์ ์ํ ๋ฌธ์ ํจํด
์๋ ์คํจํ๋ ์ฝ๋์๋ ์ด ํต์ฌ์ ์ธ ์ค์ด ์์์ต๋๋ค:
shared_sum[0] += a[row, col] # ๊ฒฝ์ ์ํ!
์ด ํ ์ค์ด 4๊ฐ์ ์ ํจํ ์ค๋ ๋ ์ฌ์ด์์ ์ฌ๋ฌ ์ํ ์์๋ฅผ ์ผ์ผํต๋๋ค:
- ์ค๋ ๋ (0,0)์ด ์ฝ์
shared_sum[0](๊ฐ: 0.0) - ์ค๋ ๋ (0,1)์ด ์ฝ์
shared_sum[0](๊ฐ: 0.0) โ Read-after-write ์ํ! - ์ค๋ ๋ (0,0)์ด ์
0.0 + 0 - ์ค๋ ๋ (1,0)์ด ์
0.0 + 2โ Write-after-write ์ํ!
ํ ์คํธ๊ฐ ์คํจํ ์ด์
+=์ฐ์ฐ ์ค ์ฌ๋ฌ ์ค๋ ๋๊ฐ ์๋ก์ ์ฐ๊ธฐ๋ฅผ ์์์ํด+=์ฐ์ฐ์ด ์ค๋จ๋์ด ์ ๋ฐ์ดํธ ์์ค ๋ฐ์- ์์ ํฉ๊ณ 6.0 (0+1+2+3)์ด์ง๋ง, ๊ฒฝ์ ์ํ๋ก ์ธํด 0.0์ด ๋จ
barrier()๊ฐ ๋๋ฌด ๋ฆ๊ฒ ์ด - ๊ฒฝ์ ์ํ๊ฐ ์ด๋ฏธ ๋ฐ์ํ ํ
๊ฒฝ์ ์ํ๋?
๊ฒฝ์ ์ํ๋ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ณต์ ๋ฐ์ดํฐ์ ๋์์ ์ ๊ทผํ๊ณ , ๊ฒฐ๊ณผ๊ฐ ์์ธก ๋ถ๊ฐ๋ฅํ ์ค๋ ๋ ์คํ ํ์ด๋ฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ๋ ๋ฐ์ํฉ๋๋ค.
์ฃผ์ ํน์ฑ:
- ๋น๊ฒฐ์ ์ ๋์: ๊ฐ์ ์ฝ๋๊ฐ ๋ค๋ฅธ ์คํ์์ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ผ ์ ์์
- ํ์ด๋ฐ ์์กด์ : ๊ฒฐ๊ณผ๊ฐ ์ด๋ค ์ค๋ ๋๊ฐ โ๊ฒฝ์์์ ์ด๊ธฐ๋์งโ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง
- ์ฌํํ๊ธฐ ์ด๋ ค์: ํน์ ์กฐ๊ฑด์ด๋ ํ๋์จ์ด์์๋ง ๋ํ๋ ์ ์์
GPU ํน์ ์ ์ํ์ฑ
๋๊ท๋ชจ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ์ํฅ:
- ์ํ ์์ค ์์: ๊ฒฝ์ ์ํ๊ฐ ์ ์ฒด ์ํ(32๊ฐ ์ค๋ ๋)์ ์ํฅ์ ์ค ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ๋ฌธ์ : ๊ฒฝ์์ผ๋ก ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ๊นจ์ง ์ ์์
- ์ปค๋ ์ ์ฒด ์คํจ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์์ด ์ ์ฒด GPU ์ปค๋์ ์ํฅ์ ์ค ์ ์์
ํ๋์จ์ด ์ฐจ์ด:
- ๋ค๋ฅธ GPU ์ํคํ ์ฒ: ๊ฒฝ์ ์ํ๊ฐ GPU ๋ชจ๋ธ๋ง๋ค ๋ค๋ฅด๊ฒ ๋ํ๋ ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต: L1 ์บ์, L2 ์บ์, ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ฐ๊ฐ ๋ค๋ฅธ ๊ฒฝ์ ๋์์ ๋ณด์ผ ์ ์์
- ์ํ ์ค์ผ์ค๋ง: ๋ค๋ฅธ ์ค๋ ๋ ์ค์ผ์ค๋ง์ด ๋ค๋ฅธ ๊ฒฝ์ ์ํ ์๋๋ฆฌ์ค๋ฅผ ๋ ธ์ถ์ํฌ ์ ์์
์ ๋ต: ๋จ์ผ ์ฐ๊ธฐ ํจํด
ํต์ฌ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ํ ๋์ ์ฐ๊ธฐ๋ฅผ ์์ ๋ ๊ฒ์ ๋๋ค:
- Single writer: ํ๋์ ์ค๋ ๋(์์น (0,0))๋ง ๋ชจ๋ ๋์ ์์ ์ํ
- ๋ก์ปฌ ๋์ : ์์น (0,0) ์ค๋ ๋๊ฐ ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํด ๋ฐ๋ณต์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ํผํจ
- ๋จ์ผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ: ๋จ์ผ ์ฐ๊ธฐ ์ฐ์ฐ์ผ๋ก write-write ๊ฒฝ์ ์ ๊ฑฐ
- ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ: writer๊ฐ ์๋ฃ๋ ํ์์ผ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ฝ๋๋ก ๋ณด์ฅ
- ๋ค์ค ์ฝ๊ธฐ: ๋ชจ๋ ์ค๋ ๋๊ฐ ์์ ํ๊ฒ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์ฝ์
๋จ๊ณ๋ณ ์๋ฃจ์ ๋ถ์
1๋จ๊ณ: ์ค๋ ๋ ์๋ณ
if row == 0 and col == 0:
์ง์ ์ขํ ๊ฒ์ฌ๋ก ์์น (0,0)์ ์ค๋ ๋๋ฅผ ์๋ณํฉ๋๋ค.
2๋จ๊ณ: ๋จ์ผ ์ค๋ ๋ ๋์
if row == 0 and col == 0:
local_sum = Scalar[dtype](0.0)
for r in range(size):
for c in range(size):
local_sum += rebind[Scalar[dtype]](a[r, c])
shared_sum[0] = local_sum # ๋จ์ผ ์ฐ๊ธฐ ์ฐ์ฐ
์์น (0,0)์ ์ค๋ ๋๋ง ๋ชจ๋ ๋์ ์์ ์ ์ํํ์ฌ ๊ฒฝ์ ์ํ๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
3๋จ๊ณ: ๋๊ธฐํ ๋ฐฐ๋ฆฌ์ด
barrier() # ์ค๋ ๋ (0,0)์ด ์๋ฃํ ํ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ฝ๋๋ก ๋ณด์ฅ
๋ชจ๋ ์ค๋ ๋๊ฐ ์์น (0,0)์ ์ค๋ ๋๊ฐ ๋์ ์ ๋ง์น ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
4๋จ๊ณ: ์์ ํ ๋ณ๋ ฌ ์ฝ๊ธฐ
if row < size and col < size:
output[row, col] = shared_sum[0]
๋๊ธฐํ ํ ๋ชจ๋ ์ค๋ ๋๊ฐ ์์ ํ๊ฒ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค.
ํจ์จ์ฑ์ ๊ดํ ์ค์ ์ฌํญ
์ด ์๋ฃจ์ ์ ํจ์จ์ฑ๋ณด๋ค ์ ํ์ฑ์ ์ฐ์ ํฉ๋๋ค. ๊ฒฝ์ ์ํ๋ ์ ๊ฑฐํ์ง๋ง, ์์น (0,0) ์ค๋ ๋๋ง ๋์ ์ ์ฌ์ฉํ๋ ๊ฒ์ GPU ์ฑ๋ฅ์ ์ต์ ์ด ์๋๋๋ค - ๋๊ท๋ชจ ๋ณ๋ ฌ ์ฅ์น์์ ์ฌ์ค์ ์ง๋ ฌ ๊ณ์ฐ์ ํ๋ ์ ์ ๋๋ค.
์ด์ด์ Puzzle 11: ํ๋ง์์: ๋ชจ๋ ์ค๋ ๋๋ฅผ ํ์ฉํด ๊ณ ์ฑ๋ฅ ํฉ์ฐ ์ฐ์ฐ์ ์ํํ๋ฉด์๋ ๊ฒฝ์ ์ํ๋ฅผ ํผํ๋ ํจ์จ์ ์ธ ๋ณ๋ ฌ ๋ฆฌ๋์ ์๊ณ ๋ฆฌ์ฆ์ ๋ฐฐ์๋๋ค. ์ด ํผ์ฆ์ ์ ํ์ฑ ์ฐ์ ์ ๊ธฐ์ด๋ฅผ ๊ฐ๋ฅด์นฉ๋๋ค - ๊ฒฝ์ ์ํ๋ฅผ ํผํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๊ณ ๋๋ฉด, Puzzle 11์์ ์ ํ์ฑ๊ณผ ์ฑ๋ฅ ๋ชจ๋๋ฅผ ๋ฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ณด๊ฒ ๋ฉ๋๋ค.
๊ฒ์ฆ
pixi run compute-sanitizer --tool racecheck mojo solutions/p10/p10.mojo --race-condition
์์ ์ถ๋ ฅ:
========= COMPUTE-SANITIZER
out shape: 2 x 2
Running race condition example...
out: HostBuffer([6.0, 6.0, 6.0, 6.0])
expected: HostBuffer([6.0, 6.0, 6.0, 6.0])
โ
Race condition test PASSED! (racecheck will find hazards)
========= RACECHECK SUMMARY: 0 hazards displayed (0 errors, 0 warnings)
โ ์ฑ๊ณต: ํ ์คํธ๊ฐ ํต๊ณผํ๊ณ ๊ฒฝ์ ์ํ๊ฐ ํ์ง๋์ง ์์์ต๋๋ค!
Puzzle 11: ํ๋ง
๊ฐ์
1D TileTensor a์์ ๊ฐ ์์น์ ์ง์ 3๊ฐ ๊ฐ์ ํฉ์ ๊ณ์ฐํ์ฌ 1D TileTensor
output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : ๊ฐ ์์น๋ง๋ค ์ค๋ ๋ 1๊ฐ๊ฐ ์์ต๋๋ค. ์ค๋ ๋๋น ์ ์ญ ์ฝ๊ธฐ 1ํ, ์ ์ญ ์ฐ๊ธฐ 1ํ๋ง ํ์ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- TileTensor๋ก ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ฐ์ฐ ๊ตฌํํ๊ธฐ
- Puzzle 8์์ ๋ค๋ฃฌ TileTensor ์ฃผ์ ๊ณต๊ฐ(address_space)์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌํ๊ธฐ
- ํจ์จ์ ์ธ ์ด์ ์ ๊ทผ ํจํด
- ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ
ํต์ฌ์ TileTensor๊ฐ ํจ์จ์ ์ธ ์๋์ฐ ๊ธฐ๋ฐ ์ฐ์ฐ์ ์ ์งํ๋ฉด์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๊ฐ์ํํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE = 8 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ์๋์ฐ ํฌ๊ธฐ: 3
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
TPB๊ฐ
์ฐธ๊ณ :
- TileTensor ํ ๋น:
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB]())์ฌ์ฉ - ์๋์ฐ ์ ๊ทผ: 3๊ฐ์ง๋ฆฌ ์๋์ฐ์ ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ฒ์ ๋ ์์น๋ ํน์ ์ผ์ด์ค
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ค๋ ๋๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ก๋ 1ํ
์์ฑํ ์ฝ๋
comptime TPB = 8
comptime SIZE = 8
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
def pooling(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
# Allocate shared memory using stack_allocation
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FIX ME IN (roughly 10 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p11/p11.mojo
ํ
- TileTensor์ ์ฃผ์ ๊ณต๊ฐ(address_space)์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ฑ
- ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ์ผ๋ก ๋ฐ์ดํฐ ๋ก๋:
shared[local_i] = a[global_i] - ์ฒ์ ๋ ์์น๋ฅผ ํน์ ์ผ์ด์ค๋ก ์ฒ๋ฆฌ
- ์๋์ฐ ์ฐ์ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- ๊ฒฝ๊ณ ์ด๊ณผ ์ ๊ทผ์ ๊ฐ๋ ์ถ๊ฐ
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p11
pixi run -e amd p11
pixi run -e apple p11
uv run poe p11
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([0.0, 1.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0])
์๋ฃจ์
def pooling(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
# Allocate shared memory using stack_allocation
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Load data into shared memory
if global_i < size:
shared[local_i] = a[global_i]
# Synchronize threads within block
barrier()
# Handle first two special cases
if global_i == 0:
output[0] = shared[0]
elif global_i == 1:
output[1] = shared[0] + shared[1]
# Handle general case
elif 1 < global_i < size:
output[global_i] = (
shared[local_i - 2] + shared[local_i - 1] + shared[local_i]
)
TileTensor๋ฅผ ํ์ฉํ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ํฉ๊ณ ๊ตฌํ์ ๋๋ค. ์ฃผ์ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ค์
-
TileTensor๊ฐ ์ฃผ์ ๊ณต๊ฐ(address_space)์ผ๋ก ๋ธ๋ก ๋ก์ปฌ ์ ์ฅ์๋ฅผ ์์ฑ:
shared = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB]()) -
๊ฐ ์ค๋ ๋๊ฐ ํ๋์ฉ ๋ก๋:
Input array: [0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0] Block shared: [0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0] -
barrier()๋ก ๋ชจ๋ ๋ฐ์ดํฐ ๋ก๋ ์๋ฃ๋ฅผ ๋ณด์ฅ
-
-
๊ฒฝ๊ณ ์ผ์ด์ค
-
์์น 0: ํ๋๋ง
output[0] = shared[0] = 0.0 -
์์น 1: ์ฒ์ ๋ ๊ฐ์ ํฉ
output[1] = shared[0] + shared[1] = 0.0 + 1.0 = 1.0
-
-
๋ฉ์ธ ์๋์ฐ ์ฐ์ฐ
-
์์น 2 ์ดํ:
Position 2: shared[0] + shared[1] + shared[2] = 0.0 + 1.0 + 2.0 = 3.0 Position 3: shared[1] + shared[2] + shared[3] = 1.0 + 2.0 + 3.0 = 6.0 Position 4: shared[2] + shared[3] + shared[4] = 2.0 + 3.0 + 4.0 = 9.0 ... -
TileTensor์ ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ:
# 3๊ฐ์ง๋ฆฌ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ window_sum = shared[i-2] + shared[i-1] + shared[i]
-
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ค๋ ๋๋ง๋ค ๊ณต์ ํ ์๋ก ์ ์ญ ์ฝ๊ธฐ 1ํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํ ํจ์จ์ ์ธ ์ด์ ์ ๊ทผ
- TileTensor์ ์ฅ์ :
- ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ
- ์์ฐ์ค๋ฌ์ด ์๋์ฐ ์ธ๋ฑ์ฑ
- ๋ ์ด์์์ ์ธ์ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ์ ๊ณผ์ ์ ๊ฑธ์น ํ์ ์์ ์ฑ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ฑ๋ฅ๊ณผ TileTensor์ ์์ ์ฑ ๋ฐ ํธ์์ฑ์ ๊ฒฐํฉํ ๋ฐฉ์์ ๋๋ค:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ํ
- ์๋์ฐ ์ฐ์ฐ ๊ฐ์ํ
- ๊น๋ํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
- ๋ณํฉ ์ ๊ทผ ํจํด ์ ์ง
์ต์ข ์ถ๋ ฅ์ ๋์ ์๋์ฐ ํฉ๊ณ์ ๋๋ค:
[0.0, 1.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0]
Puzzle 12: ๋ด์
๊ฐ์
1D TileTensor a์ 1D TileTensor b์ ๋ด์ ์ ๊ณ์ฐํ์ฌ 1D TileTensor
output(๋จ์ผ ๊ฐ)์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์. ๋ด์ ์ ํฌ๊ธฐ๊ฐ ๊ฐ์ ๋ ๋ฒกํฐ์์
๋์ํ๋ ์์๋ผ๋ฆฌ ๊ณฑํ ๋ค, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ ๋ํด ํ๋์ ์ซ์(์ค์นผ๋ผ)๋ฅผ ๊ตฌํ๋
์ฐ์ฐ์
๋๋ค.
์๋ฅผ ๋ค์ด, ๋ ๋ฒกํฐ๊ฐ ๋ค์๊ณผ ๊ฐ์ ๋:
\[a = [a_{1}, a_{2}, โฆ, a_{n}] \] \[b = [b_{1}, b_{2}, โฆ, b_{n}] \]
๋ด์ ์ ์ด๋ ๊ฒ ๊ตฌํฉ๋๋ค: \[a \cdot b = a_{1}b_{1} + a_{2}b_{2} + โฆ + a_{n}b_{n}\]
์ฐธ๊ณ : ๊ฐ ์์น๋ง๋ค ์ค๋ ๋ 1๊ฐ๊ฐ ์์ต๋๋ค. ์ค๋ ๋๋น ์ ์ญ ์ฝ๊ธฐ 2ํ, ๋ธ๋ก๋น ์ ์ญ ์ฐ๊ธฐ 1ํ๋ง ํ์ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- Puzzle 8, Puzzle 11์์ ์ด์ด์ง๋ TileTensor ๊ธฐ๋ฐ ๋ณ๋ ฌ ๋ฆฌ๋์
address_space๋ฅผ ํ์ฉํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ- ์ฌ๋ฌ ์ค๋ ๋๊ฐ ํ๋ ฅํด ํ๋์ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์ด๊ฐ๋ ๊ณผ์
- ๋ ์ด์์์ ์ธ์ํ๋ ํ ์ ์ฐ์ฐ
ํต์ฌ์ TileTensor๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๊ฐ์ํํ๋ฉด์๋, ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ํจ์จ์ ๊ทธ๋๋ก ์ด๋ฆฌ๋ ๋ฐฉ์์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 8 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ๋ธ๋ก ์: 1
- ์ถ๋ ฅ ํฌ๊ธฐ: 1
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
TPB๊ฐ
์ฐธ๊ณ :
- TileTensor ํ ๋น:
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB]())์ฌ์ฉ - ์์ ์ ๊ทผ: ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ์๋์ผ๋ก ๋ฐ๋ผ์ค๋ ์์ฐ์ค๋ฌ์ด ์ธ๋ฑ์ฑ
- ๋ ์ด์์ ์ฒ๋ฆฌ: ์ ๋ ฅ์ฉ๊ณผ ์ถ๋ ฅ์ฉ ๋ ์ด์์์ ๋ฐ๋ก ๊ตฌ์ฑ
- ์ค๋ ๋ ์กฐ์จ: ๋์ผํ ๋๊ธฐํ ํจํด์ผ๋ก
barrier()์ฌ์ฉ
์์ฑํ ์ฝ๋
from std.gpu import thread_idx, block_idx, block_dim, barrier
from std.gpu.memory import AddressSpace
from layout import TileTensor
from layout.tile_layout import row_major
from layout.tile_tensor import stack_allocation
comptime TPB = 8
comptime SIZE = 8
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime out_layout = row_major[1]()
comptime LayoutType = type_of(layout)
comptime OutLayout = type_of(out_layout)
def dot_product(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
# FILL ME IN (roughly 13 lines)
...
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p12/p12.mojo
ํ
- TileTensor์
address_space๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ฑ shared[local_i]์a[global_i] * b[global_i]๋ฅผ ์ ์ฅbarrier()์ ํจ๊ป ๋ณ๋ ฌ ๋ฆฌ๋์ ํจํด ์ ์ฉ- ์ค๋ ๋ 0์ด ์ต์ข
๊ฒฐ๊ณผ๋ฅผ
output[0]์ ๊ธฐ๋ก
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p12
pixi run -e amd p12
pixi run -e apple p12
uv run poe p12
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0])
expected: HostBuffer([140.0])
์๋ฃจ์
def dot_product(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Compute element-wise multiplication into shared memory
if global_i < size:
shared[local_i] = a[global_i] * b[global_i]
# Synchronize threads within block
barrier()
# Parallel reduction in shared memory
var stride = TPB // 2
while stride > 0:
if local_i < stride:
shared[local_i] += shared[local_i + stride]
barrier()
stride //= 2
# Only thread 0 writes the final result
if local_i == 0:
output[0] = shared[0]
TileTensor๋ฅผ ํ์ฉํ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ผ๋ก ๋ด์ ์ ๊ณ์ฐํ๋ ์๋ฃจ์ ์ ๋๋ค. ๋จ๊ณ๋ณ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค:
1๋จ๊ณ: ์์๋ณ ๊ณฑ์
๊ฐ ์ค๋ ๋๊ฐ ์ง๊ด์ ์ธ ์ธ๋ฑ์ฑ์ผ๋ก ๊ณฑ์ ์ฐ์ฐ์ ํ๋์ฉ ์ฒ๋ฆฌํฉ๋๋ค:
shared[local_i] = a[global_i] * b[global_i]
2๋จ๊ณ: ๋ณ๋ ฌ ๋ฆฌ๋์
๋ ์ด์์์ ์ธ์ํ๋ ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ฆฌ๋์ ์ ๋๋ค:
์ด๊ธฐ๊ฐ: [0*0 1*1 2*2 3*3 4*4 5*5 6*6 7*7]
= [0 1 4 9 16 25 36 49]
Step 1: [0+16 1+25 4+36 9+49 16 25 36 49]
= [16 26 40 58 16 25 36 49]
Step 2: [16+40 26+58 40 58 16 25 36 49]
= [56 84 40 58 16 25 36 49]
Step 3: [56+84 84 40 58 16 25 36 49]
= [140 84 40 58 16 25 36 49]
๊ตฌํ์ ํต์ฌ ํน์ง
-
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ:
address_spaceํ๋ผ๋ฏธํฐ ํ๋๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊น๋ํ๊ฒ ํ ๋น- ํ์ ์์ ํ ์ฐ์ฐ์ด ๋ณด์ฅ๋๊ณ
- ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ์๋์ผ๋ก ๋ฐ๋ผ์ค๋ฉฐ
- ์ธ๋ฑ์ฑ๋ ๋ ์ด์์์ ์ธ์
-
์ค๋ ๋ ๋๊ธฐํ:
- ์ด๊ธฐ ๊ณฑ์
์ด ๋๋๋ฉด
barrier() - ๋ฆฌ๋์
๋จ๊ณ ์ฌ์ด๋ง๋ค
barrier() - ์ค๋ ๋ ๊ฐ ์์ ํ ์กฐ์จ ๋ณด์ฅ
- ์ด๊ธฐ ๊ณฑ์
์ด ๋๋๋ฉด
-
๋ฆฌ๋์ ๋ก์ง:
stride = TPB // 2 while stride > 0: if local_i < stride: shared[local_i] += shared[local_i + stride] barrier() stride //= 2 -
์ฑ๋ฅ์ ์ด์ :
- \(O(\log n)\) ์๊ฐ ๋ณต์ก๋
- ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ์ต์ํ์ ์ค๋ ๋ ๋ถ๊ธฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ํจ์จ์ ํ์ฉ
TileTensor ๋ฒ์ ์ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ํจ์จ์ ๊ทธ๋๋ก ์ ์งํ๋ฉด์, ์ฌ๊ธฐ์ ๋ํด:
- ํ์ ์์ ์ฑ์ด ํ์ธต ๊ฐํ๋๊ณ
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ๋ ๊น๋ํด์ง๋ฉฐ
- ๋ ์ด์์์ ์๋์ผ๋ก ์ธ์ํ๊ณ
- ์ธ๋ฑ์ฑ ๋ฌธ๋ฒ๋ ์์ฐ์ค๋ฌ์์ง๋๋ค
๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ์ ์ค์์ฑ
๋ฆฌ๋์
๋จ๊ณ ์ฌ์ด์ barrier()๋ ์ ํํ ๊ฒฐ๊ณผ๋ฅผ ์ํด ๋ฐ๋์ ํ์ํฉ๋๋ค. ๊ทธ ์ด์ ๋ฅผ
์ดํด๋ณด๊ฒ ์ต๋๋ค:
barrier()๊ฐ ์์ผ๋ฉด ๊ฒฝ์ ์ํ๊ฐ ๋ฐ์ํฉ๋๋ค:
์ด๊ธฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: [0 1 4 9 16 25 36 49]
Step 1 (stride = 4):
Thread 0 ์ฝ๊ธฐ: shared[0] = 0, shared[4] = 16
Thread 1 ์ฝ๊ธฐ: shared[1] = 1, shared[5] = 25
Thread 2 ์ฝ๊ธฐ: shared[2] = 4, shared[6] = 36
Thread 3 ์ฝ๊ธฐ: shared[3] = 9, shared[7] = 49
barrier ์์ด:
- Thread 0 ์ฐ๊ธฐ: shared[0] = 0 + 16 = 16
- Thread 1์ด Thread 0๋ณด๋ค ๋จผ์ ๋ค์ ๋จ๊ณ(stride = 2)๋ก ๋์ด๊ฐ์
16์ด ์๋ ์ด์ ๊ฐ shared[0] = 0์ ์ฝ์ด๋ฒ๋ฆฝ๋๋ค!
barrier()๊ฐ ์์ผ๋ฉด:
Step 1 (stride = 4):
๋ชจ๋ ์ค๋ ๋๊ฐ ํฉ์ ๊ธฐ๋ก:
[16 26 40 58 16 25 36 49]
barrier()๊ฐ ๋ชจ๋ ์ค๋ ๋์๊ฒ ์ด ๊ฐ๋ค์ด ๋ณด์ด๋๋ก ๋ณด์ฅ
Step 2 (stride = 2):
์ด์ ์
๋ฐ์ดํธ๋ ๊ฐ์ ์์ ํ๊ฒ ์ฝ์ ์ ์์:
Thread 0: shared[0] = 16 + 40 = 56
Thread 1: shared[1] = 26 + 58 = 84
barrier()๋ ๋ค์์ ๋ณด์ฅํฉ๋๋ค:
- ํ์ฌ ๋จ๊ณ์ ๋ชจ๋ ์ฐ๊ธฐ๊ฐ ๋๋ ๋ค์์ผ ๋ค์์ผ๋ก ๋์ด๊ฐ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์ต์ ๊ฐ์ ๋ณผ ์ ์์
- ์ด๋ค ์ค๋ ๋๋ ์์ ๋๊ฐ์ง ์์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํญ์ ์ผ๊ด๋ ์ํ๋ฅผ ์ ์ง
์ด๋ฐ ๋๊ธฐํ ์ง์ ์ด ์์ผ๋ฉด:
- ๊ฒฝ์ ์ํ๊ฐ ๋ฐ์ํ๊ณ
- ์ค๋ ๋๊ฐ ์ด๋ฏธ ์ง๋ ๊ฐ์ ์ฝ๊ฒ ๋๋ฉฐ
- ์คํํ ๋๋ง๋ค ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๊ณ
- ์ต์ข ํฉ๊ณ๊ฐ ํ์ด์ง ์ ์์ต๋๋ค
Puzzle 13: 1D ํฉ์ฑ๊ณฑ
TileTensor๋ก ์ ํํ๊ธฐ
์ง๊ธ๊น์ง GPU ํผ์ฆ ์ฌ์ ์์ GPU ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๋ํ ๋ ๊ฐ์ง ์ ๊ทผ ๋ฐฉ์์ ํจ๊ป ์ดํด๋ณด์์ต๋๋ค:
- UnsafePointer๋ฅผ ์ฌ์ฉํ ํฌ์ธํฐ ์ง์ ์กฐ์ ๋ฐฉ์์ raw ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
- ๊ฐ๋ ฅํ
address_spaceํ๋ผ๋ฏธํฐ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๋, ๋ณด๋ค ๊ตฌ์กฐํ๋ TileTensor์ด ํผ์ฆ๋ถํฐ๋
TileTensor๋ก ์์ ํ ์ ํํฉ๋๋ค. ์ด ์ถ์ํ๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ ๊ณตํฉ๋๋ค:
- ํ์ ์์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ฐ์ดํฐ ๋ ์ด์์์ ๋ช ํํ ํํ
- ์ฝ๋ ์ ์ง๋ณด์์ฑ ํฅ์
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ๋ฒ๊ทธ ๋ฐ์ ๊ฐ๋ฅ์ฑ ๊ฐ์
- ๋ด๋ถ ์ฐ์ฐ์ ์๋๋ฅผ ๋ ์ ๋๋ฌ๋ด๋ ํํ๋ ฅ ์๋ ์ฝ๋
- ์์ผ๋ก ์ฐจ์ฐจ ์์๊ฐ ๋ ๋ง์ ๊ฒ๋ค!
์ด๋ฌํ ์ ํ์ Mojo ๐ฅ์ ํ๋์ GPU ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ฒ ์ฌ๋ก์ ๋ง๋ฟ์ ์์ต๋๋ค. ๋์ ์์ค์ ์ถ์ํ๋ก ๋ณต์ก์ฑ์ ๊ด๋ฆฌํ๋ฉด์๋ ์ฑ๋ฅ์ ๊ทธ๋๋ก ์ ์งํ ์ ์์ต๋๋ค.
๊ฐ์
์ ํธ ์ฒ๋ฆฌ์ ์ด๋ฏธ์ง ๋ถ์์์ ํฉ์ฑ๊ณฑ(convolution)์ ๋ ์ํ์ค๋ฅผ ๊ฒฐํฉํด ์๋ก์ด ์ํ์ค๋ฅผ ๋ง๋ค์ด๋ด๋ ํต์ฌ ์ฐ์ฐ์ ๋๋ค. ์ด ํผ์ฆ์์๋ ์ ๋ ฅ ๋ฐฐ์ด ์๋ก ์ปค๋์ ์ฌ๋ผ์ด๋ฉํ๋ฉด์ ๊ฐ ์ถ๋ ฅ ์์๋ฅผ ๊ณ์ฐํ๋ 1D ํฉ์ฑ๊ณฑ์ GPU์์ ๊ตฌํํด ๋ด ๋๋ค.
TileTensor ์ถ์ํ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒกํฐ a์ ๋ฒกํฐ b์ 1D ํฉ์ฑ๊ณฑ์ ๊ณ์ฐํ๊ณ ,
๊ฒฐ๊ณผ๋ฅผ output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์ค๋ ๋๋น ์ ์ญ ์ฝ๊ธฐ 2ํ, ์ ์ญ ์ฐ๊ธฐ 1ํ๋ง ํ์ํฉ๋๋ค.
ํฉ์ฑ๊ณฑ์ด ์ฒ์์ด๋ผ๋ฉด, ๊ฐ์ค์น๊ฐ ์ ์ฉ๋ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ฐ์ฐ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค. ๊ฐ ์์น์์ ์ปค๋ ๊ฐ๊ณผ ๋์ํ๋ ์ ๋ ฅ ๊ฐ์ ๊ณฑํ ๋ค ํฉ์ฐํฉ๋๋ค. ์ํ์ ํ๊ธฐ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
\[\Large output[i] = \sum_{j=0}^{\text{CONV}-1} a[i+j] \cdot b[j] \]
์์ฌ ์ฝ๋๋ก ํํํ 1D ํฉ์ฑ๊ณฑ:
for i in range(SIZE):
for j in range(CONV):
if i + j < SIZE:
ret[i] += a_host[i + j] * b_host[j]
์ด ํผ์ฆ์ ๋จ๊ณ์ ์ผ๋ก ์ดํด๋ฅผ ์์๊ฐ ์ ์๋๋ก ๋ ํํธ๋ก ๋๋ฉ๋๋ค:
-
๐ฐ ๊ธฐ๋ณธ ๋ฒ์ ์ฌ๊ธฐ์๋ถํฐ ์์ํ์ธ์. ๋จ์ผ ๋ธ๋ก์์ TileTensor์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ ํฉ์ฑ๊ณฑ ๊ตฌํ์ ๊ธฐ์ด๋ฅผ ์ตํ๋๋ค.
-
โญ ๋ธ๋ก ๊ฒฝ๊ณ ๋ฒ์ ์ด์ด์ ๋ธ๋ก ๊ฒฝ๊ณ๋ฅผ ๋์ด ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํด์ผ ํ๋ ๋ ๊น๋ค๋ก์ด ๊ฒฝ์ฐ์ ๋์ ํฉ๋๋ค. TileTensor์ ๊ธฐ๋ฅ์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํ์ฉํฉ๋๋ค.
๊ฐ ๋ฒ์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ์ค๋ ๋ ๊ฐ ํ๋ ฅ ์ธก๋ฉด์์ ์๋ก ๋ค๋ฅธ ๋์ ๊ณผ์ ๋ฅผ ์ ์ํฉ๋๋ค. ๊ธฐ๋ณธ ๋ฒ์ ์์ ํฉ์ฑ๊ณฑ ์ฐ์ฐ์ ์๋ฆฌ๋ฅผ ์ตํ ๋ค์, ๋ธ๋ก ๊ฒฝ๊ณ ๋ฒ์ ์์๋ ์ค์ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋ง์ฃผ์น๋ ๋ณต์กํ ์ํฉ์ ๋ค๋ฃจ๋ ๋ฅ๋ ฅ์ ์ํํด ๋ด ๋๋ค.
๋จ์ผ ๋ธ๋ก์ ์ฌ์ฉํ ๊ธฐ๋ณธ ๋ฒ์
1D TileTensor a์ 1D TileTensor b์ 1D ํฉ์ฑ๊ณฑ์ ๊ณ์ฐํ์ฌ 1D TileTensor
output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์ค๋ ๋๋น ์ ์ญ ์ฝ๊ธฐ 2ํ, ์ ์ญ ์ฐ๊ธฐ 1ํ๋ง ํ์ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- GPU์์ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ฐ์ฐ ๊ตฌํํ๊ธฐ
- ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ ์์กด์ฑ ๊ด๋ฆฌํ๊ธฐ
- ๊ฒน์น๋ ์์ญ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉํ๊ธฐ
ํต์ฌ์ ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์งํ๋ฉด์๋ ๊ฒน์น๋ ์์์ ํจ์จ์ ์ผ๋ก ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ์
๋ ฅ ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE = 6 - ์ปค๋ ํฌ๊ธฐ:
CONV = 3 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ๋ธ๋ก ์: 1
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
SIZE์CONVํฌ๊ธฐ์ ๋ฐฐ์ด 2๊ฐ
์ฐธ๊ณ :
- ๋ฐ์ดํฐ ๋ก๋ฉ: ๊ฐ ์ค๋ ๋๊ฐ ์ ๋ ฅ ๋ฐฐ์ด๊ณผ ์ปค๋์์ ์์๋ฅผ ํ๋์ฉ ๋ก๋
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ ๋ ฅ ๋ฐฐ์ด๊ณผ ํฉ์ฑ๊ณฑ ์ปค๋์ ์ ์ฅํ๋ ๊ณต์ ๋ฐฐ์ด
- ์ค๋ ๋ ๋๊ธฐํ: ์ฐ์ฐ ์์ ์ ์ค๋ ๋ ๊ฐ ์กฐ์จ
์์ฑํ ์ฝ๋
comptime TPB = 8
comptime SIZE = 6
comptime CONV = 3
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime in_layout = row_major[SIZE]()
comptime InLayout = type_of(in_layout)
comptime out_layout = row_major[SIZE]()
comptime OutLayout = type_of(out_layout)
comptime conv_layout = row_major[CONV]()
comptime ConvLayout = type_of(conv_layout)
def conv_1d_simple(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, ConvLayout, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL ME IN (roughly 14 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p13/p13.mojo
ํ
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[SIZE]())์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น- ์
๋ ฅ์
shared_a[local_i]์, ์ปค๋์shared_b[local_i]์ ๋ก๋ - ๋ฐ์ดํฐ ๋ก๋ ํ
barrier()ํธ์ถ - ๊ฒฝ๊ณ ์์์ ๊ณฑ์ ํฉ์ฐ:
if local_i + j < SIZE global_i < SIZE์ผ ๋๋ง ๊ฒฐ๊ณผ ๊ธฐ๋ก
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p13 --simple
pixi run -e amd p13 --simple
pixi run -e apple p13 --simple
uv run poe p13 --simple
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([5.0, 8.0, 11.0, 14.0, 5.0, 0.0])
์๋ฃจ์
def conv_1d_simple(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, ConvLayout, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var shared_a = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[SIZE]())
var shared_b = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[CONV]())
if global_i < SIZE:
shared_a[local_i] = a[global_i]
if global_i < CONV:
shared_b[local_i] = b[global_i]
barrier()
# Note: this is unsafe as it enforces no guard so could access `shared_a` beyond its bounds
# local_sum = Scalar[dtype](0)
# for j in range(CONV):
# if local_i + j < SIZE:
# local_sum += shared_a[local_i + j] * shared_b[j]
# if global_i < SIZE:
# out[global_i] = local_sum
# Safe and correct:
if global_i < SIZE:
# Note: using `var` allows us to include the type in the type inference
# `out.ElementType` is available in TileTensor
var local_sum: output.ElementType = 0
# Note: `@parameter` decorator unrolls the loop at compile time given `CONV` is a compile-time constant
# See: https://docs.modular.com/mojo/manual/decorators/parameter/#parametric-for-statement
comptime for j in range(CONV):
# Bonus: do we need this check for this specific example with fixed SIZE, CONV
if local_i + j < SIZE:
local_sum += shared_a[local_i + j] * shared_b[j]
output[global_i] = local_sum
๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํด ๊ฒน์น๋ ์์์ ํจ์จ์ ์ผ๋ก ์ ๊ทผํ๋ 1D ํฉ์ฑ๊ณฑ ๊ตฌํ์ ๋๋ค. ๋จ๊ณ๋ณ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค:
๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์
์
๋ ฅ ๋ฐฐ์ด a: [0 1 2 3 4 5]
์ปค๋ b: [0 1 2]
์ฐ์ฐ ๊ณผ์
-
๋ฐ์ดํฐ ๋ก๋ฉ:
shared_a: [0 1 2 3 4 5] // ์ ๋ ฅ ๋ฐฐ์ด shared_b: [0 1 2] // ํฉ์ฑ๊ณฑ ์ปค๋ -
๊ฐ ์์น i์ ๋ํ ํฉ์ฑ๊ณฑ ์ฐ์ฐ:
output[0] = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] = 0*0 + 1*1 + 2*2 = 5 output[1] = a[1]*b[0] + a[2]*b[1] + a[3]*b[2] = 1*0 + 2*1 + 3*2 = 8 output[2] = a[2]*b[0] + a[3]*b[1] + a[4]*b[2] = 2*0 + 3*1 + 4*2 = 11 output[3] = a[3]*b[0] + a[4]*b[1] + a[5]*b[2] = 3*0 + 4*1 + 5*2 = 14 output[4] = a[4]*b[0] + a[5]*b[1] + 0*b[2] = 4*0 + 5*1 + 0*2 = 5 output[5] = a[5]*b[0] + 0*b[1] + 0*b[2] = 5*0 + 0*1 + 0*2 = 0
๊ตฌํ ์์ธ
- ์ค๋ ๋ ์ฐธ์ฌ ๋ฒ์์ ํจ์จ์ฑ:
-
์ ์ ํ ์ค๋ ๋ ๊ฐ๋๊ฐ ์๋ ๋นํจ์จ์ ์ ๊ทผ:
# ๋นํจ์จ์ ๋ฒ์ - ๊ฒฐ๊ณผ๊ฐ ์ฌ์ฉ๋์ง ์์ ์ค๋ ๋๋ ๋ชจ๋ ์ฐ์ฐ ์ํ local_sum = Scalar[dtype](0) for j in range(CONV): if local_i + j < SIZE: local_sum += shared_a[local_i + j] * shared_b[j] # ๋ง์ง๋ง ์ฐ๊ธฐ๋ง ๊ฐ๋ if global_i < SIZE: output[global_i] = local_sum -
ํจ์จ์ ์ด๊ณ ์ฌ๋ฐ๋ฅธ ๊ตฌํ:
if global_i < SIZE: var local_sum: output.element_type = 0 # var๋ก ํ์ ์ถ๋ก ํ์ฉ @parameter # CONV๊ฐ ์์์ด๋ฏ๋ก ์ปดํ์ผ ํ์์ ๋ฃจํ ์ ๊ฐ for j in range(CONV): if local_i + j < SIZE: local_sum += shared_a[local_i + j] * shared_b[j] output[global_i] = local_sum
-
ํต์ฌ์ ์ธ ์ฐจ์ด๋ ๊ฐ๋์ ์์น์
๋๋ค. ๋นํจ์จ์ ๋ฒ์ ์ global_i >= SIZE์ธ ์ค๋ ๋๋ฅผ
ํฌํจํด ๋ชจ๋ ์ค๋ ๋๊ฐ ํฉ์ฑ๊ณฑ ์ฐ์ฐ์ ์ํํ ๋ค, ๋ง์ง๋ง ์ฐ๊ธฐ์์๋ง ๊ฐ๋๋ฅผ
์ ์ฉํฉ๋๋ค. ์ด๋ก ์ธํด:
- ๋ถํ์ํ ์ฐ์ฐ: ์ ํจ ๋ฒ์ ๋ฐ์ ์ค๋ ๋๊ฐ ์ธ๋ชจ์๋ ์์ ์ ์ํ
- ํจ์จ ์ ํ: ์ฌ์ฉ๋์ง ์์ ์ฐ์ฐ์ ์์ ์๋น
- GPU ํ์ฉ๋ ์ ํ: ์๋ฏธ ์๋ ๊ณ์ฐ์ GPU ์ฝ์ด๋ฅผ ๋ญ๋น
ํจ์จ์ ๋ฒ์ ์ ์ ํจํ global_i ๊ฐ์ ๊ฐ์ง ์ค๋ ๋๋ง ์ฐ์ฐ์ ์ํํ๋ฏ๋ก GPU ์์์
๋ ์ ํ์ฉํฉ๋๋ค.
-
์ฃผ์ ๊ตฌํ ํน์ง:
var์output.element_type์ผ๋ก ์ ์ ํ ํ์ ์ถ๋ก@parameter๋ฐ์ฝ๋ ์ดํฐ๋ก ํฉ์ฑ๊ณฑ ๋ฃจํ๋ฅผ ์ปดํ์ผ ํ์์ ์ ๊ฐ- ์๊ฒฉํ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ก ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ ํ๋ณด
- TileTensor์ ํ์ ์์คํ ์ผ๋ก ์ฝ๋ ์์ ์ฑ ํฅ์
-
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ:
- ์ ๋ ฅ ๋ฐฐ์ด๊ณผ ์ปค๋ ๋ชจ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
- ์ค๋ ๋๋น ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ 1ํ ๋ก๋
- ๋ก๋ํ ๋ฐ์ดํฐ์ ํจ์จ์ ์ฌ์ฌ์ฉ
-
์ค๋ ๋ ์กฐ์จ:
barrier()๋ก ๋ชจ๋ ๋ฐ์ดํฐ ๋ก๋๊ฐ ๋๋ ํ ์ฐ์ฐ ์์์ ๋ณด์ฅ- ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ์์ ํ๋๋ฅผ ๊ณ์ฐ
- ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ ์ง
-
์ฑ๋ฅ ์ต์ ํ:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ๋น ๋ฅธ ๋ฐ์ดํฐ ์ ๊ทผ
- ๋ฉ์ธ ์ฐ์ฐ ๋ฃจํ์์ ์ค๋ ๋ ๋ถ๊ธฐ ํํผ
@parameter๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ํตํ ๋ฃจํ ์ ๊ฐ
๋ธ๋ก ๊ฒฝ๊ณ ๋ฒ์
1D TileTensor a์ 1D TileTensor b์ 1D ํฉ์ฑ๊ณฑ์ ๊ณ์ฐํ์ฌ 1D TileTensor
output์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์ค๋ ๋๋น ์ ์ญ ์ฝ๊ธฐ 2ํ, ์ ์ญ ์ฐ๊ธฐ 1ํ๋ง ํ์ํฉ๋๋ค.
๊ตฌ์ฑ
- ์
๋ ฅ ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE_2 = 15 - ์ปค๋ ํฌ๊ธฐ:
CONV_2 = 4 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ๋ธ๋ก ์: 2
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ์
๋ ฅ์ฉ
TPB + CONV_2 - 1๊ฐ
์ฐธ๊ณ :
- ํ์ฅ ๋ก๋ฉ: ๊ฒฝ๊ณ ๊ฒน์นจ ์์ญ์ ๊ณ ๋ ค
- ๋ธ๋ก ๊ฐ์ฅ์๋ฆฌ: ๋ธ๋ก ๊ฒฝ๊ณ๋ฅผ ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ํจ์จ์ ํ์ฉ
- ๋๊ธฐํ: ์ ์ ํ ์ค๋ ๋ ๊ฐ ์กฐ์จ
์์ฑํ ์ฝ๋
comptime SIZE_2 = 15
comptime CONV_2 = 4
comptime BLOCKS_PER_GRID_2 = (2, 1)
comptime THREADS_PER_BLOCK_2 = (TPB, 1)
comptime in_2_layout = row_major[SIZE_2]()
comptime In2Layout = type_of(in_2_layout)
comptime out_2_layout = row_major[SIZE_2]()
comptime Out2Layout = type_of(out_2_layout)
comptime conv_2_layout = row_major[CONV_2]()
comptime Conv2Layout = type_of(conv_2_layout)
def conv_1d_block_boundary(
output: TileTensor[mut=True, dtype, Out2Layout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, In2Layout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, Conv2Layout, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL ME IN (roughly 18 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p13/p13.mojo
ํ
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB + CONV_2 - 1]())์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น- ๋ฉ์ธ ๋ฐ์ดํฐ ๋ก๋:
shared_a[local_i] = a[global_i] - ๊ฒฝ๊ณ ๋ฐ์ดํฐ ๋ก๋:
if local_i < CONV_2 - 1์ผ ๋ ๋ค์ ๋ธ๋ก์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ - ์ปค๋ ๋ก๋:
shared_b[local_i] = b[local_i] - ์
๋ ฅ ๋ฒ์ ์์์ ํฉ์ฐ:
if global_i + j < SIZE_2
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p13 --block-boundary
pixi run -e amd p13 --block-boundary
pixi run -e apple p13 --block-boundary
uv run poe p13 --block-boundary
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([14.0, 20.0, 26.0, 32.0, 38.0, 44.0, 50.0, 56.0, 62.0, 68.0, 74.0, 80.0, 41.0, 14.0, 0.0])
์๋ฃจ์
def conv_1d_block_boundary(
output: TileTensor[mut=True, dtype, Out2Layout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, In2Layout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, Conv2Layout, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# first: need to account for padding
var shared_a = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB + CONV_2 - 1]())
var shared_b = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[CONV_2]())
if global_i < SIZE_2:
shared_a[local_i] = a[global_i]
else:
shared_a[local_i] = 0
# second: load elements needed for convolution at block boundary
if local_i < CONV_2 - 1:
# indices from next block
var next_idx = global_i + TPB
if next_idx < SIZE_2:
shared_a[TPB + local_i] = a[next_idx]
else:
# Initialize out-of-bounds elements to 0 to avoid reading from uninitialized memory
# which is an undefined behavior
shared_a[TPB + local_i] = 0
if local_i < CONV_2:
shared_b[local_i] = b[local_i]
barrier()
if global_i < SIZE_2:
var local_sum: output.ElementType = 0
comptime for j in range(CONV_2):
if global_i + j < SIZE_2:
local_sum += shared_a[local_i + j] * shared_b[j]
output[global_i] = local_sum
ํ์ฅ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํด ๋ธ๋ก ๊ฒฝ๊ณ๋ฅผ ๋๋ 1D ํฉ์ฑ๊ณฑ์ ์ฒ๋ฆฌํ๋ ์๋ฃจ์ ์ ๋๋ค. ์์ธํ ๋ถ์ํด ๋ณด๊ฒ ์ต๋๋ค:
๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์๊ณผ ํฌ๊ธฐ ๊ณ์ฐ
ํ
์คํธ ๊ตฌ์ฑ:
- ์ ์ฒด ๋ฐฐ์ด ํฌ๊ธฐ: SIZE_2 = 15
- ๊ทธ๋ฆฌ๋: 2 ๋ธ๋ก ร 8 ์ค๋ ๋
- ํฉ์ฑ๊ณฑ ์ปค๋: CONV_2 = 4
Block 0 ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: [0 1 2 3 4 5 6 7|8 9 10] // TPB(8) + (CONV_2-1)(3) ํจ๋ฉ
Block 1 ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: [8 9 10 11 12 13 14 0|0 0 0] // ๋ ๋ฒ์งธ ๋ธ๋ก. ๋ฐ์ดํฐ(7) + ๊ทธ๋ฆฌ๋ ์ฑ์์ฉ ํจ๋ฉ(1) + (CONV_2-1)(3) ํจ๋ฉ
ํฌ๊ธฐ ๊ณ์ฐ:
- ๋ฉ์ธ ๋ฐ์ดํฐ: TPB๊ฐ (8)
- ๊ฒน์นจ ์์ญ: CONV_2 - 1๊ฐ (4 - 1 = 3)
- ํฉ๊ณ: TPB + CONV_2 - 1 = 8 + 4 - 1 = 11๊ฐ
๊ตฌํ ์์ธ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น:
# ํฉ์ฑ๊ณฑ ์๋์ฐ์ ํ์ํ ํจ๋ฉ์ ๋จผ์ ๊ณ ๋ ค shared_a = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB + CONV_2 - 1]()) shared_b = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[CONV_2]())์ด๋ ๊ฒ ํ๋ฉด ๋ธ๋ก ๋ฐ์ดํฐ์ ๊ฒน์นจ ์์ญ์ ๋ชจ๋ ๋ด๊ธฐ์ ์ถฉ๋ถํ ๊ณต๊ฐ์ด ํ๋ณด๋ฉ๋๋ค.
-
๋ฐ์ดํฐ ๋ก๋ฉ ์ ๋ต:
# ๋ฉ์ธ ๋ธ๋ก ๋ฐ์ดํฐ if global_i < SIZE_2: shared_a[local_i] = a[global_i] else: shared_a[local_i] = 0 # ๋ค์ ๋ธ๋ก์ ๊ฒฝ๊ณ ๋ฐ์ดํฐ if local_i < CONV_2 - 1: next_idx = global_i + TPB if next_idx < SIZE_2: shared_a[TPB + local_i] = a[next_idx] else: # ๋ฒ์ ๋ฐ ์์๋ฅผ 0์ผ๋ก ์ด๊ธฐํํ์ฌ # ๋ฏธ์ ์ ๋์์ ์ ๋ฐํ๋ ์ด๊ธฐํ๋์ง ์์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ๋ฅผ ๋ฐฉ์ง shared_a[TPB + local_i] = 0local_i < CONV_2 - 1์ธ ์ค๋ ๋๋ง ๊ฒฝ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ก๋- ๋ถํ์ํ ์ค๋ ๋ ๋ถ๊ธฐ ๋ฐฉ์ง
- ๋ฉ์ธ ๋ฐ์ดํฐ ๋ก๋์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ์ ์ง
- ๋ฒ์ ๋ฐ ์์๋ฅผ ๋ช ์์ ์ผ๋ก 0์ผ๋ก ์ด๊ธฐํํ์ฌ ๋ฏธ์ ์ ๋์ ๋ฐฉ์ง
-
์ปค๋ ๋ก๋ฉ:
if local_i < b_size: shared_b[local_i] = b[local_i]- ์ค๋ ๋๋น 1ํ ๋ก๋
- ์ปค๋ ํฌ๊ธฐ๋ก ๋ฒ์ ์ ํ
-
ํฉ์ฑ๊ณฑ ์ฐ์ฐ:
if global_i < SIZE_2: var local_sum: output.element_type = 0 @parameter for j in range(CONV_2): if global_i + j < SIZE_2: local_sum += shared_a[local_i + j] * shared_b[j]@parameter๋ก ์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐoutput.element_type์ผ๋ก ์ ์ ํ ํ์ ์ถ๋ก- ์๋ฏธ์ ์ผ๋ก ์ฌ๋ฐ๋ฅธ ๊ฒฝ๊ณ ๊ฒ์ฌ: ์ ํจํ ์ ๋ ฅ ์์น์์๋ง ํฉ์ฑ๊ณฑ ๊ณ์ฐ
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ถ์
-
Block 0 ์ ๊ทผ ํจํด:
Thread 0: [0 1 2 3] ร [0 1 2 3] Thread 1: [1 2 3 4] ร [0 1 2 3] Thread 2: [2 3 4 5] ร [0 1 2 3] ... Thread 7: [7 8 9 10] ร [0 1 2 3] // ๊ฒน์นจ ์์ญ ๋ฐ์ดํฐ ์ฌ์ฉ -
Block 1 ์ ๊ทผ ํจํด: Thread 4๋ถํฐ๋
global_i + j < SIZE_2๊ฐFalse๊ฐ ๋์ด ํด๋น ๋ฐ๋ณต์ ๊ฑด๋๋ฐ๋ ์ ์ ์ฃผ๋ชฉํ์ธ์.Thread 0: [8 9 10 11] ร [0 1 2 3] Thread 1: [9 10 11 12] ร [0 1 2 3] ... Thread 4: [12 13 14] ร [0 1 2] // ๋๋ถ๋ถ ์ ๋ก ํจ๋ฉ Thread 5: [13 14] ร [0 1] Thread 6: [14] ร [0] Thread 7: ๊ฑด๋๋ // ๋ชจ๋ j์ ๋ํด global_i + j < SIZE_2๊ฐ false, ์ฐ์ฐ ์์
์ฑ๋ฅ ์ต์ ํ
-
๋ฉ๋ชจ๋ฆฌ ๋ณํฉ:
- ๋ฉ์ธ ๋ฐ์ดํฐ ๋ก๋: ์ธ์ ์ค๋ ๋๊ฐ ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผ
- ๊ฒฝ๊ณ ๋ฐ์ดํฐ: ํ์ํ ์ค๋ ๋๋ง ์ฐธ์ฌ
- ๋จ์ผ ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ ์ง์
-
์ค๋ ๋ ๋ถ๊ธฐ ์ต์ํ:
- ๋ฉ์ธ ๋ก๋ฉ๊ณผ ๊ฒฝ๊ณ ๋ก๋ฉ์ ๊น๋ํ ๋ถ๋ฆฌ
- ์ํ ๋ด ๊ท ์ผํ ์ฐ์ฐ ํจํด
- ํจ์จ์ ์ธ ๊ฒฝ๊ณ ๊ฒ์ฌ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ:
- ๋ธ๋ก ๊ฒฝ๊ณ ์ฒ๋ฆฌ์ ์ต์ ํ๋ ํฌ๊ธฐ ์ค์
- ์ ๊ทผ ํจํด์์ ๋ฑ ํฌ ์ถฉ๋ ์์
- ๋ก๋ํ ๋ฐ์ดํฐ์ ํจ์จ์ ์ฌ์ฌ์ฉ
-
๊ฒฝ๊ณ ์ฒ๋ฆฌ:
- ๋ฒ์ ๋ฐ ์์๋ฅผ ๋ช ์์ ์ผ๋ก 0์ผ๋ก ์ค์ ํ์ฌ ์ด๊ธฐํ๋์ง ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ ๋ฐฉ์ง
global_i + j < SIZE_2๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์๋ ์ค์ ์ ๋ ฅ ๋ฒ์ ๊ธฐ์ค์ ๊ฒฝ๊ณ ๊ฒ์ฌ- ๋ถํ์ํ ์ฐ์ฐ ์์ด ์ ์ ํ ์ฃ์ง ์ผ์ด์ค ์ฒ๋ฆฌ
๊ฒฝ๊ณ ์กฐ๊ฑด ๊ฐ์
์ด ์๋ฃจ์
์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์๋ฅผ ํ์ธํ๋ ๋์ if global_i + j < SIZE_2:๋ฅผ
์ฌ์ฉํฉ๋๋ค. ์ด ํจํด์:
- ์ํ์ ์ผ๋ก ์ ํ: ์ ๋ ฅ ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ์กด์ฌํ๋ ์์น์์๋ง ํฉ์ฑ๊ณฑ ๊ณ์ฐ
- ๋ ํจ์จ์ : ์ ๋ ฅ ๋ฐฐ์ด์ ๋์ด์ ์์น์ ๋ํ ๋ถํ์ํ ์ฐ์ฐ ํํผ
- ๋ ์์ : ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๋ก ํจ๋ฉ ๋์์ ์์กดํ์ง ์์
์ด ๊ตฌํ์ ๋ธ๋ก ๊ฐ ํฉ์ฑ๊ณฑ์ ํจ์จ์ ์ผ๋ก ์ํํ๋ฉด์ ๋ค์์ ์ ์งํฉ๋๋ค:
- ์ ์ ํ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํตํ ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ
- ์ต์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ํตํ ๋์ ์ฑ๋ฅ
- TileTensor ์ถ์ํ๋ฅผ ํ์ฉํ ๊น๋ํ ์ฝ๋ ๊ตฌ์กฐ
- ์ต์ํ์ ๋๊ธฐํ ์ค๋ฒํค๋
- ์ํ์ ์ผ๋ก ๊ฑด์ ํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
Puzzle 14: ๋์ ํฉ
๊ฐ์
๋์ ํฉ(prefix sum, scan ์ด๋ผ๊ณ ๋ ํฉ๋๋ค)์ ์ํ์ค์ ๊ฐ์ ์ฐจ๋ก๋ก ๋ํด ๋๊ฐ๋ ๊ธฐ๋ณธ์ ์ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๋๋ค. ์ ๋ ฌ ์๊ณ ๋ฆฌ์ฆ๋ถํฐ ๊ณผํ ์๋ฎฌ๋ ์ด์ ๊น์ง ์๋ง์ ๋ณ๋ ฌ ์์ฉ์ ํต์ฌ์ ์๋ฆฌํ๊ณ ์์ผ๋ฉฐ, ์ซ์ ์ํ์ค๋ฅผ ๋์ ํฉ๊ณ๋ก ๋ณํํ๋ ์ญํ ์ ํฉ๋๋ค. ์์ฐจ์ ์ผ๋ก ๊ณ์ฐํ๊ธฐ๋ ๊ฐ๋จํ์ง๋ง, GPU์์ ํจ์จ์ ์ผ๋ก ๋ง๋ค๋ ค๋ฉด ๊ธฐ๋ฐํ ๋ณ๋ ฌ์ ์ฌ๊ณ ๊ฐ ํ์ํฉ๋๋ค!
1D TileTensor a์ ๋ํด ๋์ ํฉ์ ๊ณ์ฐํ๊ณ ๊ฒฐ๊ณผ๋ฅผ 1D TileTensor output์
์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : a์ ํฌ๊ธฐ๊ฐ ๋ธ๋ก ํฌ๊ธฐ๋ณด๋ค ํฐ ๊ฒฝ์ฐ, ๊ฐ ๋ธ๋ก์ ํฉ๊ณ๋ง ์ ์ฅํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ๋ก๊ทธ ๋ณต์ก๋๋ฅผ ๊ฐ์ง ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ๋ ฅ ํจํด
- ๋ค๋จ๊ณ ์ฐ์ฐ ์ ๋ต
ํต์ฌ ํต์ฐฐ์ ์์ฐจ ์ฐ์ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ ํจ์จ์ ์ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
์๋ฅผ ๋ค์ด, ์ ๋ ฅ ์ํ์ค \([3, 1, 4, 1, 5, 9]\) ๊ฐ ์ฃผ์ด์ง๋ฉด, ๋์ ํฉ์ ๋ค์๊ณผ ๊ฐ์ด ๋ง๋ค์ด์ง๋๋ค:
- \([3]\) (์ฒซ ๋ฒ์งธ ์์ ๊ทธ๋๋ก)
- \([3, 4]\) (3 + 1)
- \([3, 4, 8]\) (์ด์ ํฉ + 4)
- \([3, 4, 8, 9]\) (์ด์ ํฉ + 1)
- \([3, 4, 8, 9, 14]\) (์ด์ ํฉ + 5)
- \([3, 4, 8, 9, 14, 23]\) (์ด์ ํฉ + 9)
์ํ์ ์ผ๋ก, ์ํ์ค \([x_0, x_1, โฆ, x_n]\) ์ ๋์ ํฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค: \[ [x_0, x_0+x_1, x_0+x_1+x_2, โฆ, \sum_{i=0}^n x_i] \]
์์ฐจ ์๊ณ ๋ฆฌ์ฆ์ด๋ผ๋ฉด \(O(n)\) ๋จ๊ณ๊ฐ ํ์ํ๊ฒ ์ง๋ง, ์ฌ๊ธฐ์๋ ์๋ฆฌํ 2๋จ๊ณ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก \(O(\log n)\) ๋จ๊ณ๋ง์ ์๋ฃํฉ๋๋ค! ์์ ์ ๋๋ฉ์ด์ ์์ ์ด ๊ณผ์ ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด ํผ์ฆ์ ๊ฐ๋ ์ ๋จ๊ณ์ ์ผ๋ก ์ตํ ์ ์๋๋ก ๋ ํํธ๋ก ๋๋ฉ๋๋ค:
-
๐ฐ ๊ธฐ๋ณธ ๋ฒ์ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ค์ด๊ฐ๋ ๋จ์ผ ๋ธ๋ก ๊ตฌํ๋ถํฐ ์์ํฉ๋๋ค. ํต์ฌ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์๋ฆฌ๋ฅผ ํ์ ํ๋ ๋ฐ ์ข์ต๋๋ค.
-
โญ ์์ฑ ๋ฒ์ ์ด์ด์ ์ฌ๋ฌ ๋ธ๋ก์ ๊ฑธ์น๋ ํฐ ๋ฐฐ์ด์ ์ฒ๋ฆฌํ๋ ๋ ๊น๋ค๋ก์ด ๊ฒฝ์ฐ์ ๋์ ํฉ๋๋ค. ๋ธ๋ก ๊ฐ ์กฐ์จ์ด ํ์ํฉ๋๋ค.
๊ฐ ๋ฒ์ ์ ์ด์ ๋ฒ์ ์์ ์์ ์ฌ๋ฆฌ๋ ๋ฐฉ์์ผ๋ก, ๋ณ๋ ฌ ๋์ ํฉ ์ฐ์ฐ์ ๋ํ ์ดํด๋ฅผ ๊น์ด ์๊ฒ ๋ฐ์ ์์ผ ์ค๋๋ค. ๊ธฐ๋ณธ ๋ฒ์ ์์ ํต์ฌ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ง๊ณ , ์์ฑ ๋ฒ์ ์์๋ ๋ ํฐ ๋ฐ์ดํฐ์ ์ผ๋ก ํ์ฅํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค โ ์ค์ GPU ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ฃผ ๋ง์ฃผ์น๋ ๊ณผ์ ์ ๋๋ค.
๊ธฐ๋ณธ ๋ฒ์
1D TileTensor a์ ๋ํด ๋์ ํฉ์ ๊ณ์ฐํ๊ณ ๊ฒฐ๊ณผ๋ฅผ 1D TileTensor output์
์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : a์ ํฌ๊ธฐ๊ฐ ๋ธ๋ก ํฌ๊ธฐ๋ณด๋ค ํฐ ๊ฒฝ์ฐ, ๊ฐ ๋ธ๋ก์ ํฉ๊ณ๋ง ์ ์ฅํฉ๋๋ค.
๊ตฌ์ฑ
- ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE = 8 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ๋ธ๋ก ์: 1
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
TPB๊ฐ ์์
์ฐธ๊ณ :
- ๋ฐ์ดํฐ ๋ก๋ฉ: ๊ฐ ์ค๋ ๋๊ฐ TileTensor ์ ๊ทผ์ ํตํด ์์ ํ๋๋ฅผ ๋ก๋
- ๋ฉ๋ชจ๋ฆฌ ํจํด: address_space๋ฅผ ์ง์ ํ TileTensor๋ก ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ
- ์ค๋ ๋ ๋๊ธฐํ: ์ฐ์ฐ ๋จ๊ณ ๊ฐ ์กฐ์จ
- ์ ๊ทผ ํจํด: ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ๋ณ๋ ฌ ์ฐ์ฐ
- ํ์ ์์ ์ฑ: TileTensor์ ํ์ ์์คํ ํ์ฉ
์์ฑํ ์ฝ๋
comptime TPB = 8
comptime SIZE = 8
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
def prefix_sum_simple(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL ME IN (roughly 18 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p14/p14.mojo
ํ
- ๋ฐ์ดํฐ๋ฅผ
shared[local_i]์ ๋ก๋ offset = 1์์ ์์ํด ๋งค ๋จ๊ณ๋ง๋ค 2๋ฐฐ๋ก ์ฆ๊ฐlocal_i >= offset์ธ ์์์ ๋ํด ๋ง์ ์ํ- ๊ฐ ๋จ๊ณ ์ฌ์ด์
barrier()ํธ์ถ
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p14 --simple
pixi run -e amd p14 --simple
pixi run -e apple p14 --simple
uv run poe p14 --simple
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: DeviceBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([0.0, 1.0, 3.0, 6.0, 10.0, 15.0, 21.0, 28.0])
์๋ฃจ์
def prefix_sum_simple(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
if global_i < size:
shared[local_i] = a[global_i]
barrier()
var offset = 1
for i in range(Int(log2(Scalar[dtype](TPB)))):
var current_val: output.ElementType = 0
if local_i >= offset and local_i < size:
current_val = shared[local_i - offset] # read
barrier()
if local_i >= offset and local_i < size:
shared[local_i] += current_val
barrier()
offset *= 2
if global_i < size:
output[global_i] = shared[local_i]
๋ณ๋ ฌ (ํฌํจ) ๋์ ํฉ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์๊ณผ ๊ฐ์ด ๋์ํฉ๋๋ค:
์ค์ ๋ฐ ๊ตฌ์ฑ
TPB(๋ธ๋ก๋น ์ค๋ ๋ ์) = 8SIZE(๋ฐฐ์ด ํฌ๊ธฐ) = 8
๊ฒฝ์ ์ํ ๋ฐฉ์ง
์ด ์๊ณ ๋ฆฌ์ฆ์ ๋ช ์์ ๋๊ธฐํ๋ฅผ ํตํด ์ฝ๊ธฐ-์ฐ๊ธฐ ์ถฉ๋์ ๋ฐฉ์งํฉ๋๋ค:
- ์ฝ๊ธฐ ๋จ๊ณ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋จผ์ ํ์ํ ๊ฐ์ ๋ก์ปฌ ๋ณ์
current_val์ ์ฝ์ด๋ - ๋๊ธฐํ:
barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ๊ฐ ์๋ฃ๋ ํ์์ผ ์ฐ๊ธฐ๊ฐ ์์๋๋๋ก ๋ณด์ฅ - ์ฐ๊ธฐ ๋จ๊ณ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ณ์ฐ๋ ๊ฐ์ ์์ ํ๊ฒ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ก
์ด๋ ๊ฒ ํ๋ฉด ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น๋ฅผ ์ฝ๊ณ ์ธ ๋ ๋ฐ์ํ๋ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.ในใ
๋์์ ์ ๊ทผ: ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋๋ธ ๋ฒํผ๋ง ์ ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ 2๋ฐฐ๋ก ํ ๋นํ ๋ค, ํ ๋ฒํผ์์ ์ฝ๊ณ ๋ค๋ฅธ ๋ฒํผ์ ์ฐ๋ ๊ฒ์ ๋ฒ๊ฐ์ ์ํํ๋ ๋ฐฉ์์ ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ๊ฒฝ์ ์ํ๋ฅผ ์์ ํ ์ ๊ฑฐํ์ง๋ง, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋์ด๋๊ณ ๋ณต์ก๋๊ฐ ์ฌ๋ผ๊ฐ๋๋ค. ํ์ต ๋ชฉ์ ์ผ๋ก๋ ์ดํดํ๊ธฐ ๋ ์ฌ์ด ๋ช ์์ ๋๊ธฐํ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
์ค๋ ๋ ๋งคํ
thread_idx.x: \([0, 1, 2, 3, 4, 5, 6, 7]\) (local_i)block_idx.x: \([0, 0, 0, 0, 0, 0, 0, 0]\)global_i: \([0, 1, 2, 3, 4, 5, 6, 7]\) (block_idx.x * TPB + thread_idx.x)
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ด๊ธฐ ๋ก๋
Threads: Tโ Tโ Tโ Tโ Tโ Tโ
Tโ Tโ
Input array: [0 1 2 3 4 5 6 7]
shared: [0 1 2 3 4 5 6 7]
โ โ โ โ โ โ โ โ
Tโ Tโ Tโ Tโ Tโ Tโ
Tโ Tโ
Offset = 1: ์ฒซ ๋ฒ์งธ ๋ณ๋ ฌ ๋จ๊ณ
ํ์ฑ ์ค๋ ๋: \(T_1 \ldots T_7\) (local_i โฅ 1์ธ ์ค๋ ๋)
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ
reads shared[4] = 4
Tโ reads shared[1] = 1 Tโ reads shared[5] = 5
Tโ reads shared[2] = 2 Tโ reads shared[6] = 6
Tโ reads shared[3] = 3
๋๊ธฐํ: barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ
์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ํ์ฌ ์์น์ ๋ํจ:
Before: [0 1 2 3 4 5 6 7]
Add: +0 +1 +2 +3 +4 +5 +6
| | | | | | |
Result: [0 1 3 5 7 9 11 13]
โ โ โ โ โ โ โ
Tโ Tโ Tโ Tโ Tโ
Tโ Tโ
Offset = 2: ๋ ๋ฒ์งธ ๋ณ๋ ฌ ๋จ๊ณ
ํ์ฑ ์ค๋ ๋: \(T_2 \ldots T_7\) (local_i โฅ 2์ธ ์ค๋ ๋)
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ
reads shared[3] = 5
Tโ reads shared[1] = 1 Tโ reads shared[4] = 7
Tโ reads shared[2] = 3 Tโ reads shared[5] = 9
๋๊ธฐํ: barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ
์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ๋ํจ:
Before: [0 1 3 5 7 9 11 13]
Add: +0 +1 +3 +5 +7 +9
| | | | | |
Result: [0 1 3 6 10 14 18 22]
โ โ โ โ โ โ
Tโ Tโ Tโ Tโ
Tโ Tโ
Offset = 4: ์ธ ๋ฒ์งธ ๋ณ๋ ฌ ๋จ๊ณ
ํ์ฑ ์ค๋ ๋: \(T_4 \ldots T_7\) (local_i โฅ 4์ธ ์ค๋ ๋)
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ reads shared[2] = 3
Tโ
reads shared[1] = 1 Tโ reads shared[3] = 6
๋๊ธฐํ: barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ
์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ๋ํจ:
Before: [0 1 3 6 10 14 18 22]
Add: +0 +1 +3 +6
| | | |
Result: [0 1 3 6 10 15 21 28]
โ โ โ โ
Tโ Tโ
Tโ Tโ
์ต์ข ๊ฒฐ๊ณผ๋ฅผ output์ ๊ธฐ๋ก
Threads: Tโ Tโ Tโ Tโ Tโ Tโ
Tโ Tโ
global_i: 0 1 2 3 4 5 6 7
output: [0 1 3 6 10 15 21 28]
โ โ โ โ โ โ โ โ
Tโ Tโ Tโ Tโ Tโ Tโ
Tโ Tโ
์ฃผ์ ๊ตฌํ ์์ธ
๋๊ธฐํ ํจํด: ๊ฐ ๋ฐ๋ณต์ ์๊ฒฉํ ์ฝ๊ธฐ โ ๋๊ธฐํ โ ์ฐ๊ธฐ ํจํด์ ๋ฐ๋ฆ ๋๋ค:
var current_val: out.element_type = 0- ๋ก์ปฌ ๋ณ์ ์ด๊ธฐํcurrent_val = shared[local_i - offset]- ์ฝ๊ธฐ ๋จ๊ณ (์กฐ๊ฑด ์ถฉ์กฑ ์)barrier()- ๊ฒฝ์ ์ํ ๋ฐฉ์ง๋ฅผ ์ํ ๋ช ์์ ๋๊ธฐํshared[local_i] += current_val- ์ฐ๊ธฐ ๋จ๊ณ (์กฐ๊ฑด ์ถฉ์กฑ ์)barrier()- ๋ค์ ๋ฐ๋ณต ์ ๋๊ธฐํ
๊ฒฝ์ ์ํ ๋ฐฉ์ง: ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ๋ช ์์ ์ผ๋ก ๋ถ๋ฆฌํ์ง ์์ผ๋ฉด ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผํ์ฌ ๋ฏธ์ ์ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ช ์์ ๋๊ธฐํ๋ฅผ ์ฌ์ฉํ 2๋จ๊ณ ์ ๊ทผ ๋ฐฉ์์ด ์ ํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ: ์๊ณ ๋ฆฌ์ฆ์ ๋ค์์ ํตํด ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ์ ์ ์งํฉ๋๋ค:
if local_i >= offset and local_i < size๋ก ๊ฒฝ๊ณ ๊ฒ์ฌ- ์์ ๋ณ์์ ์ ์ ํ ์ด๊ธฐํ
- ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๋ ์กฐ์จ๋ ์ ๊ทผ ํจํด
์ด ์๋ฃจ์
์ barrier()๋ฅผ ์ฌ์ฉํด ๋จ๊ณ ๊ฐ ์ฌ๋ฐ๋ฅธ ๋๊ธฐํ๋ฅผ ๋ณด์ฅํ๊ณ ,
if global_i < size๋ก ๋ฐฐ์ด ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ์ต์ข
๊ฒฐ๊ณผ๋ ๊ฐ ์์
\(i\)๊ฐ \(\sum_{j=0}^{i} a[j]\) ๋ฅผ ํฌํจํ๋ ํฌํจ ๋์ ํฉ์
๋๋ค.
์์ฑ ๋ฒ์
1D TileTensor a์ ๋ํด ๋์ ํฉ์ ๊ณ์ฐํ๊ณ ๊ฒฐ๊ณผ๋ฅผ 1D TileTensor output์
์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
์ฐธ๊ณ : a์ ํฌ๊ธฐ๊ฐ ๋ธ๋ก ํฌ๊ธฐ๋ณด๋ค ํฐ ๊ฒฝ์ฐ, ์ฌ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ป์ผ๋ ค๋ฉด ์ฌ๋ฌ ๋ธ๋ก
๊ฐ ๋๊ธฐํ๊ฐ ํ์ํฉ๋๋ค.
๊ตฌ์ฑ
- ๋ฐฐ์ด ํฌ๊ธฐ:
SIZE_2 = 15 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 8 - ๋ธ๋ก ์: 2
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก๋น
TPB๊ฐ ์์
์ฐธ๊ณ :
- ๋ค์ค ๋ธ๋ก: ์ ๋ ฅ ๋ฐฐ์ด์ด ํ๋์ ๋ธ๋ก๋ณด๋ค ํด ๋๋ ๋ค๋จ๊ณ ์ ๊ทผ์ด ํ์
- ๋ธ๋ก ๋ ๋ฒจ ๋๊ธฐํ: ๋ธ๋ก ๋ด์์๋
barrier()๋ก ์ค๋ ๋๋ฅผ ๋๊ธฐํ - ํธ์คํธ ๋ ๋ฒจ ๋๊ธฐํ: Mojo์
DeviceContext๊ฐ ์ปค๋ ์คํ ์์๋ฅผ ๋ณด์ฅํ๋ฏ๋ก, ์ปค๋๋ค์ ํ์ ๋ฃ์ ์์๋๋ก ์คํ๋๊ณ ์ด์ ์ปค๋์ด ๋๋์ผ ๋ค์์ด ์์๋ฉ๋๋ค. ํธ์คํธ์์ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ธฐ ์ ์ctx.synchronize()๋ก ๋ชจ๋ GPU ์์ ์๋ฃ๋ฅผ ํ์ธํด์ผ ํ ์ ์์ต๋๋ค. - ๋ณด์กฐ ์ ์ฅ์: ๋ธ๋ก ๊ฐ ํต์ ์ ์ํด ๋ธ๋ก ํฉ๊ณ๋ฅผ ์ ์ฅํ ์ถ๊ฐ ๊ณต๊ฐ ์ฌ์ฉ
์์ฑํ ์ฝ๋
๋ฉํฐ ๋ธ๋ก ๋์ ํฉ์ ์ํด ๋ ๊ฐ์ ๋ณ๋ ์ปค๋ ํจ์๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค:
- ์ฒซ ๋ฒ์งธ ์ปค๋ (
prefix_sum_local_phase): ๊ฐ ๋ธ๋ก ๋ด์์ ๋ก์ปฌ ๋์ ํฉ์ ๊ณ์ฐํ๊ณ ๋ธ๋ก ํฉ๊ณ๋ฅผ ์ ์ฅ - ๋ ๋ฒ์งธ ์ปค๋ (
prefix_sum_block_sum_phase): ์ด์ ๋ธ๋ก์ ํฉ๊ณ๋ฅผ ํ์ ๋ธ๋ก์ ์์์ ๋ํจ
๋ฉ์ธ ํจ์๊ฐ ์ด ์ปค๋๋ค ์ฌ์ด์ ํ์ํ ํธ์คํธ ์ธก ๋๊ธฐํ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
comptime SIZE_2 = 15
comptime BLOCKS_PER_GRID_2 = (2, 1)
comptime THREADS_PER_BLOCK_2 = (TPB, 1)
comptime EXTENDED_SIZE = SIZE_2 + 2 # up to 2 blocks
comptime layout_2 = row_major[SIZE_2]()
comptime Layout2Type = type_of(layout_2)
comptime extended_layout = row_major[EXTENDED_SIZE]()
comptime ExtendedLayoutType = type_of(extended_layout)
# Kernel 1: Compute local prefix sums and store block sums in out
def prefix_sum_local_phase(
output: TileTensor[mut=True, dtype, ExtendedLayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, Layout2Type, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL ME IN (roughly 20 lines)
# Kernel 2: Add block sums to their respective blocks
def prefix_sum_block_sum_phase(
output: TileTensor[mut=True, dtype, ExtendedLayoutType, MutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 3 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p14/p14.mojo
์ด ํผ์ฆ์ ํต์ฌ์ barrier๊ฐ ๋ธ๋ก ๋ด๋ถ์ ์ค๋ ๋๋ง ๋๊ธฐํํ๋ฉฐ, ๋ธ๋ก ๊ฐ ๋๊ธฐํ๋ ํ์ง ์๋๋ค๋ ์ ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค. ๋ธ๋ก ๊ฐ ๋๊ธฐํ๋ฅผ ์ํด์๋ ๋๋ฐ์ด์ค์์ ์์ฐจ์ ์ผ๋ก ์คํ๋๋ ์ฌ๋ฌ ์ปค๋์ ํ์ ๋ฃ์ด์ผ ํฉ๋๋ค:
# Phase 1: Local prefix sums
ctx.enqueue_function[
prefix_sum_local_phase, prefix_sum_local_phase
](
out_tensor,
a_tensor,
size,
grid_dim=BLOCKS_PER_GRID_2,
block_dim=THREADS_PER_BLOCK_2,
)
# Phase 2: Add block sums
ctx.enqueue_function[
prefix_sum_block_sum_phase, prefix_sum_block_sum_phase
](
out_tensor,
size,
grid_dim=BLOCKS_PER_GRID_2,
block_dim=THREADS_PER_BLOCK_2,
)
๋ ์ปค๋์ด ์์ฐจ์ ์ผ๋ก ํ์ ๋ค์ด๊ฐ์ง๋ง, out_tensor๋ ๋ ์ปค๋์ ์์
์ด ๋ชจ๋ ๋๋
๋๊น์ง ํธ์คํธ๋ก ์ ์ก๋์ง ์๋๋ค๋ ์ ์ ์ฃผ๋ชฉํ์ธ์. Mojo์ DeviceContext๊ฐ ๋จ์ผ
์คํ ์คํธ๋ฆผ์ ์ฌ์ฉํ๋ฏ๋ก, ํ์ ๋ฃ์ ๋ชจ๋ ์ปค๋์ด ์์ฐจ์ ์ผ๋ก ์คํ๋ฉ๋๋ค.
ํธ์คํธ์์ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ธฐ ์ ์ ๋ชจ๋ GPU ์์
์ ์๋ฃ๋ฅผ ๋ช
์์ ์ผ๋ก ๋๊ธฐํ๋ ค๋ฉด
ctx.synchronize()๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ
1. ๊ธฐ๋ณธ ๋์ ํฉ ์์ ์์ ์ฌ๋ฆฌ๊ธฐ
๐ฐ ๊ธฐ๋ณธ ๋ฒ์ ์์ ๋จ์ผ ๋ธ๋ก ๋์ ํฉ ๊ตฌํ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ์ด ์ ๊ทผ๋ฒ์ ์ฌ๋ฌ ๋ธ๋ก์์ ๋์ํ๋๋ก ํ์ฅํด์ผ ํฉ๋๋ค:
๊ธฐ๋ณธ ๋ฒ์ (๋จ์ผ ๋ธ๋ก): [0,1,2,3,4,5,6,7] โ [0,1,3,6,10,15,21,28]
์์ฑ ๋ฒ์ (๋ ๋ธ๋ก):
Block 0: [0,1,2,3,4,5,6,7] โ [0,1,3,6,10,15,21,28]
Block 1: [8,9,10,11,12,13,14] โ [8,17,27,38,50,63,77]
๊ทธ๋ฐ๋ฐ ๋ ๋ฒ์งธ ๋ธ๋ก์ ๊ฐ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น์? ์ฒซ ๋ฒ์งธ ๋ธ๋ก์ ํฉ๊ณ๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค!
2. 2๋จ๊ณ ์ ๊ทผ
๊ธฐ๋ณธ ๋์ ํฉ์ผ๋ก๋ ๋ธ๋ก ๊ฐ ๋๊ธฐํ๊ฐ ๋ถ๊ฐ๋ฅํ๋ฏ๋ก, ์์ ์ ๋๋๋๋ค:
- 1๋จ๊ณ: ๊ฐ ๋ธ๋ก์ด ๋ก์ปฌ ๋์ ํฉ์ ๊ณ์ฐ (๊ธฐ๋ณธ ๋ฒ์ ๊ณผ ๋์ผ)
- 2๋จ๊ณ: ๊ฐ ๋ธ๋ก์ด ์ด์ ๋ธ๋ก์ ํฉ๊ณ๋ฅผ ๋ฐ์
์ฃผ์: barrier()๋ ํ๋์ ๋ธ๋ก ๋ด์์๋ง ์ค๋ ๋๋ฅผ ๋๊ธฐํํฉ๋๋ค. ๋จ๊ณ ๊ฐ์๋
ํธ์คํธ ๋ ๋ฒจ ๋๊ธฐํ๊ฐ ํ์ํฉ๋๋ค.
3. ํ์ฅ ๋ฉ๋ชจ๋ฆฌ ์ ๋ต
๋ธ๋ก๋ผ๋ฆฌ ์ง์ ํต์ ํ ์ ์์ผ๋ฏ๋ก, ๋ธ๋ก ํฉ๊ณ๋ฅผ ์ ์ฅํ ๊ณณ์ด ํ์ํฉ๋๋ค:
- ์ถ๋ ฅ ๋ฒํผ ๋์ ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋น
- ๊ฐ ๋ธ๋ก์ ๋ง์ง๋ง ์ค๋ ๋๊ฐ ์ต์ข ํฉ๊ณ๋ฅผ ์ด ์ถ๊ฐ ๊ณต๊ฐ์ ์ ์ฅ
- ํ์ ๋ธ๋ก์ด ์ด ํฉ๊ณ๋ฅผ ์ฝ์ด์ ์๊ธฐ ์์์ ๋ํจ
4. ์ฃผ์ ๊ตฌํ ํฌ์ธํธ
- ๋ ์ด์์ ์ฐจ์ด: ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ ํํ๊ฐ ๋ค๋ฅผ ์ ์์
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ํญ์
global_i < size๋ก ๋ฐฐ์ด ๋ฒ์ ํ์ธ - ์ค๋ ๋ ์ญํ ๋ถ๋ด: ํน์ ์ค๋ ๋(์: ๋ง์ง๋ง ์ค๋ ๋)๋ง ๋ธ๋ก ํฉ๊ณ๋ฅผ ์ ์ฅ
- ๋ ์ปค๋ ๊ฐ ๋๊ธฐํ: ๋ ๋ฒ์งธ ์ปค๋์ ๋ฐ๋์ ์ฒซ ๋ฒ์งธ ์ปค๋์ด ์๋ฃ๋ ํ์ ์คํ๋์ด์ผ ํจ
5. ๋๋ฒ๊น ์ ๋ต
๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด, 1๋จ๊ณ ์ดํ์ ์ค๊ฐ ์ํ๋ฅผ ์๊ฐํํด ๋ณด์ธ์:
1๋จ๊ณ ์ดํ: [0,1,3,6,10,15,21,28, 8,17,27,38,50,63,77, ???,???]
์ฌ๊ธฐ์ ???์๋ 2๋จ๊ณ์์ ์ฌ์ฉ๋ ๋ธ๋ก ํฉ๊ณ๊ฐ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ ค๋ฉด ๋จผ์ ๋๋ฐ์ด์ค์ ์์ ์๋ฃ๋ฅผ ๋ช ์์ ์ผ๋ก ๋ณด์ฅํด์ผ ํ๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์.
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p14 --complete
pixi run -e amd p14 --complete
pixi run -e apple p14 --complete
uv run poe p14 --complete
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([0.0, 1.0, 3.0, 6.0, 10.0, 15.0, 21.0, 28.0, 36.0, 45.0, 55.0, 66.0, 78.0, 91.0, 105.0])
์๋ฃจ์
# Kernel 1: Compute local prefix sums and store block sums in out
def prefix_sum_local_phase(
output: TileTensor[mut=True, dtype, ExtendedLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, Layout2Type, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
# Load data into shared memory
# Example with SIZE_2=15, TPB=8, BLOCKS=2:
# Block 0 shared mem: [0,1,2,3,4,5,6,7]
# Block 1 shared mem: [8,9,10,11,12,13,14,uninitialized]
# Note: The last position remains uninitialized since global_i >= size,
# but this is safe because that thread doesn't participate in computation
if global_i < size:
shared[local_i] = a[global_i]
barrier()
# Compute local prefix sum using parallel reduction
# This uses a tree-based algorithm with log(TPB) iterations
# Iteration 1 (offset=1):
# Block 0: [0,0+1,2+1,3+2,4+3,5+4,6+5,7+6] = [0,1,3,5,7,9,11,13]
# Iteration 2 (offset=2):
# Block 0: [0,1,3+0,5+1,7+3,9+5,11+7,13+9] = [0,1,3,6,10,14,18,22]
# Iteration 3 (offset=4):
# Block 0: [0,1,3,6,10+0,14+1,18+3,22+6] = [0,1,3,6,10,15,21,28]
# Block 1 follows same pattern to get [8,17,27,38,50,63,77,???]
var offset = 1
for i in range(Int(log2(Scalar[dtype](TPB)))):
var current_val: output.ElementType = 0
if local_i >= offset and local_i < TPB:
current_val = shared[local_i - offset] # read
barrier()
if local_i >= offset and local_i < TPB:
shared[local_i] += current_val # write
barrier()
offset *= 2
# Write local results to output
# Block 0 writes: [0,1,3,6,10,15,21,28]
# Block 1 writes: [8,17,27,38,50,63,77,???]
if global_i < size:
output[global_i] = shared[local_i]
# Store block sums in auxiliary space
# Block 0: Thread 7 stores shared[7] == 28 at position size+0 (position 15)
# Block 1: Thread 7 stores shared[7] == ??? at position size+1 (position 16). This sum is not needed for the final output.
# This gives us: [0,1,3,6,10,15,21,28, 8,17,27,38,50,63,77, 28,???]
# โ โ
# Block sums here
if local_i == TPB - 1:
output[size + block_idx.x] = shared[local_i]
# Kernel 2: Add block sums to their respective blocks
def prefix_sum_block_sum_phase(
output: TileTensor[mut=True, dtype, ExtendedLayout, MutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
# Second pass: add previous block's sum to each element
# Block 0: No change needed - already correct
# Block 1: Add Block 0's sum (28) to each element
# Before: [8,17,27,38,50,63,77]
# After: [36,45,55,66,78,91,105]
# Final result combines both blocks:
# [0,1,3,6,10,15,21,28, 36,45,55,66,78,91,105]
if block_idx.x > 0 and global_i < size:
var prev_block_sum = output[size + block_idx.x - 1]
output[global_i] += prev_block_sum
์ด ์๋ฃจ์ ์ ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์น๋ ๋ฐฐ์ด์ ์ฒ๋ฆฌํ๊ธฐ ์ํด 2๊ฐ์ ์ปค๋์ ์ฌ์ฉํ๋ ๋ฉํฐ ๋ธ๋ก ๋์ ํฉ์ ๊ตฌํํฉ๋๋ค. ๊ฐ ๋ถ๋ถ์ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
๋ธ๋ก ๊ฐ ํต์ ์ ๊ณผ์
GPU ํ๋ก๊ทธ๋๋ฐ์ ๊ทผ๋ณธ์ ์ธ ์ ์ฝ์ barrier()๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ๋๊ธฐํ๊ฐ ๋ธ๋ก
๋ด๋ถ์์๋ง ๊ฐ๋ฅํ๋ค๋ ์ ์
๋๋ค. ๋ฐ์ดํฐ๊ฐ ์ฌ๋ฌ ๋ธ๋ก์ ๊ฑธ์ณ ์์ ๋ ๋ค์๊ณผ ๊ฐ์
๊ณผ์ ์ ์ง๋ฉดํฉ๋๋ค: ๋ธ๋ก์ด ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅธ ๋ธ๋ก์ ์ด๋ป๊ฒ ์ ๋ฌํ ์ ์์๊น?
๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์๊ฐํ
ํ
์คํธ ์ผ์ด์ค SIZE_2 = 15, TPB = 8์ ๊ฒฝ์ฐ:
Input array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Block 0 ์ฒ๋ฆฌ: [0, 1, 2, 3, 4, 5, 6, 7]
Block 1 ์ฒ๋ฆฌ: [8, 9, 10, 11, 12, 13, 14] (์ ํจ ์์ 7๊ฐ)
๋ธ๋ก ํฉ๊ณ๋ฅผ ์ํ ๊ณต๊ฐ์ ํฌํจํ๋๋ก ์ถ๋ ฅ ๋ฒํผ๋ฅผ ํ์ฅํฉ๋๋ค:
ํ์ฅ ๋ฒํผ: [๋ฐ์ดํฐ ๊ฐ (15๊ฐ)] + [๋ธ๋ก ํฉ๊ณ (2๊ฐ)]
[0...14] + [block0_sum, block1_sum]
์ด ํ์ฅ ๋ฒํผ์ ํฌ๊ธฐ: EXTENDED_SIZE = SIZE_2 + num_blocks = 15 + 2 = 17
1๋จ๊ณ ์ปค๋: ๋ก์ปฌ ๋์ ํฉ
๋ก์ปฌ ๋จ๊ณ์์์ ๊ฒฝ์ ์ํ ๋ฐฉ์ง
๋ก์ปฌ ๋จ๊ณ๋ ๊ธฐ๋ณธ ๋ฒ์ ๊ณผ ๋์ผํ ๋ช ์์ ๋๊ธฐํ ํจํด์ ์ฌ์ฉํ์ฌ ์ฝ๊ธฐ-์ฐ๊ธฐ ์ถฉ๋์ ๋ฐฉ์งํฉ๋๋ค:
- ์ฝ๊ธฐ ๋จ๊ณ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋จผ์ ํ์ํ ๊ฐ์ ๋ก์ปฌ ๋ณ์
current_val์ ์ฝ์ด๋ - ๋๊ธฐํ:
barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ๊ฐ ์๋ฃ๋ ํ์์ผ ์ฐ๊ธฐ๊ฐ ์์๋๋๋ก ๋ณด์ฅ - ์ฐ๊ธฐ ๋จ๊ณ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ณ์ฐ๋ ๊ฐ์ ์์ ํ๊ฒ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ก
์ด๋ฅผ ํตํด ๋ณ๋ ฌ ๋ฆฌ๋์ ์ค ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผํ ๋ ๋ฐ์ํ ์ ์๋ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
Block 0 ๋จ๊ณ๋ณ ์คํ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ ๋ก๋:
shared = [0, 1, 2, 3, 4, 5, 6, 7] -
๋ณ๋ ฌ ๋ฆฌ๋์ ๋ฐ๋ณต (\(\log_2(TPB) = 3\)ํ ๋ฐ๋ณต):
๋ฐ๋ณต 1 (offset=1):
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ํ์ฑ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ reads shared[4] = 4 Tโ reads shared[1] = 1 Tโ reads shared[5] = 5 Tโ reads shared[2] = 2 Tโ reads shared[6] = 6 Tโ reads shared[3] = 3๋๊ธฐํ:
barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ๋ํจ:
shared[0] = 0 (๋ณ๊ฒฝ ์์) shared[1] = 1 + 0 = 1 shared[2] = 2 + 1 = 3 shared[3] = 3 + 2 = 5 shared[4] = 4 + 3 = 7 shared[5] = 5 + 4 = 9 shared[6] = 6 + 5 = 11 shared[7] = 7 + 6 = 13๋ฐฐ๋ฆฌ์ด ํ:
shared = [0, 1, 3, 5, 7, 9, 11, 13]๋ฐ๋ณต 2 (offset=2):
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ํ์ฑ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ reads shared[3] = 5 Tโ reads shared[1] = 1 Tโ reads shared[4] = 7 Tโ reads shared[2] = 3 Tโ reads shared[5] = 9๋๊ธฐํ:
barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ๋ํจ:
shared[0] = 0 (๋ณ๊ฒฝ ์์) shared[1] = 1 (๋ณ๊ฒฝ ์์) shared[2] = 3 + 0 = 3 (๋ณ๊ฒฝ ์์) shared[3] = 5 + 1 = 6 shared[4] = 7 + 3 = 10 shared[5] = 9 + 5 = 14 shared[6] = 11 + 7 = 18 shared[7] = 13 + 9 = 22๋ฐฐ๋ฆฌ์ด ํ:
shared = [0, 1, 3, 6, 10, 14, 18, 22]๋ฐ๋ณต 3 (offset=4):
์ฝ๊ธฐ ๋จ๊ณ: ๊ฐ ํ์ฑ ์ค๋ ๋๊ฐ ํ์ํ ๊ฐ์ ์ฝ์:
Tโ reads shared[0] = 0 Tโ reads shared[2] = 3 Tโ reads shared[1] = 1 Tโ reads shared[3] = 6๋๊ธฐํ:
barrier()๋ก ๋ชจ๋ ์ฝ๊ธฐ ์๋ฃ๋ฅผ ๋ณด์ฅ์ฐ๊ธฐ ๋จ๊ณ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ์ ๊ฐ์ ๋ํจ:
shared[0] = 0 (๋ณ๊ฒฝ ์์) shared[1] = 1 (๋ณ๊ฒฝ ์์) shared[2] = 3 (๋ณ๊ฒฝ ์์) shared[3] = 6 (๋ณ๊ฒฝ ์์) shared[4] = 10 + 0 = 10 (๋ณ๊ฒฝ ์์) shared[5] = 14 + 1 = 15 shared[6] = 18 + 3 = 21 shared[7] = 22 + 6 = 28๋ฐฐ๋ฆฌ์ด ํ:
shared = [0, 1, 3, 6, 10, 15, 21, 28] -
๋ก์ปฌ ๊ฒฐ๊ณผ๋ฅผ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ก:
output[0...7] = [0, 1, 3, 6, 10, 15, 21, 28] -
๋ธ๋ก ํฉ๊ณ๋ฅผ ๋ณด์กฐ ๊ณต๊ฐ์ ์ ์ฅ (๋ง์ง๋ง ์ค๋ ๋๋ง):
output[15] = 28 // ์์น: size + block_idx.x = 15 + 0
Block 1 ๋จ๊ณ๋ณ ์คํ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ ๋ก๋:
shared = [8, 9, 10, 11, 12, 13, 14, ๋ฏธ์ด๊ธฐํ]์ฐธ๊ณ : ์ค๋ ๋ 7์
global_i = 15 >= SIZE_2์ด๋ฏ๋ก ์๋ฌด๊ฒ๋ ๋ก๋ํ์ง ์์shared[7]์ด ๋ฏธ์ด๊ธฐํ ์ํ๋ก ๋จ์ต๋๋ค. ์ค๋ ๋ 7์ ์ต์ข ์ถ๋ ฅ์ ์ฐธ์ฌํ์ง ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. -
๋ณ๋ ฌ ๋ฆฌ๋์ ๋ฐ๋ณต (\(\log_2(TPB) = 3\)ํ ๋ฐ๋ณต):
์ค์ ๋ก ์ฐ์ฐ์ ์ฐธ์ฌํ๋ ๊ฒ์ ์ฒ์ 7๊ฐ ์ค๋ ๋๋ฟ์ ๋๋ค. ์ธ ๋ฒ์ ๋ฐ๋ณต์ ๊ฑฐ์น๋ฉด:
shared = [8, 17, 27, 38, 50, 63, 77, ๋ฏธ์ด๊ธฐํ] -
๋ก์ปฌ ๊ฒฐ๊ณผ๋ฅผ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ก:
output[8...14] = [8, 17, 27, 38, 50, 63, 77] // ์ ํจ ์ถ๋ ฅ 7๊ฐ๋ง -
๋ธ๋ก ํฉ๊ณ๋ฅผ ๋ณด์กฐ ๊ณต๊ฐ์ ์ ์ฅ (๋ธ๋ก์ ๋ง์ง๋ง ์ค๋ ๋๋ง):
output[16] = shared[7] // ์ค๋ ๋ 7 (TPB-1)์ด shared[7]์ ๊ฐ์ ์ ์ฅ์ฐธ๊ณ : ์ค๋ ๋ 7์ ์ ํจํ ์ ๋ ฅ์ ๋ก๋ํ์ง ์์์ง๋ง, ๋ธ๋ก ๋ด ๋์ ํฉ ์ฐ์ฐ์๋ ๊ทธ๋๋ก ์ฐธ์ฌํฉ๋๋ค.
shared[7]์ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ๊ฑฐ์น๋ฉฐ ๊ฐฑ์ ๋์ง๋ง, ๋ฏธ์ด๊ธฐํ ์ํ์์ ์์ํ๊ธฐ ๋๋ฌธ์ ์ต์ข ๊ฐ์ ์์ธกํ ์ ์์ต๋๋ค. ๋ค๋ง Block 1์ด ๋ง์ง๋ง ๋ธ๋ก์ด๋ฏ๋ก ์ด ๋ธ๋ก ํฉ๊ณ๋ 2๋จ๊ณ์์ ์ฌ์ฉ๋์ง ์์ ์ ํ์ฑ์๋ ์ํฅ์ด ์์ต๋๋ค.
1๋จ๊ณ ์ดํ ์ถ๋ ฅ ๋ฒํผ์ ๋ด์ฉ:
[0, 1, 3, 6, 10, 15, 21, 28, 8, 17, 27, 38, 50, 63, 77, 28, ???]
^ ^
๋ธ๋ก ํฉ๊ณ๊ฐ ์ฌ๊ธฐ์ ์ ์ฅ๋จ
์ฐธ๊ณ : ๋ง์ง๋ง ๋ธ๋ก ํฉ๊ณ (???) ๋ ๋ฏธ์ด๊ธฐํ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ฐํ๋ฏ๋ก ์์ธกํ ์ ์์ง๋ง, ์ต์ข ๊ฒฐ๊ณผ์๋ ์ํฅ์ ์ฃผ์ง ์์ต๋๋ค.
ํธ์คํธ-๋๋ฐ์ด์ค ๋๊ธฐํ: ์ค์ ๋ก ํ์ํ ์์
๋ ์ปค๋ ๋จ๊ณ๋ ๋ช ์์ ๋๊ธฐํ ์์ด ์์ฐจ์ ์ผ๋ก ์คํ๋ฉ๋๋ค:
# 1๋จ๊ณ: ๋ก์ปฌ ๋์ ํฉ
ctx.enqueue_function[prefix_sum_local_phase[...], prefix_sum_local_phase[...]](...)
# 2๋จ๊ณ: ๋ธ๋ก ํฉ๊ณ ๋ํ๊ธฐ (์๋์ผ๋ก 1๋จ๊ณ ์๋ฃ๋ฅผ ๋๊ธฐ)
ctx.enqueue_function[prefix_sum_block_sum_phase[...], prefix_sum_block_sum_phase[...]](...)
ํต์ฌ ํต์ฐฐ: Mojo์ DeviceContext๋ ๋จ์ผ ์คํ ์คํธ๋ฆผ(NVIDIA GPU์์๋ CUDA
์คํธ๋ฆผ, AMD ROCm GPU์์๋ HIP ์คํธ๋ฆผ)์ ์ฌ์ฉํ๋ฏ๋ก, ํ์ ๋ฃ์ ์ปค๋์ด ์ ํํ
๋ฃ์ ์์๋๋ก ์คํ๋จ์ ๋ณด์ฅํฉ๋๋ค. ์ปค๋ ๊ฐ์ ๋ช
์์ ๋๊ธฐํ๊ฐ ํ์ ์์ต๋๋ค.
ctx.synchronize()๊ฐ ํ์ํ ์์ :
# ๋ ์ปค๋ ์๋ฃ ํ, ํธ์คํธ์์ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ธฐ ์
ctx.synchronize() # ํธ์คํธ๊ฐ GPU ์๋ฃ๋ฅผ ๋๊ธฐ
with out.map_to_host() as out_host: # ์ด์ GPU ๊ฒฐ๊ณผ๋ฅผ ์์ ํ๊ฒ ์ฝ์ ์ ์์
print("out:", out_host)
ctx.synchronize() ํธ์ถ์ ์ญํ :
- ํธ์คํธ-๋๋ฐ์ด์ค ๋๊ธฐํ: ๊ฒฐ๊ณผ์ ์ ๊ทผํ๊ธฐ ์ ์ ํธ์คํธ๊ฐ ๋ชจ๋ GPU ์์ ์ ์๋ฃ๋ฅผ ๋๊ธฐํ๋๋ก ๋ณด์ฅ
- ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ: ์ฐ์ฐ์ด ๋๋๊ธฐ ์ ์ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฝ๋ ๊ฒ์ ๋ฐฉ์ง
์คํ ๋ชจ๋ธ: ๋ธ๋ก ๋ด๋ถ์ ์ค๋ ๋๋ฅผ ๋๊ธฐํํ๋ barrier()์ ๋ฌ๋ฆฌ, ์ปค๋ ์คํ
์์๋ Mojo์ ๋จ์ผ ์คํธ๋ฆผ ์คํ ๋ชจ๋ธ์์ ๋ณด์ฅ๋๋ฉฐ, ctx.synchronize()๋
ํธ์คํธ-๋๋ฐ์ด์ค ๊ฐ ์กฐ์จ์ ๋ด๋นํฉ๋๋ค.
2๋จ๊ณ ์ปค๋: ๋ธ๋ก ํฉ๊ณ ๋ํ๊ธฐ
-
Block 0: ๋ณ๊ฒฝ ๋ถํ์ (์ด๋ฏธ ์ฌ๋ฐ๋ฅธ ์ํ).
-
Block 1: ๊ฐ ์ค๋ ๋๊ฐ Block 0์ ํฉ๊ณ๋ฅผ ์๊ธฐ ์์์ ๋ํจ:
prev_block_sum = output[size + block_idx.x - 1] = output[15] = 28 output[global_i] += prev_block_sumBlock 1์ ๊ฐ์ด ๋ณํ๋ฉ๋๋ค:
Before: [8, 17, 27, 38, 50, 63, 77] After: [36, 45, 55, 66, 78, 91, 105]
์ฑ๋ฅ ๋ฐ ์ต์ ํ ๊ณ ๋ ค ์ฌํญ
์ฃผ์ ๊ตฌํ ์์ธ
๋ก์ปฌ ๋จ๊ณ ๋๊ธฐํ ํจํด: ๋ธ๋ก ๋ด ๊ฐ ๋ฐ๋ณต์ ์๊ฒฉํ ์ฝ๊ธฐ โ ๋๊ธฐํ โ ์ฐ๊ธฐ ํจํด์ ๋ฐ๋ฆ ๋๋ค:
var current_val: out.element_type = 0- ๋ก์ปฌ ๋ณ์ ์ด๊ธฐํcurrent_val = shared[local_i - offset]- ์ฝ๊ธฐ ๋จ๊ณ (์กฐ๊ฑด ์ถฉ์กฑ ์)barrier()- ๊ฒฝ์ ์ํ ๋ฐฉ์ง๋ฅผ ์ํ ๋ช ์์ ๋๊ธฐํshared[local_i] += current_val- ์ฐ๊ธฐ ๋จ๊ณ (์กฐ๊ฑด ์ถฉ์กฑ ์)barrier()- ๋ค์ ๋ฐ๋ณต ์ ๋๊ธฐํ
๋ธ๋ก ๊ฐ ๋๊ธฐํ: ์ด ์๊ณ ๋ฆฌ์ฆ์ ๋ ๋จ๊ณ์ ๋๊ธฐํ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
- ๋ธ๋ก ๋ด๋ถ: ๋ก์ปฌ ๋์ ํฉ ์ฐ์ฐ ์ค
barrier()๋ก ๊ฐ ๋ธ๋ก ๋ด ์ค๋ ๋๋ฅผ ๋๊ธฐํ - ๋ธ๋ก ๊ฐ:
DeviceContext๊ฐ ํ์ ๋ฃ์ ์ปค๋์ ์์ฐจ ์คํํ์ฌ 1๋จ๊ณ๊ฐ 2๋จ๊ณ ์ ์ ์๋ฃ๋๋๋ก ๋ณด์ฅ. ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ธฐ ์ ์ ํธ์คํธ-๋๋ฐ์ด์ค ๋๊ธฐํ๊ฐ ํ์ํ๋ฉดctx.synchronize()๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๊ฒฝ์ ์ํ ๋ฐฉ์ง: ๋ก์ปฌ ๋จ๊ณ์์ ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ๋ช ์์ ์ผ๋ก ๋ถ๋ฆฌํ์ฌ, ๋ณ๋ ฌ ๋ฆฌ๋์ ์ค ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ๋์์ ์ ๊ทผํ ๋ ์๊ธธ ์ ์๋ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
-
์์ ํจ์จ์ฑ: ์ด ๊ตฌํ์ ์์ ๋ณต์ก๋๋ \(O(n \log n)\)์ด๋ฉฐ, ์์ฐจ ์๊ณ ๋ฆฌ์ฆ์ \(O(n)\)์ ๋๋ค. ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์์ ์ ํ์ ์ธ ๊ณต๊ฐ-์๊ฐ ํธ๋ ์ด๋์คํ์ ๋๋ค.
-
๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋: ๋ธ๋ก ํฉ๊ณ๋ฅผ ์ํ ์ถ๊ฐ ๊ณต๊ฐ์ ์์ฃผ ์ ์ต๋๋ค (๋ธ๋ก๋น ์์ ํ๋).
์ด 2๊ฐ ์ปค๋ ์ ๊ทผ ๋ฐฉ์์ ๋ธ๋ก ๊ฐ ํต์ ์ด ํ์ํ GPU ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ณธ ํจํด์ ๋๋ค. ๊ธฐ์ ์ ๋ ฌ, ํ์คํ ๊ทธ๋จ ๊ณ์ฐ, ๋ฆฌ๋์ ์ฐ์ฐ ๋ฑ ๋ค๋ฅธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์๋ ๋์ผํ ์ ๋ต์ ์ ์ฉํ ์ ์์ต๋๋ค.
Puzzle 15: ์ถ ํฉ๊ณ
๊ฐ์
2D ํ๋ ฌ a์ ๊ฐ ํ์ ๋ํด ํฉ๊ณ๋ฅผ ๊ณ์ฐํ์ฌ TileTensor๋ฅผ ์ฌ์ฉํด output์
์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- TileTensor๋ฅผ ํ์ฉํ ํ๋ ฌ ์ฐจ์ ๋ฐฉํฅ์ ๋ณ๋ ฌ ๋ฆฌ๋์
- ๋ธ๋ก ์ขํ๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ๋ถํ
- ํจ์จ์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฆฌ๋์ ํจํด
- ๋ค์ฐจ์ ํ ์ ๋ ์ด์์ ๋ค๋ฃจ๊ธฐ
ํต์ฌ์ ์ค๋ ๋ ๋ธ๋ก์ ํ๋ ฌ์ ํ์ ๋งคํํ๊ณ , TileTensor์ ์ฐจ์๋ณ ์ธ๋ฑ์ฑ์ ํ์ฉํ๋ฉด์ ๊ฐ ๋ธ๋ก ๋ด์์ ํจ์จ์ ์ธ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ์ํํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ํ๋ ฌ ํฌ๊ธฐ: \(\text{BATCH} \times \text{SIZE} = 4 \times 6\)
- ๋ธ๋ก๋น ์ค๋ ๋ ์: \(\text{TPB} = 8\)
- ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ: \(1 \times \text{BATCH}\)
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก๋น \(\text{TPB}\)๊ฐ ์์
- ์
๋ ฅ ๋ ์ด์์:
row_major[BATCH, SIZE]() - ์ถ๋ ฅ ๋ ์ด์์:
row_major[BATCH, 1]()
ํ๋ ฌ ์๊ฐํ:
Row 0: [0, 1, 2, 3, 4, 5] โ Block(0,0)
Row 1: [6, 7, 8, 9, 10, 11] โ Block(0,1)
Row 2: [12, 13, 14, 15, 16, 17] โ Block(0,2)
Row 3: [18, 19, 20, 21, 22, 23] โ Block(0,3)
์์ฑํ ์ฝ๋
from std.gpu import thread_idx, block_idx, block_dim, barrier
from std.gpu.memory import AddressSpace
from layout import TileTensor
from layout.tile_layout import row_major
from layout.tile_tensor import stack_allocation
comptime TPB = 8
comptime BATCH = 4
comptime SIZE = 6
comptime BLOCKS_PER_GRID = (1, BATCH)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime in_layout = row_major[BATCH, SIZE]()
comptime InLayout = type_of(in_layout)
comptime out_layout = row_major[BATCH, 1]()
comptime OutLayout = type_of(out_layout)
def axis_sum(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var batch = block_idx.y
# FILL ME IN (roughly 15 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p15/p15.mojo
ํ
batch = block_idx.y๋ก ํ ์ ํ- ์์ ๋ก๋:
cache[local_i] = a[batch, local_i] - ์คํธ๋ผ์ด๋๋ฅผ ์ ๋ฐ์ฉ ์ค์ด๋ฉฐ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ํ
- ์ค๋ ๋ 0์ด ์ต์ข
ํฉ๊ณ๋ฅผ
output[batch]์ ๊ธฐ๋ก
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p15
pixi run -e amd p15
pixi run -e apple p15
uv run poe p15
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: DeviceBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([15.0, 51.0, 87.0, 123.0])
์๋ฃจ์
def axis_sum(
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var batch = block_idx.y
var cache = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
# Visualize:
# Block(0,0): [T0,T1,T2,T3,T4,T5,T6,T7] -> Row 0: [0,1,2,3,4,5]
# Block(0,1): [T0,T1,T2,T3,T4,T5,T6,T7] -> Row 1: [6,7,8,9,10,11]
# Block(0,2): [T0,T1,T2,T3,T4,T5,T6,T7] -> Row 2: [12,13,14,15,16,17]
# Block(0,3): [T0,T1,T2,T3,T4,T5,T6,T7] -> Row 3: [18,19,20,21,22,23]
# each row is handled by each block bc we have grid_dim=(1, BATCH)
if local_i < size:
cache[local_i] = a[batch, local_i]
else:
# Add zero-initialize padding elements for later reduction
cache[local_i] = 0
barrier()
# do reduction sum per each block
var stride = TPB // 2
while stride > 0:
# Read phase: all threads read the values they need first to avoid race conditions
var temp_val: output.ElementType = 0
if local_i < stride:
temp_val = cache[local_i + stride]
barrier()
# Write phase: all threads safely write their computed values
if local_i < stride:
cache[local_i] += temp_val
barrier()
stride //= 2
# writing with local thread = 0 that has the sum for each batch
if local_i == 0:
output[batch, 0] = cache[0]
TileTensor๋ฅผ ํ์ฉํด 2D ํ๋ ฌ์ ํ ๋ฐฉํฅ ํฉ๊ณ๋ฅผ ๋ณ๋ ฌ๋ก ๊ตฌํ๋ ๋ฆฌ๋์ ๊ตฌํ์ ๋๋ค. ๋จ๊ณ๋ณ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค:
ํ๋ ฌ ๋ ์ด์์๊ณผ ๋ธ๋ก ๋งคํ
Input Matrix (4ร6) with TileTensor: Block Assignment:
[[ a[0,0] a[0,1] a[0,2] a[0,3] a[0,4] a[0,5] ] โ Block(0,0)
[ a[1,0] a[1,1] a[1,2] a[1,3] a[1,4] a[1,5] ] โ Block(0,1)
[ a[2,0] a[2,1] a[2,2] a[2,3] a[2,4] a[2,5] ] โ Block(0,2)
[ a[3,0] a[3,1] a[3,2] a[3,3] a[3,4] a[3,5] ] โ Block(0,3)
๋ณ๋ ฌ ๋ฆฌ๋์ ๊ณผ์
-
์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋ฉ:
Block(0,0): cache = [a[0,0] a[0,1] a[0,2] a[0,3] a[0,4] a[0,5] * *] // * = ํจ๋ฉ Block(0,1): cache = [a[1,0] a[1,1] a[1,2] a[1,3] a[1,4] a[1,5] * *] Block(0,2): cache = [a[2,0] a[2,1] a[2,2] a[2,3] a[2,4] a[2,5] * *] Block(0,3): cache = [a[3,0] a[3,1] a[3,2] a[3,3] a[3,4] a[3,5] * *] -
๋ฆฌ๋์ ๋จ๊ณ (Block 0,0 ๊ธฐ์ค):
Initial: [0 1 2 3 4 5 * *] Stride 4: [4 5 6 7 4 5 * *] Stride 2: [10 12 6 7 4 5 * *] Stride 1: [15 12 6 7 4 5 * *]
์ฃผ์ ๊ตฌํ ํน์ง
-
๋ ์ด์์ ๊ตฌ์ฑ:
- ์ ๋ ฅ: ํ ์ฐ์ (row-major) ๋ ์ด์์ (BATCH ร SIZE)
- ์ถ๋ ฅ: ํ ์ฐ์ ๋ ์ด์์ (BATCH ร 1)
- ๊ฐ ๋ธ๋ก์ด ํ๋์ ํ ์ ์ฒด๋ฅผ ์ฒ๋ฆฌ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์
๋ ฅ์ TileTensor 2D ์ธ๋ฑ์ฑ ์ฌ์ฉ:
a[batch, local_i] - ํจ์จ์ ์ธ ๋ฆฌ๋์ ์ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- ์ถ๋ ฅ์ TileTensor 2D ์ธ๋ฑ์ฑ ์ฌ์ฉ:
output[batch, 0]
- ์
๋ ฅ์ TileTensor 2D ์ธ๋ฑ์ฑ ์ฌ์ฉ:
-
๋ณ๋ ฌ ๋ฆฌ๋์ ๋ก์ง:
stride = TPB // 2 while stride > 0: if local_i < stride: cache[local_i] += cache[local_i + stride] barrier() stride //= 2์ฐธ๊ณ : ์ด ๊ตฌํ์์๋ ๊ฐ์ ๋ฐ๋ณต ๋ด์์ ์ค๋ ๋๋ค์ด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋์์ ์ฝ๊ณ ์ฐ๊ธฐ ๋๋ฌธ์ ์ ์ฌ์ ์ธ ๊ฒฝ์ ์ํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ ์์ ํ ๋ฐฉ๋ฒ์ ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋จ๊ณ๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒ์ ๋๋ค:
stride = TPB // 2 while stride > 0: var temp_val: output.element_type = 0 if local_i < stride: temp_val = cache[local_i + stride] # ์ฝ๊ธฐ ๋จ๊ณ barrier() if local_i < stride: cache[local_i] += temp_val # ์ฐ๊ธฐ ๋จ๊ณ barrier() stride //= 2 -
์ถ๋ ฅ ๊ธฐ๋ก:
if local_i == 0: output[batch, 0] = cache[0] --> ๋ฐฐ์น๋น ๊ฒฐ๊ณผ ํ๋
์ฑ๋ฅ ์ต์ ํ
-
๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ:
- TileTensor๋ฅผ ํตํ ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ๋น ๋ฅธ ๋ฆฌ๋์ ์ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- ํ ๊ฒฐ๊ณผ๋น ํ ๋ฒ์ ์ฐ๊ธฐ
-
์ค๋ ๋ ํ์ฉ:
- ํ ๊ฐ ์๋ฒฝํ ๋ถํ ๊ท ํ
- ์ฃผ์ ์ฐ์ฐ์์ ์ค๋ ๋ ๋ถ๊ธฐ ์์
- ํจ์จ์ ์ธ ๋ณ๋ ฌ ๋ฆฌ๋์ ํจํด
-
๋๊ธฐํ:
- ์ต์ํ์ ๋ฐฐ๋ฆฌ์ด (๋ฆฌ๋์ ์ค์๋ง ์ฌ์ฉ)
- ํ ๊ฐ ๋ ๋ฆฝ์ ์ธ ์ฒ๋ฆฌ
- ๋ธ๋ก ๊ฐ ํต์ ๋ถํ์
- ๊ฒฝ์ ์ํ ๊ณ ๋ ค์ฌํญ: ํ์ฌ ๊ตฌํ์์๋ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ค์ ์ฝ๊ธฐ-์ฐ๊ธฐ ์ถฉ๋์ด ๋ฐ์ํ ์ ์์ผ๋ฉฐ, ๋ช ์์ ์ธ ์ฝ๊ธฐ-์ฐ๊ธฐ ๋จ๊ณ ๋ถ๋ฆฌ๋ก ํด๊ฒฐํ ์ ์์ต๋๋ค
๋ณต์ก๋ ๋ถ์
- ์๊ฐ: ํ๋น \(O(\log n)\), n์ ํ์ ๊ธธ์ด
- ๊ณต๊ฐ: ๋ธ๋ก๋น \(O(TPB)\) ๊ณต์ ๋ฉ๋ชจ๋ฆฌ
- ์ ์ฒด ๋ณ๋ ฌ ์๊ฐ: ์ค๋ ๋๊ฐ ์ถฉ๋ถํ ๋ \(O(\log n)\)
Puzzle 16: ํ๋ ฌ ๊ณฑ์ (MatMul)
๊ฐ์
ํ๋ ฌ ๊ณฑ์ ์ ๊ณผํ ๊ณ์ฐ, ๋จธ์ ๋ฌ๋, ๊ทธ๋ํฝ์ค์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ด ๋๋ ์ฐ์ฐ์ ๋๋ค. ๋ ํ๋ ฌ \(A\)์ \(B\)๊ฐ ์ฃผ์ด์ก์ ๋, ์ด๋ค์ ๊ณฑ \(C = A \times B\) ๋ฅผ ๊ตฌํ๊ณ ์ ํฉ๋๋ค.
ํ๋ ฌ \(A_{m\times k}\)์ \(B_{k\times n}\)์ ๋ํด, ๊ฒฐ๊ณผ \(C_{m\times n}\)์ ๊ฐ ์์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ณ์ฐ๋ฉ๋๋ค:
\[\Large C_{ij} = \sum_{l=0}^{k-1} A_{il} \cdot B_{lj} \]
์ด ํผ์ฆ์์๋ GPU์์ ํ๋ ฌ ๊ณฑ์ ์ ๊ตฌํํ๋ ์ฌ๋ฌ ์ ๊ทผ๋ฒ์ ์ดํด๋ด ๋๋ค. ๊ฐ ๋ฒ์ ์ ์๋ก ๋ค๋ฅธ ์ฑ๋ฅ ํน์ฑ์ ๊ฐ์ง๊ณ ์์ต๋๋ค:
-
์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ธฐ๋ณธ ๋ฒ์ ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ํ๋ ฌ์ ์์ ํ๋๋ฅผ ๊ณ์ฐํ๋ ์ง๊ด์ ์ธ ๊ตฌํ์ ๋๋ค. ์ดํดํ๊ธฐ ์ฝ์ง๋ง, ์ค๋ณต๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ๋ง๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์ ์ ๋ ฅ ํ๋ ฌ์ ๋ธ๋ก์ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ ค ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ค์ ๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ์์ ํ๋๋ฅผ ๊ณ์ฐํ๋ ๊ฒ์ ๊ฐ์ง๋ง, ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์ฝ์ต๋๋ค.
-
ํ์ผ๋ง ๋ฒ์ ์ฐ์ฐ์ ํ์ผ ๋จ์๋ก ๋๋์ด ์ค๋ ๋๋ค์ด ์ถ๋ ฅ ํ๋ ฌ์ ๋ธ๋ก์ ํจ๊ป ๋ก๋ํ๊ณ ๊ณ์ฐํฉ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ์ ์ค๋ ๋ ํ๋ ฅ์ ํ์ธต ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ๋ฐฉ์์ ๋๋ค.
๊ฐ ๋ฒ์ ์ ์ด์ ๋ฒ์ ์์ ์์ ์ฌ๋ฆฌ๋ฉด์ GPU ํ๋ก๊ทธ๋๋ฐ์์ ์์ฃผ ์ฌ์ฉ๋๋ ์๋ก์ด ์ต์ ํ ๊ธฐ๋ฒ์ ์๊ฐํฉ๋๋ค. ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ์ค๋ ๋ ํ๋ ฅ ์ ๋ต์ด ์ฑ๋ฅ์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง ๋ฐฐ์ธ ์ ์์ต๋๋ค.
์ด ๋จ๊ณ์ ์งํ ๊ณผ์ ์ GPU ์ต์ ํ์ ๋ํ์ ์ธ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
- ์ ํํ์ง๋ง ๋จ์ํ ๊ธฐ๋ณธ ๊ตฌํ์์ ์ถ๋ฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ค์ด๊ธฐ
- ํ์ผ๋ง์ผ๋ก ๋ฐ์ดํฐ ์ง์ญ์ฑ๊ณผ ์ค๋ ๋ ํ๋ ฅ ๊ฐ์
- ๊ณ ์์ค ์ถ์ํ๋ฅผ ํ์ฉํ๋ฉด์๋ ์ฑ๋ฅ ์ ์ง
์ํ๋ ๋ฒ์ ์ ๊ณจ๋ผ ํ๋ ฌ ๊ณฑ์ ์ฌ์ ์ ์์ํด ๋ณด์ธ์!
์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ธฐ๋ณธ ๋ฒ์
๊ฐ์
์ ๋ฐฉ ํ๋ ฌ \(A\)์ \(B\)๋ฅผ ๊ณฑํ์ฌ ๊ฒฐ๊ณผ๋ฅผ \(\text{output}\)์ ์ ์ฅํ๋ ์ปค๋์ ๊ตฌํํ์ธ์. ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ํ๋ ฌ์ ์์ ํ๋๋ฅผ ๊ณ์ฐํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๊ตฌํ์ ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- ํ๋ ฌ ์ฐ์ฐ์ ์ํ 2D ์ค๋ ๋ ๊ตฌ์ฑ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ํ ์ฐ์ (row-major) ๋ ์ด์์์์์ ํ๋ ฌ ์ธ๋ฑ์ฑ
- ์ค๋ ๋์ ์ถ๋ ฅ ์์ ๊ฐ ๋งคํ
ํต์ฌ์ 2D ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ํ๋ ฌ ์์์ ๋งคํํ๊ณ , ๋ด์ ์ ๋ณ๋ ฌ๋ก ๊ณ์ฐํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ํ๋ ฌ ํฌ๊ธฐ: \(\text{SIZE} \times \text{SIZE} = 2 \times 2\)
- ๋ธ๋ก๋น ์ค๋ ๋ ์: \(\text{TPB} \times \text{TPB} = 3 \times 3\)
- ๊ทธ๋ฆฌ๋ ์ฐจ์: \(1 \times 1\)
๋ ์ด์์ ๊ตฌ์ฑ:
- ์
๋ ฅ A:
row_major[SIZE, SIZE]() - ์
๋ ฅ B:
row_major[SIZE, SIZE]() - ์ถ๋ ฅ:
row_major[SIZE, SIZE]()
์์ฑํ ์ฝ๋
from std.gpu import thread_idx, block_idx, block_dim, barrier
from std.gpu.memory import AddressSpace
from layout import TileTensor
from layout.tile_layout import row_major
from layout.tile_tensor import stack_allocation
comptime TPB = 3
comptime SIZE = 2
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (TPB, TPB)
comptime dtype = DType.float32
comptime layout = row_major[SIZE, SIZE]()
comptime LayoutType = type_of(layout)
def naive_matmul(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 6 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p16/p16.mojo
ํ
- ์ค๋ ๋ ์ธ๋ฑ์ค๋ก
row์col๊ณ์ฐ - ์ธ๋ฑ์ค๊ฐ
size๋ฒ์ ์์ ์๋์ง ํ์ธ - ๋ก์ปฌ ๋ณ์์ ๊ณฑ์ ํฉ ๋์
- ์ต์ข ํฉ์ ์ฌ๋ฐ๋ฅธ ์ถ๋ ฅ ์์น์ ๊ธฐ๋ก
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p16 --naive
pixi run -e amd p16 --naive
pixi run -e apple p16 --naive
uv run poe p16 --naive
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([4.0, 6.0, 12.0, 22.0])
์๋ฃจ์
def naive_matmul[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
if row < size and col < size:
var acc: output.ElementType = 0
comptime for k in range(size):
acc += a[row, k] * b[k, col]
output[row, col] = acc
TileTensor๋ฅผ ํ์ฉํ ๊ธฐ๋ณธ ํ๋ ฌ ๊ณฑ์ ์ ๋ค์๊ณผ ๊ฐ์ ์ ๊ทผ ๋ฐฉ์์ ๋ฐ๋ฆ ๋๋ค:
ํ๋ ฌ ๋ ์ด์์ (2ร2 ์์)
Matrix A: Matrix B: Output C:
[a[0,0] a[0,1]] [b[0,0] b[0,1]] [c[0,0] c[0,1]]
[a[1,0] a[1,1]] [b[1,0] b[1,1]] [c[1,0] c[1,1]]
๊ตฌํ ์์ธ
-
์ค๋ ๋ ๋งคํ:
row = block_dim.y * block_idx.y + thread_idx.y col = block_dim.x * block_idx.x + thread_idx.x -
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์ง์ 2D ์ธ๋ฑ์ฑ:
a[row, k] - ์ ์น ์ ๊ทผ:
b[k, col] - ์ถ๋ ฅ ๊ธฐ๋ก:
output[row, col]
- ์ง์ 2D ์ธ๋ฑ์ฑ:
-
์ฐ์ฐ ํ๋ฆ:
# var๋ก ๊ฐ๋ณ ๋์ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ํ ์์ ์์ ํ์ ์ ์ฌ์ฉ var acc: output.element_type = 0 # @parameter๋ก ์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐ @parameter for k in range(size): acc += a[row, k] * b[k, col]
์ฃผ์ ์ธ์ด ๊ธฐ๋ฅ
-
๋ณ์ ์ ์ธ:
var acc: output.element_type = 0์์var๋ก ๊ฐ๋ณ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ,output.element_type์ผ๋ก ์ถ๋ ฅ ํ ์์ ๋์ผํ ํ์ ์ ์ง์ ํฉ๋๋ค- ๋์ ์ฐ์ฐ ์ ์ 0์ผ๋ก ์ด๊ธฐํ
-
๋ฃจํ ์ต์ ํ:
@parameter๋ฐ์ฝ๋ ์ดํฐ๋ก ์ปดํ์ผ ํ์์ ๋ฃจํ ์ ๊ฐ- ํฌ๊ธฐ๊ฐ ์๊ณ ๋ฏธ๋ฆฌ ์๋ ค์ง ํ๋ ฌ์์ ์ฑ๋ฅ ํฅ์
- ๋ ๋์ ๋ช ๋ น์ด ์ค์ผ์ค๋ง ๊ฐ๋ฅ
์ฑ๋ฅ ํน์ฑ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ๊ฐ ์ค๋ ๋๊ฐ
2 x SIZEํ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฝ์ - ์ค๋ ๋๋น ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ 1ํ
- ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ ์์
- ๊ฐ ์ค๋ ๋๊ฐ
-
์ฐ์ฐ ํจ์จ:
- ๋จ์ํ ๊ตฌํ์ด์ง๋ง ์ฑ๋ฅ์ ์ต์ ์ด ์๋
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ค๋ณต์ผ๋ก ๋ง์ด ์ฝ์
- ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ์ง ์์
-
ํ๊ณ:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๋ง์ด ์๋ชจ
- ๋ฎ์ ๋ฐ์ดํฐ ์ง์ญ์ฑ
- ํฐ ํ๋ ฌ๋ก ๊ฐ์๋ก ํ์ฅ์ฑ ๋ถ์กฑ
์ด ๊ธฐ๋ณธ ๊ตฌํ์ GPU ํ๋ ฌ ๊ณฑ์ ์ ์ดํดํ๊ธฐ ์ํ ๊ธฐ์ค์ ์ผ๋ก, ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ต์ ํํด์ผ ํ๋ ์ด์ ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
GPU ์ฑ๋ฅ ์ดํดํ๊ธฐ: ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ
๊ธฐ๋ณธ ํ๋ ฌ ๊ณฑ์ ์ ๊ตฌํํ์ผ๋, ์ด๋ฐ ๊ถ๊ธ์ฆ์ด ์๊ธธ ์ ์์ต๋๋ค: ์ฐ๋ฆฌ ์ปค๋์ ์ค์ ๋ก ์ผ๋ง๋ ์ ๋์ํ๊ณ ์์๊น? GPU์ ์ฐ์ฐ ๋ฅ๋ ฅ์ ์ํด ์ ํ๋๋ ๊ฑธ๊น, ์๋๋ฉด ๋ค๋ฅธ ๋ฌด์ธ๊ฐ๊ฐ ๋ฐ๋ชฉ์ ์ก๊ณ ์๋ ๊ฑธ๊น?
๋ฃจํ๋ผ์ธ ๋ชจ๋ธ(์ญ์ฃผ: ๋ฃจํ๋ผ์ธ์ โ์ํ์ โ์ด๋ผ๋ ๋ป์ผ๋ก, ์ฑ๋ฅ์ด ๋์ ์ ์๋ ํ๊ณ๋ฅผ ์ง๋ถ ์ ์ ๋น์ ํ ์ด๋ฆ์ ๋๋ค)์ GPU ์ต์ ํ์ ๋์นจ๋ฐ์ ๋๋ค. ์ปค๋์ ์ฑ๋ฅ์ ์ ํํ๋ ํ๋์จ์ด ๋ณ๋ชฉ์ด ๋ฌด์์ธ์ง ์๋ ค์ฃผ๊ณ , ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ์ต์ ํ ๋ฐฉํฅ์ผ๋ก ์๋ดํฉ๋๋ค. ๊ฐ์ผ๋ก ๊ฐ์ ํ๋ ๋์ , ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ด ์ ํํ ์ด๋์ ์ง์คํด์ผ ํ๋์ง ๋ณด์ฌ์ค๋๋ค.
1. ๋ชจ๋ GPU ์ปค๋์ ๋ ๊ฐ์ง ์ฑ๋ฅ ์ํ
๋ชจ๋ GPU ์ปค๋์ ๋ ๊ฐ์ง ๊ทผ๋ณธ์ ์ธ ์ ์ฝ ์๋์์ ๋์ํฉ๋๋ค:
- ์ฐ์ฐ ์ํ(compute ceiling) โ ์ฝ์ด๊ฐ ๋ถ๋์์์ ์ฐ์ฐ์ ์ผ๋ง๋ ๋น ๋ฅด๊ฒ ์ํํ ์ ์๋๊ฐ (์ต๋ FLOPs/s)
- ๋ฉ๋ชจ๋ฆฌ ์ํ(memory ceiling) โ ๋ฉ๋ชจ๋ฆฌ ์์คํ ์ด ์ฝ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ผ๋ง๋ ๋น ๋ฅด๊ฒ ๊ณต๊ธํ ์ ์๋๊ฐ (์ต๋ bytes/s)
์ด๋ค ์ํ์ด ์ปค๋์ ์ ์ฝํ๋์ง ํ์ ํ๋ ๊ฒ์ด ์ต์ ํ ์ ๋ต์ ํต์ฌ์ ๋๋ค. ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ ๋ ๊ฐ์ง ํต์ฌ ์งํ๋ฅผ ๊ทธ๋ํ๋ก ํํํ์ฌ ์ด ๊ด๊ณ๋ฅผ ์๊ฐํํฉ๋๋ค:
X์ถ: ์ฐ์ ๊ฐ๋(Arithmetic Intensity) โ ๋ฐ์ดํฐ 1๋ฐ์ดํธ๋น ์ํํ๋ ์ฐ์ฐ๋
\[\Large I = \frac{\text{Total FLOPs}}{\text{Total Bytes from Memory}} \quad [\text{FLOP/B}]\]
Y์ถ: ์ค์ธก ์ฑ๋ฅ(Sustained Performance) โ ์ปค๋์ด ์ค์ ๋ก ๋ฌ์ฑํ๋ ์๋
\[\Large P_{\text{sustained}} = \frac{\text{Total FLOPs}}{\text{Elapsed Time}} \quad [\text{GFLOP/s}]\]
๋ ๊ฐ์ โ์ํ(roof)โ์ด ๋ฌ์ฑ ๊ฐ๋ฅํ ์ฑ๋ฅ์ ์ํ์ ์ ํฉ๋๋ค:
| ์ํ | ์์ | ์๋ฏธ |
|---|---|---|
| ๋ฉ๋ชจ๋ฆฌ ์ํ | \(P = B_{\text{peak}} \cdot I\) | ๊ธฐ์ธ์ด์ง ์ง์ . ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ์ํด ์ฑ๋ฅ์ด ์ ํ๋จ |
| ์ฐ์ฐ ์ํ | \(P = P_{\text{peak}}\) | ์ํ์ . ์ฐ์ฐ ์ฒ๋ฆฌ๋์ ์ํด ์ฑ๋ฅ์ด ์ ํ๋จ |
์๊ณ ๊ฐ๋(critical intensity)
\[\Large I^* = \frac{P_{\text{peak}}}{B_{\text{peak}}}\]
๋ ์ปค๋์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋(\(I < I^\ast\) )์์ ์ฐ์ฐ ๋ฐ์ด๋(\(I > I^\ast\) )๋ก ์ ํ๋๋ ์ง์ ์ ๋๋ค.
2. ํ๋์จ์ด ์์: NVIDIA A100 ์ฌ์
์ด๋ก ์ NVIDIA A100์ ๊ตฌ์ฒด์ ์ธ ์ซ์๋ก ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค:
์ต๋ FP32 ์ฒ๋ฆฌ๋ \[\Large P_{\text{peak}} = 19.5 \text{ TFLOP/s} = 19{,}500 \text{ GFLOP/s}\]
์ต๋ HBM2 ๋์ญํญ \[\Large B_{\text{peak}} = 1{,}555 \text{ GB/s}\]
์๊ณ ๊ฐ๋ \[\Large I^* = \frac{19{,}500}{1{,}555} \approx 12.5 \text{ FLOP/B}\]
์ถ์ฒ: NVIDIA A100 Tensor Core GPU Architecture
์ด๋ ์ฐ์ ๊ฐ๋๊ฐ 12.5 FLOP/B ๋ฏธ๋ง์ธ ์ปค๋์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋, ๊ทธ ์ด์์ธ ์ปค๋์ ์ฐ์ฐ ๋ฐ์ด๋์์ ๋ปํฉ๋๋ค.
3. ํ๋ ฌ ๊ณฑ์ ๊ตฌํ์ ์๊ฐํ
์๋ ์ ๋๋ฉ์ด์ ์ ์ด ํผ์ฆ์ ๊ตฌํ๋ค์ด A100์ ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ณด์ฌ์ค๋๋ค:

์ด ์๊ฐํ๋ ์ด ํผ์ฆ์์ ๊ฑฐ์น๊ฒ ๋ ์ต์ ํ ๊ณผ์ ์ ๋ณด์ฌ์ค๋๋ค:
- ํ๋์จ์ด ์ ์ฝ โ ๋นจ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ํ๊ณผ ํ๋์ ์ฐ์ฐ ์ํ์ด ์ฑ๋ฅ ํ๊ณ๋ฅผ ์ ์
- ์ถ๋ฐ์ โ ๊ธฐ๋ณธ ๊ตฌํ(์ฃผํฉ์ ์ )์ด ๋ฉ๋ชจ๋ฆฌ ์ํ ์์ ์์น
- ์ต์ ํ ๋ชฉํ โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์ (์ฒญ๋ก์ ์ )์ผ๋ก ์ฐ์ ๊ฐ๋๊ฐ ๊ฐ์ ๋จ
- ๊ถ๊ทน์ ๋ชฉํ โ ๊ธ์ ํ์ดํ๋ ์ปค๋์ด ์ฐ์ฐ ๋ฐ์ด๋๊ฐ ๋๋ ์๊ณ ๊ฐ๋ ์ง์ ์ ๊ฐ๋ฆฌํด
4. ๊ธฐ๋ณธ ๊ตฌํ ๋ถ์
์ด์ ์น์ ์ ๊ธฐ๋ณธ ์ปค๋์ด ์ ์ด๋ฐ ์ฑ๋ฅ์ ๋ณด์ด๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค. \(2 \times 2\) ํ๋ ฌ ๊ณฑ์ ์ ๊ฒฝ์ฐ:
์ถ๋ ฅ ์์๋น ์ฐ์ฐ๋: \(\text{SIZE} + (\text{SIZE}-1) = 3 \text{ FLOPs }\)
๊ฐ ์์์๋ \(\text{SIZE}\) ํ์ ๊ณฑ์ ๊ณผ \(\text{SIZE} - 1\) ํ์ ๋ง์ ์ด ํ์ํฉ๋๋ค: \[C_{00} = A_{00} \cdot B_{00} + A_{01} \cdot B_{10}\] \(\text{SIZE} = 2\) ์ผ ๋ ๊ณฑ์ 2ํ + ๋ง์ 1ํ = 3 FLOPs
์ถ๋ ฅ ์์๋น ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ํ๋ ฌ A์ ํ: \(2 \times 4 = 8\) bytes (FP32)
- ํ๋ ฌ B์ ์ด: \(2 \times 4 = 8\) bytes (FP32)
- ํฉ๊ณ: ์ถ๋ ฅ ์์๋น \(16\) bytes
์ฐ์ ๊ฐ๋: \[\Large I_{\text{naive}} = \frac{3 \text{ FLOPs}}{16 \text{ bytes}} = 0.1875 \text{ FLOP/B}\]
์ด ์ฐ์ ๊ฐ๋๋ A100์ ์๊ณ ๊ฐ๋์ ํ์ฐธ ๋ชป ๋ฏธ์น๋ฏ๋ก, ๊ธฐ๋ณธ ์ปค๋์ ์ฌ๊ฐํ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํ์ ๋๋ค.
\[\Large I_{\text{naive}} = 0.1875 \ll I^* = 12.5\]
์์ ์ฑ๋ฅ: \[\Large P \approx B_{\text{peak}} \times I_{\text{naive}} = 1{,}555 \times 0.1875 \approx 292 \text{ GFLOP/s}\]
์ด๋ GPU ์ฐ์ฐ ์ ์ฌ๋ ฅ์ \(\frac{292}{19{,}500} \approx 1.5\%\) ์ ๋ถ๊ณผํฉ๋๋ค! ์๊ฐํ์์ ๋ ธ๋์ ์ ์ด ๋ฉ๋ชจ๋ฆฌ ์ํ ์์ ๋์ธ ๊ฒ์ด ์ด๋ฅผ ์ ๋ณด์ฌ์ค๋๋ค โ ์ฐ์ฐ ์ํ์๋ ํ์ฐธ ๋ฏธ์น์ง ๋ชปํ๋ ์์ค์ ๋๋ค.
5. ๋ค์ ๋จ๊ณ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ด ์๋ ค์ฃผ๋ ์ต์ ํ ์ ๋ต์ ๋ช ํํฉ๋๋ค: ์ค๋ณต ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ค์ฌ ์ฐ์ ๊ฐ๋๋ฅผ ๋์ด๋ ๊ฒ์ ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๋ฒ์ด ๋ฐ๋ก ์ด๋ฅผ ๋ฌ์ฑํฉ๋๋ค:
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ด์ :
- ํ๋ ฅ์ ๋ก๋ฉ: ์ค๋ ๋๋ค์ด ํจ๊ป ํ๋ ฌ ๋ธ๋ก์ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋
- ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ: ๋ก๋ํ ์์ ํ๋๋ฅผ ์ฌ๋ฌ ์ฐ์ฐ์ ํ์ฉ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ๊ฐ์: ๋๋ฆฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ๋ํ ์ ๊ทผ ํ์ ๊ฐ์
์ฐ์ ๊ฐ๋ ๊ฐ์ ์์์น: \[\Large I_{\text{shared}} = \frac{12 \text{ FLOPs}}{32 \text{ bytes}} = 0.375 \text{ FLOP/B}\]
์์ \(2 \times 2\) ๊ท๋ชจ์์๋ ์ฌ์ ํ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ด์ง๋ง, ์ด 2๋ฐฐ์ ์ฐ์ ๊ฐ๋ ํฅ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ์ ํจ์ฌ ๋ ๋ง์ด ์ฌ์ฌ์ฉํ ์ ์๋ ํฐ ํ๋ ฌ์์ ๊ทน์ ์ธ ํจ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
6. ๋ฃจํ๋ผ์ธ์ด ์๋ ค์ฃผ๋ ์ต์ ํ ์ ๋ต
๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ ํ์ฌ ์ฑ๋ฅ์ ์ง๋จํ ๋ฟ ์๋๋ผ, ์ต์ ํ ๋ฐฉํฅ๊น์ง ์๋ ค์ค๋๋ค. ์ดํ ํผ์ฆ์์ ์ดํด๋ณผ ํต์ฌ ๊ธฐ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
| ๊ธฐ๋ฒ | ๋ฃจํ๋ผ์ธ ํจ๊ณผ | ๊ตฌํ ๋ฐฉ๋ฒ |
|---|---|---|
| ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ๋ง | ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ์ผ๋ก ์ฐ์ ๊ฐ๋ โ | ํ๋ ฅ์ ๋ก๋ฉ, ๋ธ๋ก ๋จ์ ์ฐ์ฐ |
| ๋ ์ง์คํฐ ๋ธ๋กํน | ๋ ์ง์คํฐ ๋์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ๊ฐ์ | ๋ ์ง์คํฐ ๋ณ์์ ๋ฃจํ ์ ๊ฐ |
| ์ปค๋ ํจ์ | ์ฐ์ฐ ๊ฒฐํฉ์ผ๋ก ๋ฐ์ดํธ๋น FLOPs ์ฆ๊ฐ | ๋จ์ผ ์ปค๋์์ ์ฌ๋ฌ ์ฐ์ฐ ๋จ๊ณ ์ฒ๋ฆฌ |
| ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ(coalescing) | ์คํจ ๋์ญํญ ํ์ฉ ๊ทน๋ํ | ๊ตฌ์กฐํ๋ ์ ๊ทผ ํจํด, ์ ์ ํ ์ค๋ ๋ ๊ตฌ์ฑ |
| ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ๋ณต์ฌ | ์ ์ฉ ๋ณต์ฌ ์์ง์ผ๋ก ์ฐ์ฐ-๋ฉ๋ชจ๋ฆฌ ์ค์ฒฉ | copy_dram_to_sram_async์ ์ฐ์ฐ ์ค์ฒฉ |
| ํผํฉ ์ ๋ฐ๋ | ์์ ๋ฐ์ดํฐ ํ์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋ถํ ๊ฐ์ | FP16/BF16 ์ ๋ ฅ + FP32 ๋์ |
๊ฐ ๊ธฐ๋ฒ์ ์ปค๋์ ๋ฃจํ๋ผ์ธ ์์์ ์ด๋์ํต๋๋ค โ ๋ฉ๋ชจ๋ฆฌ ์ํ์ ๋ฐ๋ผ ์๋ก(๋์ญํญ ํ์ฉ ๊ฐ์ ), ๋๋ ์ค๋ฅธ์ชฝ ์ฐ์ฐ ์ํ์ ํฅํด(์ฐ์ ๊ฐ๋ ํฅ์).
๋น๋๊ธฐ ์ฐ์ฐ์ ๋ํ ์ฐธ๊ณ : ํ์ค GPU ๋ฉ๋ชจ๋ฆฌ ๋ก๋(ld.global)๋ ์ด๋ฏธ
๋น๋๊ธฐ์
๋๋ค โ ์ํ๋ ๋ก๋ํ ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ํ์ํด์ง ๋๊น์ง ๊ณ์ ์คํ๋ฉ๋๋ค.
cp.async(CUDA)๋
copy_dram_to_sram_async(Mojo)
๊ฐ์ ์ ์ฉ ๋น๋๊ธฐ ๋ณต์ฌ ๋ช
๋ น์ ์ฌ๊ธฐ์ ํ ๊ฑธ์ ๋ ๋์๊ฐ, ์ ์ฉ ๋ณต์ฌ ์์ง์ ์ฌ์ฉํ๊ณ
๋ ์ง์คํฐ๋ฅผ ์ฐํํ์ฌ ์์ ํ์ฉ์ ๋์
๋๋ค. ๋จ์ํ ๋๊ธฐ ์ฐ์ฐ์ ๋น๋๊ธฐ๋ก ๋ฐ๊พธ๋
๊ฒ๊ณผ๋ ๋ค๋ฆ
๋๋ค.
7. ๋จ์ํ ๋ฃจํ๋ผ์ธ์ ๋์ด์
๋ค๋จ๊ณ ๋ฉ๋ชจ๋ฆฌ: ๊ณ ๊ธ ๋ฃจํ๋ผ์ธ์ L2 ์บ์, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ ์ง์คํฐ ๋์ญํญ์ ๋ํด ๋ณ๋์ ์ํ์ ํฌํจํ์ฌ ์ด๋ค ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต์ด ์ฑ๋ฅ์ ์ ์ฝํ๋์ง ์๋ณํฉ๋๋ค.
ํต์ ๋ฃจํ๋ผ์ธ: ๋ฉํฐ GPU ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ๋์ ์ธํฐ์ปค๋ฅํธ ๋์ญํญ(NVLink, InfiniBand)์ ์ฌ์ฉํ์ฌ ์ค์ผ์ผ๋ง ํจ์จ์ ๋ถ์ํฉ๋๋ค.
์ ์ฉ ์ ๋: ์ต์ GPU๋ ๊ณ ์ ํ ์ฑ๋ฅ ํน์ฑ์ ๊ฐ์ง ํ ์ ์ฝ์ด๋ฅผ ํฌํจํ๋ฉฐ, ๋ณ๋์ ๋ฃจํ๋ผ์ธ ๋ถ์์ด ํ์ํฉ๋๋ค.
8. ์ค์ ์์ ๋ฃจํ๋ผ์ธ ํ์ฉํ๊ธฐ
- ์ปค๋ ํ๋กํ์ผ๋ง: Nsight Compute ๊ฐ์ ๋๊ตฌ๋ก ์ค์ FLOPs์ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ธก์
- ๋ฐ์ดํฐ ํฌ์ธํธ ํ์: ์ฐ์ ๊ฐ๋์ ์ค์ธก ์ฑ๋ฅ ๊ณ์ฐ
- ๋ณ๋ชฉ ์๋ณ: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋์ ๋ฉ๋ชจ๋ฆฌ ์ํ ์์, ์ฐ์ฐ ๋ฐ์ด๋ ์ปค๋์ ์ฐ์ฐ ์ํ์ ๊ทผ์
- ์ต์ ํ ์ ํ: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋์๋ ๋์ญํญ ๊ฐ์ ์, ์ฐ์ฐ ๋ฐ์ด๋ ์ปค๋์๋ ์๊ณ ๋ฆฌ์ฆ ๋ณ๊ฒฝ์ ์ง์ค
- ์ธก์ ๊ณผ ๋ฐ๋ณต: ์ต์ ํ๊ฐ ์ปค๋์ ๊ธฐ๋ํ ๋ฐฉํฅ์ผ๋ก ์ด๋์ํค๋์ง ๊ฒ์ฆ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํผ์ฆ๊ณผ์ ์ฐ๊ฒฐ
๋ค์ ์น์ ์์๋ ์ปค๋์ ๋ฃจํ๋ผ์ธ ์๋ก ๋์ด์ฌ๋ฆฌ๊ธฐ ์์ํ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ฅผ ๊ตฌํํฉ๋๋ค. ์๊ฐํ์์ ๋ณผ ์ ์๋ฏ์ด, ์ฃผํฉ์ ์ (๊ธฐ๋ณธ)์์ ์ฒญ๋ก์ ์ (๊ณต์ ๋ฉ๋ชจ๋ฆฌ)์ผ๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค โ ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ ๊ฐ์ ์ ํตํ ํ์คํ ์ฑ๋ฅ ํฅ์์ ๋๋ค.
\(2 \times 2\) ์์ ์์๋ ์ฐ์ฐ ์ํ์ ๋๋ฌํ์ง ๋ชปํ์ง๋ง, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฑ๋ฅ์ ๊ฒฐ์ ์ ์ธ ์ญํ ์ ํ๋ ํฐ ํ๋ ฌ์์ ๋์ผํ ์๋ฆฌ๊ฐ ์ด๋ป๊ฒ ํ์ฅ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค. ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ๋์์ด ๋๊ณ ์ผ๋ง๋ ๊ฐ์ ์ ๊ธฐ๋ํ ์ ์๋์ง ์ดํดํ๊ธฐ ์ํ ์ด๋ก ์ ํ ๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฃจํ๋ผ์ธ ๋ชจ๋ธ์ ์ดํดํ๋ฉด GPU ์ต์ ํ๊ฐ ์ถ์ธก์์ ์ฒด๊ณ์ ์ธ ์์ง๋์ด๋ง์ผ๋ก ๋ฐ๋๋๋ค. ์ด ์ฑ ์ ๋ชจ๋ ์ต์ ํ ๊ธฐ๋ฒ์ ์ด ๋จ์ํ์ง๋ง ๊ฐ๋ ฅํ ์ฑ๋ฅ ๋ชจ๋ธ์ ๋ํ ํจ๊ณผ๋ก ์ดํดํ ์ ์์ต๋๋ค.
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์
๊ฐ์
์ ๋ฐฉ ํ๋ ฌ \(A\) ์ \(B\) ์ ํ๋ ฌ ๊ณฑ์ ์ ๊ตฌํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ \(\text{output}\)์ ์ ์ฅํ๋ ํผ์ฆ์ ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ต์ ํํฉ๋๋ค. ์ฐ์ฐ ์ ์ ํ๋ ฌ ๋ธ๋ก์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ฏธ๋ฆฌ ๋ก๋ํ๋ ๋ฐฉ์์ ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
- TileTensor๋ฅผ ์ฌ์ฉํ ๋ธ๋ก ๋ก์ปฌ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
- ์ค๋ ๋ ๋๊ธฐํ ํจํด
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ ํ
- 2D ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ ํ๋ ฅ์ ๋ฐ์ดํฐ ๋ก๋ฉ
- ํ๋ ฌ ์ฐ์ฐ์ TileTensor๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ฉํ๊ธฐ
ํต์ฌ์ TileTensor๋ฅผ ํตํด ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋น์ฉ์ด ํฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ต์ํํ๋ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ
- ํ๋ ฌ ํฌ๊ธฐ: \(\text{SIZE} \times \text{SIZE} = 2 \times 2\)
- ๋ธ๋ก๋น ์ค๋ ๋ ์: \(\text{TPB} \times \text{TPB} = 3 \times 3\)
- ๊ทธ๋ฆฌ๋ ์ฐจ์: \(1 \times 1\)
๋ ์ด์์ ๊ตฌ์ฑ:
- ์
๋ ฅ A:
row_major[SIZE, SIZE]() - ์
๋ ฅ B:
row_major[SIZE, SIZE]() - ์ถ๋ ฅ:
row_major[SIZE, SIZE]() - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
TPB ร TPBํฌ๊ธฐ์ TileTensor 2๊ฐ
๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ:
Global Memory (TileTensor): Shared Memory (TileTensor):
A[i,j]: Direct access a_shared[local_row, local_col]
B[i,j]: Direct access b_shared[local_row, local_col]
์์ฑํ ์ฝ๋
def single_block_matmul(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
var local_row = thread_idx.y
var local_col = thread_idx.x
# FILL ME IN (roughly 12 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p16/p16.mojo
ํ
- ์ ์ญ ์ธ๋ฑ์ค์ ๋ก์ปฌ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ ฌ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋
- ๋ก๋ ํ
barrier()ํธ์ถ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ด์ ๊ณ์ฐ
- ๋ชจ๋ ์ฐ์ฐ์์ ๋ฐฐ์ด ๊ฒฝ๊ณ ๊ฒ์ฌ
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p16 --single-block
pixi run -e amd p16 --single-block
pixi run -e apple p16 --single-block
uv run poe p16 --single-block
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([4.0, 6.0, 12.0, 22.0])
์๋ฃจ์
def single_block_matmul[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
var row = block_dim.y * block_idx.y + thread_idx.y
var col = block_dim.x * block_idx.x + thread_idx.x
var local_row = thread_idx.y
var local_col = thread_idx.x
var a_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
var b_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
if row < size and col < size:
a_shared[local_row, local_col] = a[row, col]
b_shared[local_row, local_col] = b[row, col]
barrier()
if row < size and col < size:
var acc: output.ElementType = 0
comptime for k in range(size):
acc += a_shared[local_row, k] * b_shared[k, local_col]
output[row, col] = acc
TileTensor๋ฅผ ํ์ฉํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ตฌํ์ ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ํตํด ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค:
๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ
Input Tensors (2ร2): Shared Memory (3ร3):
Matrix A: a_shared:
[a[0,0] a[0,1]] [s[0,0] s[0,1] s[0,2]]
[a[1,0] a[1,1]] [s[1,0] s[1,1] s[1,2]]
[s[2,0] s[2,1] s[2,2]]
Matrix B: b_shared: (๋น์ทํ ๋ ์ด์์)
[b[0,0] b[0,1]] [t[0,0] t[0,1] t[0,2]]
[b[1,0] b[1,1]] [t[1,0] t[1,1] t[1,2]]
[t[2,0] t[2,1] t[2,2]]
๊ตฌํ ๋จ๊ณ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ค์ :
# address_space๋ฅผ ์ง์ ํ TileTensor๋ก 2D ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ์ ์์ฑ a_shared = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB, TPB]()) b_shared = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB, TPB]()) -
์ค๋ ๋ ์ธ๋ฑ์ฑ:
# ํ๋ ฌ ์ ๊ทผ์ ์ํ ์ ์ญ ์ธ๋ฑ์ค row = block_dim.y * block_idx.y + thread_idx.y col = block_dim.x * block_idx.x + thread_idx.x # ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ฉ ๋ก์ปฌ ์ธ๋ฑ์ค local_row = thread_idx.y local_col = thread_idx.x -
๋ฐ์ดํฐ ๋ก๋ฉ:
# TileTensor ์ธ๋ฑ์ฑ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ if row < size and col < size: a_shared[local_row, local_col] = a[row, col] b_shared[local_row, local_col] = b[row, col] -
๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ฐ์ฐ:
# ๊ฐ๋๋ก ์ ํจํ ํ๋ ฌ ์์๋ง ๊ณ์ฐ if row < size and col < size: # ์ถ๋ ฅ ํ ์์ ํ์ ์ผ๋ก ๋์ ๋ณ์ ์ด๊ธฐํ var acc: output.element_type = 0 # ์ปดํ์ผ ํ์์ ์ ๊ฐ๋๋ ํ๋ ฌ ๊ณฑ์ ๋ฃจํ @parameter for k in range(size): acc += a_shared[local_row, k] * b_shared[k, local_col] # ํ๋ ฌ ๊ฒฝ๊ณ ๋ด์ ์ค๋ ๋๋ง ๊ฒฐ๊ณผ ๊ธฐ๋ก output[row, col] = acc์ฃผ์ ํฌ์ธํธ:
-
๊ฒฝ๊ณ ๊ฒ์ฌ:
if row < size and col < size- ๋ฒ์ ๋ฐ ์ฐ์ฐ ๋ฐฉ์ง
- ์ ํจํ ์ค๋ ๋๋ง ์์ ์ํ
- TPB (3ร3) > SIZE (2ร2)์ด๋ฏ๋ก ํ์
-
๋์ ๋ณ์ ํ์ :
var acc: output.element_type- ์ถ๋ ฅ ํ ์์ ์์ ํ์ ์ผ๋ก ํ์ ์์ ์ฑ ํ๋ณด
- ์ผ๊ด๋ ์์น ์ ๋ฐ๋ ๋ณด์ฅ
- ๋์ ์ ์ 0์ผ๋ก ์ด๊ธฐํ
-
๋ฃจํ ์ต์ ํ:
@parameter for k in range(size)- ์ปดํ์ผ ํ์์ ๋ฃจํ ์ ๊ฐ
- ๋ ๋์ ๋ช ๋ น์ด ์ค์ผ์ค๋ง ๊ฐ๋ฅ
- ํฌ๊ธฐ๊ฐ ์๊ณ ๋ฏธ๋ฆฌ ์๋ ค์ง ํ๋ ฌ์ ํจ๊ณผ์
-
๊ฒฐ๊ณผ ๊ธฐ๋ก:
output[row, col] = acc- ๋์ผํ ๊ฐ๋ ์กฐ๊ฑด์ผ๋ก ๋ณดํธ
- ์ ํจํ ์ค๋ ๋๋ง ๊ฒฐ๊ณผ ๊ธฐ๋ก
- ํ๋ ฌ ๊ฒฝ๊ณ ์์ ์ฑ ์ ์ง
-
์ค๋ ๋ ์์ ์ฑ๊ณผ ๋๊ธฐํ
-
๊ฐ๋ ์กฐ๊ฑด:
- ์
๋ ฅ ๋ก๋ฉ:
if row < size and col < size - ์ฐ์ฐ: ๋์ผํ ๊ฐ๋๋ก ์ค๋ ๋ ์์ ์ฑ ๋ณด์ฅ
- ์ถ๋ ฅ ๊ธฐ๋ก: ๊ฐ์ ์กฐ๊ฑด์ผ๋ก ๋ณดํธ
- ์๋ชป๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๊ณผ ๊ฒฝ์ ์ํ ๋ฐฉ์ง
- ์
๋ ฅ ๋ก๋ฉ:
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์์ ์ฑ:
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: TPB ๋ฒ์ ๋ด์์๋ง ์ ๊ทผ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ: ํฌ๊ธฐ ๊ฒ์ฌ๋ก ๋ณดํธ
- ์ถ๋ ฅ: ๊ฐ๋๋ ์ฐ๊ธฐ๋ก ๋ฐ์ดํฐ ์์ ๋ฐฉ์ง
์ฃผ์ ์ธ์ด ๊ธฐ๋ฅ
-
TileTensor์ ์ฅ์ :
- ์ง์ 2D ์ธ๋ฑ์ฑ์ผ๋ก ์ฝ๋ ๋จ์ํ
element_type์ ํตํ ํ์ ์์ ์ฑ- ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ฒ๋ฆฌ
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น:
- address_space๋ฅผ ์ง์ ํ TileTensor๋ก ๊ตฌ์กฐํ๋ ํ ๋น
- ์ ๋ ฅ ํ ์์ ๋์ผํ ํ ์ฐ์ ๋ ์ด์์
- ํจ์จ์ ์ ๊ทผ์ ์ํ ์ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ
-
๋๊ธฐํ:
barrier()๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ผ๊ด์ฑ ๋ณด์ฅ- ๋ก๋์ ์ฐ์ฐ ๊ฐ ์ ์ ํ ๋๊ธฐํ
- ๋ธ๋ก ๋ด ์ค๋ ๋ ๊ฐ ํ๋ ฅ
์ฑ๋ฅ ์ต์ ํ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ:
- ์์๋น ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ก๋ 1ํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํ ๋ค์ค ์ฌ์ฌ์ฉ
- ๋ณํฉ๋(coalesced) ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
-
์ค๋ ๋ ํ๋ ฅ:
- ํ๋ ฅ์ ๋ฐ์ดํฐ ๋ก๋ฉ
- ๊ณต์ ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ
- ํจ์จ์ ์ธ ์ค๋ ๋ ๋๊ธฐํ
-
์ฐ์ฐ ์ด์ :
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ๊ฐ์
- ์บ์ ํ์ฉ๋ ํฅ์
- ๋ช ๋ น์ด ์ฒ๋ฆฌ๋ ๊ฐ์
์ด ๊ตฌํ์ ๋ค์์ ํตํด ๊ธฐ๋ณธ ๋ฒ์ ๋๋น ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํ์ ๊ฐ์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํ ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ
- TileTensor์ ํจ์จ์ ์ธ 2D ์ธ๋ฑ์ฑ ํ์ฉ
- ์ ์ ํ ์ค๋ ๋ ๋๊ธฐํ ์ ์ง
ํ์ผ๋ง ๋ฒ์
๊ฐ์
TileTensor๋ฅผ ์ฌ์ฉํ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ผ๋ก ์ ๋ฐฉ ํ๋ ฌ \(A\) ์ \(B\) ๋ฅผ ๊ณฑํ๋ ์ปค๋์ ๊ตฌํํ์ธ์. ํฐ ํ๋ ฌ์ ์์ ์กฐ๊ฐ(ํ์ผ)์ผ๋ก ๋๋์ด ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค.
ํต์ฌ ๊ฐ๋
- TileTensor๋ฅผ ์ฌ์ฉํ ํ๋ ฌ ํ์ผ๋ง์ผ๋ก ํจ์จ์ ์ธ ์ฐ์ฐ
- ์ ์ ํ ๋ ์ด์์์ ์ฌ์ฉํ ๋ฉํฐ ๋ธ๋ก ์กฐ์จ
- TensorBuilder๋ฅผ ํตํ ํจ์จ์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- TileTensor ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ ํ์ผ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
๊ตฌ์ฑ
- ํ๋ ฌ ํฌ๊ธฐ: \(\text{SIZE_TILED} = 9\)
- ๋ธ๋ก๋น ์ค๋ ๋ ์: \(\text{TPB} \times \text{TPB} = 3 \times 3\)
- ๊ทธ๋ฆฌ๋ ์ฐจ์: \(3 \times 3\) ๋ธ๋ก
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก๋น \(\text{TPB} \times \text{TPB}\) TileTensor 2๊ฐ
๋ ์ด์์ ๊ตฌ์ฑ:
- ์
๋ ฅ A:
row_major[SIZE_TILED, SIZE_TILED]() - ์
๋ ฅ B:
row_major[SIZE_TILED, SIZE_TILED]() - ์ถ๋ ฅ:
row_major[SIZE_TILED, SIZE_TILED]() - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: TensorBuilder๋ฅผ ์ฌ์ฉํ
TPB ร TPBTileTensor 2๊ฐ
ํ์ผ๋ง ์ ๋ต
๋ธ๋ก ๊ตฌ์ฑ
Grid Layout (3ร3): Thread Layout per Block (3ร3):
[B00][B01][B02] [T00 T01 T02]
[B10][B11][B12] [T10 T11 T12]
[B20][B21][B22] [T20 T21 T22]
๊ฐ ๋ธ๋ก์ TileTensor ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ์ฌ ํ๋์ ํ์ผ์ ์ฒ๋ฆฌ
ํ์ผ ์ฒ๋ฆฌ ๋จ๊ณ
- ์ค๋ ๋ ์์น์ ๋ํ ์ ์ญ ์ธ๋ฑ์ค์ ๋ก์ปฌ ์ธ๋ฑ์ค ๊ณ์ฐ
- A์ B ํ์ผ์ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น
- ๊ฐ ํ์ผ์ ๋ํด:
- ํ๋ ฌ A์ B์์ ํ์ผ ๋ก๋
- ๋ถ๋ถ ๊ณฑ ๊ณ์ฐ
- ๋ ์ง์คํฐ์ ๊ฒฐ๊ณผ ๋์
- ์ต์ข ๋์ ๊ฒฐ๊ณผ ๊ธฐ๋ก
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
Matrix A (8ร8) Matrix B (8ร8) Matrix C (8ร8)
+---+---+---+ +---+---+---+ +---+---+---+
|T00|T01|T02| ... |T00|T01|T02| ... |T00|T01|T02| ...
+---+---+---+ +---+---+---+ +---+---+---+
|T10|T11|T12| |T10|T11|T12| |T10|T11|T12|
+---+---+---+ +---+---+---+ +---+---+---+
|T20|T21|T22| |T20|T21|T22| |T20|T21|T22|
+---+---+---+ +---+---+---+ +---+---+---+
... ... ...
ํ์ผ ์ฒ๋ฆฌ ๊ณผ์ (C[T11] ๊ณ์ฐ ์์):
1. A์ B์์ ํ์ผ ๋ก๋:
+---+ +---+
|A11| ร |B11| ๊ฐ ๋จ๊ณ k์ ๋ํด:
+---+ +---+ C[T11] += A[row, k] ร B[k, col]
2. ํ์ผ ์ด๋:
๋จ๊ณ 1 ๋จ๊ณ 2 ๋จ๊ณ 3
A: [T10] A: [T11] A: [T12]
B: [T01] B: [T11] B: [T21]
3. ํ์ผ ๋ด ๊ฐ ์ค๋ ๋ (i,j)์ ์ฐ์ฐ:
C[i,j] = ฮฃ (A[i,k] ร B[k,j]), k๋ ํ์ผ ๋๋น ๋ฒ์
๋๊ธฐํ ํ์ ์์ :
* ํ์ผ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ํ ํ
* ๊ฐ ๋จ๊ณ์ ์ฐ์ฐ์ด ๋๋ ํ
์์ฑํ ์ฝ๋
comptime SIZE_TILED = 9
comptime BLOCKS_PER_GRID_TILED = (3, 3) # each block convers 3x3 elements
comptime THREADS_PER_BLOCK_TILED = (TPB, TPB)
comptime layout_tiled = row_major[SIZE_TILED, SIZE_TILED]()
comptime LayoutTiledType = type_of(layout_tiled)
def matmul_tiled(
output: TileTensor[mut=True, dtype, LayoutTiledType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
):
var local_row = thread_idx.y
var local_col = thread_idx.x
var tiled_row = block_idx.y * TPB + thread_idx.y
var tiled_col = block_idx.x * TPB + thread_idx.x
# FILL ME IN (roughly 20 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p16/p16.mojo
ํ
-
ํ์ค ์ธ๋ฑ์ฑ ๊ท์น์ ์ฌ์ฉํ์ธ์:
local_row = thread_idx.y,local_col = thread_idx.x -
์ ์ญ ์์น ๊ณ์ฐ:
global_row = block_idx.y * TPB + local_row๊ทธ๋ฆฌ๊ณ
global_col = block_idx.x * TPB + local_col์ ์ญ ์ธ๋ฑ์ฑ ๊ณต์ ์ดํดํ๊ธฐ:
-
๊ฐ ๋ธ๋ก์ ํ๋ ฌ์
TPB ร TPBํ์ผ์ ์ฒ๋ฆฌํฉ๋๋ค -
block_idx.y๋ ํ์ฌ ๋ช ๋ฒ์งธ ๋ธ๋ก ํ์ธ์ง๋ฅผ ๋ํ๋ ๋๋ค (0, 1, 2โฆ) -
block_idx.y * TPB๋ ํด๋น ๋ธ๋ก ํ์ผ์ ์์ ํ์ ๋๋ค -
local_row(0~TPB-1)์ ๋ธ๋ก ๋ด ์ค๋ ๋์ ์คํ์ ์ ๋๋ค -
๋์ ๋ํ๋ฉด ์ ์ฒด ํ๋ ฌ์์์ ์ค์ ํ ์์น๊ฐ ๋ฉ๋๋ค
TPB=3 ์์:
Block Layout: Global Matrix (9ร9): [B00][B01][B02] [0 1 2 | 3 4 5 | 6 7 8] [B10][B11][B12] โ [9 A B | C D E | F G H] [B20][B21][B22] [I J K | L M N | O P Q] โโโโโโโโโโโโโโโโโโโโโโ [R S T | U V W | X Y Z] [a b c | d e f | g h i] [j k l | m n o | p q r] โโโโโโโโโโโโโโโโโโโโโโ [s t u | v w x | y z ฮฑ] [ฮฒ ฮณ ฮด | ฮต ฮถ ฮท | ฮธ ฮน ฮบ] [ฮป ฮผ ฮฝ | ฮพ ฮฟ ฯ | ฯ ฯ ฯ] Thread(1,2) in Block(1,0): - block_idx.y = 1, local_row = 1 - global_row = 1 * 3 + 1 = 4 - ์ด ์ค๋ ๋๋ ํ๋ ฌ์ 4๋ฒ์งธ ํ์ ๋ด๋น ```text
-
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น (
.fill(0)์ผ๋ก ์ฌ์ ์ด๊ธฐํ๋จ) -
9ร9 ์๋ฒฝํ ํ์ผ๋ง์ด๋ฏ๋ก ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ๋ถํ์!
-
์ ์ ํ ๋๊ธฐํ์ ํจ๊ป ํ์ผ ๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋์
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p16 --tiled
pixi run -e amd p16 --tiled
pixi run -e apple p16 --tiled
uv run poe p16 --tiled
ํผ์ฆ์ ์์ง ํ์ง ์์๋ค๋ฉด ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
out: HostBuffer([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([3672.0, 3744.0, 3816.0, 3888.0, 3960.0, 4032.0, 4104.0, 4176.0, 4248.0, 9504.0, 9738.0, 9972.0, 10206.0, 10440.0, 10674.0, 10908.0, 11142.0, 11376.0, 15336.0, 15732.0, 16128.0, 16524.0, 16920.0, 17316.0, 17712.0, 18108.0, 18504.0, 21168.0, 21726.0, 22284.0, 22842.0, 23400.0, 23958.0, 24516.0, 25074.0, 25632.0, 27000.0, 27720.0, 28440.0, 29160.0, 29880.0, 30600.0, 31320.0, 32040.0, 32760.0, 32832.0, 33714.0, 34596.0, 35478.0, 36360.0, 37242.0, 38124.0, 39006.0, 39888.0, 38664.0, 39708.0, 40752.0, 41796.0, 42840.0, 43884.0, 44928.0, 45972.0, 47016.0, 44496.0, 45702.0, 46908.0, 48114.0, 49320.0, 50526.0, 51732.0, 52938.0, 54144.0, 50328.0, 51696.0, 53064.0, 54432.0, 55800.0, 57168.0, 58536.0, 59904.0, 61272.0])
์๋ฃจ์ : ์๋ ํ์ผ๋ง
def matmul_tiled[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutTiledType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
):
var local_row = thread_idx.y
var local_col = thread_idx.x
var tiled_row = block_idx.y * TPB + local_row
var tiled_col = block_idx.x * TPB + local_col
var a_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
var b_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
var acc: output.ElementType = 0
# Iterate over tiles to compute matrix product
comptime for tile in range((size + TPB - 1) // TPB):
# Load A tile - global row stays the same, col determined by tile
if tiled_row < size and (tile * TPB + local_col) < size:
a_shared[local_row, local_col] = a[
tiled_row, tile * TPB + local_col
]
# Load B tile - row determined by tile, global col stays the same
if (tile * TPB + local_row) < size and tiled_col < size:
b_shared[local_row, local_col] = b[
tile * TPB + local_row, tiled_col
]
barrier()
# Matrix multiplication within the tile
if tiled_row < size and tiled_col < size:
comptime for k in range(min(Int(TPB), Int(size - tile * TPB))):
acc += a_shared[local_row, k] * b_shared[k, local_col]
barrier()
# Write out final result
if tiled_row < size and tiled_col < size:
output[tiled_row, tiled_col] = acc
ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ๊ตฌํ์ ์์ ํ์ผ \((3 \times 3)\) ์ ์ฌ์ฉํ์ฌ ํฐ ํ๋ ฌ \((9 \times 9)\) ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ๋์ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น
Input matrices (9ร9) - (3ร3) ํ์ผ๋ง์ ๋ฑ ๋ง๋ ํฌ๊ธฐ: A = [0 1 2 3 4 5 6 7 8 ] B = [0 2 4 6 8 10 12 14 16] [9 10 11 12 13 14 15 16 17] [18 20 22 24 26 28 30 32 34] [18 19 20 21 22 23 24 25 26] [36 38 40 42 44 46 48 50 52] [27 28 29 30 31 32 33 34 35] [54 56 58 60 62 64 66 68 70] [36 37 38 39 40 41 42 43 44] [72 74 76 78 80 82 84 86 88] [45 46 47 48 49 50 51 52 53] [90 92 94 96 98 100 102 104 106] [54 55 56 57 58 59 60 61 62] [108 110 112 114 116 118 120 122 124] [63 64 65 66 67 68 69 70 71] [126 128 130 132 134 136 138 140 142] [72 73 74 75 76 77 78 79 80] [144 146 148 150 152 154 156 158 160] ๋ธ๋ก๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ (3ร3): a_shared[TPB, TPB] b_shared[TPB, TPB] -
ํ์ผ ์ฒ๋ฆฌ ๋ฃจํ
ํ์ผ ์ = 9 // 3 = 3๊ฐ (๋๋จธ์ง ์์ด ๋ฑ ๋๋ ์ง!) ๊ฐ ํ์ผ์ ๋ํด: 1. A์ B์์ ํ์ผ ๋ก๋ 2. ๋ถ๋ถ ๊ณฑ ๊ณ์ฐ 3. ๋ ์ง์คํฐ์ ๋์ -
๋ฉ๋ชจ๋ฆฌ ๋ก๋ฉ ํจํด
-
\((9 \times 9)\) ์ด ๋ฑ ๋๋ ์ง๋ฏ๋ก ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ๊ธฐ์ ์ ์ผ๋ก๋ ๋ถํ์ํ์ง๋ง, ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๋ค๋ฅธ ํ๋ ฌ ํฌ๊ธฐ์๋ ๋์ํ ์ ์๋๋ก ํฌํจํฉ๋๋ค.
# A ํ์ผ ๋ก๋ - ์ ์ญ ํ์ ๊ทธ๋๋ก, ์ด์ ํ์ผ์ ์ํด ๊ฒฐ์ if tiled_row < size and (tile * TPB + local_col) < size: a_shared[local_row, local_col] = a[ tiled_row, tile * TPB + local_col ] # B ํ์ผ ๋ก๋ - ํ์ ํ์ผ์ ์ํด ๊ฒฐ์ , ์ ์ญ ์ด์ ๊ทธ๋๋ก if (tile * TPB + local_row) < size and tiled_col < size: b_shared[local_row, local_col] = b[ tile * TPB + local_row, tiled_col ]
-
-
ํ์ผ ๋ด ์ฐ์ฐ
for k in range(min(TPB, size - tile * TPB)): acc += a_shared[local_row, k] * b_shared[k, local_col]-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋ ํํผ:
Bank Conflict Free (Good): Bank Conflicts (Bad): Thread0: a_shared[0,k] b_shared[k,0] Thread0: a_shared[k,0] b_shared[0,k] Thread1: a_shared[0,k] b_shared[k,1] Thread1: a_shared[k,0] b_shared[1,k] Thread2: a_shared[0,k] b_shared[k,2] Thread2: a_shared[k,0] b_shared[2,k] โ โ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ๋ณ๋ ฌ ์ ๊ทผ b_shared๊ฐ ์ด ์ฐ์ ์ด์๋ค๋ฉด (a_shared๋ broadcast) ๊ฐ์ ๋ฑ ํฌ์ ์ง๋ ฌ ์ ๊ทผ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋ ์ค๋ช :
- ์ผ์ชฝ (Good):
b_shared[k,threadIdx.x]๋ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผํ๊ณ ,a_shared[0,k]๋ ๋ชจ๋ ์ค๋ ๋์ ๋ธ๋ก๋์บ์คํธ ๋ฉ๋๋ค - ์ค๋ฅธ์ชฝ (Bad): b_shared๊ฐ ์ด ์ฐ์ ์ด์๋ค๋ฉด ์ค๋ ๋๋ค์ด ๋์์ ๊ฐ์ ๋ฑ ํฌ์ ์ ๊ทผํ๊ฒ ๋ฉ๋๋ค
- ํต์ฌ: ์ด๊ฒ์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๊ดํ ๊ฒ์ ๋๋ค
- ๋ฑ ํฌ ๊ตฌ์กฐ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ 32๊ฐ ๋ฑ ํฌ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๋ฑ ํฌ์ ๋ค๋ฅธ ์ฃผ์์ ์ ๊ทผํ ๋ ์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค
- ์ผ์ชฝ (Good):
-
-
๋๊ธฐํ ์ง์
barrier() ํธ์ถ ์์ : 1. ํ์ผ ๋ก๋ฉ ํ 2. ํ์ผ ์ฐ์ฐ ํ
์ฃผ์ ์ฑ๋ฅ ํน์ฑ:
- \((3 \times 3)\) ํ์ผ๋ก \((9 \times 9)\) ํ๋ ฌ ์ฒ๋ฆฌ (๋ฑ ๋ง๋ ํฌ๊ธฐ!)
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ๋น ๋ฅธ ํ์ผ ์ ๊ทผ
- ๋ณํฉ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ผ๋ก ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์ ์ต์ํ
- ๋ฑ ํฌ ์ถฉ๋์ ํผํ๋๋ก ์ต์ ํ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์๊ณผ ์ ๊ทผ ํจํด
-
๊ฒฐ๊ณผ ๊ธฐ๋ก:
if tiled_row < size and tiled_col < size: output[tiled_row, tiled_col] = acc- ๋ค๋ฅธ ํ๋ ฌ ํฌ๊ธฐ์ ํ์ผ๋ง ์ ๋ต์ ์ํ ๋ฐฉ์ด์ ๊ฒฝ๊ณ ๊ฒ์ฌ ํฌํจ
- ์ถ๋ ฅ ํ๋ ฌ์ ์ง์ ๋์
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ํจํ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ก
์ฃผ์ ์ต์ ํ
-
๋ ์ด์์ ์ต์ ํ:
- ๋ชจ๋ ํ ์์ ํ ์ฐ์ ๋ ์ด์์
- ํจ์จ์ ์ธ 2D ์ธ๋ฑ์ฑ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ๋ณํฉ๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ก๋
- ํจ์จ์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
-
์ฐ์ฐ:
- ๋ ์ง์คํฐ ๊ธฐ๋ฐ ๋์ , ์ฆ
var acc: output.element_type = 0 @parameter๋ฅผ ํตํ ์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐ
- ๋ ์ง์คํฐ ๊ธฐ๋ฐ ๋์ , ์ฆ
์ด ๊ตฌํ์ ๋ค์์ ํตํด ๋์ ์ฑ๋ฅ์ ๋ฌ์ฑํฉ๋๋ค:
- TileTensor๋ฅผ ํ์ฉํ ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ์ต์ ์ ํ์ผ๋ง ์ ๋ต
- ์ ์ ํ ์ค๋ ๋ ๋๊ธฐํ
- ์ธ์ฌํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
์๋ฃจ์ : ๊ด์ฉ์ TileTensor ํ์ผ๋ง
from std.gpu.memory import async_copy_wait_all
from layout.layout_tensor import copy_dram_to_sram_async
from layout import Layout as IntTupleLayout
comptime NUM_THREADS = TPB * TPB
comptime BLOCK_DIM_COUNT = 2
def matmul_idiomatic_tiled[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutTiledType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutTiledType, ImmutAnyOrigin],
):
var local_row = thread_idx.y
var local_col = thread_idx.x
var tiled_row = block_idx.y * TPB + local_row
var tiled_col = block_idx.x * TPB + local_col
# Get the tile of the output matrix that this thread block is responsible for
var out_tile = output.tile[TPB, TPB](block_idx.y, block_idx.x)
var a_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
var b_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB, TPB]())
var acc: output.ElementType = 0
comptime load_a_layout = IntTupleLayout.row_major(
1, TPB
) # Coalesced loading
comptime load_b_layout = IntTupleLayout.row_major(
1, TPB
) # Coalesced loading
# Note: Both matrices stored in same orientation for correct matrix multiplication
# Transposed loading would be useful if B were pre-transposed in global memory
comptime for idx in range(
size // TPB
): # Perfect division: 9 // 3 = 3 tiles
# Get tiles from A and B matrices
var a_tile = a.tile[TPB, TPB](block_idx.y, Int(idx))
var b_tile = b.tile[TPB, TPB](Int(idx), block_idx.x)
# Asynchronously copy tiles to shared memory with consistent orientation
copy_dram_to_sram_async[
thread_layout=load_a_layout,
num_threads=NUM_THREADS,
block_dim_count=BLOCK_DIM_COUNT,
](a_shared.to_layout_tensor(), a_tile.to_layout_tensor())
copy_dram_to_sram_async[
thread_layout=load_b_layout,
num_threads=NUM_THREADS,
block_dim_count=BLOCK_DIM_COUNT,
](b_shared.to_layout_tensor(), b_tile.to_layout_tensor())
# Wait for all async copies to complete
async_copy_wait_all()
barrier()
# Compute partial matrix multiplication for this tile
comptime for k in range(TPB):
acc += a_shared[local_row, k] * b_shared[k, local_col]
barrier()
# Write final result to output tile
if tiled_row < size and tiled_col < size:
out_tile[local_row, local_col] = acc
๊ด์ฉ์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ Mojo์ TileTensor API์ ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ํ์ฉํ์ฌ ๊น๋ํ ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค.
ํต์ฌ ํฌ์ธํธ: ์ด ๊ตฌํ์ ๋ ํ๋ ฌ ๋ชจ๋ ๋ณํฉ ๋ก๋ฉ์ ์ฌ์ฉํ์ฌ ํ์ค A ร B ํ๋ ฌ ๊ณฑ์ ์ ์ํํฉ๋๋ค.
์ด ๊ตฌํ์ด ํ๋ ๊ฒ:
- ํ๋ ฌ ์ฐ์ฐ: ํ์ค \(A \times B\) ๊ณฑ์ (\(A \times B^T\) ๊ฐ ์๋)
- ๋ก๋ฉ ํจํด: ๋ ํ๋ ฌ ๋ชจ๋
row_major[1, TPB]()๋ก ๋ณํฉ ์ ๊ทผ - ์ฐ์ฐ:
acc += a_shared[local_row, k] * b_shared[k, local_col] - ๋ฐ์ดํฐ ๋ ์ด์์: ๋ก๋ฉ ์ ์ ์น ์์ - ๋ ํ๋ ฌ์ ๊ฐ์ ๋ฐฉํฅ์ผ๋ก ๋ก๋
์ด ๊ตฌํ์ด ํ์ง ์๋ ๊ฒ:
- \(A \times B^T\) ๊ณฑ์ ์ ์ํํ์ง ์์
- ์ ์น ๋ก๋ฉ ํจํด์ ์ฌ์ฉํ์ง ์์
- ๋ณต์ฌ ๊ณผ์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์นํ์ง ์์
\((9 \times 9)\) ํ๋ ฌ ํฌ๊ธฐ์์๋ ์๋ฒฝํ ํ์ผ๋ง์ด ์ด๋ฃจ์ด์ ธ ๋ชจ๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ๋ถํ์ํฉ๋๋ค:
-
TileTensor ํ์ผ API
out_tile = output.tile[TPB, TPB](block_idx.y, block_idx.x) a_tile = a.tile[TPB, TPB](block_idx.y, idx) b_tile = b.tile[TPB, TPB](idx, block_idx.x)์๋ ์ขํ ๊ณ์ฐ ์์ด โ(block_idx.y, block_idx.x) ์์น์ ํ์ผ์ ๊ฐ์ ธ์จ๋คโ๋ฅผ ์ง์ ํํํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
-
๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ
copy_dram_to_sram_async[ thread_layout = load_a_layout, num_threads = NUM_THREADS, block_dim_count = BLOCK_DIM_COUNT ](a_shared,a_tile) copy_dram_to_sram_async[ thread_layout = load_b_layout, num_threads = NUM_THREADS, block_dim_count = BLOCK_DIM_COUNT ](b_shared, b_tile) async_copy_wait_all()์ด ์ฐ์ฐ๋ค์:
- ๋ ์ง์คํฐ๋ฅผ ์ฐํํ๋ ์ ์ฉ ๋ณต์ฌ ์์ง์ ์ฌ์ฉํ์ฌ ์ฐ์ฐ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ์ค์ฒฉ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค (copy_dram_to_sram_async ์ฐธ๊ณ )
- ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ํ ํนํ๋ ์ค๋ ๋ ๋ ์ด์์์ ์ฌ์ฉํฉ๋๋ค
- ์๋ ๋ฉ๋ชจ๋ฆฌ ์ด๊ธฐํ๊ฐ ๋ถํ์ํฉ๋๋ค
- ์ค์:
- ํ์ค GPU ๋ก๋๋ ์ด๋ฏธ ๋น๋๊ธฐ์ ์ ๋๋ค. ์ด ํจ์๋ค์ ๋ ๋์ ๋ฆฌ์์ค ํ์ฉ๊ณผ ๋ ์ง์คํฐ ์ฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค
copy_dram_to_sram_async๋ ๊ธฐ๋ณธ์ ์ผ๋ก 1D ์ค๋ ๋ ๋ธ๋ก(block_dim.y == block_dim.z == 1)์ ๊ฐ์ ํ๋ฉฐ, ๋ณ๋ ์ง์ ์ด ์์ผ๋ฉด ์ค๋ ๋ ๋ธ๋ก์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ณต์ฌ์ ์ฐธ์ฌํฉ๋๋ค. ๋ค์์ ์ง์ ํ์ฌ ์ด ๋์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค:block_dim_count: ์ค๋ ๋ ๋ธ๋ก์ ์ฐจ์ ์ (2D ์ค๋ ๋ ๋ธ๋กTHREADS_PER_BLOCK_TILED = (TPB, TPB)์ ๊ฒฝ์ฐ2)num_threads: ์ค๋ ๋ ๋ธ๋ก์ ์ค๋ ๋ ์ (TPB*TPB == 9)
-
์ต์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ ์ด์์
comptime load_a_layout = row_major[1, TPB]() # ๋ณํฉ ๋ก๋ฉ comptime load_b_layout = row_major[1, TPB]() # ๋ณํฉ ๋ก๋ฉ # ์ฐธ๊ณ : ํ์ค A ร B ๊ณฑ์ ์์ ๋ ํ๋ ฌ ๋ชจ๋ ๊ฐ์ ๋ ์ด์์์ ์ฌ์ฉํ์ฌ ๊ตฌํ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ถ์:
๋ ํ๋ ฌ ๋ชจ๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ ๋ณํฉ ๋ก๋ฉ์ ์ํด
row_major[1, TPB]()๋ฅผ ์ฌ์ฉํฉ๋๋ค:load_a_layout: ์ค๋ ๋๋ค์ด ํ๋ ฅํ์ฌ ํ๋ ฌ A ํ์ ์ฐ์ ์์๋ฅผ ๋ก๋load_b_layout: ์ค๋ ๋๋ค์ด ํ๋ ฅํ์ฌ ํ๋ ฌ B ํ์ ์ฐ์ ์์๋ฅผ ๋ก๋- ํต์ฌ: ์ค๋ ๋ ๋ ์ด์์์ ๋ณต์ฌ ์ ์ค๋ ๋ ๊ฐ ํ๋ ฅ ๋ฐฉ์์ ๊ฒฐ์ ํ๋ฉฐ, ์ต์ข ๋ฐ์ดํฐ ๋ ์ด์์๊ณผ๋ ๋ณ๊ฐ์ ๋๋ค
์ค์ ์ฐ์ฐ ํจํด (A ร B์์ ์ฆ๋ช ):
# ํ์ฌ ๊ตฌํ์ ์ค์ ์ฐ์ฐ acc += a_shared[local_row, k] * b_shared[k, local_col] # ์ด๊ฒ์ C[i,j] = ฮฃ(A[i,k] * B[k,j])์ ํด๋น # ์ฆ, ํ์ค ํ๋ ฌ ๊ณฑ์ A ร B๋ ํ๋ ฌ์ด ๊ฐ์ ๋ณํฉ ๋ก๋ฉ ํจํด์ ์ฌ์ฉํ๋ ์ด์ :
์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ ํ์ผ ๋ก๋ฉ: - Matrix A ํ์ผ: ์ค๋ ๋๋ค์ด A[block_row, k], A[block_row, k+1], A[block_row, k+2]... ๋ก๋ (์ฐ์) - Matrix B ํ์ผ: ์ค๋ ๋๋ค์ด B[k, block_col], B[k, block_col+1], B[k, block_col+2]... ๋ก๋ (์ฐ์) row_major[1, TPB]()๋ก ๋ ํจํด ๋ชจ๋ ๋ณํฉ์ธ ๊ฐ์ง ๋ณ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๊ณ ๋ ค์ฌํญ:
- ์ ์ญโ๊ณต์ ๋ณํฉ:
row_major[1, TPB]()๋ก ๋ณํฉ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ณด์ฅ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ:
a_shared[local_row, k] * b_shared[k, local_col]๋ก ๋ฑ ํฌ ์ถฉ๋ ํํผ - ํ๋ ฌ ์ฐ์ฐ: ์ฐ์ฐ ํจํด์ด A ร B๋ฅผ ๊ฒฐ์ (A ร B^T๊ฐ ์๋)
-
์๋ฒฝํ ํ์ผ๋ง์ผ๋ก ๊ฒฝ๊ณ ๊ฒ์ฌ ๋ถํ์
@parameter for idx in range(size // TPB): # ๋๋จธ์ง ์๋ ๋๋์ : 9 // 3 = 3\((9 \times 9)\) ํ๋ ฌ๊ณผ \((3 \times 3)\) ํ์ผ์์๋ ๋ชจ๋ ํ์ผ์ด ์ ํํ ๊ฝ ์ฐจ๊ธฐ ๋๋ฌธ์ ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ํ์ ์์ต๋๋ค!
-
๋ฐฉ์ด์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํฌํจํ ๊น๋ํ ํ์ผ ์ฒ๋ฆฌ
# ์๋ฒฝํ ํ์ผ๋ง์์๋ ๋ฐฉ์ด์ ๊ฒฝ๊ณ ๊ฒ์ฌ ํฌํจ if tiled_row < size and tiled_col < size: out_tile[local_row, local_col] = acc\((9 \times 9)\) ์ ์๋ฒฝํ ํ์ผ๋ง์์๋ ์ด ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ๊ธฐ์ ์ ์ผ๋ก ๋ถํ์ํ์ง๋ง, ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๋ค๋ฅธ ํ๋ ฌ ํฌ๊ธฐ์์ ์ผ๊ด์ฑ์ ์ํด ํฌํจํฉ๋๋ค.
์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ
๊ด์ฉ์ ๊ตฌํ์ ํ์ผ๋ง์ ์ฑ๋ฅ ์ด์ ์ ์ ์งํ๋ฉด์ ๋ ๊น๋ํ ์ถ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ: ํ์ผ๋ง์ ํตํด ๊ณต๊ฐ์ , ์๊ฐ์ ์ง์ญ์ฑ์ ํ์ฉ
- ๋ณํฉ ์ ๊ทผ: ํนํ๋ ๋ก๋ ๋ ์ด์์์ผ๋ก ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ณด์ฅ
- ์ฐ์ฐ-๋ฉ๋ชจ๋ฆฌ ์ค์ฒฉ: ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ํตํ ์ค์ฒฉ ๊ฐ๋ฅ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ: ๋ถํ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ด๊ธฐํ ์์
- ๋ ์ง์คํฐ ์๋ ฅ: ์ต์ ์ ์ฐ์ฐ ์ฒ๋ฆฌ๋์ ์ํ ๋์ ๋ ์ง์คํฐ ์ฌ์ฉ
์ด ๊ตฌํ์ ๊ณ ์์ค ์ถ์ํ๋ก๋ ์ฑ๋ฅ ์ ํ ์์ด ๋ณต์กํ GPU ์๊ณ ๋ฆฌ์ฆ์ ํํํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค. ๊ณ ์์ค์ ํํ๋ ฅ๊ณผ ์ ์์ค์ ์ฑ๋ฅ ์ ์ด๋ฅผ ๊ฒฐํฉํ๋ Mojo์ ์ฒ ํ์ ์ ๋ณด์ฌ์ฃผ๋ ์์์ ๋๋ค.
์๋ ํ์ผ๋ง๊ณผ์ ์ฃผ์ ์ฐจ์ด์
| ๊ธฐ๋ฅ | ์๋ Tiling | ๊ด์ฉ์ Tiling |
|---|---|---|
| ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ | ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ์๋ ์ง์ ์ธ๋ฑ์ฑ | TileTensor ํ์ผ API |
| ํ์ผ ๋ก๋ฉ | ์์๋ณ ๋ช ์์ ๋ณต์ฌ | ์ ์ฉ ๋ณต์ฌ ์์ง์ ๋ฒํฌ ์ ์ก |
| ๊ณต์ ๋ฉ๋ชจ๋ฆฌ | ์๋ ์ด๊ธฐํ (๋ฐฉ์ด์ ) | ๋ณต์ฌ ํจ์๊ฐ ๊ด๋ฆฌ |
| ์ฝ๋ ๋ณต์ก๋ | ๋ช ์์ ์ธ๋ฑ์ฑ์ผ๋ก ๋ค์ ์ฅํฉ | ๊ณ ์์ค API๋ก ๋ ๊ฐ๊ฒฐ |
| ๊ฒฝ๊ณ ๊ฒ์ฌ | ๋ก๋ฉ๊ณผ ์ฐ์ฐ ์ค ๋ค์์ ๊ฒ์ฌ | ์ต์ข ๊ธฐ๋ก ์ ๋จ์ผ ๋ฐฉ์ด์ ๊ฒ์ฌ |
| ํ๋ ฌ ๋ฐฉํฅ | A์ B ๋ชจ๋ ๊ฐ์ ๋ฐฉํฅ (ํ์ค A ร B) | A์ B ๋ชจ๋ ๊ฐ์ ๋ฐฉํฅ (ํ์ค A ร B) |
| ์ฑ๋ฅ | ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๋ช ์์ ์ ์ด | ๋ ์ง์คํฐ ์ฐํ๋ฅผ ํฌํจํ ์ต์ ํ๋ ๋ ์ด์์ |
๊ด์ฉ์ ์ ๊ทผ ๋ฐฉ์์ ๋จ์ํ ๋ ๊น๋ํ ๋ฟ ์๋๋ผ, ํนํ๋ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์๊ณผ ๋น๋๊ธฐ ์ฐ์ฐ ๋๋ถ์ ์ฑ๋ฅ๋ ๋ ์ข์ ์ ์์ต๋๋ค.
์ฐธ๊ณ : ์ ์น ๋ก๋ฉ์ ์ธ์ ์ ์ฉํ ๊น?
ํ์ฌ ๊ตฌํ์ ์ ์น ๋ก๋ฉ์ ์ฌ์ฉํ์ง ์์ต๋๋ค. ์ด ์น์ ์ ๋ ์ด์์ ์์คํ ์ผ๋ก ํ ์ ์๋ ๊ฒ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํ ๊ต์ก์ ๋ด์ฉ์ ๋๋ค.
ํ์ฌ ๊ตฌํ ์์ฝ:
- ๋ ํ๋ ฌ ๋ชจ๋
row_major[1, TPB]()์ฌ์ฉ - ํ์ค A ร B ๊ณฑ์ ์ํ
- ๋ณต์ฌ ์ค ๋ฐ์ดํฐ ์ ์น ์์
์ ์น ๋ก๋ฉ์ ์ฌ์ฉํ๋ ๊ต์ก์ ์๋๋ฆฌ์ค:
์ด ํผ์ฆ์ ๋ ํ๋ ฌ ๋ชจ๋ ํ์ค ๋ณํฉ ๋ก๋ฉ์ ์ฌ์ฉํ์ง๋ง, ๋ ์ด์์ ์์คํ ์ ์ ์ฐ์ฑ์ ๋ค๋ฅธ ์๋๋ฆฌ์ค์์ ๊ฐ๋ ฅํ ์ต์ ํ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค:
# ์์: A ร B๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํด ์ฌ์ ์ ์น๋ ํ๋ ฌ B^T๋ฅผ ๋ก๋
# (ํ์ฌ ๊ตฌํ์์๋ ์ด๋ ๊ฒ ํ์ง ์์)
comptime load_b_layout = row_major[TPB, 1]() # B^T๋ฅผ ๋ณํฉ ์ ๊ทผ์ผ๋ก ๋ก๋
comptime store_b_layout = row_major[1, TPB]() # ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ B๋ก ์ ์ฅ
copy_dram_to_sram_async[src_thread_layout=load_b_layout, dst_thread_layout=store_b_layout](b_shared, b_tile)
์ ์น ๋ก๋ฉ์ ํ์ฉ ์ฌ๋ก (์ด ํผ์ฆ์์๋ ์ฌ์ฉํ์ง ์์):
- ์ด๋ฏธ ์ ์น๋ ์ ๋ ฅ ํ๋ ฌ: \(B\) ๊ฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ์ ์น ์ํ๋ก ์ ์ฅ๋์ด ์๋ ๊ฒฝ์ฐ
- ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ: \(A^T \times B\), \(A \times B^T\), ๋๋ \(A^T \times B^T\) ๊ณ์ฐ
- ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ๋ณํ: ํ ์ฐ์ ๊ณผ ์ด ์ฐ์ ๋ ์ด์์ ๊ฐ ๋ณํ
- ๋ณ๋ ์ ์น ์ฐ์ฐ ์์ด ๋ก๋: ํ์ํ ๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ง์ ๋ก๋
ํต์ฌ ๊ตฌ๋ถ:
- ํ์ฌ ๊ตฌํ: ๋ ํ๋ ฌ ๋ชจ๋ ํ์ค \(A \times B\) ๊ณฑ์
์
row_major[1, TPB]()์ฌ์ฉ - ์ ์น ๋ก๋ฉ ์์: ์ด๋ฏธ ์ ์น๋ ๋ฐ์ดํฐ๋ ๋ค๋ฅธ ํ๋ ฌ ์ฐ์ฐ์ ์ฒ๋ฆฌํ ๋ ๋ค๋ฅธ ๋ ์ด์์ ์ฌ์ฉ
์ด๊ฒ์ Mojo์ ์ฒ ํ์ ๋ณด์ฌ์ค๋๋ค: ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ์ ๊ณ ์์ค ์ถ์ํ๋ฅผ ์ ์งํ๋ฉด์๋, ํ์ํ ๋ ์ ์์ค ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
์์ฝ: ํต์ฌ ์ ๋ฆฌ
๊ด์ฉ์ ํ์ผ๋ง ๊ตฌํ์ด ์ค์ ๋ก ํ๋ ๊ฒ:
- ํ๋ ฌ ์ฐ์ฐ: ํ์ค A ร B ๊ณฑ์
- ๋ฉ๋ชจ๋ฆฌ ๋ก๋ฉ: ๋ ํ๋ ฌ ๋ชจ๋
row_major[1, TPB]()๋ก ๋ณํฉ ์ ๊ทผ - ์ฐ์ฐ ํจํด:
acc += a_shared[local_row, k] * b_shared[k, local_col] - ๋ฐ์ดํฐ ๋ ์ด์์: ๋ก๋ฉ ์ ์ ์น ์์
์ด๊ฒ์ด ์ต์ ์ธ ์ด์ :
- ๋ณํฉ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
row_major[1, TPB]()๋ก ํจ์จ์ ์ธ ๋ก๋ฉ ๋ณด์ฅ - ๋ฑ ํฌ ์ถฉ๋ ํํผ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ถฉ๋์ ๋ฐฉ์ง
- ํ์ค ์๊ณ ๋ฆฌ์ฆ: ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ํ๋ ฌ ๊ณฑ์ ํจํด์ ๊ตฌํ
Puzzle 17: 1D ํฉ์ฑ๊ณฑ Op
MAX ๊ทธ๋ํ๋ก ํ์ด์ฌ ์ฐ๋ํ๊ธฐ
GPU ํผ์ฆ ์ฌ์ ์ Part IV์ ์ง์ ํ์ต๋๋ค: MAX ๊ทธ๋ํ ์ปค์คํ Op์ผ๋ก ํ์ด์ฌ ์ฐ๋ํ๊ธฐ.
์ด์ ํผ์ฆ๋ค์์๋ Mojo๋ก ํจ์จ์ ์ธ GPU ์ปค๋์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ด์ ๋ถํฐ๋ ๋ค์์ ์์๋ด ๋๋ค:
- ์ปค๋์ ํ์ด์ฌ์์ ํธ์ถํ ์ ์๋ ์ปค์คํ ์ฐ์ฐ์ผ๋ก ํจํค์งํ๊ธฐ
- MAX ๊ทธ๋ํ ์์คํ ๊ณผ ํตํฉํ์ฌ ๋จธ์ ๋ฌ๋์ ๊ฐ์ํ๊ธฐ
- ํ์ด๋ ๋ฒจ ํ์ด์ฌ API์ ๋ก์ฐ๋ ๋ฒจ GPU ์ฝ๋ ์ฌ์ด์ ๊ฐ๊ทน ๋ฉ์ฐ๊ธฐ
์ด๋ฅผ ํตํด ์ต์ํ ํ์ด์ฌ ํ๊ฒฝ์์ ์์ ํ๋ฉด์๋ Mojo GPU ์ปค๋์ ์ฑ๋ฅ์ ํ์ฉํ ์ ์์ต๋๋ค.
๊ฐ์
Puzzle 13: 1D ํฉ์ฑ๊ณฑ์์ GPU์์ ํจ์จ์ ์ผ๋ก ๋์ํ๋ 1D ํฉ์ฑ๊ณฑ ์ปค๋์ ๊ตฌํํ์ต๋๋ค. ์ด๋ฒ์๋ ์ด ์ปค๋์ MAX ๊ทธ๋ํ๋ฅผ ํตํด ํ์ด์ฌ์์ ์ง์ ํธ์ถํ ์ ์๋ ์ปค์คํ ์ฐ์ฐ์ผ๋ก ๋ณํํฉ๋๋ค.
์ฌ์ฉํ 1D ํฉ์ฑ๊ณฑ ์ปค๋์ ์ด๋ฏธ ๊ตฌํ๋์ด ์์ต๋๋ค:
comptime TPB = 15
comptime BLOCKS_PER_GRID = (2, 1)
def conv1d_kernel[
input_size: Int,
conv_size: Int,
OutLayout: TensorLayout,
InLayout: TensorLayout,
ConvLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
kernel: TileTensor[mut=False, dtype, ConvLayout, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# first: need to account for padding
var shared_a = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB + conv_size - 1]())
var shared_b = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[conv_size]())
if global_i < input_size:
shared_a[local_i] = input[global_i]
# second: load elements needed for convolution at block boundary
if local_i < conv_size - 1:
# indices from next block
var next_idx = global_i + TPB
if next_idx < input_size:
shared_a[TPB + local_i] = input[next_idx]
else:
shared_a[TPB + local_i] = 0
if local_i < conv_size:
shared_b[local_i] = kernel[local_i]
barrier()
if global_i < input_size:
var local_sum: output.ElementType = 0
comptime for j in range(conv_size):
if local_i + j < TPB + conv_size - 1:
local_sum += shared_a[local_i + j] * shared_b[j]
output[global_i] = local_sum
์ด ํผ์ฆ์ ํต์ฌ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ปค์คํ
op ๋ฑ๋ก:
@compiler.register๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ํตํด Mojo ํจ์๋ฅผ ํ์ด์ฌ์ ๋ ธ์ถํ๋ ๋ฐฉ๋ฒ ์ดํดํ๊ธฐ - ์ปค์คํ op ํจํค์ง: Mojo ์ฝ๋๋ฅผ MAX ๊ทธ๋ํ์์ ์ฌ์ฉํ ์ ์๋๋ก ํจํค์งํ๋ ๋ฐฉ๋ฒ ์ตํ๊ธฐ
- ํ์ด์ฌ ํตํฉ: MAX ๊ทธ๋ํ๋ฅผ ํตํด ํ์ด์ฌ์์ ์ปค์คํ ์ฐ์ฐ ํธ์ถํ๊ธฐ
- ํฌ๋ก์ค ์ธ์ด ๋ฐ์ดํฐ ํ๋ฆ: ํ์ด์ฌ๊ณผ GPU ์ฌ์ด์ ๋ฐ์ดํฐ ํ์ ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌํ๊ธฐ
์ด ์ปค์คํ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ ์ผ์ ์ํํฉ๋๋ค:
- ํ์ด์ฌ์์ NumPy ๋ฐฐ์ด์ ์ ๋ ฅ์ผ๋ก ๋ฐ๊ธฐ
- ์ด ๋ฐ์ดํฐ๋ฅผ GPU๋ก ์ ์กํ๊ธฐ
- ์ต์ ํ๋ ํฉ์ฑ๊ณฑ ์ปค๋ ์คํํ๊ธฐ
- ๊ฒฐ๊ณผ๋ฅผ ํ์ด์ฌ์ผ๋ก ๋ฐํํ๊ธฐ
์ด ํผ์ฆ์ ์์ฑํ๋ฉด ํ์ด์ฌ์ ํ๋ถํ ์ํ๊ณ์ Mojo์ ๊ฐ๋ ฅํ GPU ์ฑ๋ฅ์ ์๋ ๋งค๋๋ฌ์ด ๋ค๋ฆฌ๋ฅผ ๋ง๋ค๊ฒ ๋ฉ๋๋ค.
์์ฑํ ์ฝ๋
์ด ํผ์ฆ์ ์์ฑํ๋ ค๋ฉด conv1d.mojo์์ conv1d_kernel์ ํธ์ถํ๋ ํ ์ค๋ง ์ฑ์ฐ๋ฉด
๋ฉ๋๋ค:
import compiler
from std.runtime.asyncrt import DeviceContextPtr
from tensor import InputTensor, OutputTensor
from std.memory import UnsafePointer
from std.gpu.host import DeviceBuffer
@compiler.register("conv1d")
struct Conv1DCustomOp:
@staticmethod
def execute[
# The kind of device this will be run on: "cpu" or "gpu"
target: StaticString,
input_size: Int,
conv_size: Int,
dtype: DType = DType.float32,
](
output: OutputTensor[rank=1, static_spec=_],
input: InputTensor[rank=output.rank, static_spec=_],
kernel: InputTensor[rank=output.rank, static_spec=_],
# the context is needed for some GPU calls
ctx: DeviceContextPtr,
) raises:
var output_tensor = output.to_layout_tensor()
var input_tensor = input.to_layout_tensor()
var kernel_tensor = kernel.to_layout_tensor()
comptime if target == "gpu":
var gpu_ctx = ctx.get_device_context()
# making sure the output tensor is zeroed out before the kernel is called
gpu_ctx.enqueue_memset(
DeviceBuffer[output_tensor.dtype](
gpu_ctx,
output_tensor.ptr,
input_size,
owning=False,
),
0,
)
# FILL ME IN with 1 line calling our conv1d_kernel
elif target == "cpu":
# we can fallback to CPU
pass
else:
raise Error("Unsupported target: " + target)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p17/op/conv1d.mojo
๋ค์ ๋ช ๋ น์ผ๋ก ํผ์ฆ์ ์คํํ ์ ์์ต๋๋ค:
pixi run p17
pixi run -e amd p17
pixi run -e apple p17
uv run poe p17
์ฑ๊ณตํ๋ฉด ๋ค์๊ณผ ๋น์ทํ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค:
Input array: [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.]
Convolution kernel: [0. 1. 2. 3.]
Expected result (NumPy calculation): [14. 20. 26. 32. 38. 44. 50. 56. 62. 68. 74. 80. 41. 14. 0.]
Compiling 1D convolution graph...
Executing 1D convolution...
1D Convolution result (custom Mojo kernel): [14. 20. 26. 32. 38. 44. 50. 56. 62. 68. 74. 80. 41. 14. 0.]
Verification passed: Custom kernel results match NumPy calculation
์ด ์ถ๋ ฅ์ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ด 1D ํฉ์ฑ๊ณฑ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ์์ ๋ํ๋ ๋๋ค.
์๋ฃจ์
์ด ํผ์ฆ์ ํ๋ ค๋ฉด 1D ํฉ์ฑ๊ณฑ ์ปค๋์ MAX ๊ทธ๋ํ ์์คํ
๊ณผ ํตํฉํด์ผ ํฉ๋๋ค. ํต์ฌ์
Conv1DCustomOp ๊ตฌ์กฐ์ฒด์ execute ๋ฉ์๋์์ ์ปค๋์ ์ฌ๋ฐ๋ฅด๊ฒ ํธ์ถํ๋
๊ฒ์
๋๋ค.
ํ์ด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
comptime kernel = conv1d_kernel[
input_size, conv_size, OutLayout, OutLayout, ConvLayout
]
gpu_ctx.enqueue_function[kernel, kernel](
output_tensor,
input_tensor,
kernel_tensor,
grid_dim=BLOCKS_PER_GRID,
block_dim=(TPB, 1),
)
- GPU ์ปจํ
์คํธ(
gpu_ctx์ ํ์ ์ DeviceContext)์์ enqueue_function์ ํธ์ถํ์ฌ ์ปค๋ ์คํ ์์ฝ - ํ์ํ ๋ ์ด์์๊ณผ ํฌ๊ธฐ ์ ๋ณด๋ฅผ ์ปดํ์ผ ํ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
- ์ถ๋ ฅ, ์ ๋ ฅ, ์ปค๋ ํ ์๋ฅผ ๋ฐํ์ ์ธ์๋ก ์ ๊ณต
- ์ ์ ํ ์ฐจ์์ผ๋ก ์คํ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ
์ ์ฒด ๋งฅ๋ฝ์์ ์ด๋ป๊ฒ ๋์ํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค:
ํ์ด์ฌ-Mojo ํตํฉ ํ๋ฆ
-
ํ์ด์ฌ ์ชฝ (problems/p17/p17.py):
- ์ ๋ ฅ๊ณผ ์ปค๋์ฉ NumPy ๋ฐฐ์ด ์์ฑ
- MAX ๊ทธ๋ํ๋ก ์ฐ์ฐ์ ๊ฐ์ธ๋
conv_1d()ํจ์ ํธ์ถ - NumPy ๋ฐฐ์ด์
Buffer.from_numpy(input).to(device)๋ก MAX driver Buffer๋ก ๋ณํ custom_extensions=[mojo_kernels]๋ก ์ปค์คํ ์ฐ์ฐ ํจํค์ง ๋ก๋
-
๊ทธ๋ํ ๊ตฌ์ถ:
- TensorType์ผ๋ก ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ ํ ์ ํ์ ์ ์
parameters={...}๋ฅผ ํตํด ์ฐ์ฐ์ ํ๋ผ๋ฏธํฐ ์ง์ Graph("conv_1d_graph", ...)๋ก ์ฐ์ฐ ๊ทธ๋ํ ์์ฑops.custom(name="conv1d", ...)๋ก ์ปค์คํ ์ฐ์ฐ ํธ์ถ
-
์ปค์คํ op ๋ฑ๋ก:
@compiler.register("conv1d")๋ฐ์ฝ๋ ์ดํฐ๊ฐ ์ฐ์ฐ์ MAX ๊ทธ๋ํ์ ๋ ธ์ถ. @compiler.register ์ฐธ๊ณexecute๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๊ฐ ์ธํฐํ์ด์ค(์ ๋ ฅ, ์ถ๋ ฅ, ์ปจํ ์คํธ) ์ ์- ์ ์ถ๋ ฅ ํ ์๊ฐ ์ปค๋์์ ์ฌ์ฉํ ์ ์๋๋ก TileTensor๋ก ๋ณํ
- Device context๊ฐ GPU ๋ฉ๋ชจ๋ฆฌ ํ ๋น๊ณผ ์ปค๋ ์คํ ๊ด๋ฆฌ
-
์ปค๋ ์คํ:
model.execute(...)๊ฐ ํธ์ถ๋๋ฉดconv1d_kernel์ด ๋ฐ์ดํฐ ์์grid_dim๊ณผblock_dim์ผ๋ก GPU ์ค๋ ๋ ๊ตฌ์ฑ ์ค์ result.to(CPU())๋ก ๊ฒฐ๊ณผ๋ฅผ CPU๋ก ์ ์ก- NumPy ๊ฒ์ฆ์ผ๋ก ๊ธฐ๋ ์ถ๋ ฅ๊ณผ ๊ฒฐ๊ณผ ๋น๊ต
ํต์ฌ ๊ตฌ์ฑ ์์ ์์ธ
-
์ปค์คํ Op ๊ตฌ์กฐ์ฒด:
@compiler.register("conv1d") struct Conv1DCustomOp: @staticmethod def execute[target: StaticString, input_size: Int, conv_size: Int, dtype: DType = DType.float32]( output: OutputTensor[rank=1], input: InputTensor[dtype = output.dtype, rank = output.rank], kernel: InputTensor[dtype = output.dtype, rank = output.rank], ctx: DeviceContextPtr, ) raises: # ๊ตฌํtarget์ ๋๋ฐ์ด์ค ํ์ (โgpuโ ๋๋ โcpuโ)์ ๋ํ๋input_size์conv_size๋ ํ์ด์ฌ์์ ์ ๋ฌ๋๋ ํ๋ผ๋ฏธํฐ- ํ ์ ํ์ ์ด ์ฌ๋ฐ๋ฅธ shape๊ณผ ํ์ ๊ฒ์ฌ ๋ณด์ฅ
- ๋ฐํ ํ์
์ ์ ์ ํ ์ค๋ฅ ์ฒ๋ฆฌ ์ํด
raises
-
ํ ์ ๋ณํ:
output_tensor = output.to_layout_tensor() input_tensor = input.to_layout_tensor() kernel_tensor = kernel.to_layout_tensor()- MAX ๊ทธ๋ํ ํ ์๋ฅผ Mojo TileTensor๋ก ๋ณํ
- ์ปค๋์ด ํ ์๋ฅผ ์ง์ ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค
- ์ปดํ์ผ ํ์ ์ต์ ํ๋ฅผ ์ํด ๋ ์ด์์ ์ถ์ถ
-
Device Context ์ฌ์ฉ:
gpu_ctx = ctx.get_device_context() gpu_ctx.enqueue_memset(...) # ์ถ๋ ฅ ๋ฒํผ ์ด๊ธฐํ gpu_ctx.enqueue_function[..., ...](...) # ์ปค๋ ์์ฝ- ๋๋ฐ์ด์ค ์ปจํ ์คํธ๊ฐ GPU ๋ฆฌ์์ค๋ฅผ ๊ด๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ผ๋ก ์ฌ๋ฐ๋ฅธ ๋ฒํผ ์ํ๋ฅผ ๋ณด์ฅ
- ํจ์๋ฅผ ํ์ ๋ฑ๋กํ์ฌ ์ปค๋ ์คํ์ ์์ฝ
์ด ํ์ด๋ ํ์ด์ฌ ๋ฐ์ดํฐ๊ฐ MAX ๊ทธ๋ํ๋ฅผ ๊ฑฐ์ณ GPU์์ ์คํ๋๊ณ ๋ค์ ๋์์ค๋ ์ ์ฒด ํ๋ฆ์ ๋ณด์ฌ์ค๋๋ค. Mojo์ ๊ฐ๋ ฅํ ํ์ ์์คํ ๊ณผ ๋งค๊ฐ๋ณ์ํ ํจ์๋ฅผ ํ์ฉํ์ฌ ํจ์จ์ ์ด๊ณ ํ์ ์์ ํ ๊ฐ์ ์ฐ์ฐ์ ๋ง๋ค์ด๋ ๋๋ค.
MAX ๊ทธ๋ํ ์ปค์คํ op ์ดํดํ๊ธฐ
๋ ์์ธํ ๋ด์ฉ์ ์๋ ํํ ๋ฆฌ์ผ์ ์ฐธ๊ณ ํ์ธ์:
์ปค์คํ op ๋ฑ๋ก
์ปค์คํ
์ฐ์ฐ์ ๋ง๋๋ ํต์ฌ์ @compiler.register ๋ฐ์ฝ๋ ์ดํฐ์ ๊ด๋ จ ๊ตฌ์กฐ์ฒด์
๋๋ค:
@compiler.register("conv1d")
struct Conv1DCustomOp:
@staticmethod
def execute[...](
output: OutputTensor[rank=1],
input: InputTensor[dtype = output.dtype, rank = output.rank],
kernel: InputTensor[type = output.dtype, rank = output.rank],
ctx: DeviceContextPtr,
) raises:
# ๊ตฌํ
๋ฑ๋ก์ ํต์ฌ ๊ตฌ์ฑ ์์:
- ๋ฐ์ฝ๋ ์ดํฐ์ ์ ๋ฌํ๋ ์ด๋ฆ(
"conv1d")์ด ํ์ด์ฌ ์ฝ๋์์ ์ด ์ฐ์ฐ์ ํธ์ถํ ๋ ์ฌ์ฉํ๋ ์ด๋ฆ - ๊ตฌ์กฐ์ฒด์๋ ์ฌ๋ฐ๋ฅธ ์๊ทธ๋์ฒ๋ฅผ ๊ฐ์ง
execute๋ฉ์๋๊ฐ ์์ด์ผ ํจ - OutputTensor์ InputTensor ํ์ ์ด ํ์ด์ฌ ๋ฐ์ดํฐ์์ ์ธํฐํ์ด์ค๋ฅผ ์ ์
- DeviceContextPtr์ด ์คํ ํ๊ฒฝ์ ๋ํ ์ ๊ทผ์ ์ ๊ณต
์ปค์คํ op ํจํค์ง
์ปค์คํ ์ฐ์ฐ์ ํ์ด์ฌ์์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ํจํค์งํด์ผ ํฉ๋๋ค:
mojo package op -o op.mojopkg
์ด ๋ช ๋ น์:
- Mojo ์ฝ๋๋ฅผ ๋ฐฐํฌ ๊ฐ๋ฅํ ํจํค์ง๋ก ์ปดํ์ผ
- MAX ๊ทธ๋ํ๊ฐ ์ฐ์ฐ์ ์ดํดํ๋ ๋ฐ ํ์ํ ๋ฉํ๋ฐ์ดํฐ ์์ฑ
- ํ์ด์ฌ์์ ๋ก๋ํ ์ ์๋ ๋ฐ์ด๋๋ฆฌ ์ํฐํฉํธ(
op.mojopkg)๋ฅผ ์์ฑ
ํจํค์ง๋ MAX ๊ทธ๋ํ๊ฐ ์ฐพ์ ์ ์๋ ์์น์ ๋ฐฐ์นํด์ผ ํ๋ฉฐ, ๋ณดํต ํ์ด์ฌ ์ฝ๋์์ ์ ๊ทผ ๊ฐ๋ฅํ ๋๋ ํ ๋ฆฌ์ ๋ก๋๋ค.
ํ์ด์ฌ ํตํฉ
ํ์ด์ฌ ์ชฝ์์ ์ปค์คํ ์ฐ์ฐ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
# Mojo ์ฐ์ฐ์ด ํฌํจ๋ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก
mojo_kernels = Path(__file__).parent / "op"
# ์ปค์คํ
conv1d ์ฐ์ฐ์ผ๋ก ๊ทธ๋ํ ๊ตฌ์ฑ
with Graph(
"conv_1d_graph",
input_types=[...],
custom_extensions=[mojo_kernels], # ์ปค์คํ
op ํจํค์ง ๋ก๋
) as graph:
# ๊ทธ๋ํ์ ์
๋ ฅ ์ ์
input_value, kernel_value = graph.inputs
# ์ด๋ฆ์ผ๋ก ์ปค์คํ
์ฐ์ฐ ์ฌ์ฉ
output = ops.custom(
name="conv1d", # @compiler.register์ ์ด๋ฆ๊ณผ ์ผ์นํด์ผ ํจ
values=[input_value, kernel_value],
out_types=[...],
parameters={
"input_size": input_tensor.shape[0],
"conv_size": kernel_tensor.shape[0],
"dtype": dtype,
},
)[0].tensor
ํต์ฌ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
custom_extensions๋ก ์ปค์คํ ์ฐ์ฐ์ ๊ฒฝ๋ก ์ง์ - ๋ฑ๋ก๋ ์ฐ์ฐ ์ด๋ฆ์ผ๋ก
ops.customํธ์ถ - ์ฐ์ฐ์ ์๊ทธ๋์ฒ์ ๋ง๋ ์ ๋ ฅ ๊ฐ๊ณผ ํ๋ผ๋ฏธํฐ ์ ๋ฌ
Puzzle 18: ์ํํธ๋งฅ์ค Op
๊ฐ์
์ด ํผ์ฆ์์๋ ์ํํธ๋งฅ์ค ํจ์๋ฅผ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ผ๋ก ๊ตฌํํฉ๋๋ค. ์ํํธ๋งฅ์ค๋ ์ค์ ๋ฒกํฐ๋ฅผ ๋ฐ์ ํ๋ฅ ๋ถํฌ๋ก ์ ๊ทํํ๋ ํจ์์ ๋๋ค.
์ํํธ๋งฅ์ค ํจ์๋ ๋ ๊ฐ์ง ์ฃผ์ ๋จ๊ณ๋ก ๋์ํฉ๋๋ค:
-
์ง์ ํจ์ ์ ์ฉ: ์ ๋ ฅ ๋ฒกํฐ์ ๊ฐ ์์์ ์ง์ ํจ์๋ฅผ ์ ์ฉํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ชจ๋ ๊ฐ์ด ์์๊ฐ ๋๊ณ ๊ฐ ์ฌ์ด์ ์ฐจ์ด๊ฐ ์ฆํญ๋ฉ๋๋ค. ํฐ ์ ๋ ฅ๊ฐ์ ํจ์ฌ ํฐ ์ง์ ์ถ๋ ฅ์ ๋ง๋ค๊ณ , ์๊ฑฐ๋ ์์์ธ ๊ฐ์ 0์ ๊ฐ๊น์ด ์ถ๋ ฅ์ ๋ง๋ค์ด๋ ๋๋ค.
-
์ ๊ทํ: ๊ฐ ์ง์ ๊ฐ์ ๋ชจ๋ ์ง์ ๊ฐ์ ํฉ์ผ๋ก ๋๋๋๋ค. ์ด ์ ๊ทํ ๋จ๊ณ๋ฅผ ํตํด ๊ฒฐ๊ณผ๊ฐ์ด ์ ํจํ ํ๋ฅ ๋ถํฌ๊ฐ ๋ฉ๋๋ค. ์ฆ, ๋ชจ๋ ๊ฐ์ด 0๊ณผ 1 ์ฌ์ด์ด๊ณ ํฉ์ด ์ ํํ 1์ด ๋ฉ๋๋ค.
์ํ์ ์ผ๋ก ์ํํธ๋งฅ์ค ํจ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค:
$$\Large \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{n} e^{x_j}}$$
์ฌ๊ธฐ์:
- \(x_i\)๋ ์ ๋ ฅ ๋ฒกํฐ์ \(i\)๋ฒ์งธ ์์
- \(n\)์ ์ ๋ ฅ ๋ฒกํฐ์ ๊ธธ์ด
๊ทธ๋ฌ๋ ์ด ์ง์ ์ ์ธ ๊ตฌํ์ ๊ฐ์ด ํด ๋ ์์น ์ค๋ฒํ๋ก์ฐ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์์น์ ์ผ๋ก ๋ ์์ ์ ์ธ ๋ฒ์ ์ ์ฌ์ฉํฉ๋๋ค:
$$\Large \text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_{j=1}^{n} e^{x_j - \max(x)}}$$
GPU ๊ตฌํ์์๋ ์ต๋๊ฐ ์ฐพ๊ธฐ์ ์ง์ ํฉ ๊ณ์ฐ ๋ชจ๋์ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ์ฌ์ฉํ์ฌ ํฐ ๋ฒกํฐ์์๋ ๋์ ํจ์จ์ ๋ฌ์ฑํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
- ํจ์จ์ ์ธ ์ต๋๊ฐ ๋ฐ ํฉ๊ณ ๊ณ์ฐ์ ์ํ ๋ณ๋ ฌ ๋ฆฌ๋์
- ์ต๋๊ฐ ์ฐจ๊ฐ ๊ธฐ๋ฒ์ ํตํ ์์น ์์ ์ฑ
- ์ค๋ ๋ ๊ฐ ํต์ ์ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ ํ์ด์ฌ ํตํฉ
- ๋ฐฐ๋ฆฌ์ด๋ฅผ ํตํ ์ค๋ ๋ ๋๊ธฐํ
์ค์
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 128 - ๋ธ๋ก๋น ์ค๋ ๋ ์:
BLOCK_DIM_X = 1 << log2_ceil(SIZE). ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ฆฌ๋์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋ ค๋ฉดBLOCK_DIM_X๊ฐSIZE์ด์์ธ ๊ฐ์ฅ ์์ 2์ ๊ฑฐ๋ญ์ ๊ณฑ์ด์ด์ผ ํฉ๋๋ค. - ๊ทธ๋ฆฌ๋ ์ฐจ์: \(1 \times 1\) ๋ธ๋ก
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ์ต๋๊ฐ๊ณผ ํฉ๊ณ๋ฅผ ์ํ ๋ ๊ฐ์ ๊ณต์ ๋ณ์
๋ ์ด์์ ์ค์ :
- ์
๋ ฅ ํ
์:
row_major[SIZE]() - ์ถ๋ ฅ ํ
์:
row_major[SIZE]() - ์ปค์คํ
op ํ๋ผ๋ฏธํฐ:
{"input_size": input_tensor.shape[0]}
์ด ํผ์ฆ์ ํต์ฌ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์์น ์์ ์ฑ: ์ ์ฌ์ ์ธ ์์น ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ ์ดํดํ๊ธฐ
- ๋ณ๋ ฌ ๋ฆฌ๋์ : ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ํจ์จ์ ์ธ ์ต๋๊ฐ ๋ฐ ํฉ๊ณ ๊ณ์ฐ
- ์ปค์คํ op ํตํฉ: Mojo GPU ์ปค๋์ ์ํ ํ์ด์ฌ ์ธํฐํ์ด์ค ์์ฑํ๊ธฐ
- ํ ์คํธ์ ๊ฒ์ฆ: ๊ตฌํ์ด ๊ธฐ๋ ๊ฒฐ๊ณผ์ ์ผ์นํ๋์ง ํ์ธํ๊ธฐ
์ํํธ๋งฅ์ค ์ปค์คํ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ ์ผ์ ์ํํฉ๋๋ค:
- ํ์ด์ฌ์์ NumPy ๋ฐฐ์ด์ ์ ๋ ฅ์ผ๋ก ๋ฐ๊ธฐ
- GPU์์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ
- ์ ๊ทํ๋ ํ๋ฅ ๋ถํฌ๋ฅผ ๋ฐํํ๊ธฐ
- SciPy์ ์ํํธ๋งฅ์ค ๊ตฌํ ๊ฒฐ๊ณผ์ ์ผ์น์ํค๊ธฐ
์์ฑํ ์ฝ๋
์ด ํผ์ฆ์ ์์ฑํ๋ ค๋ฉด Mojo ํ์ผ์์ GPU์ CPU ์ปค๋์ ๋ชจ๋ ๊ตฌํํ๊ณ , ํ์ด์ฌ ์ฝ๋์์ ๊ทธ๋ํ ์ ์๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
1. softmax.mojo์์ GPU ์ปค๋ ๊ตฌํํ๊ธฐ
from std.gpu import thread_idx, block_idx, block_dim, barrier
from std.gpu.host import DeviceContext, HostBuffer, DeviceBuffer
from std.gpu.memory import AddressSpace
from layout import TileTensor
from layout.tile_layout import row_major
from layout.tile_tensor import stack_allocation
from std.math import exp
from std.bit import log2_ceil
from std.utils.numerics import max_finite, min_finite
comptime SIZE = 128 # This must be equal to INPUT_SIZE in p18.py
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
comptime GRID_DIM_X = 1
# Tree-based reduction require the number of threads to be the next power of two >= SIZE for correctness.
comptime BLOCK_DIM_X = 1 << log2_ceil(SIZE)
def softmax_gpu_kernel[
input_size: Int,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
comptime assert (
dtype.is_floating_point()
), "dtype must be a floating-point type"
# FILL IN (roughly 31 lines)
...
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p18/op/softmax.mojo
ํ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ๊ทผํ ์ ์๋๋ก ์ต๋๊ฐ๊ณผ ํฉ๊ณ ๋ชจ๋์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ธ์
- ์ค๋ ๋๋ฅผ ๋๊ธฐํํ๊ธฐ ์ํด ์ ์ ํ ์ง์ ์์
barrier()๋ฅผ ํธ์ถํ๋ ๊ฒ์ ์์ง ๋ง์ธ์ - ๊ฐ ์ค๋ ๋๊ฐ ์ ๋ ฅ ๋ฐฐ์ด์ ์ผ๋ถ๋ฅผ ์ฒ๋ฆฌํ๋๋ก ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ๊ตฌํํ์ธ์
- ์ค๋ ๋ ๋ถ๊ธฐ๋ฅผ ์ต์ํํ๊ธฐ ์ํด ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ฆฌ๋์ ํจํด์ ์ฌ์ฉํ์ธ์
- ํนํ ํฐ ์ ๋ ฅ์์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ๊ทผ์ ์ฃผ์ ๊น๊ฒ ์ฒ๋ฆฌํ์ธ์
- ์์น ์์ ์ฑ์ ์ํด \(e^{x_i}\) ๋์ \(e^{x_i - max}\)๋ฅผ ๊ณ์ฐํ์ธ์
2. softmax.mojo์์ CPU ์ปค๋ ๊ตฌํํ๊ธฐ
def softmax_cpu_kernel[
input_size: Int,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
comptime assert (
dtype.is_floating_point()
), "dtype must be a floating-point type"
# FILL IN (roughly 10 lines)
...
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p18/op/softmax.mojo
ํ
- GPU ๋ฒ์ ๊ณผ ๋์ผํ ์ํ์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด๋ ์์ฐจ์ ๊ตฌํ์ ์์ฑํ์ธ์
- ๋จผ์ ๋ชจ๋ ์ ๋ ฅ์์ ์ต๋๊ฐ์ ์ฐพ์ผ์ธ์
- ๊ทธ๋ค์ ๊ฐ ์์์ ๋ํด \(e^{x_i - max}\)๋ฅผ ๊ณ์ฐํ๊ณ ํฉ๊ณ๋ฅผ ๋์ ํ์ธ์
- ๋ง์ง๋ง์ผ๋ก ๊ฐ ์์๋ฅผ ํฉ๊ณ๋ก ๋๋ ์ ๊ทํํ์ธ์
- CPU ๊ตฌํ์๋ ๋ณ๋ ฌ ์ค๋ ๋๊ฐ ์์ผ๋ฏ๋ก ์ค์นผ๋ผ ์ฐ์ฐ์ ์ฌ์ฉํ์ธ์
CPU์ GPU ์ปค๋ ํ ์คํธ
uv run poe p18-test-kernels
pixi run p18-test-kernels
์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค:
Total Discovered Tests: 1
Passed : 1 (100.00%)
Failed : 0 (0.00%)
Skipped: 0 (0.00%)
3. p18.py์์ ๊ทธ๋ํ ์ ์ ์์ฑํ๊ธฐ
from pathlib import Path
import numpy as np
from max.driver import CPU, Accelerator, Buffer, Device
from max.dtype import DType
from max.engine import InferenceSession
from max.graph import DeviceRef, Graph, TensorType
from numpy.typing import NDArray
from scipy.special import softmax as scipy_softmax
def softmax(
input: NDArray[np.float32],
session: InferenceSession,
device: Device,
) -> Buffer:
dtype = DType.float32
input_tensor = Buffer.from_numpy(input).to(device)
mojo_kernels = Path(__file__).parent / "op"
with Graph(
"softmax_graph",
input_types=[
TensorType(
dtype,
shape=input_tensor.shape,
device=DeviceRef.from_device(device),
),
],
custom_extensions=[mojo_kernels],
) as graph:
# FILL IN (roughly 4 unformatted lines)
pass
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p18/p18.py
ํ
graph.inputs[0]์ผ๋ก ๊ทธ๋ํ์ ์ ๋ฌ๋ ์ ๋ ฅ ํ ์์ ์ ๊ทผํ์ธ์- ๋ฑ๋กํ ์ปค์คํ
op ์ด๋ฆ(โsoftmaxโ)์ผ๋ก
ops.custom()์ ํธ์ถํ์ธ์ - ์ ๋ ฅ ํ ์๋ฅผ ์ปค์คํ ์ฐ์ฐ์ ๊ฐ์ผ๋ก ์ ๋ฌํ์ธ์
- ์ ๋ ฅ shape๊ณผ ์ผ์นํ๋ ์ถ๋ ฅ ํ์ ์ ์ง์ ํ์ธ์
- ์ปค๋์ ํ์ํ โinput_sizeโ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ์ธ์
graph.outputs๋ฅผ ์ฐ์ฐ์ ์ถ๋ ฅ ํ ์๊ฐ ๋ด๊ธด ๋ฆฌ์คํธ๋ก ์ค์ ํ์ธ์
๋ค์ ๋ช ๋ น์ผ๋ก ํผ์ฆ์ ์คํํ ์ ์์ต๋๋ค:
pixi run p18
pixi run -e amd p18
pixi run -e apple p18
uv run poe p18
์ฑ๊ณตํ๋ฉด CPU์ GPU์์ ๋ค์๊ณผ ๋น์ทํ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค:
Input shape: (128,)
First few random input values: [ 1.1810775 0.60472375 0.5718309 0.6644599 -0.08899796]
Compiling softmax graph on Device(type=cpu,id=0)
Executing softmax on Device(type=cpu,id=0)
====================================================================================================
Compiling softmax graph on Device(type=gpu,id=0)
Executing softmax on Device(type=gpu,id=0)
====================================================================================================
First few softmax results on CPU (custom Mojo kernel): [0.01718348 0.00965615 0.0093437 0.01025055 0.0048253 ]
First few softmax results on GPU (custom Mojo kernel): [0.01718348 0.00965615 0.0093437 0.01025055 0.0048253 ]
First few expected results (SciPy calculation): [0.01718348 0.00965615 0.0093437 0.01025055 0.0048253 ]
Verification passed: Custom kernel results match SciPy calculation
Sum of all probabilities on CPU: 1.0
Sum of all probabilities on GPU: 1.0
์ด ์ถ๋ ฅ์ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ด ์ํํธ๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ์ฌ ์ ํจํ ํ๋ฅ ๋ถํฌ๋ฅผ ์์ฑํ์์ ๋ณด์ฌ์ค๋๋ค.
์๋ฃจ์
์ด ํผ์ฆ์ ํ๋ ค๋ฉด Mojo ์ปค๋(GPU์ CPU)๊ณผ ํ์ด์ฌ ๊ทธ๋ํ ์ ์๋ฅผ ๋ชจ๋ ๊ตฌํํด์ผ ํฉ๋๋ค. Puzzle 17: 1D ํฉ์ฑ๊ณฑ Op์์ ํ๋ ๊ฒ์ฒ๋ผ, ํ์ด์ฌ์ ์ํ๊ณ์ Mojo์ GPU ๊ฐ์ ์ปดํจํ ์ญ๋์ ์๋ ๋ค๋ฆฌ๋ฅผ ๋ง๋ญ๋๋ค.
๊ตฌํํ ์ํํธ๋งฅ์ค ์ฐ์ฐ์ ์ํ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค:
$$\Large \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{n} e^{x_j}}$$
ํ์ง๋ง ์์น ์ค๋ฒํ๋ก์ฐ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ ์์ ์ ์ธ ํํ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
$$\Large \text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_{j=1}^{n} e^{x_j - \max(x)}}$$
GPU ์ปค๋ ๊ตฌํ
def softmax_gpu_kernel[
input_size: Int,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
):
comptime assert (
dtype.is_floating_point()
), "dtype must be a floating-point type"
var shared_max = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[BLOCK_DIM_X]())
var shared_sum = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[BLOCK_DIM_X]())
var global_i = thread_idx.x
# Initialize out-of-bounds (shared_max[local_i], global_i >= input_size) shared memory addresses to the minimum
# finite value for dtype, ensuring that if these elements are accessed in the parallel max reduction below they
# do not influence the result (max(min_finite, x) == x for any x).
var val: Scalar[dtype] = min_finite[dtype]()
if global_i < input_size:
val = rebind[Scalar[dtype]](input[global_i])
shared_max[global_i] = val
barrier()
# Parallel reduction to find max similar to reduction we saw before
var stride = BLOCK_DIM_X // 2
while stride > 0:
if global_i < stride:
shared_max[global_i] = max(
shared_max[global_i], shared_max[global_i + stride]
)
barrier()
stride = stride // 2
var block_max = shared_max[0]
# Initialize out-of-bounds (shared_max[global_i], global_i >= input_size) shared memory addresses to 0.0,
# ensuring that if these elements are accessed in the parallel sum reduction below they
# do not influence the result (adding 0.0 does not change the sum).
var exp_val: Scalar[dtype] = 0.0
if global_i < input_size:
exp_val = rebind[Scalar[dtype]](exp(val - block_max))
shared_sum[global_i] = exp_val
barrier()
# Parallel reduction for sum similar to reduction we saw before
stride = BLOCK_DIM_X // 2
while stride > 0:
if global_i < stride:
shared_sum[global_i] += shared_sum[global_i + stride]
barrier()
stride = stride // 2
var block_sum = shared_sum[0]
# Normalize by sum
if global_i < input_size:
output[global_i] = exp_val / block_sum
์ปค๋ ์๊ทธ๋์ฒ์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
def softmax_gpu_kernel[
layout: Layout,
input_size: Int,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, layout],
input: TileTensor[mut=False, dtype, layout],
)
์ปค๋์ ํ๋ผ๋ฏธํฐ ๊ตฌ์ฑ:
- ์ ์ถ๋ ฅ ํ ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ๋ ์ด์์ ํ๋ผ๋ฏธํฐ
- ์ ์ ํ๋ผ๋ฏธํฐ๋ก ์ง์ ๋๋ ๋ฒกํฐ ํฌ๊ธฐ
- ๊ธฐ๋ณธ๊ฐ์ด float32์ธ ์ค์ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ์
- ์ฐ์ฐ ๊ฒฐ๊ณผ๋ฅผ ์ง์ ์ ์ฅํ๋ ๋ณ๊ฒฝ ๊ฐ๋ฅํ(mutable) ์ถ๋ ฅ ํ ์
- ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ(mut=False) ์ ๋ ฅ ํ ์
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น
shared_max = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[BLOCK_DIM_X]())
shared_sum = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[BLOCK_DIM_X]())
์ปค๋์ ๋ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒํผ๋ฅผ ํ ๋นํฉ๋๋ค:
shared_max: ๋ณ๋ ฌ ์ต๋๊ฐ ํ์ ๋ฆฌ๋์ ์ฉshared_sum: ๋ณ๋ ฌ ํฉ๊ณ ์ฐ์ฐ์ฉ- ๋ ๋ค
BLOCK_DIM_X = 128ํฌ๊ธฐ๋ฅผ ์ฌ์ฉ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋์ ๋น ๋ฅธ ์ ๊ทผ์ ์ ๊ณต
์ค๋ ๋ ์ธ๋ฑ์ฑ
global_i = thread_idx.x
์ด ์ํํธ๋งฅ์ค ๊ตฌํ์ ๋จ์ผ 1D ์ค๋ ๋ ๋ธ๋ก์์ ๋์ํฉ๋๋ค. ์ฆ, ์ ์ญ ์ธ๋ฑ์ค์ ๋ก์ปฌ ์ธ๋ฑ์ค๊ฐ ๋์ผํฉ๋๋ค.
์ต๋๊ฐ ํ์ ๋จ๊ณ
var val: Scalar[dtype] = min_finite[dtype]()
if global_i < input_size:
val = rebind[Scalar[dtype]](input[global_i])
shared_max[local_i] = val
barrier()
๊ฐ ์ค๋ ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ด๊ธฐํํฉ๋๋ค:
- ์ ํจ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์์์๋ ์ต์ ์ ํ(finite) ๊ฐ ํ ๋น
- ์ ํจํ ์์์ ๋งคํ๋๋ ์ค๋ ๋์๋ ์ค์ ์ ๋ ฅ๊ฐ ํ ๋น
- ๋ฆฌ๋์ ๊ณผ์ ์ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ
- ๋ชจ๋ ์ค๋ ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ๊ฐ ์๋ฃ๋๋๋ก ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ
๋ณ๋ ฌ max ๋ฆฌ๋์
stride = BLOCK_DIM_X // 2
while stride > 0:
if local_i < stride:
shared_max[local_i] = max(shared_max[local_i], shared_max[local_i + stride])
barrier()
stride = stride // 2
๋ณ๋ ฌ ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด์ ๊ตฌํํฉ๋๋ค:
stride = 64(BLOCK_DIM_X์ ์ ๋ฐ)๋ก ์์- ๊ฐ ํ์ฑ ์ค๋ ๋๊ฐ stride๋งํผ ๋จ์ด์ง ๋ ๊ฐ ๋น๊ต
- ๋ ์์ ์ธ๋ฑ์ค์ ์ต๋๊ฐ ์ ์ฅ
- ๋ฐฐ๋ฆฌ์ด๋ก ๋ชจ๋ ์ค๋ ๋ ๋๊ธฐํ
- Stride๋ฅผ ์ ๋ฐ์ผ๋ก ์ค์ด๊ณ ๋ฐ๋ณต
- \(\log_2(BLOCK\_DIM\_X)~\) ๋จ๊ณ ํ
shared_max[0]์ ์ ์ฒด ์ต๋๊ฐ์ด ๋ด๊น
์ด ๋ก๊ทธ ๋ฆฌ๋์ ์ ๋๊ท๋ชจ ์ ๋ ฅ์์ ์ ํ ์ค์บ๋ณด๋ค ํจ์ฌ ๋น ๋ฆ ๋๋ค.
์์น์ ์ผ๋ก ์์ ์ ์ธ ์ง์ ํจ์ ์ ์ฉ
block_max = shared_max[0]
var exp_val: Scalar[dtype] = 0.0
if global_i < input_size:
exp_val = rebind[Scalar[dtype]](exp(val - block_max))
๊ฐ ์ค๋ ๋๊ฐ ์ํํ๋ ์์ :
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์ ์ฒด ์ต๋๊ฐ ์ฝ์
- ์ง์ ํจ์๋ฅผ ์ ์ฉํ๊ธฐ ์ ์ ์ ๋ ฅ๊ฐ์์ ์ต๋๊ฐ ์ฐจ๊ฐ
- ์ด ์ฐจ๊ฐ์ด ์์น ์์ ์ฑ์ ํต์ฌ โ ์ค๋ฒํ๋ก์ฐ ๋ฐฉ์ง
- ๊ฐ์ฅ ํฐ ์ง์๊ฐ \(e^0 = 1\)์ด ๋๊ณ , ๋๋จธ์ง๋ ๋ชจ๋ \(e^{์์} < 1\)
๋ณ๋ ฌ sum ๋ฆฌ๋์
shared_sum[local_i] = exp_val
barrier()
stride = BLOCK_DIM_X // 2
while stride > 0:
if local_i < stride:
shared_sum[local_i] += shared_sum[local_i + stride]
barrier()
stride = stride // 2
๋ ๋ฒ์งธ ๋ฆฌ๋์ ๋จ๊ณ:
- ๋ชจ๋ ์ง์ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ
- max์ ๋์ผํ ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ฆฌ๋์ ํจํด ์ฌ์ฉ
- ๋จ, ์ต๋๊ฐ ๋น๊ต ๋์ ๋ง์ ์ํ
- \(\log_2(BLOCK\_DIM\_X)~\) ๋จ๊ณ ํ
shared_sum[0]์ ๋ชจ๋ ์ง์ ๊ฐ์ ์ดํฉ์ด ๋ด๊น
์ต์ข ์ ๊ทํ
block_sum = shared_sum[0]
if global_i < input_size:
output[global_i] = exp_val / block_sum
๊ฐ ์ค๋ ๋๊ฐ ์ํํ๋ ์์ :
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์ดํฉ์ ์ฝ์
- ์์ ์ ์ง์ ๊ฐ์ ์ด ์ดํฉ์ผ๋ก ๋๋
- ์ ๊ทํ๋ ํ๋ฅ ์ ์ถ๋ ฅ ๋ฒํผ์ ๊ธฐ๋ก
- ํฉ์ด 1์ธ ์ ํจํ ํ๋ฅ ๋ถํฌ ์์ฑ
์ฑ๋ฅ ํน์ฑ
์ด ๊ตฌํ์ ๋ฐ์ด๋ ์ฑ๋ฅ ํน์ฑ์ ๊ฐ์ต๋๋ค:
- ๋ณต์ก๋: ์์ฐจ์ ์ ๊ทผ์ \(O(n)\)์ ๋นํด max์ sum ๊ณ์ฐ ๋ชจ๋ \(O(\log n)\)
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ \(2 \times BLOCK\_DIM\_X~\) ์์๋ง ์ฌ์ฉ
- ์์ ํจ์จ: ๊ฐ ์ค๋ ๋๊ฐ ์ฝ \(2 \times \log_2(BLOCK\_DIM\_X)~\) ํ ์ฐ์ฐ ์ํ
- ๋ถํ ๋ถ์ฐ: ๊ฐ ์ค๋ ๋๊ฐ ๋์ผํ ์์ ์์ ์ฒ๋ฆฌ
- ๋๊ธฐํ: ํ์ํ ๊ณณ์์๋ง ์ต์ํ์ ๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ์ต์ ๋์ญํญ์ ์ํ ๋ณํฉ๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
์ด ์๊ณ ๋ฆฌ์ฆ์ ์์น์ ์ผ๋ก๋ ๊ฒฌ๊ณ ํฉ๋๋ค. ์ต๋๊ฐ ์ฐจ๊ฐ ๊ธฐ๋ฒ์ ์ ์ฉํ์ฌ ์ ๊ฒฝ๋ง ํ์ฑํ์์ ํํ ๋์ ๋ฒ์์ ๊ฐ์์๋ ์ ๋ฐ๋๋ฅผ ์ ์งํ๋ฉฐ, ์ค๋ฒํ๋ก์ฐ/์ธ๋ํ๋ก์ฐ ๊ฐ๋ฅ์ฑ์ ์ฒ๋ฆฌํฉ๋๋ค.
CPU ํด๋ฐฑ ๊ตฌํ
def softmax_cpu_kernel[
input_size: Int,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
):
comptime assert (
dtype.is_floating_point()
), "dtype must be a floating-point type"
var max_val: Scalar[dtype] = min_finite[dtype]()
for i in range(input_size):
max_val = max(max_val, rebind[Scalar[dtype]](input[i]))
var sum_exp: Scalar[dtype] = 0.0
for i in range(input_size):
var exp_val = rebind[Scalar[dtype]](exp(input[i] - max_val))
output[i] = exp_val
sum_exp += exp_val
for i in range(input_size):
output[i] = output[i] / sum_exp
-
์ต๋๊ฐ ํ์:
var max_val: Scalar[dtype] = min_finite[dtype]() for i in range(input_size): max_val = max(max_val, rebind[Scalar[dtype]](input[i]))์ต์ ์ ํ๊ฐ์ผ๋ก ์ด๊ธฐํํ๊ณ ๋ฐฐ์ด์ ์ ํ ์ค์บํ๋ฉฐ ๋ง๋ ์ต๋๊ฐ์ ์ถ์ ํฉ๋๋ค. \(O(n)\) ๋ณต์ก๋์ด์ง๋ง, ๋ณ๋ ฌํํ ์ฝ์ด๊ฐ ๋ง์ง ์์ CPU์์๋ ํจ์จ์ ์ผ๋ก ๋์ํฉ๋๋ค.
-
์ง์ ํจ์ ์ ์ฉ๊ณผ ํฉ์ฐ:
var sum_exp: Scalar[dtype] = 0.0 for i in range(input_size): var exp_val = rebind[Scalar[dtype]](exp(input[i] - max_val)) output[i] = exp_val sum_exp += exp_val๊ฐ ์์์ ๋ํด \(e^{x_i - max}\)๋ฅผ ๊ณ์ฐํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅ ๋ฒํผ์ ์ ์ฅํ๋ฉด์ ํฉ๊ณ \(\sum_{j=1}^{n} e^{x_j - max}\)๋ฅผ ํ ๋ฒ์ ์ํ๋ก ๋์ ํฉ๋๋ค. ๋ณ๋์ ๋ฐ๋ณต๋ฌธ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋นํด ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ์ต์ํํฉ๋๋ค.
-
์ ๊ทํ:
for i in range(input_size): output[i] = output[i] / sum_exp๋ง์ง๋ง์ผ๋ก ๊ฐ ์์๋ฅผ ํฉ๊ณ๋ก ๋๋ ์ํํธ๋งฅ์ค ๊ณต์์ ๋ฐ๋ฅธ ์ฌ๋ฐ๋ฅธ ํ๋ฅ ๋ถํฌ๋ฅผ ์์ฑํฉ๋๋ค:
$$\Large \text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_{j=1}^{n} e^{x_j - \max(x)}}$$
CPU ๊ตฌํ์ ๋์ผํ ์์น ์์ ์ฑ ๊ธฐ๋ฒ(์ต๋๊ฐ ์ฐจ๊ฐ)์ ์ฌ์ฉํ๋, ๋ณ๋ ฌ์ด ์๋ ์์ฐจ์ ์ฐ์ฐ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ ์ค๋ ๋ ๋๊ธฐํ๋ฅผ ๋ค๋ฃฐ ํ์๊ฐ ์์ด GPU ๋ฒ์ ๋ณด๋ค ๋จ์ํ์ง๋ง, ๋๊ท๋ชจ ์ ๋ ฅ์์๋ ํจ์จ์ด ๋จ์ด์ง๋๋ค.
๋ ๊ตฌํ ๋ชจ๋ @compiler.register("softmax") ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ํตํด MAX ๊ทธ๋ํ์
์ปค์คํ
์ฐ์ฐ ์์คํ
์ ๋ฑ๋ก๋๋ฏ๋ก, ๊ฐ์ฉ ์ฌ๋ถ์ ๋ฐ๋ผ ์ด๋ ๋๋ฐ์ด์ค์์๋ ๋งค๋๋ฝ๊ฒ
์คํ๋ฉ๋๋ค.
ํ์ด์ฌ ํตํฉ
with Graph(
"softmax_graph",
input_types=[
TensorType(
dtype,
shape=input_tensor.shape,
device=DeviceRef.from_device(device),
),
],
custom_extensions=[mojo_kernels],
) as graph:
input_value = graph.inputs[0]
# The output shape is the same as the input for softmax
# Note: the name must match the name used in `@compiler.register("softmax")` in op/softmax.mojo
output = ops.custom(
name="softmax",
values=[input_value],
device=DeviceRef.from_device(device),
out_types=[
TensorType(
dtype=input_value.tensor.dtype,
shape=input_value.tensor.shape,
device=DeviceRef.from_device(device),
)
],
parameters={
"target": "gpu" if device == Accelerator() else "cpu",
"input_size": input_tensor.shape[0],
"dtype": dtype,
},
)[0].tensor
graph.output(output)
-
๊ทธ๋ํ ์ค์ ๊ณผ ๊ตฌ์ฑ:
with Graph( "softmax_graph", input_types=[ TensorType( dtype, shape=input_tensor.shape, device=DeviceRef.from_device(device), ), ], custom_extensions=[mojo_kernels], ) as graph:โsoftmax_graphโ๋ผ๋ ์ด๋ฆ์ ์ฐ์ฐ ๊ทธ๋ํ๋ฅผ ์์ฑํฉ๋๋ค:
- ์ ์ ํ dtype๊ณผ shape์ผ๋ก ์ ๋ ฅ ํ ์ ํ์ ์ ์
- ํ ์๋ฅผ ๋์ ๋๋ฐ์ด์ค(CPU ๋๋ GPU)์ ๋งคํ
- ์ง์ ๋ ๋๋ ํ ๋ฆฌ์์ ์ปค์คํ Mojo ์ฐ์ฐ ๋ก๋
custom_extensionsํ๋ผ๋ฏธํฐ๊ฐ Mojo ๊ตฌํ๊ณผ์ ์ฐ๊ฒฐ ํต์ฌ
-
์ปค์คํ ์ฐ์ฐ ๊ตฌ์ฑ:
output = ops.custom( name="softmax", values=[input_value], out_types=[ TensorType( dtype=input_value.tensor.dtype, shape=input_value.tensor.shape, device=DeviceRef.from_device(device), ) ], parameters={ "target": "gpu" if device == Accelerator() else "cpu", "input_size": input_tensor.shape[0], "dtype": dtype, }, )[0].tensor์ปค์คํ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํฉ๋๋ค:
- Mojo ์ฝ๋์
@compiler.register("softmax")์ ์ผ์นํ๋ ์ด๋ฆ - ๋ฆฌ์คํธ๋ก ์ ๋ฌ๋๋ ์ ๋ ฅ ๊ฐ
- ์ ๋ ฅ shape๊ณผ ํ์ ์ ๋ง๋ ์ถ๋ ฅ ํ์ ์ ์
- ๋์ ๋๋ฐ์ด์ค, ๋ฒกํฐ ํฌ๊ธฐ, ๋ฐ์ดํฐ ํ์ ์ ํฌํจํ ์ปค๋ ํ์ ํ๋ผ๋ฏธํฐ
[0].tensor๋ก ์ฒซ ๋ฒ์งธ ๋ฐํ ์์์์ ํ ์ ์ถ์ถ
- Mojo ์ฝ๋์
-
๊ทธ๋ํ ์ถ๋ ฅ ์ ์:
graph.output(output)์ฐ์ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ํ์ ์ถ๋ ฅ์ผ๋ก ๋ฑ๋กํฉ๋๋ค.
๋ฉ์ธ ์คํฌ๋ฆฝํธ๋ ๋ค์๊ณผ ๊ฐ์ ๊ผผ๊ผผํ ๊ฒ์ฆ์ ํฌํจํฉ๋๋ค:
- ๋๋ค ์
๋ ฅ ๋ฐ์ดํฐ ์์ฑ:
np.random.randn(INPUT_SIZE).astype(np.float32) - SciPy๋ก ๊ธฐ๋ ๊ฒฐ๊ณผ ๊ณ์ฐ:
scipy_softmax(input_array) - ์์น ์ ํ๋ ๊ฒ์ฆ:
np.testing.assert_allclose(..., rtol=1e-5) - ์ถ๋ ฅ์ด ์ ํจํ ํ๋ฅ ๋ถํฌ์ธ์ง ํ์ธ:
np.sum(result.to_numpy())
์ด ๊ตฌํ์ ๊ณ ์ฑ๋ฅ Mojo ์ปค๋๊ณผ ํ์ด์ฌ์ ๊ณผํ ์ปดํจํ ์ํ๊ณ๋ฅผ ํตํฉํ๋ MAX ๊ทธ๋ํ์ ๊ฐ๋ ฅํ ์ญ๋์ ๋ณด์ฌ์ฃผ๋ฉฐ, ํจ์จ์ฑ๊ณผ ์ฌ์ฉ ํธ์์ฑ์ ๋์์ ์ ๊ณตํฉ๋๋ค.
Puzzle 19: ์ดํ ์ Op
๊ฐ์
์ด ํผ์ฆ์์๋ ์ดํ ์ ๋ฉ์ปค๋์ฆ์ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ผ๋ก ๊ตฌํํฉ๋๋ค. ์ดํ ์ ์ ํธ๋์คํฌ๋จธ์ ํจ๊ป ๋๋ฆฌ ์๋ ค์ง ํ๋ ์ ๊ฒฝ๋ง์ ํต์ฌ ์์๋ก, ๋ชจ๋ธ์ด ์์ธกํ ๋ ์ ๋ ฅ์์ ๊ด๋ จ๋ ๋ถ๋ถ์ ์ง์คํ ์ ์๊ฒ ํด์ค๋๋ค.
์ํ์ ์ผ๋ก ์ดํ ์ ํจ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค:
$$\Large \text{Attention}(Q, K, V) = \text{softmax}(Q \cdot K^T) \cdot V$$
์ฌ๊ธฐ์:
- \(Q\)๋ \((d,)~\) ํํ์ ์ฟผ๋ฆฌ ๋ฒกํฐ - ์ฐพ์ผ๋ ค๋ ๋์์ ๋ํ๋ ๋๋ค
- \(K\)๋ \((\text{seq_len}, d)~\) ํํ์ ํค ํ๋ ฌ - ๋งค์นญํ ์ ์๋ ๋์์ ๋ํ๋ ๋๋ค
- \(V\)๋ \((\text{seq_len}, d)~\) ํํ์ ๊ฐ ํ๋ ฌ - ๊ฒ์ํ ์ ๋ณด๋ฅผ ๋ํ๋ ๋๋ค
- ์ถ๋ ฅ์ \((d,)\) ํํ์ ๊ฐ์คํฉ ๋ฒกํฐ์ ๋๋ค
์ฐ์ฐ์ ์ธ ๊ฐ์ง ์ฃผ์ ๋จ๊ณ๋ก ์ด๋ฃจ์ด์ง๋๋ค:
- ์ดํ ์ ์ ์: \(Q \cdot K^T\)๋ฅผ ๊ณ์ฐํ์ฌ ์ฟผ๋ฆฌ๊ฐ ๊ฐ ํค ๋ฒกํฐ์ ์ผ๋ง๋ ์ ๋งค์นญ๋๋์ง ์ธก์ ํฉ๋๋ค
- ์ดํ ์ ๊ฐ์ค์น: ์ํํธ๋งฅ์ค๋ฅผ ์ ์ฉํ์ฌ ์ ์๋ฅผ ํ๋ฅ ๋ถํฌ๋ก ๋ณํํฉ๋๋ค (๊ฐ์ค์น์ ํฉ = 1)
- ๊ฐ์ค ํฉ: ์ดํ ์ ๊ฐ์ค์น๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ๋ฒกํฐ๋ค์ ๊ฒฐํฉํด ์ต์ข ์ถ๋ ฅ์ ์์ฑํฉ๋๋ค
์ดํ ์ ์ดํดํ๊ธฐ: ๋จ๊ณ๋ณ ๋ถ์
์ดํ ์ ์ ์ค๋งํธ ๊ฒ์ ๋ฉ์ปค๋์ฆ์ผ๋ก ์๊ฐํด ๋ณด์ธ์. ์ฟผ๋ฆฌ(์ฐพ๊ณ ์ ํ๋ ๊ฒ)๊ฐ ์ฃผ์ด์ง๋ฉด, ์ดํ ์ ์ ํค-๊ฐ ์์ ๋ชจ์์์ ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ์ ๋ณด๋ฅผ ์ฐพ์๋ ๋๋ค:
-
1๋จ๊ณ - ์ ์ฌ๋ ๋งค์นญ: ์ฟผ๋ฆฌ \(Q\)๋ฅผ ๋ชจ๋ ํค \(K\)์ ๋น๊ตํ์ฌ ์ ์ฌ๋ ์ ์๋ฅผ ๊ตฌํฉ๋๋ค
- \(Q \cdot K^T\)๋ฅผ ๊ณ์ฐํ์ฌ \(Q\)๊ฐ ๊ฐ ํค ๋ฒกํฐ์ ์ผ๋ง๋ ์ ๋งค์นญ๋๋์ง ์ธก์ ํฉ๋๋ค
- ๋์ ์ ์ = ๋ ์ข์ ๋งค์นญ
-
2๋จ๊ณ - ํ๋ฅ ๋ถํฌ: ์์ ์ ์๋ฅผ ์ ๊ทํ๋ ๊ฐ์ค์น๋ก ๋ณํํฉ๋๋ค
- ์ํํธ๋งฅ์ค๋ฅผ ์ ์ฉํ์ฌ ๋ชจ๋ ๊ฐ์ค์น์ ํฉ์ด 1.0์ด ๋๋๋ก ํฉ๋๋ค
- ์ด๋ค ๊ฐ์ ์ง์คํ ์ง์ ๋ํ ํ๋ฅ ๋ถํฌ๋ฅผ ๋ง๋ญ๋๋ค
-
3๋จ๊ณ - ๊ฐ์ค ๊ฒ์: ์ดํ ์ ๊ฐ์ค์น๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ค์ ๊ฒฐํฉํฉ๋๋ค
- ๊ฐ ๊ฐ ๋ฒกํฐ์ ํด๋นํ๋ ๊ฐ์ค์น๋ฅผ ๊ณฑํฉ๋๋ค
- ๋ชจ๋ ๊ฒ์ ๋ํด ์ต์ข ์ถ๋ ฅ์ ๊ตฌํฉ๋๋ค
์ค์ํ ๋น์ : ๋์๊ด์์ ๊ฒ์ํ๋ ๊ฒ์ ์์ํด ๋ณด์ธ์. ์ฟผ๋ฆฌ๋ ์ฐพ๊ณ ์ถ์ ๊ฒ์ด๊ณ , ์ฑ ์ ๋ชฉ์ ํค์ด๋ฉฐ, ์ฑ ๋ด์ฉ์ ๊ฐ์ ๋๋ค. ์ดํ ์ ์ ๊ฐ ์ฑ ์ด ์ฟผ๋ฆฌ์ ์ผ๋ง๋ ๊ด๋ จ ์๋์ง ๊ณ์ฐํ ๋ค์, ๊ด๋ จ๋์ ๋ฐ๋ผ ๊ฐ์ค ์์ฝ์ ์ ๊ณตํฉ๋๋ค.
์ฐ์ฐ ํ๋ฆ ์๊ฐํ
Input: Q(16,) K(16,16) V(16,16)
โ โ โ
Step 1: Q(1,16) @ K^T(16,16) โ Scores(1,16)
โ
Step 2: softmax(Scores) โ Weights(1,16) [sum = 1.0]
โ
Step 3: Weights(1,16) @ V(16,16) โ Output(1,16) โ reshape โ Output(16,)
ํต์ฌ ์์ด๋์ด: ์ฟผ๋ฆฌ ๋ฒกํฐ \(Q\)๋ฅผ \((16,)\)์์ \((1,16)\)์ผ๋ก ๋ณํํ๋ฉด, ๋ด์ ๋์ ํ๋ ฌ ๊ณฑ์ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋๋ถ์ Puzzle 18์ ๊ณ ๋๋ก ์ต์ ํ๋ ํ์ผ๋ง matmul ์ปค๋์ ๊ทธ๋๋ก ํ์ฉํ ์ ์์ต๋๋ค!
GPU ๊ตฌํ์ ์ด์ ํผ์ฆ์์ ์ต์ ํ๋ ์ปค๋๋ค์ ์ฌ์ฌ์ฉํ๊ณ ๊ฒฐํฉํฉ๋๋ค:
- Puzzle 16์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ โ ํจ์จ์ ์ธ \(Q \cdot K^T\) ๋ฐ \(\text{weights} \cdot V\) ์ฐ์ฐ์ ์ฌ์ฉ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ์น โ \(K^T\)๋ฅผ ํจ์จ์ ์ผ๋ก ๊ณ์ฐ
- Puzzle 18์ ๋ณ๋ ฌ ์ํํธ๋งฅ์ค โ ์์น์ ์ผ๋ก ์์ ์ ์ธ ์ดํ ์ ๊ฐ์ค์น ๊ณ์ฐ์ ์ฌ์ฉ
๐ ์ปค๋ ์ฌ์ฌ์ฉ ์ ๋ต: ์ด ํผ์ฆ์ ์ด์ ํผ์ฆ์์ ๊ฒ์ฆ๋ ์ต์ ํ ์ปค๋๋ค์ ๊ฒฐํฉํ์ฌ ๋ณต์กํ ์ฐ์ฐ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ๋ชจ๋ ๊ฒ์ ์ฒ์๋ถํฐ ์์ฑํ๋ ๋์ , Puzzle 16์
matmul_idiomatic_tiled๊ณผ Puzzle 18์softmax_kernel์ ํ์ฉํ์ฌ ๋ชจ๋ํ GPU ์ปค๋ ์ค๊ณ์ ๊ฐ๋ ฅํจ์ ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ๊ฐ๋
- ์ํ์ค ์ฒ๋ฆฌ๋ฅผ ์ํ ๋ฒกํฐ ์ดํ ์ ๋ฉ์ปค๋์ฆ
- ์ปค๋ ์ฌ์ฌ์ฉ: Puzzle 16๊ณผ Puzzle 18์ ๊ฒ์ฆ๋ ๊ตฌํ ํ์ฉ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ tiling์ ํ์ฉํ ํจ์จ์ ์ธ ํ๋ ฌ ๊ณฑ์
- ๋ฒํผ ํ ๋น์ ์ต์ํํ๋ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ํ ์ ํํ ๋ณํ
- ์ฌ๋ฌ ์ต์ ํ ์ปค๋์ ๋จ์ผ ์ฐ์ฐ์ผ๋ก ํตํฉ
- ๋ค์ค ์ ๋ ฅ์ ์ง์ํ๋ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ
- ํธํ์ฑ์ ์ํ CPU ํด๋ฐฑ ๊ตฌํ
์ค์
- ์ํ์ค ๊ธธ์ด: \(\text{SEQ_LEN} = 16~\) - ์ํ์ค ๋ด ํค/๊ฐ ๋ฒกํฐ์ ์
- ๋ชจ๋ธ ์ฐจ์: \(\text{D} = 16~\) - ๊ฐ ๋ฒกํฐ(์ฟผ๋ฆฌ, ํค, ๊ฐ)์ ์ฐจ์
- ๋ธ๋ก๋น ์ค๋ ๋ ์: ๊ฐ ์ปค๋์ ๋ง๊ฒ ๊ฐ๋ณ ์ต์ ํ
- ๊ทธ๋ฆฌ๋ ์ฐจ์: ๋ค์ํ ํ๋ ฌ ํฌ๊ธฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ๋์ ์ผ๋ก ๊ณ์ฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ์ ์น, matmul, ์ํํธ๋งฅ์ค ์ปค๋์์ ์ฑ๋ฅ์ ์ํด ํ์ฉ
๋ ์ด์์ ์ค์ :
- ์ฟผ๋ฆฌ ํ
์:
row_major[d]() - ํค ํ
์:
row_major[seq_len, d]() - ๊ฐ ํ
์:
row_major[seq_len, d]() - ์ถ๋ ฅ ํ
์:
row_major[d]() - ์ปค์คํ
op ํ๋ผ๋ฏธํฐ:
{"seq_len": seq_len, "d": d, "dtype": dtype}
์ด ํผ์ฆ์ ํต์ฌ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ค์ค ์ปค๋ ์ค์ผ์คํธ๋ ์ด์ : ์ ์น, matmul, ์ํํธ๋งฅ์ค ์ฐ์ฐ์ ๊ฒฐํฉ
- ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ: ํํ ๋ณํ ์ฐ์ฐ๊ณผ ๋ฒํผ ์ฌ์ฌ์ฉ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์ต์ํ
- ์์น ์์ ์ฑ: Puzzle 18์ ๊ฒ์ฆ๋ ์ํํธ๋งฅ์ค ๊ตฌํ ํ์ฉ
- ์ฑ๋ฅ ์ต์ ํ: ๋ชจ๋ ํ๋ ฌ ์ฐ์ฐ์ Puzzle 16์ ํ์ผ๋ง ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ
- ๋ค์ค ์ ๋ ฅ ์ฐ์ฐ: ๋จ์ผ ์ปค์คํ op์์ ์ธ ๊ฐ์ ์ ๋ ฅ ํ ์(Q, K, V) ์ฒ๋ฆฌ
์ดํ ์ ์ปค์คํ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ ์ผ์ ์ํํฉ๋๋ค:
- ํ์ด์ฌ์์ ์ฟผ๋ฆฌ, ํค, ๊ฐ ํ ์๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ๊ธฐ
- ์ต์ ํ๋ ์ปค๋์ ์ฌ์ฉํ์ฌ GPU์์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ
- ์ดํ ์ ๊ฐ์ค ์ถ๋ ฅ ๋ฒกํฐ ๋ฐํ
- NumPy ์ฐธ์กฐ ๊ตฌํ ๊ฒฐ๊ณผ์ ์ผ์น
์์ฑํ ์ฝ๋
์ด ํผ์ฆ์ ์์ฑํ๋ ค๋ฉด Puzzle 16์ ํ์ผ๋ง matmul ์ปค๋๊ณผ Puzzle 18์ ์ํํธ๋งฅ์ค ์ปค๋์ ํ์ฉํฉ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ Mojo ํ์ผ์์ ์ ์น ์ปค๋๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
1. ์ ์น ์ปค๋ ๊ตฌํํ๊ธฐ
def transpose_kernel[
rows: Int,
cols: Int,
OutLayout: TensorLayout,
InLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
inp: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
):
# FILL ME IN (roughly 18 lines)
...
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p19/op/attention.mojo
ํ
์ ์น ์ปค๋ ๊ตฌํ ๊ฐ์ด๋:
-
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ค์ :
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TRANSPOSE_BLOCK_DIM_XY, TRANSPOSE_BLOCK_DIM_XY]())์ ์ฌ์ฉํ์ฌTRANSPOSE_BLOCK_DIM_XYรTRANSPOSE_BLOCK_DIM_XYํฌ๊ธฐ์ ์ ์ฌ๊ฐํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ์ ์์ฑํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ค๋ ๋ ๊ฐ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๊ตํ์ด ๊ฐ๋ฅํฉ๋๋ค. -
์ค๋ ๋ ์ธ๋ฑ์ฑ: ์ค๋ ๋๋ฅผ ํ๋ ฌ ์์์ ๋งคํํฉ๋๋ค:
local_row = thread_idx.y,local_col = thread_idx.x(๋ธ๋ก ๋ด ์์น)global_row = block_idx.y * TRANSPOSE_BLOCK_DIM_XY + local_row(์ ์ฒด ํ๋ ฌ์์์ ์์น)
-
2๋จ๊ณ ์ฐ์ฐ:
- 1๋จ๊ณ: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ์ผ๋ฐ ์ธ๋ฑ์ฑ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํฉ๋๋ค
- 2๋จ๊ณ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ค๋ฐ๊พผ ์ธ๋ฑ์ฑ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค
-
ํ์ ๋๊ธฐํ: ๋ก๋์ ์ ์ฅ ์ฌ์ด์
barrier()๋ฅผ ํธ์ถํ์ฌ ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ก๋๋ฅผ ์๋ฃํ ํ์์ผ ์ ์ฅ์ ์์ํ๋๋ก ๋ณด์ฅํฉ๋๋ค -
์ ์น์ ํต์ฌ: ์ ์น๋ ๋ค๋ฐ๊พผ ์ธ๋ฑ์ฑ์ ํตํด ์ด๋ฃจ์ด์ง๋๋ค:
shared_tile[local_row, local_col]๋์shared_tile[local_col, local_row]๋ฅผ ์ฌ์ฉํฉ๋๋ค -
๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์ํํ์ฌ
TRANSPOSE_BLOCK_DIM_XYxTRANSPOSE_BLOCK_DIM_XY๋ก ์ ํํ ๋๋์ด์ง์ง ์๋ ํ๋ ฌ์์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ฝ๊ธฐ/์ฐ๊ธฐ๋ฅผ ๋ฐฉ์งํฉ๋๋ค -
๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ์ด ํจํด์ ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋ชจ๋ ๋ณํฉ๋๋๋ก ๋ณด์ฅํ์ฌ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ํ์ฉํฉ๋๋ค
2. ์ดํ ์ ์ค์ผ์คํธ๋ ์ด์
var gpu_ctx = rebind[DeviceContext](ctx[])
# Define layouts for matrix multiplication
# Q reshaped to (1, d)
comptime layout_q_2d = row_major[1, d]()
comptime Q2DLayout = type_of(layout_q_2d)
# K^T is (d, seq_len)
comptime layout_k_t = row_major[d, seq_len]()
comptime KTLayout = type_of(layout_k_t)
# Scores as (1, seq_len)
comptime layout_scores_2d = row_major[1, seq_len]()
comptime Scores2DLayout = type_of(layout_scores_2d)
# Weights as (1, seq_len)
comptime layout_weights_2d = row_major[1, seq_len]()
comptime Weights2DLayout = type_of(layout_weights_2d)
# Result as (1, d)
comptime layout_result_2d = row_major[1, d]()
comptime Result2DLayout = type_of(layout_result_2d)
# Transpose implementation limited to square (TRANSPOSE_BLOCK_DIM_XY x TRANSPOSE_BLOCK_DIM_XY) thread blocks
comptime transpose_threads_per_block = (
TRANSPOSE_BLOCK_DIM_XY,
TRANSPOSE_BLOCK_DIM_XY,
)
# Tile over the K (seq_len, d) matrix
comptime transpose_blocks_per_grid = (
(d + TRANSPOSE_BLOCK_DIM_XY - 1) // TRANSPOSE_BLOCK_DIM_XY,
(seq_len + TRANSPOSE_BLOCK_DIM_XY - 1)
// TRANSPOSE_BLOCK_DIM_XY,
)
# Matmul implementation limited to square (MATMUL_BLOCK_DIM_XY x MATMUL_BLOCK_DIM_XY) thread blocks
comptime matmul_threads_per_block = (
MATMUL_BLOCK_DIM_XY,
MATMUL_BLOCK_DIM_XY,
)
# seq_len outputs ( Q @ K^T = (1, d) @ (d, seq_len) -> (1, seq_len) ) with one thread per output
comptime scores_blocks_per_grid = (
seq_len + MATMUL_BLOCK_DIM_XY - 1
) // MATMUL_BLOCK_DIM_XY
comptime softmax_threads = SOFTMAX_BLOCK_DIM_X
comptime softmax_blocks_per_grid = 1
# d outputs ( weights @ V = (1, seq_len) @ (seq_len, d) -> (1, d) ) with one thread per output
comptime result_blocks_per_grid = (
d + MATMUL_BLOCK_DIM_XY - 1
) // MATMUL_BLOCK_DIM_XY
# Allocate minimal temporary buffers - reuse same buffer for different shapes
var k_t_buf = gpu_ctx.enqueue_create_buffer[dtype](
seq_len * d
) # K^T as (d, seq_len)
var scores_weights_buf = gpu_ctx.enqueue_create_buffer[dtype](
seq_len
) # Reused for scores and weights
var k_t = TileTensor(k_t_buf, layout_k_t)
# Step 1: Reshape Q from (d,) to (1, d) - no buffer needed
# FILL ME IN 1 line
# Step 2: Transpose K from (seq_len, d) to K^T (d, seq_len)
# FILL ME IN 1 function call
# Step 3: Compute attention scores using matmul: Q @ K^T = (1, d) @ (d, seq_len) -> (1, seq_len)
# This computes Q ยท K^T[i] = Q ยท K[i] for each column i of K^T (which is row i of K)
# Reuse scores_weights_buf as (1, seq_len) for scores
# FILL ME IN 2 lines
# Step 4: Reshape scores from (1, seq_len) to (seq_len,) for softmax
# FILL ME IN 1 line
# Step 5: Apply softmax to get attention weights
# FILL ME IN 1 function call
# Step 6: Reshape weights from (seq_len,) to (1, seq_len) for final matmul
# FILL ME IN 1 line
# Step 7: Compute final result using matmul: weights @ V = (1, seq_len) @ (seq_len, d) -> (1, d)
# Reuse out_tensor reshaped as (1, d) for result
# FILL ME IN 2 lines
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p19/op/attention.mojo
์ปค๋ ํ ์คํธ
pixi run p19
pixi run -e amd p19
pixi run -e apple p19
uv run poe p19
์ฑ๊ณตํ๋ฉด CPU์ GPU์์ ๋ค์๊ณผ ๋น์ทํ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค:
Input shapes: Q=(16,), K=(16, 16), V=(16, 16)
Sample Q values: [ 0.04967142 -0.01382643 0.06476886 0.15230298 -0.02341534]
Sample K[0] values: [-0.10128311 0.03142473 -0.09080241 -0.14123037 0.14656489]
Sample V[0] values: [ 0.11631638 0.00102331 -0.09815087 0.04621035 0.01990597]
================================================================================
STEP-BY-STEP VECTOR ATTENTION COMPUTATION DEBUG
================================================================================
1. INPUT SHAPES:
Q shape: (16,) (query vector)
K shape: (16, 16) (key matrix)
V shape: (16, 16) (value matrix)
Q[:5]: [ 0.04967142 -0.01382643 0.06476886 0.15230298 -0.02341534]
2. ATTENTION SCORES (K[i] ยท Q):
Scores shape: (16,)
Scores[:5]: [-0.03479404 -0.01563787 0.04834607 0.06764711 0.04001468]
Min: -0.061636, Max: 0.067647
Manual verification:
Q ยท K[0] = K[0] ยท Q = -0.034794 (computed: -0.034794)
Q ยท K[1] = K[1] ยท Q = -0.015638 (computed: -0.015638)
Q ยท K[2] = K[2] ยท Q = 0.048346 (computed: 0.048346)
3. SOFTMAX:
Max score: 0.067647
Attention weights shape: (16,)
Attention weights[:5]: [0.05981331 0.06097015 0.06499878 0.0662655 0.06445949]
Sum: 1.000000 (should be 1.0)
4. WEIGHTED SUM OF VALUES:
Output shape: (16,)
Output[:5]: [-0.00935538 -0.0243433 0.00306551 0.02346884 0.019306 ]
Output norm: 0.092764
Manual output[:5]: [-0.00935538 -0.0243433 0.00306551 0.02346884 0.019306 ]
Match: True
================================================================================
TESTING INDIVIDUAL OPERATIONS
================================================================================
Test 1: Vector Dot Product
a ยท b = 3.000000
Test 2: Matrix-Vector Multiplication
M @ v = [ 3. 7. 11.]
Test 3: Softmax
Input: [1. 2. 3. 4.]
Softmax: [0.0320586 0.08714432 0.2368828 0.6439143 ]
Sum: 1.000000
================================================================================
TESTING FULL ATTENTION
================================================================================
Compiling attention graph on Device(type=cpu,id=0)
Executing attention on Device(type=cpu,id=0)
====================================================================================================
CPU attention output[:5]: [-0.00935538 -0.02434331 0.00306551 0.02346884 0.019306 ]
CPU matches NumPy: True
Compiling attention graph on Device(type=gpu,id=0)
Executing attention on Device(type=gpu,id=0)
====================================================================================================
GPU attention output[:5]: [-0.00935538 -0.0243433 0.00306551 0.02346884 0.019306 ]
Expected output[:5]: [-0.00935538 -0.0243433 0.00306551 0.02346884 0.019306 ]
GPU matches NumPy: True
================================================================================
FINAL VERIFICATION
================================================================================
โ CPU implementation PASSED
โ GPU implementation PASSED
Output vector norms:
CPU: 0.092764
GPU: 0.092764
Expected: 0.092764
์ด ์ถ๋ ฅ์ ์ปค์คํ MAX ๊ทธ๋ํ ์ฐ์ฐ์ด ์ดํ ์ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ์ฌ NumPy ์ฐธ์กฐ ๊ตฌํ๊ณผ ์ผ์นํ๋ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ์์ ๋ณด์ฌ์ค๋๋ค.
์๋ฃจ์
์ด ํผ์ฆ์ ํ๋ ค๋ฉด Mojo์์ ์ ์น ์ปค๋์ ๊ตฌํํ๊ณ ์ดํ ์ ์ปค์คํ ์ฐ์ฐ์ ์ํ ํ์ด์ฌ ๊ทธ๋ํ ์ ์๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. ์ด ํผ์ฆ์ ์ด์ ํผ์ฆ์ ๊ฐ๋ ๋ค์ ๊ธฐ๋ฐ์ผ๋ก, Puzzle 16์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ๊ณผ Puzzle 18์ ์ํํธ๋งฅ์ค๋ฅผ ๊ฒฐํฉํ์ฌ ์์ ํ ์ดํ ์ ๋ฉ์ปค๋์ฆ์ ๊ตฌ์ฑํฉ๋๋ค.
์ฌ์ฌ์ฉ ์ปค๋
๊ตฌํ์์ ๋ค์์ ๊ฒ์ฆ๋ ์ปค๋๋ค์ ์ง์ ํ์ฉํฉ๋๋ค:
matmul_idiomatic_tiled(Puzzle 16) - \(Q \times K^T\)์ \(\text{weights} \times V\) ์ฐ์ฐ ๋ชจ๋๋ฅผ ์ํsoftmax_kernel(Puzzle 18) - ์์น์ ์ผ๋ก ์์ ์ ์ธ ์ดํ ์ ๊ฐ์ค์น ๊ณ์ฐ ์ ๊ณต
์ด๋ ๋ชจ๋ํ GPU ์ํคํ ์ฒ์ ์ข์ ์์์ ๋๋ค: ๋จ์ผ ๊ตฌํ์ฒด๊ฐ ์๋, ๊ฒ์ฆ๋ ์ต์ ํ ์ปดํฌ๋ํธ๋ฅผ ์ค์ผ์คํธ๋ ์ด์ ํ์ฌ ๋ณต์กํ ์ ๊ฒฝ๋ง ์ฐ์ฐ์ ๊ตฌ์ถํฉ๋๋ค.
์ดํ ์ ์ฐ์ฐ์ ํ์ค์ ์ธ ์ํ์ ์ ์๋ฅผ ๋ฐ๋ฆ ๋๋ค:
$$\Large \text{Attention}(Q, K, V) = \text{softmax}(Q \cdot K^T) \cdot V$$
์์ ๋ถ์:
- \(Q \cdot K^T~\): ์ฟผ๋ฆฌ-ํค ์ ์ฌ๋ ์ ์, ํํ: \((1, \text{seq_len})\)
- \(\text{softmax}(\cdot)~\): ์ ์๋ฅผ ํ๋ฅ ๋ก ์ ๊ทํ, ํํ: \((1, \text{seq_len})\)
- \(\text{weights} \cdot V~\): ๊ฐ์ ๊ฐ์ค ๊ฒฐํฉ, ํํ: \((1, d)\)
์ด ๊ณผ์ ์๋ ์ด์ ํผ์ฆ์ GPU ์ปค๋์ ํ์ฉํ์ฌ ์ต์ ํํ๋ ์ฌ๋ฌ ์ฐ์ฐ ๋จ๊ณ๊ฐ ํฌํจ๋ฉ๋๋ค.
1. ์ ์น ์ปค๋ ๊ตฌํ
def transpose_kernel[
rows: Int,
cols: Int,
OutLayout: TensorLayout,
InLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
inp: TileTensor[mut=True, dtype, InLayout, MutAnyOrigin],
):
"""Transpose matrix using shared memory tiling for coalesced access."""
comptime shared_layout = row_major[
TRANSPOSE_BLOCK_DIM_XY, TRANSPOSE_BLOCK_DIM_XY
]()
var shared_tile = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](shared_layout)
var local_row = thread_idx.y
var local_col = thread_idx.x
var global_row = block_idx.y * TRANSPOSE_BLOCK_DIM_XY + local_row
var global_col = block_idx.x * TRANSPOSE_BLOCK_DIM_XY + local_col
var inp_lt = inp.to_layout_tensor()
var output_lt = output.to_layout_tensor()
var shared_tile_lt = shared_tile.to_layout_tensor()
if global_row < rows and global_col < cols:
shared_tile_lt[local_row, local_col] = inp_lt[global_row, global_col]
barrier()
var out_row = block_idx.x * TRANSPOSE_BLOCK_DIM_XY + local_row
var out_col = block_idx.y * TRANSPOSE_BLOCK_DIM_XY + local_col
# Store data from shared memory to global memory (coalesced write)
# Note: we transpose the shared memory access pattern
if out_row < cols and out_col < rows:
output_lt[out_row, out_col] = shared_tile_lt[local_col, local_row]
์ ์น ์ปค๋์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ tiling์ ์ฌ์ฉํ์ฌ ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๋ฌ์ฑํฉ๋๋ค. ํต์ฌ ๊ตฌํ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
ํต์ฌ ์ ์น ํจํด
# ์ผ๋ฐ ์ธ๋ฑ์ฑ์ผ๋ก ๋ก๋
shared_tile[local_row, local_col] = inp[global_row, global_col]
barrier()
# ๋ค๋ฐ๊พผ ์ธ๋ฑ์ฑ์ผ๋ก ์ ์ฅํ์ฌ ์ ์น
output[out_row, out_col] = shared_tile[local_col, local_row]
์ ์น๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์์ ๋ค๋ฐ๊พผ ์ธ๋ฑ์ฑ([local_row, local_col] ๋์
[local_col, local_row])๊ณผ ์ถ๋ ฅ ์์น ์ง์ ์ ์ํ ๋ค๋ฐ๊พผ ๋ธ๋ก ์ขํ๋ฅผ ํตํด
์ด๋ฃจ์ด์ง๋๋ค. ์ด๋ฅผ ํตํด ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋ชจ๋ ๋ณํฉ์ ์ ์งํ๋ฉด์ ์ ์น ์ฐ์ฐ์
์ํํฉ๋๋ค.
2. GPU ์ปค๋ ์ค์ผ์คํธ๋ ์ด์
# Step 1: Reshape Q from (d,) to (1, d) - no buffer needed
var q_2d = q_tensor.reshape(layout_q_2d)
# Step 2: Transpose K from (seq_len, d) to K^T (d, seq_len)\
comptime kernel = transpose_kernel[
seq_len, d, KTLayout, KLayout, dtype
]
gpu_ctx.enqueue_function[kernel, kernel](
k_t,
k_tensor,
grid_dim=transpose_blocks_per_grid,
block_dim=transpose_threads_per_block,
)
# Step 3: Compute attention scores using matmul: Q @ K^T = (1, d) @ (d, seq_len) -> (1, seq_len)
# This computes Q ยท K^T[i] = Q ยท K[i] for each column i of K^T (which is row i of K)
# Reuse scores_weights_buf as (1, seq_len) for scores
var scores_2d = TileTensor(scores_weights_buf, layout_scores_2d)
comptime kernel2 = matmul_idiomatic_tiled[
1,
seq_len,
d,
Scores2DLayout,
Q2DLayout,
KTLayout,
dtype,
]
gpu_ctx.enqueue_function[kernel2, kernel2](
scores_2d,
q_2d,
k_t,
grid_dim=scores_blocks_per_grid,
block_dim=matmul_threads_per_block,
)
# Step 4: Reshape scores from (1, seq_len) to (seq_len,) for softmax
var weights = scores_2d.reshape(layout_scores)
# Step 5: Apply softmax to get attention weights (in-place)
comptime ScoresLayout = type_of(layout_scores)
comptime kernel3 = softmax_gpu_kernel[seq_len, ScoresLayout, dtype]
# Create two TileTensor views from the underlying buffer to avoid aliasing error
var weights_out = TileTensor[
mut=True, dtype, ScoresLayout, MutAnyOrigin
](scores_weights_buf, layout_scores)
var weights_in = TileTensor[
mut=True, dtype, ScoresLayout, MutAnyOrigin
](scores_weights_buf, layout_scores)
gpu_ctx.enqueue_function[kernel3, kernel3](
weights_out,
weights_in,
grid_dim=softmax_blocks_per_grid,
block_dim=softmax_threads,
)
# Step 6: Reshape weights from (seq_len,) to (1, seq_len) for final matmul
var weights_2d = weights.reshape(layout_weights_2d)
# Step 7: Compute final result using matmul: weights @ V = (1, seq_len) @ (seq_len, d) -> (1, d)
# Reuse out_tensor reshaped as (1, d) for result
var result_2d = output_tensor.reshape(layout_result_2d)
comptime kernel4 = matmul_idiomatic_tiled[
1,
d,
seq_len,
Result2DLayout,
Weights2DLayout,
VLayout,
dtype,
]
gpu_ctx.enqueue_function[kernel4, kernel4](
result_2d,
weights_2d,
v_tensor,
grid_dim=result_blocks_per_grid,
block_dim=matmul_threads_per_block,
)
GPU ์ค์ผ์คํธ๋ ์ด์ ์ ์ ๊ตํ ์ปค๋ ์ฒด์ด๋๊ณผ ์ ๋ก ์นดํผ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ฅผ ๋ณด์ฌ์ค๋๋ค:
๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ์ ๋ต
# ์ ๋ก ์นดํผ reshape - ๋ฐ์ดํฐ ์ด๋ ์์ด ํ
์ shape๋ง ์ฌํด์
q_2d = q_tensor.reshape[layout_q_2d]()
# ์ ๊ทน์ ์ธ ๋ฒํผ ์ฌ์ฌ์ฉ - ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ, ๋ค๋ฅธ ํด์
weights = scores_2d.reshape[layout_scores]()
๊ตฌํ์ ๋ค์์ ํตํด ์ต๋ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ๋ฌ์ฑํฉ๋๋ค:
- ์ ๋ก ์นดํผ ํํ ๋ณํ: ๋ฉ๋ชจ๋ฆฌ์์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ํ์ง ์๊ณ ํ ์ ํํ๋ฅผ ์ฌํด์
- ์ง๋ฅ์ ๋ฒํผ ์ฌ์ฌ์ฉ: ๋์ผํ
scores_weights_buf๊ฐ ์ ์ \((1,\text{seq_len})\)์ ๊ฐ์ค์น \((\text{seq_len},)\) ์ด์ค ์ฉ๋๋ก ํ์ฉ - ์ต์ ํ ๋น: ๋จ 2๊ฐ์ ์์ ๋ฒํผ๋ก ์ ์ฒด ์ดํ ์ ์ฐ์ฐ ์ํ
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ๋ชจ๋ ์ฐ์ฐ์์ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ ์ง
์ ๋ต์ ์ปค๋ ์ฌ์ฌ์ฉ ํจํด
- 3๋จ๊ณ & 7๋จ๊ณ: ๋ ๋ค Puzzle 16์
matmul_idiomatic_tiledํ์ฉ- 3๋จ๊ณ: \(Q \times K^T\) โ ์ดํ ์ ์ ์ ๊ณ์ฐ \((1,d) \times (d,\text{seq_len}) \rightarrow (1,\text{seq_len})\)
- 7๋จ๊ณ: \(\text{weights} \times V\) โ ์ต์ข ๊ฐ์ค ์ถ๋ ฅ \((1,\text{seq_len}) \times (\text{seq_len},d) \rightarrow (1,d)\)
- ๋ ์ฐ์ฐ ๋ชจ๋ ๋ค์ํ ํ๋ ฌ ํฌ๊ธฐ๋ฅผ ์์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๊ฒฝ๊ณ ๊ฒ์ฌ ํฌํจ
- 5๋จ๊ณ: Puzzle 18์
softmax_kernel์ฌ์ฉ- ์์ ์ ์๋ฅผ ์ ๊ทํ๋ ํ๋ฅ ๋ถํฌ๋ก ๋ณํ
- ์ต๋๊ฐ ์ฐจ๊ฐ๊ณผ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ํตํ ์์น ์์ ์ฑ ๋ณด์ฅ
- \(\sum_{i} \text{weights}[i] = 1.0\) ๋ณด์ฅ
์ด๋ ๋ชจ๋ํ GPU ์ํคํ ์ฒ์ ์ข์ ์์์ ๋๋ค: ๋จ์ผ ๊ตฌํ์ฒด๊ฐ ์๋, ๊ฒ์ฆ๋ ์ต์ ํ ์ปค๋๋ค์ ์ค์ผ์คํธ๋ ์ด์ ํ์ฌ ๋ณต์กํ ์ ๊ฒฝ๋ง ์ฐ์ฐ์ ๊ตฌ์ถํฉ๋๋ค!
ํต์ฌ ๊ตฌํ ์ธ์ฌ์ดํธ
๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ์ ๋ต
์ ๊ทน์ ์ธ ๋ฒํผ ์ฌ์ฌ์ฉ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ์ต์ํํฉ๋๋ค:
# ์ ์ฒด ์ฐ์ฐ์ ํ์ํ ์์ ๋ฒํผ๋ ๋จ 2๊ฐ
k_t_buf = gpu_ctx.enqueue_create_buffer[dtype](seq_len * d)
scores_weights_buf = gpu_ctx.enqueue_create_buffer[dtype](seq_len)
ํต์ฌ ์ต์ ํ ํฌ์ธํธ:
- ๋์ผํ
scores_weights_buf๊ฐ ํํ ๋ณํ ์ฐ์ฐ์ ํตํด ์ดํ ์ ์ ์์ ๊ฐ์ค์น ๋ชจ๋์ ์ฌ์ฌ์ฉ๋ฉ๋๋ค - ์ ๋ก ์นดํผ ํ ์ ํํ ๋ณํ์ผ๋ก ๋ถํ์ํ ๋ฐ์ดํฐ ์ด๋์ ์ ๊ฑฐํฉ๋๋ค
์ปค๋ ์ฌ์ฌ์ฉ ์ํคํ ์ฒ
์ด ํผ์ฆ์ ์ธ ๊ฐ์ง ํนํ๋ ์ปค๋์ ๊ฒฐํฉํ์ฌ ๋ชจ๋ํ ์ปค๋ ์ค๊ณ๋ฅผ ๋ณด์ฌ์ค๋๋ค:
matmul_idiomatic_tiled(2ํ ์ฌ์ฉ) - \(Q \times K^T\)์ \(\text{weights} \times V\) ์ฐ์ฐ ๋ชจ๋๋ฅผ ์ํsoftmax_kernel- ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ํ์ฉํ์ฌ ์์น์ ์ผ๋ก ์์ ์ ์ธ ์ดํ ์ ๊ฐ์ค์น ๊ณ์ฐtranspose_kernel- ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ผ๋ก ํจ์จ์ ์ธ \(K^T\) ๊ณ์ฐ
์ํคํ ์ฒ์ ์ฅ์ :
- ์กฐํฉ ๊ฐ๋ฅ์ฑ: ๊ฒ์ฆ๋ ์ปดํฌ๋ํธ๋ก ๋ณต์กํ ์ฐ์ฐ ๊ตฌ์ถ
- ์ ์ง๋ณด์์ฑ: ๊ฐ ์ปค๋์ด ๋ช ํํ๊ฒ ์ ์๋ ๋จ์ผ ์ญํ ์ํ
- ์ฑ๋ฅ: ์ด์ ํผ์ฆ์ ๊ณ ๋๋ก ์ต์ ํ๋ ๊ตฌํ ํ์ฉ
- ํ์ฅ์ฑ: ๋ชจ๋ํ ์ค๊ณ๋ก ๋ ํฐ ์ดํ ์ ๋ฉ์ปค๋์ฆ์ผ๋ก ํ์ฅ ์ฉ์ด
์ด ๊ตฌํ์ ์ ๊ตํ ์ ๊ฒฝ๋ง ์ฐ์ฐ์ด ๋จ์ผ ๊ตฌํ์ฒด๊ฐ ์๋, ๋ ๋จ์ํ๊ณ ์ ๊ฒ์ฆ๋ GPU ์ปค๋๋ค์ ์ค์ผ์คํธ๋ ์ด์ ํ์ฌ ๊ตฌ์ถํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค.
๋ณด๋์ค ์ฑ๋ฆฐ์ง
์ฑ๋ฆฐ์ง I: ๊ณ ๊ธ ์ํํธ๋งฅ์ค ๊ตฌํ
์ด ์ฑ๋ฆฐ์ง๋ Puzzle 18: ์ํํธ๋งฅ์ค Op์ ํ์ฅ์ ๋๋ค
์ํํธ๋งฅ์ค ๊ตฌํ์ ํ์ฅํ๋ ๊ณ ๊ธ ์ฑ๋ฆฐ์ง๋ค์ ๋๋ค:
1. ๋๊ท๋ชจ ์ํํธ๋งฅ์ค: TPB < SIZE ์ฒ๋ฆฌ
์
๋ ฅ ํฌ๊ธฐ๊ฐ ๋ธ๋ก๋น ์ค๋ ๋ ์๋ฅผ ์ด๊ณผํ๋ฉด(TPB < SIZE), ๋จ์ผ ๋ธ๋ก์ด ์ ์ฒด ๋ฐฐ์ด์
์ฒ๋ฆฌํ ์ ์์ด ํ์ฌ ๊ตฌํ์ด ๋์ํ์ง ์์ต๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๋ ๋ ๊ฐ์ง ์ ๊ทผ๋ฒ์ด
์์ต๋๋ค:
1.1 ๋ฒํผ ๋ฆฌ๋์
- ๋ธ๋ก ๋จ์ ๊ฒฐ๊ณผ(์ต๋๊ฐ๊ณผ ํฉ๊ณ)๋ฅผ ๋๋ฐ์ด์ค ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํฉ๋๋ค
- ๋ ๋ฒ์งธ ์ปค๋์ ์ฌ์ฉํ์ฌ ์ด ์ค๊ฐ ๊ฒฐ๊ณผ๋ค์ ๋ํด ๋ฆฌ๋์ ์ ์ํํฉ๋๋ค
- ์ ์ญ ์ต๋๊ฐ๊ณผ ํฉ๊ณ๋ฅผ ์ฌ์ฉํ๋ ์ต์ข ์ ๊ทํ ๋จ๊ณ๋ฅผ ๊ตฌํํฉ๋๋ค
1.2 2๋จ๊ณ ์ํํธ๋งฅ์ค
- 1์ฐจ: ๊ฐ ๋ธ๋ก์ด ๋ก์ปฌ ์ต๋๊ฐ์ ๊ณ์ฐํฉ๋๋ค
- ๋๊ธฐํ ํ ์ ์ญ ์ต๋๊ฐ์ ๊ณ์ฐํฉ๋๋ค
- 2์ฐจ: \(e^{x-max}\)์ ๋ก์ปฌ ํฉ๊ณ๋ฅผ ๊ณ์ฐํฉ๋๋ค
- ๋๊ธฐํ ํ ์ ์ญ ํฉ๊ณ๋ฅผ ๊ณ์ฐํฉ๋๋ค
- ์ต์ข : ์ ์ญ ํฉ๊ณ๋ฅผ ์ฌ์ฉํ์ฌ ์ ๊ทํํฉ๋๋ค
2. ๋ฐฐ์น ์ํํธ๋งฅ์ค
๋ฒกํฐ ๋ฐฐ์น(2D ์ ๋ ฅ ํ ์)์ ๋ํ ์ํํธ๋งฅ์ค๋ฅผ ๋ค์ ๋ณํ์ผ๋ก ๊ตฌํํฉ๋๋ค:
- ํ ๋จ์ ์ํํธ๋งฅ์ค: ๊ฐ ํ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ํํธ๋งฅ์ค๋ฅผ ์ ์ฉํฉ๋๋ค
- ์ด ๋จ์ ์ํํธ๋งฅ์ค: ๊ฐ ์ด์ ๋ ๋ฆฝ์ ์ผ๋ก ์ํํธ๋งฅ์ค๋ฅผ ์ ์ฉํฉ๋๋ค
- ๋ ๊ตฌํ ๊ฐ์ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋น๊ตํฉ๋๋ค
์ฑ๋ฆฐ์ง II: ๊ณ ๊ธ ์ดํ ์ ๋ฉ์ปค๋์ฆ
์ด ์ฑ๋ฆฐ์ง๋ Puzzle 19: ์ดํ ์ Op์ ํ์ฅ์ ๋๋ค
๋ฒกํฐ ์ดํ ์ ๊ตฌํ์ ๊ธฐ๋ฐ์ผ๋ก, ์ดํ ์ ๋ฉ์ปค๋์ฆ์ ํ๊ณ๋ฅผ ๋ํ๋ณด๋ ๊ณ ๊ธ ์ฑ๋ฆฐ์ง๋ค์ ๋๋ค:
1. ๋ ๊ธด ์ํ์ค ๊ธธ์ด
๊ธฐ์กด ์ปค๋์ ์ฌ์ฉํ์ฌ ๋ ๊ธด ์ํ์ค๋ฅผ ์ฒ๋ฆฌํ๋๋ก ์ดํ ์ ๋ฉ์ปค๋์ฆ์ ํ์ฅํฉ๋๋ค:
1.1 ์ํ์ค ๊ธธ์ด ํ์ฅ
SEQ_LEN = 32์SEQ_LEN = 64๋ฅผ ์ฒ๋ฆฌํ๋๋ก ์ดํ ์ ๊ตฌํ์ ์์ ํฉ๋๋คTPB(๋ธ๋ก๋น ์ค๋ ๋ ์) ํ๋ผ๋ฏธํฐ๋ฅผ ๊ทธ์ ๋ง๊ฒ ์ ๋ฐ์ดํธํฉ๋๋ค- ์ ์น ์ปค๋์ด ๋ ํฐ ํ๋ ฌ ํฌ๊ธฐ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ๋์ง ํ์ธํฉ๋๋ค
1.2 ๋์ ์ํ์ค ๊ธธ์ด
- ๋ฐํ์์ ๊ฐ๋ณ ์ํ์ค ๊ธธ์ด๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ์ดํ ์ ์ ๊ตฌํํฉ๋๋ค
SEQ_LEN๋ณด๋ค ์งง์ ์ํ์ค๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ปค๋์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์ถ๊ฐํฉ๋๋ค- ๊ณ ์ ์ํ์ค ๊ธธ์ด ์ฒ๋ฆฌ์ ๋์ ์ํ์ค ๊ธธ์ด ์ฒ๋ฆฌ์ ์ฑ๋ฅ์ ๋น๊ตํฉ๋๋ค
2. ๋ฐฐ์น ๋ฒกํฐ ์ดํ ์
์ฌ๋ฌ ์ดํ ์ ์ฐ์ฐ์ ๋์์ ์ฒ๋ฆฌํ๋๋ก ํ์ฅํฉ๋๋ค:
2.1 ๋ฐฐ์น ์ฒ๋ฆฌ
- ์ฌ๋ฌ ์ฟผ๋ฆฌ ๋ฒกํฐ๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋๋ก ์ดํ ์ ์ฐ์ฐ์ ์์ ํฉ๋๋ค
- ์ ๋ ฅ ํํ: Q(batch_size, d), K(seq_len, d), V(seq_len, d)
- ์ถ๋ ฅ ํํ: (batch_size, d)
- ์ ์ ํ ์ธ๋ฑ์ฑ์ผ๋ก ๊ธฐ์กด ์ปค๋์ ์ฌ์ฌ์ฉํฉ๋๋ค
2.2 ๋ฐฐ์น๋ฅผ ์ํ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
- ๋ฐฐ์น ์์ ๊ฐ ๋ฒํผ๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ์ต์ํํฉ๋๋ค
- ๋ค์ํ ๋ฐฐ์น ํฌ๊ธฐ(2, 4, 8)์์ ์ฑ๋ฅ์ ๋น๊ตํฉ๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ํจํด์ ๋ถ์ํฉ๋๋ค
Puzzle 20: 1D ํฉ์ฑ๊ณฑ Op
MAX ๊ทธ๋ํ์์ PyTorch ์ปค์คํ Op์ผ๋ก
GPU ํผ์ฆ ์ฌ์ ์ Part V์ ์ง์ ํ์ต๋๋ค: PyTorch ์ปค์คํ Op ํตํฉํ๊ธฐ.
Puzzle 17: 1D ํฉ์ฑ๊ณฑ Op์์ MAX ๊ทธ๋ํ๋ฅผ ์ฌ์ฉํ์ฌ Mojo GPU ์ปค๋์ ํ์ด์ฌ๊ณผ ์ฐ๋ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ด์ ๋ถํฐ๋ ๋ค์์ ์์๋ด ๋๋ค:
- ๋์ผํ Mojo ์ปค๋์ PyTorch์ CustomOpLibrary๋ก ์ฌ์ฉํ๊ธฐ
- PyTorch์ ํ ์ ์์คํ ๋ฐ ์คํ ๊ทธ๋๋(autograd)์ ํตํฉํ๊ธฐ
- MAX ๊ทธ๋ํ์ PyTorch ๋ฐฉ์์ ์ปค์คํ ์ฐ์ฐ ๋น๊ตํ๊ธฐ
- ๋ช ์์ ์ถ๋ ฅ ํ ์ ํ ๋น์ด๋ผ๋ ํต์ฌ ํจํด ์ดํดํ๊ธฐ
์ด ์ ํ์ ํตํด ๋์ผํ ์ต์ ํ๋ GPU ์ปค๋์ด ์๋ก ๋ค๋ฅธ ํ์ด์ฌ ํตํฉ ๋ฐฉ์์์ ์ด๋ป๊ฒ ๋์ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
๊ฐ์
์ด ํผ์ฆ์์๋ Puzzle 17: 1D ํฉ์ฑ๊ณฑ Op์ 1D ํฉ์ฑ๊ณฑ(convolution) ์ปค๋์ ๊ทธ๋๋ก ๊ฐ์ ธ์์, MAX ๊ทธ๋ํ ๋์ CustomOpLibrary๋ฅผ ์ฌ์ฉํ์ฌ PyTorch์ ํตํฉํฉ๋๋ค.
์ฌ๊ธฐ์ ํต์ฌ์ ๋์ผํ Mojo ์ปค๋์ด ์์ ์์ด ๊ทธ๋๋ก ๋์ํ๋ค๋ ๊ฒ์ ๋๋ค. MAX ๊ทธ๋ํ์ PyTorch ๋ฐฉ์ ์ฌ์ด์์ ๋ฌ๋ผ์ง๋ ๊ฒ์ ํ์ด์ฌ ํตํฉ ๋ ์ด์ด๋ฟ์ ๋๋ค.
์์ฑํ ์ฝ๋
์ด ํผ์ฆ์ ์์ฑํ๋ ค๋ฉด ์ปค์คํ ์ฐ์ฐ์ ํธ์ถํ๋ ํ ์ค๋ง ์ฑ์ฐ๋ฉด ๋ฉ๋๋ค:
import torch
from max.experimental.torch import CustomOpLibrary
def conv1d_pytorch(
input_tensor: torch.Tensor, kernel_tensor: torch.Tensor
) -> torch.Tensor:
"""
1D convolution using our custom PyTorch operation.
This demonstrates the transition from MAX Graph (p15) to PyTorch CustomOpLibrary.
Uses the EXACT same Mojo kernel, but different Python integration!
"""
# Load our custom operations
mojo_kernels = Path(__file__).parent / "op"
ops = CustomOpLibrary(mojo_kernels)
# Create output tensor with same shape as input
output_tensor = torch.empty_like(input_tensor)
# Call our custom conv1d operation with explicit output tensor
# The Mojo signature expects: (out, input, kernel)
_conv1d = ops.conv1d[
{
"input_size": input_tensor.shape[0],
"conv_size": kernel_tensor.shape[0],
}
]
# FILL IN with 1 line of code
return output_tensor
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p20/p20.py
๋ค์ ๋ช ๋ น์ผ๋ก ํผ์ฆ์ ์คํํ ์ ์์ต๋๋ค:
pixi run p20
pixi run -e amd p20
uv run poe p20
์ฑ๊ณตํ๋ฉด ๋ค์๊ณผ ๋น์ทํ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค:
Puzzle 20: From MAX Graph to PyTorch Custom Ops
============================================================
Input array: [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.]
Convolution kernel: [0. 1. 2. 3.]
NumPy reference result: [14. 20. 26. 32. 38. 44. 50. 56. 62. 68. 74. 80. 41. 14. 0.]
Testing PyTorch Custom Op (device: cuda)
----------------------------------------
PyTorch custom op result: [14. 20. 26. 32. 38. 44. 50. 56. 62. 68. 74. 80. 41. 14. 0.]
โ
PyTorch custom op verification PASSED
Comparing with MAX Graph approach (like p15)
--------------------------------------------
MAX Graph result: [14. 20. 26. 32. 38. 44. 50. 56. 62. 68. 74. 80. 41. 14. 0.]
โ
MAX Graph verification PASSED
โ
PyTorch and MAX Graph results MATCH
์๋ฃจ์
์ปดํ์ผ๋ ์ปค์คํ ์ฐ์ฐ์ ์ ์ ํ ์ธ์์ ํจ๊ป ํธ์ถํ๋ฉด ๋ฉ๋๋ค:
# Call our custom conv1d operation with explicit output tensor
# The Mojo signature expects: (out, input, kernel)
conv1d = ops.conv1d[
{
"input_size": input_tensor.shape[0],
"conv_size": kernel_tensor.shape[0],
}
]
torch.compile(conv1d)(output_tensor, input_tensor, kernel_tensor)
์ด ํ์ด๋ ๋ช ๊ฐ์ง ํต์ฌ ๊ฐ๋ ์ ๋ณด์ฌ์ค๋๋ค:
1. torch.compile() ํตํฉ
torch.compile ํตํฉ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
torch.compile(conv1d)(output_tensor, input_tensor, kernel_tensor)
2. ๋ช ์์ ์ถ๋ ฅ ํ ์ ํ ๋น
output_tensor = torch.empty_like(input_tensor)
- MAX ๊ทธ๋ํ๋ ์ถ๋ ฅ ํ ๋น์ ์๋์ผ๋ก ์ฒ๋ฆฌํ์ง๋ง
- PyTorch CustomOpLibrary๋ ๋ฏธ๋ฆฌ ํ ๋น๋ ์ถ๋ ฅ ํ ์๊ฐ ํ์ํฉ๋๋ค
- Mojo ์ฐ์ฐ ์๊ทธ๋์ฒ๋
(out, input, kernel)์์๋ฅผ ๊ธฐ๋ํฉ๋๋ค
3. ํ๋ผ๋ฏธํฐ ๋์ ๋๋ฆฌ
ops.conv1d[{"input_size": input_tensor.shape[0], "conv_size": kernel_tensor.shape[0]}]
- ํ๋ผ๋ฏธํฐ๋ ๋์ ๋๋ฆฌ ํํ๋ก ์ฐ์ฐ์ ์ ๋ฌ๋ฉ๋๋ค
- ์ด ๊ฐ๋ค์ Mojo ์ปค๋์ ์ปดํ์ผ ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋ฉ๋๋ค
- Mojo
@staticmethod fn execute์๊ทธ๋์ฒ์ ํ๋ผ๋ฏธํฐ ์ด๋ฆ๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค
4. ๊ฐ์ ์ปค๋, ๋ค๋ฅธ ํตํฉ ๋ฐฉ์
๋ด๋ถ์ Mojo ์ปค๋(conv1d_kernel)์ Puzzle 17๊ณผ ๋์ผํฉ๋๋ค:
- ๋์ผํ GPU ์ปค๋ ์ฝ๋
- ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋์ผํ ์ฐ์ฐ ๋ก์ง
- ํ์ด์ฌ ๋ํผ ๋ ์ด์ด๋ง ๋ฌ๋ผ์ง
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์ PyTorch ์ปค์คํ ์ฐ์ฐ์ ์ฃผ์ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
| ๊ฐ๋ | MAX ๊ทธ๋ํ (p15) | PyTorch CustomOpLibrary (p18) |
|---|---|---|
| ์ถ๋ ฅ ํ ๋น | ์๋ | ์๋ (torch.empty_like()) |
| ์ฐ์ฐ ํธ์ถ | ops.custom(...) | torch.compile(op)(...) |
| ํ๋ผ๋ฏธํฐ ์ ๋ฌ | parameters={...} | op[{...}] |
| ๋๋ฐ์ด์ค ๊ด๋ฆฌ | ๋ช ์์ device context | PyTorch ํ ์์ device |
| ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ | MAX ๊ทธ๋ํ ํ ์ | PyTorch ํ ์ |
ํต์ฌ ํจํด: ๋ช ์์ ์ถ๋ ฅ ํ ์ ํ ๋น
๊ฐ์ฅ ์ค์ํ ์ฐจ์ด์ ์ PyTorch CustomOpLibrary๊ฐ ๋ช ์์ ์ถ๋ ฅ ํ ์ ํ ๋น์ ์๊ตฌํ๋ค๋ ๊ฒ์ ๋๋ค:
# โ ๋์ํ์ง ์์ - ์ถ๋ ฅ ํ
์ ์์
result = torch.compile(conv1d)(input_tensor, kernel_tensor)
# โ
๋์ํจ - ๋ฏธ๋ฆฌ ํ ๋น๋ ์ถ๋ ฅ ํ
์
output_tensor = torch.empty_like(input_tensor)
torch.compile(conv1d)(output_tensor, input_tensor, kernel_tensor)
์ด ํจํด์ด ๋ณด์ฅํ๋ ๊ฒ๋ค:
- ์ฌ๋ฐ๋ฅธ ๋๋ฐ์ด์ค์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น
- ์ถ๋ ฅ ํ ์์ shape๊ณผ dtype์ด ์ ํ
- Mojo ์ปค๋์ด ์ถ๋ ฅ ๋ฒํผ์ ์ง์ ์ฐ๊ธฐ ๊ฐ๋ฅ
torch.compile() ํตํฉ
torch.compile()์ด ํ์์ ์ธ ์ด์ :
- PyTorch์ Mojo ์ฌ์ด์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ๋ณํ ์ฒ๋ฆฌ
- ๋๋ฐ์ด์ค ๋๊ธฐํ ๊ด๋ฆฌ (CPU โ GPU)
- ํ ์ ํฌ๋งท ๋ณํ ์ต์ ํ
- ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ๋ํ ์ ์ ํ ์ค๋ฅ ์ฒ๋ฆฌ ์ ๊ณต
์ฐธ๊ณ : torch.compile() ์์ด ์ฌ์ฉํ๋ฉด std::bad_alloc ์ค๋ฅ๊ฐ ๋ฐ์ํ ์
์์ต๋๋ค. ์ด๋ ์์ ์ฐ์ฐ์ด PyTorch์ ํ
์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๊ธฐ
๋๋ฌธ์
๋๋ค.
์ปค์คํ ์ฐ์ฐ ๋๋ฒ๊น
์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์ค๋ฅ: ํญ์
torch.compile()์ ์ฌ์ฉํ์ธ์ - ์๋ชป๋ ์ถ๋ ฅ ํ์: ์ถ๋ ฅ ํ ์๊ฐ ๊ธฐ๋ํ๋ ์ฐจ์๊ณผ ์ผ์นํ๋์ง ํ์ธํ์ธ์
- ๋๋ฐ์ด์ค ๋ถ์ผ์น: ๋ชจ๋ ํ ์๊ฐ ๊ฐ์ ๋๋ฐ์ด์ค์ ์์ด์ผ ํฉ๋๋ค
- ํ๋ผ๋ฏธํฐ ์ค๋ฅ: ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ด Mojo ์ฐ์ฐ ์๊ทธ๋์ฒ์ ์ผ์นํ๋์ง ํ์ธํ์ธ์
๋๋ฒ๊น ์ ๊ทผ๋ฒ: PyTorch ๊ฒฐ๊ณผ๋ฅผ ๋์ผํ ์ปค๋์ ์คํํ๋ MAX ๊ทธ๋ํ ๋ ํผ๋ฐ์ค ๊ตฌํ๊ณผ ๋น๊ตํด ๋ณด์ธ์.
Puzzle 21: ์๋ฒ ๋ฉ Op
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ์ฑ๋ฅ
๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ๊ณผ GPU ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ ํ์ ์ด์ ์ ๋ง์ถฐ Part V๋ฅผ ์ด์ด๊ฐ๋๋ค.
Puzzle 20: 1D ํฉ์ฑ๊ณฑ Op์ ์ด์ด, ๋์ผํ ์ฐ์ฐ์ ์๋ก ๋ค๋ฅธ ์ปค๋ ๊ตฌํ์ด ์ฑ๋ฅ์ ์ผ๋ง๋ ๊ทน์ ์ธ ์ฐจ์ด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋์ง ์์๋ด ๋๋ค. ๋ฐฐ์ธ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- GPU ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ์ด ์ค์ํ ์ด์
- ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ผ๋ก ์ปค๋์ ์ค๊ณํ๋ ๋ฐฉ๋ฒ
- ์๋ก ๋ค๋ฅธ ์ค๋ ๋ฉ ์ ๋ต์ด ๊ฐ์ ธ์ค๋ ์ฑ๋ฅ ์ฐจ์ด
์ด ํผ์ฆ์ ์ด๋ค ์ฐ์ฐ์ ์ํํ๋๋๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ์ ์ด๋ป๊ฒ ์ ๊ทผํ๋๋๊ฐ ๋ ์ค์ํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค.
๊ฐ์
์ด ํผ์ฆ์์๋ ์ ๊ฒฝ๋ง์ ํต์ฌ ๊ตฌ์ฑ ์์์ธ ์๋ฒ ๋ฉ(embedding) ์ฐ์ฐ์ ์ํ ๋ ๊ฐ์ง GPU ์ปค๋์ ๊ตฌํํฉ๋๋ค. ๋ ์ปค๋ ๋ชจ๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ์ง๋ง, ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ฌ์ฉํ์ฌ ์๋นํ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ณด์ ๋๋ค.
๋น๊ตํ ๋ ์ปค๋:
- 1D ๋ณํฉ(coalesced) ์ปค๋: ์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ์ต์ ํ
- 2D ๋น๋ณํฉ(non-coalesced) ์ปค๋: ๋น๊ต๋ฅผ ์ํ ์ต์ ํ๋์ง ์์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
์ด ๋น๊ต๋ฅผ ํตํด GPU ์ปค๋ ์ฑ๋ฅ์์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์ผ๋ง๋ ์ค์ํ์ง ์ฒด๊ฐํ ์ ์์ต๋๋ค.
๋ฐฐ๊ฒฝ: ์๋ฒ ๋ฉ ์ฐ์ฐ
์๋ฒ ๋ฉ ์ฐ์ฐ์ ์ด์ฐ์ ์ธ ํ ํฐ ์ธ๋ฑ์ค๋ฅผ ๋ฐ์ง ๋ฒกํฐ ํํ์ผ๋ก ๋ณํํฉ๋๋ค:
# Input: token indices
indices = [[1, 5, 2], [7, 1, 9]] # Shape: [batch_size, seq_len]
# Embedding table (learned parameters)
embedding_table = [ # Shape: [vocab_size, embed_dim]
[0.1, 0.2, 0.3, 0.4], # Token 0
[0.5, 0.6, 0.7, 0.8], # Token 1
[0.9, 1.0, 1.1, 1.2], # Token 2
# ... more tokens
]
# Output: embedded vectors
output[0,0] = embedding_table[1] # [0.5, 0.6, 0.7, 0.8]
output[0,1] = embedding_table[5] # lookup token 5's embedding
output[0,2] = embedding_table[2] # [0.9, 1.0, 1.1, 1.2]
# ... and so on
์ด ์ฐ์ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ ๋๋ค. ์ฑ๋ฅ์ ์๋ฒ ๋ฉ ํ ์ด๋ธ์์ ์ผ๋ง๋ ํจ์จ์ ์ผ๋ก ์ฝ๊ณ ์ถ๋ ฅ ํ ์์ ์ธ ์ ์๋๋์ ๋ฌ๋ ค ์์ต๋๋ค.
ํ์ต ๊ฒฝ๋ก
์ด ํผ์ฆ์ ์ฒด๊ณ์ ์ธ ์ดํด๋ฅผ ์ํด ๋ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค:
๋ณํฉ vs ๋น๋ณํฉ ์ปค๋
์ฌ๊ธฐ์๋ถํฐ ์์ํ์ฌ ์ค์ ํผ์ฆ ์ฝ๋๋ฅผ ๊ตฌํํ๊ณ ์ปค๋ ๊ตฌํ์ ์ดํดํฉ๋๋ค.
๋ฌด์์ ํ๊ฒ ๋ ๊น์:
- ๋ ๊ฐ์ง GPU ์๋ฒ ๋ฉ ์ปค๋ ์์ฑ (1D ๋ณํฉ vs 2D ๋น๋ณํฉ)
- GPU ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ๋ณธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ์ต
- ๋์ผํ ์๊ณ ๋ฆฌ์ฆ์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ฉ ์ ๋ต์ผ๋ก ๊ตฌํํ๋ ์ฌ๋ก ํ์ธ
- Mojo์์์ ์ปค์คํ ์ฐ์ฐ ๋ฑ๋ก ์ดํด
์ฑ๋ฅ ๋น๊ต
์ปค๋ ์ฑ๋ฅ์ด ์ ๋ค๋ฅธ์ง, ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ์ด๋ก ์ ๊น์ด ํ๊ณ ๋ญ๋๋ค.
๋ฌด์์ ๋ฐฐ์ธ๊น์:
- GPU ์ฑ๋ฅ์์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์ค์ํ ์ด์
- ์ค๋ ๋ ๊ตฌ์ฑ์ด ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ์ ๋ฏธ์น๋ ์ํฅ
- ์ ๊ฒฝ๋ง ์ต์ ํ์ ๋ํ ์ค์ ์์ฌ์
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์ ์ํ ์ต์ ํ ์ ๋ต
์์ํ๊ธฐ
GPU ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ฅผ ํ๊ตฌํ ์ค๋น๊ฐ ๋์ จ๋์? ๋ณํฉ vs ๋น๋ณํฉ ์ปค๋ ์์ ์ฝ๋๋ฅผ ๊ตฌํํ ํ, ์ฑ๋ฅ ๋น๊ต ๋ก ๋์ด๊ฐ ์ฑ๋ฅ ์ฐจ์ด์ ์์ธ์ ์ดํดํด ๋ณด์ธ์.
๐ก ์ฑ๊ณต ํ: ์๋ก ๋ค๋ฅธ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ(1D vs 2D)์ด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง ์ฃผ์ ๊น๊ฒ ์ดํด๋ณด์ธ์. ์ด ํต์ฐฐ์ ์๋ฒ ๋ฉ์ ๋์ด ๋ค์ํ GPU ํ๋ก๊ทธ๋๋ฐ ์๋๋ฆฌ์ค์ ์ ์ฉ๋ฉ๋๋ค.
์๋ฒ ๋ฉ ์ปค๋: ๋ณํฉ vs ๋น๋ณํฉ
์ด ํผ์ฆ์์๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ์ง๋ง ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ฌ์ฉํ๋ ๋ ๊ฐ์ง GPU ์๋ฒ ๋ฉ ์ปค๋์ ๊ตฌํํฉ๋๋ค. GPU ์ฑ๋ฅ์์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์ผ๋ง๋ ์ค์ํ์ง ์ง์ ์ฒดํํ ์ ์์ต๋๋ค.
1D ๋ณํฉ ์ปค๋ (์ต์ ํ๋ ์ ๊ทผ๋ฒ)
์ด ์ปค๋์ ๊ฐ ์ค๋ ๋๊ฐ ์ ํํ ํ๋์ ์ถ๋ ฅ ์์๋ฅผ ์ฒ๋ฆฌํ๋ ๋จ์ํ 1D ๊ทธ๋ฆฌ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ํต์ฌ์ ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผํ์ฌ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ๋ฌ์ฑํ๋ค๋ ์ ์ ๋๋ค.
์ค๋ ๋ ๊ตฌ์ฑ:
- ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
[total_elements // 256]๋ธ๋ก, ๋ธ๋ก๋น256์ค๋ ๋ - ์ค๋ ๋ ๋งคํ: ๊ฐ ์ค๋ ๋๊ฐ ํ๋์
(batch, seq, embed)์์น ์ฒ๋ฆฌ - ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ์๋ฒ ๋ฉ ์ฐจ์ ์ ๊ทผ
๊ตฌํํ ๋ด์ฉ:
- ๋ธ๋ก ์ธ๋ฑ์ค์ ์ค๋ ๋ ์ธ๋ฑ์ค๋ก๋ถํฐ ์ ์ญ ์ค๋ ๋ ์ธ๋ฑ์ค ๊ณ์ฐ
- 1์ฐจ์ ์ธ๋ฑ์ค๋ฅผ 3D ์ขํ
(batch_idx, seq_idx, embed_idx)๋ก ๋ณํ - indices ํ ์์์ ํ ํฐ ์ธ๋ฑ์ค ์กฐํ
- ํด๋นํ๋ ์๋ฒ ๋ฉ ๋ฒกํฐ ์์๋ฅผ ์ถ๋ ฅ์ ๋ณต์ฌ
์์ฑํ ์ฝ๋
๋ ์๋ฒ ๋ฉ ์ปค๋์ ๋น ๋ถ๋ถ์ ์์ฑํด์ผ ํฉ๋๋ค:
comptime THREADS_PER_BLOCK = 256
def embedding_kernel_coalesced[
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
OutLayout: TensorLayout,
IndicesLayout: TensorLayout,
WeightsLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
indices: TileTensor[mut=False, DType.int32, IndicesLayout, MutAnyOrigin],
weights: TileTensor[mut=False, dtype, WeightsLayout, MutAnyOrigin],
):
"""
Memory-coalescing focused embedding kernel.
Key insight: The bottleneck is memory access patterns, not computation.
- Each thread handles one (batch, seq, embed) position
- Simple 1D grid for maximum simplicity and correctness
- Focus on getting memory access right first
"""
# Simple 1D indexing - each thread = one output element
var global_idx = block_idx.x * block_dim.x + thread_idx.x
var total_elements = batch_size * seq_len * embed_dim
if global_idx >= total_elements:
return
# Convert to (batch, seq, embed) coordinates
# FILL IN roughly 4 lines
# Get token index
# FILL IN 1 line
# Simple, correct assignment
# FILL IN 4 lines
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p21/op/embedding.mojo
ํ
global_idx = block_idx.x * block_dim.x + thread_idx.x๋ก ์์ํ์ธ์- ๋๋์
๊ณผ ๋๋จธ์ง ์ฐ์ฐ์ผ๋ก 3D ์ขํ๋ฅผ ๊ตฌํฉ๋๋ค:
batch_idx = global_idx // (seq_len * embed_dim) remaining = global_idx % (seq_len * embed_dim)์ ์ฌ์ฉํ๋ฉด ์ดํ ๊ณ์ฐ์ด ๊ฐ๋จํด์ง๋๋ค- ํญ์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํ์ธ์:
if global_idx >= total_elements: return - ์ ํจํ์ง ์์ ํ ํฐ ์ธ๋ฑ์ค๋ ์ถ๋ ฅ์ 0์ผ๋ก ์ค์ ํ์ธ์
- ์๋ฒ ๋ฉ ์กฐํ:
output[batch_idx, seq_idx, embed_idx] = weights[token_idx, embed_idx]
2D ๋น๋ณํฉ ์ปค๋ (๋น๊ต์ฉ ์ ๊ทผ๋ฒ)
์ด ์ปค๋์ X ์ฐจ์์ด (batch ร seq) ์์น๋ฅผ, Y ์ฐจ์์ด ์๋ฒ ๋ฉ ์ฐจ์์ ๋ด๋นํ๋ 2D
๊ทธ๋ฆฌ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด ๋ฐฉ์์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ๋ณํฉ๋์ง ์์ ์ ์์ต๋๋ค.
์ค๋ ๋ ๊ตฌ์ฑ:
- ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
[batch x seq // 16, embed_dim // 16]๋ธ๋ก,16 x 16์ค๋ ๋ - ์ค๋ ๋ ๋งคํ:
thread_idx.x๋ batch/sequence์,thread_idx.y๋ ์๋ฒ ๋ฉ ์ฐจ์์ ๋งคํ - ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ํ ๋ด ์ค๋ ๋๋ค์ด ํฉ์ด์ง ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผํ ์ ์์
๊ตฌํํ ๋ด์ฉ:
- 2D ๊ทธ๋ฆฌ๋์์ X, Y ์ขํ ๊ณ์ฐ
- X ์ขํ๋ฅผ batch ์ธ๋ฑ์ค์ sequence ์ธ๋ฑ์ค๋ก ๋ถ๋ฆฌ
- Y ์ขํ๋ฅผ ์๋ฒ ๋ฉ ์ฐจ์์ผ๋ก ์ง์ ์ฌ์ฉ
- ๊ฒฝ๊ณ ๊ฒ์ฌ์ ํจ๊ป ๋์ผํ ์๋ฒ ๋ฉ ์กฐํ ์ํ
์์ฑํ ์ฝ๋
๋ ์๋ฒ ๋ฉ ์ปค๋์ ๋น ๋ถ๋ถ์ ์์ฑํด์ผ ํฉ๋๋ค:
def embedding_kernel_2d[
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
OutLayout: TensorLayout,
IndicesLayout: TensorLayout,
WeightsLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
indices: TileTensor[mut=False, DType.int32, IndicesLayout, MutAnyOrigin],
weights: TileTensor[mut=False, dtype, WeightsLayout, MutAnyOrigin],
):
"""
2D grid non-coalesced embedding kernel.
Non-optimal approach for comparison:
- 2D grid: (batch*seq, embed_dim)
- More complex indexing
- Potentially worse memory access patterns
"""
# 2D grid indexing
var batch_seq_idx = block_idx.x * block_dim.x + thread_idx.x
var embed_idx = block_idx.y * block_dim.y + thread_idx.y
var total_positions = batch_size * seq_len
if batch_seq_idx >= total_positions or embed_idx >= embed_dim:
return
# Convert to (batch, seq) coordinates
# FILL IN 2 lines
# Get token index
# FILL IN 1 line
# Assignment with 2D grid pattern
# FILL IN 4 lines
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p21/op/embedding.mojo
ํ
- X, Y ์ค๋ ๋ ์ขํ๋ฅผ ๋ชจ๋ ์ฌ์ฉํฉ๋๋ค:
batch_seq_idx = block_idx.x * block_dim.x + thread_idx.x - ๊ทธ๋ฆฌ๊ณ :
embed_idx = block_idx.y * block_dim.y + thread_idx.y batch_seq_idx๋ฅผ batch์ sequence ์ธ๋ฑ์ค๋ก ๋ถ๋ฆฌํฉ๋๋ค:batch_idx = batch_seq_idx // seq_len- ๋ ์ฐจ์ ๋ชจ๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์์ง ๋ง์ธ์:
if batch_seq_idx >= total_positions or embed_idx >= embed_dim - ํ ํฐ ์กฐํ๋ 1D์ ๋์ผํ์ง๋ง, ์ค๋ ๋๋น ํ๋์ ์๋ฒ ๋ฉ ์ฐจ์๋ง ์ฒ๋ฆฌํฉ๋๋ค
- ์ด ์ปค๋์ ์ ์ฒด ๋ฒกํฐ๊ฐ ์๋ ์ค๋ ๋๋น ํ๋์ ์๋ฒ ๋ฉ ์ฐจ์์ ์ฒ๋ฆฌํฉ๋๋ค
์ปค์คํ op ๋ฑ๋ก
์ปค๋๋ค์ PyTorch์ ์ฝ๊ฒ ํตํฉํ ์ ์๋๋ก ์ปค์คํ ์ฐ์ฐ์ผ๋ก ๋ํ๋ฉ๋๋ค. ๋ฑ๋ก ํจํด์ MAX ๊ทธ๋ํ ์ปค์คํ op ์ดํดํ๊ธฐ์์ ์ค๋ช ํ MAX ์ปค์คํ op๊ณผ ๋์ผํฉ๋๋ค:
1D ๋ณํฉ ์ฐ์ฐ
์ด ์ฐ์ฐ์ ์ต์ ํ๋ 1D ์๋ฒ ๋ฉ ์ปค๋์ "embedding"์ผ๋ก ๋ฑ๋กํฉ๋๋ค:
import compiler
from std.runtime.asyncrt import DeviceContextPtr
from tensor import InputTensor, OutputTensor
from std.memory import UnsafePointer
from std.gpu.host import DeviceBuffer
@compiler.register("embedding")
struct EmbeddingCustomOp:
@staticmethod
def execute[
target: StaticString,
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
](
output: OutputTensor[
dtype=DType.float32, rank=3, static_spec=_
], # [batch_size, seq_len, embed_dim]
indices: InputTensor[
dtype=DType.int32, rank=2, static_spec=_
], # [batch_size, seq_len]
weights: InputTensor[
dtype=output.dtype, rank=2, static_spec=_
], # [vocab_size, embed_dim]
ctx: DeviceContextPtr,
) raises:
comptime out_layout_val = row_major[batch_size, seq_len, embed_dim]()
comptime OutLayout = type_of(out_layout_val)
comptime indices_layout_val = row_major[batch_size, seq_len]()
comptime IndicesLayout = type_of(indices_layout_val)
comptime weights_layout_val = row_major[vocab_size, embed_dim]()
comptime WeightsLayout = type_of(weights_layout_val)
var output_tensor = TileTensor[
mut=True, output.dtype, OutLayout, MutAnyOrigin
](output.unsafe_ptr(), out_layout_val)
var indices_tensor = TileTensor[
mut=True, DType.int32, IndicesLayout, MutAnyOrigin
](indices.unsafe_ptr(), indices_layout_val)
var weights_tensor = TileTensor[
mut=True, output.dtype, WeightsLayout, MutAnyOrigin
](weights.unsafe_ptr(), weights_layout_val)
comptime if target == "gpu":
var gpu_ctx = ctx.get_device_context()
# Zero out output tensor
gpu_ctx.enqueue_memset(
DeviceBuffer[output.dtype](
gpu_ctx,
output.unsafe_ptr(),
batch_size * seq_len * embed_dim,
owning=False,
),
0,
)
# Calculate 1D grid dimensions (matching kernel's flat indexing)
var total_elements = batch_size * seq_len * embed_dim
var blocks = max(1, ceildiv(total_elements, THREADS_PER_BLOCK))
# Compile and launch optimized kernel
comptime kernel = embedding_kernel_coalesced[
batch_size,
seq_len,
vocab_size,
embed_dim,
OutLayout,
IndicesLayout,
WeightsLayout,
output.dtype,
]
var compiled_kernel = gpu_ctx.compile_function[kernel, kernel]()
gpu_ctx.enqueue_function(
compiled_kernel,
output_tensor,
indices_tensor,
weights_tensor,
grid_dim=(blocks,),
block_dim=(THREADS_PER_BLOCK,),
)
elif target == "cpu":
for batch in range(batch_size):
for seq in range(seq_len):
var token_idx_val = Int(indices_tensor[batch, seq])
if token_idx_val >= 0 and token_idx_val < vocab_size:
for emb in range(embed_dim):
output_tensor[batch, seq, emb] = weights_tensor[
token_idx_val, emb
]
else:
raise Error("Unsupported target: " + target)
๋ฑ๋ก์ ํต์ฌ ์์:
- ๋จ์ํ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
ceildiv(total_elements, THREADS_PER_BLOCK)๋ธ๋ก์ผ๋ก ์ง๊ด์ ์ธ 1D ๊ทธ๋ฆฌ๋ ์ฌ์ฉ - ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ: ๋จ์ผ
enqueue_memsetํธ์ถ๋ก ์ถ๋ ฅ ๋ฒํผ๋ฅผ ํจ์จ์ ์ผ๋ก ์ด๊ธฐํ - ์ปดํ์ผ ํ์ ํ๋ผ๋ฏธํฐ: ๋ชจ๋ ํ ์ ์ฐจ์์ ์ปดํ์ผ ํ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ์ฌ ์ต์ ์ฑ๋ฅ ๋ฌ์ฑ
- ๋๋ฐ์ด์ค ์ถ์ํ: GPU ์คํ๊ณผ CPU ํด๋ฐฑ์ ๋งค๋๋ฝ๊ฒ ์ฒ๋ฆฌ
2D ๋น๋ณํฉ ์ฐ์ฐ
์ด ์ฐ์ฐ์ ๋น๊ต์ฉ 2D ์๋ฒ ๋ฉ ์ปค๋์ "embedding_2d"๋ก ๋ฑ๋กํฉ๋๋ค:
@compiler.register("embedding_2d")
struct Embedding2DCustomOp:
@staticmethod
def execute[
target: StaticString,
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
](
output: OutputTensor[
dtype=DType.float32, rank=3, static_spec=_
], # [batch_size, seq_len, embed_dim]
indices: InputTensor[
dtype=DType.int32, rank=2, static_spec=_
], # [batch_size, seq_len]
weights: InputTensor[
dtype=output.dtype, rank=2, static_spec=_
], # [vocab_size, embed_dim]
ctx: DeviceContextPtr,
) raises:
comptime out_layout_val = row_major[batch_size, seq_len, embed_dim]()
comptime OutLayout = type_of(out_layout_val)
comptime indices_layout_val = row_major[batch_size, seq_len]()
comptime IndicesLayout = type_of(indices_layout_val)
comptime weights_layout_val = row_major[vocab_size, embed_dim]()
comptime WeightsLayout = type_of(weights_layout_val)
var output_tensor = TileTensor[
mut=True, output.dtype, OutLayout, MutAnyOrigin
](output.unsafe_ptr(), out_layout_val)
var indices_tensor = TileTensor[
mut=True, DType.int32, IndicesLayout, MutAnyOrigin
](indices.unsafe_ptr(), indices_layout_val)
var weights_tensor = TileTensor[
mut=True, output.dtype, WeightsLayout, MutAnyOrigin
](weights.unsafe_ptr(), weights_layout_val)
comptime if target == "gpu":
var gpu_ctx = ctx.get_device_context()
# Zero out output tensor
gpu_ctx.enqueue_memset(
DeviceBuffer[output.dtype](
gpu_ctx,
output.unsafe_ptr(),
batch_size * seq_len * embed_dim,
owning=False,
),
0,
)
# Calculate 2D grid dimensions for non-coalesced access
var total_positions = batch_size * seq_len
comptime BLOCK_X = 16 # batch*seq dimension
comptime BLOCK_Y = 16 # embed dimension
var blocks_x = max(1, ceildiv(total_positions, BLOCK_X))
var blocks_y = max(1, ceildiv(embed_dim, BLOCK_Y))
# Compile and launch 2D kernel
comptime kernel = embedding_kernel_2d[
batch_size,
seq_len,
vocab_size,
embed_dim,
OutLayout,
IndicesLayout,
WeightsLayout,
output.dtype,
]
var compiled_kernel = gpu_ctx.compile_function[kernel, kernel]()
gpu_ctx.enqueue_function(
compiled_kernel,
output_tensor,
indices_tensor,
weights_tensor,
grid_dim=(blocks_x, blocks_y),
block_dim=(BLOCK_X, BLOCK_Y),
)
elif target == "cpu":
# Same CPU fallback as 1D version
for batch in range(batch_size):
for seq in range(seq_len):
var token_idx_val = Int(indices_tensor[batch, seq])
if token_idx_val >= 0 and token_idx_val < vocab_size:
for emb in range(embed_dim):
output_tensor[batch, seq, emb] = weights_tensor[
token_idx_val, emb
]
else:
raise Error("Unsupported target: " + target)
1D ์ฐ์ฐ๊ณผ์ ์ฃผ์ ์ฐจ์ด์ :
- ๋ณต์กํ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
blocks_x์blocks_y๋ฅผ ๋ณ๋๋ก ๊ณ์ฐํ๋ 2D ๊ทธ๋ฆฌ๋ ์ฌ์ฉ - ๊ณ ์ ๋ธ๋ก ์ฐจ์: 2D ์ค๋ ๋ ๊ตฌ์ฑ์ ์ํด
BLOCK_X = 16,BLOCK_Y = 16์ผ๋ก ๊ณ ์ - ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ: ๋ฉ๋ชจ๋ฆฌ ์ด๊ธฐํ์ CPU ํด๋ฐฑ ๋ก์ง์ ๋์ผ
- ๋ค๋ฅธ ์ปค๋ ํธ์ถ ๋ฐฉ์: 2D ๊ทธ๋ฆฌ๋ ์ฐจ์
(blocks_x, blocks_y)๊ณผ ๋ธ๋ก ์ฐจ์(BLOCK_X, BLOCK_Y)์ ๋ฌ
๊ณตํต ๋ํผ ๊ธฐ๋ฅ
๋ ์ปค์คํ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ ํ์ ์ธํ๋ผ๋ฅผ ์ ๊ณตํฉ๋๋ค:
-
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ:
enqueue_memset์ผ๋ก ์ถ๋ ฅ ํ ์ 0 ์ด๊ธฐํ- ์ ์ ํ ๋ฒํผ ์์ฑ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ฒ๋ฆฌ
- ์๋ ์ ๋ฆฌ ๋ฐ ๋ฆฌ์์ค ๊ด๋ฆฌ
-
๋๋ฐ์ด์ค ์ถ์ํ:
- ์ต์ ํ๋ ์ปค๋๋ก GPU ์คํ
- ํธํ์ฑ๊ณผ ๋๋ฒ๊น ์ ์ํ CPU ํด๋ฐฑ
- ์คํ ๋์์ ๊ด๊ณ์์ด ์ผ๊ด๋ ์ธํฐํ์ด์ค
-
ํ๋ผ๋ฏธํฐ ์ ๋ฌ:
- ์ปค๋ ์ต์ ํ๋ฅผ ์ํ ์ปดํ์ผ ํ์ ํ ์ ์ฐจ์
- ๋ ์ด์์ ํ ์ ๋ณํ์ ํตํ ๋ฐํ์ ํ ์ ๋ฐ์ดํฐ
- ํ์ ์์ ํ ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ
-
๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
- ์ต์ ์ ๊ทธ๋ฆฌ๋ ์ฐจ์ ์๋ ๊ณ์ฐ
- ๊ฐ ์ปค๋์ ์ ๊ทผ ํจํด์ ์ต์ ํ๋ ์๋ก ๋ค๋ฅธ ์ ๋ต
- ์ ์ ํ ๋ธ๋ก ์ฐจ์ ๊ด๋ฆฌ
PyTorch ํตํฉ
๋ฑ๋ก๋ ์ฐ์ฐ์ CustomOpLibrary๋ฅผ ํตํด ํ์ด์ฌ์์ ํธ์ถํ ์ ์์ต๋๋ค:
# Load the custom operations
ops = CustomOpLibrary(mojo_kernels)
# Call the 1D coalesced version
result_1d = ops.embedding[{"batch_size": B, "seq_len": L, "vocab_size": V, "embed_dim": E}](
indices, weights
)
# Call the 2D non-coalesced version
result_2d = ops.embedding_2d[{"batch_size": B, "seq_len": L, "vocab_size": V, "embed_dim": E}](
indices, weights
)
์ด ์ ๊ทผ๋ฒ์ ์ฅ์ ์ ๋์ผํ ์ปค๋ ๊ตฌํ์ ๋ค์ํ ํ์ด์ฌ ํ๋ ์์ํฌ์์ ์ฌ์ฉํ๋ฉด์๋ ์ต์ ์ ์ฑ๋ฅ ํน์ฑ์ ์ ์งํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
์ฝ๋ ์คํ
๋ค์ ๋ช ๋ น์ผ๋ก ํผ์ฆ์ ์คํํ ์ ์์ต๋๋ค:
pixi run p21
pixi run -e amd p21
uv run poe p21
์ฑ๊ณตํ๋ฉด ๋ค์๊ณผ ๋น์ทํ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค:
Puzzle 21: Mojo Embedding Kernel Comparison
======================================================================
Configuration: B=8, L=512, V=10000, E=512
------------------------------------------------------------
Testing Correctness...
1D Coalesced - Max difference: 1.19e-07
2D Non-coalesced - Max difference: 1.19e-07
โ
Both implementations CORRECT
Benchmarking Mojo Kernels...
Performance Results:
1D Coalesced: 2.145 ms
2D Non-coalesced: 3.867 ms
1D is 1.80x faster than 2D
Key Learning Points:
โข Compare different GPU kernel implementations
โข 1D vs 2D grid patterns have different memory access
โข Coalesced memory access should be faster
โข Grid configuration affects GPU utilization
์๋ฃจ์
๋ ์ปค๋์ ์ขํ ๋ณํ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ๊ตฌํํ๋ฉด ๋ฉ๋๋ค:
1D ๋ณํฉ ์ปค๋
def embedding_kernel_coalesced[
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
OutLayout: TensorLayout,
IndicesLayout: TensorLayout,
WeightsLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
indices: TileTensor[mut=True, DType.int32, IndicesLayout, MutAnyOrigin],
weights: TileTensor[mut=True, dtype, WeightsLayout, MutAnyOrigin],
):
"""
Memory-coalescing focused embedding kernel.
Key insight: The bottleneck is memory access patterns, not computation.
- Each thread handles one (batch, seq, embed) position
- Simple 1D grid for maximum simplicity and correctness
- Focus on getting memory access right first
"""
# Simple 1D indexing - each thread = one output element
var global_idx = block_idx.x * block_dim.x + thread_idx.x
var total_elements = batch_size * seq_len * embed_dim
if global_idx >= total_elements:
return
var output_lt = output.to_layout_tensor()
var indices_lt = indices.to_layout_tensor()
var weights_lt = weights.to_layout_tensor()
# Convert to (batch, seq, embed) coordinates
var batch_idx = global_idx // (seq_len * embed_dim)
var remaining = global_idx % (seq_len * embed_dim)
var seq_idx = remaining // embed_dim
var embed_idx = remaining % embed_dim
# Get token index
var token_idx_val = Int(indices_lt[batch_idx, seq_idx])
# Simple, correct assignment
if token_idx_val >= 0 and token_idx_val < vocab_size:
output_lt[batch_idx, seq_idx, embed_idx] = weights_lt[
token_idx_val, embed_idx
]
else:
output_lt[batch_idx, seq_idx, embed_idx] = 0
2D ๋น๋ณํฉ ์ปค๋
def embedding_kernel_2d[
batch_size: Int,
seq_len: Int,
vocab_size: Int,
embed_dim: Int,
OutLayout: TensorLayout,
IndicesLayout: TensorLayout,
WeightsLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
indices: TileTensor[mut=True, DType.int32, IndicesLayout, MutAnyOrigin],
weights: TileTensor[mut=True, dtype, WeightsLayout, MutAnyOrigin],
):
"""
2D grid non-coalesced embedding kernel.
Non-optimal approach for comparison:
- 2D grid: (batch*seq, embed_dim)
- More complex indexing
- Potentially worse memory access patterns
"""
# 2D grid indexing
var batch_seq_idx = block_idx.x * block_dim.x + thread_idx.x
var embed_idx = block_idx.y * block_dim.y + thread_idx.y
var total_positions = batch_size * seq_len
# Bounds check
if batch_seq_idx >= total_positions or embed_idx >= embed_dim:
return
var output_lt = output.to_layout_tensor()
var indices_lt = indices.to_layout_tensor()
var weights_lt = weights.to_layout_tensor()
# Convert to (batch, seq) coordinates
var batch_idx = batch_seq_idx // seq_len
var seq_idx = batch_seq_idx % seq_len
# Get token index
var token_idx_val = Int(indices_lt[batch_idx, seq_idx])
# Assignment with 2D grid pattern
if token_idx_val >= 0 and token_idx_val < vocab_size:
output_lt[batch_idx, seq_idx, embed_idx] = weights_lt[
token_idx_val, embed_idx
]
else:
output_lt[batch_idx, seq_idx, embed_idx] = 0
๋ ํ์ด ๋ชจ๋ ๋์ผํ ์๋ฒ ๋ฉ ์กฐํ ๋ก์ง์ ๊ตฌํํ์ง๋ง ์ค๋ ๋ ๊ตฌ์ฑ์ด ๋ค๋ฆ ๋๋ค:
์ฃผ์ ์ฐจ์ด์
-
์ค๋ ๋ ๋งคํ:
- 1D ์ปค๋: ์ถ๋ ฅ ์์๋น ํ๋์ ์ค๋ ๋, ๋จ์ํ 1์ฐจ์ ์ธ๋ฑ์ฑ
- 2D ์ปค๋: (batchรseq, embed_dim) ์ขํ์ ๋ํ 2D ๊ทธ๋ฆฌ๋ ๋งคํ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- 1D ์ปค๋: ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ์๋ฒ ๋ฉ ์ฐจ์์ ์ ๊ทผ โ ๋ณํฉ๋จ
- 2D ์ปค๋: ์ค๋ ๋ ์ ๊ทผ ํจํด์ด ๋ธ๋ก ๊ตฌ์ฑ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง โ ๋ณํฉ๋์ง ์์ ์ ์์
-
์ธ๋ฑ์ฑ ๋ณต์ก๋:
- 1D ์ปค๋: ๋จ์ผ ๋๋์ /๋๋จธ์ง ์ฒด์ธ์ผ๋ก 3D ์ขํ ๊ณ์ฐ
- 2D ์ปค๋: X/Y ์ขํ๋ฅผ ๋ณ๋๋ก ๊ณ์ฐ
์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ
1D ์ปค๋์ด ์ผ๋ฐ์ ์ผ๋ก ๋ ๋์ ์ฑ๋ฅ์ ๋ณด์ด๋ ์ด์ :
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ์ ๊ทผ
- ๋จ์ํ ์ธ๋ฑ์ฑ: ์ขํ ๊ณ์ฐ์ ์ฐ์ฐ ์ค๋ฒํค๋๊ฐ ๋ฎ์
- ๋ ๋์ ์บ์ ํ์ฉ: ์์ธก ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
2D ์ปค๋์ ์ฑ๋ฅ์ด ๋จ์ด์ง ์ ์๋ ์ด์ :
- ํฉ์ด์ง ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ์ํ ๋ด ์ค๋ ๋๋ค์ด ์๋ก ๋ค๋ฅธ ์๋ฒ ๋ฉ ๋ฒกํฐ์ ์ ๊ทผํ ์ ์์
- ๋ณต์กํ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: 16ร16 ๋ธ๋ก์ด ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์๊ณผ ์ต์ ์ผ๋ก ๋ง์ง ์์ ์ ์์
- ์ํ ๋ถ๊ธฐ: ์๋ก ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ์คํ ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ฅผ ์ ์์
ํต์ฌ ๊ฐ๋
| ๊ฐ๋ | 1D ๋ณํฉ | 2D ๋น๋ณํฉ |
|---|---|---|
| ์ค๋ ๋ ๊ตฌ์ฑ | 1D 1์ฐจ์ ์ธ๋ฑ์ฑ | 2D ๊ทธ๋ฆฌ๋ (batchรseq, embed) |
| ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ | ์ฐ์๋ ์ฃผ์ | ํฉ์ด์ง ์ ์์ |
| ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ | ๋จ์: [total_elements // 256] | ๋ณต์ก: [batchรseq // 16, embed // 16] |
| ์ฑ๋ฅ | ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ์ต์ ํ | ์ต์ ํ๋์ง ์์ ๋ฉ๋ชจ๋ฆฌ ํจํด |
| ์ฌ์ฉ ๋ชฉ์ | ํ๋ก๋์ ์ปค๋ | ๊ต์ก์ฉ ๋น๊ต |
ํต์ฌ ๊ตํ: ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ์๋ฒ ๋ฉ๊ณผ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์ 2~3๋ฐฐ์ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
์ฑ๋ฅ: ๋ณํฉ vs ๋น๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ดํดํ๋ ๊ฒ์ GPU ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ์ ๋๋ค. ์ด ์น์ ์์๋ ์๋ฒ ๋ฉ ์กฐํ์ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์ ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ ๋น๋ณํฉ ํจํด๋ณด๋ค ๋ฐ์ด๋ ์ฑ๋ฅ์ ๋ณด์ด๋์ง ์ค๋ช ํฉ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ๊ธฐ์ด
๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ์ํ ๋ด ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ์ ๊ทผํ ๋ ๋ฐ์ํฉ๋๋ค. GPU๋ ์ด๋ฌํ ๊ฐ๋ณ ๋ฉ๋ชจ๋ฆฌ ์์ฒญ์ ๋ ์ ์ ์์ ๋์ฉ๋ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์ ์ผ๋ก ๊ฒฐํฉํ์ฌ ๋์ญํญ ํ์ฉ๋๋ฅผ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
๋ณํฉ vs ๋น๋ณํฉ ์ ๊ทผ
๋ณํฉ (ํจ์จ์ ):
- Thread 0 โ Address 0x1000
- Thread 1 โ Address 0x1004
- Thread 2 โ Address 0x1008
- Thread 3 โ Address 0x100C
- ...
๊ฒฐ๊ณผ: ์ํ ์ ์ฒด(32๊ฐ ์ค๋ ๋)์ ๋ํด 1๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์
๋น๋ณํฉ (๋นํจ์จ์ ):
- Thread 0 โ Address 0x1000
- Thread 1 โ Address 0x2000
- Thread 2 โ Address 0x3000
- Thread 3 โ Address 0x4000
- ...
๊ฒฐ๊ณผ: ์ต๋ 32๋ฒ์ ๊ฐ๋ณ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์
์๋ฒ ๋ฉ ์ฐ์ฐ์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ธ ์ด์
์๋ฒ ๋ฉ ์กฐํ๋ ๋ค์๊ณผ ๊ฐ์ ํน์ฑ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ ๋๋ค:
- ์ต์ํ์ ์ฐ์ฐ: ํ๋ ์ผ์ด๋ผ๊ณค ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ์ถ๋ ฅ์ผ๋ก ๋ณต์ฌํ๋ ๊ฒ๋ฟ
- ํฐ ๋ฉ๋ชจ๋ฆฌ ํํ๋ฆฐํธ: ์๋ฒ ๋ฉ ํ ์ด๋ธ์ ์ ๊ธฐ๊ฐ๋ฐ์ดํธ์ ๋ฌํ ์ ์์
- ๋์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์๊ตฌ: ๋๋์ ๋ฐ์ดํฐ ์ ์ก์ด ํ์
์ด๋ฌํ ์ฐ์ฐ์์๋ ์ฐ์ฐ ๋ณต์ก๋๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ์ด ์ฑ๋ฅ์ ๊ฒฐ์ ํฉ๋๋ค.
์ปค๋ ๋น๊ต
1D ๋ณํฉ ์ปค๋
- ์ค๋ ๋ ๊ตฌ์ฑ:
[total_elements // 256]๋ธ๋ก, ์ถ๋ ฅ ์์๋น ํ๋์ ์ค๋ ๋ - ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ฐ์๋ ์ค๋ ๋๊ฐ ์ฐ์๋ ์๋ฒ ๋ฉ ์ฐจ์์ ์ ๊ทผ
- ์ ๋ณํฉ๋๋๊ฐ:
Thread 0: output[0,0,0],Thread 1: output[0,0,1]โ ์ฐ์๋ ์ฃผ์
2D ๋น๋ณํฉ ์ปค๋
- ์ค๋ ๋ ๊ตฌ์ฑ:
[batch*seq // 16, embed_dim // 16]๋ธ๋ก, 16ร16 ์ค๋ ๋ - ๋ฉ๋ชจ๋ฆฌ ํจํด: ์ค๋ ๋๋ค์ด ์๋ก ๋ค๋ฅธ ์๋ฒ ๋ฉ ๋ฒกํฐ์ ์ ๊ทผํ ์ ์์
- ์ ๋น๋ณํฉ์ธ๊ฐ: ์ค๋ ๋ ์ ๊ทผ ํจํด์ด ๋ฉ๋ชจ๋ฆฌ ์ ์ฒด์ ํฉ์ด์ง ์ ์์
์ฑ๋ฅ ๊ฒฐ๊ณผ
์ผ๋ฐ์ ์ธ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ:
Performance Results:
1D Coalesced: 2.145 ms
2D Non-coalesced: 3.867 ms
1D is 1.80x faster than 2D
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์๊ฐํ
๋ณํฉ ํจํด (1D ์ปค๋)
output[0,0,0:32]์ ๋ํ ์ํ ์คํ:
| ์์ | ์ค๋ ๋ ID | ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ | ์ฃผ์ ํจํด |
|---|---|---|---|
output[0,0,0] | 0 | [0,0] | Base + 0 |
output[0,0,1] | 1 | [0,1] | Base + 4 |
output[0,0,2] | 2 | [0,2] | Base + 8 |
output[0,0,3] | 3 | [0,3] | Base + 12 |
| โฆ | โฆ | โฆ | โฆ |
output[0,0,31] | 31 | [0,31] | Base + 124 |
๊ฒฐ๊ณผ: ์ฐ์๋ ์ฃผ์ โ ์ํ ์ ์ฒด์ ๋ํด 1๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์
๋น๋ณํฉ ํจํด (2D ์ปค๋)
16ร16 ๋ธ๋ก์ ์ํ ์คํ:
Block organization (16ร16):
X-dim: batch*seq positions (0-15)
Y-dim: embed dimensions (0-15)
Warp threads might access:
Thread 0: batch=0, seq=0, embed=0 โ Address A
Thread 1: batch=0, seq=1, embed=0 โ Address B (different row)
Thread 2: batch=0, seq=2, embed=0 โ Address C (different row)
...
Thread 31: batch=1, seq=15, embed=0 โ Address Z (scattered)
๊ฒฐ๊ณผ: ํฉ์ด์ง ์ฃผ์ โ ์ฌ๋ฌ ๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ํธ๋์ญ์
ํต์ฌ ์ต์ ํ ์ ๋ต
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์๋ ๊ฐ๋ฅํ ํ 1D ์ธ๋ฑ์ฑ์ ์ ํธํ์ธ์
- ๋ณํฉ์ ์ ๋ฆฌํ๋๋ก ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ ๋ ฌํ์ธ์
- ์ปค๋ ์ค๊ณ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๊ณ ๋ คํ์ธ์
- ๋ณ๋ชฉ ์ง์ ์ ํ์ ํ๊ธฐ ์ํด ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ํ๋กํ์ผ๋งํ์ธ์
- ์ต์ ํ ํจ๊ณผ๋ฅผ ๊ฒ์ฆํ๊ธฐ ์ํด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ๋ฒค์น๋งํฌ๋ฅผ ํ์ฉํ์ธ์
ํต์ฌ ํต์ฐฐ: ํนํ ์๋ฒ ๋ฉ๊ณผ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์๋ ์ฐ์ฐ ๋ณต์ก๋๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด GPU ์ฑ๋ฅ์ ๊ฒฐ์ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
Puzzle 22: ์ปค๋ ํจ์ ๊ณผ ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค
์ปค๋ ํจ์ ๊ณผ ์คํ ๊ทธ๋๋ ํตํฉ
์ปค๋ ํจ์ ๊ณผ ์คํ ๊ทธ๋๋ ํตํฉ์ ์ด์ ์ ๋ง์ถฐ Part V๋ฅผ ์ด์ด๊ฐ๋๋ค.
Puzzle 21: ์๋ฒ ๋ฉ Op์ ์ด์ด, ์ฌ๋ฌ ์ฐ์ฐ์ ํ๋์ ํจ์จ์ ์ธ ์ปค๋๋ก ๊ฒฐํฉํ๊ณ ์ด๋ฅผ PyTorch์ ์คํ ๊ทธ๋๋ ์์คํ ๊ณผ ํตํฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค. ๋ฐฐ์ธ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ปค๋ ํจ์ ์ด ์๋ฐฉํฅ ํจ์ค(forward pass)์ ์ญ๋ฐฉํฅ ํจ์ค(backward pass) ๋ชจ๋์์ ์ฑ๋ฅ์ ๊ฐ์ ํ๋ ์๋ฆฌ
- ํจ์ ์ฐ์ฐ์ ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค๊ฐ ํ์์ ์ธ ์ด์
- ์ ์ ํ ๊ธฐ์ธ๊ธฐ ํ๋ฆ์ ๊ฐ์ถ ํจ์ ์ปค๋ ์ค๊ณ ๋ฐฉ๋ฒ
- ์๋ก ๋ค๋ฅธ ํจ์ ์ ๋ต์ด ๊ฐ์ ธ์ค๋ ์ฑ๋ฅ ์ฐจ์ด
์ด ํผ์ฆ์ ์ฐ์ฐ์ ์ด๋ป๊ฒ ๊ฒฐํฉํ๋๋๊ฐ ์ด๋ป๊ฒ ๊ตฌํํ๋๋๋งํผ ์ค์ํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค.
๊ฐ์
์ด ํผ์ฆ์์๋ ์๋ฐฉํฅ ํจ์ค์ ์ญ๋ฐฉํฅ ํจ์ค๋ฅผ ๋ชจ๋ ํฌํจํ๋ ํจ์ LayerNorm + Linear ์ฐ์ฐ์ ๊ตฌํํฉ๋๋ค. ํจ์ ๊ณผ ์ธํจ์ ๊ตฌํ ๋ชจ๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ์ง๋ง, ์๋ก ๋ค๋ฅธ ์ ๋ต์ ์ฌ์ฉํ์ฌ ์๋นํ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ณด์ ๋๋ค.
๋น๊ตํ ๋ด์ฉ:
- ์ธํจ์ ๋ฐฉ์: LayerNorm๊ณผ Linear๋ฅผ ๋ณ๋์ ์ปค๋๋ก ์คํ
- ํจ์ ์ปค๋: ํ๋์ ์ปค๋์์ ๋ ์ฐ์ฐ์ ๊ฒฐํฉํ์ฌ ์คํ
- ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค: ํจ์ ์ฐ์ฐ์ ์ํ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ
์ด ๋น๊ต๋ฅผ ํตํด ๋ฅ๋ฌ๋ ์ฐ์ฐ์์ ์ปค๋ ํจ์ ๊ณผ ์ ์ ํ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ด ์ผ๋ง๋ ์ค์ํ์ง ์ฒด๊ฐํ ์ ์์ต๋๋ค.
๋ฐฐ๊ฒฝ: LayerNorm + Linear ์ฐ์ฐ
LayerNorm๊ณผ Linear๋ ํธ๋์คํฌ๋จธ ์ํคํ ์ฒ์ ํต์ฌ ์ฐ์ฐ์ผ๋ก, ํนํ ์ดํ ์ ๋ฉ์ปค๋์ฆ๊ณผ ํผ๋ํฌ์๋ ๋คํธ์ํฌ์์ ๋น๋ฒํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์ฌ์ฉ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
import torch
import torch.nn.functional as F
# Input: hidden states
x = torch.randn(batch_size, seq_len, hidden_dim)
# LayerNorm parameters
ln_weight = torch.ones(hidden_dim) # scale parameter (ฮณ)
ln_bias = torch.zeros(hidden_dim) # shift parameter (ฮฒ)
# Linear layer parameters
linear_weight = torch.randn(output_dim, hidden_dim)
linear_bias = torch.zeros(output_dim)
# Unfused operations (with autograd)
ln_output = F.layer_norm(x, [hidden_dim], weight=ln_weight, bias=ln_bias)
output = F.linear(ln_output, linear_weight, linear_bias)
# Fused operation (custom implementation)
# This is what you'll implement in this puzzle
output_fused = fused_layernorm_linear(x, ln_weight, ln_bias, linear_weight, linear_bias)
ํจ์ ์ฐ์ฐ์ผ๋ก ๊ฒฐํฉํ๋ฉด ํ๋์ ํจ์จ์ ์ธ ์ปค๋์์ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ์ปค๋ ์คํ ์ค๋ฒํค๋ ์ต์ํ
- ์บ์ ํ์ฉ๋ ํฅ์
- ์ค๊ฐ ๊ฒฐ๊ณผ ์ ์ฅ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์ ๊ฑฐ
์ค์ ๋ก ์ด๋ฌํ ํจ์ ์ ์๋ฐฉํฅ ํจ์ค์ ์ญ๋ฐฉํฅ ํจ์ค ๋ชจ๋์์ ์ต๋ 1.5~2๋ฐฐ์ ์๋ ํฅ์์ ์ ๊ณตํ ์ ์์ผ๋ฉฐ, ์ด๋ ํธ๋์คํฌ๋จธ ํ์ต ํจ์จ์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค๊ฐ ์ค์ํ ์ด์
PyTorch์ ์คํ ๊ทธ๋๋ ์์คํ ์ ๊ฐ๋ณ ์ฐ์ฐ์ ๋ํ ๊ธฐ์ธ๊ธฐ๋ฅผ ์๋์ผ๋ก ๊ณ์ฐํ์ง๋ง, ํจ์ ์ฐ์ฐ์๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค๊ฐ ํ์ํฉ๋๋ค:
- ์์น ์์ ์ฑ ์ ์ง
- ์ ์ ํ ๊ธฐ์ธ๊ธฐ ํ๋ฆ ๋ณด์ฅ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ ์ฒ๋ฆฌ
ํ์ต ๊ฒฝ๋ก
์ด ํผ์ฆ์ ์ฒด๊ณ์ ์ธ ์ดํด๋ฅผ ์ํด ๋ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค:
ํจ์ vs ์ธํจ์ ์ปค๋
์ฌ๊ธฐ์๋ถํฐ ์์ํ์ฌ ํจ์ ์๋ฐฉํฅ ์ปค๋์ ๊ตฌํํ๊ณ ์ปค๋ ํจ์ ์ ์ด์ ์ ์ดํดํฉ๋๋ค.
๋ฌด์์ ํ๊ฒ ๋ ๊น์:
- ์ธํจ์ ๊ณผ ํจ์ ์๋ฐฉํฅ ์ปค๋ ๋ชจ๋ ๊ตฌํ
- ํต์ฌ ์ปค๋ ํจ์ ๊ธฐ๋ฒ ํ์ต
- ๋์ผํ ์ฐ์ฐ์ ์๋ก ๋ค๋ฅธ ์ ๋ต์ผ๋ก ๊ตฌํํ๋ ์ฌ๋ก ํ์ธ
- ํจ์ ์ด ๊ฐ์ ธ์ค๋ ์ฑ๋ฅ ์ฐจ์ด ์ดํด
- ์ต์ ์ฑ๋ฅ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ์ต
์คํ ๊ทธ๋๋ ํตํฉ๊ณผ ์ญ๋ฐฉํฅ ํจ์ค
์คํ ๊ทธ๋๋ ํตํฉ๊ณผ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ๊น์ด ํ๊ณ ๋ญ๋๋ค.
๋ฌด์์ ๋ฐฐ์ธ๊น์:
- ์ปค์คํ ์ญ๋ฐฉํฅ ํจ์ค ๊ตฌํ ๋ฐฉ๋ฒ
- ์ ์ ํ ๊ธฐ์ธ๊ธฐ ํ๋ฆ์ด ์ค์ํ ์ด์
- ํ์ต ํจ์จ์ ๋ํ ์ค์ ์์ฌ์
- ์ญ๋ฐฉํฅ ์ฐ์ฐ์ ์ํ ์ต์ ํ ์ ๋ต
- ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ์ํ์ ๊ธฐ์ด
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ
- ์ญ๋ฐฉํฅ ํจ์ค์์์ ์์น ์์ ์ฑ
์์ํ๊ธฐ
์ปค๋ ํจ์ ๊ณผ ์คํ ๊ทธ๋๋ ํตํฉ์ ํ๊ตฌํ ์ค๋น๊ฐ ๋์ จ๋์? ํจ์ vs ์ธํจ์ ์ปค๋ ์์ ํจ์ ์ปค๋์ ๊ตฌํํ ํ, ์คํ ๊ทธ๋๋ ํตํฉ๊ณผ ์ญ๋ฐฉํฅ ํจ์ค ๋ก ๋์ด๊ฐ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ์ดํดํด ๋ณด์ธ์.
์ด ํผ์ฆ์๋ ๋ค์์ ๊ฒ์ฆํ๋ ์ข ํฉ ํ ์คํธ ํ๋ ์์ํฌ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค:
- ์๋ฐฉํฅ ํจ์ค์ ์ญ๋ฐฉํฅ ํจ์ค ๋ชจ๋์์ PyTorch ๊ตฌํ๊ณผ์ ์์น์ ์ ํ๋
- CPU์ GPU ๊ตฌํ ๊ฐ์ ์ฑ๋ฅ ๋น๊ต
- ๋ชจ๋ ํ๋ผ๋ฏธํฐ(์ ๋ ฅ, LayerNorm ๊ฐ์ค์น/๋ฐ์ด์ด์ค, Linear ๊ฐ์ค์น/๋ฐ์ด์ด์ค)์ ๋ํ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ ์ ํ๋
- ์ปค๋ ํจ์ ์ ํตํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ต์ ํ
๐ก ์ฑ๊ณต ํ: ์๋ก ๋ค๋ฅธ ๊ตฌํ ๋ฐฉ์(ํจ์ vs ์ธํจ์ )์ด ์๋ฐฉํฅ ํจ์ค์ ์ญ๋ฐฉํฅ ํจ์ค ์ฑ๋ฅ ๋ชจ๋์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง ์ฃผ์ ๊น๊ฒ ์ดํด๋ณด์ธ์. ์ด ํต์ฐฐ์ LayerNorm + Linear๋ฅผ ๋์ด ๋ค์ํ ๋ฅ๋ฌ๋ ์ฐ์ฐ์ ์ ์ฉ๋ฉ๋๋ค. ํนํ ์ญ๋ฐฉํฅ ํจ์ค ๊ตฌํ์ ํ์ต ํจ์จ๊ณผ ์์น ์์ ์ฑ์ ์ง์ ์ ์ธ ์ํฅ์ ๋ฏธ์น๋ฏ๋ก ๋งค์ฐ ์ค์ํฉ๋๋ค.
โ๏ธ ํจ์ vs ์ธํจ์ ์ปค๋
๊ฐ์
์ด ํผ์ฆ์์๋ LayerNorm๊ณผ Linear ์ฐ์ฐ์ ๋ํ ๋ ๊ฐ์ง ์ ๊ทผ ๋ฐฉ์์ ๊ตฌํํ๊ณ ๋น๊ตํ๋ฉฐ, ์ปค๋ ํจ์ ์ ์ฑ๋ฅ ์ด์ ์ ํ๊ตฌํฉ๋๋ค:
- ์ธํจ์ ๋ฐฉ์: LayerNorm๊ณผ Linear๋ฅผ ๋ณ๋์ ์ฐ์ฐ์ผ๋ก ์คํ
- ํจ์ ์ปค๋: LayerNorm๊ณผ Linear ์ฐ์ฐ์ ํ๋์ GPU ์ปค๋๋ก ๊ฒฐํฉ
์ด ๋น๊ต๋ฅผ ํตํด ์ปค๋ ํจ์ ์ด ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์ฑ๋ฅ์ ํฌ๊ฒ ๊ฐ์ ํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ์ปค๋ ์คํ ์ค๋ฒํค๋ ์ต์ํ
- ์บ์ ํ์ฉ๋ ํฅ์
- ์ค๊ฐ ๊ฒฐ๊ณผ ์ ์ฅ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์ ๊ฑฐ
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ์ฌ๋ฌ ์ฐ์ฐ์ ๊ฒฐํฉํ๋ ์ปค๋ ํจ์ ๊ธฐ๋ฒ
- ํจ์ ์ฐ์ฐ์ ํตํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ต์ ํ
- ์๋ก ๋ค๋ฅธ ์ปค๋ ๊ตฌํ์ ์ฑ๋ฅ ๋ฒค์น๋งํน
- ํจ์ ์ฐ์ฐ์์์ ์์น ์์ ์ฑ
- PyTorch ์ปค์คํ ์ฐ์ฐ ํตํฉ
๊ฒฐํฉํ ์ํ์ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
LayerNorm: \[\Large \text{LayerNorm}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
-
Linear: \[\Large \text{Linear}(x) = Wx + b \]
ํจ์ ์ฐ์ฐ์ผ๋ก ๊ฒฐํฉํ๋ฉด ๋ค์์ ๊ณ์ฐํฉ๋๋ค: \[\Large \text{Fused}(x) = W(\gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta) + b \]
LayerNorm ์ดํดํ๊ธฐ
LayerNorm์ ์ฌ์ธต ์ ๊ฒฝ๋ง์ ํ์ต์ ์์ ํํ๊ณ ๊ฐ์ํ๋ ์ ๊ทํ ๊ธฐ๋ฒ์ ๋๋ค. ๊ตฌ์ฑ ์์์ ํ๋ผ๋ฏธํฐ๋ฅผ ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
LayerNorm์ด ํ๋ ์ผ
-
์ ๊ทํ: LayerNorm์ ๊ฐ ์ํ์ ํน์ฑ(์๋ ์ฐจ์, hidden dimension) ์ ์ฒด์ ๊ฑธ์ณ ํ์ฑํ ๊ฐ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ๊ทํํฉ๋๋ค. ๊ตฌ์ฒด์ ์ผ๋ก:
- ๊ฐ ์ํ์ค ์์น์์ ์๋ ์ฐจ์์ ๋ํ ํต๊ณ๋์ ๊ณ์ฐํฉ๋๋ค
- ๋ฐฐ์น์ ๊ฐ ์ํ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ๊ทํ๋ฉ๋๋ค
- ๋ฐฐ์น ์ฐจ์์ ๋ํด ์ ๊ทํํ๋ BatchNorm๊ณผ๋ ๋ค๋ฆ ๋๋ค
-
ํ๋ผ๋ฏธํฐ:
- \(\gamma\) (scale): ๋คํธ์ํฌ๊ฐ ๊ฐ ํน์ฑ์ ์ต์ ์ค์ผ์ผ์ ํ์ตํ ์ ์๊ฒ ํ๋ ํ์ต ๊ฐ๋ฅํ ํ๋ผ๋ฏธํฐ ๋ฒกํฐ
- \(\beta\) (shift): ๋คํธ์ํฌ๊ฐ ๊ฐ ํน์ฑ์ ์ต์ ์ด๋๋์ ํ์ตํ ์ ์๊ฒ ํ๋ ํ์ต ๊ฐ๋ฅํ ํ๋ผ๋ฏธํฐ ๋ฒกํฐ
- \(\epsilon\): 0์ผ๋ก ๋๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ถ์ฐ์ ๋ํ๋ ์์ ์์ (1e-5)
LayerNorm์ ์ค์ ์ญํ
LayerNorm์ ์ฌ์ธต ์ ๊ฒฝ๋ง์์ ์ฌ๋ฌ ์ค์ํ ๊ธฐ๋ฅ์ ์ํํฉ๋๋ค:
-
ํน์ฑ ํ์คํ:
- ๊ฐ ํน์ฑ์ ํ๊ท 0, ๋ถ์ฐ 1๋ก ๋ณํํฉ๋๋ค
- ๋คํธ์ํฌ์ ํ์ต ๊ณผ์ ์ ๋ ์์ ์ ์ผ๋ก ๋ง๋ญ๋๋ค
- ํ์ต ์ค ๋ ์ด์ด ์ ๋ ฅ์ ๋ถํฌ๊ฐ ๋ณํ๋ โ๋ด๋ถ ๊ณต๋ณ๋ ์ด๋(internal covariate shift)โ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํฉ๋๋ค
-
๊ธฐ์ธ๊ธฐ ํ๋ฆ:
- ๋คํธ์ํฌ๋ฅผ ํตํ ๊ธฐ์ธ๊ธฐ ํ๋ฆ์ ๊ฐ์ ํฉ๋๋ค
- ๊ธฐ์ธ๊ธฐ ์์ค/ํญ๋ฐ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํฉ๋๋ค
- ๋ ๋์ ํ์ต๋ฅ ์ ์ฌ์ฉํ ์ ์์ด ํ์ต ํจ์จ์ด ํฅ์๋ฉ๋๋ค
-
์ ๊ทํ ํจ๊ณผ:
- ์๋ฌต์ ์ธ ์ ๊ทํ ์ญํ ์ ํฉ๋๋ค
- ํน์ฑ ๋ถํฌ๋ฅผ ์ ๊ทํํ์ฌ ๊ณผ์ ํฉ์ ๋ฐฉ์งํฉ๋๋ค
- ์ ๋ ฅ ๋ณ๋์ ๋ํ ๋คํธ์ํฌ์ ๊ฐ๊ฑด์ฑ์ ๋์ ๋๋ค
-
์ํ์ค ๋ชจ๋ธ๋ง:
- ํธ๋์คํฌ๋จธ ์ํคํ ์ฒ์์ ํนํ ํจ๊ณผ์ ์ ๋๋ค
- ์๋ก ๋ค๋ฅธ ์ํ์ค ๊ธธ์ด์์๋ ์ผ๊ด๋ ์ ํธ ํฌ๊ธฐ๋ฅผ ์ ์งํฉ๋๋ค
- ๊ฐ๋ณ ๊ธธ์ด ์ํ์ค๋ฅผ ๋ ์ ์ฒ๋ฆฌํ ์ ์๊ฒ ํฉ๋๋ค
-
ํ์ต ์ญํ:
- ํ์ต ์๋ ด์ ๊ฐ์ํฉ๋๋ค
- ์ธ๋ฐํ ํ์ต๋ฅ ์กฐ์ ์ ํ์์ฑ์ ์ค์ ๋๋ค
- ๊ฐ์ค์น ์ด๊ธฐํ์ ๋ํ ๋คํธ์ํฌ์ ๋ฏผ๊ฐ๋๋ฅผ ๋ฎ์ถฅ๋๋ค
์ํ์ ๊ตฌ์ฑ ์์
-
ํ๊ท ๊ณ์ฐ (\(\mu\)): \[\Large \mu = \frac{1}{H} \sum_{i=1}^{H} x_i \]
- ์๋ ์ฐจ์(H)์ ๊ฑธ์ณ ํ๊ท ์ ๊ณ์ฐํฉ๋๋ค
- ๊ฐ ์ํ์ค ์์น๋ง๋ค ๊ณ ์ ํ ํ๊ท ์ ๊ฐ์ง๋๋ค
-
๋ถ์ฐ ๊ณ์ฐ (\(\sigma^2\)): \[\Large \sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2 \]
- ์๋ ์ฐจ์์ ๊ฑธ์ณ ๋ถ์ฐ์ ๊ณ์ฐํฉ๋๋ค
- ์ ๊ทํ๋ ๊ฐ์ ์ค์ผ์ผ๋ง์ ์ฌ์ฉ๋ฉ๋๋ค
-
์ ๊ทํ์ ์ค์ผ์ผ๋ง: \[\Large \text{LayerNorm}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
- ๋จผ์ ์ ๋ ฅ์ ํ๊ท 0, ๋ถ์ฐ 1๋ก ์ ๊ทํํฉ๋๋ค
- ๊ทธ๋ฐ ๋ค์ ํ์ต ๊ฐ๋ฅํ scale (\(\gamma\))๊ณผ shift (\(\beta\)) ํ๋ผ๋ฏธํฐ๋ฅผ ์ ์ฉํฉ๋๋ค
- \(\odot\) ๊ธฐํธ๋ ์์๋ณ ๊ณฑ์ (์๋ค๋ง๋ฅด ๊ณฑ)์ ๋ํ๋ ๋๋ค
- ์๋ฅผ ๋ค์ด, \(\gamma = [1.2, 0.8, 1.5]\)์ด๊ณ ์ ๊ทํ๋ ์ ๋ ฅ์ด \([0.5, -0.3, 0.7]\)์ด๋ฉด, \(\gamma \odot x = [0.6, -0.24, 1.05]\)์ ๋๋ค
LayerNorm์ด ์ค์ํ ์ด์
-
ํ์ต ์์ ์ฑ:
- ํ์ฑํ ๊ฐ์ด ๋๋ฌด ํฌ๊ฑฐ๋ ์์์ง๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค
- ๋คํธ์ํฌ ์ ์ฒด์ ๊ฑธ์ณ ์ผ๊ด๋ ์ ํธ ํฌ๊ธฐ๋ฅผ ์ ์งํฉ๋๋ค
-
ํน์ฑ ํ์ต:
- scale (\(\gamma\))๊ณผ shift (\(\beta\)) ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ์ด๋ค ํน์ฑ์ด ์ค์ํ์ง ํ์ตํ ์ ์์ต๋๋ค
- ํน์ ํน์ฑ์ ๋ฌด์ํ๊ฑฐ๋ ๊ฐ์กฐํ๋ ๊ฒ์ ํจ๊ณผ์ ์ผ๋ก ํ์ตํ ์ ์์ต๋๋ค
-
๋ ๋ฆฝ์ฑ:
- BatchNorm๊ณผ ๋ฌ๋ฆฌ, LayerNorm์ ํต๊ณ๋์ ๊ฐ ์ํ์ ๋ํด ๋ ๋ฆฝ์ ์ผ๋ก ๊ณ์ฐ๋ฉ๋๋ค
- ๊ฐ๋ณ ๊ธธ์ด ์ํ์ค์ ์์ ๋ฐฐ์น ํฌ๊ธฐ์ ๋ ์ ํฉํฉ๋๋ค
๊ตฌ์ฑ
- ๋ฐฐ์น ํฌ๊ธฐ:
BATCH_SIZE = 4 - ์ํ์ค ๊ธธ์ด:
SEQ_LEN = 4 - ์๋ ์ฐจ์:
HIDDEN_DIM = 8 - ์ถ๋ ฅ ์ฐจ์:
OUTPUT_DIM = 16 - ์ก์ค๋ก :
EPS = 1e-5 - ๋ฐ์ดํฐ ํ์
:
DType.float32
๊ตฌํ ๋ฐฉ์
1. ์ธํจ์ ๊ตฌํ
์ธํจ์ ๋ฐฉ์์ ์ฌ๋ฌ ์ปค๋์ ์ฌ์ฉํ์ฌ ์ฐ์ฐ์ ๊ฐ๋ณ์ ์ผ๋ก ์คํํฉ๋๋ค. ์ด์ ์ฑํฐ์์ ์์ฑํ ์ปค๋๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
ํ๋ ฌ ๊ณฑ์ ์ปค๋
Puzzle 16: ํ๋ ฌ ๊ณฑ์ (MatMul)์์ ์ฌ์ฉํ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ปค๋์ ์ ํ ๋ณํ์ ์ฌ์ฌ์ฉํฉ๋๋ค. ์ด ์ปค๋์ ๋ค์ํ ํ๋ ฌ ํฌ๊ธฐ๋ฅผ ์์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํฌํจํฉ๋๋ค:
# Idiomatic tiled matmul from p19.mojo
def matmul_idiomatic_tiled[
rows: Int,
cols: Int,
inner: Int,
OutLayout: TensorLayout,
ALayout: TensorLayout,
BLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, ALayout, MutAnyOrigin],
b: TileTensor[mut=False, dtype, BLayout, MutAnyOrigin],
):
"""Idiomatic tiled matrix multiplication from p19."""
var local_row = thread_idx.y
var local_col = thread_idx.x
var tiled_row = block_idx.y * MATMUL_BLOCK_DIM_XY + local_row
var tiled_col = block_idx.x * MATMUL_BLOCK_DIM_XY + local_col
# Get the tile of the output matrix that this thread block is responsible for
var out_tile = output.tile[MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY](
block_idx.y, block_idx.x
)
comptime shared_layout = row_major[
MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY
]()
var a_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](shared_layout)
var b_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](shared_layout)
var acc: output.ElementType = 0
comptime load_a_layout = row_major[
MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY
]() # Coalesced loading
comptime load_b_layout = row_major[
MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY
]() # Coalesced loading
comptime for idx in range(
(inner + MATMUL_BLOCK_DIM_XY - 1) // MATMUL_BLOCK_DIM_XY
):
# Get tiles from A and B matrices
var a_tile = a.tile[MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY](
block_idx.y, idx
)
var b_tile = b.tile[MATMUL_BLOCK_DIM_XY, MATMUL_BLOCK_DIM_XY](
idx, block_idx.x
)
# Asynchronously copy tiles to shared memory with consistent orientation
copy_dram_to_sram_async[
thread_layout=load_a_layout,
num_threads=MATMUL_NUM_THREADS,
block_dim_count=MATMUL_BLOCK_DIM_COUNT,
](a_shared, a_tile)
copy_dram_to_sram_async[
thread_layout=load_b_layout,
num_threads=MATMUL_NUM_THREADS,
block_dim_count=MATMUL_BLOCK_DIM_COUNT,
](b_shared, b_tile)
# Wait for all async copies to complete
async_copy_wait_all()
barrier()
# Compute partial matrix multiplication for this tile
comptime for k in range(MATMUL_BLOCK_DIM_XY):
if (
tiled_row < rows and tiled_col < cols
): # Only perform calculation for valid outputs
if k < a_tile.dim(
1
): # Only perform calculation on valid inputs
acc += a_shared[local_row, k] * b_shared[k, local_col]
barrier()
# Write final result with bounds checking (needed for variable matrix sizes)
if tiled_row < rows and tiled_col < cols:
out_tile[local_row, local_col] = acc
์ ์น ์ปค๋
ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ๋ง์ ์ฌ์ฉํ๋ ์ ์น ์ปค๋์ ๋๋ค:
def transpose_kernel[
rows: Int,
cols: Int,
OutLayout: TensorLayout,
InLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
inp: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
):
"""Transpose matrix using shared memory tiling for coalesced access.
We will learn more about coalesced access in the next part.
"""
comptime shared_layout = row_major[
TRANSPOSE_BLOCK_DIM_XY, TRANSPOSE_BLOCK_DIM_XY
]()
var shared_tile = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](shared_layout)
var local_row = thread_idx.y
var local_col = thread_idx.x
var global_row = block_idx.y * TRANSPOSE_BLOCK_DIM_XY + local_row
var global_col = block_idx.x * TRANSPOSE_BLOCK_DIM_XY + local_col
if global_row < rows and global_col < cols:
shared_tile[local_row, local_col] = inp[global_row, global_col]
barrier()
var out_row = block_idx.x * TRANSPOSE_BLOCK_DIM_XY + local_row
var out_col = block_idx.y * TRANSPOSE_BLOCK_DIM_XY + local_col
# Store data from shared memory to global memory (coalesced write)
# Note: we transpose the shared memory access pattern
if out_row < cols and out_col < rows:
output[out_row, out_col] = shared_tile[local_col, local_row]
Bias ํฉ์ฐ ์ปค๋
Bias ํญ์ ๋ํ๋ ๊ฐ๋จํ ์์๋ณ ํฉ์ฐ ์ปค๋์ ๋๋ค:
def add_bias_kernel[
batch_size: Int,
seq_len: Int,
output_dim: Int,
OutputLayout: TensorLayout,
InputLayout: TensorLayout,
BiasLayout: TensorLayout,
](
output: TileTensor[mut=True, dtype, OutputLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InputLayout, MutAnyOrigin],
bias: TileTensor[mut=False, dtype, BiasLayout, ImmutAnyOrigin],
):
"""Simple bias addition."""
var batch_idx = block_idx.x
var seq_idx = block_idx.y
var out_idx = thread_idx.x
if batch_idx >= batch_size or seq_idx >= seq_len or out_idx >= output_dim:
return
output[batch_idx, seq_idx, out_idx] = input[
batch_idx, seq_idx, out_idx
] + rebind[Scalar[dtype]](bias[out_idx])
LayerNorm ์ปค๋
์ด์ ์ด ์ปค๋์ ์์ฑํ์ฌ LayerNorm ์ฐ์ฐ์ ๊ตฌํํฉ๋๋ค. ๋ค์์ด ํ์ํฉ๋๋ค:
- ๊ฐ ์ํ์ค ์์น์ ๋ํ ํ๊ท \(\mu\)๊ณผ ๋ถ์ฐ \(\sigma^2\) ๊ณ์ฐ
- ์ด ํต๊ณ๋์ ์ฌ์ฉํ์ฌ ์ ๋ ฅ ์ ๊ทํ
- ์ค์ผ์ผ \(\gamma\)๊ณผ ์ํํธ \(\beta\) ํ๋ผ๋ฏธํฐ ์ ์ฉ
def layernorm_kernel[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
OutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
](
output: TileTensor[mut=True, dtype, OutputLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InputLayout, ImmutAnyOrigin],
ln_weight: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
ln_bias: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
):
var batch_idx = block_idx.x
var seq_idx = block_idx.y
var hidden_idx = thread_idx.x
if (
batch_idx >= batch_size
or seq_idx >= seq_len
or hidden_idx >= hidden_dim
):
return
# Compute statistics for this sequence position (redundant but simple)
var sum_val: Scalar[dtype] = 0
var sq_sum: Scalar[dtype] = 0
# FILL ME IN (roughly 11 lines)
๊ตฌํ ๋จ๊ณ:
- ๋จผ์ , ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ์ฌ์ฉํ์ฌ ํ๊ท ๊ณผ ๋ถ์ฐ์ ๊ณ์ฐํฉ๋๋ค
- ๊ทธ๋ฐ ๋ค์, ์ด ํต๊ณ๋์ผ๋ก ์ ๋ ฅ์ ์ ๊ทํํฉ๋๋ค
- ๋ง์ง๋ง์ผ๋ก, ์ค์ผ์ผ๊ณผ ์ํํธ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ์ฉํฉ๋๋ค
์ธํจ์ ๋ฐฉ์์ ํน์ฑ:
- ์ฌ๋ฌ ๋ฒ์ ์ปค๋ ์คํ (LayerNorm โ MatMul โ Bias)
- ์ฐ์ฐ ๊ฐ ์ค๊ฐ ํ ์ ํ ๋น
- ๋ณ๋์ ํจ์ค๋ก ์ธํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ฆ๊ฐ
- ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๊ฐ ๋ช ํํ ๊ฐ๊ฒฐํ ๊ตฌํ
- ๊ฐ ์ฐ์ฐ์ด ๊ฒฉ๋ฆฌ๋์ด ๋๋ฒ๊น ์ด ์ฉ์ด
ํ
-
์ค๋ ๋ ๊ตฌ์ฑ:
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก ์ฌ์ฉ (๊ทธ๋ฆฌ๋:
[batch_size, seq_len]) - ๊ฐ ์ค๋ ๋๊ฐ ํ๋์ ์๋ ์ฐจ์ ์์๋ฅผ ์ฒ๋ฆฌ
- ์ํ์ค๋น ํต๊ณ๋์ ํ ๋ฒ๋ง ๊ณ์ฐํ์ฌ ์ค๋ณต ์ฐ์ฐ ๋ฐฉ์ง
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก ์ฌ์ฉ (๊ทธ๋ฆฌ๋:
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ์
๋ ฅ ํ
์:
[batch_idx, seq_idx, hidden_idx]๋ก ์ ๊ทผ - ์ถ๋ ฅ ํ
์:
[batch_idx, seq_idx, hidden_idx]๋ก ์ ๊ทผ - LayerNorm ํ๋ผ๋ฏธํฐ:
[hidden_idx]๋ก ์ ๊ทผ
- ์
๋ ฅ ํ
์:
-
์์น ์์ ์ฑ:
- ์ ๊ณฑ๊ทผ์ ์ทจํ๊ธฐ ์ ์ ์ก์ค๋ก (1e-5)์ ๋ํฉ๋๋ค
- ์ ์ ํ ํ์
์บ์คํ
์ ์ํด
rebind[Scalar[dtype]]์ฌ์ฉ - ๋ถ์ฐ์ (sq_sum / hidden_dim) - (mean * mean)์ผ๋ก ๊ณ์ฐ
-
์ฑ๋ฅ:
- ํ ๋ฒ์ ํจ์ค๋ก ํ๊ท ๊ณผ ๋ถ์ฐ์ ๋์์ ๊ณ์ฐ
- ๊ณ์ฐ๋ ํต๊ณ๋์ ์ํ์ค ๋ด ๋ชจ๋ ์์์ ์ฌ์ฌ์ฉ
- ๋ถํ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๋ฐฉ์ง
์ฝ๋ ์คํ
์ธํจ์ ๊ตฌํ์ ํ ์คํธํ๋ ค๋ฉด ๋ค์์ ์คํํ์ธ์:
pixi run p22 --unfused
pixi run -e amd p22 --unfused
uv run poe p22 --unfused
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
Testing with dimensions: [4, 4, 8] -> [4, 4, 16]
โ
Loaded Mojo operations library
============================================================
Puzzle 22: UNFUSED Algorithm Test & Benchmark
============================================================
๐งช Correctness Testing for UNFUSED Algorithm
====================================================
Testing Reference PyTorch Implementation
-----------------------------------------------
โ
Reference PyTorch
Max difference: 0.00e+00
Result: โ
CORRECT
Testing CPU Implementation
---------------------------------
โ
Using Mojo fused kernel (CPU)
Max difference: 1.86e-08
Result: โ
CORRECT
Testing GPU Unfused Implementation
-----------------------------------------
โ
Using Mojo unfused kernel (GPU)
Max difference: 1.86e-08
Result: โ
CORRECT
Correctness Summary:
- Reference: โ
CORRECT
- CPU: โ
CORRECT
- GPU unfused: โ
CORRECT
Overall Correctness: โ
ALL CORRECT
Benchmarking CPU vs GPU UNFUSED
------------------------------------------
Testing CPU performance...
CPU: 3173.70ms (50 iterations)
Testing GPU unfused performance...
GPU unfused: 3183.57ms (50 iterations)
GPU unfused vs CPU: 1.00x slower
CPU wins (GPU overhead > computation benefit)
UNFUSED Algorithm Test Completed!
์๋ฃจ์
def layernorm_kernel[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
OutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutputLayout, MutAnyOrigin],
input: TileTensor[mut=True, dtype, InputLayout, MutAnyOrigin],
ln_weight: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
ln_bias: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
):
var batch_idx = block_idx.x
var seq_idx = block_idx.y
var hidden_idx = thread_idx.x
if (
batch_idx >= batch_size
or seq_idx >= seq_len
or hidden_idx >= hidden_dim
):
return
var output_lt = output.to_layout_tensor()
var input_lt = input.to_layout_tensor()
var ln_weight_lt = ln_weight.to_layout_tensor()
var ln_bias_lt = ln_bias.to_layout_tensor()
# Compute statistics for this sequence position (redundant but simple)
var sum_val: Scalar[dtype] = 0
var sq_sum: Scalar[dtype] = 0
comptime for h in range(hidden_dim):
var val = input_lt[batch_idx, seq_idx, h]
sum_val += rebind[Scalar[dtype]](val)
sq_sum += rebind[Scalar[dtype]](val * val)
var mean_val = sum_val / hidden_dim
var var_val = (sq_sum / hidden_dim) - (mean_val * mean_val)
var inv_std = 1.0 / sqrt(var_val + 1e-5)
# Apply LayerNorm to this element
var input_val = input_lt[batch_idx, seq_idx, hidden_idx]
var normalized = (input_val - mean_val) * inv_std * rebind[Scalar[dtype]](
ln_weight_lt[hidden_idx]
) + rebind[Scalar[dtype]](ln_bias_lt[hidden_idx])
output_lt[batch_idx, seq_idx, hidden_idx] = normalized
์ธํจ์ ๊ตฌํ์ ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ํ ์์ ํ๋์ ์์๋ฅผ ์ฒ๋ฆฌํ๋ ์ง๊ด์ ์ธ ๋ฐฉ์์ ๋ฐ๋ฆ ๋๋ค. ํต์ฌ ๊ตฌ์ฑ ์์๋ฅผ ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
-
์ค๋ ๋์ ๋ธ๋ก ๊ตฌ์ฑ:
batch_idx = block_idx.x seq_idx = block_idx.y hidden_idx = thread_idx.x-
๊ฐ ์ค๋ ๋ ๋ธ๋ก์ด ๋ฐฐ์น ๋ด ํ๋์ ์ํ์ค ์์น๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
-
๊ทธ๋ฆฌ๋ ์ฐจ์:
[batch_size, seq_len] -
๊ฐ ์ค๋ ๋๊ฐ ์๋ ์ฐจ์์ ํ๋์ ์์๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
-
์ธ๋ฑ์ค๊ฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด ์กฐ๊ธฐ ๋ฐํํฉ๋๋ค:
if (batch_idx >= batch_size or seq_idx >= seq_len or hidden_idx >= hidden_dim): return
-
-
ํต๊ณ๋ ๊ณ์ฐ:
var sum_val: Scalar[dtype] = 0 var sq_sum: Scalar[dtype] = 0 @parameter for h in range(hidden_dim): val = input[batch_idx, seq_idx, h] sum_val += rebind[Scalar[dtype]](val) sq_sum += rebind[Scalar[dtype]](val * val)-
ํ ๋ฒ์ ํจ์ค๋ก ํฉ๊ณ์ ์ ๊ณฑํฉ์ ๋์์ ๊ณ์ฐํฉ๋๋ค
-
์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐ๋ฅผ ์ํด
@parameter๋ฅผ ์ฌ์ฉํฉ๋๋ค -
rebind[Scalar[dtype]]๋ก ์ ์ ํ ํ์ ์บ์คํ ์ ์ํํฉ๋๋ค -
ํ๊ท ๊ณผ ๋ถ์ฐ์ ๊ณ์ฐํฉ๋๋ค:
mean_val = sum_val / hidden_dim var_val = (sq_sum / hidden_dim) - (mean_val * mean_val) inv_std = 1.0 / sqrt(var_val + 1e-5)
-
-
์ ๊ทํ์ ์ค์ผ์ผ๋ง:
input_val = input[batch_idx, seq_idx, hidden_idx] normalized = (input_val - mean_val) * inv_std * rebind[Scalar[dtype]]( ln_weight[hidden_idx] ) + rebind[Scalar[dtype]](ln_bias[hidden_idx]) output[batch_idx, seq_idx, hidden_idx] = normalized- ์ ๊ทํ๋ฅผ ์ ์ฉํฉ๋๋ค: \[\Large \text{normalized} = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
- ํ์ต ๊ฐ๋ฅํ ํ๋ผ๋ฏธํฐ
ฮณ(ln_weight)๋ก ์ค์ผ์ผ๋งํฉ๋๋ค - ํ์ต ๊ฐ๋ฅํ bias
ฮฒ(ln_bias)๋ฅผ ๋ํฉ๋๋ค - ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅ ํ ์์ ์ ์ฅํฉ๋๋ค
-
์ฑ๋ฅ ํน์ฑ:
- ๊ฐ ์ค๋ ๋๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ํต๊ณ๋์ ๊ณ์ฐํฉ๋๋ค
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์์ (๊ฐ๋จํ์ง๋ง ๋ ํจ์จ์ )
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์
๋ ฅ:
[batch_idx, seq_idx, h] - ์ถ๋ ฅ:
[batch_idx, seq_idx, hidden_idx] - ํ๋ผ๋ฏธํฐ:
[hidden_idx]
- ์
๋ ฅ:
- ๋ค์์ ํตํด ์์น ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค:
- ์ ๊ณฑ๊ทผ ์ ์ ์ก์ค๋ก (1e-5) ์ถ๊ฐ
- ์ ์ ํ ํ์ ์บ์คํ ์ฌ์ฉ
- ์์น์ ์ผ๋ก ์์ ์ ์ธ ๋ฐฉ์์ผ๋ก ๋ถ์ฐ ๊ณ์ฐ
-
๊ตฌํ ์ธ๋ถ ์ฌํญ:
-
ํ์ ์์ ์ฑ:
- ์ค๊ฐ ๊ณ์ฐ์
Scalar[dtype]์ฌ์ฉ - ์ ์ ํ ํ์
์บ์คํ
์ ์ํด
rebind[Scalar[dtype]]์ฌ์ฉ - ์ผ๊ด๋ ๋ถ๋์์์ ์ ๋ฐ๋ ๋ณด์ฅ
- ์ค๊ฐ ๊ณ์ฐ์
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ์ ๋ ฅ ํ ์์์ ๋ณํฉ ์ฝ๊ธฐ
- ์ถ๋ ฅ ํ ์์ ๋ณํฉ ์ฐ๊ธฐ
- LayerNorm ํ๋ผ๋ฏธํฐ์ ์์ฐจ์ ์ ๊ทผ
-
์ฐ์ฐ ํ๋ฆ:
- ํต๊ณ๋ ๊ณ์ฐ: \[\Large O(H) \text{ operations per thread} \]
- ์ ๊ทํ: \[\Large O(1) \text{ operations per thread} \]
- ์ ์ฒด ๋ณต์ก๋: \[\Large O(H) \text{ per output element} \]
-
ํ๊ณ์ :
- ํต๊ณ๋์ ์ค๋ณต ๊ณ์ฐ
- ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์
- ๋์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋
- ์ฌ๋ฌ ๋ฒ์ ์ปค๋ ์คํ ํ์
-
์ด ๊ตฌํ์ ์ ํํ์ง๋ง ์ฑ๋ฅ ๋ฉด์์ ์ต์ ์ด ์๋๋ฉฐ, ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ์์ CPU ๋ฒ์ ๋ณด๋ค ์ฝ๊ฐ ๋๋ฆฐ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ํจ์ ๊ตฌํ์์๋ ๋ค์์ ํตํด ์ด๋ฌํ ์ฑ๋ฅ ํ๊ณ๋ฅผ ํด๊ฒฐํฉ๋๋ค:
- ์ํ์ค๋น ํต๊ณ๋์ ํ ๋ฒ๋ง ๊ณ์ฐ
- ์ ๊ทํ๋ ๊ฐ ์ฌ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ๊ฐ์
- ์ค๊ฐ ํ ์ ํ ๋น ์ ๊ฑฐ
2. ํจ์ ์ปค๋ ๊ตฌํ
ํจ์ ์ปค๋์ LayerNorm๊ณผ Linear ์ฐ์ฐ์ ํ๋์ GPU ์ปค๋๋ก ๊ฒฐํฉํฉ๋๋ค:
def minimal_fused_kernel[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
output_dim: Int,
OutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
WeightLayout: TensorLayout,
BiasLayout: TensorLayout,
](
output: TileTensor[mut=True, dtype, OutputLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InputLayout, ImmutAnyOrigin],
ln_weight: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
ln_bias: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
linear_weight: TileTensor[mut=False, dtype, WeightLayout, ImmutAnyOrigin],
linear_bias: TileTensor[mut=False, dtype, BiasLayout, ImmutAnyOrigin],
):
"""Minimal fused kernel - one thread per sequence position to avoid redundancy.
"""
# Grid: (batch_size, seq_len) - one thread block per sequence position
# Block: (1,) - single thread per sequence position to avoid redundant computation
var batch_idx = block_idx.x
var seq_idx = block_idx.y
if batch_idx >= batch_size or seq_idx >= seq_len:
return
# Step 1: Compute LayerNorm statistics once per sequence position
# FILL IN roughly 10 lines
# Step 2: Compute all outputs for this sequence position
# FILL IN roughly 10 lines
ํต์ฌ ์ต์ ํ:
- ๋ ๋ฒ ๋์ ํ ๋ฒ์ ์ปค๋ ์คํ
- ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ฉ
- ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ์ค๊ฐ ํ ์ ํ ๋น ๋ถํ์
ํ
-
์ค๋ ๋ ๊ตฌ์ฑ:
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
[batch_size, seq_len]) - ์ค๋ณต์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ํ์ค ์์น๋น ๋จ์ผ ์ค๋ ๋
- ๊ฐ ์ํ์ค ์์น์ ๋ชจ๋ ์ถ๋ ฅ์ ํ๋์ ์ค๋ ๋์์ ๊ณ์ฐ
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ์
๋ ฅ ํ
์:
[batch_idx, seq_idx, h]๋ก ์ ๊ทผ - ์ถ๋ ฅ ํ
์:
[batch_idx, seq_idx, out_idx]๋ก ์ ๊ทผ - ๊ฐ์ค์น: ์ ํ ๋ ์ด์ด์์
[out_idx, h]๋ก ์ ๊ทผ
- ์
๋ ฅ ํ
์:
-
์ฐ์ฐ ํ๋ฆ:
- ์ํ์ค๋น LayerNorm ํต๊ณ๋์ ํ ๋ฒ๋ง ๊ณ์ฐ
- ๋ชจ๋ ์ถ๋ ฅ ์ฐจ์์ ์ ๊ทํ๋ ๊ฐ์ ์ฌ์ฌ์ฉ
- ์ ๊ทํ์ ์ ํ ๋ณํ์ ๊ฒฐํฉ
-
์ฑ๋ฅ:
- ํต๊ณ๋์ ์ค๋ณต ๊ณ์ฐ ๋ฐฉ์ง
- ์ฐ์ฐ์ ๊ฒฐํฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ต์ํ
rebind[Scalar[dtype]]๋ก ์ ์ ํ ํ์ ์บ์คํ ์ฌ์ฉ
์ฝ๋ ์คํ
ํจ์ ๊ตฌํ์ ํ ์คํธํ๋ ค๋ฉด ๋ค์์ ์คํํ์ธ์:
pixi run p22 --fused
pixi run -e amd p22 --fused
uv run poe p22 --fused
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
Testing with dimensions: [4, 4, 8] -> [4, 4, 16]
โ
Loaded Mojo operations library
============================================================
Puzzle 22: FUSED Algorithm Test & Benchmark
============================================================
๐งช Correctness Testing for FUSED Algorithm
==================================================
Testing Reference PyTorch Implementation
-----------------------------------------------
โ
Reference PyTorch
Max difference: 0.00e+00
Result: โ
CORRECT
Testing CPU Implementation
---------------------------------
โ
Using Mojo fused kernel (CPU)
Max difference: 1.86e-08
Result: โ
CORRECT
Testing GPU Fused Implementation
---------------------------------------
โ
Using Mojo fused kernel (GPU)
Max difference: 1.86e-08
Result: โ
CORRECT
Correctness Summary:
- Reference: โ
CORRECT
- CPU: โ
CORRECT
- GPU fused: โ
CORRECT
Overall Correctness: โ
ALL CORRECT
โก Benchmarking CPU vs GPU FUSED
----------------------------------------
Testing CPU performance...
CPU: 3144.75ms (50 iterations)
Testing GPU fused performance...
GPU fused: 3116.11ms (50 iterations)
GPU fused vs CPU: 1.01x faster
GPU fused wins!
FUSED Algorithm Test Completed!
์๋ฃจ์
def minimal_fused_kernel[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
output_dim: Int,
OutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
WeightLayout: TensorLayout,
BiasLayout: TensorLayout,
dtype: DType = DType.float32,
](
output: TileTensor[mut=True, dtype, OutputLayout, MutAnyOrigin],
input: TileTensor[mut=True, dtype, InputLayout, MutAnyOrigin],
ln_weight: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
ln_bias: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
linear_weight: TileTensor[mut=True, dtype, WeightLayout, MutAnyOrigin],
linear_bias: TileTensor[mut=True, dtype, BiasLayout, MutAnyOrigin],
):
"""Minimal fused kernel - one thread per sequence position to avoid redundancy.
"""
# Grid: (batch_size, seq_len) - one thread block per sequence position
# Block: (1,) - single thread per sequence position to avoid redundant computation
var batch_idx = block_idx.x
var seq_idx = block_idx.y
if batch_idx >= batch_size or seq_idx >= seq_len:
return
var output_lt = output.to_layout_tensor()
var input_lt = input.to_layout_tensor()
var ln_weight_lt = ln_weight.to_layout_tensor()
var ln_bias_lt = ln_bias.to_layout_tensor()
var linear_weight_lt = linear_weight.to_layout_tensor()
var linear_bias_lt = linear_bias.to_layout_tensor()
# Step 1: Compute LayerNorm statistics once per sequence position
var sum_val: Scalar[dtype] = 0
var sq_sum: Scalar[dtype] = 0
comptime for h in range(hidden_dim):
var val = input_lt[batch_idx, seq_idx, h]
sum_val += rebind[Scalar[dtype]](val)
sq_sum += rebind[Scalar[dtype]](val * val)
var mean_val = sum_val / hidden_dim
var var_val = (sq_sum / hidden_dim) - (mean_val * mean_val)
var inv_std = 1.0 / sqrt(var_val + 1e-5)
# Step 2: Compute all outputs for this sequence position
comptime for out_idx in range(output_dim):
var acc: Scalar[dtype] = 0
comptime for h in range(hidden_dim):
var input_val = input_lt[batch_idx, seq_idx, h]
var normalized = (input_val - mean_val) * inv_std * rebind[
Scalar[dtype]
](ln_weight_lt[h]) + rebind[Scalar[dtype]](ln_bias_lt[h])
acc += rebind[Scalar[dtype]](
normalized * linear_weight_lt[out_idx, h]
)
output_lt[batch_idx, seq_idx, out_idx] = acc + rebind[Scalar[dtype]](
linear_bias_lt[out_idx]
)
ํจ์ ๊ตฌํ์ ์ฐ์ฐ๋ค์ ํจ์จ์ ์ผ๋ก ๊ฒฐํฉํฉ๋๋ค:
-
์ค๋ ๋ ๊ตฌ์ฑ:
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
[batch_size, seq_len]) - ์ํ์ค ์์น๋น ๋จ์ผ ์ค๋ ๋
- ์ค๋ ๋ ์ธ๋ฑ์ค:
batch_idx = block_idx.x,seq_idx = block_idx.y
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
-
LayerNorm ๋จ๊ณ:
- ์ํ์ค ์์น์ ๋ํ ํฉ๊ณ์ ์ ๊ณฑํฉ ๊ณ์ฐ
- ํ๊ท ๊ณ์ฐ: \[\Large \mu = \frac{1}{H} \sum_{i=1}^{H} x_i \]
- ๋ถ์ฐ ๊ณ์ฐ: \[\Large \sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2 \]
- ์ญํ์คํธ์ฐจ ๊ณ์ฐ: \[\Large \text{inv_std} = \frac{1}{\sqrt{\sigma^2 + \epsilon}} \]
-
Linear ๋จ๊ณ:
- ๊ฐ ์ถ๋ ฅ ์ฐจ์์ ๋ํด:
- ์ ๊ทํ๋ ๊ฐ ๊ณ์ฐ: \[\Large \text{normalized} = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
- ์ ํ ๊ฐ์ค์น์ ๊ณฑํ๊ณ ๋์ : \[\Large \text{acc} = \sum_{h=1}^{H} \text{normalized}h \cdot W{out,h} \]
- ์ ํ bias ์ถ๊ฐ: \[\Large \text{output} = \text{acc} + b_{out} \]
- ๊ฒฐ๊ณผ๋ฅผ
output[batch_idx, seq_idx, out_idx]์ ์ ์ฅ
- ๊ฐ ์ถ๋ ฅ ์ฐจ์์ ๋ํด:
-
์ฑ๋ฅ ์ต์ ํ:
- ๋ ์ฐ์ฐ์ ์ํ ๋จ์ผ ์ปค๋ ์คํ
- ๊ณ์ฐ๋ ํต๊ณ๋ ์ฌ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ต์ํ
- ์ค๊ฐ ํ ์ ํ ๋น ๋ถํ์
- ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
์ด ๊ตฌํ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋๊ณผ ์ปค๋ ์คํ ์ค๋ฒํค๋๋ฅผ ์ค์ฌ ์ธํจ์ ๋ฒ์ ๋ณด๋ค ๋ ๋์ ์ฑ๋ฅ์ ๋ฌ์ฑํฉ๋๋ค.
์ปค๋ ํจ์ ์ ์ฅ์
์ด ํผ์ฆ์์ LayerNorm + Linear ์ฐ์ฐ์ ๊ตฌํํ๋ ๋ ๊ฐ์ง ๋ฐฉ์์ ์ดํด๋ณด์์ต๋๋ค:
-
์ธํจ์ ๊ตฌํ:
- LayerNorm๊ณผ Linear๋ฅผ ๋ณ๋์ ์ปค๋๋ก ์คํ
- ๊ตฌํ์ด ๊ฐ๋จํ์ง๋ง ๋ ํจ์จ์
- ๋์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋
- ์ฌ๋ฌ ๋ฒ์ ์ปค๋ ์คํ
- ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ: 3183.57ms (GPU)
-
ํจ์ ๊ตฌํ:
- ๋ ์ฐ์ฐ์ ๊ฒฐํฉํ ๋จ์ผ ์ปค๋
- ๋ ๋ณต์กํ์ง๋ง ํจ์ฌ ํจ์จ์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ๋จ์ผ ์ปค๋ ์คํ
- ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ: 3116.11ms (GPU)
๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ต์ ํ
-
๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ ๊ฑฐ:
- ์ฐ์ฐ ๊ฐ ์ค๊ฐ ํ ์ ํ ๋น ๋ถํ์
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ/์ฐ๊ธฐ ๊ฐ์
- ์ ํ ๋ณํ์ ์ํ ์ ๊ทํ๋ ๊ฐ ์ฌ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ ๊ฐ๋ฅ : \[\Large \text{reduction} = \frac{\text{unfused_bandwidth} - \text{fused_bandwidth}}{\text{unfused_bandwidth}}\]
-
์บ์ ํจ์จ:
- L1/L2 ์บ์ ํ์ฉ๋ ํฅ์
- ์บ์ ๋ฏธ์ค ๊ฐ์
- ๊ฐ์ ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ ๋์ ์ฐ์ ๊ฐ๋
์ค๋ฒํค๋ ๊ฐ์
-
์ปค๋ ์คํ ์ต์ ํ:
- ์ฌ๋ฌ ๋ฒ ๋์ ๋จ์ผ ์ปค๋ ์คํ
- ๋๋ผ์ด๋ฒ ์ค๋ฒํค๋ ๊ฐ์
- ๋๊ธฐํ ์ง์ ๊ฐ์
- ๋ฉ๋ชจ๋ฆฌ ํ ๋น ํ์ ๊ฐ์
-
๋ฆฌ์์ค ๊ด๋ฆฌ:
- ์ฐ์ฐ ๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฌ์ฉ
- ๋ ์ง์คํฐ ํ์ฉ๋ ํฅ์
- ์ค๋ ๋ ์ ์ ์จ ๊ฐ์
- GPU ํ์ฉ๋ฅ ํฅ์
์ฑ๋ฅ ํน์ฑ
-
ํ์ฅ์ฑ:
- ์ ๋ ฅ ํฌ๊ธฐ์ ๋ฐ๋ฅธ ์ฑ๋ฅ ํ์ฅ์ฑ ํฅ์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ๋ณ๋ชฉ ๊ฐ์
- GPU ๋ฆฌ์์ค์ ๋ ํจ์จ์ ์ธ ํ์ฉ
- ๋๊ท๋ชจ ๋ชจ๋ธ์์ ์ฒ๋ฆฌ๋ ํฅ์
-
์์น์ ํจ์จ:
- ์์น ์์ ์ฑ ์ ์ง
- ๋ฐ์ฌ๋ฆผ ์ค์ฐจ ๊ฐ์
- ์ค๊ฐ ๊ฒฐ๊ณผ์ ์ ๋ฐ๋ ํฅ์
- ์ต์ ํ๋ ์ฐ์ฐ ์์
๐ก ํต์ฌ ํต์ฐฐ: ์ปค๋ ํจ์ ์ ํธ๋์คํฌ๋จธ ์ํคํ ์ฒ์ LayerNorm + Linear์ฒ๋ผ ์ ๊ฒฝ๋ง์์ ์์ฃผ ํจ๊ป ์ฌ์ฉ๋๋ ์ฐ์ฐ์ ํนํ ์ ๋ฆฌํฉ๋๋ค. ์ ๋ ฅ ํฌ๊ธฐ๊ฐ ํฌ๊ณ ๋ชจ๋ธ์ด ๋ณต์กํ ์๋ก ์ฑ๋ฅ ์ด์ ์ ๋์ฑ ์ปค์ง๋๋ค.
โ๏ธ ์คํ ๊ทธ๋๋ ํตํฉ๊ณผ ์ญ๋ฐฉํฅ ํจ์ค
๊ฐ์
์ด ํผ์ฆ์์๋ ํจ์ LayerNorm + Linear ์ฐ์ฐ์ ์ญ๋ฐฉํฅ ํจ์ค(backward pass) ๊ตฌํ์ ์ดํด๋ด ๋๋ค. ์ญ๋ฐฉํฅ ํจ์ค๋ ๋ค์์ ๋ํ ๊ธฐ์ธ๊ธฐ๋ฅผ ๊ณ์ฐํฉ๋๋ค:
- ์ ๋ ฅ ํ ์
- LayerNorm ์ค์ผ์ผ (\(\gamma\))๊ณผ ์ํํธ (\(\beta\)) ํ๋ผ๋ฏธํฐ
- Linear ๋ ์ด์ด์ ๊ฐ์ค์น ํ๋ ฌ๊ณผ bias
๊ตฌํํ ์ํ์ ์ฐ์ฐ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
LayerNorm ์ญ๋ฐฉํฅ ํจ์ค (์ ๋ ๊ณผ์ ์ ์์ธ ๋ด์ฉ์ LayerNorm ์ญ๋ฐฉํฅ ํจ์ค์ ์์ธ ์ ๋ ์ฐธ์กฐ): \[\Large \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \odot \gamma \odot \frac{1}{\sqrt{\sigma^2 + \epsilon}} (1 - \frac{1}{H} - \frac{(x - \mu)^2}{H(\sigma^2 + \epsilon)}) \]
-
Linear ์ญ๋ฐฉํฅ ํจ์ค: \[\Large \frac{\partial L}{\partial W} = \frac{\partial L}{\partial y}x^T \] \[\Large \frac{\partial L}{\partial b} = \frac{\partial L}{\partial y} \] \[\Large \frac{\partial L}{\partial x} = W^T\frac{\partial L}{\partial y} \]
-
ํจ์ ์ฐ์ฐ์ ์ฐ์ ๋ฒ์น: \[\Large \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y_{linear}} \frac{\partial y_{linear}}{\partial y_{norm}} \frac{\partial y_{norm}}{\partial x} \] ์ฌ๊ธฐ์:
- \(y_{norm}\)์ LayerNorm ์ถ๋ ฅ
- \(y_{linear}\)์ Linear ๋ ์ด์ด ์ถ๋ ฅ
- ์ฐ์ ๋ฒ์น์ด ๋ ์ฐ์ฐ์ ํตํ ์ ์ ํ ๊ธฐ์ธ๊ธฐ ํ๋ฆ์ ๋ณด์ฅ
ํต์ฌ ๊ฐ๋
-
์ค๋ ๋ ๊ตฌ์ฑ:
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
[batch_size, seq_len]) - ์ค๋ณต์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ํ์ค ์์น๋น ๋จ์ผ ์ค๋ ๋
- ๊ฐ ์ํ์ค ์์น์ ๋ชจ๋ ๊ธฐ์ธ๊ธฐ๋ฅผ ํ๋์ ์ค๋ ๋์์ ๊ณ์ฐ
- ์์์ ์ฐ์ฐ์ ์ํ ์ ์ ํ ์ค๋ ๋ ๋๊ธฐํ ๋ณด์ฅ
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก (๊ทธ๋ฆฌ๋:
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ์
๋ ฅ ํ
์:
[batch_idx, seq_idx, h]๋ก ์ ๊ทผ - ์ถ๋ ฅ ํ
์:
[batch_idx, seq_idx, out_idx]๋ก ์ ๊ทผ - ๊ฐ์ค์น: ์ ํ ๋ ์ด์ด์์
[out_idx, h]๋ก ์ ๊ทผ - ์์์ ์ฐ์ฐ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ ๋ณด์ฅ
- ์์ฃผ ์ ๊ทผํ๋ ๋ฐ์ดํฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
- ์
๋ ฅ ํ
์:
-
์ฐ์ฐ ํ๋ฆ:
- ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์์๋ก LayerNorm ํต๊ณ๋ ๊ณ์ฐ
- ๋ชจ๋ ์ถ๋ ฅ ์ฐจ์์ ์ ๊ทํ๋ ๊ฐ ์ฌ์ฌ์ฉ
- ์ ๊ทํ์ ์ ํ ๋ณํ ๊ฒฐํฉ
- ์ ์ฒด ๊ณผ์ ์์ ์์น ์์ ์ฑ ์ ์ง
- ์ฃ์ง ์ผ์ด์ค๋ฅผ ์ ์ ํ ์ฒ๋ฆฌ
-
์ฑ๋ฅ:
- ํต๊ณ๋์ ์ค๋ณต ๊ณ์ฐ ๋ฐฉ์ง
- ์ฐ์ฐ์ ๊ฒฐํฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ต์ํ
rebind[Scalar[dtype]]๋ก ์ ์ ํ ํ์ ์บ์คํ ์ฌ์ฉ- ์ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ ๋ณด์ฅ
- ์คํ ๊ทธ๋๋ ํตํฉ์ ์ต์ ํ
๊ตฌ์ฑ
- ๋ฐฐ์น ํฌ๊ธฐ:
BATCH_SIZE = 4 - ์ํ์ค ๊ธธ์ด:
SEQ_LEN = 4 - ์๋ ์ฐจ์:
HIDDEN_DIM = 8 - ์ถ๋ ฅ ์ฐจ์:
OUTPUT_DIM = 16 - ์ก์ค๋ก :
EPS = 1e-5 - ๋ฐ์ดํฐ ํ์
:
DType.float32
๊ตฌํ (๊ณ ๊ธ)
ํจ์ ์ญ๋ฐฉํฅ ํจ์ค ์ปค๋์ LayerNorm๊ณผ Linear์ ์ญ๋ฐฉํฅ ํจ์ค ์ฐ์ฐ์ ํ๋์ GPU ์ปค๋๋ก ๊ฒฐํฉํฉ๋๋ค. ์ด ๊ตฌํ์ ๋ค์์ ์ ์คํ๊ฒ ๋ค๋ค์ผ ํ๋ ๋์ ์ ์ธ ๊ณผ์ ์ ๋๋ค:
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ
- ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์์์ ์์น ์์ ์ฑ
- ํจ์จ์ ์ธ GPU ํ์ฉ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ฐ์ฐ ๊ฐ ์ ์ ํ ๋๊ธฐํ
def minimal_fused_kernel_backward[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
output_dim: Int,
GradInputLayout: TensorLayout,
GradLnWeightLayout: TensorLayout,
GradLnBiasLayout: TensorLayout,
GradWeightLayout: TensorLayout,
GradBiasLayout: TensorLayout,
GradOutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
WeightLayout: TensorLayout,
](
grad_input: TileTensor[mut=True, dtype, GradInputLayout, MutAnyOrigin],
grad_ln_weight: TileTensor[
mut=True, dtype, GradLnWeightLayout, MutAnyOrigin
],
grad_ln_bias: TileTensor[mut=True, dtype, GradLnBiasLayout, MutAnyOrigin],
grad_weight: TileTensor[mut=True, dtype, GradWeightLayout, MutAnyOrigin],
grad_bias: TileTensor[mut=True, dtype, GradBiasLayout, MutAnyOrigin],
grad_output: TileTensor[mut=False, dtype, GradOutputLayout, ImmutAnyOrigin],
input: TileTensor[mut=False, dtype, InputLayout, ImmutAnyOrigin],
ln_weight: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
ln_bias: TileTensor[mut=False, dtype, LnParamsLayout, ImmutAnyOrigin],
linear_weight: TileTensor[mut=False, dtype, WeightLayout, ImmutAnyOrigin],
):
"""Fused backward kernel using atomic operations for safe gradient accumulation.
"""
# Grid: (batch_size, seq_len) - one thread per sequence position
# Block: (1,) - single thread per sequence position
var batch_idx = block_idx.x
var seq_idx = block_idx.y
if batch_idx >= batch_size or seq_idx >= seq_len:
return
# Initialize gradient tensors to zero (block 0,0 only to avoid UB with atomic ops)
if batch_idx == 0 and seq_idx == 0:
# Initialize grad_ln_weight and grad_ln_bias
comptime for h in range(hidden_dim):
(grad_ln_weight.ptr + h).init_pointee_copy(0)
(grad_ln_bias.ptr + h).init_pointee_copy(0)
# Initialize grad_weight and grad_bias
comptime for out_idx in range(output_dim):
(grad_bias.ptr + out_idx).init_pointee_copy(0)
comptime for h in range(hidden_dim):
(grad_weight.ptr + out_idx * hidden_dim + h).init_pointee_copy(
0
)
# Note: We cannot use barrier() here as it only synchronizes within a block.
# The atomic operations will handle synchronization across blocks.
# Step 1: Recompute forward pass statistics (needed for gradients)
var sum_val: Scalar[dtype] = 0
var sq_sum: Scalar[dtype] = 0
# FILL IN roughly 8 lines
# Step 2: Atomically accumulate gradients w.r.t. linear bias
# FILL IN roughly 4 lines
# Step 3: Atomically accumulate gradients w.r.t. linear weight
# Make sure to use the correct atomic operation to avoid race conditions
# FILL IN roughly 10 lines
# Step 4: Atomically accumulate gradients w.r.t. LayerNorm parameters
# FILL IN roughly 10 lines
# Step 5: Compute gradients w.r.t. input (LayerNorm backward)
# Compute sum terms needed for LayerNorm backward
# Make sure to use the correct atomic operation to avoid race conditions
# FILL IN roughly 12 lines
# Compute actual input gradients (no race conditions here - each thread writes to different positions)
# FILL IN roughly 10 lines
ํต์ฌ ์ต์ ํ:
- ๋ชจ๋ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ์ํ ๋จ์ผ ์ปค๋ ์คํ
- ์์ ํ ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ
- ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ์ค๊ฐ ํ ์ ํ ๋น ๋ถํ์
ํ
-
์ค๋ ๋ ๊ตฌ์ฑ:
- ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก
- ์ํ์ค ์์น๋น ๋จ์ผ ์ค๋ ๋
- ๋ชจ๋ ๊ธฐ์ธ๊ธฐ๋ฅผ ํ๋์ ์ค๋ ๋์์ ๊ณ์ฐ
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
- ์ ๋ ฅ/์ถ๋ ฅ ํ ์์ ๋ํ ๋ณํฉ ์ ๊ทผ
- ๊ฐ์ค์น ํ๋ ฌ์ ๋ํ stride ์ ๊ทผ
- ์์์ ์ฐ์ฐ์ ์ํ ์ ์ ํ ์ ๋ ฌ
-
์ฐ์ฐ ํ๋ฆ:
- ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์์๋ก ํต๊ณ๋ ๊ณ์ฐ
- ์ ๊ทํ๋ ๊ฐ ์ฌ์ฌ์ฉ
- ์์น ์์ ์ฑ ์ ์ง
-
์ฑ๋ฅ:
- ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ต์ํ
- ์ ์ ํ ํ์ ์บ์คํ ์ฌ์ฉ
- ์ ์ ํ ์ ๋ ฌ ๋ณด์ฅ
์ฝ๋ ์คํ
ํจ์ ์ญ๋ฐฉํฅ ํจ์ค ๊ตฌํ์ ํ ์คํธํ๋ ค๋ฉด ๋ค์์ ์คํํ์ธ์:
pixi run p22 --backward
pixi run -e amd p22 --backward
uv run poe p22 --backward
์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
Testing with dimensions: [4, 4, 8] -> [4, 4, 16]
โ
Loaded Mojo operations library
============================================================
Comprehensive Backward Pass Test
Testing Custom LayerNorm + Linear Gradients
============================================================
Testing with dimensions: [4, 4, 8] -> [4, 4, 16]
Testing CPU Backward Pass:
Testing CPU Backward Implementation - Backward Pass
---------------------------------------------------------
Computing PyTorch autograd reference...
Computing Mojo backward implementation (CPU)...
โ
CPU Backward Implementation backward completed
Forward max difference: 1.49e-08
grad_input: 2.98e-08 โ
grad_ln_weight: 5.96e-08 โ
grad_ln_bias: 2.38e-07 โ
grad_linear_weight: 9.54e-07 โ
grad_linear_bias: 0.00e+00 โ
Forward pass: โ
CORRECT
Gradients: โ
CORRECT
Overall: โ
CORRECT
Testing GPU Backward Pass:
Testing GPU Backward Implementation - Backward Pass
---------------------------------------------------------
Computing PyTorch autograd reference...
Computing Mojo backward implementation (GPU)...
โ
GPU Backward Implementation backward completed
Forward max difference: 1.86e-08
grad_input: 4.47e-08 โ
grad_ln_weight: 5.96e-08 โ
grad_ln_bias: 3.58e-07 โ
grad_linear_weight: 9.54e-07 โ
grad_linear_bias: 0.00e+00 โ
Forward pass: โ
CORRECT
Gradients: โ
CORRECT
Overall: โ
CORRECT
Backward Pass Test Summary:
- CPU Backward: โ
CORRECT
- GPU Backward: โ
CORRECT
Overall Result: โ
ALL CORRECT
BACKWARD PASS Test Completed!
์๋ฃจ์
def minimal_fused_kernel_backward[
batch_size: Int,
seq_len: Int,
hidden_dim: Int,
output_dim: Int,
GradInputLayout: TensorLayout,
GradLnWeightLayout: TensorLayout,
GradLnBiasLayout: TensorLayout,
GradWeightLayout: TensorLayout,
GradBiasLayout: TensorLayout,
GradOutputLayout: TensorLayout,
InputLayout: TensorLayout,
LnParamsLayout: TensorLayout,
WeightLayout: TensorLayout,
dtype: DType = DType.float32,
](
grad_input: TileTensor[mut=True, dtype, GradInputLayout, MutAnyOrigin],
grad_ln_weight: TileTensor[
mut=True, dtype, GradLnWeightLayout, MutAnyOrigin
],
grad_ln_bias: TileTensor[mut=True, dtype, GradLnBiasLayout, MutAnyOrigin],
grad_weight: TileTensor[mut=True, dtype, GradWeightLayout, MutAnyOrigin],
grad_bias: TileTensor[mut=True, dtype, GradBiasLayout, MutAnyOrigin],
grad_output: TileTensor[mut=True, dtype, GradOutputLayout, MutAnyOrigin],
input: TileTensor[mut=True, dtype, InputLayout, MutAnyOrigin],
ln_weight: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
ln_bias: TileTensor[mut=True, dtype, LnParamsLayout, MutAnyOrigin],
linear_weight: TileTensor[mut=True, dtype, WeightLayout, MutAnyOrigin],
):
"""Fused backward kernel using atomic operations for safe gradient accumulation.
"""
# Grid: (batch_size, seq_len) - one thread per sequence position
# Block: (1,) - single thread per sequence position
var batch_idx = block_idx.x
var seq_idx = block_idx.y
if batch_idx >= batch_size or seq_idx >= seq_len:
return
var grad_input_lt = grad_input.to_layout_tensor()
var grad_ln_weight_lt = grad_ln_weight.to_layout_tensor()
var grad_ln_bias_lt = grad_ln_bias.to_layout_tensor()
var grad_weight_lt = grad_weight.to_layout_tensor()
var grad_bias_lt = grad_bias.to_layout_tensor()
var grad_output_lt = grad_output.to_layout_tensor()
var input_lt = input.to_layout_tensor()
var ln_weight_lt = ln_weight.to_layout_tensor()
var ln_bias_lt = ln_bias.to_layout_tensor()
var linear_weight_lt = linear_weight.to_layout_tensor()
# Initialize gradient tensors to zero (block 0,0 only to avoid UB with atomic ops)
if batch_idx == 0 and seq_idx == 0:
# Initialize grad_ln_weight and grad_ln_bias
comptime for h in range(hidden_dim):
(grad_ln_weight.ptr + h).init_pointee_copy(0)
(grad_ln_bias.ptr + h).init_pointee_copy(0)
# Initialize grad_weight and grad_bias
comptime for out_idx in range(output_dim):
(grad_bias.ptr + out_idx).init_pointee_copy(0)
comptime for h in range(hidden_dim):
(grad_weight.ptr + out_idx * hidden_dim + h).init_pointee_copy(
0
)
# Note: We cannot use barrier() here as it only synchronizes within a block.
# The atomic operations will handle synchronization across blocks.
# Step 1: Recompute forward pass statistics (needed for gradients)
var sum_val: Scalar[dtype] = 0
var sq_sum: Scalar[dtype] = 0
comptime for h in range(hidden_dim):
var val = input_lt[batch_idx, seq_idx, h]
sum_val += rebind[Scalar[dtype]](val)
sq_sum += rebind[Scalar[dtype]](val * val)
var mean_val = sum_val / hidden_dim
var var_val = (sq_sum / hidden_dim) - (mean_val * mean_val)
var inv_std = 1.0 / sqrt(var_val + 1e-5)
# Step 2: Atomically accumulate gradients w.r.t. linear bias
comptime for out_idx in range(output_dim):
var grad_bias_ptr = grad_bias.ptr + out_idx
_ = Atomic[dtype].fetch_add(
grad_bias_ptr,
rebind[Scalar[dtype]](grad_output_lt[batch_idx, seq_idx, out_idx]),
)
# Step 3: Atomically accumulate gradients w.r.t. linear weight
comptime for out_idx in range(output_dim):
comptime for h in range(hidden_dim):
var input_val = input_lt[batch_idx, seq_idx, h]
var normalized = (input_val - mean_val) * inv_std
var ln_output_val = normalized * rebind[Scalar[dtype]](
ln_weight_lt[h]
) + rebind[Scalar[dtype]](ln_bias_lt[h])
# Atomic gradient accumulation for linear weight
var grad_w = (
grad_output_lt[batch_idx, seq_idx, out_idx] * ln_output_val
)
var grad_weight_ptr = grad_weight.ptr + out_idx * hidden_dim + h
_ = Atomic.fetch_add(grad_weight_ptr, rebind[Scalar[dtype]](grad_w))
# Step 4: Atomically accumulate gradients w.r.t. LayerNorm parameters
comptime for h in range(hidden_dim):
input_val = input_lt[batch_idx, seq_idx, h]
normalized = (input_val - mean_val) * inv_std
# Compute gradient w.r.t. LayerNorm output for this h
var grad_ln_out: Scalar[dtype] = 0
comptime for out_idx in range(output_dim):
grad_ln_out = grad_ln_out + rebind[Scalar[dtype]](
grad_output_lt[batch_idx, seq_idx, out_idx]
* linear_weight_lt[out_idx, h]
)
# Atomic accumulation of LayerNorm parameter gradients
var grad_ln_weight_ptr = grad_ln_weight.ptr + h
var grad_ln_bias_ptr = grad_ln_bias.ptr + h
_ = Atomic[dtype].fetch_add(
grad_ln_weight_ptr, rebind[Scalar[dtype]](grad_ln_out * normalized)
)
_ = Atomic[dtype].fetch_add(
grad_ln_bias_ptr, rebind[Scalar[dtype]](grad_ln_out)
)
# Step 5: Compute gradients w.r.t. input (LayerNorm backward)
# Compute sum terms needed for LayerNorm backward
var sum_grad_normalized: Scalar[dtype] = 0
var sum_grad_normalized_times_normalized: Scalar[dtype] = 0
comptime for h in range(hidden_dim):
h_input_val = input_lt[batch_idx, seq_idx, h]
h_normalized = (h_input_val - mean_val) * inv_std
var h_grad_ln_out: Scalar[dtype] = 0
comptime for out_idx in range(output_dim):
h_grad_ln_out = h_grad_ln_out + rebind[Scalar[dtype]](
grad_output_lt[batch_idx, seq_idx, out_idx]
* linear_weight_lt[out_idx, h]
)
h_grad_norm = h_grad_ln_out * rebind[Scalar[dtype]](ln_weight_lt[h])
sum_grad_normalized = sum_grad_normalized + rebind[Scalar[dtype]](
h_grad_norm
)
sum_grad_normalized_times_normalized = (
sum_grad_normalized_times_normalized
+ rebind[Scalar[dtype]](h_grad_norm * h_normalized)
)
# Compute actual input gradients (no race conditions here - each thread writes to different positions)
comptime for h in range(hidden_dim):
h_input_val = input_lt[batch_idx, seq_idx, h]
h_normalized = (h_input_val - mean_val) * inv_std
var h_grad_ln_out: Scalar[dtype] = 0
comptime for out_idx in range(output_dim):
h_grad_ln_out = h_grad_ln_out + rebind[Scalar[dtype]](
grad_output_lt[batch_idx, seq_idx, out_idx]
* linear_weight_lt[out_idx, h]
)
h_grad_norm = h_grad_ln_out * rebind[Scalar[dtype]](ln_weight_lt[h])
grad_input_lt[batch_idx, seq_idx, h] = inv_std * (
h_grad_norm
- (sum_grad_normalized / hidden_dim)
- (h_normalized * sum_grad_normalized_times_normalized / hidden_dim)
)
ํจ์ ์ญ๋ฐฉํฅ ํจ์ค ๊ตฌํ์ ์ฐ์ฐ๋ค์ ํจ์จ์ ์ผ๋ก ๊ฒฐํฉํฉ๋๋ค:
-
์ค๋ ๋ ๊ตฌ์ฑ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์:
- ๊ทธ๋ฆฌ๋ ์ฐจ์:
[batch_size, seq_len]์ผ๋ก ์ํ์ค ์์น๋น ํ๋์ ์ค๋ ๋ ๋ธ๋ก - ์ค๋ ๋ ์ธ๋ฑ์ค:
batch_idx = block_idx.x,seq_idx = block_idx.y - ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์:
- ์
๋ ฅ ํ
์:
[batch_size, seq_len, hidden_dim] - ์ถ๋ ฅ ํ
์:
[batch_size, seq_len, output_dim] - ๊ฐ์ค์น ํ๋ ฌ:
[output_dim, hidden_dim] - ๊ธฐ์ธ๊ธฐ: ์
๋ ฅ ๊ธฐ์ธ๊ธฐ์ฉ
[batch_size, seq_len, hidden_dim] - ํ๋ผ๋ฏธํฐ ๊ธฐ์ธ๊ธฐ: LayerNorm์ฉ
[hidden_dim], Linear์ฉ[output_dim, hidden_dim]
- ์
๋ ฅ ํ
์:
- ๊ทธ๋ฆฌ๋ ์ฐจ์:
-
LayerNorm ์ญ๋ฐฉํฅ ํจ์ค ๋จ๊ณ:
- ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์์๋ก ์๋ฐฉํฅ ํจ์ค ํต๊ณ๋์ ์ฌ๊ณ์ฐํฉ๋๋ค:
- ํ๊ท : \[\Large \mu = \frac{1}{H} \sum_{i=1}^{H} x_i \]
- ๋ถ์ฐ: \[\Large \sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2 \]
- ์ญํ์คํธ์ฐจ: \[\Large \text{inv_std} = \frac{1}{\sqrt{\sigma^2 + \epsilon}} \]
- ์ ๊ทํ๋ ๊ฐ์ ๊ณ์ฐํฉ๋๋ค: \[\Large \hat{x} = \frac{x - \mu}{\sqrt{\sigma^2
- \epsilon}} \]
- ๊ธฐ์ธ๊ธฐ๋ฅผ ๊ณ์ฐํฉ๋๋ค:
- ์
๋ ฅ ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial x} = \frac{\partial
L}{\partial y} \odot \gamma \odot \frac{1}{\sqrt{\sigma^2 + \epsilon}} (1
- \frac{1}{H} - \frac{(x - \mu)^2}{H(\sigma^2 + \epsilon)}) \]
- ์ค์ผ์ผ ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial \gamma} = \sum_{i=1}^{H} \frac{\partial L}{\partial y_i} \odot \hat{x}_i \]
- ์ํํธ ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial \beta} = \sum_{i=1}^{H} \frac{\partial L}{\partial y_i} \]
- ์
๋ ฅ ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial x} = \frac{\partial
L}{\partial y} \odot \gamma \odot \frac{1}{\sqrt{\sigma^2 + \epsilon}} (1
- ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์์๋ก ์๋ฐฉํฅ ํจ์ค ํต๊ณ๋์ ์ฌ๊ณ์ฐํฉ๋๋ค:
-
Linear ์ญ๋ฐฉํฅ ํจ์ค ๋จ๊ณ:
- ๊ฐ ์ถ๋ ฅ ์ฐจ์์ ๋ํด:
- Bias ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial b} = \frac{\partial L}{\partial y} \]
- ๊ฐ์ค์น ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial W} = \frac{\partial L}{\partial y}x^T \]
- ์ ๋ ฅ ๊ธฐ์ธ๊ธฐ: \[\Large \frac{\partial L}{\partial x} = W^T\frac{\partial L}{\partial y} \]
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ ์ฌ์ฉ:
- Bias ๊ธฐ์ธ๊ธฐ์ ์ ์ ํ ์ ๋ ฌ๋ก
atomic_add์ฌ์ฉ - ๊ฐ์ค์น ๊ธฐ์ธ๊ธฐ์ ์ ์ ํ ์ ๋ ฌ๋ก
atomic_add์ฌ์ฉ - LayerNorm ํ๋ผ๋ฏธํฐ ๊ธฐ์ธ๊ธฐ์ ์ ์ ํ ์ ๋ ฌ๋ก
atomic_add์ฌ์ฉ
- Bias ๊ธฐ์ธ๊ธฐ์ ์ ์ ํ ์ ๋ ฌ๋ก
- ๊ฐ ์ถ๋ ฅ ์ฐจ์์ ๋ํด:
-
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์ ๋ ฅ/์ถ๋ ฅ ํ ์์ ๋ํ ๋ณํฉ ์ ๊ทผ
- ๊ฐ์ค์น ํ๋ ฌ์ ๋ํ stride ์ ๊ทผ
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ํ ์์์ ์ฐ์ฐ
- ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ
- ์์ฃผ ์ ๊ทผํ๋ ๊ฐ์ ์ํ ๋ ์ง์คํฐ ์ฌ์ฉ
- ๋ชจ๋ ์ฐ์ฐ์ ๋ํ ์ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ
-
์์น ์์ ์ฑ:
- ๋ถ๋ชจ์ ์ก์ค๋ก ์ฒ๋ฆฌ์ ์ฃผ์
- ๊ธฐ์ธ๊ธฐ์ ์ ์ ํ ์ค์ผ์ผ๋ง
- ์์ ์ ์ธ ํต๊ณ๋ ๊ณ์ฐ
rebind[Scalar[dtype]]๋ก ํ์ ์บ์คํ- ์ฃ์ง ์ผ์ด์ค์ ์ ์ ํ ์ฒ๋ฆฌ
- ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์ฐ์ฐ ์์ ์ ์ง
-
์ฑ๋ฅ ์ต์ ํ:
- ๋ชจ๋ ์ฐ์ฐ์ ์ํ ๋จ์ผ ์ปค๋ ์คํ
- ๊ณ์ฐ๋ ํต๊ณ๋ ์ฌ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ต์ํ
- ์ค๊ฐ ํ ์ ํ ๋น ๋ถํ์
- ํจ์จ์ ์ธ ์ค๋ ๋ ํ์ฉ
- ๋๊ธฐํ ์ง์ ๊ฐ์
- ์ต์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ
-
๊ตฌํ ์ธ๋ถ ์ฌํญ:
- ์ปดํ์ผ ํ์ ์์๋ฅผ ์ํ
@parameter์ฌ์ฉ - ํ ์ ์ฐจ์์ ์ ์ ํ ์ฒ๋ฆฌ
- ํจ์จ์ ์ธ ํ์ ์บ์คํ ๊ณผ ๋ณํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์คํ ๊ด๋ฆฌ
- ์ฐ์ฐ ๊ฐ ์ ์ ํ ๋๊ธฐํ
- ์ค๋ฅ ์ฒ๋ฆฌ์ ๊ฒฝ๊ณ ๊ฒ์ฌ
- PyTorch ์คํ ๊ทธ๋๋ ์์คํ ๊ณผ์ ํตํฉ
- ์ปดํ์ผ ํ์ ์์๋ฅผ ์ํ
์ด ๊ตฌํ์ ๋ค์์ ํตํด ์ธํจ์ ๋ฒ์ ๋ณด๋ค ๋ ๋์ ์ฑ๋ฅ์ ๋ฌ์ฑํฉ๋๋ค:
- ์ปค๋ ํจ์ ์ ํตํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋ ์ ๊ฐ
- ์ปค๋ ์คํ ์ค๋ฒํค๋ ์ต์ํ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ
- GPU ๋ฆฌ์์ค์ ํจ์จ์ ํ์ฉ
- ์์น ์์ ์ฑ ์ ์ง
- ๊ธฐ์ธ๊ธฐ ๋์ ์ ์ ์ ํ ์ฒ๋ฆฌ
- ์ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๋ ฌ ๋ณด์ฅ
- ํจ์จ์ ์ธ ์คํ ๊ทธ๋๋ ํตํฉ
ํจ์ ์ญ๋ฐฉํฅ ํจ์ค๋ LayerNorm + Linear ์ฐ์ฐ์ด ์์ฃผ ํจ๊ป ์ฌ์ฉ๋๋ ํธ๋์คํฌ๋จธ ์ํคํ ์ฒ์์ ํนํ ์ค์ํ๋ฉฐ, ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋นํ ์ฑ๋ฅ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
์ฑ๋ฅ ๊ณ ๋ ค ์ฌํญ
์ญ๋ฐฉํฅ ํจ์ค ๊ตฌํ์ ์ค๋ฒํค๋๋ฅผ ์ต์ํํ๊ธฐ ์ํด ์ต์ ํ๋ torch.compile์
์ฌ์ฉํฉ๋๋ค:
# Compilation configuration
torch._dynamo.config.cache_size_limit = 64 # Increase cache
torch._dynamo.config.suppress_errors = True # Handle errors gracefully
torch._dynamo.config.automatic_dynamic_shapes = True # Dynamic shapes
์ด๋ฌํ ์ต์ ํ๊ฐ ์ญ๋ฐฉํฅ ํจ์ค์์ ํนํ ์ค์ํ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์์ ํ ์ ์ฐ์ฐ์ ์ปดํ์ผ ์บ์ฑ์ ์ด์ ์ ๋ฐ์ต๋๋ค
- ๋์ ํ์์ ์ญ๋ฐฉํฅ ํจ์ค์์ ํํ๊ฒ ๋ฐ์ํฉ๋๋ค
- ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์๋ ๊ฐ๊ฑดํ ์ค๋ฅ ์ฒ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค
- ์บ์ ํฌ๊ธฐ๋ ๋ฐ๋ณต์ ์ธ ์ญ๋ฐฉํฅ ํจ์ค ์ฐ์ฐ์ ๋์์ด ๋ฉ๋๋ค
- ์ ์ ํ ์ค๋ฅ ์ฒ๋ฆฌ๋ ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ๋งค์ฐ ์ค์ํฉ๋๋ค
- ์ปดํ์ผ ์ค๋ฒํค๋๋ ํ์ต ์๊ฐ์ ํฐ ์ํฅ์ ์ค ์ ์์ต๋๋ค
์ญ๋ฐฉํฅ ํจ์ค๋ ์ ํ์ฑ์ ์ ์งํ๋ฉด์ ์ปดํ์ผ ์ค๋ฒํค๋๋ฅผ ์ต์ํํ๊ธฐ ์ํด
reduce-overhead ๋ชจ๋๋ก ์ปดํ์ผ๋ฉ๋๋ค. ์ด๊ฒ์ด ํนํ ์ค์ํ ์ด์ ๋:
- ์ญ๋ฐฉํฅ ํจ์ค๋ ํ์ต ์ค์ ๋น๋ฒํ๊ฒ ํธ์ถ๋ฉ๋๋ค
- ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ์ ์์น์ ์ผ๋ก ์์ ์ ์ด์ด์ผ ํฉ๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ต์ ํ๋์ด์ผ ํฉ๋๋ค
- ์์์ ์ฐ์ฐ์๋ ์ ์ ํ ๋๊ธฐํ๊ฐ ํ์ํฉ๋๋ค
- ์คํ ๊ทธ๋๋ ํตํฉ์ด ํจ์จ์ ์ด์ด์ผ ํฉ๋๋ค
LayerNorm ์ญ๋ฐฉํฅ ํจ์ค์ ์์ธ ์ ๋
LayerNorm์ ์ญ๋ฐฉํฅ ํจ์ค ๊ธฐ์ธ๊ธฐ๋ ์ฐ์ ๋ฒ์น์ ์ฃผ์ ๊น๊ฒ ์ ์ฉํ์ฌ ์ ๋๋ฉ๋๋ค. ๋จ๊ณ๋ณ ์ ๋ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
์๋ฐฉํฅ ํจ์ค ์ฐ์ฐ
- ํ๊ท : \(\mu = \frac{1}{H} \sum_{i=1}^{H} x_i\)
- ๋ถ์ฐ: \(\sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2\)
- ์ ๊ทํ๋ ๊ฐ: \(\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}\)
- ์ต์ข ์ถ๋ ฅ: \(y = \gamma \odot \hat{x} + \beta\)
์ฐ์ ๋ฒ์น ์ ์ฉ
\(\frac{\partial L}{\partial x}\)๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํด ์ฐ์ ๋ฒ์น์ ์ ์ฉํฉ๋๋ค: \[\Large \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial \hat{x}} \frac{\partial \hat{x}}{\partial x}\]
๊ธฐ์ธ๊ธฐ ๊ตฌ์ฑ ์์
์ถ๋ ฅ์์ ์ ๊ทํ๋ ๊ฐ์ผ๋ก
- \(\frac{\partial y}{\partial \hat{x}} = \gamma\) (์์๋ณ ๊ณฑ์ )
์ ๊ทํ๋ ๊ฐ์์ ์ ๋ ฅ์ผ๋ก
๊ธฐ์ธ๊ธฐ \(\frac{\partial \hat{x}}{\partial x}\)์๋ ์ธ ๊ฐ์ง ๊ตฌ์ฑ ์์๊ฐ ์์ต๋๋ค:
- ๋ถ์๋ฅผ ํตํ ์ง์ ์ ํจ๊ณผ: \(\frac{1}{\sqrt{\sigma^2 + \epsilon}}\)
- ํ๊ท ์ ํตํ ๊ฐ์ ์ ํจ๊ณผ: \(-\frac{1}{H} \frac{1}{\sqrt{\sigma^2 + \epsilon}}\)
- ๋ถ์ฐ์ ํตํ ๊ฐ์ ์ ํจ๊ณผ: \(-\frac{(x - \mu)}{H(\sigma^2 + \epsilon)^{3/2}} (x
- \mu)\)
ํญ ๊ฒฐํฉ
์ ๊ทํ ํญ์ ํตํ ๊ธฐ์ธ๊ธฐ๋ ๋ค์๊ณผ ๊ฐ์ด ์ ๋ฆฌ๋ฉ๋๋ค: \[\Large \frac{\partial \hat{x}}{\partial x} = \frac{1}{\sqrt{\sigma^2 + \epsilon}} (1 - \frac{1}{H} - \frac{(x - \mu)^2}{H(\sigma^2 + \epsilon)})\]
์ต์ข ๊ธฐ์ธ๊ธฐ ํํ์
๋ชจ๋ ํญ์ ๊ฒฐํฉํ๋ฉด: \[\Large \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \odot \gamma \odot \frac{1}{\sqrt{\sigma^2 + \epsilon}} (1 - \frac{1}{H} - \frac{(x - \mu)^2}{H(\sigma^2 + \epsilon)})\]
ํต์ฌ ํต์ฐฐ
- ์ฐ์ ๋ฒ์น์ x๊ฐ ์ถ๋ ฅ์ ์ํฅ์ ๋ฏธ์น๋ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ๊ณ ๋ คํฉ๋๋ค
- ์ ๊ทํ ํญ \(\sqrt{\sigma^2 + \epsilon}\)์ ๋ถ์์ ๋ถ๋ชจ ๋ชจ๋์ ๋ฑ์ฅํฉ๋๋ค
- ํ๊ท ๊ณผ ๋ถ์ฐ ํญ์ ๊ธฐ์ธ๊ธฐ ํ๋ฆ์ ์ถ๊ฐ ๊ฒฝ๋ก๋ฅผ ์์ฑํฉ๋๋ค
- ์ต์ข ํํ์์ ๋ชจ๋ ํจ๊ณผ๋ฅผ ํ๋์ ํจ์จ์ ์ธ ๊ณ์ฐ์ผ๋ก ๊ฒฐํฉํฉ๋๋ค
๊ตฌํ ์ ๊ณ ๋ ค ์ฌํญ
- ๊ธฐ์ธ๊ธฐ๊ฐ \(\gamma\)์ ์ค์ผ์ผ๋ง ํจ๊ณผ๋ฅผ ์ ์ ํ ๋ฐ์ํฉ๋๋ค
- ํ๊ท ๊ณผ ๋ถ์ฐ์ ์ ๊ทํ ํจ๊ณผ๊ฐ ๋ณด์กด๋ฉ๋๋ค
- ์์น ์์ ์ฑ ํญ \(\epsilon\)์ด ์ ์ง๋ฉ๋๋ค
- ๊ธฐ์ธ๊ธฐ๊ฐ ์๋ ์ฐจ์ H ์ ์ฒด์ ๊ฑธ์ณ ์ ์ ํ ์ค์ผ์ผ๋ง๋ฉ๋๋ค
- ์์น ์์ ์ฑ์ ์ํด ์ฐ์ฐ ์์๊ฐ ์๋ฐฉํฅ ํจ์ค์ ์ผ์นํฉ๋๋ค
์ด ์ ๋๋ฅผ ํตํด ์ญ๋ฐฉํฅ ํจ์ค๊ฐ ์๋ฐฉํฅ ํจ์ค์ ๋์ผํ ์์น์ ํน์ฑ์ ์ ์งํ๋ฉด์ ํ์ํ ๋ชจ๋ ๊ธฐ์ธ๊ธฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ณ์ฐํ ์ ์์ต๋๋ค.
Puzzle 23: GPU ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจํด
๊ฐ์
Part VI: ํจ์ํ GPU ํ๋ก๊ทธ๋๋ฐ์์๋ GPU ์ฐ์ฐ์ ์ํ Mojo์ ๊ณ ์์ค ํ๋ก๊ทธ๋๋ฐ ํจํด์ ์๊ฐํฉ๋๋ค. ๋ฒกํฐํ, ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ, ์ฑ๋ฅ ํ๋์ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ ํจ์ํ ์ ๊ทผ ๋ฐฉ์์ ๋ฐฐ์ฐ๋ฉฐ, ์๋ GPU ์ปค๋ ํ๋ก๊ทธ๋๋ฐ์ ๋์ฒดํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: ํ๋ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ฑ๋ฅ์ ์ํด ์ฐ์ํจ์ ํฌ๊ธฐํ ํ์๊ฐ ์์ต๋๋ค - Mojo์ ํจ์ํ ํจํด์ ๋ ๊ฐ์ง๋ฅผ ๋ชจ๋ ์ ๊ณตํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ
GPU ์คํ ๊ณ์ธต ๊ตฌ์กฐ
GPU ์ค๋ ๋์ SIMD ์ฐ์ฐ ์ฌ์ด์ ๊ทผ๋ณธ์ ์ธ ๊ด๊ณ๋ฅผ ์ดํดํฉ๋๋ค:
GPU Device
โโโ Grid (์ ์ฒด ๋ฌธ์ )
โ โโโ Block 1 (์ค๋ ๋ ๊ทธ๋ฃน, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ)
โ โ โโโ Warp 1 (32๊ฐ ์ค๋ ๋, ๋ก์คํ
์คํ) --> Part VI์์ ํ์ต
โ โ โ โโโ Thread 1 โ SIMD
โ โ โ โโโ Thread 2 โ SIMD
โ โ โ โโโ ... (์ด 32๊ฐ ์ค๋ ๋)
โ โ โโโ Warp 2 (32๊ฐ ์ค๋ ๋)
โ โโโ Block 2 (๋
๋ฆฝ์ ์ธ ๊ทธ๋ฃน)
Mojo๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ๋ค:
- ๊ทธ๋ฆฌ๋/๋ธ๋ก ๊ตฌ์ฑ ์๋ ๊ณ์ฐ
- ์ํ ๊ด๋ฆฌ์ ํฌ๋ช ํ ์ฒ๋ฆฌ
- ์ค๋ ๋ ์ค์ผ์ค๋ง ์๋ ์ต์ ํ
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ ๋ด์ฅ
๐ก ์ฐธ๊ณ : ์ด Part๋ ํจ์ํ ํจํด์ ์ด์ ์ ๋ง์ถ๊ณ ์์ผ๋ฉฐ, ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๊ณ ๊ธ GPU ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ Part VII ์์ ์์ธํ ๋ค๋ฃน๋๋ค.
๋ค ๊ฐ์ง ๊ธฐ๋ณธ ํจํด
GPU ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ ํจํด์ ๋ชจ๋ ๋ค๋ฃน๋๋ค:
- Elementwise: ์๋ SIMD ๋ฒกํฐํ๋ฅผ ํตํ ์ต๋ ๋ณ๋ ฌ์ฑ
- Tiled: ์บ์ ์ต์ ํ๋ฅผ ํ์ฉํ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ด ๋์ ์ฒ๋ฆฌ
- ์๋ ๋ฒกํฐํ: SIMD ์ฐ์ฐ์ ๋ํ ์ ๋ฌธ๊ฐ ์์ค์ ์ ์ด
- Mojo vectorize: ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํฌํจํ ์์ ํ ์๋ ๋ฒกํฐํ
ํ๋์ ๋ณด๋ ์ฑ๋ฅ ํจํด
๋ฌธ์ : 1024๊ฐ ์์์ ๋ฒกํฐ ๋ ๊ฐ ๋ํ๊ธฐ (SIZE=1024, SIMD_WIDTH=4)
Elementwise: 256 ์ค๋ ๋ ร 1 SIMD ์ฐ์ฐ = ๋์ ๋ณ๋ ฌ์ฑ
Tiled: 32 ์ค๋ ๋ ร 8 SIMD ์ฐ์ฐ = ์บ์ ์ต์ ํ
Manual: 8 ์ค๋ ๋ ร 32 SIMD ์ฐ์ฐ = ์ต๋ ์ ์ด
Mojo vectorize: 32 ์ค๋ ๋ ร 8 SIMD ์ฐ์ฐ = ์๋ ์์ ์ฑ
๐ ์ค์ ์ฑ๋ฅ ๋ถ์
์ค์ฆ์ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ๋ฅผ ํด์ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค:
๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ (SIZE=1,048,576):
elementwise: 11.34ms โ ๋๊ท๋ชจ์์ ์ต๋ ๋ณ๋ ฌ์ฑ์ด ์ ๋ฆฌ
tiled: 12.04ms โ ์ง์ญ์ฑ๊ณผ ๋ณ๋ ฌ์ฑ์ ๊ท ํ
manual_vectorized: 15.75ms โ ๋จ์ ์ฐ์ฐ์์ ๋ณต์กํ ์ธ๋ฑ์ฑ์ด ๋ถ๋ฆฌ
vectorized: 13.38ms โ ์๋ ์ต์ ํ ์ค๋ฒํค๋
์ ์ ์ง์
ํจ์ํ ํจํด์ ํ์ตํ๊ธฐ ์ ์ ๋ค์ ๋ด์ฉ์ ์ต์ํด์ผ ํฉ๋๋ค:
- ๊ธฐ๋ณธ GPU ๊ฐ๋ : ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ, ์ค๋ ๋ ์คํ, SIMD ์ฐ์ฐ
- Mojo ๊ธฐ์ด: ํ๋ผ๋ฏธํฐ ํจ์, ์ปดํ์ผ ํ์ ํน์ํ, ์บก์ฒ ์๋ฏธ๋ก
- TileTensor ์ฐ์ฐ: ๋ก๋, ์ ์ฅ, ํ ์ ์กฐ์
- GPU ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ: ๋ฒํผ ํ ๋น, ํธ์คํธ-๋๋ฐ์ด์ค ๋๊ธฐํ
ํ์ต ๊ฒฝ๋ก
1. Elementwise ์ฐ์ฐ
โ elementwise - ๊ธฐ๋ณธ GPU ํจ์ํ ์ฐ์ฐ
๊ธฐ์ด๋ถํฐ ์์ํฉ๋๋ค: ์๋ ์ค๋ ๋ ๊ด๋ฆฌ์ SIMD ๋ฒกํฐํ.
๋ฐฐ์ธ ๋ด์ฉ:
elementwise๋ฅผ ํ์ฉํ ํจ์ํ GPU ํ๋ก๊ทธ๋๋ฐ- GPU ์ค๋ ๋ ๋ด์ ์๋ SIMD ๋ฒกํฐํ
- ์์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ํ TileTensor ์ฐ์ฐ
- ์ค์ฒฉ ํจ์์์์ ์บก์ฒ ์๋ฏธ๋ก
ํต์ฌ ํจํด:
elementwise[add_function, SIMD_WIDTH, target="gpu"](total_size, ctx)
2. ํ์ผ๋ง ์ฒ๋ฆฌ
โ tile - ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ํ์ผ๋ง ์ฒ๋ฆฌ
elementwise๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ํ์ผ๋ง ํจํด์ ํ์ตํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- ์บ์ ์ต์ ํ๋ฅผ ์ํ ํ์ผ ๊ธฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ
- ํ์ผ ๋ด ์์ฐจ์ SIMD ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ ์์น๊ณผ ์บ์ ์นํ์ ์ ๊ทผ ํจํด
- ์ค๋ ๋-ํ์ผ ๋งคํ vs ์ค๋ ๋-์์ ๋งคํ
ํต์ฌ ํต์ฐฐ: ํ์ผ๋ง์ ๋ณ๋ ฌ ํญ์ ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ๊ณผ ๊ตํํฉ๋๋ค - ๋ ์ ์ ์์ ์ค๋ ๋๊ฐ ๋ ๋์ ์บ์ ํ์ฉ์ผ๋ก ๋ ๋ง์ ์์ ์ ์ํํฉ๋๋ค.
3. ๊ณ ๊ธ ๋ฒกํฐํ
์๋ ์ ์ด์ ์๋ ๋ฒกํฐํ ์ ๋ต์ ํ๊ตฌํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- ๋ช ์์ ์ธ๋ฑ์ค ๊ด๋ฆฌ๋ฅผ ํตํ ์๋ SIMD ์ฐ์ฐ
- ์์ ํ๊ณ ์๋์ ์ธ ๋ฒกํฐํ๋ฅผ ์ํ Mojo์ vectorize ํจ์
- ์ต์ ์ SIMD ์ ๋ ฌ์ ์ํ ์ฒญํฌ ๊ธฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ
- ์๋ ์ ์ด์ ์์ ์ฑ ๊ฐ์ ์ฑ๋ฅ ํธ๋ ์ด๋์คํ
๋ ๊ฐ์ง ์ ๊ทผ๋ฒ:
- ์๋: ์ง์ ์ ์ด, ์ต๋ ์ฑ๋ฅ, ๋ณต์กํ ์ธ๋ฑ์ฑ
- Mojo vectorize: ์๋ ์ต์ ํ, ๋ด์ฅ ์์ ์ฑ, ๊น๋ํ ์ฝ๋
๐ง 4. ์ค๋ ๋ฉ vs SIMD ๊ฐ๋
โ GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋
๋ณ๋ ฌ์ฑ ์์ค ๊ฐ์ ๊ทผ๋ณธ์ ์ธ ๊ด๊ณ๋ฅผ ์ดํดํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- GPU ์ค๋ ๋ฉ ๊ณ์ธต ๊ตฌ์กฐ์ ํ๋์จ์ด ๋งคํ
- GPU ์ค๋ ๋ ๋ด์ SIMD ์ฐ์ฐ
- ํจํด ๋น๊ต์ ์ค๋ ๋-์์ ๋งคํ
- ์ํฌ๋ก๋์ ๋ง๋ ์ฌ๋ฐ๋ฅธ ํจํด ์ ํ
ํต์ฌ ํต์ฐฐ: GPU ์ค๋ ๋๊ฐ ๋ณ๋ ฌ์ฑ์ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๊ณ , SIMD ์ฐ์ฐ์ด ๊ฐ ์ค๋ ๋ ๋ด์์ ๋ฒกํฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๐ 5. Mojo ์ฑ๋ฅ ๋ฒค์น๋งํน
GPU ์ฑ๋ฅ์ ๊ณผํ์ ์ผ๋ก ์ธก์ , ๋ถ์, ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- Mojo์ ๋ด์ฅ ๋ฒค์น๋งํน ํ๋ ์์ํฌ
- GPU ๊ณ ์ ์ ํ์ด๋ฐ ๋ฐ ๋๊ธฐํ ๋ฌธ์
- ์ปดํ์ผ ํ์ ํน์ํ๋ฅผ ํ์ฉํ ํ๋ผ๋ฏธํฐํ๋ ๋ฒค์น๋งํฌ ํจ์
- ์ค์ฆ์ ์ฑ๋ฅ ๋ถ์๊ณผ ํจํด ์ ํ
ํต์ฌ ๊ธฐ๋ฒ: keep()์ ์ฌ์ฉํ์ฌ ๋ฒค์น๋งํฌ ์ฝ๋์ ์ปดํ์ผ๋ฌ ์ต์ ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
์์ํ๊ธฐ
Elementwise ํจํด๋ถํฐ ์์ํ์ฌ ๊ฐ ์น์ ์ ์ฒด๊ณ์ ์ผ๋ก ํ์ตํ์ธ์. ๊ฐ ํผ์ฆ์ ์ด์ ๊ฐ๋ ์ ๊ธฐ๋ฐ์ผ๋ก ์๋ก์ด ์์ค์ ์ ๊ตํจ์ ๋์ ํฉ๋๋ค.
๐ก ์ฑ๊ณต ํ: ๊ฐ ํจํด์ ์ด๋ป๊ฒ๋ฟ๋ง ์๋๋ผ ์๋ฅผ ์ดํดํ๋ ๋ฐ ์ง์คํ์ธ์. ์ฌ๊ธฐ์ ํ์ฑํ๋ ๊ฐ๋ ์ ํ๋ ์์ํฌ๋ GPU ํ๋ก๊ทธ๋๋ฐ ์ ๋ฐ์ ๊ฑธ์ณ ํ์ฉ๋ ๊ฒ์ ๋๋ค.
ํ์ต ๋ชฉํ: Part VI๋ฅผ ๋ง์น๋ฉด, ์ ์์ค GPU ๋ฉ์ปค๋์ฆ ๋์ ํจ์ํ ํจํด์ ๊ด์ ์์ ์ฌ๊ณ ํ ์ ์๊ฒ ๋์ด, ๋ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ณ , ์ฑ๋ฅ์ด ๋ฐ์ด๋๋ฉฐ, ์ด์์ฑ์ด ๋์ GPU ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์์ํ๊ธฐ: elementwise - ๊ธฐ๋ณธ GPU ํจ์ํ ์ฐ์ฐ ์์ ํจ์ํ GPU ํ๋ก๊ทธ๋๋ฐ์ ์์ํ์ธ์.
elementwise - ๊ธฐ๋ณธ GPU ํจ์ํ ์ฐ์ฐ
์ด ํผ์ฆ์ Mojo์ ํจ์ํ elementwise ํจํด์ ์ฌ์ฉํ์ฌ ๋ฒกํฐ ๋ง์
์ ๊ตฌํํฉ๋๋ค. ๊ฐ
์ค๋ ๋๊ฐ ์๋์ผ๋ก ์ฌ๋ฌ SIMD ์์๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ, ํ๋ GPU ํ๋ก๊ทธ๋๋ฐ์ด ์ด๋ป๊ฒ ์ ์์ค
์ธ๋ถ ์ฌํญ์ ์ถ์ํํ๋ฉด์๋ ๋์ ์ฑ๋ฅ์ ์ ์งํ๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: elementwise ํจ์๋ ์ค๋ ๋ ๊ด๋ฆฌ, SIMD ๋ฒกํฐํ, ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
elementwise๋ฅผ ํ์ฉํ ํจ์ํ GPU ํ๋ก๊ทธ๋๋ฐ- GPU ์ค๋ ๋ ๋ด์ ์๋ SIMD ๋ฒกํฐํ
- ์์ ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ํ TileTensor ์ฐ์ฐ
- GPU ์ค๋ ๋ ๊ณ์ธต ๊ตฌ์กฐ vs SIMD ์ฐ์ฐ
- ์ค์ฒฉ ํจ์์์์ ์บก์ฒ ์๋ฏธ๋ก
์ํ์ ์ฐ์ฐ์ ๋จ์ํ ์์๋ณ ๋ง์ ์ ๋๋ค: \[\Large \text{output}[i] = a[i] + b[i]\]
์ด ๊ตฌํ์ Mojo์์์ ๋ชจ๋ GPU ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ ์ฉํ ์ ์๋ ๊ธฐ๋ณธ ํจํด์ ๋ค๋ฃน๋๋ค.
์ค์
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 1024 - ๋ฐ์ดํฐ ํ์
:
DType.float32 - SIMD ํญ: ํ๊ฒ ์์กด์ (GPU ์ํคํ ์ฒ์ ๋ฐ์ดํฐ ํ์ ์ ๋ฐ๋ผ ๊ฒฐ์ )
- ๋ ์ด์์:
row_major[SIZE]()(1D ํ ์ฐ์ )
์์ฑํ ์ฝ๋
comptime SIZE = 1024
comptime rank = 1
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
comptime dtype = DType.float32
comptime SIMD_WIDTH = simd_width_of[dtype, target=get_gpu_target()]()
def elementwise_add[
LayoutT: TensorLayout, dtype: DType, simd_width: Int, rank: Int, size: Int
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def add[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var idx = indices[0]
print("idx:", idx)
# FILL IN (2 to 4 lines)
elementwise[add, SIMD_WIDTH, target="gpu"](size, ctx)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p23/p23.mojo
ํ
1. ํจ์ ๊ตฌ์กฐ ์ดํดํ๊ธฐ
elementwise ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ์ ํํ ์๊ทธ๋์ฒ๋ฅผ ๊ฐ์ง ์ค์ฒฉ ํจ์๋ฅผ ๊ธฐ๋ํฉ๋๋ค:
@parameter
@always_inline
def your_function[simd_width: Int, rank: Int](indices: IndexList[rank]) capturing -> None:
# ๊ตฌํ ์ฝ๋
๊ฐ ๋ถ๋ถ์ด ์ค์ํ ์ด์ :
@parameter: ์ต์ ์ GPU ์ฝ๋ ์์ฑ์ ์ํ ์ปดํ์ผ ํ์ ํน์ํ๋ฅผ ํ์ฑํํฉ๋๋ค@always_inline: GPU ์ปค๋์์ ํจ์ ํธ์ถ ์ค๋ฒํค๋๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํด ์ธ๋ผ์ด๋์ ๊ฐ์ ํฉ๋๋คcapturing: ์ธ๋ถ ์ค์ฝํ์ ๋ณ์(์ ์ถ๋ ฅ ํ ์)์ ์ ๊ทผํ ์ ์๊ฒ ํฉ๋๋คIndexList[rank]: ๋ค์ฐจ์ ์ธ๋ฑ์ฑ์ ์ ๊ณตํฉ๋๋ค (๋ฒกํฐ๋ rank=1, ํ๋ ฌ์ rank=2)
2. ์ธ๋ฑ์ค ์ถ์ถ๊ณผ SIMD ์ฒ๋ฆฌ
idx = indices[0] # 1D ์ฐ์ฐ์ ์ํ ์ ํ ์ธ๋ฑ์ค ์ถ์ถ
์ด idx๋ ๋จ์ผ ์์๊ฐ ์๋ SIMD ๋ฒกํฐ์ ์์ ์์น๋ฅผ ๋ํ๋
๋๋ค.
SIMD_WIDTH=4 (GPU ์์กด์ )์ธ ๊ฒฝ์ฐ:
- Thread 0์
idx=0๋ถํฐ ์์ํ์ฌ ์์[0, 1, 2, 3]์ ์ฒ๋ฆฌ - Thread 1์
idx=4๋ถํฐ ์์ํ์ฌ ์์[4, 5, 6, 7]์ ์ฒ๋ฆฌ - Thread 2๋
idx=8๋ถํฐ ์์ํ์ฌ ์์[8, 9, 10, 11]์ ์ฒ๋ฆฌ - ์ด๋ฐ ์์ผ๋ก ๊ณ์โฆ
3. SIMD ๋ก๋ ํจํด
a_simd = a.aligned_load[simd_width](Index(idx)) # ์ฐ์ float 4๊ฐ ๋ก๋ (GPU ์์กด์ )
b_simd = b.aligned_load[simd_width](Index(idx)) # ์ฐ์ float 4๊ฐ ๋ก๋ (GPU ์์กด์ )
๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์ 0์ ์ฐจ์ ์คํ์
์
๋๋ค (1D ๋ฒกํฐ์์๋ ํญ์ 0). ์ด ์ฐ์ฐ์ ํ
๋ฒ์ ๋ฒกํฐํ๋ ์ฒญํฌ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํฉ๋๋ค. ๋ก๋๋๋ ์ ํํ ์์ ์๋ GPU์
SIMD ๋ฅ๋ ฅ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
4. ๋ฒกํฐ ์ฐ์ฐ
result = a_simd + b_simd # 4๊ฐ ์์์ SIMD ๋ง์
์ ๋์์ ์ํ (GPU ์์กด์ )
์ ์ฒด SIMD ๋ฒกํฐ์ ๊ฑธ์ณ ์์๋ณ ๋ง์ ์ ๋ณ๋ ฌ๋ก ์ํํฉ๋๋ค - 4๊ฐ์ ๊ฐ๋ณ ์ค์นผ๋ผ ๋ง์ ๋ณด๋ค ํจ์ฌ ๋น ๋ฆ ๋๋ค.
5. SIMD ์ ์ฅ
output.store[simd_width](idx, 0, result) # 4๊ฐ ๊ฒฐ๊ณผ๋ฅผ ํ ๋ฒ์ ์ ์ฅ (GPU ์์กด์ )
์ ์ฒด SIMD ๋ฒกํฐ๋ฅผ ํ ๋ฒ์ ์ฐ์ฐ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ์ ๋ค์ ๊ธฐ๋กํฉ๋๋ค.
6. elementwise ํจ์ ํธ์ถ
elementwise[your_function, SIMD_WIDTH, target="gpu"](total_size, ctx)
total_size๋ ๋ชจ๋ ์์๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํดa.size()๋ก ์ค์ ํด์ผ ํฉ๋๋ค- GPU๋ ์คํํ ์ค๋ ๋ ์๋ฅผ ์๋์ผ๋ก ๊ฒฐ์ ํฉ๋๋ค:
total_size // SIMD_WIDTH
7. ๋๋ฒ๊น ํต์ฌ ํฌ์ธํธ
ํ
ํ๋ฆฟ์ ์๋ print("idx:", idx)์ ์ฃผ๋ชฉํ์ธ์. ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด
์ถ๋ ฅ๋ฉ๋๋ค:
idx: 0, idx: 4, idx: 8, idx: 12, ...
๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ SIMD ์ฒญํฌ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ, SIMD_WIDTH (GPU ์์กด์ ) ๊ฐ๊ฒฉ์ผ๋ก
์๋ ๋ฐฐ์น๋จ์ ๋ณด์ฌ์ค๋๋ค.
์ฝ๋ ์คํ
ํ์ด๋ฅผ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ ์คํํ์ธ์:
pixi run p23 --elementwise
pixi run -e amd p23 --elementwise
pixi run -e apple p23 --elementwise
uv run poe p23 --elementwise
ํผ์ฆ์ด ์์ง ํ๋ฆฌ์ง ์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค:
SIZE: 1024
simd_width: 4
...
idx: 404
idx: 408
idx: 412
idx: 416
...
out: HostBuffer([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])
expected: HostBuffer([1.0, 5.0, 9.0, ..., 4085.0, 4089.0, 4093.0])
์๋ฃจ์
def elementwise_add[
LayoutT: TensorLayout, dtype: DType, simd_width: Int, rank: Int, size: Int
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def add[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var idx = indices[0]
# Convert inside GPU kernel to avoid host-captured LayoutTensor issues
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
# Note: This is thread-local SIMD - each thread processes its own vector of data
# we'll later better see this hierarchy in Mojo:
# SIMD within threads, warp across threads, block across warps
var a_simd = a_lt.aligned_load[width=simd_width](Index(idx))
var b_simd = b_lt.aligned_load[width=simd_width](Index(idx))
var ret = a_simd + b_simd
out_lt.store[simd_width](Index(idx), ret)
elementwise[add, SIMD_WIDTH, target="gpu"](size, ctx)
Mojo์ elementwise ํจ์ํ ํจํด์ ํ๋ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ํ ๋ช ๊ฐ์ง ๊ธฐ๋ณธ ๊ฐ๋ ์ ์๊ฐํฉ๋๋ค:
1. ํจ์ํ ์ถ์ํ ์ฒ ํ
elementwise ํจ์๋ ๊ธฐ์กด GPU ํ๋ก๊ทธ๋๋ฐ์์์ ํจ๋ฌ๋ค์ ์ ํ์ ๋ํ๋
๋๋ค:
์ ํต์ ์ธ CUDA/HIP ๋ฐฉ์:
# ์๋ ์ค๋ ๋ ๊ด๋ฆฌ
idx = thread_idx.x + block_idx.x * block_dim.x
if idx < size:
output[idx] = a[idx] + b[idx]; // ์ค์นผ๋ผ ์ฐ์ฐ
Mojo ํจ์ํ ๋ฐฉ์:
# ์๋ ๊ด๋ฆฌ + SIMD ๋ฒกํฐํ
elementwise[add_function, simd_width, target="gpu"](size, ctx)
elementwise๊ฐ ์ถ์ํํ๋ ๊ฒ๋ค:
- ์ค๋ ๋ ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: ๋ธ๋ก/๊ทธ๋ฆฌ๋ ์ฐจ์์ ๊ณ์ฐํ ํ์ ์์
- ๊ฒฝ๊ณ ๊ฒ์ฌ: ๋ฐฐ์ด ๊ฒฝ๊ณ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ๋ด์ฅ
- SIMD ์ค์ผ์คํธ๋ ์ด์ : ๋ฒกํฐํ๋ฅผ ํฌ๋ช ํ๊ฒ ์ฒ๋ฆฌ
- GPU ํ๊ฒ ์ ํ: ๋ค์ํ GPU ์ํคํ ์ฒ์์ ๋์
2. ์ฌ์ธต ๋ถ์: ์ค์ฒฉ ํจ์ ์ํคํ ์ฒ
@parameter
@always_inline
def add[simd_width: Int, rank: Int](indices: IndexList[rank]) capturing -> None:
๋งค๊ฐ๋ณ์ ๋ถ์:
@parameter: ์ด ๋ฐ์ฝ๋ ์ดํฐ๋ ์ปดํ์ผ ํ์ ํน์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ ๊ณ ์ ํsimd_width์rank์ ๋ํด ํจ์๊ฐ ๋ณ๋๋ก ์์ฑ๋์ด ์ ๊ทน์ ์ธ ์ต์ ํ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.@always_inline: GPU ์ฑ๋ฅ์ ๋งค์ฐ ์ค์ํฉ๋๋ค - ์ฝ๋๋ฅผ ์ปค๋์ ์ง์ ๋ด์ฅํ์ฌ ํจ์ ํธ์ถ ์ค๋ฒํค๋๋ฅผ ์ ๊ฑฐํฉ๋๋ค.capturing: ๋ ์์ปฌ ์ค์ฝํ์ ํ์ฑํํฉ๋๋ค - ๋ด๋ถ ํจ์๊ฐ ๋ช ์์ ๋งค๊ฐ๋ณ์ ์ ๋ฌ ์์ด ์ธ๋ถ ์ค์ฝํ์ ๋ณ์์ ์ ๊ทผํ ์ ์์ต๋๋ค.IndexList[rank]: ์ฐจ์ ๋ฌด๊ด ์ธ๋ฑ์ฑ์ ์ ๊ณตํฉ๋๋ค - ๋์ผํ ํจํด์ด 1D ๋ฒกํฐ, 2D ํ๋ ฌ, 3D ํ ์ ๋ฑ์์ ์๋ํฉ๋๋ค.
3. SIMD ์คํ ๋ชจ๋ธ ์ฌ์ธต ๋ถ์
idx = indices[0] # ์ ํ ์ธ๋ฑ์ค: 0, 4, 8, 12... (GPU ์์กด์ ๊ฐ๊ฒฉ)
a_simd = a.aligned_load[simd_width](Index(idx)) # ๋ก๋: [a[0:4], a[4:8], a[8:12]...] (๋ก๋๋น 4๊ฐ ์์)
b_simd = b.aligned_load[simd_width](Index(idx)) # ๋ก๋: [b[0:4], b[4:8], b[8:12]...] (๋ก๋๋น 4๊ฐ ์์)
ret = a_simd + b_simd # SIMD: 4๊ฐ ๋ง์
์ ๋ณ๋ ฌ ์ํ (GPU ์์กด์ )
output.store[simd_width](Index(global_start), ret) # ์ ์ฅ: 4๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋์ ์ ์ฅ (GPU ์์กด์ )
์คํ ๊ณ์ธต ๊ตฌ์กฐ ์๊ฐํ:
GPU ์ํคํ
์ฒ:
โโโ Grid (์ ์ฒด ๋ฌธ์ )
โ โโโ Block 1 (์ฌ๋ฌ Warp)
โ โ โโโ Warp 1 (32๊ฐ ์ค๋ ๋) --> Warp๋ ๋ค์ Part VI์์ ํ์ต
โ โ โ โโโ Thread 1 โ SIMD[4๊ฐ ์์] โ ํ์ฌ ์ด์ (GPU ์์กด์ ํญ)
โ โ โ โโโ Thread 2 โ SIMD[4๊ฐ ์์]
โ โ โ โโโ ...
โ โ โโโ Warp 2 (32๊ฐ ์ค๋ ๋)
โ โโโ Block 2 (์ฌ๋ฌ Warp)
SIMD_WIDTH=4์ธ 1024๊ฐ ์์ ๋ฒกํฐ์ ๊ฒฝ์ฐ (GPU ์์):
- ํ์ํ ์ด SIMD ์ฐ์ฐ ์: 1024 รท 4 = 256
- GPU ์คํ: 256๊ฐ ์ค๋ ๋ (1024 รท 4)
- ๊ฐ ์ค๋ ๋๊ฐ ์ฒ๋ฆฌํ๋ ์: ์ ํํ 4๊ฐ์ ์ฐ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ์ค์นผ๋ผ ์ฐ์ฐ ๋๋น SIMD_WIDTH๋ฐฐ ํฅ์
์ฐธ๊ณ : SIMD ํญ์ GPU ์ํคํ ์ฒ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค (์: ์ผ๋ถ GPU๋ 4, RTX 4090์ 8, A100์ 16).
4. ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ถ์
a.aligned_load[simd_width](Index(idx)) // ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ์ด์ :
- ์์ฐจ์ ์ ๊ทผ: ์ค๋ ๋๋ค์ด ์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผ
- ์บ์ ์ต์ ํ: L1/L2 ์บ์ ํํธ์จ ๊ทน๋ํ
- ๋์ญํญ ํ์ฉ: ์ด๋ก ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๊ทผ์ ํ๋ ์ฑ๋ฅ ๋ฌ์ฑ
- ํ๋์จ์ด ํจ์จ: GPU ๋ฉ๋ชจ๋ฆฌ ์ปจํธ๋กค๋ฌ๊ฐ ์ด ํจํด์ ์ต์ ํ๋์ด ์์
SIMD_WIDTH=4 (GPU ์์กด์ ) ์์:
Thread 0: a[0:4] ๋ก๋ โ ๋ฉ๋ชจ๋ฆฌ ๋ฑ
ํฌ 0-3
Thread 1: a[4:8] ๋ก๋ โ ๋ฉ๋ชจ๋ฆฌ ๋ฑ
ํฌ 4-7
Thread 2: a[8:12] ๋ก๋ โ ๋ฉ๋ชจ๋ฆฌ ๋ฑ
ํฌ 8-11
...
๊ฒฐ๊ณผ: ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ปจํธ๋กค๋ฌ ํ์ฉ
5. ์ฑ๋ฅ ํน์ฑ ๋ฐ ์ต์ ํ
์ฐ์ ๊ฐ๋ ๋ถ์ (SIMD_WIDTH=4 ๊ธฐ์ค):
- ์ฐ์ ์ฐ์ฐ: 4๊ฐ ์์๋น 1ํ SIMD ๋ง์
- ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ: 4๊ฐ ์์๋น 2ํ SIMD ๋ก๋ + 1ํ SIMD ์ ์ฅ
- ์ฐ์ ๊ฐ๋: 1 ๋ง์ รท 3 ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ = 0.33 (๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋)
์ด๊ฒ์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ธ ์ด์ :
๋จ์ ์ฐ์ฐ์์๋ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ >>> ์ฐ์ฐ ๋ฅ๋ ฅ
์ต์ ํ ์์ฌ์ :
- ์ฐ์ ์ต์ ํ๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ง์คํด์ผ ํจ
- SIMD ๋ฒกํฐํ๊ฐ ์ฃผ์ ์ฑ๋ฅ ์ด์ ์ ์ ๊ณต
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด ์ฑ๋ฅ์ ๋งค์ฐ ์ค์
- ์ฐ์ฐ ๋ณต์ก๋๋ณด๋ค ์บ์ ์ง์ญ์ฑ์ด ๋ ์ค์
6. ํ์ฅ์ฑ๊ณผ ์ ์์ฑ
์๋ ํ๋์จ์ด ์ ์:
comptime SIMD_WIDTH = simd_width_of[dtype, target = _get_gpu_target()]()
- GPU๋ณ ์ต์ ํ: SIMD ํญ์ด ํ๋์จ์ด์ ๋ง๊ฒ ์กฐ์ ๋จ (์: ์ผ๋ถ ์นด๋๋ 4, RTX 4090์ 8, A100์ 16)
- ๋ฐ์ดํฐ ํ์ ์ธ์: float32์ float16์ ๋ํด ์๋ก ๋ค๋ฅธ SIMD ํญ ์ ์ฉ
- ์ปดํ์ผ ํ์ ์ต์ ํ: ํ๋์จ์ด ๊ฐ์ง์ ๋ํ ๋ฐํ์ ์ค๋ฒํค๋ ์์
ํ์ฅ์ฑ ํน์ฑ:
- ์ค๋ ๋ ์: ๋ฌธ์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์๋ ํ์ฅ
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: ์ ๋ ฅ ํฌ๊ธฐ์ ๋น๋กํ์ฌ ์ ํ ํ์ฅ
- ์ฑ๋ฅ: ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํฌํ ์์ ๊น์ง ๊ฑฐ์ ์ ํ์ ์ธ ์๋ ํฅ์
7. ๊ณ ๊ธ ์ธ์ฌ์ดํธ: ์ด ํจํด์ด ์ค์ํ ์ด์
๋ณต์กํ ์ฐ์ฐ์ ๊ธฐ์ด: ์ด elementwise ํจํด์ ๋ค์ ์ฐ์ฐ๋ค์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค:
- ๋ฆฌ๋์ ์ฐ์ฐ: ๋๊ท๋ชจ ๋ฐฐ์ด์์์ ํฉ๊ณ, ์ต๋๊ฐ, ์ต์๊ฐ
- ๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ: ์ค์นผ๋ผ-๋ฒกํฐ ์ฐ์ฐ
- ๋ณต์กํ ๋ณํ: ํ์ฑํ ํจ์, ์ ๊ทํ
- ๋ค์ฐจ์ ์ฐ์ฐ: ํ๋ ฌ ์ฐ์ฐ, ํฉ์ฑ๊ณฑ
์ ํต์ ์ธ ๋ฐฉ์๊ณผ์ ๋น๊ต:
// ์ ํต์ : ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ, ์ฅํฉํจ, ํ๋์จ์ด ์ข
์์
__global__ void add_kernel(float* output, float* a, float* b, int size) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < size) {
output[idx] = a[idx] + b[idx]; // ๋ฒกํฐํ ์์
}
}
// Mojo: ์์ , ๊ฐ๊ฒฐ, ์๋ ๋ฒกํฐํ
elementwise[add, SIMD_WIDTH, target="gpu"](size, ctx)
ํจ์ํ ์ ๊ทผ๋ฒ์ ์ด์ :
- ์์ ์ฑ: ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ก ๋ฒํผ ์ค๋ฒํ๋ก์ฐ ๋ฐฉ์ง
- ์ด์์ฑ: ๋์ผํ ์ฝ๋๊ฐ ๋ค์ํ GPU ๋ฒค๋/์ธ๋์์ ๋์
- ์ฑ๋ฅ: ์ปดํ์ผ๋ฌ ์ต์ ํ๊ฐ ์๋ ํ๋ ์ฝ๋๋ฅผ ์ข ์ข ๋ฅ๊ฐ
- ์ ์ง๋ณด์์ฑ: ๊น๋ํ ์ถ์ํ๋ก ๋๋ฒ๊น ๋ณต์ก๋ ๊ฐ์
- ์กฐํฉ์ฑ: ๋ค๋ฅธ ํจ์ํ ์ฐ์ฐ๊ณผ ์ฝ๊ฒ ๊ฒฐํฉ ๊ฐ๋ฅ
์ด ํจํด์ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ฏธ๋๋ฅผ ๋ํ๋ ๋๋ค - ์ฑ๋ฅ์ ํฌ์ํ์ง ์๋ ๊ณ ์์ค ์ถ์ํ๋ก, ์ต์ ์ ํจ์จ์ฑ์ ์ ์งํ๋ฉด์ GPU ์ปดํจํ ์ ๋ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๊ฒ ํฉ๋๋ค.
๋ค์ ๋จ๊ณ
Elementwise ์ฐ์ฐ์ ํ์ตํ๋ค๋ฉด ๋ค์์ผ๋ก ๋์ด๊ฐ ์ค๋น๊ฐ ๋์์ต๋๋ค:
- tile - ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ํ์ผ๋ง ์ฒ๋ฆฌ: ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ํ์ผ๋ง ์ฒ๋ฆฌ ํจํด
- vectorize - SIMD ์ ์ด: ์ธ๋ฐํ SIMD ์ ์ด
- ๐ง GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋ : ์คํ ๊ณ์ธต ๊ตฌ์กฐ ์ดํด
- ๐ Mojo ๋ฒค์น๋งํน: ์ฑ๋ฅ ๋ถ์๊ณผ ์ต์ ํ
๐ก ํต์ฌ ์์ฝ: elementwise ํจํด์ Mojo๊ฐ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฐ์ํจ๊ณผ GPU
์ฑ๋ฅ์ ์ด๋ป๊ฒ ๊ฒฐํฉํ๋์ง ๋ณด์ฌ์ค๋๋ค. ์ฐ์ฐ์ ๋ํ ์์ ํ ์ ์ด๋ฅผ ์ ์งํ๋ฉด์
๋ฒกํฐํ์ ์ค๋ ๋ ๊ด๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
tile - ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ํ์ผ๋ง ์ฒ๋ฆฌ
๊ฐ์
elementwise ํจํด์ ๊ธฐ๋ฐ์ผ๋ก, ์ด ํผ์ฆ์์๋ ํ์ผ๋ง ์ฒ๋ฆฌ๋ฅผ ์๊ฐํฉ๋๋ค. ์ด๋ GPU์์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด๊ณผ ์บ์ ํ์ฉ์ ์ต์ ํํ๋ ํต์ฌ ๊ธฐ๋ฒ์ ๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ์ ์ฒด ๋ฐฐ์ด์ ๊ฑธ์ณ ๊ฐ๋ณ SIMD ๋ฒกํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋์ , ํ์ผ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์บ์ ๋ฉ๋ชจ๋ฆฌ์ ๋ ์ ๋ง๋ ์๊ณ ๊ด๋ฆฌ ๊ฐ๋ฅํ ์ฒญํฌ๋ก ๊ตฌ์ฑํฉ๋๋ค.
Puzzle 16์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์์ ์ด๋ฏธ ํ์ผ๋ง์ ๊ฒฝํํ ๋ฐ ์์ต๋๋ค. ๊ฑฐ๊ธฐ์๋ ํ์ผ์ ์ฌ์ฉํด ๋๊ท๋ชจ ํ๋ ฌ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋์ผํ ํ์ผ๋ง ์์น์ ๋ฒกํฐ ์ฐ์ฐ์ ์ ์ฉํ์ฌ, ์ด ๊ธฐ๋ฒ์ด 2D ํ๋ ฌ์์ 1D ๋ฐฐ์ด๊น์ง ์ด๋ป๊ฒ ํ์ฅ๋๋์ง ๋ณด์ฌ์ค๋๋ค.
Mojo์ ํ์ผ๋ง ๋ฐฉ์์ ์ฌ์ฉํ์ฌ ๋์ผํ ๋ฒกํฐ ๋ง์ ์ฐ์ฐ์ ๊ตฌํํฉ๋๋ค. ๊ฐ GPU ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ์ ํ์ผ ์ ์ฒด๋ฅผ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฉฐ, ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ์ด ํน์ ์ํฌ๋ก๋์์ ์ด๋ป๊ฒ ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: ํ์ผ๋ง์ ๋ณ๋ ฌ ํญ์ ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ๊ณผ ๊ตํํฉ๋๋ค - ๋ ์ ์ ์์ ์ค๋ ๋๊ฐ ๋ ๋์ ์บ์ ํ์ฉ์ผ๋ก ๋ ๋ง์ ์์ ์ ์ํํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ์บ์ ์ต์ ํ๋ฅผ ์ํ ํ์ผ ๊ธฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ
- ํ์ผ ๋ด์ ์์ฐจ์ SIMD ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ ์์น๊ณผ ์บ์ ์นํ์ ์ ๊ทผ ํจํด
- ์ค๋ ๋-ํ์ผ ๋งคํ vs ์ค๋ ๋-์์ ๋งคํ
- ๋ณ๋ ฌ์ฑ๊ณผ ๋ฉ๋ชจ๋ฆฌ ํจ์จ ๊ฐ์ ์ฑ๋ฅ ํธ๋ ์ด๋์คํ
์์๋ณ ๋ฐฉ์๊ณผ ๋์ผํ ์ํ์ ์ฐ์ฐ: \[\Large \text{output}[i] = a[i] + b[i]\]
ํ์ง๋ง ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ์ ์ต์ ํ๋ ์์ ํ ๋ค๋ฅธ ์คํ ์ ๋ต์ ์ฌ์ฉํฉ๋๋ค.
์ค์
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 1024 - ํ์ผ ํฌ๊ธฐ:
TILE_SIZE = 32 - ๋ฐ์ดํฐ ํ์
:
DType.float32 - SIMD ํญ: GPU ์์กด์ (ํ์ผ ๋ด ์ฐ์ฐ์ฉ)
- ๋ ์ด์์:
row_major[SIZE]()(1D ํ ์ฐ์ )
์์ฑํ ์ฝ๋
comptime TILE_SIZE = 32
def tiled_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def process_tiles[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
print("tile_id:", tile_id)
var output_tile = output.tile[tile_size](tile_id)
var a_tile = a.tile[tile_size](tile_id)
var b_tile = b.tile[tile_size](tile_id)
# FILL IN (6 lines at most)
var num_tiles = (size + tile_size - 1) // tile_size
elementwise[process_tiles, 1, target="gpu"](num_tiles, ctx)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p23/p23.mojo
ํ
1. ํ์ผ ๊ตฌ์ฑ ์ดํดํ๊ธฐ
ํ์ผ๋ง ๋ฐฉ์์ ๋ฐ์ดํฐ๋ฅผ ๊ณ ์ ํฌ๊ธฐ์ ์ฒญํฌ๋ก ๋๋๋๋ค:
num_tiles = (size + tile_size - 1) // tile_size # ์ฌ๋ฆผ ๋๋์
TILE_SIZE=32์ธ 1024๊ฐ ์์ ๋ฒกํฐ์ ๊ฒฝ์ฐ: 1024 รท 32 = 32๊ฐ ํ์ผ์ด ์ ํํ
์๊น๋๋ค.
2. ํ์ผ ์ถ์ถ ํจํด
TileTensor .tile ๋ฌธ์๋ฅผ
์ฐธ๊ณ ํ์ธ์.
tile_id = indices[0] # ๊ฐ ์ค๋ ๋๊ฐ ์ฒ๋ฆฌํ ํ์ผ ํ๋๋ฅผ ๋ฐ์
out_tile = output.tile[tile_size](tile_id)
a_tile = a.tile[tile_size](tile_id)
b_tile = b.tile[tile_size](tile_id)
tile[size](id) ๋ฉ์๋๋ id ร size ์์น๋ถํฐ ์์ํ๋ size๊ฐ์ ์ฐ์ ์์์
๋ํ ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค.
3. ํ์ผ ๋ด ์์ฐจ ์ฒ๋ฆฌ
์์๋ณ ๋ฐฉ์๊ณผ ๋ฌ๋ฆฌ, ํ์ผ์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค:
@parameter
for i in range(tile_size):
# ํ์ฌ ํ์ผ ๋ด์ ์์ i๋ฅผ ์ฒ๋ฆฌ
์ด @parameter ๋ฃจํ๋ ์ต์ ์ ์ฑ๋ฅ์ ์ํด ์ปดํ์ผ ํ์์ ์ ๊ฐ๋ฉ๋๋ค.
4. ํ์ผ ์์ ๋ด SIMD ์ฐ์ฐ
a_vec = a_tile.load[simd_width](i, 0) # ํ์ผ ๋ด ์์น i์์ ๋ก๋
b_vec = b_tile.load[simd_width](i, 0) # ํ์ผ ๋ด ์์น i์์ ๋ก๋
result = a_vec + b_vec # SIMD ๋ง์
(GPU ์์กด์ ํญ)
out_tile.store[simd_width](i, 0, result) # ํ์ผ ๋ด ์์น i์ ์ ์ฅ
5. ์ค๋ ๋ ๊ตฌ์ฑ์ ์ฐจ์ด์
elementwise[process_tiles, 1, target="gpu"](num_tiles, ctx)
SIMD_WIDTH ๋์ 1์ ์ฌ์ฉํฉ๋๋ค - ๊ฐ ์ค๋ ๋๊ฐ ํ๋์ ํ์ผ ์ ์ฒด๋ฅผ ์์ฐจ์ ์ผ๋ก
์ฒ๋ฆฌํฉ๋๋ค.
6. ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ธ์ฌ์ดํธ
๊ฐ ์ค๋ ๋๋ ์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก(ํ์ผ)์ ์ ๊ทผํ ๋ค์, ๋ค์ ํ์ผ๋ก ์ด๋ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ ์ค๋ ๋์ ์คํ ๋ด์์ ์ฐ์ํ ๊ณต๊ฐ ์ง์ญ์ฑ์ด ๋ง๋ค์ด์ง๋๋ค.
7. ๋๋ฒ๊น ํต์ฌ ํฌ์ธํธ
ํ์ผ๋ง์ ์ฌ์ฉํ๋ฉด ์ค๋ ๋ ์คํ ์๋ ์ค์ด๋ค์ง๋ง ๊ฐ ์ค๋ ๋๊ฐ ๋ ๋ง์ ์์ ์ ์ํํฉ๋๋ค:
- ์์๋ณ: ~256๊ฐ ์ค๋ ๋ (SIMD_WIDTH=4 ๊ธฐ์ค), ๊ฐ๊ฐ 4๊ฐ ์์ ์ฒ๋ฆฌ
- Tiled: ~32๊ฐ ์ค๋ ๋, ๊ฐ๊ฐ 32๊ฐ ์์๋ฅผ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ
์ฝ๋ ์คํ
ํ์ด๋ฅผ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ ์คํํ์ธ์:
pixi run p23 --tiled
pixi run -e amd p23 --tiled
pixi run -e apple p23 --tiled
uv run poe p23 --tiled
ํผ์ฆ์ด ์์ง ํ๋ฆฌ์ง ์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค:
SIZE: 1024
simd_width: 4
tile size: 32
tile_id: 0
tile_id: 1
tile_id: 2
tile_id: 3
...
tile_id: 29
tile_id: 30
tile_id: 31
out: HostBuffer([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])
expected: HostBuffer([1.0, 5.0, 9.0, ..., 4085.0, 4089.0, 4093.0])
์๋ฃจ์
comptime TILE_SIZE = 32
def tiled_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def process_tiles[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
var output_tile = output.tile[tile_size](tile_id).to_layout_tensor()
var a_tile = a.tile[tile_size](tile_id).to_layout_tensor()
var b_tile = b.tile[tile_size](tile_id).to_layout_tensor()
comptime for i in range(tile_size):
var a_vec = a_tile.aligned_load[width=simd_width](Index(i))
var b_vec = b_tile.aligned_load[width=simd_width](Index(i))
var ret = a_vec + b_vec
output_tile.store[simd_width](Index(i), ret)
var num_tiles = (size + tile_size - 1) // tile_size
elementwise[process_tiles, 1, target="gpu"](num_tiles, ctx)
ํ์ผ๋ง ์ฒ๋ฆฌ ํจํด์ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ํ ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ๊ธฐ๋ฒ์ ๋ณด์ฌ์ค๋๋ค:
1. ํ์ผ๋ง ์ฒ ํ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ
ํ์ผ๋ง์ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ๋ํ ์ฌ๊ณ ๋ฐฉ์์ ๊ทผ๋ณธ์ ์ธ ์ ํ์ ๋ํ๋ ๋๋ค:
์์๋ณ ๋ฐฉ์:
- ๋์ ๋ณ๋ ฌ์ฑ: ๋ง์ ์ค๋ ๋๊ฐ ๊ฐ๊ฐ ์ต์ํ์ ์์ ์ํ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ถํ: ์ค๋ ๋๋ค์ด ์ ์ฒด ๋ฐฐ์ด์ ๋ถ์ฐ
- ์บ์ ๋ฏธ์ค: ์ค๋ ๋ ๊ฒฝ๊ณ๋ฅผ ๋๋๋๋ ๋ฎ์ ๊ณต๊ฐ ์ง์ญ์ฑ
ํ์ผ๋ง ๋ฐฉ์:
- ๊น์ ๋ณ๋ ฌ์ฑ: ๋ ์ ์ ์ค๋ ๋๊ฐ ๊ฐ๊ฐ ์๋นํ ์์ ์ํ
- ์ง์ญํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ๊ฐ ์ค๋ ๋๊ฐ ์ฐ์์ ์ธ ๋ฐ์ดํฐ์์ ์์
- ์บ์ ์ต์ ํ: ์ฐ์ํ ๊ณต๊ฐ ๋ฐ ์๊ฐ ์ง์ญ์ฑ
2. ํ์ผ ๊ตฌ์ฑ๊ณผ ์ธ๋ฑ์ฑ
tile_id = indices[0]
out_tile = output.tile[tile_size](tile_id)
a_tile = a.tile[tile_size](tile_id)
b_tile = b.tile[tile_size](tile_id)
ํ์ผ ๋งคํ ์๊ฐํ (TILE_SIZE=32):
์๋ณธ ๋ฐฐ์ด: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ..., 1023]
Tile 0 (thread 0): [0, 1, 2, ..., 31] โ ์์ 0-31
Tile 1 (thread 1): [32, 33, 34, ..., 63] โ ์์ 32-63
Tile 2 (thread 2): [64, 65, 66, ..., 95] โ ์์ 64-95
...
Tile 31 (thread 31): [992, 993, ..., 1023] โ ์์ 992-1023
ํต์ฌ ์ธ์ฌ์ดํธ:
tile[size](id)๋ ์๋ณธ ํ ์์ ๋ํ ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค- ๋ทฐ๋ ์ ๋ก ์นดํผ๋ก ๋์ํฉ๋๋ค - ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ์ง ์๊ณ ํฌ์ธํฐ ์ฐ์ฐ๋ง ์ํ
- ํ์ผ ๊ฒฝ๊ณ๋ ํญ์
tile_size๋จ์๋ก ์ ๋ ฌ๋ฉ๋๋ค
3. ์์ฐจ ์ฒ๋ฆฌ ์ฌ์ธต ๋ถ์
@parameter
for i in range(tile_size):
a_vec = a_tile.load[simd_width](i, 0)
b_vec = b_tile.load[simd_width](i, 0)
ret = a_vec + b_vec
out_tile.store[simd_width](i, 0, ret)
์ ์์ฐจ ์ฒ๋ฆฌ์ธ๊ฐ?
- ์บ์ ์ต์ ํ: ์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์บ์ ํํธ์จ์ ๊ทน๋ํ
- ์ปดํ์ผ๋ฌ ์ต์ ํ:
@parameter๋ฃจํ๊ฐ ์ปดํ์ผ ํ์์ ์์ ํ ์ ๊ฐ๋จ - ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ์์ฐจ ์ ๊ทผ์ด ๋ฉ๋ชจ๋ฆฌ ์ปจํธ๋กค๋ฌ ์ค๊ณ์ ๋ถํฉ
- ์กฐ์ ๋น์ฉ ๊ฐ์: SIMD ๊ทธ๋ฃน ๊ฐ ๋๊ธฐํ๊ฐ ๋ถํ์
ํ๋์ ํ์ผ ๋ด ์คํ ํจํด (TILE_SIZE=32, SIMD_WIDTH=4):
์ค๋ ๋๊ฐ ํ์ผ์ ์์ฐจ ์ฒ๋ฆฌ:
Step 0: ์์ [0:4]๋ฅผ SIMD๋ก ์ฒ๋ฆฌ
Step 1: ์์ [4:8]๋ฅผ SIMD๋ก ์ฒ๋ฆฌ
Step 2: ์์ [8:12]๋ฅผ SIMD๋ก ์ฒ๋ฆฌ
...
Step 7: ์์ [28:32]๋ฅผ SIMD๋ก ์ฒ๋ฆฌ
ํฉ๊ณ: ์ค๋ ๋๋น 8ํ SIMD ์ฐ์ฐ (32 รท 4 = 8)
4. ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ถ์
์บ์ ๋์ ๋น๊ต:
์์๋ณ ํจํด:
Thread 0: ๊ธ๋ก๋ฒ ์์น [0, 4, 8, 12, ...] ์ ๊ทผ โ Stride = SIMD_WIDTH
Thread 1: ๊ธ๋ก๋ฒ ์์น [4, 8, 12, 16, ...] ์ ๊ทผ โ Stride = SIMD_WIDTH
...
๊ฒฐ๊ณผ: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ ์ฒด ๋ฐฐ์ด์ ๋ถ์ฐ
Tiled ํจํด:
Thread 0: ์์น [0:32]๋ฅผ ์์ฐจ ์ ๊ทผ โ ์ฐ์์ ์ธ 32๊ฐ ์์ ๋ธ๋ก
Thread 1: ์์น [32:64]๋ฅผ ์์ฐจ ์ ๊ทผ โ ๋ค์ ์ฐ์์ ์ธ 32๊ฐ ์์ ๋ธ๋ก
...
๊ฒฐ๊ณผ: ๊ฐ ์ค๋ ๋ ๋ด์์ ์๋ฒฝํ ๊ณต๊ฐ ์ง์ญ์ฑ
์บ์ ํจ์จ ์์ฌ์ :
- L1 ์บ์: ์์ ํ์ผ์ด L1 ์บ์์ ๋ ์ ๋ง์ ์บ์ ๋ฏธ์ค ๊ฐ์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ์์ฐจ ์ ๊ทผ์ด ์ ํจ ๋์ญํญ์ ๊ทน๋ํ
- TLB ํจ์จ: TLB ๋ฏธ์ค ๊ฐ์ (์ญ์ฃผ: TLB(Translation Lookaside Buffer)๋ ๊ฐ์ ์ฃผ์๋ฅผ ๋ฌผ๋ฆฌ ์ฃผ์๋ก ๋ณํํ๋ ์บ์๋ก, ๋ฏธ์ค๊ฐ ์ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ๋นจ๋ผ์ง๋๋ค)
- ํ๋ฆฌํ์นญ: ํ๋์จ์ด ํ๋ฆฌํ์ฒ๊ฐ ์์ฐจ ํจํด์์ ์ต์ ์ผ๋ก ๋์
5. ์ค๋ ๋ ๊ตฌ์ฑ ์ ๋ต
elementwise[process_tiles, 1, target="gpu"](num_tiles, ctx)
์ SIMD_WIDTH ๋์ 1์ธ๊ฐ?
- ์ค๋ ๋ ์:
num_tiles ร SIMD_WIDTH๊ฐ ์๋ ์ ํํnum_tiles๊ฐ์ ์ค๋ ๋๋ง ์คํ - ์์ ๋ถ๋ฐฐ: ๊ฐ ์ค๋ ๋๊ฐ ํ๋์ ์์ ํ ํ์ผ์ ์ฒ๋ฆฌ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ: ์ค๋ ๋๋น ๋ ๋ง์ ์์ , ์ ์ฒด์ ์ผ๋ก ๋ ์ ์ ์ค๋ ๋
- ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ: ๊ฐ ์ค๋ ๋์ ์์ ์ด ๊ณต๊ฐ์ ์ผ๋ก ์ง์ญํ
์ฑ๋ฅ ํธ๋ ์ด๋์คํ:
- ๋ ์ ์ ๋ ผ๋ฆฌ์ ์ค๋ ๋: ๋ฎ์ ์ ์ ์จ์์ ๋ชจ๋ GPU ์ฝ์ด๋ฅผ ํ์ฉํ์ง ๋ชปํ ์ ์์
- ์ค๋ ๋๋น ๋ ๋ง์ ์์ : ๋ ๋์ ์บ์ ํ์ฉ๊ณผ ์กฐ์ ์ค๋ฒํค๋ ๊ฐ์
- ์์ฐจ ์ ๊ทผ: ๊ฐ ์ค๋ ๋ ๋ด์์ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ
- ์ค๋ฒํค๋ ๊ฐ์: ์ค๋ ๋ ์คํ ๋ฐ ์กฐ์ ์ค๋ฒํค๋ ๊ฐ์
์ค์ ์ฐธ๊ณ : โ๋ ์ ์ ์ค๋ ๋โ๋ ๋ ผ๋ฆฌ์ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ ์๋ฏธํฉ๋๋ค. GPU ์ค์ผ์ค๋ฌ๋ ์ฌ๋ฌ ์ํ๋ฅผ ์คํํ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์ ํจ์จ์ ์ผ๋ก ์ ํํ์ฌ ๋์ ํ๋์จ์ด ํ์ฉ๋ฅ ์ ๋ฌ์ฑํ ์ ์์ต๋๋ค.
6. ์ฑ๋ฅ ํน์ฑ
ํ์ผ๋ง์ด ๋์์ด ๋๋ ๊ฒฝ์ฐ:
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ: ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ๋ณ๋ชฉ์ธ ๊ฒฝ์ฐ
- ์บ์ ๋ฏผ๊ฐ ์ํฌ๋ก๋: ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ์ ์ด์ ์ด ์๋ ์ฐ์ฐ
- ๋ณต์กํ ์ฐ์ฐ: ์์๋น ์ฐ์ฐ๋์ด ๋ง์ ๊ฒฝ์ฐ
- ์ ํ๋ ๋ณ๋ ฌ์ฑ: GPU ์ฝ์ด๋ณด๋ค ์ค๋ ๋๊ฐ ์ ์ ๊ฒฝ์ฐ
ํ์ผ๋ง์ด ๋ถ๋ฆฌํ ๊ฒฝ์ฐ:
- ๊ณ ๋๋ก ๋ณ๋ ฌ์ ์ธ ์ํฌ๋ก๋: ์ต๋ ์ค๋ ๋ ํ์ฉ์ด ํ์ํ ๊ฒฝ์ฐ
- ๋จ์ํ ์ฐ์ฐ: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ฐ์ฐ๋ณด๋ค ์ง๋ฐฐ์ ์ธ ๊ฒฝ์ฐ
- ๋ถ๊ท์น์ ์ ๊ทผ ํจํด: ํ์ผ๋ง์ด ์ง์ญ์ฑ์ ๊ฐ์ ํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ
๋จ์ ๋ง์ ์์ (TILE_SIZE=32):
- ์ค๋ ๋ ์: 256๊ฐ ๋์ 32๊ฐ (8๋ฐฐ ์ ์)
- ์ค๋ ๋๋น ์์ ๋: 4๊ฐ ๋์ 32๊ฐ ์์ (8๋ฐฐ ๋ง์)
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ์์ฐจ vs ์คํธ๋ผ์ด๋ ์ ๊ทผ
- ์บ์ ํ์ฉ: ํจ์ฌ ๋์ ๊ณต๊ฐ ์ง์ญ์ฑ
7. ๊ณ ๊ธ ํ์ผ๋ง ๊ณ ๋ ค ์ฌํญ
ํ์ผ ํฌ๊ธฐ ์ ํ:
- ๋๋ฌด ์์ผ๋ฉด: ์บ์ ํ์ฉ์ด ๋จ์ด์ง๊ณ , ์ค๋ฒํค๋๊ฐ ์ฆ๊ฐ
- ๋๋ฌด ํฌ๋ฉด: ์บ์์ ๋ง์ง ์์ ์ ์๊ณ , ๋ณ๋ ฌ์ฑ์ด ๊ฐ์
- ์ต์ ์ง์ : L1 ์บ์ ์ต์ ํ๋ฅผ ์ํด ๋ณดํต 16-64๊ฐ ์์
- ํ์ฌ ์ ํ: 32๊ฐ ์์๋ก ์บ์ ํ์ฉ๊ณผ ๋ณ๋ ฌ์ฑ์ ๊ท ํ ๋ฌ์ฑ
ํ๋์จ์ด ๊ณ ๋ ค ์ฌํญ:
- ์บ์ ํฌ๊ธฐ: ๊ฐ๋ฅํ๋ฉด ํ์ผ์ด L1 ์บ์์ ๋ง์์ผ ํจ
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ๋ฉ๋ชจ๋ฆฌ ์ปจํธ๋กค๋ฌ ํญ์ ๊ณ ๋ ค
- ์ฝ์ด ์: ๋ชจ๋ ์ฝ์ด๋ฅผ ํ์ฉํ๊ธฐ์ ์ถฉ๋ถํ ํ์ผ ํ๋ณด
- SIMD ํญ: ํ์ผ ํฌ๊ธฐ๋ SIMD ํญ์ ๋ฐฐ์์ฌ์ผ ํจ
๋น๊ต ์์ฝ:
Elementwise: ๋์ ๋ณ๋ ฌ์ฑ, ๋ถ์ฐ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
Tiled: ์ ๋นํ ๋ณ๋ ฌ์ฑ, ์ง์ญํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
์์๋ณ ํจํด๊ณผ ํ์ผ๋ง ํจํด ๊ฐ์ ์ ํ์ ํน์ ์ํฌ๋ก๋ ํน์ฑ, ๋ฐ์ดํฐ ์ ๊ทผ ํจํด, ๋์ ํ๋์จ์ด ๋ฅ๋ ฅ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
๋ค์ ๋จ๊ณ
์์๋ณ ํจํด๊ณผ ํ์ผ๋ง ํจํด์ ๋ชจ๋ ์ดํดํ๋ค๋ฉด:
- vectorize - SIMD ์ ์ด: SIMD ์ฐ์ฐ์ ๋ํ ์ธ๋ฐํ ์ ์ด
- ๐ง GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋ : ์คํ ๊ณ์ธต ๊ตฌ์กฐ ์ดํด
- ๐ Mojo ๋ฒค์น๋งํน: ์ฑ๋ฅ ๋ถ์๊ณผ ์ต์ ํ
๐ก ํต์ฌ ์์ฝ: ํ์ผ๋ง์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์์ ์ฐ์ฐ ์ฒ๋ฆฌ๋๋ณด๋ค ๋ ์ค์ํ ์ ์์์ ๋ณด์ฌ์ค๋๋ค. ์ต๊ณ ์ GPU ์ฝ๋๋ ๋ณ๋ ฌ์ฑ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ์ ๊ท ํ์ ๋ง์ถฅ๋๋ค.
vectorize - SIMD ์ ์ด
๊ฐ์
์ด ํผ์ฆ์์๋ ์๋ ๋ฒกํฐํ์ vectorize๋ฅผ ์ฌ์ฉํ์ฌ GPU ์ปค๋ ๋ด์์ SIMD ์ฐ์ฐ์ ์ ๋ฐํ๊ฒ ์ ์ดํ๋ ๊ณ ๊ธ ๋ฒกํฐํ ๊ธฐ๋ฒ์ ํ๊ตฌํฉ๋๋ค. ๋ฒกํฐํ๋ ์ฐ์ฐ์ ๋ํด ๋ ๊ฐ์ง ๋ค๋ฅธ ์ ๊ทผ๋ฒ์ ๊ตฌํํฉ๋๋ค:
- ์๋ ๋ฒกํฐํ: ๋ช ์์ ์ธ๋ฑ์ค ๊ณ์ฐ์ ํตํ ์ง์ ์ ์ธ SIMD ์ ์ด
- Mojo์ vectorize ํจ์: ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํฌํจํ ๊ณ ์์ค ๋ฒกํฐํ
๋ ์ ๊ทผ๋ฒ ๋ชจ๋ ํ์ผ๋ง ๊ฐ๋ ์ ๊ธฐ๋ฐ์ผ๋ก ํ์ง๋ง, ์ ์ด, ์์ ์ฑ, ์ฑ๋ฅ ์ต์ ํ ๊ฐ์ ํธ๋ ์ด๋์คํ๊ฐ ๋ค๋ฆ ๋๋ค.
ํต์ฌ ํต์ฐฐ: ๋ฒกํฐํ ์ ๋ต์ ์ฑ๋ฅ ์๊ตฌ ์ฌํญ๊ณผ ๋ณต์ก๋ ์์ค์ ๋ฐ๋ผ ๋ฌ๋ฆฌ ์ ํํด์ผ ํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- ๋ช ์์ ์ธ๋ฑ์ค ๊ด๋ฆฌ๋ฅผ ํตํ ์๋ SIMD ์ฐ์ฐ
- ์์ ํ๊ณ ์๋์ ์ธ ๋ฒกํฐํ๋ฅผ ์ํ Mojo์ vectorize ํจ์
- ์ต์ ์ SIMD ์ ๋ ฌ์ ์ํ ์ฒญํฌ ๊ธฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์ฑ
- ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ํ ๊ฒฝ๊ณ ๊ฒ์ฌ ์ ๋ต
- ์๋ ์ ์ด์ ์์ ์ฑ ๊ฐ์ ์ฑ๋ฅ ํธ๋ ์ด๋์คํ
์ด์ ๊ณผ ๋์ผํ ์ํ์ ์ฐ์ฐ: \[\Large \text{output}[i] = a[i] + b[i]\]
ํ์ง๋ง ์ต๋ ์ฑ๋ฅ์ ์ํ ์ ๊ตํ ๋ฒกํฐํ ์ ๋ต์ ์ฌ์ฉํฉ๋๋ค.
์ค์
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 1024 - ํ์ผ ํฌ๊ธฐ:
TILE_SIZE = 32 - ๋ฐ์ดํฐ ํ์
:
DType.float32 - SIMD ํญ: GPU ์์กด์
- ๋ ์ด์์:
row_major[SIZE]()(1D ํ ์ฐ์ )
1. ์๋ ๋ฒกํฐํ ๋ฐฉ์
์์ฑํ ์ฝ๋
def manual_vectorized_tiled_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
num_threads_per_tile: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
# Each tile contains tile_size groups of simd_width elements
comptime chunk_size = tile_size * simd_width
@parameter
@always_inline
def process_manual_vectorized_tiles[
num_threads_per_tile: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
print("tile_id:", tile_id)
var output_tile = output.tile[chunk_size](tile_id)
var a_tile = a.tile[chunk_size](tile_id)
var b_tile = b.tile[chunk_size](tile_id)
# FILL IN (7 lines at most)
# Number of tiles needed: each tile processes chunk_size elements
var num_tiles = (size + chunk_size - 1) // chunk_size
elementwise[
process_manual_vectorized_tiles, num_threads_per_tile, target="gpu"
](num_tiles, ctx)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p23/p23.mojo
ํ
1. ์ฒญํฌ ๊ตฌ์ฑ ์ดํดํ๊ธฐ
comptime chunk_size = tile_size * simd_width # 32 * 4 = ์ฒญํฌ๋น 128๊ฐ ์์
๊ฐ ํ์ผ์ ์ด์ ๋จ์ํ ์์ฐจ ์์๊ฐ ์๋ ์ฌ๋ฌ SIMD ๊ทธ๋ฃน์ ํฌํจํฉ๋๋ค.
2. ์ ์ญ ์ธ๋ฑ์ค ๊ณ์ฐ
global_start = tile_id * chunk_size + i * simd_width
์ฒญํฌ ๋ด ๊ฐ SIMD ๋ฒกํฐ์ ์ ํํ ์ ์ญ ์์น๋ฅผ ๊ณ์ฐํฉ๋๋ค.
3. ํ ์ ์ง์ ์ ๊ทผ
a_vec = a.load[simd_width](global_start, 0) # ์ ์ญ ํ
์์์ ๋ก๋
output.store[simd_width](global_start, 0, ret) # ์ ์ญ ํ
์์ ์ ์ฅ
์ฐธ๊ณ : ํ์ผ ๋ทฐ๊ฐ ์๋ ์๋ณธ ํ ์์ ์ ๊ทผํฉ๋๋ค.
4. ์ฃผ์ ํน์ฑ
- ๋ ๋ง์ ์ ์ด, ๋ ๋ง์ ๋ณต์ก์ฑ, ์ ์ญ ํ ์ ์ ๊ทผ
- ํ๋์จ์ด์ ๋ํ ์๋ฒฝํ SIMD ์ ๋ ฌ
- ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ ํ์
์๋ ๋ฒกํฐํ ์คํ
pixi run p23 --manual-vectorized
pixi run -e amd p23 --manual-vectorized
pixi run -e apple p23 --manual-vectorized
uv run poe p23 --manual-vectorized
ํผ์ฆ์ด ์์ง ํ๋ฆฌ์ง ์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค:
SIZE: 1024
simd_width: 4
tile size: 32
tile_id: 0
tile_id: 1
tile_id: 2
tile_id: 3
tile_id: 4
tile_id: 5
tile_id: 6
tile_id: 7
out: HostBuffer([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])
expected: HostBuffer([1.0, 5.0, 9.0, ..., 4085.0, 4089.0, 4093.0])
์๋ ๋ฒกํฐํ ํ์ด
def manual_vectorized_tiled_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
num_threads_per_tile: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
# Each tile contains tile_size groups of simd_width elements
comptime chunk_size = tile_size * simd_width
@parameter
@always_inline
def process_manual_vectorized_tiles[
num_threads_per_tile: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
# Convert inside GPU kernel to avoid host-captured LayoutTensor issues
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
comptime for i in range(tile_size):
var global_start = tile_id * chunk_size + i * simd_width
var a_vec = a_lt.aligned_load[width=simd_width](Index(global_start))
var b_vec = b_lt.aligned_load[width=simd_width](Index(global_start))
var ret = a_vec + b_vec
out_lt.store[simd_width](Index(global_start), ret)
# Number of tiles needed: each tile processes chunk_size elements
var num_tiles = (size + chunk_size - 1) // chunk_size
elementwise[
process_manual_vectorized_tiles, num_threads_per_tile, target="gpu"
](num_tiles, ctx)
์๋ ๋ฒกํฐํ ์ฌ์ธต ๋ถ์
์๋ ๋ฒกํฐํ๋ ๋ช ์์ ์ธ๋ฑ์ค ๊ณ์ฐ์ ํตํด SIMD ์ฐ์ฐ์ ๋ํ ์ง์ ์ ์ธ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค:
- ์ฒญํฌ ๊ธฐ๋ฐ ๊ตฌ์ฑ:
chunk_size = tile_size * simd_width - ์ ์ญ ์ธ๋ฑ์ฑ: ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ง์ ๊ณ์ฐ
- ์๋ ๊ฒฝ๊ณ ๊ด๋ฆฌ: ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ง์ ์ฒ๋ฆฌ
์ํคํ ์ฒ์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์:
comptime chunk_size = tile_size * simd_width # 32 * 4 = 128
์ฒญํฌ ๊ตฌ์ฑ ์๊ฐํ (TILE_SIZE=32, SIMD_WIDTH=4):
์๋ณธ ๋ฐฐ์ด: [0, 1, 2, 3, ..., 1023]
์ฒญํฌ 0 (thread 0): [0:128] โ 128๊ฐ ์์ = 4๊ฐ์ฉ 32๊ฐ SIMD ๊ทธ๋ฃน
์ฒญํฌ 1 (thread 1): [128:256] โ ๋ค์ 128๊ฐ ์์
์ฒญํฌ 2 (thread 2): [256:384] โ ๋ค์ 128๊ฐ ์์
...
์ฒญํฌ 7 (thread 7): [896:1024] โ ๋ง์ง๋ง 128๊ฐ ์์
ํ๋์ ์ฒญํฌ ๋ด ์ฒ๋ฆฌ:
@parameter
for i in range(tile_size): # i = 0, 1, 2, ..., 31
global_start = tile_id * chunk_size + i * simd_width
# tile_id=0์ผ ๋: global_start = 0, 4, 8, 12, ..., 124
# tile_id=1์ผ ๋: global_start = 128, 132, 136, 140, ..., 252
์ฑ๋ฅ ํน์ฑ:
- ์ค๋ ๋ ์: 8๊ฐ ์ค๋ ๋ (1024 รท 128 = 8)
- ์ค๋ ๋๋น ์์ ๋: 128๊ฐ ์์ (๊ฐ 4๊ฐ ์์์ SIMD ์ฐ์ฐ 32ํ)
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ์๋ฒฝํ SIMD ์ ๋ ฌ์ ๊ฐ์ถ ๋ํ ์ฒญํฌ
- ์ค๋ฒํค๋: ์ต์ - ํ๋์จ์ด์ ์ง์ ๋งคํ
- ์์ ์ฑ: ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ ํ์
์ฃผ์ ์ฅ์ :
- ์์ธก ๊ฐ๋ฅํ ์ธ๋ฑ์ฑ: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๋ํ ์ ํํ ์ ์ด
- ์ต์ ์ ์ ๋ ฌ: SIMD ์ฐ์ฐ์ด ํ๋์จ์ด์ ์๋ฒฝํ ์ ๋ ฌ
- ์ต๋ ์ฒ๋ฆฌ๋: ์์ ์ฑ ๊ฒ์ฌ๋ก ์ธํ ์ค๋ฒํค๋ ์์
- ํ๋์จ์ด ์ต์ ํ: GPU SIMD ์ ๋์ ์ง์ ๋งคํ
์ฃผ์ ๊ณผ์ :
- ์ธ๋ฑ์ค ๋ณต์ก์ฑ: ์ ์ญ ์์น์ ์๋ ๊ณ์ฐ
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ ์ฑ ์: ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ง์ ์ฒ๋ฆฌํด์ผ ํจ
- ๋๋ฒ๊น ๋์ด๋: ์ ํ์ฑ ๊ฒ์ฆ์ด ๋ ๋ณต์ก
2. Mojo vectorize ๋ฐฉ์
์์ฑํ ์ฝ๋
def vectorize_within_tiles_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
num_threads_per_tile: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
# Each tile contains tile_size elements (not SIMD groups)
@parameter
@always_inline
def process_tile_with_vectorize[
num_threads_per_tile: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
var tile_start = tile_id * tile_size
var tile_end = min(tile_start + tile_size, size)
var actual_tile_size = tile_end - tile_start
print(
"tile_id:",
tile_id,
"tile_start:",
tile_start,
"tile_end:",
tile_end,
"actual_tile_size:",
actual_tile_size,
)
# FILL IN (9 lines at most)
var num_tiles = (size + tile_size - 1) // tile_size
elementwise[
process_tile_with_vectorize, num_threads_per_tile, target="gpu"
](num_tiles, ctx)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p23/p23.mojo
ํ
1. ํ์ผ ๊ฒฝ๊ณ ๊ณ์ฐ
tile_start = tile_id * tile_size
tile_end = min(tile_start + tile_size, size)
actual_tile_size = tile_end - tile_start
๋ง์ง๋ง ํ์ผ์ด tile_size๋ณด๋ค ์์ ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
2. ๋ฒกํฐํ ํจ์ ํจํด
def vectorized_add[
width: Int
](i: Int) unified {read tile_start, read a, read b, mut output}:
global_idx = tile_start + i
if global_idx + width <= size: # ๊ฒฝ๊ณ ๊ฒ์ฌ
# SIMD ์ฐ์ฐ ์ฝ๋
width ๋งค๊ฐ๋ณ์๋ vectorize ํจ์์ ์ํด ์๋์ผ๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
3. vectorize ํธ์ถ
vectorize[simd_width](actual_tile_size, vectorized_add)
์ ๊ณต๋ SIMD ํญ์ผ๋ก ๋ฒกํฐํ ๋ฃจํ๋ฅผ ์๋ ์ฒ๋ฆฌํฉ๋๋ค.
4. ์ฃผ์ ํน์ฑ
- ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ, ๋ด์ฅ ์์ ์ฑ, ํ์ผ ๊ธฐ๋ฐ ์ ๊ทผ
- ๋ช ์์ SIMD ํญ ๋งค๊ฐ๋ณ์ ์ฌ์ฉ
- ๋ด์ฅ ๊ฒฝ๊ณ ๊ฒ์ฌ์ ์๋ ๋๋จธ์ง ์์ ์ฒ๋ฆฌ
Mojo vectorize ์คํ
uv run poe p23 --vectorized
pixi run p23 --vectorized
ํผ์ฆ์ด ์์ง ํ๋ฆฌ์ง ์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค:
SIZE: 1024
simd_width: 4
tile size: 32
tile_id: 0 tile_start: 0 tile_end: 32 actual_tile_size: 32
tile_id: 1 tile_start: 32 tile_end: 64 actual_tile_size: 32
tile_id: 2 tile_start: 64 tile_end: 96 actual_tile_size: 32
tile_id: 3 tile_start: 96 tile_end: 128 actual_tile_size: 32
...
tile_id: 29 tile_start: 928 tile_end: 960 actual_tile_size: 32
tile_id: 30 tile_start: 960 tile_end: 992 actual_tile_size: 32
tile_id: 31 tile_start: 992 tile_end: 1024 actual_tile_size: 32
out: HostBuffer([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])
expected: HostBuffer([1.0, 5.0, 9.0, ..., 4085.0, 4089.0, 4093.0])
Mojo vectorize ํ์ด
def vectorize_within_tiles_elementwise_add[
LayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
num_threads_per_tile: Int,
rank: Int,
size: Int,
tile_size: Int,
](
output: TileTensor[mut=True, dtype, LayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
# Each tile contains tile_size elements (not SIMD groups)
@parameter
@always_inline
def process_tile_with_vectorize[
num_threads_per_tile: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var tile_id = indices[0]
var tile_start = tile_id * tile_size
var tile_end = min(tile_start + tile_size, size)
var actual_tile_size = tile_end - tile_start
# Convert inside GPU kernel to avoid host-captured LayoutTensor issues
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
def vectorized_add[
width: Int
](i: Int) {read tile_start, read a_lt, read b_lt, mut out_lt}:
var global_idx = tile_start + i
if global_idx + width <= size:
var a_vec = a_lt.aligned_load[width](Index(global_idx))
var b_vec = b_lt.aligned_load[width](Index(global_idx))
var result = a_vec + b_vec
out_lt.store[width](Index(global_idx), result)
# Use vectorize within each tile
vectorize[simd_width](actual_tile_size, vectorized_add)
var num_tiles = (size + tile_size - 1) // tile_size
elementwise[
process_tile_with_vectorize, num_threads_per_tile, target="gpu"
](num_tiles, ctx)
Mojo vectorize ์ฌ์ธต ๋ถ์
Mojo์ vectorize ํจ์๋ ๋ด์ฅ ์์ ์ฑ๊ณผ ํจ๊ป ์๋ ๋ฒกํฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค:
- ๋ช ์์ SIMD ํญ ๋งค๊ฐ๋ณ์: ์ฌ์ฉํ simd_width๋ฅผ ์ง์ ์ง์
- ๋ด์ฅ ๊ฒฝ๊ณ ๊ฒ์ฌ: ๋ฒํผ ์ค๋ฒํ๋ก์ฐ๋ฅผ ์๋์ผ๋ก ๋ฐฉ์ง
- ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ: ๋จ์ ์์๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
- ์ค์ฒฉ ํจ์ ํจํด: ๋ฒกํฐํ ๋ก์ง์ ๊น๋ํ ๋ถ๋ฆฌ
ํ์ผ ๊ธฐ๋ฐ ๊ตฌ์ฑ:
tile_start = tile_id * tile_size # 0, 32, 64, 96, ...
tile_end = min(tile_start + tile_size, size)
actual_tile_size = tile_end - tile_start
์๋ ๋ฒกํฐํ ๋ฉ์ปค๋์ฆ:
def vectorized_add[
width: Int
](i: Int) unified {read tile_start, read a, read b, mut output}:
global_idx = tile_start + i
if global_idx + width <= size:
# ์๋ SIMD ์ต์ ํ
vectorize์ ๋์ ๋ฐฉ์:
- ์๋ ์ฒญํฌ ๋ถํ :
actual_tile_size๋ฅผ ์ง์ ํsimd_width์ ์ฒญํฌ๋ก ๋ถํ - ๋๋จธ์ง ์ฒ๋ฆฌ: ๋จ์ ์์๋ฅผ ๋ ์์ ํญ์ผ๋ก ์๋ ์ฒ๋ฆฌ
- ๊ฒฝ๊ณ ์์ ์ฑ: ๋ฒํผ ์ค๋ฒํ๋ก์ฐ๋ฅผ ์๋์ผ๋ก ๋ฐฉ์ง
- ๋ฃจํ ๊ด๋ฆฌ: ๋ฒกํฐํ ๋ฃจํ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
์คํ ์๊ฐํ (TILE_SIZE=32, SIMD_WIDTH=4):
Tile 0 ์ฒ๋ฆฌ:
vectorize ํธ์ถ 0: ์์ [0:4]๋ฅผ SIMD_WIDTH=4๋ก ์ฒ๋ฆฌ
vectorize ํธ์ถ 1: ์์ [4:8]๋ฅผ SIMD_WIDTH=4๋ก ์ฒ๋ฆฌ
...
vectorize ํธ์ถ 7: ์์ [28:32]๋ฅผ SIMD_WIDTH=4๋ก ์ฒ๋ฆฌ
ํฉ๊ณ: 8ํ ์๋ SIMD ์ฐ์ฐ
์ฑ๋ฅ ํน์ฑ:
- ์ค๋ ๋ ์: 32๊ฐ ์ค๋ ๋ (1024 รท 32 = 32)
- ์ค๋ ๋๋น ์์ ๋: 32๊ฐ ์์ (์๋ SIMD ์ฒญํฌ ๋ถํ )
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ์๋ ๋ฒกํฐํ๋ฅผ ๊ฐ์ถ ์์ ํ์ผ
- ์ค๋ฒํค๋: ์ฝ๊ฐ - ์๋ ์ต์ ํ ๋ฐ ๊ฒฝ๊ณ ๊ฒ์ฌ
- ์์ ์ฑ: ๋ด์ฅ ๊ฒฝ๊ณ ๊ฒ์ฌ์ ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ
์ฑ๋ฅ ๋น๊ต์ ๋ชจ๋ฒ ์ฌ๋ก
๊ฐ ์ ๊ทผ๋ฒ์ ์ ํ ๊ธฐ์ค
์๋ ๋ฒกํฐํ๋ฅผ ์ ํํ ๋:
- ์ต๋ ์ฑ๋ฅ์ด ์ค์ํ ๊ฒฝ์ฐ
- ์์ธก ๊ฐ๋ฅํ๊ณ ์ ๋ ฌ๋ ๋ฐ์ดํฐ ํจํด์ด ์๋ ๊ฒฝ์ฐ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๋ํ ์ ๋ฌธ๊ฐ ์์ค์ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ์๋์ผ๋ก ๊ฒฝ๊ณ ์์ ์ฑ์ ๋ณด์ฅํ ์ ์๋ ๊ฒฝ์ฐ
- ํ๋์จ์ด๋ณ ์ต์ ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ
Mojo vectorize๋ฅผ ์ ํํ ๋:
- ๊ฐ๋ฐ ์๋์ ์์ ์ฑ์ด ์ฐ์ ์ธ ๊ฒฝ์ฐ
- ๋ถ๊ท์นํ๊ฑฐ๋ ๋์ ์ธ ๋ฐ์ดํฐ ํฌ๊ธฐ๋ฅผ ๋ค๋ฃจ๋ ๊ฒฝ์ฐ
- ์๋ ๊ฒฝ๊ณ ์กฐ๊ฑด ๊ด๋ฆฌ ๋์ ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ๋ฅผ ์ํ๋ ๊ฒฝ์ฐ
- ๊ฒฝ๊ณ ๊ฒ์ฌ ๋ณต์ก๋๊ฐ ์ค๋ฅ๋ฅผ ์ ๋ฐํ ์ ์๋ ๊ฒฝ์ฐ
- ์๋ ๋ฃจํ ๊ด๋ฆฌ๋ณด๋ค ๊น๋ํ ๋ฒกํฐํ ํจํด์ ์ ํธํ๋ ๊ฒฝ์ฐ
๊ณ ๊ธ ์ต์ ํ ์ธ์ฌ์ดํธ
๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ:
์๋: 8 ์ค๋ ๋ ร 32 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
vectorize: 32 ์ค๋ ๋ ร 8 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
๋ ๋ค ๋น์ทํ ์ด ์ฒ๋ฆฌ๋์ ๋ฌ์ฑํ์ง๋ง, ๋ณ๋ ฌ์ฑ ์ ๋ต์ด ๋ค๋ฆ ๋๋ค.
์บ์ ๋์:
- ์๋: ๋ํ ์ฒญํฌ๊ฐ L1 ์บ์๋ฅผ ์ด๊ณผํ ์ ์์ง๋ง, ์๋ฒฝํ ์์ฐจ ์ ๊ทผ
- vectorize: ์์ ํ์ผ์ด ์บ์์ ๋ ์ ๋ง๊ณ , ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ
ํ๋์จ์ด ๋งคํ:
- ์๋: ์ํ ํ์ฉ๊ณผ SIMD ์ ๋ ๋งคํ์ ๋ํ ์ง์ ์ ์ด
- vectorize: ์๋ ๋ฃจํ ๋ฐ ๋๋จธ์ง ๊ด๋ฆฌ๋ฅผ ํตํ ๊ฐ์ํ๋ ๋ฒกํฐํ
๋ชจ๋ฒ ์ฌ๋ก ์์ฝ
์๋ ๋ฒกํฐํ ๋ชจ๋ฒ ์ฌ๋ก:
- ์ธ๋ฑ์ค ๊ณ์ฐ์ ํญ์ ์ ์คํ๊ฒ ๊ฒ์ฆ
- ๊ฐ๋ฅํ๋ฉด
chunk_size์ ์ปดํ์ผ ํ์ ์์ ์ฌ์ฉ - ์บ์ ์ต์ ํ๋ฅผ ์ํด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ๋กํ์ผ๋ง
- ์ต์ ์ SIMD ์ฑ๋ฅ์ ์ํ ์ ๋ ฌ ์๊ตฌ ์ฌํญ ๊ณ ๋ ค
Mojo vectorize ๋ชจ๋ฒ ์ฌ๋ก:
- ๋ฐ์ดํฐ์ ํ๋์จ์ด์ ์ ํฉํ SIMD ํญ ์ ํ
- ๋ฏธ์ธ ์ต์ ํ๋ณด๋ค ์๊ณ ๋ฆฌ์ฆ์ ๋ช ํ์ฑ์ ์ง์ค
- ๊น๋ํ ๋ฒกํฐํ ๋ก์ง์ ์ํด ์ค์ฒฉ ํ๋ผ๋ฏธํฐ ํจ์ ์ฌ์ฉ
- ๊ฒฝ๊ณ ์กฐ๊ฑด์๋ ์๋ ๊ฒฝ๊ณ ๊ฒ์ฌ์ ๋๋จธ์ง ์ฒ๋ฆฌ ์ ๋ขฐ
๋ ์ ๊ทผ๋ฒ ๋ชจ๋ GPU ์ฑ๋ฅ ์ต์ ํ ๋๊ตฌ ๋ชจ์์์ ์ ํจํ ์ ๋ต์ ๋๋ค. ์๋ ๋ฒกํฐํ๋ ์ต๋ํ์ ์ ์ด๋ฅผ, Mojo์ vectorize๋ ์์ ์ฑ๊ณผ ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ค์ ๋จ๊ณ
์ธ ๊ฐ์ง ๊ธฐ๋ณธ ํจํด์ ๋ชจ๋ ์ดํดํ๋ค๋ฉด:
- ๐ง GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋ : ์คํ ๊ณ์ธต ๊ตฌ์กฐ ์ดํด
- ๐ Mojo ๋ฒค์น๋งํน: ์ฑ๋ฅ ๋ถ์๊ณผ ์ต์ ํ
๐ก ํต์ฌ ์์ฝ: ๋ฒกํฐํ ์ ๋ต์ ์ฑ๋ฅ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ๋ฌ๋ฆฌ ์ ํํด์ผ ํฉ๋๋ค. ์๋ ๋ฒกํฐํ๋ ์ต๋ํ์ ์ ์ด๋ฅผ, Mojo์ vectorize ํจ์๋ ์์ ์ฑ๊ณผ ์๋ ๋๋จธ์ง ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ตฌ์ฒด์ ์ธ ์ฑ๋ฅ ์๊ตฌ ์ฌํญ๊ณผ ๊ฐ๋ฐ ์ ์ฝ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ ํํ์ธ์.
๐ง GPU ์ค๋ ๋ฉ vs SIMD - ์คํ ๊ณ์ธต ๊ตฌ์กฐ ์ดํดํ๊ธฐ
๊ฐ์
์์๋ณ, ํ์ผ๋ง, ๋ฒกํฐํ ํจํด์ ํ๊ตฌํ๋ฉด์ GPU ์ฐ์ฐ์ ๊ตฌ์ฑํ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ์ดํด๋ณด์์ต๋๋ค. ์ด ์น์ ์์๋ GPU ์ค๋ ๋์ SIMD ์ฐ์ฐ ์ฌ์ด์ ๊ทผ๋ณธ์ ์ธ ๊ด๊ณ๋ฅผ ๋ช ํํ ํฉ๋๋ค. ์ด ๋์ ์๋ก ๋ค๋ฅด์ง๋ง ์ํธ ๋ณด์์ ์ธ ๋ณ๋ ฌ์ฑ ์์ค์ผ๋ก, ์ต์ ์ ์ฑ๋ฅ์ ์ํด ํจ๊ป ๋์ํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ์ค๋ ๋๊ฐ ๋ณ๋ ฌ์ฑ์ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๊ณ , SIMD ์ฐ์ฐ์ด ๊ฐ ์ค๋ ๋ ๋ด์์ ๋ฒกํฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
GPU ์ค๋ ๋ฉ ๊ณ์ธต ๊ตฌ์กฐ
GPU ์คํ์ ํ๋์จ์ด์ ๋ณต์ก์ฑ์ ์ถ์ํํ๋ ์ ์ ์๋ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฆ ๋๋ค:
GPU Device
โโโ Grid (์ ์ฒด ๋ฌธ์ )
โ โโโ Block 1 (์ค๋ ๋ ๊ทธ๋ฃน, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ)
โ โ โโโ ์ํ 1 (32๊ฐ ์ค๋ ๋, ๋ก์คํ
์คํ)
โ โ โ โโโ Thread 1 โ SIMD ์ฐ์ฐ
โ โ โ โโโ Thread 2 โ SIMD ์ฐ์ฐ
โ โ โ โโโ ... (์ด 32๊ฐ ์ค๋ ๋)
โ โ โโโ ์ํ 2 (32๊ฐ ์ค๋ ๋)
โ โโโ Block 2 (๋
๋ฆฝ์ ์ธ ๊ทธ๋ฃน)
๐ก ์ฐธ๊ณ : ์ด Part๋ ํจ์ํ ํจํด์ ์ด์ ์ ๋ง์ถ๊ณ ์์ผ๋ฉฐ, ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๊ณ ๊ธ GPU ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ Part VII ์์ ์์ธํ ๋ค๋ฃน๋๋ค.
Mojo๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ๋ค:
- ๊ทธ๋ฆฌ๋/๋ธ๋ก ๊ตฌ์ฑ: ๋ฌธ์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์๋ ๊ณ์ฐ
- ์ํ ๊ด๋ฆฌ: ํ๋์จ์ด๊ฐ 32๊ฐ ์ค๋ ๋ ๊ทธ๋ฃน์ ํฌ๋ช ํ๊ฒ ์ฒ๋ฆฌ
- ์ค๋ ๋ ์ค์ผ์ค๋ง: GPU ์ค์ผ์ค๋ฌ๊ฐ ์คํ์ ์๋ ๊ด๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ: ํจ์ํ ์ฐ์ฐ์ ์ต์ ์ ์ ๊ทผ ํจํด ๋ด์ฅ
GPU ์ค๋ ๋ ๋ด์ SIMD
๊ฐ GPU ์ค๋ ๋๋ SIMD (Single Instruction, Multiple Data) ์ฐ์ฐ์ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ๋ฐ์ดํฐ ์์๋ฅผ ๋์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค:
# ํ๋์ GPU ์ค๋ ๋ ๋ด๋ถ:
a_simd = a.load[simd_width](idx, 0) # float 4๊ฐ๋ฅผ ๋์์ ๋ก๋
b_simd = b.load[simd_width](idx, 0) # float 4๊ฐ๋ฅผ ๋์์ ๋ก๋
result = a_simd + b_simd # 4์์ ๋์์ ๋ง์
output.store[simd_width](idx, 0, result) # ๊ฒฐ๊ณผ 4๊ฐ๋ฅผ ๋์์ ์ ์ฅ
ํจํด ๋น๊ต์ ์ค๋ ๋-์์ ๋งคํ
ํต์ฌ ์ธ์ฌ์ดํธ: ๋ชจ๋ ํจํด์ ๋์ผํ ์ด ์์ ๋ - SIMD_WIDTH=4๋ก 1024๊ฐ ์์์ ๋ํด 256ํ์ SIMD ์ฐ์ฐ - ์ ์ํํฉ๋๋ค. ์ฐจ์ด์ ์ ์ด ์์ ์ด GPU ์ค๋ ๋์ ์ด๋ป๊ฒ ๋ถ๋ฐฐ๋๋๋์ ์์ต๋๋ค.
์ค๋ ๋ ๊ตฌ์ฑ ๋น๊ต (SIZE=1024, SIMD_WIDTH=4)
| ํจํด | ์ค๋ ๋ ์ | ์ค๋ ๋๋น SIMD ์ฐ์ฐ | ๋ฉ๋ชจ๋ฆฌ ํจํด | ํธ๋ ์ด๋์คํ |
|---|---|---|---|---|
| ์์๋ณ | 256 | 1 | ๋ถ์ฐ ์ ๊ทผ | ์ต๋ ๋ณ๋ ฌ์ฑ, ๋ฎ์ ์ง์ญ์ฑ |
| ํ์ผ๋ง | 32 | 8 | ์ํ ๋ธ๋ก | ๋ณ๋ ฌ์ฑ + ์ง์ญ์ฑ ๊ท ํ |
| ์๋ ๋ฒกํฐํ | 8 | 32 | ๋ํ ์ฒญํฌ | ๋์ ๋์ญํญ, ์ ์ ์ค๋ ๋ |
| Mojo vectorize | 32 | 8 | ์ค๋งํธ ๋ธ๋ก | ์๋ ์ต์ ํ |
์์ธ ์คํ ํจํด
์์๋ณ ํจํด:
Thread 0: [0,1,2,3] โ Thread 1: [4,5,6,7] โ ... โ Thread 255: [1020,1021,1022,1023]
256 ์ค๋ ๋ ร 1 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
ํ์ผ๋ง ํจํด:
Thread 0: [0:32] (8 SIMD) โ Thread 1: [32:64] (8 SIMD) โ ... โ Thread 31: [992:1024] (8 SIMD)
32 ์ค๋ ๋ ร 8 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
์๋ ๋ฒกํฐํ ํจํด:
Thread 0: [0:128] (32 SIMD) โ Thread 1: [128:256] (32 SIMD) โ ... โ Thread 7: [896:1024] (32 SIMD)
8 ์ค๋ ๋ ร 32 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
Mojo vectorize ํจํด:
Thread 0: [0:32] ์๋ ๋ฒกํฐํ โ Thread 1: [32:64] ์๋ ๋ฒกํฐํ โ ... โ Thread 31: [992:1024] ์๋ ๋ฒกํฐํ
32 ์ค๋ ๋ ร 8 SIMD ์ฐ์ฐ = ์ด 256ํ SIMD ์ฐ์ฐ
์ฑ๋ฅ ํน์ฑ๊ณผ ํธ๋ ์ด๋์คํ
ํต์ฌ ํธ๋ ์ด๋์คํ ์์ฝ
| ์ธก๋ฉด | ์ค๋ ๋ ๋ง์ (์์๋ณ) | ์ค๋ ๋ ์ค๊ฐ (ํ์ผ๋ง/vectorize) | ์ค๋ ๋ ์ ์ (์๋) |
|---|---|---|---|
| ๋ณ๋ ฌ์ฑ | ์ต๋ ์ง์ฐ ์๊ฐ ์๋ | ๊ท ํ ์กํ ์ ๊ทผ | ์ต์ํ์ ๋ณ๋ ฌ์ฑ |
| ์บ์ ์ง์ญ์ฑ | ์ค๋ ๋ ๊ฐ ๋ฎ์ | ํ์ผ ๋ด์์ ์ํธ | ์์ฐจ ์ ๊ทผ์ผ๋ก ์ฐ์ |
| ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ | ์ํธํ ๋ณํฉ | ์ํธ + ์บ์ ์ฌ์ฌ์ฉ | ์ด๋ก ์ ์ต๋๊ฐ |
| ๋ณต์ก๋ | ๊ฐ์ฅ ๋จ์ | ๋ณดํต | ๊ฐ์ฅ ๋ณต์ก |
๊ฐ ํจํด์ ์ ํ ๊ธฐ์ค
์์๋ณ ํจํด์ ์ฌ์ฉํ ๋:
- ์์๋น ์ฐ์ฐ๋์ด ์ ์ ๋จ์ํ ์ฐ์ฐ
- ์ง์ฐ ์๊ฐ ์๋์ ์ํด ์ต๋ ๋ณ๋ ฌ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ
- ๋ค์ํ ๋ฌธ์ ํฌ๊ธฐ์ ๋ํ ํ์ฅ์ฑ์ด ์ค์ํ ๊ฒฝ์ฐ
ํ์ผ๋ง/vectorize๋ฅผ ์ฌ์ฉํ ๋:
- ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ์ ์ด์ ์ด ์๋ ์บ์ ๋ฏผ๊ฐ ์ฐ์ฐ
- ์ฑ๋ฅ๊ณผ ์ ์ง๋ณด์์ฑ์ ๊ท ํ์ด ํ์ํ ๊ฒฝ์ฐ
- ์๋ ์ต์ ํ(vectorize)๊ฐ ์ ํธ๋๋ ๊ฒฝ์ฐ
์๋ ๋ฒกํฐํ๋ฅผ ์ฌ์ฉํ ๋:
- ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๋ํ ์ ๋ฌธ๊ฐ ์์ค์ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ์ต๋ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ์ด ์ค์ํ ๊ฒฝ์ฐ
- ๊ฐ๋ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ํ ์ ์๋ ๊ฒฝ์ฐ
ํ๋์จ์ด ๊ณ ๋ ค ์ฌํญ
ํ๋ GPU ์ํคํ ์ฒ์๋ Mojo๊ฐ ์ถ์ํํ๋ ์ฌ๋ฌ ์์ค์ด ์์ต๋๋ค:
ํ๋์จ์ด ์ค์ ๊ตฌ์กฐ:
- ์ํ: 32๊ฐ ์ค๋ ๋๊ฐ ๋ก์คํ ์ผ๋ก ์คํ
- Streaming Multiprocessor (SM): ์ฌ๋ฌ ์ํ๊ฐ ๋์์ ์คํ
- SIMD ์ ๋: ๊ฐ SM ๋ด์ ๋ฒกํฐ ์ฒ๋ฆฌ ์ ๋
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ: L1/L2 ์บ์, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์ ์ญ ๋ฉ๋ชจ๋ฆฌ
Mojo ์ถ์ํ์ ์ด์ :
- ์ํ ์ ๋ ฌ๊ณผ ์ค์ผ์ค๋ง์ ์๋์ผ๋ก ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ํฌ๋ช ํ๊ฒ ์ต์ ํ
- SM ๊ฐ ๋ฆฌ์์ค ํ ๋น์ ๊ด๋ฆฌ
- GPU ๋ฒค๋ ๊ฐ ์ด์ ๊ฐ๋ฅํ ์ฑ๋ฅ ์ ๊ณต
์ฑ๋ฅ์ ๋ํ ์ฌ๊ณ ๋ชจ๋ธ
GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ ๊ฐ์ง ์ํธ ๋ณด์์ ์ธ ๋ณ๋ ฌ์ฑ ์ ํ์ ๊ด๋ฆฌํ๋ ๊ฒ์ผ๋ก ์๊ฐํ์ธ์:
์ค๋ ๋ ์์ค ๋ณ๋ ฌ์ฑ:
- ๋ณ๋ ฌ ๊ตฌ์กฐ๋ฅผ ์ ๊ณต (์คํ ์ ๋์ ์)
- ๋์ ์คํ์ ํตํ ์ง์ฐ ์๊ฐ ์๋ ๊ฐ๋ฅ
- GPU ์ค์ผ์ค๋ฌ๊ฐ ์๋์ผ๋ก ๊ด๋ฆฌ
SIMD ์์ค ๋ณ๋ ฌ์ฑ:
- ๊ฐ ์ค๋ ๋ ๋ด์์ ๋ฒกํฐํ๋ฅผ ์ ๊ณต
- ์ค๋ ๋๋น ์ฐ์ ์ฒ๋ฆฌ๋์ ๊ทน๋ํ
- ๋ฒกํฐ ์ฒ๋ฆฌ ์ ๋์ ํจ์จ์ ์ผ๋ก ํ์ฉ
์ต์ ์ฑ๋ฅ ๊ณต์:
์ฑ๋ฅ = (์ง์ฐ ์๊ฐ ์๋์ ์ํ ์ถฉ๋ถํ ์ค๋ ๋) ร
(ํจ์จ์ ์ธ SIMD ํ์ฉ) ร
(์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด)
ํ์ฅ์ฑ ๊ณ ๋ ค ์ฌํญ
| ๋ฌธ์ ํฌ๊ธฐ | ์ต์ ํจํด | ๊ทผ๊ฑฐ |
|---|---|---|
| ์๊ท๋ชจ (< 1K) | ํ์ผ๋ง/vectorize | ๋ฎ์ ์คํ ์ค๋ฒํค๋ |
| ์ค๊ท๋ชจ (1K-1M) | ๋ชจ๋ ํจํด | ์ ์ฌํ ์ฑ๋ฅ |
| ๋๊ท๋ชจ (> 1M) | ๋ณดํต ์์๋ณ | ๋ณ๋ ฌ์ฑ์ด ์ง๋ฐฐ์ |
์ต์ ์ ์ ํ์ ํน์ ํ๋์จ์ด, ์ํฌ๋ก๋ ๋ณต์ก๋, ๊ฐ๋ฐ ์ ์ฝ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
๋ค์ ๋จ๊ณ
GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋ ์ ํ์คํ ์ดํดํ๋ค๋ฉด:
- ๐ Mojo ๋ฒค์น๋งํน: ์ค์ ์ฑ๋ฅ์ ์ธก์ ํ๊ณ ๋น๊ต
๐ก ํต์ฌ ์์ฝ: GPU ์ค๋ ๋์ SIMD ์ฐ์ฐ์ ์ํธ ๋ณด์์ ์ธ ๋ณ๋ ฌ์ฑ ์์ค์ผ๋ก ํจ๊ป ๋์ํฉ๋๋ค. ์ด ๋์ ๊ด๊ณ๋ฅผ ์ดํดํ๋ฉด ๊ตฌ์ฒด์ ์ธ ์ฑ๋ฅ ์๊ตฌ ์ฌํญ๊ณผ ์ ์ฝ ์กฐ๊ฑด์ ๋ง๋ ์ฌ๋ฐ๋ฅธ ํจํด์ ์ ํํ ์ ์์ต๋๋ค.
๐ Mojo ๋ฒค์น๋งํน - ์ฑ๋ฅ ๋ถ์๊ณผ ์ต์ ํ
๊ฐ์
์์๋ณ, ํ์ผ๋ง, ์๋ ๋ฒกํฐํ, Mojo vectorize ํจํด์ ํ์ตํ ํ,
์ด์ ์ค์ ์ฑ๋ฅ์ ์ธก์ ํ ์ฐจ๋ก์
๋๋ค. p21.mojo์ ๋ด์ฅ๋ ๋ฒค์น๋งํน ์์คํ
์
์ฌ์ฉํ์ฌ ์ด๋ฌํ ์ ๊ทผ๋ฒ์ ๊ณผํ์ ์ผ๋ก ๋น๊ตํ๊ณ ์ฑ๋ฅ ํน์ฑ์ ์ดํดํ๋ ๋ฐฉ๋ฒ์
์์๋ด
๋๋ค.
ํต์ฌ ํต์ฐฐ: ์ด๋ก ์ ๋ถ์์ ๊ฐ์น ์์ง๋ง, ์ค์ฆ์ ๋ฒค์น๋งํน์ด ํน์ ํ๋์จ์ด์์์ ์ค์ ์ฑ๋ฅ์ ๋ณด์ฌ์ค๋๋ค.
๋ฒค์น๋งํฌ ์คํ
์ ์ฒด ๋ฒค์น๋งํฌ๋ฅผ ์คํํ๋ ค๋ฉด:
pixi run p23 --benchmark
pixi run -e amd p23 --benchmark
pixi run -e apple p23 --benchmark
uv run poe p23 --benchmark
๊ฐ ํจํด์ ๋ํ ์ฑ๋ฅ ์ธก์ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค:
SIZE: 1024
simd_width: 4
Running P21 GPU Benchmarks...
SIMD width: 4
--------------------------------------------------------------------------------
Testing SIZE=16, TILE=4
Running elementwise_16_4
Running tiled_16_4
Running manual_vectorized_16_4
Running vectorized_16_4
--------------------------------------------------------------------------------
Testing SIZE=128, TILE=16
Running elementwise_128_16
Running tiled_128_16
Running manual_vectorized_128_16
--------------------------------------------------------------------------------
Testing SIZE=128, TILE=16, Vectorize within tiles
Running vectorized_128_16
--------------------------------------------------------------------------------
Testing SIZE=1048576 (1M), TILE=1024
Running elementwise_1M_1024
Running tiled_1M_1024
Running manual_vectorized_1M_1024
Running vectorized_1M_1024
| name | met (ms) | iters |
| ------------------------- | --------------------- | ----- |
| elementwise_16_4 | 0.0033248 | 100 |
| tiled_16_4 | 0.00327392 | 100 |
| manual_vectorized_16_4 | 0.0036169600000000002 | 100 |
| vectorized_16_4 | 0.0037209599999999997 | 100 |
| elementwise_128_16 | 0.00351999 | 100 |
| tiled_128_16 | 0.00370431 | 100 |
| manual_vectorized_128_16 | 0.0043696 | 100 |
| vectorized_128_16 | 0.00378048 | 100 |
| elementwise_1M_1024 | 0.03130143 | 100 |
| tiled_1M_1024 | 0.6892189000000001 | 100 |
| manual_vectorized_1M_1024 | 0.5923888 | 100 |
| vectorized_1M_1024 | 0.1876688 | 100 |
Benchmarks completed!
๋ฒค์น๋งํฌ ์ค์
๋ฒค์น๋งํน ์์คํ
์ Mojo์ ๋ด์ฅ benchmark ๋ชจ๋์ ์ฌ์ฉํฉ๋๋ค:
from benchmark import Bench, BenchConfig, Bencher, BenchId, keep
bench_config = BenchConfig(max_iters=10, num_warmup_iters=1)
max_iters=10: ํต๊ณ์ ์ ๋ขฐ์ฑ์ ์ํด ์ต๋ 10ํ ๋ฐ๋ณตnum_warmup_iters=1: ์ธก์ ์ GPU ์๋ฐ์- Benchmark ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์
๋ฒค์น๋งํน ๊ตฌํ์ ํต์ฌ
ํต์ฌ ์ํฌํ๋ก์ฐ ํจํด
๊ฐ ๋ฒค์น๋งํฌ๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ๊ฒฐํ ํจํด์ ๋ฐ๋ฆ ๋๋ค:
@parameter
def benchmark_pattern_parameterized[test_size: Int, tile_size: Int](mut b: Bencher) raises:
bench_ctx = DeviceContext()
# ์
์
: ๋ฒํผ ์์ฑ ๋ฐ ๋ฐ์ดํฐ ์ด๊ธฐํ
@parameter
def pattern_workflow(ctx: DeviceContext) raises:
# ์ฐ์ฐ: ์ธก์ ๋์ ์๊ณ ๋ฆฌ์ฆ ์คํ
b.iter_custom[pattern_workflow](bench_ctx)
# ์ต์ ํ ๋ฐฉ์ง: keep(out.unsafe_ptr())
# ๋๊ธฐํ: ctx.synchronize()
์ฃผ์ ๋จ๊ณ:
- ์ ์ : ๋ฒํผ ํ ๋น ๋ฐ ๋ฐ์ดํฐ ์ด๊ธฐํ
- ์ฐ์ฐ: ๋ฒค์น๋งํฌ ๋์ ์๊ณ ๋ฆฌ์ฆ ์คํ
- ์ต์ ํ ๋ฐฉ์ง: ์ ํํ ์ธก์ ์ ์ํด ํ์
- ๋๊ธฐํ: GPU ์์ ์๋ฃ ํ์ธ
์ค์:
keep()ํจ์keep(out.unsafe_ptr())๋ ์ปดํ์ผ๋ฌ๊ฐ ์ฐ์ฐ ๊ฒฐ๊ณผ๋ฅผ โ์ฌ์ฉ๋์ง ์๋ ์ฝ๋โ๋ก ์ต์ ํํ์ฌ ์ ๊ฑฐํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ์ด๊ฒ์ด ์์ผ๋ฉด ์๊ณ ๋ฆฌ์ฆ ๋์ ์๋ฌด๊ฒ๋ ์ธก์ ํ์ง ๋ชปํ ์ ์์ต๋๋ค! GPU ์ปค๋์ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ์ ํํ GPU ๋ฒค์น๋งํน์ ํ์์ ์ ๋๋ค.
์ปค์คํ ๋ฐ๋ณต์ด GPU์ ํ์ํ ์ด์
์ผ๋ฐ์ ์ธ ๋ฒค์น๋งํน์ CPU ์คํ์ผ์ ๋๊ธฐ ์คํ์ ๊ฐ์ ํฉ๋๋ค. GPU ์ปค๋์ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋๋ฏ๋ก ๋ค์์ด ํ์ํฉ๋๋ค:
- GPU ์ปจํ ์คํธ ๊ด๋ฆฌ: ์ ์ ํ DeviceContext ์๋ช ์ฃผ๊ธฐ
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ: ๋ฐ๋ณต ๊ฐ ๋ฒํผ ์ ๋ฆฌ
- ๋๊ธฐํ ์ฒ๋ฆฌ: ๋น๋๊ธฐ ์ฐ์ฐ์ ์ ํํ ํ์ด๋ฐ
- ์ค๋ฒํค๋ ๋ถ๋ฆฌ: ์ ์ ๋น์ฉ๊ณผ ์ฐ์ฐ ๋น์ฉ์ ๋ถ๋ฆฌ
ํ ์คํธ ์๋๋ฆฌ์ค์ ์ค๋ ๋ ๋ถ์
๋ฒค์น๋งํฌ ๋ชจ์์ ์ฑ๋ฅ ํน์ฑ์ ํ์ ํ๊ธฐ ์ํด ์ธ ๊ฐ์ง ์๋๋ฆฌ์ค๋ฅผ ํ ์คํธํฉ๋๋ค:
์ค๋ ๋ ํ์ฉ ์์ฝ
| ๋ฌธ์ ํฌ๊ธฐ | ํจํด | ์ค๋ ๋ ์ | ์ค๋ ๋๋น SIMD ์ฐ์ฐ | ์ด SIMD ์ฐ์ฐ |
|---|---|---|---|---|
| SIZE=16 | ์์๋ณ | 4 | 1 | 4 |
| ํ์ผ๋ง | 4 | 1 | 4 | |
| ์๋ | 1 | 4 | 4 | |
| vectorize | 4 | 1 | 4 | |
| SIZE=128 | ์์๋ณ | 32 | 1 | 32 |
| ํ์ผ๋ง | 8 | 4 | 32 | |
| ์๋ | 2 | 16 | 32 | |
| vectorize | 8 | 4 | 32 | |
| SIZE=1M | ์์๋ณ | 262,144 | 1 | 262,144 |
| ํ์ผ๋ง | 1,024 | 256 | 262,144 | |
| ์๋ | 256 | 1,024 | 262,144 | |
| vectorize | 1,024 | 256 | 262,144 |
๋ฌธ์ ํฌ๊ธฐ๋ณ ์ฑ๋ฅ ํน์ฑ
์๊ท๋ชจ ๋ฌธ์ (SIZE=16):
- ์คํ ์ค๋ฒํค๋๊ฐ ์ง๋ฐฐ์ (~0.003ms ๊ธฐ์ค์ )
- ์ค๋ ๋ ์ ์ฐจ์ด๋ ๊ฑฐ์ ๋ฌด์๋ฏธ
- ํ์ผ๋ง/vectorize๊ฐ ์ฝ๊ฐ ๋ฎ์ ์ค๋ฒํค๋๋ฅผ ๋ณด์
์ค๊ท๋ชจ ๋ฌธ์ (SIZE=128):
- ์ฌ์ ํ ์ค๋ฒํค๋๊ฐ ์ง๋ฐฐ์ (~0.003ms ์ ํจํด)
- ์ฑ๋ฅ ์ฐจ์ด๊ฐ ๊ฑฐ์ ์ฌ๋ผ์ง
- ์ค๋ฒํค๋ ์ง๋ฐฐ์์ ์ฐ์ฐ ์ง๋ฐฐ๋ก์ ์ ํ ๊ตฌ๊ฐ
๋๊ท๋ชจ ๋ฌธ์ (SIZE=1M):
- ์ค์ง์ ์ธ ์๊ณ ๋ฆฌ์ฆ ์ฐจ์ด๊ฐ ๋๋ฌ๋จ
- ๋น๋ณํฉ ๋ก๋์ ์ํฅ์ด ๋ช ํํด์ง
- ๋๋ ทํ ์ฑ๋ฅ ์์๊ฐ ๋ํ๋จ
๋ฐ์ดํฐ๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ
๋ค์ํ ํ๋์จ์ด์์์ ์ค์ฆ์ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก:
์ฑ๋ฅ ์์ (๋๊ท๋ชจ ๋ฌธ์ )
| ์์ | ํจํด | ์์ ์๊ฐ | ํต์ฌ ์ธ์ฌ์ดํธ |
|---|---|---|---|
| ๐ฅ | ์์๋ณ | ~0.03ms | ๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์ ์น๋ฆฌ |
| ๐ฅ | Mojo vectorize | ~0.19ms | ๋น๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ฑ๋ฅ์ ์ ํ |
| ๐ฅ | ์๋ ๋ฒกํฐํ | ~0.59ms | ๋น๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๊ณผ ์๋ ์ต์ ํ๊ฐ ์ฑ๋ฅ ๊ฐ์ |
| 4์ | ํ์ผ๋ง | ~0.69ms | ๋น๋ณํฉ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ, SIMD ๋ก๋ ์๋ ์๋ ์ต์ ํ๊ฐ ์ฑ๋ฅ์ ๋ ์ ํ |
ํต์ฌ ์ฑ๋ฅ ์ธ์ฌ์ดํธ
๋จ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์ ๊ฒฝ์ฐ: ์ต๋ ๋ณ๋ ฌ์ฑ(elementwise)์ด ๋๊ท๋ชจ์์ ๋ณต์กํ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ณด๋ค ์ฐ์ํฉ๋๋ค.
์์๋ณ ํจํด์ด ์น๋ฆฌํ๋ ์ด์ :
- 262,144๊ฐ ์ค๋ ๋๊ฐ ์ฐ์ํ ์ง์ฐ ์๊ฐ ์๋์ ์ ๊ณต
- ๋จ์ํ ๋ฉ๋ชจ๋ฆฌ ํจํด์ด ์ข์ ๋ณํฉ์ ๋ฌ์ฑ
- ์ค๋ ๋๋น ์ต์ํ์ ์ค๋ฒํค๋
- GPU ์ฝ์ด ์์ ๋ฐ๋ผ ์์ฐ์ค๋ฝ๊ฒ ํ์ฅ
ํ์ผ๋ง๊ณผ vectorize๊ฐ ๊ฒฝ์๋ ฅ ์๋ ์ด์ :
- ๋ณ๋ ฌ์ฑ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ ์ฌ์ด์ ๊ท ํ ์กํ ์ ๊ทผ
- ์๋ ์ต์ ํ(vectorize)๊ฐ ์๋ ํ์ผ๋ง๊ณผ ๊ฑฐ์ ๋๋ฑํ ์ฑ๋ฅ
- ๊ณผ๋ํ ๋ณต์ก๋ ์์ด ์ํธํ ์ค๋ ๋ ํ์ฉ
์๋ ๋ฒกํฐํ๊ฐ ๊ณ ์ ํ๋ ์ด์ :
- 256๊ฐ ์ค๋ ๋๋ง์ผ๋ก๋ ๋ณ๋ ฌ์ฑ์ด ์ ํ์
- ๋ณต์กํ ์ธ๋ฑ์ฑ์ด ์ฐ์ฐ ์ค๋ฒํค๋๋ฅผ ์ถ๊ฐ
- ์ค๋ ๋๋น ๋ํ ์ฒญํฌ๋ก ์ธํ ์บ์ ๋ถ๋ด
- ๋จ์ ์ฐ์ ์์ ํจ๊ณผ ์ฒด๊ฐ
ํ๋ ์์ํฌ ์๋ํ ๊ธฐ๋ฅ:
- ์๋ ๋ฐ๋ณต ํ์ ์กฐ์ (91-100ํ ๋ฐ๋ณต)
- ์๋ก ๋ค๋ฅธ ์คํ ์๊ฐ์ ๊ฑธ์น ํต๊ณ์ ์ ๋ขฐ์ฑ
- ๋ฐ์ด ์ ํ๊ณผ ์์คํ ๋ณ๋์ ๋์
๊ฒฐ๊ณผ ํด์ํ๊ธฐ
์ถ๋ ฅ ํ ์ด๋ธ ์ฝ๊ธฐ
| name | met (ms) | iters |
| elementwise_1M_1024 | 0.03130143 | 100 |
met (ms): ๋จ์ผ ๋ฐ๋ณต์ ์คํ ์๊ฐiters: ์ํ๋ ๋ฐ๋ณต ํ์- ๋์ผ ๋ฌธ์ ํฌ๊ธฐ ๋ด์์ ๋น๊ต: ๊ฐ์ ํฌ๊ธฐ๋ผ๋ฆฌ ๋น๊ตํ๋ ๊ฒ์ด ๊ฐ์ฅ ์๋ฏธ ์์
์ต์ ํ ์์ฌ๊ฒฐ์
์ค์ฆ์ ์ฆ๊ฑฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํจํด์ ์ ํํ์ธ์:
ํ๋ก๋์ ์ํฌ๋ก๋์ ๊ฒฝ์ฐ:
- ๋๊ท๋ชจ ๋ฐ์ดํฐ์ (>100K ์์): ์์๋ณ ํจํด์ด ์ผ๋ฐ์ ์ผ๋ก ์ต์
- ์๊ท๋ชจ/์์ ๋ฐ์ดํฐ์ (<1K ์์): ๋ฎ์ ์ค๋ฒํค๋๋ฅผ ์ํด ํ์ผ๋ง ๋๋ vectorize
- ๊ฐ๋ฐ ์๋ ์ฐ์ : ์๋ ์ต์ ํ๋ฅผ ์ํ Mojo vectorize
- ์๋ ๋ฒกํฐํ ์ง์: ๋จ์ ์ฐ์ฐ์์๋ ๋ณต์ก๋๊ฐ ์ฑ๋ฅ์ผ๋ก ๋ณด์๋๋ ๊ฒฝ์ฐ๊ฐ ๋๋ฌพ
์ฑ๋ฅ ์ต์ ํ ์ํฌํ๋ก์ฐ:
- ๋จผ์ ํ๋กํ์ผ๋ง: ์ต์ ํํ๊ธฐ ์ ์ ์ธก์
- ๋๊ท๋ชจ์์ ํ ์คํธ: ์๊ท๋ชจ ๋ฌธ์ ๋ ์ค์ ์ฑ๋ฅ์ ๋ํด ์คํด๋ฅผ ์ค ์ ์์
- ์ด๋น์ฉ ๊ณ ๋ ค: ๊ฐ๋ฐ ๋ฐ ์ ์ง๋ณด์ ๋ ธ๋ ฅ์ ํฌํจ
- ๊ฐ์ ์ฌํญ ๊ฒ์ฆ: ๋์ ํ๋์จ์ด์์ ๋ฒค์น๋งํฌ๋ก ํ์ธ
๊ณ ๊ธ ๋ฒค์น๋งํน ๊ธฐ๋ฒ
์ปค์คํ ํ ์คํธ ์๋๋ฆฌ์ค
๋งค๊ฐ๋ณ์๋ฅผ ์์ ํ์ฌ ๋ค์ํ ์กฐ๊ฑด์ ํ ์คํธํ ์ ์์ต๋๋ค:
# ๋ค์ํ ๋ฌธ์ ํฌ๊ธฐ
benchmark_elementwise_parameterized[1024, 32] # ๋๊ท๋ชจ ๋ฌธ์
benchmark_elementwise_parameterized[64, 8] # ์๊ท๋ชจ ๋ฌธ์
# ๋ค์ํ ํ์ผ ํฌ๊ธฐ
benchmark_tiled_parameterized[256, 8] # ์์ ํ์ผ
benchmark_tiled_parameterized[256, 64] # ํฐ ํ์ผ
ํ๋์จ์ด ๊ณ ๋ ค ์ฌํญ
๊ฒฐ๊ณผ๋ ๋ค์์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค:
- GPU ์ํคํ ์ฒ: SIMD ํญ, ์ฝ์ด ์, ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ
- ์์คํ ๊ตฌ์ฑ: PCIe ๋์ญํญ, CPU ์ฑ๋ฅ
- ์ด ์ํ: GPU ๋ถ์คํธ ํด๋ญ vs ์ง์ ์ฑ๋ฅ
- ๋์ ์ํฌ๋ก๋: GPU ํ์ฉ์ ์ํฅ์ ์ฃผ๋ ๋ค๋ฅธ ํ๋ก์ธ์ค
๋ชจ๋ฒ ์ฌ๋ก ์์ฝ
๋ฒค์น๋งํน ์ํฌํ๋ก์ฐ:
- ์ค์ํ ์ธก์ ์ ์ GPU ์๋ฐ์
- ํต๊ณ์ ์ ์์ฑ์ ์ํด ์ฌ๋ฌ ๋ฒ ๋ฐ๋ณต ์คํ
- ํ์ฅ ํน์ฑ์ ์ดํดํ๊ธฐ ์ํด ๋ค์ํ ๋ฌธ์ ํฌ๊ธฐ ํ ์คํธ
- ์ต์ ํ ์ํฐํฉํธ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด
keep()์ ์ผ๊ด๋๊ฒ ์ฌ์ฉ - ๋์ผ ์กฐ๊ฑด์์ ๋น๊ต (๊ฐ์ ๋ฌธ์ ํฌ๊ธฐ, ๊ฐ์ ํ๋์จ์ด)
์ฑ๋ฅ ์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ:
- ๋จ์ํ๊ฒ ์์: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์๋ ์์๋ณ ํจํด๋ถํฐ
- ์ถ์ธกํ์ง ๋ง๊ณ ์ธก์ : ์ด๋ก ์ ๋ถ์์ ๋ฐฉํฅ์, ์ค์ฆ์ ๋ฐ์ดํฐ๊ฐ ๊ฒฐ์ ์
- ๊ท๋ชจ๊ฐ ์ค์: ์๊ท๋ชจ ๋ฌธ์ ์ ์ฑ๋ฅ์ด ๋๊ท๋ชจ ๋ฌธ์ ์ ๋์์ ์์ธกํ์ง ๋ชปํจ
- ์ด๋น์ฉ ์ต์ ํ: ๊ฐ๋ฐ ์๊ฐ vs ๋ฐํ์ ์ฑ๋ฅ์ ๊ท ํ
๋ค์ ๋จ๊ณ
๋ฒค์น๋งํน ๊ธฐ์ ์ ๊ฐ์ถ์๋ค๋ฉด:
- ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ํ๋กํ์ผ๋ง: ์ด ํจํด๋ค์ ์ค์ ์ํฌ๋ก๋์ ์ ์ฉ
- ๊ณ ๊ธ GPU ํจํด: ๋ฆฌ๋์ , ํฉ์ฑ๊ณฑ, ํ๋ ฌ ์ฐ์ฐ ํ๊ตฌ
- ๋ฉํฐ GPU ํ์ฅ: ๋ถ์ฐ GPU ์ปดํจํ ํจํด ์ดํด
- ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ ๊ธ ์บ์ฑ์ ๋ ๊น์ด ํ๊ตฌ
๐ก ํต์ฌ ์์ฝ: ๋ฒค์น๋งํน์ ์ด๋ก ์ ์ดํด๋ฅผ ์ค์ง์ ์ธ ์ฑ๋ฅ ์ต์ ํ๋ก ์ ํํฉ๋๋ค. ์ค์ฆ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ํ๋์จ์ด์ ์ํฌ๋ก๋ ํน์ฑ์ ๊ฐ์ฅ ์ ํฉํ ํจํด์ ์ ํํ์ธ์.
์์ผ๋ก์ ๋ฐฉํฅ: ๋ ๋ง์ ์ ์ด๊ฐ ํ์ํ ๋
Part VI์ ํจ์ํ ํจํด์ ๋๋ถ๋ถ์ ์ํฌ๋ก๋์์ ์ฐ์ํ ์ฑ๋ฅ์ ์ ๊ณตํ์ง๋ง, ์ผ๋ถ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ ์ ์ธ ์ค๋ ๋ ๊ฐ ํต์ ์ด ํ์ํฉ๋๋ค:
์ํ ํ๋ก๊ทธ๋๋ฐ์ด ์ ์ฉํ ์๊ณ ๋ฆฌ์ฆ:
- ๋ฆฌ๋์ : ์ค๋ ๋ ๊ทธ๋ฃน์ ๊ฑธ์น ํฉ๊ณ, ์ต๋๊ฐ, ์ต์๊ฐ ์ฐ์ฐ
- ๋์ ์ฐ์ฐ: ๋์ ํฉ, ์ด๋ ์ต๋๊ฐ
- ๋ฐ์ดํฐ ์ ํ: ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ ์ฌ๋ฐฐ์น
- ํ๋ ฅ ์๊ณ ๋ฆฌ์ฆ: ์ค๋ ๋ ๊ฐ ๊ธด๋ฐํ ์กฐ์ ์ด ํ์ํ ๊ฒฝ์ฐ
์ฑ๋ฅ ๋ฏธ๋ฆฌ๋ณด๊ธฐ:
Part VII์์๋ Part III์ ์ฌ๋ฌ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ์ดํด๋ณด๋ฉฐ ์ํ ์ฐ์ฐ์ด ์ด๋ป๊ฒ:
- ์ฝ๋๋ฅผ ๊ฐ์ํํ๋์ง: ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒด
- ์ฑ๋ฅ์ ํฅ์์ํค๋์ง: ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ ๊ฑฐํ๊ณ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ์ ๊ฐ์
- ์๋ก์ด ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํ๋์ง: ์์ ํจ์ํ ์ ๊ทผ์ผ๋ก๋ ๋ถ๊ฐ๋ฅํ ํจํด์ ๊ตฌํ
๋ค์ ๋ด์ฉ: Part VII: ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ - Puzzle 14์ ๋์ ํฉ์ ์์ ํ ์๋กญ๊ฒ ๊ตฌํํ๋ ๊ฒ๋ถํฐ ์์ํฉ๋๋ค.
Puzzle 24: ์ํ ๊ธฐ์ด
๊ฐ์
Part VII: ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ์์๋ GPU์ ์ํ ๋ ๋ฒจ ๊ธฐ๋ณธ ์์ - ์ํ ๋ด ๋๊ธฐํ๋ ์ค๋ ๋ ์คํ์ ํ์ฉํ๋ ํ๋์จ์ด ๊ฐ์ ์ฐ์ฐ์ ์๊ฐํฉ๋๋ค. ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๊ฐ๋จํ๊ณ ํจ์จ์ ์ธ ํจ์ ํธ์ถ๋ก ๋์ฒดํ๋ ๋ด์ฅ ์ํ ์ฐ์ฐ์ ๋ฐฐ์๋๋ค.
๋ชฉํ: ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด์ ํ๋์จ์ด ๋๊ธฐํ๋ฅผ ํ์ฉํ๋ ํจ์จ์ ์ธ ์ํ ๊ธฐ๋ณธ ์์ ํธ์ถ๋ก ๋์ฒดํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ์ํ๋ ๋ก์คํ (lockstep)์ผ๋ก ์คํ๋ฉ๋๋ค - Mojo์ ์ํ ์ฐ์ฐ์ ์ด ๋๊ธฐํ๋ฅผ ํ์ฉํ์ฌ ๋ช ์์ ๋๊ธฐํ ์์ด ๊ฐ๋ ฅํ ๋ณ๋ ฌ ๊ธฐ๋ณธ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ
GPU ์ํ ์คํ ๋ชจ๋ธ
GPU ๋ณ๋ ฌ์ฑ์ ๊ธฐ๋ณธ ํ๋์จ์ด ๋จ์๋ฅผ ์ดํดํฉ๋๋ค:
GPU ๋ธ๋ก (์: 256 ์ค๋ ๋)
โโโ ์ํ 0 (32 ์ค๋ ๋, SIMT ๋ก์คํ
์คํ)
โ โโโ ๋ ์ธ 0 โโ
โ โโโ ๋ ์ธ 1 โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ช
๋ น์
โ โโโ ๋ ์ธ 2 โ ๋์์ ์คํ (SIMT)
โ โ ... โ
โ โโโ ๋ ์ธ 31 โโ
โโโ ์ํ 1 (32 ์ค๋ ๋, ๋
๋ฆฝ์ )
โโโ ์ํ 2 (32 ์ค๋ ๋, ๋
๋ฆฝ์ )
โโโ ...
ํ๋์จ์ด ํ์ค:
- NVIDIA GPU์์ ์ํ๋น 32 ์ค๋ ๋ (
WARP_SIZE=32) - AMD GPU์์ ์ํ๋น 32 ๋๋ 64 ์ค๋ ๋ (
WARP_SIZE=32 or 64) - ๋ก์คํ ์คํ: ์ํ ๋ด ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ๋ช ๋ น์ ๋์์ ์คํํฉ๋๋ค
- ๋๊ธฐํ ๋น์ฉ ์ ๋ก: ์ํ ์ฐ์ฐ์ ๊ฐ ์ํ ๋ด์์ ์ฆ์ ์ํ๋ฉ๋๋ค
Mojo์์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ํ ์ฐ์ฐ
gpu.primitives.warp์ ํต์ฌ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ๋ฐฐ์๋๋ค:
sum(value): ์ํ์ ๋ชจ๋ ๋ ์ธ์์ ๊ฐ์ ํฉ์ฐshuffle_idx(value, lane): ํน์ ๋ ์ธ์์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐshuffle_down(value, delta): lane+delta ์์น์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐprefix_sum(value): ๋ ์ธ ์ ์ฒด์ ๊ฑธ์ณ ๋์ ํฉ ๊ณ์ฐlane_id(): ํ์ฌ ์ค๋ ๋์ ๋ ์ธ ๋ฒํธ ๋ฐํ (0-31 ๋๋ 0-63)
์ฑ๋ฅ ๋ณํ ์์
# 1. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํ ๋ฆฌ๋์
# ์์ ์ดํด๋ณธ ๋ณต์กํ ํจํด (p12.mojo):
shared = TileTensor[
dtype,
row_major[WARP_SIZE](),
MutAnyOrigin,
address_space = AddressSpace.SHARED,
].stack_allocation()
shared[local_i] = partial_product
barrier()
# ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํ ์์ ํ ํธ๋ฆฌ ๋ฆฌ๋์
์ ๊ฐ ๋จ๊ณ๋ง๋ค ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ํฉ๋๋ค:
stride = WARP_SIZE // 2
while stride > 0:
if local_i < stride:
shared[local_i] += shared[local_i + stride]
barrier()
stride //= 2
# 2. ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ํ์ฉํ ๋ฆฌ๋์
# ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ ์์ ํ ํธ๋ฆฌ ๋ฆฌ๋์
์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ ๊ฐ ๋จ๊ณ์ ๋ฐฐ๋ฆฌ์ด๊ฐ
# ํ์ํ์ง ์์ต๋๋ค.
# Mojo์ ์ํ ๋ ๋ฒจ sum ์ฐ์ฐ์ ๋ด๋ถ์ ์ผ๋ก ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ชจ๋ ๋ณต์ก์ฑ์
# ์จ๊น๋๋ค:
total = sum(partial_product) # ๋ด๋ถ์ ์ผ๋ก ๋ฐฐ๋ฆฌ์ด๋, ๊ฒฝ์ ์ํ๋ ์์ต๋๋ค!
์ํ ์ฐ์ฐ์ด ๋น๋๋ ์๊ฐ
์ฑ๋ฅ ํน์ฑ์ ์ดํดํฉ๋๋ค:
๋ฌธ์ ๊ท๋ชจ ๊ธฐ์กด ๋ฐฉ์ ์ํ ์ฐ์ฐ
๋จ์ผ ์ํ (32) ๋น ๋ฆ ๊ฐ์ฅ ๋น ๋ฆ (๋ฐฐ๋ฆฌ์ด ์์)
์์ ์ํ (128) ์ข์ ์ฐ์ (์ค๋ฒํค๋ ์ต์)
๋ค์ ์ํ (1024+) ์ข์ ๋ฐ์ด๋จ (์ ํ ํ์ฅ)
๋๊ท๋ชจ (16K+) ๋ณ๋ชฉ ๋ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ ํ
์ ์ ์ง์
์ํ ํ๋ก๊ทธ๋๋ฐ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ๋ค์ ๋ด์ฉ์ ์ต์ํด์ผ ํฉ๋๋ค:
- Part VI ํจ์ํ ํจํด: elementwise, tiled, vectorize ์ ๊ทผ ๋ฐฉ์
- GPU ์ค๋ ๋ ๊ณ์ธต ๊ตฌ์กฐ: ๋ธ๋ก, ์ํ, ์ค๋ ๋์ ๋ํ ์ดํด
- TileTensor ์ฐ์ฐ: ๋ก๋, ์ ์ฅ, ํ ์ ์กฐ์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ฐ๋ : ๋ฐฐ๋ฆฌ์ด์ ํธ๋ฆฌ ๋ฆฌ๋์ ์ด ์ ๋ณต์กํ์ง
ํ์ต ๊ฒฝ๋ก
1. SIMT ์คํ ๋ชจ๋ธ
โ ์ํ ๋ ์ธ๊ณผ SIMT ์คํ
์ํ ์ฐ์ฐ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋์จ์ด ๊ธฐ๋ฐ์ ์ดํดํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- SIMT(Single Instruction, Multiple Thread) ์คํ ๋ชจ๋ธ
- ์ํ ๋ถ๊ธฐ์ ์๋ ด ํจํด
- ์ํ ๋ด ๋ ์ธ ๋๊ธฐํ
- ํ๋์จ์ด vs ์ํํธ์จ์ด ์ค๋ ๋ ๊ด๋ฆฌ
ํต์ฌ ํต์ฐฐ: ์ํ๋ GPU ์คํ์ ๊ธฐ๋ณธ ๋จ์์ ๋๋ค - SIMT๋ฅผ ์ดํดํ๋ฉด ์ํ ํ๋ก๊ทธ๋๋ฐ์ ๋ฌธ์ด ์ด๋ฆฝ๋๋ค.
2. ์ํ sum ๊ธฐ์ด
๋ด์ ๊ตฌํ์ ํตํด ๊ฐ์ฅ ์ค์ํ ์ํ ์ฐ์ฐ์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด๋ฅผ
sum()์ผ๋ก ๋์ฒด - GPU ์ํคํ
์ฒ ๊ฐ ํธํ์ฑ (
WARP_SIZE) - ์ํ๋ฅผ ํ์ฉํ ์ปค๋ vs ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจํด
- ๊ธฐ์กด ๋ฐฉ์๊ณผ์ ์ฑ๋ฅ ๋น๊ต
ํต์ฌ ํจํด:
partial_result = compute_per_lane_value()
total = sum(partial_result) # ๋ง๋ฒ์ด ์ผ์ด๋๋ ๊ณณ!
if lane_id() == 0:
output[0] = total
3. ์ธ์ ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉํ ๊น
โ ์ธ์ ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉํ ๊น
๋์ ๋๋น ์ํ ์ฐ์ฐ์ ์ ํํ๊ธฐ ์ํ ์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ๋ฅผ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
- ์ํ ์ฐ์ฐ์ ์ ๋ฆฌํ ๋ฌธ์ ํน์ฑ
- ์ํ ์์ ๋ฐ๋ฅธ ์ฑ๋ฅ ํ์ฅ ํจํด
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ vs ์ฐ์ฐ๋ ํธ๋ ์ด๋์คํ
- ์ํ ์ฐ์ฐ ์ ํ ๊ฐ์ด๋๋ผ์ธ
์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ: ๋ฆฌ๋์ ์ฐ์ฐ์ด ๋ณ๋ชฉ์ด ๋ ๋, ์ํ ๊ธฐ๋ณธ ์์๊ฐ ๋ํ๊ตฌ๋ฅผ ์ ๊ณตํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
ํต์ฌ ๊ฐ๋
ํ๋์จ์ด-์ํํธ์จ์ด ์ ๋ ฌ
Mojo ์ํ ์ฐ์ฐ์ด GPU ํ๋์จ์ด์ ๋งคํ๋๋ ๋ฐฉ์์ ์ดํดํฉ๋๋ค:
- SIMT ์คํ: ๋ชจ๋ ๋ ์ธ์ด ๋์ผํ ๋ช ๋ น์ ๋์์ ์คํํฉ๋๋ค
- ๋ด์ฅ ๋๊ธฐํ: ์ํ ๋ด์์ ๋ช ์์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ํ์ง ์์ต๋๋ค
- ํฌ๋ก์ค ์ํคํ
์ฒ ์ง์:
WARP_SIZE๊ฐ NVIDIA์ AMD์ ์ฐจ์ด๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
ํจํด ๋ณํ
๋ณต์กํ ๋ณ๋ ฌ ํจํด์ ์ํ ๊ธฐ๋ณธ ์์๋ก ๋ณํํฉ๋๋ค:
- ํธ๋ฆฌ ๋ฆฌ๋์
โ
sum() - ๋์ ํฉ ์ฐ์ฐ โ
prefix_sum() - ๋ฐ์ดํฐ ์
ํ โ
shuffle_idx(),shuffle_down()
์ฑ๋ฅ ํน์ฑ
์ํ ์ฐ์ฐ์ด ์ด์ ์ ์ ๊ณตํ๋ ๊ฒฝ์ฐ๋ฅผ ํ์ ํฉ๋๋ค:
- ์~์ค๊ท๋ชจ ๋ฌธ์ : ๋ฐฐ๋ฆฌ์ด ์ค๋ฒํค๋๋ฅผ ์ ๊ฑฐํฉ๋๋ค
- ๋๊ท๋ชจ ๋ฌธ์ : ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ์ ์ค์ด๊ณ ์บ์ ํ์ฉ์ ๊ฐ์ ํฉ๋๋ค
- ๊ท์น์ ์ธ ํจํด: ์์ธก ๊ฐ๋ฅํ ์ ๊ทผ ํจํด์์ ์ํ ์ฐ์ฐ์ด ํ์ํฉ๋๋ค
์์ํ๊ธฐ
SIMT ์คํ ๋ชจ๋ธ์ ์ดํดํ๋ ๊ฒ์ผ๋ก ์์ํ์ฌ, ์ค์ฉ์ ์ธ warp.sum ๊ตฌํ์ ๋ค๋ฃจ๊ณ , ์ ๋ต์ ์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ๋ก ๋ง๋ฌด๋ฆฌํฉ๋๋ค.
๐ก ์ฑ๊ณต ํ: ์ํ๋ฅผ ๋ ๋ฆฝ์ ์ธ ์ค๋ ๋๊ฐ ์๋ ๋๊ธฐํ๋ ๋ฒกํฐ ์ ๋์ผ๋ก ์๊ฐํ์ธ์. ์ด ๋ฉํ ๋ชจ๋ธ์ด ํจ๊ณผ์ ์ธ ์ํ ํ๋ก๊ทธ๋๋ฐ ํจํด์ผ๋ก ์๋ดํ ๊ฒ์ ๋๋ค.
ํ์ต ๋ชฉํ: Part VII์ ๋ง์น๋ฉด, ์ํ ์ฐ์ฐ์ด ๋ณต์กํ ๋๊ธฐํ ํจํด์ ๋์ฒดํ ์ ์๋ ์ํฉ์ ์ธ์ํ์ฌ ๋ ๊ฐ๋จํ๊ณ ๋น ๋ฅธ GPU ์ฝ๋๋ฅผ ์์ฑํ ์ ์๊ฒ ๋ฉ๋๋ค.
์์ํ๊ธฐ: ์ํ ๋ ์ธ๊ณผ SIMT ์คํ ์์ ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ์ ํ์ ๋ง๋๋ณด์ธ์!
๐ง ์ํ ๋ ์ธ๊ณผ SIMT ์คํ
์ํ ํ๋ก๊ทธ๋๋ฐ vs SIMD ๋ฉํ ๋ชจ๋ธ
์ํ๋ ๋ฌด์์ธ๊ฐ?
์ํ๋ 32๊ฐ(๋๋ 64๊ฐ)์ GPU ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋ํด ๋์ผํ ๋ช ๋ น์ ๋์์ ์คํํ๋ ๊ทธ๋ฃน์ ๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ๋ฒกํฐ ํ๋ก์ธ์์ โ๋ ์ธโ ์ญํ ์ ํ๋ ๋๊ธฐํ๋ ๋ฒกํฐ ์ ๋์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
๊ฐ๋จํ ์์:
from gpu.primitives.warp import sum
# ์ํ ๋ด 32๊ฐ ์ค๋ ๋๊ฐ ๋์์ ์คํ:
var my_value = input[my_thread_id] # ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
var warp_total = sum(my_value) # ๋ชจ๋ ์ค๋ ๋๊ฐ ํ๋์ ํฉ๊ณ์ ๊ธฐ์ฌ
๋ฌด์จ ์ผ์ด ์ผ์ด๋ ๊ฑธ๊น์? 32๊ฐ์ ๊ฐ๋ณ ์ค๋ ๋๊ฐ ๋ณต์กํ ์กฐ์จ์ ํ๋ ๋์ , ์ํ๊ฐ ์๋์ผ๋ก ๋๊ธฐํํ์ฌ ํ๋์ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์ด๋์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก SIMT(Single Instruction, Multiple Thread) ์คํ์ ๋๋ค.
SIMT vs SIMD ๋น๊ต
CPU ๋ฒกํฐ ํ๋ก๊ทธ๋๋ฐ(SIMD)์ ์ต์ํ๋ค๋ฉด, GPU ์ํ๋ ๋น์ทํ์ง๋ง ํต์ฌ์ ์ธ ์ฐจ์ด๊ฐ ์์ต๋๋ค:
| ๊ด์ | CPU SIMD (์: AVX) | GPU ์ํ (SIMT) |
|---|---|---|
| ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ | ๋ช ์์ ๋ฒกํฐ ์ฐ์ฐ | ์ค๋ ๋ ๊ธฐ๋ฐ ํ๋ก๊ทธ๋๋ฐ |
| ๋ฐ์ดํฐ ํญ | ๊ณ ์ (256/512 ๋นํธ) | ์ ์ฐ (32/64 ์ค๋ ๋) |
| ๋๊ธฐํ | ๋ช ๋ น ๋ด ์์์ | ์ํ ๋ด ์์์ |
| ํต์ | ๋ฉ๋ชจ๋ฆฌ/๋ ์ง์คํฐ ๊ฒฝ์ | ์ ํ ์ฐ์ฐ ๊ฒฝ์ |
| ๋ถ๊ธฐ ์ฒ๋ฆฌ | ํด๋น ์์ | ํ๋์จ์ด ๋ง์คํน |
| ์์ | a + b | sum(thread_value) |
CPU SIMD ๋ฐฉ์ (C++ intrinsics):
// ๋ช
์์ ๋ฒกํฐ ์ฐ์ฐ - 8๊ฐ์ float๋ฅผ ๋ณ๋ ฌ๋ก
__m256 result = _mm256_add_ps(a, b); // 8์์ ๋์์ ๋ง์
CPU SIMD ๋ฐฉ์ (Mojo):
# Mojo์์ SIMD๋ ์ผ๊ธ ์๋ฏผ ํ์
์ด๋ฏ๋ก a, b๊ฐ SIMD ํ์
์ด๋ฉด
# ๋ง์
์ด ๋ณ๋ ฌ๋ก ์ํ๋ฉ๋๋ค
var result = a + b # 8์์ ๋์์ ๋ง์
GPU SIMT ๋ฐฉ์ (Mojo):
# ์ค๋ ๋ ๊ธฐ๋ฐ ์ฝ๋๊ฐ ๋ฒกํฐ ์ฐ์ฐ์ผ๋ก ๋ณํ๋ฉ๋๋ค
from gpu.primitives.warp import sum
var my_data = input[thread_id] # ๊ฐ ์ค๋ ๋๊ฐ ์๊ธฐ ์์๋ฅผ ๊ฐ์ ธ์ด
var partial = my_data * coefficient # ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์์ ๊ณ์ฐ
var total = sum(partial) # ํ๋์จ์ด๊ฐ ํฉ์ฐ์ ์กฐ์จ
์ํ๋ฅผ ๊ฐ๋ ฅํ๊ฒ ๋ง๋๋ ํต์ฌ ๊ฐ๋
1. ๋ ์ธ ์๋ณ: ๊ฐ ์ค๋ ๋๋ ์ฌ์ค์ ๋น์ฉ ์์ด ์ ๊ทผํ ์ ์๋ โ๋ ์ธ IDโ (0~31)๋ฅผ ๊ฐ์ต๋๋ค
var my_lane = lane_id() # ํ๋์จ์ด ๋ ์ง์คํฐ๋ฅผ ์ฝ์ ๋ฟ
2. ์์์ ๋๊ธฐํ: ์ํ ๋ด์์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ ์์ต๋๋ค
# ๊ทธ๋ฅ ๋์ - ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋์ผ๋ก ๋๊ธฐํ
var sum = sum(my_contribution)
3. ํจ์จ์ ์ธ ํต์ : ๋ฉ๋ชจ๋ฆฌ ์์ด๋ ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ ๊ณต์ ๊ฐ ๊ฐ๋ฅํฉ๋๋ค
# ๋ ์ธ 0์ ๊ฐ์ ๋ค๋ฅธ ๋ชจ๋ ๋ ์ธ์ผ๋ก ์ ๋ฌ
var broadcasted = shuffle_idx(my_value, 0)
ํต์ฌ ํต์ฐฐ: SIMT๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฐ์ค๋ฌ์ด ์ค๋ ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์๋ ํจ์จ์ ์ธ ๋ฒกํฐ ์ฐ์ฐ์ผ๋ก ์คํํ ์ ์์ด, ์ค๋ ๋ ํ๋ก๊ทธ๋๋ฐ์ ํธ๋ฆฌํจ๊ณผ ๋ฒกํฐ ์ฒ๋ฆฌ์ ์ฑ๋ฅ์ ๋ชจ๋ ์ป์ ์ ์์ต๋๋ค.
GPU ์คํ ๊ณ์ธต ๊ตฌ์กฐ์์ ์ํ์ ์์น
์ํ๊ฐ ์ ์ฒด GPU ์คํ ๋ชจ๋ธ๊ณผ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋๋์ง ์์ธํ ์์๋ณด๋ ค๋ฉด GPU ์ค๋ ๋ฉ vs SIMD ๊ฐ๋ ์ ์ฐธ๊ณ ํ์ธ์. ์ํ์ ์์น๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
GPU ๋๋ฐ์ด์ค
โโโ ๊ทธ๋ฆฌ๋ (์ ์ฒด ๋ฌธ์ )
โ โโโ ๋ธ๋ก 1 (์ค๋ ๋ ๊ทธ๋ฃน, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ)
โ โ โโโ ์ํ 1 (32 ์ค๋ ๋, ๋ก์คํ
์คํ) โ ์ด ๋ ๋ฒจ
โ โ โ โโโ ์ค๋ ๋ 1 โ SIMD ์ฐ์ฐ
โ โ โ โโโ ์ค๋ ๋ 2 โ SIMD ์ฐ์ฐ
โ โ โ โโโ ... (์ด 32๊ฐ ์ค๋ ๋)
โ โ โโโ ์ํ 2 (32 ์ค๋ ๋)
โ โโโ ๋ธ๋ก 2 (๋
๋ฆฝ์ ์ธ ๊ทธ๋ฃน)
์ํ ํ๋ก๊ทธ๋๋ฐ์ โ์ํ ๋ ๋ฒจโ์์ ๋์ํฉ๋๋ค - ๋จ์ผ ์ํ ๋ด์ 32๊ฐ ์ค๋ ๋๋ฅผ
๋ชจ๋ ์กฐ์จํ๋ ์ฐ์ฐ์ ๋ค๋ฃจ๋ฉฐ, ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์จ์ด ํ์ํ
sum() ๊ฐ์ ๊ฐ๋ ฅํ ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด ๋ฉํ ๋ชจ๋ธ์ ๋ฌธ์ ๊ฐ ์ํ ์ฐ์ฐ์ ์์ฐ์ค๋ฝ๊ฒ ๋งคํ๋๋ ๊ฒฝ์ฐ์ ๊ธฐ์กด์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์์ด ํ์ํ ๊ฒฝ์ฐ๋ฅผ ๊ตฌ๋ถํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ํ ํ๋ก๊ทธ๋๋ฐ์ ํ๋์จ์ด ๊ธฐ๋ฐ
Single Instruction, Multiple Thread(SIMT) ์คํ์ ์ดํดํ๋ ๊ฒ์ ํจ๊ณผ์ ์ธ ์ํ ํ๋ก๊ทธ๋๋ฐ์ ํ์์ ์ ๋๋ค. ์ด๊ฒ์ ๋จ์ํ ์ํํธ์จ์ด ์ถ์ํ๊ฐ ์๋๋ผ, GPU ํ๋์จ์ด๊ฐ ์ค๋ฆฌ์ฝ ์์ค์์ ์ค์ ๋ก ์๋ํ๋ ๋ฐฉ์์ ๋๋ค.
SIMT ์คํ์ด๋?
SIMT๋ ์ํ ๋ด์์ ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋ํด ๊ฐ์ ๋ช ๋ น์ ๋์์ ์คํํ๋ค๋ ๋ป์ ๋๋ค. ์ด๋ ์์ ํ ๋ค๋ฅธ ๋ช ๋ น์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํํ ์ ์๋ CPU ์ค๋ ๋์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฆ ๋๋ค.
CPU vs GPU ์คํ ๋ชจ๋ธ
| ๊ด์ | CPU (MIMD) | GPU ์ํ (SIMT) |
|---|---|---|
| ๋ช ๋ น ๋ชจ๋ธ | Multiple Instructions, Multiple Data | Single Instruction, Multiple Thread |
| Core 1 | add r1, r2 | add r1, r2 |
| Core 2 | load r3, [mem] | add r1, r2 (๋์ผ ๋ช
๋ น) |
| Core 3 | branch loop | add r1, r2 (๋์ผ ๋ช
๋ น) |
| โฆ Core 32 | ๋ค๋ฅธ ๋ช
๋ น | add r1, r2 (๋์ผ ๋ช
๋ น) |
| ์คํ ๋ฐฉ์ | ๋ ๋ฆฝ์ , ๋น๋๊ธฐ | ๋๊ธฐํ, ๋ก์คํ |
| ์ค์ผ์ค๋ง | ๋ณต์ก, OS ๊ด๋ฆฌ | ๋จ์, ํ๋์จ์ด ๊ด๋ฆฌ |
| ๋ฐ์ดํฐ | ๋ ๋ฆฝ์ ์ธ ๋ฐ์ดํฐ ์ธํธ | ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ, ๊ฐ์ ์ฐ์ฐ |
GPU ์ํ ์คํ ํจํด:
- ๋ช
๋ น: 32๊ฐ ๋ ์ธ ๋ชจ๋ ๋์ผ:
add r1, r2 - ๋ ์ธ 0:
Data0์ ์ฐ์ฐ โResult0 - ๋ ์ธ 1:
Data1์ ์ฐ์ฐ โResult1 - ๋ ์ธ 2:
Data2์ ์ฐ์ฐ โResult2 - โฆ (๋ชจ๋ ๋ ์ธ์ด ๋์์ ์คํ)
- ๋ ์ธ 31:
Data31์ ์ฐ์ฐ โResult31
ํต์ฌ ํต์ฐฐ: ๋ชจ๋ ๋ ์ธ์ด ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋ํด ๊ฐ์ ๋ช ๋ น์ ๋์์ ์คํํฉ๋๋ค.
SIMT๊ฐ GPU์ ์ ํฉํ ์ด์
GPU๋ ์ง์ฐ ์๊ฐ์ด ์๋ ์ฒ๋ฆฌ๋์ ์ต์ ํ๋์ด ์์ต๋๋ค. SIMT๊ฐ ๊ฐ๋ฅํ๊ฒ ํ๋ ๊ฒ๋ค:
- ํ๋์จ์ด ๋จ์ํ: ํ๋์ ๋ช ๋ น ๋์ฝ๋๊ฐ 32๊ฐ ๋๋ 64๊ฐ ์ค๋ ๋๋ฅผ ์ฒ๋ฆฌ
- ์คํ ํจ์จ์ฑ: ์ํ ๋ด ์ค๋ ๋ ๊ฐ ๋ณต์กํ ์ค์ผ์ค๋ง ๋ถํ์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ๋ณํฉ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ ๋ ฅ ํจ์จ์ฑ: ๋ ์ธ ์ ์ฒด์ ๊ฑธ์ณ ์ ์ด ๋ก์ง ๊ณต์
์ํ ์คํ ๋ฉ์ปค๋์ฆ
๋ ์ธ ๋ฒํธ์ ์๋ณ
์ํ ๋ด ๊ฐ ์ค๋ ๋๋ 0๋ถํฐ WARP_SIZE-1๊น์ง์ ๋ ์ธ ID๋ฅผ ๊ฐ์ต๋๋ค:
from gpu import lane_id
from gpu.primitives.warp import WARP_SIZE
# ์ปค๋ ํจ์ ๋ด์์:
my_lane = lane_id() # 0-31 (NVIDIA/RDNA) ๋๋ 0-63 (CDNA) ๋ฐํ
ํต์ฌ ํต์ฐฐ: lane_id()๋ ๋น์ฉ์ด ์์ต๋๋ค - ๊ฐ์ ๊ณ์ฐํ๋ ๊ฒ์ด ์๋๋ผ
ํ๋์จ์ด ๋ ์ง์คํฐ๋ฅผ ์ฝ์ ๋ฟ์
๋๋ค.
์ํ ๋ด ๋๊ธฐํ
SIMT์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ์ธก๋ฉด: ์์์ ๋๊ธฐํ.
# thread_idx.x < WARP_SIZE์ธ ๊ฒฝ์ฐ์ ์์
# 1. ๊ธฐ์กด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์:
shared[thread_idx.x] = partial_result
barrier() # ๋ช
์์ ๋๊ธฐํ ํ์
var total = shared[0] + shared[1] + ... + shared[WARP_SIZE] # ํฉ์ฐ ๋ฆฌ๋์
# 2. ์ํ ๋ฐฉ์:
from gpu.primitives.warp import sum
var total = sum(partial_result) # ์์์ ๋๊ธฐํ!
์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ ์์๊น์? ๋ชจ๋ ๋ ์ธ์ด ๊ฐ ๋ช
๋ น์ ์ ํํ ๊ฐ์ ์์ ์ ์คํํ๊ธฐ
๋๋ฌธ์
๋๋ค. sum()์ด ์์๋ ๋, ๋ชจ๋ ๋ ์ธ์ ์ด๋ฏธ partial_result ๊ณ์ฐ์ ๋ง์น
์ํ์
๋๋ค.
์ํ ๋ถ๊ธฐ์ ์๋ ด
์กฐ๊ฑด ์ฝ๋์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋ ๊น?
if lane_id() % 2 == 0:
# ์ง์ ๋ ์ธ์ด ์ด ๊ฒฝ๋ก๋ฅผ ์คํ
result = compute_even()
else:
# ํ์ ๋ ์ธ์ด ์ด ๊ฒฝ๋ก๋ฅผ ์คํ
result = compute_odd()
# ๋ชจ๋ ๋ ์ธ์ด ์ฌ๊ธฐ์ ์๋ ด
ํ๋์จ์ด ๋์ ๋จ๊ณ:
| ๋จ๊ณ | ํ์ด์ฆ | ํ์ฑ ๋ ์ธ | ๋๊ธฐ ๋ ์ธ | ํจ์จ | ์ฑ๋ฅ ๋น์ฉ |
|---|---|---|---|---|---|
| 1 | ์กฐ๊ฑด ํ๊ฐ | 32๊ฐ ๋ ์ธ ์ ๋ถ | ์์ | 100% | ์ ์ ์๋ |
| 2 | ์ง์ ๋ ์ธ ๋ถ๊ธฐ | ๋ ์ธ 0,2,4โฆ30 (16๊ฐ) | ๋ ์ธ 1,3,5โฆ31 (16๊ฐ) | 50% | 2๋ฐฐ ๋๋ฆผ |
| 3 | ํ์ ๋ ์ธ ๋ถ๊ธฐ | ๋ ์ธ 1,3,5โฆ31 (16๊ฐ) | ๋ ์ธ 0,2,4โฆ30 (16๊ฐ) | 50% | 2๋ฐฐ ๋๋ฆผ |
| 4 | ์๋ ด | 32๊ฐ ๋ ์ธ ์ ๋ถ | ์์ | 100% | ์ ์ ์๋ ๋ณต๊ท |
์์ ๋ถ์:
- 2๋จ๊ณ: ์ง์ ๋ ์ธ๋ง
compute_even()์ ์คํํ๊ณ ํ์ ๋ ์ธ์ ๋๊ธฐ - 3๋จ๊ณ: ํ์ ๋ ์ธ๋ง
compute_odd()๋ฅผ ์คํํ๊ณ ์ง์ ๋ ์ธ์ ๋๊ธฐ - ์ด ์์ ์๊ฐ:
time(compute_even) + time(compute_odd)(์์ฐจ ์คํ) - ๋ถ๊ธฐ ์๋ ๊ฒฝ์ฐ:
max(time(compute_even), time(compute_odd))(๋ณ๋ ฌ ์คํ)
์ฑ๋ฅ ์ํฅ:
- ๋ถ๊ธฐ: ์ํ๊ฐ ์คํ์ ๋ถ๋ฆฌ - ์ผ๋ถ ๋ ์ธ์ ํ์ฑ, ๋๋จธ์ง๋ ๋๊ธฐ
- ์์ฐจ ์คํ: ์๋ก ๋ค๋ฅธ ๊ฒฝ๋ก๊ฐ ๋ณ๋ ฌ์ด ์๋ ์์ฐจ์ ์ผ๋ก ์คํ
- ์๋ ด: ๋ชจ๋ ๋ ์ธ์ด ๋ค์ ํฉ๋ฅํ์ฌ ํจ๊ป ์งํ
- ๋น์ฉ: ๋ถ๊ธฐ๊ฐ ์๋ ์ํ๋ ํตํฉ ์คํ ๋๋น 2๋ฐฐ ์ด์์ ์๊ฐ ์์
์ํ ํจ์จ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
์ํ ํจ์จ ํจํด
โ ์ฐ์: ๊ท ์ผ ์คํ (100% ํจ์จ)
# ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ ์์
์ํ - ๋ถ๊ธฐ ์์
var partial = a[global_i] * b[global_i]
var total = sum(partial)
์ฑ๋ฅ: 32๊ฐ ๋ ์ธ ๋ชจ๋ ๋์ ํ์ฑ
โ ๏ธ ํ์ฉ: ์์ธก ๊ฐ๋ฅํ ๋ถ๊ธฐ (~95% ํจ์จ)
# lane_id() ๊ธฐ๋ฐ ๋ถ๊ธฐ - ํ๋์จ์ด ์ต์ ํ๋จ
if lane_id() == 0:
output[block_idx] = sum(partial)
์ฑ๋ฅ: ๋จ์ผ ๋ ์ธ์ ์งง์ ์ฐ์ฐ, ์์ธก ๊ฐ๋ฅํ ํจํด
๐ถ ์ฃผ์: ๊ตฌ์กฐํ๋ ๋ถ๊ธฐ (~50-75% ํจ์จ)
# ๊ท์น์ ์ธ ํจํด์ ์ปดํ์ผ๋ฌ๊ฐ ์ต์ ํ ๊ฐ๋ฅ
if (global_i / 4) % 2 == 0:
result = method_a()
else:
result = method_b()
์ฑ๋ฅ: ์์ธก ๊ฐ๋ฅํ ๊ทธ๋ฃน, ์ผ๋ถ ์ต์ ํ ๊ฐ๋ฅ
โ ํํผ: ๋ฐ์ดํฐ ์์กด์ ๋ถ๊ธฐ (~25-50% ํจ์จ)
# ๋ฐ์ดํฐ์ ๋ฐ๋ผ ๋ ์ธ๋ง๋ค ๋ค๋ฅธ ๊ฒฝ๋ก๋ฅผ ํ ์ ์์
if input[global_i] > threshold: # ์์ธก ๋ถ๊ฐ๋ฅํ ๋ถ๊ธฐ
result = expensive_computation()
else:
result = simple_computation()
์ฑ๋ฅ: ๋ฌด์์ ๋ถ๊ธฐ๊ฐ ์ํ ํจ์จ์ ๋จ์ด๋จ๋ฆผ
๐ ์ต์ : ์ค์ฒฉ๋ ๋ฐ์ดํฐ ์์กด์ ๋ถ๊ธฐ (~10-25% ํจ์จ)
# ์์ธก ๋ถ๊ฐ๋ฅํ ๋ถ๊ธฐ์ ๋ค๋จ๊ณ ์ค์ฒฉ
if input[global_i] > threshold1:
if input[global_i] > threshold2:
result = very_expensive()
else:
result = expensive()
else:
result = simple()
์ฑ๋ฅ: ์ํ ํจ์จ์ด ์ฌ์ค์ ๋ฌด๋์ง
ํฌ๋ก์ค ์ํคํ ์ฒ ํธํ์ฑ
NVIDIA vs AMD ์ํ ํฌ๊ธฐ
from gpu.primitives.warp import WARP_SIZE
# NVIDIA GPUs: WARP_SIZE = 32
# AMD RDNA GPUs: WARP_SIZE = 32 (wavefront32 ๋ชจ๋)
# AMD CDNA GPUs: WARP_SIZE = 64 (์ ํต์ ์ธ wavefront64)
์ ์ค์ํ ๊น์:
- ๋ฉ๋ชจ๋ฆฌ ํจํด: ๋ณํฉ๋ ์ ๊ทผ์ด ์ํ ํฌ๊ธฐ์ ์์กด
- ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ: ๋ฆฌ๋์ ํธ๋ฆฌ๊ฐ ์ํ ํฌ๊ธฐ๋ฅผ ๊ณ ๋ คํด์ผ ํจ
- ์ฑ๋ฅ ํ์ฅ: AMD์์ ์ํ๋น ๋ ์ธ์ด 2๋ฐฐ
์ด์ ๊ฐ๋ฅํ ์ํ ์ฝ๋ ์์ฑ
์ํคํ ์ฒ ์ ์ ์ ๋ต
โ
์ด์ ๊ฐ๋ฅ: ํญ์ WARP_SIZE ์ฌ์ฉ
comptime THREADS_PER_BLOCK = (WARP_SIZE, 1) # ์๋์ผ๋ก ์ ์
comptime ELEMENTS_PER_WARP = WARP_SIZE # ํ๋์จ์ด์ ๋ง๊ฒ ํ์ฅ
๊ฒฐ๊ณผ: NVIDIA/AMD (32)์ AMD (64) ๋ชจ๋์์ ์ต์ ์ผ๋ก ๋์
โ ์๋ชป๋ ๋ฐฉ์: ์ํ ํฌ๊ธฐ๋ฅผ ํ๋์ฝ๋ฉํ์ง ๋ง์ธ์
comptime THREADS_PER_BLOCK = (32, 1) # AMD GPU์์ ๋์ ์ ํจ!
comptime REDUCTION_SIZE = 32 # AMD์์ ์๋ชป๋ ๊ฐ!
๊ฒฐ๊ณผ: AMD์์ ์ฑ๋ฅ ์ ํ, ์ ํ์ฑ ๋ฌธ์ ๊ฐ๋ฅ
์ค์ ํ๋์จ์ด ์ํฅ
| GPU ์ํคํ ์ฒ | WARP_SIZE | ์ํ๋น ๋ฉ๋ชจ๋ฆฌ | ๋ฆฌ๋์ ๋จ๊ณ | ๋ ์ธ ํจํด |
|---|---|---|---|---|
| NVIDIA/AMD RDNA | 32 | 128 bytes (4ร32) | 5๋จ๊ณ: 32โ16โ8โ4โ2โ1 | ๋ ์ธ 0-31 |
| AMD CDNA | 64 | 256 bytes (4ร64) | 6๋จ๊ณ: 64โ32โ16โ8โ4โ2โ1 | ๋ ์ธ 0-63 |
64 vs 32์ ์ฑ๋ฅ ์ฐจ์ด:
- CDNA ์ฅ์ : ์ํ๋น 2๋ฐฐ์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ
- CDNA ์ฅ์ : ์ํ๋น 2๋ฐฐ์ ์ฐ์ฐ๋
- NVIDIA/RDNA ์ฅ์ : ๋ธ๋ก๋น ๋ ๋ง์ ์ํ (๋ ๋์ ์ ์ ์จ)
- ์ฝ๋ ์ด์์ฑ: ๊ฐ์ ์์ค ์ฝ๋๋ก ์์ชฝ ๋ชจ๋ ์ต์ ์ฑ๋ฅ
์ํ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
๋ณํฉ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
โ ์๋ฒฝ: ๋ณํฉ๋ ์ ๊ทผ (100% ๋์ญํญ ํ์ฉ)
# ์ธ์ ๋ ์ธ โ ์ธ์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์
var value = input[global_i] # ๋ ์ธ 0โinput[0], ๋ ์ธ 1โinput[1], ๋ฑ
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
| ์ ๊ทผ ํจํด | NVIDIA/RDNA (32 ๋ ์ธ) | CDNA (64 ๋ ์ธ) | ๋์ญํญ ํ์ฉ | ์ฑ๋ฅ |
|---|---|---|---|---|
| โ ๋ณํฉ | ๋ ์ธ N โ ์ฃผ์ 4รN | ๋ ์ธ N โ ์ฃผ์ 4รN | 100% | ์ต์ |
| 1ํ ํธ๋์ญ์ : 128 bytes | 1ํ ํธ๋์ญ์ : 256 bytes | ์ ์ฒด ๋ฒ์ค ํญ | ๋น ๋ฆ | |
| โ ๋ถ์ฐ | ๋ ์ธ N โ ์์ ์ฃผ์ | ๋ ์ธ N โ ์์ ์ฃผ์ | ~6% | ์ต์ |
| 32ํ ๊ฐ๋ณ ํธ๋์ญ์ | 64ํ ๊ฐ๋ณ ํธ๋์ญ์ | ๋๋ถ๋ถ ์ ํด ๋ฒ์ค | 32๋ฐฐ ๋๋ฆผ |
์ฃผ์ ์์:
- ๋ณํฉ: ๋ ์ธ 0โ0, ๋ ์ธ 1โ4, ๋ ์ธ 2โ8, ๋ ์ธ 3โ12, โฆ
- ๋ถ์ฐ: ๋ ์ธ 0โ1000, ๋ ์ธ 1โ52, ๋ ์ธ 2โ997, ๋ ์ธ 3โ8, โฆ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋
๋ฑ ํฌ ์ถฉ๋์ด๋?
GPU ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋์ ์ ๊ทผ์ด ๊ฐ๋ฅํ 32๊ฐ์ ๋ ๋ฆฝ์ ์ธ ๋ฑ ํฌ๋ก ๋๋์ด ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋ฑ ํฌ ์ถฉ๋์ ์ํ ๋ด ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฑ ํฌ์ ์๋ก ๋ค๋ฅธ ์ฃผ์์ ๋์์ ์ ๊ทผํ๋ ค ํ ๋ ๋ฐ์ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ ํ๋์จ์ด๊ฐ ์ ๊ทผ์ ์ง๋ ฌํํด์ผ ํ๋ฏ๋ก, ๋จ์ผ ์ฌ์ดํด์ด์ด์ผ ํ ์ฐ์ฐ์ด ์ฌ๋ฌ ์ฌ์ดํด๋ก ๋์ด๋ฉ๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ์ถฉ๋ ์์: ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผ โ ๋ชจ๋ ์ ๊ทผ์ด ๋์์ ๋ฐ์ (1 ์ฌ์ดํด)
- ๋ฑ ํฌ ์ถฉ๋: ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฑ ํฌ์ ์ ๊ทผ โ ์ ๊ทผ์ด ์์ฐจ์ ์ผ๋ก ๋ฐ์ (N๊ฐ ์ค๋ ๋์ N ์ฌ์ดํด)
- ๋ธ๋ก๋์บ์คํธ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์ฃผ์์ ์ ๊ทผ โ ํ๋์จ์ด๊ฐ 1 ์ฌ์ดํด๋ก ์ต์ ํ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ๊ตฌ์ฑ:
| ๋ฑ ํฌ | ์ฃผ์ (๋ฐ์ดํธ ์คํ์ ) | ์์ ๋ฐ์ดํฐ (float32) |
|---|---|---|
| ๋ฑ ํฌ 0 | 0, 128, 256, 384, โฆ | shared[0], shared[32], shared[64], โฆ |
| ๋ฑ ํฌ 1 | 4, 132, 260, 388, โฆ | shared[1], shared[33], shared[65], โฆ |
| ๋ฑ ํฌ 2 | 8, 136, 264, 392, โฆ | shared[2], shared[34], shared[66], โฆ |
| โฆ | โฆ | โฆ |
| ๋ฑ ํฌ 31 | 124, 252, 380, 508, โฆ | shared[31], shared[63], shared[95], โฆ |
๋ฑ ํฌ ์ถฉ๋ ์์:
| ์ ๊ทผ ํจํด | ๋ฑ ํฌ ์ฌ์ฉ | ์ฌ์ดํด | ์ฑ๋ฅ | ์ค๋ช |
|---|---|---|---|---|
| โ ์์ฐจ์ | shared[thread_idx.x] | 1 ์ฌ์ดํด | 100% | ๊ฐ ๋ ์ธ์ด ๋ค๋ฅธ ๋ฑ ํฌ ์ ๊ทผ |
| ๋ ์ธ 0โ๋ฑ ํฌ 0, ๋ ์ธ 1โ๋ฑ ํฌ 1, โฆ | ์ต์ | ์ถฉ๋ ์์ | ||
| โ ๋์ผ ์ธ๋ฑ์ค | shared[0] | 1 ์ฌ์ดํด | 100% | ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ ์ฃผ์์์ ๋ธ๋ก๋์บ์คํธ |
| 32๊ฐ ๋ ์ธ ์ ๋ถโ๋ฑ ํฌ 0 (๊ฐ์ ์ฃผ์) | ์ต์ | ์ถฉ๋ ์์ | ||
| โ ์คํธ๋ผ์ด๋ 2 | shared[thread_idx.x * 2] | 2 ์ฌ์ดํด | 50% | ๋ฑ ํฌ๋น 2๊ฐ ๋ ์ธ |
| ๋ ์ธ 0,16โ๋ฑ ํฌ 0; ๋ ์ธ 1,17โ๋ฑ ํฌ 1 | 2๋ฐฐ ๋๋ฆผ | ์ง๋ ฌํ๋ ์ ๊ทผ | ||
| ๐ ์คํธ๋ผ์ด๋ 32 | shared[thread_idx.x * 32] | 32 ์ฌ์ดํด | 3% | ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ ๋ฑ ํฌ ์ ๊ทผ |
| 32๊ฐ ๋ ์ธ ์ ๋ถโ๋ฑ ํฌ 0 (๋ค๋ฅธ ์ฃผ์) | 32๋ฐฐ ๋๋ฆผ | ์์ ํ ์ง๋ ฌํ |
์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ค์ ํ์ฉ
์ํ ์ฐ์ฐ์ด ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๊ฒฝ์ฐ
- ๋ฆฌ๋์
์ฐ์ฐ:
sum(),max()๋ฑ - ๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ:
shuffle_idx()๋ก ๊ฐ ๊ณต์ - ์ด์ ํต์ :
shuffle_down()์ผ๋ก ์ฌ๋ผ์ด๋ฉ ์๋์ฐ - ๋์ ํฉ ์ฐ์ฐ:
prefix_sum()์ผ๋ก scan ์๊ณ ๋ฆฌ์ฆ
์ฑ๋ฅ ํน์ฑ
| ์ฐ์ฐ ์ ํ | ๊ธฐ์กด ๋ฐฉ์ | ์ํ ์ฐ์ฐ |
|---|---|---|
| ๋ฆฌ๋์ (32๊ฐ ์์) | ~20๊ฐ ๋ช ๋ น | 10๊ฐ ๋ช ๋ น |
| ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ | ๋์ | ์ต์ |
| ๋๊ธฐํ ๋น์ฉ | ๋น์ฉ ๋์ | ๋ฌด๋ฃ |
| ์ฝ๋ ๋ณต์ก๋ | ๋์ | ๋ฎ์ |
๋ค์ ๋จ๊ณ
SIMT์ ๊ธฐ๋ฐ์ ์ดํดํ์ผ๋, ์ด ๊ฐ๋
์ด ์ด๋ป๊ฒ ๊ฐ๋ ฅํ ์ํ ์ฐ์ฐ์ ๊ฐ๋ฅํ๊ฒ ํ๋์ง
์์๋ณผ ์ฐจ๋ก์
๋๋ค. ๋ค์ ์น์
์์๋ sum()์ด ๋ณต์กํ ๋ฆฌ๋์
ํจํด์ ๊ฐ๋จํ๊ณ
ํจ์จ์ ์ธ ํจ์ ํธ์ถ๋ก ์ด๋ป๊ฒ ๋ณํํ๋์ง ๋ณด์ฌ์ค๋๋ค.
โ ๋ค์: warp.sum()์ ํต์ฌ
warp.sum()์ ํต์ฌ - ์ํ ๋ ๋ฒจ ๋ด์
Puzzle 12์์ ์ดํด๋ณธ ๋ด์ ์ Mojo์ ์ํ ์ฐ์ฐ์ผ๋ก
๊ตฌํํฉ๋๋ค. ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๊ฐ๋จํ ํจ์ ํธ์ถ๋ก ๋์ฒดํฉ๋๋ค. ๊ฐ ์ํ
๋ ์ธ์ด ํ๋์ ์์๋ฅผ ์ฒ๋ฆฌํ๊ณ warp.sum()์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์๋์ผ๋ก ํฉ์ฐํ์ฌ, ์ํ
ํ๋ก๊ทธ๋๋ฐ์ด GPU ๋๊ธฐํ๋ฅผ ์ด๋ป๊ฒ ๋ณํํ๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: warp.sum() ์ฐ์ฐ์ SIMT ์คํ์ ํ์ฉํ์ฌ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์ ์ ๋จ์ผ ํ๋์จ์ด ๊ฐ์ ๋ช ๋ น์ผ๋ก ๋์ฒดํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
warp.sum()์ ํ์ฉํ ์ํ ๋ ๋ฒจ ๋ฆฌ๋์ - SIMT ์คํ ๋ชจ๋ธ๊ณผ ๋ ์ธ ๋๊ธฐํ
WARP_SIZE๋ฅผ ํ์ฉํ ํฌ๋ก์ค ์ํคํ ์ฒ ํธํ์ฑ- ๋ณต์กํ ํจํด์์ ๊ฐ๋จํ ํจํด์ผ๋ก์ ์ฑ๋ฅ ๋ณํ
- ๋ ์ธ ID ๊ด๋ฆฌ์ ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ
์ํ์ ์ฐ์ฐ์ ๋ด์ ์ ๋๋ค: \[\Large \text{output}[0] = \sum_{i=0}^{N-1} a[i] \times b[i]\]
ํ์ง๋ง ๊ตฌํ ๊ณผ์ ์์ Mojo์ ๋ชจ๋ ์ํ ๋ ๋ฒจ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ ์ฉ๋๋ ๊ธฐ๋ณธ ํจํด์ ๋ฐฐ์๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU ์ํคํ ์ฒ์ ๋ฐ๋ผ 32 ๋๋ 64) - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ ์ด์์:
row_major[SIZE]()(1D ํ ์ฐ์ )
๊ธฐ์กด ๋ฐฉ์์ ๋ณต์ก์ฑ (Puzzle 12์์)
solutions/p12/p12.mojo์ ๋ณต์กํ ๋ฐฉ์์ ๋ ์ฌ๋ ค ๋ด ์๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ฐฐ๋ฆฌ์ด, ํธ๋ฆฌ ๋ฆฌ๋์ ์ด ํ์ํ์ต๋๋ค:
comptime SIZE = WARP_SIZE
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (WARP_SIZE, 1)
comptime dtype = DType.float32
comptime SIMD_WIDTH = simd_width_of[dtype]()
comptime in_layout = row_major[SIZE]()
comptime InLayoutType = type_of(in_layout)
comptime out_layout = row_major[1]()
comptime OutLayoutType = type_of(out_layout)
def traditional_dot_product_p12_style[
size: Int
](
output: TileTensor[mut=True, dtype, OutLayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
):
"""
This is the complex approach from p12_layout_tensor.mojo - kept for comparison.
"""
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[WARP_SIZE]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
if global_i < size:
shared[local_i] = (a[global_i] * b[global_i]).reduce_add()
else:
shared[local_i] = 0.0
barrier()
var stride = WARP_SIZE // 2
while stride > 0:
if local_i < stride:
shared[local_i] += shared[local_i + stride]
barrier()
stride //= 2
if local_i == 0:
output[global_i // WARP_SIZE] = shared[0]
์ด ๋ฐฉ์์ด ๋ณต์กํ ์ด์ :
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น: ๋ธ๋ก ๋ด์์ ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ด๋ฆฌ
- ๋ช
์์ ๋ฐฐ๋ฆฌ์ด: ์ค๋ ๋ ๋๊ธฐํ๋ฅผ ์ํ
barrier()ํธ์ถ - ํธ๋ฆฌ ๋ฆฌ๋์ : ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ๋ ๋ณต์กํ ๋ฃจํ
- ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ: ์ค๋ ๋ 0๋ง ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ก
๋์์ ํ์ง๋ง, ์ฝ๋๊ฐ ์ฅํฉํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์ฐ๋ฉฐ GPU ๋๊ธฐํ์ ๋ํ ๊น์ ์ดํด๊ฐ ํ์ํฉ๋๋ค.
๊ธฐ์กด ๋ฐฉ์ ํ ์คํธ:
pixi run p24 --traditional
pixi run -e amd p24 --traditional
pixi run -e apple p24 --traditional
uv run poe p24 --traditional
์์ฑํ ์ฝ๋
1. ๊ฐ๋จํ ์ํ ์ปค๋ ๋ฐฉ์
๋ณต์กํ ๊ธฐ์กด ๋ฐฉ์์ warp_sum()์ ์ฌ์ฉํ๋ ๊ฐ๋จํ ์ํ ์ปค๋๋ก ๋ณํํฉ๋๋ค:
def simple_warp_dot_product[
size: Int
](
output: TileTensor[mut=True, dtype, OutLayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
# FILL IN (6 lines at most)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p24/p24.mojo
ํ
1. ๊ฐ๋จํ ์ํ ์ปค๋ ๊ตฌ์กฐ ์ดํดํ๊ธฐ
simple_warp_dot_product ํจ์๋ฅผ 6์ค ์ด๋ด๋ก ์์ฑํด์ผ ํฉ๋๋ค:
def simple_warp_dot_product[...](output, a, b):
global_i = block_dim.x * block_idx.x + thread_idx.x
# ์ฌ๊ธฐ๋ฅผ ์ฑ์ฐ์ธ์ (์ต๋ 6์ค)
๋ฐ๋ผ์ผ ํ ํจํด:
- ์ด ์ค๋ ๋์ ์์์ ๋ํ ๋ถ๋ถ๊ณฑ ๊ณ์ฐ
warp_sum()์ผ๋ก ๋ชจ๋ ์ํ ๋ ์ธ์ ๊ฐ์ ํฉ์ฐ- ๋ ์ธ 0์ด ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ก
2. ๋ถ๋ถ๊ณฑ ๊ณ์ฐํ๊ธฐ
var partial_product: Scalar[dtype] = 0
if global_i < size:
partial_product = (a[global_i] * b[global_i]).reduce_add()
.reduce_add()๊ฐ ํ์ํ ์ด์ : Mojo์ ๊ฐ์ SIMD ๊ธฐ๋ฐ์ด๋ฏ๋ก
a[global_i] * b[global_i]๋ SIMD ๋ฒกํฐ๋ฅผ ๋ฐํํฉ๋๋ค. .reduce_add()๋ก ๋ฒกํฐ๋ฅผ
์ค์นผ๋ผ ๊ฐ์ผ๋ก ํฉ์ฐํฉ๋๋ค.
๊ฒฝ๊ณ ๊ฒ์ฌ: ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ํจํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์์ง ์์ ์ ์์ผ๋ฏ๋ก ํ์์ ์ ๋๋ค.
3. ์ํ ๋ฆฌ๋์ ์ ๋ง๋ฒ
total = warp_sum(partial_product)
warp_sum()์ด ํ๋ ์ผ:
- ๊ฐ ๋ ์ธ์
partial_product๊ฐ์ ๊ฐ์ ธ์ด - ์ํ ๋ด ๋ชจ๋ ๋ ์ธ์ ๊ฐ์ ํฉ์ฐ (ํ๋์จ์ด ๊ฐ์)
- ๋ชจ๋ ๋ ์ธ์ ๊ฐ์ ํฉ๊ณ๋ฅผ ๋ฐํ (๋ ์ธ 0๋ง์ด ์๋)
- ๋ช ์์ ๋๊ธฐํ๊ฐ ์ ํ ํ์ ์์ (SIMT๊ฐ ์ฒ๋ฆฌ)
4. ๊ฒฐ๊ณผ ๊ธฐ๋กํ๊ธฐ
if lane_id() == 0:
output[global_i // WARP_SIZE] = total
์ ๋ ์ธ 0๋ง? warp_sum() ์ดํ ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ total ๊ฐ์ ๊ฐ์ง๋ง, ๊ฒฝ์
์ํ๋ฅผ ํผํ๊ธฐ ์ํด ํ ๋ฒ๋ง ๊ธฐ๋กํฉ๋๋ค.
์ output[0]์ ์ง์ ์ฐ์ง ์์๊น? ์ ์ฐ์ฑ์ ์ํด์์
๋๋ค. ์ด ํจ์๋ ์ํ๊ฐ
์ฌ๋ฌ ๊ฐ์ธ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๊ฐ ์ํ์ ๊ฒฐ๊ณผ๊ฐ global_i // WARP_SIZE
์์น์ ๊ธฐ๋ก๋ฉ๋๋ค.
lane_id(): 0-31 (NVIDIA) ๋๋ 0-63 (AMD)์ ๋ฐํ - ์ํ ๋ด์์ ์ด๋
๋ ์ธ์ธ์ง ์๋ณํฉ๋๋ค.
๊ฐ๋จํ ์ํ ์ปค๋ ํ ์คํธ:
uv run poe p24 --kernel
pixi run p24 --kernel
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
SIZE: 32
WARP_SIZE: 32
SIMD_WIDTH: 8
=== RESULT ===
out: 10416.0
expected: 10416.0
๐ Notice how simple the warp version is compared to p12.mojo!
Same kernel structure, but warp_sum() replaces all the complexity!
ํ์ด
def simple_warp_dot_product[
InLayoutT: TensorLayout, OutLayoutT: TensorLayout, size: Int
](
output: TileTensor[mut=True, dtype, OutLayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
):
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
var global_i = block_dim.x * block_idx.x + thread_idx.x
# Each thread computes one partial product using vectorized approach as values in Mojo are SIMD based
var partial_product: Scalar[dtype] = 0
if global_i < size:
partial_product = rebind[Scalar[dtype]](a_lt[global_i]) * rebind[
Scalar[dtype]
](b_lt[global_i])
# warp_sum() replaces all the shared memory + barriers + tree reduction
var total = warp_sum(partial_product)
# Only lane 0 writes the result (all lanes have the same total)
if lane_id() == 0:
out_lt.store[1](Index(global_i // WARP_SIZE), total)
๊ฐ๋จํ ์ํ ์ปค๋์ ๋ณต์กํ ๋๊ธฐํ์์ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ณธ ์์๋ก์ ๊ทผ๋ณธ์ ์ธ ๋ณํ์ ๋ณด์ฌ์ค๋๋ค:
๊ธฐ์กด ๋ฐฉ์์์ ์ฌ๋ผ์ง ๊ฒ๋ค:
- 15์ค ์ด์ โ 6์ค: ํ๊ธฐ์ ์ธ ์ฝ๋ ์ถ์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ถํ์
- 3ํ ์ด์์ barrier() ํธ์ถ: ๋ช ์์ ๋๊ธฐํ ์ ๋ก
- ๋ณต์กํ ํธ๋ฆฌ ๋ฆฌ๋์ : ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒด
- ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ: ์์ ํ ์ ๊ฑฐ
SIMT ์คํ ๋ชจ๋ธ:
์ํ ๋ ์ธ (SIMT ์คํ):
๋ ์ธ 0: partial_product = a[0] * b[0] = 0.0
๋ ์ธ 1: partial_product = a[1] * b[1] = 4.0
๋ ์ธ 2: partial_product = a[2] * b[2] = 16.0
...
๋ ์ธ 31: partial_product = a[31] * b[31] = 3844.0
warp_sum() ํ๋์จ์ด ์ฐ์ฐ:
๋ชจ๋ ๋ ์ธ โ 0.0 + 4.0 + 16.0 + ... + 3844.0 = 10416.0
๋ชจ๋ ๋ ์ธ์ด ์์ โ total = 10416.0 (๋ธ๋ก๋์บ์คํธ ๊ฒฐ๊ณผ)
๋ฐฐ๋ฆฌ์ด ์์ด ๋์ํ๋ ์ด์ :
- SIMT ์คํ: ๋ชจ๋ ๋ ์ธ์ด ๊ฐ ๋ช ๋ น ๋์ ์คํ
- ํ๋์จ์ด ๋๊ธฐํ:
warp_sum()์ด ์์๋ ๋ ๋ชจ๋ ๋ ์ธ์ด ์ด๋ฏธpartial_product๊ณ์ฐ ์๋ฃ - ๋ด์ฅ ํต์ : GPU ํ๋์จ์ด๊ฐ ๋ฆฌ๋์ ์ฐ์ฐ ์ฒ๋ฆฌ
- ๋ธ๋ก๋์บ์คํธ ๊ฒฐ๊ณผ: ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์
total๊ฐ ์์
2. ํจ์ํ ๋ฐฉ์
์ด๋ฒ์๋ Mojo์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจํด์ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ํ ๋ด์ ์ ๊ตฌํํฉ๋๋ค:
def functional_warp_dot_product[
dtype: DType,
simd_width: Int,
rank: Int,
size: Int,
](
output: TileTensor[mut=True, dtype, OutLayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutType, MutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutType, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def compute_dot_product[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var idx = indices[0]
print("idx:", idx)
# FILL IN (10 lines at most)
# Launch exactly size == WARP_SIZE threads (one warp) to process all elements
elementwise[compute_dot_product, 1, target="gpu"](size, ctx)
ํ
1. ํจ์ํ ๋ฐฉ์์ ๊ตฌ์กฐ ์ดํดํ๊ธฐ
compute_dot_product ํจ์๋ฅผ 10์ค ์ด๋ด๋ก ์์ฑํด์ผ ํฉ๋๋ค:
@parameter
@always_inline
def compute_dot_product[simd_width: Int, rank: Int](indices: IndexList[rank]) capturing -> None:
idx = indices[0]
# ์ฌ๊ธฐ๋ฅผ ์ฑ์ฐ์ธ์ (์ต๋ 10์ค)
ํจ์ํ ํจํด์ ์ฐจ์ด์ :
elementwise๋ฅผ ์ฌ์ฉํ์ฌ ์ ํํWARP_SIZE๊ฐ์ ์ค๋ ๋ ์คํ- ๊ฐ ์ค๋ ๋๊ฐ
idx๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋์ ์์ ์ฒ๋ฆฌ - ๊ฐ์ ์ํ ์ฐ์ฐ, ๋ค๋ฅธ ์คํ ๋ฉ์ปค๋์ฆ
2. ๋ถ๋ถ๊ณฑ ๊ณ์ฐํ๊ธฐ
var partial_product: Scalar[dtype] = 0.0
if idx < size:
a_val = a.load[1](idx, 0)
b_val = b.load[1](idx, 0)
partial_product = (a_val * b_val).reduce_add()
else:
partial_product = 0.0
๋ก๋ฉ ํจํด: a.load[1](idx, 0)์ ์์น idx์์ ์ ํํ 1๊ฐ ์์๋ฅผ ๋ก๋ํฉ๋๋ค
(SIMD ๋ฒกํฐํ ์์).
๊ฒฝ๊ณ ์ฒ๋ฆฌ: ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ค๋ ๋์ partial_product๋ฅผ 0.0์ผ๋ก ์ค์ ํ์ฌ
ํฉ์ฐ์ ๊ธฐ์ฌํ์ง ์๋๋ก ํฉ๋๋ค.
3. ์ํ ์ฐ์ฐ๊ณผ ์ ์ฅ
total = warp_sum(partial_product)
if lane_id() == 0:
output.store[1](Index(idx // WARP_SIZE), total)
์ ์ฅ ํจํด: output.store[1](Index(idx // WARP_SIZE), 0, total)์ ์ถ๋ ฅ
ํ
์์ ์์น (idx // WARP_SIZE, 0)์ 1๊ฐ ์์๋ฅผ ์ ์ฅํฉ๋๋ค.
๋์ผํ ์ํ ๋ก์ง: warp_sum()๊ณผ ๋ ์ธ 0์ ๊ธฐ๋ก ๋ก์ง์ ํจ์ํ ๋ฐฉ์์์๋
๋์ผํ๊ฒ ๋์ํฉ๋๋ค.
4. import์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ค
from gpu import lane_id
from gpu.primitives.warp import sum as warp_sum, WARP_SIZE
# ํจ์ ๋ด์์:
my_lane = lane_id() # 0 ~ WARP_SIZE-1
total = warp_sum(my_value) # ํ๋์จ์ด ๊ฐ์ ๋ฆฌ๋์
warp_size = WARP_SIZE # 32 (NVIDIA) ๋๋ 64 (AMD)
ํจ์ํ ๋ฐฉ์ ํ ์คํธ:
uv run poe p24 --functional
pixi run p24 --functional
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
SIZE: 32
WARP_SIZE: 32
SIMD_WIDTH: 8
=== RESULT ===
out: 10416.0
expected: 10416.0
๐ง Functional approach shows modern Mojo style with warp operations!
Clean, composable, and still leverages warp hardware primitives!
ํ์ด
def functional_warp_dot_product[
InLayoutT: TensorLayout,
OutLayoutT: TensorLayout,
dtype: DType,
simd_width: Int,
rank: Int,
size: Int,
](
output: TileTensor[mut=True, dtype, OutLayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
ctx: DeviceContext,
) raises:
@parameter
@always_inline
def compute_dot_product[
simd_width: Int, rank: Int, alignment: Int = align_of[dtype]()
](indices: IndexList[rank]) capturing -> None:
var idx = indices[0]
# Convert inside GPU kernel to avoid host-captured LayoutTensor issues
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
# Each thread computes one partial product
var partial_product: Scalar[dtype] = 0.0
if idx < size:
var a_val = a_lt.load[1](Index(idx))
var b_val = b_lt.load[1](Index(idx))
partial_product = rebind[Scalar[dtype]](a_val) * rebind[
Scalar[dtype]
](b_val)
else:
partial_product = 0.0
# Warp magic - combines all WARP_SIZE partial products!
var total = warp_sum(partial_product)
# Only lane 0 writes the result (all lanes have the same total)
if lane_id() == 0:
out_lt.store[1](Index(idx // WARP_SIZE), total)
# Launch exactly size == WARP_SIZE threads (one warp) to process all elements
elementwise[compute_dot_product, 1, target="gpu"](size, ctx)
ํจ์ํ ์ํ ๋ฐฉ์์ ์ํ ์ฐ์ฐ์ ํ์ฉํ ํ๋์ ์ธ Mojo ํ๋ก๊ทธ๋๋ฐ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
ํจ์ํ ๋ฐฉ์์ ํน์ง:
elementwise[compute_dot_product, 1, target="gpu"](size, ctx)
์ฅ์ :
- ํ์ ์์ ์ฑ: ์ปดํ์ผ ํ์ ํ ์ ๋ ์ด์์ ๊ฒ์ฌ
- ์กฐํฉ ๊ฐ๋ฅ์ฑ: ๋ค๋ฅธ ํจ์ํ ์ฐ์ฐ๊ณผ ์ฝ๊ฒ ํตํฉ
- ํ๋์ ํจํด: Mojo์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฅ ํ์ฉ
- ์๋ ์ต์ ํ: ์ปดํ์ผ๋ฌ๊ฐ ๊ณ ์์ค ์ต์ ํ๋ฅผ ์ ์ฉ ๊ฐ๋ฅ
์ปค๋ ๋ฐฉ์๊ณผ์ ์ฃผ์ ์ฐจ์ด:
- ์คํ ๋ฉ์ปค๋์ฆ:
enqueue_function๋์elementwise์ฌ์ฉ - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ:
.load[1]()๊ณผ.store[1]()ํจํด ์ฌ์ฉ - ํตํฉ์ฑ: ๋ค๋ฅธ ํจ์ํ ์ฐ์ฐ๊ณผ ์์ฐ์ค๋ฝ๊ฒ ๊ฒฐํฉ
๋์ผํ ์ํ์ ์ด์ :
- ๋๊ธฐํ ์ ๋ก:
warp_sum()์ด ๋์ผํ๊ฒ ๋์ - ํ๋์จ์ด ๊ฐ์: ์ปค๋ ๋ฐฉ์๊ณผ ๊ฐ์ ์ฑ๋ฅ
- ํฌ๋ก์ค ์ํคํ
์ฒ:
WARP_SIZE๊ฐ ์๋์ผ๋ก ์ ์
๋ฒค์น๋งํฌ๋ฅผ ํตํ ์ฑ๋ฅ ๋น๊ต
์ข ํฉ ๋ฒค์น๋งํฌ๋ฅผ ์คํํ์ฌ ์ํ ์ฐ์ฐ์ ํ์ฅ์ฑ์ ํ์ธํฉ๋๋ค:
uv run poe p24 --benchmark
pixi run p24 --benchmark
์ ์ฒด ๋ฒค์น๋งํฌ ์คํ ๊ฒฐ๊ณผ์ ์์์ ๋๋ค:
SIZE: 32
WARP_SIZE: 32
SIMD_WIDTH: 8
--------------------------------------------------------------------------------
Testing SIZE=1 x WARP_SIZE, BLOCKS=1
Running traditional_1x
Running simple_warp_1x
Running functional_warp_1x
--------------------------------------------------------------------------------
Testing SIZE=4 x WARP_SIZE, BLOCKS=4
Running traditional_4x
Running simple_warp_4x
Running functional_warp_4x
--------------------------------------------------------------------------------
Testing SIZE=32 x WARP_SIZE, BLOCKS=32
Running traditional_32x
Running simple_warp_32x
Running functional_warp_32x
--------------------------------------------------------------------------------
Testing SIZE=256 x WARP_SIZE, BLOCKS=256
Running traditional_256x
Running simple_warp_256x
Running functional_warp_256x
--------------------------------------------------------------------------------
Testing SIZE=2048 x WARP_SIZE, BLOCKS=2048
Running traditional_2048x
Running simple_warp_2048x
Running functional_warp_2048x
--------------------------------------------------------------------------------
Testing SIZE=16384 x WARP_SIZE, BLOCKS=16384 (Large Scale)
Running traditional_16384x
Running simple_warp_16384x
Running functional_warp_16384x
--------------------------------------------------------------------------------
Testing SIZE=65536 x WARP_SIZE, BLOCKS=65536 (Massive Scale)
Running traditional_65536x
Running simple_warp_65536x
Running functional_warp_65536x
| name | met (ms) | iters |
| ---------------------- | --------------------- | ----- |
| traditional_1x | 0.00460128 | 100 |
| simple_warp_1x | 0.00574047 | 100 |
| functional_warp_1x | 0.00484192 | 100 |
| traditional_4x | 0.00492671 | 100 |
| simple_warp_4x | 0.00485247 | 100 |
| functional_warp_4x | 0.00587679 | 100 |
| traditional_32x | 0.0062406399999999996 | 100 |
| simple_warp_32x | 0.0054918400000000004 | 100 |
| functional_warp_32x | 0.00552447 | 100 |
| traditional_256x | 0.0050614300000000004 | 100 |
| simple_warp_256x | 0.00488768 | 100 |
| functional_warp_256x | 0.00461472 | 100 |
| traditional_2048x | 0.01120031 | 100 |
| simple_warp_2048x | 0.00884383 | 100 |
| functional_warp_2048x | 0.007038720000000001 | 100 |
| traditional_16384x | 0.038533750000000005 | 100 |
| simple_warp_16384x | 0.0323264 | 100 |
| functional_warp_16384x | 0.01674271 | 100 |
| traditional_65536x | 0.19784991999999998 | 100 |
| simple_warp_65536x | 0.12870176 | 100 |
| functional_warp_65536x | 0.048680310000000004 | 100 |
Benchmarks completed!
WARP OPERATIONS PERFORMANCE ANALYSIS:
GPU Architecture: NVIDIA (WARP_SIZE=32) vs AMD (WARP_SIZE=64)
- 1,...,256 x WARP_SIZE: Grid size too small to benchmark
- 2048 x WARP_SIZE: Warp primative benefits emerge
- 16384 x WARP_SIZE: Large scale (512K-1M elements)
- 65536 x WARP_SIZE: Massive scale (2M-4M elements)
Expected Results at Large Scales:
โข Traditional: Slower due to more barrier overhead
โข Warp operations: Faster, scale better with problem size
โข Memory bandwidth becomes the limiting factor
์ด ์์์์ ์ป์ ์ ์๋ ์ฑ๋ฅ ์ธ์ฌ์ดํธ:
- ์๊ท๋ชจ (1x-4x): ์ํ ์ฐ์ฐ์ด ์ํญ์ ๊ฐ์ ์ ๋ณด์ (~10-15% ๋น ๋ฆ)
- ์ค๊ท๋ชจ (32x-256x): ํจ์ํ ๋ฐฉ์์ด ๊ฐ์ฅ ์ข์ ์ฑ๋ฅ์ ๋ณด์ด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ๋๊ท๋ชจ (16K-65K): ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ์ง๋ฐฐ์ ์ด ๋๋ฉด์ ๋ชจ๋ ๋ฐฉ์์ ์ฑ๋ฅ์ด ์๋ ด
- ๋ณ๋์ฑ: ์ฑ๋ฅ์ ํน์ GPU ์ํคํ ์ฒ์ ๋ฉ๋ชจ๋ฆฌ ์๋ธ์์คํ ์ ํฌ๊ฒ ์์กด
์ฐธ๊ณ : ํ๋์จ์ด(GPU ๋ชจ๋ธ, ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ, WARP_SIZE)์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ํฌ๊ฒ
๋ฌ๋ผ์ง๋๋ค. ํต์ฌ์ ์ ๋์ ์ธ ์์น๋ณด๋ค ์๋์ ์ธ ์ฑ๋ฅ ์ถ์ธ๋ฅผ ๊ด์ฐฐํ๋ ๊ฒ์
๋๋ค.
๋ค์ ๋จ๊ณ
warp.sum ์ฐ์ฐ์ ๋ฐฐ์ ์ผ๋, ๋ค์์ผ๋ก ์งํํ ์ ์์ต๋๋ค:
- ์ธ์ ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉํ ๊น: ์ํ vs ๊ธฐ์กด ๋ฐฉ์์ ๋ํ ์ ๋ต์ ์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ
- ๊ณ ๊ธ ์ํ ์ฐ์ฐ: ๋ณต์กํ ํต์ ํจํด์ ์ํ
shuffle_idx(),shuffle_down(),prefix_sum() - ๋ฉํฐ ์ํ ์๊ณ ๋ฆฌ์ฆ: ์ํ ์ฐ์ฐ๊ณผ ๋ธ๋ก ๋ ๋ฒจ ๋๊ธฐํ์ ๊ฒฐํฉ
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ์ต์ ํ: ์ต๋ ๋์ญํญ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ
๐ก ํต์ฌ ์์ : ์ํ ์ฐ์ฐ์ ๋ณต์กํ ๋๊ธฐํ ํจํด์ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ณธ ์์๋ก ๋์ฒดํ์ฌ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ณํํฉ๋๋ค. ์คํ ๋ชจ๋ธ์ ์ดํดํ๋ฉด ์ฑ๋ฅ์ ํฌ์ํ์ง ์๊ณ ๋ ํ๊ธฐ์ ์ธ ๋จ์ํ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
์ธ์ ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉํ ๊น
๋น ๋ฅธ ํ๋จ ๊ฐ์ด๋
โ ์ํ ์ฐ์ฐ์ ์ฌ์ฉํ ๋:
- 32๊ฐ ์ด์์ ์์์ ๋ํ ๋ฆฌ๋์
์ฐ์ฐ (
sum,max,min) - ๊ท์น์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด (์ธ์ ๋ ์ธ โ ์ธ์ ์ฃผ์)
- ํฌ๋ก์ค ์ํคํ ์ฒ ์ด์์ฑ์ด ํ์ํ ๊ฒฝ์ฐ (NVIDIA/RDNA 32 vs CDNA 64 ์ค๋ ๋)
- ๋ ๊ฐ๋จํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ์ํ ๋
โ ๊ธฐ์กด ๋ฐฉ์์ ์ฌ์ฉํ ๋:
- ๋ณต์กํ ์ํ ๊ฐ ๋๊ธฐํ๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ๋ถ๊ท์นํ๊ฑฐ๋ ์ฐ๋ฐ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ์ค๋ ๋๋ณ ์์ ๋์ด ๋ค๋ฅธ ๊ฒฝ์ฐ (์ํ ๋ถ๊ธฐ ๋ฐ์)
- ๋ฌธ์ ํฌ๊ธฐ๊ฐ
size < WARP_SIZE์ธ ๊ฒฝ์ฐ
์ฑ๋ฅ ํน์ฑ
๋ฌธ์ ํฌ๊ธฐ๋ณ ํ์ฅ์ฑ
| ์์ ์ | ์ํ ์ด์ | ๋น๊ณ |
|---|---|---|
| < 32 | ์์ | ๊ธฐ์กด ๋ฐฉ์์ด ์ ๋ฆฌ |
| 32-1K | 1.2-1.5๋ฐฐ | ์ด์ ์ด ๋ํ๋๊ธฐ ์์ |
| 1K-32K | 1.5-2.5๋ฐฐ | ์ํ ์ฐ์ฐ์ด ํ์ |
| > 32K | ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ | ์์ชฝ ๋ชจ๋ ๋์ญํญ์ ์ํด ์ ํ |
์ํ์ ํต์ฌ ์ด์
- ๋๊ธฐํ ์ค๋ฒํค๋ ์ ๋ก: ๋ฐฐ๋ฆฌ์ด ๋น์ฉ ์ ๊ฑฐ
- ์ต์ํ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ถํ์
- ์ฐ์ํ ํ์ฅ์ฑ: ์ํ ์๊ฐ ๋์ด๋ ์๋ก ์ฑ๋ฅ ํฅ์
- ๊ฐ๊ฒฐํ ์ฝ๋: ๋ ์ ์ ์ค ์, ๋ ์ ์ ์ค๋ฅ ๊ฐ๋ฅ์ฑ
์๊ณ ๋ฆฌ์ฆ๋ณ ๊ฐ์ด๋
| ์๊ณ ๋ฆฌ์ฆ | ๊ถ์ฅ ์ฌํญ | ์ด์ |
|---|---|---|
| ๋ด์ | ์ํ ์ฐ์ฐ (1K+ ์์) | ๋จ์ผ ๋ฆฌ๋์ , ๊ท์น์ ์ ๊ทผ |
| ํ๋ ฌ ํ/์ด ํฉ๊ณ | ์ํ ์ฐ์ฐ | ์์ฐ์ค๋ฌ์ด ๋ฆฌ๋์ ํจํด |
| ๋์ ํฉ | ํญ์ prefix_sum() ์ฌ์ฉ | ํ๋์จ์ด ์ต์ ํ๋ ๊ธฐ๋ณธ ์์ |
| ํ๋ง (max/min) | ์ํ ์ฐ์ฐ (๊ท์น์ ์๋์ฐ) | ํจ์จ์ ์ธ ์๋์ฐ ๋ฆฌ๋์ |
| ๊ตฌ๊ฐ์ด ๋ง์ ํ์คํ ๊ทธ๋จ | ๊ธฐ์กด ๋ฐฉ์ | ๋ถ๊ท์นํ ์ฐ๊ธฐ, ์์์ ์ ๋ฐ์ดํธ |
์ฝ๋ ์์
โ ์ํ์ ์ ํฉํ ๊ฒฝ์ฐ
# ๋ฆฌ๋์
์ฐ์ฐ
from gpu.primitives.warp import sum, max
var total = sum(partial_values)
var maximum = max(partial_values)
# ํต์ ํจํด
from gpu.primitives.warp import shuffle_idx, prefix_sum
var broadcast = shuffle_idx(my_value, 0)
var running_sum = prefix_sum(my_value)
โ ๊ธฐ์กด ๋ฐฉ์์ด ๋์ ๊ฒฝ์ฐ
# ๋ณต์กํ ๋ค๋จ๊ณ ๋๊ธฐํ
stage1_compute()
barrier() # ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ
stage2_depends_on_stage1()
# ๋ถ๊ท์นํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
var value = input[random_indices[global_i]] # ์ฐ๋ฐ์ ์ฝ๊ธฐ
# ๋ฐ์ดํฐ ์์กด์ ์์
if input[global_i] > threshold:
result = expensive_computation() # ์ํ ๋ถ๊ธฐ ๋ฐ์
์ฑ๋ฅ ์ธก์
# ํญ์ ์์ชฝ ๋ฐฉ์์ ๋ฒค์น๋งํฌํ์ธ์
mojo p22.mojo --benchmark
# ํ์ฅ ํจํด์ ํ์ธํ์ธ์:
# traditional_1x: X.XX ms
# warp_1x: Y.YY ms # ๋ ๋นจ๋ผ์ผ ํจ
# warp_32x: Z.ZZ ms # ์ด์ ์ด ์ปค์ ธ์ผ ํจ
์์ฝ
์ํ ์ฐ์ฐ์ผ๋ก ์์ํ์ธ์:
- ๊ท์น์ ์ธ ์ ๊ทผ ํจํด์ ๊ฐ์ง ๋ฆฌ๋์
- ๋ฌธ์ โฅ 1 ์ํ ํฌ๊ธฐ
- ํฌ๋ก์ค ํ๋ซํผ ํธํ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ
๊ธฐ์กด ๋ฐฉ์์ ์ฌ์ฉํ์ธ์:
- ๋ณต์กํ ๋๊ธฐํ๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ๋ถ๊ท์นํ ๋ฉ๋ชจ๋ฆฌ ํจํด
- ์์ ๋ฌธ์ ๋๋ ์ฌํ ๋ถ๊ธฐ
ํ๋จ์ด ์ด๋ ค์ธ ๋: ์์ชฝ ๋ชจ๋ ๊ตฌํํ๊ณ ๋ฒค์น๋งํฌํ์ธ์. ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ณด๋ฉด ๋ต์ด ๋์ต๋๋ค.
Puzzle 25: ์ํ ํต์
๊ฐ์
Puzzle 25: ์ํ ํต์ ๊ธฐ๋ณธ ์์์์๋ ๊ณ ๊ธ GPU ์ํ ๋ ๋ฒจ ํต์ ์ฐ์ฐ - ์ํ ๋ด์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๊ตํ๊ณผ ์กฐ์ ํจํด์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ณธ ์์๋ฅผ ์๊ฐํฉ๋๋ค. shuffle_down๊ณผ broadcast๋ฅผ ์ฌ์ฉํ์ฌ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด ์์ด ์ด์ ํต์ ๊ณผ ์งํฉ ์กฐ์ ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
Part VII: GPU ์ํ ํต์ ์์๋ ์ค๋ ๋ ๊ทธ๋ฃน ๋ด ์ํ ๋ ๋ฒจ ๋ฐ์ดํฐ ์ด๋ ์ฐ์ฐ์ ๋ค๋ฃน๋๋ค. ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ์ธ๋ฑ์ฑ + ๊ฒฝ๊ณ ๊ฒ์ฌ ํจํด์ ํ๋์จ์ด ์ต์ ํ๋ ๋ฐ์ดํฐ ์ด๋์ ํ์ฉํ๋ ํจ์จ์ ์ธ ์ํ ํต์ ํธ์ถ๋ก ๋์ฒดํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ์ํ๋ ๋ก์คํ ์ผ๋ก ์คํ๋ฉ๋๋ค - Mojo์ ์ํ ํต์ ์ฐ์ฐ์ ์ด ๋๊ธฐํ๋ฅผ ํ์ฉํ์ฌ ์๋ ๊ฒฝ๊ณ ์ฒ๋ฆฌ์ ๋ช ์์ ๋๊ธฐํ ์์ด ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๊ตํ ๊ธฐ๋ณธ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ
์ํ ํต์ ๋ชจ๋ธ
GPU ์ํ ๋ด ๊ธฐ๋ณธ ํต์ ํจํด์ ์ดํดํฉ๋๋ค:
GPU ์ํ (32 ์ค๋ ๋, SIMT ๋ก์คํ
์คํ)
โโโ ๋ ์ธ 0 โโshuffle_downโโ> ๋ ์ธ 1 โโshuffle_downโโ> ๋ ์ธ 2
โโโ ๋ ์ธ 1 โโshuffle_downโโ> ๋ ์ธ 2 โโshuffle_downโโ> ๋ ์ธ 3
โโโ ๋ ์ธ 2 โโshuffle_downโโ> ๋ ์ธ 3 โโshuffle_downโโ> ๋ ์ธ 4
โ ...
โโโ ๋ ์ธ 31 โโshuffle_downโโ> undefined (๊ฒฝ๊ณ)
๋ธ๋ก๋์บ์คํธ ํจํด:
๋ ์ธ 0 โโbroadcastโโ> ๋ชจ๋ ๋ ์ธ (0, 1, 2, ..., 31)
ํ๋์จ์ด ํ์ค:
- ๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์ : ๋ฐ์ดํฐ๊ฐ ์ค๋ ๋ ๋ ์ง์คํฐ ์ฌ์ด๋ฅผ ์ง์ ์ด๋ํฉ๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ด ํ์ํ์ง ์์ต๋๋ค
- ์๋ ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ํ๋์จ์ด๊ฐ ์ํ ๊ฒฝ๊ณ์ ์์ธ ์ํฉ์ ๊ด๋ฆฌํฉ๋๋ค
- ๋จ์ผ ์ฌ์ดํด ์ฐ์ฐ: ํ๋์ ๋ช ๋ น ์ฌ์ดํด์์ ํต์ ์ด ์๋ฃ๋ฉ๋๋ค
Mojo์ ์ํ ํต์ ์ฐ์ฐ
gpu.primitives.warp์ ํต์ฌ ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ๋ฐฐ์๋๋ค:
shuffle_down(value, offset): ๋ ๋์ ์ธ๋ฑ์ค์ ๋ ์ธ์์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ (์ด์ ์ ๊ทผ)broadcast(value): ๋ ์ธ 0์ ๊ฐ์ ๋ชจ๋ ๋ ์ธ์ ๊ณต์ (์ผ๋๋ค)shuffle_idx(value, lane): ํน์ ๋ ์ธ์์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ (์์ ์ ๊ทผ)shuffle_up(value, offset): ๋ ๋ฎ์ ์ธ๋ฑ์ค์ ๋ ์ธ์์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ (์ญ๋ฐฉํฅ ์ด์)
์ฐธ๊ณ : ์ด ํผ์ฆ์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ํต์ ํจํด์ธ
shuffle_down()๊ณผbroadcast()์ ์ด์ ์ ๋ง์ถฅ๋๋ค. ๋ชจ๋ ์ํ ์ฐ์ฐ์ ๋ํ ์ ์ฒด ๋ด์ฉ์ Mojo GPU ์ํ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ฑ๋ฅ ๋ณํ ์์
# ๋ณต์กํ ์ด์ ์ ๊ทผ ํจํด (๊ธฐ์กด ๋ฐฉ์):
shared = TileTensor[
dtype,
row_major[WARP_SIZE](),
MutAnyOrigin,
address_space = AddressSpace.SHARED,
].stack_allocation()
shared[local_i] = input[global_i]
barrier()
if local_i < WARP_SIZE - 1:
next_value = shared[local_i + 1] # ์ด์ ์ ๊ทผ
result = next_value - shared[local_i]
else:
result = 0 # ๊ฒฝ๊ณ ์ฒ๋ฆฌ
barrier()
# ์ํ ํต์ ์ ์ด ๋ชจ๋ ๋ณต์ก์ฑ์ ์ ๊ฑฐํฉ๋๋ค:
current_val = input[global_i]
next_val = shuffle_down(current_val, 1) # ์ด์์ ์ง์ ์ ๊ทผ
if lane < WARP_SIZE - 1:
result = next_val - current_val
else:
result = 0
์ํ ํต์ ์ด ๋น๋๋ ์๊ฐ
์ฑ๋ฅ ํน์ฑ์ ์ดํดํฉ๋๋ค:
| ํต์ ํจํด | ๊ธฐ์กด ๋ฐฉ์ | ์ํ ์ฐ์ฐ |
|---|---|---|
| ์ด์ ์ ๊ทผ | ๊ณต์ ๋ฉ๋ชจ๋ฆฌ | ๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์ |
| ์คํ ์ค ์ฐ์ฐ | ๋ณต์กํ ์ธ๋ฑ์ฑ | ๊ฐ๋จํ ์ ํ ํจํด |
| ๋ธ๋ก ์กฐ์ | ๋ฐฐ๋ฆฌ์ด + ๊ณต์ ๋ฉ๋ชจ๋ฆฌ | ๋จ์ผ ๋ธ๋ก๋์บ์คํธ |
| ๊ฒฝ๊ณ ์ฒ๋ฆฌ | ์๋ ๊ฒ์ฌ | ํ๋์จ์ด ์๋ ์ฒ๋ฆฌ |
์ ์ ์ง์
์ํ ํต์ ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ๋ค์ ๋ด์ฉ์ ์ต์ํด์ผ ํฉ๋๋ค:
- Part VII ์ํ ๊ธฐ์ด: SIMT ์คํ๊ณผ ๊ธฐ๋ณธ ์ํ ์ฐ์ฐ์ ๋ํ ์ดํด (Puzzle 24: ์ํ ๊ธฐ์ด ์ฐธ๊ณ )
- GPU ์ค๋ ๋ ๊ณ์ธต ๊ตฌ์กฐ: ๋ธ๋ก, ์ํ, ๋ ์ธ ๋ฒํธ ๋งค๊ธฐ๊ธฐ
- TileTensor ์ฐ์ฐ: ๋ก๋, ์ ์ฅ, ํ ์ ์กฐ์
- ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ: ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ์ฅ์๋ฆฌ ์ผ์ด์ค ๊ด๋ฆฌ
ํ์ต ๊ฒฝ๋ก
1. shuffle_down์ ์ด์ฉํ ์ด์ ํต์
์คํ ์ค ์ฐ์ฐ๊ณผ ์ ํ ์ฐจ๋ถ์ ์ํ ์ด์ ๊ธฐ๋ฐ ํต์ ํจํด์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
shuffle_down()์ผ๋ก ์ธ์ ๋ ์ธ ๋ฐ์ดํฐ ์ ๊ทผํ๊ธฐ- ์ ํ ์ฐจ๋ถ๊ณผ ์ด๋ ํ๊ท ๊ตฌํ
- ์ํ ๊ฒฝ๊ณ ์๋ ์ฒ๋ฆฌ
- ํ์ฅ๋ ์ด์ ์ ๊ทผ์ ์ํ ๋ค์ค ์คํ์ ์ ํ
ํต์ฌ ํจํด:
current_val = input[global_i]
next_val = shuffle_down(current_val, 1)
if lane < WARP_SIZE - 1:
result = compute_with_neighbors(current_val, next_val)
2. ๋ธ๋ก๋์บ์คํธ๋ฅผ ์ด์ฉํ ์งํฉ ์กฐ์
โ warp.broadcast()
๋ธ๋ก ๋ ๋ฒจ ์กฐ์ ๊ณผ ์งํฉ์ ์์ฌ๊ฒฐ์ ์ ์ํ ์ผ๋๋ค ํต์ ํจํด์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
broadcast()๋ก ๊ณ์ฐ๋ ๊ฐ์ ๋ชจ๋ ๋ ์ธ์ ๊ณต์- ๋ธ๋ก ๋ ๋ฒจ ํต๊ณ์ ์งํฉ์ ์์ฌ๊ฒฐ์ ๊ตฌํ
- ๋ธ๋ก๋์บ์คํธ์ ์กฐ๊ฑด๋ถ ๋ก์ง ๊ฒฐํฉ
- ๊ณ ๊ธ ๋ธ๋ก๋์บ์คํธ-์ ํ ์กฐ์ ํจํด
ํต์ฌ ํจํด:
var shared_value = 0.0
if lane == 0:
shared_value = compute_block_statistic()
shared_value = broadcast(shared_value)
result = use_shared_value(shared_value, local_data)
ํต์ฌ ๊ฐ๋
ํต์ ํจํด
์ํ ํต์ ์ ๊ธฐ๋ณธ ํจ๋ฌ๋ค์์ ์ดํดํฉ๋๋ค:
- ์ด์ ํต์ : ๋ ์ธ ๊ฐ ์ธ์ ๋ฐ์ดํฐ ๊ตํ
- ์งํฉ ์กฐ์ : ํ๋์ ๋ ์ธ์์ ๋ชจ๋ ๋ ์ธ์ผ๋ก ์ ๋ณด ๊ณต์
- ์คํ ์ค ์ฐ์ฐ: ๊ณ ์ ๋ ํจํด์ผ๋ก ์ด์ ๋ฐ์ดํฐ ์ ๊ทผ
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ํ ๊ฐ์ฅ์๋ฆฌ์์์ ํต์ ๊ด๋ฆฌ
ํ๋์จ์ด ์ต์ ํ
์ํ ํต์ ์ด GPU ํ๋์จ์ด์ ๋งคํ๋๋ ๋ฐฉ์์ ์ดํดํฉ๋๋ค:
- ๋ ์ง์คํฐ ํ์ผ ํต์ : ์ค๋ ๋ ๊ฐ ๋ ์ง์คํฐ ์ง์ ์ ๊ทผ
- SIMT ์คํ: ๋ชจ๋ ๋ ์ธ์ด ํต์ ์ ๋์์ ์คํํฉ๋๋ค
- ์ ๋ก ์ง์ฐ ์๊ฐ: ์คํ ์ ๋ ๋ด์์ ํต์ ์ด ์๋ฃ๋ฉ๋๋ค
- ์๋ ๋๊ธฐํ: ๋ช ์์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ํ์ง ์์ต๋๋ค
์๊ณ ๋ฆฌ์ฆ ๋ณํ
๊ธฐ์กด ๋ณ๋ ฌ ํจํด์ ์ํ ํต์ ์ผ๋ก ๋ณํํฉ๋๋ค:
- ๋ฐฐ์ด ์ด์ ์ ๊ทผ โ
shuffle_down() - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ โ
broadcast() - ๋ณต์กํ ๊ฒฝ๊ณ ๋ก์ง โ ํ๋์จ์ด ์๋ ์ฒ๋ฆฌ
- ๋ค๋จ๊ณ ๋๊ธฐํ โ ๋จ์ผ ํต์ ์ฐ์ฐ
์์ํ๊ธฐ
์ด์ ๊ธฐ๋ฐ ์ ํ ์ฐ์ฐ์ผ๋ก ๊ธฐ์ด๋ฅผ ๋ค์ง ๋ค์, ๊ณ ๊ธ ์กฐ์ ์ ์ํ ์งํฉ ๋ธ๋ก๋์บ์คํธ ํจํด์ผ๋ก ๋์๊ฐ๋๋ค.
๐ก ์ฑ๊ณต ํ: ์ํ ํต์ ์ ๊ฐ์ ์ํ ๋ด ์ค๋ ๋ ๊ฐ์ ํ๋์จ์ด ๊ฐ์ ๋ฉ์์ง ํจ์ฑ์ผ๋ก ์๊ฐํ์ธ์. ์ด ๋ฉํ ๋ชจ๋ธ์ด GPU์ SIMT ์ํคํ ์ฒ๋ฅผ ํ์ฉํ๋ ํจ์จ์ ์ธ ํต์ ํจํด์ผ๋ก ์๋ดํ ๊ฒ์ ๋๋ค.
ํ์ต ๋ชฉํ: Puzzle 25๋ฅผ ๋ง์น๋ฉด, ์ํ ํต์ ์ด ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๋์ฒดํ ์ ์๋ ์ํฉ์ ์ธ์ํ์ฌ ๋ ๊ฐ๋จํ๊ณ ๋น ๋ฅธ ์ด์ ๊ธฐ๋ฐ ์๊ณ ๋ฆฌ์ฆ๊ณผ ์กฐ์ ์๊ณ ๋ฆฌ์ฆ์ ์์ฑํ ์ ์๊ฒ ๋ฉ๋๋ค.
์์ํ๊ธฐ: warp.shuffle_down() ์์ ์ด์ ํต์ ์ ๋ฐฐ์ด ๋ค์, warp.broadcast() ์์ ์งํฉ ์กฐ์ ํจํด์ผ๋ก ๋์๊ฐ์ธ์.
warp.shuffle_down() ์ผ๋์ผ ํต์
์ํ ๋ ๋ฒจ ์ด์ ํต์ ์์๋ shuffle_down()์ ์ฌ์ฉํ์ฌ ์ํ ๋ด ์ธ์ ๋ ์ธ์
๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด ๊ฐ๋ ฅํ ๊ธฐ๋ณธ ์์๋ฅผ ํตํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ ๋ช
์์
๋๊ธฐํ ์์ด ์ ํ ์ฐจ๋ถ, ์ด๋ ํ๊ท , ์ด์ ๊ธฐ๋ฐ ๊ณ์ฐ์ ํจ์จ์ ์ผ๋ก ์ํํ ์
์์ต๋๋ค.
ํต์ฌ ํต์ฐฐ: shuffle_down() ์ฐ์ฐ์ SIMT ์คํ์ ํ์ฉํ์ฌ ๊ฐ ๋ ์ธ์ด ๊ฐ์ ์ํ ๋ด ์ด์์ ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์๊ฒ ํ๋ฉฐ, ํจ์จ์ ์ธ ์คํ ์ค ํจํด๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ฐ์ฐ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
์คํ ์ค ์ฐ์ฐ์ด๋? ์คํ ์ค ์ฐ์ฐ์ ๊ฐ ์ถ๋ ฅ ์์๊ฐ ์ด์ ์ ๋ ฅ ์์์ ๊ณ ์ ๋ ํจํด์ ์์กดํ๋ ๊ณ์ฐ์ ๋๋ค. ๋ํ์ ์ธ ์๋ก ์ ํ ์ฐจ๋ถ(๋ํจ์), ํฉ์ฑ๊ณฑ, ์ด๋ ํ๊ท ์ด ์์ต๋๋ค. โ์คํ ์คโ์ ์ด์ ์ ๊ทผ ํจํด์ ๊ฐ๋ฆฌํต๋๋ค - ์๋ฅผ ๋ค์ด
[i-1, i, i+1]์ ์ฝ๋ 3์ ์คํ ์ค์ด๋[i-2, i-1, i, i+1, i+2]๋ฅผ ์ฝ๋ 5์ ์คํ ์ค์ด ์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
shuffle_down()์ ํ์ฉํ ์ํ ๋ ๋ฒจ ๋ฐ์ดํฐ ์ ํ- ์คํ ์ค ๊ณ์ฐ์ ์ํ ์ด์ ์ ๊ทผ ํจํด
- ์ํ ๊ฐ์ฅ์๋ฆฌ์์์ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
- ํ์ฅ๋ ์ด์ ์ ๊ทผ์ ์ํ ๋ค์ค ์คํ์ ์ ํ
- ๋ฉํฐ ๋ธ๋ก ์๋๋ฆฌ์ค์์์ ์ํ ๊ฐ ์กฐ์
shuffle_down ์ฐ์ฐ์ ๊ฐ ๋ ์ธ์ด ๋ ๋์ ์ธ๋ฑ์ค์ ๋ ์ธ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์
์๊ฒ ํฉ๋๋ค: \[\Large \text{shuffle_down}(\text{value}, \text{offset}) =
\text{value_from_lane}(\text{lane_id} + \text{offset})\]
์ด๋ฅผ ํตํด ๋ณต์กํ ์ด์ ์ ๊ทผ ํจํด์ด ๊ฐ๋จํ ์ํ ๋ ๋ฒจ ์ฐ์ฐ์ผ๋ก ๋ณํ๋์ด, ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ์ธ๋ฑ์ฑ ์์ด ํจ์จ์ ์ธ ์คํ ์ค ๊ณ์ฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
1. ๊ธฐ๋ณธ ์ด์ ์ฐจ๋ถ
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ ์ด์์:
row_major[SIZE]()(1D row-major)
shuffle_down ๊ฐ๋
๊ธฐ์กด ์ด์ ์ ๊ทผ ๋ฐฉ์์ ๋ณต์กํ ์ธ๋ฑ์ฑ๊ณผ ๊ฒฝ๊ณ ๊ฒ์ฌ๊ฐ ํ์ํฉ๋๋ค:
# ๊ธฐ์กด ๋ฐฉ์ - ๋ณต์กํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์
if global_i < size - 1:
next_value = input[global_i + 1] # ๋ฒ์ ์ด๊ณผ ๊ฐ๋ฅ์ฑ
result = next_value - current_value
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์ :
- ๊ฒฝ๊ณ ๊ฒ์ฌ: ๋ฐฐ์ด ๊ฒฝ๊ณ๋ฅผ ์๋์ผ๋ก ํ์ธํด์ผ ํจ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ๋ณ๋์ ๋ฉ๋ชจ๋ฆฌ ๋ก๋๊ฐ ํ์
- ๋๊ธฐํ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ํ ์ ์์
- ๋ณต์กํ ๋ก์ง: ๊ฒฝ๊ณ์ ์์ธ ์ํฉ ์ฒ๋ฆฌ๊ฐ ์ฅํฉํด์ง
shuffle_down()์ ์ฌ์ฉํ๋ฉด ์ด์ ์ ๊ทผ์ด ๊ฐ๊ฒฐํด์ง๋๋ค:
# ์ํ ์
ํ ๋ฐฉ์ - ๊ฐ๋จํ๊ณ ์์
current_val = input[global_i]
next_val = shuffle_down(current_val, 1) # lane+1์์ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
if lane < WARP_SIZE - 1:
result = next_val - current_val
shuffle_down์ ์ฅ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋ถํ์
- ์๋ ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ํ๋์จ์ด๊ฐ ์ํ ๊ฒฝ๊ณ๋ฅผ ๊ด๋ฆฌ
- ๋๊ธฐํ ๋ถํ์: SIMT ์คํ์ด ์ ํ์ฑ์ ๋ณด์ฅ
- ์กฐํฉ ๊ฐ๋ฅ: ๋ค๋ฅธ ์ํ ์ฐ์ฐ๊ณผ ์ฝ๊ฒ ๊ฒฐํฉ
์์ฑํ ์ฝ๋
shuffle_down()์ผ๋ก ๋ค์ ์์์ ์ ๊ทผํ์ฌ ์ ํ ์ฐจ๋ถ์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ๊ฐ ์์์ ์ด์ฐ ๋ํจ์(์ ํ ์ฐจ๋ถ)๋ฅผ ๊ณ์ฐํฉ๋๋ค: \[\Large \text{output}[i] = \text{input}[i+1] - \text{input}[i]\]
์
๋ ฅ ๋ฐ์ดํฐ [0, 1, 4, 9, 16, 25, ...] (์ ๊ณฑ์: i * i)๋ฅผ ์ฐจ๋ถ๊ฐ
[1, 3, 5, 7, 9, ...] (ํ์)๋ก ๋ณํํ์ฌ, ์ด์ฐจ ํจ์์ ์ด์ฐ ๋ํจ์๋ฅผ ํจ๊ณผ์ ์ผ๋ก
๊ณ์ฐํฉ๋๋ค.
comptime SIZE = WARP_SIZE
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (WARP_SIZE, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
def neighbor_difference[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Compute finite differences: output[i] = input[i+1] - input[i]
Uses shuffle_down(val, 1) to get the next neighbor's value.
Works across multiple blocks, each processing one warp worth of data.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
# FILL IN (roughly 7 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p25/p25.mojo
ํ
1. shuffle_down ์ดํดํ๊ธฐ
shuffle_down(value, offset) ์ฐ์ฐ์ ๊ฐ ๋ ์ธ์ด ๋ ๋์ ์ธ๋ฑ์ค์ ๋ ์ธ์์
๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๊ฒ ํฉ๋๋ค. ๋ช
์์ ๋ฉ๋ชจ๋ฆฌ ๋ก๋ ์์ด ์ด์ ์์์ ์ ๊ทผํ๋
๋ฐฉ๋ฒ์ ์ดํด๋ณด์ธ์.
shuffle_down(val, 1)์ด ํ๋ ์ผ:
- ๋ ์ธ 0์ด ๋ ์ธ 1์ ๊ฐ์ ๋ฐ์
- ๋ ์ธ 1์ด ๋ ์ธ 2์ ๊ฐ์ ๋ฐ์
- โฆ
- ๋ ์ธ 30์ด ๋ ์ธ 31์ ๊ฐ์ ๋ฐ์
- ๋ ์ธ 31์ ๋ฏธ์ ์ ๊ฐ์ ๋ฐ์ (๊ฒฝ๊ณ ๊ฒ์ฌ๋ก ์ฒ๋ฆฌ)
2. ์ํ ๊ฒฝ๊ณ ๊ณ ๋ ค์ฌํญ
์ํ์ ๊ฐ์ฅ์๋ฆฌ์์ ์ด๋ค ์ผ์ด ์ผ์ด๋๋์ง ์๊ฐํด ๋ณด์ธ์. ์ผ๋ถ ๋ ์ธ์ ์ ํ ์ฐ์ฐ์ผ๋ก ์ ๊ทผํ ์ ํจํ ์ด์์ด ์์ ์ ์์ต๋๋ค.
๊ณผ์ : ์ํ ๊ฒฝ๊ณ์์ ์ ํ ์ฐ์ฐ์ด ๋ฏธ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ๋๋ก ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํ์ธ์.
WARP_SIZE = 32์์์ ์ด์ ์ฐจ๋ถ:
-
์ ํจํ ์ฐจ๋ถ (
lane < WARP_SIZE - 1): ๋ ์ธ 0-30 (31๊ฐ ๋ ์ธ)- ์กฐ๊ฑด: \(\text{lane_id}() \in {0, 1, \cdots, 30}\)
- ์ด์ :
shuffle_down(current_val, 1)์ด ๋ค์ ์ด์์ ๊ฐ์ ์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ธ์ด - ๊ฒฐ๊ณผ:
output[i] = input[i+1] - input[i](์ ํ ์ฐจ๋ถ)
-
๊ฒฝ๊ณ ์ผ์ด์ค (else): ๋ ์ธ 31 (1๊ฐ ๋ ์ธ)
- ์กฐ๊ฑด: \(\text{lane_id}() = 31\)
- ์ด์ :
shuffle_down(current_val, 1)์ด ๋ฏธ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ (๋ ์ธ 32๊ฐ ์์) - ๊ฒฐ๊ณผ:
output[i] = 0(์ฐจ๋ถ ๊ณ์ฐ ๋ถ๊ฐ)
3. ๋ ์ธ ์๋ณ
lane = lane_id() # 0๋ถํฐ WARP_SIZE-1๊น์ง ๋ฐํ
๋ ์ธ ๋ฒํธ ๋งค๊ธฐ๊ธฐ: ๊ฐ ์ํ ๋ด์์ ๋ ์ธ์ 0, 1, 2,โฆ, WARP_SIZE-1๋ก ๋ฒํธ๊ฐ
๋งค๊ฒจ์ง๋๋ค
์ด์ ์ฐจ๋ถ ํ ์คํธ:
pixi run p25 --neighbor
pixi run -e amd p25 --neighbor
pixi run -e apple p25 --neighbor
uv run poe p25 --neighbor
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: [1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0, 33.0, 35.0, 37.0, 39.0, 41.0, 43.0, 45.0, 47.0, 49.0, 51.0, 53.0, 55.0, 57.0, 59.0, 61.0, 0.0]
expected: [1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0, 33.0, 35.0, 37.0, 39.0, 41.0, 43.0, 45.0, 47.0, 49.0, 51.0, 53.0, 55.0, 57.0, 59.0, 61.0, 0.0]
โ
Basic neighbor difference test passed!
์๋ฃจ์
def neighbor_difference[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
):
"""
Compute finite differences: output[i] = input[i+1] - input[i]
Uses shuffle_down(val, 1) to get the next neighbor's value.
Works across multiple blocks, each processing one warp worth of data.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
# Get current value
var current_val = input[global_i]
# Get next neighbor's value using shuffle_down
var next_val = shuffle_down(current_val, 1)
# Compute difference - valid within warp boundaries
# Last lane of each warp has no valid neighbor within the warp
# Note there's only one warp in this test, so we don't need to check global_i < size - 1
# We'll see how this works with multiple blocks in the next tests
if lane < WARP_SIZE - 1:
output[global_i] = next_val - current_val
else:
# Last thread in warp or last thread overall, set to 0
output[global_i] = 0
์ด ์๋ฃจ์
์ shuffle_down()์ด ๊ธฐ์กด ๋ฐฐ์ด ์ธ๋ฑ์ฑ์ ํจ์จ์ ์ธ ์ํ ๋ ๋ฒจ ํต์ ์ผ๋ก
์ด๋ป๊ฒ ๋ณํํ๋์ง ๋ณด์ฌ์ค๋๋ค.
์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
current_val = input[global_i] # ๊ฐ ๋ ์ธ์ด ์์ ์ ์์๋ฅผ ์ฝ์
next_val = shuffle_down(current_val, 1) # ํ๋์จ์ด๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ค๋ฅธ์ชฝ์ผ๋ก ์ด๋
if lane < WARP_SIZE - 1:
output[global_i] = next_val - current_val # ์ฐจ๋ถ ๊ณ์ฐ
else:
output[global_i] = 0 # ๊ฒฝ๊ณ ์ฒ๋ฆฌ
SIMT ์คํ ์์ธ ๋ถ์:
์ฌ์ดํด 1: ๋ชจ๋ ๋ ์ธ์ด ๋์์ ๊ฐ์ ๋ก๋
๋ ์ธ 0: current_val = input[0] = 0
๋ ์ธ 1: current_val = input[1] = 1
๋ ์ธ 2: current_val = input[2] = 4
...
๋ ์ธ 31: current_val = input[31] = 961
์ฌ์ดํด 2: shuffle_down(current_val, 1)์ด ๋ชจ๋ ๋ ์ธ์์ ์คํ
๋ ์ธ 0: ๋ ์ธ 1์์ current_val ์์ โ next_val = 1
๋ ์ธ 1: ๋ ์ธ 2์์ current_val ์์ โ next_val = 4
๋ ์ธ 2: ๋ ์ธ 3์์ current_val ์์ โ next_val = 9
...
๋ ์ธ 30: ๋ ์ธ 31์์ current_val ์์ โ next_val = 961
๋ ์ธ 31: ๋ฏธ์ ์ ์์ (๋ ์ธ 32 ์์) โ next_val = ?
์ฌ์ดํด 3: ์ฐจ๋ถ ๊ณ์ฐ (๋ ์ธ 0-30๋ง ํด๋น)
๋ ์ธ 0: output[0] = 1 - 0 = 1
๋ ์ธ 1: output[1] = 4 - 1 = 3
๋ ์ธ 2: output[2] = 9 - 4 = 5
...
๋ ์ธ 31: output[31] = 0 (๊ฒฝ๊ณ ์กฐ๊ฑด)
์ํ์ ํต์ฐฐ: ์ด์ฐ ๋ํจ์ ์ฐ์ฐ์ \(D\)๋ฅผ ๊ตฌํํฉ๋๋ค: \[\Large D\lbrack f\rbrack(i) = f(i+1) - f(i)\]
์ด์ฐจ ์ ๋ ฅ \(f(i) = i^2\)์ ๋ํด: \[\Large D[i^2] = (i+1)^2 - i^2 = i^2 + 2i + 1 - i^2 = 2i + 1\]
shuffle_down์ด ์ฐ์ํ ์ด์ :
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๊ธฐ์กด ๋ฐฉ์์
input[global_i + 1]๋ก๋๊ฐ ํ์ํ์ฌ ์บ์ ๋ฏธ์ค๋ฅผ ์ ๋ฐํ ์ ์์ - ๊ฒฝ๊ณ ์์ ์ฑ: ๋ฒ์ ์ด๊ณผ ์ ๊ทผ ์ํ์ด ์์ - ํ๋์จ์ด๊ฐ ์ํ ๊ฒฝ๊ณ๋ฅผ ์ฒ๋ฆฌ
- SIMT ์ต์ ํ: ๋จ์ผ ๋ช ๋ น์ด ๋ชจ๋ ๋ ์ธ์ ๋์์ ์ฒ๋ฆฌ
- ๋ ์ง์คํฐ ํต์ : ๋ฐ์ดํฐ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ๊ฐ ์๋ ๋ ์ง์คํฐ ์ฌ์ด๋ฅผ ์ด๋
์ฑ๋ฅ ํน์ฑ:
- ์ง์ฐ ์๊ฐ: 1 ์ฌ์ดํด (๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ 100+ ์ฌ์ดํด ๋๋น)
- ๋์ญํญ: 0 ๋ฐ์ดํธ (๊ธฐ์กด ๋ฐฉ์์ ์ค๋ ๋๋น 4๋ฐ์ดํธ ๋๋น)
- ๋ณ๋ ฌ์ฑ: 32๊ฐ ๋ ์ธ ๋ชจ๋ ๋์์ ์ฒ๋ฆฌ
2. ๋ค์ค ์คํ์ ์ด๋ ํ๊ท
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE_2 = 64(๋ฉํฐ ๋ธ๋ก ์๋๋ฆฌ์ค) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
BLOCKS_PER_GRID = (2, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
THREADS_PER_BLOCK = (WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
์ฌ๋ฌ shuffle_down ์ฐ์ฐ์ ์ฌ์ฉํ์ฌ 3์ ์ด๋ ํ๊ท ์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ์ธ ๊ฐ์ ์ฐ์ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ํ๊ท ์ ๊ณ์ฐํฉ๋๋ค: \[\Large \text{output}[i] = \frac{1}{3}\left(\text{input}[i] + \text{input}[i+1] + \text{input}[i+2]\right)\]
๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ํ ๊ฒฝ๊ณ์์ ์๊ณ ๋ฆฌ์ฆ์ด ์ฐ์ํ๊ฒ ์ ์ํฉ๋๋ค:
- 3์ ์ ์ฒด ์๋์ฐ: \(\text{output}[i] = \frac{1}{3}\sum_{k=0}^{2} \text{input}[i+k]\) - ๋ชจ๋ ์ด์์ด ์ฌ์ฉ ๊ฐ๋ฅํ ๋
- 2์ ์๋์ฐ: \(\text{output}[i] = \frac{1}{2}\sum_{k=0}^{1} \text{input}[i+k]\) - ๋ค์ ์ด์๋ง ์ฌ์ฉ ๊ฐ๋ฅํ ๋
- 1์ ์๋์ฐ: \(\text{output}[i] = \text{input}[i]\) - ์ด์์ด ์ฌ์ฉ ๋ถ๊ฐํ ๋
์ด๋ shuffle_down()์ด ์ํ ๋ฒ์ ๋ด์์ ์๋ ๊ฒฝ๊ณ ์ฒ๋ฆฌ์ ํจ๊ป ํจ์จ์ ์ธ ์คํ
์ค
์ฐ์ฐ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
comptime SIZE_2 = 64
comptime BLOCKS_PER_GRID_2 = (2, 1)
comptime THREADS_PER_BLOCK_2 = (WARP_SIZE, 1)
comptime layout_2 = row_major[SIZE_2]()
comptime LayoutType_2 = type_of(layout_2)
def moving_average_3[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType_2, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType_2, ImmutAnyOrigin],
):
"""
Compute 3-point moving average: output[i] = (input[i] + input[i+1] + input[i+2]) / 3
Uses shuffle_down with offsets 1 and 2 to access neighbors.
Works within warp boundaries across multiple blocks.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
# FILL IN (roughly 10 lines)
ํ
1. ๋ค์ค ์คํ์ ์ ํ ํจํด
์ด ํผ์ฆ์ ์ฌ๋ฌ ์ด์์ ๋์์ ์ ๊ทผํด์ผ ํฉ๋๋ค. ์๋ก ๋ค๋ฅธ ์คํ์ ์ผ๋ก ์ ํ ์ฐ์ฐ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
ํต์ฌ ์ง๋ฌธ:
input[i+1]๊ณผinput[i+2]๋ฅผ ์ ํ ์ฐ์ฐ์ผ๋ก ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ ์ ์์๊น์?- ์ ํ ์คํ์ ๊ณผ ์ด์ ๊ฑฐ๋ฆฌ์ ๊ด๊ณ๋ ๋ฌด์์ผ๊น์?
- ๊ฐ์ ์์ค ๊ฐ์ ๋ํด ์ฌ๋ฌ ๋ฒ ์ ํ์ ์ํํ ์ ์์๊น์?
์๊ฐํ ๊ฐ๋ :
ํ์ฌ ๋ ์ธ์ด ํ์ํ ๊ฐ: current_val, next_val, next_next_val
์
ํ ์คํ์
: 0 (์ง์ ), 1, 2
์๊ฐํด ๋ณด์ธ์: ๋ช ๋ฒ์ ์ ํ ์ฐ์ฐ์ด ํ์ํ๊ณ , ์ด๋ค ์คํ์ ์ ์ฌ์ฉํด์ผ ํ ๊น์?
2. ๋จ๊ณ์ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
๋จ์ํ ์ด์ ์ฐจ๋ถ๊ณผ ๋ฌ๋ฆฌ, ์ด ํผ์ฆ์ 2๊ฐ์ ์ด์์ ์ ๊ทผํด์ผ ํ๋ฏ๋ก ์ฌ๋ฌ ๊ฒฝ๊ณ ์๋๋ฆฌ์ค๊ฐ ์์ต๋๋ค.
๊ณ ๋ คํ ๊ฒฝ๊ณ ์๋๋ฆฌ์ค:
- ์ ์ฒด ์๋์ฐ: ๋ ์ธ์ด ๋ ์ด์ ๋ชจ๋ ์ ๊ทผ ๊ฐ๋ฅ โ 3๊ฐ ๊ฐ ๋ชจ๋ ์ฌ์ฉ
- ๋ถ๋ถ ์๋์ฐ: ๋ ์ธ์ด 1๊ฐ ์ด์๋ง ์ ๊ทผ ๊ฐ๋ฅ โ 2๊ฐ ๊ฐ ์ฌ์ฉ
- ์๋์ฐ ์์: ๋ ์ธ์ด ์ด์์ ์ ๊ทผ ๋ถ๊ฐ โ 1๊ฐ ๊ฐ ์ฌ์ฉ
๋นํ์ ์ฌ๊ณ :
- ์ด๋ค ๋ ์ธ์ด ๊ฐ ์นดํ ๊ณ ๋ฆฌ์ ํด๋นํ ๊น์?
- ๊ฐ์ด ์ ์ ๋ ํ๊ท ์ ๊ฐ์ค์น๋ฅผ ์ด๋ป๊ฒ ์กฐ์ ํด์ผ ํ ๊น์?
- ์ด๋ค ๊ฒฝ๊ณ ์กฐ๊ฑด์ ๊ฒ์ฌํด์ผ ํ ๊น์?
๊ณ ๋ คํ ํจํด:
if (๋ ์ด์ ๋ชจ๋ ์ ๊ทผ ๊ฐ๋ฅ):
# 3์ ํ๊ท
elif (ํ ์ด์๋ง ์ ๊ทผ ๊ฐ๋ฅ):
# 2์ ํ๊ท
else:
# 1์ (ํ๊ท ์์)
3. ๋ฉํฐ ๋ธ๋ก ์กฐ์
์ด ํผ์ฆ์ ์ฌ๋ฌ ๋ธ๋ก์ ์ฌ์ฉํ๋ฉฐ, ๊ฐ ๋ธ๋ก์ด ๋ฐ์ดํฐ์ ๋ค๋ฅธ ์์ญ์ ์ฒ๋ฆฌํฉ๋๋ค.
์ค์ํ ๊ณ ๋ ค์ฌํญ:
- ๊ฐ ๋ธ๋ก์ ๋ ์ธ 0๋ถํฐ WARP_SIZE-1๊น์ง์ ์์ฒด ์ํ๋ฅผ ๊ฐ์ง
- ๊ฒฝ๊ณ ์กฐ๊ฑด์ ๊ฐ ์ํ ๋ด์์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ์ฉ
- ๋ธ๋ก๋ง๋ค ๋ ์ธ ๋ฒํธ๊ฐ ์ด๊ธฐํ๋จ
์๊ฐํด ๋ณผ ์ง๋ฌธ:
- ๊ฒฝ๊ณ ๋ก์ง์ด ๋ธ๋ก 0๊ณผ ๋ธ๋ก 1 ๋ชจ๋์์ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋์?
- ๋ ์ธ ๊ฒฝ๊ณ์ ์ ์ญ ๋ฐฐ์ด ๊ฒฝ๊ณ๋ฅผ ๋ชจ๋ ๊ฒ์ฌํ๊ณ ์๋์?
- ์๋ก ๋ค๋ฅธ ๋ธ๋ก์์
global_i์lane_id()์ ๊ด๊ณ๋ ์ด๋ป๊ฒ ๋ ๊น์?
๋๋ฒ๊น ํ: ๊ฐ ๋ธ๋ก์ ๊ฒฝ๊ณ ๋ ์ธ์์ ์ด๋ค ์ผ์ด ์ผ์ด๋๋์ง ์ถ์ ํ์ฌ ๋ก์ง์ ํ ์คํธํ์ธ์.
์ด๋ ํ๊ท ํ ์คํธ:
pixi run p25 --average
pixi run -e amd p25 --average
uv run poe p25 --average
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE_2: 64
output: HostBuffer([3.3333333, 6.3333335, 10.333333, 15.333333, 21.333334, 28.333334, 36.333332, 45.333332, 55.333332, 66.333336, 78.333336, 91.333336, 105.333336, 120.333336, 136.33333, 153.33333, 171.33333, 190.33333, 210.33333, 231.33333, 253.33333, 276.33334, 300.33334, 325.33334, 351.33334, 378.33334, 406.33334, 435.33334, 465.33334, 496.33334, 512.0, 528.0, 595.3333, 630.3333, 666.3333, 703.3333, 741.3333, 780.3333, 820.3333, 861.3333, 903.3333, 946.3333, 990.3333, 1035.3334, 1081.3334, 1128.3334, 1176.3334, 1225.3334, 1275.3334, 1326.3334, 1378.3334, 1431.3334, 1485.3334, 1540.3334, 1596.3334, 1653.3334, 1711.3334, 1770.3334, 1830.3334, 1891.3334, 1953.3334, 2016.3334, 2048.0, 2080.0])
expected: HostBuffer([3.3333333, 6.3333335, 10.333333, 15.333333, 21.333334, 28.333334, 36.333332, 45.333332, 55.333332, 66.333336, 78.333336, 91.333336, 105.333336, 120.333336, 136.33333, 153.33333, 171.33333, 190.33333, 210.33333, 231.33333, 253.33333, 276.33334, 300.33334, 325.33334, 351.33334, 378.33334, 406.33334, 435.33334, 465.33334, 496.33334, 512.0, 528.0, 595.3333, 630.3333, 666.3333, 703.3333, 741.3333, 780.3333, 820.3333, 861.3333, 903.3333, 946.3333, 990.3333, 1035.3334, 1081.3334, 1128.3334, 1176.3334, 1225.3334, 1275.3334, 1326.3334, 1378.3334, 1431.3334, 1485.3334, 1540.3334, 1596.3334, 1653.3334, 1711.3334, 1770.3334, 1830.3334, 1891.3334, 1953.3334, 2016.3334, 2048.0, 2080.0])
โ
Moving average test passed!
์๋ฃจ์
def moving_average_3[
size: Int
](
output: TileTensor[mut=True, dtype, Layout2Type, MutAnyOrigin],
input: TileTensor[mut=False, dtype, Layout2Type, MutAnyOrigin],
):
"""
Compute 3-point moving average: output[i] = (input[i] + input[i+1] + input[i+2]) / 3
Uses shuffle_down with offsets 1 and 2 to access neighbors.
Works within warp boundaries across multiple blocks.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
# Get current, next, and next+1 values
var current_val = input[global_i]
var next_val = shuffle_down(current_val, 1)
var next_next_val = shuffle_down(current_val, 2)
# Compute 3-point average - valid within warp boundaries
if lane < WARP_SIZE - 2 and global_i < size - 2:
output[global_i] = (current_val + next_val + next_next_val) / 3.0
elif lane < WARP_SIZE - 1 and global_i < size - 1:
# Second-to-last in warp: only current + next available
output[global_i] = (current_val + next_val) / 2.0
else:
# Last thread in warp or boundary cases: only current available
output[global_i] = current_val
์ด ์๋ฃจ์ ์ ๋ณต์กํ ์คํ ์ค ์ฐ์ฐ์ ์ํ ๊ณ ๊ธ ๋ค์ค ์คํ์ ์ ํ์ ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
# ๋จ๊ณ 1: ์ฌ๋ฌ ์
ํ๋ก ํ์ํ ๋ฐ์ดํฐ ๋ชจ๋ ํ๋ณด
current_val = input[global_i] # ์ง์ ์ ๊ทผ
next_val = shuffle_down(current_val, 1) # ์ค๋ฅธ์ชฝ ์ด์
next_next_val = shuffle_down(current_val, 2) # ์ค๋ฅธ์ชฝ+1 ์ด์
# ๋จ๊ณ 2: ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐ์ดํฐ์ ๋ฐ๋ฅธ ์ ์ํ ๊ณ์ฐ
if lane < WARP_SIZE - 2 and global_i < size - 2:
# 3์ ์คํ
์ค ์ ์ฒด ์ฌ์ฉ ๊ฐ๋ฅ
output[global_i] = (current_val + next_val + next_next_val) / 3.0
elif lane < WARP_SIZE - 1 and global_i < size - 1:
# 2์ ์คํ
์ค๋ง ์ฌ์ฉ ๊ฐ๋ฅ (์ํ ๊ฒฝ๊ณ ๊ทผ์ฒ)
output[global_i] = (current_val + next_val) / 2.0
else:
# ์คํ
์ค ์ฌ์ฉ ๋ถ๊ฐ (์ํ ๊ฒฝ๊ณ)
output[global_i] = current_val
๋ค์ค ์คํ์
์คํ ์ถ์ (WARP_SIZE = 32):
์ด๊ธฐ ์ํ (๋ธ๋ก 0, ์์ 0-31):
๋ ์ธ 0: current_val = input[0] = 1
๋ ์ธ 1: current_val = input[1] = 2
๋ ์ธ 2: current_val = input[2] = 4
...
๋ ์ธ 31: current_val = input[31] = X
์ฒซ ๋ฒ์งธ ์
ํ: shuffle_down(current_val, 1)
๋ ์ธ 0: next_val = input[1] = 2
๋ ์ธ 1: next_val = input[2] = 4
๋ ์ธ 2: next_val = input[3] = 7
...
๋ ์ธ 30: next_val = input[31] = X
๋ ์ธ 31: next_val = ๋ฏธ์ ์
๋ ๋ฒ์งธ ์
ํ: shuffle_down(current_val, 2)
๋ ์ธ 0: next_next_val = input[2] = 4
๋ ์ธ 1: next_next_val = input[3] = 7
๋ ์ธ 2: next_next_val = input[4] = 11
...
๋ ์ธ 29: next_next_val = input[31] = X
๋ ์ธ 30: next_next_val = ๋ฏธ์ ์
๋ ์ธ 31: next_next_val = ๋ฏธ์ ์
๊ณ์ฐ ๋จ๊ณ:
๋ ์ธ 0-29: 3์ ์ ์ฒด ํ๊ท โ (current + next + next_next) / 3
๋ ์ธ 30: 2์ ํ๊ท โ (current + next) / 2
๋ ์ธ 31: 1์ ํ๊ท โ current (๊ทธ๋๋ก ์ ๋ฌ)
์ํ์ ๊ธฐ๋ฐ: ๊ฐ๋ณ ํญ ์ด์ฐ ํฉ์ฑ๊ณฑ์ ๊ตฌํํฉ๋๋ค: \[\Large h[i] = \sum_{k=0}^{K(i)-1} w_k^{(i)} \cdot f[i+k]\]
์์น์ ๋ฐ๋ผ ์ปค๋์ด ์ ์ํฉ๋๋ค:
- ๋ด๋ถ ์ : \(K(i) = 3\), \(\mathbf{w}^{(i)} = [\frac{1}{3}, \frac{1}{3}, \frac{1}{3}]\)
- ๊ฒฝ๊ณ ๊ทผ์ฒ: \(K(i) = 2\), \(\mathbf{w}^{(i)} = [\frac{1}{2}, \frac{1}{2}]\)
- ๊ฒฝ๊ณ: \(K(i) = 1\), \(\mathbf{w}^{(i)} = [1]\)
๋ฉํฐ ๋ธ๋ก ์กฐ์ : SIZE_2 = 64์ 2๊ฐ ๋ธ๋ก:
๋ธ๋ก 0 (์ ์ญ ์ธ๋ฑ์ค 0-31):
์ ์ญ ์ธ๋ฑ์ค 29, 30, 31์ ๋ ์ธ ๊ฒฝ๊ณ ์ ์ฉ
๋ธ๋ก 1 (์ ์ญ ์ธ๋ฑ์ค 32-63):
์ ์ญ ์ธ๋ฑ์ค 61, 62, 63์ ๋ ์ธ ๊ฒฝ๊ณ ์ ์ฉ
๋ ์ธ ๋ฒํธ ์ด๊ธฐํ: global_i=32 โ lane=0, global_i=63 โ lane=31
์ฑ๋ฅ ์ต์ ํ:
- ๋ณ๋ ฌ ๋ฐ์ดํฐ ํ๋ณด: ๋ ์ ํ ์ฐ์ฐ์ด ๋์์ ์คํ
- ์กฐ๊ฑด๋ถ ๋ถ๊ธฐ: GPU๊ฐ ํ๋ ๋์ผ์ด์ ์ ํตํด ๋ถ๊ธฐ ๋ ์ธ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ: ์์ฐจ์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด GPU์ ์ต์
- ๋ ์ง์คํฐ ์ฌ์ฌ์ฉ: ๋ชจ๋ ์ค๊ฐ ๊ฐ์ด ๋ ์ง์คํฐ์ ์ ์ง
์ ํธ ์ฒ๋ฆฌ ๊ด์ : ์ด๊ฒ์ ์ํ์ค ์๋ต \(h[n] = \frac{1}{3}[\delta[n] + \delta[n-1] + \delta[n-2]]\)๋ฅผ ๊ฐ์ง ์ธ๊ณผ FIR ํํฐ๋ก, ์ฐจ๋จ ์ฃผํ์ \(f_c \approx 0.25f_s\)์์ ์ค๋ฌด๋ฉ์ ์ ๊ณตํฉ๋๋ค.
์์ฝ
์ด ์น์ ์ ํต์ฌ ํจํด์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
current_val = input[global_i]
neighbor_val = shuffle_down(current_val, offset)
if lane < WARP_SIZE - offset:
result = compute(current_val, neighbor_val)
ํต์ฌ ์ฅ์ :
- ํ๋์จ์ด ํจ์จ์ฑ: ๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์
- ๊ฒฝ๊ณ ์์ ์ฑ: ์๋ ์ํ ๋ฒ์ ์ฒ๋ฆฌ
- SIMT ์ต์ ํ: ๋จ์ผ ๋ช ๋ น, ๋ชจ๋ ๋ ์ธ ๋ณ๋ ฌ ์ฒ๋ฆฌ
ํ์ฉ ๋ถ์ผ: ์ ํ ์ฐจ๋ถ, ์คํ ์ค ์ฐ์ฐ, ์ด๋ ํ๊ท , ํฉ์ฑ๊ณฑ.
warp.broadcast() ์ผ๋๋ค ํต์
์ํ ๋ ๋ฒจ ์กฐ์ ์์๋ broadcast()๋ฅผ ์ฌ์ฉํ์ฌ ํ๋์ ๋ ์ธ์์ ์ํ ๋ด ๋ค๋ฅธ ๋ชจ๋
๋ ์ธ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์์ต๋๋ค. ์ด ๊ฐ๋ ฅํ ๊ธฐ๋ณธ ์์๋ฅผ ํตํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋
๋ช
์์ ๋๊ธฐํ ์์ด ๋ธ๋ก ๋ ๋ฒจ ๊ณ์ฐ, ์กฐ๊ฑด๋ถ ๋ก์ง ์กฐ์ , ์ผ๋๋ค ํต์ ํจํด์
ํจ์จ์ ์ผ๋ก ์ํํ ์ ์์ต๋๋ค.
ํต์ฌ ํต์ฐฐ: broadcast() ์ฐ์ฐ์ SIMT ์คํ์ ํ์ฉํ์ฌ ํ๋์ ๋ ์ธ(๋ณดํต ๋ ์ธ 0)์ด ๊ณ์ฐํ ๊ฐ์ ๊ฐ์ ์ํ์ ๋ชจ๋ ๋ ์ธ์ ์ ๋ฌํ๋ฉฐ, ํจ์จ์ ์ธ ์กฐ์ ํจํด๊ณผ ์งํฉ์ ์์ฌ๊ฒฐ์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ์ด๋? ๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ์ ํ๋์ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ณ์ฐํ๊ณ ๊ทธ๋ฃน ๋ด ๋ค๋ฅธ ๋ชจ๋ ์ค๋ ๋์ ๊ณต์ ํ๋ ํต์ ํจํด์ ๋๋ค. ๋ธ๋ก ๋ ๋ฒจ ํต๊ณ ๊ณ์ฐ, ์งํฉ์ ์์ฌ๊ฒฐ์ , ์ํ ๋ด ๋ชจ๋ ์ค๋ ๋์ ์ค์ ํ๋ผ๋ฏธํฐ ์ ๋ฌ ๋ฑ์ ์กฐ์ ์์ ์ ํ์์ ์ ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
broadcast()๋ฅผ ํ์ฉํ ์ํ ๋ ๋ฒจ ๋ธ๋ก๋์บ์คํธ- ์ผ๋๋ค ํต์ ํจํด
- ์งํฉ ๊ณ์ฐ ์ ๋ต
- ๋ ์ธ ๊ฐ ์กฐ๊ฑด๋ถ ์กฐ์
- ๋ธ๋ก๋์บ์คํธ-shuffle ๊ฒฐํฉ ์ฐ์ฐ
broadcast() ์ฐ์ฐ์ ํ๋์ ๋ ์ธ(๊ธฐ๋ณธ์ ์ผ๋ก ๋ ์ธ 0)์ด ์์ ์ ๊ฐ์ ๋ค๋ฅธ ๋ชจ๋
๋ ์ธ๊ณผ ๊ณต์ ํ ์ ์๊ฒ ํฉ๋๋ค: \[\Large \text{broadcast}(\text{value}) =
\text{value_from_lane_0_to_all_lanes}\]
์ด๋ฅผ ํตํด ๋ณต์กํ ์กฐ์ ํจํด์ด ๊ฐ๋จํ ์ํ ๋ ๋ฒจ ์ฐ์ฐ์ผ๋ก ๋ณํ๋์ด, ๋ช ์์ ๋๊ธฐํ ์์ด ํจ์จ์ ์ธ ์งํฉ ๊ณ์ฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
๋ธ๋ก๋์บ์คํธ ๊ฐ๋
๊ธฐ์กด ์กฐ์ ๋ฐฉ์์ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ด ํ์ํฉ๋๋ค:
# ๊ธฐ์กด ๋ฐฉ์ - ๋ณต์กํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์
shared_memory[lane] = local_computation()
sync_threads() # ๋น์ฉ์ด ํฐ ๋๊ธฐํ
if lane == 0:
result = compute_from_shared_memory()
sync_threads() # ๋ ๋ค๋ฅธ ๋น์ฉ์ด ํฐ ๋๊ธฐํ
final_result = shared_memory[0] # ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฝ์
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ด ํ์
- ๋๊ธฐํ: ๋น์ฉ์ด ํฐ ๋ฐฐ๋ฆฌ์ด ์ฐ์ฐ์ด ์ฌ๋ฌ ๋ฒ ํ์
- ๋ณต์กํ ๋ก์ง: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ธ๋ฑ์ค์ ์ ๊ทผ ํจํด ๊ด๋ฆฌ
- ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ์ฑ: ๊ฒฝ์ ์ํ๊ฐ ์ฝ๊ฒ ๋ฐ์
broadcast()๋ฅผ ์ฌ์ฉํ๋ฉด ์กฐ์ ์ด ๊ฐ๊ฒฐํด์ง๋๋ค:
# ์ํ ๋ธ๋ก๋์บ์คํธ ๋ฐฉ์ - ๊ฐ๋จํ๊ณ ์์
collective_value = 0
if lane == 0:
collective_value = compute_block_statistic()
collective_value = broadcast(collective_value) # ๋ชจ๋ ๋ ์ธ๊ณผ ๊ณต์
result = use_collective_value(collective_value)
๋ธ๋ก๋์บ์คํธ์ ์ฅ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ถํ์
- ์๋ ๋๊ธฐํ: SIMT ์คํ์ด ์ ํ์ฑ์ ๋ณด์ฅ
- ๊ฐ๋จํ ํจํด: ํ๋์ ๋ ์ธ์ด ๊ณ์ฐํ๊ณ ๋ชจ๋ ๋ ์ธ์ด ์์
- ์กฐํฉ ๊ฐ๋ฅ: ๋ค๋ฅธ ์ํ ์ฐ์ฐ๊ณผ ์ฝ๊ฒ ๊ฒฐํฉ
1. ๊ธฐ๋ณธ ๋ธ๋ก๋์บ์คํธ
๋ ์ธ 0์ด ๋ธ๋ก ๋ ๋ฒจ ํต๊ณ๋ฅผ ๊ณ์ฐํ๊ณ ๋ชจ๋ ๋ ์ธ๊ณผ ๊ณต์ ํ๋ ๊ธฐ๋ณธ ๋ธ๋ก๋์บ์คํธ ํจํด์ ๊ตฌํํฉ๋๋ค.
์๊ตฌ์ฌํญ:
- ๋ ์ธ 0์ด ํ์ฌ ๋ธ๋ก์ ์ฒ์ 4๊ฐ ์์์ ํฉ์ ๊ณ์ฐํด์ผ ํฉ๋๋ค
- ์ด ๊ณ์ฐ๋ ๊ฐ์
broadcast()๋ฅผ ์ฌ์ฉํ์ฌ ์ํ์ ๋ค๋ฅธ ๋ชจ๋ ๋ ์ธ๊ณผ ๊ณต์ ํด์ผ ํฉ๋๋ค - ๊ฐ ๋ ์ธ์ ์ด ๊ณต์ ๋ ๊ฐ์ ์์ ์ ์ ๋ ฅ ์์์ ๋ํด์ผ ํฉ๋๋ค
ํ
์คํธ ๋ฐ์ดํฐ: ์
๋ ฅ [1, 2, 3, 4, 5, 6, 7, 8, ...]์ ์ถ๋ ฅ
[11, 12, 13, 14, 15, 16, 17, 18, ...]์ ์์ฑํด์ผ ํฉ๋๋ค
๊ณผ์ : ํ๋์ ๋ ์ธ๋ง ๋ธ๋ก ๋ ๋ฒจ ๊ณ์ฐ์ ์ํํ๋, ๋ชจ๋ ๋ ์ธ์ด ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์์ ์ ๊ฐ๋ณ ์ฐ์ฐ์ ์ฌ์ฉํ๋ ค๋ฉด ์ด๋ป๊ฒ ์กฐ์ ํด์ผ ํ ๊น์?
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ ์ด์์:
row_major[SIZE]()(1D row-major)
์์ฑํ ์ฝ๋
def basic_broadcast[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Basic broadcast: Lane 0 computes a block-local value, broadcasts it to all lanes.
Each lane then uses this broadcast value in its own computation.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
var broadcast_value: output.ElementType = 0.0
# FILL IN (roughly 10 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p25/p25.mojo
ํ
1. ๋ธ๋ก๋์บ์คํธ ๋์ ๋ฐฉ์ ์ดํดํ๊ธฐ
broadcast(value) ์ฐ์ฐ์ ๋ ์ธ 0์ ๊ฐ์ ๊ฐ์ ธ์ ์ํ์ ๋ชจ๋ ๋ ์ธ์ ์ ๋ฌํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: ๋ธ๋ก๋์บ์คํธ์์๋ ๋ ์ธ 0์ ๊ฐ๋ง ์๋ฏธ๊ฐ ์์ต๋๋ค. ๋ค๋ฅธ ๋ ์ธ์ ๊ฐ์ ๋ฌด์๋์ง๋ง, ๋ชจ๋ ๋ ์ธ์ด ๋ ์ธ 0์ ๊ฐ์ ์์ ํฉ๋๋ค.
์๊ฐํ:
๋ธ๋ก๋์บ์คํธ ์ : ๋ ์ธ 0์ valโ, ๋ ์ธ 1์ valโ, ๋ ์ธ 2๋ valโ, ...
๋ธ๋ก๋์บ์คํธ ํ: ๋ ์ธ 0์ valโ, ๋ ์ธ 1์ valโ, ๋ ์ธ 2๋ valโ, ...
์๊ฐํด ๋ณด์ธ์: ๋ ์ธ 0๋ง ๋ธ๋ก๋์บ์คํธํ๋ ค๋ ๊ฐ์ ๊ณ์ฐํ๋๋ก ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
2. ๋ ์ธ๋ณ ๊ณ์ฐ
๋ ์ธ 0์ด ํน๋ณํ ๊ณ์ฐ์ ์ํํ๊ณ ๋ค๋ฅธ ๋ ์ธ์ ๋๊ธฐํ๋๋ก ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํฉ๋๋ค.
๊ณ ๋ คํ ํจํด:
var shared_value = ์ด๊ธฐ๊ฐ
if lane == 0:
# ๋ ์ธ 0๋ง ๊ณ์ฐ
shared_value = ํน๋ณํ_๊ณ์ฐ()
# ๋ชจ๋ ๋ ์ธ์ด ๋ธ๋ก๋์บ์คํธ์ ์ฐธ์ฌ
shared_value = broadcast(shared_value)
ํต์ฌ ์ง๋ฌธ:
- ๋ธ๋ก๋์บ์คํธ ์ ์ ๋ค๋ฅธ ๋ ์ธ์ ๊ฐ์ ์ด๋ค ์ํ์ฌ์ผ ํ ๊น์?
- ๋ ์ธ 0์ด ๋ธ๋ก๋์บ์คํธํ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ๋๋ก ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
3. ์งํฉ์ ํ์ฉ
๋ธ๋ก๋์บ์คํธ ํ ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ ๊ฐ์ ๊ฐ๊ฒ ๋๋ฉฐ, ์ด๋ฅผ ๊ฐ์์ ๊ฐ๋ณ ๊ณ์ฐ์ ํ์ฉํ ์ ์์ต๋๋ค.
์๊ฐํด ๋ณด์ธ์: ๊ฐ ๋ ์ธ์ด ๋ธ๋ก๋์บ์คํธ ๊ฐ๊ณผ ์์ ์ ๋ก์ปฌ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฒฐํฉํ ๊น์?
๊ธฐ๋ณธ ๋ธ๋ก๋์บ์คํธ ํ ์คํธ:
pixi run p25 --broadcast-basic
pixi run -e amd p25 --broadcast-basic
pixi run -e apple p25 --broadcast-basic
uv run poe p25 --broadcast-basic
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: HostBuffer([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0])
expected: HostBuffer([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0])
โ
Basic broadcast test passed!
์๋ฃจ์
def basic_broadcast[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
):
"""
Basic broadcast: Lane 0 computes a block-local value, broadcasts it to all lanes.
Each lane then uses this broadcast value in its own computation.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
# Step 1: Lane 0 computes special value (sum of first 4 elements in this block)
var broadcast_value: output.ElementType = 0.0
if lane == 0:
var block_start = block_idx.x * block_dim.x
var sum: output.ElementType = 0.0
for i in range(4):
if block_start + i < size:
sum += input[block_start + i]
broadcast_value = sum
# Step 2: Broadcast lane 0's value to all lanes in this warp
broadcast_value = broadcast(broadcast_value)
# Step 3: All lanes use broadcast value in their computation
output[global_i] = broadcast_value + input[global_i]
์ด ์๋ฃจ์ ์ ์ํ ๋ ๋ฒจ ์กฐ์ ์ ์ํ ๊ธฐ๋ณธ ๋ธ๋ก๋์บ์คํธ ํจํด์ ๋ณด์ฌ์ค๋๋ค.
์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
# ๋จ๊ณ 1: ๋ ์ธ 0์ด ํน๋ณํ ๊ฐ์ ๊ณ์ฐ
var broadcast_value: output.element_type = 0.0
if lane == 0:
# ๋ ์ธ 0๋ง ์ด ๊ณ์ฐ์ ์ํ
block_start = block_idx.x * block_dim.x
var sum: output.element_type = 0.0
for i in range(4):
if block_start + i < size:
sum += input[block_start + i]
broadcast_value = sum
# ๋จ๊ณ 2: ๋ ์ธ 0์ ๊ฐ์ ๋ชจ๋ ๋ ์ธ๊ณผ ๊ณต์
broadcast_value = broadcast(broadcast_value)
# ๋จ๊ณ 3: ๋ชจ๋ ๋ ์ธ์ด ๋ธ๋ก๋์บ์คํธ ๊ฐ์ ํ์ฉ
output[global_i] = broadcast_value + input[global_i]
SIMT ์คํ ์ถ์ :
์ฌ์ดํด 1: ๋ ์ธ๋ณ ๊ณ์ฐ
๋ ์ธ 0: input[0] + input[1] + input[2] + input[3] = 1+2+3+4 = 10์ ๊ณ์ฐ
๋ ์ธ 1: broadcast_value๋ 0.0 ์ ์ง (๋ ์ธ 0์ด ์๋)
๋ ์ธ 2: broadcast_value๋ 0.0 ์ ์ง (๋ ์ธ 0์ด ์๋)
...
๋ ์ธ 31: broadcast_value๋ 0.0 ์ ์ง (๋ ์ธ 0์ด ์๋)
์ฌ์ดํด 2: broadcast(broadcast_value) ์คํ
๋ ์ธ 0: ์์ ์ ๊ฐ ์ ์ง โ broadcast_value = 10.0
๋ ์ธ 1: ๋ ์ธ 0์ ๊ฐ ์์ โ broadcast_value = 10.0
๋ ์ธ 2: ๋ ์ธ 0์ ๊ฐ ์์ โ broadcast_value = 10.0
...
๋ ์ธ 31: ๋ ์ธ 0์ ๊ฐ ์์ โ broadcast_value = 10.0
์ฌ์ดํด 3: ๋ธ๋ก๋์บ์คํธ ๊ฐ์ ํ์ฉํ ๊ฐ๋ณ ๊ณ์ฐ
๋ ์ธ 0: output[0] = 10.0 + input[0] = 10.0 + 1.0 = 11.0
๋ ์ธ 1: output[1] = 10.0 + input[1] = 10.0 + 2.0 = 12.0
๋ ์ธ 2: output[2] = 10.0 + input[2] = 10.0 + 3.0 = 13.0
...
๋ ์ธ 31: output[31] = 10.0 + input[31] = 10.0 + 32.0 = 42.0
๋ธ๋ก๋์บ์คํธ๊ฐ ์ฐ์ํ ์ด์ :
- ์กฐ์ ํจ์จ์ฑ: ๋จ์ผ ์ฐ์ฐ์ผ๋ก ๋ชจ๋ ๋ ์ธ์ ์กฐ์
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ถํ์
- ๋๊ธฐํ ๋ถํ์: SIMT ์คํ์ด ์๋์ผ๋ก ์กฐ์ ์ ์ฒ๋ฆฌ
- ํ์ฅ ๊ฐ๋ฅํ ํจํด: ์ํ ํฌ๊ธฐ์ ๋ฌด๊ดํ๊ฒ ๋์ผํ๊ฒ ๋์
์ฑ๋ฅ ํน์ฑ:
- ์ง์ฐ ์๊ฐ: ๋ธ๋ก๋์บ์คํธ ์ฐ์ฐ 1 ์ฌ์ดํด
- ๋์ญํญ: 0 ๋ฐ์ดํธ (๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์ )
- ์กฐ์ : 32๊ฐ ๋ ์ธ ๋ชจ๋ ์๋ ๋๊ธฐํ
2. ์กฐ๊ฑด๋ถ ๋ธ๋ก๋์บ์คํธ
๋ ์ธ 0์ด ๋ธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๊ณ ๋ชจ๋ ๋ ์ธ์ ์ํฅ์ ๋ฏธ์น๋ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์กฐ๊ฑด๋ถ ์กฐ์ ์ ๊ตฌํํฉ๋๋ค.
์๊ตฌ์ฌํญ:
- ๋ ์ธ 0์ด ํ์ฌ ๋ธ๋ก์ ์ฒ์ 8๊ฐ ์์๋ฅผ ๋ถ์ํ๊ณ ์ต๋๊ฐ์ ์ฐพ์์ผ ํฉ๋๋ค
- ์ด ์ต๋๊ฐ์
broadcast()๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ๋ชจ๋ ๋ ์ธ์ ์ ๋ฌํด์ผ ํฉ๋๋ค - ๊ฐ ๋ ์ธ์ ์กฐ๊ฑด๋ถ ๋ก์ง์ ์ ์ฉํฉ๋๋ค: ์์ ์ ์์๊ฐ ์ต๋๊ฐ์ ์ ๋ฐ๋ณด๋ค ํฌ๋ฉด 2๋ฐฐ๋ก, ๊ทธ๋ ์ง ์์ผ๋ฉด ์ ๋ฐ์ผ๋ก ๋ง๋ญ๋๋ค
ํ
์คํธ ๋ฐ์ดํฐ: ์
๋ ฅ [3, 1, 7, 2, 9, 4, 6, 8, ...] (๋ฐ๋ณต ํจํด)์ ์ถ๋ ฅ
[1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, ...]์ ์์ฑํด์ผ ํฉ๋๋ค
๊ณผ์ : ๋ธ๋ก ๋ ๋ฒจ ๋ถ์๊ณผ ์์๋ณ ์กฐ๊ฑด๋ถ ๋ณํ์ ๋ชจ๋ ๋ ์ธ์ ๊ฑธ์ณ ์ด๋ป๊ฒ ์กฐ์ ํ ๊น์?
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
def conditional_broadcast[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Conditional broadcast: Lane 0 makes a decision based on block-local data, broadcasts it to all lanes.
All lanes apply different logic based on the broadcast decision.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
var decision_value: output.ElementType = 0.0
# FILL IN (roughly 10 lines)
var current_input = input[global_i]
var threshold = decision_value / 2.0
if current_input >= threshold:
output[global_i] = current_input * 2.0 # Double if >= threshold
else:
output[global_i] = current_input / 2.0 # Halve if < threshold
ํ
1. ๋ถ์๊ณผ ์์ฌ๊ฒฐ์
๋ ์ธ 0์ด ์ฌ๋ฌ ๋ฐ์ดํฐ ํฌ์ธํธ๋ฅผ ๋ถ์ํ๊ณ ๋ค๋ฅธ ๋ชจ๋ ๋ ์ธ์ ๋์์ ์๋ดํ ๊ฒฐ์ ์ ๋ด๋ ค์ผ ํฉ๋๋ค.
ํต์ฌ ์ง๋ฌธ:
- ๋ ์ธ 0์ด ์ฌ๋ฌ ์์๋ฅผ ํจ์จ์ ์ผ๋ก ๋ถ์ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
- ๋ ์ธ์ ๋์์ ์กฐ์ ํ๊ธฐ ์ํด ์ด๋ค ์ข ๋ฅ์ ๊ฒฐ์ ์ ๋ธ๋ก๋์บ์คํธํด์ผ ํ ๊น์?
- ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ ๋ ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น์?
๊ณ ๋ คํ ํจํด:
var decision = ๊ธฐ๋ณธ๊ฐ
if lane == 0:
# ๋ธ๋ก ๋ก์ปฌ ๋ฐ์ดํฐ ๋ถ์
decision = ๋ถ์_ํ_๊ฒฐ์ ()
decision = broadcast(decision)
2. ์กฐ๊ฑด๋ถ ์คํ ์กฐ์
๋ธ๋ก๋์บ์คํธ๋ ๊ฒฐ์ ์ ์์ ํ ํ, ๋ชจ๋ ๋ ์ธ์ด ๊ทธ ๊ฒฐ์ ์ ๊ธฐ๋ฐํ์ฌ ์๋ก ๋ค๋ฅธ ๋ก์ง์ ์ ์ฉํด์ผ ํฉ๋๋ค.
์๊ฐํด ๋ณด์ธ์:
- ๋ ์ธ์ด ๋ธ๋ก๋์บ์คํธ ๊ฐ์ ์ฌ์ฉํ์ฌ ๋ก์ปฌ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๋ฐฉ๋ฒ์?
- ๊ฐ ์กฐ๊ฑด๋ถ ๋ถ๊ธฐ์์ ์ด๋ค ์ฐ์ฐ์ ์ ์ฉํด์ผ ํ ๊น์?
- ๋ชจ๋ ๋ ์ธ์์ ์ผ๊ด๋ ๋์์ ๋ณด์ฅํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
์กฐ๊ฑด๋ถ ํจํด:
if (๋ก์ปฌ_๋ฐ์ดํฐ๊ฐ broadcast_๊ธฐ์ค์ ์ถฉ์กฑ):
# ํ๋์ ๋ณํ ์ ์ฉ
else:
# ๋ค๋ฅธ ๋ณํ ์ ์ฉ
3. ๋ฐ์ดํฐ ๋ถ์ ์ ๋ต
๋ ์ธ 0์ด ์ฌ๋ฌ ๋ฐ์ดํฐ ํฌ์ธํธ๋ฅผ ํจ์จ์ ์ผ๋ก ๋ถ์ํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํ์ธ์.
๊ณ ๋ คํ ์ ๊ทผ๋ฒ:
- ์ต๋๊ฐ/์ต์๊ฐ ์ฐพ๊ธฐ
- ํ๊ท ์ด๋ ํฉ๊ณ ๊ณ์ฐ
- ํจํด์ด๋ ์๊ณ๊ฐ ๊ฐ์ง
- ๋ฐ์ดํฐ ํน์ฑ์ ๊ธฐ๋ฐํ ์ด์ง ๊ฒฐ์
์กฐ๊ฑด๋ถ ๋ธ๋ก๋์บ์คํธ ํ ์คํธ:
pixi run p25 --broadcast-conditional
pixi run -e amd p25 --broadcast-conditional
uv run poe p25 --broadcast-conditional
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: HostBuffer([1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0])
expected: HostBuffer([1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0, 1.5, 0.5, 14.0, 1.0, 18.0, 2.0, 12.0, 16.0])
โ
Conditional broadcast test passed!
์๋ฃจ์
def conditional_broadcast[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
):
"""
Conditional broadcast: Lane 0 makes a decision based on block-local data, broadcasts it to all lanes.
All lanes apply different logic based on the broadcast decision.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
# Step 1: Lane 0 analyzes block-local data and makes decision (find max of first 8 in block)
var decision_value: output.ElementType = 0.0
if lane == 0:
var block_start = block_idx.x * block_dim.x
decision_value = input[block_start] if block_start < size else 0.0
for i in range(1, min(8, min(WARP_SIZE, size - block_start))):
if block_start + i < size:
var current_val = input[block_start + i]
if current_val > decision_value:
decision_value = current_val
# Step 2: Broadcast decision to all lanes in this warp
decision_value = broadcast(decision_value)
# Step 3: All lanes apply conditional logic based on broadcast decision
var current_input = input[global_i]
var threshold = decision_value / 2.0
if current_input >= threshold:
output[global_i] = current_input * 2.0 # Double if >= threshold
else:
output[global_i] = current_input / 2.0 # Halve if < threshold
์ด ์๋ฃจ์ ์ ๋ ์ธ ๊ฐ ์กฐ๊ฑด๋ถ ์กฐ์ ์ ์ํ ๊ณ ๊ธ ๋ธ๋ก๋์บ์คํธ ํจํด์ ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
# ๋จ๊ณ 1: ๋ ์ธ 0์ด ๋ธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๊ณ ๊ฒฐ์ ์ ๋ด๋ฆผ
var decision_value: output.element_type = 0.0
if lane == 0:
# ๋ธ๋ก์ ์ฒ์ 8๊ฐ ์์ ์ค ์ต๋๊ฐ ์ฐพ๊ธฐ
block_start = block_idx.x * block_dim.x
decision_value = input[block_start] if block_start < size else 0.0
for i in range(1, min(8, min(WARP_SIZE, size - block_start))):
if block_start + i < size:
current_val = input[block_start + i]
if current_val > decision_value:
decision_value = current_val
# ๋จ๊ณ 2: ๊ฒฐ์ ์ broadcastํ์ฌ ๋ชจ๋ ๋ ์ธ์ ์กฐ์
decision_value = broadcast(decision_value)
# ๋จ๊ณ 3: ๋ชจ๋ ๋ ์ธ์ด ๋ธ๋ก๋์บ์คํธ์ ๊ธฐ๋ฐํ ์กฐ๊ฑด๋ถ ๋ก์ง์ ์ ์ฉ
current_input = input[global_i]
threshold = decision_value / 2.0
if current_input >= threshold:
output[global_i] = current_input * 2.0 # ์๊ณ๊ฐ ์ด์์ด๋ฉด 2๋ฐฐ
else:
output[global_i] = current_input / 2.0 # ์๊ณ๊ฐ ๋ฏธ๋ง์ด๋ฉด ์ ๋ฐ
์์ฌ๊ฒฐ์ ์คํ ์ถ์ :
์
๋ ฅ ๋ฐ์ดํฐ: [3.0, 1.0, 7.0, 2.0, 9.0, 4.0, 6.0, 8.0, ...]
๋จ๊ณ 1: ๋ ์ธ 0์ด ์ฒ์ 8๊ฐ ์์์ ์ต๋๊ฐ์ ์ฐพ์
๋ ์ธ 0 ๋ถ์:
input[0] = 3.0์ผ๋ก ์์
input[1] = 1.0๊ณผ ๋น๊ต โ 3.0 ์ ์ง
input[2] = 7.0๊ณผ ๋น๊ต โ 7.0์ผ๋ก ๊ฐฑ์
input[3] = 2.0๊ณผ ๋น๊ต โ 7.0 ์ ์ง
input[4] = 9.0๊ณผ ๋น๊ต โ 9.0์ผ๋ก ๊ฐฑ์
input[5] = 4.0๊ณผ ๋น๊ต โ 9.0 ์ ์ง
input[6] = 6.0๊ณผ ๋น๊ต โ 9.0 ์ ์ง
input[7] = 8.0๊ณผ ๋น๊ต โ 9.0 ์ ์ง
์ต์ข
decision_value = 9.0
๋จ๊ณ 2: decision_value = 9.0์ ๋ชจ๋ ๋ ์ธ์ broadcast
๋ชจ๋ ๋ ์ธ: decision_value = 9.0, threshold = 4.5
๋จ๊ณ 3: ๋ ์ธ๋ณ ์กฐ๊ฑด๋ถ ์คํ
๋ ์ธ 0: input[0] = 3.0 < 4.5 โ output[0] = 3.0 / 2.0 = 1.5
๋ ์ธ 1: input[1] = 1.0 < 4.5 โ output[1] = 1.0 / 2.0 = 0.5
๋ ์ธ 2: input[2] = 7.0 โฅ 4.5 โ output[2] = 7.0 * 2.0 = 14.0
๋ ์ธ 3: input[3] = 2.0 < 4.5 โ output[3] = 2.0 / 2.0 = 1.0
๋ ์ธ 4: input[4] = 9.0 โฅ 4.5 โ output[4] = 9.0 * 2.0 = 18.0
๋ ์ธ 5: input[5] = 4.0 < 4.5 โ output[5] = 4.0 / 2.0 = 2.0
๋ ์ธ 6: input[6] = 6.0 โฅ 4.5 โ output[6] = 6.0 * 2.0 = 12.0
๋ ์ธ 7: input[7] = 8.0 โฅ 4.5 โ output[7] = 8.0 * 2.0 = 16.0
...๋๋จธ์ง ๋ ์ธ์ ํจํด ๋ฐ๋ณต
์ํ์ ๊ธฐ๋ฐ: ์๊ณ๊ฐ ๊ธฐ๋ฐ ๋ณํ์ ๊ตฌํํฉ๋๋ค: \[\Large f(x) = \begin{cases} 2x & \text{if } x \geq \tau \\ \frac{x}{2} & \text{if } x < \tau \end{cases}\]
์ฌ๊ธฐ์ \(\tau = \frac{\max(\text{block_data})}{2}\)๋ ๋ธ๋ก๋์บ์คํธ๋ ์๊ณ๊ฐ์ ๋๋ค.
์กฐ์ ํจํด์ ์ฅ์ :
- ์ค์ํ๋ ๋ถ์: ํ๋์ ๋ ์ธ์ด ๋ถ์ํ๊ณ ๋ชจ๋ ๋ ์ธ์ด ํํ์ ๋ฐ์
- ์ผ๊ด๋ ๊ฒฐ์ : ๋ชจ๋ ๋ ์ธ์ด ๊ฐ์ ์๊ณ๊ฐ์ ์ฌ์ฉ
- ์ ์ํ ๋์: ์๊ณ๊ฐ์ด ๋ธ๋ก ๋ก์ปฌ ๋ฐ์ดํฐ ํน์ฑ์ ๋ฐ๋ผ ์ ์
- ํจ์จ์ ์กฐ์ : ๋จ์ผ ๋ธ๋ก๋์บ์คํธ๋ก ๋ณต์กํ ์กฐ๊ฑด๋ถ ๋ก์ง์ ์กฐ์
ํ์ฉ ๋ถ์ผ:
- ์ ์ํ ์๊ณ ๋ฆฌ์ฆ: ๋ก์ปฌ ๋ฐ์ดํฐ ํน์ฑ์ ๋ฐ๋ผ ํ๋ผ๋ฏธํฐ ์กฐ์
- ํ์ง ๊ด๋ฆฌ: ๋ฐ์ดํฐ ํ์ง ์งํ์ ๋ฐ๋ผ ๋ค๋ฅธ ์ฒ๋ฆฌ ์ ์ฉ
- ๋ถํ ๋ถ์ฐ: ๋ธ๋ก ๋ก์ปฌ ๋ณต์ก๋ ๋ถ์์ ๊ธฐ๋ฐํ ์์ ๋ถ๋ฐฐ
3. ๋ธ๋ก๋์บ์คํธ-shuffle ์กฐ์
broadcast()์ shuffle_down()์ ๋ชจ๋ ๊ฒฐํฉํ ๊ณ ๊ธ ์กฐ์ ์ ๊ตฌํํฉ๋๋ค.
์๊ตฌ์ฌํญ:
- ๋ ์ธ 0์ด ๋ธ๋ก์ ์ฒ์ 4๊ฐ ์์์ ํ๊ท ์ ๊ณ์ฐํ๊ณ ์ด ์ค์ผ์ผ๋ง ํฉํฐ๋ฅผ ๋ชจ๋ ๋ ์ธ์ ๋ธ๋ก๋์บ์คํธํด์ผ ํฉ๋๋ค
- ๊ฐ ๋ ์ธ์
shuffle_down(offset=1)์ ์ฌ์ฉํ์ฌ ๋ค์ ์ด์์ ๊ฐ์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค - ๋๋ถ๋ถ์ ๋ ์ธ: ์ค์ผ์ผ๋ง ํฉํฐ์
(ํ์ฌ_๊ฐ + ๋ค์_์ด์_๊ฐ)์ ๊ณฑํฉ๋๋ค - ์ํ์ ๋ง์ง๋ง ๋ ์ธ: ์ค์ผ์ผ๋ง ํฉํฐ์
ํ์ฌ_๊ฐ๋ง ๊ณฑํฉ๋๋ค (์ ํจํ ์ด์ ์์)
ํ
์คํธ ๋ฐ์ดํฐ: ์
๋ ฅ์ [2, 4, 6, 8, 1, 3, 5, 7, ...] ํจํด์ ๋ฐ๋ฆ
๋๋ค (์ฒ์
4๊ฐ ์์: 2,4,6,8 ์ดํ 1,3,5,7 ๋ฐ๋ณต)
- ๋ ์ธ 0์ด ์ค์ผ์ผ๋ง ํฉํฐ๋ฅผ ๊ณ์ฐ:
(2+4+6+8)/4 = 5.0 - ์์ ์ถ๋ ฅ:
[30.0, 50.0, 70.0, 45.0, 20.0, 40.0, 60.0, 40.0, ...]
๊ณผ์ : ํ๋์ ๋ ์ธ์ ๊ณ์ฐ์ด ๋ชจ๋ ๋ ์ธ์ ์ํฅ์ ๋ฏธ์น๋ฉด์, ๊ฐ ๋ ์ธ์ด ์์ ์ ์ด์ ๋ฐ์ดํฐ์๋ ์ ๊ทผํด์ผ ํ๋ ์ํฉ์์ ์ฌ๋ฌ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ์ด๋ป๊ฒ ์กฐ์ ํ ๊น์?
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
def broadcast_shuffle_coordination[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Combine broadcast() and shuffle_down() for advanced warp coordination.
Lane 0 computes block-local scaling factor, broadcasts it to all lanes in the warp.
Each lane uses shuffle_down() for neighbor access and applies broadcast factor.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
var scale_factor: output.ElementType = 0.0
# FILL IN (roughly 14 lines)
ํ
1. ๋ค์ค ๊ธฐ๋ณธ ์์ ์กฐ์
์ด ํผ์ฆ์ broadcast์ ์ ํ ์ฐ์ฐ์ ์์๋๋ก ์กฐ์จํด์ผ ํฉ๋๋ค.
ํ๋ฆ์ ์๊ฐํด ๋ณด์ธ์:
- ํ๋์ ๋ ์ธ์ด ์ ์ฒด ์ํ๋ฅผ ์ํ ๊ฐ์ ๊ณ์ฐ
- ์ด ๊ฐ์ด ๋ชจ๋ ๋ ์ธ์ broadcast๋จ
- ๊ฐ ๋ ์ธ์ด ์ ํ๋ก ์ด์ ๋ฐ์ดํฐ์ ์ ๊ทผ
- ๋ธ๋ก๋์บ์คํธ ๊ฐ์ด ์ด์ ๋ฐ์ดํฐ์ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ํฅ
์กฐ์ ํจํด:
# ๋จ๊ณ 1: ๋ธ๋ก๋์บ์คํธ ์กฐ์
var shared_param = lane_0์ด๋ฉด_๊ณ์ฐ()
shared_param = broadcast(shared_param)
# ๋จ๊ณ 2: ์
ํ ์ด์ ์ ๊ทผ
current_val = input[global_i]
neighbor_val = shuffle_down(current_val, offset)
# ๋จ๊ณ 3: ๊ฒฐํฉ ๊ณ์ฐ
result = ๊ฒฐํฉ(current_val, neighbor_val, shared_param)
2. ํ๋ผ๋ฏธํฐ ๊ณ์ฐ ์ ๋ต
์ด์ ์ฐ์ฐ์ ์ค์ผ์ผ๋งํ๋ ๋ฐ ์ ์ฉํ ๋ธ๋ก ๋ ๋ฒจ ํ๋ผ๋ฏธํฐ๊ฐ ๋ฌด์์ผ์ง ๊ณ ๋ คํ์ธ์.
ํ๊ตฌํ ์ง๋ฌธ:
- ๋ ์ธ 0์ด ๋ธ๋ก ๋ฐ์ดํฐ์์ ์ด๋ค ํต๊ณ๋ฅผ ๊ณ์ฐํด์ผ ํ ๊น์?
- ์ด ํ๋ผ๋ฏธํฐ๊ฐ ์ด์ ๊ธฐ๋ฐ ๊ณ์ฐ์ ์ด๋ค ์ํฅ์ ๋ฏธ์ณ์ผ ํ ๊น์?
- ์ ํ ์ฐ์ฐ์ด ํฌํจ๋ ๋ ์ํ ๊ฒฝ๊ณ์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋ ๊น์?
3. ๊ฒฐํฉ ์ฐ์ฐ ์ค๊ณ
๋ธ๋ก๋์บ์คํธ ํ๋ผ๋ฏธํฐ์ ์ ํ ๊ธฐ๋ฐ ์ด์ ์ ๊ทผ์ ์๋ฏธ ์๊ฒ ๊ฒฐํฉํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ์ธ์.
ํจํด ๊ณ ๋ ค์ฌํญ:
- ๋ธ๋ก๋์บ์คํธ ํ๋ผ๋ฏธํฐ๊ฐ ์ ๋ ฅ, ์ถ๋ ฅ, ๋๋ ๊ณ์ฐ์ ์ค์ผ์ผ๋งํด์ผ ํ ๊น์?
- ์ ํ์ด ๋ฏธ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ ๊ฒฝ๊ณ ์ผ์ด์ค๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น์?
- ๊ฐ์ฅ ํจ์จ์ ์ธ ์ฐ์ฐ ์์๋ ๋ฌด์์ผ๊น์?
๋ธ๋ก๋์บ์คํธ-shuffle ์กฐ์ ํ ์คํธ:
pixi run p25 --broadcast-shuffle-coordination
pixi run -e amd p25 --broadcast-shuffle-coordination
uv run poe p25 --broadcast-shuffle-coordination
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: HostBuffer([30.0, 50.0, 70.0, 45.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 35.0])
expected: HostBuffer([30.0, 50.0, 70.0, 45.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 40.0, 20.0, 40.0, 60.0, 35.0])
โ
๋ธ๋ก๋์บ์คํธ + ์
ํ coordination test passed!
์๋ฃจ์
def broadcast_shuffle_coordination[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
):
"""
Combine broadcast() and shuffle_down() for advanced warp coordination.
Lane 0 computes block-local scaling factor, broadcasts it to all lanes in the warp.
Each lane uses shuffle_down() for neighbor access and applies broadcast factor.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = Int(lane_id())
if global_i < size:
# Step 1: Lane 0 computes block-local scaling factor
var scale_factor: output.ElementType = 0.0
if lane == 0:
# Compute average of first 4 elements in this block's data
var block_start = block_idx.x * block_dim.x
var sum: output.ElementType = 0.0
for i in range(4):
if block_start + i < size:
sum += input[block_start + i]
scale_factor = sum / 4.0
# Step 2: Broadcast scaling factor to all lanes in this warp
scale_factor = broadcast(scale_factor)
# Step 3: Each lane gets current and next values
var current_val = input[global_i]
var next_val = shuffle_down(current_val, 1)
# Step 4: Apply broadcast factor with neighbor coordination
if lane < WARP_SIZE - 1 and global_i < size - 1:
# Combine current + next, then scale by broadcast factor
output[global_i] = (current_val + next_val) * scale_factor
else:
# Last lane in warp or last element: only current value, scaled by broadcast factor
output[global_i] = current_val * scale_factor
์ด ์๋ฃจ์ ์ broadcast์ ์ ํ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํ ๊ฐ์ฅ ๊ณ ๊ธ ์ํ ์กฐ์ ํจํด์ ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
# ๋จ๊ณ 1: ๋ ์ธ 0์ด ๋ธ๋ก ๋ก์ปฌ ์ค์ผ์ผ๋ง ํฉํฐ๋ฅผ ๊ณ์ฐ
var scale_factor: output.element_type = 0.0
if lane == 0:
block_start = block_idx.x * block_dim.x
var sum: output.element_type = 0.0
for i in range(4):
if block_start + i < size:
sum += input[block_start + i]
scale_factor = sum / 4.0
# ๋จ๊ณ 2: ์ค์ผ์ผ๋ง ํฉํฐ๋ฅผ ๋ชจ๋ ๋ ์ธ์ broadcast
scale_factor = broadcast(scale_factor)
# ๋จ๊ณ 3: ๊ฐ ๋ ์ธ์ด shuffle์ ํตํด ํ์ฌ ๊ฐ๊ณผ ๋ค์ ๊ฐ์ ๊ฐ์ ธ์ด
current_val = input[global_i]
next_val = shuffle_down(current_val, 1)
# ๋จ๊ณ 4: ๋ธ๋ก๋์บ์คํธ ํฉํฐ๋ฅผ ์ด์ ์กฐ์ ๊ณผ ๊ฒฐํฉํ์ฌ ์ ์ฉ
if lane < WARP_SIZE - 1 and global_i < size - 1:
output[global_i] = (current_val + next_val) * scale_factor
else:
output[global_i] = current_val * scale_factor
๋ค์ค ๊ธฐ๋ณธ ์์ ์คํ ์ถ์ :
์
๋ ฅ ๋ฐ์ดํฐ: [2, 4, 6, 8, 1, 3, 5, 7, ...]
๋จ๊ณ 1: ๋ ์ธ 0์ด ์ค์ผ์ผ๋ง ํฉํฐ๋ฅผ ๊ณ์ฐ
๋ ์ธ 0 ๊ณ์ฐ: (input[0] + input[1] + input[2] + input[3]) / 4
= (2 + 4 + 6 + 8) / 4 = 20 / 4 = 5.0
๋ค๋ฅธ ๋ ์ธ: scale_factor๋ 0.0 ์ ์ง
๋จ๊ณ 2: scale_factor = 5.0์ ๋ชจ๋ ๋ ์ธ์ broadcast
๋ชจ๋ ๋ ์ธ: scale_factor = 5.0
๋จ๊ณ 3: ์ด์ ์ ๊ทผ์ ์ํ ์
ํ ์ฐ์ฐ
๋ ์ธ 0: current_val = input[0] = 2, next_val = shuffle_down(2, 1) = input[1] = 4
๋ ์ธ 1: current_val = input[1] = 4, next_val = shuffle_down(4, 1) = input[2] = 6
๋ ์ธ 2: current_val = input[2] = 6, next_val = shuffle_down(6, 1) = input[3] = 8
๋ ์ธ 3: current_val = input[3] = 8, next_val = shuffle_down(8, 1) = input[4] = 1
...
๋ ์ธ 31: current_val = input[31], next_val = ๋ฏธ์ ์
๋จ๊ณ 4: ๋ธ๋ก๋์บ์คํธ ์ค์ผ์ผ๋ง๊ณผ ๊ฒฐํฉํ ๊ณ์ฐ
๋ ์ธ 0: output[0] = (2 + 4) * 5.0 = 6 * 5.0 = 30.0
๋ ์ธ 1: output[1] = (4 + 6) * 5.0 = 10 * 5.0 = 50.0
๋ ์ธ 2: output[2] = (6 + 8) * 5.0 = 14 * 5.0 = 70.0
๋ ์ธ 3: output[3] = (8 + 1) * 5.0 = 9 * 5.0 = 45.0
...
๋ ์ธ 31: output[31] = 7 * 5.0 = 35.0 (๊ฒฝ๊ณ - ์ด์ ์์)
ํต์ ํจํด ๋ถ์: ์ด ์๊ณ ๋ฆฌ์ฆ์ ๊ณ์ธต์ ์กฐ์ ํจํด์ ๊ตฌํํฉ๋๋ค:
- ์์ง ์กฐ์ (broadcast): ๋ ์ธ 0 โ ๋ชจ๋ ๋ ์ธ
- ์ํ ์กฐ์ (shuffle): ๋ ์ธ i โ ๋ ์ธ i+1
- ๊ฒฐํฉ ๊ณ์ฐ: ๋ธ๋ก๋์บ์คํธ ๋ฐ์ดํฐ์ ์ ํ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ํ์ฉ
์ํ์ ๊ธฐ๋ฐ: \[\Large \text{output}[i] = \begin{cases} (\text{input}[i]
- \text{input}[i+1]) \cdot \beta & \text{if lane} i < \text{WARP_SIZE} - 1 \\ \text{input}[i] \cdot \beta & \text{if lane } i = \text{WARP_SIZE} - 1 \end{cases}\]
์ฌ๊ธฐ์ \(\beta = \frac{1}{4}\sum_{k=0}^{3} \text{input}[\text{block_start} + k]\)๋ ๋ธ๋ก๋์บ์คํธ๋ ์ค์ผ์ผ๋ง ํฉํฐ์ ๋๋ค.
๊ณ ๊ธ ์กฐ์ ์ ์ฅ์ :
- ๋ค๋จ๊ณ ํต์ : ์ ์ญ(broadcast)๊ณผ ์ง์ญ(shuffle) ์กฐ์ ์ ๊ฒฐํฉ
- ์ ์ํ ์ค์ผ์ผ๋ง: ๋ธ๋ก ๋ ๋ฒจ ํ๋ผ๋ฏธํฐ๊ฐ ์ด์ ์ฐ์ฐ์ ์ํฅ
- ํจ์จ์ ๊ตฌ์ฑ: ๋ ๊ธฐ๋ณธ ์์๊ฐ ๋งค๋๋ฝ๊ฒ ํ๋ ฅ
- ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ: ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํจ
์ค์ ํ์ฉ ์ฌ๋ก:
- ์ ์ํ ํํฐ๋ง: ๋ธ๋ก ๋ ๋ฒจ ๋ ธ์ด์ฆ ์ถ์ ๊ณผ ์ด์ ๊ธฐ๋ฐ ํํฐ๋ง
- ๋์ ๋ถํ ๋ถ์ฐ: ์ ์ญ ์์ ๋ถ๋ฐฐ์ ๋ก์ปฌ ์กฐ์
- ๋ค์ค ์ค์ผ์ผ ์ฒ๋ฆฌ: ์ ์ญ ํ๋ผ๋ฏธํฐ๊ฐ ๋ก์ปฌ ์คํ ์ค ์ฐ์ฐ์ ์ ์ด
์์ฝ
์ด ์น์ ์ ํต์ฌ ํจํด์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
var shared_value = initial_value
if lane == 0:
shared_value = compute_block_statistic()
shared_value = broadcast(shared_value)
result = use_shared_value(shared_value, local_data)
ํต์ฌ ์ฅ์ :
- ์ผ๋๋ค ์กฐ์ : ํ๋์ ๋ ์ธ์ด ๊ณ์ฐํ๊ณ ๋ชจ๋ ๋ ์ธ์ด ํํ์ ๋ฐ์
- ๋๊ธฐํ ์ค๋ฒํค๋ ์ ๋ก: SIMT ์คํ์ด ์กฐ์ ์ ์ฒ๋ฆฌ
- ์กฐํฉ ๊ฐ๋ฅํ ํจํด: ์ ํ๊ณผ ๋ค๋ฅธ ์ํ ์ฐ์ฐ๊ณผ ์ฝ๊ฒ ๊ฒฐํฉ
ํ์ฉ ๋ถ์ผ: ๋ธ๋ก ํต๊ณ, ์งํฉ์ ์์ฌ๊ฒฐ์ , ํ๋ผ๋ฏธํฐ ๊ณต์ , ์ ์ํ ์๊ณ ๋ฆฌ์ฆ.
Puzzle 26: ๊ณ ๊ธ ์ํ ํจํด
๊ฐ์
Puzzle 26: ๊ณ ๊ธ ์ํ ํต์ ๊ธฐ๋ณธ ์์์์๋ ์ ๊ตํ GPU ์ํ ๋ ๋ฒจ ๋ฒํฐํ๋ผ์ด ํต์ ๊ณผ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ - ์ํ ๋ด์์ ํจ์จ์ ์ธ ํธ๋ฆฌ ๊ธฐ๋ฐ ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ณธ ์์๋ฅผ ์๊ฐํฉ๋๋ค. shuffle_xor์ ์ฌ์ฉํ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ์ prefix_sum์ ์ฌ์ฉํ ํ๋์จ์ด ์ต์ ํ ๋ณ๋ ฌ ์ค์บ์ ๋ฐฐ์ฐ๋ฉฐ, ๋ณต์กํ ๋ค๋จ๊ณ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ ์์ด ์ด๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ตํ๋๋ค.
๋ฌ์ฑ ๋ชฉํ: ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ๋ค๋จ๊ณ ๋ฆฌ๋์ ํจํด์์ ๋ฒ์ด๋, ํ๋์จ์ด ์ต์ ํ๋ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ์ ๋ณ๋ ฌ ์ค์บ ์ ๋์ ํ์ฉํ๋ ์ฐ์ํ ๋จ์ผ ํจ์ ํธ์ถ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ ํํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ์ํ๋ ํ๋์จ์ด์์ ์ ๊ตํ ํธ๋ฆฌ ๊ธฐ๋ฐ ํต์ ๊ณผ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ์ ์ํํ ์ ์์ต๋๋ค - Mojo์ ๊ณ ๊ธ ์ํ ๊ธฐ๋ณธ ์์๋ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ์ ์ ์ฉ ์ค์บ ์ ๋์ ํ์ฉํ์ฌ \(O(\log n)\) ์๊ณ ๋ฆฌ์ฆ์ ๋จ์ผ ๋ช ๋ น ์์ค์ ๊ฐ๊ฒฐํจ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ
๊ณ ๊ธ ์ํ ํต์ ๋ชจ๋ธ
GPU ์ํ ๋ด ์ ๊ตํ ํต์ ํจํด์ ์ดํดํฉ๋๋ค:
GPU ์ํ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ (32 ์ค๋ ๋, XOR ๊ธฐ๋ฐ ํต์ )
Offset 16: Lane 0 โ Lane 16, Lane 1 โ Lane 17, ..., Lane 15 โ Lane 31
Offset 8: Lane 0 โ Lane 8, Lane 1 โ Lane 9, ..., Lane 23 โ Lane 31
Offset 4: Lane 0 โ Lane 4, Lane 1 โ Lane 5, ..., Lane 27 โ Lane 31
Offset 2: Lane 0 โ Lane 2, Lane 1 โ Lane 3, ..., Lane 29 โ Lane 31
Offset 1: Lane 0 โ Lane 1, Lane 2 โ Lane 3, ..., Lane 30 โ Lane 31
ํ๋์จ์ด ๋์ ํฉ (๋ณ๋ ฌ ์ค์บ ๊ฐ์)
์
๋ ฅ: [1, 2, 3, 4, 5, 6, 7, 8, ...]
์ถ๋ ฅ: [1, 3, 6, 10, 15, 21, 28, 36, ...] (inclusive scan)
ํ๋์จ์ด ํ์ค:
- ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ: XOR ๊ธฐ๋ฐ ํต์ ์ด ์ต์ ์ ํธ๋ฆฌ ํ ํด๋ก์ง๋ฅผ ์์ฑํฉ๋๋ค
- ์ ์ฉ ์ค์บ ์ ๋: ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ๋์ ํฉ ์ฐ์ฐ
- ๋ก๊ทธ ๋ณต์ก๋: \(O(\log n)\) ์๊ณ ๋ฆฌ์ฆ์ด \(O(n)\) ์์ฐจ ํจํด์ ๋์ฒดํฉ๋๋ค
- ๋จ์ผ ์ฌ์ดํด ์ฐ์ฐ: ๋ณต์กํ ๋ฆฌ๋์ ์ด ์ ์ฉ ํ๋์จ์ด์์ ์ฒ๋ฆฌ๋ฉ๋๋ค
Mojo์ ๊ณ ๊ธ ์ํ ์ฐ์ฐ
gpu.primitives.warp์ ์ ๊ตํ ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ๋ฐฐ์๋๋ค:
shuffle_xor(value, mask): ํธ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ์ํ XOR ๊ธฐ๋ฐ ๋ฒํฐํ๋ผ์ด ํต์prefix_sum(value): ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ- ๊ณ ๊ธ ์กฐ์ ํจํด: ์ฌ๋ฌ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํ ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ
์ฐธ๊ณ : ์ด ๊ธฐ๋ณธ ์์๋ค์ ๋ณ๋ ฌ ๋ฆฌ๋์ , ์คํธ๋ฆผ ์ปดํฉ์ , quicksort ํํฐ์ ๋, FFT ์ฐ์ฐ ๋ฑ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ ์ฝ๋๊ฐ ์์ญ ์ค ํ์ํ์ ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
์ฑ๋ฅ ๋ณํ ์์
# ๋ณต์กํ ๋ณ๋ ฌ ๋ฆฌ๋์
(๊ธฐ์กด ๋ฐฉ์ - Puzzle 14 ์ฐธ๊ณ ):
shared = TileTensor[
dtype,
row_major[WARP_SIZE](),
MutAnyOrigin,
address_space = AddressSpace.SHARED,
].stack_allocation()
shared[local_i] = input[global_i]
barrier()
offset = 1
for i in range(Int(log2(Scalar[dtype](WARP_SIZE)))):
var current_val: output.element_type = 0
if local_i >= offset and local_i < WARP_SIZE:
current_val = shared[local_i - offset]
barrier()
if local_i >= offset and local_i < WARP_SIZE:
shared[local_i] += current_val
barrier()
offset *= 2
# ๊ณ ๊ธ ์ํ ๊ธฐ๋ณธ ์์๊ฐ ์ด ๋ชจ๋ ๋ณต์ก์ฑ์ ์ ๊ฑฐํฉ๋๋ค:
current_val = input[global_i]
scan_result = prefix_sum[exclusive=False](current_val) # ๋จ์ผ ํธ์ถ!
output[global_i] = scan_result
๊ณ ๊ธ ์ํ ์ฐ์ฐ์ด ๋น๋๋ ์๊ฐ
์ฑ๋ฅ ํน์ฑ์ ์ดํดํฉ๋๋ค:
| ์๊ณ ๋ฆฌ์ฆ ํจํด | ๊ธฐ์กด ๋ฐฉ์ | ๊ณ ๊ธ ์ํ ์ฐ์ฐ |
|---|---|---|
| ๋ณ๋ ฌ ๋ฆฌ๋์ | ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด | ๋จ์ผ shuffle_xor ํธ๋ฆฌ |
| ๋์ ํฉ/์ค์บ ์ฐ์ฐ | ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ | ํ๋์จ์ด prefix_sum |
| ์คํธ๋ฆผ ์ปดํฉ์ | ๋ณต์กํ ์ธ๋ฑ์ฑ | prefix_sum + ์กฐ์ |
| Quicksort ํํฐ์ | ์๋ ์์น ๊ณ์ฐ | ๊ฒฐํฉ๋ ๊ธฐ๋ณธ ์์ |
| ํธ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ | ์ฌ๊ท์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ | ๋ฒํฐํ๋ผ์ด ํต์ |
์ ์ ์ง์
๊ณ ๊ธ ์ํ ํต์ ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ๋ค์ ๋ด์ฉ์ ์ต์ํด์ผ ํฉ๋๋ค:
- Part VII ์ํ ๊ธฐ์ด: SIMT ์คํ๊ณผ ๊ธฐ๋ณธ ์ํ ์ฐ์ฐ์ ๋ํ ์ดํด (Puzzle 24: ์ํ ๊ธฐ์ด์ Puzzle 25: ์ํ ํต์ ์ฐธ๊ณ )
- ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ ์ด๋ก : ํธ๋ฆฌ ๋ฆฌ๋์ , ๋ณ๋ ฌ ์ค์บ, ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ
- GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด๊ณผ ๋๊ธฐํ (Puzzle 14: ๋์ ํฉ ์ฐธ๊ณ )
- ์ํ ์ฐ์ฐ: XOR ์ฐ์ฐ๊ณผ ๋ก๊ทธ ๋ณต์ก๋์ ๋ํ ์ดํด
ํ์ต ๊ฒฝ๋ก
1. shuffle_xor์ ์ด์ฉํ ๋ฒํฐํ๋ผ์ด ํต์
โ warp.shuffle_xor()์ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ
ํจ์จ์ ์ธ ํธ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ์ํ XOR ๊ธฐ๋ฐ ๋ฒํฐํ๋ผ์ด ํต์ ํจํด์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
shuffle_xor()์ผ๋ก ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ ํ ํด๋ก์ง ๊ตฌ์ฑํ๊ธฐ- ํธ๋ฆฌ ํต์ ์ ํ์ฉํ \(O(\log n)\) ๋ณ๋ ฌ ๋ฆฌ๋์ ๊ตฌํ
- XOR ๊ธฐ๋ฐ ๋ ์ธ ํ์ด๋ง๊ณผ ํต์ ํจํด ์ดํด
- ๋ค์ค ๊ฐ ๋ฆฌ๋์ ์ ์ํ ๊ณ ๊ธ ์กฐ๊ฑด๋ถ ๋ฒํฐํ๋ผ์ด ์ฐ์ฐ
ํต์ฌ ํจํด:
max_val = input[global_i]
offset = WARP_SIZE // 2
while offset > 0:
max_val = max(max_val, shuffle_xor(max_val, offset))
offset //= 2
# ๋ชจ๋ ๋ ์ธ์ด ์ ์ญ ์ต๋๊ฐ์ ๊ฐ์ง๊ฒ ๋ฉ๋๋ค
2. prefix_sum์ ์ด์ฉํ ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ
โ warp.prefix_sum()๊ณผ ์ค์บ ์ฐ์ฐ
๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒดํ๋ ํ๋์จ์ด ์ต์ ํ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ์ ๋ฐฐ์๋๋ค.
๋ฐฐ์ธ ๋ด์ฉ:
prefix_sum()์ ํ์ฉํ ํ๋์จ์ด ๊ฐ์ ๋์ ์ฐ์ฐ- ์คํธ๋ฆผ ์ปดํฉ์ ๊ณผ ๋ณ๋ ฌ ํํฐ์ ๋ ๊ตฌํ
prefix_sum๊ณผshuffle_xor์ ๊ฒฐํฉํ ๊ณ ๊ธ ์กฐ์ - Inclusive vs exclusive ์ค์บ ํจํด ์ดํด
ํต์ฌ ํจํด:
current_val = input[global_i]
scan_result = prefix_sum[exclusive=False](current_val)
output[global_i] = scan_result # ํ๋์จ์ด ์ต์ ํ ๋์ ํฉ
ํต์ฌ ๊ฐ๋
๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ ํต์
XOR ๊ธฐ๋ฐ ํต์ ํ ํด๋ก์ง๋ฅผ ์ดํดํฉ๋๋ค:
- XOR ํ์ด๋ง:
lane_id โ mask๊ฐ ๋์นญ ํต์ ์์ ์์ฑํฉ๋๋ค - ํธ๋ฆฌ ๋ฆฌ๋์ : ๊ณ์ธต์ ๋ฐ์ดํฐ ๊ตํ์ ํตํ ๋ก๊ทธ ๋ณต์ก๋
- ๋ณ๋ ฌ ์กฐ์ : ๋ชจ๋ ๋ ์ธ์ด ๋ฆฌ๋์ ์ ๋์์ ์ฐธ์ฌํฉ๋๋ค
- ๋์ ์๊ณ ๋ฆฌ์ฆ: 2์ ๊ฑฐ๋ญ์ ๊ณฑ
WARP_SIZE(32, 64 ๋ฑ) ์ด๋์๋ ๋์ํฉ๋๋ค
ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ
์ ์ฉ ์ค์บ ์ ๋์ ๋ฅ๋ ฅ์ ์ดํดํฉ๋๋ค:
- ๋์ ํฉ ์ฐ์ฐ: ํ๋์จ์ด ๊ฐ์์ ํ์ฉํ ๋์ ์ฐ์ฐ
- ์คํธ๋ฆผ ์ปดํฉ์ : ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ๋ฐ์ดํฐ ์ฌ๋ฐฐ์น
- ๋จ์ผ ํจ์ ๊ฐ๊ฒฐ์ฑ: ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ด ๋จ์ผ ํธ์ถ๋ก ๋ณํ๋ฉ๋๋ค
- ๋๊ธฐํ ๋ถํ์: ํ๋์จ์ด๊ฐ ๋ชจ๋ ์กฐ์ ์ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค
์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋ ๋ณํ
๊ธฐ์กด ํจํด์ ๊ณ ๊ธ ์ํ ์ฐ์ฐ์ผ๋ก ๋ณํํฉ๋๋ค:
- ์์ฐจ ๋ฆฌ๋์ (\(O(n)\)) โ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ (\(O(\log n)\))
- ๋ค๋จ๊ณ ์ค์บ ์๊ณ ๋ฆฌ์ฆ โ ๋จ์ผ ํ๋์จ์ด prefix_sum
- ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด โ ๋ ์ง์คํฐ ์ ์ฉ ์ฐ์ฐ
- ๋ช ์์ ๋๊ธฐํ โ ํ๋์จ์ด ๊ด๋ฆฌ ์กฐ์
๊ณ ๊ธ ์กฐ์ ํจํด
์ฌ๋ฌ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํ ์ ๊ตํ ์๊ณ ๋ฆฌ์ฆ:
- ์ด์ค ๋ฆฌ๋์ : ๋ฒํฐํ๋ผ์ด ํจํด์ ํ์ฉํ ๋์ min/max ์ถ์
- ๋ณ๋ ฌ ํํฐ์
๋: quicksort ์คํ์ผ ์ฐ์ฐ์ ์ํ
shuffle_xor+prefix_sum - ์กฐ๊ฑด๋ถ ์ฐ์ฐ: ์ ์ญ ์กฐ์ ์ ํตํ ๋ ์ธ ๊ธฐ๋ฐ ์ถ๋ ฅ ์ ํ
- ๋ค์ค ๊ธฐ๋ณธ ์์ ์๊ณ ๋ฆฌ์ฆ: ์ต์ ์ฑ๋ฅ์ ๋ณต์กํ ๋ณ๋ ฌ ํจํด
์์ํ๊ธฐ
๊ณ ๊ธ GPU ์ํ ๋ ๋ฒจ ํต์ ์ ํ์ฉํ ์ค๋น๊ฐ ๋์ จ๋์? ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ ์ฐ์ฐ์ผ๋ก ํธ๋ฆฌ ๊ธฐ๋ฐ ํต์ ์ ์ดํดํ ๋ค์, ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ์ผ๋ก ๋์๊ฐ ์ต์ ์ ์๊ณ ๋ฆฌ์ฆ ์ฑ๋ฅ์ ๋ฌ์ฑํ์ธ์.
๐ก ์ฑ๊ณต ํ: ๊ณ ๊ธ ์ํ ์ฐ์ฐ์ ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ ๋น๋ฉ ๋ธ๋ก์ผ๋ก ์๊ฐํ์ธ์. ์ด ๊ธฐ๋ณธ ์์๋ค์ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ์ ์ฒด ๋ฒ์ฃผ๋ฅผ ๋จ์ผ ์ต์ ํ ํจ์ ํธ์ถ๋ก ๋์ฒดํฉ๋๋ค.
ํ์ต ๋ชฉํ: Puzzle 26์ ๋ง์น๋ฉด, ๊ณ ๊ธ ์ํ ๊ธฐ๋ณธ ์์๊ฐ ๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๋์ฒดํ ์ ์๋ ์ํฉ์ ์ธ์ํ์ฌ ํจ์ฌ ๊ฐ๋จํ๊ณ ๋น ๋ฅธ ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ฆฌ๋์ , ๋ณ๋ ฌ ์ค์บ, ์กฐ์ ํจํด์ ์์ฑํ ์ ์๊ฒ ๋ฉ๋๋ค.
์์ํ๊ธฐ: warp.shuffle_xor()์ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ ์์ ๋ฒํฐํ๋ผ์ด ํต์ ์ ๋ฐฐ์ด ๋ค์, warp.prefix_sum()๊ณผ ์ค์บ ์ฐ์ฐ ์์ ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ ํจํด์ผ๋ก ๋์๊ฐ์ธ์!
warp.shuffle_xor() ๋ฒํฐํ๋ผ์ด ํต์
์ํ ๋ ๋ฒจ ๋ฒํฐํ๋ผ์ด ํต์ ์์๋ shuffle_xor()์ ์ฌ์ฉํ์ฌ ์ํ ๋ด์ ์ ๊ตํ ํธ๋ฆฌ
๊ธฐ๋ฐ ํต์ ํจํด์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ด ๊ฐ๋ ฅํ ๊ธฐ๋ณธ ์์๋ฅผ ํตํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋
๋ช
์์ ๋๊ธฐํ ์์ด ํจ์จ์ ์ธ ๋ณ๋ ฌ ๋ฆฌ๋์
, ์ ๋ ฌ ๋คํธ์ํฌ, ๊ณ ๊ธ ์กฐ์ ์๊ณ ๋ฆฌ์ฆ์
๊ตฌํํ ์ ์์ต๋๋ค.
ํต์ฌ ํต์ฐฐ: shuffle_xor() ์ฐ์ฐ์ SIMT ์คํ์ ํ์ฉํ์ฌ XOR ๊ธฐ๋ฐ ํต์ ํธ๋ฆฌ๋ฅผ ์์ฑํ๋ฉฐ, ์ํ ํฌ๊ธฐ์ ๋ํด \(O(\log n)\) ๋ณต์ก๋๋ก ํ์ฅ๋๋ ํจ์จ์ ์ธ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ์ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ๋? ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ๋ ์ค๋ ๋๋ค์ด ์ธ๋ฑ์ค์ XOR ํจํด์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ๊ตํํ๋ ํต์ ํ ํด๋ก์ง์ ๋๋ค. ์ด๋ฆ์ ์๊ฐ์ ์ผ๋ก ๊ทธ๋ ธ์ ๋ ๋๋น ๋ ๊ฐ์ฒ๋ผ ๋ณด์ด๋ ์ฐ๊ฒฐ ํจํด์์ ์ ๋ํ์ต๋๋ค. ์ด ๋คํธ์ํฌ๋ \(O(\log n)\) ํต์ ๋ณต์ก๋๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ๋๋ฌธ์ FFT, bitonic ์ ๋ ฌ, ๋ณ๋ ฌ ๋ฆฌ๋์ ๊ฐ์ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
shuffle_xor()์ ํ์ฉํ XOR ๊ธฐ๋ฐ ํต์ ํจํด- ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ ํ ํด๋ก์ง
- \(O(\log n)\) ๋ณต์ก๋์ ํธ๋ฆฌ ๊ธฐ๋ฐ ๋ณ๋ ฌ ๋ฆฌ๋์
- ๊ณ ๊ธ ์กฐ์ ์ ์ํ ์กฐ๊ฑด๋ถ ๋ฒํฐํ๋ผ์ด ์ฐ์ฐ
- ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋์ฒดํ๋ ํ๋์จ์ด ์ต์ ํ ๋ณ๋ ฌ ๊ธฐ๋ณธ ์์
shuffle_xor ์ฐ์ฐ์ ๊ฐ ๋ ์ธ์ด XOR
ํจํด์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ ์ธ๊ณผ ๋ฐ์ดํฐ๋ฅผ ๊ตํํ ์ ์๊ฒ ํฉ๋๋ค: \[\Large
\text{shuffle_xor}(\text{value}, \text{mask}) =
\text{value_from_lane}(\text{lane_id} \oplus \text{mask})\]
์ด๋ฅผ ํตํด ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ด ์ฐ์ํ ๋ฒํฐํ๋ผ์ด ํต์ ํจํด์ผ๋ก ๋ณํ๋์ด, ๋ช ์์ ์กฐ์ ์์ด ํจ์จ์ ์ธ ํธ๋ฆฌ ๋ฆฌ๋์ ๊ณผ ์ ๋ ฌ ๋คํธ์ํฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
1. ๊ธฐ๋ณธ ๋ฒํฐํ๋ผ์ด ํ์ด ๊ตํ
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ ์ด์์:
row_major[SIZE]()(1D row-major)
shuffle_xor ๊ฐ๋
๊ธฐ์กด ํ์ด ๊ตํ ๋ฐฉ์์ ๋ณต์กํ ์ธ๋ฑ์ฑ๊ณผ ์กฐ์ ์ด ํ์ํฉ๋๋ค:
# ๊ธฐ์กด ๋ฐฉ์ - ๋ณต์กํ๊ณ ๋๊ธฐํ๊ฐ ํ์
shared_memory[lane] = input[global_i]
barrier()
if lane % 2 == 0:
partner = lane + 1
else:
partner = lane - 1
if partner < WARP_SIZE:
swapped_val = shared_memory[partner]
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ด ํ์
- ๋๊ธฐํ: ๋ช ์์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์
- ๋ณต์กํ ๋ก์ง: ์๋ ํํธ๋ ๊ณ์ฐ๊ณผ ๊ฒฝ๊ณ ๊ฒ์ฌ
- ๋ฎ์ ํ์ฅ์ฑ: ํ๋์จ์ด ํต์ ์ ํ์ฉํ์ง ๋ชปํจ
shuffle_xor()์ ์ฌ์ฉํ๋ฉด ํ์ด ๊ตํ์ด ์ฐ์ํด์ง๋๋ค:
# ๋ฒํฐํ๋ผ์ด XOR ๋ฐฉ์ - ๊ฐ๋จํ๊ณ ํ๋์จ์ด ์ต์ ํ
current_val = input[global_i]
swapped_val = shuffle_xor(current_val, 1) # 1๊ณผ XORํ๋ฉด ํ์ด๊ฐ ์์ฑ๋จ
output[global_i] = swapped_val
shuffle_xor์ ์ฅ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์
- ๋๊ธฐํ ๋ถํ์: SIMT ์คํ์ด ์ ํ์ฑ์ ๋ณด์ฅ
- ํ๋์จ์ด ์ต์ ํ: ๋ชจ๋ ๋ ์ธ์ ๋ํด ๋จ์ผ ๋ช ๋ น์ผ๋ก ์ฒ๋ฆฌ
- ๋ฒํฐํ๋ผ์ด ๊ธฐ๋ฐ: ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๋น๋ฉ ๋ธ๋ก
์์ฑํ ์ฝ๋
shuffle_xor()์ ์ฌ์ฉํ์ฌ ์ธ์ ํ์ด ๊ฐ ๊ฐ์ ๊ตํํ๋ ํ์ด ๊ตํ์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: XOR ํจํด์ผ๋ก ์ธ์ ํ์ด๋ฅผ ๋ง๋ค์ด ๊ฐ์ ๊ตํํฉ๋๋ค: \[\Large \text{output}[i] = \text{input}[i \oplus 1]\]
์
๋ ฅ ๋ฐ์ดํฐ [0, 1, 2, 3, 4, 5, 6, 7, ...]์ ํ์ด
[1, 0, 3, 2, 5, 4, 7, 6, ...]์ผ๋ก ๋ณํํ๋ฉฐ, ๊ฐ ํ์ด (i, i+1)์ด XOR ํต์ ์ผ๋ก
๊ฐ์ ๊ตํํฉ๋๋ค.
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p26/p26.mojo
ํ
1. shuffle_xor ์ดํดํ๊ธฐ
shuffle_xor(value, mask) ์ฐ์ฐ์ ๊ฐ ๋ ์ธ์ด XOR ๋ง์คํฌ๋งํผ ์ฐจ์ด๋๋ ๋ ์ธ๊ณผ
๋ฐ์ดํฐ๋ฅผ ๊ตํํ ์ ์๊ฒ ํฉ๋๋ค. ์๋ก ๋ค๋ฅธ ๋ง์คํฌ ๊ฐ์ผ๋ก ๋ ์ธ ID๋ฅผ XORํ์ ๋
์ด๋ค ์ผ์ด ์ผ์ด๋๋์ง ์๊ฐํด ๋ณด์ธ์.
ํ๊ตฌํ ํต์ฌ ์ง๋ฌธ:
- ๋ ์ธ 0์ด ๋ง์คํฌ 1๋ก XORํ๋ฉด ์ด๋ค ํํธ๋๋ฅผ ์ป๋์?
- ๋ ์ธ 1์ด ๋ง์คํฌ 1๋ก XORํ๋ฉด ์ด๋ค ํํธ๋๋ฅผ ์ป๋์?
- ํจํด์ด ๋ณด์ด๋์?
ํํธ: ์ฒ์ ๋ช ๊ฐ์ ๋ ์ธ ID์ ๋ํด XOR ์ฐ์ฐ์ ์ง์ ํด๋ณด๋ฉด ํ์ด๋ง ํจํด์ ์ดํดํ ์ ์์ต๋๋ค.
2. XOR ํ์ด ํจํด
๋ ์ธ ID์ ์ด์ง ํํ๊ณผ ์ตํ์ ๋นํธ๋ฅผ ๋ค์ง์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ์๊ฐํด ๋ณด์ธ์.
๊ณ ๋ คํ ์ง๋ฌธ:
- ์ง์ ๋ ์ธ์ 1๊ณผ XORํ๋ฉด ์ด๋ป๊ฒ ๋๋์?
- ํ์ ๋ ์ธ์ 1๊ณผ XORํ๋ฉด ์ด๋ป๊ฒ ๋๋์?
- ์ ์ด๊ฒ์ด ์๋ฒฝํ ํ์ด๋ฅผ ๋ง๋๋์?
3. ๊ฒฝ๊ณ ๊ฒ์ฌ ๋ถํ์
shuffle_down()๊ณผ ๋ฌ๋ฆฌ shuffle_xor() ์ฐ์ฐ์ ์ํ ๊ฒฝ๊ณ ๋ด์์ ์ ์ง๋ฉ๋๋ค. ์์
๋ง์คํฌ๋ก์ XOR์ด ์ ๋๋ก ๋ฒ์ ๋ฐ์ ๋ ์ธ ID๋ฅผ ๋ง๋ค์ง ์๋ ์ด์ ๋ฅผ ์๊ฐํด ๋ณด์ธ์.
์๊ฐํด ๋ณด์ธ์: ์ ํจํ ๋ ์ธ ID๋ฅผ 1๊ณผ XORํ์ ๋ ๋์ฌ ์ ์๋ ์ต๋ ๋ ์ธ ID๋ ์ผ๋ง์ธ๊ฐ์?
๋ฒํฐํ๋ผ์ด ํ์ด ๊ตํ ํ ์คํธ:
pixi run p26 --pair-swap
pixi run -e amd p26 --pair-swap
pixi run -e apple p26 --pair-swap
uv run poe p26 --pair-swap
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: [1.0, 0.0, 3.0, 2.0, 5.0, 4.0, 7.0, 6.0, 9.0, 8.0, 11.0, 10.0, 13.0, 12.0, 15.0, 14.0, 17.0, 16.0, 19.0, 18.0, 21.0, 20.0, 23.0, 22.0, 25.0, 24.0, 27.0, 26.0, 29.0, 28.0, 31.0, 30.0]
expected: [1.0, 0.0, 3.0, 2.0, 5.0, 4.0, 7.0, 6.0, 9.0, 8.0, 11.0, 10.0, 13.0, 12.0, 15.0, 14.0, 17.0, 16.0, 19.0, 18.0, 21.0, 20.0, 23.0, 22.0, 25.0, 24.0, 27.0, 26.0, 29.0, 28.0, 31.0, 30.0]
โ
Butterfly pair swap test passed!
์๋ฃจ์
def butterfly_pair_swap[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Basic butterfly pair swap: Exchange values between adjacent pairs using XOR pattern.
Each thread exchanges its value with its XOR-1 neighbor, creating pairs: (0,1), (2,3), (4,5), etc.
Uses shuffle_xor(val, 1) to swap values within each pair.
This is the foundation of butterfly network communication patterns.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
if global_i < size:
var current_val = input[global_i]
# Exchange with XOR-1 neighbor using butterfly pattern
# Lane 0 exchanges with lane 1, lane 2 with lane 3, etc.
var swapped_val = shuffle_xor(current_val, 1)
# For demonstration, we'll store the swapped value
# In real applications, this might be used for sorting, reduction, etc.
output[global_i] = swapped_val
์ด ํ์ด๋ shuffle_xor()์ด XOR ํต์ ํจํด์ ํตํด ์๋ฒฝํ ํ์ด ๊ตํ์ ์ด๋ป๊ฒ
๋ง๋๋์ง ๋ณด์ฌ์ค๋๋ค.
์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
current_val = input[global_i] # ๊ฐ ๋ ์ธ์ด ์์ ์ ์์๋ฅผ ์ฝ์
swapped_val = shuffle_xor(current_val, 1) # XOR๋ก ํ์ด ๊ตํ ์์ฑ
# ๊ตํ๋ ๊ฐ์ ์ ์ฅ
output[global_i] = swapped_val
SIMT ์คํ ์์ธ ๋ถ์:
์ฌ์ดํด 1: ๋ชจ๋ ๋ ์ธ์ด ๋์์ ๊ฐ์ ๋ก๋
Lane 0: current_val = input[0] = 0
Lane 1: current_val = input[1] = 1
Lane 2: current_val = input[2] = 2
Lane 3: current_val = input[3] = 3
...
Lane 31: current_val = input[31] = 31
์ฌ์ดํด 2: shuffle_xor(current_val, 1)์ด ๋ชจ๋ ๋ ์ธ์์ ์คํ
Lane 0: Lane 1์์ ์์ (0โ1=1) โ swapped_val = 1
Lane 1: Lane 0์์ ์์ (1โ1=0) โ swapped_val = 0
Lane 2: Lane 3์์ ์์ (2โ1=3) โ swapped_val = 3
Lane 3: Lane 2์์ ์์ (3โ1=2) โ swapped_val = 2
...
Lane 30: Lane 31์์ ์์ (30โ1=31) โ swapped_val = 31
Lane 31: Lane 30์์ ์์ (31โ1=30) โ swapped_val = 30
์ฌ์ดํด 3: ๊ฒฐ๊ณผ ์ ์ฅ
Lane 0: output[0] = 1
Lane 1: output[1] = 0
Lane 2: output[2] = 3
Lane 3: output[3] = 2
...
์ํ์ ํต์ฐฐ: XOR ์์ฑ์ ํ์ฉํ ์๋ฒฝํ ํ์ด ๊ตํ์ ๊ตฌํํฉ๋๋ค: \[\Large \text{XOR}(i, 1) = \begin{cases} i + 1 & \text{if } i \bmod 2 = 0 \\ i - 1 & \text{if } i \bmod 2 = 1 \end{cases}\]
shuffle_xor์ด ์ฐ์ํ ์ด์ :
- ์๋ฒฝํ ๋์นญ: ๋ชจ๋ ๋ ์ธ์ด ์ ํํ ํ๋์ ํ์ด์ ์ฐธ์ฌ
- ์กฐ์ ๋ถํ์: ๋ชจ๋ ํ์ด๊ฐ ๋์์ ๊ตํ
- ํ๋์จ์ด ์ต์ ํ: ์ํ ์ ์ฒด์ ๋ํด ๋จ์ผ ๋ช ๋ น์ผ๋ก ์ฒ๋ฆฌ
- ๋ฒํฐํ๋ผ์ด ๊ธฐ๋ฐ: ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๋น๋ฉ ๋ธ๋ก
์ฑ๋ฅ ํน์ฑ:
- ์ง์ฐ ์๊ฐ: 1 ์ฌ์ดํด (ํ๋์จ์ด ๋ ์ง์คํฐ ๊ตํ)
- ๋์ญํญ: 0 ๋ฐ์ดํธ (๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์์)
- ๋ณ๋ ฌ์ฑ: WARP_SIZE๊ฐ ๋ ์ธ ๋ชจ๋ ๋์์ ๊ตํ
- ํ์ฅ์ฑ: ๋ฐ์ดํฐ ํฌ๊ธฐ์ ๊ด๊ณ์์ด \(O(1)\) ๋ณต์ก๋
2. ๋ฒํฐํ๋ผ์ด ๋ณ๋ ฌ ์ต๋๊ฐ
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
๊ฐ์ํ๋ offset์ผ๋ก ๋ฒํฐํ๋ผ์ด shuffle_xor์ ์ฌ์ฉํ์ฌ ๋ณ๋ ฌ ์ต๋๊ฐ ๋ฆฌ๋์
์
๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ํธ๋ฆฌ ๋ฆฌ๋์ ์ ํตํด ๋ชจ๋ ์ํ ๋ ์ธ์์ ์ต๋๊ฐ์ ๊ณ์ฐํฉ๋๋ค: \[\Large \text{max_result} = \max_{i=0}^{\small\text{WARP_SIZE}-1} \text{input}[i]\]
๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
ํจํด: XOR ์คํ์
์ WARP_SIZE/2์์ 1๊น์ง ์ ๋ฐ์ฉ
์ค์ฌ๊ฐ๋ฉฐ, ํต์ ๋ฒ์๊ฐ ๋จ๊ณ๋ง๋ค ๋ฐ์ผ๋ก ์ข์์ง๋ ์ด์ง ํธ๋ฆฌ๋ฅผ ๊ตฌ์ฑํฉ๋๋ค:
- 1๋จ๊ณ:
WARP_SIZE/2๊ฑฐ๋ฆฌ์ ๋ ์ธ๊ณผ ๋น๊ต (์ํ ์ ์ฒด๋ฅผ ํฌ๊ด) - 2๋จ๊ณ:
WARP_SIZE/4๊ฑฐ๋ฆฌ์ ๋ ์ธ๊ณผ ๋น๊ต (๋ฒ์๋ฅผ ์ ๋ฐ์ผ๋ก ์ขํ) - 3๋จ๊ณ:
WARP_SIZE/8๊ฑฐ๋ฆฌ์ ๋ ์ธ๊ณผ ๋น๊ต - 4๋จ๊ณ:
offset = 1์ด ๋ ๋๊น์ง ๊ณ์ ์ ๋ฐ์ผ๋ก ์ค์
\(\log_2(\text{WARP_SIZE})\) ๋จ๊ณ๋ฅผ ๊ฑฐ์น๋ฉด ๋ชจ๋ ๋ ์ธ์ด ์ ์ญ ์ต๋๊ฐ์ ๊ฐ๊ฒ
๋ฉ๋๋ค. ์ด ๋ฐฉ์์ ๋ชจ๋ WARP_SIZE (32, 64 ๋ฑ)์์ ๋์ํฉ๋๋ค.
def butterfly_parallel_max[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Parallel maximum reduction using butterfly pattern.
Uses shuffle_xor with decreasing offsets starting from WARP_SIZE/2 down to 1.
Each step reduces the active range by half until all threads have the maximum value.
This implements an efficient O(log n) parallel reduction algorithm that works
for any WARP_SIZE (32, 64, etc.).
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 7 lines)
ํ
1. ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ดํดํ๊ธฐ
๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ์ด์ง ํธ๋ฆฌ ํต์ ํจํด์ ์์ฑํฉ๋๋ค. ๊ฐ ๋จ๊ณ์์ ๋ฌธ์ ํฌ๊ธฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ค์ด๋ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋ณด์ธ์.
ํต์ฌ ์ง๋ฌธ:
- ์ต๋ ๋ฒ์๋ฅผ ์ปค๋ฒํ๋ ค๋ฉด ์์ offset์ด ์ผ๋ง์ฌ์ผ ํ๋์?
- ๋จ๊ณ ์ฌ์ด์ ์คํ์ ์ ์ด๋ป๊ฒ ๋ณ๊ฒฝํด์ผ ํ๋์?
- ์ธ์ ๋ฆฌ๋์ ์ ๋ฉ์ถฐ์ผ ํ๋์?
ํํธ: โ๋ฒํฐํ๋ผ์ดโ๋ผ๋ ์ด๋ฆ์ ํต์ ํจํด์์ ์ ๋ํฉ๋๋ค - ์์ ์์ ์ ๋ํด ์ง์ ๊ทธ๋ ค๋ณด์ธ์.
2. XOR ๋ฆฌ๋์ ํน์ฑ
XOR์ ๊ฐ ๋จ๊ณ์์ ๊ฒน์น์ง ์๋ ํต์ ํ์ด๋ฅผ ์์ฑํฉ๋๋ค. ์ด๊ฒ์ด ๋ณ๋ ฌ ๋ฆฌ๋์ ์์ ์ ์ค์ํ์ง ์๊ฐํด ๋ณด์ธ์.
์๊ฐํด ๋ณด์ธ์:
- ์๋ก ๋ค๋ฅธ ์คํ์ ์ผ๋ก์ XOR์ด ์ด๋ป๊ฒ ๋ค๋ฅธ ํต์ ํจํด์ ๋ง๋๋์?
- ๊ฐ์ ๋จ๊ณ์์ ๋ ์ธ๋ค์ด ์ ์๋ก ๊ฐ์ญํ์ง ์๋์?
- XOR์ด ํธ๋ฆฌ ๋ฆฌ๋์ ์ ํนํ ์ ํฉํ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
3. ์ต๋๊ฐ ๋์
๊ฐ ๋ ์ธ์ ์์ ์ โ์์ญโ์์ ์ต๋๊ฐ์ ์ง์์ ์ ์ง์ ์ผ๋ก ์์๊ฐ์ผ ํฉ๋๋ค.
์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ:
- ์์ ์ ๊ฐ์ผ๋ก ์์
- ๊ฐ ๋จ๊ณ์์ ์ด์์ ๊ฐ๊ณผ ๋น๊ต
- ์ต๋๊ฐ์ ์ ์งํ๊ณ ๊ณ์ ์งํ
ํต์ฌ ํต์ฐฐ: ๊ฐ ๋จ๊ณ ํ, โ์ง์์ ์์ญโ์ด ๋ ๋ฐฐ๋ก ํ์ฅ๋ฉ๋๋ค.
- ๋ง์ง๋ง ๋จ๊ณ ํ: ๊ฐ ๋ ์ธ์ด ์ ์ญ ์ต๋๊ฐ์ ์๊ฒ ๋ฉ๋๋ค
4. ์ด ํจํด์ด ๋์ํ๋ ์ด์
๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ \(\log_2(\text{WARP_SIZE})\) ๋จ๊ณ ํ์ ๋ค์์ ๋ณด์ฅํฉ๋๋ค:
- ๋ชจ๋ ๋ ์ธ์ด ๋ค๋ฅธ ๋ชจ๋ ๋ ์ธ์ ๊ฐ์ ๊ฐ์ ์ ์ผ๋ก ํ์ธ
- ์ค๋ณต ํต์ ์์: ๊ฐ ํ์ด๊ฐ ๋จ๊ณ๋น ์ ํํ ํ ๋ฒ ๊ตํ
- ์ต์ ๋ณต์ก๋: \(O(n)\) ์์ฐจ ๋น๊ต ๋์ \(O(\log n)\) ๋จ๊ณ
์ถ์ ์์ (4๊ฐ ๋ ์ธ, ๊ฐ [3, 1, 7, 2]):
์ด๊ธฐ ์ํ: Lane 0=3, Lane 1=1, Lane 2=7, Lane 3=2
1๋จ๊ณ (offset=2): 0 โ 2, 1 โ 3
Lane 0: max(3, 7) = 7
Lane 1: max(1, 2) = 2
Lane 2: max(7, 3) = 7
Lane 3: max(2, 1) = 2
2๋จ๊ณ (offset=1): 0 โ 1, 2 โ 3
Lane 0: max(7, 2) = 7
Lane 1: max(2, 7) = 7
Lane 2: max(7, 2) = 7
Lane 3: max(2, 7) = 7
๊ฒฐ๊ณผ: ๋ชจ๋ ๋ ์ธ์ด ์ ์ญ ์ต๋๊ฐ = 7์ ๊ฐ์ง
๋ฒํฐํ๋ผ์ด ๋ณ๋ ฌ ์ต๋๊ฐ ํ ์คํธ:
pixi run p26 --parallel-max
pixi run -e amd p26 --parallel-max
uv run poe p26 --parallel-max
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: [1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0]
expected: [1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0]
โ
Butterfly parallel max test passed!
์๋ฃจ์
def butterfly_parallel_max[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Parallel maximum reduction using butterfly pattern.
Uses shuffle_xor with decreasing offsets (16, 8, 4, 2, 1) to perform tree-based reduction.
Each step reduces the active range by half until all threads have the maximum value.
This implements an efficient O(log n) parallel reduction algorithm.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
if global_i < size:
var max_val = input[global_i]
# Butterfly reduction tree: dynamic for any WARP_SIZE (32, 64, etc.)
# Start with half the warp size and reduce by half each step
var offset = WARP_SIZE // 2
while offset > 0:
max_val = max(max_val, shuffle_xor(max_val, UInt32(offset)))
offset //= 2
# All threads now have the maximum value across the entire warp
output[global_i] = max_val
์ด ํ์ด๋ shuffle_xor()์ด \(O(\log n)\) ๋ณต์ก๋์ ํจ์จ์ ์ธ ๋ณ๋ ฌ ๋ฆฌ๋์
ํธ๋ฆฌ๋ฅผ ์ด๋ป๊ฒ ์์ฑํ๋์ง ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
max_val = input[global_i] # ๋ก์ปฌ ๊ฐ์ผ๋ก ์์
# ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
ํธ๋ฆฌ: ๋ชจ๋ WARP_SIZE์ ๋์ ์ผ๋ก ๋์
offset = WARP_SIZE // 2
while offset > 0:
max_val = max(max_val, shuffle_xor(max_val, offset))
offset //= 2
output[global_i] = max_val # ๋ชจ๋ ๋ ์ธ์ด ์ ์ญ ์ต๋๊ฐ์ ๊ฐ์ง
๋ฒํฐํ๋ผ์ด ์คํ ์ถ์ (8-๋ ์ธ ์์ , ๊ฐ [0,2,4,6,8,10,12,1000]):
์ด๊ธฐ ์ํ:
Lane 0: max_val = 0, Lane 1: max_val = 2
Lane 2: max_val = 4, Lane 3: max_val = 6
Lane 4: max_val = 8, Lane 5: max_val = 10
Lane 6: max_val = 12, Lane 7: max_val = 1000
1๋จ๊ณ: shuffle_xor(max_val, 4) - ์ ๋ฐ ๊ตํ
Lane 0โ4: max(0,8)=8, Lane 1โ5: max(2,10)=10
Lane 2โ6: max(4,12)=12, Lane 3โ7: max(6,1000)=1000
Lane 4โ0: max(8,0)=8, Lane 5โ1: max(10,2)=10
Lane 6โ2: max(12,4)=12, Lane 7โ3: max(1000,6)=1000
2๋จ๊ณ: shuffle_xor(max_val, 2) - 1/4 ๊ตํ
Lane 0โ2: max(8,12)=12, Lane 1โ3: max(10,1000)=1000
Lane 2โ0: max(12,8)=12, Lane 3โ1: max(1000,10)=1000
Lane 4โ6: max(8,12)=12, Lane 5โ7: max(10,1000)=1000
Lane 6โ4: max(12,8)=12, Lane 7โ5: max(1000,10)=1000
3๋จ๊ณ: shuffle_xor(max_val, 1) - ํ์ด ๊ตํ
Lane 0โ1: max(12,1000)=1000, Lane 1โ0: max(1000,12)=1000
Lane 2โ3: max(12,1000)=1000, Lane 3โ2: max(1000,12)=1000
Lane 4โ5: max(12,1000)=1000, Lane 5โ4: max(1000,12)=1000
Lane 6โ7: max(12,1000)=1000, Lane 7โ6: max(1000,12)=1000
์ต์ข
๊ฒฐ๊ณผ: ๋ชจ๋ ๋ ์ธ์ max_val = 1000
์ํ์ ํต์ฐฐ: ๋ฒํฐํ๋ผ์ด ํต์ ์ผ๋ก ๋ณ๋ ฌ ๋ฆฌ๋์ ์ฐ์ฐ์๋ฅผ ๊ตฌํํฉ๋๋ค: \[\Large \text{Reduce}(\oplus, [a_0, a_1, \ldots, a_{n-1}]) = a_0 \oplus a_1 \oplus \cdots \oplus a_{n-1}\]
์ฌ๊ธฐ์ \(\oplus\)๋ max ์ฐ์ฐ์ด๋ฉฐ, ๋ฒํฐํ๋ผ์ด ํจํด์ด ์ต์ \(O(\log n)\)
๋ณต์ก๋๋ฅผ ๋ณด์ฅํฉ๋๋ค.
๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ด ์ฐ์ํ ์ด์ :
- ๋ก๊ทธ ๋ณต์ก๋: ์์ฐจ ๋ฆฌ๋์ ์ \(O(n)\)์ ๋นํด \(O(\log n)\)
- ์๋ฒฝํ ๋ถํ ๋ถ์ฐ: ๋ชจ๋ ๋ ์ธ์ด ๊ฐ ๋จ๊ณ์์ ๋๋ฑํ๊ฒ ์ฐธ์ฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ณ๋ชฉ ์์: ์์ ๋ ์ง์คํฐ ๊ฐ ํต์
- ํ๋์จ์ด ์ต์ ํ: GPU ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ์ ์ง์ ๋งคํ
์ฑ๋ฅ ํน์ฑ:
- ๋จ๊ณ ์: \(\log_2(\text{WARP_SIZE})\) (์: 32-์ค๋ ๋ ์ํ๋ 5๋จ๊ณ, 64-์ค๋ ๋ ์ํ๋ 6๋จ๊ณ)
- ๋จ๊ณ๋น ์ง์ฐ ์๊ฐ: 1 ์ฌ์ดํด (๋ ์ง์คํฐ ๊ตํ + ๋น๊ต)
- ์ด ์ง์ฐ ์๊ฐ: ์์ฐจ ๋ฐฉ์์ \((\text{WARP_SIZE}-1)\) ์ฌ์ดํด ๋๋น \(\log_2(\text{WARP_SIZE})\) ์ฌ์ดํด
- ๋ณ๋ ฌ์ฑ: ์๊ณ ๋ฆฌ์ฆ ์ ์ฒด์์ ๋ชจ๋ ๋ ์ธ์ด ํ์ฑ ์ํ
3. ๋ฒํฐํ๋ผ์ด ์กฐ๊ฑด๋ถ ์ต๋๊ฐ
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE_2 = 64(๋ฉํฐ ๋ธ๋ก ์๋๋ฆฌ์ค) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
BLOCKS_PER_GRID_2 = (2, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
THREADS_PER_BLOCK_2 = (WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
์ง์ ๋ ์ธ์ ์ต๋๊ฐ์, ํ์ ๋ ์ธ์ ์ต์๊ฐ์ ์ ์ฅํ๋ ์กฐ๊ฑด๋ถ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ์ต๋๊ฐ๊ณผ ์ต์๊ฐ ๋ชจ๋์ ๋ํด ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ์ํํ ํ, ๋ ์ธ ํ์ง์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ๋ก ์ถ๋ ฅํฉ๋๋ค: \[\Large \text{output}[i] = \begin{cases} \max_{j=0}^{\text{WARP_SIZE}-1} \text{input}[j] & \text{if} i \bmod 2 = 0 \\ \min_{j=0}^{\text{WARP_SIZE}-1} \text{input}[j] & \text{if } i \bmod 2 = 1 \end{cases}\]
์ด์ค ๋ฆฌ๋์ ํจํด: ๋ฒํฐํ๋ผ์ด ํธ๋ฆฌ๋ฅผ ํตํด ์ต๋๊ฐ๊ณผ ์ต์๊ฐ์ ๋์์ ์ถ์ ํ ํ, ๋ ์ธ ID ํ์ง์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ๋ก ์ถ๋ ฅํฉ๋๋ค. ์ด๋ ๋ฒํฐํ๋ผ์ด ํจํด์ด ๋ณต์กํ ๋ค์ค ๊ฐ ๋ฆฌ๋์ ์ผ๋ก ์ด๋ป๊ฒ ํ์ฅ๋๋์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค.
comptime SIZE_2 = 64
comptime BLOCKS_PER_GRID_2 = (2, 1)
comptime THREADS_PER_BLOCK_2 = (WARP_SIZE, 1)
comptime layout_2 = row_major[SIZE_2]()
comptime LayoutType_2 = type_of(layout_2)
def butterfly_conditional_max[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType_2, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType_2, ImmutAnyOrigin],
):
"""
Conditional butterfly maximum: Perform butterfly max reduction, but only store result
in even-numbered lanes. Odd-numbered lanes store the minimum value seen.
Demonstrates conditional logic combined with butterfly communication patterns.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = lane_id()
if global_i < size:
var current_val = input[global_i]
var min_val = current_val
# FILL ME IN (roughly 11 lines)
ํ
1. ์ด์ค ์ถ์ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
์ด ํผ์ฆ์ ๋ฒํฐํ๋ผ์ด ํธ๋ฆฌ๋ฅผ ํตํด ๋ ๊ฐ์ง ๋ค๋ฅธ ๊ฐ์ ๋์์ ์ถ์ ํด์ผ ํฉ๋๋ค. ์ฌ๋ฌ ๋ฆฌ๋์ ์ ๋ณ๋ ฌ๋ก ์คํํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋ณด์ธ์.
ํต์ฌ ์ง๋ฌธ:
- ๋ฆฌ๋์ ๊ณผ์ ์์ ์ต๋๊ฐ๊ณผ ์ต์๊ฐ์ ์ด๋ป๊ฒ ๋์์ ์ ์งํ ์ ์๋์?
- ๋ ์ฐ์ฐ์ ๊ฐ์ ๋ฒํฐํ๋ผ์ด ํจํด์ ์ฌ์ฉํ ์ ์๋์?
- ์ด๋ค ๋ณ์๋ฅผ ์ถ์ ํด์ผ ํ๋์?
2. ์กฐ๊ฑด๋ถ ์ถ๋ ฅ ๋ก์ง
๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ์๋ฃํ ํ, ๋ ์ธ ํ์ง์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ฐ์ ์ถ๋ ฅํด์ผ ํฉ๋๋ค.
๊ณ ๋ คํ ์ :
- ๋ ์ธ์ด ์ง์์ธ์ง ํ์์ธ์ง ์ด๋ป๊ฒ ํ๋ณํ๋์?
- ์ด๋ค ๋ ์ธ์ด ์ต๋๊ฐ์, ์ด๋ค ๋ ์ธ์ด ์ต์๊ฐ์ ์ถ๋ ฅํด์ผ ํ๋์?
- ๋ ์ธ ID์ ์ด๋ป๊ฒ ์ ๊ทผํ๋์?
3. min๊ณผ max ๋์ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
์ด ๊ณผ์ ์ ํต์ฌ์ ๊ฐ์ ๋ฒํฐํ๋ผ์ด ํต์ ํจํด์ผ๋ก min๊ณผ max๋ฅผ ํจ์จ์ ์ผ๋ก ๋ณ๋ ฌ ๊ณ์ฐํ๋ ๊ฒ์ ๋๋ค.
์๊ฐํด ๋ณด์ธ์:
- min๊ณผ max์ ๋ณ๋์ ์ ํ ์ฐ์ฐ์ด ํ์ํ๊ฐ์?
- ๋ ์ฐ์ฐ์ ๊ฐ์ ์ด์ ๊ฐ์ ์ฌ์ฌ์ฉํ ์ ์๋์?
- ๋ ๋ฆฌ๋์ ๋ชจ๋ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ฃ๋๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ๋์?
4. ๋ฉํฐ ๋ธ๋ก ๊ฒฝ๊ณ ๊ณ ๋ ค์ฌํญ
์ด ํผ์ฆ์ ์ฌ๋ฌ ๋ธ๋ก์ ์ฌ์ฉํฉ๋๋ค. ์ด๊ฒ์ด ๋ฆฌ๋์ ๋ฒ์์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง ์๊ฐํด ๋ณด์ธ์.
์ค์ํ ๊ณ ๋ ค์ฌํญ:
- ๊ฐ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ๋ฒ์๋ ์ด๋๊น์ง์ธ๊ฐ์?
- ๋ธ๋ก ๊ตฌ์กฐ๊ฐ ๋ ์ธ ๋ฒํธ ๋งค๊ธฐ๊ธฐ์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์?
- ์ ์ญ min/max๋ฅผ ๊ณ์ฐํ๋์, ๋ธ๋ก๋ณ min/max๋ฅผ ๊ณ์ฐํ๋์?
๋ฒํฐํ๋ผ์ด ์กฐ๊ฑด๋ถ ์ต๋๊ฐ ํ ์คํธ:
pixi run p26 --conditional-max
pixi run -e amd p26 --conditional-max
uv run poe p26 --conditional-max
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE_2: 64
output: [9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0]
expected: [9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 9.0, 0.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0, 63.0, 32.0]
โ
Butterfly conditional max test passed!
์๋ฃจ์
def butterfly_conditional_max[
size: Int
](
output: TileTensor[mut=True, dtype, Layout2Type, MutAnyOrigin],
input: TileTensor[mut=False, dtype, Layout2Type, ImmutAnyOrigin],
):
"""
Conditional butterfly maximum: Perform butterfly max reduction, but only store result
in even-numbered lanes. Odd-numbered lanes store the minimum value seen.
Demonstrates conditional logic combined with butterfly communication patterns.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var lane = lane_id()
if global_i < size:
var current_val = input[global_i]
var min_val = current_val
# Butterfly reduction for both maximum and minimum: dynamic for any WARP_SIZE
var offset = WARP_SIZE // 2
while offset > 0:
var neighbor_val = shuffle_xor(current_val, UInt32(offset))
current_val = max(current_val, neighbor_val)
var min_neighbor_val = shuffle_xor(min_val, UInt32(offset))
min_val = min(min_val, min_neighbor_val)
offset //= 2
# Conditional output: max for even lanes, min for odd lanes
if lane % 2 == 0:
output[global_i] = current_val # Maximum
else:
output[global_i] = min_val # Minimum
์ด ํ์ด๋ ์ด์ค ์ถ์ ๊ณผ ์กฐ๊ฑด๋ถ ์ถ๋ ฅ์ ์ฌ์ฉํ๋ ๊ณ ๊ธ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
current_val = input[global_i]
min_val = current_val # ์ต์๊ฐ์ ๋ณ๋๋ก ์ถ์
# max์ min ๋์ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
(log_2(WARP_SIZE) ๋จ๊ณ)
offset = WARP_SIZE // 2
while offset > 0:
neighbor_val = shuffle_xor(current_val, offset)
current_val = max(current_val, neighbor_val) # Max ๋ฆฌ๋์
min_neighbor_val = shuffle_xor(min_val, offset)
min_val = min(min_val, min_neighbor_val) # Min ๋ฆฌ๋์
offset //= 2
# ๋ ์ธ ํ์ง์ ๋ฐ๋ฅธ ์กฐ๊ฑด๋ถ ์ถ๋ ฅ
if lane % 2 == 0:
output[global_i] = current_val # ์ง์ ๋ ์ธ: ์ต๋๊ฐ
else:
output[global_i] = min_val # ํ์ ๋ ์ธ: ์ต์๊ฐ
์ด์ค ๋ฆฌ๋์ ์คํ ์ถ์ (4-๋ ์ธ ์์ , ๊ฐ [3, 1, 7, 2]):
์ด๊ธฐ ์ํ:
Lane 0: current_val=3, min_val=3
Lane 1: current_val=1, min_val=1
Lane 2: current_val=7, min_val=7
Lane 3: current_val=2, min_val=2
1๋จ๊ณ: shuffle_xor(current_val, 2)์ shuffle_xor(min_val, 2) - ์ ๋ฐ ๊ตํ
Lane 0โ2: max_neighbor=7, min_neighbor=7 โ current_val=max(3,7)=7, min_val=min(3,7)=3
Lane 1โ3: max_neighbor=2, min_neighbor=2 โ current_val=max(1,2)=2, min_val=min(1,2)=1
Lane 2โ0: max_neighbor=3, min_neighbor=3 โ current_val=max(7,3)=7, min_val=min(7,3)=3
Lane 3โ1: max_neighbor=1, min_neighbor=1 โ current_val=max(2,1)=2, min_val=min(2,1)=1
2๋จ๊ณ: shuffle_xor(current_val, 1)์ shuffle_xor(min_val, 1) - ํ์ด ๊ตํ
Lane 0โ1: max_neighbor=2, min_neighbor=1 โ current_val=max(7,2)=7, min_val=min(3,1)=1
Lane 1โ0: max_neighbor=7, min_neighbor=3 โ current_val=max(2,7)=7, min_val=min(1,3)=1
Lane 2โ3: max_neighbor=2, min_neighbor=1 โ current_val=max(7,2)=7, min_val=min(3,1)=1
Lane 3โ2: max_neighbor=7, min_neighbor=3 โ current_val=max(2,7)=7, min_val=min(1,3)=1
์ต์ข
๊ฒฐ๊ณผ: ๋ชจ๋ ๋ ์ธ์ด current_val=7 (์ ์ญ max)๊ณผ min_val=1 (์ ์ญ min)์ ๊ฐ์ง
๋์ ์๊ณ ๋ฆฌ์ฆ (๋ชจ๋ WARP_SIZE์์ ๋์):
offset = WARP_SIZE // 2
while offset > 0:
neighbor_val = shuffle_xor(current_val, offset)
current_val = max(current_val, neighbor_val)
min_neighbor_val = shuffle_xor(min_val, offset)
min_val = min(min_val, min_neighbor_val)
offset //= 2
์ํ์ ํต์ฐฐ: ์กฐ๊ฑด๋ถ ๋๋ฉํฐํ๋ ์ฑ์ ์ฌ์ฉํ๋ ์ด์ค ๋ณ๋ ฌ ๋ฆฌ๋์ ์ ๊ตฌํํฉ๋๋ค: \[\Large \begin{align} \text{max_result} &= \max_{i=0}^{n-1} \text{input}[i] \\ \text{min_result} &= \min_{i=0}^{n-1} \text{input}[i] \\ \text{output}[i] &= \text{lane_parity}(i) \; \text{?} \; \text{min_result}: \text{max_result} \end{align}\]
์ด์ค ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ์ด ๋์ํ๋ ์ด์ :
- ๋ ๋ฆฝ์ ๋ฆฌ๋์ : Max์ min ๋ฆฌ๋์ ์ ์ํ์ ์ผ๋ก ๋ ๋ฆฝ
- ๋ณ๋ ฌ ์คํ: ๋ ๋ค ๊ฐ์ ๋ฒํฐํ๋ผ์ด ํต์ ํจํด์ ์ฌ์ฉ ๊ฐ๋ฅ
- ํต์ ๊ณต์ : ๊ฐ์ ์ ํ ์ฐ์ฐ์ด ๋ ๋ฆฌ๋์ ๋ชจ๋์ ํ์ฉ
- ์กฐ๊ฑด๋ถ ์ถ๋ ฅ: ๋ ์ธ ํ์ง์ด ์ด๋ค ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ ์ง ๊ฒฐ์
์ฑ๋ฅ ํน์ฑ:
- ํต์ ๋จ๊ณ: \(\log_2(\text{WARP_SIZE})\) (๋จ์ผ ๋ฆฌ๋์ ๊ณผ ๋์ผ)
- ๋จ๊ณ๋น ์ฐ์ฐ: ๋จ์ผ ๋ฆฌ๋์ ์ 1๊ฐ ๋๋น 2๊ฐ ์ฐ์ฐ (max + min)
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์ ๋๋น ์ค๋ ๋๋น ๋ ์ง์คํฐ 2๊ฐ
- ์ถ๋ ฅ ์ ์ฐ์ฑ: ์๋ก ๋ค๋ฅธ ๋ ์ธ์ด ๋ค๋ฅธ ๋ฆฌ๋์ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅ ๊ฐ๋ฅ
์์ฝ
shuffle_xor() ๊ธฐ๋ณธ ์์๋ ํจ์จ์ ์ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ฐ์ด ๋๋ ๊ฐ๋ ฅํ
๋ฒํฐํ๋ผ์ด ํต์ ํจํด์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ์ธ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ํตํด ๋ค์์ ๋ฐฐ์ ์ต๋๋ค:
ํต์ฌ ๋ฒํฐํ๋ผ์ด ํจํด
-
ํ์ด ๊ตํ (
shuffle_xor(value, 1)):- ์๋ฒฝํ ์ธ์ ํ์ด ์์ฑ: (0,1), (2,3), (4,5), โฆ
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก์ \(O(1)\) ๋ณต์ก๋
- ์ ๋ ฌ ๋คํธ์ํฌ์ ๋ฐ์ดํฐ ์ฌ๋ฐฐ์น์ ๊ธฐ๋ฐ
-
ํธ๋ฆฌ ๋ฆฌ๋์ (๋์ offset:
WARP_SIZE/2โ1):- ๋ก๊ทธ ๋ณ๋ ฌ ๋ฆฌ๋์ : ์์ฐจ์ \(O(n)\) ๋๋น \(O(\log n)\)
- ๋ชจ๋ ๊ฒฐํฉ ์ฐ์ฐ์ ์ ์ฉ ๊ฐ๋ฅ (max, min, sum ๋ฑ)
- ๋ชจ๋ ์ํ ๋ ์ธ์ ๊ฑธ์ณ ์ต์ ์ ๋ถํ ๋ถ์ฐ
-
์กฐ๊ฑด๋ถ ๋ค์ค ๋ฆฌ๋์ (์ด์ค ์ถ์ + ๋ ์ธ ํ์ง):
- ์ฌ๋ฌ ๋ฆฌ๋์ ์ ๋์์ ๋ณ๋ ฌ ์ํ
- ์ค๋ ๋ ํน์ฑ์ ๋ฐ๋ฅธ ์กฐ๊ฑด๋ถ ์ถ๋ ฅ
- ๋ช ์์ ๋๊ธฐํ ์๋ ๊ณ ๊ธ ์กฐ์
ํต์ฌ ์๊ณ ๋ฆฌ์ฆ ํต์ฐฐ
XOR ํต์ ํน์ฑ:
shuffle_xor(value, mask)๊ฐ ๋์นญ์ ์ด๊ณ ๊ฒน์น์ง ์๋ ํ์ด๋ฅผ ์์ฑ- ๊ฐ ๋ง์คํฌ๊ฐ ๊ณ ์ ํ ํต์ ํ ํด๋ก์ง๋ฅผ ์์ฑ
- ์ด์ง XOR ํจํด์์ ๋ฒํฐํ๋ผ์ด ๋คํธ์ํฌ๊ฐ ์์ฐ์ค๋ฝ๊ฒ ๋์ถ
๋์ ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ:
offset = WARP_SIZE // 2
while offset > 0:
neighbor_val = shuffle_xor(current_val, offset)
current_val = operation(current_val, neighbor_val)
offset //= 2
์ฑ๋ฅ ์ด์ :
- ํ๋์จ์ด ์ต์ ํ: ๋ ์ง์คํฐ ๊ฐ ์ง์ ํต์
- ๋๊ธฐํ ๋ถํ์: SIMT ์คํ์ด ์ ํ์ฑ์ ๋ณด์ฅ
- ํ์ฅ ๊ฐ๋ฅํ ๋ณต์ก๋: ๋ชจ๋ WARP_SIZE (32, 64 ๋ฑ)์์ \(O(\log n)\)
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ถํ์
์ค์ฉ์ ํ์ฉ
์ด ๋ฒํฐํ๋ผ์ด ํจํด๋ค์ ๊ธฐ๋ฐ์ด ๋๋ ๋ถ์ผ:
- ๋ณ๋ ฌ ๋ฆฌ๋์ : ํฉ๊ณ, max, min, ๋ ผ๋ฆฌ ์ฐ์ฐ
- ๋์ ํฉ/์ค์บ ์ฐ์ฐ: ๋์ ํฉ, ๋ณ๋ ฌ ์ ๋ ฌ
- FFT ์๊ณ ๋ฆฌ์ฆ: ์ ํธ ์ฒ๋ฆฌ์ ํฉ์ฑ๊ณฑ
- Bitonic ์ ๋ ฌ: ๋ณ๋ ฌ ์ ๋ ฌ ๋คํธ์ํฌ
- ๊ทธ๋ํ ์๊ณ ๋ฆฌ์ฆ: ํธ๋ฆฌ ์ํ์ ์ฐ๊ฒฐ์ฑ
shuffle_xor() ๊ธฐ๋ณธ ์์๋ ๋ณต์กํ ๋ณ๋ ฌ ์กฐ์ ์ ์ฐ์ํ๊ณ ํ๋์จ์ด ์ต์ ํ๋ ํต์
ํจํด์ผ๋ก ๋ณํํ๋ฉฐ, ๋ค์ํ GPU ์ํคํ
์ฒ์์ ํจ์จ์ ์ผ๋ก ํ์ฅ๋ฉ๋๋ค.
warp.prefix_sum() ํ๋์จ์ด ์ต์ ํ ๋ณ๋ ฌ ์ค์บ
์ํ ๋ ๋ฒจ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ์์๋ prefix_sum()์ ์ฌ์ฉํ์ฌ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ
์๊ณ ๋ฆฌ์ฆ์ ํ๋์จ์ด ์ต์ ํ ๊ธฐ๋ณธ ์์๋ก ๋์ฒดํ ์ ์์ต๋๋ค. ์ด ๊ฐ๋ ฅํ ์ฐ์ฐ์ ํตํด
์์ญ ์ค์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ ๋๊ธฐํ ์ฝ๋๊ฐ ํ์ํ์ ํจ์จ์ ์ธ ๋์ ๊ณ์ฐ, ๋ณ๋ ฌ
ํํฐ์
๋, ๊ณ ๊ธ ์กฐ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ ์ ์์ต๋๋ค.
ํต์ฌ ํต์ฐฐ: prefix_sum() ์ฐ์ฐ์ ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ์ ํ์ฉํ์ฌ ์ํ ๋ ์ธ์ ๊ฑธ์ณ \(O(\log n)\) ๋ณต์ก๋๋ก ๋์ ์ฐ์ฐ์ ์ํํ๋ฉฐ, ๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒดํฉ๋๋ค.
๋ณ๋ ฌ ์ค์บ์ด๋? ๋ณ๋ ฌ ์ค์บ (๋์ ํฉ)์ ๋ฐ์ดํฐ ์์์ ๊ฑธ์ณ ๋์ ์ฐ์ฐ์ ์ํํ๋ ๊ธฐ๋ณธ์ ์ธ ๋ณ๋ ฌ ๊ธฐ๋ณธ ์์์ ๋๋ค. ๋ง์ ์ ๊ฒฝ์ฐ
[a, b, c, d]๋ฅผ[a, a+b, a+b+c, a+b+c+d]๋ก ๋ณํํฉ๋๋ค. ์ด ์ฐ์ฐ์ ์คํธ๋ฆผ ์ปดํฉ์ , quicksort ํํฐ์ ๋, ๋ณ๋ ฌ ์ ๋ ฌ ๊ฐ์ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ํ์์ ์ ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
prefix_sum()์ ํ์ฉํ ํ๋์จ์ด ์ต์ ํ ๋ณ๋ ฌ ์ค์บ- ํฌํจ(inclusive) vs ๋นํฌํจ(exclusive) ๋์ ํฉ ํจํด
- ๋ฐ์ดํฐ ์ฌ๋ฐฐ์น๋ฅผ ์ํ ์ํ ๋ ๋ฒจ ์คํธ๋ฆผ ์ปดํฉ์
- ์ฌ๋ฌ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํ ๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ์ ๋
- ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋์ฒดํ๋ ๋จ์ผ ์ํ ์๊ณ ๋ฆฌ์ฆ ์ต์ ํ
์ด๋ฅผ ํตํด ๋ค๋จ๊ณ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ด ์ฐ์ํ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋ณํ๋์ด, ๋ช ์์ ๋๊ธฐํ ์์ด ํจ์จ์ ์ธ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
1. ์ํ ํฌํจ ๋์ ํฉ
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ ์ด์์:
row_major[SIZE]()(1D row-major)
prefix_sum์ ์ด์
๊ธฐ์กด ๋์ ํฉ์ ๋ณต์กํ ๋ค๋จ๊ณ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ด ํ์ํฉ๋๋ค. Puzzle 14: ๋์ ํฉ์์๋ ๋ช ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ก ์ด๋ฅผ ํ๋ค๊ฒ ๊ตฌํํ์ต๋๋ค:
def prefix_sum_simple(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
if global_i < size:
shared[local_i] = a[global_i]
barrier()
var offset = 1
for i in range(Int(log2(Scalar[dtype](TPB)))):
var current_val: output.ElementType = 0
if local_i >= offset and local_i < size:
current_val = shared[local_i - offset] # read
barrier()
if local_i >= offset and local_i < size:
shared[local_i] += current_val
barrier()
offset *= 2
if global_i < size:
output[global_i] = shared[local_i]
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ด ํ์
- ๋ค์ค ๋ฐฐ๋ฆฌ์ด: ๋ณต์กํ ๋ค๋จ๊ณ ๋๊ธฐํ
- ๋ณต์กํ ์ธ๋ฑ์ฑ: ์๋ ์คํธ๋ผ์ด๋ ๊ณ์ฐ๊ณผ ๊ฒฝ๊ณ ๊ฒ์ฌ
- ๋ฎ์ ํ์ฅ์ฑ: ๊ฐ ๋จ๊ณ ์ฌ์ด์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์ํ \(O(\log n)\) ๋จ๊ณ
prefix_sum()์ ์ฌ์ฉํ๋ฉด ๋ณ๋ ฌ ์ค์บ์ด ๊ฐ๋จํด์ง๋๋ค:
# ํ๋์จ์ด ์ต์ ํ ๋ฐฉ์ - ๋จ์ผ ํจ์ ํธ์ถ!
current_val = input[global_i]
scan_result = prefix_sum[exclusive=False](current_val)
output[global_i] = scan_result
prefix_sum์ ์ฅ์ :
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ํ๋์จ์ด ๊ฐ์ ์ฐ์ฐ
- ๋๊ธฐํ ๋ถํ์: ๋จ์ผ ์ํ ๋ฏน ์ฐ์ฐ
- ํ๋์จ์ด ์ต์ ํ: ์ ์ฉ ์ค์บ ์ ๋ ํ์ฉ
- ์๋ฒฝํ ํ์ฅ์ฑ: ๋ชจ๋
WARP_SIZE(32, 64 ๋ฑ)์์ ๋์
์์ฑํ ์ฝ๋
ํ๋์จ์ด ์ต์ ํ prefix_sum() ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ์ฌ ํฌํจ ๋์ ํฉ์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ๊ฐ ๋ ์ธ์ด ์์ ์ ์์น๊น์ง ๋ชจ๋ ์์์ ํฉ์ ํฌํจํ๋ ๋์ ํฉ์ ๊ณ์ฐํฉ๋๋ค: \[\Large \text{output}[i] = \sum_{j=0}^{i} \text{input}[j]\]
์
๋ ฅ ๋ฐ์ดํฐ [1, 2, 3, 4, 5, ...]๋ฅผ ๋์ ํฉ [1, 3, 6, 10, 15, ...]์ผ๋ก
๋ณํํ๋ฉฐ, ๊ฐ ์์น์ ์ด์ ๋ชจ๋ ์์์ ์๊ธฐ ์์ ์ ํฉ์ด ๋ด๊น๋๋ค.
def warp_inclusive_prefix_sum[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Inclusive prefix sum using warp primitive:
Each thread gets sum of all elements up to and including its position.
Compare this to Puzzle 12's complex shared memory + barrier approach.
Puzzle 12 approach:
- Shared memory allocation
- Multiple barrier synchronizations
- Log(n) iterations with manual tree reduction
- Complex multi-phase algorithm
Warp prefix_sum approach:
- Single function call!
- Hardware-optimized parallel scan
- Automatic synchronization
- O(log n) complexity, but implemented in hardware.
NOTE: This implementation only works correctly within a single warp (WARP_SIZE threads).
For multi-warp scenarios, additional coordination would be needed.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
# FILL ME IN (roughly 4 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p26/p26.mojo
ํ
1. prefix_sum ๋งค๊ฐ๋ณ์ ์ดํดํ๊ธฐ
prefix_sum() ํจ์์๋ ์ค์บ ์ ํ์ ์ ์ดํ๋ ์ค์ํ ํ
ํ๋ฆฟ ๋งค๊ฐ๋ณ์๊ฐ ์์ต๋๋ค.
ํต์ฌ ์ง๋ฌธ:
- ํฌํจ ๋์ ํฉ๊ณผ ๋นํฌํจ ๋์ ํฉ์ ์ฐจ์ด๋ ๋ฌด์์ธ๊ฐ์?
- ์ด๋ค ๋งค๊ฐ๋ณ์๊ฐ ์ด ๋์์ ์ ์ดํ๋์?
- ํฌํจ ์ค์บ์์ ๊ฐ ๋ ์ธ์ ๋ฌด์์ ์ถ๋ ฅํด์ผ ํ๋์?
ํํธ: ํจ์ ์๊ทธ๋์ฒ๋ฅผ ๋ณด๊ณ ๋์ ์ฐ์ฐ์์ โํฌํจ(inclusive)โ์ด ๋ฌด์์ ์๋ฏธํ๋์ง ์๊ฐํด ๋ณด์ธ์.
2. ๋จ์ผ ์ํ ์ ํ
์ด ํ๋์จ์ด ๊ธฐ๋ณธ ์์๋ ๋จ์ผ ์ํ ๋ด์์๋ง ๋์ํฉ๋๋ค. ์ด ์ ํ์ ์๋ฏธ๋ฅผ ์๊ฐํด ๋ณด์ธ์.
์๊ฐํด ๋ณด์ธ์:
- ์ฌ๋ฌ ์ํ๊ฐ ์์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์?
- ์ด ์ ํ์ ์ดํดํ๋ ๊ฒ์ด ์ ์ค์ํ๊ฐ์?
- ๋ฉํฐ ์ํ ์๋๋ฆฌ์ค๋ก ํ์ฅํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ๋์?
3. ๋ฐ์ดํฐ ํ์ ๊ณ ๋ ค์ฌํญ
prefix_sum ํจ์๋ ์ต์ ์ฑ๋ฅ์ ์ํด ํน์ ๋ฐ์ดํฐ ํ์
์ ์๊ตฌํ ์ ์์ต๋๋ค.
๊ณ ๋ คํ ์ :
- ์ ๋ ฅ์ด ์ด๋ค ๋ฐ์ดํฐ ํ์ ์ ์ฌ์ฉํ๋์?
prefix_sum์ด ํน์ ์ค์นผ๋ผ ํ์ ์ ๊ธฐ๋ํ๋์?- ํ์ํ ๊ฒฝ์ฐ ํ์ ๋ณํ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์?
์ํ ํฌํจ ๋์ ํฉ ํ ์คํธ:
pixi run p26 --prefix-sum
pixi run -e amd p26 --prefix-sum
pixi run -e apple p26 --prefix-sum
uv run poe p26 --prefix-sum
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: [1.0, 3.0, 6.0, 10.0, 15.0, 21.0, 28.0, 36.0, 45.0, 55.0, 66.0, 78.0, 91.0, 105.0, 120.0, 136.0, 153.0, 171.0, 190.0, 210.0, 231.0, 253.0, 276.0, 300.0, 325.0, 351.0, 378.0, 406.0, 435.0, 465.0, 496.0, 528.0]
expected: [1.0, 3.0, 6.0, 10.0, 15.0, 21.0, 28.0, 36.0, 45.0, 55.0, 66.0, 78.0, 91.0, 105.0, 120.0, 136.0, 153.0, 171.0, 190.0, 210.0, 231.0, 253.0, 276.0, 300.0, 325.0, 351.0, 378.0, 406.0, 435.0, 465.0, 496.0, 528.0]
โ
Warp inclusive prefix sum test passed!
์๋ฃจ์
def warp_inclusive_prefix_sum[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
"""
Inclusive prefix sum using warp primitive: Each thread gets sum of all elements up to and including its position.
Compare this to Puzzle 12's complex shared memory + barrier approach.
Puzzle 12 approach:
- Shared memory allocation
- Multiple barrier synchronizations
- Log(n) iterations with manual tree reduction
- Complex multi-phase algorithm
Warp prefix_sum approach:
- Single function call!
- Hardware-optimized parallel scan
- Automatic synchronization
- O(log n) complexity, but implemented in hardware.
NOTE: This implementation only works correctly within a single warp (WARP_SIZE threads).
For multi-warp scenarios, additional coordination would be needed.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
if global_i < size:
var current_val = input[global_i]
# This one call replaces ~30 lines of complex shared memory logic from Puzzle 12!
# But it only works within the current warp (WARP_SIZE threads)
var scan_result = prefix_sum[exclusive=False](
rebind[Scalar[dtype]](current_val)
)
output[global_i] = scan_result
์ด ์๋ฃจ์
์ prefix_sum()์ด ๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ํ๋์จ์ด ์ต์ ํ๋ ๋จ์ผ
ํจ์ ํธ์ถ๋ก ์ด๋ป๊ฒ ๋์ฒดํ๋์ง ๋ณด์ฌ์ค๋๋ค.
์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
current_val = input[global_i]
# ์ด ํ ์ค์ด Puzzle 14์ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ก์ง ~30์ค์ ๋์ฒดํฉ๋๋ค!
# ๋จ, ํ์ฌ ์ํ (WARP_SIZE ์ค๋ ๋) ๋ด์์๋ง ๋์ํฉ๋๋ค
scan_result = prefix_sum[exclusive=False](
rebind[Scalar[dtype]](current_val)
)
output[global_i] = scan_result
SIMT ์คํ ์์ธ ๋ถ์:
์
๋ ฅ: [1, 2, 3, 4, 5, 6, 7, 8, ...]
์ฌ์ดํด 1: ๋ชจ๋ ๋ ์ธ์ด ๋์์ ๊ฐ์ ๋ก๋
Lane 0: current_val = 1
Lane 1: current_val = 2
Lane 2: current_val = 3
Lane 3: current_val = 4
...
Lane 31: current_val = 32
์ฌ์ดํด 2: prefix_sum[exclusive=False] ์คํ (ํ๋์จ์ด ๊ฐ์)
Lane 0: scan_result = 1 (์์ 0~0์ ํฉ)
Lane 1: scan_result = 3 (์์ 0~1์ ํฉ: 1+2)
Lane 2: scan_result = 6 (์์ 0~2์ ํฉ: 1+2+3)
Lane 3: scan_result = 10 (์์ 0~3์ ํฉ: 1+2+3+4)
...
Lane 31: scan_result = 528 (์์ 0~31์ ํฉ)
์ฌ์ดํด 3: ๊ฒฐ๊ณผ ์ ์ฅ
Lane 0: output[0] = 1
Lane 1: output[1] = 3
Lane 2: output[2] = 6
Lane 3: output[3] = 10
...
์ํ์ ํต์ฐฐ: ํฌํจ ๋์ ํฉ ์ฐ์ฐ์ ๊ตฌํํฉ๋๋ค: \[\Large \text{output}[i] = \sum_{j=0}^{i} \text{input}[j]\]
Puzzle 14 ๋ฐฉ์๊ณผ์ ๋น๊ต:
- Puzzle 14: ๋์ ํฉ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ~30์ค + ๋ค์ค ๋ฐฐ๋ฆฌ์ด + ๋ณต์กํ ์ธ๋ฑ์ฑ
- ์ํ ๊ธฐ๋ณธ ์์: ํ๋์จ์ด ๊ฐ์์ ํจ์ ํธ์ถ 1๊ฐ
- ์ฑ๋ฅ: ๊ฐ์ \(O(\log n)\) ๋ณต์ก๋์ด์ง๋ง, ์ ์ฉ ํ๋์จ์ด์์ ๊ตฌํ
- ๋ฉ๋ชจ๋ฆฌ: ๋ช ์์ ํ ๋น ๋๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ ๋ก
Puzzle 12์์์ ๋ฐ์ : ํ๋ GPU ์ํคํ
์ฒ์ ๊ฐ๋ ฅํจ์ ๋ณด์ฌ์ค๋๋ค - Puzzle
12์์ ์ ์คํ ์๋ ๊ตฌํ์ด ํ์ํ๋ ๊ฒ์ด ์ด์ ๋ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ณธ ์์ ํ๋๋ก
ํด๊ฒฐ๋ฉ๋๋ค. ์ํ ๋ ๋ฒจ prefix_sum()์ ๊ตฌํ ๋ณต์ก๋ ์ ๋ก๋ก ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ ์ด์ ์
์ ๊ณตํฉ๋๋ค.
prefix_sum์ด ์ฐ์ํ ์ด์ :
- ํ๋์จ์ด ๊ฐ์: ํ๋ GPU์ ์ ์ฉ ์ค์บ ์ ๋
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ถํ์
- ์๋ ๋๊ธฐํ: ๋ช ์์ ๋ฐฐ๋ฆฌ์ด ๋ถํ์
- ์๋ฒฝํ ํ์ฅ์ฑ: ๋ชจ๋
WARP_SIZE์์ ์ต์ ์ผ๋ก ๋์
์ฑ๋ฅ ํน์ฑ:
- ์ง์ฐ ์๊ฐ: ~1-2 ์ฌ์ดํด (ํ๋์จ์ด ์ค์บ ์ ๋)
- ๋์ญํญ: ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ ์ ๋ก (๋ ์ง์คํฐ ์ ์ฉ ์ฐ์ฐ)
- ๋ณ๋ ฌ์ฑ:
WARP_SIZE๊ฐ ๋ ์ธ ๋ชจ๋ ๋์์ ์ฐธ์ฌ - ํ์ฅ์ฑ: ํ๋์จ์ด ์ต์ ํ๋ฅผ ๋๋ฐํ \(O(\log n)\) ๋ณต์ก๋
์ค์ํ ์ ํ์ฌํญ: ์ด ๊ธฐ๋ณธ ์์๋ ๋จ์ผ ์ํ ๋ด์์๋ง ๋์ํฉ๋๋ค. ๋ฉํฐ ์ํ ์๋๋ฆฌ์ค์์๋ ์ํ ๊ฐ ์ถ๊ฐ ์กฐ์ ์ด ํ์ํฉ๋๋ค.
2. ์ํ ํํฐ์
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = WARP_SIZE(GPU์ ๋ฐ๋ผ 32 ๋๋ 64) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ธ๋ก ๊ตฌ์ฑ:
(WARP_SIZE, 1)๋ธ๋ก๋น ์ค๋ ๋ ์
์์ฑํ ์ฝ๋
shuffle_xor๊ณผ prefix_sum ๊ธฐ๋ณธ ์์๋ฅผ ๋ชจ๋ ์ฌ์ฉํ์ฌ ๋จ์ผ ์ํ ๋ณ๋ ฌ
ํํฐ์
๋์ ๊ตฌํํฉ๋๋ค.
์ํ์ ์ฐ์ฐ: ํผ๋ฒ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์์๋ฅผ ๋ถํ ํ์ฌ, < pivot์ธ ์์๋ ์ผ์ชฝ์,
>= pivot์ธ ์์๋ ์ค๋ฅธ์ชฝ์ ๋ฐฐ์นํฉ๋๋ค:
\[\Large \text{output} = [\text{elements} < \text{pivot}] \,|\, [\text{elements} \geq \text{pivot}]\]
๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ: ์ด ์๊ณ ๋ฆฌ์ฆ์ ๋ ๊ฐ์ง ์ ๊ตํ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํฉ๋๋ค:
shuffle_xor(): ์ผ์ชฝ ์์ ๊ฐ์๋ฅผ ์ธ๊ธฐ ์ํ ์ํ ๋ ๋ฒจ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ prefix_sum(): ๊ฐ ํํฐ์ ๋ด ์์น ๊ณ์ฐ์ ์ํ ๋นํฌํจ ์ค์บ
์ด๋ ๋จ์ผ ์ํ ๋ด์์ ์ฌ๋ฌ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ๊ฒฐํฉํ์ฌ ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ ๊ฐ๋ ฅํจ์ ๋ณด์ฌ์ค๋๋ค.
def warp_partition[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
pivot: Float32,
):
"""
Single-warp parallel partitioning using BOTH shuffle_xor AND prefix_sum.
This implements a warp-level quicksort partition step that places elements < pivot
on the left and elements >= pivot on the right.
ALGORITHM COMPLEXITY - combines two advanced warp primitives:
1. shuffle_xor(): Butterfly pattern for warp-level reductions
2. prefix_sum(): Warp-level exclusive scan for position calculation.
This demonstrates the power of warp primitives for sophisticated parallel algorithms
within a single warp (works for any WARP_SIZE: 32, 64, etc.).
Example with pivot=5:
Input: [3, 7, 1, 8, 2, 9, 4, 6]
var Result: [3, 1, 2, 4, 7, 8, 9, 6] (< pivot | >= pivot).
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
if global_i < size:
var current_val = input[global_i]
# FILL ME IN (roughly 13 lines)
ํ
1. ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ
์ด ์๊ณ ๋ฆฌ์ฆ์ ์ฌ๋ฌ ์กฐ์ ๋ ๋จ๊ณ๊ฐ ํ์ํฉ๋๋ค. ํํฐ์ ๋์ ํ์ํ ๋ ผ๋ฆฌ์ ๋จ๊ณ๋ฅผ ์๊ฐํด ๋ณด์ธ์.
๊ณ ๋ คํ ํต์ฌ ๋จ๊ณ:
- ์ด๋ค ์์๊ฐ ์ด๋ ํํฐ์ ์ ์ํ๋์ง ์ด๋ป๊ฒ ์๋ณํ๋์?
- ๊ฐ ํํฐ์ ๋ด์์ ์์น๋ฅผ ์ด๋ป๊ฒ ๊ณ์ฐํ๋์?
- ์ผ์ชฝ ํํฐ์ ์ ์ ์ฒด ํฌ๊ธฐ๋ฅผ ์ด๋ป๊ฒ ์ ์ ์๋์?
- ์ต์ข ์์น์ ์์๋ฅผ ์ด๋ป๊ฒ ๊ธฐ๋กํ๋์?
2. ํ๋ ๋์ผ์ดํธ ์์ฑ
์ด๋ ํํฐ์ ์ ์ํ๋์ง ํ๋ณํ๋ ๋ถ๋ฆฌ์ธ ํ๋ ๋์ผ์ดํธ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
์๊ฐํด ๋ณด์ธ์:
- โ์ด ์์๋ ์ผ์ชฝ ํํฐ์ ์ ์ํ๋คโ๋ฅผ ์ด๋ป๊ฒ ํํํ๋์?
- โ์ด ์์๋ ์ค๋ฅธ์ชฝ ํํฐ์ ์ ์ํ๋คโ๋ฅผ ์ด๋ป๊ฒ ํํํ๋์?
prefix_sum์ ์ ๋ฌํ ํ๋ ๋์ผ์ดํธ๋ ์ด๋ค ๋ฐ์ดํฐ ํ์ ์ด์ด์ผ ํ๋์?
3. shuffle_xor๊ณผ prefix_sum ๊ฒฐํฉ
์ด ์๊ณ ๋ฆฌ์ฆ์ ๋ ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ์๋ก ๋ค๋ฅธ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
๊ณ ๋ คํ ์ :
- ์ด ๋งฅ๋ฝ์์
shuffle_xor์ ๋ฌด์์ ์ฌ์ฉ๋๋์? - ์ด ๋งฅ๋ฝ์์
prefix_sum์ ๋ฌด์์ ์ฌ์ฉ๋๋์? - ์ด ๋ ์ฐ์ฐ์ด ์ด๋ป๊ฒ ํจ๊ป ๋์ํ๋์?
4. ์์น ๊ณ์ฐ
๊ฐ์ฅ ๊น๋ค๋ก์ด ๋ถ๋ถ์ ๊ฐ ์์๊ฐ ์ถ๋ ฅ์์ ์ด๋์ ๊ธฐ๋ก๋์ด์ผ ํ๋์ง ๊ณ์ฐํ๋ ๊ฒ์ ๋๋ค.
ํต์ฌ ํต์ฐฐ:
- ์ผ์ชฝ ํํฐ์ ์์: ์ต์ข ์์น๋ฅผ ๋ฌด์์ด ๊ฒฐ์ ํ๋์?
- ์ค๋ฅธ์ชฝ ํํฐ์ ์์: ์คํ์ ์ ์ด๋ป๊ฒ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉํ๋์?
- ๋ก์ปฌ ์์น์ ํํฐ์ ๊ฒฝ๊ณ๋ฅผ ์ด๋ป๊ฒ ๊ฒฐํฉํ๋์?
์ํ ํํฐ์ ํ ์คํธ:
uv run poe p26 --partition
pixi run p26 --partition
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
WARP_SIZE: 32
SIZE: 32
output: HostBuffer([3.0, 1.0, 2.0, 4.0, 0.0, 3.0, 1.0, 4.0, 3.0, 1.0, 2.0, 4.0, 0.0, 3.0, 1.0, 4.0, 7.0, 8.0, 9.0, 6.0, 10.0, 11.0, 12.0, 13.0, 7.0, 8.0, 9.0, 6.0, 10.0, 11.0, 12.0, 13.0])
expected: HostBuffer([3.0, 1.0, 2.0, 4.0, 0.0, 3.0, 1.0, 4.0, 3.0, 1.0, 2.0, 4.0, 0.0, 3.0, 1.0, 4.0, 7.0, 8.0, 9.0, 6.0, 10.0, 11.0, 12.0, 13.0, 7.0, 8.0, 9.0, 6.0, 10.0, 11.0, 12.0, 13.0])
pivot: 5.0
โ
Warp partition test passed!
์๋ฃจ์
def warp_partition[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
pivot: Float32,
):
"""
Single-warp parallel partitioning using BOTH shuffle_xor AND prefix_sum.
This implements a warp-level quicksort partition step that places elements < pivot
on the left and elements >= pivot on the right.
ALGORITHM COMPLEXITY - combines two advanced warp primitives:
1. shuffle_xor(): Butterfly pattern for warp-level reductions
2. prefix_sum(): Warp-level exclusive scan for position calculation.
This demonstrates the power of warp primitives for sophisticated parallel algorithms
within a single warp (works for any WARP_SIZE: 32, 64, etc.).
Example with pivot=5:
Input: [3, 7, 1, 8, 2, 9, 4, 6]
var Result: [3, 1, 2, 4, 7, 8, 9, 6] (< pivot | >= pivot).
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
if global_i < size:
var current_val = input[global_i]
# Phase 1: Create warp-level predicates
var predicate_left = Scalar[dtype](
1.0
) if current_val < pivot else Scalar[dtype](0.0)
var predicate_right = Scalar[dtype](
1.0
) if current_val >= pivot else Scalar[dtype](0.0)
# Phase 2: Warp-level prefix sum to get positions within warp
var warp_left_pos = prefix_sum[exclusive=True](predicate_left)
var warp_right_pos = prefix_sum[exclusive=True](predicate_right)
# Phase 3: Get total left count using shuffle_xor reduction
var warp_left_total = predicate_left
# Butterfly reduction to get total across the warp: dynamic for any WARP_SIZE
var offset = WARP_SIZE // 2
while offset > 0:
warp_left_total += shuffle_xor(warp_left_total, UInt32(offset))
offset //= 2
# Phase 4: Write to output positions
if current_val < pivot:
# Left partition: use warp-level position
output[Int(warp_left_pos)] = current_val
else:
# Right partition: offset by total left count + right position
output[Int(warp_left_total + warp_right_pos)] = current_val
์ด ์๋ฃจ์ ์ ์ฌ๋ฌ ์ํ ๊ธฐ๋ณธ ์์ ๊ฐ์ ๊ณ ๊ธ ์กฐ์ ์ ํตํด ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
์ ์ฒด ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
if global_i < size:
current_val = input[global_i]
# 1๋จ๊ณ: ์ํ ๋ ๋ฒจ ํ๋ ๋์ผ์ดํธ ์์ฑ
predicate_left = Float32(1.0) if current_val < pivot else Float32(0.0)
predicate_right = Float32(1.0) if current_val >= pivot else Float32(0.0)
# 2๋จ๊ณ: ์ํ ๋ ๋ฒจ ๋์ ํฉ์ผ๋ก ์ํ ๋ด ์์น ๊ณ์ฐ
warp_left_pos = prefix_sum[exclusive=True](predicate_left)
warp_right_pos = prefix_sum[exclusive=True](predicate_right)
# 3๋จ๊ณ: shuffle_xor ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
์ผ๋ก ์ผ์ชฝ ์ด ๊ฐ์ ๊ตฌํ๊ธฐ
warp_left_total = predicate_left
# ์ํ ์ ์ฒด์ ํฉ์ฐ์ ์ํ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
: ๋ชจ๋ WARP_SIZE์ ๋์ ๋์
offset = WARP_SIZE // 2
while offset > 0:
warp_left_total += shuffle_xor(warp_left_total, offset)
offset //= 2
# 4๋จ๊ณ: ์ถ๋ ฅ ์์น์ ๊ธฐ๋ก
if current_val < pivot:
# ์ผ์ชฝ ํํฐ์
: ์ํ ๋ ๋ฒจ ์์น ์ฌ์ฉ
output[Int(warp_left_pos)] = current_val
else:
# ์ค๋ฅธ์ชฝ ํํฐ์
: ์ผ์ชฝ ์ด ๊ฐ์ + ์ค๋ฅธ์ชฝ ์์น๋ก offset
output[Int(warp_left_total + warp_right_pos)] = current_val
๋ค๋จ๊ณ ์คํ ์ถ์ (8-๋ ์ธ ์์ , pivot=5, ๊ฐ [3,7,1,8,2,9,4,6]):
์ด๊ธฐ ์ํ:
Lane 0: current_val=3 (< 5) Lane 1: current_val=7 (>= 5)
Lane 2: current_val=1 (< 5) Lane 3: current_val=8 (>= 5)
Lane 4: current_val=2 (< 5) Lane 5: current_val=9 (>= 5)
Lane 6: current_val=4 (< 5) Lane 7: current_val=6 (>= 5)
1๋จ๊ณ: ํ๋ ๋์ผ์ดํธ ์์ฑ
Lane 0: predicate_left=1.0, predicate_right=0.0
Lane 1: predicate_left=0.0, predicate_right=1.0
Lane 2: predicate_left=1.0, predicate_right=0.0
Lane 3: predicate_left=0.0, predicate_right=1.0
Lane 4: predicate_left=1.0, predicate_right=0.0
Lane 5: predicate_left=0.0, predicate_right=1.0
Lane 6: predicate_left=1.0, predicate_right=0.0
Lane 7: predicate_left=0.0, predicate_right=1.0
2๋จ๊ณ: ์์น ๊ณ์ฐ์ ์ํ ๋นํฌํจ ๋์ ํฉ
warp_left_pos: [0, 0, 1, 1, 2, 2, 3, 3]
warp_right_pos: [0, 0, 0, 1, 1, 2, 2, 3]
3๋จ๊ณ: ์ผ์ชฝ ์ด ๊ฐ์๋ฅผ ์ํ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
์ด๊ธฐ๊ฐ: [1, 0, 1, 0, 1, 0, 1, 0]
๋ฆฌ๋์
ํ: ๋ชจ๋ ๋ ์ธ์ด warp_left_total = 4๋ฅผ ๊ฐ์ง
4๋จ๊ณ: ์ถ๋ ฅ ์์น์ ๊ธฐ๋ก
Lane 0: current_val=3 < pivot โ output[0] = 3
Lane 1: current_val=7 >= pivot โ output[4+0] = output[4] = 7
Lane 2: current_val=1 < pivot โ output[1] = 1
Lane 3: current_val=8 >= pivot โ output[4+1] = output[5] = 8
Lane 4: current_val=2 < pivot โ output[2] = 2
Lane 5: current_val=9 >= pivot โ output[4+2] = output[6] = 9
Lane 6: current_val=4 < pivot โ output[3] = 4
Lane 7: current_val=6 >= pivot โ output[4+3] = output[7] = 6
์ต์ข
๊ฒฐ๊ณผ: [3, 1, 2, 4, 7, 8, 9, 6] (< pivot | >= pivot)
์ํ์ ํต์ฐฐ: ์ด์ค ์ํ ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ ๋ณ๋ ฌ ํํฐ์ ๋์ ๊ตฌํํฉ๋๋ค: \[\Large \begin{align} \text{left_pos}[i] &= \text{prefix_sum}_{\text{exclusive}}(\text{predicate_left}[i]) \\ \text{right_pos}[i] &= \text{prefix_sum}_{\text{exclusive}}(\text{predicate_right}[i]) \\ \text{left_total} &= \text{butterfly_reduce}(\text{predicate_left}) \\ \text{final_pos}[i] &= \begin{cases} \text{left_pos}[i] & \text{if } \text{input}[i] < \text{pivot} \\ \text{left_total} + \text{right_pos}[i] & \text{if} \text{input}[i] \geq \text{pivot} \end{cases} \end{align}\]
๋ค์ค ๊ธฐ๋ณธ ์์ ์ ๊ทผ ๋ฐฉ์์ด ๋์ํ๋ ์ด์ :
- ํ๋ ๋์ผ์ดํธ ์์ฑ: ๊ฐ ์์์ ํํฐ์ ์์์ ์๋ณ
- ๋นํฌํจ ๋์ ํฉ: ๊ฐ ํํฐ์ ๋ด ์๋์ ์์น๋ฅผ ๊ณ์ฐ
- ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ : ํํฐ์ ๊ฒฝ๊ณ (์ผ์ชฝ ์ด ๊ฐ์)๋ฅผ ์ฐ์ถ
- ์กฐ์ ๋ ๊ธฐ๋ก: ๋ก์ปฌ ์์น์ ์ ์ญ ํํฐ์ ๊ตฌ์กฐ๋ฅผ ๊ฒฐํฉ
์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋:
- 1๋จ๊ณ: \(O(1)\) - ํ๋ ๋์ผ์ดํธ ์์ฑ
- 2๋จ๊ณ: \(O(\log n)\) - ํ๋์จ์ด ๊ฐ์ ๋์ ํฉ
- 3๋จ๊ณ: \(O(\log n)\) -
shuffle_xor์ ํ์ฉํ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ - 4๋จ๊ณ: \(O(1)\) - ์กฐ์ ๋ ๊ธฐ๋ก
- ์ ์ฒด: ์ฐ์ํ ์์๋ฅผ ๊ฐ์ง \(O(\log n)\)
์ฑ๋ฅ ํน์ฑ:
- ํต์ ๋จ๊ณ: \(2 \times \log_2(\text{WARP_SIZE})\) (๋์ ํฉ + ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ )
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๋ก, ๋ชจ๋ ๋ ์ง์คํฐ ๊ธฐ๋ฐ
- ๋ณ๋ ฌ์ฑ: ์๊ณ ๋ฆฌ์ฆ ์ ์ฒด์์ ๋ชจ๋ ๋ ์ธ์ด ํ์ฑ ์ํ
- ํ์ฅ์ฑ: ๋ชจ๋
WARP_SIZE(32, 64 ๋ฑ)์์ ๋์
์ค์ฉ์ ํ์ฉ: ์ด ํจํด์ ๊ธฐ๋ฐ์ด ๋๋ ๋ถ์ผ:
- Quicksort ํํฐ์ ๋: ๋ณ๋ ฌ ์ ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ํต์ฌ ๋จ๊ณ
- ์คํธ๋ฆผ ์ปดํฉ์ : ๋ฐ์ดํฐ ์คํธ๋ฆผ์์ null/๋ฌดํจ ์์ ์ ๊ฑฐ
- ๋ณ๋ ฌ ํํฐ๋ง: ๋ณต์กํ ํ๋ ๋์ผ์ดํธ์ ๋ฐ๋ฅธ ๋ฐ์ดํฐ ๋ถ๋ฆฌ
- ๋ถํ ๋ถ์ฐ: ์ฐ์ฐ ์๊ตฌ๋์ ๋ฐ๋ฅธ ์์ ์ฌ๋ถ๋ฐฐ
์์ฝ
prefix_sum() ๊ธฐ๋ณธ ์์๋ ๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒดํ๋
ํ๋์จ์ด ๊ฐ์ ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ๋ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ํตํด ๋ค์์
๋ฐฐ์ ์ต๋๋ค:
ํต์ฌ ๋์ ํฉ ํจํด
-
ํฌํจ ๋์ ํฉ (
prefix_sum[exclusive=False]):- ํ๋์จ์ด ๊ฐ์ ๋์ ์ฐ์ฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๋ ~30์ค์ ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒด
- ์ ์ฉ ํ๋์จ์ด ์ต์ ํ๋ฅผ ๋๋ฐํ \(O(\log n)\) ๋ณต์ก๋
-
๊ณ ๊ธ ๋ค์ค ๊ธฐ๋ณธ ์์ ์กฐ์ (
prefix_sum+shuffle_xor๊ฒฐํฉ):- ๋จ์ผ ์ํ ๋ด ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ
- ์์น ๊ณ์ฐ์ ์ํ ๋นํฌํจ ์ค์บ + ์ดํฉ์ ์ํ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
- ์ต์ ์ ๋ณ๋ ฌ ํจ์จ์ฑ์ ๊ฐ์ง ๋ณต์กํ ํํฐ์ ๋ ์ฐ์ฐ
ํต์ฌ ์๊ณ ๋ฆฌ์ฆ ํต์ฐฐ
ํ๋์จ์ด ๊ฐ์์ ์ด์ :
prefix_sum()์ด ํ๋ GPU์ ์ ์ฉ ์ค์บ ์ ๋์ ํ์ฉ- ๊ธฐ์กด ๋ฐฉ์ ๋๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ ๋ก
- ๋ช ์์ ๋ฐฐ๋ฆฌ์ด ์๋ ์๋ ๋๊ธฐํ
๋ค์ค ๊ธฐ๋ณธ ์์ ์กฐ์ :
# 1๋จ๊ณ: ํํฐ์
์์์ ์ํ ํ๋ ๋์ผ์ดํธ ์์ฑ
predicate = 1.0 if condition else 0.0
# 2๋จ๊ณ: ๋ก์ปฌ ์์น๋ฅผ ์ํ prefix_sum ์ฌ์ฉ
local_pos = prefix_sum[exclusive=True](predicate)
# 3๋จ๊ณ: ์ ์ญ ์ดํฉ์ ์ํ shuffle_xor ์ฌ์ฉ
global_total = butterfly_reduce(predicate)
# 4๋จ๊ณ: ์ต์ข
์์น ๊ฒฐ์ ์ ์ํ ๊ฒฐํฉ
final_pos = local_pos + partition_offset
์ฑ๋ฅ ์ด์ :
- ํ๋์จ์ด ์ต์ ํ: ์ํํธ์จ์ด ๊ตฌํ ๋๋น ์ ์ฉ ์ค์บ ์ ๋
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋๋น ๋ ์ง์คํฐ ์ ์ฉ ์ฐ์ฐ
- ํ์ฅ ๊ฐ๋ฅํ ๋ณต์ก๋: ํ๋์จ์ด ๊ฐ์์ ๋๋ฐํ \(O(\log n)\)
- ๋จ์ผ ์ํ ์ต์ ํ:
WARP_SIZEํ๋ ๋ด ์๊ณ ๋ฆฌ์ฆ์ ์ต์
์ค์ฉ์ ํ์ฉ
์ด ๋์ ํฉ ํจํด๋ค์ ๊ธฐ๋ฐ์ด ๋๋ ๋ถ์ผ:
- ๋ณ๋ ฌ ์ค์บ ์ฐ์ฐ: ๋์ ํฉ, ๋์ ๊ณฑ, min/max ์ค์บ
- ์คํธ๋ฆผ ์ปดํฉ์ : ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ๋ฐ์ดํฐ ์ฌ๋ฐฐ์น
- Quicksort ํํฐ์ ๋: ๋ณ๋ ฌ ์ ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ํต์ฌ ๋น๋ฉ ๋ธ๋ก
- ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ: ๋ถํ ๋ถ์ฐ, ์์ ๋ถ๋ฐฐ, ๋ฐ์ดํฐ ์ฌ๊ตฌ์กฐํ
prefix_sum()๊ณผ shuffle_xor()์ ๊ฒฐํฉ์ ํ๋ GPU ์ํ ๊ธฐ๋ณธ ์์๊ฐ ์ต์ํ์
์ฝ๋ ๋ณต์ก๋์ ์ต์ ์ ์ฑ๋ฅ ํน์ฑ์ผ๋ก ์ ๊ตํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์ด๋ป๊ฒ ๊ตฌํํ ์
์๋์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค.
Puzzle 27: ๋ธ๋ก ์ ์ฒด ํจํด
๊ฐ์
Puzzle 27: ๋ธ๋ก ์ ์ฒด ํจํด์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! ์ด ํผ์ฆ์ GPU ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ ๊ตฌ์ฑ ์์์ธ ๋ธ๋ก ๋ ๋ฒจ ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ์๊ฐํฉ๋๋ค. ์ ์ฒด ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์น ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ ์ ์๊ฒ ํด์ฃผ๋ ์ธ ๊ฐ์ง ํต์ฌ ํต์ ํจํด์ ํ๊ตฌํ๋ฉฐ, ๋ณต์กํ ์๋ ๋๊ธฐํ๋ฅผ ๊ฐ๊ฒฐํ๊ณ ํ๋์จ์ด์ ์ต์ ํ๋ ์ฐ์ฐ์ผ๋ก ๋์ฒดํฉ๋๋ค.
๋ชฉํ: ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด(Puzzle 12)์์ ๋ฒ์ด๋, ์ฌ๋ฌ ์ํ์ ๊ฑธ์น ํ๋์จ์ด ์ต์ ํ ๋ธ๋ก ์ ์ฒด ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ํ์ฉํ๋ ๊ฐ๊ฒฐํ ๋จ์ผ ํจ์ ํธ์ถ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ ํํฉ๋๋ค.
ํต์ฌ ํต์ฐฐ: GPU ์ค๋ ๋ ๋ธ๋ก์ ์ ๊ตํ ํ๋์จ์ด ์กฐ์จ๋ก ์คํ๋ฉ๋๋ค - Mojo์ ๋ธ๋ก ์ฐ์ฐ์ ํฌ๋ก์ค ์ํ ํต์ ๊ณผ ์ ์ฉ ํ๋์จ์ด ์ ๋์ ํ์ฉํ์ฌ ์๋ฒฝํ ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ ๋น๋ฉ ๋ธ๋ก์ ์ ๊ณตํฉ๋๋ค: ๋ฆฌ๋์ (์ ์ฒดโํ๋), ์ค์บ(์ ์ฒดโ๊ฐ๊ฐ), ๋ธ๋ก๋์บ์คํธ(ํ๋โ์ ์ฒด).
๋ฐฐ์ธ ๋ด์ฉ
๋ธ๋ก ๋ ๋ฒจ ํต์ ๋ชจ๋ธ
GPU ์ค๋ ๋ ๋ธ๋ก ๋ด ์ธ ๊ฐ์ง ๊ธฐ๋ณธ ํต์ ํจํด์ ์ดํดํฉ๋๋ค:
GPU ์ค๋ ๋ ๋ธ๋ก (128 ์ค๋ ๋, 4๊ฐ ๋๋ 2๊ฐ ์ํ, ํ๋์จ์ด ์กฐ์จ)
์ ์ฒดโํ๋ (Reduction): ๋ชจ๋ ์ค๋ ๋ โ ์ค๋ ๋ 0์ ๋จ์ผ ๊ฒฐ๊ณผ
์ ์ฒดโ๊ฐ๊ฐ (Scan): ๋ชจ๋ ์ค๋ ๋ โ ๊ฐ ์ค๋ ๋๊ฐ ๋์ ์์น๋ฅผ ๋ฐ์
ํ๋โ์ ์ฒด (Broadcast): ์ค๋ ๋ 0 โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ฐ์ ๋ฐ์
ํฌ๋ก์ค ์ํ ์กฐ์จ:
โโโ ์ํ 0 (์ค๋ ๋ 0-31) โโblock.sum()โโโ
โโโ ์ํ 1 (์ค๋ ๋ 32-63) โโblock.sum()โโโผโ ์ค๋ ๋ 0 ๊ฒฐ๊ณผ
โโโ ์ํ 2 (์ค๋ ๋ 64-95) โโblock.sum()โโโค
โโโ ์ํ 3 (์ค๋ ๋ 96-127) โโblock.sum()โโโ
ํ๋์จ์ด ํ์ค:
- ํฌ๋ก์ค ์ํ ๋๊ธฐํ: ๋ธ๋ก ๋ด ์ฌ๋ฌ ์ํ ๊ฐ ์๋ ์กฐ์จ
- ์ ์ฉ ํ๋์จ์ด ์ ๋: ํนํ๋ ์ค์บ ์ ๋๊ณผ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์ ๋คํธ์ํฌ
- ๋ช ์์ ๋ฐฐ๋ฆฌ์ด ๋ถํ์: ํ๋์จ์ด๊ฐ ๋ชจ๋ ๋๊ธฐํ๋ฅผ ๋ด๋ถ์ ์ผ๋ก ๊ด๋ฆฌ
- ๋ก๊ทธ ๋ณต์ก๋: \(O(\log n)\) ์๊ณ ๋ฆฌ์ฆ์ ๋จ์ผ ๋ช ๋ น์ ๋จ์ํจ์ผ๋ก
Mojo์ ๋ธ๋ก ์ฐ์ฐ
gpu.primitives.block์ ์์ ํ ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ ๋๊ตฌ ๋ชจ์์ ๋ฐฐ์๋๋ค:
block.sum(value): ํฉ๊ณ, ํ๊ท , ์ต๋๊ฐ/์ต์๊ฐ์ ์ํ ์ ์ฒดโํ๋ ๋ฆฌ๋์ block.prefix_sum(value): ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ์ถ์ถ์ ์ํ ์ ์ฒดโ๊ฐ๊ฐ ์ค์บblock.broadcast(value): ๋งค๊ฐ๋ณ์ ๊ณต์ ์ ์กฐ์จ์ ์ํ ํ๋โ์ ์ฒด ๋ถ๋ฐฐ
์ฐธ๊ณ : ์ด ๊ธฐ๋ณธ ์์๋ค์ ํต๊ณ ์ฐ์ฐ, ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ, ์ ๊ทํ ์ํฌํ๋ก์ฐ์ ๊ฐ์ ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ์ด๋ฐ ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ณธ ์์ ์์ด ๊ตฌํํ๋ ค๋ฉด ์์ญ ์ค์ ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์จ ์ฝ๋๊ฐ ํ์ํฉ๋๋ค.
์ฑ๋ฅ ๋ณํ ์์
# ๋ณต์กํ ๋ธ๋ก ์ ์ฒด ๋ฆฌ๋์
(๊ธฐ์กด ๋ฐฉ์ - Puzzle 12์์):
shared_memory[local_i] = my_value
barrier()
stride = 64
while stride > 0:
if local_i < stride:
shared_memory[local_i] += shared_memory[local_i + stride]
barrier()
stride //= 2
if local_i == 0:
output[block_idx.x] = shared_memory[0]
# ๋ธ๋ก ์ฐ์ฐ์ผ๋ก ์ด ๋ชจ๋ ๋ณต์ก์ฑ์ ์ ๊ฑฐ:
my_partial = compute_local_contribution()
total = block.sum[block_size=128, broadcast=False](my_partial) # ํ ์ค์ด๋ฉด ๋!
if local_i == 0:
output[block_idx.x] = total[0]
๋ธ๋ก ์ฐ์ฐ์ด ๋น๋๋ ์๊ฐ
์ฑ๋ฅ ํน์ฑ์ ์ดํดํฉ๋๋ค:
| ์๊ณ ๋ฆฌ์ฆ ํจํด | ๊ธฐ์กด ๋ฐฉ์ | ๋ธ๋ก ์ฐ์ฐ |
|---|---|---|
| ๋ธ๋ก ์ ์ฒด ๋ฆฌ๋์ | ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด | ๋จ์ผ block.sum ํธ์ถ |
| ๋ณ๋ ฌ ํํฐ๋ง | ๋ณต์กํ ์ธ๋ฑ์ฑ | block.prefix_sum ์กฐ์จ |
| ๋งค๊ฐ๋ณ์ ๊ณต์ | ์๋ ๋๊ธฐํ | ๋จ์ผ block.broadcast ํธ์ถ |
| ํฌ๋ก์ค ์ํ ์๊ณ ๋ฆฌ์ฆ | ๋ช ์์ ๋ฐฐ๋ฆฌ์ด ๊ด๋ฆฌ | ํ๋์จ์ด ๊ด๋ฆฌ ์กฐ์จ |
GPU ํ๋ก๊ทธ๋๋ฐ ํจํด์ ์งํ
์ถ๋ฐ์ : ์๋ ์กฐ์จ (Puzzle 12)
๋ณต์กํ์ง๋ง ๊ต์ก์ - ๋ช ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ฐฐ๋ฆฌ์ด, ํธ๋ฆฌ ๋ฆฌ๋์ :
# ์๋ ๋ฐฉ์: 15์ค ์ด์์ ๋ณต์กํ ๋๊ธฐํ
shared_memory[local_i] = my_value
barrier()
# ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ ํธ๋ฆฌ ๋ฆฌ๋์
...
stride = 64
while stride > 0:
if local_i < stride:
shared_memory[local_i] += shared_memory[local_i + stride]
barrier()
stride //= 2
์ค๊ฐ ๋จ๊ณ: ์ํ ํ๋ก๊ทธ๋๋ฐ (Puzzle 24)
ํ๋์จ์ด ๊ฐ์์ด์ง๋ง ๋ฒ์๊ฐ ์ ํ์ - 32 ์ค๋ ๋ ์ํ ๋ด์ warp.sum():
# ์ํ ๋ฐฉ์: 1์ค์ด์ง๋ง ๋จ์ผ ์ํ๋ง
total = warp.sum[warp_size=WARP_SIZE](val=partial_product)
์ต์ข ๋ชฉ์ ์ง: ๋ธ๋ก ํ๋ก๊ทธ๋๋ฐ (์ด๋ฒ ํผ์ฆ)
์์ ํ ๋๊ตฌ ๋ชจ์ - ์ ์ฒด ๋ธ๋ก์ ๊ฑธ์น ํ๋์จ์ด ์ต์ ํ ๊ธฐ๋ณธ ์์:
# ๋ธ๋ก ๋ฐฉ์: ์ฌ๋ฌ ์ํ์ ๊ฑธ์น 1์ค (128+ ์ค๋ ๋)
total = block.sum[block_size=128, broadcast=False](val=partial_product)
์ธ ๊ฐ์ง ๊ธฐ๋ณธ ํต์ ํจํด
๋ธ๋ก ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ์ ๋ชจ๋ ๋ณ๋ ฌ ํต์ ์๊ตฌ๋ฅผ ์ถฉ์กฑํ๋ ์ธ ๊ฐ์ง ํต์ฌ ๊ธฐ๋ณธ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค:
1. ์ ์ฒดโํ๋: ๋ฆฌ๋์
(block.sum())
- ํจํด: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ธฐ์ฌ โ ํ๋์ ์ค๋ ๋๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์
- ์ฉ๋: ํฉ๊ณ, ํ๊ท , ์ต๋๊ฐ/์ต์๊ฐ ๊ณ์ฐ
- ์์: ๋ด์ , ํต๊ณ ์ง๊ณ
- ํ๋์จ์ด: ์๋ ๋ฐฐ๋ฆฌ์ด๊ฐ ํฌํจ๋ ํฌ๋ก์ค ์ํ ๋ฒํฐํ๋ผ์ด ๋ฆฌ๋์
2. ์ ์ฒดโ๊ฐ๊ฐ: ์ค์บ (block.prefix_sum())
- ํจํด: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ธฐ์ฌ โ ๊ฐ ์ค๋ ๋๊ฐ ๋์ ์์น๋ฅผ ๋ฐ์
- ์ฉ๋: ๋ณ๋ ฌ ํํฐ๋ง, ์คํธ๋ฆผ ์ปดํฉ์ , ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
- ์์: ๋ณ๋ ฌ ๋ฐ์ดํฐ ์ถ์ถ์ ์ํ ์ฐ๊ธฐ ์์น ๊ณ์ฐ
- ํ๋์จ์ด: ํฌ๋ก์ค ์ํ ์กฐ์จ์ ํฌํจํ ๋ณ๋ ฌ ์ค์บ
3. ํ๋โ์ ์ฒด: ๋ธ๋ก๋์บ์คํธ (block.broadcast())
- ํจํด: ํ๋์ ์ค๋ ๋๊ฐ ์ ๊ณต โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ฐ์ ๋ฐ์
- ์ฉ๋: ๋งค๊ฐ๋ณ์ ๊ณต์ , ์ค์ ๊ฐ ๋ถ๋ฐฐ
- ์์: ์ ๊ทํ ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๊ณ์ฐ๋ ํ๊ท ๊ณต์
- ํ๋์จ์ด: ์ฌ๋ฌ ์ํ์ ๊ฑธ์น ์ต์ ํ๋ ๋ถ๋ฐฐ
ํ์ต ๊ฒฝ๋ก
์ธ ๋จ๊ณ๋ก ์ด ํผ์ฆ์ ์์ฑํ๋ฉฐ, ๋จ์ํ ๊ฒ์์ ๋ณต์กํ ๊ฒ์ผ๋ก ์งํํฉ๋๋ค:
Part 1: block.sum()์ ํต์ฌ
๋ณต์กํ ๋ฆฌ๋์ ์ ๋จ์ํ ํจ์ ํธ์ถ๋ก ๋ณํ
block.sum()์ผ๋ก ๋ด์ ์ ๊ตฌํํ๋ฉฐ ๋ธ๋ก ๋ฆฌ๋์
์ ๊ธฐ๋ณธ ํจํด์ ๋ฐฐ์๋๋ค. ๋ธ๋ก
์ฐ์ฐ์ด 15์ค ์ด์์ ์๋ ๋ฐฐ๋ฆฌ์ด๋ฅผ ๋จ์ผ ์ต์ ํ ํธ์ถ๋ก ๋์ฒดํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ์ฌ๋ฌ ์ํ์ ๊ฑธ์น ๋ธ๋ก ์ ์ฒด ๋๊ธฐํ
- ํ๋์จ์ด ์ต์ ํ ๋ฆฌ๋์ ํจํด
- ์ค๋ ๋ 0 ๊ฒฐ๊ณผ ๊ด๋ฆฌ
- ๊ธฐ์กด ๋ฐฉ์๊ณผ์ ์ฑ๋ฅ ๋น๊ต
ํ์ต ๋ชฉํ: block.sum()์ด ๋ธ๋ก ๊ท๋ชจ์์ warp.sum()์ ๋จ์ํจ์ ์ ๊ณตํ๋
๋ฐฉ๋ฒ์ ์ดํดํฉ๋๋ค.
Part 2: block.prefix_sum()๊ณผ ๋ณ๋ ฌ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ์ถ์ถ
ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ๋ฅผ ์ํด block.prefix_sum()์ ์ฌ์ฉํ์ฌ ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์
๊ตฌ์ถํฉ๋๋ค. ๋์ ํฉ์ด ๋จ์ํ ๋ฆฌ๋์
์ผ๋ก๋ ๊ตฌํํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ๋ฐ์ดํฐ ์ฌ๊ตฌ์ฑ์
๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ์ด์ง ํ๋ ๋์ผ์ดํธ๋ฅผ ์ด์ฉํ ๋ณ๋ ฌ ํํฐ๋ง
- ์กฐ์จ๋ ์ฐ๊ธฐ ์์น ๊ณ์ฐ
- ๊ณ ๊ธ ํํฐ์ ๋ ์๊ณ ๋ฆฌ์ฆ
- ํฌ๋ก์ค ์ค๋ ๋ ๋ฐ์ดํฐ ์ถ์ถ ํจํด
ํ์ต ๋ชฉํ: block.prefix_sum()์ด ๋จ์ํ ์ง๊ณ๋ฅผ ๋์ด์๋ ๊ณ ๊ธ ๋ณ๋ ฌ
์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ์ดํดํฉ๋๋ค.
Part 3: block.broadcast()์ ๋ฒกํฐ ์ ๊ทํ
๋ชจ๋ ํจํด์ ๊ฒฐํฉํ๋ ์์ ํ ์ํฌํ๋ก์ฐ
๋ธ๋ก ์ฐ์ฐ ๋๊ตฌ ๋ชจ์ ์ ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒกํฐ ํ๊ท ์ ๊ทํ๋ฅผ ๊ตฌํํฉ๋๋ค. ์ธ ๊ฐ์ง ๊ธฐ๋ณธ ์์๊ฐ ์ด๋ป๊ฒ ํจ๊ป ์๋ํ์ฌ ์ํ์ ์ ํ์ฑ์ ๊ฐ์ถ ์ค์ ์ฐ์ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ํ๋โ์ ์ฒด ํต์ ํจํด
- ์กฐ์จ๋ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ
- ์์ ํ ๋ธ๋ก ์ฐ์ฐ ์ํฌํ๋ก์ฐ
- ์ค์ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ
ํ์ต ๋ชฉํ: ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์ํด ๋ธ๋ก ์ฐ์ฐ์ ์กฐํฉํ๋ ๋ฐฉ๋ฒ์ ์ดํดํฉ๋๋ค.
๋ธ๋ก ์ฐ์ฐ์ด ์ค์ํ ์ด์
์ฝ๋ ๋จ์ํ ๋ณํ:
๊ธฐ์กด ๋ฐฉ์: 20์ค ์ด์์ ๋ฐฐ๋ฆฌ์ด, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ณต์กํ ์ธ๋ฑ์ฑ
๋ธ๋ก ์ฐ์ฐ: 3-5์ค์ ์กฐํฉ ๊ฐ๋ฅํ ํ๋์จ์ด ์ต์ ํ ๊ธฐ๋ณธ ์์
์ฑ๋ฅ ์ด์ :
- ํ๋์จ์ด ์ต์ ํ: GPU ์ํคํ ์ฒ๋ณ ์ต์ ํ๋ฅผ ํ์ฉ
- ์๋ ๋๊ธฐํ: ์๋ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น ์ค๋ฅ ์ ๊ฑฐ
- ์กฐํฉ ๊ฐ๋ฅ์ฑ: ์ฐ์ฐ๋ค์ด ๋งค๋๋ฝ๊ฒ ํจ๊ป ๋์
- ์ด์์ฑ: ๋์ผํ ์ฝ๋๊ฐ ๋ค์ํ GPU ์ํคํ ์ฒ์์ ์๋
๊ต์ก์ ๊ฐ์น:
- ๊ฐ๋ ์ ๋ช ํ์ฑ: ๊ฐ ์ฐ์ฐ์ด ๋ช ํํ ํต์ ๋ชฉ์ ์ ๊ฐ์ง
- ์ ์ง์ ๋ณต์ก์ฑ: ๋จ์ํ ๋ฆฌ๋์ ์์ ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋ฐ์
- ์ค์ ์์ฉ: ๊ณผํ ์ฐ์ฐ, ๊ทธ๋ํฝ, AI์์ ๊ด๋ฒ์ํ๊ฒ ์ฌ์ฉ๋๋ ํจํด
์ ์ ์ง์
์ด ํผ์ฆ์ ์์ํ๊ธฐ ์ ์ ๋ค์์ ์๋ฃํด์ผ ํฉ๋๋ค:
- Puzzle 12: ๋ด์ : ์๋ GPU ๋๊ธฐํ์ ๋ํ ์ดํด
- Puzzle 24: ์ํ ๊ธฐ์ด: ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ ๊ฒฝํ
ํ์ต ์ฑ๊ณผ
์ธ ํํธ๋ฅผ ๋ชจ๋ ์๋ฃํ๋ฉด ๋ค์์ ์ดํดํ๊ฒ ๋ฉ๋๋ค:
- ๊ฐ ๋ธ๋ก ์ฐ์ฐ์ ์ฉ๋ - ๋ค์ํ ๋ณ๋ ฌ ํต์ ์๊ตฌ์ ๋ง๋ ์ ํ
- ์ฐ์ฐ ์กฐํฉ ๋ฐฉ๋ฒ - ๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์ถ
- ์ฑ๋ฅ ํธ๋ ์ด๋์คํ - ์๋ ๋ฐฉ์๊ณผ ์๋ํ ๋ฐฉ์ ๊ฐ์ ๋น๊ต
- ์ค์ ์์ฉ - ๋ธ๋ก ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ ํจํด์ ํ์ฉ
- ์ํคํ ์ฒ ๋ ๋ฆฝ์ ํ๋ก๊ทธ๋๋ฐ - ํ๋์จ์ด ์ต์ ํ ๊ธฐ๋ณธ ์์ ํ์ฉ
์์ํ๊ธฐ
๊ถ์ฅ ์์: ๊ฐ ํํธ๊ฐ ์ด์ ํํธ์ ๊ฐ๋ ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฏ๋ก ์์๋๋ก ์์ฑํ์ธ์. ๋จ์ํ ๋ฆฌ๋์ โ ๊ณ ๊ธ ํํฐ์ ๋ โ ์์ ํ ์ํฌํ๋ก์ฐ๋ก ์ด์ด์ง๋ ์งํ์ด ๋ธ๋ก ๋ ๋ฒจ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ดํดํ๋ ์ต์ ์ ํ์ต ๊ฒฝ๋ก๋ฅผ ์ ๊ณตํฉ๋๋ค.
๐ก ํต์ฌ ํต์ฐฐ: ๋ธ๋ก ์ฐ์ฐ์ ํ๋ก๊ทธ๋๋จธ ์์ฐ์ฑ๊ณผ ํ๋์จ์ด ์ฑ๋ฅ ์ฌ์ด์ ์ต์ ์ง์ ์ ๋ํ๋ ๋๋ค - ๊ณ ์์ค ์ฐ์ฐ์ ๋จ์ํจ๊ณผ ์ธ์ฌํ๊ฒ ์ต์ ํ๋ ์ ์์ค ๊ตฌํ์ ํจ์จ์ฑ์ ๋์์ ์ ๊ณตํฉ๋๋ค. ์ด ํผ์ฆ์ ํ๋ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ ํฉํ ์ถ์ํ ์์ค์์ ์ฌ๊ณ ํ๋ ๋ฒ์ ๊ฐ๋ฅด์นฉ๋๋ค.
block.sum()์ ํต์ฌ - ๋ธ๋ก ๋ ๋ฒจ ๋ด์
Puzzle 12์์ ์ดํด๋ณธ ๋ด์ ์ ๋ธ๋ก ๋ ๋ฒจ
sum ์ฐ์ฐ์ผ๋ก
๊ตฌํํฉ๋๋ค. ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ๊ฐ๋จํ ํจ์ ํธ์ถ๋ก ๋์ฒดํฉ๋๋ค. ๋ธ๋ก ๋ด ๊ฐ
์ค๋ ๋๊ฐ ํ๋์ ์์๋ฅผ ์ฒ๋ฆฌํ๊ณ block.sum()์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์๋์ผ๋ก ํฉ์ฐํ์ฌ, ๋ธ๋ก
ํ๋ก๊ทธ๋๋ฐ์ด ์ ์ฒด ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์น GPU ๋๊ธฐํ๋ฅผ ์ด๋ป๊ฒ ๋ณํํ๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: block.sum() ์ฐ์ฐ์ ๋ธ๋ก ์ ์ฒด ์คํ์ ํ์ฉํ์ฌ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์ ์ ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋์ ๊ฑธ์ณ ์ํ ํจํด์ ์ฌ์ฉํ๋ ์ ๊ตํ๊ฒ ์ต์ ํ๋ ๊ตฌํ์ผ๋ก ๋์ฒดํฉ๋๋ค. LLVM ๋ถ์์ ๊ธฐ์ ๋ถ์์ ์ฐธ๊ณ ํ์ธ์.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
block.sum()์ ํ์ฉํ ๋ธ๋ก ๋ ๋ฒจ ๋ฆฌ๋์ - ๋ธ๋ก ์ ์ฒด ๋๊ธฐํ์ ์ค๋ ๋ ์กฐ์จ
- ๋จ์ผ ๋ธ๋ก ๋ด ํฌ๋ก์ค ์ํ ํต์
- ๋ณต์กํ ํจํด์์ ๊ฐ๋จํ ํจํด์ผ๋ก์ ์ฑ๋ฅ ๋ณํ
- ์ค๋ ๋ 0 ๊ฒฐ๊ณผ ๊ด๋ฆฌ์ ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ
์ํ์ ์ฐ์ฐ์ ๋ด์ ์ ๋๋ค: \[\Large \text{output}[0] = \sum_{i=0}^{N-1} a[i] \times b[i]\]
ํ์ง๋ง ๊ตฌํ ๊ณผ์ ์์ Mojo์ ๋ชจ๋ ๋ธ๋ก ๋ ๋ฒจ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ ์ฉ๋๋ ๊ธฐ๋ณธ ํจํด์ ๋ฐฐ์๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 128์์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ธ๋ก ๊ตฌ์ฑ:
(128, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ (TPB = 128) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ ์ด์์:
row_major[SIZE]()(1D row-major) - ๋ธ๋ก๋น ์ํ ์:
128 / WARP_SIZE(NVIDIA์์ 4๊ฐ, AMD์์ 2๊ฐ ๋๋ 4๊ฐ)
๊ธฐ์กด ๋ฐฉ์์ ๋ณต์ก์ฑ (Puzzle 12์์)
Puzzle 12์ ๋ณต์กํ ๋ฐฉ์์ ๋ ์ฌ๋ ค ๋ด ์๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ฐฐ๋ฆฌ์ด, ํธ๋ฆฌ ๋ฆฌ๋์ ์ด ํ์ํ์ต๋๋ค:
def traditional_dot_product[
tpb: Int
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
size: Int,
):
"""Traditional dot product using shared memory + barriers + tree reduction.
Educational but complex - shows the manual coordination needed."""
var shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[tpb]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Each thread computes partial product
if global_i < size:
var a_val = rebind[Scalar[dtype]](a[global_i])
var b_val = rebind[Scalar[dtype]](b[global_i])
shared[local_i] = a_val * b_val
barrier()
# Tree reduction in shared memory - complex but educational
var stride = tpb // 2
while stride > 0:
if local_i < stride:
shared[local_i] += shared[local_i + stride]
barrier()
stride //= 2
# Only thread 0 writes final result
if local_i == 0:
output[0] = shared[0]
์ด ๋ฐฉ์์ด ๋ณต์กํ ์ด์ :
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น: ๋ธ๋ก ๋ด์์ ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ด๋ฆฌ
- ๋ช
์์ ๋ฐฐ๋ฆฌ์ด: ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋๋ฅผ ๋๊ธฐํํ๊ธฐ ์ํ
barrier()ํธ์ถ - ํธ๋ฆฌ ๋ฆฌ๋์ : ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ์ ์ฌ์ฉํ๋ ๋ณต์กํ ๋ฃจํ (64โ32โ16โ8โ4โ2โ1)
- ํฌ๋ก์ค ์ํ ์กฐ์จ: ์ฌ๋ฌ ์ํ ๊ฐ ๋๊ธฐํ๊ฐ ํ์
- ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ: ์ค๋ ๋ 0๋ง ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ก
์ด ๋ฐฉ์์ ์ ์ฒด ๋ธ๋ก(GPU์ ๋ฐ๋ผ 2๊ฐ ๋๋ 4๊ฐ ์ํ์ ๊ฑธ์น 128 ์ค๋ ๋)์์ ๋์ํ์ง๋ง, ์ฝ๋๊ฐ ์ฅํฉํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์ฐ๋ฉฐ ๋ธ๋ก ๋ ๋ฒจ GPU ๋๊ธฐํ์ ๋ํ ๊น์ ์ดํด๊ฐ ํ์ํฉ๋๋ค.
์ํ ๋ ๋ฒจ ๊ฐ์ (Puzzle 24์์)
๋ธ๋ก ๋ ๋ฒจ ์ฐ์ฐ์ผ๋ก ๋์ด๊ฐ๊ธฐ ์ ์, Puzzle 24์์
warp.sum()์ ์ฌ์ฉํ์ฌ ๋จ์ผ ์ํ ๋ด ๋ฆฌ๋์
์ ์ด๋ป๊ฒ ๋จ์ํํ๋์ง ๋ ์ฌ๋ ค ๋ด
์๋ค:
def simple_warp_dot_product[
InLayoutT: TensorLayout, OutLayoutT: TensorLayout, size: Int
](
output: TileTensor[mut=True, dtype, OutLayoutT, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutT, MutAnyOrigin],
):
var a_lt = a.to_layout_tensor()
var b_lt = b.to_layout_tensor()
var out_lt = output.to_layout_tensor()
var global_i = block_dim.x * block_idx.x + thread_idx.x
# Each thread computes one partial product using vectorized approach as values in Mojo are SIMD based
var partial_product: Scalar[dtype] = 0
if global_i < size:
partial_product = rebind[Scalar[dtype]](a_lt[global_i]) * rebind[
Scalar[dtype]
](b_lt[global_i])
# warp_sum() replaces all the shared memory + barriers + tree reduction
var total = warp_sum(partial_product)
# Only lane 0 writes the result (all lanes have the same total)
if lane_id() == 0:
out_lt.store[1](Index(global_i // WARP_SIZE), total)
warp.sum()์ด ๋ฌ์ฑํ ๊ฒ:
- ๋จ์ผ ์ํ ๋ฒ์: 32 ์ค๋ ๋(NVIDIA) ๋๋ 32/64 ์ค๋ ๋(AMD) ๋ด์์ ๋์
- ํ๋์จ์ด ์
ํ: ํจ์จ์ ์ธ
shfl.sync.bfly.b32๋ช ๋ น ์ฌ์ฉ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ถํ์: ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์์
- ํ ์ค ๋ฆฌ๋์
:
total = warp_sum[warp_size=WARP_SIZE](val=partial_product)
๊ทธ๋ฌ๋ ํ๊ณ๊ฐ ์์ต๋๋ค: warp.sum()์ ๋จ์ผ ์ํ ๋ด์์๋ง ๋์ํฉ๋๋ค. ์ฌ๋ฌ
์ํ๊ฐ ํ์ํ ๋ฌธ์ (์: 128 ์ค๋ ๋ ๋ธ๋ก)์์๋ ์ฌ์ ํ ์ํ ๊ฐ ์กฐ์จ์ ์ํด ๋ณต์กํ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด ๋ฐฉ์์ด ํ์ํฉ๋๋ค.
๊ธฐ์กด ๋ฐฉ์ ํ ์คํธ:
pixi run p27 --traditional-dot-product
pixi run -e amd p27 --traditional-dot-product
pixi run -e apple p27 --traditional-dot-product
uv run poe p27 --traditional-dot-product
์์ฑํ ์ฝ๋
block.sum() ๋ฐฉ์
๋ณต์กํ ๊ธฐ์กด ๋ฐฉ์์ block.sum()์ ์ฌ์ฉํ๋ ๊ฐ๋จํ ๋ธ๋ก ์ปค๋๋ก ๋ณํํฉ๋๋ค:
comptime SIZE = 128
comptime TPB = 128
comptime NUM_BINS = 8
comptime in_layout = row_major[SIZE]()
comptime InLayoutType = type_of(in_layout)
comptime out_layout = row_major[1]()
comptime OutLayoutType = type_of(out_layout)
comptime dtype = DType.float32
def block_sum_dot_product[
tpb: Int
](
output: TileTensor[mut=True, dtype, OutLayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
size: Int,
):
"""Dot product using block.sum() - convenience function like warp.sum()!
Replaces manual shared memory + barriers + tree reduction with one line."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL IN (roughly 6 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p27/p27.mojo
pixi run p27 --block-sum-dot-product
pixi run -e amd p27 --block-sum-dot-product
uv run poe p27 --block-sum-dot-product
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
SIZE: 128
TPB: 128
Expected result: 1381760.0
Block.sum result: 1381760.0
Block.sum() gives identical results!
Compare the code: 15+ lines of barriers โ 1 line of block.sum()!
Just like warp.sum() but for the entire block
ํ
1. ์ธ ๋จ๊ณ ํจํด ์ดํดํ๊ธฐ
๋ชจ๋ ๋ธ๋ก ๋ฆฌ๋์ ์ ๋์ผํ ๊ฐ๋ ์ ํจํด์ ๋ฐ๋ฆ ๋๋ค:
- ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ๋ก์ปฌ ๊ธฐ์ฌ๋ถ์ ๊ณ์ฐ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ธ๋ก ์ ์ฒด ๋ฆฌ๋์ ์ ์ฐธ์ฌ
- ์ง์ ๋ ํ๋์ ์ค๋ ๋๊ฐ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌ
2. ๋ด์ ์ํ ๊ธฐ์ตํ๊ธฐ
๊ฐ ์ค๋ ๋๋ ๋ฒกํฐ a์ b์์ ํ๋์ ์์ ์์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์ด๋ค์ ์ค๋ ๋
๊ฐ์ ํฉ์ฐํ ์ ์๋ โ๋ถ๋ถ ๊ฒฐ๊ณผโ๋ก ํฉ์น๋ ์ฐ์ฐ์ ๋ฌด์์ผ๊น์?
3. TileTensor ์ธ๋ฑ์ฑ ํจํด
TileTensor ์์์ ์ ๊ทผํ ๋, ์ธ๋ฑ์ฑ์ด SIMD ๊ฐ์ ๋ฐํํ๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์.
์ฐ์ ์ฐ์ฐ์ ์ํด ์ค์นผ๋ผ ๊ฐ์ ์ถ์ถํด์ผ ํฉ๋๋ค.
4. block.sum() API ๊ฐ๋
ํจ์ ์๊ทธ๋์ฒ๋ฅผ ์ดํด๋ณด์ธ์ - ๋ค์์ด ํ์ํฉ๋๋ค:
- ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ์ง์ ํ๋ ํ ํ๋ฆฟ ํ๋ผ๋ฏธํฐ
- ๊ฒฐ๊ณผ ๋ถ๋ฐฐ ๋ฐฉ์์ ์ํ ํ
ํ๋ฆฟ ํ๋ผ๋ฏธํฐ (
broadcast) - ๋ฆฌ๋์คํ ๊ฐ์ ๋ด์ ๋ฐํ์ ํ๋ผ๋ฏธํฐ
5. ์ค๋ ๋ ์กฐ์จ ์์น
- ์ด๋ค ์ค๋ ๋๊ฐ ์ฒ๋ฆฌํ ์ ํจํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์์๊น์? (ํํธ: ๊ฒฝ๊ณ ๊ฒ์ฌ)
- ์ด๋ค ์ค๋ ๋๊ฐ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํด์ผ ํ ๊น์? (ํํธ: ์ผ๊ด๋ ์ ํ)
- ๊ทธ ํน์ ์ค๋ ๋๋ฅผ ์ด๋ป๊ฒ ์๋ณํ ๊น์? (ํํธ: ์ค๋ ๋ ์ธ๋ฑ์ฑ)
์๋ฃจ์
def block_sum_dot_product[
tpb: Int
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
a: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
size: Int,
):
"""Dot product using block.sum() - convenience function like warp.sum()!
Replaces manual shared memory + barriers + tree reduction with one line."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Each thread computes partial product
var partial_product: Scalar[dtype] = 0.0
if global_i < size:
# TileTensor indexing `[0]` returns the underlying SIMD value
partial_product = a[global_i][0] * b[global_i][0]
# The magic: block.sum() replaces 15+ lines of manual reduction!
# Just like warp.sum() but for the entire block
var total = block.sum[block_size=tpb, broadcast=False](
val=SIMD[DType.float32, 1](partial_product)
)
# Only thread 0 writes the result
if local_i == 0:
output[0] = total[0]
block.sum() ์ปค๋์ ๋ณต์กํ ๋ธ๋ก ๋๊ธฐํ์์ ์ ๊ตํ๊ฒ ์ต์ ํ๋ ๊ตฌํ์ผ๋ก์
๊ทผ๋ณธ์ ์ธ ๋ณํ์ ๋ณด์ฌ์ค๋๋ค:
๊ธฐ์กด ๋ฐฉ์์์ ์ฌ๋ผ์ง ๊ฒ๋ค:
- 15์ค ์ด์ โ 8์ค: ํ๊ธฐ์ ์ธ ์ฝ๋ ์ถ์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ถํ์
- 7ํ ์ด์์ barrier() ํธ์ถ: ๋ช ์์ ๋๊ธฐํ ์ ๋ก
- ๋ณต์กํ ํธ๋ฆฌ ๋ฆฌ๋์ : ๋จ์ผ ํจ์ ํธ์ถ๋ก ๋์ฒด
- ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ์ธ๋ฑ์ฑ: ์์ ํ ์ ๊ฑฐ
- ํฌ๋ก์ค ์ํ ์กฐ์จ: ์ต์ ํ๋ ๊ตฌํ์ด ์๋์ผ๋ก ์ฒ๋ฆฌ
๋ธ๋ก ์ ์ฒด ์คํ ๋ชจ๋ธ:
๋ธ๋ก ์ค๋ ๋ (128 ์ค๋ ๋, 4๊ฐ ์ํ):
์ํ 0 (์ค๋ ๋ 0-31):
์ค๋ ๋ 0: partial_product = a[0] * b[0] = 0.0
์ค๋ ๋ 1: partial_product = a[1] * b[1] = 2.0
...
์ค๋ ๋ 31: partial_product = a[31] * b[31] = 1922.0
์ํ 1 (์ค๋ ๋ 32-63):
์ค๋ ๋ 32: partial_product = a[32] * b[32] = 2048.0
...
์ํ 2 (์ค๋ ๋ 64-95):
์ค๋ ๋ 64: partial_product = a[64] * b[64] = 8192.0
...
์ํ 3 (์ค๋ ๋ 96-127):
์ค๋ ๋ 96: partial_product = a[96] * b[96] = 18432.0
์ค๋ ๋ 127: partial_product = a[127] * b[127] = 32258.0
block.sum() ํ๋์จ์ด ์ฐ์ฐ:
๋ชจ๋ ์ค๋ ๋ โ 0.0 + 2.0 + 1922.0 + 2048.0 + ... + 32258.0 = 1381760.0
์ค๋ ๋ 0์ด ์์ โ total = 1381760.0 (broadcast=False์ผ ๋)
๋ฐฐ๋ฆฌ์ด ์์ด ๋์ํ๋ ์ด์ :
- ๋ธ๋ก ์ ์ฒด ์คํ: ๋ชจ๋ ์ค๋ ๋๊ฐ ์ํ ๋ด์์ ๋ก์คํ ์ผ๋ก ๊ฐ ๋ช ๋ น์ ์คํ
- ๋ด์ฅ ๋๊ธฐํ:
block.sum()๊ตฌํ์ด ๋๊ธฐํ๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌ - ํฌ๋ก์ค ์ํ ํต์ : ๋ธ๋ก ๋ด ์ํ ๊ฐ ์ต์ ํ๋ ํต์
- ์กฐ์จ๋ ๊ฒฐ๊ณผ ์ ๋ฌ: ์ค๋ ๋ 0๋ง ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์์
warp.sum() (Puzzle 24)๊ณผ์ ๋น๊ต:
- ์ํ ๋ฒ์:
warp.sum()์ 32/64 ์ค๋ ๋(๋จ์ผ ์ํ) ๋ด์์ ๋์ - ๋ธ๋ก ๋ฒ์:
block.sum()์ ์ ์ฒด ๋ธ๋ก(์ฌ๋ฌ ์ํ)์ ๊ฑธ์ณ ๋์ - ๋์ผํ ๋จ์ํจ: ๋ ๋ค ๋ณต์กํ ์๋ ๋ฆฌ๋์ ์ ํ ์ค ํธ์ถ๋ก ๋์ฒด
- ์๋ ์กฐ์จ:
block.sum()์warp.sum()์ด ์ฒ๋ฆฌํ ์ ์๋ ํฌ๋ก์ค ์ํ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
๊ธฐ์ ๋ถ์: block.sum()์ ์ค์ ๋ก ๋ฌด์์ผ๋ก ์ปดํ์ผ๋ ๊น?
block.sum()์ด ์ค์ ๋ก ๋ฌด์์ ์์ฑํ๋์ง ์ดํดํ๊ธฐ ์ํด, ๋๋ฒ๊ทธ ์ ๋ณด์ ํจ๊ป
ํผ์ฆ์ ์ปดํ์ผํ์ต๋๋ค:
pixi run mojo build --emit llvm --debug-level=line-tables solutions/p27/p27.mojo -o solutions/p27/p27.ll
์ด๋ ๊ฒ ์์ฑ๋ LLVM ํ์ผ solutions/p27/p27.ll์๋, ํธํ NVIDIA GPU์์ ์ค์
GPU ๋ช
๋ น์ ๋ณด์ฌ์ฃผ๋ PTX ์ด์
๋ธ๋ฆฌ๊ฐ ๋ด์ฅ๋์ด ์์ต๋๋ค:
๋ฐ๊ฒฌ 1: ๋จ์ผ ๋ช ๋ น์ด ์๋๋ค
block.sum()์ ์ฝ 20๊ฐ ์ด์์ PTX ๋ช
๋ น์ผ๋ก ์ปดํ์ผ๋๋ฉฐ, 2๋จ๊ณ ๋ฆฌ๋์
์ผ๋ก
๊ตฌ์ฑ๋ฉ๋๋ค:
1๋จ๊ณ: ์ํ ๋ ๋ฒจ ๋ฆฌ๋์ (๋ฒํฐํ๋ผ์ด ์ ํ)
shfl.sync.bfly.b32 %r23, %r46, 16, 31, -1; // ์คํ์
16์ผ๋ก ์
ํ
add.f32 %r24, %r46, %r23; // ์
ํ๋ ๊ฐ์ ํฉ์ฐ
shfl.sync.bfly.b32 %r25, %r24, 8, 31, -1; // ์คํ์
8๋ก ์
ํ
add.f32 %r26, %r24, %r25; // ์
ํ๋ ๊ฐ์ ํฉ์ฐ
// ... ์คํ์
4, 2, 1์ ๋ํด ๊ณ์
2๋จ๊ณ: ํฌ๋ก์ค ์ํ ์กฐ์จ
shr.u32 %r32, %r1, 5; // ์ํ ID๋ฅผ ๊ณ์ฐ
mov.b32 %r34, _global_alloc_$__gpu_shared_mem; // ๊ณต์ ๋ฉ๋ชจ๋ฆฌ
bar.sync 0; // ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ
// ... ํฌ๋ก์ค ์ํ ๋ฆฌ๋์
์ ์ํ ๋ ๋ค๋ฅธ ๋ฒํฐํ๋ผ์ด ์
ํ ์ํ์ค
๋ฐ๊ฒฌ 2: ํ๋์จ์ด ์ต์ ํ ๊ตฌํ
- ๋ฒํฐํ๋ผ์ด ์ ํ: ํธ๋ฆฌ ๋ฆฌ๋์ ๋ณด๋ค ํจ์จ์
- ์๋ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น: ํฌ๋ก์ค ์ํ ๋๊ธฐํ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
- ์ต์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๋ต์ ์ผ๋ก ์ฌ์ฉ
- ์ํคํ ์ฒ ์ธ์: ๋์ผํ API๊ฐ NVIDIA(32 ์ค๋ ๋ ์ํ)์ AMD(32 ๋๋ 64 ์ค๋ ๋ ์ํ)์์ ๋์
๋ฐ๊ฒฌ 3: ์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋ ๋ถ์
๋ถ์ ์ ๊ทผ ๋ฐฉ์:
- ๋ฐ์ด๋๋ฆฌ ELF ์น์
(
.nv_debug_ptx_txt)์์ PTX ์ด์ ๋ธ๋ฆฌ๋ฅผ ํ์ธ - ๊ฐ๋ณ ๋ช ๋ น ์๋ฅผ ์ธ๊ธฐ๋ณด๋ค ์๊ณ ๋ฆฌ์ฆ์ ์ฐจ์ด๋ฅผ ์๋ณ
๊ด์ฐฐ๋ ์ฃผ์ ์๊ณ ๋ฆฌ์ฆ ์ฐจ์ด:
- ๊ธฐ์กด ๋ฐฉ์: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ํธ๋ฆฌ ๋ฆฌ๋์
+ ๋ค์์
bar.syncํธ์ถ - block.sum(): ๋ฒํฐํ๋ผ์ด ์ ํ ํจํด + ์ต์ ํ๋ ํฌ๋ก์ค ์ํ ์กฐ์จ
์ฑ๋ฅ ์ด์ ์ ๋ช ๋ น ์๋ ๋ง๋ฒ ๊ฐ์ ํ๋์จ์ด๊ฐ ์๋๋ผ ์ ๊ตํ๊ฒ ์ต์ ํ๋ ์๊ณ ๋ฆฌ์ฆ ์ ํ(๋ฒํฐํ๋ผ์ด > ํธ๋ฆฌ)์์ ๋น๋กฏ๋ฉ๋๋ค. ๊ตฌํ์ ๋ํ ์์ธํ ๋ด์ฉ์ Mojo gpu ๋ชจ๋์ block.mojo๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ฑ๋ฅ ์ธ์ฌ์ดํธ
block.sum() vs ๊ธฐ์กด ๋ฐฉ์:
- ์ฝ๋ ๋จ์ํจ: ๋ฆฌ๋์ ๋ถ๋ถ์ด 15์ค ์ด์ โ 1์ค๋ก
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ถํ์
- ๋๊ธฐํ: ๋ช ์์ ๋ฐฐ๋ฆฌ์ด ๋ถํ์
- ํ์ฅ์ฑ: ํ๋์จ์ด ํ๋ ๋ด์์ ๋ชจ๋ ๋ธ๋ก ํฌ๊ธฐ์ ๋์
block.sum() vs warp.sum():
- ๋ฒ์: ๋ธ๋ก ์ ์ฒด(128 ์ค๋ ๋) vs ์ํ ์ ์ฒด(32 ์ค๋ ๋)
- ์ฉ๋: ์ ์ฒด ๋ธ๋ก์ ๊ฑธ์น ๋ฆฌ๋์ ์ด ํ์ํ ๋
- ํธ์์ฑ: ๋์ผํ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ, ๋ค๋ฅธ ๊ท๋ชจ
block.sum()์ ์ฌ์ฉํด์ผ ํ ๋:
- ๋จ์ผ ๋ธ๋ก ๋ฌธ์ : ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ํ๋์ ๋ธ๋ก์ ๋ค์ด๊ฐ ๋
- ๋ธ๋ก ๋ ๋ฒจ ์๊ณ ๋ฆฌ์ฆ: ๋ฆฌ๋์ ์ด ํ์ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ
- ํ์ฅ์ฑ๋ณด๋ค ํธ์์ฑ: ๋ฉํฐ ๋ธ๋ก ๋ฐฉ์๋ณด๋ค ๋จ์
์ด์ ํผ์ฆ๊ณผ์ ๊ด๊ณ
Puzzle 12 (๊ธฐ์กด ๋ฐฉ์)์์:
๋ณต์กํจ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์
โ
๋จ์ํจ: block.sum() ํ๋์จ์ด ๊ธฐ๋ณธ ์์
Puzzle 24 (warp.sum())์์:
์ํ ๋ ๋ฒจ: warp.sum() - 32 ์ค๋ ๋ (๋จ์ผ ์ํ)
โ
๋ธ๋ก ๋ ๋ฒจ: block.sum() - 128 ์ค๋ ๋ (์ฌ๋ฌ ์ํ)
3๋จ๊ณ ์งํ:
- ์๋ ๋ฆฌ๋์ (Puzzle 12): ๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด + ํธ๋ฆฌ ๋ฆฌ๋์
- ์ํ ๊ธฐ๋ณธ ์์ (Puzzle 24):
warp.sum()- ๋จ์ํ์ง๋ง ๋จ์ผ ์ํ๋ก ์ ํ - ๋ธ๋ก ๊ธฐ๋ณธ ์์ (Puzzle 27):
block.sum()- ์ํ์ ๋จ์ํจ์ ์ฌ๋ฌ ์ํ๋ก ํ์ฅ
ํต์ฌ ํต์ฐฐ: block.sum()์ warp.sum()์ ๋จ์ํจ์ ์ ๊ณตํ๋ฉด์ ์ ์ฒด ๋ธ๋ก์ผ๋ก
ํ์ฅ๋ฉ๋๋ค. ์๋์ผ๋ก ๊ตฌํํด์ผ ํ๋ ๋ณต์กํ ํฌ๋ก์ค ์ํ ์กฐ์จ์ ์๋์ผ๋ก
์ฒ๋ฆฌํฉ๋๋ค.
๋ค์ ๋จ๊ณ
block.sum() ์ฐ์ฐ์ ๋ฐฐ์ ์ผ๋, ๋ค์์ผ๋ก ์งํํ ์ ์์ต๋๋ค:
- block.prefix_sum()๊ณผ ๋ณ๋ ฌ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ: ๋ธ๋ก ์ค๋ ๋์ ๊ฑธ์น ๋์ ์ฐ์ฐ
- block.broadcast()์ ๋ฒกํฐ ์ ๊ทํ: ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋์ ๊ฐ์ ๊ณต์
๐ก ํต์ฌ ์์ : ๋ธ๋ก ์ฐ์ฐ์ ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋
์ ์ ์ฒด ์ค๋ ๋ ๋ธ๋ก์ผ๋ก
ํ์ฅํ์ฌ, ์ฌ๋ฌ ์ํ์ ๊ฑธ์ณ ๋์์ ๋์ํ๋ฉด์ ๋ณต์กํ ๋๊ธฐํ ํจํด์ ๋์ฒดํ๋
์ต์ ํ๋ ๊ธฐ๋ณธ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค. warp.sum()์ด ์ํ ๋ ๋ฒจ ๋ฆฌ๋์
์ ๋จ์ํํ
๊ฒ์ฒ๋ผ, block.sum()์ ์ฑ๋ฅ์ ํฌ์ํ์ง ์๊ณ ๋ธ๋ก ๋ ๋ฒจ ๋ฆฌ๋์
์ ๋จ์ํํฉ๋๋ค.
block.prefix_sum()๊ณผ ๋ณ๋ ฌ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
์ด ํผ์ฆ์ ๋ธ๋ก ๋ ๋ฒจ
block.prefix_sum
์ฐ์ฐ์ ์ฌ์ฉํ์ฌ ๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ์ถ์ถ์ ์ํ ๋ณ๋ ฌ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ๋ฅผ
๊ตฌํํฉ๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ์์๊ฐ ์ํ ๋์ ๊ตฌ๊ฐ์ ๊ฒฐ์ ํ ๋ค์,
block.prefix_sum()์ ์ ์ฉํ์ฌ ํน์ ๊ตฌ๊ฐ์ ์์๋ฅผ ์ถ์ถํ๊ธฐ ์ํ ์ฐ๊ธฐ ์์น๋ฅผ
๊ณ์ฐํฉ๋๋ค. ๋์ ํฉ์ด ๋จ์ํ ๋ฆฌ๋์
์ ๋์ด ๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ์
๋์ ๊ฐ๋ฅํ๊ฒ ํ๋
๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: block.prefix_sum() ์ฐ์ฐ์ ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋์ ๊ฑธ์ณ ์ผ์นํ๋ ์์์ ๋์ ์ฐ๊ธฐ ์์น๋ฅผ ๊ณ์ฐํ์ฌ ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ์ถ์ถ์ ์ ๊ณตํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ค๋ฃจ๋ ๋ด์ฉ:
block.prefix_sum()์ ํ์ฉํ ๋ธ๋ก ๋ ๋ฒจ ๋์ ํฉ- ๋์ ์ฐ์ฐ์ ์ฌ์ฉํ ๋ณ๋ ฌ ํํฐ๋ง๊ณผ ์ถ์ถ
- ๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ์ ๋ ์๊ณ ๋ฆฌ์ฆ
- ๋ธ๋ก ์ ์ฒด ์กฐ์จ์ ํตํ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
- ๋นํฌํจ(exclusive) vs ํฌํจ(inclusive) ๋์ ํฉ ํจํด
์ด ์๊ณ ๋ฆฌ์ฆ์ ํน์ ๊ฐ ๋ฒ์(๊ตฌ๊ฐ)์ ์ํ๋ ์์๋ฅผ ์ถ์ถํ์ฌ ํ์คํ ๊ทธ๋จ์ ๊ตฌ์ฑํฉ๋๋ค: \[\Large \text{Bin}_k = \{x_i: k/N \leq x_i < (k+1)/N\}\]
๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ์์๊ฐ ์ํ๋ ๊ตฌ๊ฐ์ ๊ฒฐ์ ํ๊ณ , block.prefix_sum()์ด ๋ณ๋ ฌ
์ถ์ถ์ ์กฐ์จํฉ๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 128์์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ธ๋ก ๊ตฌ์ฑ:
(128, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ (TPB = 128) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๊ตฌ๊ฐ ์:
NUM_BINS = 8(๋ฒ์ [0.0, 0.125), [0.125, 0.25) ๋ฑ) - ๋ ์ด์์:
row_major[SIZE]()(1D row-major) - ๋ธ๋ก๋น ์ํ ์:
128 / WARP_SIZE(GPU์ ๋ฐ๋ผ 2๊ฐ ๋๋ 4๊ฐ)
๋์ ๊ณผ์ : ๋ณ๋ ฌ ๊ตฌ๊ฐ ์ถ์ถ
๊ธฐ์กด์ ์์ฐจ์ ํ์คํ ๊ทธ๋จ ๊ตฌ์ฑ์ ์์๋ฅผ ํ๋์ฉ ์ฒ๋ฆฌํฉ๋๋ค:
# ์์ฐจ์ ๋ฐฉ์ - ๋ณ๋ ฌํ๊ฐ ์ด๋ ค์
histogram = [[] for _ in range(NUM_BINS)]
for element in data:
bin_id = int(element * NUM_BINS) # ๊ตฌ๊ฐ ๊ฒฐ์
histogram[bin_id].append(element) # ์์ฐจ์ ์ถ๊ฐ
๋จ์ํ GPU ๋ณ๋ ฌํ์ ๋ฌธ์ ์ :
- ๊ฒฝ์ ์ํ: ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ตฌ๊ฐ์ ๋์์ ์ฐ๊ธฐ
- ๋น์ ๋ ฌ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ์ค๋ ๋๋ค์ด ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ ๊ทผ
- ๋ถํ ๋ถ๊ท ํ: ์ผ๋ถ ๊ตฌ๊ฐ์ ํจ์ฌ ๋ง์ ์์๊ฐ ๋ชฐ๋ฆด ์ ์์
- ๋ณต์กํ ๋๊ธฐํ: ๋ฐฐ๋ฆฌ์ด์ ์์์ ์ฐ์ฐ์ด ํ์
๊ณ ๊ธ ๋ฐฉ์: block.prefix_sum() ์กฐ์จ
๋ณต์กํ ๋ณ๋ ฌ ํํฐ์ ๋์ ์กฐ์จ๋ ์ถ์ถ๋ก ๋ณํํฉ๋๋ค:
์์ฑํ ์ฝ๋
block.prefix_sum() ๋ฐฉ์
block.prefix_sum()์ ์ฌ์ฉํ์ฌ ๋ณ๋ ฌ ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ๋ฅผ ๊ตฌํํฉ๋๋ค:
comptime bin_layout = row_major[SIZE]() # Max SIZE elements per bin
comptime BinLayoutType = type_of(bin_layout)
def block_histogram_bin_extract[
tpb: Int
](
input_data: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
bin_output: TileTensor[mut=True, dtype, BinLayoutType, MutAnyOrigin],
count_output: TileTensor[
mut=True, DType.int32, OutLayoutType, MutAnyOrigin
],
size: Int,
target_bin: Int,
num_bins: Int,
):
"""Parallel histogram using block.prefix_sum() for bin extraction.
This demonstrates advanced parallel filtering and extraction:
1. Each thread determines which bin its element belongs to
2. Use block.prefix_sum() to compute write positions for target_bin elements
3. Extract and pack only elements belonging to target_bin
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Step 1: Each thread determines its bin and element value
# FILL IN (roughly 9 lines)
# Step 2: Create predicate for target bin extraction
# FILL IN (roughly 3 line)
# Step 3: Use block.prefix_sum() for parallel bin extraction!
# This computes where each thread should write within the target bin
# FILL IN (1 line)
# Step 4: Extract and pack elements belonging to target_bin
# FILL IN (roughly 2 line)
# Step 5: Final thread computes total count for this bin
# FILL IN (roughly 3 line)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p27/p27.mojo
ํ
1. ํต์ฌ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ (์ด์ ํผ์ฆ์์ ์ ์ฉ)
block_sum_dot_product์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ค์ ํต์ฌ ๋ณ์๊ฐ ํ์ํฉ๋๋ค:
global_i = block_dim.x * block_idx.x + thread_idx.x
local_i = thread_idx.x
ํจ์๋ 5๊ฐ์ง ์ฃผ์ ๋จ๊ณ(์ด ์ฝ 15-20์ค)๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
- ์์๋ฅผ ๋ก๋ํ๊ณ ๊ตฌ๊ฐ์ ๊ฒฐ์
- ๋์ ๊ตฌ๊ฐ์ ๋ํ ์ด์ง ํ๋ ๋์ผ์ดํธ ์์ฑ
- ํ๋ ๋์ผ์ดํธ์
block.prefix_sum()์คํ - ๊ณ์ฐ๋ ์คํ์ ์ ์ฌ์ฉํ์ฌ ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ
- ๋ง์ง๋ง ์ค๋ ๋๊ฐ ์ด ๊ฐ์๋ฅผ ๊ณ์ฐ
2. ๊ตฌ๊ฐ ๊ณ์ฐ (math.floor ์ฌ์ฉ)
Float32 ๊ฐ์ ๊ตฌ๊ฐ์ผ๋ก ๋ถ๋ฅํ๋ ค๋ฉด:
my_value = input_data[global_i][0] # ๋ด์ ์์์ฒ๋ผ SIMD ์ถ์ถ
bin_number = Int(floor(my_value * num_bins))
๊ฒฝ๊ณ ์ฌ๋ก ์ฒ๋ฆฌ: ์ ํํ 1.0์ธ ๊ฐ์ ๊ตฌ๊ฐ NUM_BINS์ ๋ค์ด๊ฐ์ง๋ง, ์ค์ ๊ตฌ๊ฐ์
0๋ถํฐ NUM_BINS-1๊น์ง์
๋๋ค. if ๋ฌธ์ ์ฌ์ฉํ์ฌ ์ต๋ ๊ตฌ๊ฐ์ ์ ํํ์ธ์.
3. ์ด์ง ํ๋ ๋์ผ์ดํธ ์์ฑ
์ด ์ค๋ ๋์ ์์๊ฐ target_bin์ ์ํ๋์ง๋ฅผ ๋ํ๋ด๋ ์ ์ ๋ณ์(0 ๋๋ 1)๋ฅผ ๋ง๋ญ๋๋ค:
var belongs_to_target: Int = 0
if (thread_has_valid_element) and (my_bin == target_bin):
belongs_to_target = 1
์ด๊ฒ์ด ํต์ฌ ํต์ฐฐ์ ๋๋ค: ๋์ ํฉ์ด ์ด ์ด์ง ํ๋๊ทธ์ ์์ฉํ์ฌ ์์น๋ฅผ ๊ณ์ฐํฉ๋๋ค!
4. block.prefix_sum() ํธ์ถ ํจํด
๋ฌธ์์ ๋ฐ๋ฅด๋ฉด ํธ์ถ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
offset = block.prefix_sum[
dtype=DType.int32, # ์ ์ ํ๋ ๋์ผ์ดํธ๋ก ์์
block_size=tpb, # block.sum()๊ณผ ๋์ผ
exclusive=True # ํต์ฌ: ๊ฐ ์ค๋ ๋ ์ด์ ์ ์์น๋ฅผ ์ ๊ณต
](val=SIMD[DType.int32, 1](my_predicate_value))
์ ๋นํฌํจ(exclusive)์ธ๊ฐ? ์์น 5์์ ํ๋ ๋์ผ์ดํธ=1์ธ ์ค๋ ๋๋, ์์ ์์ 4๊ฐ์ ์์๊ฐ ์์๋ค๋ฉด output[4]์ ์จ์ผ ํฉ๋๋ค.
5. ์กฐ๊ฑด๋ถ ์ฐ๊ธฐ ํจํด
belongs_to_target == 1์ธ ์ค๋ ๋๋ง ๊ธฐ๋กํด์ผ ํฉ๋๋ค:
if belongs_to_target == 1:
bin_output[Int(offset[0])] = my_value # ์ธ๋ฑ์ฑ์ ์ํด SIMD๋ฅผ Int๋ก ๋ณํ
์ด๊ฒ์ Puzzle 12์ ๊ฒฝ๊ณ ๊ฒ์ฌ ํจํด๊ณผ ๋์ผํ์ง๋ง, ์กฐ๊ฑด์ด โ๋์ ๊ตฌ๊ฐ์ ์ํ๋์งโ๋ก ๋ฐ๋์์ต๋๋ค.
6. ์ต์ข ๊ฐ์ ๊ณ์ฐ
๋ง์ง๋ง ์ค๋ ๋(์ค๋ ๋ 0์ด ์๋!)๊ฐ ์ด ๊ฐ์๋ฅผ ๊ณ์ฐํฉ๋๋ค:
if local_i == tpb - 1: # ๋ธ๋ก์ ๋ง์ง๋ง ์ค๋ ๋
total_count = offset[0] + belongs_to_target # ํฌํจ = ๋นํฌํจ + ์์ ์ ๊ธฐ์ฌ๋ถ
count_output[0] = total_count
์ ๋ง์ง๋ง ์ค๋ ๋์ธ๊ฐ? ๊ฐ์ฅ ๋์ offset ๊ฐ์ ๊ฐ์ง๋ฏ๋ก, offset + ๊ธฐ์ฌ๋ถ์ด
์ด ๊ฐ์๊ฐ ๋ฉ๋๋ค.
7. ๋ฐ์ดํฐ ํ์ ๊ณผ ๋ณํ
์ด์ ํผ์ฆ์ ํจํด์ ๊ธฐ์ตํ์ธ์:
TileTensor์ธ๋ฑ์ฑ์ SIMD๋ฅผ ๋ฐํ:input_data[i][0]block.prefix_sum()์ SIMD๋ฅผ ๋ฐํ:offset[0]์ผ๋ก ์ถ์ถ- ๋ฐฐ์ด ์ธ๋ฑ์ฑ์
Int๊ฐ ํ์:bin_output[...]์Int(offset[0])
block.prefix_sum() ๋ฐฉ์ ํ ์คํธ:
pixi run p27 --histogram
pixi run -e amd p27 --histogram
pixi run -e apple p27 --histogram
uv run poe p27 --histogram
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
SIZE: 128
TPB: 128
NUM_BINS: 8
Input sample: 0.0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1 0.11 0.12 0.13 0.14 0.15 ...
=== Processing Bin 0 (range [ 0.0 , 0.125 )) ===
Bin 0 count: 26
Bin 0 extracted elements: 0.0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 ...
=== Processing Bin 1 (range [ 0.125 , 0.25 )) ===
Bin 1 count: 24
Bin 1 extracted elements: 0.13 0.14 0.15 0.16 0.17 0.18 0.19 0.2 ...
=== Processing Bin 2 (range [ 0.25 , 0.375 )) ===
Bin 2 count: 26
Bin 2 extracted elements: 0.25 0.26 0.27 0.28 0.29 0.3 0.31 0.32 ...
=== Processing Bin 3 (range [ 0.375 , 0.5 )) ===
Bin 3 count: 22
Bin 3 extracted elements: 0.38 0.39 0.4 0.41 0.42 0.43 0.44 0.45 ...
=== Processing Bin 4 (range [ 0.5 , 0.625 )) ===
Bin 4 count: 13
Bin 4 extracted elements: 0.5 0.51 0.52 0.53 0.54 0.55 0.56 0.57 ...
=== Processing Bin 5 (range [ 0.625 , 0.75 )) ===
Bin 5 count: 12
Bin 5 extracted elements: 0.63 0.64 0.65 0.66 0.67 0.68 0.69 0.7 ...
=== Processing Bin 6 (range [ 0.75 , 0.875 )) ===
Bin 6 count: 5
Bin 6 extracted elements: 0.75 0.76 0.77 0.78 0.79
=== Processing Bin 7 (range [ 0.875 , 1.0 )) ===
Bin 7 count: 0
Bin 7 extracted elements:
์๋ฃจ์
def block_histogram_bin_extract[
tpb: Int
](
input_data: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
bin_output: TileTensor[mut=True, dtype, BinLayout, MutAnyOrigin],
count_output: TileTensor[mut=True, DType.int32, OutLayout, MutAnyOrigin],
size: Int,
target_bin: Int,
num_bins: Int,
):
"""Parallel histogram using block.prefix_sum() for bin extraction.
This demonstrates advanced parallel filtering and extraction:
1. Each thread determines which bin its element belongs to
2. Use block.prefix_sum() to compute write positions for target_bin elements
3. Extract and pack only elements belonging to target_bin
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Step 1: Each thread determines its bin and element value
var my_value: Scalar[dtype] = 0.0
var my_bin: Int = -1
if global_i < size:
# `[0]` returns the underlying SIMD value
my_value = input_data[global_i][0]
# Bin values [0.0, 1.0) into num_bins buckets
my_bin = Int(floor(my_value * Scalar[dtype](num_bins)))
# Clamp to valid range
if my_bin >= num_bins:
my_bin = num_bins - 1
if my_bin < 0:
my_bin = 0
# Step 2: Create predicate for target bin extraction
var belongs_to_target: Int = 0
if global_i < size and my_bin == target_bin:
belongs_to_target = 1
# Step 3: Use block.prefix_sum() for parallel bin extraction!
# This computes where each thread should write within the target bin
var write_offset = block.prefix_sum[
dtype=DType.int32, block_size=tpb, exclusive=True
](val=SIMD[DType.int32, 1](belongs_to_target))
# Step 4: Extract and pack elements belonging to target_bin
if belongs_to_target == 1:
bin_output[Int(write_offset[0])] = my_value
# Step 5: Final thread computes total count for this bin
if local_i == tpb - 1:
# Inclusive sum = exclusive sum + my contribution
var total_count = write_offset[0] + Int32(belongs_to_target)
count_output[0] = total_count
block.prefix_sum() ์ปค๋์ ์ด์ ํผ์ฆ์ ๊ฐ๋
์ ๊ธฐ๋ฐ์ผ๋ก ๊ณ ๊ธ ๋ณ๋ ฌ ์กฐ์จ ํจํด์
๋ณด์ฌ์ค๋๋ค:
๋จ๊ณ๋ณ ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
1๋จ๊ณ: ์์ ์ฒ๋ฆฌ (Puzzle 12 ๋ด์ ๊ณผ ์ ์ฌ)
์ค๋ ๋ ์ธ๋ฑ์ฑ (์ต์ํ ํจํด):
global_i = block_dim.x * block_idx.x + thread_idx.x // ์ ์ญ ์์ ์ธ๋ฑ์ค
local_i = thread_idx.x // ๋ก์ปฌ ์ค๋ ๋ ์ธ๋ฑ์ค
์์ ๋ก๋ฉ (TileTensor ํจํด๊ณผ ๋์ผ):
์ค๋ ๋ 0: my_value = input_data[0][0] = 0.00
์ค๋ ๋ 1: my_value = input_data[1][0] = 0.01
์ค๋ ๋ 13: my_value = input_data[13][0] = 0.13
์ค๋ ๋ 25: my_value = input_data[25][0] = 0.25
...
2๋จ๊ณ: ๊ตฌ๊ฐ ๋ถ๋ฅ (์๋ก์ด ๊ฐ๋ )
floor ์ฐ์ฐ์ ์ฌ์ฉํ ๊ตฌ๊ฐ ๊ณ์ฐ:
์ค๋ ๋ 0: my_bin = Int(floor(0.00 * 8)) = 0 // ๊ฐ [0.000, 0.125) โ ๊ตฌ๊ฐ 0
์ค๋ ๋ 1: my_bin = Int(floor(0.01 * 8)) = 0 // ๊ฐ [0.000, 0.125) โ ๊ตฌ๊ฐ 0
์ค๋ ๋ 13: my_bin = Int(floor(0.13 * 8)) = 1 // ๊ฐ [0.125, 0.250) โ ๊ตฌ๊ฐ 1
์ค๋ ๋ 25: my_bin = Int(floor(0.25 * 8)) = 2 // ๊ฐ [0.250, 0.375) โ ๊ตฌ๊ฐ 2
...
3๋จ๊ณ: ์ด์ง ํ๋ ๋์ผ์ดํธ ์์ฑ (ํํฐ๋ง ํจํด)
target_bin=0์ ๋ํด ์ถ์ถ ๋ง์คํฌ ์์ฑ:
์ค๋ ๋ 0: belongs_to_target = 1 (๊ตฌ๊ฐ 0 == ๋์ 0)
์ค๋ ๋ 1: belongs_to_target = 1 (๊ตฌ๊ฐ 0 == ๋์ 0)
์ค๋ ๋ 13: belongs_to_target = 0 (๊ตฌ๊ฐ 1 != ๋์ 0)
์ค๋ ๋ 25: belongs_to_target = 0 (๊ตฌ๊ฐ 2 != ๋์ 0)
...
์ด์ง ๋ฐฐ์ด ์์ฑ: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, ...]
4๋จ๊ณ: ๋ณ๋ ฌ ๋์ ํฉ (๋ง๋ฒ์ด ์ผ์ด๋๋ ๊ณณ!)
ํ๋ ๋์ผ์ดํธ์ block.prefix_sum[exclusive=True] ์ ์ฉ:
์
๋ ฅ: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, ...]
๋นํฌํจ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12, -, -, -, ...]
^
์ค์ํ์ง ์์
ํต์ฌ ํต์ฐฐ: ๊ฐ ์ค๋ ๋๊ฐ ์ถ๋ ฅ ๋ฐฐ์ด์์ ์์ ์ ์ฐ๊ธฐ ์์น๋ฅผ ๋ฐ์ต๋๋ค!
5๋จ๊ณ: ์กฐ์จ๋ ์ถ์ถ (์กฐ๊ฑด๋ถ ์ฐ๊ธฐ)
belongs_to_target=1์ธ ์ค๋ ๋๋ง ๊ธฐ๋ก:
์ค๋ ๋ 0: bin_output[0] = 0.00 // write_offset[0] = 0 ์ฌ์ฉ
์ค๋ ๋ 1: bin_output[1] = 0.01 // write_offset[1] = 1 ์ฌ์ฉ
์ค๋ ๋ 12: bin_output[12] = 0.12 // write_offset[12] = 12 ์ฌ์ฉ
์ค๋ ๋ 13: (๊ธฐ๋ก ์ ํจ) // belongs_to_target = 0
์ค๋ ๋ 25: (๊ธฐ๋ก ์ ํจ) // belongs_to_target = 0
...
๊ฒฐ๊ณผ: [0.00, 0.01, 0.02, ..., 0.12, ???, ???, ...] // ๋นํ์์ด ์ฑ์์ง!
6๋จ๊ณ: ๊ฐ์ ๊ณ์ฐ (block.sum() ํจํด๊ณผ ์ ์ฌ)
๋ง์ง๋ง ์ค๋ ๋๊ฐ ์ด ๊ฐ์๋ฅผ ๊ณ์ฐ (์ค๋ ๋ 0์ด ์๋!):
if local_i == tpb - 1: // ์ด ๊ฒฝ์ฐ ์ค๋ ๋ 127
total = write_offset[0] + belongs_to_target // ํฌํจ ํฉ ๊ณต์
count_output[0] = total
์ด ๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ์ด ๋์ํ๋ ์ด์ :
Puzzle 12 (๊ธฐ์กด ๋ด์ )๊ณผ์ ์ฐ๊ฒฐ:
- ๋์ผํ ์ค๋ ๋ ์ธ๋ฑ์ฑ:
global_i์local_iํจํด - ๋์ผํ ๊ฒฝ๊ณ ๊ฒ์ฌ:
if global_i < size๊ฒ์ฆ - ๋์ผํ ๋ฐ์ดํฐ ๋ก๋ฉ:
[0]์ ์ฌ์ฉํ TileTensor SIMD ์ถ์ถ
block.sum() (์ด ํผ์ฆ์ ์๋ถ๋ถ)๊ณผ์ ์ฐ๊ฒฐ:
- ๋์ผํ ๋ธ๋ก ์ ์ฒด ์ฐ์ฐ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ธ๋ก ๊ธฐ๋ณธ ์์์ ์ฐธ์ฌ
- ๋์ผํ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ: ํน์ ์ค๋ ๋(์ฒซ ๋ฒ์งธ ๋์ ๋ง์ง๋ง)๊ฐ ์ต์ข ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
- ๋์ผํ SIMD ๋ณํ: ๋ฐฐ์ด ์ธ๋ฑ์ฑ์ ์ํ
Int(result[0])ํจํด
block.prefix_sum()๋ง์ ๊ณ ๊ธ ๊ฐ๋
:
- ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์: ์ค๋ ๋ 0๋ง ์ค์ํ
block.sum()๊ณผ ๋ฌ๋ฆฌ - ์กฐ์จ๋ ์ฐ๊ธฐ ์์น: ๋์ ํฉ์ด ๊ฒฝ์ ์ํ๋ฅผ ์๋์ผ๋ก ์ ๊ฑฐ
- ๋ณ๋ ฌ ํํฐ๋ง: ์ด์ง ํ๋ ๋์ผ์ดํธ๊ฐ ๊ณ ๊ธ ๋ฐ์ดํฐ ์ฌ๊ตฌ์ฑ์ ๊ฐ๋ฅํ๊ฒ ํจ
๋จ์ํ ๋ฐฉ์ ๋๋น ์ฑ๋ฅ ์ด์ :
vs. ์์์ ์ฐ์ฐ:
- ๊ฒฝ์ ์ํ ์์: ๋์ ํฉ์ด ๊ณ ์ ํ ์ฐ๊ธฐ ์์น๋ฅผ ์ ๊ณต
- ๋ณํฉ๋ ๋ฉ๋ชจ๋ฆฌ: ์์ฐจ์ ์ฐ๊ธฐ๊ฐ ์บ์ ์ฑ๋ฅ์ ํฅ์
- ์ง๋ ฌํ ์์: ๋ชจ๋ ์ฐ๊ธฐ๊ฐ ๋ณ๋ ฌ๋ก ์ํ
vs. ๋ค์ค ํจ์ค ์๊ณ ๋ฆฌ์ฆ:
- ๋จ์ผ ์ปค๋: ํ ๋ฒ์ GPU ์คํ์ผ๋ก ํ์คํ ๊ทธ๋จ ์ถ์ถ ์๋ฃ
- ์์ ํ์ฉ: ๋ฐ์ดํฐ ๋ถํฌ์ ๊ด๊ณ์์ด ๋ชจ๋ ์ค๋ ๋๊ฐ ์์
- ์ต์ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ์ ์ต์ ํ๋ ํจํด
์ด๊ฒ์ block.prefix_sum()์ด block.sum() ๊ฐ์ ๋จ์ํ ๊ธฐ๋ณธ ์์๋ก๋ ๋ณต์กํ๊ฑฐ๋
๋ถ๊ฐ๋ฅํ ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์ด๋ป๊ฒ ๊ฐ๋ฅํ๊ฒ ํ๋์ง ๋ณด์ฌ์ค๋๋ค.
์ฑ๋ฅ ์ธ์ฌ์ดํธ
block.prefix_sum() vs ๊ธฐ์กด ๋ฐฉ์:
- ์๊ณ ๋ฆฌ์ฆ ์ ๊ตํจ: ๊ณ ๊ธ ๋ณ๋ ฌ ํํฐ์ ๋ vs ์์ฐจ์ ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ: ๋ณํฉ๋ ์ฐ๊ธฐ vs ๋ถ์ฐ๋ ๋ฌด์์ ์ ๊ทผ
- ๋๊ธฐํ: ๋ด์ฅ ์กฐ์จ vs ์๋ ๋ฐฐ๋ฆฌ์ด์ ์์์ ์ฐ์ฐ
- ํ์ฅ์ฑ: ๋ชจ๋ ๋ธ๋ก ํฌ๊ธฐ์ ๊ตฌ๊ฐ ์์ ๋์
block.prefix_sum() vs block.sum():
- ๋ฒ์: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ vs ์ค๋ ๋ 0๋ง
- ์ฉ๋: ๋ณต์กํ ํํฐ์ ๋ vs ๋จ์ํ ์ง๊ณ
- ์๊ณ ๋ฆฌ์ฆ ์ ํ: ๋ณ๋ ฌ ์ค์บ ๊ธฐ๋ณธ ์์ vs ๋ฆฌ๋์ ๊ธฐ๋ณธ ์์
- ์ถ๋ ฅ ํจํด: ์ค๋ ๋๋ณ ์์น vs ๋จ์ผ ํฉ๊ณ
block.prefix_sum()์ ์ฌ์ฉํด์ผ ํ ๋:
- ๋ณ๋ ฌ ํํฐ๋ง: ์กฐ๊ฑด์ ๋ง๋ ์์ ์ถ์ถ
- ์คํธ๋ฆผ ์ปดํฉ์ : ๋ถํ์ํ ์์ ์ ๊ฑฐ
- ๋ณ๋ ฌ ํํฐ์ ๋: ๋ฐ์ดํฐ๋ฅผ ์นดํ ๊ณ ๋ฆฌ๋ณ๋ก ๋ถ๋ฆฌ
- ๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ: ๋ถํ ๋ถ์ฐ, ์ ๋ ฌ, ๊ทธ๋ํ ์๊ณ ๋ฆฌ์ฆ
๋ค์ ๋จ๊ณ
block.prefix_sum() ์ฐ์ฐ์ ๋ฐฐ์ ์ผ๋, ๋ค์์ผ๋ก ์งํํ ์ ์์ต๋๋ค:
- block.broadcast()์ ๋ฒกํฐ ์ ๊ทํ: ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋์ ๊ฐ์ ๊ณต์
- ๋ฉํฐ ๋ธ๋ก ์๊ณ ๋ฆฌ์ฆ: ๋ ํฐ ๋ฌธ์ ๋ฅผ ์ํ ์ฌ๋ฌ ๋ธ๋ก ๊ฐ ์กฐ์จ
- ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ: ์ ๋ ฌ, ๊ทธ๋ํ ํ์, ๋์ ๋ถํ ๋ถ์ฐ
- ๋ณต์กํ ๋ฉ๋ชจ๋ฆฌ ํจํด: ๋ธ๋ก ์ฐ์ฐ๊ณผ ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๊ฒฐํฉ
๐ก ํต์ฌ ์์ : ๋ธ๋ก ๋์ ํฉ ์ฐ์ฐ์ GPU ํ๋ก๊ทธ๋๋ฐ์ ๋จ์ํ ๋ณ๋ ฌ ๊ณ์ฐ์์ ๊ณ ๊ธ
๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋ณํํฉ๋๋ค. block.sum()์ด ๋ฆฌ๋์
์ ๋จ์ํํ๋ค๋ฉด,
block.prefix_sum()์ ๊ณ ์ฑ๋ฅ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ํ์์ ์ธ ๊ณ ๊ธ ๋ฐ์ดํฐ ์ฌ๊ตฌ์ฑ ํจํด์
๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
block.broadcast()์ ๋ฒกํฐ ์ ๊ทํ
block.sum๊ณผ block.broadcast ์ฐ์ฐ์ ๊ฒฐํฉํ์ฌ ๋ฒกํฐ ํ๊ท ์ ๊ทํ๋ฅผ ๊ตฌํํ๊ณ , ๋ธ๋ก ๋ ๋ฒจ ํต์ ์ํฌํ๋ก์ฐ์ ์ ์ฒด ๋ชจ์ต์ ๋ณด์ฌ์ค๋๋ค. ๊ฐ ์ค๋ ๋๊ฐ ํ๊ท ๊ณ์ฐ์ ๊ธฐ์ฌํ ๋ค์, ๋ธ๋ก๋์บ์คํธ๋ ํ๊ท ์ ๋ฐ์ ์์ ์ ์์๋ฅผ ์ ๊ทํํ์ฌ, ๋ธ๋ก ์ฐ์ฐ์ด ์ค์ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ํด๊ฒฐํ๊ธฐ ์ํด ์ด๋ป๊ฒ ํจ๊ป ๋์ํ๋์ง ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ํต์ฐฐ: block.broadcast() ์ฐ์ฐ์ ํ๋โ์ ์ฒด ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ์ฌ, ๊ธฐ๋ณธ ๋ธ๋ก ํต์ ํจํด์ ์์ฑํฉ๋๋ค: ๋ฆฌ๋์ (์ ์ฒดโํ๋), ์ค์บ(์ ์ฒดโ๊ฐ๊ฐ), ๋ธ๋ก๋์บ์คํธ(ํ๋โ์ ์ฒด).
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์ ๋ฐฐ์ธ ๋ด์ฉ:
block.broadcast()๋ฅผ ํ์ฉํ ๋ธ๋ก ๋ ๋ฒจ ๋ธ๋ก๋์บ์คํธ- ํ๋โ์ ์ฒด ํต์ ํจํด
- ์์ค ์ค๋ ๋ ์ง์ ๊ณผ ํ๋ผ๋ฏธํฐ ์ ์ด
- ์ฌ๋ฌ ์ฐ์ฐ์ ๊ฒฐํฉํ๋ ์์ ํ ๋ธ๋ก ์ฐ์ฐ ์ํฌํ๋ก์ฐ
- ์กฐ์จ๋ ๋ธ๋ก ๊ธฐ๋ณธ ์์๋ฅผ ์ฌ์ฉํ ์ค์ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ
์ด ์๊ณ ๋ฆฌ์ฆ์ ๋ฒกํฐ ํ๊ท ์ ๊ทํ๋ฅผ ๋ณด์ฌ์ค๋๋ค: \[\Large \text{output}[i] = \frac{\text{input}[i]}{\frac{1}{N}\sum_{j=0}^{N-1} \text{input}[j]}\]
๊ฐ ์ค๋ ๋๊ฐ ํ๊ท ๊ณ์ฐ์ ๊ธฐ์ฌํ ๋ค์, ๋ธ๋ก๋์บ์คํธ๋ ํ๊ท ์ ๋ฐ์ ์์ ์ ์์๋ฅผ ์ ๊ทํํฉ๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
SIZE = 128์์ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ธ๋ก ๊ตฌ์ฑ:
(128, 1)๋ธ๋ก๋น ์ค๋ ๋ ์ (TPB = 128) - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ:
(1, 1)๊ทธ๋ฆฌ๋๋น ๋ธ๋ก ์ - ๋ ์ด์์:
row_major[SIZE]()(์ ๋ ฅ๊ณผ ์ถ๋ ฅ ๋ชจ๋ 1D row-major) - ํ ์คํธ ๋ฐ์ดํฐ: 1-8 ๋ฐ๋ณต ๊ฐ, ํ๊ท = 4.5
- ์์ ์ถ๋ ฅ: ํ๊ท ์ด 1.0์ธ ์ ๊ทํ๋ ๋ฒกํฐ
๋์ ๊ณผ์ : ๋ธ๋ก ์ ์ฒด ๊ณ์ฐ๊ณผ ๋ถ๋ฐฐ์ ์กฐ์จ
๊ธฐ์กด์ ํ๊ท ์ ๊ทํ ๋ฐฉ์์ ๋ณต์กํ ์กฐ์จ์ด ํ์ํฉ๋๋ค:
# ์์ฐจ์ ๋ฐฉ์ - ๋ณ๋ ฌ์ฑ์ ํ์ฉํ์ง ๋ชปํจ
total = sum(input_array)
mean = total / len(input_array)
output_array = [x / mean for x in input_array]
๋จ์ํ GPU ๋ณ๋ ฌํ์ ๋ฌธ์ ์ :
- ๋ค์ค ์ปค๋ ์คํ: ํ๊ท ๊ณ์ฐ๊ณผ ์ ๊ทํ์ ๊ฐ๊ฐ ๋ณ๋์ ํจ์ค๊ฐ ํ์
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์๋ณต: ํ๊ท ์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๋ค๊ฐ ๋์ค์ ๋ค์ ์ฝ๊ธฐ
- ๋๊ธฐํ ๋ณต์ก์ฑ: ๊ณ์ฐ ๋จ๊ณ ๊ฐ์ ๋ฐฐ๋ฆฌ์ด๊ฐ ํ์
- ์ค๋ ๋ ๋ถ๊ธฐ: ์๋ก ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ์์ ์ ์ํ
๊ธฐ์กด GPU ํ์ด์ ๋ณต์ก์ฑ:
# 1๋จ๊ณ: ํฉ๊ณ๋ฅผ ๊ตฌํ๊ธฐ ์ํ ๋ฆฌ๋์
(๋ณต์กํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + ๋ฐฐ๋ฆฌ์ด)
shared_sum[local_i] = my_value
barrier()
# ์ฌ๋ฌ barrier() ํธ์ถ์ด ํ์ํ ์๋ ํธ๋ฆฌ ๋ฆฌ๋์
...
# 2๋จ๊ณ: ์ค๋ ๋ 0์ด ํ๊ท ์ ๊ณ์ฐ
if local_i == 0:
mean = shared_sum[0] / size
shared_mean[0] = mean
barrier()
# 3๋จ๊ณ: ๋ชจ๋ ์ค๋ ๋๊ฐ ํ๊ท ์ ์ฝ๊ณ ์ ๊ทํ
mean = shared_mean[0] # ๋ชจ๋๊ฐ ๊ฐ์ ๊ฐ์ ์ฝ์
output[global_i] = my_value / mean
๊ณ ๊ธ ๋ฐฉ์: block.sum() + block.broadcast() ์กฐ์จ
๋ค๋จ๊ณ ์กฐ์จ์ ๊ฐ๊ฒฐํ ๋ธ๋ก ์ฐ์ฐ ์ํฌํ๋ก์ฐ๋ก ๋ณํํฉ๋๋ค:
์์ฑํ ์ฝ๋
์์ ํ ๋ธ๋ก ์ฐ์ฐ ์ํฌํ๋ก์ฐ
๋ธ๋ก ์ฐ์ฐ ๋๊ตฌ ๋ชจ์ ์ ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ ๊ธ ๋ฒกํฐ ํ๊ท ์ ๊ทํ๋ฅผ ๊ตฌํํฉ๋๋ค:
comptime vector_layout = row_major[SIZE]()
comptime VectorLayoutType = type_of(vector_layout)
def block_normalize_vector[
tpb: Int
](
input_data: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
output_data: TileTensor[mut=True, dtype, VectorLayoutType, MutAnyOrigin],
size: Int,
):
"""Vector mean normalization using block.sum() + block.broadcast() combination.
This demonstrates the complete block operations workflow:
1. Use block.sum() to compute sum of all elements (all โ one)
2. Thread 0 computes mean = sum / size
3. Use block.broadcast() to share mean to all threads (one โ all)
4. Each thread normalizes: output[i] = input[i] / mean
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Step 1: Each thread loads its element
# FILL IN (roughly 3 lines)
# Step 2: Use block.sum() to compute total sum (familiar from earlier!)
# FILL IN (1 line)
# Step 3: Thread 0 computes mean value
# FILL IN (roughly 4 lines)
# Step 4: block.broadcast() shares mean to ALL threads!
# This completes the block operations trilogy demonstration
# FILL IN (1 line)
# Step 5: Each thread normalizes by the mean
# FILL IN (roughly 3 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p27/p27.mojo
ํ
1. ์์ ํ ์ํฌํ๋ก์ฐ ๊ตฌ์กฐ (๋ชจ๋ ์ด์ ์ฐ์ฐ์ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ถ)
์๊ณ ๋ฆฌ์ฆ์ ์๋ฒฝํ ๋ธ๋ก ์ฐ์ฐ ํจํด์ ๋ฐ๋ฆ ๋๋ค:
- ๊ฐ ์ค๋ ๋๊ฐ ์์ ์ ์์๋ฅผ ๋ก๋ (๋ชจ๋ ์ด์ ํผ์ฆ์์ ์ต์ํ ํจํด)
block.sum()์ผ๋ก ํฉ๊ณ๋ฅผ ๊ณ์ฐ (์ด ํผ์ฆ์ ์๋ถ๋ถ์์ ๋ฐฐ์ด ๋ด์ฉ)- ์ค๋ ๋ 0์ด ํฉ๊ณ๋ก๋ถํฐ ํ๊ท ์ ๊ณ์ฐ
block.broadcast()๋ก ํ๊ท ์ ๋ชจ๋ ์ค๋ ๋์ ๊ณต์ (์๋ก์ด ๋ด์ฉ!)- ๊ฐ ์ค๋ ๋๊ฐ ๋ธ๋ก๋์บ์คํธ๋ ํ๊ท ์ผ๋ก ์ ๊ทํ
2. ๋ฐ์ดํฐ ๋ก๋ฉ๊ณผ ํฉ๊ณ ๊ณ์ฐ (์ต์ํ ํจํด)
๊ธฐ์กด TileTensor ํจํด์ผ๋ก ์์๋ฅผ ๋ก๋ํฉ๋๋ค:
var my_value: Scalar[dtype] = 0.0
if global_i < size:
my_value = input_data[global_i][0] # SIMD ์ถ์ถ
๊ทธ๋ฐ ๋ค์ ์์ ๋ฐฐ์ด ๋ด์ ๊ณผ ๋์ผํ๊ฒ block.sum()์ ์ฌ์ฉํฉ๋๋ค:
total_sum = block.sum[block_size=tpb, broadcast=False](...)
3. ํ๊ท ๊ณ์ฐ (์ค๋ ๋ 0๋ง)
์ค๋ ๋ 0๋ง ํ๊ท ์ ๊ณ์ฐํด์ผ ํฉ๋๋ค:
var mean_value: Scalar[dtype] = 1.0 # ์์ ํ ๊ธฐ๋ณธ๊ฐ
if local_i == 0:
# total_sum๊ณผ size๋ก ํ๊ท ๊ณ์ฐ
์ ์ค๋ ๋ 0์ธ๊ฐ? block.sum() ํจํด์์ ์ค๋ ๋ 0์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ ๊ฒ๊ณผ ์ผ๊ด์ฑ์
์ ์งํฉ๋๋ค.
4. block.broadcast() API ๊ฐ๋
ํจ์ ์๊ทธ๋์ฒ๋ฅผ ์ดํด๋ณด์ธ์ - ๋ค์์ด ํ์ํฉ๋๋ค:
- ํ
ํ๋ฆฟ ํ๋ผ๋ฏธํฐ:
dtype,width,block_size - ๋ฐํ์ ํ๋ผ๋ฏธํฐ:
val(๋ธ๋ก๋์บ์คํธํ SIMD ๊ฐ),src_thread(๊ธฐ๋ณธ๊ฐ=0)
ํธ์ถ ํจํด์ ๊ธฐ์กด ํ ํ๋ฆฟ ์คํ์ผ์ ๋ฐ๋ฆ ๋๋ค:
result = block.broadcast[
dtype = DType.float32,
width = 1,
block_size = tpb
](val=SIMD[DType.float32, 1](value_to_broadcast), src_thread=UInt(0))
5. ๋ธ๋ก๋์บ์คํธ ํจํด ์ดํดํ๊ธฐ
ํต์ฌ ํต์ฐฐ: block.broadcast()๋ ํ๋์ ์ค๋ ๋์์ ๊ฐ์ ๊ฐ์ ธ์ ๋ชจ๋ ์ค๋ ๋์
์ ๋ฌํฉ๋๋ค:
- ์ค๋ ๋ 0์ด ๊ณ์ฐ๋ ํ๊ท ๊ฐ์ ๊ฐ์ง๊ณ ์์
- ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ํ๊ท ๊ฐ์ด ํ์
block.broadcast()๊ฐ ์ค๋ ๋ 0์ ๊ฐ์ ๋ชจ๋์๊ฒ ๋ณต์ฌ
์ด๊ฒ์ block.sum()(์ ์ฒดโํ๋)์ ๋ฐ๋์ด๋ฉฐ, block.prefix_sum()(์ ์ฒดโ๊ฐ๊ฐ
์์น)๊ณผ๋ ๋ค๋ฆ
๋๋ค.
6. ์ต์ข ์ ๊ทํ ๋จ๊ณ
๋ชจ๋ ์ค๋ ๋๊ฐ ๋ธ๋ก๋์บ์คํธ๋ ํ๊ท ์ ๋ฐ์ผ๋ฉด, ์์ ์ ์์๋ฅผ ์ ๊ทํํฉ๋๋ค:
if global_i < size:
normalized_value = my_value / broadcasted_mean[0] # SIMD ์ถ์ถ
output_data[global_i] = normalized_value
SIMD ์ถ์ถ: block.broadcast()๊ฐ SIMD๋ฅผ ๋ฐํํ๋ฏ๋ก [0]์ผ๋ก ์ค์นผ๋ผ๋ฅผ
์ถ์ถํด์ผ ํฉ๋๋ค.
7. ์ด์ ํผ์ฆ์์์ ํจํด ์ธ์
- ์ค๋ ๋ ์ธ๋ฑ์ฑ: ํญ์ ๋์ผํ
global_i,local_iํจํด - ๊ฒฝ๊ณ ๊ฒ์ฌ: ๋์ผํ
if global_i < size๊ฒ์ฆ - SIMD ์ฒ๋ฆฌ: ๋์ผํ
[0]์ถ์ถ ํจํด - ๋ธ๋ก ์ฐ์ฐ:
block.sum()๊ณผ ๋์ผํ ํ ํ๋ฆฟ ํ๋ผ๋ฏธํฐ ์คํ์ผ
๊ฐ ๋ธ๋ก ์ฐ์ฐ์ด ์ผ๊ด๋ ํจํด์ ๋ฐ๋ฅด๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค!
block.broadcast() ๋ฐฉ์ ํ ์คํธ:
pixi run p27 --normalize
pixi run -e amd p27 --normalize
pixi run -e apple p27 --normalize
uv run poe p27 --normalize
ํ์์ ๋์ ์์ ์ถ๋ ฅ:
SIZE: 128
TPB: 128
Input sample: 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 ...
Sum value: 576.0
Mean value: 4.5
Mean Normalization Results:
Normalized sample: 0.22222222 0.44444445 0.6666667 0.8888889 1.1111112 1.3333334 1.5555556 1.7777778 ...
Output sum: 128.0
Output mean: 1.0
โ
Success: Output mean is 1.0 (should be close to 1.0)
์๋ฃจ์
def block_normalize_vector[
tpb: Int
](
input_data: TileTensor[mut=False, dtype, InLayout, ImmutAnyOrigin],
output_data: TileTensor[mut=True, dtype, VectorLayout, MutAnyOrigin],
size: Int,
):
"""Vector mean normalization using block.sum() + block.broadcast() combination.
This demonstrates the complete block operations workflow:
1. Use block.sum() to compute sum of all elements (all -> one)
2. Thread 0 computes mean = sum / size
3. Use block.broadcast() to share mean to all threads (one -> all)
4. Each thread normalizes: output[i] = input[i] / mean
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Step 1: Each thread loads its element
var my_value: Scalar[dtype] = 0.0
if global_i < size:
my_value = input_data[global_i][0] # Extract SIMD value
# Step 2: Use block.sum() to compute total sum (familiar from earlier!)
var total_sum = block.sum[block_size=tpb, broadcast=False](
val=SIMD[DType.float32, 1](my_value)
)
# Step 3: Thread 0 computes mean value
var mean_value: Scalar[dtype] = 1.0 # Default to avoid division by zero
if local_i == 0:
if total_sum[0] > 0.0:
mean_value = total_sum[0] / Scalar[dtype](size)
# Step 4: block.broadcast() shares mean to ALL threads!
# This completes the block operations trilogy demonstration
var broadcasted_mean = block.broadcast[
dtype=DType.float32, width=1, block_size=tpb
](val=SIMD[DType.float32, 1](mean_value), src_thread=UInt(0))
# Step 5: Each thread normalizes by the mean
if global_i < size:
var normalized_value = my_value / broadcasted_mean[0]
output_data[global_i] = normalized_value
block.broadcast() ์ปค๋์ ์ธ ๊ฐ์ง ๊ธฐ๋ณธ ํต์ ํจํด์ ๋ชจ๋ ๊ฒฐํฉํ์ฌ ์ํ์ ์ผ๋ก
๊ฒ์ฆ ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ๋ ์ค์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์์ ํ ๋ธ๋ก ์ฐ์ฐ ์ํฌํ๋ก์ฐ๋ฅผ
๋ณด์ฌ์ค๋๋ค:
๊ตฌ์ฒด์ ์ธ ์คํ์ ํตํ ์์ ํ ์๊ณ ๋ฆฌ์ฆ ๋ถ์:
1๋จ๊ณ: ๋ณ๋ ฌ ๋ฐ์ดํฐ ๋ก๋ฉ (๋ชจ๋ ์ด์ ํผ์ฆ์์ ํ๋ฆฝ๋ ํจํด)
์ค๋ ๋ ์ธ๋ฑ์ฑ (๋ชจ๋ ํผ์ฆ์์ ์ผ๊ด๋จ):
global_i = block_dim.x * block_idx.x + thread_idx.x // ์
๋ ฅ ๋ฐฐ์ด ์์น์ ๋งคํ
local_i = thread_idx.x // ๋ธ๋ก ๋ด ์์น (0-127)
TileTensor ํจํด์ ์ฌ์ฉํ ๋ณ๋ ฌ ์์ ๋ก๋ฉ:
์ค๋ ๋ 0: my_value = input_data[0][0] = 1.0 // ์ฒซ ๋ฒ์งธ ์ํ ๊ฐ
์ค๋ ๋ 1: my_value = input_data[1][0] = 2.0 // ๋ ๋ฒ์งธ ์ํ ๊ฐ
์ค๋ ๋ 7: my_value = input_data[7][0] = 8.0 // ๋ง์ง๋ง ์ํ ๊ฐ
์ค๋ ๋ 8: my_value = input_data[8][0] = 1.0 // ์ํ ๋ฐ๋ณต: 1,2,3,4,5,6,7,8,1,2...
์ค๋ ๋ 15: my_value = input_data[15][0] = 8.0 // 15 % 8 = 7, 8๋ฒ์งธ ๊ฐ
์ค๋ ๋ 127: my_value = input_data[127][0] = 8.0 // 127 % 8 = 7, 8๋ฒ์งธ ๊ฐ
128๊ฐ ์ค๋ ๋๊ฐ ๋์์ ๋ก๋ - ์๋ฒฝํ ๋ณ๋ ฌ ํจ์จ!
2๋จ๊ณ: ๋ธ๋ก ์ ์ฒด ํฉ๊ณ ๋ฆฌ๋์ (์์ ๋ฐฐ์ด block.sum() ์ง์ ํ์ฉ)
128๊ฐ ์ค๋ ๋์ ๊ฑธ์น block.sum() ์กฐ์จ:
๊ธฐ์ฌ๋ถ ๋ถ์:
- ๊ฐ 1,2,3,4,5,6,7,8์ด ๊ฐ๊ฐ 16๋ฒ ๋ฐ๋ณต (128/8 = 16)
- ์ค๋ ๋ ๊ธฐ์ฌ๋ถ: 16ร1 + 16ร2 + 16ร3 + 16ร4 + 16ร5 + 16ร6 + 16ร7 + 16ร8
- ์ํ์ ํฉ๊ณ: 16 ร (1+2+3+4+5+6+7+8) = 16 ร 36 = 576.0
block.sum() ํ๋์จ์ด ์คํ:
๋ชจ๋ ์ค๋ ๋ โ [๋ฆฌ๋์
ํธ๋ฆฌ] โ ์ค๋ ๋ 0
total_sum = SIMD[DType.float32, 1](576.0) // ์ค๋ ๋ 0๋ง ์ด ๊ฐ์ ์์
์ค๋ ๋ 1-127: total_sum์ ์ ๊ทผ ๋ถ๊ฐ (block.sum์์ broadcast=False)
3๋จ๊ณ: ๋ ์ ์ ํ๊ท ๊ณ์ฐ (๋จ์ผ ์ค๋ ๋ ์ฒ๋ฆฌ)
์ค๋ ๋ 0์ด ํต์ฌ ๊ณ์ฐ์ ์ํ:
์
๋ ฅ: total_sum[0] = 576.0, size = 128
๊ณ์ฐ: mean_value = 576.0 / 128.0 = 4.5
๊ฒ์ฆ: ๊ธฐ๋ ํ๊ท = (1+2+3+4+5+6+7+8)/8 = 36/8 = 4.5 โ
๋ค๋ฅธ ๋ชจ๋ ์ค๋ ๋ (1-127):
mean_value = 1.0 (๊ธฐ๋ณธ ์์ ๊ฐ)
์ด ๊ฐ๋ค์ ๋ฌด๊ด - ๋ธ๋ก๋์บ์คํธ๋ก ๋ฎ์ด์์์ง ์์
ํต์ฌ ํต์ฐฐ: ์ด ์์ ์์ ์ฌ๋ฐ๋ฅธ ํ๊ท ๊ฐ์ ๊ฐ์ง ๊ฒ์ ์ค๋ ๋ 0๋ฟ์
๋๋ค!
4๋จ๊ณ: ๋ธ๋ก ์ ์ฒด ๋ธ๋ก๋์บ์คํธ ๋ถ๋ฐฐ (ํ๋ โ ์ ์ฒด ํต์ )
block.broadcast() API ์คํ:
์์ค: src_thread = UInt(0) โ ์ค๋ ๋ 0์ mean_value = 4.5
๋์: ๋ธ๋ก ๋ด ๋ชจ๋ 128 ์ค๋ ๋
๋ธ๋ก๋์บ์คํธ ์ :
์ค๋ ๋ 0: mean_value = 4.5 โ ์ง์ค์ ์์ฒ
์ค๋ ๋ 1: mean_value = 1.0 โ ๋ฎ์ด์์์ง ์์
์ค๋ ๋ 2: mean_value = 1.0 โ ๋ฎ์ด์์์ง ์์
...
์ค๋ ๋ 127: mean_value = 1.0 โ ๋ฎ์ด์์์ง ์์
block.broadcast() ์คํ ํ:
์ค๋ ๋ 0: broadcasted_mean[0] = 4.5 โ ์์ ์ ๊ฐ์ ๋ค์ ์์
์ค๋ ๋ 1: broadcasted_mean[0] = 4.5 โ ์ด์ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ์ง!
์ค๋ ๋ 2: broadcasted_mean[0] = 4.5 โ ์ด์ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ์ง!
...
์ค๋ ๋ 127: broadcasted_mean[0] = 4.5 โ ์ด์ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ์ง!
๊ฒฐ๊ณผ: ์๋ฒฝํ ๋๊ธฐํ - ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ํ๊ท ๊ฐ์ ๊ฐ์ง!
5๋จ๊ณ: ๋ณ๋ ฌ ํ๊ท ์ ๊ทํ (์กฐ์จ๋ ์ฒ๋ฆฌ)
๊ฐ ์ค๋ ๋๊ฐ ๋ธ๋ก๋์บ์คํธ๋ ํ๊ท ์ ์ฌ์ฉํ์ฌ ๋
๋ฆฝ์ ์ผ๋ก ์ ๊ทํ:
์ค๋ ๋ 0: normalized = 1.0 / 4.5 = 0.22222222...
์ค๋ ๋ 1: normalized = 2.0 / 4.5 = 0.44444444...
์ค๋ ๋ 2: normalized = 3.0 / 4.5 = 0.66666666...
์ค๋ ๋ 7: normalized = 8.0 / 4.5 = 1.77777777...
์ค๋ ๋ 8: normalized = 1.0 / 4.5 = 0.22222222... (ํจํด ๋ฐ๋ณต)
...
์ํ์ ๊ฒ์ฆ:
์ถ๋ ฅ ํฉ๊ณ = (0.222... + 0.444... + ... + 1.777...) ร 16 = 4.5 ร 16 ร 2 = 128.0
์ถ๋ ฅ ํ๊ท = 128.0 / 128 = 1.0 ์๋ฒฝํ ์ ๊ทํ!
๊ฐ ๊ฐ์ ์๋ ํ๊ท ์ผ๋ก ๋๋๋ฉด ํ๊ท ์ด 1.0์ธ ์ถ๋ ฅ์ ์์ฑ
6๋จ๊ณ: ์ ํ์ฑ ๊ฒ์ฆ
์
๋ ฅ ๋ถ์:
- ํฉ๊ณ: 576.0, ํ๊ท : 4.5
- ์ต๋๊ฐ: 8.0, ์ต์๊ฐ: 1.0
- ๋ฒ์: [1.0, 8.0]
์ถ๋ ฅ ๋ถ์:
- ํฉ๊ณ: 128.0, ํ๊ท : 1.0 โ
- ์ต๋๊ฐ: 1.777..., ์ต์๊ฐ: 0.222...
- ๋ฒ์: [0.222, 1.777] (๋ชจ๋ ๊ฐ์ด 1/4.5 ๋น์จ๋ก ์ค์ผ์ผ๋ง)
๋น๋ก ๊ด๊ณ ๋ณด์กด:
- ์๋ 8:1 ๋น์จ์ด 1.777:0.222 = 8:1๋ก ์ ์ง โ
- ๋ชจ๋ ์๋์ ํฌ๊ธฐ๊ฐ ์๋ฒฝํ๊ฒ ์ ์ง
์ด ์์ ํ ์ํฌํ๋ก์ฐ๊ฐ ์ํ์ ยท๊ณ์ฐ์ ์ผ๋ก ์ฐ์ํ ์ด์ :
๊ธฐ์ ์ ์ ํ์ฑ๊ณผ ๊ฒ์ฆ:
์ํ์ ์ ํ์ฑ ์ฆ๋ช
:
์
๋ ฅ: xโ, xโ, ..., xโ (n = 128)
ํ๊ท : ฮผ = (โxแตข)/n = 576/128 = 4.5
์ ๊ทํ: yแตข = xแตข/ฮผ
์ถ๋ ฅ ํ๊ท : (โyแตข)/n = (โxแตข/ฮผ)/n = (1/ฮผ)(โxแตข)/n = (1/ฮผ)ฮผ = 1 โ
์๊ณ ๋ฆฌ์ฆ์ด ์ฆ๋ช
๊ฐ๋ฅํ๊ฒ ์ฌ๋ฐ๋ฅธ ์ํ์ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค.
Puzzle 12 (๊ธฐ์ด ํจํด)๊ณผ์ ์ฐ๊ฒฐ:
- ์ค๋ ๋ ์กฐ์จ์ ์งํ: ๋์ผํ
global_i,local_iํจํด์ด์ง๋ง ๋ธ๋ก ๊ธฐ๋ณธ ์์ ์ฌ์ฉ - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด: ๋์ผํ TileTensor SIMD ์ถ์ถ
[0]์ด์ง๋ง ์ต์ ํ๋ ์ํฌํ๋ก์ฐ - ๋ณต์ก์ฑ ์ ๊ฑฐ: 20์ค ์ด์์ ์๋ ๋ฐฐ๋ฆฌ์ด๋ฅผ 2๊ฐ์ ๋ธ๋ก ์ฐ์ฐ์ผ๋ก ๋์ฒด
- ๊ต์ก์ ์งํ: ์๋ โ ์๋, ๋ณต์ก โ ๋จ์, ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ โ ์ ๋ขฐ์ฑ
block.sum() (์๋ฒฝํ ํตํฉ)๊ณผ์ ์ฐ๊ฒฐ:
- API ์ผ๊ด์ฑ: ๋์ผํ ํ
ํ๋ฆฟ ๊ตฌ์กฐ
[block_size=tpb, broadcast=False] - ๊ฒฐ๊ณผ ํ๋ฆ ์ค๊ณ: ์ค๋ ๋ 0์ด ํฉ๊ณ๋ฅผ ์์ ํ๊ณ , ์์ฐ์ค๋ฝ๊ฒ ํ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ณ์ฐ
- ๋งค๋๋ฌ์ด ์กฐํฉ:
block.sum()์ ์ถ๋ ฅ์ด ๊ณ์ฐ + ๋ธ๋ก๋์บ์คํธ์ ์ ๋ ฅ์ด ๋จ - ์ฑ๋ฅ ์ต์ ํ: ๋จ์ผ ์ปค๋ ์ํฌํ๋ก์ฐ vs ๋ค์ค ํจ์ค ๋ฐฉ์
block.prefix_sum() (์๋ณด์ ํต์ )๊ณผ์ ์ฐ๊ฒฐ:
-
๋ถ๋ฐฐ ํจํด:
prefix_sum์ ๊ณ ์ ํ ์์น๋ฅผ,broadcast๋ ๊ณต์ ๊ฐ์ ์ ๊ณต -
์ฌ์ฉ ์๋๋ฆฌ์ค:
prefix_sum์ ๋ณ๋ ฌ ํํฐ์ ๋์ฉ,broadcast๋ ๋งค๊ฐ๋ณ์ ๊ณต์ ์ฉ -
ํ ํ๋ฆฟ ์ผ๊ด์ฑ: ๋ชจ๋ ์ฐ์ฐ์์ ๋์ผํ
dtype,block_sizeํ๋ผ๋ฏธํฐ ํจํด -
SIMD ์ฒ๋ฆฌ ํต์ผ์ฑ: ๋ชจ๋ ๋ธ๋ก ์ฐ์ฐ์ด
[0]์ถ์ถ์ด ํ์ํ SIMD๋ฅผ ๋ฐํ
๊ณ ๊ธ ์๊ณ ๋ฆฌ์ฆ ์ธ์ฌ์ดํธ:
ํต์ ํจํด ๋น๊ต:
๊ธฐ์กด ๋ฐฉ์:
1. ์๋ ๋ฆฌ๋์
: O(log n), ๋ช
์์ ๋ฐฐ๋ฆฌ์ด ํ์
2. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ: O(1), ๋๊ธฐํ ํ์
3. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ: O(1), ๋ฑ
ํฌ ์ถฉ๋ ๊ฐ๋ฅ์ฑ
์ดํฉ: ๋ค์์ ๋๊ธฐํ ์ง์ , ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ
๋ธ๋ก ์ฐ์ฐ ๋ฐฉ์:
1. block.sum(): O(log n), ํ๋์จ์ด ์ต์ ํ, ์๋ ๋ฐฐ๋ฆฌ์ด
2. ๊ณ์ฐ: O(1), ๋จ์ผ ์ค๋ ๋
3. block.broadcast(): O(log n), ํ๋์จ์ด ์ต์ ํ, ์๋ ๋ถ๋ฐฐ
์ดํฉ: ๋ ๊ฐ์ ๊ธฐ๋ณธ ์์, ์๋ ๋๊ธฐํ, ์ฆ๋ช
๋ ์ ํ์ฑ
์ค์ ์์ฉ ์๊ณ ๋ฆฌ์ฆ ํจํด:
์ผ๋ฐ์ ์ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ:
1๋จ๊ณ: ๋ณ๋ ฌ ๋ฐ์ดํฐ ์ฒ๋ฆฌ โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ธฐ์ฌ
2๋จ๊ณ: ์ ์ญ ํ๋ผ๋ฏธํฐ ๊ณ์ฐ โ ํ๋์ ์ค๋ ๋๊ฐ ๊ณ์ฐ
3๋จ๊ณ: ํ๋ผ๋ฏธํฐ ๋ถ๋ฐฐ โ ๋ชจ๋ ์ค๋ ๋๊ฐ ์์
4๋จ๊ณ: ์กฐ์จ๋ ๋ณ๋ ฌ ์ถ๋ ฅ โ ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฒ๋ฆฌ
์ด ์ ํํ ํจํด์ด ๋ฑ์ฅํ๋ ๋ถ์ผ:
- ๋ฐฐ์น ์ ๊ทํ (๋ฅ๋ฌ๋)
- ํ์คํ ๊ทธ๋จ ๊ท ๋ฑํ (์ด๋ฏธ์ง ์ฒ๋ฆฌ)
- ๋ฐ๋ณต์ ์์น ํด๋ฒ (๊ณผํ ์ฐ์ฐ)
- ์กฐ๋ช
๊ณ์ฐ (์ปดํจํฐ ๊ทธ๋ํฝ)
ํ๊ท ์ ๊ทํ๋ ์ด ๊ทผ๋ณธ์ ์ธ ํจํด์ ์๋ฒฝํ ๊ต์ก ์ฌ๋ก์
๋๋ค.
๋ธ๋ก ์ฐ์ฐ 3๋ถ์ ์์ฑ:
1. block.sum() - ์ ์ฒดโํ๋ (Reduction)
- ์ ๋ ฅ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์ ๊ณต
- ์ถ๋ ฅ: ์ค๋ ๋ 0์ด ์ง๊ณ๋ ๊ฒฐ๊ณผ๋ฅผ ์์
- ์ฉ๋: ํฉ๊ณ, ์ต๋๊ฐ ๊ณ์ฐ ๋ฑ
2. block.prefix_sum() - ์ ์ฒดโ๊ฐ๊ฐ (Scan)
- ์ ๋ ฅ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์ ๊ณต
- ์ถ๋ ฅ: ๊ฐ ์ค๋ ๋๊ฐ ๋์ ์์น๋ฅผ ์์
- ์ฉ๋: ์ฐ๊ธฐ ์์น ๊ณ์ฐ, ๋ณ๋ ฌ ํํฐ์ ๋
3. block.broadcast() - ํ๋โ์ ์ฒด (Broadcast)
- ์ ๋ ฅ: ํ๋์ ์ค๋ ๋๊ฐ ๊ฐ์ ์ ๊ณต (์ผ๋ฐ์ ์ผ๋ก ์ค๋ ๋ 0)
- ์ถ๋ ฅ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๊ฐ์ ์์
- ์ฉ๋: ๊ณ์ฐ๋ ๋งค๊ฐ๋ณ์ ๊ณต์ , ์ค์ ๊ฐ ๋ถ๋ฐฐ
์์ ํ ๋ธ๋ก ์ฐ์ฐ ์งํ:
- ์๋ ์กฐ์จ (Puzzle 12): ๋ณ๋ ฌ ๊ธฐ์ด ์ดํด
- ์ํ ๊ธฐ๋ณธ ์์ (Puzzle 24): ํ๋์จ์ด ๊ฐ์ ํจํด ํ์ต
- ๋ธ๋ก ๋ฆฌ๋์
(
block.sum()): ์ ์ฒดโํ๋ ํต์ ํ์ต - ๋ธ๋ก ์ค์บ (
block.prefix_sum()): ์ ์ฒดโ๊ฐ๊ฐ ํต์ ํ์ต - ๋ธ๋ก ๋ธ๋ก๋์บ์คํธ (
block.broadcast()): ํ๋โ์ ์ฒด ํต์ ํ์ต
์ ์ฒด ๊ทธ๋ฆผ: ๋ธ๋ก ์ฐ์ฐ์ ๊ณ ๊ธ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๊ธฐ๋ณธ ํต์ ๋น๋ฉ ๋ธ๋ก์ ์ ๊ณตํ๋ฉฐ, ๋ณต์กํ ์๋ ์กฐ์จ์ ๊น๋ํ๊ณ ์กฐํฉ ๊ฐ๋ฅํ ๊ธฐ๋ณธ ์์๋ก ๋์ฒดํฉ๋๋ค.
์ฑ๋ฅ ์ธ์ฌ์ดํธ์ ๊ธฐ์ ๋ถ์
์ ๋์ ์ฑ๋ฅ ๋น๊ต:
block.broadcast() vs ๊ธฐ์กด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์ (์ฐธ๊ณ ์ฉ):
๊ธฐ์กด ์๋ ๋ฐฉ์:
1๋จ๊ณ: ์๋ ๋ฆฌ๋์
โข ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น: ~5 ์ฌ์ดํด
โข ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ: ~10 ์ฌ์ดํด
โข ํธ๋ฆฌ ๋ฆฌ๋์
๋ฃจํ: ~15 ์ฌ์ดํด
โข ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅํ ์๋ ์ธ๋ฑ์ฑ
2๋จ๊ณ: ํ๊ท ๊ณ์ฐ: ~2 ์ฌ์ดํด
3๋จ๊ณ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก๋์บ์คํธ
โข ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์๋ ์ฐ๊ธฐ: ~2 ์ฌ์ดํด
โข ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ: ~10 ์ฌ์ดํด
โข ๋ชจ๋ ์ค๋ ๋ ์ฝ๊ธฐ: ~3 ์ฌ์ดํด
์ดํฉ: ~47 ์ฌ์ดํด
+ ๋๊ธฐํ ์ค๋ฒํค๋
+ ๊ฒฝ์ ์ํ ๊ฐ๋ฅ์ฑ
+ ์๋ ์ค๋ฅ ๋๋ฒ๊น
๋ธ๋ก ์ฐ์ฐ ๋ฐฉ์:
1๋จ๊ณ: block.sum()
โข ํ๋์จ์ด ์ต์ ํ: ~3 ์ฌ์ดํด
โข ์๋ ๋ฐฐ๋ฆฌ์ด: ๋ช
์์ ๋น์ฉ 0
โข ์ต์ ํ๋ ๋ฆฌ๋์
: ~8 ์ฌ์ดํด
โข ๊ฒ์ฆ๋ ์ฌ๋ฐ๋ฅธ ๊ตฌํ
2๋จ๊ณ: ํ๊ท ๊ณ์ฐ: ~2 ์ฌ์ดํด
3๋จ๊ณ: block.broadcast()
โข ํ๋์จ์ด ์ต์ ํ: ~4 ์ฌ์ดํด
โข ์๋ ๋ถ๋ฐฐ: ๋ช
์์ ๋น์ฉ 0
โข ๊ฒ์ฆ๋ ์ฌ๋ฐ๋ฅธ ๊ตฌํ
์ดํฉ: ~17 ์ฌ์ดํด
+ ์๋ ์ต์ ํ
+ ๋ณด์ฅ๋ ์ ํ์ฑ
+ ์กฐํฉ ๊ฐ๋ฅํ ์ค๊ณ
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ด์ :
์บ์ ํจ์จ:
- block.sum(): ์ต์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ผ๋ก ์บ์ ๋ฏธ์ค ๊ฐ์
- block.broadcast(): ํจ์จ์ ์ธ ๋ถ๋ฐฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ ์ต์ํ
- ๊ฒฐํฉ ์ํฌํ๋ก์ฐ: ๋จ์ผ ์ปค๋์ด ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์๋ณต์ 100% ๊ฐ์
๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ:
๊ธฐ์กด ๋ฉํฐ ์ปค๋ ๋ฐฉ์:
์ปค๋ 1: ์
๋ ฅ โ ๋ฆฌ๋์
โ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ
์ปค๋ 2: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ โ ๋ธ๋ก๋์บ์คํธ โ ์ถ๋ ฅ
์ด ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ์ก: ๋ฐฐ์ด ํฌ๊ธฐ์ 3๋ฐฐ
๋ธ๋ก ์ฐ์ฐ ๋จ์ผ ์ปค๋:
์
๋ ฅ โ block.sum() โ block.broadcast() โ ์ถ๋ ฅ
์ด ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ์ก: ๋ฐฐ์ด ํฌ๊ธฐ์ 2๋ฐฐ (33% ๊ฐ์ )
๊ฐ ๋ธ๋ก ์ฐ์ฐ์ ์ต์ ์ฌ์ฉ ์๋๋ฆฌ์ค:
block.sum() ์ต์ ์๋๋ฆฌ์ค:
- ๋ฐ์ดํฐ ์ง๊ณ: ํฉ๊ณ, ํ๊ท , ์ต๋๊ฐ/์ต์๊ฐ ๊ณ์ฐ
- ๋ฆฌ๋์ ํจํด: ์ ์ฒดโํ๋ ํต์ ์ด ํ์ํ ๋ชจ๋ ๊ฒฝ์ฐ
- ํต๊ณ ์ฐ์ฐ: ํ๊ท , ๋ถ์ฐ, ์๊ด๊ด๊ณ ๊ณ์ฐ
block.prefix_sum() ์ต์ ์๋๋ฆฌ์ค:
- ๋ณ๋ ฌ ํํฐ์ ๋: ์คํธ๋ฆผ ์ปดํฉ์ , ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
- ์ฐ๊ธฐ ์์น ๊ณ์ฐ: ๋ณ๋ ฌ ์ถ๋ ฅ ์์ฑ
- ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ: ์ ๋ ฌ, ๊ฒ์, ๋ฐ์ดํฐ ์ฌ๊ตฌ์ฑ
block.broadcast() ์ต์ ์๋๋ฆฌ์ค:
- ๋งค๊ฐ๋ณ์ ๋ถ๋ฐฐ: ๊ณ์ฐ๋ ๊ฐ์ ๋ชจ๋ ์ค๋ ๋์ ๊ณต์
- ์ค์ ์ ํ: ๋ชจ๋ ํ๋๊ทธ, ์ค์ผ์ผ๋ง ํฉํฐ, ์๊ณ๊ฐ
- ์กฐ์จ๋ ์ฒ๋ฆฌ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ๊ณ์ฐ๋ ๋งค๊ฐ๋ณ์๊ฐ ํ์ํ ๋
์กฐํฉ์ ์ด์ :
๊ฐ๋ณ ์ฐ์ฐ: ์ข์ ์ฑ๋ฅ, ์ ํ๋ ๋ฒ์
๊ฒฐํฉ ์ฐ์ฐ: ํ์ํ ์ฑ๋ฅ, ํฌ๊ด์ ์ธ ์๊ณ ๋ฆฌ์ฆ
์ค์ ์์ฉ์์ ๋ณผ ์ ์๋ ์กฐํฉ ์์:
โข block.sum() + block.broadcast(): ์ ๊ทํ ์๊ณ ๋ฆฌ์ฆ
โข block.prefix_sum() + block.sum(): ๊ณ ๊ธ ํํฐ์
๋
โข ์ธ ๊ฐ์ง ๋ชจ๋ ๊ฒฐํฉ: ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ
โข ๊ธฐ์กด ํจํด๊ณผ ํจ๊ป: ํ์ด๋ธ๋ฆฌ๋ ์ต์ ํ ์ ๋ต
๋ค์ ๋จ๊ณ
์์ ํ ๋ธ๋ก ์ฐ์ฐ 3๋ถ์์ ๋ฐฐ์ ์ผ๋, ๋ค์์ผ๋ก ์งํํ ์ ์์ต๋๋ค:
- ๋ฉํฐ ๋ธ๋ก ์๊ณ ๋ฆฌ์ฆ: ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์น ์ฐ์ฐ ์กฐ์จ
- ๊ณ ๊ธ ๋ณ๋ ฌ ํจํด: ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋ธ๋ก ์ฐ์ฐ ๊ฒฐํฉ
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ: ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ด๋ ํจํด
- ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ: ๋ธ๋ก ์ฐ์ฐ ๋น๋ฉ ๋ธ๋ก์ ์ฌ์ฉํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐํ
- ์ฑ๋ฅ ์ต์ ํ: ์ต์ ์ ๋ธ๋ก ํฌ๊ธฐ์ ์ฐ์ฐ ์กฐํฉ ์ ํ
๐ก ํต์ฌ ์์ : ๋ธ๋ก ์ฐ์ฐ 3๋ถ์(sum, prefix_sum, broadcast)์ ๋ธ๋ก ๋ ๋ฒจ
๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ์ ์ํ ์์ ํ ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด ์ฐ์ฐ๋ค์ ์กฐํฉํ๋ฉด
GPU ํ๋์จ์ด ์ต์ ํ๋ฅผ ํ์ฉํ๋ ๊น๋ํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ก ๊ณ ๊ธ ๋ณ๋ ฌ
์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ ์ ์์ต๋๋ค. ํ๊ท ์ ๊ทํ๋ ์ด ์ฐ์ฐ๋ค์ด ํจ๊ป ์๋ํ์ฌ ์ค์ ์ฐ์ฐ
๋ฌธ์ ๋ฅผ ํจ์จ์ ์ผ๋ก ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
Puzzle 28: ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ๊ณผ ๋ณต์ฌ ์ค์ฒฉ
GPU ๋ฉ๋ชจ๋ฆฌ ๋ณ๋ชฉ ํ์: ์ค์ GPU ์๊ณ ๋ฆฌ์ฆ ๋๋ถ๋ถ์ ์ข์ ์ค๋ฌ์ด ๋ฒฝ์ ๋ถ๋ชํ๋๋ค
- ์ฐ์ฐ ๋ฅ๋ ฅ์ด ์๋๋ผ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ์ํด ์ ํ๋๋ค๋ ๊ฒ์ ๋๋ค. ๋น์ผ GPU ์ฝ์ด๊ฐ ๋๋ฆฐ DRAM์์ ๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๋๊ณ ์๋ ๊ฒ์ด์ฃ .
GPU ํ๋ก๊ทธ๋๋ฐ์์ ํํ ๋ณผ ์ ์๋ ์ํฉ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
# ์ฑ๋ฅ์ ์ - ์์ฐจ์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ
load_input_tile() # โ DRAM ๋๊ธฐ 500 ์ฌ์ดํด
load_kernel_data() # โ ๋ 100 ์ฌ์ดํด ๋๊ธฐ
barrier() # โ ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ํด ๋๊ธฐ
compute() # โ ๋๋์ด ์ค์ ์ฐ์ฐ 50 ์ฌ์ดํด
# ์ด: 650 ์ฌ์ดํด, ์ฐ์ฐ ํ์ฉ๋ฅ ๊ฒจ์ฐ 7.7%!
์ด๋ ๊ฒ ํ ์ ์๋ค๋ฉด ์ด๋จ๊น์?
# ์ฑ๋ฅ ๊ฐ์ - ์ค์ฒฉ ์ฐ์ฐ
launch_async_load() # โ ๋ฐฑ๊ทธ๋ผ์ด๋์์ 500 ์ฌ์ดํด ์ ์ก ์์
load_small_data() # โ ๋๊ธฐ ์ค ์ ์ฉํ ์์
100 ์ฌ์ดํด
wait_and_compute() # โ ๋๋จธ์ง ~400 ์ฌ์ดํด๋ง ๋๊ธฐ ํ ์ฐ์ฐ
# ์ด: ~550 ์ฌ์ดํด, 45% ํฅ์!
์ด๊ฒ์ด ๋น๋๊ธฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ์๋ ฅ์ ๋๋ค - ๋๋ฆฐ ์๊ณ ๋ฆฌ์ฆ๊ณผ GPU์ ์ ์ฌ๋ ฅ์ ์ต๋ํ ๋ฐํํ๋ ์๊ณ ๋ฆฌ์ฆ์ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด ๋ ๋๋ค.
์ ์ค์ํ๊ฐ
์ด ํผ์ฆ์์๋ Puzzle 13์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ 1D ํฉ์ฑ๊ณฑ์ ์ฐ์ฐ ๋ค์ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๋ ๊ณ ์ฑ๋ฅ ๊ตฌํ์ผ๋ก ๋ณํํฉ๋๋ค. ๋จ์ํ ํ์ ์ ์ฐ์ต์ด ์๋๋๋ค - ์ด ํจํด๋ค์ ๋ค์ ๋ถ์ผ์ ํต์ฌ์ ๋๋ค:
- ๋ฅ๋ฌ๋: ๊ฐ์ค์น์ ํ์ฑํ๊ฐ์ ํจ์จ์ ๋ก๋ฉ
- ๊ณผํ ์ฐ์ฐ: ์คํ ์ค ์ฐ์ฐ์์ ๋ฐ์ดํฐ ์ ์ก ์ค์ฒฉ
- ์ด๋ฏธ์ง ์ฒ๋ฆฌ: ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํตํ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์คํธ๋ฆฌ๋ฐ
- ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์๊ณ ๋ฆฌ์ฆ: ๋๊ธฐ ์๊ฐ์ ์์ฐ์ ์ธ ์์ ์ผ๋ก ์ ํ
์ฌ์ ์ค๋น
์์ํ๊ธฐ ์ ์ ๋ค์ ๋ด์ฉ์ ํ์คํ ์ดํดํ๊ณ ์์ด์ผ ํฉ๋๋ค:
ํ์ GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ :
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ๋ก๊ทธ๋๋ฐ (Puzzle 8, Puzzle 16) - matmul ํจํด์ ํ์ฅํฉ๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ(coalescing) (Puzzle 21) - ์ต์ ์ ๋น๋๊ธฐ ์ ์ก์ ํ์
- ํ์ผ ๊ธฐ๋ฐ ์ฒ๋ฆฌ (Puzzle 23) - ์ด ์ต์ ํ์ ๊ธฐ๋ฐ
ํ๋์จ์ด ์ดํด:
- GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ (DRAM โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ๋ ์ง์คํฐ)
- ์ค๋ ๋ ๋ธ๋ก ๊ตฌ์ฑ๊ณผ ๋๊ธฐํ
- ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ vs. ๋์ญํญ์ ๋ํ ๊ธฐ๋ณธ ์ดํด
API ์์ง: Mojo GPU Memory Operations
โ ๏ธ ํ๋์จ์ด ํธํ์ฑ ์ฐธ๊ณ : ์ด ํผ์ฆ์ ์ต์ GPU ์ํคํ ์ฒ๊ฐ ํ์ํ ์ ์๋ ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ(
copy_dram_to_sram_async,async_copy_wait_all)์ ์ฌ์ฉํฉ๋๋ค..async์์ ์๋ ์ง์๋์ง ์๋ ์ฐ์ฐ ๊ด๋ จ ์ปดํ์ผ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ํด๋น GPU๊ฐ ์ด ๊ธฐ๋ฅ์ ์ง์ํ์ง ์๋ ๊ฒ์ผ ์ ์์ต๋๋ค. ๊ทธ๋๋ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ํจํด์ ์ดํดํ๋ ๋ฐ ๊ฐ๋ ์ ์ฌ์ ํ ์ ์ฉํฉ๋๋ค.GPU ์ปดํจํ ๋ฅ๋ ฅ ํ์ธ:
nvidia-smi --query-gpu=name,compute_cap --format=csv,noheader,nounits
- SM_70 ์ด์ (์: V100, T4, A10G, RTX 20+ ์๋ฆฌ์ฆ): ๊ธฐ๋ณธ ๋น๋๊ธฐ ๋ณต์ฌ ์ง์
- SM_80 ์ด์ (์: A100, RTX 30+ ์๋ฆฌ์ฆ): ์ ์ฒด ๋น๋๊ธฐ ๋ณต์ฌ ๊ธฐ๋ฅ
- SM_90 ์ด์ (์: H100, RTX 40+ ์๋ฆฌ์ฆ): ๊ณ ๊ธ TMA ์ฐ์ฐ ์ง์
ํ์ต ๋ด์ฉ
์ด ํผ์ฆ์ ๋ง์น๋ฉด ๋ค์์ ์ง์ ๊ฒฝํํ๊ฒ ๋ฉ๋๋ค:
ํต์ฌ ๊ธฐ๋ฒ
- ๋น๋๊ธฐ ๋ณต์ฌ ๊ธฐ๋ณธ ์์: ๋ฐฑ๊ทธ๋ผ์ด๋ DRAMโSRAM ์ ์ก ์์
- ์ง์ฐ ์๊ฐ ์ํ(latency hiding): ๋น์ฉ์ด ํฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ์ ์ฉํ ์ฐ์ฐ๊ณผ ์ค์ฒฉ
- ์ค๋ ๋ ๋ ์ด์์ ์ต์ ํ: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ํ๋์จ์ด์ ๋ง์ถ๊ธฐ
- ํ์ดํ๋ผ์ธ ํ๋ก๊ทธ๋๋ฐ: ๋ฉ๋ชจ๋ฆฌ ํ์ฉ์ ๊ทน๋ํํ๋๋ก ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐํ
์ฃผ์ API
Puzzle 16์ ๊ด์ฉ์ matmul์์ ์๊ฐํ ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ๊ธฐ๋ฐ์ผ๋ก, ์ด์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ์ ์ฌ๋ ฅ์ ์ง์คํฉ๋๋ค:
copy_dram_to_sram_async(): ์ ์ฉ ๋ณต์ฌ ์์ง์ ์ฌ์ฉํ์ฌ ๋ฐฑ๊ทธ๋ผ์ด๋ DRAMโSRAM ์ ์ก ์์async_copy_wait_all(): ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ ์ ์ก ์๋ฃ ๋๊ธฐํ
Puzzle 16๊ณผ ๋ค๋ฅธ ์ ์? Puzzle 16์์๋ matmul์ ๊น๋ํ ํ์ผ ๋ก๋ฉ์ ์ํด ๋น๋๊ธฐ ๋ณต์ฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ์ด ํผ์ฆ์ ์ง์ฐ ์๊ฐ ์ํ์ ์ง์คํฉ๋๋ค - ๋น์ฉ์ด ํฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ๊ณผ ์ ์ฉํ ์ฐ์ฐ ์์ ์ ์ค์ฒฉํ๋๋ก ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌ์กฐํํ๋ ๊ฒ์ ๋๋ค.
์ฑ๋ฅ ํจ๊ณผ
์ด ๊ธฐ๋ฒ๋ค์ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์๊ณ ๋ฆฌ์ฆ์ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค:
- DRAM ์ง์ฐ ์๊ฐ ์จ๊ธฐ๊ธฐ: ์ ํด ๋๊ธฐ๋ฅผ ์์ฐ์ ์ธ ์ฐ์ฐ ์๊ฐ์ผ๋ก ์ ํ
- ๋์ญํญ ๊ทน๋ํ: ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ผ๋ก ์บ์ ๋ฏธ์ค ๋ฐฉ์ง
- ํ์ดํ๋ผ์ธ ํจ์จ: ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ด ๋ณ๋ ฌ๋ก ์ผ์ด๋๋ ๋์ ์ฐ์ฐ ์ ๋์ ๋ฐ์๊ฒ ์ ์ง
๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ด๋? ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ GPU ๋ธ๋ก์ด ๋ค๋ฅธ ์์ ์ ๊ณ์ํ๋ ๋์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ์์ํ ์ ์๊ฒ ํด์ค๋๋ค. ์ด๋ฅผ ํตํด ์ฐ์ฐ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ด๋์ ์ค์ฒฉํ ์ ์์ผ๋ฉฐ, ์ด๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์๊ณ ๋ฆฌ์ฆ์ ๊ทผ๋ณธ์ ์ธ ์ต์ ํ ๊ธฐ๋ฒ์ ๋๋ค.
๐ก ์ฑ๊ณต ํ: ์ด๊ฒ์ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ํ ํ์ดํ๋ผ์ธ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์๊ฐํ์ธ์ - ๋จ๊ณ๋ฅผ ์ค์ฒฉํ๊ณ , ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๊ณ , ์ฒ๋ฆฌ๋์ ๊ทน๋ํํฉ๋๋ค. ๋ชฉํ๋ ๋ฐ์ดํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ด๋ํ๋ ๋์ ๋น์ผ ์ฐ์ฐ ์ ๋์ ๋ฐ์๊ฒ ์ ์งํ๋ ๊ฒ์ ๋๋ค.
ํค์ผ๋ก ์์ญ ์ดํดํ๊ธฐ
๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์, ํฉ์ฑ๊ณฑ๊ณผ ๊ฐ์ ์คํ ์ค ์ฐ์ฐ์ ํ์ผ ๊ธฐ๋ฐ ์ฒ๋ฆฌ์ ํ์์ ์ธ ํค์ผ๋ก ์์ญ(ghost cell ๋๋ guard cell์ด๋ผ๊ณ ๋ ํจ)์ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
ํค์ผ๋ก ์์ญ์ด๋?
ํค์ผ๋ก ์์ญ์ ์คํ ์ค ์ฐ์ฐ์ ํ์ํ ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ์ฒ๋ฆฌ ํ์ผ์ ๊ฒฝ๊ณ๋ฅผ ๋์ด ํ์ฅ๋๋ ์ถ๊ฐ ์์์ ๋๋ค. ํ์ผ ๊ฐ์ฅ์๋ฆฌ ๊ทผ์ฒ์ ์์๋ฅผ ์ฒ๋ฆฌํ ๋, ์คํ ์ค ์ฐ์ฐ์ ์ธ์ ํ์ผ์ ๋ฐ์ดํฐ์ ์ ๊ทผํด์ผ ํฉ๋๋ค.
ํค์ผ๋ก ์์ญ์ด ํ์ํ ์ด์
ํ์ผ์์ 5์ ์ปค๋์ ์ฌ์ฉํ๋ 1D ํฉ์ฑ๊ณฑ์ ์๊ฐํด ๋ด ์๋ค:
์๋ณธ ๋ฐ์ดํฐ: [... | a b c d e f g h i j k l m n o | ...]
์ฒ๋ฆฌ ํ์ผ: [c d e f g h i j k l m n o]
^ ^
์ผ์ชฝ ํ์ผ์์ ์ค๋ฅธ์ชฝ ํ์ผ์์
์ด์ ํ์ ์ด์ ํ์
ํค์ผ๋ก ํฌํจ: [a b | c d e f g h i j k l m n o | p q]
^^^ ^^^
์ผ์ชฝ ํค์ผ๋ก ์ค๋ฅธ์ชฝ ํค์ผ๋ก
์ฃผ์ ํน์ฑ:
- ํค์ผ๋ก ํฌ๊ธฐ: ์ผ๋ฐ์ ์ผ๋ก ๊ฐ ์ธก๋ฉด์
KERNEL_SIZE // 2๊ฐ ์์ - ๋ชฉ์ : ํ์ผ ๊ฒฝ๊ณ์์ ์ ํํ ์คํ ์ค ์ฐ์ฐ ๊ฐ๋ฅ
- ๋ด์ฉ: ์ด์ ํ์ผ์ ๋ฐ์ดํฐ ๋ณต์ฌ๋ณธ ๋๋ ๊ฒฝ๊ณ ์กฐ๊ฑด
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋: ํฐ ์ฐ์ฐ ์ด์ ์ ์ํ ์ ์ ์ถ๊ฐ ์ ์ฅ ๊ณต๊ฐ
ํฉ์ฑ๊ณฑ์์์ ํค์ผ๋ก ์์ญ
5์ ํฉ์ฑ๊ณฑ ์ปค๋ \([k_0, k_1, k_2, k_3, k_4]\)์ ๊ฒฝ์ฐ:
- ์ค์ฌ ์์: \(k_2\)๊ฐ ํ์ฌ ์ฒ๋ฆฌ ์์์ ์ ๋ ฌ
- ์ผ์ชฝ ์ด์: \(k_0, k_1\)์ ์ผ์ชฝ 2๊ฐ ์์ ํ์
- ์ค๋ฅธ์ชฝ ์ด์: \(k_3, k_4\)์ ์ค๋ฅธ์ชฝ 2๊ฐ ์์ ํ์
- ํค์ผ๋ก ํฌ๊ธฐ: ๊ฐ ์ธก๋ฉด์
HALO_SIZE = 5 // 2 = 2๊ฐ ์์
ํค์ผ๋ก ์์ญ ์์ด:
- ํ์ผ ๊ฒฝ๊ณ ์์์์ ์ ์ฒด ํฉ์ฑ๊ณฑ์ ์ํํ ์ ์์
- ์๋ชป๋ ์ถ๋ ฅ์ด๋ ๋ณต์กํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ ๋ก์ง์ด ํ์
- ๋ถ์ฐ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ผ๋ก ์ฑ๋ฅ ์ ํ
ํค์ผ๋ก ์์ญ ์ฌ์ฉ ์:
- ๋ชจ๋ ํ์ผ ์์๊ฐ ๋ก์ปฌ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฒด ํฉ์ฑ๊ณฑ ์ํ ๊ฐ๋ฅ
- ์์ธก ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ผ๋ก ๊ฐ๊ฒฐํ๊ณ ํจ์จ์ ์ธ ์ฐ์ฐ
- ๋ ๋์ ์บ์ ํ์ฉ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ
์ด ๊ฐ๋ ์ ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ๊ตฌํํ ๋ ํนํ ์ค์ํฉ๋๋ค. ํค์ผ๋ก ์์ญ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ก๋ฉํ๊ณ ๋๊ธฐํํด์ผ ์ฌ๋ฌ ํ์ผ์ ๊ฑธ์น ์ ํํ ๋ณ๋ ฌ ์ฐ์ฐ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
๋น๋๊ธฐ ๋ณต์ฌ ์ค์ฒฉ์ ํ์ฉํ 1D ํฉ์ฑ๊ณฑ
Puzzle 13 ๊ธฐ๋ฐ: ์ด ํผ์ฆ์ Puzzle 13์ 1D ํฉ์ฑ๊ณฑ์ ๋ค์ ๋ค๋ฃจ์ง๋ง, ์ด๋ฒ์๋ ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์ฐ์ฐ ๋ค์ ์จ๊ธฐ๋ ์ต์ ํ๋ฅผ ์ ์ฉํฉ๋๋ค. ๋จ์ํ ๋๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๋์ , ํ๋์จ์ด ๊ฐ์์ ์ฌ์ฉํ์ฌ ๋น์ฉ์ด ํฐ DRAM ์ ์ก๊ณผ ์ ์ฉํ ์์ ์ ์ค์ฒฉํฉ๋๋ค.
๊ตฌ์ฑ
- ๋ฒกํฐ ํฌ๊ธฐ:
VECTOR_SIZE = 16384(์ฌ๋ฌ ๋ธ๋ก์ ๊ฑธ์น 16K ์์) - ํ์ผ ํฌ๊ธฐ:
CONV_TILE_SIZE = 256(์ฒ๋ฆฌ ํ์ผ ํฌ๊ธฐ) - ๋ธ๋ก ๊ตฌ์ฑ: ๋ธ๋ก๋น
(256, 1)์ค๋ ๋ - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: ๊ทธ๋ฆฌ๋๋น
(VECTOR_SIZE // CONV_TILE_SIZE, 1)๋ธ๋ก (64๊ฐ ๋ธ๋ก) - ์ปค๋ ํฌ๊ธฐ:
KERNEL_SIZE = 5(Puzzle 13๊ณผ ๋์ผํ ๊ฐ๋จํ 1D ํฉ์ฑ๊ณฑ) - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ ์ด์์:
row_major[VECTOR_SIZE]()(1D row-major)
๋น๋๊ธฐ ๋ณต์ฌ์ ๊ธฐํ
Puzzle 16 ๊ธฐ๋ฐ: matmul์์ ๊น๋ํ ํ์ผ ๋ก๋ฉ์ ์ํด
copy_dram_to_sram_async๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ด๋ฏธ ๋ณด์
จ์ต๋๋ค. ์ด์ ๊ณ ์ฑ๋ฅ ๋ฉ๋ชจ๋ฆฌ
๋ฐ์ด๋ ์๊ณ ๋ฆฌ์ฆ์ ํต์ฌ์ธ ์ง์ฐ ์๊ฐ ์ํ ๊ธฐ๋ฅ์ ์ง์คํฉ๋๋ค.
๊ธฐ์กด์ ๋๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ก๋ฉ์ ์ ์ก ์ค ์ฐ์ฐ ์ ๋์ ์ ํด ์ํ๋ก ๋๊ธฐํ๊ฒ ํฉ๋๋ค. ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ์ ์ก๊ณผ ์ ์ฉํ ์์ ์ ์ค์ฒฉ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค:
# ๋๊ธฐ์ ์ ๊ทผ - ๋นํจ์จ์ :
for i in range(CONV_TILE_SIZE):
input_shared[i] = input[base_idx + i] # ๊ฐ ๋ก๋๊ฐ DRAM์ ๊ธฐ๋ค๋ฆผ
for i in range(KERNEL_SIZE):
kernel_shared[i] = kernel[i] # DRAM ์ถ๊ฐ ๋๊ธฐ
barrier() # ์ฐ์ฐ ์์ ์ ๋ชจ๋ ์ค๋ ๋ ๋๊ธฐ
# โ ์ด ์๊ฐ = input_transfer_time + kernel_transfer_time
# ๋น๋๊ธฐ ๋ณต์ฌ ์ ๊ทผ - ํจ์จ์ :
copy_dram_to_sram_async[thread_layout](input_shared, input_tile) # ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ์ก ์์
# ์
๋ ฅ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ ์ก๋๋ ๋์, ์ปค๋์ ๋๊ธฐ์์ผ๋ก ๋ก๋ฉ
for i in range(KERNEL_SIZE):
kernel_shared[i] = kernel[i] # ๋น๋๊ธฐ ์
๋ ฅ ์ ์ก๊ณผ ์ค์ฒฉ
async_copy_wait_all() # ๋ ์ฐ์ฐ์ด ๋ชจ๋ ์๋ฃ๋ ๋๋ง ๋๊ธฐ
# โ ์ด ์๊ฐ = MAX(input_transfer_time, kernel_transfer_time)
๋น๋๊ธฐ ๋ณต์ฌ๊ฐ ์ ๋์ํ๋ ์ด์ :
- ์ ์ฉ ๋ณต์ฌ ์์ง: ์ต์ GPU๋ ๋ ์ง์คํฐ๋ฅผ ์ฐํํ๊ณ ์ง์ ํ ์ฐ์ฐ-๋ฉ๋ชจ๋ฆฌ ์ค์ฒฉ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ์ ์ฉ ํ๋์จ์ด๋ฅผ ๊ฐ์ถ๊ณ ์์ต๋๋ค (Puzzle 16์์ ์ค๋ช )
- ์ง์ฐ ์๊ฐ ์ํ: GPU ์ค๋ ๋๊ฐ ๋ค๋ฅธ ์ฐ์ฐ์ ์คํํ๋ ๋์ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ด ์ด๋ฃจ์ด์ง๋๋ค
- ์ต์ ์ ๋ณํฉ: ์ค๋ ๋ ๋ ์ด์์์ด ํจ์จ์ ์ธ DRAM ์ ๊ทผ ํจํด์ ๋ณด์ฅํฉ๋๋ค
- ๋ฆฌ์์ค ํ์ฉ: ์ฐ์ฐ ์ ๋์ด ์ ํด ๋๊ธฐ ๋์ ๊ณ์ ๋ฐ์๊ฒ ๋์ํฉ๋๋ค
์์ฑํ ์ฝ๋
Puzzle 16์ matmul ๊ตฌํ ํจํด์ ๋ฐ๋ผ, ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ ์ก๊ณผ ์ฐ์ฐ์ ์ค์ฒฉํ๋ 1D ํฉ์ฑ๊ณฑ์ ๊ตฌํํ์ธ์.
์ํ์ ์ฐ์ฐ: ๋น๋๊ธฐ ๋ณต์ฌ๋ฅผ ํ์ฉํ์ฌ ๋๊ท๋ชจ ๋ฒกํฐ์ ๋ํ 1D ํฉ์ฑ๊ณฑ์ ํจ์จ์ ์ผ๋ก ๊ณ์ฐํฉ๋๋ค: \[\text{output}[i] = \sum_{k=0}^{\text{KERNEL_SIZE}-1} \text{input}[i+k-\text{HALO_SIZE}] \times \text{kernel}[k]\]
๋น๋๊ธฐ ๋ณต์ฌ ์๊ณ ๋ฆฌ์ฆ:
- ๋น๋๊ธฐ ํ์ผ ๋ก๋ฉ: ์ ๋ ฅ ๋ฐ์ดํฐ์ ๋ฐฑ๊ทธ๋ผ์ด๋ DRAMโSRAM ์ ์ก ์์
- ์ค์ฒฉ ์ฐ์ฐ: ์ ๋ ฅ ์ ์ก ์ค ์์ ์ปค๋ ๋ฐ์ดํฐ ๋ก๋ฉ
- ๋๊ธฐํ: ์ ์ก ์๋ฃ ๋๊ธฐ ํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ์ฐ
comptime VECTOR_SIZE = 16384
comptime CONV_TILE_SIZE = 256
comptime KERNEL_SIZE = 5
comptime HALO_SIZE = KERNEL_SIZE // 2 # Halo elements needed for boundary
comptime BUFFER_SIZE = CONV_TILE_SIZE + 2 * HALO_SIZE # Include halo for boundary conditions
comptime BLOCKS_PER_GRID_ASYNC = (
VECTOR_SIZE + CONV_TILE_SIZE - 1
) // CONV_TILE_SIZE
comptime THREADS_PER_BLOCK_ASYNC = 256
comptime dtype = DType.float32
comptime layout_async = row_major[VECTOR_SIZE]()
comptime LayoutAsyncType = type_of(layout_async)
comptime kernel_layout = row_major[KERNEL_SIZE]()
comptime KernelLayoutType = type_of(kernel_layout)
def async_copy_overlap_convolution[
dtype: DType
](
output: TileTensor[mut=True, dtype, LayoutAsyncType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutAsyncType, ImmutAnyOrigin],
kernel: TileTensor[mut=False, dtype, KernelLayoutType, ImmutAnyOrigin],
):
"""Demonstrates async copy operations building on p14 patterns.
This shows how to use copy_dram_to_sram_async and async_copy_wait_all
for efficient memory transfers, extending the patterns from p14 matmul.
"""
# Shared memory buffers (like p14, but without .fill(0) to avoid race)
var input_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[CONV_TILE_SIZE]())
var kernel_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[KERNEL_SIZE]())
# FILL IN HERE (roughly 19 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p28/p28.mojo
ํ
1. ๋น๋๊ธฐ ๋ณต์ฌ ๋ฉ์ปค๋์ฆ ์ดํด
๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ๋ธ๋ก์ด ๋ค๋ฅธ ์ฝ๋๋ฅผ ๊ณ์ ์คํํ๋ ๋์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ์ก์ ์์ํฉ๋๋ค.
ํ๊ตฌํ ํต์ฌ ์ง๋ฌธ:
- DRAM์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์ ์กํด์ผ ํ๋๊ฐ?
- ์ ์ก์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ผ์ด๋๋ ๋์ ์ด๋ค ์ฐ์ฐ์ ์คํํ ์ ์๋๊ฐ?
- ํ๋์จ์ด๊ฐ ์ฌ๋ฌ ๋์ ์ฐ์ฐ์ ์ด๋ป๊ฒ ์กฐ์จํ๋๊ฐ?
์ค๋ ๋ ๋ ์ด์์ ๊ณ ๋ ค์ฌํญ:
- ๋ธ๋ก์๋
THREADS_PER_BLOCK_ASYNC = 256๊ฐ์ ์ค๋ ๋๊ฐ ์์ต๋๋ค - ํ์ผ์๋
CONV_TILE_SIZE = 256๊ฐ์ ์์๊ฐ ์์ต๋๋ค - ์ด๋ค ๋ ์ด์์ ํจํด์ด ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ๋ณด์ฅํ๋๊ฐ?
2. ์ค์ฒฉ ๊ธฐํ ํ์
๋ชฉํ๋ ์ ์ฉํ ์ฐ์ฐ ๋ค์ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๋ ๊ฒ์ ๋๋ค.
๋ถ์ ์ ๊ทผ๋ฒ:
- ์ด๋ค ์ฐ์ฐ์ด ์์ฐจ์ ์ผ๋ก vs. ๋ณ๋ ฌ๋ก ์ผ์ด๋์ผ ํ๋๊ฐ?
- ์ด๋ค ๋ฐ์ดํฐ ์ ์ก์ด ํฐ(๋น์ฉ์ด ๋์) vs. ์์(๋น์ฉ์ด ๋ฎ์)๊ฐ?
- ๋ณ๋ ฌ ์คํ์ ์ต๋ํํ๋๋ก ์๊ณ ๋ฆฌ์ฆ์ ์ด๋ป๊ฒ ๊ตฌ์กฐํํ ์ ์๋๊ฐ?
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ๊ณ ๋ ค์ฌํญ:
- ํฐ ์ ๋ ฅ ํ์ผ: 256 ์์ ร 4 ๋ฐ์ดํธ = 1KB ์ ์ก
- ์์ ์ปค๋: 5 ์์ ร 4 ๋ฐ์ดํธ = 20 ๋ฐ์ดํธ
- ์ด๋ค ์ ์ก์ด ๋น๋๊ธฐ ์ต์ ํ์ ์ด์ ์ ๊ฐ์ฅ ๋ง์ด ๋ฐ๋๊ฐ?
3. ๋๊ธฐํ ์ ๋ต
์ ์ ํ ๋๊ธฐํ๋ ์ฑ๋ฅ์ ํฌ์ํ์ง ์์ผ๋ฉด์ ์ ํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
ํ์ด๋ฐ ๋ถ์:
- ๊ฐ ์ฐ์ฐ์ด ์ค์ ๋ก ๋ฐ์ดํฐ๊ฐ ์ค๋น๋์ด์ผ ํ๋ ์์ ์ ์ธ์ ์ธ๊ฐ?
- ์ ํ์ฑ์ ์ํด ํ์ํ ์ต์ํ์ ๋๊ธฐํ๋ ๋ฌด์์ธ๊ฐ?
- ๋ฐ์ดํฐ ์์กด์ฑ์ ์ ์งํ๋ฉด์ ๋ถํ์ํ ์ ์ฒด๋ฅผ ์ด๋ป๊ฒ ํผํ ์ ์๋๊ฐ?
๊ฒฝ์ ์ํ ๋ฐฉ์ง:
- ์ ์ก์ด ์๋ฃ๋๊ธฐ ์ ์ ์ฐ์ฐ์ด ์์๋๋ฉด ์ด๋ป๊ฒ ๋๋๊ฐ?
- ๋ฉ๋ชจ๋ฆฌ ํ์ค์ ๋ฐฐ๋ฆฌ์ด๊ฐ ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ์ด๋ป๊ฒ ์กฐ์จํ๋๊ฐ?
๋น๋๊ธฐ ๋ณต์ฌ ์ค์ฒฉ ํ ์คํธ:
pixi run p28
pixi run -e amd p28
pixi run -e apple p28
uv run poe p28
์๋ฃจ์
์์ธ ์ค๋ช ์ด ํฌํจ๋ ์ ์ฒด ์๋ฃจ์
๋น๋๊ธฐ ๋ณต์ฌ ์ค์ฒฉ ์๋ฃจ์ ๋ ๋น์ฉ์ด ํฐ DRAM ์ ์ก๊ณผ ์ ์ฉํ ์ฐ์ฐ์ ์ค์ฒฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค:
def async_copy_overlap_convolution[
dtype: DType
](
output: TileTensor[mut=True, dtype, AsyncLayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, AsyncLayoutType, MutAnyOrigin],
kernel: LayoutTensor[dtype, kernel_layout, ImmutAnyOrigin],
):
"""Demonstrates async copy operations building on p14 patterns.
This shows how to use copy_dram_to_sram_async and async_copy_wait_all
for efficient memory transfers, extending the patterns from p14 matmul.
"""
# Shared memory buffers (like p14, but without .fill(0) to avoid race)
var input_shared = LayoutTensor[
dtype,
Layout.row_major(CONV_TILE_SIZE),
MutAnyOrigin,
address_space=AddressSpace.SHARED,
].stack_allocation()
var kernel_shared = LayoutTensor[
dtype,
Layout.row_major(KERNEL_SIZE),
MutAnyOrigin,
address_space=AddressSpace.SHARED,
].stack_allocation()
var local_i = thread_idx.x
# Phase 1: Launch async copy for input tile
# Note: tile() does NOT perform bounds checking - ensure valid tile bounds
var input_tile = input.tile[CONV_TILE_SIZE](block_idx.x).to_layout_tensor()
# Use async copy with thread layout matching p14 pattern
comptime load_layout = Layout.row_major(THREADS_PER_BLOCK_ASYNC)
copy_dram_to_sram_async[thread_layout=load_layout](input_shared, input_tile)
# Phase 2: Load kernel synchronously (small data)
if local_i < KERNEL_SIZE:
kernel_shared[local_i] = kernel[local_i]
# Phase 3: Wait for async copy to complete
async_copy_wait_all() # Always wait since we always do async copy
barrier() # Sync all threads
# Phase 4: Compute convolution
var global_i = block_idx.x * CONV_TILE_SIZE + local_i
if local_i < CONV_TILE_SIZE and global_i < Int(output.dim[0]()):
var result: output.ElementType = 0
# Simple convolution avoiding boundary issues
if local_i >= HALO_SIZE and local_i < CONV_TILE_SIZE - HALO_SIZE:
# Full convolution for center elements
for k in range(KERNEL_SIZE):
var input_idx = local_i + k - HALO_SIZE
if input_idx >= 0 and input_idx < CONV_TILE_SIZE:
result += rebind[Scalar[dtype]](
input_shared[input_idx]
) * rebind[Scalar[dtype]](kernel_shared[k])
else:
# For boundary elements, just copy input (no convolution)
result = rebind[Scalar[dtype]](input_shared[local_i])
output[global_i] = result
๋จ๊ณ๋ณ ๋ถ์
Phase 1: ๋น๋๊ธฐ ๋ณต์ฌ ์์
# Phase 1: Launch async copy for input tile
input_tile = input.tile[CONV_TILE_SIZE](block_idx.x)
comptime load_layout = row_major[THREADS_PER_BLOCK_ASYNC]()
copy_dram_to_sram_async[thread_layout=load_layout](input_shared, input_tile)
-
ํ์ผ ์์ฑ:
input.tile[CONV_TILE_SIZE](block_idx.x)๋block_idx.x * 256์์ ์์ํ๋ 256๊ฐ ์์์ ์ ๋ ฅ ๋ฐฐ์ด ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค. Mojo์tile๋ฉ์๋๋ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ ์ ๋ก ํจ๋ฉ์ ์ํํ์ง ์์ต๋๋ค. ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ธ๋ฑ์ค ์ ๊ทผ์ ๋ฏธ์ ์ ๋์์ ์ด๋ํฉ๋๋ค. ๊ตฌํ์์ ํ์ผ ํฌ๊ธฐ์ offset์ด ์ ํจํ ๋ฐฐ์ด ๋ฒ์ ๋ด์ ์๋์ง ํ์ธํด์ผ ํฉ๋๋ค. -
์ค๋ ๋ ๋ ์ด์์:
row_major[THREADS_PER_BLOCK_ASYNC, 1]()๋ ๋ธ๋ก ๊ตฌ์ฑ๊ณผ ์ผ์นํ๋256 x 1๋ ์ด์์์ ์์ฑํฉ๋๋ค. ์ด๊ฒ์ ํ์์ ๋๋ค - ์ต์ ์ ๋ณํฉ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ํด ๋ ์ด์์์ด ๋ฌผ๋ฆฌ์ ์ค๋ ๋ ๋ฐฐ์น์ ์ผ์นํด์ผ ํฉ๋๋ค. ๋ ์ด์์์ด ์ผ์นํ์ง ์์ผ๋ฉด ์ค๋ ๋๊ฐ ๋น์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ์ ๊ทผํ์ฌ ๋ณํฉ์ด ๊นจ์ง๊ณ ์ฑ๋ฅ์ด ์ฌ๊ฐํ๊ฒ ์ ํ๋ฉ๋๋ค. -
๋น๋๊ธฐ ๋ณต์ฌ ์์:
copy_dram_to_sram_async๋ DRAM์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ก์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ์ก์ ์์ํฉ๋๋ค. ํ๋์จ์ด๊ฐ 256๊ฐ์ float(1KB)๋ฅผ ๋ณต์ฌํ๋ ๋์ ๋ธ๋ก์ ๊ณ์ ์คํ๋ฉ๋๋ค.
Phase 2: ์ค์ฒฉ ์ฐ์ฐ
# Phase 2: Load kernel synchronously (small data)
if local_i < KERNEL_SIZE:
kernel_shared[local_i] = kernel[local_i]
-
๋์ ์คํ: 1KB ์ ๋ ฅ ํ์ผ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ ์ก๋๋ ๋์, ์ค๋ ๋๋ค์ ์์ 20๋ฐ์ดํธ ์ปค๋์ ๋๊ธฐ์์ผ๋ก ๋ก๋ฉํฉ๋๋ค. ์ด ์ค์ฒฉ์ด ํต์ฌ ์ต์ ํ์ ๋๋ค.
-
ํฌ๊ธฐ ๊ธฐ๋ฐ ์ ๋ต: ํฐ ์ ์ก(์ ๋ ฅ ํ์ผ)์ ๋น๋๊ธฐ ๋ณต์ฌ๋ฅผ, ์์ ์ ์ก(์ปค๋)์ ๋๊ธฐ์ ๋ก๋ฉ์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ๋ณต์ก์ฑ๊ณผ ์ฑ๋ฅ ์ด์ ์ ๊ท ํ์ ๋ง์ถฅ๋๋ค.
Phase 3: ๋๊ธฐํ
# Phase 3: Wait for async copy to complete
async_copy_wait_all() # Always wait since we always do async copy
barrier() # Sync all threads
-
์ ์ก ์๋ฃ:
async_copy_wait_all()์ ๋ชจ๋ ๋น๋๊ธฐ ์ ์ก์ด ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค.input_shared์ ์ ๊ทผํ๊ธฐ ์ ์ ๋ฐ๋์ ํ์ํฉ๋๋ค. -
์ค๋ ๋ ๋๊ธฐํ:
barrier()๋ ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฐ์ฐ์ผ๋ก ๋์ด๊ฐ๊ธฐ ์ ์ ์๋ฃ๋ ์ ์ก์ ํ์ธํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
Phase 4: ์ฐ์ฐ
# Phase 4: Compute convolution
global_i = block_idx.x * CONV_TILE_SIZE + local_i
if local_i < CONV_TILE_SIZE and global_i < output.shape[0]():
var result: output.element_type = 0
if local_i >= HALO_SIZE and local_i < CONV_TILE_SIZE - HALO_SIZE:
# Full convolution for center elements
for k in range(KERNEL_SIZE):
input_idx = local_i + k - HALO_SIZE
if input_idx >= 0 and input_idx < CONV_TILE_SIZE:
result += input_shared[input_idx] * kernel_shared[k]
else:
# For boundary elements, just copy input (no convolution)
result = input_shared[local_i]
output[global_i] = result
-
๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ: ๋ชจ๋ ์ฐ์ฐ์ด ๋ฏธ๋ฆฌ ๋ก๋๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ, ์ฐ์ฐ ์ง์ฝ์ ์ธ ํฉ์ฑ๊ณฑ ๋ฃจํ์์ ๋๋ฆฐ DRAM ์ ๊ทผ์ ํผํฉ๋๋ค.
-
๋จ์ํ๋ ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ด ๊ตฌํ์ ํ์ผ ๊ฒฝ๊ณ ๊ทผ์ฒ ์์๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ค์ฉ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค:
- ์ค์ฌ ์์ (
local_i >= HALO_SIZE์ด๊ณlocal_i < CONV_TILE_SIZE - HALO_SIZE): ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฒด 5์ ํฉ์ฑ๊ณฑ ์ ์ฉ - ๊ฒฝ๊ณ ์์ (๊ฐ ํ์ผ์ ์ฒ์ 2๊ฐ์ ๋ง์ง๋ง 2๊ฐ ์์): ๋ณต์กํ ๊ฒฝ๊ณ ๋ก์ง์ ํผํ๊ธฐ ์ํด ํฉ์ฑ๊ณฑ ์์ด ์ ๋ ฅ์ ์ง์ ๋ณต์ฌ
- ์ค์ฌ ์์ (
๊ต์ก์ ๊ทผ๊ฑฐ: ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ณต์กํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ๋ณด๋ค ๋น๋๊ธฐ ๋ณต์ฌ ํจํด ์์ฐ์
์ฐ์ ์ํฉ๋๋ค. HALO_SIZE = 2์ธ 256๊ฐ ์์ ํ์ผ์์, ์์ 0-1๊ณผ 254-255๋ ์
๋ ฅ
๋ณต์ฌ๋ฅผ, ์์ 2-253์ ์ ์ฒด ํฉ์ฑ๊ณฑ์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋์ํ๋ ๊ตฌํ์
์ ๊ณตํ๋ฉด์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ์ ์ด์ ์ ์ ์งํฉ๋๋ค.
์ฑ๋ฅ ๋ถ์
๋น๋๊ธฐ ๋ณต์ฌ ์์ด (๋๊ธฐ์):
Total Time = Input_Transfer_Time + Kernel_Transfer_Time + Compute_Time
= Large_DRAM_transfer + Small_DRAM_transfer + convolution
= Major_latency + Minor_latency + computation_work
๋น๋๊ธฐ ๋ณต์ฌ ์ฌ์ฉ (์ค์ฒฉ):
Total Time = MAX(Input_Transfer_Time, Kernel_Transfer_Time) + Compute_Time
= MAX(Major_latency, Minor_latency) + computation_work
= Major_latency + computation_work
์ฑ๋ฅ ํฅ์: ๋ ํฐ ์ ๋ ฅ ์ ์ก ๋ค์ ๋ ์์ ์ปค๋ ์ ์ก์ ์ง์ฐ ์๊ฐ์ ์จ๊น์ผ๋ก์จ ์ฑ๋ฅ์ด ํฅ์๋ฉ๋๋ค. ์ค์ ์ฑ๋ฅ ํฅ์ ํญ์ ์ ์ก์ ์๋์ ํฌ๊ธฐ์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ๋ ํฐ ์ค์ฒฉ์ด ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์๋๋ฆฌ์ค์์๋ ์ฑ๋ฅ ํฅ์์ด ํจ์ฌ ํด ์ ์์ต๋๋ค.
ํต์ฌ ๊ธฐ์ ์ ํต์ฐฐ
-
์ค๋ ๋ ๋ ์ด์์ ๋งค์นญ:
row_major[256, 1]()๋ ์ด์์์ด ๋ธ๋ก์(256, 1)์ค๋ ๋ ๊ตฌ์ฑ๊ณผ ์ ํํ ์ผ์นํ์ฌ ์ต์ ์ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. -
๊ฒฝ์ ์ํ ๋ฐฉ์ง: ์ ์ ํ ์์ ์ง์ (๋น๋๊ธฐ ๋ณต์ฌ โ ์ปค๋ ๋ก๋ โ ๋๊ธฐ โ ๋ฐฐ๋ฆฌ์ด โ ์ฐ์ฐ)์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์์์ํฌ ์ ์๋ ๋ชจ๋ ๊ฒฝ์ ์ํ๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
-
ํ๋์จ์ด ์ต์ ํ: ์ต์ GPU๋ ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ์ํ ์ ์ฉ ํ๋์จ์ด๋ฅผ ๊ฐ์ถ๊ณ ์์ด, ๋ฉ๋ชจ๋ฆฌ ์ ๋๊ณผ ์ฐ์ฐ ์ ๋ ์ฌ์ด์ ์ง์ ํ ๋ณ๋ ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
-
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ํ์ฉ: ์ด ํจํด์ ๋ฐ์ดํฐ๋ฅผ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํตํด ํจ์จ์ ์ผ๋ก ์ด๋์ํต๋๋ค: DRAM โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ๋ ์ง์คํฐ โ ์ฐ์ฐ.
-
ํ ์คํธ-๊ตฌํ ์ผ๊ด์ฑ: ํ ์คํธ ๊ฒ์ฆ ๋ก์ง์
local_i_in_tile = i % CONV_TILE_SIZE๋ฅผ ๊ฒ์ฌํ์ฌ ๊ฐ ์์๊ฐ ํฉ์ฑ๊ณฑ ๊ฒฐ๊ณผ(์ค์ฌ ์์)๋ฅผ ๊ธฐ๋ํด์ผ ํ๋์ง ์ ๋ ฅ ๋ณต์ฌ(๊ฒฝ๊ณ ์์)๋ฅผ ๊ธฐ๋ํด์ผ ํ๋์ง ํ๋ณํ๋ฉฐ, ๊ฒฝ๊ณ ์ฒ๋ฆฌ ์ ๋ต๊ณผ ์ผ์นํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋จ์ํ๋ ๊ฒฝ๊ณ ์ ๊ทผ ๋ฐฉ์์ ์ ํํ ๊ฒ์ฆ์ ๋ณด์ฅํฉ๋๋ค.
์ด ์๋ฃจ์ ๋ ๋จ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ํฉ์ฑ๊ณฑ์ ์ ์ฉํ ์์ ๋ค์ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๋ ์ต์ ํ๋ ๊ตฌํ์ผ๋ก ๋ณํํ์ฌ, ๊ณ ์ฑ๋ฅ GPU ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
Puzzle 29: GPU ๋๊ธฐํ ๊ธฐ๋ณธ ์์
๋จ์ํ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๋์ด์
์ด ์ฅ์์๋ ์ค๋ ๋ ๊ฐ ์ ๋ฐํ ์กฐ์จ์ด ํ์ํ ๋ณต์กํ GPU ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋๊ธฐํ ํจํด์ ์๊ฐํฉ๋๋ค. ๋จ์ํ ๋ณ๋ ฌ ์ฐ์ฐ์ ์ด์ ์ ๋ง์ถ ์ด์ ํผ์ฆ๋ค๊ณผ ๋ฌ๋ฆฌ, ์ด ์ฑ๋ฆฐ์ง๋ค์ ์ค์ GPU ์ํํธ์จ์ด์์ ์ฌ์ฉ๋๋ ์ํคํ ์ฒ ์ ๊ทผ ๋ฐฉ์์ ํ๊ตฌํฉ๋๋ค.
ํ์ต ๋ด์ฉ:
- ์ค๋ ๋ ํนํ: ํ๋์ ๋ธ๋ก ์์์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ๊ฐ๊ฐ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
- ์์ฐ์-์๋น์ ํ์ดํ๋ผ์ธ: ๋ช ์์ ๋ฐ์ดํฐ ์์กด์ฑ์ ๊ฐ์ง ๋ค๋จ๊ณ ์ฒ๋ฆฌ
- ๊ณ ๊ธ ๋ฐฐ๋ฆฌ์ด API: ๊ธฐ๋ณธ
barrier()ํธ์ถ์ ๋์ด์ ์ธ๋ฐํ ๋๊ธฐํ ์ ์ด- ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ : ๋ฉ๋ชจ๋ฆฌ ๊ฐ์์ฑ๊ณผ ์์์ ๋ํ ๋ช ์์ ์ ์ด
- ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ ํจํด: ๋ณต์กํ ์ฐ์ฐ์ ์ํ ๋๋ธ ๋ฒํผ๋ง๊ณผ ํ์ดํ๋ผ์ธ ์กฐ์
์ ์ค์ํ๊ฐ: ๋๋ถ๋ถ์ GPU ํํ ๋ฆฌ์ผ์ ๋จ์ํ ๋ฐ์ดํฐ ๋ณ๋ ฌ ํจํด์ ๊ฐ๋ฅด์น์ง๋ง, ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์๋ก ๋ค๋ฅธ ์ฒ๋ฆฌ ๋จ๊ณ, ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด, ์๊ณ ๋ฆฌ์ฆ ๋จ๊ณ ๊ฐ์ ์ ๊ตํ ์กฐ์จ์ด ํ์ํฉ๋๋ค. ์ด ํผ์ฆ๋ค์ ํ์ ์ ์์ ์ ์ค์ GPU ์ปดํจํ ์ฌ์ด์ ๊ฐ๊ทน์ ๋ฉ์์ค๋๋ค.
๊ฐ์
GPU ๋๊ธฐํ๋ ๋ณต์กํ ๋ณ๋ ฌ ์๊ณ ๋ฆฌ์ฆ์ด ์ฌ๋ฐ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ๋์ํ๊ฒ ํ๋ ํ ๋์ ๋๋ค. ์ด ์ฅ์์๋ ๊ณ ์ฑ๋ฅ GPU ์ปดํจํ ์ ๋ฐ์ ๊ฑธ์ณ ๋ํ๋๋ ์ธ ๊ฐ์ง ๊ธฐ๋ณธ์ ์ธ ๋๊ธฐํ ํจํด์ธ ํ์ดํ๋ผ์ธ ์กฐ์ , ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ด๋ฆฌ, ์คํธ๋ฆฌ๋ฐ ์ฐ์ฐ์ ํ๊ตฌํฉ๋๋ค.
ํต์ฌ ํ์ต ๋ชฉํ:
- ์๋ก ๋ค๋ฅธ ๋๊ธฐํ ๊ธฐ๋ณธ ์์๊ฐ ์ธ์ , ์ ํ์ํ์ง ์ดํด
- ์ ์ ํ ์ค๋ ๋ ํนํ๋ฅผ ํตํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ
- ์ ๋ฐํ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ ์ด ํ์ํ ๋ฐ๋ณต ํจํด ๊ตฌํ
- ์ ํ์ฑ์ ๋ณด์ฅํ๋ฉด์ ๋๊ธฐํ ์ค๋ฒํค๋ ์ต์ ํ
์ํคํ ์ฒ ์งํ ๊ตฌ์กฐ: ์ด ํผ์ฆ๋ค์ ๊ธฐ๋ณธ์ ์ธ ํ์ดํ๋ผ์ธ ์กฐ์ ๋ถํฐ ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ด๋ฆฌ๊น์ง, ๊ทธ๋ฆฌ๊ณ ์ต์ข ์ ์ผ๋ก ๊ณ ์ฒ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ๋๋ ์คํธ๋ฆฌ๋ฐ ์ฐ์ฐ ํจํด๊น์ง ๋จ๊ณ์ ์ผ๋ก ์งํ๋๋๋ก ์ค๊ณ๋์์ต๋๋ค.
ํต์ฌ ๊ฐ๋
์ค๋ ๋ ์กฐ์จ ํจ๋ฌ๋ค์:
- ๋จ์ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ์ฐ์ฐ์ ์ํ (์ด์ ํผ์ฆ๋ค)
- ํนํ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ๊ฐ๊ฐ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์ํ (์ด ์ฅ)
- ํ์ดํ๋ผ์ธ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์์ฐ์-์๋น์ ๊ด๊ณ๋ฅผ ๊ฐ์ง ์์ฐจ์ ๋จ๊ณ
- ๋ฐ๋ณต ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์ ์คํ ๋ฒํผ ๊ด๋ฆฌ๋ฅผ ์๋ฐํ๋ ๋ค์ค ํจ์ค
๋๊ธฐํ ๊ธฐ๋ณธ ์์์ ๊ณ์ธต ๊ตฌ์กฐ:
- ๊ธฐ๋ณธ
barrier(): ๋ธ๋ก ๋ด ๋จ์ ์ค๋ ๋ ๋๊ธฐํ - ๊ณ ๊ธ mbarrier API: ์ํ ์ถ์ ์ ์ง์ํ๋ ์ธ๋ฐํ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์ ์ด
- ์คํธ๋ฆฌ๋ฐ ์กฐ์ : ๋น๋๊ธฐ ๋ณต์ฌ ๋ฐ ๋๋ ์ ์ก ๋๊ธฐํ
๋ฉ๋ชจ๋ฆฌ ์ผ๊ด์ฑ ๋ชจ๋ธ:
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ : ์ค๋ ๋ ๊ฐ ํต์ ์ ์ํ ๋น ๋ฅธ ์จ์นฉ ๋ฉ๋ชจ๋ฆฌ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์์ ๋ณด์ฅ: ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ๊ฑธ์ณ ์ฐ๊ธฐ์ ๊ฐ์์ฑ ๋ณด์ฅ
- ๋ฒํผ ๊ด๋ฆฌ: ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋๋ธ ๋ฒํผ๋ง๊ณผ ํํ ํจํด
๊ตฌ์ฑ
์์คํ ์ํคํ ์ฒ:
- ๋ธ๋ก ํฌ๊ธฐ: ์ต์ ์ ์ ์ ์จ์ ์ํด ๋ธ๋ก๋น
TPB = 256์ค๋ ๋ - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: ๊ฐ๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ ํ์ผ์ ์ฒ๋ฆฌํ๋ ๋ค์์ ๋ธ๋ก
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋ ์ง์คํฐ, ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์ ์ ๋ต์ ํ์ฉ
- ๋ฐ์ดํฐ ํ์
: ์์น ์ฐ์ฐ์ ์ํ
DType.float32
๋ค๋ฃจ๋ ๋๊ธฐํ ํจํด:
- ๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ: ๋ฐฐ๋ฆฌ์ด ์กฐ์ ์ ํ์ฉํ ์ค๋ ๋ ํนํ
- ๋๋ธ ๋ฒํผ๋ง ๋ฐ๋ณต: ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ด๋ฆฌ
- ์คํธ๋ฆฌ๋ฐ ์ฐ์ฐ: ๊ณ ์ฒ๋ฆฌ๋ ์ฒ๋ฆฌ๋ฅผ ์ํ ๋น๋๊ธฐ ๋ณต์ฌ ์กฐ์
์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ:
- ๋๊ธฐํ ์ค๋ฒํค๋: ์๋ก ๋ค๋ฅธ ๋ฐฐ๋ฆฌ์ด ์ ํ์ ๋น์ฉ ์ดํด
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ์ต๋ ์ฒ๋ฆฌ๋์ ์ํ ์ ๊ทผ ํจํด ์ต์ ํ
- ์ค๋ ๋ ํ์ฉ๋: ํนํ๋ ์ญํ ๊ณผ ์ ์ฒด ํจ์จ์ฑ ๊ฐ์ ๊ท ํ
ํผ์ฆ ๊ตฌ์ฑ
์ด ์ฅ์๋ ์๋ก๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ ํ๋ ์ธ ๊ฐ์ ์ฐ๊ฒฐ๋ ํผ์ฆ์ด ํฌํจ๋์ด ์์ต๋๋ค:
๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ ์กฐ์
์ด์ : ์ค๋ ๋ ํนํ์ ํ์ดํ๋ผ์ธ ์ํคํ ์ฒ
ํ๋์ ๋ธ๋ก ์์์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์์ ํ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๋ GPU ์ปค๋์ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. ์ด ํผ์ฆ์์๋ ์์ฐ์-์๋น์ ๊ด๊ณ์ ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๋จ๊ณ ๊ฐ์ ์ ๋ต์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น๋ฅผ ์๊ฐํฉ๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ์ค๋ ๋ ์ญํ ํนํ (Stage 1: ๋ก๋, Stage 2: ์ฒ๋ฆฌ, Stage 3: ์ถ๋ ฅ)
- ์ฒ๋ฆฌ ๋จ๊ณ ๊ฐ ์์ฐ์-์๋น์ ๋ฐ์ดํฐ ํ๋ฆ
- ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ์ฌ์ด์ ์ ๋ต์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น
์ค์ ์์ฉ ๋ถ์ผ: ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ, ๋ค๋จ๊ณ ๊ณผํ ์ฐ์ฐ, ์ ๊ฒฝ๋ง ๋ ์ด์ด ์กฐ์
๋๋ธ ๋ฒํผ๋ง ์คํ ์ค ์ฐ์ฐ
์ด์ : ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด API์ ๋ฐ๋ณต ์ฒ๋ฆฌ
์ ๋ฐํ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ ์ด ํ์ํ ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์ ์ํด mbarrier API๋ฅผ ์ฌ์ฉํ ์ธ๋ฐํ ๋๊ธฐํ ์ ์ด๋ฅผ ํ๊ตฌํฉ๋๋ค. ์ด ํผ์ฆ์ ๋ฐ๋ณต๋ฒ๊ณผ ์๋ฎฌ๋ ์ด์ ์๊ณ ๋ฆฌ์ฆ์ ํ์์ ์ธ ๋๋ธ ๋ฒํผ๋ง ํจํด์ ๋ณด์ฌ์ค๋๋ค.
ํต์ฌ ๊ฐ๋ :
- ๊ณ ๊ธ mbarrier API vs ๊ธฐ๋ณธ
barrier() - ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ฒํผ ์ญํ ์ ๊ต๋ํ๋ ๋๋ธ ๋ฒํผ๋ง
- ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ฌ์ฉํ ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ ์กฐ์
์ค์ ์์ฉ ๋ถ์ผ: ๋ฐ๋ณต๋ฒ (Jacobi, Gauss-Seidel), ์ ๋ฃฐ๋ฌ ์คํ ๋งํ, ์๋ฎฌ๋ ์ด์ ์๊ฐ ์คํ
์์ํ๊ธฐ
๊ถ์ฅ ํ์ต ์์:
- ํ์ดํ๋ผ์ธ ์กฐ์ ๋ถํฐ ์์: ์ค๋ ๋ ํนํ์ ๊ธฐ์ด ์ดํด
- ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ก ์งํ: ์ธ๋ฐํ ๋๊ธฐํ ์ ์ด ํ์ต
- ์คํธ๋ฆฌ๋ฐ ํจํด์ ์ ์ฉ: ๊ณ ์ฒ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๊ฐ๋ ๊ฒฐํฉ
์ฌ์ ์ค๋น:
- ๊ธฐ๋ณธ GPU ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ (์ค๋ ๋, ๋ธ๋ก, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ)์ ๋ํ ์ดํด
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ์ ์ ๊ทผ ํจํด์ ๋ํ ์ดํด
- ์ด์ ํผ์ฆ์์ ๋ฐฐ์ด ๋ฐฐ๋ฆฌ์ด ๋๊ธฐํ์ ๋ํ ์น์ํจ
ํ์ต ์ฑ๊ณผ: ์ด ์ฅ์ ์๋ฃํ๋ฉด, ์ ๋ฐํ ์กฐ์จ์ด ํ์ํ ์ ๊ตํ GPU ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํ๊ณ ๊ตฌํํ ์ ์๋ ํ ๋๋ฅผ ๊ฐ์ถ๊ฒ ๋์ด, ์ค์ GPU ์ปดํจํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ง์ฃผํ๋ ์ํคํ ์ฒ์ ๋ณต์ก์ฑ์ ๋๋นํ ์ ์์ต๋๋ค.
์์ํ ์ค๋น๊ฐ ๋์ จ๋์? ๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ ์กฐ์ ์์ ์ค๋ ๋ ํนํ์ ๊ธฐ๋ณธ์ ๋ฐฐ์ด ๋ค์, ๋๋ธ ๋ฒํผ๋ง ์คํ ์ค ์ฐ์ฐ ์ผ๋ก ๋์๊ฐ ๊ณ ๊ธ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ธฐ๋ฒ์ ํ๊ตฌํด ๋ณด์ธ์.
๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ ์กฐ์
๊ฐ์
์กฐ์จ๋ 3๋จ๊ณ ํ์ดํ๋ผ์ธ์ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ์ปค๋์ ๊ตฌํํฉ๋๋ค. ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ํนํ๋ ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ๋ด๋นํ๊ณ , ๋ช ์์ ๋ฐฐ๋ฆฌ์ด๋ก ๋๊ธฐํ๋ฉ๋๋ค.
์ฐธ๊ณ : ์ค๋ ๋ ์ญํ ์ด ํนํ๋์ด ์์ต๋๋ค: Stage 1 (์ค๋ ๋ 0-127)์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ ์ ์ฒ๋ฆฌํ๋ฉฐ, Stage 2 (์ค๋ ๋ 128-255)๋ ๋ธ๋ฌ ์ฐ์ฐ์ ์ ์ฉํ๊ณ , Stage 3 (์ ์ฒด ์ค๋ ๋)์ ์ต์ข ์ค๋ฌด๋ฉ์ ์ํํฉ๋๋ค.
์๊ณ ๋ฆฌ์ฆ ์ํคํ ์ฒ: ์ด ํผ์ฆ์ ํ๋์ GPU ๋ธ๋ก ์์์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์์ ํ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๋ ์์ฐ์-์๋น์ ํ์ดํ๋ผ์ธ์ ๊ตฌํํฉ๋๋ค. ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋ํด ๋์ผํ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๋ ์ ํต์ ์ธ GPU ํ๋ก๊ทธ๋๋ฐ๊ณผ ๋ฌ๋ฆฌ, ์ด ์ ๊ทผ ๋ฐฉ์์ ์ค๋ ๋๋ฅผ ๊ธฐ๋ฅ๋ณ๋ก ํนํํ์ฌ ๋ถํ ํฉ๋๋ค.
ํ์ดํ๋ผ์ธ ๊ฐ๋ : ์๊ณ ๋ฆฌ์ฆ์ ์ธ ๊ฐ์ ๊ตฌ๋ถ๋ ๋จ๊ณ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ, ๊ฐ ๋จ๊ณ์๋ ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๋ ํนํ๋ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์์ต๋๋ค. ๊ฐ ๋จ๊ณ๋ ๋ค์ ๋จ๊ณ๊ฐ ์๋นํ๋ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ์ฌ, ๋ฐฐ๋ฆฌ์ด๋ก ์ ์คํ๊ฒ ๋๊ธฐํํด์ผ ํ๋ ๋ช ์์ ์์ฐ์-์๋น์ ๊ด๊ณ๋ฅผ ๋ง๋ญ๋๋ค.
๋ฐ์ดํฐ ์์กด์ฑ๊ณผ ๋๊ธฐํ: ๊ฐ ๋จ๊ณ๋ ๋ค์ ๋จ๊ณ๊ฐ ์๋นํ๋ ๋ฐ์ดํฐ๋ฅผ ์์ฑํฉ๋๋ค:
- Stage 1 โ Stage 2: ์ฒซ ๋ฒ์งธ ๋จ๊ณ๊ฐ ๋ธ๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ํ ์ ์ฒ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์์ฑ
- Stage 2 โ Stage 3: ๋ ๋ฒ์งธ ๋จ๊ณ๊ฐ ์ต์ข ์ค๋ฌด๋ฉ์ ์ํ ๋ธ๋ฌ ๊ฒฐ๊ณผ๋ฅผ ์์ฑ
- ๋ฐฐ๋ฆฌ์ด๊ฐ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์ง: ์์กดํ๋ ๋จ๊ณ๊ฐ ์์๋๊ธฐ ์ ์ ํด๋น ๋จ๊ณ๊ฐ ์์ ํ ์๋ฃ๋๋๋ก ๋ณด์ฅ
๊ตฌ์ฒด์ ์ผ๋ก, ๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ์ ์ธ ๊ฐ์ง ์ํ ์ฐ์ฐ์ผ๋ก ๊ตฌ์ฑ๋ ์กฐ์จ๋ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค:
Stage 1 - ์ ์ฒ๋ฆฌ ๊ฐํ:
\[P[i] = I[i] \times 1.1\]
์ฌ๊ธฐ์ \(P[i]\)๋ ์ ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ์ด๊ณ \(I[i]\)๋ ์ ๋ ฅ ๋ฐ์ดํฐ์ ๋๋ค.
Stage 2 - ์ํ ๋ธ๋ฌ ํํฐ:
\[B[i] = \frac{1}{N_i} \sum_{k=-2}^{2} P[i+k] \quad \text{where} i+k \in [0, 255]\]
์ฌ๊ธฐ์ \(B[i]\)๋ ๋ธ๋ฌ ๊ฒฐ๊ณผ์ด๊ณ , \(N_i\)๋ ํ์ผ ๊ฒฝ๊ณ ๋ด์ ์ ํจํ ์ด์ ์์ ๋๋ค.
Stage 3 - ์ฐ์์ ์ด์ ์ค๋ฌด๋ฉ:
\[F[i] = \begin{cases} (B[i] + B[i+1]) \times 0.6 & \text{if } i = 0 \\ ((B[i] + B[i-1]) \times 0.6 + B[i+1]) \times 0.6 & \text{if } 0 < i < 255 \\ (B[i] + B[i-1]) \times 0.6 & \text{if } i = 255 \end{cases}\]
์ฌ๊ธฐ์ \(F[i]\)๋ ์ฐ์์ ์ค๋ฌด๋ฉ์ด ์ ์ฉ๋ ์ต์ข ์ถ๋ ฅ์ ๋๋ค.
์ค๋ ๋ ํนํ:
- ์ค๋ ๋ 0-127: \(i \in \{0, 1, 2, \ldots, 255\}\)์ ๋ํด \(P[i]\) ๊ณ์ฐ (์ค๋ ๋๋น 2๊ฐ ์์)
- ์ค๋ ๋ 128-255: \(i \in \{0, 1, 2, \ldots, 255\}\)์ ๋ํด \(B[i]\) ๊ณ์ฐ (์ค๋ ๋๋น 2๊ฐ ์์)
- ์ ์ฒด 256๊ฐ ์ค๋ ๋: \(i \in \{0, 1, 2, \ldots, 255\}\)์ ๋ํด \(F[i]\) ๊ณ์ฐ (์ค๋ ๋๋น 1๊ฐ ์์)
๋๊ธฐํ ์ง์ :
\[\text{barrier}_1 \Rightarrow P[i] \text{ complete} \Rightarrow \text{barrier}_2 \Rightarrow B[i] \text{ complete} \Rightarrow \text{barrier}_3 \Rightarrow F[i] \text{ complete}\]
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์๋ ๋ค์์ ๋ฐฐ์๋๋ค:
- ํ๋์ GPU ๋ธ๋ก ์์์ ์ค๋ ๋ ์ญํ ํนํ ๊ตฌํ
- ์ฒ๋ฆฌ ๋จ๊ณ ๊ฐ ์์ฐ์-์๋น์ ๊ด๊ณ ์กฐ์จ
- ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๊ฐ์ ๋๊ธฐํ๋ฅผ ์ํ ๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ (๋์ผํ ์๊ณ ๋ฆฌ์ฆ ๋ด๋ถ๋ฟ ์๋๋ผ)
ํต์ฌ ํต์ฐฐ์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์์ ํ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๋ฉด์ ์ ๋ต์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น๋ฅผ ํตํด ์กฐ์จ๋๋ ๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ์ ์ด๋ป๊ฒ ์ค๊ณํ๋์ง ์ดํดํ๋ ๊ฒ์ ๋๋ค.
์ ์ค์ํ๊ฐ: ๋๋ถ๋ถ์ GPU ํํ ๋ฆฌ์ผ์ ๋จ์ผ ์๊ณ ๋ฆฌ์ฆ ๋ด์์์ ๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ๋ฒ - ๋ฆฌ๋์ ์ด๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ ์ค ์ค๋ ๋๋ฅผ ๋๊ธฐํํ๋ ๊ฒ - ์ ๊ฐ๋ฅด์นฉ๋๋ค. ํ์ง๋ง ์ค์ GPU ์๊ณ ๋ฆฌ์ฆ์์๋ ์ ์คํ๊ฒ ์กฐ์จํด์ผ ํ๋ ์ฌ๋ฌ ๊ฐ์ ๊ตฌ๋ถ๋ ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ํฌํจํ๋ ์ํคํ ์ฒ์ ๋ณต์ก์ฑ์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด ํผ์ฆ์ ๋จ์ผ์ฒด์ ์๊ณ ๋ฆฌ์ฆ์ ํนํ๋๊ณ ์กฐ์จ๋ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ผ๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
์ด์ ํผ์ฆ๊ณผ ํ์ฌ์ ๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ ๋น๊ต:
- ์ด์ ํผ์ฆ (P8, P12, P15): ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๊ณ , ๋ฐฐ๋ฆฌ์ด๋ ์๊ณ ๋ฆฌ์ฆ ๋จ๊ณ ๋ด์์ ๋๊ธฐํ
- ์ด ํผ์ฆ: ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํํ๊ณ , ๋ฐฐ๋ฆฌ์ด๋ ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๊ฐ์ ์กฐ์จ
์ค๋ ๋ ํนํ ์ํคํ ์ฒ: ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ ์ธ๋ฑ์ค๋ง ๋ค๋ฅธ ๋ฐ์ดํฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ๋ฌ๋ฆฌ, ์ด ํผ์ฆ์ ํ์ดํ๋ผ์ธ์์์ ์ญํ ์ ๋ฐ๋ผ ์ค๋ ๋๊ฐ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์ฝ๋ ๊ฒฝ๋ก๋ฅผ ์คํํ๋ ์๊ณ ๋ฆฌ์ฆ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํฉ๋๋ค.
๊ตฌ์ฑ
์์คํ ๋งค๊ฐ๋ณ์:
- ์ด๋ฏธ์ง ํฌ๊ธฐ:
SIZE = 1024์์ (๊ฐ์ํ๋ฅผ ์ํด 1D) - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 256์ค๋ ๋,(256, 1)๋ธ๋ก ์ฐจ์์ผ๋ก ๊ตฌ์ฑ - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: ์ ์ฒด ์ด๋ฏธ์ง๋ฅผ ํ์ผ ๋จ์๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ
(4, 1)๋ธ๋ก (์ด 4๊ฐ ๋ธ๋ก) - ๋ฐ์ดํฐ ํ์
: ๋ชจ๋ ์ฐ์ฐ์
DType.float32
์ค๋ ๋ ํนํ ์ํคํ ์ฒ:
-
Stage 1 ์ค๋ ๋:
STAGE1_THREADS = 128(์ค๋ ๋ 0-127, ๋ธ๋ก์ ์ ๋ฐ๋ถ)- ์ญํ : ์ ์ญ ๋ฉ๋ชจ๋ฆฌ์์ ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ ์ ์ฒ๋ฆฌ ์ ์ฉ
- ์์ ๋ถ๋ฐฐ: ํจ์จ์ ์ธ ๋ถํ ๊ท ํ์ ์ํด ์ค๋ ๋๋น 2๊ฐ ์์ ์ฒ๋ฆฌ
- ์ถ๋ ฅ:
input_shared[256]์ ์ ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ
-
Stage 2 ์ค๋ ๋:
STAGE2_THREADS = 128(์ค๋ ๋ 128-255, ๋ธ๋ก์ ํ๋ฐ๋ถ)- ์ญํ : ์ ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ์ ์ํ ๋ธ๋ฌ ํํฐ ์ ์ฉ
- ์์ ๋ถ๋ฐฐ: ์ค๋ ๋๋น 2๊ฐ์ ๋ธ๋ฌ ์ฐ์ฐ ์ฒ๋ฆฌ
- ์ถ๋ ฅ:
blur_shared[256]์ ๋ธ๋ฌ ๊ฒฐ๊ณผ ์ฑ์ฐ๊ธฐ
-
Stage 3 ์ค๋ ๋: ์ ์ฒด 256๊ฐ ์ค๋ ๋ ํ๋ ฅ
- ์ญํ : ์ต์ข ์ค๋ฌด๋ฉ ๋ฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ก ์ถ๋ ฅ
- ์์
๋ถ๋ฐฐ: ์ผ๋์ผ ๋งคํ (์ค๋ ๋
i๊ฐ ์์i๋ฅผ ์ฒ๋ฆฌ) - ์ถ๋ ฅ: ๊ธ๋ก๋ฒ
output๋ฐฐ์ด์ ์ต์ข ๊ฒฐ๊ณผ ๊ธฐ๋ก
์์ฑํ ์ฝ๋
comptime TPB = 256 # Threads per block for pipeline stages
comptime SIZE = 1024 # Image size (1D for simplicity)
comptime BLOCKS_PER_GRID = (4, 1)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
# Multi-stage processing configuration
comptime STAGE1_THREADS = TPB // 2
comptime STAGE2_THREADS = TPB // 2
comptime BLUR_RADIUS = 2
def multi_stage_image_blur_pipeline(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
"""Multi-stage image blur pipeline with barrier coordination.
Stage 1 (threads 0-127): Load input data and apply 1.1x preprocessing
Stage 2 (threads 128-255): Apply 5-point blur with BLUR_RADIUS=2
Stage 3 (all threads): Final neighbor smoothing and output
"""
# Shared memory buffers for pipeline stages
var input_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var blur_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Stage 1: Load and preprocess (threads 0-127)
# FILL ME IN (roughly 10 lines)
barrier() # Wait for Stage 1 completion
# Stage 2: Apply blur (threads 128-255)
# FILL ME IN (roughly 25 lines)
barrier() # Wait for Stage 2 completion
# Stage 3: Final smoothing (all threads)
# FILL ME IN (roughly 7 lines)
barrier() # Ensure all writes complete
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p29/p29.mojo
ํ
์ค๋ ๋ ์ญํ ์๋ณ
- ์ค๋ ๋ ์ธ๋ฑ์ค ๋น๊ต๋ฅผ ํตํด ๊ฐ ์ค๋ ๋๊ฐ ์ด๋ค ๋จ๊ณ๋ฅผ ์คํํด์ผ ํ๋์ง ๊ฒฐ์
- Stage 1: ์ ๋ฐ๋ถ ์ค๋ ๋ (์ค๋ ๋ 0-127)
- Stage 2: ํ๋ฐ๋ถ ์ค๋ ๋ (์ค๋ ๋ 128-255)
- Stage 3: ๋ชจ๋ ์ค๋ ๋ ์ฐธ์ฌ
Stage 1 ์ ๊ทผ ๋ฐฉ์
- ์ ์ ํ ์ธ๋ฑ์ค ๋น๊ต๋ฅผ ํตํด Stage 1 ์ค๋ ๋ ์๋ณ
- ๋ถํ ๊ท ํ์ ์ํด ๊ฐ ์ค๋ ๋๊ฐ ์ฌ๋ฌ ์์๋ฅผ ์ฒ๋ฆฌ
- ์ ์ฒ๋ฆฌ ๊ฐํ ๊ณ์ ์ ์ฉ
- ์ ๋ก ํจ๋ฉ์ ์ฌ์ฉํ ์ ์ ํ ๊ฒฝ๊ณ ์ฒ๋ฆฌ ๊ตฌํ
Stage 2 ์ ๊ทผ ๋ฐฉ์
- Stage 2 ์ค๋ ๋๋ฅผ ์๋ณํ๊ณ ์ธ๋ฑ์ค๋ฅผ ์ฒ๋ฆฌ ๋ฒ์์ ๋งคํ
- ์ด์ ์์์ ํ๊ท ์ ๊ตฌํ๋ ๋ธ๋ฌ ์ปค๋ ๊ตฌํ
- ์ ํจํ ์ด์๋ง ํฌํจํ์ฌ ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ
- ํจ์จ์ฑ์ ์ํด ์ค๋ ๋๋น ์ฌ๋ฌ ์์ ์ฒ๋ฆฌ
Stage 3 ์ ๊ทผ ๋ฐฉ์
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์ต์ข ์ฒ๋ฆฌ์ ์ฐธ์ฌ
- ์ง์ ๋ ์ค์ผ์ผ๋ง ๊ณ์๋ฅผ ์ฌ์ฉํ ์ด์ ์ค๋ฌด๋ฉ ์ ์ฉ
- ์ด์์ด ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ์ ์ฃ์ง ์ผ์ด์ค ์ฒ๋ฆฌ
- ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํตํด ๊ธ๋ก๋ฒ ์ถ๋ ฅ์ ๊ฒฐ๊ณผ ๊ธฐ๋ก
๋๊ธฐํ ์ ๋ต
- ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋จ๊ณ ์ฌ์ด์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น
- ์์กดํ๋ ๋จ๊ณ๊ฐ ์์๋๊ธฐ ์ ์ ๊ฐ ๋จ๊ณ๊ฐ ์๋ฃ๋๋๋ก ๋ณด์ฅ
- ๋ธ๋ก ์ข ๋ฃ ์ ์๋ฃ๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด ์ต์ข ๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ ์คํํฉ๋๋ค:
pixi run p29 --multi-stage
pixi run -e amd p29 --multi-stage
uv run poe p29 --multi-stage
ํผ์ฆ์ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃํ๋ฉด ๋ค์๊ณผ ์ ์ฌํ ์ถ๋ ฅ์ด ํ์๋ฉ๋๋ค:
Puzzle 29: GPU Synchronization Primitives
==================================================
TPB: 256
SIZE: 1024
STAGE1_THREADS: 128
STAGE2_THREADS: 128
BLUR_RADIUS: 2
Testing Puzzle 29A: Multi-Stage Pipeline Coordination
============================================================
Multi-stage pipeline blur completed
Input sample: 0.0 1.01 2.02
Output sample: 1.6665002 2.3331003 3.3996604
โ
Multi-stage pipeline coordination test PASSED!
์๋ฃจ์
def multi_stage_image_blur_pipeline(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
size: Int,
):
"""Multi-stage image blur pipeline with barrier coordination.
Stage 1 (threads 0-127): Load input data and apply 1.1x preprocessing
Stage 2 (threads 128-255): Apply 5-point blur with BLUR_RADIUS=2
Stage 3 (all threads): Final neighbor smoothing and output
"""
# Shared memory buffers for pipeline stages
var input_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var blur_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Stage 1: Load and preprocess (threads 0-127)
if local_i < STAGE1_THREADS:
if global_i < size:
input_shared[local_i] = input[global_i] * 1.1
# Each thread loads 2 elements
if local_i + STAGE1_THREADS < size:
input_shared[local_i + STAGE1_THREADS] = (
input[global_i + STAGE1_THREADS] * 1.1
)
else:
# Zero-padding for out-of-bounds
input_shared[local_i] = 0.0
if local_i + STAGE1_THREADS < TPB:
input_shared[local_i + STAGE1_THREADS] = 0.0
barrier() # Wait for Stage 1 completion
# Stage 2: Apply blur (threads 128-255)
if local_i >= STAGE1_THREADS:
var blur_idx = local_i - STAGE1_THREADS
var blur_sum: Scalar[dtype] = 0.0
blur_count = 0
# 5-point blur kernel
for offset in range(-BLUR_RADIUS, BLUR_RADIUS + 1):
sample_idx = blur_idx + offset
if sample_idx >= 0 and sample_idx < TPB:
blur_sum += rebind[Scalar[dtype]](input_shared[sample_idx])
blur_count += 1
if blur_count > 0:
blur_shared[blur_idx] = blur_sum / Scalar[dtype](blur_count)
else:
blur_shared[blur_idx] = 0.0
# Process second element
var second_idx = blur_idx + STAGE1_THREADS
if second_idx < TPB:
blur_sum = 0.0
blur_count = 0
for offset in range(-BLUR_RADIUS, BLUR_RADIUS + 1):
sample_idx = second_idx + offset
if sample_idx >= 0 and sample_idx < TPB:
blur_sum += rebind[Scalar[dtype]](input_shared[sample_idx])
blur_count += 1
if blur_count > 0:
blur_shared[second_idx] = blur_sum / Scalar[dtype](blur_count)
else:
blur_shared[second_idx] = 0.0
barrier() # Wait for Stage 2 completion
# Stage 3: Final smoothing (all threads)
if global_i < size:
final_value = blur_shared[local_i]
# Neighbor smoothing with 0.6 scaling
if local_i > 0:
final_value = (final_value + blur_shared[local_i - 1]) * 0.6
if local_i < TPB - 1:
final_value = (final_value + blur_shared[local_i + 1]) * 0.6
output[global_i] = final_value
barrier() # Ensure all writes complete
ํต์ฌ ํต์ฐฐ์ ์ด๊ฒ์ด ์ค๋ ๋ ์ญํ ํนํ๋ฅผ ๊ฐ์ง ํ์ดํ๋ผ์ธ ์ํคํ ์ฒ ๋ฌธ์ ์์ ์ธ์ํ๋ ๊ฒ์ ๋๋ค:
- ๋จ๊ณ๋ณ ์ค๋ ๋ ๊ทธ๋ฃน ์ค๊ณ: ๋ฐ์ดํฐ๋ฟ๋ง ์๋๋ผ ๊ธฐ๋ฅ๋ณ๋ก ์ค๋ ๋๋ฅผ ๋ถํ
- ์์ฐ์-์๋น์ ์ฒด์ธ ๊ตฌํ: Stage 1์ด Stage 2๋ฅผ ์ํด ์์ฐํ๊ณ , Stage 2๊ฐ Stage 3์ ์ํด ์์ฐ
- ์ ๋ต์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น: ๋์ผํ ์๊ณ ๋ฆฌ์ฆ ๋ด๊ฐ ์๋๋ผ ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๊ฐ์ ๋๊ธฐํ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ: ๋ณํฉ๋ ์ฝ๊ธฐ์ ํจ์จ์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ๋ณด์ฅ
์์ธ ์ค๋ช ์ด ํฌํจ๋ ์ ์ฒด ์๋ฃจ์
๋ค๋จ๊ณ ํ์ดํ๋ผ์ธ ์๋ฃจ์ ์ ์ ๊ตํ ์ค๋ ๋ ํนํ์ ๋ฐฐ๋ฆฌ์ด ์กฐ์ ์ ๋ณด์ฌ์ค๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ ํต์ ์ธ ๋จ์ผ์ฒด์ GPU ์๊ณ ๋ฆฌ์ฆ์ ํนํ๋๊ณ ์กฐ์จ๋ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ผ๋ก ๋ณํํฉ๋๋ค.
ํ์ดํ๋ผ์ธ ์ํคํ ์ฒ ์ค๊ณ
์ด ํผ์ฆ์ ๊ทผ๋ณธ์ ์ธ ๋ํ๊ตฌ๋ ๋ฐ์ดํฐ๊ฐ ์๋ ์ญํ ์ ์ํ ์ค๋ ๋ ํนํ์ ๋๋ค:
์ ํต์ ์ธ ์ ๊ทผ ๋ฐฉ์: ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋ํด ๋์ผํ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ์ฐ์ฐ์ ์ํ (๋ฆฌ๋์ ์ด๋ ํ๋ ฌ ์ฐ์ฐ ๋ฑ)
- ๋ฐฐ๋ฆฌ์ด๋ ๋์ผํ ์๊ณ ๋ฆฌ์ฆ ๋จ๊ณ ๋ด์์ ์ค๋ ๋๋ฅผ ๋๊ธฐํ
- ์ค๋ ๋ ์ญํ ์ ์ฒ๋ฆฌํ๋ ๋ฐ์ดํฐ ์ธ๋ฑ์ค๋ง ๋ค๋ฆ
์ด ํผ์ฆ์ ํ์ : ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์์ ํ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
- ์ค๋ ๋ 0-127์ด ๋ก๋ฉ ๋ฐ ์ ์ฒ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
- ์ค๋ ๋ 128-255๊ฐ ๋ธ๋ฌ ์ฒ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์ต์ข ์ค๋ฌด๋ฉ ์๊ณ ๋ฆฌ์ฆ์ ํ๋ ฅ
- ๋ฐฐ๋ฆฌ์ด๋ ๋์ผํ ์๊ณ ๋ฆฌ์ฆ ๋ด๊ฐ ์๋๋ผ ์๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๊ฐ์ ์กฐ์จ
์์ฐ์-์๋น์ ์กฐ์
์ค๋ ๋๊ฐ ๋์ผํ ์๊ณ ๋ฆฌ์ฆ ๋ด์์ ๋๋ฑํ ์ญํ ์ ํ๋ ์ด์ ํผ์ฆ๊ณผ ๋ฌ๋ฆฌ, ์ด ํผ์ฆ์ ๋ช ์์ ์ธ ์์ฐ์-์๋น์ ๊ด๊ณ๋ฅผ ์ค์ ํฉ๋๋ค:
- Stage 1: ์์ฐ์ (Stage 2๋ฅผ ์ํ ์ ์ฒ๋ฆฌ ๋ฐ์ดํฐ ์์ฑ)
- Stage 2: ์๋น์ (Stage 1์ ๋ฐ์ดํฐ ์ฌ์ฉ) + ์์ฐ์ (Stage 3์ ์ํ ๋ธ๋ฌ ๋ฐ์ดํฐ ์์ฑ)
- Stage 3: ์๋น์ (Stage 2์ ๋ฐ์ดํฐ ์ฌ์ฉ)
์ ๋ต์ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น
๋ฐฐ๋ฆฌ์ด๊ฐ ์ธ์ ํ์ํ๊ณ ์ธ์ ๋ญ๋น์ ์ธ์ง ์ดํดํ๊ธฐ:
- ํ์ํ ๊ฒฝ์ฐ: ์์กด์ ์ธ ๋จ๊ณ ์ฌ์ด์์ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด
- ๋ญ๋น์ ์ธ ๊ฒฝ์ฐ: ๊ฐ์ ๋จ๊ณ์ ๋ ๋ฆฝ์ ์ธ ์ฐ์ฐ ๋ด์์
- ์ฑ๋ฅ ํต์ฐฐ: ๊ฐ ๋ฐฐ๋ฆฌ์ด์๋ ๋น์ฉ์ด ์์ผ๋ฏ๋ก ์ ๋ต์ ์ผ๋ก ์ฌ์ฉ
ํต์ฌ ๋๊ธฐํ ์ง์ :
- Stage 1 ์ดํ: Stage 2๊ฐ ๋ถ์์ ํ ์ ์ฒ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ๊ฒ์ ๋ฐฉ์ง
- Stage 2 ์ดํ: Stage 3์ด ๋ถ์์ ํ ๋ธ๋ฌ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๋ ๊ฒ์ ๋ฐฉ์ง
- Stage 3 ์ดํ: ๋ธ๋ก ์ข ๋ฃ ์ ๋ชจ๋ ์ถ๋ ฅ ์ฐ๊ธฐ๊ฐ ์๋ฃ๋๋๋ก ๋ณด์ฅ
์ค๋ ๋ ํ์ฉ ํจํด
- Stage 1: 50% ํ์ฉ (256๊ฐ ์ค 128๊ฐ ์ค๋ ๋ ํ์ฑ, 128๊ฐ ์ ํด)
- Stage 2: 50% ํ์ฉ (128๊ฐ ํ์ฑ, 128๊ฐ ์ ํด)
- Stage 3: 100% ํ์ฉ (์ ์ฒด 256๊ฐ ์ค๋ ๋ ํ์ฑ)
์ด๊ฒ์ ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์กฐ์จ๋ ํ์ดํ๋ผ์ธ ๋ด์์ ์๋ก ๋ค๋ฅธ ์ฐ์ฐ ์์ ์ ํนํ๋๋ ์ ๊ตํ ์๊ณ ๋ฆฌ์ฆ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉฐ, ๋จ์ํ ๋ฐ์ดํฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๋์ด ์ค์ GPU ์๊ณ ๋ฆฌ์ฆ์ ํ์ํ ์ํคํ ์ฒ์ ์ฌ๊ณ ๋ก ๋์๊ฐ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํคํ ์ฒ:
- ๋ ๊ฐ์ ํนํ๋ ๋ฒํผ๊ฐ ๋จ๊ณ ๊ฐ ๋ฐ์ดํฐ ํ๋ฆ์ ์ฒ๋ฆฌ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๊ฒฝ๊ณ ์ฐ์ฐ์๋ง ์ต์ํ
- ๋ชจ๋ ์ค๊ฐ ์ฒ๋ฆฌ์ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
์ ๊ทผ ํจํด์ ์ด์ :
- Stage 1: ์ ๋ ฅ ๋ก๋ฉ์ ์ํ ๋ณํฉ๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ
- Stage 2: ๋ธ๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ํ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ
- Stage 3: ์ถ๋ ฅ์ ์ํ ๋ณํฉ๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ
์ค์ ์์ฉ ๋ถ์ผ
์ด ํ์ดํ๋ผ์ธ ์ํคํ ์ฒ ํจํด์ ๋ค์ ๋ถ์ผ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค:
์ด๋ฏธ์ง ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ:
- ๋ค๋จ๊ณ ํํฐ (๋ธ๋ฌ, ์ ๋ช ํ, ์ฃ์ง ๊ฒ์ถ์ ์์ฐจ์ ์ผ๋ก)
- ์ ๊ณต๊ฐ ๋ณํ (RGB โ HSV โ ์ฒ๋ฆฌ โ RGB)
- ๋ค์ค ์๊ณ ๋ฆฌ์ฆ ํจ์ค๋ฅผ ์ฌ์ฉํ ๋ ธ์ด์ฆ ๊ฐ์
๊ณผํ ์ฐ์ฐ:
- ๋ค๋จ๊ณ ์ ํ ์ฐจ๋ถ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์คํ ์ค ์ฐ์ฐ
- ํํฐ๋ง, ๋ณํ, ๋ถ์ ํ์ดํ๋ผ์ธ์ ์ฌ์ฉํ ์ ํธ ์ฒ๋ฆฌ
- ๋ค๋จ๊ณ ์๋ฒ ๋ฐ๋ณต์ ์ฌ์ฉํ ์ ์ฐ ์ ์ฒด ์ญํ
๋จธ์ ๋ฌ๋:
- ์๋ก ๋ค๋ฅธ ์ฐ์ฐ์ ์ํด ํนํ๋ ์ค๋ ๋ ๊ทธ๋ฃน์ ๊ฐ์ง ์ ๊ฒฝ๋ง ๋ ์ด์ด
- ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ (์กฐ์จ๋ ๋จ๊ณ์์ ๋ก๋, ์ ๊ทํ, ์ฆ๊ฐ)
- ์๋ก ๋ค๋ฅธ ์ค๋ ๋ ๊ทธ๋ฃน์ด ์๋ก ๋ค๋ฅธ ์ฐ์ฐ์ ์ฒ๋ฆฌํ๋ ๋ฐฐ์น ์ฒ๋ฆฌ
ํต์ฌ ๊ธฐ์ ์ ํต์ฐฐ
์๊ณ ๋ฆฌ์ฆ ๋ณ๋ ฌ ์ฒ๋ฆฌ vs. ๋ฐ์ดํฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ:
- ๋ฐ์ดํฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ ์์์ ๋์ผํ ์ฝ๋๋ฅผ ์คํ
- ์๊ณ ๋ฆฌ์ฆ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์ค๋ ๋๊ฐ ํนํ๋ ์ญํ ์ ๋ฐ๋ผ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ์ ์คํ
๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ ์ฒ ํ:
- ์ ๋ต์ ๋ฐฐ์น: ์์กด์ ์ธ ๋จ๊ณ ๊ฐ์ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํ์ํ ๊ณณ์๋ง ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น
- ์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ: ๊ฐ ๋ฐฐ๋ฆฌ์ด์๋ ๋๊ธฐํ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ๋ฏ๋ก ์ ํํ์ง๋ง ์ ์ ๋ ์ฌ์ฉ
- ์ ํ์ฑ ๋ณด์ฅ: ์ ์ ํ ๋ฐฐ๋ฆฌ์ด ๋ฐฐ์น๋ก ์ค๋ ๋ ์คํ ํ์ด๋ฐ์ ๊ด๊ณ์์ด ๊ฒฐ์ ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฅ
์ค๋ ๋ ํนํ์ ์ด์ :
- ์๊ณ ๋ฆฌ์ฆ ์ต์ ํ: ๊ฐ ๋จ๊ณ๋ฅผ ํด๋น ์ฐ์ฐ ํจํด์ ๋ง๊ฒ ์ต์ ํ ๊ฐ๋ฅ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ ํ: ์๋ก ๋ค๋ฅธ ๋จ๊ณ์์ ์๋ก ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ ๋ต ์ฌ์ฉ ๊ฐ๋ฅ
- ๋ฆฌ์์ค ํ์ฉ: ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ ํนํ๋๊ณ ํจ์จ์ ์ธ ๊ตฌ์ฑ ์์๋ก ๋ถํด ๊ฐ๋ฅ
์ด ์๋ฃจ์ ์ ๋ณต์กํ ๋ค๋จ๊ณ ์ฐ์ฐ์ ์ํด ์ค๋ ๋ ํนํ์ ์ ๋ต์ ๋๊ธฐํ๋ฅผ ํ์ฉํ๋ ์ ๊ตํ GPU ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฉฐ, ๋จ์ํ ๋ณ๋ ฌ ๋ฃจํ๋ฅผ ๋์ด ์ค์ GPU ์ํํธ์จ์ด์์ ์ฌ์ฉ๋๋ ์ํคํ ์ฒ์ ์ ๊ทผ ๋ฐฉ์์ผ๋ก ๋์๊ฐ๋๋ค.
๋๋ธ ๋ฒํผ๋ง ์คํ ์ค ์ฐ์ฐ
๐ฌ ์ธ๋ฐํ ๋๊ธฐํ: mbarrier vs barrier()
์ด ํผ์ฆ์ ์ด์ ํผ์ฆ์์ ์ฌ์ฉํ ๊ธฐ๋ณธ
barrier()ํจ์๋ณด๋ค ํจ์ฌ ๊ฐ๋ ฅํ ์ ์ด๋ฅผ ์ ๊ณตํ๋ ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด API๋ฅผ ์๊ฐํฉ๋๋ค.๊ธฐ๋ณธ
barrier()์ ํ๊ณ:
- ์ผํ์ฑ ์ฌ์ฉ: ์ํ ์ถ์ ์์ด ๋จ์ผ ๋๊ธฐํ ์ง์ ๋ง ์ ๊ณต
- ๋ธ๋ก ์ ์ฒด ์ ์ฉ: ๋ธ๋ก์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์์ ์ฐธ์ฌํด์ผ ํจ
- ์ฌ์ฌ์ฉ ๋ถ๊ฐ: ๋งค barrier() ํธ์ถ์ด ์๋ก์ด ๋๊ธฐํ ์ด๋ฒคํธ๋ฅผ ์์ฑ
- ์ธ๋ฐ๋ ๋ถ์กฑ: ๋ฉ๋ชจ๋ฆฌ ์์์ ํ์ด๋ฐ์ ๋ํ ์ ํ์ ์ ์ด
- ์ ์ ์กฐ์ : ์ค๋ ๋ ์ฐธ์ฌ ํจํด์ ๋ณํ์ ์ ์ ๋ถ๊ฐ
๊ณ ๊ธ
mbarrier API์ ๊ธฐ๋ฅ:
- ์ ๋ฐํ ์ ์ด:
mbarrier_init()๋ก ํน์ ์ค๋ ๋ ์๋ฅผ ์ง์ ํ์ฌ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐฐ๋ฆฌ์ด ๊ฐ์ฒด๋ฅผ ์ค์ - ์ํ ์ถ์ :
mbarrier_arrive()๋ก ๊ฐ๋ณ ์ค๋ ๋ ์๋ฃ๋ฅผ ์๋ฆฌ๊ณ ๋์ฐฉ ํ์๋ฅผ ์ ์ง- ์ ์ฐํ ๋๊ธฐ:
mbarrier_test_wait()๋ก ํน์ ์๋ฃ ์ํ๋ฅผ ๊ธฐ๋ค๋ฆด ์ ์์- ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฐ์ฒด: ๋์ผํ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ฌ๋ฌ ๋ฐ๋ณต์ ๊ฑธ์ณ ์ฌ์ด๊ธฐํํ๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
- ๋ค์ค ๋ฐฐ๋ฆฌ์ด: ์๋ก ๋ค๋ฅธ ๋๊ธฐํ ์ง์ (์ด๊ธฐํ, ๋ฐ๋ณต, ๋ง๋ฌด๋ฆฌ)์ ์๋ก ๋ค๋ฅธ ๋ฐฐ๋ฆฌ์ด ๊ฐ์ฒด ์ฌ์ฉ
- ํ๋์จ์ด ์ต์ ํ: GPU ํ๋์จ์ด ๋๊ธฐํ ๊ธฐ๋ณธ ์์์ ์ง์ ๋งคํํ์ฌ ๋ ๋์ ์ฑ๋ฅ
- ๋ฉ๋ชจ๋ฆฌ ์๋ฏธ๋ก : ๋ฉ๋ชจ๋ฆฌ ๊ฐ์์ฑ๊ณผ ์์ ๋ณด์ฅ์ ๋ํ ๋ช ์์ ์ ์ด
๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์์ ์ ์ค์ํ๊ฐ: ๋๋ธ ๋ฒํผ๋ง ํจํด์์๋ ๋ฒํผ ๊ต์ฒด ๋จ๊ณ ๊ฐ์ ์ ๋ฐํ ์กฐ์ ์ด ํ์ํฉ๋๋ค. ๊ธฐ๋ณธ
barrier()๋ก๋ ๋ค์์ ํ์ํ ์ธ๋ฐํ ์ ์ด๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค:
- ๋ฒํผ ์ญํ ๊ต๋: buffer_A์ ๋ํ ๋ชจ๋ ์ฐ๊ธฐ๊ฐ ์๋ฃ๋ ํ์์ผ buffer_A์์ ์ฝ๊ธฐ ์์๋๋๋ก ๋ณด์ฅ
- ๋ฐ๋ณต ๊ฒฝ๊ณ: ๋จ์ผ ์ปค๋ ๋ด์์ ์ฌ๋ฌ ๋๊ธฐํ ์ง์ ์กฐ์จ
- ์ํ ๊ด๋ฆฌ: ์ด๋ค ์ค๋ ๋๊ฐ ์ด๋ค ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ์๋ฃํ๋์ง ์ถ์
- ์ฑ๋ฅ ์ต์ ํ: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐฐ๋ฆฌ์ด ๊ฐ์ฒด๋ฅผ ํตํด ๋๊ธฐํ ์ค๋ฒํค๋ ์ต์ํ
์ด ํผ์ฆ์ ๋ฐ๋ณต๋ฒ, ์๋ฎฌ๋ ์ด์ ํ๋ ์์ํฌ, ๊ณ ์ฑ๋ฅ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๋ฑ ์ค์ GPU ์ปดํจํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ๋๋ ๋๊ธฐํ ํจํด์ ๋ณด์ฌ์ค๋๋ค.
๊ฐ์
๋๋ธ ๋ฒํผ๋ง ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ๋ณต ์คํ ์ค ์ฐ์ฐ์ ์ํํ๋ ์ปค๋์ ๊ตฌํํฉ๋๋ค. ๋ฐ๋ณต ๊ฐ ์์ ํ ๋ฒํผ ๊ต์ฒด๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ก ์กฐ์ ํฉ๋๋ค. ์คํ ์ค ์ฐ์ฐ์ ๋ฐฐ์ด์ ๊ฐ ์์ ๊ฐ์ ์ด์ ์์๋ค์ ๊ณ ์ ๋ ํจํด์ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ฐํ๋ ์ฐ์ฐ ํจํด์ ๋๋ค.
์ฐธ๊ณ : ๋ฒํผ ์ญํ ์ด ๊ต๋ํฉ๋๋ค: buffer_A์ buffer_B๊ฐ ๋งค ๋ฐ๋ณต๋ง๋ค ์ฝ๊ธฐ์
์ฐ๊ธฐ ์ฐ์ฐ์ ๊ต๋ํ๋ฉฐ, mbarrier ๋๊ธฐํ๊ฐ ๋ฒํผ ๊ต์ฒด ์ ์ ๋ชจ๋ ์ค๋ ๋์ ์ฐ๊ธฐ ์๋ฃ๋ฅผ
๋ณด์ฅํฉ๋๋ค.
์๊ณ ๋ฆฌ์ฆ ์ํคํ ์ฒ: ์ด ํผ์ฆ์ ๋ ๊ฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒํผ๊ฐ ์ฌ๋ฌ ๋ฐ๋ณต์ ๊ฑธ์ณ ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋์์ ์ญํ ์ ๊ต๋ํ๋ ๋๋ธ ๋ฒํผ๋ง ํจํด์ ๊ตฌํํฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ๋ง ์ฒ๋ฆฌํ๋ ๋จ์ํ ์คํ ์ค ์ฐ์ฐ๊ณผ ๋ฌ๋ฆฌ, ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ฒํผ ์ ํ ์ค ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํ ์ธ์ฌํ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ ๊ณผ ํจ๊ป ๋ฐ๋ณต์ ๊ฐ์ ์ ์ํํฉ๋๋ค.
ํ์ดํ๋ผ์ธ ๊ฐ๋ : ์๊ณ ๋ฆฌ์ฆ์ ๋ฐ๋ณต์ ์คํ ์ค ๊ฐ์ ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ๊ฐ ๋ฐ๋ณต์ ํ๋์ ๋ฒํผ์์ ์ฝ๊ณ ๋ค๋ฅธ ๋ฒํผ์ ์ฐ๋ฉฐ, ๋ฒํผ๋ค์ ๋งค ๋ฐ๋ณต๋ง๋ค ์ญํ ์ ๊ต๋ํ์ฌ ๋ฐ์ดํฐ ์์ ์์ด ์ฐ์ ์ฒ๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ ํํ ํจํด์ ๋ง๋ญ๋๋ค.
๋ฐ์ดํฐ ์์กด์ฑ๊ณผ ๋๊ธฐํ: ๊ฐ ๋ฐ๋ณต์ ์ด์ ๋ฐ๋ณต์ ์์ฑ๋ ๊ฒฐ๊ณผ์ ์์กดํฉ๋๋ค:
- ๋ฐ๋ณต N โ ๋ฐ๋ณต N+1: ํ์ฌ ๋ฐ๋ณต์ด ๋ค์ ๋ฐ๋ณต์ด ์๋นํ๋ ๊ฐ์ ๋ ๋ฐ์ดํฐ๋ฅผ ์์ฑ
- ๋ฒํผ ์กฐ์ : ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋ฒํผ๊ฐ ๋งค ๋ฐ๋ณต๋ง๋ค ์ญํ ์ ๊ตํ
- ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๊ฐ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์ง: ์๋ก ๊ธฐ๋ก๋ ๋ฒํผ์์ ์ฝ๊ธฐ๋ฅผ ์์ํ๊ธฐ ์ ์ ๋ชจ๋ ์ฐ๊ธฐ๊ฐ ์๋ฃ๋๋๋ก ๋ณด์ฅ
๊ตฌ์ฒด์ ์ผ๋ก, ๋๋ธ ๋ฒํผ๋ง ์คํ ์ค์ ์ธ ๊ฐ์ง ์ํ ์ฐ์ฐ์ผ๋ก ๊ตฌ์ฑ๋ ๋ฐ๋ณต์ ์ค๋ฌด๋ฉ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค:
๋ฐ๋ณต ํจํด - ๋ฒํผ ๊ต๋:
\[\text{Iteration} i: \begin{cases} \text{Read from buffer_A, Write to buffer_B} & \text{if} i \bmod 2 = 0 \\ \text{Read from buffer_B, Write to buffer_A} & \text{if } i \bmod 2 = 1 \end{cases}\]
์คํ ์ค ์ฐ์ฐ - 3์ ํ๊ท :
\[S^{(i+1)}[j] = \frac{1}{N_j} \sum_{k=-1}^{1} S^{(i)}[j+k] \quad \text{where} j+k \in [0, 255]\]
์ฌ๊ธฐ์ \(S^{(i)}[j]\)๋ ๋ฐ๋ณต \(i\) ์ดํ ์์น \(j\)์์์ ์คํ ์ค ๊ฐ์ด๊ณ , \(N_j\)๋ ์ ํจํ ์ด์ ์์ ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ :
\[\text{mbarrier_arrive}() \Rightarrow \text{mbarrier_test_wait}() \Rightarrow \text{buffer swap} \Rightarrow \text{next iteration}\]
์ต์ข ์ถ๋ ฅ ์ ํ:
\[\text{Output}[j] = \begin{cases} \text{buffer_A}[j] & \text{if STENCIL_ITERATIONS } \bmod 2 = 0 \\ \text{buffer_B}[j] & \text{if STENCIL_ITERATIONS } \bmod 2 = 1 \end{cases}\]
ํต์ฌ ๊ฐ๋
์ด ํผ์ฆ์์๋ ๋ค์์ ๋ฐฐ์๋๋ค:
- ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋๋ธ ๋ฒํผ๋ง ํจํด ๊ตฌํ
- mbarrier API๋ฅผ ์ฌ์ฉํ ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์
- ๋ฐ๋ณต์ ๊ฑธ์ณ ๊ต๋ํ๋ ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ฒํผ ์ญํ ๊ด๋ฆฌ
ํต์ฌ ํต์ฐฐ์ ์ฝ๊ธฐ์ ์ฐ๊ธฐ ์ฐ์ฐ ์ฌ์ด์ ๊ฒฝ์ ์ํ๊ฐ ์ ์ ํ ๋๊ธฐํ๋์ง ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์์์ํฌ ์ ์๋ ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ์์ ๋ฒํผ ๊ต์ฒด๋ฅผ ์์ ํ๊ฒ ์กฐ์จํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋๋ค.
์ ์ค์ํ๊ฐ: ๋๋ถ๋ถ์ GPU ํํ ๋ฆฌ์ผ์ ๋จ์ํ ๋จ์ผ ํจ์ค ์๊ณ ๋ฆฌ์ฆ์ ๋ณด์ฌ์ฃผ์ง๋ง, ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ฐ์ดํฐ์ ๋ํ ๋ค์ค ํจ์ค๋ฅผ ์ํํ๋ ๋ฐ๋ณต์ ๊ฐ์ ์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ๋๋ธ ๋ฒํผ๋ง์ ๊ฐ ๋ฐ๋ณต์ด ์ด์ ๋ฐ๋ณต์ ์์ฑ๋ ๊ฒฐ๊ณผ์ ์์กดํ๋ ๋ฐ๋ณต๋ฒ, ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํํฐ, ์๋ฎฌ๋ ์ด์ ์ ๋ฐ์ดํธ ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ ํ์์ ์ ๋๋ค.
์ด์ ํผ์ฆ๊ณผ ํ์ฌ์ ๋๊ธฐํ ๋น๊ต:
- ์ด์ ํผ์ฆ (P8,
P12, P15): ๋จ์ผ
ํจ์ค ์๊ณ ๋ฆฌ์ฆ์ ์ํ ๋จ์
barrier()ํธ์ถ - ์ด ํผ์ฆ: ๋ฒํผ ๊ต์ฒด ํ์ด๋ฐ์ ๋ํ ์ ๋ฐํ ์ ์ด๋ฅผ ์ํ ๋ช ์์ mbarrier API
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ํนํ: ๊ธฐ๋ณธ์ ์ธ ์ค๋ ๋ ๋๊ธฐํ์ ๋ฌ๋ฆฌ, ์ด ํผ์ฆ์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ด ์ธ์ ์๋ฃ๋๋์ง์ ๋ํ ์ธ๋ฐํ ์ ์ด๋ฅผ ์ ๊ณตํ๋ ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์ด๋ ๋ณต์กํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ํ์์ ์ ๋๋ค.
๊ตฌ์ฑ
์์คํ ๋งค๊ฐ๋ณ์:
- ์ด๋ฏธ์ง ํฌ๊ธฐ:
SIZE = 1024์์ (๊ฐ์ํ๋ฅผ ์ํด 1D) - ๋ธ๋ก๋น ์ค๋ ๋ ์:
TPB = 256์ค๋ ๋,(256, 1)๋ธ๋ก ์ฐจ์์ผ๋ก ๊ตฌ์ฑ - ๊ทธ๋ฆฌ๋ ๊ตฌ์ฑ: ์ ์ฒด ์ด๋ฏธ์ง๋ฅผ ํ์ผ ๋จ์๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ
(4, 1)๋ธ๋ก (์ด 4๊ฐ ๋ธ๋ก) - ๋ฐ์ดํฐ ํ์
: ๋ชจ๋ ์ฐ์ฐ์
DType.float32
๋ฐ๋ณต ๋งค๊ฐ๋ณ์:
- ์คํ
์ค ๋ฐ๋ณต ํ์:
STENCIL_ITERATIONS = 3๊ฐ์ ํจ์ค - ๋ฒํผ ์:
BUFFER_COUNT = 2(๋๋ธ ๋ฒํผ๋ง) - ์คํ ์ค ์ปค๋: ๋ฐ์ง๋ฆ 1์ 3์ ํ๊ท
๋ฒํผ ์ํคํ ์ฒ:
- buffer_A: ์ฃผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒํผ (
[256]์์) - buffer_B: ๋ณด์กฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒํผ (
[256]์์) - ์ญํ ๊ต๋: ๋งค ๋ฐ๋ณต๋ง๋ค ๋ฒํผ๊ฐ ์ฝ๊ธฐ ์์ค์ ์ฐ๊ธฐ ๋์ ์ฌ์ด๋ฅผ ๊ต์ฒด
์ฒ๋ฆฌ ์๊ตฌ์ฌํญ:
์ด๊ธฐํ ๋จ๊ณ:
- ๋ฒํผ ์ค์ : buffer_A๋ฅผ ์ ๋ ฅ ๋ฐ์ดํฐ๋ก, buffer_B๋ฅผ 0์ผ๋ก ์ด๊ธฐํ
- ๋ฐฐ๋ฆฌ์ด ์ด๊ธฐํ: ๋๊ธฐํ ์ง์ ์ ์ํ mbarrier ๊ฐ์ฒด ์ค์
- ์ค๋ ๋ ์กฐ์ : ๋ชจ๋ ์ค๋ ๋๊ฐ ์ด๊ธฐํ์ ์ฐธ์ฌ
๋ฐ๋ณต ์ฒ๋ฆฌ:
- ์ง์ ๋ฐ๋ณต (0, 2, 4โฆ): buffer_A์์ ์ฝ๊ณ buffer_B์ ์ฐ๊ธฐ
- ํ์ ๋ฐ๋ณต (1, 3, 5โฆ): buffer_B์์ ์ฝ๊ณ buffer_A์ ์ฐ๊ธฐ
- ์คํ ์ค ์ฐ์ฐ: 3์ ํ๊ท \((\text{left} + \text{center} + \text{right}) / 3\)
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ๋ฒํผ ๊ฐ์ฅ์๋ฆฌ์ ์์์ ๋ํด ์ ์์ ํ๊ท ์ฌ์ฉ
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ :
- mbarrier_arrive(): ๊ฐ ์ค๋ ๋๊ฐ ์ฐ๊ธฐ ๋จ๊ณ ์๋ฃ๋ฅผ ์๋ฆผ
- mbarrier_test_wait(): ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฐ๊ธฐ๋ฅผ ์๋ฃํ ๋๊น์ง ๋๊ธฐ
- ๋ฒํผ ๊ต์ฒด ์์ ์ฑ: ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์์ง ์ฐ๊ณ ์๋ ๋์ ๋ฒํผ์์ ์ฝ๋ ๊ฒ์ ๋ฐฉ์ง
- ๋ฐฐ๋ฆฌ์ด ์ฌ์ด๊ธฐํ: ๋ฐ๋ณต ๊ฐ์ ๋ฐฐ๋ฆฌ์ด ์ํ๋ฅผ ์ฌ์ค์
์ถ๋ ฅ ๋จ๊ณ:
- ์ต์ข ๋ฒํผ ์ ํ: ๋ฐ๋ณต ํ์์ ํ์ง์ ๋ฐ๋ผ ํ์ฑ ๋ฒํผ ์ ํ
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ: ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅ ๋ฐฐ์ด์ ๋ณต์ฌ
- ์๋ฃ ๋ฐฐ๋ฆฌ์ด: ๋ธ๋ก ์ข ๋ฃ ์ ๋ชจ๋ ์ฐ๊ธฐ ์๋ฃ ๋ณด์ฅ
์์ฑํ ์ฝ๋
# Double-buffered stencil configuration
comptime STENCIL_ITERATIONS = 3
comptime BUFFER_COUNT = 2
def double_buffered_stencil_computation(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
"""Double-buffered stencil computation with memory barrier coordination.
Iteratively applies 3-point stencil using alternating buffers.
Uses mbarrier APIs for precise buffer swap coordination.
"""
# Double-buffering: Two shared memory buffers
var buffer_A = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var buffer_B = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
# Memory barriers for coordinating buffer swaps
var init_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var iter_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var final_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Initialize barriers (only thread 0)
if local_i == 0:
mbarrier_init(init_barrier.ptr, TPB)
mbarrier_init(iter_barrier.ptr, TPB)
mbarrier_init(final_barrier.ptr, TPB)
# Initialize buffer_A with input data
# FILL ME IN (roughly 4 lines)
# Wait for buffer_A initialization
_ = mbarrier_arrive(init_barrier.ptr)
_ = mbarrier_test_wait(init_barrier.ptr, TPB)
# Iterative stencil processing with double-buffering
comptime for iteration in range(STENCIL_ITERATIONS):
comptime if iteration % 2 == 0:
# Even iteration: Read from A, Write to B
# FILL ME IN (roughly 12 lines)
...
else:
# Odd iteration: Read from B, Write to A
# FILL ME IN (roughly 12 lines)
...
# Memory barrier: wait for all writes before buffer swap
_ = mbarrier_arrive(iter_barrier.ptr)
_ = mbarrier_test_wait(iter_barrier.ptr, TPB)
# Reinitialize barrier for next iteration
if local_i == 0:
mbarrier_init(iter_barrier.ptr, TPB)
# Write final results from active buffer
if local_i < TPB and global_i < size:
comptime if STENCIL_ITERATIONS % 2 == 0:
# Even iterations end in buffer_A
output[global_i] = buffer_A[local_i]
else:
# Odd iterations end in buffer_B
output[global_i] = buffer_B[local_i]
# Final barrier
_ = mbarrier_arrive(final_barrier.ptr)
_ = mbarrier_test_wait(final_barrier.ptr, TPB)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p29/p29.mojo
ํ
๋ฒํผ ์ด๊ธฐํ
buffer_A๋ฅผ ์ ๋ ฅ ๋ฐ์ดํฐ๋ก ์ด๊ธฐํํ๊ณ ,buffer_B๋ ๋น ์ํ๋ก ์์ ๊ฐ๋ฅ- ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์์์ ๋ํด ์ ๋ก ํจ๋ฉ์ ์ฌ์ฉํ ์ ์ ํ ๊ฒฝ๊ณ ๊ฒ์ฌ
- ์ค๋ ๋ 0๋ง mbarrier ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํด์ผ ํจ
- ์๋ก ๋ค๋ฅธ ๋๊ธฐํ ์ง์ ์ ๋ณ๋์ ๋ฐฐ๋ฆฌ์ด ์ค์
๋ฐ๋ณต ์ ์ด
- ์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐ๋ฅผ ์ํด
@parameter for iteration in range(STENCIL_ITERATIONS)์ฌ์ฉ iteration % 2๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ธฐ/์ฐ๊ธฐ ํ ๋น์ ๊ต๋ํ๋ฉด์ ๋ฒํผ ์ญํ ๊ฒฐ์ - ์ด์ ๊ฒ์ฌ๋ฅผ ํตํด ์ ํจํ ๋ฒ์ ๋ด์์๋ง ์คํ ์ค ์ฐ์ฐ ์ ์ฉ
์คํ ์ค ์ฐ์ฐ
- 3์ ํ๊ท ๊ตฌํ:
(left + center + right) / 3 - ์ ํจํ ์ด์๋ง ํ๊ท ์ ํฌํจํ์ฌ ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ
- ์ฃ์ง ์ผ์ด์ค๋ฅผ ๋งค๋๋ฝ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ ์์ ์นด์ดํ ์ฌ์ฉ
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์
- ๊ฐ ์ค๋ ๋๊ฐ ์ฐ๊ธฐ ์ฐ์ฐ์ ์๋ฃํ ํ
mbarrier_arrive()ํธ์ถ - ๋ฒํผ ๊ต์ฒด ์ ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ฃํ๋๋ก
mbarrier_test_wait()์ฌ์ฉ - ์ฌ์ฌ์ฉ์ ์ํด ๋ฐ๋ณต ๊ฐ์ ๋ฐฐ๋ฆฌ์ด ์ฌ์ด๊ธฐํ:
mbarrier_init() - ๊ฒฝ์ ์ํ๋ฅผ ํผํ๊ธฐ ์ํด ์ค๋ ๋ 0๋ง ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ฌ์ด๊ธฐํ
์ถ๋ ฅ ์ ํ
STENCIL_ITERATIONS % 2๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ต์ข ํ์ฑ ๋ฒํผ ์ ํ- ์ง์ ๋ฐ๋ณต ํ์๋ buffer_A์ ๋ฐ์ดํฐ๊ฐ ๋จ์
- ํ์ ๋ฐ๋ณต ํ์๋ buffer_B์ ๋ฐ์ดํฐ๊ฐ ๋จ์
- ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ํตํด ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ธ๋ก๋ฒ ์ถ๋ ฅ์ ๊ธฐ๋ก
์ฝ๋ ์คํ
์๋ฃจ์ ์ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ ์คํํฉ๋๋ค:
pixi run p29 --double-buffer
pixi run -e amd p29 --double-buffer
uv run poe p29 --double-buffer
ํผ์ฆ์ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃํ๋ฉด ๋ค์๊ณผ ์ ์ฌํ ์ถ๋ ฅ์ด ํ์๋ฉ๋๋ค:
Puzzle 29: GPU Synchronization Primitives
==================================================
TPB: 256
SIZE: 1024
STENCIL_ITERATIONS: 3
BUFFER_COUNT: 2
Testing Puzzle 29B: Double-Buffered Stencil Computation
============================================================
Double-buffered stencil completed
Input sample: 1.0 1.0 1.0
GPU output sample: 1.0 1.0 1.0
โ
Double-buffered stencil test PASSED!
์๋ฃจ์
def double_buffered_stencil_computation(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, MutAnyOrigin],
size: Int,
):
"""Double-buffered stencil computation with memory barrier coordination.
Iteratively applies 3-point stencil using alternating buffers.
Uses mbarrier APIs for precise buffer swap coordination.
"""
# Double-buffering: Two shared memory buffers
var buffer_A = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var buffer_B = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
# Memory barriers for coordinating buffer swaps
var init_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var iter_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var final_barrier = stack_allocation[
dtype=DType.uint64, address_space=AddressSpace.SHARED
](row_major[1]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Initialize barriers (only thread 0)
if local_i == 0:
mbarrier_init(init_barrier.ptr, TPB)
mbarrier_init(iter_barrier.ptr, TPB)
mbarrier_init(final_barrier.ptr, TPB)
# Initialize buffer_A with input data
if local_i < TPB and global_i < size:
buffer_A[local_i] = input[global_i]
else:
buffer_A[local_i] = 0.0
# Wait for buffer_A initialization
_ = mbarrier_arrive(init_barrier.ptr)
_ = mbarrier_test_wait(init_barrier.ptr, TPB)
# Iterative stencil processing with double-buffering
comptime for iteration in range(STENCIL_ITERATIONS):
comptime if iteration % 2 == 0:
# Even iteration: Read from A, Write to B
if local_i < TPB:
var stencil_sum: Scalar[dtype] = 0.0
var stencil_count: Int = 0
# 3-point stencil: [i-1, i, i+1]
for offset in range(-1, 2):
sample_idx = local_i + offset
if sample_idx >= 0 and sample_idx < TPB:
stencil_sum += rebind[Scalar[dtype]](
buffer_A[sample_idx]
)
stencil_count += 1
if stencil_count > 0:
buffer_B[local_i] = stencil_sum / Scalar[dtype](
stencil_count
)
else:
buffer_B[local_i] = buffer_A[local_i]
else:
# Odd iteration: Read from B, Write to A
if local_i < TPB:
var stencil_sum: Scalar[dtype] = 0.0
var stencil_count: Int = 0
# 3-point stencil: [i-1, i, i+1]
for offset in range(-1, 2):
sample_idx = local_i + offset
if sample_idx >= 0 and sample_idx < TPB:
stencil_sum += rebind[Scalar[dtype]](
buffer_B[sample_idx]
)
stencil_count += 1
if stencil_count > 0:
buffer_A[local_i] = stencil_sum / Scalar[dtype](
stencil_count
)
else:
buffer_A[local_i] = buffer_B[local_i]
# Memory barrier: wait for all writes before buffer swap
_ = mbarrier_arrive(iter_barrier.ptr)
_ = mbarrier_test_wait(iter_barrier.ptr, TPB)
# Reinitialize barrier for next iteration
if local_i == 0:
mbarrier_init(iter_barrier.ptr, TPB)
# Write final results from active buffer
if local_i < TPB and global_i < size:
comptime if STENCIL_ITERATIONS % 2 == 0:
# Even iterations end in buffer_A
output[global_i] = buffer_A[local_i]
else:
# Odd iterations end in buffer_B
output[global_i] = buffer_B[local_i]
# Final barrier
_ = mbarrier_arrive(final_barrier.ptr)
_ = mbarrier_test_wait(final_barrier.ptr, TPB)
ํต์ฌ ํต์ฐฐ์ ์ด๊ฒ์ด ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ ์ ์ฌ์ฉํ๋ ๋๋ธ ๋ฒํผ๋ง ์ํคํ ์ฒ ๋ฌธ์ ์์ ์ธ์ํ๋ ๊ฒ์ ๋๋ค:
- ๊ต๋ํ๋ ๋ฒํผ ์ญํ ์ค๊ณ: ๋งค ๋ฐ๋ณต๋ง๋ค ์ฝ๊ธฐ/์ฐ๊ธฐ ์ฑ ์์ ๊ตํ
- ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ตฌํ: ์ ๋ฐํ ๋๊ธฐํ ์ ์ด๋ฅผ ์ํด mbarrier API ์ฌ์ฉ
- ๋ฐ๋ณต ์ฒ๋ฆฌ ์กฐ์จ: ๋ฒํผ ๊ต์ฒด ์ ๋ฐ๋ณต ๊ฒฐ๊ณผ๊ฐ ์์ ํ ์๋ฃ๋๋๋ก ๋ณด์ฅ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ: ๋ชจ๋ ์ฒ๋ฆฌ๋ฅผ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ์ํ
์์ธ ์ค๋ช ์ด ํฌํจ๋ ์ ์ฒด ์๋ฃจ์
๋๋ธ ๋ฒํผ๋ง ์คํ ์ค ์๋ฃจ์ ์ ์ ๊ตํ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์กฐ์ ๊ณผ ๋ฐ๋ณต ์ฒ๋ฆฌ ํจํด์ ๋ณด์ฌ์ค๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํ์ด๋ฐ์ ๋ํ ์ ๋ฐํ ์ ์ด๊ฐ ํ์ํ ์์ ํ ๋ฐ๋ณต์ ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
๋๋ธ ๋ฒํผ๋ง ์ํคํ ์ฒ ์ค๊ณ
์ด ํผ์ฆ์ ๊ทผ๋ณธ์ ์ธ ๋ํ๊ตฌ๋ ๋จ์ํ ์ค๋ ๋ ๋๊ธฐํ๊ฐ ์๋ ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์ ์ด์ ๋๋ค:
์ ํต์ ์ธ ์ ๊ทผ ๋ฐฉ์: ๋จ์ํ ์ค๋ ๋ ์กฐ์ ์ ์ํด ๊ธฐ๋ณธ
barrier() ์ฌ์ฉ
- ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ์ ๋์ผํ ์ฐ์ฐ์ ์คํ
- ๋จ์ผ ๋ฐฐ๋ฆฌ์ด ํธ์ถ๋ก ์ค๋ ๋ ์๋ฃ๋ฅผ ๋๊ธฐํ
- ํน์ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ ํ์ด๋ฐ์ ๋ํ ์ ์ด ์์
์ด ํผ์ฆ์ ํ์ : ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ก ์กฐ์ ๋๋ ์๋ก ๋ค๋ฅธ ๋ฒํผ ์ญํ
- buffer_A์ buffer_B๊ฐ ์ฝ๊ธฐ ์์ค์ ์ฐ๊ธฐ ๋์ ์ฌ์ด๋ฅผ ๊ต๋
- mbarrier API๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ ์๋ฃ์ ๋ํ ์ ๋ฐํ ์ ์ด๋ฅผ ์ ๊ณต
- ๋ช ์์ ์กฐ์ ์ผ๋ก ๋ฒํผ ์ ํ ์ค ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์ง
๋ฐ๋ณต ์ฒ๋ฆฌ ์กฐ์จ
๋จ์ผ ํจ์ค ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ฌ๋ฆฌ, ์ด ํผ์ฆ์ ์ ์คํ ๋ฒํผ ๊ด๋ฆฌ๋ฅผ ํตํ ๋ฐ๋ณต์ ๊ฐ์ ์ ์ค์ ํฉ๋๋ค:
- ๋ฐ๋ณต 0: buffer_A์์ ์ฝ๊ธฐ (์ ๋ ฅ์ผ๋ก ์ด๊ธฐํ๋จ), buffer_B์ ์ฐ๊ธฐ
- ๋ฐ๋ณต 1: buffer_B์์ ์ฝ๊ธฐ (์ด์ ๊ฒฐ๊ณผ), buffer_A์ ์ฐ๊ธฐ
- ๋ฐ๋ณต 2: buffer_A์์ ์ฝ๊ธฐ (์ด์ ๊ฒฐ๊ณผ), buffer_B์ ์ฐ๊ธฐ
- ๊ต๋ ๊ณ์: ๊ฐ ๋ฐ๋ณต์ด ์ด์ ๋ฐ๋ณต์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด API ์ฌ์ฉ๋ฒ
mbarrier ์กฐ์ ํจํด์ ์ดํด:
- mbarrier_init(): ํน์ ์ค๋ ๋ ์(TPB)๋ฅผ ์ง์ ํ์ฌ ๋ฐฐ๋ฆฌ์ด ์ด๊ธฐํ
- mbarrier_arrive(): ๊ฐ๋ณ ์ค๋ ๋์ ์ฐ๊ธฐ ๋จ๊ณ ์๋ฃ๋ฅผ ์๋ฆผ
- mbarrier_test_wait(): ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ฃ๋ฅผ ์๋ฆด ๋๊น์ง ๋๊ธฐ
- ์ฌ์ด๊ธฐํ: ์ฌ์ฌ์ฉ์ ์ํด ๋ฐ๋ณต ๊ฐ์ ๋ฐฐ๋ฆฌ์ด ์ํ๋ฅผ ์ฌ์ค์
ํต์ฌ ํ์ด๋ฐ ์์:
- ๋ชจ๋ ์ค๋ ๋ ์ฐ๊ธฐ: ๊ฐ ์ค๋ ๋๊ฐ ํ ๋น๋ ๋ฒํผ ์์๋ฅผ ์ ๋ฐ์ดํธ
- ์๋ฃ ์๋ฆผ: ๊ฐ ์ค๋ ๋๊ฐ
mbarrier_arrive()ํธ์ถ - ์ ์ฒด ๋๊ธฐ: ๋ชจ๋ ์ค๋ ๋๊ฐ
mbarrier_test_wait()ํธ์ถ - ์งํ ์์ : ์ด์ ๋ค์ ๋ฐ๋ณต์ ์ํด ๋ฒํผ ์ญํ ์ ์์ ํ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ
์คํ ์ค ์ฐ์ฐ ๋ฉ์ปค๋์ฆ
์ ์์ ๊ฒฝ๊ณ ์ฒ๋ฆฌ๋ฅผ ํฌํจํ 3์ ์คํ ์ค ์ฐ์ฐ:
๋ด๋ถ ์์ (์ธ๋ฑ์ค 1๋ถํฐ 254):
# ์ผ์ชฝ, ์ค์ฌ, ์ค๋ฅธ์ชฝ ์ด์๊ณผ์ ํ๊ท
stencil_sum = buffer[i-1] + buffer[i] + buffer[i+1]
result[i] = stencil_sum / 3.0
๊ฒฝ๊ณ ์์ (์ธ๋ฑ์ค 0๊ณผ 255):
# ์ ํจํ ์ด์๋ง ํ๊ท ์ ํฌํจ
stencil_count = 0
for neighbor in valid_neighbors:
stencil_sum += buffer[neighbor]
stencil_count += 1
result[i] = stencil_sum / stencil_count
๋ฒํผ ์ญํ ๊ต๋
ํํ ๋ฒํผ ํจํด์ด ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํฉ๋๋ค:
์ง์ ๋ฐ๋ณต (0, 2, 4โฆ):
- ์ฝ๊ธฐ ์์ค: buffer_A์ ํ์ฌ ๋ฐ์ดํฐ ํฌํจ
- ์ฐ๊ธฐ ๋์: buffer_B๊ฐ ์ ๋ฐ์ดํธ๋ ๊ฒฐ๊ณผ๋ฅผ ์์
- ๋ฉ๋ชจ๋ฆฌ ํ๋ฆ: buffer_A โ ์คํ ์ค ์ฐ์ฐ โ buffer_B
ํ์ ๋ฐ๋ณต (1, 3, 5โฆ):
- ์ฝ๊ธฐ ์์ค: buffer_B์ ํ์ฌ ๋ฐ์ดํฐ ํฌํจ
- ์ฐ๊ธฐ ๋์: buffer_A๊ฐ ์ ๋ฐ์ดํธ๋ ๊ฒฐ๊ณผ๋ฅผ ์์
- ๋ฉ๋ชจ๋ฆฌ ํ๋ฆ: buffer_B โ ์คํ ์ค ์ฐ์ฐ โ buffer_A
๊ฒฝ์ ์ํ ๋ฐฉ์ง
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๊ฐ ์ฌ๋ฌ ์ ํ์ ๊ฒฝ์ ์ํ๋ฅผ ์ ๊ฑฐํฉ๋๋ค:
๋ฐฐ๋ฆฌ์ด ์์ด (์๋ชป๋ ๊ฒฝ์ฐ):
# ์ค๋ ๋ A๊ฐ buffer_B[10]์ ์ฐ๊ธฐ
buffer_B[10] = stencil_result_A
# ์ค๋ ๋ B๊ฐ ์คํ
์ค ์ฐ์ฐ์ ์ํด buffer_B[10]์ ์ฆ์ ์ฝ๊ธฐ
# ๊ฒฝ์ ์ํ: ์ค๋ ๋ B๊ฐ ์ค๋ ๋ A์ ์ฐ๊ธฐ๊ฐ ์๋ฃ๋๊ธฐ ์ ์ ์ด์ ๊ฐ์ ์ฝ์ ์ ์์
stencil_input = buffer_B[10] // ๋ฏธ์ ์ ๋์!
๋ฐฐ๋ฆฌ์ด ์ฌ์ฉ (์ฌ๋ฐ๋ฅธ ๊ฒฝ์ฐ):
# ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ฐ๊ธฐ
buffer_B[local_i] = stencil_result
# ์ฐ๊ธฐ ์๋ฃ ์๋ฆผ
mbarrier_arrive(barrier)
# ๋ชจ๋ ์ค๋ ๋์ ์ฐ๊ธฐ ์๋ฃ๊น์ง ๋๊ธฐ
mbarrier_test_wait(barrier, TPB)
# ์ด์ ์ฝ๊ธฐ ์์ - ๋ชจ๋ ์ฐ๊ธฐ ์๋ฃ ๋ณด์ฅ
stencil_input = buffer_B[neighbor_index] // ํญ์ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ์ฝ์
์ถ๋ ฅ ๋ฒํผ ์ ํ
์ต์ข ๊ฒฐ๊ณผ ์์น๋ ๋ฐ๋ณต ํ์์ ํ์ง์ ๋ฐ๋ผ ๊ฒฐ์ ๋ฉ๋๋ค:
์ํ์ ๊ฒฐ์ :
- STENCIL_ITERATIONS = 3 (ํ์)
- ์ต์ข ํ์ฑ ๋ฒํผ: ๋ฐ๋ณต 2๊ฐ buffer_B์ ์ฐ๊ธฐ
- ์ถ๋ ฅ ์์ค: buffer_B์์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ณต์ฌ
๊ตฌํ ํจํด:
@parameter
if STENCIL_ITERATIONS % 2 == 0:
# ์ง์ ์ด ๋ฐ๋ณต ํ์๋ buffer_A์์ ์ข
๋ฃ
output[global_i] = buffer_A[local_i]
else:
# ํ์ ์ด ๋ฐ๋ณต ํ์๋ buffer_B์์ ์ข
๋ฃ
output[global_i] = buffer_B[local_i]
์ฑ๋ฅ ํน์ฑ
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ: ์ ๋ ฅ ๋ก๋ฉ๊ณผ ์ต์ข ์ถ๋ ฅ์๋ง ์ ๊ทผ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ชจ๋ ๋ฐ๋ณต ์ฒ๋ฆฌ์ ๋น ๋ฅธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
- ๋ ์ง์คํฐ ์ฌ์ฉ๋: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ค์ฌ์ผ๋ก ์ต์ํ
๋๊ธฐํ ์ค๋ฒํค๋:
- mbarrier ๋น์ฉ: ๊ธฐ๋ณธ barrier()๋ณด๋ค ๋์ง๋ง ํ์์ ์ธ ์ ์ด๋ฅผ ์ ๊ณต
- ๋ฐ๋ณต ํ์ฅ์ฑ: ์ค๋ฒํค๋๊ฐ ๋ฐ๋ณต ํ์์ ๋น๋กํ์ฌ ์ ํ์ ์ผ๋ก ์ฆ๊ฐ
- ์ค๋ ๋ ํจ์จ์ฑ: ๋ชจ๋ ์ค๋ ๋๊ฐ ์ฒ๋ฆฌ ์ ๋ฐ์ ๊ฑธ์ณ ํ์ฑ ์ํ ์ ์ง
์ค์ ์์ฉ ๋ถ์ผ
์ด ๋๋ธ ๋ฒํผ๋ง ํจํด์ ๋ค์ ๋ถ์ผ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค:
๋ฐ๋ณต๋ฒ:
- ์ ํ ์์คํ ์ ์ํ Gauss-Seidel ๋ฐ Jacobi ๋ฐฉ๋ฒ
- ์์น ์ ํ๋๋ฅผ ์ํ ๋ฐ๋ณต์ ๊ฐ์
- ๋ ๋ฒจ๋ณ ์ฒ๋ฆฌ๋ฅผ ์ํํ๋ ๋ค์ค ๊ทธ๋ฆฌ๋ ๋ฐฉ๋ฒ
์ด๋ฏธ์ง ์ฒ๋ฆฌ:
- ๋ค์ค ํจ์ค ํํฐ (์์ธก, ์ ๋, ์ฃ์ง ๋ณด์กด)
- ๋ฐ๋ณต์ ๋๋ ธ์ด์ง ์๊ณ ๋ฆฌ์ฆ
- ์ด ํ์ฐ๊ณผ ์ด๋ฐฉ์ฑ ์ค๋ฌด๋ฉ
์๋ฎฌ๋ ์ด์ ์๊ณ ๋ฆฌ์ฆ:
- ์ํ ์งํ๋ฅผ ๊ฐ์ง ์ ๋ฃฐ๋ฌ ์คํ ๋งํ
- ์์น ์ ๋ฐ์ดํธ๋ฅผ ์๋ฐํ๋ ์ ์ ์์คํ
- ๋ฐ๋ณต์ ์๋ ฅ ์๋น์ ์ฌ์ฉํ ์ ์ฒด ์ญํ
ํต์ฌ ๊ธฐ์ ์ ํต์ฐฐ
๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์ฒ ํ:
- ๋ช ์์ ์ ์ด: ์๋ ๋๊ธฐํ ๋๋น ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ๋ํ ์ ๋ฐํ ํ์ด๋ฐ ์ ์ด
- ๊ฒฝ์ ์ํ ๋ฐฉ์ง: ๊ต๋ํ๋ ์ฝ๊ธฐ/์ฐ๊ธฐ ํจํด์ ๊ฐ์ง ๋ชจ๋ ์๊ณ ๋ฆฌ์ฆ์ ํ์
- ์ฑ๋ฅ ์ ์ถฉ: ๋ณด์ฅ๋ ์ ํ์ฑ์ ์ํ ๋ ๋์ ๋๊ธฐํ ๋น์ฉ
๋๋ธ ๋ฒํผ๋ง์ ์ด์ :
- ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ: ์ฐ๊ธฐ ์ค ์ฝ๊ธฐ hazard ์ ๊ฑฐ
- ์๊ณ ๋ฆฌ์ฆ ๋ช ํ์ฑ: ํ์ฌ์ ๋ค์ ๋ฐ๋ณต ์ํ ๊ฐ์ ๊น๋ํ ๋ถ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ค๊ฐ ์ ์ฅ์ ๋ถํ์
๋ฐ๋ณต ๊ด๋ฆฌ:
- ์ปดํ์ผ ํ์ ๋ฃจํ ์ ๊ฐ:
@parameter for๊ฐ ์ต์ ํ ๊ธฐํ๋ฅผ ์ ๊ณต - ์ํ ์ถ์ : ๋ฒํผ ์ญํ ๊ต๋๊ฐ ๊ฒฐ์ ์ ์ด์ด์ผ ํจ
- ๊ฒฝ๊ณ ์ฒ๋ฆฌ: ์ ์์ ์คํ ์ค ์ฐ์ฐ์ด ์ฃ์ง ์ผ์ด์ค๋ฅผ ๋งค๋๋ฝ๊ฒ ์ฒ๋ฆฌ
์ด ์๋ฃจ์ ์ ์ ๋ฐํ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ ์ด๊ฐ ํ์ํ ๋ฐ๋ณต GPU ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฉฐ, ๋จ์ํ ๋ณ๋ ฌ ๋ฃจํ๋ฅผ ๋์ด ์ค์ ์์น ์ํํธ์จ์ด์์ ์ฌ์ฉ๋๋ ์ ๊ตํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํจํด์ผ๋ก ๋์๊ฐ๋๋ค.
Puzzle 30: GPU ํ๋กํ์ผ๋ง
์ฌ๋ฐ๋ฅธ ์ฝ๋, ๊ทธ ๋๋จธ๋ก
์ฐธ๊ณ : ์ด ํํธ๋ ํธํ๋๋ NVIDIA GPU ์ ์ฉ์ ๋๋ค
์ด ์ฑํฐ์์๋ ๋์ํ๋ GPU ์ฝ๋๋ฅผ ๊ณ ์ฑ๋ฅ ์ฝ๋๋ก ํ๋ฐ๊ฟ์ํค๋ ์ฒด๊ณ์ ์ฑ๋ฅ ๋ถ์์ ์๊ฐํฉ๋๋ค. ์ ํ์ฑ๊ณผ GPU ๊ธฐ๋ฅ์ ์ง์คํ๋ ์ด์ ํผ์ฆ๋ค๊ณผ ๋ฌ๋ฆฌ, ์ฌ๊ธฐ์๋ ์ค๋ฌด GPU ์ํํธ์จ์ด ๊ฐ๋ฐ์์ ์ฌ์ฉ๋๋ ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ ํ๊ตฌํฉ๋๋ค.
ํ์ต ๋ด์ฉ:
- ์ ๋ฌธ ํ๋กํ์ผ๋ง ๋๊ตฌ: ์ข ํฉ์ ์ธ ์ฑ๋ฅ ๋ถ์์ ์ํ NSight Systems์ NSight Compute
- ์ฑ๋ฅ ํ์ ์์ : ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ๋ณ๋ชฉ๊ณผ ์ต์ ํ ๊ธฐํ ํ์
- ๋ฉ๋ชจ๋ฆฌ ์์คํ ํต์ฐฐ: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ฑ๋ฅ์ ๋ฏธ์น๋ ๊ทน์ ์ธ ์ํฅ ์ดํด
- ๋ฐ์ง๊ด์ ์ธ ๋ฐ๊ฒฌ: โ์ข์ ๋ณด์ด๋โ ์งํ๊ฐ ์คํ๋ ค ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ๊ฐ๋ฆฌํค๋ ๊ฒฝ์ฐ
- ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ต์ ํ: ๊ฐ์ ์ด ์๋ ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ์ ๊ธฐ๋ฐํ ์ต์ ํ ํ๋จ
์ ์ค์ํ๊ฐ: ๋๋ถ๋ถ์ GPU ํํ ๋ฆฌ์ผ์ ๊ธฐ๋ณธ์ ์ธ ์ฑ๋ฅ ๊ฐ๋ ๋ง ๊ฐ๋ฅด์น์ง๋ง, ์ค์ GPU ๊ฐ๋ฐ์์๋ ์ค์ง์ ์ธ ๋ณ๋ชฉ์ ์ฐพ์๋ด๊ณ , ๋ฉ๋ชจ๋ฆฌ ์์คํ ๋์์ ์ดํดํ๋ฉฐ, ๊ทผ๊ฑฐ ์๋ ์ต์ ํ ๊ฒฐ์ ์ ๋ด๋ฆฌ๊ธฐ ์ํ ์ฒด๊ณ์ ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ด ํ์ํฉ๋๋ค. ์ด๋ฐ ์ญ๋์ด ํ์ ์ ์์ ์ ์ค๋ฌด GPU ์ปดํจํ ์ฌ์ด์ ๊ฒฉ์ฐจ๋ฅผ ๋ฉ์์ค๋๋ค.
๊ฐ์
GPU ์ฑ๋ฅ ํ๋กํ์ผ๋ง์ ์ฒด๊ณ์ ๋ถ์์ ํตํด ์ฌ๋ฐ๋ฅธ ์ฝ๋๋ฅผ ๊ณ ์ฑ๋ฅ ์ฝ๋๋ก ๋ณํํฉ๋๋ค. ์ด ์ฑํฐ์์๋ ์ค๋ฌด GPU ๊ฐ๋ฐ์์ ์ฌ์ฉ๋๋ ์ ๋ฌธ ํ๋กํ์ผ๋ง ๋๊ตฌ์ ํ์ ๋ฐฉ๋ฒ๋ก ์ ์ดํด๋ด ๋๋ค.
ํต์ฌ ํ์ต ๋ชฉํ:
- ํ๋กํ์ผ๋ง ๋๊ตฌ ์ ํ๋ฒ ํ์ต - NSight Systems์ NSight Compute๋ฅผ ์ธ์ ์ฌ์ฉํ๋์ง ์ดํด
- ์ฑ๋ฅ ํ์ ๋ฅ๋ ฅ ๊ฐ๋ฐ - ์ค์ ํ๋กํ์ผ๋ฌ ์ถ๋ ฅ์ ํ์ฉํ์ฌ ๋ณ๋ชฉ ์๋ณ
- ๋ฐ์ง๊ด์ ์ธ ํต์ฐฐ ๋ฐ๊ฒฌ - GPU ๋ฉ๋ชจ๋ฆฌ ์์คํ ๊ณผ ์บ์ฑ ๋์์ ๋ํ ์๋ก์ด ์๊ฐ
- ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ต์ ํ ํ์ต - ๊ฐ์ ์ด ์๋ ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ์ ๊ธฐ๋ฐํ ์ต์ ํ
ํต์ฌ ๊ฐ๋
์ ๋ฌธ ํ๋กํ์ผ๋ง ๋๊ตฌ:
- NSight Systems (
nsys): CPU-GPU ์กฐ์จ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ์ํ ์์คํ ์ ์ฒด ํ์๋ผ์ธ ๋ถ์ - NSight Compute (
ncu): ๋ฉ๋ชจ๋ฆฌ ํจ์จ๊ณผ ์ฐ์ฐ ํ์ฉ๋๋ฅผ ์ํ ์์ธ ์ปค๋ ๋ถ์ - ์ฒด๊ณ์ ๋ฐฉ๋ฒ๋ก : ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ๋ณ๋ชฉ ์๋ณ๊ณผ ์ต์ ํ ๊ฒ์ฆ
๋ฐ๊ฒฌํ๊ฒ ๋ ํต์ฌ ํต์ฐฐ:
- ๋ฐ์ง๊ด์ ๋์: ๋์ ์บ์ ํํธ์จ์ด ์ค์ ๋ก๋ ๋ฎ์ ์ฑ๋ฅ์ ๋ํ๋ด๋ ๊ฒฝ์ฐ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด: ๋ณํฉ์ด ๋์ญํญ ํ์ฉ์ ๋ฏธ์น๋ ๊ทน์ ์ธ ์ํฅ
- ๋๊ตฌ ๊ธฐ๋ฐ ์ต์ ํ: ์ฑ๋ฅ ๊ฐ์ ์ด ์๋ ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ์์ฌ๊ฒฐ์
๊ตฌ์ฑ
์๊ตฌ ์ฌํญ:
- NVIDIA GPU: ํ๋กํ์ผ๋ง์ด ํ์ฑํ๋ CUDA ํธํ ํ๋์จ์ด
- CUDA Toolkit: NSight Systems ๋ฐ NSight Compute ๋๊ตฌ
- ๋น๋ ์ค์ : ๋๋ฒ๊ทธ ์ ๋ณด๊ฐ ํฌํจ๋ ์ต์ ํ ์ฝ๋ (
--debug-level=full)
๋ฐฉ๋ฒ๋ก :
- NSight Systems๋ฅผ ํ์ฉํ ์์คํ ์ ์ฒด ๋ถ์์ผ๋ก ์ฃผ์ ๋ณ๋ชฉ ์๋ณ
- NSight Compute๋ฅผ ํ์ฉํ ์ปค๋ ์ฌ์ธต ๋ถ์์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์์คํ ๋ถ์
- ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ๊ฒฐ๋ก ์ผ๋ก ์ต์ ํ ๋ฐฉํฅ ๋์ถ
ํผ์ฆ ๊ตฌ์ฑ
์ด ์ฑํฐ๋ ์๋ก ์ฐ๊ฒฐ๋์ด ์ ์ง์ ์ผ๋ก ๋ฐ์ ํ๋ ๋ ๊ฐ์ ๊ตฌ์ฑ ์์๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค:
NVIDIA ํ๋กํ์ผ๋ง ๊ธฐ์ด
์ค์ ํ๋กํ์ผ๋ฌ ์ถ๋ ฅ์ ์ฌ์ฉํ ์ค์ต ์์ ๋ฅผ ํตํด NVIDIA ํ๋กํ์ผ๋ง ์ํ๊ณ์ ํต์ฌ์ ๋ฐฐ์๋๋ค.
ํ์ต ๋ด์ฉ:
- ์์คํ ์ ์ฒด ํ์๋ผ์ธ ๋ถ์๊ณผ ๋ณ๋ชฉ ์๋ณ์ ์ํ NSight Systems
- ์์ธ ์ปค๋ ๋ถ์๊ณผ ๋ฉ๋ชจ๋ฆฌ ์์คํ ํต์ฐฐ์ ์ํ NSight Compute
- ์ค๋ฌด GPU ๊ฐ๋ฐ์์ ์ฌ์ฉ๋๋ ์ ๋ฌธ ํ๋กํ์ผ๋ง ์ํฌํ๋ก์ฐ์ ๋ชจ๋ฒ ์ฌ๋ก
์บ์ ํํธ์ ์ญ์ค
๋์ผํ ๋ฒกํฐ ๋ง์ ์ปค๋ ์ธ ๊ฐ๊ฐ ๊ทน์ ์ผ๋ก ๋ค๋ฅธ ์ฑ๋ฅ์ ๋ณด์ด๋ ๋ฏธ์คํฐ๋ฆฌ๋ฅผ ํ๋กํ์ผ๋ง์ผ๋ก ํ์ด๋ด ๋๋ค.
๋์ ๊ณผ์ : ์บ์ ํํธ์จ์ด ๊ฐ์ฅ ๋์ ์ปค๋์ด ์ฑ๋ฅ์ ๊ฐ์ฅ ๋ฎ์ ์ด์ ๋ฅผ ๋ฐํ๋ด์ธ์ - CPU ์ค์ฌ์ ์ ํต์ ์ธ ์ฑ๋ฅ ๊ด๋ ์ ๋ค์ง๋ ๋ฐ์ง๊ด์ ํต์ฐฐ์ ๋๋ค.
ํ์ ๋ฅ๋ ฅ: ์ค์ NSight Systems์ NSight Compute ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ ํจ๊ณผ์ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ต์ ํ๋ฅผ ์ดํดํฉ๋๋ค.
์์ํ๊ธฐ
ํ์ต ๊ฒฝ๋ก:
- NVIDIA ํ๋กํ์ผ๋ง ๊ธฐ์ด - NSight Systems์ NSight Compute ํ์ต
- ์บ์ ํํธ์ ์ญ์ค - ์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ ํ๊ธฐ์ ๋ฅ๋ ฅ ์ ์ฉ
์ฌ์ ์ค๋น:
- GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ์ ์ ๊ทผ ํจํด
- GPU ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด (์ค๋ ๋, ๋ธ๋ก, ์ํ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ)
- ์ปค๋งจ๋๋ผ์ธ ํ๋กํ์ผ๋ง ๋๊ตฌ ์ฌ์ฉ ๊ฒฝํ
ํ์ต ์ฑ๊ณผ: ์ค๋ฌด GPU ๊ฐ๋ฐ์์ ์ฌ์ฉ๋๋ ์ฒด๊ณ์ ๋ณ๋ชฉ ์๋ณ๊ณผ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ต์ ํ๋ฅผ ์ํ ์ ๋ฌธ๊ฐ ์์ค์ ํ๋กํ์ผ๋ง ์ญ๋.
์ด ์ฑํฐ๋ ์ฒด๊ณ์ ํ๋กํ์ผ๋ง์ด ์ง๊ด์ด ๋์น๋ ์ง์ค์ ๋๋ฌ๋ธ๋ค๋ ๊ฒ์ ์๋ ค์ค๋๋ค - GPU ์ฑ๋ฅ ์ต์ ํ๋ ๊ฐ์ ์ด ์๋ ๋๊ตฌ ๊ธฐ๋ฐ์ ๋ฐ๊ฒฌ์ด ํ์ํฉ๋๋ค.
์ถ๊ฐ ์๋ฃ:
- NVIDIA CUDA Best Practices Guide - Profiling
- NSight Systems User Guide
- NSight Compute CLI User Guide
๐ NVIDIA ํ๋กํ์ผ๋ง ๊ธฐ์ด
๊ฐ์
์ง๊ธ๊น์ง GPU ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ์ด์ ๊ณ ๊ธ ํจํด์ ๋ฐฐ์ ์ต๋๋ค. Part II์์๋
compute-sanitizer์ cuda-gdb๋ฅผ ์ฌ์ฉํ ์ ํ์ฑ ๋๋ฒ๊น
๊ธฐ๋ฒ์, ๋ค๋ฅธ
ํํธ์์๋ ์ํ ํ๋ก๊ทธ๋๋ฐ, ๋ฉ๋ชจ๋ฆฌ ์์คํ
, ๋ธ๋ก ๋ ๋ฒจ ์ฐ์ฐ ๋ฑ ๋ค์ํ GPU ๊ธฐ๋ฅ์
๋ค๋ค์ต๋๋ค. ์ปค๋์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๊ธด ํฉ๋๋ค - ํ์ง๋ง ๋น ๋ฅด๊ธฐ๋ ํ ๊น์?
์ด ํํ ๋ฆฌ์ผ์ CUDA Best Practices Guide์์ ๊ถ์ฅํ๋ NVIDIA ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ ๋ฐ๋ฆ ๋๋ค.
ํต์ฌ ํต์ฐฐ: ์ฌ๋ฐ๋ฅธ ์ปค๋์ด๋ผ๋ ์ต์ ์ ์ฑ๋ฅ๋ณด๋ค ์์ญ ๋ฐฐ๋ ๋๋ฆด ์ ์์ต๋๋ค. ํ๋กํ์ผ๋ง์ ๋์ํ๋ ์ฝ๋์ ๊ณ ์ฑ๋ฅ ์ฝ๋ ์ฌ์ด์ ๊ฒฉ์ฐจ๋ฅผ ์ขํ๋๋ค.
ํ๋กํ์ผ๋ง ๋๊ตฌ ๋ชจ์
pixi๋ฅผ ํตํด cuda-toolkit์ด ์ค์น๋์ด ์์ผ๋ฏ๋ก, NVIDIA์ ์ ๋ฌธ ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ
๋ฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค:
NSight Systems (nsys) - โ์ ์ฒด ๊ทธ๋ฆผโ ๋๊ตฌ
์ฉ๋: ์์คํ ์ ์ฒด ์ฑ๋ฅ ๋ถ์ (NSight Systems ๋ฌธ์)
- CPU-GPU ์ํธ์์ฉ์ ํ์๋ผ์ธ ๋ทฐ
- ๋ฉ๋ชจ๋ฆฌ ์ ์ก ๋ณ๋ชฉ
- ์ปค๋ ์คํ ์ค๋ฒํค๋
- ๋ฉํฐ GPU ์กฐ์จ
- API ํธ์ถ ์ถ์
์ฌ์ฉ ๊ฐ๋ฅํ ์ธํฐํ์ด์ค: ์ปค๋งจ๋๋ผ์ธ (nsys) ๋ฐ GUI (nsys-ui)
์ฌ์ฉ ์์ :
- ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ํ๋ฆ ํ์
- CPU-GPU ๋๊ธฐํ ๋ฌธ์ ์๋ณ
- ๋ฉ๋ชจ๋ฆฌ ์ ์ก ํจํด ๋ถ์
- ์ปค๋ ์คํ ๋ณ๋ชฉ ๋ฐ๊ฒฌ
# ๋์๋ง ๋ณด๊ธฐ
pixi run nsys --help
# ๊ธฐ๋ณธ ์์คํ
์ ์ฒด ํ๋กํ์ผ๋ง
pixi run nsys profile --trace=cuda,nvtx --output=timeline mojo your_program.mojo
# ๋ํํ ๋ถ์
pixi run nsys stats --force-export=true timeline.nsys-rep
NSight Compute (ncu) - โ์ปค๋ ์ฌ์ธต ๋ถ์โ ๋๊ตฌ
์ฉ๋: ์์ธํ ๋จ์ผ ์ปค๋ ์ฑ๋ฅ ๋ถ์ (NSight Compute ๋ฌธ์)
- ๋ฃจํ๋ผ์ธ ๋ชจ๋ธ ๋ถ์
- ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ํ์ฉ๋
- ์ํ ์คํ ํจ์จ
- ๋ ์ง์คํฐ/๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋
- ์ฐ์ฐ ์ ๋ ํ์ฉ๋
์ฌ์ฉ ๊ฐ๋ฅํ ์ธํฐํ์ด์ค: ์ปค๋งจ๋๋ผ์ธ (ncu) ๋ฐ GUI (ncu-ui)
์ฌ์ฉ ์์ :
- ํน์ ์ปค๋ ์ฑ๋ฅ ์ต์ ํ
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ์
- ์ฐ์ฐ ๋ฐ์ด๋ vs ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋ ๋ถ์
- ์ํ ๋ถ๊ธฐ ๋ฌธ์ ์๋ณ
# ๋์๋ง ๋ณด๊ธฐ
pixi run ncu --help
# ์์ธ ์ปค๋ ํ๋กํ์ผ๋ง
pixi run ncu --set full --output kernel_profile mojo your_program.mojo
# ํน์ ์ปค๋์ ์ง์ค
pixi run ncu --kernel-name regex:your_kernel_name mojo your_program.mojo
๋๊ตฌ ์ ํ ์์ฌ๊ฒฐ์ ํธ๋ฆฌ
์ฑ๋ฅ ๋ฌธ์ ๋ฐ์
|
v
์ด๋ค ์ปค๋์ธ์ง ์๋๊ฐ?
| |
์๋์ค ์
| |
v v
NSight ์ปค๋ ๊ณ ์ ์ ๋ฌธ์ ์ธ๊ฐ?
Systems | |
| ์๋์ค ์
v | |
ํ์๋ผ์ธ | v
๋ถ์ <------+ NSight Compute
|
v
์ปค๋ ์ฌ์ธต ๋ถ์
๋น ๋ฅธ ์์ฌ๊ฒฐ์ ๊ฐ์ด๋:
- ๋ณ๋ชฉ์ด ์ด๋์ธ์ง ๋ชจ๋ฅด๊ฒ ์ผ๋ฉด NSight Systems (
nsys)๋ถํฐ ์์ - ์ต์ ํํ ์ปค๋์ ์ ํํ ์๋ฉด NSight Compute (
ncu) ์ฌ์ฉ - ์ข ํฉ์ ์ธ ๋ถ์์ด ํ์ํ๋ฉด ๋ ๋ค ์ฌ์ฉ (์ผ๋ฐ์ ์ธ ์ํฌํ๋ก์ฐ)
์ค์ต: NSight Systems๋ก ์์คํ ์ ์ฒด ํ๋กํ์ผ๋ง
Puzzle 16์ ํ๋ ฌ ๊ณฑ์ ๊ตฌํ๋ค์ ํ๋กํ์ผ๋งํ์ฌ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ํ์ ํด ๋ด ์๋ค.
GUI ์ฐธ๊ณ : NSight Systems์ Compute GUI (
nsys-ui,ncu-ui)๋ ๋์คํ๋ ์ด์ OpenGL ์ง์์ด ํ์ํฉ๋๋ค. X11 ํฌ์๋ฉ์ด ์๋ ํค๋๋ฆฌ์ค ์๋ฒ๋ ์๊ฒฉ ์์คํ ์์๋ ์ปค๋งจ๋๋ผ์ธ ๋ฒ์ (nsys,ncu)์ ์ฌ์ฉํ์ฌnsys stats์ncu --import --page details๋ก ํ ์คํธ ๊ธฐ๋ฐ ๋ถ์์ ์ํํ์ธ์..nsys-rep์.ncu-repํ์ผ์ ๋ก์ปฌ ๋จธ์ ์ผ๋ก ์ ์กํ์ฌ GUI๋ก ๋ถ์ํ ์๋ ์์ต๋๋ค.
Step 1: ํ๋กํ์ผ๋ง์ ์ํ ์ฝ๋ ์ค๋น
์ค์: ์ ํํ ํ๋กํ์ผ๋ง์ ์ํด ์ต์ ํ๋ฅผ ์ ์งํ๋ฉด์ ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ๋น๋ํฉ๋๋ค:
pixi shell -e nvidia
# ์ต์ ํ๋ฅผ ์ ์งํ๋ฉด์ ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด ํฌํจ ๋น๋ (ํฌ๊ด์ ์ธ ์์ค ๋งคํ์ฉ)
mojo build --debug-level=full solutions/p16/p16.mojo -o solutions/p16/p16_optimized
# ์ต์ ํ ๋น๋ ํ
์คํธ
./solutions/p16/p16_optimized --naive
์ด๊ฒ์ด ์ค์ํ ์ด์ :
- ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด: ํ๋กํ์ผ๋ฌ๋ฅผ ์ํ ์์ ํ ์ฌ๋ณผ ํ ์ด๋ธ, ๋ณ์๋ช , ์์ค ๋ผ์ธ ๋งคํ ์ ๊ณต
- ํฌ๊ด์ ๋ถ์: NSight ๋๊ตฌ๊ฐ ์ฑ๋ฅ ๋ฐ์ดํฐ๋ฅผ ํน์ ์ฝ๋ ์์น์ ์ฐ๊ฒฐ ๊ฐ๋ฅ
- ์ต์ ํ ์ ์ง: ํ๋ก๋์ ๋น๋์ ์ผ์นํ๋ ํ์ค์ ์ธ ์ฑ๋ฅ ์ธก์ ๋ณด์ฅ
Step 2: ์์คํ ์ ์ฒด ํ๋กํ์ผ ์์ง
# ํฌ๊ด์ ์ถ์ ์ผ๋ก ์ต์ ํ ๋น๋ ํ๋กํ์ผ๋ง
nsys profile \
--trace=cuda,nvtx \
--output=matmul_naive \
--force-overwrite=true \
./solutions/p16/p16_optimized --naive
๋ช ๋ น์ด ๋ถ์:
--trace=cuda,nvtx: CUDA API ํธ์ถ ๋ฐ ์ปค์คํ ์ด๋ ธํ ์ด์ ์บก์ฒ--output=matmul_naive: ํ๋กํ์ผ์matmul_naive.nsys-rep๋ก ์ ์ฅ--force-overwrite=true: ๊ธฐ์กด ํ๋กํ์ผ ๋ฎ์ด์ฐ๊ธฐ- ๋ง์ง๋ง ์ธ์: Mojo ํ๋ก๊ทธ๋จ
Step 3: ํ์๋ผ์ธ ๋ถ์
# ํ
์คํธ ๊ธฐ๋ฐ ํต๊ณ ์์ฑ
nsys stats --force-export=true matmul_naive.nsys-rep
# ์ฃผ์ ์งํ ํ์ธ:
# - GPU ํ์ฉ๋ฅ
# - ๋ฉ๋ชจ๋ฆฌ ์ ์ก ์๊ฐ
# - ์ปค๋ ์คํ ์๊ฐ
# - CPU-GPU ๋๊ธฐํ ๊ฐ๊ฒฉ
ํ์ธํ ์ ์๋ ๊ฒฐ๊ณผ (2ร2 ํ๋ ฌ ๊ณฑ์ ์ ์ค์ ์ถ๋ ฅ):
** CUDA API Summary (cuda_api_sum):
Time (%) Total Time (ns) Num Calls Avg (ns) Med (ns) Min (ns) Max (ns) StdDev (ns) Name
-------- --------------- --------- --------- -------- -------- -------- ----------- --------------------
81.9 8617962 3 2872654.0 2460.0 1040 8614462 4972551.6 cuMemAllocAsync
15.1 1587808 4 396952.0 5965.5 3810 1572067 783412.3 cuMemAllocHost_v2
0.6 67152 1 67152.0 67152.0 67152 67152 0.0 cuModuleLoadDataEx
0.4 44961 1 44961.0 44961.0 44961 44961 0.0 cuLaunchKernelEx
** CUDA GPU Kernel Summary (cuda_gpu_kern_sum):
Time (%) Total Time (ns) Instances Avg (ns) Med (ns) Min (ns) Max (ns) StdDev (ns) Name
-------- --------------- --------- -------- -------- -------- -------- ----------- ----------------------------------------
100.0 1920 1 1920.0 1920.0 1920 1920 0.0 p16_naive_matmul_Layout_Int6A6AcB6A6AsA6A6A
** CUDA GPU MemOps Summary (by Time) (cuda_gpu_mem_time_sum):
Time (%) Total Time (ns) Count Avg (ns) Med (ns) Min (ns) Max (ns) StdDev (ns) Operation
-------- --------------- ----- -------- -------- -------- -------- ----------- ----------------------------
49.4 4224 3 1408.0 1440.0 1312 1472 84.7 [CUDA memcpy Device-to-Host]
36.0 3072 4 768.0 528.0 416 1600 561.0 [CUDA memset]
14.6 1248 3 416.0 416.0 416 416 0.0 [CUDA memcpy Host-to-Device]
์ฃผ์ ์ฑ๋ฅ ํต์ฐฐ:
- ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ด ์ง๋ฐฐ์ : ์ ์ฒด ์๊ฐ์ 81.9%๊ฐ
cuMemAllocAsync์ ์๋น - ์ปค๋์ ๋ฒ๊ฐ์ฒ๋ผ ๋น ๋ฆ: ์คํ ์๊ฐ 1,920 ns (0.000001920์ด)์ ๋ถ๊ณผ
- ๋ฉ๋ชจ๋ฆฌ ์ ์ก ๋ด์ญ: 49.4% DeviceโHost, 36.0% memset, 14.6% HostโDevice
- ์์ฃผ ์์ ๋ฐ์ดํฐ: ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ด 0.001 MB ๋ฏธ๋ง (float32 4๊ฐ = 16๋ฐ์ดํธ)
Step 4: ๊ตฌํ ๋น๊ต
๋ค๋ฅธ ๋ฒ์ ๋ค์ ํ๋กํ์ผ๋งํ๊ณ ๋น๊ตํฉ๋๋ค:
# pixi shell ์ํ๋ฅผ ์ ์งํ์ธ์ `pixi run -e nvidia`
# ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฒ์ ํ๋กํ์ผ๋ง
nsys profile --trace=cuda,nvtx --force-overwrite=true --output=matmul_shared ./solutions/p16/p16_optimized --single-block
# Tiled ๋ฒ์ ํ๋กํ์ผ๋ง
nsys profile --trace=cuda,nvtx --force-overwrite=true --output=matmul_tiled ./solutions/p16/p16_optimized --tiled
# ๊ด์ฉ์ Tiled ๋ฒ์ ํ๋กํ์ผ๋ง
nsys profile --trace=cuda,nvtx --force-overwrite=true --output=matmul_idiomatic_tiled ./solutions/p16/p16_optimized --idiomatic-tiled
# ๊ฐ ๊ตฌํ์ ๊ฐ๋ณ์ ์ผ๋ก ๋ถ์ (nsys stats๋ ํ ๋ฒ์ ํ๋์ ํ์ผ๋ง ์ฒ๋ฆฌ)
nsys stats --force-export=true matmul_shared.nsys-rep
nsys stats --force-export=true matmul_tiled.nsys-rep
nsys stats --force-export=true matmul_idiomatic_tiled.nsys-rep
๊ฒฐ๊ณผ ๋น๊ต ๋ฐฉ๋ฒ:
- GPU Kernel Summary ํ์ธ - ๊ตฌํ ๊ฐ ์คํ ์๊ฐ ๋น๊ต
- Memory Operations ํ์ธ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ์ ์ค์ด๋์ง ํ์ธ
- API ์ค๋ฒํค๋ ๋น๊ต - ๋ชจ๋ ๋น์ทํ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ํจํด์ ๊ฐ์ ธ์ผ ํจ
์๋ ๋น๊ต ์ํฌํ๋ก์ฐ:
# ๊ฐ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ์ฌ ๋น๊ต
nsys stats --force-export=true matmul_naive.nsys-rep > naive_stats.txt
nsys stats --force-export=true matmul_shared.nsys-rep > shared_stats.txt
nsys stats --force-export=true matmul_tiled.nsys-rep > tiled_stats.txt
nsys stats --force-export=true matmul_idiomatic_tiled.nsys-rep > idiomatic_tiled_stats.txt
๊ณต์ ํ ๋น๊ต ๊ฒฐ๊ณผ (์ค์ ํ๋กํ์ผ๋ง ์ถ๋ ฅ):
๋น๊ต 1: 2 x 2 ํ๋ ฌ
| ๊ตฌํ | ๋ฉ๋ชจ๋ฆฌ ํ ๋น | ์ปค๋ ์คํ | ์ฑ๋ฅ |
|---|---|---|---|
| Naive | 81.9% cuMemAllocAsync | โ 1,920 ns | ๊ธฐ์ค์ |
Shared (--single-block) | 81.8% cuMemAllocAsync | โ 1,984 ns | +3.3% ๋๋ฆผ |
๋น๊ต 2: 9 x 9 ํ๋ ฌ
| ๊ตฌํ | ๋ฉ๋ชจ๋ฆฌ ํ ๋น | ์ปค๋ ์คํ | ์ฑ๋ฅ |
|---|---|---|---|
| Tiled (์๋) | 81.1% cuMemAllocAsync | โ 2,048 ns | ๊ธฐ์ค์ |
| Idiomatic Tiled | 81.6% cuMemAllocAsync | โ 2,368 ns | +15.6% ๋๋ฆผ |
๊ณต์ ๋น๊ต์์ ์ป์ ํต์ฌ ํต์ฐฐ:
๋ ํ๋ ฌ ํฌ๊ธฐ ๋ชจ๋ GPU ์์ ์๋ ๋๋ฌด ์์!:
- 2ร2 ํ๋ ฌ: 4๊ฐ ์์ - ์์ ํ ์ค๋ฒํค๋๊ฐ ์ง๋ฐฐ
- 9ร9 ํ๋ ฌ: 81๊ฐ ์์ - ์ฌ์ ํ ์ค๋ฒํค๋๊ฐ ์ง๋ฐฐ
- ์ค์ GPU ์ํฌ๋ก๋: ์ฐจ์๋น ์์ฒ~์๋ฐฑ๋ง ๊ฐ ์์
์ด ๊ฒฐ๊ณผ๊ฐ ์ค์ ๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ:
- ๋ชจ๋ ๋ณํ์ด ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ์ง๋ฐฐ๋จ (์๊ฐ์ 81% ์ด์)
- ์ปค๋ ์คํ์ ์๋ฏธ ์์ - ์ค์ ๋น์ฉ์ ๋นํ๋ฉด ๋ฏธ๋ฏธ
- โ์ต์ ํโ๊ฐ ์คํ๋ ค ํด๋ก์ธ ์ ์์: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ 3.3%, async_copy๊ฐ 15.6% ์ค๋ฒํค๋ ์ถ๊ฐ
- ์ง์ง ๊ตํ: ์์ ์ํฌ๋ก๋์์๋ ์๊ณ ๋ฆฌ์ฆ ์ ํ์ด ๋ฌด์๋ฏธ - ์ค๋ฒํค๋๊ฐ ๋ชจ๋ ๊ฒ์ ์๋
์ด๋ฐ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ ์ด์ :
- GPU ์ค์ ๋น์ฉ(๋ฉ๋ชจ๋ฆฌ ํ ๋น, ์ปค๋ ์คํ)์ ๋ฌธ์ ํฌ๊ธฐ์ ๊ด๊ณ์์ด ๊ณ ์
- ์์ ๋ฌธ์ ์์๋ ์ด ๊ณ ์ ๋น์ฉ์ด ์ฐ์ฐ ์๊ฐ์ ๋ฌด์ํ๊ฒ ๋ง๋ฆ
- ํฐ ๋ฌธ์ ๋ฅผ ์ํด ์ค๊ณ๋ ์ต์ ํ๊ฐ ์์ ๋ฌธ์ ์์๋ ์ค๋ฒํค๋๊ฐ ๋จ
์ค๋ฌด ํ๋กํ์ผ๋ง ๊ตํ:
- ๋ฌธ์ ํฌ๊ธฐ ๋งฅ๋ฝ์ด ์ค์: 2ร2์ 9ร9 ๋ชจ๋ GPU์๊ฒ๋ ์์
- ๊ณ ์ ๋น์ฉ์ด ์์ ๋ฌธ์ ๋ฅผ ์ง๋ฐฐ: ๋ฉ๋ชจ๋ฆฌ ํ ๋น, ์ปค๋ ์คํ ์ค๋ฒํค๋
- โ์ต์ ํโ๊ฐ ์์ ์ํฌ๋ก๋์ ํด๋ก์ธ ์ ์์: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ๋น๋๊ธฐ ์ฐ์ฐ์ด ์ค๋ฒํค๋ ์ถ๊ฐ
- ์์ ๋ฌธ์ ๋ฅผ ์ต์ ํํ์ง ๋ง ๊ฒ: ์ค์ ์ํฌ๋ก๋๋ก ํ์ฅ ๊ฐ๋ฅํ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ค
- ํญ์ ๋ฒค์น๋งํนํ ๊ฒ: โ๋ ์ข์โ ์ฝ๋์ ๋ํ ๊ฐ์ ์ ํํ ํ๋ฆผ
์์ ์ปค๋ ํ๋กํ์ผ๋ง์ ์ดํด: ์ด 2ร2 ํ๋ ฌ ์์ ๋ ์ ํ์ ์ธ ์์ ์ปค๋ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
- ์ค์ ์ฐ์ฐ(ํ๋ ฌ ๊ณฑ์ )์ ๊ทนํ ๋น ๋ฆ (1,920 ns)
- ๋ฉ๋ชจ๋ฆฌ ์ค์ ์ค๋ฒํค๋๊ฐ ์ ์ฒด ์๊ฐ์ ์ง๋ฐฐ (์คํ์ 97% ์ด์)
- ์ด๊ฒ์ด ์ค๋ฌด GPU ์ต์ ํ๊ฐ ๋ค์์ ์ง์คํ๋ ์ด์ ์
๋๋ค:
- ์ฐ์ฐ ์ผ๊ด ์ฒ๋ฆฌ๋ก ์ค์ ๋น์ฉ ๋ถ์ฐ
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฌ์ฉ์ผ๋ก ํ ๋น ์ค๋ฒํค๋ ๊ฐ์
- ์ฐ์ฐ์ด ๋ณ๋ชฉ์ด ๋๋ ๋ ํฐ ๋ฌธ์ ํฌ๊ธฐ
์ค์ต: NSight Compute๋ก ์ปค๋ ์ฌ์ธต ๋ถ์
์ด์ ํน์ ์ปค๋์ ์ฑ๋ฅ ํน์ฑ์ ์ฌ์ธต์ ์ผ๋ก ๋ค์ฌ๋ค๋ด ์๋ค.
Step 1: ํน์ ์ปค๋ ํ๋กํ์ผ๋ง
# ํ์ฑ shell ์ํ์ธ์ง ํ์ธ
pixi shell -e nvidia
# Naive MatMul ์ปค๋์ ์์ธ ํ๋กํ์ผ๋ง (์ต์ ํ ๋น๋ ์ฌ์ฉ)
ncu \
--set full \
-o kernel_analysis \
--force-overwrite \
./solutions/p16/p16_optimized --naive
ํํ ๋ฌธ์ : ๊ถํ ์ค๋ฅ
ERR_NVGPUCTRPERM - The user does not have permission to access NVIDIA GPU Performance Counters์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๋ค์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์๋ํ์ธ์:# NVIDIA ๋๋ผ์ด๋ฒ ์ต์ ์ถ๊ฐ (rmmod๋ณด๋ค ์์ ) echo 'options nvidia "NVreg_RestrictProfilingToAdminUsers=0"' | sudo tee -a /etc/modprobe.d/nvidia-kernel-common.conf # ์ปค๋ ํ๋ผ๋ฏธํฐ ์ค์ sudo sysctl -w kernel.perf_event_paranoid=0 # ์๊ตฌ ์ ์ฉ echo 'kernel.perf_event_paranoid=0' | sudo tee -a /etc/sysctl.conf # ๋๋ผ์ด๋ฒ ๋ณ๊ฒฝ ์ฌํญ ์ ์ฉ์ ์ํด ์ฌ๋ถํ ํ์ sudo reboot # ๊ทธ๋ฐ ๋ค์ ncu ๋ช ๋ น์ ๋ค์ ์คํ ncu \ --set full \ -o kernel_analysis \ --force-overwrite \ ./solutions/p16/p16_optimized --naive
Step 2: ์ฃผ์ ์งํ ๋ถ์
# ์์ธ ๋ณด๊ณ ์ ์์ฑ (์ฌ๋ฐ๋ฅธ ๊ตฌ๋ฌธ)
ncu --import kernel_analysis.ncu-rep --page details
์ค์ NSight Compute ์ถ๋ ฅ (2ร2 Naive MatMul):
GPU Speed Of Light Throughput
----------------------- ----------- ------------
DRAM Frequency Ghz 6.10
SM Frequency Ghz 1.30
Elapsed Cycles cycle 3733
Memory Throughput % 1.02
DRAM Throughput % 0.19
Duration us 2.88
Compute (SM) Throughput % 0.00
----------------------- ----------- ------------
Launch Statistics
-------------------------------- --------------- ---------------
Block Size 9
Grid Size 1
Threads thread 9
Waves Per SM 0.00
-------------------------------- --------------- ---------------
Occupancy
------------------------------- ----------- ------------
Theoretical Occupancy % 33.33
Achieved Occupancy % 2.09
------------------------------- ----------- ------------
์ค์ ๋ฐ์ดํฐ์์ ์ป์ ํต์ฌ ํต์ฐฐ:
์ฑ๋ฅ ๋ถ์ - ๋ํนํ ํ์ค
- Compute Throughput: 0.00% - GPU๊ฐ ์ฐ์ฐ์ ์ผ๋ก ์์ ํ ์ ํด ์ํ
- Memory Throughput: 1.02% - ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๊ฑฐ์ ์ฌ์ฉํ์ง ์์
- Achieved Occupancy: 2.09% - GPU ๋ฅ๋ ฅ์ 2%๋ง ์ฌ์ฉ ์ค
- Grid Size: 1 ๋ธ๋ก - 80๊ฐ ๋ฉํฐํ๋ก์ธ์๋ฅผ ์์ ํ ๋ญ๋น!
์ฑ๋ฅ์ด ์ด๋ ๊ฒ ๋ฎ์ ์ด์
- ์์ ๋ฌธ์ ํฌ๊ธฐ: 2ร2 ํ๋ ฌ = ์ด 4๊ฐ ์์
- ์๋ชป๋ ์คํ ๊ตฌ์ฑ: 1๊ฐ ๋ธ๋ก์ 9๊ฐ ์ค๋ ๋ (32์ ๋ฐฐ์์ฌ์ผ ํจ)
- ์ฌ๊ฐํ ๊ณผ์ ํ์ฉ: SM๋น 0.00 wave (ํจ์จ์ ์ํด ์์ฒ ๊ฐ ํ์)
NSight Compute์ ํต์ฌ ์ต์ ํ ๊ถ๊ณ ์ฌํญ
- โEst. Speedup: 98.75%โ - 80๊ฐ SM์ ๋ชจ๋ ์ฌ์ฉํ๋๋ก ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ ์ฆ๊ฐ
- โEst. Speedup: 71.88%โ - ์ค๋ ๋ ๋ธ๋ก์ 32์ ๋ฐฐ์๋ก ์ฌ์ฉ
- โKernel grid is too smallโ - GPU ํจ์จ์ ์ํด ํจ์ฌ ํฐ ๋ฌธ์ ํ์
Step 3: ํ์ค ์ง์
์ด ํ๋กํ์ผ๋ง ๋ฐ์ดํฐ๊ฐ ์๋ ค์ฃผ๋ ๊ฒ:
- ์์ ๋ฌธ์ ๋ GPU์๊ฒ ๋ : 2ร2 ํ๋ ฌ์ GPU ๋ฆฌ์์ค๋ฅผ ์์ ํ ๋ญ๋น
- ์คํ ๊ตฌ์ฑ์ด ์ค์: ์๋ชป๋ ์ค๋ ๋/๋ธ๋ก ํฌ๊ธฐ๊ฐ ์ฑ๋ฅ์ ์ฃฝ์
- ๊ท๋ชจ๊ฐ ์๊ณ ๋ฆฌ์ฆ๋ณด๋ค ์ค์: ๊ทผ๋ณธ์ ์ผ๋ก ์์ ๋ฌธ์ ๋ ์ด๋ค ์ต์ ํ๋ก๋ ํด๊ฒฐ ๋ถ๊ฐ
- NSight Compute๋ ์ ์งํจ: ์ปค๋ ์ฑ๋ฅ์ด ๋ฎ์ ๋ ๊ทธ๋๋ก ์๋ ค์ค
์ง์ง ๊ตํ:
- ํ ์ด ๋ฌธ์ ๋ฅผ ์ต์ ํํ์ง ๋ง ๊ฒ - ์ค์ GPU ์ํฌ๋ก๋๋ฅผ ๋ํํ์ง ์์
- ํ์ค์ ์ธ ์ํฌ๋ก๋์ ์ง์ค - ์ต์ ํ๊ฐ ์ค์ ๋ก ์๋ฏธ ์๋ 1000ร1000+ ํ๋ ฌ
- ํ๋กํ์ผ๋ง์ผ๋ก ์ต์ ํ๋ฅผ ์๋ด - ๋จ, ์ต์ ํํ ๊ฐ์น๊ฐ ์๋ ๋ฌธ์ ์๋ง
2ร2 ์์ ์ ๊ฒฝ์ฐ: ์ ๊ตํ ์๊ณ ๋ฆฌ์ฆ(๊ณต์ ๋ฉ๋ชจ๋ฆฌ, tiling)์ด ์ด๋ฏธ ์ค๋ฒํค๋๊ฐ ์ง๋ฐฐ์ ์ธ ์ํฌ๋ก๋์ ์ค๋ฒํค๋๋ง ์ถ๊ฐํฉ๋๋ค.
ํ๋กํ์ผ๋ฌ ์ถ๋ ฅ์ ์ฑ๋ฅ ํ์ ์ฒ๋ผ ์ฝ๊ธฐ
์์ฃผ ๋ํ๋๋ ์ฑ๋ฅ ํจํด
ํจํด 1: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋
NSight Systems๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ๊ธด ๋ฉ๋ชจ๋ฆฌ ์ ์ก ์๊ฐ NSight Compute๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋, ๋ฎ์ ์ฐ์ฐ ํ์ฉ๋ ํด๊ฒฐ์ฑ : ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
ํจํด 2: ๋ฎ์ ์ ์ ์จ
NSight Systems๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ์งง์ ์ปค๋ ์คํ๊ณผ ๊ฐ๊ฒฉ NSight Compute๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ์ค์ ์ ์ ์จ์ด ๋ฎ์ ํด๊ฒฐ์ฑ : ๋ ์ง์คํฐ ์ฌ์ฉ๋ ์ค์ด๊ธฐ, ๋ธ๋ก ํฌ๊ธฐ ์ต์ ํ
ํจํด 3: ์ํ ๋ถ๊ธฐ
NSight Systems๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ๋ถ๊ท์นํ ์ปค๋ ์คํ ํจํด NSight Compute๊ฐ ๋ณด์ฌ์ฃผ๋ ๊ฒ: ๋ฎ์ ์ํ ์คํ ํจ์จ ํด๊ฒฐ์ฑ : ์กฐ๊ฑด ๋ถ๊ธฐ ์ต์ํ, ์๊ณ ๋ฆฌ์ฆ ์ฌ๊ตฌ์ฑ
ํ๋กํ์ผ๋ง ํ์ ์ํฌํ๋ก์ฐ
์ฑ๋ฅ ๋ฌธ์ ๋ฐ์
|
v
NSight Systems: ์ ์ฒด ๊ทธ๋ฆผ
|
v
GPU๋ฅผ ์ ํ์ฉํ๊ณ ์๋๊ฐ?
| |
์๋์ค ์
| |
v v
CPU-GPU NSight Compute: ์ปค๋ ์์ธ
ํ์ดํ๋ผ์ธ |
์์ v
๋ฉ๋ชจ๋ฆฌ ๋๋ ์ฐ์ฐ ๋ฐ์ด๋์ธ๊ฐ?
| | |
๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ ๋ ๋ค ์๋
| | |
v v v
๋ฉ๋ชจ๋ฆฌ ์ฐ์ ์ ์ ์จ
์ ๊ทผ ์ต์ ํ ํ์ธ
์ต์ ํ
ํ๋กํ์ผ๋ง ๋ชจ๋ฒ ์ฌ๋ก
ํฌ๊ด์ ์ธ ํ๋กํ์ผ๋ง ์ง์นจ์ Best Practices Guide - Performance Metrics๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ด๋ ๊ฒ ํ์ธ์
- ๋ํ์ ์ธ ์ํฌ๋ก๋๋ฅผ ํ๋กํ์ผ๋ง: ํ์ค์ ์ธ ๋ฐ์ดํฐ ํฌ๊ธฐ์ ํจํด ์ฌ์ฉ
- ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด๋ก ๋น๋: ์ต์ ํ์ ํจ๊ป ํฌ๊ด์ ์ธ ํ๋กํ์ผ๋ง ๋ฐ์ดํฐ ๋ฐ ์์ค
๋งคํ์ ์ํด
--debug-level=full์ฌ์ฉ - GPU ์๋ฐ์ : ์ปค๋์ ์ฌ๋ฌ ๋ฒ ์คํํ ํ ํ๋ฐ ๋ฐ๋ณต์ ํ๋กํ์ผ๋ง
- ๋์ ๋น๊ต: ํญ์ ์ฌ๋ฌ ๊ตฌํ์ ํ๋กํ์ผ๋ง
- ํซ์คํ์ ์ง์ค: ๊ฐ์ฅ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์ปค๋์ ์ต์ ํ
์ด๋ ๊ฒ ํ์ง ๋ง์ธ์
- ๋๋ฒ๊ทธ ์ ๋ณด ์์ด ํ๋กํ์ผ๋งํ์ง ๋ง ๊ฒ: ์ฑ๋ฅ์ ์์ค ์ฝ๋์ ๋งคํํ ์ ์์
(
mojo build --help) - ๋จ์ผ ์คํ๋ง ํ๋กํ์ผ๋งํ์ง ๋ง ๊ฒ: GPU ์ฑ๋ฅ์ ์คํ๋ง๋ค ๋ฌ๋ผ์ง ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ๋ฌด์ํ์ง ๋ง ๊ฒ: CPU-GPU ์ ์ก์ด ํํ ์ง๋ฐฐ์
- ์ฃ๋ถ๋ฆฌ ์ต์ ํํ์ง ๋ง ๊ฒ: ๋จผ์ ํ๋กํ์ผ๋ง, ๊ทธ๋ค์ ์ต์ ํ
ํํ ํจ์ ๊ณผ ํด๊ฒฐ์ฑ
ํจ์ 1: ์ฝ๋ ์คํํธ ํจ๊ณผ
# ์๋ชป๋ ๋ฐฉ๋ฒ: ์ฒซ ๋ฒ์งธ ์คํ์ ํ๋กํ์ผ๋ง
nsys profile mojo your_program.mojo
# ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์๋ฐ์
ํ ํ๋กํ์ผ๋ง
nsys profile --delay=5 mojo your_program.mojo # GPU ์๋ฐ์
๋๊ธฐ
ํจ์ 2: ์๋ชป๋ ๋น๋ ๊ตฌ์ฑ
# ์๋ชป๋ ๋ฐฉ๋ฒ: ์ ์ฒด ๋๋ฒ๊ทธ ๋น๋ (์ต์ ํ ๋นํ์ฑํ) ์ฆ, `--no-optimization`
mojo build -O0 your_program.mojo -o your_program
# ์๋ชป๋ ๋ฐฉ๋ฒ: ๋๋ฒ๊ทธ ์ ๋ณด ์์ (์์ค ๋งคํ ๋ถ๊ฐ)
mojo build your_program.mojo -o your_program
# ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ํ๋กํ์ผ๋ง์ ์ํ ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด ํฌํจ ์ต์ ํ ๋น๋
mojo build --debug-level=full your_program.mojo -o optimized_program
nsys profile ./optimized_program
ํจ์ 3: ๋ฉ๋ชจ๋ฆฌ ์ ์ก ๋ฌด์
# NSight Systems์์ ์ด ํจํด์ ์ฐพ์๋ณด์ธ์:
CPU -> GPU transfer: 50ms
Kernel execution: 2ms
GPU -> CPU transfer: 48ms
# ์ด: 100ms (์ปค๋์ ๊ฒจ์ฐ 2%!)
ํด๊ฒฐ์ฑ : ์ ์ก๊ณผ ์ฐ์ฐ์ ์ค์ฒฉํ๊ณ ์ ์ก ๋น๋๋ฅผ ์ค์ด๊ธฐ (Part IX์์ ๋ค๋ฃธ)
ํจ์ 4: ๋จ์ผ ์ปค๋์๋ง ์ง์ค
# ์๋ชป๋ ๋ฐฉ๋ฒ: "๋๋ฆฐ" ์ปค๋๋ง ํ๋กํ์ผ๋ง
ncu --kernel-name regex:slow_kernel program
# ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ๋จผ์ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ํ๋กํ์ผ๋ง
nsys profile mojo program.mojo # ์ค์ ๋ณ๋ชฉ ์ฐพ๊ธฐ
๋ชจ๋ฒ ์ฌ๋ก์ ๊ณ ๊ธ ์ต์
๊ณ ๊ธ NSight Systems ํ๋กํ์ผ๋ง
ํฌ๊ด์ ์ธ ์์คํ
์ ์ฒด ๋ถ์์ ์ํด ๋ค์ ๊ณ ๊ธ nsys ํ๋๊ทธ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
# ํ๋ก๋์
๊ธ ํ๋กํ์ผ๋ง ๋ช
๋ น
nsys profile \
--gpu-metrics-devices=all \
--trace=cuda,osrt,nvtx \
--trace-fork-before-exec=true \
--cuda-memory-usage=true \
--cuda-um-cpu-page-faults=true \
--cuda-um-gpu-page-faults=true \
--opengl-gpu-workload=false \
--delay=2 \
--duration=30 \
--sample=cpu \
--cpuctxsw=process-tree \
--output=comprehensive_profile \
--force-overwrite=true \
./your_program
ํ๋๊ทธ ์ค๋ช :
--gpu-metrics-devices=all: ๋ชจ๋ ๋๋ฐ์ด์ค์์ GPU ์งํ ์์ง--trace=cuda,osrt,nvtx: ํฌ๊ด์ API ์ถ์ --cuda-memory-usage=true: ๋ฉ๋ชจ๋ฆฌ ํ ๋น/ํด์ ์ถ์ --cuda-um-cpu/gpu-page-faults=true: Unified Memory ํ์ด์ง ํดํธ ๋ชจ๋ํฐ๋ง--delay=2: ํ๋กํ์ผ๋ง ์ 2์ด ๋๊ธฐ (์ฝ๋ ์คํํธ ํํผ)--duration=30: ์ต๋ 30์ด๊ฐ ํ๋กํ์ผ๋ง--sample=cpu: ํซ์คํ ๋ถ์์ ์ํ CPU ์ํ๋ง ํฌํจ--cpuctxsw=process-tree: CPU ์ปจํ ์คํธ ์ค์์น ์ถ์
๊ณ ๊ธ NSight Compute ํ๋กํ์ผ๋ง
ํฌ๊ด์ ์งํ๋ฅผ ํฌํจํ ์์ธ ์ปค๋ ๋ถ์:
# ๋ชจ๋ ์งํ ์ธํธ๋ก ์ ์ฒด ์ปค๋ ๋ถ์
ncu \
--set full \
--import-source=on \
--kernel-id=:::1 \
--launch-skip=0 \
--launch-count=1 \
--target-processes=all \
--replay-mode=kernel \
--cache-control=all \
--clock-control=base \
--apply-rules=yes \
--check-exit-code=yes \
--export=detailed_analysis \
--force-overwrite \
./your_program
# ํน์ ์ฑ๋ฅ ์ธก๋ฉด์ ์ง์ค
ncu \
--set=@roofline \
--section=InstructionStats \
--section=LaunchStats \
--section=Occupancy \
--section=SpeedOfLight \
--section=WarpStateStats \
--metrics=sm__cycles_elapsed.avg,dram__throughput.avg.pct_of_peak_sustained_elapsed \
--kernel-name regex:your_kernel_.* \
--export=targeted_analysis \
./your_program
์ฃผ์ NSight Compute ํ๋๊ทธ:
--set full: ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ์งํ ์์ง (ํฌ๊ด์ ์ด์ง๋ง ๋๋ฆผ)--set @roofline: ๋ฃจํ๋ผ์ธ ๋ถ์์ ์ต์ ํ๋ ์ธํธ--import-source=on: ๊ฒฐ๊ณผ๋ฅผ ์์ค ์ฝ๋์ ๋งคํ--replay-mode=kernel: ์ ํํ ์ธก์ ์ ์ํด ์ปค๋ ๋ฆฌํ๋ ์ด--cache-control=all: ์ผ๊ด๋ ๊ฒฐ๊ณผ๋ฅผ ์ํ GPU ์บ์ ์ ์ด--clock-control=base: ๊ธฐ๋ณธ ์ฃผํ์๋ก ํด๋ญ ๊ณ ์ --section=SpeedOfLight: Speed of Light ๋ถ์ ํฌํจ--metrics=...: ํน์ ์งํ๋ง ์์ง--kernel-name regex:pattern: ์ ๊ท์ ํจํด์ผ๋ก ์ปค๋ ์ง์ (--kernel-regex๊ฐ ์๋)
ํ๋กํ์ผ๋ง ์ํฌํ๋ก์ฐ ๋ชจ๋ฒ ์ฌ๋ก
1. ์ ์ง์ ํ๋กํ์ผ๋ง ์ ๋ต
# Step 1: ๋น ๋ฅธ ๊ฐ์ (๋น ๋ฆ)
nsys profile --trace=cuda --duration=10 --output=quick_look ./program
# Step 2: ์์ธ ์์คํ
๋ถ์ (์ค๊ฐ)
nsys profile --trace=cuda,osrt,nvtx --cuda-memory-usage=true --output=detailed ./program
# Step 3: ์ปค๋ ์ฌ์ธต ๋ถ์ (๋๋ฆฌ์ง๋ง ํฌ๊ด์ )
ncu --set=@roofline --kernel-name regex:hotspot_kernel ./program
2. ์ ๋ขฐ์ฑ์ ์ํ ๋ค์ค ์คํ ๋ถ์
# ์ฌ๋ฌ ๋ฒ ํ๋กํ์ผ๋งํ๊ณ ๋น๊ต
for i in {1..5}; do
nsys profile --output=run_${i} ./program
nsys stats run_${i}.nsys-rep > stats_${i}.txt
done
# ๊ฒฐ๊ณผ ๋น๊ต
diff stats_1.txt stats_2.txt
3. ํ๊ฒ ์ปค๋ ํ๋กํ์ผ๋ง
# ๋จผ์ ํซ์คํ ์ปค๋ ์๋ณ
nsys profile --trace=cuda,nvtx --output=overview ./program
nsys stats overview.nsys-rep | grep -A 10 "GPU Kernel Summary"
# ๊ทธ๋ฐ ๋ค์ ํน์ ์ปค๋ ํ๋กํ์ผ๋ง
ncu --kernel-name="identified_hotspot_kernel" --set full ./program
ํ๊ฒฝ ๋ฐ ๋น๋ ๋ชจ๋ฒ ์ฌ๋ก
์ต์ ๋น๋ ๊ตฌ์ฑ
# ํ๋กํ์ผ๋ง์ฉ: ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด ํฌํจ ์ต์ ํ ๋น๋
mojo build --debug-level=full --optimization-level=3 program.mojo -o program_profile
# ๋น๋ ์ค์ ํ์ธ
mojo build --help | grep -E "(debug|optimization)"
ํ๋กํ์ผ๋ง ํ๊ฒฝ ์ค์
# ์ผ๊ด๋ ๊ฒฐ๊ณผ๋ฅผ ์ํด GPU ๋ถ์คํธ ๋นํ์ฑํ
sudo nvidia-smi -ac 1215,1410 # ๋ฉ๋ชจ๋ฆฌ ๋ฐ GPU ํด๋ญ ๊ณ ์
# ๊ฒฐ์ ๋ก ์ ๋์ ์ค์
export CUDA_LAUNCH_BLOCKING=1 # ์ ํํ ํ์ด๋ฐ์ ์ํ ๋๊ธฐ์ ์คํ
# ํ๋กํ์ผ๋ง์ ์ํ ๋๋ผ์ด๋ฒ ์ ํ ์ํ
echo 0 | sudo tee /proc/sys/kernel/perf_event_paranoid
echo 'options nvidia "NVreg_RestrictProfilingToAdminUsers=0"' | sudo tee -a /etc/modprobe.d/nvidia-kernel-common.conf
๋ฉ๋ชจ๋ฆฌ ๋ฐ ์ฑ๋ฅ ๊ฒฉ๋ฆฌ
# ํ๋กํ์ผ๋ง ์ GPU ๋ฉ๋ชจ๋ฆฌ ์ด๊ธฐํ
nvidia-smi --gpu-reset
# ๋ค๋ฅธ GPU ํ๋ก์ธ์ค ๋นํ์ฑํ
sudo fuser -v /dev/nvidia* # GPU ์ฌ์ฉ ์ค์ธ ํ๋ก์ธ์ค ํ์ธ
sudo pkill -f cuda # ํ์์ CUDA ํ๋ก์ธ์ค ์ข
๋ฃ
# ๋์ ์ฐ์ ์์๋ก ์คํ
sudo nice -n -20 nsys profile ./program
๋ถ์ ๋ฐ ๋ณด๊ณ ๋ชจ๋ฒ ์ฌ๋ก
์ข ํฉ ๋ณด๊ณ ์ ์์ฑ
# ์ฌ๋ฌ ๋ณด๊ณ ์ ํ์ ์์ฑ
nsys stats --report=cuda_api_sum,cuda_gpu_kern_sum,cuda_gpu_mem_time_sum --format=csv --output=. profile.nsys-rep
# ์ธ๋ถ ๋ถ์์ ์ํด ๋ด๋ณด๋ด๊ธฐ
nsys export --type=sqlite profile.nsys-rep
nsys export --type=json profile.nsys-rep
# ๋น๊ต ๋ณด๊ณ ์ ์์ฑ
nsys stats --report=cuda_gpu_kern_sum baseline.nsys-rep > baseline_kernels.txt
nsys stats --report=cuda_gpu_kern_sum optimized.nsys-rep > optimized_kernels.txt
diff -u baseline_kernels.txt optimized_kernels.txt
์ฑ๋ฅ ํ๊ท ํ ์คํธ
#!/bin/bash
# CI/CD์ฉ ์๋ํ ํ๋กํ์ผ๋ง ์คํฌ๋ฆฝํธ
BASELINE_TIME=$(nsys stats baseline.nsys-rep | grep "Total Time" | awk '{print $3}')
CURRENT_TIME=$(nsys stats current.nsys-rep | grep "Total Time" | awk '{print $3}')
REGRESSION_THRESHOLD=1.10 # 10% ์ฑ๋ฅ ์ ํ ์๊ณ๊ฐ
if (( $(echo "$CURRENT_TIME > $BASELINE_TIME * $REGRESSION_THRESHOLD" | bc -l) )); then
echo "Performance regression detected: ${CURRENT_TIME}ns vs ${BASELINE_TIME}ns"
exit 1
fi
๋ค์ ๋จ๊ณ
ํ๋กํ์ผ๋ง ๊ธฐ์ด๋ฅผ ์ดํดํ์ผ๋:
- ๊ธฐ์กด ์ปค๋๋ก ์ฐ์ต: ์ด๋ฏธ ํ์๋ ํผ์ฆ๋ค์ ํ๋กํ์ผ๋งํด ๋ณด์ธ์
- ์ต์ ํ ์ค๋น: Puzzle 31์์ ์ด ํต์ฐฐ์ ์ ์ ์จ ์ต์ ํ์ ํ์ฉํฉ๋๋ค
- ๋๊ตฌ ์ตํ๊ธฐ: ๋ค์ํ NSight Systems์ NSight Compute ์ต์ ์ ์คํํด ๋ณด์ธ์
๊ธฐ์ตํ์ธ์: ํ๋กํ์ผ๋ง์ ๋จ์ํ ๋๋ฆฐ ์ฝ๋๋ฅผ ์ฐพ๋ ๊ฒ์ด ์๋๋๋ค - ํ๋ก๊ทธ๋จ์ ๋์์ ์ดํดํ๊ณ ๊ทผ๊ฑฐ ์๋ ์ต์ ํ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๊ฒ์ ๋๋ค.
์ถ๊ฐ ํ๋กํ์ผ๋ง ์๋ฃ:
๐ต ์บ์ ํํธ์ ์ญ์ค
๊ฐ์
์ฒซ ๋ฒ์งธ ํ๋กํ์ผ๋ง ํ์ ์ฌ๊ฑด์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! ์ธ ๊ฐ์ GPU ์ปค๋์ด ๋ชจ๋
๋์ผํ ๋ฒกํฐ ๋ง์
output[i] = a[i] + b[i]์ ์ํํฉ๋๋ค. ๋น์ฐํ ์ฑ๋ฅ๋ ๊ฐ๊ฒ ์ฃ ?
์๋๋๋ค! ์ด ์ปค๋๋ค์ ์ฑ๋ฅ ์ฐจ์ด๋ ๊ทน์ ์ ๋๋ค - ํ๋๋ ๋๋จธ์ง๋ณด๋ค ์์ญ ๋ฐฐ๋ ๋๋ฆฝ๋๋ค. ์ฌ๋ฌ๋ถ์ ์๋ฌด: ๋ฐฉ๊ธ ๋ฐฐ์ด ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ํ์ฉํ์ฌ ์ ๊ทธ๋ฐ์ง ๋ฐํ๋ด์ธ์.
๋์ ๊ณผ์
GPU ์ต์ ํ์ ๋ํ ๊ธฐ์กด ์์์ ์์ ํ ๋ค์ง๋ ์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! ๋์์๋ ๊ฒ๋ณด๊ธฐ์ ๋์ผํ ๋ฒกํฐ ๋ง์ ์ปค๋ ์ธ ๊ฐ๊ฐ ์๊ณ , ๋ชจ๋ ์ ํํ ๊ฐ์ ์ํ ์ฐ์ฐ์ ์ํํฉ๋๋ค:
output[i] = a[i] + b[i] // ๋จ์ํ ์ฐ์ ์ฐ์ฐ - ๋ญ๊ฐ ์๋ชป๋ ์ ์์๊น?
์ถฉ๊ฒฉ์ ์ธ ํ์ค:
- ์ธ ์ปค๋ ๋ชจ๋ ๋์ผํ๊ณ ์ ํํ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค
- ํ๋์ ์ปค๋์ด ๋๋จธ์ง๋ณด๋ค ~50๋ฐฐ ๋๋ฆฝ๋๋ค
- ๊ฐ์ฅ ๋๋ฆฐ ์ปค๋์ด ๊ฐ์ฅ ๋์ ์บ์ ํํธ์จ์ ๋ณด์ ๋๋ค (์์๊ณผ ์ ๋ฐ๋!)
- ์ผ๋ฐ์ ์ธ ์ฑ๋ฅ ์ง๊ด์ด ์์ ํ ๋น๋๊ฐ๋๋ค
ํ์ ์๋ฌด:
- ์ฑ๋ฅ ๋ฒ์ธ ์๋ณ - ์ด๋ค ์ปค๋์ด ์น๋ช ์ ์ผ๋ก ๋๋ฆฐ๊ฐ?
- ์บ์์ ์ญ์ค ๊ท๋ช - ๋์ ์บ์ ํํธ๊ฐ ์ ๋ฎ์ ์ฑ๋ฅ์ ์๋ฏธํ๋๊ฐ?
- ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํด๋ - ๋์ผํ ์ฐ์ฐ์ด ์ด๋ป๊ฒ ์ด๋ ๊ฒ ๋ค๋ฅด๊ฒ ๋์ํ๋๊ฐ?
- ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ํ์ต - ์ถ์ธก์ด ์๋ NSight ๋๊ตฌ๋ก ๊ทผ๊ฑฐ๋ฅผ ํ๋ณดํ๋ผ
์ ์ค์ํ๊ฐ: ์ด ํผ์ฆ์ CPU ๊ธฐ๋ฐ ์ง๊ด์ ๋์ ํ๋ GPU ์ฑ๋ฅ์ ๊ทผ๋ณธ ์๋ฆฌ๋ฅผ ๋๋ฌ๋ ๋๋ค. ์ฌ๊ธฐ์ ๊ธฐ๋ฅด๋ ์ญ๋์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋๋ณด๋ค ์ค์ํ ์ค๋ฌด GPU ์ต์ ํ์ ์ง์ ์ ์ฉ๋ฉ๋๋ค.
๋ฐ์ : ์ด ๊ณผ์ ์ ํ๋ก๋์ ์ฑ๋ฅ ์ด์๋ฅผ ๋๋ฒ๊น ํ๋ฏ์ด, ์์ค ์ฝ๋๋ฅผ ๋จผ์ ๋ณด์ง ์๊ณ ํ๋กํ์ผ๋ง ๋๊ตฌ๋ง์ผ๋ก ์ ๊ทผํฉ๋๋ค. ํ๋กํ์ผ๋ง ๊ฒฐ๊ณผ๋ฅผ ์ป์ ํ์ ์ฝ๋๋ฅผ ๋ค์ฌ๋ค๋ด ๋๋ค.
ํ์ ๋๊ตฌ ๋ชจ์
ํ๋กํ์ผ๋ง ํํ ๋ฆฌ์ผ์์ ๋ฐฐ์ด ๋๊ตฌ๋ค:
- NSight Systems (
nsys) - ์ด๋ค ์ปค๋์ด ๋๋ฆฐ์ง ์ฐพ๊ธฐ - NSight Compute (
ncu) - ์ปค๋์ด ์ ๋๋ฆฐ์ง ๋ถ์ํ๊ธฐ - ๋ฉ๋ชจ๋ฆฌ ํจ์จ ์งํ - ๋นํจ์จ์ ์ธ ์ ๊ทผ ํจํด ํ์ง
์์ํ๊ธฐ
Step 1: ๋ฒค์น๋งํฌ ์คํ
pixi shell -e nvidia
mojo problems/p30/p30.mojo --benchmark
์ปค๋ ๊ฐ์ ๊ทน์ ์ธ ์คํ ์๊ฐ ์ฐจ์ด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค! ํ๋์ ์ปค๋์ด ๋๋จธ์ง๋ณด๋ค ํจ์ฌ ๋๋ฆฝ๋๋ค. ์ฝ๋๋ฅผ ๋ณด์ง ์๊ณ ํ๋กํ์ผ๋ง ๋๊ตฌ๋ง์ผ๋ก ์์ธ์ ์ฐพ์๋ด๋ ๊ฒ์ด ๋ชฉํ์ ๋๋ค.
์ถ๋ ฅ ์์:
| name | met (ms) | iters |
| ------- | --------- | ----- |
| kernel1 | 171.85 | 11 |
| kernel2 | 1546.68 | 11 | <- ์ด๊ฒ๋ง ์ ๋
๋๋ฆฌ๋ค!
| kernel3 | 172.18 | 11 |
Step 2: ํ๋กํ์ผ๋ง์ ์ํ ๋น๋ ์ค๋น
ํ์: ์ ํํ ํ๋กํ์ผ๋ง์ ์ํด ์ต์ ํ๋ฅผ ์ ์งํ๋ฉด์ ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ๋น๋ํฉ๋๋ค:
mojo build --debug-level=full problems/p30/p30.mojo -o problems/p30/p30_profiler
์ค์ํ ์ด์ :
- ์ ์ฒด ๋๋ฒ๊ทธ ์ ๋ณด: ํ๋กํ์ผ๋ฌ์ ์์ ํ ์ฌ๋ณผ ํ ์ด๋ธ, ๋ณ์๋ช , ์์ค ๋ผ์ธ ๋งคํ์ ์ ๊ณต
- ์ข ํฉ ๋ถ์: NSight ๋๊ตฌ๊ฐ ์ฑ๋ฅ ๋ฐ์ดํฐ๋ฅผ ํน์ ์ฝ๋ ์์น์ ์ฐ๊ด ์ง๋ ๊ฒ์ด ๊ฐ๋ฅ
- ์ต์ ํ ์ ์ง: ํ๋ก๋์ ๋น๋์ ๋์ผํ ํ์ค์ ์ธ ์ฑ๋ฅ ์ธก์ ๋ณด์ฅ
Step 3: ์์คํ ์ ์ฒด ์กฐ์ฌ (NSight Systems)
๊ฐ ์ปค๋์ ํ๋กํ์ผ๋งํ์ฌ ์ ์ฒด ๊ทธ๋ฆผ์ ํ์ธํฉ๋๋ค:
# ์ต์ ํ ๋น๋๋ก ๊ฐ ์ปค๋์ ๊ฐ๋ณ ํ๋กํ์ผ๋ง (์ฝ๋ ์คํํธ ๋ฐฉ์ง๋ฅผ ์ํ ์๋ฐ์
ํฌํจ)
nsys profile --trace=cuda,osrt,nvtx --delay=2 --output=./problems/p30/kernel1_profile ./problems/p30/p30_profiler --kernel1
nsys profile --trace=cuda,osrt,nvtx --delay=2 --output=./problems/p30/kernel2_profile ./problems/p30/p30_profiler --kernel2
nsys profile --trace=cuda,osrt,nvtx --delay=2 --output=./problems/p30/kernel3_profile ./problems/p30/p30_profiler --kernel3
# ๊ฒฐ๊ณผ ๋ถ์
nsys stats --force-export=true ./problems/p30/kernel1_profile.nsys-rep > ./problems/p30/kernel1_profile.txt
nsys stats --force-export=true ./problems/p30/kernel2_profile.nsys-rep > ./problems/p30/kernel2_profile.txt
nsys stats --force-export=true ./problems/p30/kernel3_profile.nsys-rep > ./problems/p30/kernel3_profile.txt
ํ์ธํ ์ฌํญ:
- GPU ์ปค๋ ์์ฝ - ์ด๋ค ์ปค๋์ด ๊ฐ์ฅ ์ค๋ ๊ฑธ๋ฆฌ๋๊ฐ?
- ์ปค๋ ์คํ ์๊ฐ - ์ฐจ์ด๊ฐ ์ผ๋ง๋ ๋๋๊ฐ?
- ๋ฉ๋ชจ๋ฆฌ ์ ์ก ํจํด - ๊ตฌํ ๊ฐ์ ๋น์ทํ๊ฐ?
Step 4: ์ปค๋ ์ฌ์ธต ๋ถ์ (NSight Compute)
๋๋ฆฐ ์ปค๋์ ์๋ณํ ํ, NSight Compute๋ก ๋ถ์ํฉ๋๋ค:
# ์ต์ ํ ๋น๋๋ก ๊ฐ ์ปค๋์ ๋ฉ๋ชจ๋ฆฌ ํจํด ์ฌ์ธต ๋ถ์
ncu --set=@roofline --section=MemoryWorkloadAnalysis -f -o ./problems/p30/kernel1_analysis ./problems/p30/p30_profiler --kernel1
ncu --set=@roofline --section=MemoryWorkloadAnalysis -f -o ./problems/p30/kernel2_analysis ./problems/p30/p30_profiler --kernel2
ncu --set=@roofline --section=MemoryWorkloadAnalysis -f -o ./problems/p30/kernel3_analysis ./problems/p30/p30_profiler --kernel3
# ๊ฒฐ๊ณผ ํ์ธ
ncu --import ./problems/p30/kernel1_analysis.ncu-rep --page details
ncu --import ./problems/p30/kernel2_analysis.ncu-rep --page details
ncu --import ./problems/p30/kernel3_analysis.ncu-rep --page details
์ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ถ๋ ฅ์ด ๋ํ๋ฉ๋๋ค:
Kernel1: Memory Throughput: ~308 Gbyte/s, Max Bandwidth: ~51%
Kernel2: Memory Throughput: ~6 Gbyte/s, Max Bandwidth: ~12%
Kernel3: Memory Throughput: ~310 Gbyte/s, Max Bandwidth: ~52%
์ฃผ์ ์กฐ์ฌ ์งํ:
- Memory Throughput (Gbyte/s) - ์ค์ ๋ฌ์ฑํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ
- Max Bandwidth (%) - ์ด๋ก ์ ์ต๋ ๋์ญํญ ๋๋น ํ์ฉ๋ฅ
- L1/TEX Hit Rate (%) - L1 ์บ์ ํจ์จ
- L2 Hit Rate (%) - L2 ์บ์ ํจ์จ
๐ค ๋ฐ์ง๊ด์ ์ธ ๊ฒฐ๊ณผ: Kernel2๊ฐ ๊ฐ์ฅ ๋์ ์บ์ ํํธ์จ์ ๋ณด์ด๋ฉด์ ๊ฐ์ฅ ๋ฎ์ ์ฑ๋ฅ์ ๋ณด์ ๋๋ค! ์ด๊ฒ์ด ํ์ด์ผ ํ ํต์ฌ ๋ฏธ์คํฐ๋ฆฌ์ ๋๋ค.
Step 5: ํ์ ์ง๋ฌธ
ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ๋ฅผ ๋ฐํ์ผ๋ก ์ปค๋ ์ฝ๋ problems/p30/p30.mojo๋ฅผ ์ดํด๋ณด๋ฉฐ ๋ค์ ์ง๋ฌธ์ ๋ตํด ๋ณด์ธ์:
์ฑ๋ฅ ๋ถ์
- ์ด๋ค ์ปค๋์ด ๊ฐ์ฅ ๋์ Memory Throughput์ ๋ฌ์ฑํ๋๊ฐ? (Gbyte/s ๊ฐ ํ์ธ)
- ์ด๋ค ์ปค๋์ Max Bandwidth ํ์ฉ๋ฅ ์ด ๊ฐ์ฅ ๋ฎ์๊ฐ? (๋ฐฑ๋ถ์จ ๋น๊ต)
- ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋์ ์ฑ๋ฅ ๊ฒฉ์ฐจ๋ ์ผ๋ง์ธ๊ฐ? (๊ฐ์ฅ ๋น ๋ฅธ ๊ฒ๊ณผ ๊ฐ์ฅ ๋๋ฆฐ ๊ฒ์ ๋ฐฐ์ ์ฐจ์ด)
์บ์์ ์ญ์ค
- ์ด๋ค ์ปค๋์ L1/TEX Hit Rate๊ฐ ๊ฐ์ฅ ๋์๊ฐ?
- ์ด๋ค ์ปค๋์ L2 Hit Rate๊ฐ ๊ฐ์ฅ ๋์๊ฐ?
- ๐คฏ ์บ์ ํํธ์จ์ด ๊ฐ์ฅ ๋์ ์ปค๋์ด ์ ์ฑ๋ฅ์ด ๊ฐ์ฅ ๋์๊ฐ?
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ํ๊ตฌ
- ๋์ ์บ์ ํํธ์จ์ด ์ค์ ๋ก ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ๋ํ๋ผ ์ ์๋๊ฐ?
- ์ด๋ค ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ๋์ ์บ์ ํํธ์ ๋ฎ์ ์ฒ๋ฆฌ๋์ ๋์์ ์ ๋ฐํ๋๊ฐ?
- ์ โํจ์จ์ ์ธ ์บ์ฑโ์ด โ๋นํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผโ์ ์ฆ์์ผ ์ ์๋๊ฐ?
โ์ํ!โ ์๊ฐ
- ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ๋ฅผ ๋ฐํ์ผ๋ก, ์ด ์ฌ๋ก๊ฐ ๋ณด์ฌ์ฃผ๋ GPU ๋ฉ๋ชจ๋ฆฌ์ ๊ทผ๋ณธ ์๋ฆฌ๋ ๋ฌด์์ธ๊ฐ?
๋ฐ๊ฒฌํ ํต์ฌ ํต์ฐฐ: ๋๋ก๋ ๋์ ์บ์ ํํธ์จ์ด ์ฑ๋ฅ ์น๋ฆฌ๊ฐ ์๋๋ผ ์ํ ์ ํธ์ ๋๋ค!
์๋ฃจ์
์ด ๋ฏธ์คํฐ๋ฆฌ๋ GPU ์ฑ๋ฅ์ ๊ทผ๋ณธ ์๋ฆฌ๋ฅผ ๋๋ฌ๋ ๋๋ค: ์ปค๋์ด ๋์ผํ ์ฐ์ฐ์ ์ํํ๋๋ผ๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ์์๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ฑ๋ฅ์ ์ง๋ฐฐํฉ๋๋ค.
ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ๊ฐ ๋ฐํ๋ ๊ฒ:
- ์ฑ๋ฅ ์๊ณ: Kernel1๊ณผ Kernel3์ ๋น ๋ฅด๊ณ , Kernel2๋ ์น๋ช ์ ์ผ๋ก ๋๋ฆผ (์์ญ ๋ฐฐ ์ฐจ์ด)
- ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋์ด ๋ต์ ๋งํด์ค๋ค: ๋น ๋ฅธ ์ปค๋์ ๋์ ๋์ญํญ ํ์ฉ๋ฅ ์ ๋ฌ์ฑํ๊ณ , ๋๋ฆฐ ์ปค๋์ ์ต์ํ์ ํ์ฉ๋ฅ ๋ง ๋ฌ์ฑ
- ์บ์์ ์ญ์ค: ๊ฐ์ฅ ๋๋ฆฐ ์ปค๋์ด ๊ฐ์ฅ ๋์ ์บ์ ํํธ์จ์ ๋ณด์ - ๋์ ์บ์ ํํธ๊ฐ ๋นํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๋ํ๋ผ ์ ์์์ ์์ฌ
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ GPU ์ํฌ๋ก๋์์๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋๋ณด๋ค ์ค์
์์ธ ์๋ฃจ์ ๊ณผ ์ฌ์ธต ์ค๋ช
์ด ํ๋กํ์ผ๋ง ํ์ ์ฌ๊ฑด์ ์ปค๋์ด ๋์ผํ ์ํ ์ฐ์ฐ์ ์ํํ๋๋ผ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ด๋ป๊ฒ ์์ญ ๋ฐฐ์ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด๋ด๋์ง ๋ณด์ฌ์ค๋๋ค.
ํ๋กํ์ผ๋ง์ผ๋ก ํ์ธํ ์ฑ๋ฅ ๊ทผ๊ฑฐ
NSight Systems ํ์๋ผ์ธ ๋ถ์:
- Kernel 1: ์งง์ ์คํ ์๊ฐ - ํจ์จ์
- Kernel 3: Kernel 1๊ณผ ์ ์ฌ - ํจ์จ์
- Kernel 2: ๊ทน์ ์ผ๋ก ๊ธด ์คํ ์๊ฐ - ๋นํจ์จ์
NSight Compute ๋ฉ๋ชจ๋ฆฌ ๋ถ์ (ํ๋์จ์ด ๋ฌด๊ดํ ํจํด):
- ํจ์จ์ ์ธ ์ปค๋ (1 & 3): ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋, ์ํธํ ๋์ญํญ ํ์ฉ๋ฅ , ๋ณดํต ์์ค์ ์บ์ ํํธ์จ
- ๋นํจ์จ์ ์ธ ์ปค๋ (2): ๋งค์ฐ ๋ฎ์ ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋, ์ด์ ํ ๋์ญํญ ํ์ฉ๋ฅ , ๊ทน๋๋ก ๋์ ์บ์ ํํธ์จ
์บ์์ ์ญ์ค ๊ท๋ช
๐คฏ ๋ฐ์ง๊ด์ ์ธ ๋ฐ๊ฒฌ:
- Kernel2๊ฐ ๊ฐ์ฅ ๋์ ์บ์ ํํธ์จ์ ๋ณด์ด๋ฉด์ ์ฑ๋ฅ์ ์ต์
- ๊ธฐ์กด ์์์ ๋ํ ๋์ : โ๋์ ์บ์ ํํธ = ์ข์ ์ฑ๋ฅโ
- ์ง์ค: ๋์ ์บ์ ํํธ์จ์ ๋นํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ฆ์์ผ ์ ์์
์บ์์ ์ญ์ค์ด ๋ฐ์ํ๋ ์ด์ :
์ ํต์ ์ธ CPU ์ง๊ด (GPU์์๋ ํ๋ฆผ):
- ์บ์ ํํธ์จ์ด ๋์์๋ก ํญ์ ์ฑ๋ฅ์ด ์ข๋ค
- ์บ์ ํํธ๋ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ์ ์ค์ฌ ํจ์จ์ ๋์ธ๋ค
GPU ๋ฉ๋ชจ๋ฆฌ์ ํ์ค (์ฌ๋ฐ๋ฅธ ์ดํด):
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋์์๋ ๋ณํฉ์ด ์บ์ฑ๋ณด๋ค ์ค์
- ๋นํจ์จ์ ์ธ ์ ๊ทผ ํจํด์ ์ธ์์ ์ผ๋ก ์บ์ ํํธ์จ์ ๋ถํ๋ฆด ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ๋ฅ ์ด ์ง์ ํ ์ฑ๋ฅ ์งํ
๊ทผ๋ณธ ์์ธ ๋ถ์ - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
p30.mojo์ ์ค์ ์ปค๋ ๊ตฌํ:
Kernel 1 - ํจ์จ์ ์ธ ๋ณํฉ ์ ๊ทผ:
def kernel1(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var i = block_dim.x * block_idx.x + thread_idx.x
if i < size:
output[i] = a[i] + b[i]
ํ์ค ์ค๋ ๋ ์ธ๋ฑ์ฑ - ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผ
Kernel 2 - ๋นํจ์จ์ ์ธ ์คํธ๋ผ์ด๋ ์ ๊ทผ:
def kernel2(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var tid = block_idx.x * block_dim.x + thread_idx.x
var stride = 512
var i = tid
while i < size:
output[i] = a[i] + b[i]
i += stride
ํฐ stride=512๋ก ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๊ฐ๊ฒฉ ๋ฐ์ - ๋์ผํ ์ฐ์ฐ์ด์ง๋ง ํฉ์ด์ง ์ ๊ทผ
Kernel 3 - ํจ์จ์ ์ธ ์ญ์ ์ ๊ทผ:
def kernel3(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
var tid = block_idx.x * block_dim.x + thread_idx.x
var total_threads = (SIZE // 1024) * 1024
for step in range(0, size, total_threads):
var forward_i = step + tid
if forward_i < size:
var reverse_i = size - 1 - forward_i
output[reverse_i] = a[reverse_i] + b[reverse_i]
์ญ์ ์ธ๋ฑ์ฑ์ด์ง๋ง ์ฌ์ ํ ์์ธก ๊ฐ๋ฅ - ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ์ฃผ์์ ์ ๊ทผ (๋ฐฉํฅ๋ง ๋ฐ๋)
ํจํด ๋ถ์:
- Kernel 1: ์ ํ์ ์ธ ๋ณํฉ ์ ๊ทผ - ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผ
- Kernel 2: ์น๋ช ์ ์ธ ์คํธ๋ผ์ด๋ ์ ๊ทผ - ์ค๋ ๋๊ฐ 512๊ฐ ์์์ฉ ๊ฑด๋๋
- Kernel 3: ์ญ์์ด์ง๋ง ์ํ ๋ด์์๋ ๋ณํฉ ์ ์ง - ์์ธก ๊ฐ๋ฅํ ํจํด
๋ฉ๋ชจ๋ฆฌ ์์คํ ์ดํด
GPU ๋ฉ๋ชจ๋ฆฌ ์ํคํ ์ฒ ๊ธฐ์ด:
- ์ํ ์คํ: 32๊ฐ ์ค๋ ๋๊ฐ ํจ๊ป ์คํ
- ์บ์ ๋ผ์ธ ํฌ๊ธฐ: 128๋ฐ์ดํธ (float32 ๊ฐ 32๊ฐ)
- ๋ณํฉ ์๊ฑด: ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํด์ผ ํจ
p30.mojo ์ค์ ์์ธ:
comptime SIZE = 16 * 1024 * 1024 # 16M ์์ (float32 ๋ฐ์ดํฐ 64MB)
comptime THREADS_PER_BLOCK = (1024, 1) # ๋ธ๋ก๋น 1024 ์ค๋ ๋
comptime BLOCKS_PER_GRID = (SIZE // 1024, 1) # ์ด 16,384 ๋ธ๋ก
comptime dtype = DType.float32 # ์์๋น 4๋ฐ์ดํธ
์ด ์ค์ ์ด ์ค์ํ ์ด์ :
- ๋์ฉ๋ ๋ฐ์ดํฐ์ (16M): ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ฐจ์ด๊ฐ ๋ช ํํ๊ฒ ๋๋ฌ๋จ
- ๋ธ๋ก๋น 1024 ์ค๋ ๋: CUDA ์ต๋ ์ค๋ ๋ ์
- ๋ธ๋ก๋น 32๊ฐ ์ํ: ๊ฐ ๋ธ๋ก์ 32๊ฐ์ ์ํ(๊ฐ 32 ์ค๋ ๋)๊ฐ ํฌํจ
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ ์๊ฐํ:
KERNEL 1 (๋ณํฉ): KERNEL 2 (stride 512):
์ํ ์ค๋ ๋ 0-31: ์ํ ์ค๋ ๋ 0-31:
Thread 0: Memory[0] Thread 0: Memory[0]
Thread 1: Memory[1] Thread 1: Memory[512]
Thread 2: Memory[2] Thread 2: Memory[1024]
... ...
Thread 31: Memory[31] Thread 31: Memory[15872]
๊ฒฐ๊ณผ: ์บ์ ๋ผ์ธ 1ํ fetch ๊ฒฐ๊ณผ: ๋ณ๋์ ์บ์ ๋ผ์ธ 32ํ fetch
์ํ: ~308 GB/s ์ฒ๋ฆฌ๋ ์ํ: ~6 GB/s ์ฒ๋ฆฌ๋
์บ์: ํจ์จ์ ํ์ฉ ์บ์: ๊ฐ์ ๋ผ์ธ์ ๋ฐ๋ณต ํํธ!
KERNEL 3 (์ญ์์ด์ง๋ง ๋ณํฉ):
์ํ ์ค๋ ๋ 0-31 (์ฒซ ๋ฒ์งธ ๋ฐ๋ณต):
Thread 0: Memory[SIZE-1] (reverse_i = SIZE-1-0)
Thread 1: Memory[SIZE-2] (reverse_i = SIZE-1-1)
Thread 2: Memory[SIZE-3] (reverse_i = SIZE-1-2)
...
Thread 31: Memory[SIZE-32] (reverse_i = SIZE-1-31)
๊ฒฐ๊ณผ: ์ธ์ ํ ์ฃผ์ (๋ฐฉํฅ๋ง ๋ฐ๋)
์ํ: ~310 GB/s ์ฒ๋ฆฌ๋ (Kernel 1๊ณผ ๊ฑฐ์ ๋์ผ)
์บ์: ์ญ์์์๋ ํจ์จ์ ํ์ฉ
์บ์์ ์ญ์ค ์ค๋ช
Kernel2 (stride=512)๊ฐ ๋์ ์บ์ ํํธ์จ์๋ ์ฑ๋ฅ์ด ๋์ ์ด์ :
stride=512์ ์ฌ์ ์ค๋ช :
# ๊ฐ ์ค๋ ๋๊ฐ ํฐ ๊ฐ๊ฒฉ์ผ๋ก ์ฌ๋ฌ ์์๋ฅผ ์ฒ๋ฆฌ:
Thread 0: elements [0, 512, 1024, 1536, 2048, ...]
Thread 1: elements [1, 513, 1025, 1537, 2049, ...]
Thread 2: elements [2, 514, 1026, 1538, 2050, ...]
...
์ด๊ฒ์ด ์บ์์ ์ญ์ค์ ๋ง๋๋ ์ด์ :
- ์บ์ ๋ผ์ธ ๋ฐ๋ณต: 512๊ฐ ์์๋ฅผ ๊ฑด๋๋ฐ์ด๋ ๊ฒน์น๋ ์บ์ ๋ผ์ธ ์์ญ ์์ ๋จธ๋ฌด๋ฆ
- ๊ฑฐ์ง ํจ์จ์ ํ์: ๊ฐ์ ์บ์ ๋ผ์ธ์ ๋ฐ๋ณต ์ ๊ทผ = ์ธ์์ ์ผ๋ก ๋์ โํํธ์จโ
- ๋์ญํญ ์ฌ์: 32๊ฐ ์ค๋ ๋ ร 32๊ฐ ๋ณ๋ ์บ์ ๋ผ์ธ = ๋ง๋ํ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ
- ์ํ ์คํ ๋ถ์ผ์น: GPU๋ ๋ณํฉ ์ ๊ทผ์ ๋ง๊ฒ ์ค๊ณ๋์์ง๋ง, ํฉ์ด์ง ์ ๊ทผ์ ๋ฐ์
float32 (๊ฐ 4๋ฐ์ดํธ) ๊ตฌ์ฒด ์์:
- ์บ์ ๋ผ์ธ: 128๋ฐ์ดํธ = float32 ๊ฐ 32๊ฐ
- stride 512: ์ค๋ ๋๊ฐ 512ร4 = 2048๋ฐ์ดํธ = 16 ์บ์ ๋ผ์ธ ๊ฐ๊ฒฉ์ผ๋ก ์ ํ!
- ์ํ ์ํฅ: 32๊ฐ ์ค๋ ๋๊ฐ 1๊ฐ ๋์ 32๊ฐ์ ์๋ก ๋ค๋ฅธ ์บ์ ๋ผ์ธ์ ํ์๋ก ํจ
ํต์ฌ ํต์ฐฐ: Kernel2์ ๋์ ์บ์ ํํธ๋ ๋นํจ์จ์ ์ผ๋ก ๊ฐ์ ธ์จ ๋ฐ์ดํฐ์ ๋ํ ๋ฐ๋ณต ์ ๊ทผ์ด์ง, ํ๋ช ํ ์บ์ฑ์ด ์๋๋๋ค!
ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ํต์ฐฐ
์ฒด๊ณ์ ํ์ ์ ๊ทผ๋ฒ:
1๋จ๊ณ: NSight Systems (์ ์ฒด ๊ทธ๋ฆผ)
- ์ด๋ค ์ปค๋์ด ๋๋ฆฐ์ง ์๋ณ
- ๋ช ๋ฐฑํ ๋ณ๋ชฉ ๋ฐฐ์ (๋ฉ๋ชจ๋ฆฌ ์ ์ก, API ์ค๋ฒํค๋)
- ์ปค๋ ์คํ ์๊ฐ ์ฐจ์ด์ ์ง์ค
2๋จ๊ณ: NSight Compute (์ฌ์ธต ๋ถ์)
- ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋ ์งํ ๋ถ์
- ๋์ญํญ ํ์ฉ๋ฅ ๋ฐฑ๋ถ์จ ๋น๊ต
- ์บ์ ํํธ์จ๊ณผ ํจํด ์กฐ์ฌ
3๋จ๊ณ: ๊ทผ๊ฑฐ๋ฅผ ์ด๋ก ์ผ๋ก ์ฐ๊ฒฐ
ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ โ ์ฝ๋ ๋ถ์:
NSight Compute ๊ฒฐ๊ณผ: ์ค์ ์ฝ๋ ํจํด:
- Kernel1: ~308 GB/s โ i = block_idx*block_dim + thread_idx (๋ณํฉ)
- Kernel2: ~6 GB/s, 99% L2 hits โ i += 512 (์น๋ช
์ stride)
- Kernel3: ~310 GB/s โ reverse_i = size-1-forward_i (์ญ์ ๋ณํฉ)
ํ๋กํ์ผ๋ฌ ๋ฐ์ดํฐ๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ์ ์ง์ ๋๋ฌ๋
๋๋ค!
๊ทผ๊ฑฐ์์ ์ฝ๋๋ก์ ์ฐ๊ฒฐ:
- ๋์ ์ฒ๋ฆฌ๋ + ๋ณดํต ์บ์ ํํธ์จ = ๋ณํฉ ์ ๊ทผ (Kernel 1 & 3)
- ๋ฎ์ ์ฒ๋ฆฌ๋ + ๋์ ์บ์ ํํธ์จ = ๋นํจ์จ์ ์คํธ๋ผ์ด๋ ์ ๊ทผ (Kernel 2)
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ๋ฅ ์ด ์บ์ ํต๊ณ์ ๋ฌด๊ดํ๊ฒ ์ง์ ํ ํจ์จ์ ๋๋ฌ๋
์ค๋ฌด ์ฑ๋ฅ ์์ฌ์
์ด ํจํด์ด ์ํฅ์ ๋ฏธ์น๋ GPU ์์ฉ ๋ถ์ผ:
๊ณผํ ์ปดํจํ :
- ์คํ ์ค ์ฐ์ฐ: ๊ทธ๋ฆฌ๋ ์๋ฎฌ๋ ์ด์ ์์์ ์ด์ ์ ๊ทผ ํจํด
- ์ ํ ๋์: ํ๋ ฌ ์ํ ์์ (ํ ์ฐ์ vs ์ด ์ฐ์ )
- ํธ๋ฏธ๋ถ ๋ฐฉ์ ์ ํ์ด: ์ ํ ์ฐจ๋ถ๋ฒ์์์ ๊ฒฉ์์ ์ ๊ทผ ํจํด
๊ทธ๋ํฝ์ค ๋ฐ ์ด๋ฏธ์ง ์ฒ๋ฆฌ:
- ํ ์ค์ฒ ํํฐ๋ง: ์ ฐ์ด๋์์์ ์ํ ์ ๊ทผ ํจํด
- ์ด๋ฏธ์ง ํฉ์ฑ๊ณฑ: ํํฐ ์ปค๋์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ์ ๊ณต๊ฐ ๋ณํ: ์ฑ๋ ์ธํฐ๋ฆฌ๋น ์ ๋ต
๋จธ์ ๋ฌ๋:
- ํ๋ ฌ ์ฐ์ฐ: GEMM์์์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ์ต์ ํ
- ํ ์ ์ถ์ฝ: ๋ค์ฐจ์ ๋ฐฐ์ด ์ ๊ทผ ํจํด
- ๋ฐ์ดํฐ ๋ก๋ฉ: ๋ฐฐ์น ์ฒ๋ฆฌ์ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ
GPU ์ต์ ํ์ ๊ทผ๋ณธ ์์น
๋ฉ๋ชจ๋ฆฌ ์ฐ์ ์ต์ ํ ์ ๋ต:
- ๋ฉ๋ชจ๋ฆฌ ํจํด์ด ์ง๋ฐฐ: ์ ๊ทผ ํจํด์ด ์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋๋ณด๋ค ๋ ์ค์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ๋ณํฉ์ด ํต์ฌ: ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋๋ก ์ค๊ณ
- ๋์ญํญ ํ์ฉ๋ฅ ์ธก์ : ์บ์ ํต๊ณ๊ฐ ์๋ ์ค์ ์ฒ๋ฆฌ๋์ ์ง์ค
- ์ฒด๊ณ์ ํ๋กํ์ผ๋ง: NSight ๋๊ตฌ๋ก ์ค์ ๋ณ๋ชฉ์ ํ์
ํต์ฌ ๊ธฐ์ ํต์ฐฐ:
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋: ๋์ญํญ ํ์ฉ๋ฅ ์ด ์ฑ๋ฅ์ ๊ฒฐ์
- ์บ์ ์งํ์ ํจ์ : ๋์ ํํธ์จ์ด ํญ์ ํจ์จ์ ์๋ฏธํ์ง๋ ์์
- ์ํ ๋ ๋ฒจ ์ฌ๊ณ : 32๊ฐ ์ค๋ ๋ ์คํ ๊ทธ๋ฃน์ ์ํ ์ ๊ทผ ํจํด ์ค๊ณ
- ํ๋์จ์ด ์ธ์ ํ๋ก๊ทธ๋๋ฐ: GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ดํด๊ฐ ํ์
ํต์ฌ ๊ตํ
์ด๋ฒ์ ํ๊ตฌํ ์ฌ๋ก๋ GPU ์ฑ๋ฅ ์ต์ ํ๊ฐ CPU ์ง๊ด์ ๋ฒ๋ฆฌ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ค์ฌ ์ฌ๊ณ ๋ก ์ ํํ ๊ฒ์ ์๊ตฌํ๋ค๋ ์ ์ ๋ณด์ฌ์ค๋๋ค:
ํต์ฌ ํต์ฐฐ:
- ๋์ ์บ์ ํํธ์จ์ ์ข์ ์ฑ๋ฅ์ด ์๋๋ผ ๋นํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ๋ํ๋ผ ์ ์์
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ๋ฅ ์ด ์บ์ ํต๊ณ๋ณด๋ค ์ค์
- ๋จ์ํ ๋ณํฉ ํจํด์ด ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ๋ณด๋ค ๋ ๋น ๋ฅธ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ํ๋กํ์ผ๋ง ๋๊ตฌ๊ฐ ์ง๊ด์ผ๋ก๋ ์ ์ ์๋ ์ฑ๋ฅ์ ์ง์ค์ ๋๋ฌ๋
์ค์ ๋ฐฉ๋ฒ๋ก :
- NSight Systems์ NSight Compute๋ก ์ฒด๊ณ์ ์ผ๋ก ํ๋กํ์ผ๋ง
- ์ธ์ ์ค๋ ๋๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋๋ก ์ค๊ณ (๋ณํฉ)
- ์ง๊ด์ด ์๋ ํ๋กํ์ผ๋ฌ ๊ทผ๊ฑฐ๋ฅผ ๋ฐํ์ผ๋ก ์ต์ ํ ๊ฒฐ์
์บ์์ ์ญ์ค์ ์ํคํ ์ฒ์ ๋ํ ์ดํด ์์ด ๊ณ ์์ค ์งํ์ ์์กดํ๋ฉด ์๋ชป๋ ๊ฒฐ๋ก ์ ์ด๋ฅผ ์ ์๋ค๋ ์ ์ ๋ณด์ฌ์ค๋๋ค - GPU ํ๋ก๊ทธ๋๋ฐ์ ๋์ด ๋๋ฃจ ์ ์ฉ๋๋ ๊ตํ์ ๋๋ค.
Puzzle 31: ์ ์ ์จ ์ต์ ํ
์ด ํผ์ฆ์ด ์ค์ํ ์ด์
Puzzle 30์ ์ฐ์ฅ์ : GPU ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ๋ฐฐ์ฐ๊ณ , ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ์ด๋ป๊ฒ ๊ทน์ ์ธ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด๋ด๋์ง ๋ฐ๊ฒฌํ์ต๋๋ค. ์ด์ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ ์ค๋น๊ฐ ๋์์ต๋๋ค: ๋ฆฌ์์ค ์ต์ ํ.
ํ์ต ์ฌ์ :
- Puzzle 30์์๋ NSight ํ๋กํ์ผ๋ง(
nsys์ncu)์ ํตํด ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ง๋จํ๋ ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค - Puzzle 31์์๋ ๋ฆฌ์์ค ๊ด๋ฆฌ๋ฅผ ํตํด ์ฑ๋ฅ์ ์์ธกํ๊ณ ์ ์ดํ๋ ๋ฒ์ ๋ฐฐ์๋๋ค
- ๋์ ํฉ์น๋ฉด GPU ์ต์ ํ๋ฅผ ์ํ ์์ ํ ๋๊ตฌ ์ธํธ๋ฅผ ๊ฐ์ถ๊ฒ ๋ฉ๋๋ค
๋ฐ๊ฒฌํ๊ฒ ๋ ๊ฒ: GPU ์ฑ๋ฅ์ ๋จ์ํ ์๊ณ ๋ฆฌ์ฆ ํจ์จ์ ๋ฌธ์ ๊ฐ ์๋๋๋ค - ์ฝ๋๊ฐ ํ์ ๋ ํ๋์จ์ด ๋ฆฌ์์ค๋ฅผ ์ด๋ป๊ฒ ํ์ฉํ๋๋๊ฐ ํต์ฌ์ ๋๋ค. ๋ชจ๋ GPU๋ ์ ํํ ๋ ์ง์คํฐ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์คํ ์ ๋์ ๊ฐ๊ณ ์์ต๋๋ค. ์ ์ ์จ(occupancy) - SM๋น ํ์ฑ ์ํ ์ ๋๋น ์ต๋ ๊ฐ๋ฅ ์ํ ์์ ๋น์จ - ์ ์ดํดํ๋ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ค์ํฉ๋๋ค:
- ์ง์ฐ ์๊ฐ ์๋: ๋ฉ๋ชจ๋ฆฌ ๋๊ธฐ ์๊ฐ ๋์ GPU๊ฐ ์ ํด ์ํ์ ๋น ์ง์ง ์๋๋ก ์ ์ง
- ๋ฆฌ์์ค ํ ๋น: ๋ ์ง์คํฐ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์ค๋ ๋ ๋ธ๋ก ๊ฐ์ ๊ท ํ ์กฐ์
- ์ฑ๋ฅ ์์ธก: ๋ณ๋ชฉ์ด ๋ฐ์ํ๊ธฐ ์ ์ ๋ฏธ๋ฆฌ ํ์
- ์ต์ ํ ์ ๋ต: ์ ์ ์จ์ ์ง์คํด์ผ ํ ๋์ ๋ค๋ฅธ ์์์ ์ง์คํด์ผ ํ ๋ ํ๋จ
GPU๋ฅผ ๋์ด์ ์ ์ฉ๋๋ ์๋ฆฌ: ์ฌ๊ธฐ์ ๋ฐฐ์ฐ๋ ์๋ฆฌ๋ ๋ฆฌ์์ค๋ฅผ ์ฌ๋ฌ ์คํ ์ ๋์ด ๊ณต์ ํ๋ ๋ชจ๋ ๋ณ๋ ฌ ์ปดํจํ ์์คํ ์ ์ ์ฉ๋ฉ๋๋ค - ํ์ดํผ์ค๋ ๋ฉ์ ์ฌ์ฉํ๋ CPU๋ถํฐ ๋ถ์ฐ ์ปดํจํ ํด๋ฌ์คํฐ๊น์ง.
๊ฐ์
GPU ์ ์ ์จ์ SM๋น ํ์ฑ ์ํ ์ ๋๋น ์ต๋ ๊ฐ๋ฅ ์ํ ์์ ๋น์จ์ ๋๋ค. GPU๊ฐ ์ํ ์ ํ์ ํตํด ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์ผ๋ง๋ ํจ๊ณผ์ ์ผ๋ก ์จ๊ธธ ์ ์๋์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
SAXPY๋ Single-precision Alpha times X plus Y์ ์ฝ์์
๋๋ค. ์ด ํผ์ฆ์์๋
์ํ์ ์ผ๋ก ๋์ผํ์ง๋ง ๋ฆฌ์์ค ์ฌ์ฉ์ด ๋ค๋ฅธ ์ธ ๊ฐ์ง SAXPY
์ปค๋(y[i] = alpha * x[i] + y[i])์ ํ๊ตฌํฉ๋๋ค:
comptime SIZE = 32 * 1024 * 1024 # 32M elements - larger workload to show occupancy effects
comptime THREADS_PER_BLOCK = (1024, 1)
comptime BLOCKS_PER_GRID = (SIZE // 1024, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
comptime ALPHA = Scalar[dtype](2.5) # SAXPY coefficient
def minimal_kernel(
y: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
x: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
alpha: Float32,
size: Int,
):
"""Minimal SAXPY kernel - simple and register-light for high occupancy."""
var i = block_dim.x * block_idx.x + thread_idx.x
if i < size:
# Direct computation: y[i] = alpha * x[i] + y[i]
# Uses minimal registers (~8), no shared memory
y[i] = alpha * x[i] + y[i]
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p31/p31.mojo
def sophisticated_kernel(
y: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
x: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
alpha: Float32,
size: Int,
):
"""Sophisticated SAXPY kernel - over-engineered with excessive resource usage.
"""
# Maximum shared memory allocation (close to 48KB limit)
var shared_cache = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](
row_major[1024 * 12]()
) # 48KB
var i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
if i < size:
# REAL computational work that can't be optimized away - affects final result
var base_x = x[i]
var base_y = y[i]
# Simulate "precision enhancement" - multiple small adjustments that add up
# Each computation affects the final result so compiler can't eliminate them
# But artificially increases register pressure
var precision_x1 = base_x * 1.0001
var precision_x2 = precision_x1 * 0.9999
var precision_x3 = precision_x2 * 1.000001
var precision_x4 = precision_x3 * 0.999999
var precision_y1 = base_y * 1.000005
var precision_y2 = precision_y1 * 0.999995
var precision_y3 = precision_y2 * 1.0000001
var precision_y4 = precision_y3 * 0.9999999
# Multiple alpha computations for "stability" - should equal alpha
var alpha1 = alpha * 1.00001 * 0.99999
var alpha2 = alpha1 * 1.000001 * 0.999999
var alpha3 = alpha2 * 1.0000001 * 0.9999999
var alpha4 = alpha3 * 1.00000001 * 0.99999999
# Complex polynomial "optimization" - creates register pressure
var x_power2 = precision_x4 * precision_x4
var x_power3 = x_power2 * precision_x4
var x_power4 = x_power3 * precision_x4
var x_power5 = x_power4 * precision_x4
var x_power6 = x_power5 * precision_x4
var x_power7 = x_power6 * precision_x4
var x_power8 = x_power7 * precision_x4
# "Advanced" mathematical series that contributes tiny amount to result
var series_term1 = x_power2 * 0.0000001 # x^2/10M
var series_term2 = x_power4 * 0.00000001 # x^4/100M
var series_term3 = x_power6 * 0.000000001 # x^6/1B
var series_term4 = x_power8 * 0.0000000001 # x^8/10B
var series_correction = (
series_term1 - series_term2 + series_term3 - series_term4
)
# Over-engineered shared memory usage with multiple caching strategies
if local_i < 1024:
shared_cache[local_i] = precision_x4
shared_cache[local_i + 1024] = precision_y4
shared_cache[local_i + 2048] = alpha4
shared_cache[local_i + 3072] = series_correction
barrier()
# Load from shared memory for "optimization"
var cached_x = shared_cache[local_i] if local_i < 1024 else precision_x4
var cached_y = (
shared_cache[local_i + 1024] if local_i < 1024 else precision_y4
)
var cached_alpha = (
shared_cache[local_i + 2048] if local_i < 1024 else alpha4
)
var cached_correction = (
shared_cache[local_i + 3072] if local_i
< 1024 else series_correction
)
# Final "high precision" computation - all work contributes to result
var high_precision_result = (
cached_alpha * cached_x + cached_y + cached_correction
)
# Over-engineered result with massive resource usage but mathematically ~= alpha*x + y
y[i] = high_precision_result
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p31/p31.mojo
def balanced_kernel(
y: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
x: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
alpha: Float32,
size: Int,
):
"""Balanced SAXPY kernel - efficient optimization with moderate resources.
"""
# Reasonable shared memory usage for effective caching (16KB)
var shared_cache = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](
row_major[1024 * 4]()
) # 16KB total
var i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
if i < size:
# Moderate computational work that contributes to result
var base_x = x[i]
var base_y = y[i]
# Light precision enhancement - less than sophisticated kernel
var enhanced_x = base_x * 1.00001 * 0.99999
var enhanced_y = base_y * 1.00001 * 0.99999
var stable_alpha = alpha * 1.000001 * 0.999999
# Moderate computational optimization
var x_squared = enhanced_x * enhanced_x
var optimization_hint = x_squared * 0.000001
# Efficient shared memory caching - only what we actually need
if local_i < 1024:
shared_cache[local_i] = enhanced_x
shared_cache[local_i + 1024] = enhanced_y
barrier()
# Use cached values efficiently
var cached_x = shared_cache[local_i] if local_i < 1024 else enhanced_x
var cached_y = (
shared_cache[local_i + 1024] if local_i < 1024 else enhanced_y
)
# Balanced computation - moderate work, good efficiency
var result = stable_alpha * cached_x + cached_y + optimization_hint
# Balanced result with moderate resource usage (~15 registers, 16KB shared)
y[i] = result
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p31/p31.mojo
๋์ ๊ณผ์
ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ธ ์ปค๋์ ์กฐ์ฌํ๊ณ , ์ ์ ์จ ์ต์ ํ์ ๋ํ ๋ถ์ ์ง๋ฌธ์ ๋ตํ์ธ์. ์ปค๋๋ค์ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ์ง๋ง ๋ฆฌ์์ค ์ฌ์ฉ์ด ๊ทน์ ์ผ๋ก ๋ค๋ฆ ๋๋ค - ์ฑ๋ฅ๊ณผ ์ ์ ์จ์ด ์ ์ง๊ด์ ์ด๊ธ๋๋ ๋ฐฉ์์ผ๋ก ๋์ํ๋์ง ๋ฐ๊ฒฌํ๋ ๊ฒ์ด ์ฌ๋ฌ๋ถ์ ์๋ฌด์ ๋๋ค!
์ด ํผ์ฆ์ ํ์๋ ๊ตฌ์ฒด์ ์ธ ์์น ๊ฒฐ๊ณผ๋ NVIDIA A10G (Ampere 8.6) ํ๋์จ์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ํฉ๋๋ค. ๊ฒฐ๊ณผ๋ GPU ์ ์กฐ์ฌ์ ์ํคํ ์ฒ(NVIDIA: Pascal/Turing/Ampere/Ada/Hopper, AMD: RDNA/GCN, Apple: M1/M2/M3/M4/M5)์ ๋ฐ๋ผ ๋ฌ๋ผ์ง์ง๋ง, ๊ธฐ๋ณธ ๊ฐ๋ , ๋ฐฉ๋ฒ๋ก , ํต์ฐฐ์ ๋ชจ๋ ์ต์ GPU์ ๋ณดํธ์ ์ผ๋ก ์ ์ฉ๋ฉ๋๋ค.
pixi run gpu-specs๋ฅผ ์คํํ์ฌ ํ๋์จ์ด๋ณ ์์น๋ฅผ ํ์ธํ์ธ์.
๊ตฌ์ฑ
์๊ตฌ ์ฌํญ:
- CUDA ํดํท์ด ์ค์น๋ NVIDIA GPU
- Puzzle 30์ NSight Compute
โ ๏ธ GPU ํธํ์ฑ ์ฐธ๊ณ : ๊ธฐ๋ณธ ์ค์ ์ ๊ณต๊ฒฉ์ ์ธ ๊ฐ์ ์ฌ์ฉํ๋ฏ๋ก ๊ตฌํ์ด๋ ์ ์ฌ์ GPU์์๋ ์คํจํ ์ ์์ต๋๋ค:
comptime SIZE = 32 * 1024 * 1024 # 32M ์์ (๋ฐฐ์ด๋น ~256MB ๋ฉ๋ชจ๋ฆฌ) comptime THREADS_PER_BLOCK = (1024, 1) # ๋ธ๋ก๋น 1024 ์ค๋ ๋ comptime BLOCKS_PER_GRID = (SIZE // 1024, 1) # 32768 ๋ธ๋ก์คํ ์คํจ ์
problems/p31/p31.mojo์์ ๋ค์ ๊ฐ์ ์ค์ด์ธ์:
- ๊ตฌํ GPU (Compute Capability < 3.0):
THREADS_PER_BLOCK = (512, 1),SIZE = 16 * 1024 * 1024์ฌ์ฉ- ๋ฉ๋ชจ๋ฆฌ ์ ํ GPU (< 2GB):
SIZE = 8 * 1024 * 1024๋๋SIZE = 4 * 1024 * 1024์ฌ์ฉ- ๊ทธ๋ฆฌ๋ ์ฐจ์ ์ ํ:
BLOCKS_PER_GRID๋SIZE์ ๋ง์ถฐ ์๋ ์กฐ์ ๋ฉ๋๋ค
์ ์ ์จ ๊ณต์:
์ด๋ก ์ ์ ์ ์จ = min(
SM๋น ๋ ์ง์คํฐ ์ / (์ค๋ ๋๋น ๋ ์ง์คํฐ ์ ร ๋ธ๋ก๋น ์ค๋ ๋ ์),
SM๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ / ๋ธ๋ก๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ,
SM๋น ์ต๋ ๋ธ๋ก ์
) ร ๋ธ๋ก๋น ์ค๋ ๋ ์ / SM๋น ์ต๋ ์ค๋ ๋ ์
์กฐ์ฌ ๊ณผ์
Step 1: ์ปค๋ ํ ์คํธ
pixi shell -e nvidia
mojo problems/p31/p31.mojo --all
์ธ ์ปค๋ ๋ชจ๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ผ ํฉ๋๋ค. ๋ฏธ์คํฐ๋ฆฌ: ์ ์ฑ๋ฅ์ ๋ค๋ฅผ๊น์?
Step 2: ์ฑ๋ฅ ๋ฒค์น๋งํฌ
mojo problems/p31/p31.mojo --benchmark
์ธ ์ปค๋ ๋ชจ๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ผ ํฉ๋๋ค. ๋ฏธ์คํฐ๋ฆฌ: ์ ์ฑ๋ฅ์ ๋ค๋ฅผ๊น์?
Step 3: ํ๋กํ์ผ๋ง์ฉ ๋น๋
mojo build --debug-level=full problems/p31/p31.mojo -o problems/p31/p31_profiler
Step 4: ๋ฆฌ์์ค ์ฌ์ฉ๋ ํ๋กํ์ผ๋ง
# ๊ฐ ์ปค๋์ ๋ฆฌ์์ค ์ฌ์ฉ๋ ํ๋กํ์ผ๋ง
ncu --set=@occupancy --section=LaunchStats problems/p31/p31_profiler --minimal
ncu --set=@occupancy --section=LaunchStats problems/p31/p31_profiler --sophisticated
ncu --set=@occupancy --section=LaunchStats problems/p31/p31_profiler --balanced
์ ์ ์จ ๋ถ์์ ์ํด ๋ฆฌ์์ค ์ฌ์ฉ๋์ ๊ธฐ๋กํ์ธ์.
Step 5: ์ด๋ก ์ ์ ์ ์จ ๊ณ์ฐ
๋จผ์ GPU ์ํคํ ์ฒ์ ์ธ๋ถ ์คํ์ ํ์ธํฉ๋๋ค:
pixi run gpu-specs
์ฐธ๊ณ : gpu-specs๋ GPU ์ ์กฐ์ฌ(NVIDIA/AMD/Apple)๋ฅผ ์๋ ๊ฐ์งํ๊ณ
ํ๋์จ์ด์์ ํ์๋ ๋ชจ๋ ์ํคํ
์ฒ ์ธ๋ถ ์ ๋ณด๋ฅผ ํ์ํฉ๋๋ค - ๋ณ๋์ ์ฐธ์กฐํ๊ฐ
ํ์ ์์ต๋๋ค!
์ฃผ์ ์ํคํ ์ฒ ์คํ (์ฐธ๊ณ ์ฉ):
| ์ํคํ ์ฒ | Compute Cap | ๋ ์ง์คํฐ/SM | ๊ณต์ ๋ฉ๋ชจ๋ฆฌ/SM | ์ต๋ ์ค๋ ๋/SM | ์ต๋ ๋ธ๋ก/SM |
|---|---|---|---|---|---|
| Hopper (H100) | 9.0 | 65,536 | 228KB | 2,048 | 32 |
| Ada (RTX 40xx) | 8.9 | 65,536 | 128KB | 2,048 | 32 |
| Ampere (RTX 30xx, A100, A10G) | 8.0, 8.6 | 65,536 | 164KB | 2,048 | 32 |
| Turing (RTX 20xx) | 7.5 | 65,536 | 96KB | 1,024 | 16 |
| Pascal (GTX 10xx) | 6.1 | 65,536 | 96KB | 2,048 | 32 |
๐ ๊ณต์ ๋ฌธ์:
- NVIDIA CUDA Compute Capability Table
- CUDA Programming Guide - Compute Capabilities
- Hopper Architecture In-Depth
- Ampere Architecture Whitepaper
โ ๏ธ ์ฐธ๊ณ : ์ด ๊ฐ๋ค์ ์ด๋ก ์ ์ต๋์น์ ๋๋ค. ์ค์ ์ ์ ์จ์ ํ๋์จ์ด ์ค์ผ์ค๋ง ์ ์ฝ, ๋๋ผ์ด๋ฒ ์ค๋ฒํค๋ ๋ฑ์ ์์ธ์ผ๋ก ๋ ๋ฎ์ ์ ์์ต๋๋ค.
GPU ์คํ๊ณผ ์ ์ ์จ ๊ณต์์ ์ฌ์ฉํ์ฌ:
- ๋ธ๋ก๋น ์ค๋ ๋ ์: 1024 (์ปค๋ ์ค์ ๊ฐ)
์ ์ ์จ ๊ณต์๊ณผ ํ๋์จ์ด ์คํ์ ์ฌ์ฉํ์ฌ ๊ฐ ์ปค๋์ ์ด๋ก ์ ์ ์ ์จ์ ์์ธกํ์ธ์.
Step 6: ์ค์ ์ ์ ์จ ์ธก์
# ๊ฐ ์ปค๋์ ์ค์ ์ ์ ์จ ์ธก์
ncu --metrics=smsp__warps_active.avg.pct_of_peak_sustained_active problems/p31/p31_profiler --minimal
ncu --metrics=smsp__warps_active.avg.pct_of_peak_sustained_active problems/p31/p31_profiler --sophisticated
ncu --metrics=smsp__warps_active.avg.pct_of_peak_sustained_active problems/p31/p31_profiler --balanced
์ด๋ก ์ ๊ณ์ฐ๊ณผ ์ค์ ์ธก์ ๋ ์ ์ ์จ์ ๋น๊ตํ์ธ์ - ๋ฏธ์คํฐ๋ฆฌ๊ฐ ๋๋ฌ๋๋ ์๊ฐ์ ๋๋ค!
ํต์ฌ ํต์ฐฐ
๐ก ์ ์ ์จ ์๊ณ๊ฐ: ๋๊ธฐ ์๊ฐ์ ์จ๊ธฐ๊ธฐ์ ์ถฉ๋ถํ ์ ์ ์จ(~25-50%)์ ํ๋ณดํ๋ฉด, ๊ทธ ์ด์์ ์ ์ ์จ์ ์ํ ์ฒด๊ฐ ํจ๊ณผ๋ฅผ ๋ณด์ ๋๋ค.
๐ก ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ vs ์ฐ์ฐ ๋ฐ์ด๋: SAXPY๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋์์๋ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ์ ์ ์จ๋ณด๋ค ๋ ์ค์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
๐ก ๋ฆฌ์์ค ํจ์จ: ์ต์ GPU๋ ์ ๋นํ ์์ค์ ๋ ์ง์คํฐ ์๋ฐ(์ค๋ ๋๋น 20-40๊ฐ)์ ์ ์ ์จ์ ๊ทน์ ์ธ ๊ฐ์ ์์ด ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
๋์ ๊ณผ์ : ๋ค์ ์ง๋ฌธ์ ๋ตํ์ธ์
์์ ์กฐ์ฌ ๋จ๊ณ๋ฅผ ์๋ฃํ ํ, ๋ค์ ๋ถ์ ์ง๋ฌธ์ ๋ตํ์ฌ ์ ์ ์จ ๋ฏธ์คํฐ๋ฆฌ๋ฅผ ํ์ด๋ณด์ธ์:
์ฑ๋ฅ ๋ถ์ (Step 2):
- ์ด๋ค ์ปค๋์ด ๊ฐ์ฅ ๋น ๋ฅด๊ณ , ์ด๋ค ์ปค๋์ด ๊ฐ์ฅ ๋๋ฆฐ๊ฐ์? ์คํ ์๊ฐ ์ฐจ์ด๋ฅผ ๊ธฐ๋กํ์ธ์.
๋ฆฌ์์ค ํ๋กํ์ผ๋ง (Step 4):
- ๊ฐ ์ปค๋์ ์ค๋ ๋๋น ๋ ์ง์คํฐ ์, ๋ธ๋ก๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, SM๋น ์ํ ์๋ฅผ ๊ธฐ๋กํ์ธ์.
์ด๋ก ์ ๊ณ์ฐ (Step 5):
- GPU ์คํ๊ณผ ์ ์ ์จ ๊ณต์์ ์ฌ์ฉํ์ฌ ๊ฐ ์ปค๋์ ์ด๋ก ์ ์ ์ ์จ์ ๊ณ์ฐํ์ธ์. ์ด๋ค ์ปค๋์ด ๊ฐ์ฅ ๋๊ณ /๋ฎ์์ผ ํ๋์?
์ธก์ ๋ ์ ์ ์จ (Step 6):
- ์ธก์ ๋ ์ ์ ์จ ๊ฐ์ด ๊ณ์ฐ ๊ฒฐ๊ณผ์ ์ด๋ป๊ฒ ๋น๊ต๋๋์?
์ ์ ์จ ๋ฏธ์คํฐ๋ฆฌ:
- ๋ฆฌ์์ค ์ฌ์ฉ์ด ๊ทน์ ์ผ๋ก ๋ค๋ฅธ๋ฐ๋ ์ธ ์ปค๋ ๋ชจ๋ ๋น์ทํ ์ ์ ์จ(~64-66%, GPU ์ํคํ ์ฒ์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์์)๋ฅผ ๋ฌ์ฑํ๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
- ๋ฆฌ์์ค ์ฌ์ฉ์ด ๊ทน์ ์ผ๋ก ์ฐจ์ด๋๋๋ฐ(19 vs 40 ๋ ์ง์คํฐ, 0KB vs 49KB ๊ณต์ ๋ฉ๋ชจ๋ฆฌ) ์ฑ๋ฅ์ด ๊ฑฐ์ ๋์ผํ(<2% ์ฐจ์ด) ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
- ์ด๋ก ์ ์ ์ ์จ ๊ณ์ฐ๊ณผ ์ค์ GPU ๋์ ์ฌ์ด์ ๊ด๊ณ์ ๋ํด ๋ฌด์์ ์ ์ ์๋์?
- ์ด SAXPY ์ํฌ๋ก๋์ ์ค์ ์ฑ๋ฅ ๋ณ๋ชฉ์ด ์ ์ ์จ์ด ์๋๋ผ๋ฉด ๋ฌด์์ธ๊ฐ์?
ํ
ํ์ ๋๊ตฌ ๋ชจ์:
- NSight Compute (
ncu) - ์ ์ ์จ๊ณผ ๋ฆฌ์์ค ์ฌ์ฉ๋ ์ธก์ - GPU ์ํคํ
์ฒ ์คํ -
pixi run gpu-specs๋ฅผ ์ฌ์ฉํ ์ด๋ก ์ ํ๊ณ ๊ณ์ฐ - ์ ์ ์จ ๊ณต์ - ๋ฆฌ์์ค ๋ณ๋ชฉ ์์ธก
- ์ฑ๋ฅ ๋ฒค์น๋งํฌ - ์ด๋ก ์ ๋ถ์ ๊ฒ์ฆ
ํต์ฌ ์ต์ ํ ์์น:
- ์ต์ ํ ์ ์ ๊ณ์ฐํ๊ธฐ: ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ์ ์ ์จ ๊ณต์์ผ๋ก ๋ฆฌ์์ค ํ๊ณ๋ฅผ ์์ธก
- ์ธก์ ์ผ๋ก ๊ฒ์ฆํ๊ธฐ: ์ด๋ก ์ ๊ณ์ฐ์ ์ปดํ์ผ๋ฌ ์ต์ ํ์ ํ๋์จ์ด ์ธ๋ถ ์ฌํญ์ ๋ฐ์ํ์ง ๋ชปํจ
- ์ํฌ๋ก๋ ํน์ฑ ๊ณ ๋ คํ๊ธฐ: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋๋ ์ฐ์ฐ ๋ฐ์ด๋๋ณด๋ค ์ ์ ์จ์ด ๋ ํ์
- ์ต๋ ์ ์ ์จ์ ๋ชฉํ๋ก ํ์ง ์๊ธฐ: ์ถฉ๋ถํ ์ ์ ์จ + ๋ค๋ฅธ ์ฑ๋ฅ ์์๋ฅผ ์ต์ ํ
- ์๊ณ๊ฐ ๊ด์ ์ผ๋ก ์ฌ๊ณ ํ๊ธฐ: 25-50% ์ ์ ์จ์ด๋ฉด ๋๋ถ๋ถ ๋๊ธฐ ์๊ฐ์ ์จ๊ธฐ๊ธฐ์ ์ถฉ๋ถ
- ๋ฆฌ์์ค ์ฌ์ฉ๋ ํ๋กํ์ผ๋งํ๊ธฐ: NSight Compute๋ก ์ค์ ๋ ์ง์คํฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๋น๋ ํ์
์กฐ์ฌ ์ ๊ทผ๋ฒ:
- ๋ฒค์น๋งํน๋ถํฐ ์์ - ๋จผ์ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ํ์ธ
- NSight Compute๋ก ํ๋กํ์ผ๋ง - ์ค์ ๋ฆฌ์์ค ์ฌ์ฉ๋๊ณผ ์ ์ ์จ ๋ฐ์ดํฐ ํ๋ณด
- ์ด๋ก ์ ์ ์ ์จ ๊ณ์ฐ - GPU ์คํ๊ณผ ์ ์ ์จ ๊ณต์ ํ์ฉ
- ์ด๋ก ๊ณผ ํ์ค ๋น๊ต - ๋ฏธ์คํฐ๋ฆฌ๊ฐ ๋๋ฌ๋๋ ์๊ฐ!
- ์ํฌ๋ก๋ ํน์ฑ ๊ณ ์ฐฐ - ์ด๋ก ๊ณผ ์ค์ ๊ฐ ์ ๋ค๋ฅผ ์ ์๋์ง ์๊ฐํด๋ณด๊ธฐ
์๋ฃจ์
์ฌ์ธต ํด์ค์ด ํฌํจ๋ ์์ ํ ํ์ด
์ด ์ ์ ์จ ํ์ ์ฌ๊ฑด์ ๋ฆฌ์์ค ์ฌ์ฉ์ด GPU ์ฑ๋ฅ์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง ๋ณด์ฌ์ฃผ๊ณ , ์ด๋ก ์ ์ ์ ์จ๊ณผ ์ค์ ์ฑ๋ฅ ์ฌ์ด์ ๋ณต์กํ ๊ด๊ณ๋ฅผ ๋๋ฌ๋ ๋๋ค.
์๋ ๊ตฌ์ฒด์ ์ธ ๊ณ์ฐ์ NVIDIA A10G (Ampere 8.6) - ํ ์คํธ์ ์ฌ์ฉ๋ GPU - ๊ธฐ์ค์ ๋๋ค. ๊ฒฐ๊ณผ๋ GPU ์ํคํ ์ฒ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง์ง๋ง, ๋ฐฉ๋ฒ๋ก ๊ณผ ํต์ฐฐ์ ๋ณดํธ์ ์ผ๋ก ์ ์ฉ๋ฉ๋๋ค.
pixi run gpu-specs๋ฅผ ์คํํ์ฌ ํ๋์จ์ด๋ณ ์์น๋ฅผ ํ์ธํ์ธ์.
๋ฆฌ์์ค ๋ถ์์ ํตํ ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ
NSight Compute ๋ฆฌ์์ค ๋ถ์:
์ค์ ํ๋กํ์ผ๋ง ๊ฒฐ๊ณผ (NVIDIA A10G - GPU์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅผ ์ ์์):
- Minimal: 19 ๋ ์ง์คํฐ, ~0KB ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ์ ์ ์จ 63.87%, 327.7ms
- Balanced: 25 ๋ ์ง์คํฐ, 16.4KB ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ์ ์ ์จ 65.44%, 329.4ms
- Sophisticated: 40 ๋ ์ง์คํฐ, 49.2KB ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ์ ์ ์จ 65.61%, 330.9ms
๋ฒค์น๋งํฌ ์ฑ๋ฅ ๊ทผ๊ฑฐ:
- ์ธ ์ปค๋ ๋ชจ๋ ๊ฑฐ์ ๋์ผํ ์ฑ๋ฅ์ ๋ณด์ (~327-331ms, <2% ์ฐจ์ด)
- ๋ฆฌ์์ค ์ฐจ์ด๊ฐ ํฌ์ง๋ง ๋ชจ๋ ๋น์ทํ ์ ์ ์จ์ ๋ฌ์ฑ (~64-66%)
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ์ ํ ์์ธ์ผ๋ก ์์ฉ
์ ์ ์จ ๊ณ์ฐ์ ์ค์ฒด
์ด๋ก ์ ์ ์ ์จ ๋ถ์ (NVIDIA A10G, Ampere 8.6):
GPU ์คํ (pixi run gpu-specs ์ถ๋ ฅ):
- SM๋น ๋ ์ง์คํฐ: 65,536
- SM๋น ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: 164KB (์ํคํ ์ฒ ์ต๋์น)
- SM๋น ์ต๋ ์ค๋ ๋: 1,536 (A10G ํ๋์จ์ด ์ ํ)
- ๋ธ๋ก๋น ์ค๋ ๋: 1,024 (์ปค๋ ์ค์ ๊ฐ)
- SM๋น ์ต๋ ๋ธ๋ก: 32
Minimal ์ปค๋ ๊ณ์ฐ:
๋ ์ง์คํฐ ์ ํ = 65,536 / (19 ร 1,024) = 3.36 ๋ธ๋ก/SM
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ํ = 164KB / 0KB = โ ๋ธ๋ก/SM
ํ๋์จ์ด ๋ธ๋ก ์ ํ = 32 ๋ธ๋ก/SM
์ค๋ ๋ ์ ํ = 1,536 / 1,024 = 1 ๋ธ๋ก/SM (๋ด๋ฆผ)
์ค์ ๋ธ๋ก = min(3, โ, 1) = 1 ๋ธ๋ก/SM
์ด๋ก ์ ์ ์ ์จ = (1 ร 1,024) / 1,536 = 66.7%
Balanced ์ปค๋ ๊ณ์ฐ:
๋ ์ง์คํฐ ์ ํ = 65,536 / (25 ร 1,024) = 2.56 ๋ธ๋ก/SM
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ํ = 164KB / 16.4KB = 10 ๋ธ๋ก/SM
ํ๋์จ์ด ๋ธ๋ก ์ ํ = 32 ๋ธ๋ก/SM
์ค๋ ๋ ์ ํ = 1,536 / 1,024 = 1 ๋ธ๋ก/SM (๋ด๋ฆผ)
์ค์ ๋ธ๋ก = min(2, 10, 1) = 1 ๋ธ๋ก/SM
์ด๋ก ์ ์ ์ ์จ = (1 ร 1,024) / 1,536 = 66.7%
Sophisticated ์ปค๋ ๊ณ์ฐ:
๋ ์ง์คํฐ ์ ํ = 65,536 / (40 ร 1,024) = 1.64 ๋ธ๋ก/SM
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ํ = 164KB / 49.2KB = 3.33 ๋ธ๋ก/SM
ํ๋์จ์ด ๋ธ๋ก ์ ํ = 32 ๋ธ๋ก/SM
์ค๋ ๋ ์ ํ = 1,536 / 1,024 = 1 ๋ธ๋ก/SM (๋ด๋ฆผ)
์ค์ ๋ธ๋ก = min(1, 3, 1) = 1 ๋ธ๋ก/SM
์ด๋ก ์ ์ ์ ์จ = (1 ร 1,024) / 1,536 = 66.7%
ํต์ฌ ๋ฐ๊ฒฌ: ์ด๋ก ๊ณผ ํ์ค์ด ์ผ์นํ๋ค!
- ์ด๋ก ์ : ๋ชจ๋ ์ปค๋ ~66.7% (A10G์ ์ค๋ ๋ ์ฉ๋์ ์ํด ์ ํ)
- ์ค์ธก: ๋ชจ๋ ~64-66% (๋งค์ฐ ๊ทผ์ ํ ๊ฒฐ๊ณผ!)
์ด๋ A10G์ ์ค๋ ๋ ์ ํ์ด ์ง๋ฐฐ์ ์์ ๋ณด์ฌ์ค๋๋ค - SM๋น ์ต๋ ์ค๋ ๋๊ฐ 1,536๊ฐ์ด๋ฏ๋ก 1,024 ์ค๋ ๋ ๋ธ๋ก์ 1๊ฐ๋ง ๋ค์ด๊ฐ๋๋ค. ์ด๋ก (66.7%)๊ณผ ์ค์ธก(~65%) ์ฌ์ด์ ์์ ์ฐจ์ด๋ ํ๋์จ์ด ์ค์ผ์ค๋ง ์ค๋ฒํค๋์ ๋๋ผ์ด๋ฒ ์ ์ฝ์์ ๋น๋กฏ๋ฉ๋๋ค.
์ด๋ก ๊ณผ ํ์ค์ด ๊ทผ์ ํ ์ด์
์ด๋ก ์ (66.7%)๊ณผ ์ค์ธก(~65%) ์ ์ ์จ ์ฌ์ด ์์ ์ฐจ์ด์ ์์ธ:
- ํ๋์จ์ด ์ค์ผ์ค๋ง ์ค๋ฒํค๋: ์ค์ ์ํ ์ค์ผ์ค๋ฌ๋ ์ด๋ก ์ ๊ณ์ฐ์ ๋์ด์๋ ์ค์ง์ ์ ์ฝ์ด ์์
- CUDA ๋ฐํ์ ์์ฝ: ๋๋ผ์ด๋ฒ์ ๋ฐํ์ ์ค๋ฒํค๋๊ฐ ๊ฐ์ฉ SM ๋ฆฌ์์ค๋ฅผ ์ฝ๊ฐ ์ค์
- ๋ฉ๋ชจ๋ฆฌ ์ปจํธ๋กค๋ฌ ์๋ฐ: A10G์ ๋ฉ๋ชจ๋ฆฌ ์๋ธ์์คํ ์ด ์ฝ๊ฐ์ ์ค์ผ์ค๋ง ์ ์ฝ์ ๋ง๋ฆ
- ์ ๋ ฅ ๋ฐ ์ด ๊ด๋ฆฌ: ๋์ ์ฃผํ์ ์กฐ์ ์ด ์ต๋ ์ฑ๋ฅ์ ์ํฅ
- ๋ช ๋ น์ด ์บ์ ํจ๊ณผ: ์ค์ ์ปค๋์ ์ ์ ์จ ๊ณ์ฐ์ ํฌ์ฐฉ๋์ง ์๋ ๋ช ๋ น์ด ํ์น ์ค๋ฒํค๋๊ฐ ์์
ํต์ฌ ํต์ฐฐ: ์ด๋ก ๊ณผ ์ค์ธก์ด ๊ทผ์ ํ๋ค๋ ๊ฒ(66.7% vs ~65%)์ ๋ ์ง์คํฐ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐจ์ด์ ๋ฌด๊ดํ๊ฒ A10G์ ์ค๋ ๋ ์ ํ์ด ์ธ ์ปค๋ ๋ชจ๋๋ฅผ ์ง๋ฐฐํ๋ค๋ ๋ป์ ๋๋ค. ์ง์ง ๋ณ๋ชฉ์ ์ ํํ ์ง์ด๋ธ ์ข์ ์ฌ๋ก์ ๋๋ค!
์ ์ ์จ ๋ฏธ์คํฐ๋ฆฌ ํด์ค
๋ฏธ์คํฐ๋ฆฌ์ ์ง์ง ์ ์ฒด:
- ๋ฆฌ์์ค ์ฐจ์ด๊ฐ ๊ทน์ ์ธ๋ฐ๋ ์ธ ์ปค๋ ๋ชจ๋ ๊ฑฐ์ ๋์ผํ ์ ์ ์จ์ ๋ฌ์ฑ (~64-66%)
- ์ฑ๋ฅ์ด ๋ณธ์ง์ ์ผ๋ก ๋์ผ (์ธ ์ปค๋ ๋ชจ๋ <2% ๋ณ๋)
- ์ด๋ก ์ด ์ ์ ์จ์ ์ ํํ ์์ธก (66.7% ์ด๋ก โ 65% ์ค์ธก)
- ๋ฏธ์คํฐ๋ฆฌ๋ ์ ์ ์จ ๋ถ์ผ์น๊ฐ ์๋๋๋ค - ๋ฆฌ์์ค ์ฌ์ฉ์ด ํฌ๊ฒ ๋ค๋ฅธ๋ฐ๋ ์ ์ ์ ์จ๊ณผ ์ฑ๋ฅ์ด ๋์ผํ์ง๊ฐ ์ง์ง ๋ฏธ์คํฐ๋ฆฌ์ ๋๋ค!
๋ฆฌ์์ค ์ฌ์ฉ์ด ๋ค๋ฅธ๋ฐ ์ฑ๋ฅ์ด ๋์ผํ ์ด์ :
SAXPY ์ํฌ๋ก๋์ ํน์ฑ:
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ: ๊ฐ ์ค๋ ๋์ ์ฐ์ฐ๋์ด ๊ทนํ ์ ์
(
y[i] = alpha * x[i] + y[i]) - ๋์ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ: ์ค๋ ๋๋น 2๊ฐ ๊ฐ ์ฝ๊ธฐ, 1๊ฐ ๊ฐ ์ฐ๊ธฐ
- ๋ฎ์ ์ฐ์ ๊ฐ๋: 12๋ฐ์ดํธ ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ๋น 2 FLOPS๋ง ์ํ
๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ๋ถ์ (A10G):
๋จ์ผ ์ปค๋ ํจ์ค ๋ถ์:
- ์
๋ ฅ ๋ฐฐ์ด: 32M ร 4๋ฐ์ดํธ ร 2 ๋ฐฐ์ด = 256MB ์ฝ๊ธฐ
- ์ถ๋ ฅ ๋ฐฐ์ด: 32M ร 4๋ฐ์ดํธ ร 1 ๋ฐฐ์ด = 128MB ์ฐ๊ธฐ
- ์ปค๋๋น ์ด๋: 384MB ๋ฉ๋ชจ๋ฆฌ ํธ๋ํฝ
์ต๋ ๋์ญํญ (A10G): 600 GB/s
๋จ์ผ ํจ์ค ์๊ฐ: 384MB / 600 GB/s โ 0.64ms ์ด๋ก ์ ์ต์์น
๋ฒค์น๋งํฌ ์๊ฐ: ~328ms (์ฌ๋ฌ ๋ฐ๋ณต + ์ค๋ฒํค๋ ํฌํจ)
์ค์ ์ฑ๋ฅ ๊ฒฐ์ ์์ธ:
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ: ๋ชจ๋ ์ปค๋์ด ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ํฌํ์ํด
- ์ฐ์ฐ ์ค๋ฒํค๋: Sophisticated ์ปค๋์ด ์ถ๊ฐ ์์ ์ ์ํ (๋ ์ง์คํฐ ์๋ฐ ํจ๊ณผ)
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ด์ : Balanced ์ปค๋์ด ์ผ๋ถ ์บ์ฑ ์ด์ ์ ์ป์
- ์ปดํ์ผ๋ฌ ์ต์ ํ: ์ต์ ์ปดํ์ผ๋ฌ๊ฐ ๊ฐ๋ฅํ ํ ๋ ์ง์คํฐ ์ฌ์ฉ์ ์ต์ํ
์ ์ ์จ ์๊ณ๊ฐ ๊ฐ๋ ์ดํดํ๊ธฐ
ํต์ฌ ํต์ฐฐ: ์ ์ ์จ์ โ์ต๋โ๊ฐ ์๋ โ์ถฉ๋ถํจโ์ ๋ฌธ์
๋๊ธฐ ์๊ฐ ์๋ ์๊ตฌ ์ฌํญ:
- ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ: ์ต์ GPU์์ ~500-800 ์ฌ์ดํด
- ์ํ ์ค์ผ์ค๋ง: GPU๋ ์ด ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๊ธฐ ์ํด ์ถฉ๋ถํ ์ํ๊ฐ ํ์
- ์ถฉ๋ถํ ์๊ณ๊ฐ: ๋ณดํต 25-50% ์ ์ ์จ์ด๋ฉด ๋๊ธฐ ์๊ฐ์ ํจ๊ณผ์ ์ผ๋ก ์จ๊ธธ ์ ์์
๋์ ์ ์ ์จ์ด ํญ์ ๋์์ด ๋์ง ์๋ ์ด์ :
๋ฆฌ์์ค ๊ฒฝ์:
- ๋ ๋ง์ ํ์ฑ ์ค๋ ๋๊ฐ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๋๊ณ ๊ฒฝ์
- ๋์ ์ ๊ทผ์ด ๋ง์์ง๋ฉด ์บ์ ์๋ฐ์ด ์ฆ๊ฐ
- ๋ ์ง์คํฐ/๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ์ด ๊ฐ๋ณ ์ค๋ ๋ ์ฑ๋ฅ์ ์ ํ์ํฌ ์ ์์
์ํฌ๋ก๋๋ณ ์ต์ ํ:
- ์ฐ์ฐ ๋ฐ์ด๋: ๋์ ์ ์ ์จ์ด ALU ํ์ดํ๋ผ์ธ ์ง์ฐ ์๊ฐ์ ์จ๊ธฐ๋ ๋ฐ ๋์
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋: ์ ์ ์จ๊ณผ ๋ฌด๊ดํ๊ฒ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ์ฑ๋ฅ์ ์ ํ
- ํผํฉ ์ํฌ๋ก๋: ์ ์ ์จ๊ณผ ๋ค๋ฅธ ์ต์ ํ ์์ ์ฌ์ด์์ ๊ท ํ ํ์
์ค์ ์ ์ ์จ ์ต์ ํ ์์น
์ฒด๊ณ์ ์ ์ ์จ ๋ถ์ ์ ๊ทผ๋ฒ:
1๋จ๊ณ: ์ด๋ก ์ ํ๊ณ ๊ณ์ฐ
# GPU ์คํ ํ์ธ
pixi run gpu-specs
2๋จ๊ณ: ์ค์ ์ฌ์ฉ๋ ํ๋กํ์ผ๋ง
# ๋ฆฌ์์ค ์๋น๋ ์ธก์
ncu --set=@occupancy --section=LaunchStats your_kernel
# ๋ฌ์ฑ๋ ์ ์ ์จ ์ธก์
ncu --metrics=smsp__warps_active.avg.pct_of_peak_sustained_active your_kernel
3๋จ๊ณ: ์ฑ๋ฅ ๊ฒ์ฆ
# ํญ์ ์ค์ ์ฑ๋ฅ ์ธก์ ์ผ๋ก ๊ฒ์ฆ
ncu --set=@roofline --section=MemoryWorkloadAnalysis your_kernel
๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์์ฌ๊ฒฐ์ ํ๋ ์์ํฌ:
์ ์ ์จ ๋ถ์ โ ์ต์ ํ ์ ๋ต:
๋์ ์ ์ ์จ (>70%) + ์ข์ ์ฑ๋ฅ:
โ ์ ์ ์จ์ ์ถฉ๋ถ, ๋ค๋ฅธ ๋ณ๋ชฉ์ ์ง์ค
๋ฎ์ ์ ์ ์จ (<30%) + ๋์ ์ฑ๋ฅ:
โ ๋ฆฌ์์ค ์ต์ ํ๋ฅผ ํตํด ์ ์ ์จ ํฅ์ ํ์
์ ๋นํ ์ ์ ์จ (50-70%) + ๋์ ์ฑ๋ฅ:
โ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ, ์บ์, ์ฐ์ฐ ๋ณ๋ชฉ ์กฐ์ฌ ํ์
๋ฎ์ ์ ์ ์จ (<30%) + ์ข์ ์ฑ๋ฅ:
โ ์ํฌ๋ก๋๊ฐ ๋์ ์ ์ ์จ์ ํ์๋ก ํ์ง ์์ (๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋)
์ค์ฉ์ ์ธ ์ ์ ์จ ์ต์ ํ ๊ธฐ๋ฒ
๋ ์ง์คํฐ ์ต์ ํ:
- ์ ์ ํ ๋ฐ์ดํฐ ํ์
์ฌ์ฉ:
float32vsfloat64,int32vsint64 - ์ค๊ฐ ๋ณ์ ์ต์ํ: ์ปดํ์ผ๋ฌ๊ฐ ์์ ์ ์ฅ์๋ฅผ ์ต์ ํํ๋๋ก ๋งก๊ธฐ๊ธฐ
- ๋ฃจํ ์ ๊ฐ ๊ณ ๋ ค: ์ ์ ์จ๊ณผ ๋ช ๋ น์ด ์์ค ๋ณ๋ ฌ์ฑ ์ฌ์ด์ ๊ท ํ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ:
- ํ์ํ ํฌ๊ธฐ ๊ณ์ฐ: ๊ณผ๋ค ํ ๋น ๋ฐฉ์ง
- ํ์ผ๋ง ์ ๋ต ๊ณ ๋ ค: ์ ์ ์จ๊ณผ ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ ์ฌ์ด์ ๊ท ํ
- ๋ฑ ํฌ ์ถฉ๋ ํํผ: ์ถฉ๋ ์๋ ์ ๊ทผ ํจํด ์ค๊ณ
๋ธ๋ก ํฌ๊ธฐ ํ๋:
- ์ฌ๋ฌ ์ค์ ํ ์คํธ: ๋ธ๋ก๋น 256, 512, 1024 ์ค๋ ๋
- ์ํ ํ์ฉ ๊ณ ๋ ค: ๊ฐ๋ฅํ๋ฉด ๋ถ์์ ํ ์ํ ๋ฐฉ์ง
- ์ ์ ์จ๊ณผ ๋ฆฌ์์ค ์ฌ์ฉ์ ๊ท ํ: ๋ธ๋ก์ด ํด์๋ก ๋ฆฌ์์ค ํ๊ณ์ ๋๋ฌํ ์ ์์
ํต์ฌ ์ ๋ฆฌ: A10G ๋ฏธ์คํฐ๋ฆฌ์์ ๋ณดํธ์ ์์น์ผ๋ก
์ด A10G ์ ์ ์จ ์กฐ์ฌ๋ ๋ชจ๋ GPU ์ต์ ํ์ ์ ์ฉ๋๋ ๋ช ํํ ํต์ฐฐ์ ์งํ์ ๋ณด์ฌ์ค๋๋ค:
A10G ๋ฐ๊ฒฌ ๊ณผ์ :
- ์ค๋ ๋ ์ ํ์ด ๋ชจ๋ ๊ฒ์ ์ง๋ฐฐ - 19 vs 40 ๋ ์ง์คํฐ, 0KB vs 49KB ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฐจ์ด์๋ ๋ถ๊ตฌํ๊ณ , A10G์ 1,536 ์ค๋ ๋ ์ฉ๋ ๋๋ฌธ์ ๋ชจ๋ ์ปค๋์ด SM๋น 1๋ธ๋ก์ด๋ผ๋ ๋์ผํ ์ ํ์ ๊ฑธ๋ฆผ
- ์ด๋ก ์ด ํ์ค๊ณผ ๊ทผ์ ํ๊ฒ ์ผ์น - 66.7% ์ด๋ก vs ~65% ์ค์ธก ์ ์ ์จ์ ์ฌ๋ฐ๋ฅธ ๋ณ๋ชฉ์ ์๋ณํ์ ๋ ๊ณ์ฐ์ด ์ ํจํจ์ ๋ณด์ฌ์ค
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ด ์ฑ๋ฅ์ ์ง๋ฐฐ - ๋์ผํ 66.7% ์ ์ ์จ์์, SAXPY์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ํน์ฑ(600 GB/s ํฌํ)์ด ๋ฆฌ์์ค ์ฐจ์ด์๋ ๋ถ๊ตฌํ๊ณ ๋์ผํ ์ฑ๋ฅ์ ์ค๋ช
๋ณดํธ์ ์ธ GPU ์ต์ ํ ์์น:
์ง์ง ๋ณ๋ชฉ ์๋ณํ๊ธฐ:
- ๋ชจ๋ ๋ฆฌ์์ค์์ ์ ์ ์จ ์ ํ์ ๊ณ์ฐ: ๋ ์ง์คํฐ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์ค๋ ๋ ์ฉ๋
- ๊ฐ์ฅ ์ ํ์ ์ธ ์์๊ฐ ๊ฒฐ์ ์ - ๋ ์ง์คํฐ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํญ์ ๋ณ๋ชฉ์ด๋ผ๊ณ ๊ฐ์ ํ์ง ๋ง ๊ฒ
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋(SAXPY ๊ฐ์)๋ ๋๊ธฐ ์๊ฐ์ ์จ๊ธธ ๋งํผ ์ถฉ๋ถํ ์ค๋ ๋๋ง ํ๋ณด๋๋ฉด ์ ์ ์จ์ด ์๋ ๋์ญํญ์ด ์ ํ ์์ธ
์ ์ ์จ์ด ์ค์ํ ๊ฒฝ์ฐ vs ์ค์ํ์ง ์์ ๊ฒฝ์ฐ:
- ๋์ ์ ์ ์จ์ด ์ค์: ์ฐ์ฐ ์ง์ฝ์ ์ปค๋(GEMM, ๊ณผํ ์๋ฎฌ๋ ์ด์ )์์ ALU ํ์ดํ๋ผ์ธ์ด ๋ฉ์ถ๋ ์๊ฐ์ ๋ค๋ฅธ ์ํ ์คํ์ผ๋ก ์จ๊ฒจ์ผ ํ๋ ๊ฒฝ์ฐ
- ์ ์ ์จ์ด ๋ ์ค์: ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฐ์ฐ(BLAS Level 1, ๋ฉ๋ชจ๋ฆฌ ๋ณต์ฌ)์์ ์ ์ ์จ์ด ์ ํ ์์ธ์ด ๋๊ธฐ ์ ์ ๋์ญํญ์ด ํฌํ๋๋ ๊ฒฝ์ฐ
- ์ ์ ์์ค: 60-70% ์ ์ ์จ์ด๋ฉด ๋๊ธฐ ์๊ฐ์ ์จ๊ธฐ๊ธฐ์ ์ถฉ๋ถ - ๊ทธ ์ด์์ ์ง์ง ๋ณ๋ชฉ์ ์ง์ค
์ค์ ์ต์ ํ ์ํฌํ๋ก์ฐ:
- ๋จผ์ ํ๋กํ์ผ๋ง (
ncu --set=@occupancy) - ์ค์ ๋ฆฌ์์ค ์ฌ์ฉ๋๊ณผ ์ ์ ์จ ์ธก์ - ์ด๋ก ์ ํ๊ณ ๊ณ์ฐ - GPU ์คํ ํ์ฉ (
pixi run gpu-specs) - ์ง๋ฐฐ์ ์ ์ฝ ์๋ณ - ๋ ์ง์คํฐ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ, ์ค๋ ๋ ์ฉ๋, ๋๋ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ
- ๋ณ๋ชฉ ์ต์ ํ - ์ ํ ์์ธ์ด ์๋ ๋ฆฌ์์ค์ ์๊ฐ ๋ญ๋นํ์ง ์๊ธฐ
- ์ข ๋จ๊ฐ ์ฑ๋ฅ์ผ๋ก ๊ฒ์ฆ - ์ ์ ์จ์ ์ฑ๋ฅ์ ์ํ ์๋จ์ด์ง ๋ชฉํ๊ฐ ์๋
A10G ์ฌ๋ก๋ ์ฒด๊ณ์ ๋ณ๋ชฉ ๋ถ์์ด ์ง๊ด๋ณด๋ค ๋ซ๋ค๋ ๊ฒ์ ์๋ฒฝํ๊ฒ ๋ณด์ฌ์ค๋๋ค - ์ค๋ ๋ ์ฉ๋์ด ์ง๋ฐฐ์ ์ด์๊ธฐ์ Sophisticated ์ปค๋์ ๋์ ๋ ์ง์คํฐ ์๋ฐ์ ๋ฌด๊ดํ๊ณ , ๋์ผํ ์ ์ ์จ๊ณผ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํฌํ๊ฐ ์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ๋ฅผ ์์ ํ ์ค๋ช ํด์ค๋๋ค.
Puzzle 32: ๋ฑ ํฌ ์ถฉ๋
์ด ํผ์ฆ์ด ์ค์ํ ์ด์
์ฑ๋ฅ ์ต์ ํ 3๋ถ์์ ์๊ฒฐ: Puzzle 30์์ GPU ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ๋ฐฐ์ฐ๊ณ , Puzzle 31์์ ์ ์ ์จ ์ต์ ํ๋ฅผ ์ดํดํ์ต๋๋ค. ์ด์ ์ฑ๋ฅ ์ต์ ํ ํผ์ฆ์ ๋ง์ง๋ง ์กฐ๊ฐ์ ๋ง์ถ ์ค๋น๊ฐ ๋์์ต๋๋ค: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ.
์จ๊ฒจ์ง ์ฑ๋ฅ ํจ์ : ์๋ฒฝํ ์ ์ ์จ, ์ต์ ์ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ, ๋์ผํ ์ํ์ ์ฐ์ฐ์ ๊ฐ์ถ GPU ์ปค๋์ ์์ฑํ๊ณ ๋ ์ค๋ ๋๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ๋ฐฉ์ ๋๋ฌธ์ ๊ทน์ ์ธ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๊ฒฝํํ ์ ์์ต๋๋ค. ๋ฑ ํฌ ์ถฉ๋์ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๊ฐ์ฅ ๋ฏธ๋ฌํ๋ฉด์๋ ์ํฅ๋ ฅ์ด ํฐ ์ฑ๋ฅ ํจ์ ์ค ํ๋์ ๋๋ค.
ํ์ต ์ฌ์ :
- Puzzle 30์์๋ NSight ํ๋กํ์ผ๋ง์ผ๋ก ์ฑ๋ฅ์ ์ธก์ ํ๊ณ ์ง๋จํ๋ ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค
- Puzzle 31์์๋ ์ ์ ์จ ๋ถ์์ ํตํด ๋ฆฌ์์ค ์ฌ์ฉ์ ์์ธกํ๊ณ ์ ์ดํ๋ ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค
- Puzzle 32์์๋ ์ต๋ ํจ์จ์ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ต์ ํํ๋ ๋ฒ์ ๋ฐฐ์๋๋ค
GPU๋ฅผ ๋์ด์ ์ ์ฉ๋๋ ์๋ฆฌ: ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน, ์ถฉ๋ ๊ฐ์ง, ์ฒด๊ณ์ ์ธ ์ ๊ทผ ํจํด ์ต์ ํ์ ์๋ฆฌ๋ CPU ์บ์ ๊ณ์ธต ๊ตฌ์กฐ๋ถํฐ ๋ถ์ฐ ๋ฉ๋ชจ๋ฆฌ ์ํคํ ์ฒ๊น์ง ๋ค์ํ ๋ณ๋ ฌ ์ปดํจํ ์์คํ ์ ์ ์ฉ๋ฉ๋๋ค.
์ฐธ๊ณ : ์ด ํผ์ฆ์ NVIDIA GPU ์ ์ฉ์ ๋๋ค
๋ฑ ํฌ ์ถฉ๋ ๋ถ์์ NVIDIA์ 32-๋ฑ ํฌ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํคํ ์ฒ์ NSight Compute ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ต์ ํ ์๋ฆฌ๋ ๋๋ฆฌ ์ ์ฉ๋์ง๋ง, ๊ตฌ์ฒด์ ์ธ ๊ธฐ๋ฒ๊ณผ ์ธก์ ๋ฐฉ๋ฒ์ NVIDIA CUDA์ ์ด์ ์ ๋ง์ถ๊ณ ์์ต๋๋ค.
๊ฐ์
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋์ ์ํ ๋ด์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ์ ์๋ก ๋ค๋ฅธ ์ฃผ์์ ๋์์ ์ ๊ทผํ ๋ ๋ฐ์ํ๋ฉฐ, ํ๋์จ์ด๊ฐ ์ด๋ฌํ ์ ๊ทผ์ ์ง๋ ฌํํ๋๋ก ๊ฐ์ ํฉ๋๋ค. ๋จ์ผ ์ฌ์ดํด ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ด์ด์ผ ํ ๊ฒ์ด ์ฌ๋ฌ ์ฌ์ดํด์ ์ง๋ ฌํ๋ ์ ๊ทผ์ผ๋ก ๋ฐ๋ ์ ์์ต๋๋ค.
๋ฐ๊ฒฌํ๊ฒ ๋ ๊ฒ:
- ํ๋์จ์ด ์์ค์์ GPU ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน์ด ์๋ํ๋ ๋ฐฉ์
- ๋์ผํ ์ปค๋์ด ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์์ ํฌ๊ฒ ๋ค๋ฅผ ์ ์๋์ง
- ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น๊ธฐ ์ ์ ๋ฑ ํฌ ์ถฉ๋์ ์์ธกํ๊ณ ์ธก์ ํ๋ ๋ฐฉ๋ฒ
- ์ถฉ๋ ์๋ ์๊ณ ๋ฆฌ์ฆ์ ์ค๊ณํ๊ธฐ ์ํ ์ ๋ฌธ์ ์ธ ์ต์ ํ ์ ๋ต
ํ์ ๋ฐฉ๋ฒ๋ก : ์ด ํผ์ฆ์ ์ด์ ์ฑ๋ฅ ํผ์ฆ๊ณผ ๋์ผํ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ ๊ทผ๋ฒ์ ๋ฐ๋ฆ ๋๋ค - ํ๋กํ์ผ๋ง ๋๊ตฌ๋ก ์จ๊ฒจ์ง ๋นํจ์จ์ ๋ฐํ๋ธ ๋ค์, ์ฒด๊ณ์ ์ธ ์ต์ ํ ์์น์ ์ ์ฉํ์ฌ ์ ๊ฑฐํฉ๋๋ค.
ํต์ฌ ๊ฐ๋
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํคํ ์ฒ์ ๊ธฐ์ด:
- 32-๋ฑ ํฌ ์ค๊ณ: NVIDIA GPU๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ 32๊ฐ์ ๋ ๋ฆฝ์ ์ธ ๋ฑ ํฌ๋ก ๊ตฌ์ฑํฉ๋๋ค
- ์ถฉ๋ ์ ํ: ์ถฉ๋ ์์(์ต์ ), N-way ์ถฉ๋(์ง๋ ฌํ), ๋ธ๋ก๋์บ์คํธ(์ต์ ํ)
- ์ ๊ทผ ํจํด ์ํ: ๋ฑ ํฌ ํ ๋น ๊ณต์๊ณผ ์ถฉ๋ ์์ธก
- ์ฑ๋ฅ ์ํฅ: ์ต์ ์ 1์ฌ์ดํด ์ ๊ทผ๋ถํฐ ์ต์ ์ 32์ฌ์ดํด ์ง๋ ฌํ๊น์ง
์ ๋ฌธ์ ์ธ ์ต์ ํ ๊ธฐ์ :
- ํจํด ๋ถ์: ๋ฑ ํน ๋์์ ์ํ์ ์์ธก
- ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก : ์ถฉ๋ ์ธก์ ์ ์ํ NSight Compute ๋ฉํธ๋ฆญ
- ์ค๊ณ ์์น: ์ถฉ๋ ์๋ ์๊ณ ๋ฆฌ์ฆ ํจํด๊ณผ ์๋ฐฉ ์ ๋ต
- ์ฑ๋ฅ ๊ฒ์ฆ: ์ฒด๊ณ์ ์ธ ์ธก์ ์ ํตํ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ต์ ํ
ํผ์ฆ ๊ตฌ์ฑ
์ด ํผ์ฆ์ ์ ๋ฌธ์ฑ์ ์ ์ง์ ์ผ๋ก ์์๊ฐ๋ ๋ ๊ฐ์ ์ํธ ๋ณด์์ ์ธ ์น์ ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค:
๐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ดํดํ๊ธฐ
๋ช ํํ ์ค๋ช ๊ณผ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ํตํด GPU ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน์ ์ด๋ก ์ ๊ธฐ์ด๋ฅผ ํ์ตํฉ๋๋ค.
๋ฐฐ์ฐ๊ฒ ๋ ๊ฒ:
- NVIDIA์ 32-๋ฑ ํฌ ์ํคํ ์ฒ๊ฐ ๋ณ๋ ฌ ์ ๊ทผ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ์
- ๋ฑ ํฌ ํ ๋น๊ณผ ์ถฉ๋ ์์ธก์ ์ํ
- ์ถฉ๋ ์ ํ๊ณผ ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ
- ์ด์ ๊ฐ๋ ๊ณผ์ ์ฐ๊ฒฐ (์ํ ์คํ, ์ ์ ์จ, ํ๋กํ์ผ๋ง)
ํต์ฌ ํต์ฐฐ: ํ๋์จ์ด๋ฅผ ์ดํดํ๋ฉด ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ์ฑ๋ฅ์ ์์ธกํ ์ ์์ต๋๋ค.
์ถฉ๋ ์๋ ํจํด
๋ฑ ํน ์ง์์ ํ์ฉํ์ฌ ์ ๋ฌธ ํ๋กํ์ผ๋ง ๊ธฐ๋ฒ์ผ๋ก ์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ๋ฅผ ํ์ด๋ด ๋๋ค.
ํ์ ๋์ ๊ณผ์ : ๋ ์ปค๋์ด ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ์ง๋ง ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ์ ๊ทน์ ์ผ๋ก ๋ค๋ฆ ๋๋ค. NSight Compute๋ฅผ ์ฌ์ฉํ์ฌ ํ ์ปค๋์ ์ฒด๊ณ์ ์ธ ๋ฑ ํฌ ์ถฉ๋์ ๊ฒช๊ณ ๋ค๋ฅธ ์ปค๋์ ์ต์ ์ ์ฑ๋ฅ์ ๋ฌ์ฑํ๋ ์ด์ ๋ฅผ ๋ฐํ๋ด์ธ์.
๊ธธ๋ฌ์ง๋ ์ญ๋: ํจํด ๋ถ์, ์ถฉ๋ ์ธก์ , ์ฒด๊ณ์ ์ต์ ํ, ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ฑ๋ฅ ๊ฐ์ .
์์ํ๊ธฐ
ํ์ต ๊ฒฝ๋ก:
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ดํดํ๊ธฐ - ์ด๋ก ์ ๊ธฐ์ด ์๊ธฐ
- ์ถฉ๋ ์๋ ํจํด - ์ค์ ์ต์ ํ์ ํ์ ์ญ๋ ์ ์ฉํ๊ธฐ
์ ์ ์กฐ๊ฑด:
- Puzzle 30์์ ์ตํ GPU ํ๋กํ์ผ๋ง ๊ฒฝํ
- Puzzle 31์์ ์ตํ ๋ฆฌ์์ค ์ต์ ํ ์ดํด
- Puzzle 8๊ณผ Puzzle 16์์ ์ตํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ๋ก๊ทธ๋๋ฐ ๊ฒฝํ
ํ๋์จ์ด ์๊ตฌ ์ฌํญ:
- CUDA ํดํท์ด ์ค์น๋ NVIDIA GPU
- NSight Compute ํ๋กํ์ผ๋ง ๋๊ตฌ
- ํ๋กํ์ผ๋ง ๋ฑ์ ์์กด์ฑ์
pixi๋ก ๊ด๋ฆฌ๋ฉ๋๋ค - ํธํ ๊ฐ๋ฅํ GPU ์ํคํ ์ฒ
์ต์ ํ์ ํจ๊ณผ
๋ฑ ํฌ ์ถฉ๋์ด ๊ฐ์ฅ ์ค์ํ ๊ฒฝ์ฐ:
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ๋ง์ ์ฌ์ฉํ๋ ํ๋ ฌ ๊ณฑ์
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ์ ์ฌ์ฉํ๋ ์คํ ์ค ์ฐ์ฐ
- ์คํธ๋ผ์ด๋ ๊ธฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ํจํด์ ์ฌ์ฉํ๋ ๋ณ๋ ฌ ๋ฆฌ๋์
์ ๋ฌธ ์ญ๋ ๊ฐ๋ฐ:
- ์ฒด๊ณ์ ์ต์ ํ: ๊ทผ๊ฑฐ ๊ธฐ๋ฐ ์ฑ๋ฅ ๊ฐ์ ๋ฐฉ๋ฒ๋ก
- ํ๋์จ์ด ์ธ์: ์ํํธ์จ์ด๊ฐ ํ๋์จ์ด ์ ์ฝ์ ์ด๋ป๊ฒ ๋งคํ๋๋์ง ์ดํด
- ํจํด ์ธ์: ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ์์ ๋ฌธ์ ๊ฐ ๋๋ ์ ๊ทผ ํจํด ์๋ณ
ํ์ต ์ฑ๊ณผ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ ์ค๊ณ, ์ธก์ , ์ต์ ํํ๋ ์ญ๋๊น์ง ๊ฐ์ถ๋ฉด GPU ์ฑ๋ฅ ์ต์ ํ ๋๊ตฌ ์ธํธ๊ฐ ์์ฑ๋ฉ๋๋ค - ์ ๋ฌธ๊ฐ ์์ค์ GPU ํ๋ก๊ทธ๋๋ฐ์ ์ํ ๋ง์ง๋ง ํผ์ฆ ์กฐ๊ฐ์ ๋๋ค.
์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์์ ์ ์ ์จ ๊ด๋ฆฌ๋ฅผ ๊ฑฐ์ณ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน ํจ์จ๊น์ง, ์ด ํผ์ฆ์ ์ต์ ์ GPU ์ฑ๋ฅ์ ์ํด์๋ ์ฌ๋ฌ ์์ค์์ ํ๋์จ์ด๋ฅผ ์ดํดํด์ผ ํ๋ค๋ ๊ฒ์ ๋ณด์ฌ์ค๋๋ค.
๐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ดํดํ๊ธฐ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๊ฒ์ ๋ฐํ์ผ๋ก
GPU ์ต์ ํ ์ฌ์ ์์ ์ด๋ฏธ ๋ง์ ๊ธธ์ ๊ฑธ์ด์์ต๋๋ค. Puzzle 8์์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ํจ์ฌ ๋น ๋ฅธ ๋ธ๋ก ๋ด๋ถ ์ ์ฅ์๋ฅผ ์ ๊ณตํ๋ค๋ ๊ฒ์ ๋ฐฐ์ ์ต๋๋ค. Puzzle 16์์๋ ํ๋ ฌ ๊ณฑ์ ์ปค๋์ด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ํ์ผ์ ์บ์ฑํ๊ณ , ๋น์ฉ์ด ํฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ค์ด๋ ๋ฐฉ๋ฒ์ ํ์ธํ์ต๋๋ค.
ํ์ง๋ง ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์๋ ๋ณ๋ ฌ ์ฐ์ฐ์ ์ง๋ ฌํ์ํฌ ์ ์๋ ์จ๊ฒจ์ง ์ฑ๋ฅ ํจ์ ์ด ๋์ฌ๋ฆฌ๊ณ ์์ต๋๋ค: ๋ฑ ํฌ ์ถฉ๋.
์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ: ๊ฒ๋ณด๊ธฐ์ ๋์ผํ ๋ฐฉ์์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ๋ ์ปค๋์ ์์ฑํ ์ ์์ต๋๋ค - ๋ ๋ค ๊ฐ์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ , ์๋ฒฝํ ์ ์ ์จ์ ๊ฐ์ง๋ฉฐ, ๊ฒฝ์ ์ํ๋ ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ํ๋๊ฐ ๋ค๋ฅธ ๊ฒ๋ณด๋ค 32๋ฐฐ ๋๋ฆฝ๋๋ค. ๋ฒ์ธ์? ์ค๋ ๋๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๋๋ค.
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ๋?
๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ฑ ํฌ๋ผ๊ณ ๋ถ๋ฆฌ๋ 32๊ฐ์ ๋ ๋ฆฝ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๋์ ์งํฉ์ด๋ผ๊ณ ์๊ฐํ์ธ์. ๊ฐ ๋ฑ ํฌ๋ ํด๋ก ์ฌ์ดํด๋น ํ๋์ ๋ฉ๋ชจ๋ฆฌ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ด ๋ฑ ํน ์์คํ ์ด ์กด์ฌํ๋ ๊ทผ๋ณธ์ ์ธ ์ด์ ๋ ํ๋์จ์ด ๋ณ๋ ฌ์ฑ ๋๋ฌธ์ ๋๋ค.
32๊ฐ ์ค๋ ๋๋ก ๊ตฌ์ฑ๋ ์ํ๊ฐ ๋์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํด์ผ ํ ๋, ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผํ๋ค๋ฉด GPU๋ 32๊ฐ์ ์์ฒญ์ ๋ชจ๋ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฑ ํฌ์ ์ ๊ทผํ๋ ค ํ๋ฉด ํ๋์จ์ด๋ ์ด๋ฅผ ์ง๋ ฌํํด์ผ ํ๋ฏ๋ก, 1์ฌ์ดํด์ด๋ฉด ๋ ์ฐ์ฐ์ด ์ฌ๋ฌ ์ฌ์ดํด๋ก ๋์ด๋ฉ๋๋ค.
๋ฑ ํฌ ์ฃผ์ ๋งคํ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ 4๋ฐ์ดํธ ์๋๋ ๋ค์ ๊ณต์์ ๋ฐ๋ผ ํน์ ๋ฑ ํฌ์ ๋ฐฐ์ ๋ฉ๋๋ค:
bank_id = (byte_address / 4) % 32
๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ฒ์ 128๋ฐ์ดํธ๊ฐ ๋ฑ ํฌ์ ๋งคํ๋๋ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
| Address Range | Bank ID | Example float32 Elements |
|---|---|---|
| 0-3 bytes | Bank 0 | shared[0] |
| 4-7 bytes | Bank 1 | shared[1] |
| 8-11 bytes | Bank 2 | shared[2] |
| โฆ | โฆ | โฆ |
| 124-127 bytes | Bank 31 | shared[31] |
| 128-131 bytes | Bank 0 | shared[32] |
| 132-135 bytes | Bank 1 | shared[33] |
ํต์ฌ ํต์ฐฐ: float32 ๋ฐฐ์ด์์ ๋ฑ
ํน ํจํด์ 32๊ฐ ์์๋ง๋ค ๋ฐ๋ณต๋๋ฉฐ, ์ด๋ 32๊ฐ
์ค๋ ๋๋ก ๊ตฌ์ฑ๋ ์ํ ํฌ๊ธฐ์ ์ ํํ ์ผ์นํฉ๋๋ค. ์ด๊ฒ์ ์ฐ์ฐ์ด ์๋๋๋ค - ์ต์ ์
๋ณ๋ ฌ ์ ๊ทผ์ ์ํด ์ค๊ณ๋ ๊ฒ์
๋๋ค.
๋ฑ ํฌ ์ถฉ๋์ ์ ํ
์ถฉ๋ ์์: ์ด์์ ์ธ ๊ฒฝ์ฐ
์ํ ๋ด ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผํ๋ฉด 32๊ฐ์ ์ ๊ทผ์ด ๋ชจ๋ 1์ฌ์ดํด์ ์๋ฃ๋ฉ๋๋ค:
# Perfect case: each thread accesses a different bank
shared[thread_idx.x] # Thread 0โBank 0, Thread 1โBank 1, ..., Thread 31โBank 31
๊ฒฐ๊ณผ: 32๊ฐ ๋ณ๋ ฌ ์ ๊ทผ, ์ด 1์ฌ์ดํด
N-way ๋ฑ ํฌ ์ถฉ๋
N๊ฐ์ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฑ ํฌ์ ์๋ก ๋ค๋ฅธ ์ฃผ์์ ์ ๊ทผํ๋ฉด ํ๋์จ์ด๊ฐ ์ ๊ทผ์ ์ง๋ ฌํํฉ๋๋ค:
# 2-way conflict: stride-2 access pattern
shared[thread_idx.x * 2] # Thread 0,16โBank 0; Thread 1,17โBank 1; etc.
๊ฒฐ๊ณผ: ๋ฑ ํฌ๋น 2ํ ์ ๊ทผ, ์ด 2์ฌ์ดํด (ํจ์จ 50%)
# Worst case: all threads access different addresses in Bank 0
shared[thread_idx.x * 32] # All threadsโBank 0
๊ฒฐ๊ณผ: 32ํ ์ง๋ ฌํ๋ ์ ๊ทผ, ์ด 32์ฌ์ดํด (ํจ์จ 3%)
๋ธ๋ก๋์บ์คํธ ์์ธ
์ถฉ๋ ๊ท์น์๋ ํ ๊ฐ์ง ์ค์ํ ์์ธ๊ฐ ์์ต๋๋ค: ๋ธ๋ก๋์บ์คํธ ์ ๊ทผ. ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ์ฃผ์๋ฅผ ์ฝ์ผ๋ฉด ํ๋์จ์ด๊ฐ ์ด๋ฅผ ๋จ์ผ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ผ๋ก ์ต์ ํํฉ๋๋ค:
# Broadcast: all threads read the same value
constant = shared[0] # All threads read shared[0]
๊ฒฐ๊ณผ: 1ํ ์ ๊ทผ์ผ๋ก 32๊ฐ ์ค๋ ๋์ ๋ธ๋ก๋์บ์คํธ, ์ด 1์ฌ์ดํด
์ด ์ต์ ํ๊ฐ ์กด์ฌํ๋ ์ด์ ๋ ๋ธ๋ก๋์บ์คํธ๊ฐ ํํ ํจํด(์์ ๋ก๋ฉ, ๋ฆฌ๋์ ์ฐ์ฐ ๋ฑ)์ด๊ณ , ํ๋์จ์ด๊ฐ ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์์ด ๋จ์ผ ๊ฐ์ ๋ชจ๋ ์ค๋ ๋์ ๋ณต์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฑ ํฌ ์ถฉ๋์ด ์ค์ํ ์ด์
์ฑ๋ฅ ์ํฅ
๋ฑ ํฌ ์ถฉ๋์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์๊ฐ์ ์ง์ ์ ์ผ๋ก ๋ฐฐ๊ฐ์ํต๋๋ค:
| ์ถฉ๋ ์ ํ | ์ ๊ทผ ์๊ฐ | ํจ์จ | ์ฑ๋ฅ ์ํฅ |
|---|---|---|---|
| ์ถฉ๋ ์์ | 1์ฌ์ดํด | 100% | ๊ธฐ์ค์ |
| 2-way conflict | 2์ฌ์ดํด | 50% | 2๋ฐฐ ๋๋ฆผ |
| 4-way conflict | 4์ฌ์ดํด | 25% | 4๋ฐฐ ๋๋ฆผ |
| 32-way conflict | 32์ฌ์ดํด | 3% | 32๋ฐฐ ๋๋ฆผ |
์ค์ ๋งฅ๋ฝ
Puzzle 30์์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด ๊ทน์ ์ธ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด๋ธ๋ค๋ ๊ฒ์ ๋ฐฐ์ ์ต๋๋ค. ๋ฑ ํฌ ์ถฉ๋์ ์ด ์๋ฆฌ๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ค์์ ์๋ํ๋ ๋ ๋ค๋ฅธ ์ฌ๋ก์ ๋๋ค.
์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ณํฉ์ด DRAM ๋์ญํญ ํ์ฉ์ ์ํฅ์ ์ฃผ๋ ๊ฒ์ฒ๋ผ, ๋ฑ ํฌ ์ถฉ๋์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋์ ์ํฅ์ ์ค๋๋ค. ์ฐจ์ด๋ ๊ท๋ชจ์ ์์ต๋๋ค: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ์๋ฐฑ ์ฌ์ดํด์ด์ง๋ง, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ถฉ๋์ ์ ๊ทผ๋น ๋ช ์ฌ์ดํด๋ง ์ถ๊ฐํฉ๋๋ค. ๊ทธ๋ฌ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ง์ค์ ์ผ๋ก ์ฌ์ฉํ๋ ์ฐ์ฐ ์ง์ฝ์ ์ปค๋์์๋ ์ด โ๋ช ์ฌ์ดํดโ์ด ๋น ๋ฅด๊ฒ ๋์ ๋ฉ๋๋ค.
์ํ ์คํ๊ณผ์ ๊ด๊ณ
Puzzle 24์์ ์ํ๊ฐ SIMT(Single Instruction, Multiple Thread) ๋ฐฉ์์ผ๋ก ์คํ๋๋ค๋ ๊ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ํ๊ฐ ๋ฑ ํฌ ์ถฉ๋์ ๋ถ๋ชํ๋ฉด ์ง๋ ฌํ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์๋ฃ๋ ๋๊น์ง 32๊ฐ ์ค๋ ๋ ๋ชจ๋๊ฐ ๋๊ธฐํด์ผ ํฉ๋๋ค. ์ด ๋๊ธฐ ์๊ฐ์ ์ถฉ๋์ ์ผ์ผํจ ์ค๋ ๋๋ง์ด ์๋๋ผ ์ํ ์ ์ฒด์ ์งํ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
์ด๋ Puzzle 31์ ์ ์ ์จ ๊ฐ๋ ๊ณผ ์ฐ๊ฒฐ๋ฉ๋๋ค: ๋ฑ ํฌ ์ถฉ๋์ ์ํ๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ง์ฐ ์๊ฐ์ ํจ๊ณผ์ ์ผ๋ก ์จ๊ธฐ๋ ๊ฒ์ ๋ฐฉํดํ์ฌ, ๋์ ์ ์ ์จ์ ์ค์ง์ ์ธ ์ด์ ์ ์ค์ผ ์ ์์ต๋๋ค.
๋ฑ ํฌ ์ถฉ๋ ๊ฐ์งํ๊ธฐ
์๊ฐ์ ํจํด ์ธ์
์ ๊ทผ ํจํด์ ๋ถ์ํ๋ฉด ๋ฑ ํฌ ์ถฉ๋์ ์์ธกํ ์ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค:
์์ฐจ ์ ๊ทผ (์ถฉ๋ ์์):
# Thread ID: 0 1 2 3 ... 31
# Address: 0 4 8 12 ... 124
# Bank: 0 1 2 3 ... 31 โ
All different banks
Stride-2 ์ ๊ทผ (2-way conflict):
# Thread ID: 0 1 2 3 ... 15 16 17 18 ... 31
# Address: 0 8 16 24 ... 120 4 12 20 ... 124
# Bank: 0 2 4 6 ... 30 1 3 5 ... 31
# Conflict: Banks 0,2,4... have 2 threads each โ
Stride-32 ์ ๊ทผ (32-way conflict):
# Thread ID: 0 1 2 3 ... 31
# Address: 0 128 256 384 ... 3968
# Bank: 0 0 0 0 ... 0 โ All threadsโBank 0
NSight Compute(ncu)๋ฅผ ์ฌ์ฉํ ํ๋กํ์ผ๋ง
Puzzle 30์์ ๋ฐฐ์ด ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ ๋ฐํ์ผ๋ก, ๋ฑ ํฌ ์ถฉ๋์ ์ ๋์ ์ผ๋ก ์ธก์ ํ ์ ์์ต๋๋ค:
# Key metrics for shared memory bank conflicts
ncu --metrics=l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld,l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st your_kernel
# Additional context metrics
ncu --metrics=smsp__sass_average_branch_targets_threads_uniform.pct your_kernel
ncu --metrics=smsp__warps_issue_stalled_membar_per_warp_active.pct your_kernel
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld์
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st ๋ฉํธ๋ฆญ์ ์ปค๋ ์คํ ์ค
๋ก๋ ๋ฐ ์คํ ์ด ์ฐ์ฐ์ ๋ฑ
ํฌ ์ถฉ๋ ํ์๋ฅผ ์ง์ ์นด์ดํธํฉ๋๋ค. ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
ํ์์ ๊ฒฐํฉํ๋ฉด ์ถฉ๋ ๋น์จ์ ๊ตฌํ ์ ์์ผ๋ฉฐ, ์ด๋ ํต์ฌ์ ์ธ ์ฑ๋ฅ ์งํ์
๋๋ค.
๋ฑ ํฌ ์ถฉ๋์ด ๊ฐ์ฅ ์ค์ํ ๊ฒฝ์ฐ
์ฐ์ฐ ์ง์ฝ์ ์ปค๋
๋ฑ ํฌ ์ถฉ๋์ ๋ค์๊ณผ ๊ฐ์ ์ปค๋์์ ๊ฐ์ฅ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค:
- ํ์ดํธํ ๋ฃจํ ์์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์์ฃผ ์ ๊ทผํ๋ ๊ฒฝ์ฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๋น ์ฐ์ฐ๋์ด ์ ์ ๊ฒฝ์ฐ
- ์ปค๋์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋๊ฐ ์๋ ์ฐ์ฐ ๋ฐ์ด๋์ธ ๊ฒฝ์ฐ
๋ํ์ ์ธ ์๋๋ฆฌ์ค:
- ํ๋ ฌ ๊ณฑ์ ๋ด๋ถ ๋ฃจํ (Puzzle 16์ ํ์ผ๋ง ๋ฒ์ ๊ณผ ๊ฐ์)
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ์ ์ฌ์ฉํ๋ ์คํ ์ค ์ฐ์ฐ
- ๋ณ๋ ฌ ๋ฆฌ๋์ ์ฐ์ฐ
๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ vs ์ฐ์ฐ ๋ฐ์ด๋ ํธ๋ ์ด๋์คํ
Puzzle 31์์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋์์๋ ์ ์ ์จ์ด ๋ ์ค์ํ๋ค๋ ๊ฒ์ ๋ณด์๋ฏ์ด, ์ปค๋์ด ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ์ ๋ณ๋ชฉ์ด ๊ฑธ๋ฆฌ๊ฑฐ๋ ์ฐ์ ๊ฐ๋๊ฐ ๋งค์ฐ ๋ฎ์ ๊ฒฝ์ฐ์๋ ๋ฑ ํฌ ์ถฉ๋์ ์ํฅ๋ ์ค์ด๋ญ๋๋ค.
๊ทธ๋ฌ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋ง์ ์ปค๋์ ๋ฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์์ ์ฐ์ฐ ๋ฐ์ด๋๋ก ์ ํํ๊ธฐ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํฉ๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ ๋ฑ ํฌ ์ถฉ๋์ ์ ์ด์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋์ ํ ์ด์ ์๋ ์ฑ๋ฅ ํฅ์์ ๋ฌ์ฑํ์ง ๋ชปํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
์์ผ๋ก์ ๋ฐฉํฅ
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน์ ์ดํดํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ธฐ์ด๋ฅผ ๊ฐ์ถ๊ฒ ๋ฉ๋๋ค:
- ์ ๊ทผ ํจํด์ ๋ถ์ํ์ฌ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ์ฑ๋ฅ์ ์์ธก
- ์ฒด๊ณ์ ์ธ ํ๋กํ์ผ๋ง ์ ๊ทผ๋ฒ์ผ๋ก ์ฑ๋ฅ ์ ํ๋ฅผ ์ง๋จ
- ๋์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฒ๋ฆฌ๋์ ์ ์งํ๋ ์ถฉ๋ ์๋ ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ
- ์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ ์ฌ์ด์ ๊ท ํ ์กํ ํ๋จ
๋ค์ ์น์ ์์๋ ์ด ์ง์์ ์ค์ต์ ์ ์ฉํ์ฌ ์ผ๋ฐ์ ์ธ ์ถฉ๋ ํจํด๊ณผ ํด๊ฒฐ์ฑ ์ ์ง์ ๋ค๋ค๋ด ๋๋ค - ์ด๋ก ์ ์ดํด๋ฅผ ์ค์ ์ต์ ํ ์ญ๋์ผ๋ก ๋ฐ๊พธ๋ ๊ณผ์ ์ ๋๋ค.
์ถฉ๋ ์๋ ํจํด
์ฐธ๊ณ : ์ด ์น์ ์ NVIDIA GPU ์ ์ฉ์ ๋๋ค
์ฌ๊ธฐ์ ๋ค๋ฃจ๋ ๋ฑ ํฌ ์ถฉ๋ ๋ถ์๊ณผ ํ๋กํ์ผ๋ง ๊ธฐ๋ฒ์ NVIDIA GPU์ ํนํ๋์ด ์์ต๋๋ค. ํ๋กํ์ผ๋ง ๋ช ๋ น์ NVIDIA CUDA ํดํท์ ํฌํจ๋ NSight Compute ๋๊ตฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ํ๋กํ์ผ๋ง ์ญ๋์ ๋ฐํ์ผ๋ก
Puzzle 30์์ GPU ํ๋กํ์ผ๋ง ๊ธฐ์ด๋ฅผ ๋ฐฐ์ฐ๊ณ , Puzzle 31์์ ๋ฆฌ์์ค ์ต์ ํ๋ฅผ ์ดํดํ์ต๋๋ค. ์ด์ ๋ฐฐ์ด ํ์ ๊ธฐ์ ์ ์๋ก์ด ์ฑ๋ฅ ๋ฏธ์คํฐ๋ฆฌ์ ์ ์ฉํ ์ฐจ๋ก์ ๋๋ค: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋.
ํ์ ๋์ ๊ณผ์ : ๋์ผํ ์ํ์ ์ฐ์ฐ((input + 10) * 2)์ ์ํํ๋ ๋ GPU
์ปค๋์ด ์์ต๋๋ค. ๋ ๋ค ์ ํํ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋
๋๋ค. ๊ฐ์ ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ
์ฌ์ฉํฉ๋๋ค. ์ ์ ์จ๋ ๋์ผํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ํ๋๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ๋ฐฉ์
๋๋ฌธ์ ์ฒด๊ณ์ ์ธ ์ฑ๋ฅ ์ ํ๋ฅผ ๊ฒช์ต๋๋ค.
์ฌ๋ฌ๋ถ์ ์๋ฌด: ์ง๊ธ๊น์ง ๋ฐฐ์ด ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ผ๋ก ์ด ์จ๊ฒจ์ง ์ฑ๋ฅ ํจ์ ์ ๋ฐํ๋ด๊ณ , ์ค์ GPU ํ๋ก๊ทธ๋๋ฐ์์ ๋ฑ ํฌ ์ถฉ๋์ด ์ธ์ ์ค์ํ์ง ์ดํดํ์ธ์.
๊ฐ์
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋์ ์ํ ๋ด์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ์ ์๋ก ๋ค๋ฅธ ์ฃผ์์ ๋์์ ์ ๊ทผํ ๋ ๋ฐ์ํฉ๋๋ค. ์ด ํ์ ์ฌ๊ฑด์์๋ ๋์กฐ์ ์ธ ์ ๊ทผ ํจํด์ ๊ฐ์ง ๋ ์ปค๋์ ์ดํด๋ด ๋๋ค:
comptime SIZE = 8 * 1024 # 8K elements - small enough to focus on shared memory patterns
comptime TPB = 256 # Threads per block - divisible by 32 (warp size)
comptime THREADS_PER_BLOCK = (TPB, 1)
comptime BLOCKS_PER_GRID = (SIZE // TPB, 1)
comptime dtype = DType.float32
comptime layout = row_major[SIZE]()
comptime LayoutType = type_of(layout)
def no_conflict_kernel(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
"""Perfect shared memory access - no bank conflicts.
Each thread accesses a different bank: thread_idx.x maps to bank thread_idx.x % 32.
This achieves optimal shared memory bandwidth utilization.
"""
# Shared memory buffer - each thread loads one element
var shared_buf = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Load from global memory to shared memory - no conflicts
if global_i < size:
shared_buf[local_i] = (
input[global_i] + 10.0
) # Add 10 as simple operation
barrier() # Synchronize shared memory writes
# Read back from shared memory and write to output - no conflicts
if global_i < size:
output[global_i] = shared_buf[local_i] * 2.0 # Multiply by 2
barrier() # Ensure completion
def two_way_conflict_kernel(
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
size: Int,
):
"""Stride-2 shared memory access - creates 2-way bank conflicts.
Threads 0,16 -> Bank 0, Threads 1,17 -> Bank 1, etc.
Each bank serves 2 threads, doubling access time.
"""
# Shared memory buffer - stride-2 access pattern creates conflicts
var shared_buf = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TPB]())
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# CONFLICT: stride-2 access creates 2-way bank conflicts
var conflict_index = (local_i * 2) % TPB
# Load with bank conflicts
if global_i < size:
shared_buf[conflict_index] = (
input[global_i] + 10.0
) # Same operation as no-conflict
barrier() # Synchronize shared memory writes
# Read back with same conflicts
if global_i < size:
output[global_i] = (
shared_buf[conflict_index] * 2.0
) # Same operation as no-conflict
barrier() # Ensure completion
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p32/p32.mojo
๋ฏธ์คํฐ๋ฆฌ: ์ด ์ปค๋๋ค์ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ์ง๋ง ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจ์จ์ ๊ทน์ ์ผ๋ก ๋ค๋ฆ ๋๋ค. ์ฒด๊ณ์ ์ธ ํ๋กํ์ผ๋ง ๋ถ์์ ํตํด ๊ทธ ์ด์ ๋ฅผ ๋ฐํ๋ด๋ ๊ฒ์ด ์๋ฌด์ ๋๋ค.
๊ตฌ์ฑ
์๊ตฌ ์ฌํญ:
- Puzzle 30์ CUDA ํดํท๊ณผ NSight Compute๊ฐ ์ค์น๋ NVIDIA GPU
- ์ด์ ์น์ ์์ ๋ค๋ฃฌ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํน ๊ฐ๋ ์ ๋ํ ์ดํด
์ปค๋ ์ค์ :
comptime SIZE = 8 * 1024 # 8K elements - focus on shared memory patterns
comptime TPB = 256 # 256 threads per block (8 warps)
comptime BLOCKS_PER_GRID = (SIZE // TPB, 1) # 32 blocks
ํต์ฌ ํต์ฐฐ: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ ํ์ด ์๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ๊ณผ๋ฅผ ๋ถ๊ฐํ๊ธฐ ์ํด ๋ฌธ์ ํฌ๊ธฐ๋ฅผ ์๋์ ์ผ๋ก ์ด์ ํผ์ฆ๋ณด๋ค ์๊ฒ ์ค์ ํ์ต๋๋ค.
์กฐ์ฌ ๊ณผ์
Step 1: ์ ํ์ฑ ๊ฒ์ฆ
pixi shell -e nvidia
mojo problems/p32/p32.mojo --test
๋ ์ปค๋ ๋ชจ๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ฑ ํฌ ์ถฉ๋์ด ์ ํ์ฑ์ด ์๋ ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น๋ค๋ ๊ฒ์ ํ์ธํฉ๋๋ค.
Step 2: ์ฑ๋ฅ ๊ธฐ์ค์ ๋ฒค์น๋งํฌ
mojo problems/p32/p32.mojo --benchmark
์คํ ์๊ฐ์ ๊ธฐ๋กํ์ธ์. ์ํฌ๋ก๋๊ฐ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์ํด ์ง๋ฐฐ๋๊ธฐ ๋๋ฌธ์ ๋น์ทํ ์ฑ๋ฅ์ด ๋์ฌ ์ ์์ง๋ง, ๋ฑ ํฌ ์ถฉ๋์ ํ๋กํ์ผ๋ง ๋ฉํธ๋ฆญ์ ํตํด ๋๋ฌ๋ฉ๋๋ค.
Step 3: ํ๋กํ์ผ๋ง์ฉ ๋น๋
mojo build --debug-level=full problems/p32/p32.mojo -o problems/p32/p32_profiler
Step 4: ๋ฑ ํฌ ์ถฉ๋ ํ๋กํ์ผ๋ง
NSight Compute๋ฅผ ์ฌ์ฉํ์ฌ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋์ ์ ๋์ ์ผ๋ก ์ธก์ ํฉ๋๋ค:
# Profile no-conflict kernel
ncu --metrics=l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld,l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st problems/p32/p32_profiler --no-conflict
๊ทธ๋ฆฌ๊ณ
# Profile two-way conflict kernel
ncu --metrics=l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld,l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st problems/p32/p32_profiler --two-way
๊ธฐ๋กํ ํต์ฌ ๋ฉํธ๋ฆญ:
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld.sum- ๋ก๋ ์ถฉ๋l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st.sum- ์คํ ์ด ์ถฉ๋
Step 5: ์ ๊ทผ ํจํด ๋ถ์
ํ๋กํ์ผ๋ง ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ์ผ๋ก ์ํ์ ์ ๊ทผ ํจํด์ ๋ถ์ํฉ๋๋ค:
์ถฉ๋ ์๋ ์ปค๋ ์ ๊ทผ ํจํด:
# Thread mapping: thread_idx.x directly maps to shared memory index
shared_buf[thread_idx.x] # Thread 0โIndex 0, Thread 1โIndex 1, etc.
# Bank mapping: Index % 32 = Bank ID
# Result: Thread 0โBank 0, Thread 1โBank 1, ..., Thread 31โBank 31
2-way ์ถฉ๋ ์ปค๋ ์ ๊ทผ ํจํด:
# Thread mapping with stride-2 modulo operation
shared_buf[(thread_idx.x * 2) % TPB]
# For threads 0-31: Index 0,2,4,6,...,62, then wraps to 64,66,...,126, then 0,2,4..
# Bank mapping examples:
# Thread 0 โ Index 0 โ Bank 0
# Thread 16 โ Index 32 โ Bank 0 (conflict!)
# Thread 1 โ Index 2 โ Bank 2
# Thread 17 โ Index 34 โ Bank 2 (conflict!)
๋์ ๊ณผ์ : ๋ฑ ํฌ ์ถฉ๋ ๋ฏธ์คํฐ๋ฆฌ๋ฅผ ํ์ด๋ณด์ธ์
์์ ์กฐ์ฌ ๋จ๊ณ๋ฅผ ์๋ฃํ ํ, ๋ค์ ๋ถ์ ์ง๋ฌธ์ ๋ตํ์ธ์:
์ฑ๋ฅ ๋ถ์ (Step 1-2)
- ๋ ์ปค๋์ด ๋์ผํ ์ํ์ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋์?
- ์ปค๋ ๊ฐ ์คํ ์๊ฐ ์ฐจ์ด๊ฐ ์๋์?
- ์ ๊ทผ ํจํด์ด ๋ค๋ฅธ๋ฐ๋ ์ฑ๋ฅ์ด ๋น์ทํ ์ ์๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
๋ฑ ํฌ ์ถฉ๋ ํ๋กํ์ผ๋ง (Step 4)
- ์ถฉ๋ ์๋ ์ปค๋์ ๋ก๋์ ์คํ ์ด์์ ๋ช ๊ฑด์ ๋ฑ ํฌ ์ถฉ๋์ ๋ฐ์์ํค๋์?
- 2-way ์ถฉ๋ ์ปค๋์ ๋ก๋์ ์คํ ์ด์์ ๋ช ๊ฑด์ ๋ฑ ํฌ ์ถฉ๋์ ๋ฐ์์ํค๋์?
- ๋ ์ปค๋ ๊ฐ ์ด ์ถฉ๋ ํ์ ์ฐจ์ด๋ ์ผ๋ง์ธ๊ฐ์?
์ ๊ทผ ํจํด ๋ถ์ (Step 5)
- ์ถฉ๋ ์๋ ์ปค๋์์ Thread 0์ ์ด๋ค ๋ฑ ํฌ์ ์ ๊ทผํ๋์? Thread 31์?
- 2-way ์ถฉ๋ ์ปค๋์์ Bank 0์ ์ ๊ทผํ๋ ์ค๋ ๋๋? Bank 2์ ์ ๊ทผํ๋ ์ค๋ ๋๋?
- ์ถฉ๋ ์ปค๋์์ ๊ฐ์ ๋ฑ ํฌ๋ฅผ ๋๊ณ ๊ฒฝ์ํ๋ ์ค๋ ๋๋ ๋ช ๊ฐ์ธ๊ฐ์?
๋ฑ ํฌ ์ถฉ๋ ํ์ ์์
- ์ถฉ๋ ์๋ ์ปค๋์ ์ถฉ๋์ด 0์ธ๋ฐ, 2-way ์ถฉ๋ ์ปค๋์์๋ ์ธก์ ๊ฐ๋ฅํ ์ถฉ๋์ด ๋ํ๋๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
- stride-2 ์ ๊ทผ ํจํด
(thread_idx.x * 2) % TPB๋ ์ด๋ป๊ฒ ์ฒด๊ณ์ ์ธ ์ถฉ๋์ ๋ง๋ค์ด๋ด๋์? - ๋ฑ ํฌ ์ถฉ๋์ด ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ปค๋๋ณด๋ค ์ฐ์ฐ ์ง์ฝ์ ์ปค๋์์ ๋ ์ค์ํ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
์ค์ ์์ฌ์
- ๋ฑ ํฌ ์ถฉ๋์ด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์น ๊ฒ์ผ๋ก ์์๋๋ ๊ฒฝ์ฐ๋ ์ธ์ ์ธ๊ฐ์?
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๊ธฐ ์ ์ ๋ฑ ํฌ ์ถฉ๋ ํจํด์ ์ด๋ป๊ฒ ์์ธกํ ์ ์๋์?
- ํ๋ ฌ ์ฐ์ฐ๊ณผ ์คํ ์ค ์ฐ์ฐ์์ ๋ฑ ํฌ ์ถฉ๋์ ํผํ๋ ๋ฐ ๋์์ด ๋๋ ์ค๊ณ ์์น์ ๋ฌด์์ธ๊ฐ์?
ํ
๋ฑ ํฌ ์ถฉ๋ ํ์ ๋๊ตฌ ๋ชจ์:
- NSight Compute ๋ฉํธ๋ฆญ - ์ ๋ฐํ ์ธก์ ์ผ๋ก ์ถฉ๋์ ์ ๋ํ
- ์ ๊ทผ ํจํด ์๊ฐํ - ์ค๋ ๋ ์ธ๋ฑ์ค๋ฅผ ๋ฑ ํฌ์ ์ฒด๊ณ์ ์ผ๋ก ๋งคํ
- ์ํ์ ๋ถ์ - ๋ชจ๋๋ก ์ฐ์ฐ์ผ๋ก ์ถฉ๋ ์์ธก
- ์ํฌ๋ก๋ ํน์ฑ - ์ถฉ๋์ด ์ค์ํ ๊ฒฝ์ฐ์ ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ ์ดํด
ํต์ฌ ์กฐ์ฌ ์์น:
- ์ฒด๊ณ์ ์ผ๋ก ์ธก์ ํ๊ธฐ: ์ถฉ๋์ ์ถ์ธกํ์ง ๋ง๊ณ ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ์ฌ์ฉ
- ์ ๊ทผ ํจํด ์๊ฐํํ๊ธฐ: ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ ์ค๋ ๋-๋ฑ ํฌ ๋งคํ์ ๊ทธ๋ ค๋ณด๊ธฐ
- ์ํฌ๋ก๋ ๋งฅ๋ฝ ๊ณ ๋ คํ๊ธฐ: ๋ฑ ํฌ ์ถฉ๋์ ์ฐ์ฐ ์ง์ฝ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์์ ๊ฐ์ฅ ์ค์
- ์๋ฐฉ์ ์ผ๋ก ์ฌ๊ณ ํ๊ธฐ: ์ฒ์๋ถํฐ ์ถฉ๋ ์๋ ์ ๊ทผ ํจํด์ผ๋ก ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ
์ ๊ทผ ํจํด ๋ถ์ ๋ฐฉ๋ฒ:
- ์ค๋ ๋๋ฅผ ์ธ๋ฑ์ค์ ๋งคํ: ์ํ์ ์ฃผ์ ๊ณ์ฐ์ ์ดํด
- ๋ฑ
ํฌ ํ ๋น ๊ณ์ฐ: ๊ณต์
bank_id = (address / 4) % 32์ฌ์ฉ - ์ถฉ๋ ์๋ณ: ๊ฐ์ ๋ฑ ํฌ์ ์ ๊ทผํ๋ ์ค๋ ๋๊ฐ ์ฌ๋ฌ ๊ฐ์ธ์ง ํ์ธ
- ํ๋กํ์ผ๋ง์ผ๋ก ๊ฒ์ฆ: NSight Compute ์ธก์ ์ผ๋ก ์ด๋ก ์ ๋ถ์ ํ์ธ
์ผ๋ฐ์ ์ธ ์ถฉ๋ ์๋ ํจํด:
- ์์ฐจ ์ ๊ทผ:
shared[thread_idx.x]- ๊ฐ ์ค๋ ๋๊ฐ ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผ - ๋ธ๋ก๋์บ์คํธ ์ ๊ทผ: ๋ชจ๋ ์ค๋ ๋๊ฐ
shared[0]- ํ๋์จ์ด ์ต์ ํ - 2์ ๊ฑฐ๋ญ์ ๊ณฑ ์คํธ๋ผ์ด๋: stride-32๋ ๋ฑ ํน ํจํด์ ๊น๋ํ๊ฒ ๋งคํ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ํจ๋ฉ๋ ๋ฐฐ์ด: ํจ๋ฉ์ ์ถ๊ฐํ์ฌ ๋ฌธ์ ๊ฐ ๋๋ ์ ๊ทผ ํจํด์ ์ด๋
์๋ฃจ์
๋ฑ ํฌ ์ถฉ๋ ๋ถ์์ด ํฌํจ๋ ์์ ํ ํ์ด
์ด ๋ฑ ํฌ ์ถฉ๋ ํ์ ์ฌ๊ฑด์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด์ด GPU ์ฑ๋ฅ์ ์ด๋ค ์ํฅ์ ๋ฏธ์น๋์ง, ๊ทธ๋ฆฌ๊ณ ์ต์ ํ๋ฅผ ์ํ ์ฒด๊ณ์ ํ๋กํ์ผ๋ง์ ์ค์์ฑ์ ๋ณด์ฌ์ค๋๋ค.
ํ๋กํ์ผ๋ง์ ํตํ ์กฐ์ฌ ๊ฒฐ๊ณผ
Step 1: ์ ํ์ฑ ๊ฒ์ฆ ๋ ์ปค๋ ๋ชจ๋ ๋์ผํ ์ํ์ ๊ฒฐ๊ณผ๋ฅผ ๋ ๋๋ค:
โ
No-conflict kernel: PASSED
โ
Two-way conflict kernel: PASSED
โ
Both kernels produce identical results
Step 2: ์ฑ๋ฅ ๊ธฐ์ค์ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ๋ ๋น์ทํ ์คํ ์๊ฐ์ ๋ณด์ฌ์ค๋๋ค:
| name | met (ms) | iters |
| ---------------- | ------------------ | ----- |
| no_conflict | 2.1930616745886655 | 547 |
| two_way_conflict | 2.1978922967032966 | 546 |
ํต์ฌ ํต์ฐฐ: ์ฑ๋ฅ์ด ๊ฑฐ์ ๋์ผํ ์ด์ (~2.19ms vs ~2.20ms)๋ ์ด ์ํฌ๋ก๋๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋๊ฐ ์๋ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฑ ํฌ ์ถฉ๋์ ์คํ ์๊ฐ์ด ์๋ ํ๋กํ์ผ๋ง ๋ฉํธ๋ฆญ์ ํตํด ๋๋ฌ๋ฉ๋๋ค.
๋ฑ ํฌ ์ถฉ๋ ํ๋กํ์ผ๋ง ๊ทผ๊ฑฐ
์ถฉ๋ ์๋ ์ปค๋ (์ต์ ์ ๊ทผ ํจํด):
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld.sum 0
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st.sum 0
๊ฒฐ๊ณผ: ๋ก๋์ ์คํ ์ด ๋ชจ๋ ์ถฉ๋ 0๊ฑด - ์๋ฒฝํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ.
2-Way ์ถฉ๋ ์ปค๋ (๋ฌธ์ ์๋ ์ ๊ทผ ํจํด):
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_ld.sum 256
l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st.sum 256
๊ฒฐ๊ณผ: ๋ก๋์ ์คํ ์ด ๊ฐ๊ฐ 256๊ฑด์ ์ถฉ๋ - ์ฒด๊ณ์ ์ธ ๋ฑ ํน ๋ฌธ์ ์ ๋ช ํํ ๊ทผ๊ฑฐ.
์ด ์ถฉ๋ ์ฐจ์ด: 512๊ฑด์ ์ถฉ๋(256 + 256)์ด ์ธก์ ๊ฐ๋ฅํ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋นํจ์จ์ ๋ณด์ฌ์ค๋๋ค.
์ ๊ทผ ํจํด ์ํ์ ๋ถ์
์ถฉ๋ ์๋ ์ปค๋ ์ ๊ทผ ํจํด
์ค๋ ๋-์ธ๋ฑ์ค ๋งคํ:
shared_buf[thread_idx.x]
๋ฑ ํฌ ํ ๋น ๋ถ์:
Thread 0 โ Index 0 โ Bank 0 % 32 = 0
Thread 1 โ Index 1 โ Bank 1 % 32 = 1
Thread 2 โ Index 2 โ Bank 2 % 32 = 2
...
Thread 31 โ Index 31 โ Bank 31 % 32 = 31
๊ฒฐ๊ณผ: ์๋ฒฝํ ๋ฑ ํฌ ๋ถ๋ฐฐ - ๊ฐ ์ํ ๋ด์์ ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๋ฑ ํฌ์ ์ ๊ทผํ์ฌ ๋ณ๋ ฌ ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
2-way ์ถฉ๋ ์ปค๋ ์ ๊ทผ ํจํด
์ค๋ ๋-์ธ๋ฑ์ค ๋งคํ:
shared_buf[(thread_idx.x * 2) % TPB] # TPB = 256
์ฒซ ๋ฒ์งธ ์ํ(์ค๋ ๋ 0-31)์ ๋ฑ ํฌ ํ ๋น ๋ถ์:
Thread 0 โ Index (0*2)%256 = 0 โ Bank 0
Thread 1 โ Index (1*2)%256 = 2 โ Bank 2
Thread 2 โ Index (2*2)%256 = 4 โ Bank 4
...
Thread 16 โ Index (16*2)%256 = 32 โ Bank 0 โ Thread 0๊ณผ ์ถฉ๋
Thread 17 โ Index (17*2)%256 = 34 โ Bank 2 โ Thread 1๊ณผ ์ถฉ๋
Thread 18 โ Index (18*2)%256 = 36 โ Bank 4 โ Thread 2์ ์ถฉ๋
...
์ถฉ๋ ํจํด: ๊ฐ ๋ฑ ํฌ๊ฐ ์ ํํ 2๊ฐ์ ์ค๋ ๋๋ฅผ ์ฒ๋ฆฌํ์ฌ 32๊ฐ ๋ฑ ํฌ ์ ์ฒด์์ ์ฒด๊ณ์ ์ธ 2-way ์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค.
์ํ์ ์ค๋ช : stride-2 ํจํด๊ณผ ๋ชจ๋๋ก 256์ ์กฐํฉ์ด ๋ฐ๋ณต์ ์ธ ์ ๊ทผ ํจํด์ ๋ง๋ค์ด๋ ๋๋ค:
- ์ค๋ ๋ 0-15๋ ๋ฑ ํฌ 0,2,4,โฆ,30์ ์ ๊ทผ
- ์ค๋ ๋ 16-31์ ๋์ผํ ๋ฑ ํฌ 0,2,4,โฆ,30์ ์ ๊ทผ
- ๊ฐ ๋ฑ ํฌ ์ถฉ๋๋ง๋ค ํ๋์จ์ด ์ง๋ ฌํ๊ฐ ํ์
์ด๊ฒ์ด ์ค์ํ ์ด์ : ์ํฌ๋ก๋ ๋งฅ๋ฝ ๋ถ์
๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ vs ์ฐ์ฐ ๋ฐ์ด๋ ์์ฌ์
์ด ์ํฌ๋ก๋์ ํน์ฑ:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ์ง๋ฐฐ์ : ๊ฐ ์ค๋ ๋๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ ์ก ๋๋น ์ต์ํ์ ์ฐ์ฐ๋ง ์ํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ ๋ถ์ฐจ์ : ๋ฑ ํฌ ์ถฉ๋์ด ์ค๋ฒํค๋๋ฅผ ์ถ๊ฐํ์ง๋ง ์ ์ฒด ์คํ ์๊ฐ์ ์ง๋ฐฐํ์ง๋ ์์
- ๋์ผํ ์ฑ๋ฅ: ์ ์ญ ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํฌํ๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋นํจ์จ์ ๊ฐ๋ฆผ
๋ฑ ํฌ ์ถฉ๋์ด ๊ฐ์ฅ ์ค์ํ ๊ฒฝ์ฐ:
- ์ฐ์ฐ ์ง์ฝ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ - ํ๋ ฌ ๊ณฑ์ , ์คํ ์ค ์ฐ์ฐ, FFT
- ํ์ดํธํ ์ฐ์ฐ ๋ฃจํ - ๋ด๋ถ ๋ฃจํ ์์์ ๋ฐ๋ณต์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
- ๋์ ์ฐ์ ๊ฐ๋ - ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๋น ์๋นํ ์ฐ์ฐ๋
- ๋๊ท๋ชจ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ ์ธํธ - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ์ ์ง์ค์ ์ผ๋ก ํ์ฉํ๋ ์๊ณ ๋ฆฌ์ฆ
์ค์ ์ฑ๋ฅ ์์ฌ์
๋ฑ ํฌ ์ถฉ๋์ด ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์น๋ ์ ํ๋ฆฌ์ผ์ด์ :
ํ๋ ฌ ๊ณฑ์ :
# Problematic: All threads in warp access same column
for k in range(tile_size):
acc += a_shared[local_row, k] * b_shared[k, local_col] # b_shared[k, 0] conflicts
์คํ ์ค ์ฐ์ฐ:
# Problematic: Stride access in boundary handling
shared_buf[thread_idx.x * stride] # Creates systematic conflicts
๋ณ๋ ฌ ๋ฆฌ๋์ :
# Problematic: Power-of-2 stride patterns
if thread_idx.x < stride:
shared_buf[thread_idx.x] += shared_buf[thread_idx.x + stride] # Conflict potential
์ถฉ๋ ์๋ ์ค๊ณ ์์น
์๋ฐฉ ์ ๋ต
1. ์์ฐจ ์ ๊ทผ ํจํด:
shared[thread_idx.x] # Optimal - each thread different bank
2. ๋ธ๋ก๋์บ์คํธ ์ต์ ํ:
constant = shared[0] # All threads read same address - hardware optimized
3. ํจ๋ฉ ๊ธฐ๋ฒ:
shared = stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[TPB + 1]()) # Shift access patterns
4. ์ ๊ทผ ํจํด ๋ถ์:
- ๊ตฌํ ์ ์ ๋ฑ ํฌ ํ ๋น์ ๊ณ์ฐ
- ๋ชจ๋๋ก ์ฐ์ฐ ์ฌ์ฉ:
bank_id = (address_bytes / 4) % 32 - ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ ์ค๋ ๋-๋ฑ ํฌ ๋งคํ์ ์๊ฐํ
์ฒด๊ณ์ ์ต์ ํ ์ํฌํ๋ก์ฐ
์ค๊ณ ๋จ๊ณ:
- ์ ๊ทผ ํจํด ๊ณํ - ์ค๋ ๋-๋ฉ๋ชจ๋ฆฌ ๋งคํ์ ์ค์ผ์น
- ๋ฑ ํฌ ํ ๋น ๊ณ์ฐ - ์ํ์ ๋ถ์ ํ์ฉ
- ์ถฉ๋ ์์ธก - ๋ฌธ์ ๊ฐ ๋๋ ์ ๊ทผ ํจํด ์๋ณ
- ๋์ ์ค๊ณ - ํจ๋ฉ, ์ ์น, ๋๋ ์๊ณ ๋ฆฌ์ฆ ๋ณ๊ฒฝ ๊ณ ๋ ค
๊ตฌํ ๋จ๊ณ:
- ์ฒด๊ณ์ ํ๋กํ์ผ๋ง - NSight Compute ์ถฉ๋ ๋ฉํธ๋ฆญ ์ฌ์ฉ
- ์ํฅ ์ธก์ - ๊ตฌํ ๊ฐ ์ถฉ๋ ํ์ ๋น๊ต
- ์ฑ๋ฅ ๊ฒ์ฆ - ์ต์ ํ๊ฐ ์ข ๋จ๊ฐ ์ฑ๋ฅ์ ๊ฐ์ ํ๋์ง ํ์ธ
- ํจํด ๋ฌธ์ํ - ์ฑ๊ณต์ ์ธ ์ถฉ๋ ์๋ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฌ์ฉ์ ์ํด ๊ธฐ๋ก
ํต์ฌ ์ ๋ฆฌ: ํ์ ์์ ์์ ์ต์ ํ ์ ๋ฌธ์ฑ์ผ๋ก
๋ฑ ํฌ ์ถฉ๋ ์กฐ์ฌ์์ ๋ฐํ์ง ๊ฒ:
- ์ธก์ ์ด ์ง๊ด๋ณด๋ค ๋ซ๋ค - ํ๋กํ์ผ๋ง ๋๊ตฌ๊ฐ ์ฑ๋ฅ ํ์ด๋ฐ์ผ๋ก๋ ๋ณด์ด์ง ์๋ ์ถฉ๋์ ๋๋ฌ๋
- ํจํด ๋ถ์์ด ์ ํจํ๋ค - ์ํ์ ์์ธก์ด NSight Compute ๊ฒฐ๊ณผ์ ์ ํํ ์ผ์น
- ๋งฅ๋ฝ์ด ์ค์ํ๋ค - ๋ฑ ํฌ ์ถฉ๋์ ์ฐ์ฐ ์ง์ฝ์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ํฌ๋ก๋์์ ๊ฐ์ฅ ์ค์
- ์๋ฐฉ์ด ์์ ๋ณด๋ค ๋ซ๋ค - ์ถฉ๋ ์๋ ํจํด์ ์ค๊ณํ๋ ๊ฒ์ด ์ฌํ ์ต์ ํ๋ณด๋ค ์ฌ์
๋ณดํธ์ ์ธ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ์์น:
๋ฑ ํฌ ์ถฉ๋์ ์ฃผ์ํด์ผ ํ๋ ๊ฒฝ์ฐ:
- ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ์ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ์ฐ์ฐ ์ง์ฝ์ ์ปค๋
- ํ์ดํธํ ๋ฃจํ์์ ๋ฐ๋ณต์ ์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ๋ฐ๋ณต ์๊ณ ๋ฆฌ์ฆ
- ๋ชจ๋ ์ฌ์ดํด์ด ์ค์ํ ์ฑ๋ฅ ํต์ฌ ์ฝ๋
- ๋์ญํญ ๋ฐ์ด๋๊ฐ ์๋ ์ฐ์ฐ ๋ฐ์ด๋์ธ ๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ ์ฐ์ฐ
๋ฑ ํฌ ์ถฉ๋์ด ๋ ์ค์ํ ๊ฒฝ์ฐ:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฑ๋ฅ์ ์ง๋ฐฐํ๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ํฌ๋ก๋
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฌ์ฉ์ด ์ต์์ธ ๋จ์ ์บ์ฑ ์๋๋ฆฌ์ค
- ๋ฐ๋ณต์ ์ธ ์ถฉ๋ ๋ฐ์ ์ฐ์ฐ์ด ์๋ ์ผํ์ฑ ์ ๊ทผ ํจํด
์ ๋ฌธ์ ๊ฐ๋ฐ ๋ฐฉ๋ฒ๋ก :
- ์ต์ ํ ์ ์ ํ๋กํ์ผ๋ง - NSight Compute๋ก ์ถฉ๋์ ์ ๋์ ์ผ๋ก ์ธก์
- ์ ๊ทผ ์ํ ์ดํด - ๋ฑ ํฌ ํ ๋น ๊ณต์์ผ๋ก ๋ฌธ์ ๋ฅผ ์์ธก
- ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ - ๋ฑ ํน์ ์ฌํ ๊ณ ๋ ค๊ฐ ์๋ ์๊ณ ๋ฆฌ์ฆ ์ค๊ณ ๋จ๊ณ์์ ๊ณ ๋ ค
- ์ต์ ํ ๊ฒ์ฆ - ์ถฉ๋ ๊ฐ์๊ฐ ์ค์ ์ฑ๋ฅ์ ๊ฐ์ ํ๋์ง ํ์ธ
์ด ํ์ ์ฌ๊ฑด์ ์ฒด๊ณ์ ํ๋กํ์ผ๋ง์ด ์ฑ๋ฅ ํ์ด๋ฐ๋ง์ผ๋ก๋ ๋ณด์ด์ง ์๋ ์ต์ ํ ๊ธฐํ๋ฅผ ๋๋ฌ๋ธ๋ค๋ ๊ฒ์ ๋ณด์ฌ์ค๋๋ค - ๋ฑ ํฌ ์ถฉ๋์ ์ธก์ ๊ธฐ๋ฐ ์ต์ ํ๊ฐ ์ถ์ธก๋ณด๋ค ๋์ ๋ํ์ ์ธ ์ฌ๋ก์ ๋๋ค.
Puzzle 33: ํ ์ ์ฝ์ด ์ฐ์ฐ
์๊ฐ
GPU ํ๋ ฌ ๊ณฑ์ ์ต์ ํ์ ์ต์ ์ ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! ์ด ํผ์ฆ์์๋ ํผํฉ ์ ๋ฐ๋ ํ๋ ฌ ์ฐ์ฐ์ ์ ๋ก ์๋ ์๋๋ก ๊ฐ์ํ๊ธฐ ์ํด ์ค๊ณ๋ ์ ์ฉ ํ๋์จ์ด ์ ๋์ธ ํ ์ ์ฝ์ด๋ฅผ ํ๊ตฌํฉ๋๋ค.
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ชจ๋ ๊ฒ, ํนํ Puzzle 16์ ๊ด์ฉ์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ ๊ธฐ๋ฐ์ผ๋ก, ์ต์ GPU๊ฐ ํ๋ ฌ ์ฐ์ฐ์ ๊ทน์ ์ผ๋ก ๋น ๋ฅด๊ฒ ๋ง๋๋ ์ ์ฉ ์ค๋ฆฌ์ฝ์ ์ด๋ป๊ฒ ์ ๊ณตํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
ํ ์ ์ฝ์ด๋?
ํ ์ ์ฝ์ด(AMD ํ๋์จ์ด์์๋ Matrix Core๋ผ๊ณ ๋ ํจ)๋ ๋จ์ผ ๋ช ๋ น์ด๋ก ํผํฉ ์ ๋ฐ๋ ํ๋ ฌ-ํ๋ ฌ ์ฐ์ฐ์ ์ํํ ์ ์๋ ์ ์ฉ ํ๋ก์ธ์ฑ ์ ๋์ ๋๋ค. ์ด ์ ๋์ ์ต์ GPU ์ํคํ ์ฒ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
- NVIDIA: Tensor Cores (Volta, Turing, Ampere, Hopper)
- AMD: Matrix Cores (CDNA/CDNA2/CDNA3 ์ํคํ ์ฒ)
GPU์ ์ง์ ๋ด์ฅ๋ ํ๋์จ์ด ๊ฐ์ GEMM(์ญ์ฃผ: General Matrix Multiply, ๋ฒ์ฉ ํ๋ ฌ ๊ณฑ์ ) ์์ง์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
ํต์ฌ ํน์ง
- ์ํ ์์ค ์ฐ์ฐ: ๊ฐ ๋ช ๋ น์ด๊ฐ ์ ์ฒด ์ํ์ ๋ฐ์ดํฐ๋ฅผ ๋์์ผ๋ก ๋์ํฉ๋๋ค (NVIDIA์์ 32๊ฐ ์ค๋ ๋, AMD์์ 32 ๋๋ 64๊ฐ)
- ๊ณ ์ ํ์ผ ํฌ๊ธฐ: ์ฐ์ฐ์ด ํน์ ํ๋ ฌ ํ๋๊ทธ๋จผํธ ํฌ๊ธฐ์์ ๋์ํฉ๋๋ค (์: FP32์ ๊ฒฝ์ฐ 16ร8ร8)
- ํผํฉ ์ ๋ฐ๋: ์ต์ ์ ์ฑ๋ฅ์ ์ํด ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ ์ ๋ฐ๋๋ฅผ ํผํฉํ ์ ์์ต๋๋ค
- ๋๊ท๋ชจ ์ฒ๋ฆฌ๋: ํ๋ ฌ ์ฐ์ฐ์์ ์ผ๋ฐ ์ปดํจํธ ์ฝ์ด ๋๋น 10~100๋ฐฐ ์๋ ํฅ์์ ๋ฌ์ฑํ ์ ์์ต๋๋ค
ํ์ผ๋ง์์ ํ ์ ์ฝ์ด๋ก
๊ธฐ๋ณธ ํ๋ ฌ ๊ณฑ์ ์์ ํ ์ ์ฝ์ด๊น์ง์ ์ฌ์ ์ ๋์๋ณด๊ฒ ์ต๋๋ค:
- Puzzle 16: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ ๊ด์ฉ์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ ๋ฐฐ์ ์ต๋๋ค
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ: ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ์ํด
copy_dram_to_sram_async๋ฅผ ์ฌ์ฉํ์ต๋๋ค - ์ค๋ ๋ ํ๋ ฅ: ๋ฐฐ๋ฆฌ์ด์ ๋น๋๊ธฐ ์ฐ์ฐ์ผ๋ก ์ํ๋ฅผ ์กฐ์ ํ์ต๋๋ค
- ์ง๊ธ: ํต์ฌ ์ฐ์ฐ์ ๊ฐ์ํ๊ธฐ ์ํด ์ ์ฉ ํ๋์จ์ด(ํ ์ ์ฝ์ด)๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค
ํ ์ ์ฝ์ด ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ
ํ ์ ์ฝ์ด๋ ๊ธฐ์กด๊ณผ ๋ค๋ฅธ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์ ์ ๊ณตํฉ๋๋ค:
๊ธฐ์กด ์ปดํจํธ ์ฝ์ด ๋ฐฉ์
# Each thread computes one element
acc += a_shared[local_row, k] * b_shared[k, local_col]
ํ ์ ์ฝ์ด ๋ฐฉ์
# Entire warp cooperates on matrix fragments
a_reg = mma_op.load_a(A_mma_tile) # Load 16ร8 fragment
b_reg = mma_op.load_b(B_mma_tile) # Load 8ร8 fragment
c_reg = mma_op.load_c(C_mma_tile) # Load 16ร8 accumulator
d_reg = mma_op.mma_op(a_reg, b_reg, c_reg) # D = AรB + C
mma_op.store_d(C_mma_tile, d_reg) # Store result
Mojo์ ํ ์ ์ฝ์ด API
Mojo๋
TensorCore
ํ์
์ ํตํด ํ
์ ์ฝ์ด์ ๋ํ ๊น๋ํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค:
from layout.tensor_core import TensorCore
# Create a Tensor Core operator for specific tile sizes
mma_op = TensorCore[A.dtype, C.dtype, Index(MMA_M, MMA_N, MMA_K)]()
# Core operations:
# - load_a(): Load matrix A fragment from shared memory
# - load_b(): Load matrix B fragment from shared memory
# - load_c(): Load matrix C fragment (accumulator)
# - mma_op(): Perform D = AรB + C operation
# - store_d(): Store result fragment to memory
๊ณ ๊ธ ๊ธฐ๋ฅ: TensorCore API๋ ์์ํ ์ฐ์ฐ, ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ ํ๋ฅผ ์ํ ๋ค์ํ ์ค์์ฆ ํจํด(์ญ์ฃผ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ฑ ํฌ ์ถฉ๋์ ํผํ๊ธฐ ์ํด ๋ฐ์ดํฐ ์ฃผ์๋ฅผ ๋นํธ ์ฐ์ฐ์ผ๋ก ์ฌ๋ฐฐ์นํ๋ ๊ธฐ๋ฒ), ํผํฉ ์ ๋ฐ๋ ์ฐ์ฐ๋ ์ง์ํฉ๋๋ค. ์ง์๋๋ ๋ชจ๋ ํํ, ๋ฐ์ดํฐ ํ์ , ๋ฉ์๋์ ๋ํ ์ ์ฒด ๋ฌธ์๋ ๊ณต์ TensorCore API ๋ ํผ๋ฐ์ค๋ฅผ ์ฐธ๊ณ ํ์ธ์.
ํ๋ ฌ ํ๋๊ทธ๋จผํธ ํฌ๊ธฐ
TensorCore API๋ GPU ํ๋์จ์ด์ ๋ฐ๋ผ ๋ค์ํ ํํ์ ๋ฐ์ดํฐ ํ์ ์ ์ง์ํฉ๋๋ค:
NVIDIA GPU:
- float32: 16ร8ร8 ๋๋ 16ร8ร4
- half-precision: 16ร8ร16
- float8: 16ร8ร32
AMD GPU:
- float32: 16ร16ร4
- half-precision: 16ร16ร16 ๋๋ 32ร32ร8
์ด ํผ์ฆ์์๋ FP32์ 16ร8ร8 ํ๋๊ทธ๋จผํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
- MMA_M = 16: ํ๋ ฌ A์ ๋์ด (์ถ๋ ฅ ๋์ด์ ๋์ผ)
- MMA_N = 8: ํ๋ ฌ B์ ๋๋น (์ถ๋ ฅ ๋๋น์ ๋์ผ)
- MMA_K = 8: ๋ด๋ถ ์ฐจ์ (A์ ๋๋น = B์ ๋์ด)
MMA๋? MMA๋ โMixed-precision Matrix-Multiply-Accumulateโ์ ์ฝ์๋ก, ํ
์
์ฝ์ด๊ฐ ์ํํ๋ ๊ธฐ๋ณธ ์ฐ์ฐ์
๋๋ค. ๊ฐ MMA ๋ช
๋ น์ด๋ D = A ร B + C๋ฅผ ๊ณ์ฐํ๋ฉฐ,
์ฌ๊ธฐ์ A, B, C, D๋ ํ๋ ฌ ํ๋๊ทธ๋จผํธ์
๋๋ค.
ํ๋๊ทธ๋จผํธ ์๊ฐํ:
A fragment (16ร8) ร B fragment (8ร8) + C fragment (16ร8) = D fragment (16ร8)
16 rows 8 rows 16 rows 16 rows
8 cols 8 cols 8 cols 8 cols
| | | |
[A data] ร [B data] + [C data] = [D result]
์ฆ, ๊ฐ ํ ์ ์ฝ์ด ๋ช ๋ น์ด๋ A์ 16ร8 ํ์ผ๊ณผ B์ 8ร8 ํ์ผ์ ๊ณฑํ ๋ค ๊ธฐ์กด 16ร8 ๋์ฐ๊ธฐ์ ๋ํ์ฌ 16ร8 ์ถ๋ ฅ ํ์ผ์ ๊ณ์ฐํฉ๋๋ค.
ํ ์ ์ฝ์ด๋ฅผ ์ํ ์ํ ๊ตฌ์ฑ
์ํ๋? ์ํ๋ ๋ก์คํ ์ผ๋ก ๋ช ๋ น์ด๋ฅผ ํจ๊ป ์คํํ๋ ์ค๋ ๋ ๊ทธ๋ฃน(NVIDIA์์ 32๊ฐ, AMD์์ 32 ๋๋ 64๊ฐ)์ ๋๋ค. ํ ์ ์ฝ์ด๋ ๋จ์ผ ํ๋ ฌ ์ฐ์ฐ์ ์ํ ๋ด ๋ชจ๋ ์ค๋ ๋๊ฐ ํ๋ ฅํด์ผ ํฉ๋๋ค.
์ ์ํ ์์ค์ผ๊น? ๊ฐ ์ค๋ ๋๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋ ์ผ๋ฐ ์ฐ์ฐ๊ณผ ๋ฌ๋ฆฌ, ํ ์ ์ฝ์ด๋ ์ ์ฒด ์ํ๊ฐ ํจ๊ป ํ๋ ฌ ํ๋๊ทธ๋จผํธ๋ฅผ ๋ก๋ํ๊ณ , MMA ์ฐ์ฐ์ ์ํํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํด์ผ ํฉ๋๋ค.
ํ ์ ์ฝ์ด๊ฐ ์ํ ์์ค์์ ๋์ํ๋ฏ๋ก, ์ค๋ ๋๋ฅผ ๋ค๋ฅด๊ฒ ๊ตฌ์ฑํด์ผ ํฉ๋๋ค:
# Calculate warp coordinates within the block
warp_id = thread_idx.x // WARP_SIZE
warps_in_n = BN // WN # Number of warps along N dimension
warps_in_m = BM // WM # Number of warps along M dimension
warp_y = warp_id // warps_in_n # Warp's row
warp_x = warp_id % warps_in_n # Warp's column
# Each warp handles a WMรWN tile of the output
C_warp_tile = C_block_tile.tile[WM, WN](warp_y, warp_x)
์ํ ๊ตฌ์ฑ ์์ (BM=128, BN=64, WM=32, WN=32์ธ ๊ฒฝ์ฐ):
Block (128ร64) contains 8 warps arranged as:
32 cols 32 cols
| |
[ Warp 0 ][ Warp 1 ] โ 32 rows each
[ Warp 2 ][ Warp 3 ] โ 32 rows each
[ Warp 4 ][ Warp 5 ] โ 32 rows each
[ Warp 6 ][ Warp 7 ] โ 32 rows each
Total: 4ร2 = 8 warps, each handling 32ร32 output region
ํ ์ ์ฝ์ด์ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ
ํ ์ ์ฝ์ด๋ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ์ ํ ๋จ๊ณ๋ฅผ ๋ ์ถ๊ฐํฉ๋๋ค:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ:
copy_dram_to_sram_async์ฌ์ฉ (Puzzle 16์์ ๋ฐฐ์ด ๊ฒ) - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ๋ ์ง์คํฐ ํ๋๊ทธ๋จผํธ:
mma_op.load_a/load_b์ฌ์ฉ - ์ฐ์ฐ: ๋ ์ง์คํฐ ํ๋๊ทธ๋จผํธ์์
mma_op.mma_op์ฌ์ฉ - ๋ ์ง์คํฐ ํ๋๊ทธ๋จผํธ โ ์ ์ญ ๋ฉ๋ชจ๋ฆฌ:
mma_op.store_d์ฌ์ฉ
๋์ ๊ณผ์
tensor_core_matrix_multiplication ํจ์๋ฅผ ์์ฑํ๋ ๊ฒ์ด ๋ชฉํ์
๋๋ค. ์ค์ผ๋ ํค
์ฝ๋๋ ํ์ผ๋ง ๋ฐฉ์์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ์ค์ ํ
์ ์ฝ์ด ํ๋์จ์ด ์ฐ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
ํต์ฌ ์๊ตฌ์ฌํญ
- ์ค์ ํ
์ ์ฝ์ด API ์ฌ์ฉ: ์๋ฎฌ๋ ์ด์
์ด ์๋ ์ค์
mma_op.load_a(),mma_op.mma_op()๋ฑ์ ์ฌ์ฉํ์ธ์ - ์ ํ์ฑ ์ ์ง: ๊ฒฐ๊ณผ๊ฐ CPU ์ฐธ์กฐ ๊ตฌํ๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค
- ์ฌ๋ฐ๋ฅธ ์ํ ์กฐ์ : ๋ธ๋ก๋น ์ฌ๋ฌ ์ํ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํฉ๋๋ค (NVIDIA์ AMD ๋ชจ๋์์ ๋์)
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: Puzzle 16์์ ๋ฐฐ์ด ๋น๋๊ธฐ ๋ณต์ฌ ํจํด์ ๋์ผํ๊ฒ ์ฌ์ฉํฉ๋๋ค
- ํฌ๋ก์ค ํ๋ซํผ ํธํ์ฑ: ํ์ผ๋ง ํ๋ผ๋ฏธํฐ๊ฐ
WARP_SIZE์ ๋ฐฐ์์ธ์ง ํ์ธํฉ๋๋ค
์ค์
- ํ๋ ฌ ํฌ๊ธฐ: \(\text{SIZE} = 1024\)
- ๋ธ๋ก ํ์ผ๋ง: \(\text{BM} = 128, \text{BN} = 64, \text{BK} = 32\)
- ์ํ ํ์ผ๋ง: \(\text{WM} = 32, \text{WN} = 32\) (
WARP_SIZE์ ๋ฐฐ์) - MMA ํ๋๊ทธ๋จผํธ: \(16 \times 8 \times 8\) (FP32)
- ๋ธ๋ก๋น ์ค๋ ๋ ์: \(8 \times \text{WARP_SIZE}\) (๋ธ๋ก๋น 8๊ฐ ์ํ)
- ๊ทธ๋ฆฌ๋ ์ฐจ์: ๋ธ๋ก ํ์ผ๋ก ์ ์ฒด ํ๋ ฌ์ ์ปค๋ฒ
๋ ์ด์์ ์ค์ :
- ์
๋ ฅ A:
row_major[SIZE, SIZE]() - ์
๋ ฅ B:
row_major[SIZE, SIZE]() - ์ถ๋ ฅ C:
row_major[SIZE, SIZE]() - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋น๋๊ธฐ ๋ณต์ฌ ์ฐ์ฐ์ ์ฌ์ฉํ๋ ๋ธ๋ก ํฌ๊ธฐ ํ์ผ
๋์ ๊ณผ์
์ด ํผ์ฆ์์๋ Puzzle 16์ ๊ด์ฉ์ ํ์ผ๋ง ํ๋ ฌ ๊ณฑ์ ์ ํ ์ ์ฝ์ด ๊ตฌํ์ผ๋ก ๋ณํํฉ๋๋ค. ๋จ๊ณ๋ณ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค:
1๋จ๊ณ: ํ์ผ๋ง ๊ธฐ๋ณธ ๊ตฌํ ์ดํดํ๊ธฐ
ํผ์ฆ์ ์ฐธ์กฐ์ฉ์ผ๋ก ์์ฑ๋ ๊ด์ฉ์ ํ์ผ๋ง ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค:
def matmul_idiomatic_tiled[
size: Int
](
output: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
a: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
b: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
):
# Use block_dim to get actual tile size dynamically
var tile_size_x = block_dim.x
var tile_size_y = block_dim.y
var local_row = thread_idx.y
var local_col = thread_idx.x
var tiled_row = block_idx.y * tile_size_y + local_row
var tiled_col = block_idx.x * tile_size_x + local_col
# Get the tile of the output matrix that this thread block is responsible for
var out_tile = output.tile[TILE_SIZE, TILE_SIZE](block_idx.y, block_idx.x)
var a_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TILE_SIZE, TILE_SIZE]())
var b_shared = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[TILE_SIZE, TILE_SIZE]())
var acc: output.ElementType = 0
comptime load_a_layout = row_major[1, TILE_SIZE]() # Coalesced loading
comptime load_b_layout = row_major[1, TILE_SIZE]() # Coalesced loading
# Note: Both matrices stored in same orientation for correct matrix multiplication
# Transposed loading would be useful if B were pre-transposed in global memory
for idx in range(size // TILE_SIZE): # Iterate over K tiles
# Get tiles from A and B matrices
var a_tile = a.tile[TILE_SIZE, TILE_SIZE](block_idx.y, idx)
var b_tile = b.tile[TILE_SIZE, TILE_SIZE](idx, block_idx.x)
# Asynchronously copy tiles to shared memory with consistent orientation
copy_dram_to_sram_async[
thread_layout=load_a_layout,
num_threads=TILE_SIZE * TILE_SIZE,
block_dim_count=BLOCK_DIM_COUNT,
](a_shared, a_tile)
copy_dram_to_sram_async[
thread_layout=load_b_layout,
num_threads=TILE_SIZE * TILE_SIZE,
block_dim_count=BLOCK_DIM_COUNT,
](b_shared, b_tile)
async_copy_wait_all()
barrier()
# Compute partial matrix multiplication for this tile
for k in range(TILE_SIZE):
if (
local_row < TILE_SIZE
and local_col < TILE_SIZE
and k < TILE_SIZE
):
acc += a_shared[local_row, k] * b_shared[k, local_col]
barrier()
# Write final result to output tile
if tiled_row < size and tiled_col < size:
out_tile[local_row, local_col] = acc
์ด ๊ธฐ๋ณธ ๊ตฌํ์ด ํ๋ ์ผ:
- ์ ํ์ฑ: ์ด ๊ตฌํ์ ์๋ฒฝํ๊ฒ ๋์ํ๋ฉฐ ๋ชจ๋ ํ ์คํธ๋ฅผ ํต๊ณผํฉ๋๋ค
- ์ค๋ ๋ ํ๋ ฅ: ํจ์จ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ์ก์ ์ํด
copy_dram_to_sram_async๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ฐฐ๋ฆฌ์ด์ ๋น๋๊ธฐ ์ฐ์ฐ์ผ๋ก ์ค๋ ๋๋ฅผ ์กฐ์ ํฉ๋๋ค
- ํ์ผ๋ง ์ฐ์ฐ: ๊ฐ ์ค๋ ๋๊ฐ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ์ผ์ ์ฌ์ฉํ์ฌ ํ๋์ ์ถ๋ ฅ ์์๋ฅผ ๊ณ์ฐํฉ๋๋ค
2๋จ๊ณ: ํ ์ ์ฝ์ด ๋ฏธ์
์ ๋ฐฉ์์ ์ ์ฉ ํ๋์จ์ด ๊ฐ์์ ํ์ฉํ๋๋ก ๋ณํํฉ๋๋ค:
- ๊ธฐ์กด: ์ค๋ ๋ ์์ค ์ฐ์ฐ โ ๋ณํ ํ: ์ํ ์์ค ํ๋ ฌ ํ๋๊ทธ๋จผํธ
- ๊ธฐ์กด: ํ์ค FP32 ์ฐ์ โ ๋ณํ ํ: ํ๋์จ์ด ๊ฐ์ GEMM ์ฐ์ฐ
- ๊ธฐ์กด: ๊ฐ๋ณ ์์ ๊ฒฐ๊ณผ โ ๋ณํ ํ: 16ร8 ํ๋ ฌ ํ๋๊ทธ๋จผํธ ๊ฒฐ๊ณผ
3๋จ๊ณ: ์ค์ ์ดํดํ๊ธฐ
ํ ์ ์ฝ์ด ๋ฒ์ ์ ํ๋์จ์ด์ ์ต์ ํ๋ ๋ค๋ฅธ ํ์ผ๋ง ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
- ๋ธ๋ก ํ์ผ๋ง:
BM=128, BN=64, BK=32(๋ ๋์ ์ ์ ์จ์ ์ํด ๋ ํฐ ๋ธ๋ก) - ์ํ ํ์ผ๋ง:
WM=32, WN=32(๊ฐ ์ํ๊ฐ 32ร32 ์ถ๋ ฅ ์์ญ์ ๋ด๋น) - MMA ํ๋๊ทธ๋จผํธ:
16ร8ร8(ํ๋์จ์ด๊ฐ ์ ์ํ ํ๋ ฌ ํ๋๊ทธ๋จผํธ ํฌ๊ธฐ) - ๋ธ๋ก๋น ์ํ: 8๊ฐ (BMรBN ๋ธ๋ก ๋ด์์ 4ร2๋ก ๋ฐฐ์น)
์ ์ด ํน์ ํฌ๊ธฐ์ธ๊ฐ?
- BM=128, BN=64: ํ ์ ์ฝ์ด๋ฅผ ๋ ์ ํ์ฉํ๊ธฐ ์ํด ํ์ผ๋ง ๋ฒ์ (32ร32)๋ณด๋ค ํฝ๋๋ค
- WM=WN=32: WARP_SIZE์ ๋ฐฐ์์ด๋ฉฐ 2ร4=8๊ฐ์ MMA ํ๋๊ทธ๋จผํธ๋ฅผ ํฌํจํฉ๋๋ค (32รท16=2, 32รท8=4)
- MMA 16ร8ร8: ํ๋์จ์ด์ ์ํด ๊ณ ์ ๋จ - ํ ์ ์ฝ์ด๊ฐ ๋ฌผ๋ฆฌ์ ์ผ๋ก ๊ณ์ฐํ๋ ํฌ๊ธฐ์ ๋๋ค
- 8 ์ํ: BMรทWM ร BNรทWN = 128รท32 ร 64รท32 = 4ร2 = ๋ธ๋ก๋น 8๊ฐ ์ํ
์ํ๊ฐ MMA ํ๋๊ทธ๋จผํธ์ ๋งคํ๋๋ ๋ฐฉ์:
Each 32ร32 warp tile contains multiple 16ร8 MMA fragments:
16 cols 16 cols
| |
[ MMA 0,0 ][ MMA 0,1 ] โ 8 rows each (32รท8=4 fragments down)
[ MMA 1,0 ][ MMA 1,1 ] โ 8 rows each
[ MMA 2,0 ][ MMA 2,1 ] โ 8 rows each
[ MMA 3,0 ][ MMA 3,1 ] โ 8 rows each
2 fragments across (32รท16=2) ร 4 fragments down (32รท8=4) = 8 MMA operations per warp per K-tile
4๋จ๊ณ: ์์ฑํ ์ฝ๋
# Block and warp tiling sizes
comptime BM = 4 * WARP_SIZE # Block tile M (4 warps along M)
comptime BN = 2 * WARP_SIZE # Block tile N (2 warps along N)
comptime BK = WARP_SIZE # Block tile K (stay within SMEM limit)
comptime WM = WARP_SIZE # Warp tile M
comptime WN = WARP_SIZE # Warp tile N
# MMA tile sizes for tensor cores
comptime MMA_M = 16
comptime MMA_N = 8
comptime MMA_K = 8
comptime THREADS_PER_BLOCK_TENSOR_CORE = (8 * WARP_SIZE, 1) # 8 warps per block
# grid_dim is (x, y). We want x to sweep N (columns) and y to sweep M (rows)
comptime BLOCKS_PER_GRID_TENSOR_CORE = (
(SIZE + BN - 1) // BN,
(SIZE + BM - 1) // BM,
)
def tensor_core_matrix_multiplication[
dtype: DType,
BM: Int,
BN: Int,
BK: Int,
WM: Int,
WN: Int,
MMA_M: Int,
MMA_N: Int,
MMA_K: Int,
](
A: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
B: TileTensor[mut=False, dtype, LayoutType, ImmutAnyOrigin],
C: TileTensor[mut=True, dtype, LayoutType, MutAnyOrigin],
):
comptime M = C.dim[0]()
comptime N = C.dim[1]()
comptime K = A.dim[1]()
var warp_id = thread_idx.x // WARP_SIZE
var warps_in_n = BN // WN
var warps_in_m = BM // WM
var warp_y = warp_id // warps_in_n
var warp_x = warp_id % warps_in_n
var warp_is_active = warp_y < warps_in_m
var C_block_tile = C.tile[BM, BN](block_idx.y, block_idx.x)
var C_warp_tile = C_block_tile.tile[WM, WN](warp_y, warp_x)
var mma_op = TensorCore[A.dtype, C.dtype, Index(MMA_M, MMA_N, MMA_K)]()
# Shared SRAM tiles (no padding to stay under shared memory limit)
var A_sram_tile = stack_allocation[
dtype=A.dtype, address_space=AddressSpace.SHARED
](row_major[BM, BK]())
var B_sram_tile = stack_allocation[
dtype=B.dtype, address_space=AddressSpace.SHARED
](row_major[BK, BN]())
# One per-warp accumulator tile of shape [WM, WN]
var C_warp_accum = stack_allocation[
dtype=C.dtype, address_space=AddressSpace.GENERIC
](row_major[WM, WN]())
# Zero initialize accumulator (only for active warps)
if warp_is_active:
comptime for i in range(WM):
comptime for j in range(WN):
C_warp_accum[i, j] = 0.0
# Sweep across K in BK chunks (single-buffered)
for k_i in range(K // BK):
barrier()
var A_dram_tile = A.tile[BM, BK](block_idx.y, k_i)
var B_dram_tile = B.tile[BK, BN](k_i, block_idx.x)
copy_dram_to_sram_async[
thread_layout=row_major[4, 8](),
num_threads=256,
block_dim_count=BLOCK_DIM_COUNT,
](A_sram_tile.vectorize[1, 4](), A_dram_tile.vectorize[1, 4]())
copy_dram_to_sram_async[
thread_layout=row_major[4, 8](),
num_threads=256,
block_dim_count=BLOCK_DIM_COUNT,
](B_sram_tile.vectorize[1, 4](), B_dram_tile.vectorize[1, 4]())
async_copy_wait_all()
barrier()
if warp_is_active:
var A_warp_tile = A_sram_tile.tile[WM, BK](warp_y, 0)
var B_warp_tile = B_sram_tile.tile[BK, WN](0, warp_x)
comptime for mma_k in range(BK // MMA_K):
comptime for mma_m in range(WM // MMA_M):
comptime for mma_n in range(WN // MMA_N):
# FILL IN (roughly 8 lines)
...
# Store the final per-warp accumulation to the output warp tile
if warp_is_active:
comptime for mma_m in range(WM // MMA_M):
comptime for mma_n in range(WN // MMA_N):
var C_mma_tile = C_warp_tile.tile[MMA_M, MMA_N](mma_m, mma_n)
var Acc_mma_tile = C_warp_accum.tile[MMA_M, MMA_N](mma_m, mma_n)
var frag = mma_op.load_c(Acc_mma_tile)
mma_op.store_d(C_mma_tile, frag)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p33/p33.mojo
ํ ์ผ: ์ธ ๊ฒน์ ์ค์ฒฉ ๋ฃจํ ์์ ์๋ ๋น ๋ถ๋ถ(# FILL IN (roughly 8 lines)์ผ๋ก
ํ์๋จ)์ ์์ฑํ์ธ์.
์ดํดํด์ผ ํ ๊ฒ:
- ์ค์ผ๋ ํค์ด ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ์ํ ๊ตฌ์ฑ, ๋๊ธฐํ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ํต์ฌ ํ ์ ์ฝ์ด ์ฐ์ฐ๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค
- ๋ฃจํ๋ MMA ํ๋๊ทธ๋จผํธ๋ฅผ ์ํํฉ๋๋ค:
mma_k,mma_m,mma_n - ๊ฐ ๋ฐ๋ณต์์ ํ๋์ 16ร8ร8 ํ๋ ฌ ํ๋๊ทธ๋จผํธ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
์ธ ๊ฒน ์ค์ฒฉ ๋ฃจํ ์ดํดํ๊ธฐ:
@parameter
for mma_k in range(BK // MMA_K): # 32รท8 = 4 iterations (K dimension)
@parameter
for mma_m in range(WM // MMA_M): # 32รท16 = 2 iterations (M dimension)
@parameter
for mma_n in range(WN // MMA_N): # 32รท8 = 4 iterations (N dimension)
# YOUR CODE HERE: Process one 16ร8ร8 MMA fragment
๊ฐ ๋ฃจํ๊ฐ ํ๋ ์ผ:
mma_k: ํ์ฌ K-ํ์ผ์ K-์ฌ๋ผ์ด์ค๋ฅผ ์ํํฉ๋๋ค (๊ฐ 8๊ฐ ์์์ 4๊ฐ ์ฌ๋ผ์ด์ค)mma_m: ์ํ ์ถ๋ ฅ์ M-์ฌ๋ผ์ด์ค๋ฅผ ์ํํฉ๋๋ค (๊ฐ 16ํ์ 2๊ฐ ์ฌ๋ผ์ด์ค)mma_n: ์ํ ์ถ๋ ฅ์ N-์ฌ๋ผ์ด์ค๋ฅผ ์ํํฉ๋๋ค (๊ฐ 8์ด์ 4๊ฐ ์ฌ๋ผ์ด์ค)- ํฉ๊ณ: 4ร2ร4 = K-ํ์ผ๋น ์ํ๋น 32๊ฐ MMA ์ฐ์ฐ
ํ
ํ ์ ์ฝ์ด ์ํฌํ๋ก์ฐ๋ฅผ ์๊ฐํด ๋ณด์ธ์. ํ์ํ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
์ฌ๋ฐ๋ฅธ ํ๋ ฌ ํ๋๊ทธ๋จผํธ ์ถ์ถํ๊ธฐ:
- ์ํ ํ์ผ(
A_warp_tile,B_warp_tile,C_warp_accum)์์ MMA ํฌ๊ธฐ์ ํน์ ํ๋๊ทธ๋จผํธ๋ฅผ ์ถ์ถํฉ๋๋ค - ๋ฃจํ ์ธ๋ฑ์ค(
mma_m,mma_k,mma_n)๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฐ๋ฅธ ํ์ผ ์ขํ๋ฅผ ๊ตฌํฉ๋๋ค - ๊ธฐ์ตํ์ธ์: A๋ [MMA_M, MMA_K], B๋ [MMA_K, MMA_N], C๋ [MMA_M, MMA_N]์ด ํ์ํฉ๋๋ค
- ์ํ ํ์ผ(
-
ํ๋๊ทธ๋จผํธ๋ฅผ ํ ์ ์ฝ์ด ๋ ์ง์คํฐ์ ๋ก๋ํ๊ธฐ:
mma_op๊ฐ์ฒด์๋ ๊ฐ ํ๋ ฌ ํ์ ์ ๋ก๋ํ๋ ๋ฉ์๋๊ฐ ์์ต๋๋ค- ๊ฐ ๋ก๋ ๋ฉ์๋๋ ํ์ผ์ ๋ฐ์์ ๋ ์ง์คํฐ ํ๋๊ทธ๋จผํธ๋ฅผ ๋ฐํํฉ๋๋ค
- ์๊ฐํด ๋ณด์ธ์:
load_a(),load_b(),load_c()- ๊ฐ๊ฐ ๋ฌด์์ ๋ฐ์๊น์?
-
ํ๋์จ์ด ์ฐ์ฐ์ ์ํํ๊ณ ๊ฒฐ๊ณผ ์ ์ฅํ๊ธฐ:
- MMA ์ฐ์ฐ์ ์ํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํฉ๋๋ค
- ๊ฒฐ๊ณผ๋ฅผ ๋์ฐ๊ธฐ ํ์ผ์ ์ ์ฅํฉ๋๋ค
- ์ฐ์ฐ ํจํด: result = A ร B + C
ํต์ฌ ์ธ์ฌ์ดํธ: 128๊ฐ์ ๊ฐ๋ณ ๊ณฑ์ -๋ง์ ์ฐ์ฐ์ ํ๋์ ํ๋์จ์ด ๋ช ๋ น์ด๋ก ๋์ฒดํ๋ ๊ฒ์ ๋๋ค!
๋๋ฒ๊น
ํ: ์ฐจ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ํ์ผ ์ธ๋ฑ์ฑ์ ๋ค์ ํ์ธํ์ธ์ - mma_m,
mma_k, mma_n์ ์์๊ฐ ์ฌ๋ฐ๋ฅธ ํ๋๊ทธ๋จผํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐ ์ค์ํฉ๋๋ค.
์ฝ๋ ์คํ
ํ์ด๋ฅผ ํ ์คํธํ๋ ค๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ธ์:
pixi run p33 --test
uv run poe p33 --test
์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ ํ๋ ํ ์คํธ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค:
=== Running All Accuracy Tests ===
--- Test 1: Tensor Core vs CPU Reference ---
โ
TENSOR CORE ACCURACY TEST PASSED!
--- Test 2: Idiomatic Tiled vs CPU Reference ---
โ
IDIOMATIC TILED ACCURACY TEST PASSED!
ALL TESTS PASSED!
์๋ฃจ์
def tensor_core_matrix_multiplication[
dtype: DType,
layout_a: Layout,
layout_b: Layout,
layout_c: Layout,
BM: Int,
BN: Int,
BK: Int,
WM: Int,
WN: Int,
MMA_M: Int,
MMA_N: Int,
MMA_K: Int,
](
A: LayoutTensor[dtype, layout_a, ImmutAnyOrigin],
B: LayoutTensor[dtype, layout_b, ImmutAnyOrigin],
C: LayoutTensor[dtype, layout_c, MutAnyOrigin],
):
comptime M = C.shape[0]()
comptime N = C.shape[1]()
comptime K = A.shape[1]()
var warp_id = thread_idx.x // WARP_SIZE
var warps_in_n = BN // WN
var warps_in_m = BM // WM
var warp_y = warp_id // warps_in_n
var warp_x = warp_id % warps_in_n
var warp_is_active = warp_y < warps_in_m
var C_block_tile = C.tile[BM, BN](block_idx.y, block_idx.x)
var C_warp_tile = C_block_tile.tile[WM, WN](warp_y, warp_x)
var mma_op = TensorCore[A.dtype, C.dtype, Index(MMA_M, MMA_N, MMA_K)]()
# Shared SRAM tiles (no padding to stay under shared memory limit)
var A_sram_tile = LayoutTensor[
A.dtype,
Layout.row_major(BM, BK),
MutAnyOrigin,
address_space=AddressSpace.SHARED,
].stack_allocation()
var B_sram_tile = LayoutTensor[
B.dtype,
Layout.row_major(BK, BN),
MutAnyOrigin,
address_space=AddressSpace.SHARED,
].stack_allocation()
# One per-warp accumulator tile of shape [WM, WN]
var C_warp_accum = LayoutTensor[
C.dtype,
Layout.row_major(WM, WN),
MutAnyOrigin,
address_space=AddressSpace.LOCAL,
].stack_allocation()
# Zero initialize accumulator (only for active warps)
if warp_is_active:
comptime for i in range(WM):
comptime for j in range(WN):
C_warp_accum[i, j] = 0.0
# (Removed shared C accumulator to reduce shared usage)
# Sweep across K in BK chunks (single-buffered)
for k_i in range(K // BK):
barrier()
var A_dram_tile = A.tile[BM, BK](block_idx.y, k_i)
var B_dram_tile = B.tile[BK, BN](k_i, block_idx.x)
copy_dram_to_sram_async[
thread_layout=Layout.row_major(4, 8),
num_threads=256,
block_dim_count=BLOCK_DIM_COUNT,
](A_sram_tile.vectorize[1, 4](), A_dram_tile.vectorize[1, 4]())
copy_dram_to_sram_async[
thread_layout=Layout.row_major(4, 8),
num_threads=256,
block_dim_count=BLOCK_DIM_COUNT,
](B_sram_tile.vectorize[1, 4](), B_dram_tile.vectorize[1, 4]())
async_copy_wait_all()
barrier()
if warp_is_active:
var A_warp_tile = A_sram_tile.tile[WM, BK](warp_y, 0)
var B_warp_tile = B_sram_tile.tile[BK, WN](0, warp_x)
comptime for mma_k in range(BK // MMA_K):
comptime for mma_m in range(WM // MMA_M):
comptime for mma_n in range(WN // MMA_N):
var A_mma_tile = A_warp_tile.tile[MMA_M, MMA_K](
mma_m, mma_k
)
var B_mma_tile = B_warp_tile.tile[MMA_K, MMA_N](
mma_k, mma_n
)
C_mma_tile = C_warp_accum.tile[MMA_M, MMA_N](
mma_m, mma_n
)
var a_reg = mma_op.load_a(A_mma_tile)
var b_reg = mma_op.load_b(B_mma_tile)
var c_reg = mma_op.load_c(C_mma_tile)
var d_reg = mma_op.mma_op(a_reg, b_reg, c_reg)
mma_op.store_d(C_mma_tile, d_reg)
# Store the final per-warp accumulation to the output warp tile
if warp_is_active:
comptime for mma_m in range(WM // MMA_M):
comptime for mma_n in range(WN // MMA_N):
var C_mma_tile = C_warp_tile.tile[MMA_M, MMA_N](mma_m, mma_n)
var Acc_mma_tile = C_warp_accum.tile[MMA_M, MMA_N](mma_m, mma_n)
var frag = mma_op.load_c(Acc_mma_tile)
mma_op.store_d(C_mma_tile, frag)
์ด ํ์ด๋ ํ ์ ์ฝ์ด ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ ๋ณด์ฌ์ค๋๋ค:
-
์ํ ๊ตฌ์ฑ
warp_id = thread_idx.x // WARP_SIZE๋ก ๋ธ๋ก ๋ด ์ํ ์ขํ๋ฅผ ๊ณ์ฐํฉ๋๋ค- ์ํ๋ฅผ ์ถ๋ ฅ ํ์ผ์ ๋งคํํฉ๋๋ค: ๊ฐ ์ํ๊ฐ
WMรWN์์ญ์ ๋ด๋นํฉ๋๋ค - ์์๋ณด๋ค ์ ์ ์์ ์ํ๊ฐ ์๋ ๋ธ๋ก์ ์ฒ๋ฆฌํ๊ธฐ ์ํด
warp_is_active๊ฐ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค
-
๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์ต์ ํ
- ๊ธ๋ก๋ฒ โ ๊ณต์ : ํจ์จ์ ์ธ ๋ธ๋ก ์์ค ์ ์ก์ ์ํด
copy_dram_to_sram_async๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๊ณต์ โ ๋ ์ง์คํฐ: ์ํ ์์ค ํ๋๊ทธ๋จผํธ ๋ก๋ฉ์ ์ํด
mma_op.load_a/load_b๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๋ ์ง์คํฐ ์ฐ์ฐ: ํ๋์จ์ด ๊ฐ์ ํ๋ ฌ ์ฐ์ฐ์ ์ํด
mma_op.mma_op๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๋ ์ง์คํฐ โ ๊ธ๋ก๋ฒ: ํจ์จ์ ์ธ ๊ฒฐ๊ณผ ์ ์ฅ์ ์ํด
mma_op.store_d๋ฅผ ์ฌ์ฉํฉ๋๋ค
- ๊ธ๋ก๋ฒ โ ๊ณต์ : ํจ์จ์ ์ธ ๋ธ๋ก ์์ค ์ ์ก์ ์ํด
-
ํ ์ ์ฝ์ด ์ฐ์ฐ
load_a(A_mma_tile): 16ร8 ํ๋ ฌ A ํ๋๊ทธ๋จผํธ๋ฅผ ๋ ์ง์คํฐ์ ๋ก๋ํฉ๋๋คload_b(B_mma_tile): 8ร8 ํ๋ ฌ B ํ๋๊ทธ๋จผํธ๋ฅผ ๋ ์ง์คํฐ์ ๋ก๋ํฉ๋๋คload_c(C_mma_tile): 16ร8 ๋์ฐ๊ธฐ ํ๋๊ทธ๋จผํธ๋ฅผ ๋ก๋ํฉ๋๋คmma_op(a_reg, b_reg, c_reg): ์ ์ฉ ํ๋์จ์ด๋ฅผ ์ฌ์ฉํ์ฌ D = AรB + C๋ฅผ ๊ณ์ฐํฉ๋๋คstore_d(C_mma_tile, d_reg): 16ร8 ๊ฒฐ๊ณผ ํ๋๊ทธ๋จผํธ๋ฅผ ์ ์ฅํฉ๋๋ค
-
ํฌ๋ก์ค ํ๋ซํผ ํธํ์ฑ
- ๋ชจ๋ ํ์ผ๋ง ํ๋ผ๋ฏธํฐ๊ฐ
WARP_SIZE์ ๋ฐฐ์์ ๋๋ค (NVIDIA์์ 32, AMD์์ 64) - Mojo๋
TensorCore์ธํฐํ์ด์ค๋ฅผ ํตํด ํ๋์จ์ด ์ฐจ์ด๋ฅผ ์ถ์ํํฉ๋๋ค - ๋์ผํ ์ฝ๋๊ฐ NVIDIA ํ ์ ์ฝ์ด์ AMD Matrix Core ๋ชจ๋์์ ๋์ํฉ๋๋ค
- ๋ชจ๋ ํ์ผ๋ง ํ๋ผ๋ฏธํฐ๊ฐ
ํต์ฌ ์ธ์ฌ์ดํธ๋ ํ ์ ์ฝ์ด๊ฐ ์ค๋ ๋ ์์ค์ ๊ฐ๋ณ ์์๊ฐ ์๋ ์ํ ์์ค์ ์ ์ฒด ํ๋ ฌ ํ๋๊ทธ๋จผํธ ๋จ์๋ก ๋์ํ๋ค๋ ๊ฒ์ ๋๋ค. ์ด๋ฅผ ํตํด ๋๊ท๋ชจ ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ์ ์ฉ ํ๋์จ์ด ๊ฐ์์ด ๊ฐ๋ฅํด์ง๋๋ค.
์ฑ๋ฅ ๋ถ์: ์ด๊ฒ์ผ๋ก ๋์ผ๊น?
์ด์ ํ ์ ์ฝ์ด๊ฐ ๊ด์ฉ์ ํ์ผ๋ง ๋ฐฉ์ ๋๋น ์ฝ์๋ ์ฑ๋ฅ ์ฐ์๋ฅผ ์ค์ ๋ก ์ ๊ณตํ๋์ง ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค.
ํ๋กํ์ผ๋ง์ฉ ๋น๋
uv run mojo build problems/p33/p33.mojo -o problems/p33/p33_profiler
pixi run mojo build problems/p33/p33.mojo -o problems/p33/p33_profiler
NVIDIA Nsight Compute๋ก ํ๋กํ์ผ๋ง (NVIDIA ์ ์ฉ)
๋จผ์ ncu์ ์ ๊ทผํ๊ธฐ ์ํด CUDA ํ๊ฒฝ์ ์ง์
ํฉ๋๋ค:
# Enter CUDA environment
pixi shell -e nvidia
# Profile tensor core version
ncu --set full --metrics sm__cycles_elapsed.avg,smsp__cycles_active.avg.pct_of_peak_sustained_elapsed,dram__throughput.avg.pct_of_peak_sustained_elapsed,smsp__inst_executed_pipe_tensor_op_hmma.sum ./problems/p33p33_profiler --tensor-core
# Profile tiled version for comparison
ncu --set full --metrics sm__cycles_elapsed.avg,smsp__cycles_active.avg.pct_of_peak_sustained_elapsed,dram__throughput.avg.pct_of_peak_sustained_elapsed ./problems/p33p33_profiler --tiled
๋น๊ตํ ํต์ฌ ๋ฉํธ๋ฆญ
์ฑ๋ฅ ๋ฉํธ๋ฆญ:
- Duration: ์ ์ฒด kernel ์คํ ์๊ฐ (๋ฎ์์๋ก ์ข์)
- SM Active %: SM ํ์ฉ๋ฅ (๋์์๋ก ์ข์)
- DRAM Throughput: ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ํ์ฉ๋ฅ (๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋ ์ฌ๋ถ๋ฅผ ๋ณด์ฌ์ค)
- Tensor Op Instructions: ์ค์ ํ ์ ์ฝ์ด ์ฐ์ฐ ํ์ (ํ ์ ์ฝ์ด ๋ฒ์ ์๋ง ํด๋น)
์ผ๋ฐ์ ์ธ ๊ฒฐ๊ณผ:
ํ ์ ์ฝ์ด ๋ฒ์ (๋ ๋๋ฆผ):
- Duration: ~13.9 ms (ํจ์ฌ ๋๋ฆผ!)
- SM Active: 83.7% (์ข์ ํ์ฉ๋ฅ )
- DRAM Throughput: 72.5% (๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋!)
- Occupancy: 26.3% (๋์จ - ๋ ์ง์คํฐ์ ์ํด ์ ํ๋จ)
- Tensor Op Instructions: 1,048,576 (ํ ์ ์ฝ์ด๊ฐ ๋์ ์ค์์ ํ์ธ)
ํ์ผ๋ง ๋ฒ์ (๋ ๋น ๋ฆ):
- Duration: ~1.62 ms (8.6๋ฐฐ ๋น ๋ฆ!)
- SM Active: 98.0% (ํ์ํ ํ์ฉ๋ฅ )
- DRAM Throughput: 1.7% (์์๋๋ก ์ฐ์ฐ ๋ฐ์ด๋)
- Occupancy: 66.7% (ํจ์ฌ ๋์)
- L2 Hit Rate: 96.9% vs 29.7% (ํจ์ฌ ๋์ ์บ์ ์ง์ญ์ฑ)
์ ํ ์ ์ฝ์ด๊ฐ ๋ ๋๋ฆด๊น?
- ๋ฉ๋ชจ๋ฆฌ ๋ณ๋ชฉ: 72% DRAM ์ฌ์ฉ๋์ ์ฐ์ฐ ๋ฐ์ด๋๊ฐ ์๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์์ ๋ณด์ฌ์ค๋๋ค
- ๋ฎ์ ์ ์ ์จ: 26% vs 67% - ๋์ ๋ ์ง์คํฐ ์ฌ์ฉ๋(์ค๋ ๋๋น 68 vs 38)์ด ๋์ ์ํ ์๋ฅผ ์ ํํฉ๋๋ค
- ์บ์ ๋ฏธ์ค: 29% L2 ์ ์ค๋ฅ vs 97%๋ ๋ฎ์ ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ์ ๋ณด์ฌ์ค๋๋ค
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ถฉ๋: ์ต์ ํ๋์ง ์์ ์ ๊ทผ ํจํด์ผ๋ก ์ธํ ๋ฑ ํฌ ์ถฉ๋
- ์คํ ์ค์ : ์ด ๋ฌธ์ ํฌ๊ธฐ์ ๋ํด ์ต์ ์ด ์๋ ๋ธ๋ก/์ํ ๊ตฌ์ฑ
์ฑ๋ฅ์ ํ์ค
ํ๋กํ์ผ๋ง ๊ฒฐ๊ณผ์์ ๋ณผ ์ ์๋ฏ์ด, โ์ ์ฉ ํ๋์จ์ดโ๊ฐ ์๋์ผ๋ก ๋นจ๋ผ์ง๋ ๊ฒ์ ์๋๋๋ค! ํ ์ ์ฝ์ด ๋ฒ์ ์ ๋จ์ํ ํ์ผ๋ง ๋ฐฉ์๋ณด๋ค ์๋นํ ๋๋ฆฝ๋๋ค(~8.6๋ฐฐ). ์ด๋ GPU ์ต์ ํ์์ ํํ ๋ณผ ์ ์๋ ํ์ค์ ๋๋ค - ํ๋์จ์ด์ ์์ ์ฑ๋ฅ์ด ๊ณง ๋ ๋์ ์ฑ๋ฅ์ ๋ณด์ฅํ์ง๋ ์์ต๋๋ค.
ํต์ฌ ์ธ์ฌ์ดํธ:
- ๋ฉ๋ชจ๋ฆฌ ๋ณ๋ชฉ: 72% DRAM ์ฌ์ฉ๋์ ํ ์ ์ฝ์ด๊ฐ ์ฐ์ฐ ๋ฐ์ด๋๊ฐ ์๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์์ ๋ณด์ฌ์ค๋๋ค
- ๋ฎ์ ์ ์ ์จ: ๋์ ๋ ์ง์คํฐ ์ฌ์ฉ๋์ผ๋ก ์ธํด 26% vs 67%๋ก ๋์ ์ํ ์๊ฐ ์ ํ๋ฉ๋๋ค
- ์บ์ ๋ฏธ์ค: 29% vs 97% L2 ์ ์ค๋ฅ ์ ๋ฎ์ ๋ฉ๋ชจ๋ฆฌ ์ง์ญ์ฑ์ ๋ณด์ฌ์ค๋๋ค
- ๋ฆฌ์์ค ๋ญ๋น: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๋ฑ ํฌ ์ถฉ๋๊ณผ ์ต์ ์ด ์๋ ์คํ ์ค์
๊ตํ: ์ฑ๋ฅ ๋ณ๋ชฉ์ ์ดํดํ๊ณ ์ฒด๊ณ์ ์ผ๋ก ์ต์ ํํ๋ ๊ฒ์ด โ์ต์ ์ ๊ฐ์ฅ ๋ฐ์ด๋โ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ์ค์ํฉ๋๋ค. ํ๋์จ์ด ๊ธฐ๋ฅ์ ์ธ์ฌํ ํ๋์ด ํ์ํ ๋๊ตฌ์ด์ง, ๋ง๋ฒ์ ์ํํ์ด ์๋๋๋ค.
๋ค์ ๋จ๊ณ
๋ณด๋ ์๋ GPU ์ต์ ํ ๋์ ์ ํ ์ค๋น๊ฐ ๋์ จ๋์? ๐ฏ ์ฑ๋ฅ ๋ณด๋์ค ์ฑ๋ฆฐ์ง๋ก ์ด๋ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ธ ํ ์ ์ฝ์ด ๊ตฌํ์ ๋จ์ํ ํ์ผ๋ง ๋ฒ์ ์ ์ค์ ๋ก ์ด๊ธฐ๋ ๊ตฌํ์ผ๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์!
๐ฏ ์ฑ๋ฅ ๋ณด๋์ค ์ฑ๋ฆฐ์ง
๋ฐ๊ฒฌ
Puzzle 33์ ์๋ฃํ๊ณ Mojo์ TensorCore API๋ฅผ
์ฌ์ฉํ์ฌ ์ค์ ํ
์ ์ฝ์ด ํ๋ ฌ ๊ณฑ์
์ ๊ตฌํํ์ต๋๋ค. ๊ตฌํ์ ์ ํํ๊ฒ ๋์ํ๊ณ , ๋ชจ๋
์ ํ๋ ํ
์คํธ๋ฅผ ํต๊ณผํ๋ฉฐ, ์ค์ ํ๋์จ์ด ๊ฐ์ ํ๋ ฌ ์ฐ์ฐ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ
Puzzle 16์ ํ์ผ๋ง ๋ฒ์ ๊ณผ ํ๋กํ์ผ๋ง์ผ๋ก ๋น๊ตํ๋ฉดโฆ
โ์ ์ฉ ํ๋์จ์ดโ๊ฐ ์์ฒญ๋๊ฒ ๋ ๋๋ฆฝ๋๋ค!
๋ฌด์์ด ์๋ชป๋ ๊ฑธ๊น?
(NVIDIA ์ ์ฉ) ncu๋ฅผ ์ฌ์ฉํ ํ๋กํ์ผ๋ง์ด ๋ํนํ ํ์ค์ ๋๋ฌ๋์ต๋๋ค (ํ๋กํ์ผ๋ง
๊ธฐ๋ฒ์ ๋ณต์ตํ๋ ค๋ฉด Puzzle 10์ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฅ ํ์ง์
Puzzle 30์ GPU ํ๋กํ์ผ๋ง์ ์ฐธ๊ณ ํ์ธ์):
ํ ์ ์ฝ์ด ๋ฒ์ (๊ธฐ๋์ ๋ชป ๋ฏธ์นจ):
- Duration: ~13.9 ms
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋: 72.5% DRAM ์ฒ๋ฆฌ๋ (์ฐ์ฐ ๋ฐ์ด๋์ฌ์ผ ํ๋๋ฐ!)
- ๋ฎ์ ์ ์ ์จ: 26.3% (ํ๋์จ์ด ๋ญ๋น)
- ์บ์ ์ฌ์: 29.7% L2 ์ ์ค๋ฅ
- ๋ ์ง์คํฐ ์๋ฐ: ์ค๋ ๋๋น 68๊ฐ ๋ ์ง์คํฐ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ถฉ๋: ๋ฑ ํฌ ์ถฉ๋์ด ์ฑ๋ฅ์ ํ๊ดด
ํ์ผ๋ง ๋ฒ์ (์น์):
- Duration: ~1.62 ms (8.6๋ฐฐ ๋น ๋ฆ!)
- ์ฐ์ฐ ๋ฐ์ด๋: 1.7% DRAM ์ฒ๋ฆฌ๋ (์์๋๋ก)
- ํ์ํ ์ ์ ์จ: 66.7%
- ์บ์ ์นํ์ : 96.9% L2 ์ ์ค๋ฅ
- ํจ์จ์ : ์ค๋ ๋๋น 38๊ฐ ๋ ์ง์คํฐ
- ๊น๋ํ ๋ฉ๋ชจ๋ฆฌ: ์ ์๋ฏธํ ๋ฑ ํฌ ์ถฉ๋ ์์
๋ํนํ ํ์ค
์ด๋ GPU ์ต์ ํ์์ ํํ ์ด์ผ๊ธฐ์ ๋๋ค: ํ๋์จ์ด์ ์์ ์ฑ๋ฅ โ ์ค์ ์ฑ๋ฅ. ํ ์ ์ฝ์ด๋ ๋๋๋๋ก ๊ฐ๋ ฅํ์ง๋ง, ๋์์ ์๊ตฌ์ฌํญ๋ ๋๋๋๋ก ๊น๋ค๋กญ์ต๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ๋ฒฝ: ์ฐ์ฐ์ด ๋๋ฌด ๋นจ๋ผ์ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ๋ณ๋ชฉ์ด ๋๋ฌ๋จ
- ๋ฆฌ์์ค ํ์: ๋์ ๋ ์ง์คํฐ ์ฌ์ฉ๋์ด ์ ์ ์จ์ ์ ํ์ํด
- ์ ๊ทผ ํจํด ๋ฏผ๊ฐ: ๋์ ๋ฉ๋ชจ๋ฆฌ ํจํด์ด ์บ์ ๋์์ ํ๊ดดํจ
- ์ค์ ์ด ํต์ฌ: ์คํ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ฒฝํ๊ฒ ํ๋ํด์ผ ํจ
๋ฏธ์ : ํ ์ ์ฝ์ด ์ฑ๋ฅ ๊ฐ์ ํ๊ธฐ
๋์ ๊ณผ์ : ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ด๋์ ๋ฎ์ ์ ์ ์จ์ธ ํ ์ ์ฝ์ด ๊ตฌํ์ ๋จ์ํ ํ์ผ๋ง ๋ฒ์ ์ ์ค์ ๋ก ์ด๊ธฐ๋ ๊ตฌํ์ผ๋ก ๋ณํํ์ธ์.
์ด๊ฒจ์ผ ํ ๊ธฐ์ค:
- ๋ชฉํ Duration: < 1.62 ms
- ์ ์ ์จ: > 26.3% ๊ธฐ์ค์
- DRAM ๋ถํ: < 72.5% ๊ธฐ์ค์
- ์บ์ ์ฑ๋ฅ: > 29.7% L2 ์ ์ค๋ฅ ๊ธฐ์ค์
ํ๊ตฌํ ์ต์ ํ ์ ๋ต:
-
๋ ์ง์คํฐ ์๋ฐ ์ค์ด๊ธฐ
- ๋ ์์ ๋์ฐ๊ธฐ ํ์ผ ์ฌ์ฉ
- ์ค๊ฐ ์ ์ฅ ๊ณต๊ฐ ์ต์ํ
- ๋ ์ง์คํฐ ์ฌ์ฉ๋์ ์ค์ด๊ธฐ ์ํด ํผํฉ ์ ๋ฐ๋ ๊ณ ๋ ค
- ํจ์จ์ ์ธ ๋์ ํจํด์ Puzzle 16์ ํ์ผ๋ง ๋ฐฉ์ ์ฐธ๊ณ
-
๋ฉ๋ชจ๋ฆฌ ํจํด ์ต์ ํ
- ๋ฑ ํฌ ์ถฉ๋์ ์ ๊ฑฐํ๊ธฐ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํจ๋ฉ ์ถ๊ฐ (๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ฐ๋ ์ฐธ๊ณ )
copy_dram_to_sram_async๋ ์ด์์ ์ต์ ํ- ๋ณํฉ ํจํด ๊ฐ์ (์ด๋ฐ ํผ์ฆ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๊ธฐ์ด ์ฐธ๊ณ )
-
์ ์ ์จ ๊ฐ์
- ๋ ๋์ ์ํ ํ์ฉ์ ์ํ ๋ธ๋ก ํฌ๊ธฐ ํ๋
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ vs ๋ ์ง์คํฐ ์ฌ์ฉ๋ ๊ท ํ ๋ง์ถ๊ธฐ
- ์ํ-SM ๋งคํ ์ต์ ํ
- Puzzle 11-20 ์๋ฆฌ์ฆ์ ์ค๋ ๋ ์กฐ์ ๊ตํ ์ ์ฉ
-
์บ์ ์ต์ ํ
- ๋ฐ์ดํฐ ์ฌ์ฌ์ฉ ํจํด ๊ฐ์
- ์บ์ ๊ณ์ธต ๊ตฌ์กฐ์ ๋ง๋ ํ์ผ ํฌ๊ธฐ ์ต์ ํ
- ๋ฐ์ดํฐ ๋ ์ด์์ ๋ณํ ๊ณ ๋ ค
- ์ด์ ํผ์ฆ ๊ณผ์ ์ ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ๊ฐ๋ ํ์ฉ
-
๊ณ ๊ธ ๊ธฐ๋ฒ
- ๋ฉ๋ชจ๋ฆฌ์ ์ฐ์ฐ์ ์ค์ฒฉํ๊ธฐ ์ํ ๋๋ธ ๋ฒํผ๋ง ๊ตฌํ
- ์ํํธ์จ์ด ํ์ดํ๋ผ์ด๋ ์ฌ์ฉ
- ๋น๋๊ธฐ ์คํ ํจํด ํ๊ตฌ
- ์๋ํ์ด์ ํผ์ฆ์ ๊ณ ๊ธ ์กฐ์ ๊ธฐ๋ฒ ์ ์ฉ
์ฑ๊ณต ๊ธฐ์ค
- ์ ํ์ฑ: ๋ชจ๋ ์ ํ๋ ํ ์คํธ๊ฐ ์ฌ์ ํ ํต๊ณผ
- ์ฑ๋ฅ: ํ ์ ์ฝ์ด Duration < 1.62 ms
- ํจ์จ์ฑ: ๋ ๋์ ์ ์ ์จ (>26.3%)
- ๋ฉ๋ชจ๋ฆฌ: ๋ ๋ฎ์ DRAM ๋ถํ (<72.5%)
- ์บ์: ๋ ๋์ ์ ์ค๋ฅ (>29.7% L2)
๋ ๊น์ ๊ตํ
์ด ๋ณด๋์ค ์ฑ๋ฆฐ์ง๋ GPU ์ต์ ํ์์ ๊ฐ์ฅ ์ค์ํ ๊ตํ์ ๊ฐ๋ฅด์นฉ๋๋ค: ๋ณ๋ชฉ์ ์ดํดํ๋ ๊ฒ์ด ์ต์ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ์ค์ํฉ๋๋ค.
๋ชฉํ๋ ๋จ์ํ ํ ์ ์ฝ์ด๋ฅผ ๋ ๋น ๋ฅด๊ฒ ๋ง๋๋ ๊ฒ์ด ์๋๋๋ค - ํ ์ ์ฝ์ด๊ฐ ์ ๋ ๋๋ ค์ง ์ ์๋์ง ์ดํดํ๊ณ , ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ง๋จํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๊ณ , ์์น์ ๊ธฐ๋ฐํ ์ต์ ํ ๊ธฐ๋ฒ์ ์ ์ฉํ๋ ๊ฒ์ ๋๋ค.
์ด ์ฑ๋ฆฐ์ง๋ฅผ ์์ํ๋ฉด, ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋์จ์ด ๊ธฐ๋ฅ๊ณผ ๊ด๊ณ์์ด ์ด๋ค GPU ์ํฌ๋ก๋๋ ์ต์ ํํ ์ ์๋ ์ญ๋์ ๊ฐ์ถ๊ฒ ๋ฉ๋๋ค.
Puzzle 34: GPU ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ (SM90+)
์๊ฐ
ํ๋์จ์ด ์๊ตฌ์ฌํญ: โ ๏ธ NVIDIA SM90+ ์ ์ฉ
์ด ํผ์ฆ์ SM90+ ์ปดํจํธ ๋ฅ๋ ฅ์ ๊ฐ์ถ NVIDIA Hopper ์ํคํ ์ฒ (H100, H200) ์ด์์ GPU๊ฐ ํ์ํฉ๋๋ค. ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ API๋ ํ๋์จ์ด ๊ฐ์ ๊ธฐ๋ฐ์ด๋ฉฐ, ์ง์ํ์ง ์๋ ํ๋์จ์ด์์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ฌ์ฉ ์ค์ธ ์ํคํ ์ฒ๊ฐ ํ์คํ์ง ์๋ค๋ฉด
pixi run gpu-specs๋ฅผ ์คํํ์ฌ ์ต์Compute Cap: 9.0์ด์์ธ์ง ํ์ธํ์ธ์ (ํ๋์จ์ด ์๋ณ์ ๋ํ ์์ธํ ๋ด์ฉ์ NVIDIA ํ๋กํ์ผ๋ง ๊ธฐ์ด๋ฅผ ์ฐธ๊ณ ํ์ธ์)
์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ (Puzzle 24-26) ์์ ๋ธ๋ก ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ (Puzzle 27) ๊น์ง์ ์ฌ์ ์ ์ด์ด, ์ด์ ํด๋ฌ์คํฐ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ์ ๋ฐฐ์๋๋ค - ๋จ์ผ ๋ธ๋ก์ ํ๊ณ๋ฅผ ๋์ด์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ์กฐ์ ํ๋ ๊ธฐ๋ฒ์ ๋๋ค.
์ค๋ ๋ ๋ธ๋ก ํด๋ฌ์คํฐ๋?
์ค๋ ๋ ๋ธ๋ก ํด๋ฌ์คํฐ๋ ํ๋์จ์ด ๊ฐ์ ๋๊ธฐํ ๋ฐ ํต์ ๊ธฐ๋ณธ ์์๋ฅผ ํตํด ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ด ํ๋ ฅํ์ฌ ํ๋์ ์ฐ์ฐ ์์ ์ ์ํํ ์ ์๊ฒ ํด์ฃผ๋ ํ์ ์ ์ธ SM90+ ๊ธฐ๋ฅ์ ๋๋ค.
ํต์ฌ ๊ธฐ๋ฅ:
- ๋ธ๋ก ๊ฐ ๋๊ธฐํ:
cluster_sync,cluster_arrive,cluster_wait๋ก ์ฌ๋ฌ ๋ธ๋ก์ ์กฐ์ ํฉ๋๋ค - ๋ธ๋ก ์๋ณ:
block_rank_in_cluster๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ ์ ํ ๋ธ๋ก ์กฐ์ ์ ์ํํฉ๋๋ค - ํจ์จ์ ์ธ ์กฐ์ :
elect_one_sync๋ก ์ต์ ํ๋ ์ํ ์์ค ํ๋ ฅ์ ๊ตฌํํฉ๋๋ค - ๊ณ ๊ธ ํจํด:
cluster_mask_base๋ก ์ ํ์ ๋ธ๋ก ์กฐ์ ์ ์ํํฉ๋๋ค
ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ
๊ธฐ์กด GPU ํ๋ก๊ทธ๋๋ฐ ๊ณ์ธต ๊ตฌ์กฐ
Grid (Multiple Blocks)
โโโ Block (Multiple Warps) - barrier() synchronization
โโโ Warp (32 Threads) - SIMT lockstep execution
โ โโโ Lane 0 โโ
โ โโโ Lane 1 โ All execute same instruction
โ โโโ Lane 2 โ at same time (SIMT)
โ โ ... โ warp.sum(), warp.broadcast()
โ โโโ Lane 31 โโ
โโโ Thread (SIMD operations within each thread)
์๋ก์ด ๊ณ์ธต: ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๊ณ์ธต ๊ตฌ์กฐ:
Grid (Multiple Clusters)
โโโ ๐ Cluster (Multiple Blocks) - cluster_sync(), cluster_arrive()
โโโ Block (Multiple Warps) - barrier() synchronization
โโโ Warp (32 Threads) - SIMT lockstep execution
โ โโโ Lane 0 โโ
โ โโโ Lane 1 โ All execute same instruction
โ โโโ Lane 2 โ at same time (SIMT)
โ โ ... โ warp.sum(), warp.broadcast()
โ โโโ Lane 31 โโ
โโโ Thread (SIMD operations within each thread)
์คํ ๋ชจ๋ธ ์์ธ:
- ์ค๋ ๋ ๋ ๋ฒจ: ๊ฐ๋ณ ์ค๋ ๋ ๋ด์์์ SIMD ์ฐ์ฐ
- ์ํ ๋ ๋ฒจ: SIMT ์คํ - 32๊ฐ ์ค๋ ๋์ ๋ก์คํ ์กฐ์
- ๋ธ๋ก ๋ ๋ฒจ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ฐฐ๋ฆฌ์ด๋ฅผ ํ์ฉํ ๋ฉํฐ ์ํ ์กฐ์
- ๐ ํด๋ฌ์คํฐ ๋ ๋ฒจ: SM90+ ํด๋ฌ์คํฐ API๋ฅผ ํ์ฉํ ๋ฉํฐ ๋ธ๋ก ์กฐ์
ํ์ต ๋จ๊ณ
์ด ํผ์ฆ์ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ์ญ๋์ ์ฒด๊ณ์ ์ผ๋ก ์์๊ฐ๋ 3๋จ๊ณ ๊ตฌ์ฑ์ผ๋ก ์ค๊ณ๋์์ต๋๋ค:
๐ฐ ๋ฉํฐ ๋ธ๋ก ์กฐ์ ๊ธฐ์ด
ํต์ฌ: ํด๋ฌ์คํฐ ๋๊ธฐํ ํจํด์ ๊ธฐ๋ณธ ์ดํด
์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ด
cluster_arrive()์
cluster_wait()๋ฅผ
์ฌ์ฉํ์ฌ ๊ธฐ๋ณธ์ ์ธ ๋ธ๋ก ๊ฐ ํต์ ๊ณผ ๋ฐ์ดํฐ ๋ถ๋ฐฐ๋ฅผ ์ํด ์คํ์ ์กฐ์ ํ๋ ๋ฐฉ๋ฒ์
๋ฐฐ์๋๋ค.
์ฃผ์ API:
block_rank_in_cluster(),
cluster_arrive(),
cluster_wait()
โธ๏ธ ํด๋ฌ์คํฐ ์ ์ฒด ์งํฉ ์ฐ์ฐ
ํต์ฌ: ๋ธ๋ก ๋ ๋ฒจ ํจํด์ ํด๋ฌ์คํฐ ๊ท๋ชจ๋ก ํ์ฅ
์ต์ํ block.sum() ๊ฐ๋
์ ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์ณ ํ์ฅํ์ฌ ๋๊ท๋ชจ ์ฐ์ฐ์
์กฐ์ ํ๋ ํด๋ฌ์คํฐ ์ ์ฒด ๋ฆฌ๋์
๊ณผ ์งํฉ ์ฐ์ฐ์ ๋ฐฐ์๋๋ค.
์ฃผ์ API:
cluster_sync(),
ํจ์จ์ ์ธ ํด๋ฌ์คํฐ ์กฐ์ ์ ์ํ
elect_one_sync()
๐ง ๊ณ ๊ธ ํด๋ฌ์คํฐ ์๊ณ ๋ฆฌ์ฆ
ํต์ฌ: ํ๋ก๋์ ์์ค์ ๋ค๋จ๊ณ ์กฐ์ ํจํด
GPU ํ์ฉ๋ฅ ์ ๊ทน๋ํํ๊ณ ๋ณต์กํ ์ฐ์ฐ ์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ์ํ ๋ ๋ฒจ, ๋ธ๋ก ๋ ๋ฒจ, ํด๋ฌ์คํฐ ๋ ๋ฒจ์ ์กฐ์ ์ ๊ฒฐํฉํ๋ ์ ๊ตํ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค.
์ฃผ์ API:
elect_one_sync(),
cluster_arrive(),
๊ณ ๊ธ ์กฐ์ ํจํด
ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ์ด ์ค์ํ ์ด์
๋ฌธ์ ๊ท๋ชจ: ํ๋ AI ๋ฐ ๊ณผํ ์ํฌ๋ก๋๋ ๋จ์ผ ์ค๋ ๋ ๋ธ๋ก์ ๋ฅ๋ ฅ์ ์ด๊ณผํ๋ ์ฐ์ฐ์ ํ์๋ก ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค:
- ๋ธ๋ก ๊ฐ ์กฐ์ ์ด ํ์ํ ๋๊ท๋ชจ ํ๋ ฌ ์ฐ์ฐ (Puzzle 16์ ํ๋ ฌ ๊ณฑ์ ๊ณผ ๊ฐ์)
- Puzzle 29์ ์์ฐ์-์๋น์ ์์กด์ฑ์ ๊ฐ๋ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ
- Puzzle 8์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ํฐ ๋ฐ์ดํฐ์ ์ ๋ํ ์ ์ญ ํต๊ณ
- ์ด์ ๋ธ๋ก ๊ฐ ํต์ ์ด ํ์ํ ๊ณ ๊ธ ์คํ ์ค ์ฐ์ฐ
ํ๋์จ์ด ๋ฐ์ : GPU๊ฐ ๋ ๋ง์ ์ฐ์ฐ ์ ๋์ ๊ฐ์ถ๊ฒ ๋จ์ ๋ฐ๋ผ (Puzzle 30์ GPU ์ํคํ ์ฒ ํ๋กํ์ผ๋ง ์ฐธ๊ณ ), ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ์ ์ฐจ์ธ๋ ํ๋์จ์ด๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ฉํ๋ ๋ฐ ํ์์ ์ด ๋ฉ๋๋ค.
๊ต์ก์ ๊ฐ์น
์ด ํผ์ฆ์ ์๋ฃํ๋ฉด ์์ ํ GPU ํ๋ก๊ทธ๋๋ฐ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํ์ตํ๊ฒ ๋ฉ๋๋ค:
- ์ค๋ ๋ ๋ ๋ฒจ: SIMD ์ฐ์ฐ์ ์ํํ๋ ๊ฐ๋ณ ์ฐ์ฐ ๋จ์
- ์ํ ๋ ๋ฒจ: 32๊ฐ ์ค๋ ๋ SIMT ์กฐ์ (Puzzle 24-26)
- ๋ธ๋ก ๋ ๋ฒจ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ฉํ ๋ฉํฐ ์ํ ์กฐ์ (Puzzle 27)
- ๐ ํด๋ฌ์คํฐ ๋ ๋ฒจ: ๋ฉํฐ ๋ธ๋ก ์กฐ์ (Puzzle 34)
- ๊ทธ๋ฆฌ๋ ๋ ๋ฒจ: ๋ค์์ SM(Streaming Multiprocessor)์ ๊ฑธ์น ๋ ๋ฆฝ์ ๋ธ๋ก ์คํ
์ด ๊ณผ์ ์ Puzzle 30-32์ ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ์ ๊ธฐ๋ฐ์ผ๋ก, ์ฐจ์ธ๋ GPU ํ๋ก๊ทธ๋๋ฐ๊ณผ ๋๊ท๋ชจ ๋ณ๋ ฌ ์ปดํจํ ๋์ ์ ๋๋นํ ์ ์๋๋ก ์ค๋น์์ผ ์ค๋๋ค.
์์ํ๊ธฐ
์ ์ ์กฐ๊ฑด:
- ๋ธ๋ก ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ (Puzzle 27)์ ๋ํ ์์ ํ ์ดํด
- ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ (Puzzle 24-26) ๊ฒฝํ
- ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ฐ๋ (Puzzle 8)์ ํตํ GPU ๋ฉ๋ชจ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ ์์ง
- ๋ฐฐ๋ฆฌ์ด๋ฅผ ํ์ฉํ GPU ๋๊ธฐํ (Puzzle 29)์ ๋ํ ์ดํด
- NVIDIA SM90+ ํ๋์จ์ด ๋๋ ํธํ ํ๊ฒฝ ์ ๊ทผ
๊ถ์ฅ ํ์ต ๋ฐฉ๋ฒ: 3๋จ๊ณ ๊ตฌ์ฑ์ ์์๋๋ก ๋ฐ๋ผ๊ฐ์ธ์. ๊ฐ ๋จ๊ณ๊ฐ ๋ค์ ๋จ๊ณ์ ๋ณต์ก์ฑ์ ์ํ ํต์ฌ ๊ฐ๋ ์ ๊ตฌ์ถํฉ๋๋ค.
ํ๋์จ์ด ์ฐธ๊ณ : SM90+ ์ด์ธ์ ํ๋์จ์ด์์ ์คํํ๋ ๊ฒฝ์ฐ, ์ด ํผ์ฆ์ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ๊ณผ API ์ฌ์ฉ ํจํด์ ๊ต์ก์ ์์ ๋ก ํ์ฉํ ์ ์์ต๋๋ค.
GPU ํ๋ก๊ทธ๋๋ฐ์ ๋ฏธ๋๋ฅผ ๋ฐฐ์ธ ์ค๋น๊ฐ ๋์ จ๋์? ๋ฉํฐ ๋ธ๋ก ์กฐ์ ๊ธฐ์ด ๋ถํฐ ์์ํ์ฌ ๊ธฐ๋ณธ์ ์ธ ํด๋ฌ์คํฐ ๋๊ธฐํ ํจํด์ ๋ฐฐ์๋ณด์ธ์!
๋ฉํฐ ๋ธ๋ก ์กฐ์ ๊ธฐ์ด
๊ฐ์
์ฒซ ๋ฒ์งธ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๋์ ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค! ์ด ์น์ ์์๋ SM90+ ํด๋ฌ์คํฐ API๋ฅผ ์ฌ์ฉํ ๋ธ๋ก ๊ฐ ์กฐ์ ์ ๊ธฐ๋ณธ ๊ตฌ์ฑ ์์๋ฅผ ์๊ฐํฉ๋๋ค.
๋์ ๊ณผ์ : 4๊ฐ์ ์ค๋ ๋ ๋ธ๋ก์ด ์กฐ์ ํ์ฌ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ ๋ฒ์๋ฅผ ์ฒ๋ฆฌํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ์ถ๋ ฅ ๋ฐฐ์ด์ ์ ์ฅํ๋ ๋ฉํฐ ๋ธ๋ก ํ์คํ ๊ทธ๋จ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค.
ํต์ฌ ํ์ต:
cluster_arrive()
โ ์ฒ๋ฆฌ โ
cluster_wait()๋ผ๋
ํ์์ ์ธ ํด๋ฌ์คํฐ ๋๊ธฐํ ํจํด์ ๋ฐฐ์๋๋ค.
Puzzle 29์ barrier()์์ ๋ฐฐ์ด ๋๊ธฐํ ๊ฐ๋
์
ํ์ฅํฉ๋๋ค.
๋ฌธ์ : ๋ฉํฐ ๋ธ๋ก ํ์คํ ๊ทธ๋จ ๊ตฌ๊ฐ ๋ถ๋ฅ
Puzzle 27๊ณผ ๊ฐ์ ๊ธฐ์กด์ ๋จ์ผ ๋ธ๋ก ์๊ณ ๋ฆฌ์ฆ์ ํ๋์ ๋ธ๋ก์ด ๊ฐ์ง ์ค๋ ๋ ์ฉ๋(์: 256๊ฐ ์ค๋ ๋) ๋ด์ ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ง ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. Puzzle 8์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ ์ด๊ณผํ๋ ๋ ํฐ ๋ฐ์ดํฐ์ ์ ๊ฒฝ์ฐ, ์ฌ๋ฌ ๋ธ๋ก์ด ํ๋ ฅํด์ผ ํฉ๋๋ค.
๊ณผ์ : 4๊ฐ ๋ธ๋ก ๊ฐ๊ฐ์ด ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ ๋ฒ์๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๊ณ ์ ํ ๋ธ๋ก ์์๋ก ๊ฐ์ ์ค์ผ์ผ๋งํ๋ฉฐ, Puzzle 29์ ๋๊ธฐํ ํจํด์ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ๋ธ๋ก๋ค๊ณผ ์กฐ์ ํจ์ผ๋ก์จ ๋ชจ๋ ๋ธ๋ก์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋ ํ์์ผ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์ฝ์ ์ ์๋๋ก ํ๋ ํ์คํ ๊ทธ๋จ์ ๊ตฌํํ์ธ์.
๋ฌธ์ ๋ช ์ธ
๋ฉํฐ ๋ธ๋ก ๋ฐ์ดํฐ ๋ถ๋ฐฐ:
- Block 0: ์์ 0-255๋ฅผ ์ฒ๋ฆฌ, 1๋ฐฐ ์ค์ผ์ผ๋ง
- Block 1: ์์ 256-511์ ์ฒ๋ฆฌ, 2๋ฐฐ ์ค์ผ์ผ๋ง
- Block 2: ์์ 512-767์ ์ฒ๋ฆฌ, 3๋ฐฐ ์ค์ผ์ผ๋ง
- Block 3: ์์ 768-1023์ ์ฒ๋ฆฌ, 4๋ฐฐ ์ค์ผ์ผ๋ง
์กฐ์ ์๊ตฌ์ฌํญ:
- ๊ฐ ๋ธ๋ก์
cluster_arrive()๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฃ๋ฅผ ์๋ ค์ผ ํฉ๋๋ค - ๋ชจ๋ ๋ธ๋ก์
cluster_wait()๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ๋ธ๋ก์ ๊ธฐ๋ค๋ ค์ผ ํฉ๋๋ค - ์ต์ข ์ถ๋ ฅ์ ๊ฐ ๋ธ๋ก์ ์ฒ๋ฆฌ๋ ํฉ๊ณ๋ฅผ 4๊ฐ ์์ ๋ฐฐ์ด๋ก ๋ณด์ฌ์ค๋๋ค
์ค์
- ๋ฌธ์ ํฌ๊ธฐ:
SIZE = 1024์์ (1D ๋ฐฐ์ด) - ๋ธ๋ก ์ค์ :
TPB = 256๋ธ๋ก๋น ์ค๋ ๋ ์(256, 1) - ๊ทธ๋ฆฌ๋ ์ค์ :
CLUSTER_SIZE = 4ํด๋ฌ์คํฐ๋น ๋ธ๋ก ์(4, 1) - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ์
๋ ฅ
row_major[SIZE](), ์ถ๋ ฅrow_major[CLUSTER_SIZE]()
์ค๋ ๋ ๋ธ๋ก ๋ถ๋ฐฐ:
- Block 0: ์ค๋ ๋ 0-255 โ ์์ 0-255
- Block 1: ์ค๋ ๋ 0-255 โ ์์ 256-511
- Block 2: ์ค๋ ๋ 0-255 โ ์์ 512-767
- Block 3: ์ค๋ ๋ 0-255 โ ์์ 768-1023
์์ฑํ ์ฝ๋
def cluster_coordination_basics[
tpb: Int
](
output: TileTensor[mut=True, dtype, ClusterLayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
size: Int,
):
"""Real cluster coordination using SM90+ cluster APIs."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Check what's happening with cluster ranks
var my_block_rank = Int(block_rank_in_cluster())
var block_id = block_idx.x
var shared_data = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[tpb]())
# FIX: Use block_idx.x for data distribution instead of cluster rank
# Each block should process different portions of the data
var data_scale = Scalar[dtype](
block_id + 1
) # Use block_idx instead of cluster rank
# Phase 1: Each block processes its portion
if global_i < size:
shared_data[local_i] = input[global_i] * data_scale
else:
shared_data[local_i] = 0.0
barrier()
# Phase 2: Use cluster_arrive() for inter-block coordination
# Signal this block has completed processing
# FILL IN 1 line here
# Block-level aggregation (only thread 0)
if local_i == 0:
# FILL IN 4 line here
...
# Wait for all blocks in cluster to complete
# FILL IN 1 line here
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p34/p34.mojo
ํ
๋ธ๋ก ์๋ณ ํจํด
block_rank_in_cluster()๋ฅผ ์ฌ์ฉํ์ฌ ํด๋ฌ์คํฐ ์์(0-3)๋ฅผ ์ป์ต๋๋ค- ๊ทธ๋ฆฌ๋ ์คํ์์ ์์ ์ ์ธ ๋ธ๋ก ์ธ๋ฑ์ฑ์ ์ํด
Int(block_idx.x)๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๋ธ๋ก ์์น์ ๋ฐ๋ผ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ค์ผ์ผ๋งํ์ฌ ๊ณ ์ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ญ๋๋ค
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[tpb]())์ผ๋ก ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํฉ๋๋ค (Puzzle 8์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ์ด ์ฐธ๊ณ )block_id + 1๋ก ์ค์ผ์ผ๋งํ์ฌ ๋ธ๋ก๋ง๋ค ๊ณ ์ ํ ์ค์ผ์ผ๋ง์ ์ ์ฉํฉ๋๋ค- ์ ๋ ฅ ๋ฐ์ดํฐ ์ ๊ทผ ์ ๊ฒฝ๊ณ ๊ฒ์ฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค (Puzzle 3์ ๊ฐ๋ ํจํด)
ํด๋ฌ์คํฐ ๋๊ธฐํ ํจํด
- ์ฒ๋ฆฌ: ๊ฐ ๋ธ๋ก์ด ์์ ์ ๋ฐ์ดํฐ ์์ญ์ ์ฒ๋ฆฌํฉ๋๋ค
- ์ ํธ:
cluster_arrive()๋ก ์ฒ๋ฆฌ ์๋ฃ๋ฅผ ์๋ฆฝ๋๋ค - ์ฐ์ฐ: ๋ธ๋ก ๋ด๋ถ ์ฐ์ฐ (๋ฆฌ๋์ , ์ง๊ณ)
- ๋๊ธฐ:
cluster_wait()๋ก ๋ชจ๋ ๋ธ๋ก์ด ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค
๋ธ๋ก ๋ด๋ถ ์ค๋ ๋ ์กฐ์
- ํด๋ฌ์คํฐ ์ฐ์ฐ ์ ์ ๋ธ๋ก ๋ด๋ถ ๋๊ธฐํ๋ฅผ ์ํด
barrier()๋ฅผ ์ฌ์ฉํฉ๋๋ค (Puzzle 29์ ๋ฐฐ๋ฆฌ์ด ๊ฐ๋ ) - ์ค๋ ๋ 0๋ง ์ต์ข ๋ธ๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํด์ผ ํฉ๋๋ค (๋ธ๋ก ํ๋ก๊ทธ๋๋ฐ์ ๋จ์ผ ์ฐ๊ธฐ ํจํด)
- ์์ ์ ์ธ ์ธ๋ฑ์ฑ์ ์ํด ๊ฒฐ๊ณผ๋ฅผ
output[block_id]์ ์ ์ฅํฉ๋๋ค
์ฝ๋ ์คํ
pixi run p34 --coordination
uv run poe p34 --coordination
์์ ์ถ๋ ฅ:
Testing Multi-Block Coordination
SIZE: 1024 TPB: 256 CLUSTER_SIZE: 4
Block coordination results:
Block 0 : 127.5
Block 1 : 255.0
Block 2 : 382.5
Block 3 : 510.0
โ
Multi-block coordination tests passed!
์ฑ๊ณต ๊ธฐ์ค:
- 4๊ฐ ๋ธ๋ก ๋ชจ๋ 0์ด ์๋ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค
- ๊ฒฐ๊ณผ๊ฐ ์ค์ผ์ผ๋ง ํจํด์ ๋ณด์ฌ์ค๋๋ค: Block 1 > Block 0, Block 2 > Block 1 ๋ฑ
- ๊ฒฝ์ ์ํ๋ ์กฐ์ ์คํจ๊ฐ ์์ด์ผ ํฉ๋๋ค
์๋ฃจ์
def cluster_coordination_basics[
tpb: Int
](
output: TileTensor[mut=True, dtype, ClusterLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayout, MutAnyOrigin],
size: Int,
):
"""Real cluster coordination using SM90+ cluster APIs."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# Check what's happening with cluster ranks
var my_block_rank = Int(block_rank_in_cluster())
var block_id = block_idx.x
var shared_data = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[tpb]())
# FIX: Use block_idx.x for data distribution instead of cluster rank
# Each block should process different portions of the data
var data_scale = Scalar[dtype](
block_id + 1
) # Use block_idx instead of cluster rank
# Phase 1: Each block processes its portion
if global_i < size:
shared_data[local_i] = input[global_i] * data_scale
else:
shared_data[local_i] = 0.0
barrier()
# Phase 2: Use cluster_arrive() for inter-block coordination
cluster_arrive() # Signal this block has completed processing
# Block-level aggregation (only thread 0)
if local_i == 0:
var block_sum: Float32 = 0.0
for i in range(tpb):
block_sum += shared_data[i][0]
# FIX: Store result at block_idx position (guaranteed unique per block)
output[block_id] = block_sum
# Wait for all blocks in cluster to complete
cluster_wait()
ํด๋ฌ์คํฐ ์กฐ์ ํ์ด๋ ์ ์คํ๊ฒ ์ค๊ณ๋ 2๋จ๊ณ ์ ๊ทผ ๋ฐฉ์์ ํตํด ๊ธฐ๋ณธ์ ์ธ ๋ฉํฐ ๋ธ๋ก ๋๊ธฐํ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
1๋จ๊ณ: ๋ ๋ฆฝ์ ๋ธ๋ก ์ฒ๋ฆฌ
์ค๋ ๋ ๋ฐ ๋ธ๋ก ์๋ณ:
global_i = block_dim.x * block_idx.x + thread_idx.x # Global thread index
local_i = thread_idx.x # Local thread index within block
my_block_rank = Int(block_rank_in_cluster()) # Cluster rank (0-3)
block_id = Int(block_idx.x) # Block index for reliable addressing
๊ณต์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ:
- ๊ฐ ๋ธ๋ก์ด ์์ฒด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์
๊ณต๊ฐ์ ํ ๋นํฉ๋๋ค:
stack_allocation[dtype=dtype, address_space=AddressSpace.SHARED](row_major[tpb]()) - ์ค์ผ์ผ๋ง ์ ๋ต:
data_scale = Float32(block_id + 1)๋ก ๊ฐ ๋ธ๋ก์ด ๋ค๋ฅด๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋๋ก ํฉ๋๋ค- Block 0: 1.0๋ฐฐ, Block 1: 2.0๋ฐฐ, Block 2: 3.0๋ฐฐ, Block 3: 4.0๋ฐฐ
- ๊ฒฝ๊ณ ๊ฒ์ฌ:
if global_i < size:๋ก ๋ฒ์ ๋ฐ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๋ฐฉ์งํฉ๋๋ค - ๋ฐ์ดํฐ ์ฒ๋ฆฌ:
shared_data[local_i] = input[global_i] * data_scale๋ก ๋ธ๋ก๋ณ ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ์ค์ผ์ผ๋งํฉ๋๋ค
๋ธ๋ก ๋ด๋ถ ๋๊ธฐํ:
barrier()๋ ๊ฐ ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ ๋ก๋ฉ์ ์๋ฃํ ํ์์ผ ๋ค์ ๋จ๊ณ๋ก ์งํํ๋๋ก ๋ณด์ฅํฉ๋๋ค- ๋ฐ์ดํฐ ๋ก๋ฉ๊ณผ ์ดํ์ ํด๋ฌ์คํฐ ์กฐ์ ์ฌ์ด์ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค
2๋จ๊ณ: ํด๋ฌ์คํฐ ์กฐ์
๋ธ๋ก ๊ฐ ์ ํธ:
cluster_arrive()๋ ์ด ๋ธ๋ก์ด ๋ก์ปฌ ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ์๋ฃํ์์ ์๋ฆฝ๋๋ค- ํด๋ฌ์คํฐ ํ๋์จ์ด์ ์๋ฃ๋ฅผ ๋ฑ๋กํ๋ ๋ ผ๋ธ๋กํน ์ฐ์ฐ์ ๋๋ค
๋ก์ปฌ ์ง๊ณ (์ค๋ ๋ 0๋ง):
if local_i == 0:
var block_sum: Float32 = 0.0
for i in range(tpb):
block_sum += shared_data[i][0] # Sum all elements in shared memory
output[block_id] = block_sum # Store result at unique block position
- ๊ฒฝ์ ์ํ๋ฅผ ํผํ๊ธฐ ์ํด ์ค๋ ๋ 0๋ง ํฉ์ฐ์ ์ํํฉ๋๋ค
output[block_id]์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ์ฌ ๊ฐ ๋ธ๋ก์ด ๊ณ ์ ํ ์์น์ ๊ธฐ๋กํ๋๋ก ํฉ๋๋ค
์ต์ข ๋๊ธฐํ:
cluster_wait()๋ ํด๋ฌ์คํฐ ๋ด ๋ชจ๋ ๋ธ๋ก์ด ์์ ์ ์๋ฃํ ๋๊น์ง ๋๊ธฐํฉ๋๋ค- ์ด๋ฅผ ํตํด ์ ์ฒด ํด๋ฌ์คํฐ์ ๊ฑธ์ณ ๊ฒฐ์ ๋ก ์ ์๋ฃ ์์๋ฅผ ๋ณด์ฅํฉ๋๋ค
ํต์ฌ ๊ธฐ์ ์ธ์ฌ์ดํธ
์ my_block_rank ๋์ block_id๋ฅผ ์ฌ์ฉํ ๊น?
block_idx.x๋ ์์ ์ ์ธ ๊ทธ๋ฆฌ๋ ์คํ ์ธ๋ฑ์ฑ์ ์ ๊ณตํฉ๋๋ค (0, 1, 2, 3)block_rank_in_cluster()๋ ํด๋ฌ์คํฐ ์ค์ ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์ํ ์ ์์ต๋๋คblock_id๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ๋ธ๋ก์ด ๊ณ ์ ํ ๋ฐ์ดํฐ ์์ญ๊ณผ ์ถ๋ ฅ ์์น๋ฅผ ํ๋ณดํ ์ ์์ต๋๋ค
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์ ์ญ ๋ฉ๋ชจ๋ฆฌ: ๊ฐ ์ค๋ ๋๊ฐ
input[global_i]๋ฅผ ์ ํํ ํ ๋ฒ ์ฝ์ต๋๋ค - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก ๋ด๋ถ ํต์ ๊ณผ ์ง๊ณ์ ์ฌ์ฉ๋ฉ๋๋ค
- ์ถ๋ ฅ ๋ฉ๋ชจ๋ฆฌ: ๊ฐ ๋ธ๋ก์ด
output[block_id]์ ์ ํํ ํ ๋ฒ ๊ธฐ๋กํฉ๋๋ค
๋๊ธฐํ ๊ณ์ธต ๊ตฌ์กฐ:
barrier(): ๊ฐ ๋ธ๋ก ๋ด ์ค๋ ๋๋ฅผ ๋๊ธฐํํฉ๋๋ค (๋ธ๋ก ๋ด๋ถ)cluster_arrive(): ๋ค๋ฅธ ๋ธ๋ก์ ์๋ฃ๋ฅผ ์๋ฆฝ๋๋ค (๋ธ๋ก ๊ฐ, ๋ ผ๋ธ๋กํน)cluster_wait(): ๋ชจ๋ ๋ธ๋ก์ด ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค (๋ธ๋ก ๊ฐ, ๋ธ๋กํน)
์ฑ๋ฅ ํน์ฑ:
- ์ฐ์ฐ ๋ณต์ก๋: ๋ธ๋ก๋น ๋ก์ปฌ ํฉ์ฐ์ O(TPB), ํด๋ฌ์คํฐ ์กฐ์ ์ O(1)
- ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ: ๊ฐ ์ ๋ ฅ ์์๋ฅผ ํ ๋ฒ๋ง ์ฝ์ผ๋ฉฐ, ๋ธ๋ก ๊ฐ ํต์ ์ ์ต์ํ
- ํ์ฅ์ฑ: ํจํด์ด ๋ ํฐ ํด๋ฌ์คํฐ ํฌ๊ธฐ์๋ ์ต์ํ์ ์ค๋ฒํค๋๋ก ํ์ฅ ๊ฐ๋ฅ
ํจํด ์ดํดํ๊ธฐ
ํด๋ฌ์คํฐ ์กฐ์ ์ ํต์ฌ ํจํด์ ๋จ์ํ์ง๋ง ๊ฐ๋ ฅํ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฆ ๋๋ค:
- 1๋จ๊ณ: ๊ฐ ๋ธ๋ก์ด ํ ๋น๋ ๋ฐ์ดํฐ ์์ญ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค
- ์ ํธ:
cluster_arrive()๋ก ์ฒ๋ฆฌ ์๋ฃ๋ฅผ ์๋ฆฝ๋๋ค - 2๋จ๊ณ: ๋ค๋ฅธ ๋ธ๋ก์ ๊ฒฐ๊ณผ์ ์์กดํ๋ ์ฐ์ฐ์ ์์ ํ๊ฒ ์ํํ ์ ์์ต๋๋ค
- ๋๊ธฐํ:
cluster_wait()๋ก ๋ชจ๋ ๋ธ๋ก์ด ์๋ฃ๋ ํ ๋ค์์ผ๋ก ์งํํฉ๋๋ค
๋ค์ ๋จ๊ณ: ๋ ๊ณ ๊ธ ์กฐ์ ์ ๋ฐฐ์ธ ์ค๋น๊ฐ ๋์
จ๋์?
ํด๋ฌ์คํฐ ์ ์ฒด ์งํฉ ์ฐ์ฐ ์ผ๋ก ์ด๋ํ์ฌ
Puzzle 27์ block.sum() ํจํด์ ํด๋ฌ์คํฐ ๊ท๋ชจ๋ก
ํ์ฅํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
Puzzle 24์ ์ํ ๋ ๋ฒจ ๋ฆฌ๋์
์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค!
โธ๏ธ ํด๋ฌ์คํฐ ์ ์ฒด ์งํฉ ์ฐ์ฐ
๊ฐ์
์ด์ ์น์
์ ๊ธฐ๋ณธ ํด๋ฌ์คํฐ ์กฐ์ ์ ๋ฐํ์ผ๋ก, ์ด ๋์ ์์๋
ํด๋ฌ์คํฐ ์ ์ฒด ์งํฉ ์ฐ์ฐ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค -
Puzzle 27์์ ์ตํ
block.sum ํจํด์
์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ๊ฑธ์ณ ํ์ฅํฉ๋๋ค.
๋์ ๊ณผ์ : 4๊ฐ์ ์กฐ์ ๋ ๋ธ๋ก์ ๊ฑธ์ณ 1024๊ฐ ์์๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๊ฐ ๋ธ๋ก์ ๊ฐ๋ณ ๋ฆฌ๋์ ์ ํ๋์ ์ ์ญ ๊ฒฐ๊ณผ๋ก ํฉ์น๋ ํด๋ฌ์คํฐ ์ ์ฒด ๋ฆฌ๋์ ์ ๊ตฌํํฉ๋๋ค.
ํต์ฌ ํ์ต: ์ ์ฒด ํด๋ฌ์คํฐ ์กฐ์ ์ ์ํ
cluster_sync()์
ํจ์จ์ ์ธ ์ต์ข
๋ฆฌ๋์
์ ์ํ
elect_one_sync()๋ฅผ
๋ฐฐ์๋๋ค.
๋ฌธ์ : ๋๊ท๋ชจ ์ ์ญ ํฉ์ฐ
๋จ์ผ ๋ธ๋ก์ (Puzzle 27์์ ๋ฐฐ์ ๋ฏ์ด) ์ค๋ ๋ ์์ Puzzle 8์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ ์ํด ์ ํ๋ฉ๋๋ค. ๋จ์ผ ๋ธ๋ก ๋ฆฌ๋์ ์ ๋์ด์๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ ์ ์ญ ํต๊ณ(ํ๊ท , ๋ถ์ฐ, ํฉ๊ณ)๋ฅผ ๊ตฌํ๋ ค๋ฉด ํด๋ฌ์คํฐ ์ ์ฒด ์งํฉ ์ฐ์ฐ์ด ํ์ํฉ๋๋ค.
๊ณผ์ : ๋ค์๊ณผ ๊ฐ์ ํด๋ฌ์คํฐ ์ ์ฒด ํฉ์ฐ ๋ฆฌ๋์ ์ ๊ตฌํํ์ธ์:
- ๊ฐ ๋ธ๋ก์ด ๋ก์ปฌ ๋ฆฌ๋์
์ ์ํํฉ๋๋ค
(Puzzle 27์
block.sum()๊ณผ ์ ์ฌ) - Puzzle 29์ ๋๊ธฐํ๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ก๋ค์ด ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ํฉ์นฉ๋๋ค
- ์ ์ถ๋ ํ๋์ ์ค๋ ๋๊ฐ ์ํ ์ ์ถ ํจํด์ ์ฌ์ฉํ์ฌ ์ต์ข ์ ์ญ ํฉ๊ณ๋ฅผ ๊ณ์ฐํฉ๋๋ค
๋ฌธ์ ๋ช ์ธ
์๊ณ ๋ฆฌ์ฆ ํ๋ฆ:
1๋จ๊ณ - ๋ก์ปฌ ๋ฆฌ๋์ (๊ฐ ๋ธ๋ก ๋ด๋ถ): \[R_i = \sum_{j=0}^{TPB-1} input[i \times TPB + j] \quad \text{for block } i\]
2๋จ๊ณ - ์ ์ญ ์ง๊ณ (ํด๋ฌ์คํฐ ์ ์ฒด): \[\text{Global Sum} = \sum_{i=0}^{\text{CLUSTER_SIZE}-1} R_i\]
์กฐ์ ์๊ตฌ์ฌํญ:
- ๋ก์ปฌ ๋ฆฌ๋์ : ๊ฐ ๋ธ๋ก์ด ํธ๋ฆฌ ๋ฆฌ๋์ ์ผ๋ก ๋ถ๋ถ ํฉ์ ๊ณ์ฐํฉ๋๋ค
- ํด๋ฌ์คํฐ ๋๊ธฐํ:
cluster_sync()๋ก ๋ชจ๋ ๋ถ๋ถ ๊ฒฐ๊ณผ๊ฐ ์ค๋น๋์๋์ง ๋ณด์ฅํฉ๋๋ค - ์ต์ข ์ง๊ณ: ์ ์ถ๋ ํ๋์ ์ค๋ ๋๊ฐ ๋ชจ๋ ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ํฉ์นฉ๋๋ค
์ค์
- ๋ฌธ์ ํฌ๊ธฐ:
SIZE = 1024์์ - ๋ธ๋ก ์ค์ :
TPB = 256๋ธ๋ก๋น ์ค๋ ๋ ์(256, 1) - ๊ทธ๋ฆฌ๋ ์ค์ :
CLUSTER_SIZE = 4ํด๋ฌ์คํฐ๋น ๋ธ๋ก ์(4, 1) - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ์
๋ ฅ
row_major[SIZE](), ์ถ๋ ฅrow_major[1]() - ์์ ์ ์ฅ์: ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ์ํ
row_major[CLUSTER_SIZE]()
์์ ๊ฒฐ๊ณผ: ์์ด 0, 0.01, 0.02, ..., 10.23์ ํฉ = 523,776
์์ฑํ ์ฝ๋
def cluster_collective_operations[
tpb: Int
](
output: TileTensor[mut=True, dtype, OutLayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
temp_storage: TileTensor[mut=True, dtype, ClusterLayoutType, MutAnyOrigin],
size: Int,
):
"""Cluster-wide collective operations using real cluster APIs."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL IN (roughly 24 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p34/p34.mojo
ํ
๋ก์ปฌ ๋ฆฌ๋์ ํจํด
- Puzzle 27์ block sum์์ ์ฌ์ฉํ ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด์ ํ์ฉํฉ๋๋ค
- stride =
tpb // 2๋ก ์์ํ์ฌ ๋งค ๋ฐ๋ณต๋ง๋ค ์ ๋ฐ์ผ๋ก ์ค์ ๋๋ค (๊ณ ์ ์ ์ธ Puzzle 12์ ๋ฆฌ๋์ ) - ๊ฐ ๋จ๊ณ์์
local_i < stride์ธ ์ค๋ ๋๋ง ์ฐธ์ฌํฉ๋๋ค - ๋ฆฌ๋์
๋จ๊ณ ์ฌ์ด์
barrier()๋ฅผ ์ฌ์ฉํฉ๋๋ค (Puzzle 29์ ๋ฐฐ๋ฆฌ์ด ๊ฐ๋ )
ํด๋ฌ์คํฐ ์กฐ์ ์ ๋ต
- ์์ ์ ์ธ ์ธ๋ฑ์ฑ์ ์ํด ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ
temp_storage[block_id]์ ์ ์ฅํฉ๋๋ค - ์ ์ฒด ํด๋ฌ์คํฐ ๋๊ธฐํ๋ฅผ ์ํด
cluster_sync()๋ฅผ ์ฌ์ฉํฉ๋๋ค (arrive/wait๋ณด๋ค ๊ฐ๋ ฅ) - ์ต์ข ์ ์ญ ์ง๊ณ๋ ํ๋์ ์ค๋ ๋๋ง ์ํํด์ผ ํฉ๋๋ค
ํจ์จ์ ์ธ ์ ์ถ ํจํด
- ์ฒซ ๋ฒ์งธ ๋ธ๋ก(
my_block_rank == 0) ๋ด์์elect_one_sync()๋ฅผ ์ฌ์ฉํฉ๋๋ค (์ํ ํ๋ก๊ทธ๋๋ฐ์ ํจํด) - ์ค๋ณต ์ฐ์ฐ์ ํผํ๊ธฐ ์ํด ํ๋์ ์ค๋ ๋๋ง ์ต์ข ํฉ์ฐ์ ์ํํ๋๋ก ๋ณด์ฅํฉ๋๋ค
- ์ ์ถ๋ ์ค๋ ๋๊ฐ
temp_storage์์ ๋ชจ๋ ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์ต๋๋ค (Puzzle 8์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ๊ณผ ์ ์ฌ)
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด
- ๊ฐ ์ค๋ ๋๊ฐ ๊ฒฝ๊ณ ๊ฒ์ฌ์ ํจ๊ป
input[global_i]๋ฅผ ์ฝ์ต๋๋ค (Puzzle 3์ ๊ฐ๋) - ๋ธ๋ก ๋ด๋ถ ๋ฆฌ๋์ ์ ์ํด ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํฉ๋๋ค
- ๋ธ๋ก ๊ฐ ํต์ ์ ์ํด ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ
temp_storage[block_id]์ ์ ์ฅํฉ๋๋ค - ์ต์ข
๊ฒฐ๊ณผ๋
output[0]์ ๊ธฐ๋กํฉ๋๋ค (๋ธ๋ก ์กฐ์ ์ ๋จ์ผ ์ฐ๊ธฐ ํจํด)
ํด๋ฌ์คํฐ API ์ฐธ์กฐ
gpu.primitives.cluster
๋ชจ๋:
cluster_sync(): ์ ์ฒด ํด๋ฌ์คํฐ ๋๊ธฐํ - arrive/wait ํจํด๋ณด๋ค ๊ฐ๋ ฅelect_one_sync(): ํจ์จ์ ์ธ ์กฐ์ ์ ์ํด ์ํ ๋ด์์ ๋จ์ผ ์ค๋ ๋๋ฅผ ์ ์ถblock_rank_in_cluster(): ํด๋ฌ์คํฐ ๋ด ๊ณ ์ ํ ๋ธ๋ก ์๋ณ์๋ฅผ ๋ฐํ
ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด
Puzzle 27์ ์ ํต์ ์ธ ๋ด์ ์์ ๋ฐฐ์ด ํธ๋ฆฌ ๋ฆฌ๋์ ํจํด์ ๋ ์ฌ๋ ค ๋ณด์ธ์:
Stride 128: [T0] += [T128], [T1] += [T129], [T2] += [T130], ...
Stride 64: [T0] += [T64], [T1] += [T65], [T2] += [T66], ...
Stride 32: [T0] += [T32], [T1] += [T33], [T2] += [T34], ...
Stride 16: [T0] += [T16], [T1] += [T17], [T2] += [T18], ...
...
Stride 1: [T0] += [T1] โ Final result at T0
์ด์ ์ด ํจํด์ ํด๋ฌ์คํฐ ๊ท๋ชจ๋ก ํ์ฅํฉ๋๋ค - ๊ฐ ๋ธ๋ก์ด ํ๋์ ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ ๋ค, ๋ธ๋ก ๊ฐ์ ๊ฒฐํฉํฉ๋๋ค.
์ฝ๋ ์คํ
pixi run p34 --reduction
uv run poe p34 --reduction
์์ ์ถ๋ ฅ:
Testing Cluster-Wide Reduction
SIZE: 1024 TPB: 256 CLUSTER_SIZE: 4
Expected sum: 523776.0
Cluster reduction result: 523776.0
Expected: 523776.0
Error: 0.0
โ
Passed: Cluster reduction accuracy test
โ
Cluster-wide collective operations tests passed!
์ฑ๊ณต ๊ธฐ์ค:
- ์๋ฒฝํ ์ ํ๋: ๊ฒฐ๊ณผ๊ฐ ์์ ํฉ๊ณ(523,776)์ ์ ํํ ์ผ์นํฉ๋๋ค
- ํด๋ฌ์คํฐ ์กฐ์ : 4๊ฐ ๋ธ๋ก ๋ชจ๋๊ฐ ๋ถ๋ถ ํฉ์ ๊ธฐ์ฌํฉ๋๋ค
- ํจ์จ์ ์ธ ์ต์ข ๋ฆฌ๋์ : ์ ์ถ๋ ๋จ์ผ ์ค๋ ๋๊ฐ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํฉ๋๋ค
์๋ฃจ์
def cluster_collective_operations[
tpb: Int
](
output: TileTensor[mut=True, dtype, OutLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayout, MutAnyOrigin],
temp_storage: TileTensor[mut=True, dtype, ClusterLayout, MutAnyOrigin],
size: Int,
):
"""Cluster-wide collective operations using real cluster APIs."""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var my_block_rank = Int(block_rank_in_cluster())
var block_id = block_idx.x
# Each thread accumulates its data
var my_value: Float32 = 0.0
if global_i < size:
my_value = input[global_i][0]
# Block-level reduction using shared memory
var shared_mem = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[tpb]())
shared_mem[local_i] = my_value
barrier()
# Tree reduction within block
var stride = tpb // 2
while stride > 0:
if local_i < stride and local_i + stride < tpb:
shared_mem[local_i] += shared_mem[local_i + stride]
barrier()
stride = stride // 2
# FIX: Store block result using block_idx for reliable indexing
if local_i == 0:
temp_storage[block_id] = shared_mem[0]
# Use cluster_sync() for full cluster synchronization
cluster_sync()
# Final cluster reduction (elect one thread to do the final work)
if elect_one_sync() and my_block_rank == 0:
var total: Float32 = 0.0
for i in range(CLUSTER_SIZE):
total += temp_storage[i][0]
output[0] = total
ํด๋ฌ์คํฐ ์งํฉ ์ฐ์ฐ ํ์ด๋ ๋ถ์ฐ ์ปดํจํ ์ ๊ณ ์ ์ ์ธ ํจํด์ ๋ณด์ฌ์ค๋๋ค: ๋ก์ปฌ ๋ฆฌ๋์ โ ์ ์ญ ์กฐ์ โ ์ต์ข ์ง๊ณ:
1๋จ๊ณ: ๋ก์ปฌ ๋ธ๋ก ๋ฆฌ๋์ (์ ํต์ ํธ๋ฆฌ ๋ฆฌ๋์ )
๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์ด๊ธฐํ:
var my_value: Float32 = 0.0
if global_i < size:
my_value = input[global_i][0] # Load with bounds checking
shared_mem[local_i] = my_value # Store in shared memory
barrier() # Ensure all threads complete loading
ํธ๋ฆฌ ๋ฆฌ๋์ ์๊ณ ๋ฆฌ์ฆ:
var stride = tpb // 2 # Start with half the threads (128)
while stride > 0:
if local_i < stride and local_i + stride < tpb:
shared_mem[local_i] += shared_mem[local_i + stride]
barrier() # Synchronize after each reduction step
stride = stride // 2
ํธ๋ฆฌ ๋ฆฌ๋์ ์๊ฐํ (TPB=256):
Step 1: stride=128 [T0]+=T128, [T1]+=T129, ..., [T127]+=T255
Step 2: stride=64 [T0]+=T64, [T1]+=T65, ..., [T63]+=T127
Step 3: stride=32 [T0]+=T32, [T1]+=T33, ..., [T31]+=T63
Step 4: stride=16 [T0]+=T16, [T1]+=T17, ..., [T15]+=T31
Step 5: stride=8 [T0]+=T8, [T1]+=T9, ..., [T7]+=T15
Step 6: stride=4 [T0]+=T4, [T1]+=T5, [T2]+=T6, [T3]+=T7
Step 7: stride=2 [T0]+=T2, [T1]+=T3
Step 8: stride=1 [T0]+=T1 โ Final result at shared_mem[0]
๋ถ๋ถ ๊ฒฐ๊ณผ ์ ์ฅ:
- ์ค๋ ๋ 0๋ง ๊ธฐ๋กํฉ๋๋ค:
temp_storage[block_id] = shared_mem[0] - ๊ฐ ๋ธ๋ก์ด ์์ ์ ํฉ๊ณ๋ฅผ
temp_storage[0],temp_storage[1],temp_storage[2],temp_storage[3]์ ์ ์ฅํฉ๋๋ค
2๋จ๊ณ: ํด๋ฌ์คํฐ ๋๊ธฐํ
์ ์ฒด ํด๋ฌ์คํฐ ๋ฐฐ๋ฆฌ์ด:
cluster_sync()๋cluster_arrive()/cluster_wait()๋ณด๋ค ๋ ๊ฐ๋ ฅํ ๋ณด์ฅ์ ์ ๊ณตํฉ๋๋ค- ์ด๋ค ๋ธ๋ก์ด๋ ๋ค์์ผ๋ก ์งํํ๊ธฐ ์ ์ ๋ชจ๋ ๋ธ๋ก์ด ๋ก์ปฌ ๋ฆฌ๋์ ์ ์๋ฃํ๋๋ก ๋ณด์ฅํฉ๋๋ค
- ํด๋ฌ์คํฐ ๋ด ๋ชจ๋ ๋ธ๋ก์ ๊ฑธ์น ํ๋์จ์ด ๊ฐ์ ๋๊ธฐํ์ ๋๋ค
3๋จ๊ณ: ์ต์ข ์ ์ญ ์ง๊ณ
ํจ์จ์ ์ธ ์ค๋ ๋ ์ ์ถ:
if elect_one_sync() and my_block_rank == 0:
var total: Float32 = 0.0
for i in range(CLUSTER_SIZE):
total += temp_storage[i][0] # Sum: temp[0] + temp[1] + temp[2] + temp[3]
output[0] = total
์ ์ด ์ ์ถ ์ ๋ต์ ์ฌ์ฉํ ๊น?
elect_one_sync(): ์ํ๋น ์ ํํ ํ๋์ ์ค๋ ๋๋ฅผ ์ ํํ๋ ํ๋์จ์ด ๊ธฐ๋ณธ ์์์ ๋๋คmy_block_rank == 0: ๋จ์ผ ์ฐ๊ธฐ๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด ์ฒซ ๋ฒ์งธ ๋ธ๋ก์์๋ง ์ ์ถํฉ๋๋ค- ๊ฒฐ๊ณผ: ์ ์ฒด ํด๋ฌ์คํฐ์์ ๋จ ํ๋์ ์ค๋ ๋๋ง ์ต์ข ํฉ์ฐ์ ์ํํฉ๋๋ค
- ํจ์จ์ฑ: 1024๊ฐ ์ ์ฒด ์ค๋ ๋์ ๊ฑธ์น ์ค๋ณต ์ฐ์ฐ์ ํผํฉ๋๋ค
ํต์ฌ ๊ธฐ์ ์ธ์ฌ์ดํธ
3๋จ๊ณ ๋ฆฌ๋์ ๊ณ์ธต ๊ตฌ์กฐ:
- ์ค๋ ๋ โ ์ํ: ๊ฐ๋ณ ์ค๋ ๋๊ฐ ์ํ ๋ ๋ฒจ ๋ถ๋ถ ํฉ์ ๊ธฐ์ฌํฉ๋๋ค
- ์ํ โ ๋ธ๋ก: ํธ๋ฆฌ ๋ฆฌ๋์ ์ด ์ํ๋ค์ ํ๋์ ๋ธ๋ก ๊ฒฐ๊ณผ๋ก ํฉ์นฉ๋๋ค (256 โ 1)
- ๋ธ๋ก โ ํด๋ฌ์คํฐ: ๋จ์ ๋ฃจํ๊ฐ ๋ธ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ต์ข ํฉ๊ณ๋ก ํฉ์นฉ๋๋ค (4 โ 1)
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์
๋ ฅ: ๊ฐ ์์๋ฅผ ์ ํํ ํ ๋ฒ ์ฝ์ต๋๋ค (
input[global_i]) - ๊ณต์ ๋ฉ๋ชจ๋ฆฌ: ๋ธ๋ก ๋ด๋ถ ํธ๋ฆฌ ๋ฆฌ๋์ ์ ์ํ ๊ณ ์ ์์ ๊ณต๊ฐ
- ์์ ์ ์ฅ์: ์ ๋น์ฉ ๋ธ๋ก ๊ฐ ํต์ (4๊ฐ ๊ฐ๋ง)
- ์ถ๋ ฅ: ๋จ์ผ ์ ์ญ ๊ฒฐ๊ณผ๋ฅผ ํ ๋ฒ ๊ธฐ๋ก
๋๊ธฐํ ๋ณด์ฅ:
barrier(): ๋ธ๋ก ๋ด ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ ํธ๋ฆฌ ๋ฆฌ๋์ ๋จ๊ณ๋ฅผ ์๋ฃํ๋๋ก ๋ณด์ฅํฉ๋๋คcluster_sync(): ์ ์ญ ๋ฐฐ๋ฆฌ์ด - ๋ชจ๋ ๋ธ๋ก์ด ๋์ผํ ์คํ ์ง์ ์ ๋๋ฌํฉ๋๋ค- ๋จ์ผ ์ฐ๊ธฐ: ์ ์ถ์ ํตํด ์ต์ข ์ถ๋ ฅ์ ๋ํ ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค
์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋ ๋ถ์:
- ํธ๋ฆฌ ๋ฆฌ๋์ : O(logโ TPB) = O(logโ 256) = ๋ธ๋ก๋น 8๋จ๊ณ
- ํด๋ฌ์คํฐ ์กฐ์ : O(1) ๋๊ธฐํ ์ค๋ฒํค๋
- ์ต์ข ์ง๊ณ: O(CLUSTER_SIZE) = O(4) ๋จ์ ๋ง์
- ์ ์ฒด: ๋ธ๋ก ๋ด๋ถ๋ ๋ก๊ทธ, ๋ธ๋ก ๊ฐ์ ์ ํ
ํ์ฅ์ฑ ํน์ฑ:
- ๋ธ๋ก ๋ ๋ฒจ: ๋ก๊ทธ ๋ณต์ก๋๋ก ์์ฒ ๊ฐ์ ์ค๋ ๋๊น์ง ํ์ฅ ๊ฐ๋ฅ
- ํด๋ฌ์คํฐ ๋ ๋ฒจ: ์ ํ ๋ณต์ก๋๋ก ์์ญ ๊ฐ์ ๋ธ๋ก๊น์ง ํ์ฅ ๊ฐ๋ฅ
- ๋ฉ๋ชจ๋ฆฌ: ์์ ์ ์ฅ์ ์๊ตฌ๋์ด ํด๋ฌ์คํฐ ํฌ๊ธฐ์ ๋น๋กํ์ฌ ์ ํ ์ฆ๊ฐ
- ํต์ : ์ต์ํ์ ๋ธ๋ก ๊ฐ ๋ฐ์ดํฐ ์ด๋ (๋ธ๋ก๋น ํ๋์ ๊ฐ)
์งํฉ ์ฐ์ฐ ํจํด ์ดํดํ๊ธฐ
์ด ํผ์ฆ์ ๋ถ์ฐ ์ปดํจํ ์์ ์ฌ์ฉ๋๋ ๊ณ ์ ์ ์ธ 2๋จ๊ณ ๋ฆฌ๋์ ํจํด์ ๋ณด์ฌ์ค๋๋ค:
- ๋ก์ปฌ ์ง๊ณ: ๊ฐ ์ฒ๋ฆฌ ๋จ์(๋ธ๋ก)๊ฐ ์์ ์ ๋ฐ์ดํฐ ์์ญ์ ๋ฆฌ๋์ ํฉ๋๋ค
- ์ ์ญ ์กฐ์ : ์ฒ๋ฆฌ ๋จ์๋ค์ด ๋๊ธฐํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ตํํฉ๋๋ค
- ์ต์ข ๋ฆฌ๋์ : ์ ์ถ๋ ํ๋์ ๋จ์๊ฐ ๋ชจ๋ ๋ถ๋ถ ๊ฒฐ๊ณผ๋ฅผ ํฉ์นฉ๋๋ค
๋จ์ผ ๋ธ๋ก ๋ฐฉ์๊ณผ์ ๋น๊ต:
- ๊ธฐ์กด
block.sum(): ์ต๋ 256๊ฐ ์ค๋ ๋ ๋ด์์๋ง ๋์ํฉ๋๋ค - ํด๋ฌ์คํฐ ์งํฉ ์ฐ์ฐ: ์ฌ๋ฌ ๋ธ๋ก์ ๊ฑธ์ณ 1000๊ฐ ์ด์์ ์ค๋ ๋๋ก ํ์ฅ๋ฉ๋๋ค
- ๋์ผํ ์ ํ๋: ๋ ๋ค ๋์ผํ ์ํ์ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค
- ๋ค๋ฅธ ๊ท๋ชจ: ํด๋ฌ์คํฐ ๋ฐฉ์์ด ๋ ํฐ ๋ฐ์ดํฐ์ ์ ์ฒ๋ฆฌํฉ๋๋ค
์ฑ๋ฅ ์ด์ :
- ๋ ํฐ ๋ฐ์ดํฐ์ : ๋จ์ผ ๋ธ๋ก ์ฉ๋์ ์ด๊ณผํ๋ ๋ฐฐ์ด์ ์ฒ๋ฆฌํฉ๋๋ค
- ๋ ๋์ ํ์ฉ๋ฅ : ๋ ๋ง์ GPU ์ฐ์ฐ ์ ๋์ ๋์์ ์ฌ์ฉํฉ๋๋ค
- ํ์ฅ ๊ฐ๋ฅํ ํจํด: ๋ณต์กํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค
๋ค์ ๋จ๊ณ: ์ต์ข ๋์ ์ ํ ์ค๋น๊ฐ ๋์ จ๋์? ๊ณ ๊ธ ํด๋ฌ์คํฐ ์๊ณ ๋ฆฌ์ฆ ์ผ๋ก ์ด๋ํ์ฌ ์ํ ํ๋ก๊ทธ๋๋ฐ+๋ธ๋ก ์กฐ์ +ํด๋ฌ์คํฐ ๋๊ธฐํ๋ฅผ ๊ฒฐํฉํ ๊ณ์ธต์ ํจํด์ ๋ฐฐ์๋ณด์ธ์. ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค!
๐ง ๊ณ ๊ธ ํด๋ฌ์คํฐ ์๊ณ ๋ฆฌ์ฆ
๊ฐ์
์ด ๋ง์ง๋ง ๋์ ์์๋ ์ํ ๋ ๋ฒจ (Puzzle 24-26), ๋ธ๋ก ๋ ๋ฒจ (Puzzle 27), ํด๋ฌ์คํฐ ์กฐ์ ์ ์ด๋ฅด๊ธฐ๊น์ง GPU ํ๋ก๊ทธ๋๋ฐ ๊ณ์ธต ๊ตฌ์กฐ์ ๋ชจ๋ ๋ ๋ฒจ์ ๊ฒฐํฉํ์ฌ GPU ํ์ฉ๋ฅ ์ ๊ทน๋ํํ๋ ์ ๊ตํ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค.
๋์ ๊ณผ์ : ์ํ ๋ ๋ฒจ ์ต์ ํ (elect_one_sync()), ๋ธ๋ก ๋ ๋ฒจ ์ง๊ณ,
ํด๋ฌ์คํฐ ๋ ๋ฒจ ์กฐ์ ์ ํ๋์ ํตํฉ๋ ํจํด์ผ๋ก ์ฌ์ฉํ๋ ๊ณ์ธต์ ํด๋ฌ์คํฐ
์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํฉ๋๋ค.
ํต์ฌ ํ์ต: ๊ณ ๊ธ ์ฐ์ฐ ์ํฌ๋ก๋์์ ์ฌ์ฉ๋๋ ํ๋ก๋์ ์์ค์ ์กฐ์ ํจํด๊ณผ ํจ๊ป ์์ ํ GPU ํ๋ก๊ทธ๋๋ฐ ์คํ์ ๋ฐฐ์๋๋ค.
๋ฌธ์ : ๋ค๋จ๊ณ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ
์ค์ GPU ์๊ณ ๋ฆฌ์ฆ์ GPU ๊ณ์ธต ๊ตฌ์กฐ์ ์๋ก ๋ค๋ฅธ ๋ ๋ฒจ(Puzzle 24์ ์ํ, Puzzle 27์ ๋ธ๋ก, ํด๋ฌ์คํฐ)์ด ์กฐ์ ๋ ์ฐ์ฐ ํ์ดํ๋ผ์ธ์์ ๊ฐ๊ฐ ์ ๋ฌธํ๋ ์ญํ ์ ์ํํ๋ ๊ณ์ธต์ ์กฐ์ ์ ํ์๋ก ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ, ์ด๋ Puzzle 29์ ๋ค๋จ๊ณ ์ฒ๋ฆฌ๋ฅผ ํ์ฅํฉ๋๋ค.
๊ณผ์ : ๋ค์๊ณผ ๊ฐ์ ๋ค๋จ๊ณ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ์ธ์:
- ์ํ ๋ ๋ฒจ: ํจ์จ์ ์ธ ์ํ ๋ด๋ถ ์กฐ์ ์ ์ํด
elect_one_sync()๋ฅผ ์ฌ์ฉํฉ๋๋ค (SIMT ์คํ) - ๋ธ๋ก ๋ ๋ฒจ: ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์กฐ์ ์ ์ฌ์ฉํ์ฌ ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ง๊ณํฉ๋๋ค
- ํด๋ฌ์คํฐ ๋ ๋ฒจ:
cluster_arrive()/cluster_wait()Puzzle 29์ ๋จ๊ณ์ ๋๊ธฐํ๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ก ๊ฐ ์กฐ์ ์ ์ํํฉ๋๋ค
์๊ณ ๋ฆฌ์ฆ ๋ช ์ธ
๋ค๋จ๊ณ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ:
- 1๋จ๊ณ (์ํ ๋ ๋ฒจ): ๊ฐ ์ํ๊ฐ ํ๋์ ์ค๋ ๋๋ฅผ ์ ์ถํ์ฌ 32๊ฐ์ ์ฐ์ ์์๋ฅผ ํฉ์ฐํฉ๋๋ค
- 2๋จ๊ณ (๋ธ๋ก ๋ ๋ฒจ): ๊ฐ ๋ธ๋ก ๋ด์ ๋ชจ๋ ์ํ ํฉ๊ณ๋ฅผ ์ง๊ณํฉ๋๋ค
- 3๋จ๊ณ (ํด๋ฌ์คํฐ ๋ ๋ฒจ):
cluster_arrive()/cluster_wait()๋ก ๋ธ๋ก ๊ฐ ์กฐ์ ์ ์ํํฉ๋๋ค
์
๋ ฅ: ํ
์คํธ๋ฅผ ์ํ (i % 50) * 0.02 ํจํด์ 1024๊ฐ float ๊ฐ
์ถ๋ ฅ: ๊ณ์ธต์ ์ฒ๋ฆฌ ํจ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ 4๊ฐ ๋ธ๋ก ๊ฒฐ๊ณผ
์ค์
- ๋ฌธ์ ํฌ๊ธฐ:
SIZE = 1024์์ - ๋ธ๋ก ์ค์ :
TPB = 256๋ธ๋ก๋น ์ค๋ ๋ ์(256, 1) - ๊ทธ๋ฆฌ๋ ์ค์ :
CLUSTER_SIZE = 4๋ธ๋ก(4, 1) - ์ํ ํฌ๊ธฐ:
WARP_SIZE = 32์ํ๋น ์ค๋ ๋ ์ (NVIDIA ํ์ค) - ๋ธ๋ก๋น ์ํ ์:
TPB / WARP_SIZE = 8์ํ - ๋ฐ์ดํฐ ํ์
:
DType.float32 - ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์: ์
๋ ฅ
row_major[SIZE](), ์ถ๋ ฅrow_major[CLUSTER_SIZE]()
์ฒ๋ฆฌ ๋ถ๋ฐฐ:
- Block 0: 256 ์ค๋ ๋ โ 8 ์ํ โ ์์ 0-255
- Block 1: 256 ์ค๋ ๋ โ 8 ์ํ โ ์์ 256-511
- Block 2: 256 ์ค๋ ๋ โ 8 ์ํ โ ์์ 512-767
- Block 3: 256 ์ค๋ ๋ โ 8 ์ํ โ ์์ 768-1023
์์ฑํ ์ฝ๋
def advanced_cluster_patterns[
tpb: Int
](
output: TileTensor[mut=True, dtype, ClusterLayoutType, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayoutType, ImmutAnyOrigin],
size: Int,
):
"""Advanced cluster programming using cluster masks and relaxed synchronization.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
# FILL IN (roughly 26 lines)
์ ์ฒด ํ์ผ ๋ณด๊ธฐ: problems/p34/p34.mojo
ํ
์ํ ๋ ๋ฒจ ์ต์ ํ ํจํด
elect_one_sync()๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋น ํ๋์ ์ค๋ ๋๋ฅผ ์ฐ์ฐ์ฉ์ผ๋ก ์ ์ถํฉ๋๋ค (์ํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด)- ์ ์ถ๋ ์ค๋ ๋๊ฐ 32๊ฐ์ ์ฐ์ ์์๋ฅผ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค (SIMT ์คํ ํ์ฉ)
(local_i // 32) * 32๋ก ์ํ ์์์ ์ ๊ณ์ฐํ์ฌ ์ํ ๊ฒฝ๊ณ๋ฅผ ์ฐพ์ต๋๋ค (์ํ ๊ฐ๋ ์ Lane ์ธ๋ฑ์ฑ)- ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ ์ถ๋ ์ค๋ ๋ ์์น์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํฉ๋๋ค
๋ธ๋ก ๋ ๋ฒจ ์ง๊ณ ์ ๋ต
- ์ํ ์ฒ๋ฆฌ ํ ๋ชจ๋ ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ง๊ณํฉ๋๋ค (Puzzle 27์ ๋ธ๋ก ์กฐ์ ํ์ฅ)
- ์ ์ถ๋ ์์น์์ ์ฝ์ต๋๋ค: ์ธ๋ฑ์ค 0, 32, 64, 96, 128, 160, 192, 224
for i in range(0, tpb, 32)๋ฃจํ๋ก ์ํ ๋ฆฌ๋๋ฅผ ์ํํฉ๋๋ค (๋ฆฌ๋์ ์๊ณ ๋ฆฌ์ฆ์ ํจํด)- ์ค๋ ๋ 0๋ง ์ต์ข ๋ธ๋ก ํฉ๊ณ๋ฅผ ๊ณ์ฐํด์ผ ํฉ๋๋ค (๋ฐฐ๋ฆฌ์ด ์กฐ์ ์ ๋จ์ผ ์ฐ๊ธฐ ํจํด)
ํด๋ฌ์คํฐ ์กฐ์ ํ๋ฆ
- ์ฒ๋ฆฌ: ๊ฐ ๋ธ๋ก์ด ๊ณ์ธต์ ์ํ ์ต์ ํ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ์ ํธ:
cluster_arrive()๋ก ๋ก์ปฌ ์ฒ๋ฆฌ ์๋ฃ๋ฅผ ์๋ฆฝ๋๋ค - ์ ์ฅ: ์ค๋ ๋ 0์ด ๋ธ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅ์ ๊ธฐ๋กํฉ๋๋ค
- ๋๊ธฐ:
cluster_wait()๋ก ๋ชจ๋ ๋ธ๋ก์ด ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค
๋ฐ์ดํฐ ์ค์ผ์ผ๋ง ๋ฐ ๊ฒฝ๊ณ ๊ฒ์ฌ
Float32(block_id + 1)๋ก ์ ๋ ฅ์ ์ค์ผ์ผ๋งํ์ฌ ๋ธ๋ก๋ณ ๊ณ ์ ํจํด์ ๋ง๋ญ๋๋ค- ์
๋ ฅ์ ์ฝ๊ธฐ ์ ์ ํญ์
global_i < size๋ฅผ ๊ฒ์ฌํฉ๋๋ค (Puzzle 3์ ๊ฐ๋) - ๋ธ๋ก ๋ด ์ฒ๋ฆฌ ๋จ๊ณ ์ฌ์ด์
barrier()๋ฅผ ์ฌ์ฉํฉ๋๋ค (๋๊ธฐํ ํจํด) - ๋ฃจํ์์ ์ํ ๊ฒฝ๊ณ ์กฐ๊ฑด์ ์ฃผ์ ๊น๊ฒ ์ฒ๋ฆฌํฉ๋๋ค (์ํ ํ๋ก๊ทธ๋๋ฐ์ ๊ณ ๋ ค์ฌํญ)
๊ณ ๊ธ ํด๋ฌ์คํฐ API
gpu.primitives.cluster
๋ชจ๋:
elect_one_sync(): ํจ์จ์ ์ธ ์ฐ์ฐ์ ์ํ ์ํ ๋ ๋ฒจ ์ค๋ ๋ ์ ์ถcluster_arrive(): ๋จ๊ณ์ ํด๋ฌ์คํฐ ์กฐ์ ์ ์ํ ์๋ฃ ์ ํธcluster_wait(): ๋ชจ๋ ๋ธ๋ก์ด ๋๊ธฐํ ์ง์ ์ ๋๋ฌํ ๋๊น์ง ๋๊ธฐblock_rank_in_cluster(): ํด๋ฌ์คํฐ ๋ด ๊ณ ์ ํ ๋ธ๋ก ์๋ณ์ ๋ฐํ
๊ณ์ธต์ ์กฐ์ ํจํด
์ด ํผ์ฆ์ 3๋จ๊ณ ์กฐ์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ณด์ฌ์ค๋๋ค:
๋ ๋ฒจ 1: ์ํ ์กฐ์ (Puzzle 24)
Warp (32 threads) โ elect_one_sync() โ 1 elected thread โ processes 32 elements
๋ ๋ฒจ 2: ๋ธ๋ก ์กฐ์ (Puzzle 27)
Block (8 warps) โ aggregate warp results โ 1 block total
๋ ๋ฒจ 3: ํด๋ฌ์คํฐ ์กฐ์ (์ด ํผ์ฆ)
Cluster (4 blocks) โ cluster_arrive/wait โ synchronized completion
๊ฒฐํฉ ํจ๊ณผ: 1024๊ฐ ์ค๋ ๋ โ 32๊ฐ ์ํ ๋ฆฌ๋ โ 4๊ฐ ๋ธ๋ก ๊ฒฐ๊ณผ โ ์กฐ์ ๋ ํด๋ฌ์คํฐ ์๋ฃ
์ฝ๋ ์คํ
pixi run p34 --advanced
uv run poe p34 --advanced
์์ ์ถ๋ ฅ:
Testing Advanced Cluster Algorithms
SIZE: 1024 TPB: 256 CLUSTER_SIZE: 4
Advanced cluster algorithm results:
Block 0 : 122.799995
Block 1 : 247.04001
Block 2 : 372.72
Block 3 : 499.83997
โ
Advanced cluster patterns tests passed!
์ฑ๊ณต ๊ธฐ์ค:
- ๊ณ์ธต์ ์ค์ผ์ผ๋ง: ๊ฒฐ๊ณผ๊ฐ ๋ค๋จ๊ณ ์กฐ์ ํจ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค
- ์ํ ์ต์ ํ:
elect_one_sync()๊ฐ ์ค๋ณต ์ฐ์ฐ์ ์ค์ ๋๋ค - ํด๋ฌ์คํฐ ์กฐ์ : ๋ชจ๋ ๋ธ๋ก์ด ์ฒ๋ฆฌ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃํฉ๋๋ค
- ์ฑ๋ฅ ํจํด: ๋ ๋์ ๋ธ๋ก ID๊ฐ ๋น๋ก์ ์ผ๋ก ๋ ํฐ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค
์๋ฃจ์
def advanced_cluster_patterns[
tpb: Int
](
output: TileTensor[mut=True, dtype, ClusterLayout, MutAnyOrigin],
input: TileTensor[mut=False, dtype, InLayout, MutAnyOrigin],
size: Int,
):
"""Advanced cluster programming using cluster masks and relaxed synchronization.
"""
var global_i = block_dim.x * block_idx.x + thread_idx.x
var local_i = thread_idx.x
var my_block_rank = Int(block_rank_in_cluster())
var block_id = block_idx.x
var shared_data = stack_allocation[
dtype=dtype, address_space=AddressSpace.SHARED
](row_major[tpb]())
# Compute cluster mask for advanced coordination
# base_mask = cluster_mask_base() # Requires cluster_shape parameter
# FIX: Process data with block_idx-based scaling for guaranteed uniqueness
var data_scale = Scalar[dtype](block_id + 1)
if global_i < size:
shared_data[local_i] = input[global_i] * data_scale
else:
shared_data[local_i] = 0.0
barrier()
# Advanced pattern: Use elect_one_sync for efficient coordination
if elect_one_sync(): # Only one thread per warp does this work
var warp_sum: Float32 = 0.0
var warp_start = (local_i // 32) * 32 # Get warp start index
for i in range(32): # Sum across warp
if warp_start + i < tpb:
warp_sum += shared_data[warp_start + i][0]
shared_data[local_i] = warp_sum
barrier()
# Use cluster_arrive for staged synchronization in sm90+
cluster_arrive()
# Only first thread in each block stores result
if local_i == 0:
var block_total: Float32 = 0.0
for i in range(0, tpb, 32): # Sum warp results
if i < tpb:
block_total += shared_data[i][0]
output[block_id] = block_total
# Wait for all blocks to complete their calculations in sm90+
cluster_wait()
๊ณ ๊ธ ํด๋ฌ์คํฐ ํจํด ํ์ด๋ GPU ํ์ฉ๋ฅ ์ ๊ทน๋ํํ๊ธฐ ์ํด ์ํ, ๋ธ๋ก, ํด๋ฌ์คํฐ ์กฐ์ ์ ๊ฒฐํฉํ๋ ์ ๊ตํ 3๋จ๊ณ ๊ณ์ธต์ ์ต์ ํ๋ฅผ ๋ณด์ฌ์ค๋๋ค:
๋ ๋ฒจ 1: ์ํ ๋ ๋ฒจ ์ต์ ํ (์ค๋ ๋ ์ ์ถ)
๋ฐ์ดํฐ ์ค๋น ๋ฐ ์ค์ผ์ผ๋ง:
var data_scale = Float32(block_id + 1) # Block-specific scaling factor
if global_i < size:
shared_data[local_i] = input[global_i] * data_scale
else:
shared_data[local_i] = 0.0 # Zero-pad for out-of-bounds
barrier() # Ensure all threads complete data loading
์ํ ๋ ๋ฒจ ์ค๋ ๋ ์ ์ถ:
if elect_one_sync(): # Hardware elects exactly 1 thread per warp
var warp_sum: Float32 = 0.0
var warp_start = (local_i // 32) * 32 # Calculate warp boundary
for i in range(32): # Process entire warp's data
if warp_start + i < tpb:
warp_sum += shared_data[warp_start + i][0]
shared_data[local_i] = warp_sum # Store result at elected thread's position
์ํ ๊ฒฝ๊ณ ๊ณ์ฐ ์ค๋ช :
- ์ค๋ ๋ 37 (์ํ 1):
warp_start = (37 // 32) * 32 = 1 * 32 = 32 - ์ค๋ ๋ 67 (์ํ 2):
warp_start = (67 // 32) * 32 = 2 * 32 = 64 - ์ค๋ ๋ 199 (์ํ 6):
warp_start = (199 // 32) * 32 = 6 * 32 = 192
์ ์ถ ํจํด ์๊ฐํ (TPB=256, 8 ์ํ):
Warp 0 (threads 0-31): elect_one_sync() โ Thread 0 processes elements 0-31
Warp 1 (threads 32-63): elect_one_sync() โ Thread 32 processes elements 32-63
Warp 2 (threads 64-95): elect_one_sync() โ Thread 64 processes elements 64-95
Warp 3 (threads 96-127): elect_one_sync() โ Thread 96 processes elements 96-127
Warp 4 (threads 128-159):elect_one_sync() โ Thread 128 processes elements 128-159
Warp 5 (threads 160-191):elect_one_sync() โ Thread 160 processes elements 160-191
Warp 6 (threads 192-223):elect_one_sync() โ Thread 192 processes elements 192-223
Warp 7 (threads 224-255):elect_one_sync() โ Thread 224 processes elements 224-255
๋ ๋ฒจ 2: ๋ธ๋ก ๋ ๋ฒจ ์ง๊ณ (์ํ ๋ฆฌ๋ ์กฐ์ )
์ํ ๊ฐ ๋๊ธฐํ:
barrier() # Ensure all warps complete their elected computations
์ํ ๋ฆฌ๋ ์ง๊ณ (์ค๋ ๋ 0๋ง):
if local_i == 0:
var block_total: Float32 = 0.0
for i in range(0, tpb, 32): # Iterate through warp leader positions
if i < tpb:
block_total += shared_data[i][0] # Sum warp results
output[block_id] = block_total
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด:
- ์ค๋ ๋ 0์ด ๋ค์ ์์น์์ ์ฝ์ต๋๋ค:
shared_data[0],shared_data[32],shared_data[64],shared_data[96],shared_data[128],shared_data[160],shared_data[192],shared_data[224] - ์ด ์์น๋ค์๋ ์ ์ถ๋ ์ค๋ ๋๊ฐ ๊ณ์ฐํ ์ํ ํฉ๊ณ๊ฐ ์ ์ฅ๋์ด ์์ต๋๋ค
- ๊ฒฐ๊ณผ: 8๊ฐ ์ํ ํฉ๊ณ โ 1๊ฐ ๋ธ๋ก ํฉ๊ณ
๋ ๋ฒจ 3: ํด๋ฌ์คํฐ ๋ ๋ฒจ ๋จ๊ณ์ ๋๊ธฐํ
๋จ๊ณ์ ๋๊ธฐํ ์ ๊ทผ:
cluster_arrive() # Non-blocking: signal this block's completion
# ... Thread 0 computes and stores block result ...
cluster_wait() # Blocking: wait for all blocks to complete
์ ๋จ๊ณ์ ๋๊ธฐํ๋ฅผ ์ฌ์ฉํ ๊น?
cluster_arrive()๋ฅผ ์ต์ข ์ฐ์ฐ ์ด์ ์ ํธ์ถํ๋ฉด ์์ ์ค์ฒฉ์ด ๊ฐ๋ฅํฉ๋๋ค- ๋ค๋ฅธ ๋ธ๋ก์ด ์์ง ์ฒ๋ฆฌ ์ค์ธ ๋์์๋ ๋ธ๋ก์ด ์์ฒด ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ ์ ์์ต๋๋ค
cluster_wait()๋ก ๊ฒฐ์ ๋ก ์ ์๋ฃ ์์๋ฅผ ๋ณด์ฅํฉ๋๋ค- ๋
๋ฆฝ์ ์ธ ๋ธ๋ก ์ฐ์ฐ์ ๊ฒฝ์ฐ
cluster_sync()๋ณด๋ค ๋ ํจ์จ์ ์ ๋๋ค
๊ณ ๊ธ ํจํด ํน์ฑ
๊ณ์ธต์ ์ฐ์ฐ ์ถ์:
- 256๊ฐ ์ค๋ ๋ โ 8๊ฐ ์ ์ถ ์ค๋ ๋ (๋ธ๋ก๋น 32๋ฐฐ ์ถ์)
- 8๊ฐ ์ํ ํฉ๊ณ โ 1๊ฐ ๋ธ๋ก ํฉ๊ณ (๋ธ๋ก๋น 8๋ฐฐ ์ถ์)
- 4๊ฐ ๋ธ๋ก โ ๋จ๊ณ์ ์๋ฃ (๋๊ธฐํ๋ ์ข ๋ฃ)
- ์ ์ฒด ํจ์จ: ๋ธ๋ก๋น ์ค๋ณต ์ฐ์ฐ 256๋ฐฐ ์ถ์
๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ต์ ํ:
- ๋ ๋ฒจ 1:
input[global_i]์์ ๋ณํฉ๋ ์ฝ๊ธฐ, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ค์ผ์ผ๋ง๋ ์ฐ๊ธฐ - ๋ ๋ฒจ 2: ์ ์ถ๋ ์ค๋ ๋๊ฐ ์ํ ๋ ๋ฒจ ์ง๊ณ๋ฅผ ์ํํฉ๋๋ค (256๊ฐ ๋์ 8๊ฐ ์ฐ์ฐ)
- ๋ ๋ฒจ 3: ์ค๋ ๋ 0์ด ๋ธ๋ก ๋ ๋ฒจ ์ง๊ณ๋ฅผ ์ํํฉ๋๋ค (8๊ฐ ๋์ 1๊ฐ ์ฐ์ฐ)
- ๊ฒฐ๊ณผ: ๊ณ์ธต์ ๋ฆฌ๋์ ์ ํตํด ๋ฉ๋ชจ๋ฆฌ ๋์ญํญ ์ฌ์ฉ๋์ ์ต์ํํฉ๋๋ค
๋๊ธฐํ ๊ณ์ธต ๊ตฌ์กฐ:
barrier(): ๋ธ๋ก ๋ด๋ถ ์ค๋ ๋ ๋๊ธฐํ (๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์ํ ์ฒ๋ฆฌ ํ)cluster_arrive(): ๋ธ๋ก ๊ฐ ์ ํธ (๋ ผ๋ธ๋กํน, ์์ ์ค์ฒฉ ๊ฐ๋ฅ)cluster_wait(): ๋ธ๋ก ๊ฐ ๋๊ธฐํ (๋ธ๋กํน, ์๋ฃ ์์ ๋ณด์ฅ)
์ โ๊ณ ๊ธโ์ธ๊ฐ:
- ๋ค๋จ๊ณ ์ต์ ํ: ์ํ, ๋ธ๋ก, ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ๊ฒฐํฉํฉ๋๋ค
- ํ๋์จ์ด ํจ์จ:
elect_one_sync()๋ฅผ ํ์ฉํ์ฌ ์ํ ํ์ฉ๋ฅ ์ ์ต์ ํํฉ๋๋ค - ๋จ๊ณ์ ์กฐ์ : ๊ณ ๊ธ ํด๋ฌ์คํฐ API๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฐํ ๋๊ธฐํ๋ฅผ ๊ตฌํํฉ๋๋ค
- ํ๋ก๋์ ์์ค: ์ค์ GPU ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฌ์ฉ๋๋ ํจํด์ ๋ณด์ฌ์ค๋๋ค
์ค์ ์ฑ๋ฅ ์ด์ :
- ๋ฉ๋ชจ๋ฆฌ ๋ถํ ๊ฐ์: ๋์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ ์ค๋ ๋ ์๊ฐ ์ ์ด์ง๋๋ค
- ๋ ๋์ ์ํ ํ์ฉ: ์ ์ถ๋ ์ค๋ ๋๊ฐ ์ง์ค์ ์ธ ์ฐ์ฐ์ ์ํํฉ๋๋ค
- ํ์ฅ ๊ฐ๋ฅํ ์กฐ์ : ๋จ๊ณ์ ๋๊ธฐํ๊ฐ ๋ ํฐ ํด๋ฌ์คํฐ ํฌ๊ธฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ์๊ณ ๋ฆฌ์ฆ ์ ์ฐ์ฑ: ๋ณต์กํ ๋ค๋จ๊ณ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค
๋ณต์ก๋ ๋ถ์:
- ์ํ ๋ ๋ฒจ: ์ ์ถ๋ ์ค๋ ๋๋น O(32) ์ฐ์ฐ = ๋ธ๋ก๋น ์ด O(256)
- ๋ธ๋ก ๋ ๋ฒจ: ๋ธ๋ก๋น O(8) ์ง๊ณ ์ฐ์ฐ
- ํด๋ฌ์คํฐ ๋ ๋ฒจ: ๋ธ๋ก๋น O(1) ๋๊ธฐํ ์ค๋ฒํค๋
- ์ ์ฒด: ๋๊ท๋ชจ ๋ณ๋ ฌํ ์ด์ ์ ๊ฐ์ง ์ ํ ๋ณต์ก๋
์์ ํ GPU ๊ณ์ธต ๊ตฌ์กฐ
์ถํํฉ๋๋ค! ์ด ํผ์ฆ์ ์๋ฃํจ์ผ๋ก์จ ์์ ํ GPU ํ๋ก๊ทธ๋๋ฐ ์คํ์ ํ์ตํ์ต๋๋ค:
โ ์ค๋ ๋ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ: ๊ฐ๋ณ ์คํ ๋จ์ โ ์ํ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ: 32๊ฐ ์ค๋ ๋ SIMT ์กฐ์ โ ๋ธ๋ก ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ: ๋ฉํฐ ์ํ ์กฐ์ ๊ณผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ โ ๐ ํด๋ฌ์คํฐ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ: SM90+ API๋ฅผ ํ์ฉํ ๋ฉํฐ ๋ธ๋ก ์กฐ์ โ ํด๋ฌ์คํฐ ๋๊ธฐํ ๊ธฐ๋ณธ ์์๋ก ์ฌ๋ฌ ์ค๋ ๋ ๋ธ๋ก์ ์กฐ์ โ ํด๋ฌ์คํฐ API๋ฅผ ์ฌ์ฉํ์ฌ ๋จ์ผ ๋ธ๋ก ํ๊ณ๋ฅผ ๋์ด ์๊ณ ๋ฆฌ์ฆ์ ํ์ฅ โ ์ํ + ๋ธ๋ก + ํด๋ฌ์คํฐ ์กฐ์ ์ ๊ฒฐํฉํ ๊ณ์ธต์ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํ โ SM90+ ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์ฐจ์ธ๋ GPU ํ๋์จ์ด๋ฅผ ํ์ฉ
์ค์ ์์ฉ
์ด ํผ์ฆ์ ๊ณ์ธต์ ์กฐ์ ํจํด์ ๋ค์ ๋ถ์ผ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค:
๊ณ ์ฑ๋ฅ ์ปดํจํ :
- ๋ฉํฐ ๊ทธ๋ฆฌ๋ ๊ธฐ๋ฒ: ๊ฐ ๋ ๋ฒจ์ด ์๋ก ๋ค๋ฅธ ํด์๋์ ๊ทธ๋ฆฌ๋๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ๋๋ฉ์ธ ๋ถํด: ๋ฌธ์ ์ ํ์ ๋๋ฉ์ธ์ ๊ฑธ์น ๊ณ์ธต์ ์กฐ์
- ๋ณ๋ ฌ ๋ฐ๋ณต๋ฒ: ์ํ ๋ ๋ฒจ์ ๋ก์ปฌ ์ฐ์ฐ๊ณผ ํด๋ฌ์คํฐ ๋ ๋ฒจ์ ์ ์ญ ํต์
๋ฅ๋ฌ๋:
- ๋ชจ๋ธ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ๊ฐ ๋ธ๋ก์ด ๋ชจ๋ธ์ ์๋ก ๋ค๋ฅธ ๊ตฌ์ฑ ์์๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ํ์ดํ๋ผ์ธ ๋ณ๋ ฌ ์ฒ๋ฆฌ: ์ฌ๋ฌ ํธ๋์คํฌ๋จธ ๋ ์ด์ด์ ๊ฑธ์น ๋จ๊ณ์ ์ฒ๋ฆฌ
- ๊ธฐ์ธ๊ธฐ ์ง๊ณ: ๋ถ์ฐ ํ์ต ๋ ธ๋์ ๊ฑธ์น ๊ณ์ธต์ ๋ฆฌ๋์
๊ทธ๋ํฝ์ค ๋ฐ ์๊ฐํ:
- ๋ฉํฐ ํจ์ค ๋ ๋๋ง: ๋ณต์กํ ์๊ฐ ํจ๊ณผ๋ฅผ ์ํ ๋จ๊ณ์ ์ฒ๋ฆฌ
- ๊ณ์ธต์ ์ปฌ๋ง: ๊ฐ ๋ ๋ฒจ์ด ์๋ก ๋ค๋ฅธ ์ธ๋ถ๋์์ ์ปฌ๋งํฉ๋๋ค
- ๋ณ๋ ฌ ์ง์ค๋ฉํธ๋ฆฌ ์ฒ๋ฆฌ: ์กฐ์ ๋ ๋ณํ ํ์ดํ๋ผ์ธ
๋ค์ ๋จ๊ณ
์ด์ ์ต์ ํ๋์จ์ด์์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ต์ฒจ๋จ GPU ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค!
๋ ๋ง์ ๋์ ์ ํ ์ค๋น๊ฐ ๋์ จ๋์? ๋ค๋ฅธ ๊ณ ๊ธ GPU ํ๋ก๊ทธ๋๋ฐ ์ฃผ์ ๋ฅผ ํ๊ตฌํ๊ณ , Puzzle 30-32์ ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ์ ๋ณต์ตํ๊ณ , NVIDIA ๋๊ตฌ์ ํ๋กํ์ผ๋ง ๋ฐฉ๋ฒ๋ก ์ ์ ์ฉํ๊ฑฐ๋, ์ด ํด๋ฌ์คํฐ ํ๋ก๊ทธ๋๋ฐ ํจํด์ ๊ธฐ๋ฐ์ผ๋ก ์์ ๋ง์ ์ฐ์ฐ ์ํฌ๋ก๋๋ฅผ ๊ตฌ์ถํด ๋ณด์ธ์!