들어가며
딥러닝 모델, 특히 트랜스포머(Transformer) 기반 모델을 학습시키거나 추론할 때 GPU가 CPU보다 훨씬 빠르다는 사실은 널리 알려져 있습니다. 하지만 "왜" 빠른지, 그리고 "어떻게" 빠른지를 구체적으로 설명하려고 하면 갑자기 막막해집니다. CUDA 코어, 텐서 코어, FP16, 혼합 정밀도, FLOPS, TFLOPS 같은 용어들이 줄줄이 등장하고, 각 용어의 의미를 찾다 보면 또 다른 낯선 용어를 만나게 됩니다.
이 글은 그 용어들을 한 자리에서 하나씩 풀어가며, 최종적으로는 "트랜스포머의 어텐션 연산이 GPU에서 왜 그렇게 빠르게 돌아가는가"라는 질문에 답하는 것을 목표로 합니다. CPU와 GPU의 내부 구조 차이부터 시작해서, 숫자를 표현하는 방식, 연산 유닛의 종류, 그리고 이것들이 트랜스포머의 행렬곱과 어떻게 맞물려 작동하는지까지 이어서 설명하겠습니다.
1. CPU와 GPU의 구조 차이
CPU의 내부 구조
CPU는 Central Processing Unit의 약자로, 컴퓨터의 중앙 처리 장치입니다. CPU 칩 안에는 여러 구성 요소가 들어 있는데, 대표적으로 다음과 같습니다.
첫째, 코어(core) 가 있습니다. 코어는 실제로 명령어를 실행하는 단위입니다. 예전에는 한 칩에 코어가 하나였지만, 지금은 4코어, 8코어, 16코어처럼 한 칩에 여러 개의 코어가 들어 있습니다. 각 코어는 독립적으로 프로그램의 명령어를 읽고 실행할 수 있기 때문에 코어가 많을수록 동시에 처리할 수 있는 작업이 많아집니다.
둘째, 코어 안에는 ALU(Arithmetic and Logical Unit, 산술 논리 장치) 가 있습니다. ALU는 덧셈, 뺄셈, 곱셈, 나눗셈 같은 사칙 연산과 AND, OR 같은 논리 연산을 실제로 수행하는 회로입니다.
셋째, 캐시 메모리(cache memory) 가 있습니다. CPU가 매번 RAM(주기억장치)까지 가서 데이터를 가져오면 속도가 느려지기 때문에, 자주 쓰는 데이터는 CPU 바로 옆의 작고 빠른 메모리에 보관해 둡니다. 이것이 캐시 메모리입니다. L1, L2, L3로 계층이 나뉘어 있고, L1이 가장 작고 가장 빠르며, L3가 상대적으로 크고 느립니다.
넷째, 제어 장치(control unit) 와 분기 예측기(branch predictor), 디스패처(dispatcher) 같은 복잡한 제어용 회로들이 있습니다. 이들의 역할은 다음에 어떤 명령어를 실행할지, 조건문에서 어느 쪽으로 분기할지, 각 명령어를 어느 연산 유닛에 보낼지를 결정하는 것입니다. 현대 CPU는 한 번에 여러 명령어를 파이프라인 방식으로 처리하면서 분기를 미리 예측해서 실행하기 때문에 이런 제어 회로가 전체 칩 면적의 상당 부분을 차지합니다.
CPU 코어 블록 다이어그램 읽는 법
CPU 코어 내부의 블록 다이어그램을 보면 보통 4~5개의 큰 영역으로 색이 나뉘어 있습니다. 각 영역이 하는 역할을 연결해서 보면 앞에서 설명한 구성 요소들이 어디에 해당하는지 한눈에 보입니다.

- Front End (프론트엔드 영역): 다이어그램 위쪽에 크게 자리잡은 영역입니다. 이 안에는
Branch Predictor(분기 예측기, 때로는 L1 BTB, L2 BTB 같은 세부 표가 함께 표시됨),L1 Instruction Cache(명령어 캐시),Instruction Queue,Simple Decoder× 여러 개,μcode,Op Queue,Rename / Dispatch같은 박스가 있습니다. 이 모든 것이 "다음에 어떤 명령어를 어떤 순서로 어디로 보낼지"를 결정하는 제어용 회로입니다. 즉, 넷째 항목에서 말한 복잡한 제어 장치가 다이어그램의 이 넓은 영역 전체에 해당합니다. - Execution Engine (실행 엔진 영역): 프론트엔드 아래, 중앙에 위치한 큰 회색 영역입니다.
ReOrder Buffer, 여러 개의Scheduler(ALU Scheduler, Branch Scheduler, Load Scheduler 등), 그리고 그 아래에 실제 연산을 수행하는ALU,ALU Shift,ALU MUL DIV,Branch박스들이 있습니다. 여기서 실제로 숫자를 더하고 곱하는 ALU는 전체 다이어그램에서 보면 의외로 작은 부분만 차지한다는 것을 볼 수 있습니다. ALU 몇 개를 움직이기 위해 그 위의 스케줄러와 리오더 버퍼가 훨씬 더 큰 공간을 쓰고 있습니다. - FP / Vector Execution (부동소수점/벡터 실행 영역): 왼쪽 아래의 파란색 영역입니다.
Vector RF(벡터 레지스터 파일), 그리고ALU,ALU MUL ADD AES SHA DIV같은 부동소수점/SIMD 연산 유닛이 들어 있습니다. CPU에서 소수점 연산과 벡터 연산은 정수 ALU와는 별개의 유닛에서 처리됩니다. - Load / Store (데이터 입출력 영역): 오른쪽 아래의 주황색 영역입니다.
Store Queue,Load Queue,L1 Data Cache(보통 32KiB 정도),DTLB같은 박스가 있습니다. 연산 유닛이 필요로 하는 데이터를 메모리에서 가져오고, 결과를 다시 메모리로 내보내는 통로입니다. - Memory (메모리 영역): 가장 오른쪽에 위치한 분홍색 영역입니다.
Shared L2 Cache(보통 2~4 MiB),L2 TLB가 있고, 그 위로L3 캐시까지 이어지는 화살표가 붙어 있습니다. 셋째 항목에서 말한 캐시 계층이 바로 이 부분입니다.
이 다이어그램에서 가장 중요하게 볼 점은 "실제 계산을 수행하는 ALU가 전체 면적에서 차지하는 비율" 입니다. 숫자를 직접 더하고 곱하는 박스는 FP/Vector 영역의 ALU 몇 개와 Execution Engine 영역의 ALU 몇 개뿐이고, 나머지 대부분의 공간은 "그 ALU들을 효율적으로 쓰기 위한 제어 회로"가 차지하고 있습니다.
GPU의 내부 구조
GPU는 Graphics Processing Unit의 약자로, 원래는 그래픽을 렌더링하기 위해 만들어진 칩입니다. 화면에 나타나는 수백만 개의 픽셀에 대해 거의 같은 연산(색 계산, 변환 행렬 곱 등)을 동시에 수행해야 하기 때문에, 처음부터 "같은 연산을 엄청나게 많은 데이터에 동시에 적용"하는 용도로 설계되었습니다.
GPU의 가장 중요한 구조적 단위는 SM(Streaming Multiprocessor) 입니다. SM은 NVIDIA GPU에서 쓰는 용어이고, CPU의 "코어 하나"에 대응되는 모듈이라고 생각하면 됩니다. 즉, GPU 한 장에는 SM이 여러 개 있고, 각 SM은 독립적으로 동작할 수 있는 하나의 처리 단위입니다.
SM 내부에는 CPU의 코어와는 다른 비율로 회로가 배치되어 있습니다.
첫째, ALU가 매우 많습니다. CPU 한 코어에 ALU가 몇 개 들어 있는 정도라면, GPU의 SM 하나에는 수십 개에서 백여 개 단위의 ALU(NVIDIA 용어로는 CUDA 코어)가 들어 있습니다. 그래서 SM 하나만 봐도 같은 연산을 동시에 수십~백여 개 수행할 수 있습니다.
둘째, 제어 회로(디스패처, 분기 예측기 등)는 CPU보다 적습니다. GPU는 연산이라는 용도에 특화되어 있어서, 복잡한 분기 예측이나 정교한 명령 스케줄링이 덜 필요합니다. 그래서 제어 회로에 쓸 칩 면적을 줄이고 그만큼을 ALU에 더 할당하여서 연산 능력을 강화하였습니다.
셋째, SM 내부의 ALU들은 보통 4개의 묶음(처리 블록) 으로 그룹화되어 있습니다. 각 묶음은 한 번에 하나의 명령어 스트림을 받아서 그 안의 ALU들이 서로 다른 데이터에 대해 동시에 같은 연산을 수행하는 방식으로 동작합니다.
결과적으로 GPU가 잘하는 일은 같은 계산을 서로 다른 수많은 데이터에 동시에 적용하는 일입니다.
GPU SM 다이어그램 읽는 법

GPU SM 다이어그램을 처음 보면 CPU 다이어그램과 구조가 상당히 다릅니다. 위쪽에 얇게 L1 Instruction Cache가 하나 있고, 그 아래로 거의 똑같이 생긴 네 개의 블록이 2 × 2 격자 형태로 배치된 구조가 먼저 눈에 들어옵니다. 이 네 블록이 위에서 말한 4개의 처리 블록입니다. SM 한 개 안에서 네 개의 처리 블록이 독립적인 명령어 스트림을 받아 서로 다른 일을 동시에 진행합니다.
각 처리 블록의 안을 위에서 아래로 읽으면 다음과 같은 순서로 구성되어 있습니다.
L0 Instruction Cache: 그 블록에서 실행할 명령어를 잠깐 보관해 두는 가장 작은 캐시입니다.Warp Scheduler (32 thread/clk): 워프(warp)는 32개의 스레드를 하나로 묶은 GPU의 기본 실행 단위입니다. 이 스케줄러가 "이번 클럭에는 어느 워프를 돌릴지"를 결정합니다. 즉, 32개의 스레드가 동시에 같은 명령어를 서로 다른 데이터에 대해 수행하도록 묶어서 보냅니다.Dispatch Unit (32 thread/clk): 스케줄러가 선택한 명령어를 실제 연산 유닛들에게 분배하는 회로입니다.Register File (16,384 × 32-bit): 블록 안의 모든 연산 유닛이 공유하는 고속 저장 공간입니다. ALU가 계산할 값을 꺼내고, 결과를 저장하는 곳입니다.INT32,FP32,FP64컬럼: 여기가 바로 ALU, 즉 CUDA 코어가 놓인 곳입니다. 초록색 점들로 빼곡히 채워진 세 개의 열이 눈에 들어오는데, 각각 "정수 연산용", "단정밀도 부동소수점용", "배정밀도 부동소수점용" CUDA 코어를 뜻합니다. 한 블록 안에 이런 유닛이 수십 개씩 줄지어 있고, 이것이 네 블록에 걸쳐 있으니 SM 하나 안의 CUDA 코어 총합이 백 단위로 금방 커집니다.TENSOR CORE 4th GENERATION: 각 블록에서 INT32/FP32/FP64 컬럼 오른쪽에 크게 자리잡은 초록 박스입니다. 이것이 행렬곱 전용 연산 유닛인 텐서 코어이고, 세대별로 크기와 성능이 다릅니다. 다이어그램을 보면 CUDA 코어들과 거의 맞먹는 면적을 차지하고 있다는 점이 중요합니다. 최신 GPU로 올수록 텐서 코어가 칩 면적에서 차지하는 비중이 점점 커지고 있습니다.LD/ST(Load/Store) 유닛과SFU: 블록의 맨 아래쪽에 줄지어 있는 빨간색 박스들입니다. LD/ST 유닛은 메모리에서 데이터를 읽어오거나 쓰고, SFU(Special Function Unit)는 사인, 코사인, 제곱근, 역수 같은 특수 함수 연산을 담당합니다.
그리고 네 블록 아래 공통으로 자리잡은 영역이 또 있습니다.
Tensor Memory Accelerator(최신 세대에서 등장): 텐서 코어가 사용할 큰 행렬 데이터를 메모리에서 고속으로 옮겨주는 전용 회로입니다. 텐서 코어가 아무리 빨라도 데이터를 제때 공급하지 못하면 성능이 나오지 않기 때문에, 메모리 전송 자체를 담당하는 회로를 별도로 둔 것입니다.256 KB L1 Data Cache / Shared Memory: SM 안의 네 블록이 함께 쓰는 데이터 캐시이자 공유 메모리입니다. 같은 SM 안의 스레드들은 이 공유 메모리를 통해 빠르게 데이터를 주고받을 수 있습니다.
이 다이어그램에서 가장 중요하게 볼 점은 CPU 다이어그램과의 비례 차이입니다. CPU 다이어그램에서는 실제 연산 유닛(ALU)이 전체에서 차지하는 비중이 매우 작았던 반면 GPU SM 다이어그램에서는 면적의 대부분을 연산 유닛(INT32/FP32/FP64 컬럼 + 텐서 코어)이 차지합니다. 제어 회로에 해당하는 워프 스케줄러와 디스패치 유닛은 각 블록의 맨 위에 얇은 띠처럼 있을 뿐입니다.
또 한 가지 짚어야 할 점은, 이 전체 다이어그램이 SM 하나에 불과하다는 사실입니다. 실제 GPU에는 이런 SM이 수십에서 백 개 이상 들어 있습니다. 예를 들어 H100에는 SM이 132개 있고, RTX 3060에는 SM이 28개 있습니다. 다이어그램 한 장 분량의 구조가 수십 번 복제되어 있는 셈이라, GPU 전체로 보면 연산 유닛의 수가 수천~수만 개 단위가 됩니다.
왜 GPU가 딥러닝에 유리한가
딥러닝 모델의 핵심 연산은 대부분 행렬과 벡터의 곱셈, 그리고 그에 수반되는 덧셈입니다.
여기서 행렬(matrix)은 숫자를 직사각형으로 배열한 표이고, 벡터(vector)는 한 줄짜리 행렬이라고 생각하면 됩니다. 예를 들어 [1, 2, 3]은 길이 3짜리 벡터이고, 아래 같은 것은 2행 3열의 행렬입니다.
[1, 2, 3]
[4, 5, 6]
행렬곱 A × B는 A의 각 행과 B의 각 열을 짝지어서 대응되는 원소끼리 곱한 뒤 그 결과를 모두 더하는 방식으로 계산됩니다. 결과 행렬의 원소 하나를 구하는 데만 해도 여러 번의 곱셈과 덧셈이 필요하고 결과 행렬의 모든 원소를 구하려면 그 연산이 어마어마하게 반복됩니다.
중요한 점은 각 원소를 구하는 계산이 서로 독립적이라는 것입니다. 결과 행렬의 (1,1) 원소를 구하는 계산과 (2,3) 원소를 구하는 계산은 서로 영향을 주지 않습니다. 그래서 GPU처럼 같은 연산을 수많은 데이터에 동시에 적용하는 구조가 행렬곱에 아주 잘 맞습니다. 수천 개의 ALU가 동시에 결과 행렬의 서로 다른 원소들을 계산하고 마지막에 합치면 행렬곱이 끝납니다.
반면 CPU는 코어 수가 적어서 동시에 처리할 수 있는 곱셈-덧셈의 개수가 훨씬 적고 큰 행렬 하나를 계산하는 데도 상대적으로 긴 시간이 걸립니다.
CPU가 "이 큰 행렬곱을 해줘"라고 GPU에 작업을 던지면, GPU가 그 대규모 계산을 맡아서 빠르게 처리한 뒤 결과를 돌려줍니다. 딥러닝 프레임워크(PyTorch, TensorFlow 등)가 내부적으로 이런 역할 분담을 처리해 줍니다.
2. FLOPS: 연산 능력을 재는 단위
CPU와 GPU의 성능을 비교할 때 가장 자주 등장하는 단위가 FLOPS입니다. FLOPS는 FLoating-point Operations Per Second의 약자로, 초당 수행할 수 있는 부동소수점 연산의 횟수를 뜻합니다.
여기서 "부동소수점 연산"이란 소수점이 있는 숫자(예: 3.14, 0.0001, -2.5 등)를 더하거나 곱하는 연산을 말합니다. 딥러닝의 모든 계산은 부동소수점 연산으로 이루어지기 때문에, FLOPS는 곧 "이 하드웨어가 1초에 얼마나 많은 딥러닝 계산을 할 수 있는가"를 나타내는 지표가 됩니다.
숫자가 너무 크기 때문에 보통은 접두어를 붙여서 표기합니다.
- GFLOPS (Giga FLOPS): 10⁹, 즉 초당 10억 번의 부동소수점 연산
- TFLOPS (Tera FLOPS): 10¹², 즉 초당 1조 번의 부동소수점 연산
- PFLOPS (Peta FLOPS): 10¹⁵, 즉 초당 1000조 번의 부동소수점 연산
참고로 정수 연산만 세는 경우에는 TOPS(Tera Operations Per Second) 라는 단위를 씁니다. 정수는 부동소수점보다 단순해서 연산 속도가 빠른데 이 구분은 뒤에서 INT8 설명할 때 다시 나옵니다.
구체적인 수치로 비교해 보면 차이가 분명합니다. 한 세대 최상급 CPU인 Intel Core i9-12900KF의 이론적 최대 성능은 약 800 GFLOPS 수준, 즉 0.8 TFLOPS 정도입니다. 반면 데이터센터용 GPU인 NVIDIA H100은 FP16 기준 약 1,000 TFLOPS에 달합니다. 약 1,000배 이상 차이가 나는 셈입니다.
3. 숫자를 표현하는 방법: FP32, FP16, INT8
비트와 정밀도의 관계
컴퓨터는 모든 숫자를 0과 1의 조합, 즉 이진수로 표현합니다. 이때 하나의 숫자를 저장하는 데 몇 비트를 사용하느냐에 따라 표현할 수 있는 값의 범위와 정밀도(precision) 가 달라집니다.
비트(bit)는 0 또는 1 중 하나를 저장하는 최소 단위입니다. 1비트로는 2가지(0 또는 1), 2비트로는 4가지(00, 01, 10, 11), n비트로는 2ⁿ가지의 서로 다른 값을 표현할 수 있습니다. 비트 수가 많을수록 한 숫자를 더 섬세하게 표현할 수 있지만, 그만큼 메모리를 더 많이 차지하고 연산 회로도 더 복잡해집니다.
부동소수점 형식의 구조
부동소수점(floating-point) 형식은 과학적 표기법과 비슷한 방식으로 소수를 표현합니다. 예를 들어 3.14를 3.14 × 10⁰처럼 "유효숫자 × 기수^지수" 형태로 표현하는 방식을 이진수로 확장한 것입니다. 부동소수점 숫자는 크게 세 부분으로 나뉩니다.
- 부호 비트: 양수(0)인지 음수(1)인지를 나타내는 1비트
- 지수 비트: 숫자가 얼마나 큰지/작은지를 나타내는 부분. 비트 수가 많을수록 표현할 수 있는 숫자의 범위가 넓어집니다.
- 가수 비트(유효숫자): 실제 숫자의 정밀한 값을 나타내는 부분. 비트 수가 많을수록 소수점 아래를 더 정확하게 표현할 수 있습니다.
이 세 부분에 각각 몇 비트를 할당하느냐에 따라 다양한 부동소수점 형식이 만들어집니다.
딥러닝에서 자주 마주치는 세 가지 형식은 다음과 같습니다
FP32 (32비트 부동소수점, 단정밀도)
- 부호 1비트 + 지수 8비트 + 가수 23비트
- 전통적으로 과학 계산과 딥러닝에서 표준으로 쓰이는 형식
- 넓은 범위와 높은 정밀도를 동시에 제공
FP16 (16비트 부동소수점, 반정밀도)
- 부호 1비트 + 지수 5비트 + 가수 10비트
- FP32의 딱 절반 크기
- 메모리도 절반, 연산에 쓰는 회로도 단순해서 속도가 빠름
- 대신 표현 범위와 정밀도가 줄어듦
INT8 (8비트 정수)
- 부동소수점이 아니라 정수로 값을 표현
- 가장 작은 8비트만 사용하므로 가장 빠르고 가장 적은 메모리를 차지
- 소수점 이하가 없으므로 그대로 쓸 수 없고, 별도의 변환(양자화, quantization) 과정을 거침
- 주로 학습이 끝난 모델을 추론(inference, 이미 학습된 모델로 예측값을 뽑는 과정) 할 때 사용
숫자로 보는 정밀도 차이
같은 값을 각 형식으로 저장하면 어떤 차이가 나는지 살펴보겠습니다. 원주율 3.14159265358979… 를 저장할 때,
- FP32로 저장: 약
3.14159274— 소수점 아래 6~7자리까지 정확 - FP16으로 저장: 약
3.140625— 소수점 아래 2~3자리까지만 정확, 뒤쪽은 반올림/버림됨 - INT8로 저장:
3— 정수만 저장되므로 소수점 이하가 전부 사라짐
왜 딥러닝은 낮은 정밀도로도 괜찮은가
"소수점 이하가 이렇게 잘려나가는데 학습이 제대로 되나?"라는 의문이 생길 수 있습니다. 그러나 딥러닝 모델은 수치의 미세한 오차에 생각보다 강합니다.
이유는 두 가지입니다. 첫째, 딥러닝의 가중치(weight, 신경망이 학습하는 매개변수) 값 자체가 아주 작은 실수들이고, 그 값들은 학습 과정에서 매 스텝마다 업데이트됩니다. 예를 들어 어떤 가중치가 0.123456789인지 0.123456인지는 모델의 최종 성능에 거의 영향을 주지 않습니다. 어차피 다음 업데이트에서 그 값이 또 조금 바뀌기 때문입니다.
둘째, 어텐션 가중치처럼 확률적으로 정규화되는 값들도 마찬가지입니다. 어떤 두 단어 사이의 어텐션 가중치가 0.0723이든 0.0720이든, 예측 결과는 거의 똑같이 나옵니다.
그래서 현대 딥러닝에서는 "정확도를 유지할 수 있는 만큼 최대한 낮은 정밀도로 계산하자"는 전략이 표준이 되었습니다. 이 전략의 대표적인 형태가 뒤에서 다룰 혼합 정밀도(mixed precision) 입니다.
4. CUDA 코어와 텐서 코어: 두 종류의 연산 유닛
GPU의 SM 안에는 두 종류의 연산 유닛이 들어 있습니다. CUDA 코어와 텐서 코어입니다.
CUDA 코어
CUDA(Compute Unified Device Architecture) 는 NVIDIA가 만든 GPU 프로그래밍 플랫폼의 이름이고, CUDA 코어는 그 플랫폼에서 프로그램이 실행되는 기본 연산 유닛을 가리킵니다. 앞에서 설명한 SM 안의 ALU가 바로 CUDA 코어입니다.
CUDA 코어의 특징은 범용성입니다. 덧셈, 곱셈, 비교, 형 변환 같은 다양한 연산을 할 수 있고, 프로그램에서 지시하는 대로 자유롭게 사용됩니다. 다만 한 번에 할 수 있는 일은 "숫자 하나에 대한 연산 하나"입니다. 한 CUDA 코어는 한 클럭 사이클에 한 번의 부동소수점 연산(덧셈 한 번, 또는 곱셈 한 번, 또는 경우에 따라 곱셈-덧셈 한 번)을 수행합니다.
여기서 클럭 사이클(clock cycle) 은 GPU가 한 번 신호를 내보내는 기본 시간 단위입니다. CUDA 코어 하나가 1 GHz 클럭에서 동작한다면, 초당 10억 번의 연산을 처리한다고 보시면 됩니다. 흔하게 아시는 RTX 3060에는 3,584개의 CUDA 코어가 들어 있습니다. 이 3,584개가 동시에 작동하면서 서로 다른 데이터에 대해 병렬로 연산을 수행합니다.
간단한 2×2 행렬곱을 예로 들어 CUDA 코어가 어떻게 작동하는지 보겠습니다.
[1, 2] [5, 6] [1×5+2×7, 1×6+2×8] [19, 22]
[3, 4] × [7, 8] = [3×5+4×7, 3×6+4×8] = [43, 50]
결과 행렬의 원소 하나, 예를 들어 19 = 1×5 + 2×7을 구하려면 곱셈 2번 + 덧셈 1번 = 총 3번의 연산이 필요합니다. 결과 행렬에 원소가 4개이므로, 2×2 행렬곱 전체에는 4 × 3 = 12번의 연산이 필요합니다.
CUDA 코어는 이 12번의 연산을 하나씩 처리합니다. 여러 CUDA 코어가 서로 다른 원소를 맡아 병렬로 진행할 수 있지만, 각 코어가 한 번에 하는 일은 어디까지나 "숫자 하나 × 숫자 하나" 또는 "숫자 하나 + 숫자 하나" 수준입니다. 결과 행렬의 원소 4개를 CUDA 코어 4개에 하나씩 배정해도, 각 코어는 3번의 연산을 순차적으로 수행해야 합니다.
텐서 코어
텐서 코어(Tensor Core) 는 행렬곱에 특화된 전용 연산 유닛입니다. 2017년 NVIDIA의 Volta 아키텍처(V100)에서 처음 도입된 이후, 세대를 거치며 발전해 왔습니다.
텐서 코어의 가장 큰 특징은 작은 행렬 단위를 한 번에 곱하고 더한다는 것입니다. CUDA 코어가 숫자 하나에 대해 연산 하나를 하는 것과 대비됩니다. 예를 들어 RTX 3060의 3세대 텐서 코어는 8×4×8 크기의 행렬 연산을 1 클럭 사이클에 처리합니다(정확한 형식은 뒤에서 다룹니다).
텐서 코어가 수행하는 기본 연산은 다음과 같은 형태입니다.
D = A × B + C
여기서 A, B, C, D는 모두 행렬입니다. 두 행렬 A와 B를 곱하고, 그 결과에 행렬 C를 더해서 D에 저장하는 연산을 한 번에 수행합니다. 이렇게 곱셈과 덧셈을 묶어서 한 번에 처리하는 것을 FMA(Fused Multiply-Add, 융합 곱셈-덧셈) 연산이라고 합니다.
FMA 연산이 중요한 이유는 두 가지입니다. 첫째, 곱셈과 덧셈을 따로 수행할 때보다 하드웨어 회로가 단순해지고 속도가 빨라집니다. 둘째, 중간 결과를 반올림 없이 더 높은 정밀도로 누적할 수 있어서, 같은 연산을 CUDA 코어로 나눠서 할 때보다 수치적으로 더 정확합니다.
RTX 3060에는 112개의 3세대 텐서 코어가 탑재되어 있습니다.
5. RTX 3060의 실제 성능
같은 RTX 3060 GPU라도 어떤 연산 유닛을 쓰고 어떤 정밀도로 계산하느냐에 따라 성능이 크게 달라집니다. 공식 스펙을 기준으로 정리하면 다음과 같습니다.
| 정밀도 | 연산 유닛 | 성능 |
|---|---|---|
| FP32 (32비트) | CUDA 코어 | 약 12.7 TFLOPS |
| FP16 (16비트) | 텐서 코어 (dense) | 약 25.5 TFLOPS |
| FP16 (16비트) | 텐서 코어 (sparsity) | 약 51 TFLOPS |
| INT8 (8비트) | 텐서 코어 (dense) | 약 102 TOPS |
- TFLOPS: 앞서 설명한 Tera FLOPS. 초당 1조 번의 부동소수점 연산.
- TOPS: 초당 1조 번의 정수 연산. INT8처럼 정수 연산인 경우 FLOPS 대신 TOPS로 표기합니다.
- dense(밀집): 행렬에 0이 아닌 값들이 빽빽하게 들어 있는 일반적인 경우.
- sparsity(희소성 가속): 행렬에 0이 많은 경우, 0인 부분의 연산을 건너뛰어 속도를 두 배로 올리는 NVIDIA의 하드웨어 기능. Ampere 세대(RTX 30시리즈)부터 지원됩니다. 정확히는 "2:4 희소성 패턴"을 만족하도록 모델을 조정해야 적용됩니다.
이 표가 의미하는 바는 이렇습니다. 가장 단순한 FP32 + CUDA 코어 조합과 비교했을 때, FP16 + 텐서 코어는 약 2배, 희소성 가속까지 적용하면 약 4배, INT8까지 내려가면 약 8배 빨라집니다. 같은 그래픽 카드인데도 "숫자를 몇 비트로 표현하는가", "어떤 연산 유닛에 작업을 보내는가"라는 두 가지 선택만으로 체감 성능이 수 배씩 차이 납니다.
6. 텐서 코어는 행렬을 어떻게 한 번에 처리하는가
4×4 행렬 FMA가 기본 단위
텐서 코어가 수행하는 연산을 좀 더 구체적으로 들여다보면 다음과 같습니다. 1세대 텐서 코어를 기준으로 할 때, 텐서 코어 하나는 한 클럭에 다음 연산을 수행합니다.
D = A × B + C
여기서 A와 B는 4×4 크기의 FP16 행렬, C와 D는 4×4 크기의 FP32 행렬입니다. 즉, 곱셈은 낮은 정밀도인 FP16으로 빠르게 수행하고, 누적(덧셈)은 높은 정밀도인 FP32로 수행합니다. 이렇게 서로 다른 정밀도를 섞어서 쓰는 방식을 혼합 정밀도(mixed precision) 라고 합니다. 곱셈은 수치 오차에 상대적으로 덜 민감하기 때문에 FP16으로 빠르게 처리하고, 여러 번의 곱셈 결과를 쌓아 올리는 덧셈 단계는 오차가 누적되기 쉬우므로 FP32로 정확하게 유지하는 것입니다.
4×4 행렬곱은 총 몇 번의 연산일까요? 결과 행렬의 원소 하나를 구하려면 곱셈 4번 + 덧셈 3번 = 7번이 필요하고, 일반적으로 "곱셈 1번 + 덧셈 1번"을 한 번의 연산으로 취급합니다. 4×4 행렬곱은 총 64번의 곱셈과 64번의 덧셈, 합쳐서 128번의 부동소수점 연산 에 해당합니다.
CUDA 코어로 같은 연산을 하면 128 클럭이 필요하지만, 텐서 코어 하나는 이걸 1 클럭에 끝냅니다. 단순 계산으로는 128배의 속도 향상입니다.
세대별 텐서 코어의 발전
텐서 코어는 NVIDIA GPU 세대가 올라갈수록 더 큰 행렬을 한 번에 처리할 수 있게 되었고, 지원하는 정밀도도 다양해졌습니다.
| GPU | 세대 | 텐서 코어당 행렬 크기 | 클럭당 FP16 연산 수 | 지원 정밀도 |
|---|---|---|---|---|
| V100 (데이터센터) | 1세대 | 4×4×4 | 128 FLOPS | FP16 |
| RTX 3060 (게이밍) | 3세대 | 8×4×8 | 512 FLOPS | FP16, BF16, TF32, INT8, INT4 |
| H100 (데이터센터) | 4세대 | 8×4×16 | 1024 FLOPS | FP16, BF16, TF32, FP8, INT8 |
표에서 "텐서 코어당 행렬 크기"의 A×B×C 표기는 "A행 × B열" 행렬과 "B행 × C열" 행렬의 곱을 한 번에 처리한다는 뜻입니다. 즉, 입력 행렬의 모양이 커질수록 한 번에 더 많은 연산을 할 수 있고, 그만큼 클럭당 처리하는 연산 수가 늘어납니다.
- BF16 (bfloat16, Brain Floating Point 16): 부호 1비트 + 지수 8비트 + 가수 7비트. FP16과 달리 지수 부분이 FP32와 동일하게 8비트입니다. 덕분에 표현 범위가 FP32만큼 넓어서 학습 과정에서 값이 너무 커지거나 작아져 발생하는 문제에 덜 취약합니다. 대신 가수가 7비트뿐이라 정밀도는 낮습니다.
- TF32 (TensorFloat-32): 이름은 32인데 실제로는 19비트만 사용하는 형식. 부호 1비트 + 지수 8비트(FP32와 동일) + 가수 10비트(FP16과 동일). FP32처럼 보이지만 내부적으로는 더 빠르게 처리할 수 있는 형식입니다.
- FP8: 지수/가수 배분을 다르게 한 두 가지 변형(E4M3, E5M2)이 있는 8비트 부동소수점.
- INT4: 4비트 정수. 주로 초경량 추론에 사용.
1세대와 4세대를 비교하면 텐서 코어 하나의 처리량이 약 8배 늘어났습니다. RTX 3060의 3세대 텐서 코어는 1세대 대비 한 클럭에 4배 많은 연산을 수행하는 셈입니다.
7. 트랜스포머 어텐션과 텐서 코어의 연결
어텐션에서 가장 무거운 연산
트랜스포머에서 가장 연산량이 많은 부분은 어텐션(attention) 메커니즘의 Q · K 행렬곱입니다. (이전 게시글에서 상세하게 다루었습니다
- 트랜스포머는 입력 문장을 토큰으로 만듭니다.
- 각 토큰을 벡터로 바꾼 뒤, 이 벡터들에서 쿼리, 키, 값 이라는 세 종류의 벡터를 만듭니다. Q, K, V는 각각 선형 변환(하나의 행렬곱)으로 생성됩니다.
- 어텐션의 핵심 계산은 "각 토큰의 Q가 다른 모든 토큰의 K와 얼마나 닮았는지"를 내적(두 벡터의 유사도 계산)으로 구하고, 이 결과를 바탕으로 V의 가중합을 만드는 것입니다.
이 때 Q와 K는 각각 "토큰 개수 × 차원(토큰 하나를 몇 개의 숫자로 표현할 수 있는가)" 크기의 행렬로 표현할 수 있기 때문에,"행렬 x 행렬"로, 하나의 큰 행렬곱으로 계산됩니다.
차원을 그대로 두고, 토큰의 관점에서, 한 번에 처리하는 토큰의 개수를 n이라고 하면, 이 행렬곱의 결과는 n×n 크기의 행렬이 됩니다. 연산량은 토큰 개수의 제곱에 비례해서 커지므로 O(n²) 의 복잡도를 갖습니다. 문장이 2배 길어지면 연산량은 4배, 4배 길어지면 16배가 된다는 뜻입니다.
문서 수준의 긴 문장을 다루는 최신 모델일수록 이 부분의 연산 비용이 전체 성능을 좌우합니다.
큰 행렬을 작은 타일로 쪼개는 기법
그런데 문제는, 텐서 코어가 한 번에 처리하는 단위는 고작 4×4 또는 8×4×8 같은 작은 행렬이라는 것입니다. 시퀀스 길이가 2048이면 Q × Kᵀ는 2048×2048짜리 결과를 만들어야 하는데, 이걸 어떻게 작은 텐서 코어 단위로 처리할까요?
답은 타일링(tiling) 입니다. 큰 행렬을 작은 블록으로 쪼갠 뒤, 각 타일 단위의 작은 행렬곱을 수많은 텐서 코어에 분배해서 동시에 처리하고, 그 결과를 합쳐서 전체 행렬곱을 완성하는 방식입니다.
예를 들어 2048×2048 행렬곱을 16×16 타일로 쪼개면, 가로로 128개, 세로로 128개, 총 16,384개의 타일이 만들어집니다. 각 타일의 결과는 독립적으로 계산할 수 있으므로, GPU의 수백 개 텐서 코어에 골고루 분배되어 병렬로 처리됩니다. cuBLAS, cuDNN 같은 NVIDIA의 라이브러리가 이런 타일링을 자동으로 최적화해서 수행해 줍니다.
이 타일링이 가능한 이유는 행렬곱의 연산을 블록 단위로 분해 가능하기 때문입니다. 큰 행렬곱은 작은 행렬곱의 합으로 정확하게 표현될 수 있습니다.
멀티헤드 어텐션과 배치 행렬곱
트랜스포머의 멀티 어텐션에서는 여러 개의 헤드를 사용합니다. 헤드가 8개라면, 같은 입력을 8번 서로 다른 관점으로 처리하고, 그 결과를 합치는 방식입니다. 각 헤드는 서로 독립적으로 자기만의 Q, K, V 행렬곱을 수행합니다.
각 헤드의 연산은 서로 영향을 주지 않기 때문에 GPU는 헤드 개수만큼의 독립된 행렬곱을 동시에 수행할 수 있습니다. GPU의 성능은 특정 코어가 놀지 않고 모두 일할 때 최대가 되는데 헤드 사이에 데이터를 주고받을 필요가 없으므로 수백 개의 텐서 코어에 작업을 균등하게 분배하게 되어 효율적입니다.
8. 혼합 정밀도 학습: 낮은 정밀도를 보완하는 법
앞서 텐서 코어가 FP16으로 곱하고 FP32로 누적한다는 이야기를 했습니다. 이 원리를 학습 전체로 확장한 것이 혼합 정밀도(mixed precision) 학습입니다.
혼합 정밀도 학습의 기본 아이디어는 이렇습니다.
- 순전파(forward pass)와 역전파(backward pass)의 행렬곱: FP16으로 빠르게 계산. 여기가 전체 연산의 대부분을 차지하는 곳이기 때문에 이곳의 속도를 올리는 것이 가장 효과적입니다.
- 가중치 업데이트와 손실 스케일링: FP32로 정확하게 유지. 가중치가 업데이트될 때 값이 아주 작은 변화량이 누적되는데, 이 부분을 FP16으로 하면 너무 작은 변화량이 반올림돼서 사라지는 문제가 생깁니다. 그래서 "마스터 가중치"라고 부르는 FP32 복사본을 따로 두고, 업데이트는 그 복사본에 대해 수행합니다.
여기서 몇 가지 용어를 정리하면 다음과 같습니다.
- 순전파(forward pass): 입력 데이터를 신경망에 통과시켜 예측값을 계산하는 과정.
- 역전파(backward pass): 예측이 정답과 얼마나 다른지를 계산한 뒤, 그 오차를 이용해 각 가중치를 얼마나 조정해야 할지 구하는 과정.
- 손실 스케일링(loss scaling): FP16은 표현 범위가 좁아서, 역전파 과정에서 아주 작은 값이 0으로 반올림되는 언더플로(underflow) 문제가 자주 발생합니다. 이를 막기 위해 손실(loss) 값에 큰 수(예: 1024)를 곱해서 역전파를 수행한 뒤, 가중치를 업데이트하기 직전에 다시 나눠주는 기법입니다.
이 전략 덕분에 혼합 정밀도 학습은 다음과 같은 효과를 냅니다.
- 학습 속도가 2~3배 빨라짐 (주로 텐서 코어가 활용되기 때문)
- GPU 메모리 사용량이 절반 가까이 줄어듦 (중간 활성화값이 FP16이므로)
- 최종 모델의 성능은 FP32 학습과 거의 동일하게 유지됨
PyTorch에서는 torch.cuda.amp(Automatic Mixed Precision) 모듈을 사용하면 몇 줄의 코드만으로 혼합 정밀도 학습을 켤 수 있습니다.
9. 데이터센터 GPU vs 게이밍 GPU
같은 NVIDIA GPU라도 데이터센터용과 게이밍용은 규모가 꽤 다릅니다. 텐서 코어의 기본 원리와 아키텍처는 거의 같지만, 탑재된 양과 보조 구성 요소가 다릅니다.
| 항목 | RTX 3060 (게이밍) | A100 (데이터센터) | H100 (데이터센터) |
|---|---|---|---|
| 텐서 코어 수 | 112개 | 432개 | 528개 |
| FP16 텐서 성능 | ~51 TFLOPS | ~312 TFLOPS | ~990 TFLOPS |
| GPU 메모리 | 12GB GDDR6 | 80GB HBM2e | 80GB HBM3 |
| 메모리 대역폭 | 360 GB/s | 2,039 GB/s | 3,350 GB/s |
| 용도 | 소규모 추론, 학습 실험 | 대규모 학습 | 초대규모 학습 |
- GDDR6, HBM2e, HBM3: GPU에 장착되는 고속 메모리의 종류입니다. GDDR은 게이밍 GPU에 주로 쓰이고, HBM(High Bandwidth Memory)은 데이터센터 GPU에 쓰이는 초고대역폭 메모리입니다. 같은 용량이라도 HBM이 훨씬 빠르고 그만큼 단가도 높습니다.
- 메모리 대역폭(memory bandwidth): 초당 GPU 메모리에서 연산 유닛으로 얼마나 많은 데이터를 옮길 수 있는지를 나타내는 값. 단위는 GB/s입니다. 딥러닝은 엄청난 양의 데이터를 계속 메모리에서 꺼내 써야 하기 때문에 연산 유닛이 아무리 빨라도 메모리 대역폭이 따라오지 못하면 성능이 안 나옵니다.
GPT 시리즈나 LLaMA 같은 대규모 언어 모델의 사전학습(pre-training)에는 A100이나 H100이 수백에서 수천 장 사용됩니다. 단일 GPU로는 모델이 메모리에 들어가지도 않고 학습 시간도 수십 년 단위가 되기 때문입니다.
반면 RTX 3060만 있어도 다음과 같은 일은 충분히 가능합니다.
- 소규모 모델(수백만~수천만 파라미터)의 처음부터 학습
- 공개된 대형 모델의 파인튜닝(fine-tuning, 기존에 학습된 모델을 특정 작업에 맞게 추가 학습), 특히 LoRA처럼 일부 파라미터만 학습하는 기법
- 대형 모델의 양자화(quantization) 추론. INT8이나 INT4로 변환된 모델이라면 12GB 메모리 안에서도 꽤 큰 모델이 돌아갑니다.
텐서 코어의 원리 자체는 RTX 3060이나 H100이나 같습니다.
정리
정리하자면, GPU가 트랜스포머를 빠르게 처리할 수 있는 이유는 단 하나의 기술 때문이 아닙니다. 구조적으로는 수많은 ALU를 가진 SM들이 있고 그 안에 행렬곱 전용인 텐서 코어가 있으며이 텐서 코어가 FP16 같은 낮은 정밀도로 빠르게 곱하고 FP32로 누적합니다. 소프트웨어 수준에서는 혼합 정밀도 학습과 타일링, 배치 행렬곱 같은 기법이 이 하드웨어를 최대한 활용합니다. 그리고 트랜스포머의 멀티헤드 어텐션 기법의 병렬성이 GPU에서 효율적이기에 GPU를 사용하고 있는 것입니다.
출처
- Mixed Precision Training. https://arxiv.org/abs/1710.03740
- Accelerating AI Training with TF32 Tensor Cores. https://developer.nvidia.com/blog/accelerating-ai-training-with-tf32-tensor-cores/
- Using Tensor Cores for Mixed-Precision Scientific Computing. https://developer.nvidia.com/blog/tensor-cores-mixed-precision-scientific-computing/
- Harnessing GPU Tensor Cores for Fast FP16 Arithmetic to Speed up Mixed-Precision Iterative Refinement Solvers. https://www.netlib.org/utk/people/JackDongarra/PAPERS/haidar_fp16_sc18.pdf
- RTX 3060 스펙 https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti/
- CPU와 GPU의 차이, 그리고 딥러닝. https://yozm.wishket.com/magazine/detail/2294/
'개발지식 > AI' 카테고리의 다른 글
| LLM 서빙 prefill·decode 분리 (1) - 분리의 이유 (0) | 2026.06.11 |
|---|---|
| LLM 서빙의 메모리 문제와 PagedAttention (2) - PagedAttention과 vLLM (0) | 2026.05.12 |
| LLM 서빙의 메모리 문제와 PagedAttention (1) - KV 캐시와 단편화 (0) | 2026.05.12 |
| 트랜스포머 쉽게 이해하기 (2) - 디코더 (0) | 2026.04.20 |
| 트랜스포머 쉽게 이해하기 (1) - 인코더 (0) | 2026.04.15 |