[PyTorch] 부동소수점 (Floating Point)
부동소수점 (Floating Point)
부동소수점(Floating Point)이란 컴퓨터가 소수점이 있는 실수를 메모리에 저장하고 표현하는 방식 중 하나이다.
부동소수점의 ‘부동’은 고정의 뜻이 아니라, 떠다닌다는 뜻이다.
즉, 소수점의 위치를 한 곳에 고정하지 않고 자유롭게 옮겨가며 숫자를 표현한다는 의미이다.
Fixed Point vs Floating Point
예를 들어, 숫자를 저장할 수 있는 메모리 공간이 총 6칸이고, 여기에 숫자
12345를 기록한다고 해보자.고정소수점 방식에서는 항상 정가운데에 소수점을 찍도록 위치를 고정한다.
이 경우 모든 숫자가 무조건
xxx.xxx형태를 따라야 하므로, 소수점 앞자리가 5자리나 되는12345는 이 공간에 아예 담을 수 없다.반면, 부동소수점 방식은 소수점의 위치를 고정하지 않고 자유롭게 옮길 수 있기 때문에, 지수를 조절하여 다음과 같이 유연하게 표현할 수 있다.
- 기본 상태: $12345.0 \times 10^0$
- 왼쪽으로 1칸 이동: $1234.5 \times 10^1$
- 왼쪽으로 4칸 이동: $1.2345 \times 10^4$
FP32를 기준으로, 부동소수점은 아래의 그림처럼 3가지 영역으로 나뉜다.
- 부호(Sign): 양수/음수를 결정한다.
- 지수부(Exponent): 숫자의 표현 범위(Range)를 결정한다.
- 가수부(Mantissa/Fraction): 숫자의 정밀도(Precision)를 결정한다.
숫자를 64bit로 표현하는 것을 Double Precision, 32bit로 표현하는 것을 Single Precision, 16bit로 표현하는 것을 Half Precision이라고 한다.
부동소수점을 10진수로 변환하는 방법
컴퓨터 메모리에 이진수로 저장된 부동소수점 데이터를 우리가 아는 실제 10진수 실수로 변환하는 방법은 다음의 공식을 따른다.
\[\text{Value} = (-1)^{\text{Sign}} \times (1 + \text{Mantissa}) \times 2^{(\text{Exponent} - \text{Bias})}\]부호 (Sign)
비트가
0이면 양수, 비트가1이면 음수를 의미한다.지수 (Exponent)
$2$의 거듭제곱 값을 나타내어 숫자의 크기(범위)를 결정한다.
8비트 이진수를 10진수로 변환한 뒤, Bias라는 고정값을 빼서 실제 지수 값을 구한다.
FP32의 경우 Bias는 $127$인데, 이는 숫자를 표현할 수 있는 범위가 $2^{-(127-1)} \sim 2^{127}$라는 뜻이다.가수 (Mantissa/Fraction)
소수점 이하의 값을 나타내어 숫자의 정밀도를 결정한다.
가수부의 $i$번째 자리 비트는 $2^{-i}$의 값을 가진다.
즉, 앞에서부터 차례대로 $2^{-1}$, $2^{-2}$, $2^{-3}$을 의미하며, 비트가1인 자리의 값을 모두 더해 소수 부분을 구한다.
FP32 기준으로 예시를 하나 들어보겠다. (FP32의 Bias는 $127$임)
- 부호: 부호 비트가
0이므로, 숫자는 양수이다. $\to$ $(-1)^0$ - 지수:
10000010은 10진수로 $130$이다. 따라서 $\text{Exponent} - \text{Bias}=130-127=3$이다. $\to$ $2^3 = 8$ - 가수:
1111000...의 자리값을 더하면 $0.5 + 0.25 + 0.125 + 0.0625 = 0.9375$이다. $\to$ $1+0.9375=1.9375$
이제 구한 값들을 모두 곱하면, 아래와 같이 10진수 값으로 변환된다.
\[(+1) \times 1.9375 \times 8 = 15.5\]부동소수점 종류
부동소수점 포맷에는 FP32, FP16 외에도 딥러닝 연산에 최적화된 설계된 BF16, TF32같은 형식도 존재한다.
아래는 한 눈에 보는 비교 표이다.
| 포맷 | 총 비트 수 | 부호 비트 수 | 지수 비트 수 | 가수 비트 수 |
|---|---|---|---|---|
FP32 | $32$ | $1$ | $8$ | $23$ |
FP16 | $16$ | $1$ | $5$ | $10$ |
BF16 | $16$ | $1$ | $8$ | $7$ |
TF32 | $32$ (입력), $19$ (연산) | $1$ | $8$ | $10$ |
FP32 (Floating Point, 32-bit)
딥러닝과 일반적인 컴퓨팅에서 오랫동안 표준으로 사용된 32bit 포맷이다.
\[\text{부호 1bit} + \text{지수 8bit} + \text{가수 23bit}\]정밀도가 매우 높고 표현할 수 있는 숫자의 범위가 넓지만, 메모리를 많이 차지하고 연산 속도가 상대적으로 느리다.
FP16 (Floating Point, 16-bit)
FP32의 절반 크기인 16bit로 데이터를 표현하는 포맷이다.
메모리 사용량이 절반으로 줄어들고, 데이터 전송 및 연산 속도가 대폭 향상되지만, 지수부가 5bit밖에 되지 않아 표현할 수 있는 숫자의 범위가 매우 좁다.
이로 인해 학습 중 그래디언트 값이 0으로 소실되는 Underflow나 범위를 초과하는 Overflow가 발생하기 쉽다.
BF16 (Brain Floating Point, 16-bit)
구글이 인공지능 연산을 위해 특별히 고안한 16bit 포맷이다.
\[\text{부호 1bit} + \text{지수 8bit} + \text{가수 7bit}\]딥러닝 모델이 약간의 정밀도(가수부) 손실에는 강하지만, 숫자 표현 범위(지수부)가 좁으면 학습이 무너진다는 점에서 착안했다.
FP32와 동일한 8bit 지수부를 가져 표현 범위가 똑같다.
정밀도는 FP16보다 떨어지지만, FP16의 Overflow/Underflow 문제를 해결하여 FP32 환경에서 학습하던 모델을 거의 수정 없이 BF16으로 변환해 안정적으로 학습할 수 있다.
TF32 (TensorFloat-32, 19-bit 연산)
엔비디아가 Ampere 아키텍처부터 도입한 독특한 포맷이다.
\[\text{부호 1bit} + \text{지수 8bit} + \text{가수 10bit}\]데이터 입출력은 기존처럼 FP32로 받지만, 내부 Tensor Core에서 연산할 때만 19bit의 TF32 방식으로 동작한다.
즉, FP32의 넓은 범위와 FP16의 정밀도를 합친 하이브리드 포맷이다.
코드나 모델을 수정할 필요 없이 FP32를 그대로 입력하면, 하드웨어 단에서 자동으로 가속하여 처리 속도를 높여준다. (단, Ampere 이상의 NVIDIA GPU가 필요함)

