일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- gemma3
- pdf parsing
- fp32
- 합성곱 신경망
- 활성화함수
- rrf
- qlora
- bf16
- 딥러닝
- LLM
- LLaVA
- Cross Entropy Error
- 활성화 함수
- visual instruction tuning
- fp16
- 이상탐지
- Mean squared error
- 오차역전파
- Time Series
- deep learning
- multi-query
- Non-Maximum Suppression
- rag parsing
- leetcode
- 파인튜닝
- 손실함수
- rag-fusion
- anomaly detection
- fine tuning
- 데이터 파싱
- Today
- Total
Attention, Please!!!
거대 언어 모델 : BF16, FP16, FP32에 따른 추론 성능 알아보기 본문
초기 대형 언어 모델들은 대체적으로 float32 데이터 유형을 통해 훈련하고 배포된다. 하지만 float32는 각 파라미터가 32비트 (즉, 4바이트)를 차지하기 때문에, LLaMA 3와 같은 700 억 개 파라미터를 가진 모델의 경우 필요한 메모리의 소비량이 대략 280GB에 달한다. 이 엄청난 메모리 사용량은 대규모 모델의 배포와 운영적인 측면에서 어려움을 겪고 있다. 이에 이러한 문제를 해결하기 위해 LLM 개발자들은 메모리 소비를 절반으로 줄일 수 있는 float16를 사용하고 있는 추세이다. 하지만 float16으로 전환하는 것은 다양한 문제점을 동반한다. float16은 표현 가능한 숫자의 범위와 정밀도가 float32에 비해 훨씬 적기 때문에, 모델 훈련 중에 오버플로우(숫자가 커져 버리는 현상) 혹은 언더플로우(숫자가 작아져 소실되는 현상)가 발생할 수 있다. 이에 개발자들은 mixed-precision을 도입하였으며, 이는 모델의 핵심 구성 요소나 중요한 연산은 float32로 유지하면서, 덜 민감한 부분은 float16으로 적용한다. 그러나 float16은 훈련과 추론 중 불안정성을 초래하는 경우가 많아 손실 스케일링 및 다른 방법이 필요하다. 이에 구글에서 bfloa16을 고안하였으며, 이는 float32와 비슷한 성능을 발휘하는 것 뿐만 아니라 메모리 소비량도 감소하였다. 따라서, 본 게시물에서는 bfloat16, float16, 그리고 float32에 따른 성능 차이를 Gemma 3 모델을 기반으로 알아보고자 한다.
1. The Differences Between bfloat16, float16, and float32
부동소수점(floating-point) 숫자는 컴퓨터가 실수를 표현하는 방식으로, 크게 세 가지 주요 구성 요소로 나뉜다: 부호(sign), 지수(exponential), 그리고 가수(mantissa). 이 세 요소는 숫자의 크기와 정밀도를 계산하게 되는데, 비트를 할당하는 방식에서 bfloat16, float16, 그리고 float32로 나뉘게 된다.
- 부호(Sign): 숫자의 양수 또는 음수를 나타내며, 이는 주로 1 비트로 표현된다. (양수: 0 & 음수: 1)
- 지수(Exponent): 숫자의 스케일(크기)을 결정한다. 이는 정수로 저장이 되며, 보통 bias를 적용하여 음수 지수로 표현될 수 있다. 조금 더 구체적으로 설명을 해보자면, 컴퓨터는 지수를 저장할 때 부호 없는 정수를 처리하는 게 비교적 간단하고 빠르다. 이에 음수를 저장하려면 부호 비트를 따로 둬야하는데, 이러한 복잡한 방법을 피하고자 Bias 값을 더해서 모든 지수를 양수로 만든다.
- 가수(Mantissa): 숫자의 유효 정밀도를 나타낸다. 즉, 숫자의 내용을 저장하는 부분이라고 생각하면 좋을거 같다. 그래서 가수는 숫자의 세부적인 값을 이진수로 저장하며, 정규화 과정을 통해 (1.xxxxx X 2^숫자) 지수 형태로 변한된 소수 부분(xxxxx)을 저장해 더 정확한 표현이 가능하다.
그럼 이제 구체적으로 float32, float16, bfloat16에 대해 알아보고자 한다. 이들의 가장 핵심적인 차이점은 지수(Exponent)와 가수(Mantissa)에 비트를 어떻게 할당하느냐에 있다.
- Float32 (IEEE 754 single-precision)
- 1-bit sign, 8-bit exponent, 23-bit mantissa
- Maximum exponent value: 127 (bias: 127)
- Approximate range: ±(1.18 × 10⁻³⁸) to (3.4 × 10³⁸)
- Float16 (IEEE 754 half-precision)
- 1-bit sign, 5-bit exponent, 10-bit mantissa
- Maximum exponent value: 15 (bias: 15)
- Approximate range: ±(6.1 × 10⁻⁵) to (6.5 × 10⁴)
- Bfloat16 (Brain floating point)
- 1-bit sign, 8-bit exponent, 7-bit mantissa
- Maximum exponent value: 127 (bias: 127)
- Approximate range: ±(1.18 × 10⁻³⁸) to (3.4 × 10³⁸) (same as FP32)
2. Impact on Large Language Models
(i) Numerical Stability
대형 언어 모델과 같은 대규모 모델은 훈련 과정에서 Exploding Gradient 혹은 Vanishing Gradient 문제로 인해 높은 동적 범위(Dynamic Range)가 필요하다. 이러한 문제는 모델의 가중치 업데이트나 활성화 함수 계산에서 숫자가 너무 커지지거나 작아지는 경우 발생하게 되는데, 이때 지수와 비트 할당 차이를 통해 Numerical Stability (수치 안정성)에 직접적인 영향을 주게 된다.
BF16 같은 경우 8비트 지수와 7비트 가수를 사용한다. 이때 8비트 지수는 넓은 동적 범위를 제공하여 매우 크거나 작은 값을 표현할 때, Exploding Gradient 혹은 Vanishing Gradient와 같은 문제점을 방지할 수 있다는 큰 장점이 있다. 이러한 특성으로 인하여 BF16은 가중치 업데이트와 활성화 함수 단계에서 수치적 안정성을 유지할 수 있으며, 손실 스케일링와 같은 기법 필요 없이 안정적으로 학습이 가능해진다. 이에 결과적으로 놓고 본다면, BF16은 계산 오버헤드를 줄이고 훈련 과정을 단순화 시킬 수 있다.
반면, FP16은 5비트 지수와 10비트 가수를 사용한다. 이는 F16의 8비트 지수에 비하면 상대적으로 제한적이다. 어찌보면 단순하게 3비트 차이가 나지만, 이를 수치적인 관점에서 바라본다면 굉징히 크다. 이로 인해 FP16은 학습 시 그레디언트가 매우 크거나 작을 때 오버플로우 혹은 언더플로우 문제가 발생할 가능성이 높다. 예를 들어, 숫자 3.14159를 표현할 때 FP16은 약 3.140625로 근사하지만, BF16은 3.125로 큰 오차가 발생한다. 하지만, 딥러닝 학습 단계에서는 대체적으로 정밀도보다 범위가 중요할 때가 더 많아, FP16의 높은 정밀도에도 불구하고 좁은 범위로 인하여 Loss Scaling와 같은 추가적인 기법이 필수적이다.
(ii) Memory Efficiency & Throughput
FP16과 BF16은 FP32에 비해 메모리 사용량을 50% 줄여, 각각 32비트(4바이트)에서 16비트(2바이트)로 파라미터를 저장한다. 예를 들어, Gemma 3와 같은 700억 개 파라미터를 보유한 대형 언어 모델은 FP32로 대략 280GB 메모리가 필요한 반면, FP16 또는 BF16 같은 경우 140GB으로 절반이 줄어든다. 이러한 메모리 절약은 아래와 같은 장점을 제공한다:
- INCREASED BATCH SIZE: 더 많은 데이터를 한 꺼번에 처리할 수 있어, 학습의 효율성 향상 가능
- INCREASED ACTIVATION: 깊은 신경먕에서 Hidden Layer에 대한 값을 더 많이 저장할 수 있음
- DEEPER MODEL: 메모리의 제약이 줄어들어 더욱 더 복잡한 모델 설계가 가능
또한, FP16과 BF16의 저정밀도 형식을 사용하면 GPU 혹은 TPU와 같은 하드웨어에서 사이클 당 FLOPs (Floating-Point Operations per Second)가 증가하여 처리량(Throughput)이 향상된다. 즉, 짧은 시간에 더 많은 토큰을 생성한다는 뜻 이다.
3. Potential Issues When Converting LLMs (BF16 to FP16)
앞서 살펴보았듯이, BF16은 FP32와 동일하게 8비트 지수(바이어스 127)를 사용하지만, FP16은 5비트 지수(바이어스 15)만을 사용한다. 즉, 8비트 지수를 보유한 BF16과 FP32가 훨씬 더 크거나 작은 숫자를 표현할 수 있다는 것을 의미한다. 여기에서 BF16의 동적 범위는
± 1.18 × 10e−38 에서 ±3.4 × 10e38이며, FP16의 ±6.1 × 10e−5에서 ±6.5 × 10e4 보다 대략 10e67배 넓다. 겨우 3비트 밖에 차이가 나지 않지만, 만약에 BF16으로 학습된 모델이 FP16으로 전환이 된다면 어떠한 문제를 살펴보고자 한다.
Gemma 3와 같은 BF16을 기반으로 훈련된 모델은 큰 활성화 값이나 작은 그래디언트를 포함할 경우, FP16으로 전환되면 다음과 같은 문제가 발생할 수 있다. 위에서 많이 언급되었던 내용이지만, 복습 차원에서 다시 언급하도록 하겠습니다.
- 오버플로우(Overflow): 값이 FP16의 최대 범위 (6.5 × 10e4)를 초과하여, NaN 또는 Inf로 처리할 가능성이 매우 높음
- 언더플로우(Underflow): 값이 FP16의 최소 범위 (6.1 × 10e−5)보다 작아 값이 소실될 가능성이 매우 높음
이와 같은 문제는 특히 로짓(Logit)과 어텐션 레이어(Attention Layer)에서 자주 발생한다. 예를 들어, 어텐션 스코어가 10e5처럼 커지거나 그래디언트가 10e-6처럼 작아질 때, FP16은 이를 표현하지 못해 추론 중에 불안정성을 초래할 가능성이 매우 높아진다. 이에 BF16으로 훈련된 모델의 가중치는 FP32와 유사한 범위를 가지게 때문에, FP16으로 전환 시 일부 가중치 값이 FP16의 최대 범위를 초과하여 수치적인 불안정성을 유발할 수 있게 된다.
또한, BF16은 7비트 가수를 사용하고, FP16은 10비트 가수를 사용한다. 이는 FP16이 더 높은 정밀도를 내포하고 있다. 예를 들어, 숫자를 표현할 때, FP16은 대략 3.140625 (10비트 가수, 작은 오차)로 표현이 되는 반면에 BF16은 대략 3.125(7비트 가수, 더 큰 오차)로 표현된다. 이론적으로 따지고 보면, FP16의 정밀도를 기반으로 추론의 정확도를 향상 시킬 수 있다. 하지만, BF16으로 훈련된 모델을 FP16으로 전환되면, 오히려 정확도가 개선되지 않고, 오히려 미세하지만 무시할 수 없는 perturbation이 발생할 수 있다.
그렇다면, BF16으로 훈련된 모델을 FP16으로 전환 시키고자 할때, 어떻게 해야할까?
- Clamping Activations: 활성화 값을 FP16의 표현 범위 내로 제한하기 위해 activating clipping을 적용한다. 말 그대로 범위를 제한하는 것이므로 오버플로우 혹은 언더플로우를 방지하여 NaN 및 Inf 출력을 방지할 수 있지만, 이 만큼 값을 제한하기 때문에 정보 손실이 유발될 수 있다.
- Mixed-Precision Inference: DeepSeek 연구진들이 사용한 방법으로 일부 레이어는 FP32으로 유지하되, 중요도가 높지 않은 레이어는 FP16으로 실행하는 방법이다. 이러한 방법은 안정성과 성능의 균형을 유지할 수 있지만, 구현 복잡성과 메모리 사용량이 증가하는 단점이 발생한다.
4. Inferencing with BF16 and FP16
Gemma 3 모델을 기반으로 FP16, FP32 및 BF16에 대한 결과를 한번 보도록 하겠습니다.
BF16:
Here's a detailed description of the image:
**Overall Impression:**
The image is a close-up, vibrant photograph of a cluster of pink cosmos flowers with a bumblebee actively foraging on one of them. The scene is set in a garden or natural setting, with a soft, natural light.
**Main Elements:**
* **Cosmos Flowers:** The primary focus is on the cosmos flowers. They are a lovely shade of pink, with broad, slightly ruffled petals. Several
Peak reserved memory = 36.596 GB.
FP32:
Okay, here's a detailed description of the image:
**Overall Impression:**
The image is a close-up shot of a vibrant garden scene, focusing on a cluster of pink cosmos flowers and a busy bumblebee. It has a slightly soft, natural feel, likely captured in daylight.
**Foreground:**
* **Cosmos Flowers:** The dominant feature is a large, bright pink cosmos flower in the center. It’s fully open, displaying its layered petals and a yellow
FP16:
""
각각의 dtype에 따라 추론을 해보면, BF16 및 FP32는 답변이 잘 생성되는 반면, FP16은 에러가 생기지 않았지만 <EOS> 토큰만 생성되는 것을 볼 수 있다. 이는 위에서 언급되었던 모델이 BF16에서 FP16으로 변환됨에 따라 생기는 오버플로우 및 언더플로우 때문이다. 이는 특히 레이어 노름 (Layer Norm), 언어 모델링 헤드 (Language Modeling Head), 토큰 임베딩 (Token Embedding)과 같은 민감한 구성 요서에서 수치적 불안정성 (Numerical Unstability)을 초래하여 EOS 토큰만 생성되었다고 생각한다.
이러한 문제를 해결하기 위해 주요 구성 요소를 FP32로 수동적으로 캐스팅 하였으나, 특별한 효과는 없었다. 이에 추론 중 핵심적인 연산을 담당하는 레이어 (Attention Layer, Softmax)에 캐스팅 해야할 것으로 생각된다. 그렇다면, 위에서 이야기 했던 Mixed-Precision 방법을 통해 모델 가중치를 FP16으로 유지하면서 계산은 FP32으로 진행하면 해결이 된다고 생각하였다.
이에 Pytorch의 Automatic Mixed-Precision(AMP)을 적용하였다. Pytorch AMP는 딥러닝 훈련과 추론에서 FP16의 메모리 효율성과 속도 이점을 활용하면서, FP32을 통해 수치적인 안정성을 유지하기 위해 설계되었다. 여기에서는 민간한 연산이 필요한 부분에는 FP32을 적용하고, 민감하지 않은 레이어에는 FP16을 적용한다고 생각하면 될거 같다.
model_id = "google/gemma-3-4b-it"
print("Testing with default AMP (mixed FP16/FP32):")
test_dtype(model_id, torch.float16, autocast=True, autocast_dtype=None)
=====결과=========
Testing with default AMP (mixed FP16/FP32):
GPU = NVIDIA A100-SXM4-40GB. Max memory = 39.381 GB.
30.955 GB of memory reserved.
결과를 출력해보니... 생각했던 방법이 틀린거 같다. 생각을 해보면 Gemma 3 같은 경우, BF16으로 훈련이 되었기 때문에 이와 비슷한 FP32를 기반으로 모든 레이어를 처리를 하고, 가중치 값만 FP16으로 저장하면 되지 않을까 한다.
print("\nTesting with non-standard AMP (all FP32):")
test_dtype(model_id, torch.float16, autocast=True, autocast_dtype=torch.float32)
=====결과==========
Generated output: Okay, here's a detailed description of the image:
**Overall Impression:**
The image is a close-up shot of a vibrant garden scene, focusing on a cluster of pink cosmos flowers and a busy bumblebee. It has a slightly soft, natural feel, likely captured in daylight.
**Foreground:**
* **Cosmos Flowers:** The dominant feature is a large, bright pink cosmos flower in the center. It’s fully open, displaying its layered petals and a yellow
Peak reserved memory = 30.955 GB.
-----
결론적으로 보면, BF16를 기반으로 훈련된 모델이 FP16으로 변환되는 과정에서 언더플로우 및 오버플로우 문제가 발생하여, 정상적으로 토큰을 생성하지 못하게 된다. 이에 일반적으로 Pytorch AMP를 통해 dtype을 명시하여 추론하는 것이 옳바르지 않다. 이에 이러한 문제점을 해결하기 위해 BF16와 비슷한 범위를 FP32으로 모든 레이러를 처리하고, 가중치만 FP16으로 저장하는 방식을 사용하였다.
'LLM' 카테고리의 다른 글
Pytorch의 Buffer를 사용해야 하는 이유 via. Attention (0) | 2025.06.23 |
---|---|
"Attention Is All You Need" 의 대항마 : Multi-Head Latent Attention (0) | 2025.05.31 |
과연, Perplexity를 기반으로 LLM을 평가하는 것이 합리적일까? (0) | 2025.04.09 |
Google의 새로운 대항마 Gemma 3 모델 리뷰 (0) | 2025.03.31 |
LLM 추론 시 GPU 메모리 사용량 알아보기 (0) | 2025.03.22 |