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.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 ๊ทธ๋ํ ๋ ํผ๋ฐ์ค ๊ตฌํ๊ณผ ๋น๊ตํด ๋ณด์ธ์.