[딥러닝 알아듣기] 2.1. GPGPU와 딥 러닝

“딥러닝 알아듣기” 시리즈는 딥러닝의 기초 지식을 저만의 방식으로 쉽게 풀어내는 시리즈입니다. 이번 챕터에서는 딥러닝에 왜 GPU가 활용되는지 알아봅니다.


앞선 챕터에서 인공 신경망을 코드로 어떻게 구현할 수 있을지 간단히 알아보았다. 인공 신경망을 효율적으로 표현하기 위해서 행렬을 사용한다. 앞서서도 이야기했지만, 행렬 연산으로써 인공 신경망을 구현할 때의 가장 큰 이점은 GPU의 활용이 가능 하다는 것이다. GPU를 활용하면서 인공 신경망의 구현과 학습 속도가 폭발적으로 빨라졌고, 그에 따라 더 깊은 인공 신경망이 등장할 수 있게 되었다. GPU의 활용으로 인해 딥 러닝 분야가 생기고 발전했다고 해도 과언이 아닐 것이다. 이번 챕터에서는, 딥 러닝을 가능케 한 기반 기술인 GPGPU(General-Purpose GPU) 기술에 대해 간단히 살펴보자. 지피지기면 백전백승이라는 말이 있듯이, GPU가 어떻게 딥 러닝을 강력하게 만들어 주는지 이해하면 딥 러닝 공부에 큰 도움이 될 것이다.

2.1.1. GPU가 하는 일

보통 GPU 라고 하면, 고사양의 그래픽을 제공하는 게임을 떠올린다. GPU는 복잡한 그래픽 연산을 위해 특화된 처리 장치로, 컴퓨터의 전체적인 성능이 향상되고 일반 사용자에게 널리 보급되면서 증가하기 시작한 다량의 그래픽 처리 수요를 충족시키기 위해서 개발되었다. 실제로 GPU란 단어 자체는 1990년대부터 본격적으로 사용되기 시작했다. 이전에도 그래픽 처리를 위한 연산 장치는 존재했다. 1970년대부터 디스플레이 상에 여러 가지 모양을 띄우는 속도를 빠르게 해주는 장치를 ‘그래픽 칩’ 이라고 불렀다. 주로 게임기와 같이 화면에 출력되는 그래픽이 중요시되는 장치에 붙어, 여러 가지의 2D 도형과 그림들을 빠르게 그려주는 장치로써 사용되었다.

시간이 흘러 게임 산업과 영상 산업이 발달하면서, 컴퓨터 디스플레이에 표현하고자 하는 그림이 2D 형태에서 3D 형태로 옮겨가기 시작했다. 도형을 메모리 버퍼의 구조에 맞추어 미리 그려 놓고 띄우기만 하면 됐던 이전과는 달리, 3D 그래픽이 필요해지면서 메모리 버퍼에 올라갈 그림을 그리는 것 자체가 매우 어려운 일이 되었다. 같은 나무를 화면에 그리더라도, 2D 그래픽에서는 간단히 단면적인 나무의 모양만 그리면 끝날 일이다. 그러나 3D 그래픽에서는 나무의 입체적인 모양과 광원의 위치 등 복합적인 요소들을 고려해서 나무를 그려야 한다. 디스플레이에 출력할 그래픽을 그리는 작업의 연산량이 엄청나게 증가한 것이다. 이런 수요에 따라 3D 그래픽 처리를 빠르고 효율적으로 할 수 있는 연산 장치의 필요성이 대두되었다.

그래서 1990년대부터 본격적으로 그래픽 카드라고 불리는 고성능 그래픽 처리 장치가 개발되기 시작했다. 그와 동시에 GPU를 활용해 복잡한 3D 연산을 처리할 수 있도록 돕는 소프트웨어들이 발전했는데, Windows 환경의 DirectX가 대표적이다. GPU의 등장으로 3D 그래픽의 처리 속도가 빨라지면서, 3D 게임과 영화 등 수준높은 그래픽이 필요한 분야의 성장을 견인했다.

3D 그래픽 처리의 궁극적인 목표는 현실 세계에서 우리가 보는 것들을 그대로 2D 디스플레이 상에 옮기고자 하는 것이다. 이 방법을 연구하는 분야가 컴퓨터 그래픽스(Computer Graphics) 분야이다. 컴퓨터 그래픽스 분야는 ‘어떻게 하면 더 사실적으로 3D의 세계를 표현하고, 그것을 2D인 디스플레이 상에 옮길 수 있을까?’ 라는 질문으로 인해 발전했다. 디지털 공간에서 현실과 최대한 비슷하게 물체들을 표현하고, 그것들의 상호작용을 구현해야 한다.

컴퓨터 그래픽스에서 물체의 표현, 이동, 회전 등의 모든 작용은 행렬 로써 구현된다. 선형대수나 컴퓨터 비전을 조금이라도 공부해보았다면 알고 있겠지만, 행렬곱으로 이동, 확대, 축소, 회전 등을 모두 구현할 수 있다. 이 사실은 3D 그래픽에서도 동일하다. 다만 표현하고자 하는 세계가 3D 세계이므로 3차원 행렬 사이의 연산으로 표현된다. 이것 뿐만이 아니다. 가상 세계에서 광원의 위치를 설정한 후 물체에 빛을 쏘면 물체가 빛을 받아들이는 과정도 행렬로 표현된다. 또 그래픽스에서 가장 중요한 3D 세계에서 2D 디스플레이로의 변환도 모두 행렬 연산으로 처리된다.

그래서 GPU가 기본적으로 가져야 할 가장 중요한 능력이 커다랗고 복잡한 벡터와 행렬의 연산이다. 행렬 연산이 빨라진다는 것은 3D 그래픽 처리가 빨라진다는 것과 동일한 의미이기 때문이다. 떄문에 GPU는 행렬 계산을 효율적이고 빠르게 할 수 있는 방향으로 발전해왔다. 지금의 GPU는 컴퓨터 디스플레이에 현실 세계의 모습을 거의 그대로 보여줄 수 있는 수준까지 왔다. 필자는 디즈니의 애니메이션 겨울왕국을 보고 바다가 넘치는 장면이 너무 사실적이라 놀랐는데, 후에 이야기를 들어보니 더욱 사실적으로 구현할 수 있었지만 애니메이션인 점을 고려해 일부러 힘을 뺐다고 한다.

여러 반도체 회사에서 GPU를 생산하고 판매하지만, 가장 대중적으로 많이 사용되는 GPU는 NVIDIA 사의 제품들이다. 현재 그래픽스와 딥 러닝 분야에서도 NVIDIA사의 GPU가 가장 강세를 보이고 있다. NVIDIA의 GPU는 하드웨어의 강력함도 있지만, 그것을 활용할 수 있도록 돕는 CUDA 등의 소프트웨어 프레임워크도 강력하다는 장점이 있다.

2.1.2. GPU의 연산 구조

그럼 GPU는 어떤 방식으로 연산을 하길래 행렬 연산을 이렇게 빠르게 할 수 있을까? 가장 많이 사용되는 NVIDIA GPU의 구조를 기반으로 해서 GPU의 연산 구조와 특징을 간단히 알아보도록 하자.

GPU도 CPU와 같이 숫자를 연산하는 장치이다. CPU가 연산을 처리하는 기본 단위는 코어(Core) 이다. CPU의 코어 안에는 단순 연산 작업을 위한 장치 외에도, 명령을 해석하는 해석기와 연산의 흐름을 제어하는 장치 등 수많은 장치들이 같이 들어있다. 코어가 연산 명령을 전달받으면, 일련의 절차에 따라 순차적으로 계산하고 결과를 내놓는다.

CPU의 코어는 하나하나가 많은 기능과 강력한 연산 능력을 가지고 있기 때문에, 하나의 CPU안에 그다지 많은 코어가 들어가지 않는다. 정말 많아봤자 32개 또는 64개 정도이다. 그러나 GPU는 다르다. GPU는 기본적으로 병렬 연산 에 강하도록 설계되었다. CPU에 강력한 코어가 몇 개 박혀있는 것과는 다르게, GPU는 수백 개에서 수천 개의 많은 코어들을 가지고 있다. 다만 각각의 코어들이 많은 기능을 가지지 않고, 단순한 숫자 계산 작업만을 하도록 설계되어 있다. 말그대로 계산밖에 할 줄 아는게 없다. 그래서 GPU의 코어는 CPU 코어와 달리 스스로 명령의 모든 부분을 처리할 수 없다.

대신 GPU는 계산 자체를 병렬적 으로 처리한다. 하나의 간단한 예를 들어 보자. 1부터 8까지의 숫자를 모두 더한 계산 결과를 달라는 명령이 떨어졌다. 이 계산 작업을 CPU 코어에게 시킬 때와 GPU에게 시킬 때, 각각 아래와 같은 과정으로 연산한다.

<그림 1> CPU와 GPU의 연산 과정

물론 정확히 이런 식으로 동작한다기보다는, 이런 방식의 차이가 있다는 점을 알기 위한 그림이다. CPU가 순서대로 1부터 8까지의 숫자들을 더하는 것과 달리, GPU는 각각의 계산을 따로 수행하고 점점 합쳐나가는 방식으로 연산한다. 언뜻 보면 덧셈 연산의 개수는 7개로 동일하니, 속도의 차이가 왜 생기는지 의문일 수 있다. CPU가 GPU가 차이를 보이는 이유는 두 프로세서가 코어를 활용하는 방식이 달라서 이다. CPU는 저 모든 연산 과정을 하나의 코어만을 이용해 실행한다. 그러나 GPU는 각각의 연산을 모두 다른 코어를 사용해 동시에 실행 한다.

<그림 2> CPU와 GPU의 코어 사용

각각의 점선 박스가 하나의 코어 안에서 이루어지는 연산이고, 같은 색깔 박스끼리 동시에 실행된다고 보면 된다. CPU 코어는 모든 연산의 과정을 하나의 코어 안에서 순차적으로 실행한다. 그에 반해 GPU는 각각의 연산을 잘게 나누어 여러 코어들에 할당하고, 동시에 계산해 결과를 합친다. 여러 코어가 동시에 계산한 결과를 합치는 과정의 반복인 것이다. 그래서 위의 그림에서 CPU 코어는 7번의 연산을 할 시간이 필요하지만, GPU는 3번의 연산을 할 시간이면 동일한 계산을 끝낼 수 있게 된다. 이렇게 병렬 연산을 활용하는 것이 GPU가 가지는 힘이다.

그러나 앞서 말했다시피 GPU 안 각각의 코어는 단순 숫자 연산밖에 할 수 없다. CPU의 코어는 1부터 8까지 더하는 모든 과정을 스스로 처리할 수 있다. 두 개의 숫자를 선택해서 덧셈하고 결과를 잠시 저장한 후, 같이 덧셈할 다음 숫자를 찾는 일까지 모두 스스로 할 수 있다. 그러나 GPU의 코어는 그렇지 않다. 스스로 연산할 숫자를 결정할 수도 없고, 연산 명령을 내릴 수도 없다. 그저 연산할 숫자와 연산의 종류가 전달되면 그것에 따라 단순 계산만 할 뿐이다. 그래서 GPU는 CPU처럼 스스로 명령을 해석하고 동작할 수 없다. 명령을 해석하고 실행하는 능력이 있는 CPU가, GPU에게 숫자 8개를 병렬 연산으로 더해 오라고 시킨다. 그럼 GPU는 자신의 능력대로 수많은 코어들을 동시에 굴려서 빠르게 결과값을 구하고, 다시 CPU에게 그 값을 전해준다. GPU의 능력은 CPU와 같이 이러한 순서를 거쳐야만 발휘될 수 있다.

컴퓨터 그래픽스 분야에서 GPU가 강력한 이유가 바로 이러한 GPU의 연산 구조에서 나타난다. 앞서 그래픽 처리의 대부분은 행렬 연산으로 이루어진다고 말한 바 있다. 단순한 행렬끼리의 곱셈을 계산할 때도, CPU와 GPU의 처리 방식은 연산 속도에 아주 큰 차이를 가져온다.
아래의 행렬곱을 CPU와 GPU로 각각 계산해보자.

행렬곱은 각 열과 각 행의 원소들을 순서대로 곱하고 더하는 과정의 연속이다. CPU가 이 행렬곱을 연산한다면, 이 모든 과정을 순서대로 하나씩 연산해 나갈 것이다. 모든 과정은 하나의 CPU 코어 안에서 진행된다.

<그림 4> CPU는 하나의 코어에서 모든 연산을 순서대로 실행한다.

그러나 GPU로 연산한다면 다르다. GPU의 병렬 연산을 활용하기 위해서 행렬곱의 과정을 각 열과 행끼리의 곱셈으로 쪼개고, 그 안에서의 덧셈 연산들조차 쪼개서 여러 코어에서 동시에 실행한다.

<그림 5> GPU는 연산을 모두 분할해 병렬로 실행한다.

마찬가지로 점선 블럭이 하나의 코어에서 수행하는 계산이고, 같은 색깔 블럭끼리 동시에 수행된다. 그림으로 보니 차이가 이해되지 않는가? 모든 곱셈과 덧셈을 순서대로 수행했던 CPU에서의 연산과 달리, 행렬곱의 과정을 쪼개고 쪼개서 여러 코어를 동시에 계산시킨다. 행렬곱이란 결국 동일한 곱셈 연산의 수많은 반복인데, 이런 수많은 반복을 동시에 처리할 수 있으니 당연히 GPU로 연산하는 것이 빠를 수밖에 없다.
CPU와 GPU의 행렬 연산 속도 차이는 계산하는 행렬이 커질수록 지수적으로 증가한다. 연산 과정의 차이를 알고 나니, 당연한 속도 차이라는 생각이 든다. 행렬 연산과 같은 단순한 반복 작업이 많은 그래픽스 분야의 특성 상, GPU가 강력한 힘을 발휘하는 것은 당연할 것이다.

2.1.3. GPGPU의 등장과 딥 러닝

앞서 보았듯이 GPU는 단순 계산을 반복하는 작업을 수많은 코어로 병렬 처리해서 속도를 향상시켰다. 이렇게 강력한 GPU의 능력을 컴퓨터 그래픽스 말고 더욱 다양한 분야에 사용하고자 하는 연구가 꾸준히 이루어졌다. 그 중 대표적인 분야가 인공 신경망을 다루는 딥 러닝 이다.

앞선 챕터에서 인공 신경망을 행렬곱의 연속으로 표현해보았다. 인공 신경망의 은닉층이 두꺼워져도 커다란 행렬끼리의 곱셈으로 생각하여 코드로 간단히 표현할 수 있었다. 그러나 다르게 생각하면, 은닉층이 두꺼워질수록 행렬 연산의 복잡성도 기하급수적으로 증가하게 된다. 은닉층이 깊고 넓은 인공 신경망의 계산을 CPU에게 시키면 정말이지 한참 오래 걸릴 것이다. 위에서 CPU가 행렬 연산을 계산하는 과정을 직접 보지 않았는가. 일일이 풀어서 순서대로 곱셈하고 더하고 하는 과정을 각각 512 x 256, 256 x 512의 크기를 가지는 행렬끼리의 곱셈 계산에 적용한다고 생각하면 끔찍하다. 심지어 은닉층이 수십 개면 이런 계산을 수십 번이나 더 해야 한다. 그래서 GPU가 필요하다. 수 천개의 코어로 행렬곱 과정에 있는 수많은 곱셈과 덧셈 계산을 병렬처리 하기때문에, CPU로 인공 신경망을 구현했을 때보다 훨씬 빠를 것이다.

특히 NVIDIA는 딥 러닝에 GPU가 적극적으로 활용될 것임을 예상했고, GPU의 연산 능력을 그래픽스가 아닌 다른 분야에서 효율적으로 활용할 수 있게 해주는 CUDA 라이브러리를 개발했다. CUDA는 NVIDIA에서 개발된 GPU를 활용한 연산을 할 수 있게 만들어주는 라이브러리다. CUDA는 프로그래머가 작성한 병렬 처리 연산 코드를 알맞게 가공해서, CPU 대신 GPU에게 연산 작업을 넘겨줄 수 있게 한다.

GPGPU가 연산 속도에서만 이점을 가지고 있는 것은 아니다. 인공 신경망의 문제점 중 하나는, 신경망의 크기가 커질수록 컴퓨터의 메모리를 정말 많이 사용한다는 점이다.
단순히 생각해보자. 보통 컴퓨터에서 실수 값 하나는 4바이트로 표현된다. 1024 x 1024 크기의 가중치 행렬을 기억하고 있으려면 1024 x 1024 x 4 = 4,194,304 바이트 즉, 메모리 공간이 4MB나 필요하다. 고작 4MB밖에 안 되냐고 생각할 수도 있다. 파라미터가 백만 개 언저리밖에 되지 않으니 크지는 않아 보인다. 그러나 현대의 인공 신경망은 수 억개에서 많게는 수 십억개의 파라미터를 가지고 있다. 신경망 파라미터들을 저장하면 기가바이트 단위의 크기가 되는 것은 일상적이다. 이런 거대한 인공 신경망을 컴퓨터의 메모리(RAM)에 올려 놓는 것은 컴퓨터 시스템의 운용 관점에서도 매우 좋지 않을 것이다.

그러나 GPU를 사용하면 이런 문제를 어느 정도 해결할 수 있다. 현대의 GPU들은 대부분 그 내부에 VRAM 이라는 메모리를 가지고 있다. GPU가 연산할 데이터들을 미리 올려놓는 목적으로 존재하는 저장 공간이다. GPU를 활용하는 인공 신경망은 본격적으로 사용되기 전에 모든 파라미터를 미리 GPU의 VRAM에 올려 놓고 시작한다. CPU는 GPU에게 연산 명령을 내릴 때, VRAM 안의 어떤 데이터를 사용해서 연산하라고 같이 알려준다. GPU는 굳이 컴퓨터의 RAM을 참조하지 않아도, 연산할 데이터를 빠르게 로딩할 수 있다. 그리고 GPU의 연산 결과가 VRAM에 다시 저장되면 CPU가 그것을 다시 읽어온다. 이렇게 해서 컴퓨터의 CPU와 RAM에 큰 영향을 끼치지 않고, GPU를 최대한으로 활용해서 인공 신경망을 구현할 수 있다.

NVIDIA GPU와 CUDA를 기반으로 인공 신경망을 구현하고 학습시키는 다양한 딥 러닝 프레임워크 들이 등장했다. 딥 러닝 프레임워크들은 CUDA 기반의 빠른 GPU 연산으로 신경망을 쉽고 효율적으로 구현할 수 있게 해준다. GPGPU의 발전은 여기서 멈추지 않아서, 하나의 딥 러닝 모델을 여러 GPU에 나누어서 적재하고 사용할 수도 있게 되었다. 수많은 GPU를 동시에 사용할 수 있게 되니, 학습되는 딥 러닝 모델의 크기도 예전엔 생각도 하지 못했을 수준으로 커졌다. 수 십 기가바이트에 육박하는 모델도 있다. 이처럼 GPGPU를 비롯한 컴퓨팅 기술이 발전할수록 딥 러닝도 같이 발전할 수 밖에 없을 것이다.