nVidia GPU 아키텍처 변천사 (상편)
얼마전 2017년 GTC(GPU Technology Conference)에서 새로운 GPU 아키텍처인 Volta가 공개된 김에, 처음 unified shader가 탑재됐던 Tesla 시절부터 지금의 Volta까지 nVidia의 GPU 아키텍처가 어떻게 변해 왔는지 정리해 보려 합니다. 아키텍처 이름은 유명한 수학자나 과학자의 이름을 따와 알파벳 순으로 이름을 붙이고 있으며(Tesla는 예외) 이름과 발표된 순서는 아래와 같습니다.
Tesla(2006) – Fermi(2010) – Kepler(2012) – Maxwell(2014/2015) – Pascal(2016) – Volta(2017)
각 세대마다 공정과 메모리 기술의 발달로 인해 성능은 자연스럽게 증가해왔는데 이에 대해서는 일본어 사이트이긴 하지만 이미 잘 정리된 글이 있어 (표의 숫자만 보셔도 내용 파악에는 문제가 없습니다) 자세히 다루지는 않겠습니다. 대신 이번 글에서는 성능을 나타내는 숫자에 집중하기 보다는 nVidia GPU의 프로그램 실행 단위인 Streaming Multiprocessor의 구조가 어떻게 변해왔는지 살펴보고 변화의 단계마다 어떤 고민이 있었는지를 살펴보겠습니다.
Tesla 아키텍처
nVidia GPU 아키텍처 중 Tesla에서 최초로 vertex shader와 pixel shader의 기능이 하나로 합쳐진 unified shader가 등장했습니다. Unified shader에서 vertex/pixel 프로그램을 번갈아가며 실행할 수 있게 된 덕분에 이전의 G70 아키텍처와는 달리 기능이 구분된 vertex shader와 pixel shader의 탑재 비율에 따른 workload 밸런싱 문제에서 자유로워졌습니다. 또한 unified shader에서 지원하는 프로그램의 유연성이 크게 높아졌기 때문에 GPGPU(General Purpose GPU)라는 개념이 등장했고 GPU상에서 그래픽 연산외에 일반연산을 가속할 수 있는 CUDA API의 지원도 시작되었습니다.
Tesla 아키텍처에서 vertex/pixel shader 또는 GPGPU 프로그램을 수행할 수 있는 기본 단위는 Streaming Multiprocessor(SM)인데 각 SM마다 독립적인 명령어 스케쥴러를 갖추어 최대 768개의 thread를 동시에 실행할 수 있습니다. SM은 아래 그림과 같이 8개의 Stream Processor(SP, 또는 CUDA 코어), 2개의 SFU(Special Function Unit), shared memory 등으로 이루어지는데 SM 하나를 multi-thread 실행이 가능한 unified shader로 보시면 됩니다.
SM의 구성요소를 살펴보면, 먼저 SP는 기본적인 논리/수학 연산을 수행하는데 MAD(Multiply-Add) 명령어도 지원하여 한 클럭 당 최대 2개의 연산 수행이 가능합니다. SFU는 초월 함수, 픽셀 attribute 보간등의 연산에 사용되며 4개의 부동 소숫점 곱셈기도 포함하고 있어 8개의 SP와 2개의 SFU가 모두 사용될 경우 SM에서는 1 클럭 싸이클당 최대 16(=8+4*2)회의 부동소수점 곱셈을 수행할 수 있습니다. SM에서 여러개의 thread가 동시 실행 될 때 8개의 SP와 2개의 SFU로 동일한 명령어(instruction)가 broadcasting 되는데 이 때 각 유닛(SP 또는 SFU)은 동일한 명령을 수행하지만 레지스터와 메모리 주소는 각각 다르게 관리됩니다. 또한 분기문의 조건에 따라 일부 유닛은 broadcasting 되는 명렁어의 실행을 건너 뛸 수도 있는데 nVidia에서는 이러한 동작 모델에 SIMT(Single-instruction multiple-thread)라는 이름을 붙였습니다. Shared memory는 16KB의 용량을 가지는데 SM 내에서 실행되는 thread 사이의 data 교환을 가능하게 합니다.
마지막으로 위의 그림에서 2개의 SM이 하나의 TPC(Texture/Processor Cluster)로 묶인 이유는 그래픽 작업시 수행시의 texture unit에서 이루어지는 연산과 SM에서 이루어지는 연산의 비율을 고려했기 때문이지 두 개의 SM이 동일한 프로그램을 실행한다는 뜻은 아닙니다. Texture unit은 SM에서 외부메모리로의 load/store 명령어 수행시에 사용됩니다.
Fermi 아키텍처
Fermi 아키텍처는 Tesla에 비해 GPGPU 컴퓨팅을 용이하게 하기 위한 변화가 추가되었습니다. Tesla에서 각 SM마다 제공되던 16KB shared memory는 64KB로 용량이 늘었고 필요에따라 그 중 16KB 또는 48KB를 L1 cache로 설정하여 사용할 수 있게 되었습니다. Shared memory대신 cache를 사용하면 프로그램을 짤 때 shared memory의 용량 제한을 생각하지 않아도 되는 장점이 있습니다. SM 외부의 texture unit의 도움을 받아 실행되던 load/store 명령도 SM내에 load/store(LD&ST) 유닛이 추가됨으로서 SM 자체적으로 실행이 가능해 졌는데, 그래픽 기능을 중심으로 설계되었던 모듈들이 general purpose에 맞게 변경되어가는 과정으로 생각됩니다.
위의 그림은 Fermi 아키텍처의 SM 구조를 보여주고 있는데, SP는 8개에서 32개로 개수가 4배로 늘어나고 이름도 CUDA 코어로 변경 되었습니다. 32개의 CUDA 코어는 16개씩 2개의 그룹으로 나누어져 있으며, 16개의 LD&ST 유닛이 1그룹, 4개의 SFU가 1그룹을 이루어 하나의 SM은 총 4개의 그룹으로 구성된 실행 유닛을 가집니다. 실행 유닛의 윗 부분에는 Tesla 아키텍처에서는 보이지 않던 Register File이 보이는데, Tesla에서 각 SP 내부에 분산되어 존재하던 register file이 하나로 통합되었기 때문입니다. 이렇게 할 경우 레지스터를 많이 쓰는 thread와 적게 쓰는 thread가 섞여 있을 때 레지스터를 보다 효율적으로 사용할 수 있는 장점이 있습니다.
늘어난 실행 유닛에 끊김없이 명령어를 공급하기 위해 Warp Scheduler의 개수는 2개로 늘었는데 각 Warp Scheduler는 서로 dependency가 없어 독립적으로 Dispatch Unit을 통해 명령어를 보낼 수 있습니다. Dispatch Unit이 2개이기 때문에 일반적으로는 SM 내에서 2개의 명령이 동시 실행될 수 있지만 두 Warp Scheduler에서 사용하려는 실행 유닛 그룹이 겹치거나 64-bit 부동소수점 연산을 사용할 때는 1개의 명령어만 실행할 수 있습니다. Warp는 명령어(instruction) 실행 순서가 동기(synchronize)된 32개의 thread로 이루어지는데 CUDA 코어가 32개 단위가 아니라 16개 단위로 그룹 지어진 이유는 CUDA 코어, LD&ST 유닛, SFU가 Warp Scheduler의 두 배 클럭으로 동작하기 때문입니다. SM의 실행 유닛들이 두 배 빠른 클럭으로 동작하는 것은 Tesla 아키텍처에서도 마찬가지였는데 차기 Kepler 아키텍처에서 부터는 소비전력 문제 때문에 Warp Scheduler와 동일한 클럭을 사용하는 것으로 변경됩니다.
Fermi 아키텍처에서는 부동소수점 연산 성능과 정확도의 향상이 있었는데, 성능면에서는 CUDA 코어에 64-bit 정밀도(precision) 부동소수점 연산의 지원이 추가됐고 정확도 면에서는 Fused Multiply-Add가 추가됐기 때문입니다. Tesla의 SP는 32-bit 부동소수점 연산만을 지원해서 SM 마다 1개의 64-bit 부동소수점 연산 유닛을 별도로 두었지만(관련 링크) Fermi에서는 32-bit 부동소수점을 지원하는 CUDA 코어 2개를 동시에 사용하면 64-bit 부동소수점 연산을 할 수 있습니다. 이 경우 하나의 64-bit 연산이 두개의 CUDA 코어 그룹을 점유하고 LD&ST, SFU에서는 명령어가 실행될 수 없는 제약은 존재하지만, Tesla에서 32-bit에 비해 1/8로 줄어들던 64-bit 부동소수점 연산 성능이 Fermi 에서는 1/2로 줄어들어 성능저하가 훨씬 덜합니다.
위의 그림은 Multiply-add(MAD)와 Fused Multiply-add(FMA)의 차이를 보여주는데, MAD의 경우 중간 결과의 하위 bit 일부를 버린다음(truncate) 다시 최종 결과를 계산하는 반면 FMA에서는 중간 결과의 모든 하위 bit을 보존한 상태에서 최종 결과를 계산하는 차이점이 있습니다. 기존의 MAD는 계산 결과가 살짝 부정확해서 그래픽 분야에서는 문제가 없지만 General Purpose 컴퓨팅에는 문제가 될 경우가 있는데, Fermi 아키텍처에서 FMA를 지원하면서 GPGPU 컴퓨팅에 대한 지원이 강화되었습니다.
Kepler 아키텍처
Kepler 아키텍처는 Fermi 아키텍처에 비해 성능과 효율성(Performance/Watt) 모두를 향상시키는 것을 목표로 개발되었습니다. Fermi 아키텍처 까지는 CUDA 코어, LD&ST 유닛, SFU 등의 실행 유닛들이 다른 부분에 비해 두 배 빠른 속도로 동작했지만 Kepler 아키텍처 부터는 GPU 전체가 동일한 클럭으로 동작하도록 변경되었습니다. 변경의 이유는 두 가지가 있는데 GPU의 클럭 속도가 빨라지면서 GPU의 일부를 두 배 빠르게 동작시키는 것이 점점 어려운 일이 되었고, 회로를 두 배 빠르게 동작시키는 것이 전력소모면에서 손해가 크기 때문입니다. 아래 슬라이드는 하나의 data path를 2x 클럭으로 동작시키는 것과 두개의 data path를 1x 클럭으로 동작시킬 때의 전력소모를 비교하고 있는데, 한 싸이클당 계산량이 같다면 두 개의 data path를 1x 클럭으로 동작시키는 것이 유리함을 보여주고 있습니다. (관련 링크, AnandTech)
아래 그림은 Kepler 아키텍처의 Streaming Multiprocessor의 구조를 보여주는데, Kepler부터 SM이 SMX로 이름이 바뀌었습니다. 2배 빠른 클럭을 사용하지 않게 되었기 때문에 성능을 유지하려면 실행 유닛의 개수가 적어도 2배가 되어야 하는데 이에 더해 성능향상까지 고려하여 CUDA 코어와, LD&ST 유닛, SFU의 수가 크게 늘었습니다. Kepler의 SMX는 무려 192개의 CUDA 코어, 64개의 64-bit Double Precision(DP) 유닛, 32개의 LD&ST 유닛, 32개의 SFU로 이루어져 있습니다. Kepler에서는 GPU를 이용한 High Performance Computing(HPC)을 고려하여 64-bit 부동소수점 연산을 위한 전용 DP 유닛이 추가되었는데 Fermi 아키텍처와는 달리 32-bit과 64-bit 부동소수점 연산이 동시에 실행될 수 있어 성능이 크게 향상되었습니다.
늘어난 실행 유닛의 수와 균형을 맞추기 위해 Warp Scheduler의 갯수도 2개에서 4개로 늘었고 하나의 Warp Scheduler에 대응되는 Dispatch Unit도 1개에서 2개로 늘어 SMX는 동시에 최대 8개의 명령을 처리하는 것이 가능하게 되었습니다. 뿐만 아니라 Register File의 크기도 최대 128KB로 4배가 늘어났으며 L1 cache의 크기도 128KB로 Fermi SM의 두배가 되었습니다. 하나의 thread가 사용할 수 있는 최대 register의 수가 Fermi의 63개에서 255개로 늘어난 부분도 주목할만한 변화인데 DP 유닛의 추가와 더불어 그래픽 연산보다는 HPC 응용분야의 성능 향상을 고려한 변화입니다.
효율성 향상을 위해 명령어 스케쥴링 방식에도 변화가 적지않은 변화가 생겼습니다. Fermi 아키텍처의 경우 윗그림 상단과 같이 CUDA 코어, LD&ST유닛, SFU 내의 파이프라인 상에서 진행중인 명령어(instruction)들의 계산이 완료되었는지를 체크하는 스코어보드 회로와 이를 바탕으로 현재 진행중인 명령어들과 다음에 실행될 명령어 사이의 dependency를 동적으로 체크하는 회로가 있었습니다. 만약 명령어 사이의 dependency 때문에 data hazard가 발생할 위험이 있다면 실행할 명령어의 순서를 바꾸게 됩니다.
Kepler 아키텍처에서는 한번에 처리하는 명령어가 4개에서 8개로 늘고 실행 유닛 그룹의 갯수도 크게 늘어나면서 스케쥴링 회로가 크게 복잡해질 위험이 있었습니다. 이를 해결하기 위해 Kepler 아키텍처에서는 컴파일러에서 미리 명령어 실행 완료에 필요한 cycle수 정보를 명령어의 일부로 포함하고 이 정보를 참고하여 다음에 실행할 명령어를 선택함으로써 스코어보드와 dependency 체크 회로가 없이도 간단히 스케쥴링을 구현할 수 있었다고 합니다.
중간 정리
지금까지 Tesla, Fermi, Kepler 아키텍처의 Streaming Multiprocessor의 구조가 어떻게 변해 왔는지 살펴봤습니다. GPU의 구조가 워낙 복잡해서이기도 하지만 각 아키텍처를 거치면서 GPU 전체 수준에서 추가되어온 다양한 기능이나 메모리 시스템의 변화는 전혀 다루지 않고 SM/SMX의 구조에만 집중했음에도 생각보다 글이 길어졌습니다. Maxwell 아키텍처 부터는 다음 글에서 다룰 계획인데 양해 부탁드립니다.
다음 글 : nVidia GPU 아키텍처 변천사 (하편)
이렇게 알차고, 명료하게 정리된 글은 처음입니다.
잘 읽고 갑니다 🙂
지포스 8800GTX랑 8800GTS 640MB가 2006년 11월에 나왔으니
테슬라 아키텍처는 2006년이라고 표기하는게 맞지 않을까요?
물론 GPGPU에 특화된 테슬라 제품군 자체가 2007년에 처음 나온건 맞지만
지포스와는 달리 발표하고 판매하기까지의 텀이 길어서 그랬던 걸로 알고 있습니다
댓글 알림 이메일 받기를 체크하지 않아서 한 번 더 댓글로 남겼습니다
지적 감사합니다. 2007년이 아니라 2006년이 맞습니다. 본문의 내용도 고쳤습니다. ^^
감사합니다 엄청 잘 읽고 갑니다
너무 좋은 자료 입니다. 감사합니다.