Day 23: 양자화(Quantization) 가이드
양자화는 모델의 가중치 정밀도를 낮춰서 모델 크기와 메모리 사용량을 줄이는 기법입니다. 70B 모델을 소비자 GPU에서 실행할 수 있게 해주는 핵심 기술입니다.
정밀도별 비교
숫자 하나를 표현하는 데 사용하는 비트 수에 따라 모델의 크기와 성능이 달라집니다. FP32(32비트)가 가장 정확하지만 가장 크고, INT4(4비트)가 가장 작지만 약간의 품질 손실이 있습니다.
# 정밀도별 모델 크기와 성능 비교
precision_table = {
"정밀도": ["FP32", "FP16", "INT8", "INT4"],
"비트/값": ["32bit", "16bit", "8bit", "4bit"],
"7B 크기": ["28GB", "14GB", "7GB", "3.5GB"],
"13B 크기": ["52GB", "26GB", "13GB", "6.5GB"],
"70B 크기": ["280GB", "140GB", "70GB", "35GB"],
"품질 손실": ["없음", "거의 없음", "미미함", "약간 있음"],
"추론 속도": ["느림", "빠름", "매우 빠름", "가장 빠름"],
}
for key, values in precision_table.items():
print(f"{key:10} | {' | '.join(values)}")
bitsandbytes로 4bit/8bit 로드
가장 간단한 양자화 방법은 bitsandbytes를 통해 모델을 로드할 때 바로 양자화하는 것입니다.
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch
# 8bit 양자화 (INT8)
model_8bit = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
load_in_8bit=True,
device_map="auto",
)
print(f"8bit 메모리: {model_8bit.get_memory_footprint() / 1e9:.1f} GB")
# 4bit 양자화 (NF4 - QLoRA에서 사용하는 방식)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 이중 양자화로 추가 절약
)
model_4bit = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
quantization_config=bnb_config,
device_map="auto",
)
print(f"4bit 메모리: {model_4bit.get_memory_footprint() / 1e9:.1f} GB")
GGUF 포맷과 llama.cpp
GGUF(GPT-Generated Unified Format)는 llama.cpp에서 사용하는 포맷으로, CPU에서도 LLM을 실행할 수 있게 해줍니다. 다양한 양자화 수준(Q2_K ~ Q8_0)을 제공합니다.
# llama.cpp를 활용한 GGUF 양자화
# 1단계: llama.cpp 설치
# git clone https://github.com/ggerganov/llama.cpp
# cd llama.cpp && make
# 2단계: HF 모델을 GGUF로 변환
# python convert_hf_to_gguf.py ../model_path --outtype f16 --outfile model.gguf
# 3단계: 양자화 실행
# ./llama-quantize model.gguf model-q4_k_m.gguf Q4_K_M
# GGUF 양자화 수준별 비교
gguf_levels = {
"양자화": ["Q2_K", "Q3_K_M", "Q4_K_M", "Q5_K_M", "Q6_K", "Q8_0"],
"비트": ["2bit", "3bit", "4bit", "5bit", "6bit", "8bit"],
"7B 크기": ["2.7GB","3.3GB", "4.1GB", "4.8GB", "5.5GB","7.2GB"],
"품질": ["낮음", "보통", "좋음", "매우좋음", "우수", "거의무손실"],
"추천": ["비추", "제한적", "범용추천", "고품질", "고품질","메모리여유시"],
}
for key, vals in gguf_levels.items():
print(f"{key:8} | {' | '.join(vals)}")
AWQ와 GPTQ 양자화
AWQ(Activation-aware Weight Quantization)와 GPTQ는 학습 데이터 기반으로 더 정밀하게 양자화하여 품질 손실을 최소화합니다.
# AWQ 양자화 모델 사용 (미리 양자화된 모델 로드)
from transformers import AutoModelForCausalLM, AutoTokenizer
# AWQ 모델 로드 (Hub에서 이미 양자화된 모델)
model_awq = AutoModelForCausalLM.from_pretrained(
"TheBloke/Llama-2-7B-Chat-AWQ",
device_map="auto",
)
# GPTQ 모델 로드
# pip install auto-gptq
model_gptq = AutoModelForCausalLM.from_pretrained(
"TheBloke/Llama-2-7B-Chat-GPTQ",
device_map="auto",
)
# 양자화 방식 비교 요약
methods = {
"방식": ["bitsandbytes", "GGUF", "AWQ", "GPTQ"],
"속도": ["로드 시 변환", "사전 양자화", "사전 양자화", "사전 양자화"],
"CPU 지원": ["아니오", "예", "아니오", "아니오"],
"추론 속도": ["보통", "빠름(CPU)", "빠름(GPU)", "빠름(GPU)"],
"파인튜닝 지원": ["예(QLoRA)", "아니오", "제한적", "제한적"],
}
for key, vals in methods.items():
print(f"{key:12} | {' | '.join(vals)}")
오늘의 연습문제
- 같은 모델을 FP16, INT8, INT4로 로드하여
get_memory_footprint()로 실제 메모리 사용량을 비교하고, 동일 프롬프트에 대한 출력 품질을 정성적으로 평가해보세요. - Hugging Face Hub에서 GGUF 포맷 모델을 다운로드하고, llama.cpp 또는 Ollama로 실행하여 응답 속도를 측정해보세요.
- Q4_K_M과 Q8_0 양자화 모델의 출력을 10개 프롬프트에 대해 비교하고, 품질 차이가 유의미한 경우가 있는지 분석해보세요.