2장. 신경망과 딥러닝의 기본 원리
PyTorch 와 GPU
큰 식당의 주방을 떠올려 보세요. 솜씨 좋은 요리사 몇 명은 복잡한 요리를 차례로 해냅니다. 반면 감자 만 개를 깎는 일은 손 빠른 보조 수천 명이 한꺼번에 덤비는 편이 훨씬 빠릅니다. CPU 가 요리사, GPU 가 보조들입니다.
왼쪽 CPU 는 코어가 몇 개에서 수십 개라 복잡한 일을 차례로 잘합니다. 오른쪽 GPU 는 단순한 계산을 하는 코어가 수천 개라, 신경망의 행렬 곱처럼 같은 곱셈을 엄청나게 많이 하는 일에 빠릅니다. 여기서 감자 깎기가 행렬 곱입니다. 비유와 다른 점은, GPU 에 일을 맡기려면 재료(텐서)를 GPU 의 메모리로 옮기는 수고가 든다는 것입니다. 일이 작으면 옮기는 시간이 더 걸려 오히려 느립니다.
텐서와 장치
PyTorch 의 텐서는 NumPy 배열과 거의 같게 쓰고, 어느 장치에 있는지를 하나 더 가집니다.
| 할 일 | 코드 |
|---|---|
| GPU 를 쓸 수 있나 | torch.cuda.is_available() |
| 쓸 장치를 고르기 | device = "cuda" if torch.cuda.is_available() else "cpu" |
| 텐서를 옮기기 | x = x.to(device) |
| 모델을 옮기기 | model = model.to(device) |
| NumPy 로 돌아오기 | x.cpu().numpy() |
모델과 데이터는 같은 장치에 있어야 계산할 수 있습니다. 하나만 옮기면 오류가 납니다.
import torch
device = "cuda" if torch.cuda.is_available() else "cpu" # GPU 가 없으면 CPU 를 쓴다
x = torch.ones(2, 3) # 처음에는 CPU 에 만들어진다
x = x.to(device) # 고른 장치로 옮긴다
print(device, x.device, x.shape)GPU 가 있는 PC 에서는 cuda cuda:0 torch.Size([2, 3]) 처럼, 없는 PC 에서는 cpu cpu torch.Size([2, 3]) 처럼 나옵니다. 같은 코드가 어느 PC 에서나 돌아가도록 장치를 이렇게 고르는 것이 관례입니다.
코드에 나오는 함수
주방 비유로 정리합니다. 텐서가 재료, CPU 가 요리사의 조리대, GPU 가 보조들의 작업대입니다. 장치(device)는 "재료가 지금 어느 조리대 위에 있나"입니다.
| 함수 | 하는 일 | 주방으로 치면 |
|---|---|---|
torch.cuda.is_available() | 이 PC 에서 PyTorch 가 NVIDIA GPU 를 쓸 수 있는지 True, False 로 알려 준다 | 보조들의 작업대가 있는지 본다 |
"cuda" if ... else "cpu" | 쓸 수 있으면 "cuda"(GPU), 아니면 "cpu" 를 고른다. PyTorch 는 NVIDIA GPU 를 CUDA 라는 기술로 쓰므로 GPU 를 "cuda" 라고 부른다 | 작업대가 있으면 그쪽, 없으면 조리대를 쓰기로 한다 |
torch.ones(2, 3) | 모든 칸이 1 인 (2, 3) 텐서를 만든다. 따로 정하지 않으면 CPU 에 만들어진다 | 재료를 조리대 위에 꺼내 놓는다 |
x.to(device) | 텐서를 고른 장치로 옮긴 새 텐서를 돌려준다. 그래서 x = x.to(device) 처럼 다시 받아 둔다 | 재료를 작업대로 옮긴다 |
x.device | 텐서가 지금 있는 장치. cpu 또는 cuda:0(0번 GPU)이다 | 재료가 어느 조리대 위에 있나 |
x.shape | 텐서의 모양("텐서는 무엇인가" 절) | 재료의 크기 |
x.cpu().numpy() | GPU 에 있는 텐서를 CPU 로 가져온 뒤 NumPy 배열로 바꾼다 | 다 된 요리를 손님 식탁(NumPy)으로 내간다 |
cuda:0 의 0 은 GPU 번호입니다. GPU 가 여러 장이면 cuda:0, cuda:1 처럼 번호로 가립니다. x.to(device) 는 원래 텐서를 바꾸지 않고 옮긴 복사본을 돌려준다는 점을 기억해 둡니다. x.to(device) 라고만 쓰고 받아 두지 않으면 x 는 그대로 CPU 에 남습니다.
옮기고 되돌리기를 들여다보기
텐서가 장치 사이를 오가는 모습과, 장치가 다를 때 나는 오류를 찍어 봅니다.
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
x = torch.ones(2, 3)
print("처음 자리:", x.device) # 처음에는 늘 CPU
x = x.to(device)
print("옮긴 자리:", x.device) # GPU 가 있으면 cuda:0
back = x.cpu().numpy() # CPU 로 되돌려 NumPy 배열로
print(type(back).__name__, back.shape)
if device == "cuda":
print("GPU 이름:", torch.cuda.get_device_name(0))
try:
x + torch.ones(2, 3) # GPU 의 x 와 CPU 의 텐서를 더하려 한다
except RuntimeError as e:
print("오류:", e)
else:
print("GPU 가 없어 장치가 다른 경우의 오류는 생기지 않습니다.")이 교안을 만든 PC(GeForce GTX 1650)에서는 이렇게 나왔습니다.
처음 자리: cpu
옮긴 자리: cuda:0
ndarray (2, 3)
GPU 이름: NVIDIA GeForce GTX 1650
오류: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!GPU 가 없는 PC 에서는 "옮긴 자리"가 cpu 이고 마지막 줄에 안내 문장이 나옵니다. 오류 문장은 "모든 텐서가 같은 장치에 있어야 하는데 cuda:0 과 cpu 두 곳에 있다"는 뜻입니다. 재료가 서로 다른 조리대에 있으면 한 냄비에 넣을 수 없는 것과 같습니다. 이 오류가 나면 모델과 데이터 양쪽에 .to(device) 를 했는지부터 봅니다. torch.cuda.get_device_name(0) 은 0번 GPU 의 이름을 알려 줍니다.
이 교안의 약속
이 장의 실습 4부터 6까지는 일부러 CPU 에서 학습합니다. 모델이 작아 CPU 로도 몇 초면 끝나고, GPU 가 없는 수강생과 결과 숫자를 똑같이 맞추기 위해서입니다. GPU 는 실습 1에서 속도를 견주어 보고, 모델이 커지는 4장부터 본격적으로 씁니다.
주의할 점
torch.cuda.is_available()이 False 로 나오는데 NVIDIA GPU 가 있다면, CPU 판 PyTorch 를 설치한 경우가 많습니다. 환경설정의 GPU 판 설치 명령으로 다시 설치합니다.- GPU 에서 학습하면 같은 씨앗이라도 마지막 자리의 숫자가 CPU 와 조금 다를 수 있습니다.