컴퓨터의 내부장치를 분류해보면, 크게 다음의 두 파트로 구분된다는 걸 알 수 있다. CPU와 RAM 등으로 구성된 일반적인 장치, 그리고 GPU와 VRAM 등으로 구성된 '그래픽카드'라는 특수한 장치.
CPU와 GPU는 이름에서부터 알 수 있듯 둘 다 특정한 연산을 수행하는 프로세서인데, 이 둘은 서로 내부적인 구조가 달라서, 수행해야 할 연산에 따라 필요한 프로세서가 달라진다. GPU는 대규모 병렬 연산에 특화된 프로세서라고 이해할 수 있다.
RAM은 우리가 흔하게 알고 있듯, CPU와 내부적으로 시스템버스를 통해 연결되어 있는 저장장치이다. CPU와 거리가 가깝게 연결되어 존재한다.
'그래픽카드'는 GPU와 나머지 다른 몇 가지 요소들을 포함하는 전체 장치로서, GPU와 구분되는 용어이다. 이 그래픽카드에는 VRAM이라는 특수한 RAM이 존재하는데, 따라서 GPU라는 연산장치와 VRAM이라는 저장장치는 둘 다 같은 '그래픽카드' 내부에 위치하므로, 서로 데이터 전송속도가 빠르다. 하지만 VRAM에 있는 것을 CPU로 전송할 때나, 그래픽카드 외부에 존재하는 RAM에 있는 것을 GPU로 전송할 때는 시간이 더 걸리게 된다.
이것이 CPU와 RAM, 그리고 GPU와 VRAM의 관계이다.
PyTorch에서 사용되는 텐서는 선언할 때 서로 구분되듯, CPU텐서와 GPU텐서로 종류가 나뉜다. 그리고 CPU텐서는 컴퓨터의 일반 저장장치인 RAM에, GPU텐서는 그래픽카드의 저장장치인 VRAM에 저장된다. 그리고 이 텐서들의 연산은, 각 데이터가 존재하는 영역에서만 할 수 있다고 보면 된다. GPU에서의 연산은 VRAM에 있는 데이터로, CPU에서의 연산은 RAM에 있는 데이터로. 그래서 PyTorch는 두 종류의 텐서를 구분하는 것이다.
그러면 궁금한 게 생길 것이다. 왜 GPU텐서만 쓰지 않고 CPU텐서도 존재하는 거지?
이유는 간단하다. GPU가 없는 환경도 있으니까. 그런 환경에서도 CPU를 이용하여 코드를 돌려야 하니까 CPU텐서가 존재해야 한다. 또한 작은 연산은 GPU를 사용하는 게 오히려 손해일 수 있다. 처음에 코드상으로 텐서 데이터를 넣으면, 그건 CPU의 메모리인 RAM에 생성된다. 이걸 GPU에서 연산시키기 위해 GPU텐서로 생성했었다면, 필히 GPU가 존재하는 그래픽카드의 메모리인 VRAM으로 초기에 한번은 데이터를 복사하는 과정이 필요하다. 만약 텐서의 연산이 매우 가벼운 연산이라면, 이 데이터 복사과정이 실제 연산과정보다 더 무거울 수 있고, 배보다 배꼽이 더 큰 상황이 발생할 수 있다. 차라리 복사를 안 하고 CPU에서 연산시키는 게 나은 것이다.
또한 다른 이유도 존재한다. 희귀한 함수나 특이한 일부 연산은 GPU에 구현이 안 되어 있는 경우도 있다. 이럴 땐 어쩔 수 없이 CPU텐서로 수행해야 한다. 또한 GPU메모리는 작고 비싸서, 필요한 배치만 GPU로 옮겨서 연산하는 게 일반적이다.
이렇게 보면, 이제 다음의 내용도 이해가 될 것이다.
데이터는 NumPy 배열로 존재할 수도 있고, CPU텐서로, 혹은 GPU텐서로 존재할 수도 있다. 데이터가 존재하는 형식에 따라 연결이 되는 게 있고 안 되는 게 있다.
우선, NumPy와 CPU텐서는 같은 메모리를 공유하므로 직접 연결되어 있다. 따라서 CPU텐서에서는 NumPy 연산이 가능하다. 하지만 GPU텐서는 .numpy()와 같은 연산이 불가능하다. 반드시 해당 텐서를 CPU로 옮겨야 한다. NumPy는 CPU메모리만 다룰 줄 아는 것이다.
또한 같은 연산에 들어가는 텐서는 같은 device여야 한다. 즉 CPU텐서와 GPU텐서는 HW 차원에서 아예 다른 주소공간을 가지고 있어서, GPU 커널이 CPU메모리를, CPU가 GPU메모리를 그냥 단순하게 바로 가져와서 쓸 수가 없다. 근본적으로 물리적인 메모리가 따로 떨어져 있어서 그렇다.
다음으로, GPU텐서로 짠 코드인데 로컬에서 해당 GPU가 없으면 어떻게 되는지에 대해 알아보겠다. CUDA는 PyTorch에서 주로 사용하는 것으로, NVIDIA가 만든 GPU 프로그래밍 플랫폼이자 API이다. 그래서 NVIDIA GPU와 CUDA 드라이버가 설치되어 있어야 CUDA로 코딩된 GPU텐서를 사용할 수 있다. 만약 AMD GPU이거나 Apple 모델이라면 다른 환경을 구성해야 한다. CUDA가 없는데 CUDA를 쓰고 코드를 돌리면, 그냥 에러가 뜬다. 그래서 보통 실전에서는 코드에 조건문을 포함하여, 경우에 따라 GPU와 CPU를 바꿔서 선언한다.