Hugging Face, LLM.int8() 8비트 행렬 곱셈을 Transformers와 Accelerate에 통합
TL;DR
Hugging Face는 8‑bit LLM.int8() 양자화 방법이 이제 transformers와 accelerate 라이브러리에 완전히 통합되었으며, BLOOM‑176B와 같은 거대한 모델을 대략 절반 수준의 메모리 사용량으로 추론할 수 있게 되었고 정확도 손실은 측정되지 않았다고 발표했습니다.
왜 대형 언어 모델에 8‑bit 양자화가 중요한가
대형 언어 모델(LLM)은 현재 수천억 개의 파라미터를 초과합니다(예: PaLM 540B, OPT 176B, BLOOM 176B). 전체 정밀도 FP32로 모델을 저장하려면 가중치당 4 바이트가 필요해 수백 기가바이트의 메모리가 요구되며, 이는 대부분의 GPU 용량을 훨씬 초과합니다. 반정밀도(FP16/BF16)로 정밀도를 낮추면 메모리가 절반으로 줄지만, 여전히 BLOOM 176B는 약 350 GB가 필요합니다. 8‑bit 정수(INT8) 양자화를 사용하면 추가로 2배 감소가 가능하지만, 전통적인 양자화는 특히 6 B 파라미터를 초과하는 모델에서 정확도가 크게 떨어지는 문제가 있었습니다.
LLM.int8()의 핵심 아이디어: 정확도 손실 없는 행렬 곱셈
LLM.int8()는 아웃라이어 값을 별도로 처리함으로써 정확도 저하를 방지합니다:
- 아웃라이어 추출 – 크기가 임계값(≈6)보다 큰 값들을 은닉 상태 행렬의 각 열마다 식별합니다.
- 혼합 정밀도 행렬 곱 – 아웃라이어는 FP16으로 곱하고, 나머지 대부분의 행렬은 INT8로 양자화하여 벡터‑단위(활성화는 행‑단위, 가중치는 열‑단위) 양자화로 곱합니다.
- 역양자화 및 합산 – INT8 결과를 FP16으로 역양자화한 뒤 아웃라이어 FP16 결과와 합산해 최종 FP16 출력을 얻습니다. 이 세 단계 과정은 원래 FP16/BF16 모델과 동일한 추론 품질을 유지하면서 메모리 사용량을 1/4로 줄입니다.
양자화 메커니즘: 제로‑포인트 vs. 절대최대값
- 제로‑포인트 양자화 – 부동소수점 범위(예: [-1, 1])를 INT8 범위 [-127, 127]에 맞춰 스케일링하고 각 값을 반올림합니다. 역스케일링을 통해 원래 값의 근사치를 복원합니다.
- 절대최대값 양자화 – 각 텐서를 그 절대값 최대값으로 나눈 뒤 127을 곱하고 반올림합니다. 예를 들어 벡터 [1.2, ‑0.5, ‑4.3, …, 5.4]의 경우 스케일링 팩터는 127/5.4 ≈ 23.5가 되어 [-127, 127] 정수값을 얻습니다. 두 방식 모두 행‑단위 또는 열‑단위로 적용할 수 있으며, 이는 대규모 행렬 곱에서 정확성을 확보하는 데 필수적입니다.
제로 정확도 저하에 대한 실증적 증거
lm‑eval‑harness를 사용한 OPT‑175B와 BLOOM‑176B 벤치마크 결과, INT8와 FP16/BF16 점수 간 절대 차이는 모든 작업에서 표준 오차 이하였습니다(예: HellaSwag 정확도 0.7849 vs. 0.7849, Lambada 퍼플렉시티 3.0142 vs. 3.0152). 한 경우(BLOOM‑176B의 Lambada)에서는 INT8 모델이 약간 더 좋은 성능을 보였습니다. 논문 LLM.int8(): 8‑bit Matrix Multiplication for Transformers at Scale에서 전체 평가를 확인할 수 있습니다.
속도 트레이드‑오프
메모리 절감은 가장 큰 모델에서는 약간의 속도 저하를 동반합니다: BLOOM‑176B는 INT8에서 FP16 대비 15 %–23 % 느리게 실행됩니다. 작은 모델(e.g., T5‑3B, T5‑11B)은 초기에는 더 큰 속도 저하를 겪었지만, 최근 최적화로 토큰당 지연 시간이 T5‑3B는 312 ms에서 173 ms로, T5‑11B는 45 ms에서 25 ms로 감소했습니다. 향후 릴리즈에서는 이 격차를 더욱 줄이는 것이 목표입니다.
| 모델 | 정밀도 | GPU | 토큰 / ms (배치 1) |
|---|---|---|---|
| BLOOM‑176B | BF16 | 8 × A100 80GB | 239 |
| BLOOM‑176B | INT8 | 4 × A100 80GB | 282 |
| T5‑11B | FP16 | 2 × T4 15GB | 11.7 |
| T5‑11B | INT8 | 1 × T4 15GB | 43.5 |
transformers에의 통합
핵심 구성 요소는 bitsandbytes.nn.Linear8bitLt이며, 이는 torch.nn.Linear를 대체하는 드롭‑인 구현입니다. 최소 변환 워크플로는 다음과 같습니다:
import torch, bitsandbytes as bnb
from bnb.nn import Linear8bitLt
# FP16 모델 정의 및 가중치 저장
fp16 = torch.nn.Sequential(torch.nn.Linear(64, 64), torch.nn.Linear(64, 64))
torch.save(fp16.state_dict(), "model.pt")
# INT8 버전 구축
int8 = torch.nn.Sequential(
Linear8bitLt(64, 64, has_fp16_weights=False),
Linear8bitLt(64, 64, has_fp16_weights=False),
)
int8.load_state_dict(torch.load("model.pt"))
int8 = int8.to(0) # 양자화가 GPU에서 수행됨
.to 호출 이후 가중치는 [-127, 127] 범위의 int8 텐서로 저장됩니다. 원래 FP16 값은 (weight.CB * weight.SCB) / 127을 통해 복원할 수 있습니다.
accelerate를 활용한 제로‑메모리 모델 구축
accelerate.init_empty_weights()는 meta 디바이스에 모델을 생성해 RAM을 전혀 할당하지 않습니다. 통합 패치는 accelerate가 파라미터를 meta 디바이스에서 이동할 때 사용자 정의 클래스(Int8Params)를 유지하도록 합니다. 재귀 헬퍼는 모든 nn.Linear를 Linear8bitLt로 교체하면서 lm_head와 같이 전체 정밀도로 유지해야 하는 모듈은 그대로 둡니다:
from accelerate import init_empty_weights
import torch.nn as nn, bitsandbytes as bnb
def replace_8bit_linear(model, threshold=6.0, exclude="lm_head"):
for name, module in model.named_children():
if list(module.children()):
replace_8bit_linear(module, threshold, exclude)
if isinstance(module, nn.Linear) and name != exclude:
with init_empty_weights():
model._modules[name] = bnb.nn.Linear8bitLt(
module.in_features,
module.out_features,
module.bias is not None,
has_fp16_weights=False,
threshold=threshold,
)
return model
두 개의 PR이 accelerate에 추가되어 set_module_tensor_to_device가 각 INT8 텐서당 정확히 한 번만 호출되도록 하여 이중 양자화 버그를 방지합니다.
하드웨어 및 설치 요구사항
- GPU 지원 – INT8 텐서 코어가 필요합니다(NVIDIA Turing, Ampere, RTX 20/30, A40‑A100, T4). CPU와 구형 Kepler GPU는 네이티브 지원이 없습니다.
- 설치 – Python ≥ 3.8:
pip install accelerate bitsandbytes
pip install git+https://github.com/huggingface/transformers.git
시연 예시
Google Colab 노트북에서는 T5‑11B(원래 FP32 기준 42 GB)를 INT8로 단 11 GB만 사용해 실행하는 예시와, BLOOM‑3B를 단일 T4에 맞게 실행하는 데모를 제공합니다.
향후 작업 및 제한 사항
- 소형 모델 속도 – ≤6 B 모델에 대해 INT8 지연 시간을 FP16 수준으로 맞추는 작업이 진행 중입니다.
- Kepler GPU 지원 – 네이티브 INT8 텐서 코어가 없는 GPU(GTX 1080 등)를 위한 별도 소프트웨어 스택을 추가할 계획입니다.
- State‑dict 지속성 – 현재 INT8 체크포인트는 양자화 통계(
CB,SCB)를 포함하지 않아 Hub에서 직접 로드할 수 없습니다. 메타데이터 추가가 우선 과제입니다. - CPU 실행 – CPU에는 8‑bit 텐서 코어가 없으며, 향후 소프트웨어 경로를 통해 접근성을 확대할 수 있습니다.
- 텍스트 외 분야 – 대형 비전, 오디오, 멀티모달 모델에 기술을 확장하는 연구가 진행 중입니다.
Credits: Younes B., Tim Dettmers, and contributors listed in the original blog post.