데이터타입이 왜 이렇게 많아졌나

몇 년 전만 해도 딥러닝의 숫자는 그냥 float32 하나면 충분했다. 그런데 요즘 논문이나 모델 카드를 보면 FP16, BF16, FP8 (E4M3/E5M2), INT8, INT4, FP4, NF4… 온갖 데이터타입이 쏟아진다. 이름만 봐서는 뭐가 뭔지, 왜 이렇게 여러 개인지 헷갈린다.

이유는 단순하다. 저비트 연산은 그냥 싸기 때문이다. 45nm 공정 기준으로 연산 하나의 에너지 비용은 대략 이렇다.

연산에너지 (pJ)
8-bit int ADD0.03
32-bit int ADD0.1
32-bit float ADD0.9
8-bit int MULT0.2
32-bit float MULT3.7

8비트 정수 곱셈은 32비트 float 곱셈보다 약 18배 싸고, 덧셈은 30배 싸다. 게다가 메모리에서 값 하나를 읽어오는 비용은 곱셈·덧셈 한 번보다 수백 배 비싸다. 그러니 같은 모델이라도 숫자를 더 적은 비트에 담을수록 전력도, 메모리도, 대역폭도 아낄 수 있다. 위의 온갖 데이터타입들은 결국 “몇 비트를, 어디에 쓸 것인가"를 저마다 다르게 답한 결과물이다.

문제는 비트를 줄이면 표현할 수 있는 숫자의 종류가 줄고, 그만큼 정밀도나 범위를 잃는다는 것이다. 그래서 포맷마다 부호·지수·가수에 비트를 어떻게 배분하느냐로 성격이 갈린다. 이 글은 그 배분 규칙을 — 비트가 실제로 어떤 숫자로 해석되는지 — 하나씩 뜯어본다.

각 데이터타입마다 비트를 직접 클릭할 수 있는 위젯을 넣어뒀다. 0과 1을 눌러 뒤집으면 계산식과 결과값이 실시간으로 바뀐다. 설명을 눈으로 읽는 것보다, 직접 비트를 만져보는 편이 훨씬 빨리 감이 온다.


1. 정수 (Integer)

부호 없는 정수 (Unsigned Integer)

가장 단순하다. $n$개의 비트 각각이 $2^k$ 자리값을 가지고, 켜진 비트($=1$)의 자리값을 모두 더한다.

$$\text{value} = \sum_{i=0}^{n-1} b_i \cdot 2^i$$

표현 범위는 $[0,\ 2^n - 1]$. 8비트면 0부터 255까지다.

아래 위젯에서 비트를 눌러보자. 기본값 00110001은 $2^5 + 2^4 + 2^0 = 49$ 다.

부호 있는 정수 (Signed Integer)

음수를 표현하려면 부호가 필요하다. 두 가지 방식이 있다.

부호-크기 (Sign-Magnitude) — 맨 앞 비트를 부호로 쓰고($0$=양수, $1$=음수), 나머지로 크기를 표현한다. 직관적이지만 치명적인 단점이 있다. 0000000010000000둘 다 0이다 (+0과 −0). 0이 두 개라 하드웨어가 번거로워진다. 범위는 $[-2^{n-1}+1,\ 2^{n-1}-1]$.

2의 보수 (Two’s Complement) — 현대 컴퓨터가 실제로 쓰는 방식이다. 아이디어는 간단하다. 맨 앞 비트의 자리값을 음수로 만든다. 즉 MSB의 가중치가 $+2^{n-1}$이 아니라 $-2^{n-1}$이다.

$$\text{value} = -b_{n-1}\cdot 2^{n-1} + \sum_{i=0}^{n-2} b_i \cdot 2^i$$

이러면 0은 00000000 하나뿐이고, 범위는 $[-2^{n-1},\ 2^{n-1}-1]$로 음수 쪽이 하나 더 넓다. 아래 11001111은 $-2^7 + 2^6 + 2^3 + 2^2 + 2^1 + 2^0 = -49$ 다. MSB를 끄면 어떻게 바뀌는지 눌러보자.


2. 고정소수점 (Fixed-Point)

정수만으로는 소수를 못 담는다. 가장 쉬운 확장은 소수점의 위치를 고정해두는 것이다. 비트열을 그냥 2의 보수 정수로 읽은 다음, 정해진 만큼 $2^{-f}$로 스케일한다.

$$\text{value} = (\text{정수로 읽은 값}) \times 2^{-f}$$

아래는 8비트를 정수부 4비트 · 소수부 4비트($f=4$)로 나눈 것이다. 각 비트의 가중치가 $2^3, 2^2, \dots, 2^0, 2^{-1}, \dots, 2^{-4}$로 이어진다. 00110001은 정수로 읽으면 49이고, $49 \times 2^{-4} = 3.0625$ 다.

고정소수점은 단순하지만 한계가 뚜렷하다. 소수점 위치가 고정이라 표현할 수 있는 크기의 폭(range)이 좁다. 아주 큰 수와 아주 작은 수를 동시에 다루기 어렵다. 이 문제를 푸는 것이 다음 주인공, 부동소수점이다.


3. 부동소수점 (Floating-Point) — IEEE 754

부동소수점은 이름 그대로 소수점이 떠다닌다. 과학적 표기법($1.5 \times 10^3$)의 2진수 버전이라고 보면 된다. 비트를 세 부분으로 나눈다.

  • Sign (부호) — 1비트
  • Exponent (지수) — 크기의 규모를 정한다 → 표현 범위(range) 를 결정
  • Fraction (가수부) — 세부 값을 정한다 → 정밀도(precision) 를 결정

일반적인(normal) 수의 해석 공식은 이렇다.

$$\text{value} = (-1)^{\text{sign}} \times (1 + \text{Fraction}) \times 2^{\text{Exponent} - \text{bias}}$$

여기서 bias는 지수를 음수까지 표현하기 위한 오프셋으로 $2^{e-1}-1$ ($e$=지수 비트 수)이다. 그리고 가수 앞의 $(1 + \cdots)$ 에 주목하자. 정규 수는 항상 맨 앞에 암묵적인 1이 붙는다 (이걸 “implicit leading 1"이라 부른다).

잠깐, 용어 정리 — 가수 / Fraction / Mantissa / Significand

이 부분의 용어는 문헌마다 조금씩 섞여 쓰여서 헷갈리기 쉽다. 위 공식의 $1.\text{Fraction}$ 전체, 즉 유효숫자 부분을 가리키는 정식 명칭은 Significand(유효숫자) 다.

  • Fraction (가수부) — 비트에 실제로 저장되는 소수 부분만 가리킨다. 위 위젯에서 노란색으로 칠해진 23개 비트가 바로 이것. 소수점 아래 값 $0.\text{b}_1\text{b}_2\cdots$ 이다.
  • Significand (유효숫자) — 암묵적 1까지 포함한 $1.\text{Fraction}$ 전체. 실제로 곱해지는 유효숫자다.
  • Mantissa (가수) — 역사적으로 로그표의 소수 부분을 부르던 말인데, 부동소수점에서는 보통 Fraction과 같은 뜻으로 느슨하게 쓴다. IEEE 754 표준 자체는 “Significand"를 정식 용어로 쓰고 “mantissa"는 권장하지 않지만, 현장에서는 여전히 “가수/mantissa"가 흔하다.

정리하면 Significand = 1 + Fraction, 그리고 일상적으로 “가수(mantissa)“라고 하면 대개 저장되는 Fraction을 가리킨다. 이 글에서는 비트에 담기는 필드를 Fraction(가수부) 으로 부른다.

아래는 32비트 단정밀도(FP32)다. 부호 1 + 지수 8 + 가수 23 = 32비트, bias는 127. 기본값은 $0.265625 = (1 + 0.0625) \times 2^{125-127}$를 표현한다. 지수 비트를 하나씩 눌러 값이 2배씩 뛰는 걸 확인해보자.

특별한 값들: 0, 무한대, NaN, 그리고 subnormal

공식을 보면 이상한 점이 하나 있다. $(1 + \text{Fraction})$ 때문에 정규 수로는 0을 표현할 수 없다. 그래서 IEEE 754는 지수 필드를 특별한 신호로 쓴다.

지수(Exponent)Fraction = 0Fraction ≠ 0해석
00…0 (=0)$\pm 0$subnormal$(-1)^s \times \text{Fraction} \times 2^{1-\text{bias}}$
00…1 ~ 11…0normalnormal$(-1)^s \times (1+\text{Fraction}) \times 2^{\text{Exp}-\text{bias}}$
11…1 (=max)$\pm\infty$NaN

핵심은 지수가 전부 0일 때다. 이때는 암묵적 1을 떼고($1+\text{Fraction} \to \text{Fraction}$), 지수를 $2^{1-\text{bias}}$로 고정한다. 이렇게 만들어지는 게 subnormal(비정규) 수로, 0 근처의 아주 작은 값들을 촘촘히 메워준다. 반대로 지수가 전부 1일 때는 무한대(가수 0)와 NaN(가수 ≠ 0)이 된다.

위의 FP32 위젯에서 직접 만들어보자.

  • 지수를 전부 1로 (0 11111111 0…0) → +∞
  • 거기서 가수 아무 비트나 켜면NaN
  • 전부 0으로0
  • 지수만 0으로 두고 가수를 조금 켜면 → 아주 작은 subnormal 값

지수 폭이 넓을수록 표현 범위가 넓어지고, 가수 폭이 넓을수록 정밀도가 올라간다. Exponent → Range, Fraction → Precision. 이 한 줄이 이후 모든 저정밀도 포맷 설계의 핵심 트레이드오프다.


4. 절반 크기로: FP16과 BF16

FP32는 정확하지만 32비트나 먹는다. 딥러닝은 그렇게까지 정밀할 필요가 없는 경우가 많아서, 16비트 포맷을 쓴다. 같은 16비트인데 비트를 어디에 배분하느냐가 갈린다.

FP16 (IEEE 754 Half Precision)

지수 5 + 가수 10. bias는 15. 정밀도(가수)에 더 투자한 배분이다. 아래는 $1\,10001\,1100000000$, 즉 $-(1+0.75)\times 2^{17-15} = -7.0$ 이다.

BF16 (Google Brain Float)

지수 8 + 가수 7. bias는 127. FP16과 총 비트 수는 같지만 지수를 FP32와 똑같이 8비트로 유지한다. 즉 표현 범위는 FP32와 동일하고, 대신 정밀도를 희생했다. 학습 중 그래디언트처럼 값의 규모(scale)가 크게 요동치는 상황에서 오버플로 걱정이 없어 인기가 많다.

아래는 $2.5 = (1 + 0.25)\times 2^{1}$을 BF16으로 표현한 것이다($0\,10000000\,0100000$).

두 위젯에서 지수 비트 개수 차이를 느껴보자. BF16은 지수 셀이 8칸이라 아주 큰/작은 수까지 닿지만, 가수가 7칸뿐이라 값이 듬성듬성하다. FP16은 그 반대다.


5. 더 아래로: FP8과 FP4

FP8 (E4M3 / E5M2)

8비트 부동소수점은 최신 하드웨어(예: Nvidia Hopper/Blackwell)가 지원한다. 두 가지 배분이 표준처럼 쓰인다.

  • E4M3 — 지수 4 + 가수 3. 정밀도 우선. 순전파(forward)의 가중치·활성값에 주로 쓴다. INF가 없고 S.1111.111만 NaN으로 쓴다. 표현 가능한 최대 정규값은 $448$.
  • E5M2 — 지수 5 + 가수 2. 범위 우선. 역전파(backward)의 그래디언트처럼 규모가 큰 값에 쓴다. IEEE처럼 INF와 NaN을 갖는다.

E4M3부터. bias는 7이다. 기본값 0 0111 000은 $ (1+0)\times 2^{7-7} = 1.0$ 이다.

E5M2. bias 15. 지수 5칸으로 훨씬 넓은 범위를 담지만, 가수가 2칸뿐이라 값 간격이 성기다.

INT4와 FP4

가장 극단이다. 4비트로 표현 가능한 값은 단 16개뿐이다. 이 16개를 어떻게 배치하느냐가 포맷마다 다르다.

INT4 — 2의 보수 정수. $-8$부터 $7$까지 균등한 간격으로 놓인다.

FP4는 지수/가수 배분에 따라 값의 분포가 달라진다. 지수 비트를 늘릴수록 0 근처는 촘촘하고 바깥쪽은 성기게, 비균등(non-uniform) 하게 퍼진다.

  • E1M2 — 지수 1 + 가수 2. 정수에 가장 가깝다 (bias 0). 0111 = $(1+0.75)\times 2^{1-0} = 3.5$.
  • E2M1 — 지수 2 + 가수 1 (bias 1). 0111 = $(1+0.5)\times 2^{3-1} = 6$.
  • E3M0 — 지수 3 + 가수 0 (bias 3). 가수가 없어서 사실상 2의 거듭제곱만 표현한다. 0111 = $(1+0)\times 2^{7-3} = 16$. random 버튼을 눌러 값들이 $\dots, 4, 8, 16$처럼 지수적으로 벌어지는 걸 확인해보자.

여기서 재미있는 관찰이 하나 나온다. 신경망의 가중치 분포는 대개 0 근처에 몰려 있고 꼬리가 길다. 그렇다면 값을 균등하게 늘어놓는 INT보다, 0 근처를 촘촘하게 메우는 FP 계열이 같은 4비트로도 실제 값들을 더 잘 맞출 수 있다. 어떤 분포에는 어떤 포맷이 어울리는가 — 데이터타입 선택이 곧 정확도로 이어지는 이유다.


마무리

여기까지가 딥러닝에서 마주치는 데이터타입들이 실제로 무슨 뜻인지에 대한 이야기다. 정리하면,

  • 정수 / 고정소수점 — 값이 균등한 간격. 단순하지만 범위가 좁다.
  • 부동소수점 — 지수로 범위를, 가수로 정밀도를 나눠 갖는다. 0 근처는 촘촘하고 바깥은 성기다.
  • 비트를 줄인다는 것 = 표현 가능한 값의 개수를 줄인다는 것. FP32의 약 43억 개에서 FP4의 16개까지.

이제 FP16, BF16, FP8 E4M3, INT4 같은 이름을 보면 머릿속에 비트 배치가 그려질 것이다. 딥러닝에서 “어떤 데이터타입을 쓸까?“는 더 이상 사소한 구현 디테일이 아니라, 모델을 얼마나 작고 빠르게, 그러면서도 정확하게 굴릴 수 있느냐를 가르는 설계 선택이 됐다.