Warpspace는 Caveduck.io를 온디바이스·저비용으로 굴리기 위해 모델을 작게 만드는 여러 실험을 합니다. 이 글은 그 경량화 시리즈의 첫 편(#1 데이터타입)입니다. 이어지는 #2 양자화·#3 프루닝 편에서는 여기서 다룬 데이터타입으로 실제 모델을 줄이고 그 결과를 직접 측정합니다.
데이터타입이 왜 이렇게 많아졌나
몇 년 전만 해도 딥러닝의 숫자는 그냥 float32 하나면 충분했습니다. 그런데 요즘 논문이나 모델 카드를 보면 FP16, BF16, FP8 (E4M3/E5M2), INT8, INT4, FP4, NF4… 온갖 데이터타입이 쏟아집니다. 이름만 봐서는 뭐가 뭔지, 왜 이렇게 여러 개인지 헷갈립니다.
이유는 단순합니다. 저비트 연산은 그냥 싸기 때문입니다. 45nm 공정 기준으로 연산 하나의 에너지 비용은 대략 이렇습니다.
| 연산 | 에너지 (pJ) |
|---|---|
| 8-bit int ADD | 0.03 |
| 32-bit int ADD | 0.1 |
| 32-bit float ADD | 0.9 |
| 8-bit int MULT | 0.2 |
| 32-bit float MULT | 3.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$=음수), 나머지로 크기를 표현합니다. 직관적이지만 치명적인 단점이 있습니다. 00000000과 10000000이 둘 다 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 (가수부) — 비트에 실제로 저장되는 소수 부분만 가리킵니다. 위젯에서 노란색으로 칠해진 비트가 바로 이것. 소수점 아래 값 $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 = 0 | Fraction ≠ 0 | 해석 |
|---|---|---|---|
00…0 (=0) | $\pm 0$ | subnormal | $(-1)^s \times \text{Fraction} \times 2^{1-\text{bias}}$ |
00…1 ~ 11…0 | normal | normal | $(-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$처럼 지수적으로 벌어지는 걸 확인해보세요.
이 네 포맷의 “표현 가능한 16개 값"을 수직선 위에 한번에 찍어 보면 성격 차이가 한눈에 들어옵니다. INT4는 일정 간격, FP4는 지수 비트를 늘릴수록 0 근처가 촘촘해지고 바깥이 벌어집니다.

여기서 재미있는 관찰이 하나 나옵니다. 신경망의 가중치 분포는 대개 0 근처에 몰려 있고 꼬리가 깁니다. 그렇다면 값을 균등하게 늘어놓는 INT보다, 0 근처를 촘촘하게 메우는 FP 계열이 같은 4비트로도 실제 값들을 더 잘 맞출 수 있습니다. 어떤 분포에는 어떤 포맷이 어울리는가 — 데이터타입 선택이 곧 정확도로 이어지는 이유입니다. (이 관찰을 실제 모델로 측정하는 건 양자화 편에서 다룹니다.)
마무리
여기까지가 딥러닝에서 마주치는 데이터타입들이 실제로 무슨 뜻인지에 대한 이야기입니다. 정리하면,
- 정수 / 고정소수점 — 값이 균등한 간격. 단순하지만 범위가 좁다.
- 부동소수점 — 지수로 범위를, 가수로 정밀도를 나눠 갖는다. 0 근처는 촘촘하고 바깥은 성기다.
- 비트를 줄인다는 것 = 표현 가능한 값의 개수를 줄인다는 것. FP32의 약 43억 개에서 FP4의 16개까지.
이제 FP16, BF16, FP8 E4M3, INT4 같은 이름을 보면 머릿속에 비트 배치가 그려질 것입니다. 딥러닝에서 “어떤 데이터타입을 쓸까?“는 더 이상 사소한 구현 디테일이 아니라, 모델을 얼마나 작고 빠르게, 그러면서도 정확하게 굴릴 수 있느냐를 가르는 설계 선택이 됐습니다.
다음 글에서는 이 데이터타입들을 무기 삼아, 이미 학습된 32비트 모델을 실제로 2~8비트로 줄여 보고 — 정확도가 얼마나 유지되는지, 크기가 얼마나 주는지 — 직접 코드를 돌려 측정합니다.