Quantization
양자화
모델 가중치를 낮은 정밀도로 변환. 크기 감소, 추론 속도 향상.
양자화
모델 가중치를 낮은 정밀도로 변환. 크기 감소, 추론 속도 향상.
양자화(Quantization)는 신경망의 가중치와 활성화 값을 높은 정밀도(FP32/FP16)에서 낮은 정밀도(INT8/INT4)로 변환하는 기법입니다. 이를 통해 모델 크기를 2-8배 줄이고, 추론 속도를 1.5-4배 향상시킬 수 있습니다. 특히 LLM 시대에 70B 이상의 대형 모델을 소비자용 GPU에서 실행하기 위한 필수 기술로 자리잡았습니다.
양자화는 크게 두 가지 방식으로 나뉩니다. PTQ(Post-Training Quantization)는 이미 학습된 모델을 별도 학습 없이 양자화하는 방법으로, GPTQ, AWQ, GGUF 등이 대표적입니다. 빠르고 간편하지만 극단적인 저정밀도(4-bit 이하)에서는 품질 저하가 발생할 수 있습니다. QAT(Quantization-Aware Training)는 학습 과정에서 양자화를 시뮬레이션하여, 모델이 저정밀도 환경에 적응하도록 합니다. 품질은 더 좋지만 추가 학습이 필요합니다.
현대 양자화 기법의 핵심은 "중요한 가중치를 보존"하는 것입니다. GPTQ는 Hessian 정보를 활용하여 손실 함수에 민감한 가중치를 우선 보존합니다. AWQ(Activation-aware Weight Quantization)는 활성화 값의 크기를 기준으로 중요한 채널을 식별하고 보호합니다. NF4(NormalFloat 4-bit)는 가중치가 정규분포를 따른다는 점을 활용하여 양자화 bins를 최적화합니다. 이런 기법들 덕분에 4-bit 양자화에서도 원본 대비 1-2%의 성능 저하만으로 모델을 압축할 수 있게 되었습니다.
실무에서 양자화 모델은 다양한 포맷으로 배포됩니다. GGUF(GPT-Generated Unified Format)는 llama.cpp에서 사용하는 포맷으로, CPU 추론에 최적화되어 있고 Q4_K_M, Q5_K_S 같은 다양한 양자화 레벨을 지원합니다. GPTQ는 GPU 추론에 최적화되어 있으며 Hugging Face에서 널리 사용됩니다. AWQ는 GPTQ보다 빠른 속도와 유사한 품질을 제공합니다. 모바일/엣지 배포에는 TensorRT-LLM, ONNX Runtime 등의 최적화 런타임과 함께 사용됩니다.
다양한 양자화 기법을 활용하여 LLM을 로드하고 추론하는 예제입니다:
# 1. bitsandbytes 4-bit 양자화 (QLoRA용)
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True # 추가 압축
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)
# 원본 28GB -> 양자화 후 ~4GB
# 2. GPTQ 양자화 모델 로드 (GPU 추론)
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
"TheBloke/Llama-2-7B-GPTQ",
device_map="auto",
trust_remote_code=True
)
# 3. AWQ 양자화 모델 (더 빠른 추론)
from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_pretrained(
"TheBloke/Llama-2-7B-AWQ",
device_map="auto"
)
# 4. llama.cpp GGUF 모델 (CPU/저사양 GPU)
from llama_cpp import Llama
llm = Llama(
model_path="./llama-2-7b.Q4_K_M.gguf",
n_ctx=4096, # 컨텍스트 길이
n_threads=8, # CPU 스레드
n_gpu_layers=35 # GPU 오프로드 레이어 수
)
output = llm(
"AI의 미래는",
max_tokens=100,
temperature=0.7
)
print(output["choices"][0]["text"])
# 5. 직접 GPTQ 양자화 수행
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
gptq_config = GPTQConfig(
bits=4,
dataset="c4", # 캘리브레이션 데이터셋
tokenizer=tokenizer,
group_size=128
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=gptq_config,
device_map="auto"
)
model.save_pretrained("./llama-2-7b-gptq")
PyTorch 네이티브 양자화 (비-LLM 모델용):
import torch
from torch.quantization import quantize_dynamic
# 동적 양자화 (추론 시 활성화 양자화)
model_fp32 = MyModel()
model_int8 = quantize_dynamic(
model_fp32,
{torch.nn.Linear, torch.nn.LSTM}, # 양자화 대상 레이어
dtype=torch.qint8
)
# 정적 양자화 (캘리브레이션 필요)
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
torch.quantization.prepare(model, inplace=True)
# ... 캘리브레이션 데이터로 forward pass ...
torch.quantization.convert(model, inplace=True)
"Llama 70B를 서비스하려면 A100 2장이 필요한데, AWQ 4-bit 양자화하면 단일 A100에서 돌릴 수 있습니다. 성능 저하는 MMLU 기준 1.5% 정도예요. 인프라 비용 50% 절감 효과가 있습니다."
"양자화 방식 선택은 배포 환경에 따라 달라집니다. GPU 서버면 GPTQ나 AWQ가 빠르고, Mac이나 CPU 환경이면 GGUF Q4_K_M이 좋은 품질-속도 트레이드오프를 제공합니다. 극한의 압축이 필요하면 2-bit도 가능하지만 품질 저하가 큽니다."
"AWQ가 GPTQ보다 빠른 이유는 그룹 단위 양자화 대신 채널별 스케일링을 사용해서 행렬 연산이 더 효율적이기 때문이에요. 다만 GPTQ는 perplexity가 살짝 더 낫고, 생태계가 더 성숙해서 선택은 상황에 따라 다릅니다."