Split View: LLM 양자화 기법 완벽 비교: GPTQ, AWQ, GGUF 실전 적용 가이드
LLM 양자화 기법 완벽 비교: GPTQ, AWQ, GGUF 실전 적용 가이드
- 들어가며
- 양자화 기본 원리
- GPTQ: Generative Pre-trained Transformer Quantization
- AWQ: Activation-aware Weight Quantization
- GGUF: GPU-poor를 위한 범용 포맷
- BitsAndBytes와 QLoRA 통합
- 종합 비교: GPTQ vs AWQ vs GGUF vs BitsAndBytes
- 모델별 양자화 품질 가이드
- 운영 시 주의사항과 트러블슈팅
- 마치며
- 참고자료

들어가며
70B 파라미터 LLM을 FP16으로 로딩하면 약 140GB의 VRAM이 필요하다. A100 80GB 2장으로도 빠듯한 수준이다. 하지만 **양자화(Quantization)**를 적용하면 동일한 모델을 단일 GPU에서 운용할 수 있다. INT4 양자화를 적용하면 70B 모델이 약 35GB로 줄어 A100 한 장에 올릴 수 있게 된다.
2024~2026년 사이에 양자화 기법은 급격히 발전했다. GPTQ, AWQ, GGUF, BitsAndBytes 등 다양한 방법이 등장하며, 각각 정확도-속도-메모리의 트레이드오프가 다르다. 이 글에서는 각 기법의 원리를 깊이 있게 분석하고, 실전 코드와 벤치마크를 통해 최적의 선택지를 안내한다.
양자화 기본 원리
FP16에서 INT4까지의 여정
양자화란 높은 정밀도의 부동소수점(FP16/FP32) 가중치를 낮은 비트의 정수(INT8/INT4)로 변환하는 과정이다.
| 데이터 타입 | 비트 수 | 표현 범위 | 70B 모델 메모리 |
|---|---|---|---|
| FP32 | 32bit | 약 ±3.4×10^38 | ~280GB |
| FP16 | 16bit | 약 ±6.5×10^4 | ~140GB |
| INT8 | 8bit | -128 ~ 127 | ~70GB |
| INT4 | 4bit | -8 ~ 7 | ~35GB |
Absmax 양자화
가장 단순한 양자화 방법으로, 텐서의 절대 최대값을 기준으로 스케일링한다.
import torch
def absmax_quantize(tensor: torch.Tensor, bits: int = 8) -> tuple:
"""Absmax 양자화: 절대 최대값 기준 스케일링"""
qmax = 2 ** (bits - 1) - 1 # INT8이면 127
scale = tensor.abs().max() / qmax
quantized = torch.round(tensor / scale).clamp(-qmax, qmax).to(torch.int8)
return quantized, scale
def absmax_dequantize(quantized: torch.Tensor, scale: float) -> torch.Tensor:
"""역양자화: 원래 스케일로 복원"""
return quantized.float() * scale
# 예시: FP16 가중치 양자화
weight = torch.randn(4096, 4096, dtype=torch.float16)
q_weight, scale = absmax_quantize(weight.float())
restored = absmax_dequantize(q_weight, scale)
error = (weight.float() - restored).abs().mean()
print(f"평균 양자화 오차: {error:.6f}")
Zero-Point 양자화
Absmax는 분포가 대칭이 아닐 때 비효율적이다. Zero-Point 양자화는 비대칭 분포를 처리한다.
import torch
def zeropoint_quantize(tensor: torch.Tensor, bits: int = 8) -> tuple:
"""Zero-Point 양자화: 비대칭 분포 대응"""
qmin = -(2 ** (bits - 1))
qmax = 2 ** (bits - 1) - 1
rmin, rmax = tensor.min(), tensor.max()
scale = (rmax - rmin) / (qmax - qmin)
zero_point = torch.round(qmin - rmin / scale).clamp(qmin, qmax)
quantized = torch.round(tensor / scale + zero_point).clamp(qmin, qmax).to(torch.int8)
return quantized, scale, zero_point
def zeropoint_dequantize(quantized: torch.Tensor, scale: float, zero_point: float) -> torch.Tensor:
"""Zero-Point 역양자화"""
return (quantized.float() - zero_point) * scale
# 비대칭 분포 테스트 (ReLU 출력처럼 양수 편향)
weight = torch.randn(4096, 4096).abs() # 양수만
q_weight, scale, zp = zeropoint_quantize(weight)
restored = zeropoint_dequantize(q_weight, scale, zp)
error = (weight - restored).abs().mean()
print(f"Zero-Point 양자화 평균 오차: {error:.6f}")
Group-wise 양자화
텐서 전체가 아니라 작은 그룹(보통 128개 원소) 단위로 양자화하면 정확도가 크게 향상된다. GPTQ, AWQ 모두 group_size=128을 기본 설정으로 사용한다.
GPTQ: Generative Pre-trained Transformer Quantization
알고리즘 상세
GPTQ는 2022년 Frantar 등이 제안한 Post-Training Quantization(PTQ) 방법으로, OBQ(Optimal Brain Quantization) 알고리즘을 기반으로 한다. 핵심 아이디어는 다음과 같다.
- Layer-wise 양자화: 모델 전체를 한꺼번에 양자화하지 않고, 레이어 단위로 순차 처리한다
- Hessian 기반 보정: 양자화로 인한 출력 오차를 Hessian 역행렬을 활용해 나머지 가중치로 보상한다
- Column 순서 최적화: 양자화 순서를 최적화하여 오차 전파를 최소화한다
수학적으로, 각 가중치 w_q를 양자화할 때 나머지 가중치를 아래와 같이 업데이트한다:
delta_w = -(w - quant(w)) / H_inv[q,q] * H_inv[:, q]
여기서 H_inv는 Hessian 역행렬이다. 이를 통해 양자화 오차가 다른 가중치로 재분배된다.
AutoGPTQ로 양자화 수행
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
import torch
# 1. 양자화 설정
model_name = "meta-llama/Llama-3.1-8B-Instruct"
quantize_config = BaseQuantizeConfig(
bits=4, # 4비트 양자화
group_size=128, # 128개 원소 단위 그룹
desc_act=True, # Activation order 사용
damp_percent=0.01, # Hessian damping 비율
sym=True, # 대칭 양자화
)
# 2. 모델 및 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoGPTQForCausalLM.from_pretrained(
model_name,
quantize_config=quantize_config,
torch_dtype=torch.float16,
)
# 3. 캘리브레이션 데이터 준비
calibration_data = [
tokenizer("The meaning of life is", return_tensors="pt"),
tokenizer("Artificial intelligence has", return_tensors="pt"),
tokenizer("In the context of machine learning", return_tensors="pt"),
# 실제로는 128~256개의 다양한 샘플 사용 권장
]
# 4. 양자화 실행 (Llama-3.1-8B 기준 약 15~30분)
model.quantize(calibration_data)
# 5. 양자화된 모델 저장
output_dir = "./llama-3.1-8b-gptq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"양자화 완료. 저장 위치: {output_dir}")
GPTQ의 장단점
장점:
- 4비트까지 양자화해도 원본 대비 높은 정확도 유지
- Marlin 커널 사용 시 추론 속도 대폭 향상 (2.6배 속도 증가)
- Hugging Face Transformers 네이티브 지원
단점:
- 캘리브레이션 데이터 필요 (보통 128~256 샘플)
- 양자화 시간이 상대적으로 긺 (8B 모델 기준 15~30분)
- GPU 전용 (CPU 추론 미지원)
AWQ: Activation-aware Weight Quantization
원리와 차별점
AWQ는 2024년 MIT의 Lin 등이 제안한 방법으로, 핵심 관찰은 모든 가중치가 동일하게 중요하지 않다는 점이다.
- Salient Weight 식별: 활성화(Activation) 크기를 기준으로 중요한 가중치 채널(약 1%)을 식별한다
- 선택적 보호: 중요 가중치는 스케일링 팩터를 적용하여 양자화 오차를 줄인다
- 나머지 양자화: 나머지 99% 가중치는 일반 양자화를 적용한다
GPTQ와의 핵심 차이점은 Hessian 역행렬 계산이 불필요하다는 점이다. AWQ는 단순한 통계 기반 접근으로 더 빠르게 양자화하면서도 비슷하거나 더 나은 품질을 달성한다.
AutoAWQ로 양자화 수행
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
# 1. 모델 로드
model_path = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoAWQForCausalLM.from_pretrained(
model_path,
low_cpu_mem_usage=True,
use_cache=False,
)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 2. 양자화 설정
quant_config = {
"zero_point": True, # Zero-Point 양자화 사용
"q_group_size": 128, # 그룹 크기
"w_bit": 4, # 4비트 양자화
"version": "GEMM", # GEMM 커널 (범용) vs GEMV (배치=1 최적화)
}
# 3. 양자화 실행 (GPTQ보다 빠름, 약 10분)
model.quantize(tokenizer, quant_config=quant_config)
# 4. 저장
output_dir = "./llama-3.1-8b-awq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"AWQ 양자화 완료: {output_dir}")
AWQ의 장단점
장점:
- GPTQ보다 빠른 양자화 속도
- Activation-aware 접근으로 높은 품질 유지 (HumanEval Pass@1: 51.8%)
- Marlin-AWQ 커널 적용 시 741 tok/s의 높은 처리량
- vLLM 네이티브 지원
단점:
- GPU 전용 (CPU 추론 미지원)
- GGUF 대비 에코시스템이 작음
- 일부 모델 아키텍처에서 호환성 문제
GGUF: GPU-poor를 위한 범용 포맷
GGUF 포맷과 llama.cpp 생태계
GGUF(GPT-Generated Unified Format)는 양자화 알고리즘이 아니라 파일 포맷이다. llama.cpp 프로젝트에서 만든 이 포맷은 CPU와 GPU 하이브리드 추론을 지원한다.
GGUF의 핵심 특징:
- 단일 파일: 모델 가중치, 토크나이저, 메타데이터가 하나의 파일에 포함
- CPU 추론 지원: GPU 없이도 추론 가능
- 하이브리드 오프로딩: 일부 레이어는 GPU, 나머지는 CPU로 처리
- 다양한 양자화 수준: Q2_K부터 Q8_0까지 세밀한 품질-크기 조절
GGUF 양자화 타입별 비교
| 양자화 타입 | 비트 수 | Llama-3.1-8B 크기 | Perplexity 증가 | 권장 용도 |
|---|---|---|---|---|
| Q2_K | 2.6bit | ~2.8GB | +2.5~3.0 | 극한 메모리 제한 |
| Q3_K_M | 3.3bit | ~3.5GB | +1.0~1.5 | 메모리 제한 환경 |
| Q4_K_M | 4.5bit | ~4.9GB | +0.3~0.5 | 범용 권장 |
| Q5_K_M | 5.3bit | ~5.7GB | +0.1~0.2 | 품질 중시 |
| Q6_K | 6.6bit | ~6.6GB | +0.05 | 거의 무손실 |
| Q8_0 | 8.0bit | ~8.5GB | ~0 | 무손실에 가까움 |
llama.cpp로 GGUF 양자화
# 1. llama.cpp 빌드
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp && mkdir build && cd build
cmake .. -DGGML_CUDA=ON # GPU 가속 사용 시
cmake --build . --config Release
# 2. HF 모델을 GGUF FP16으로 변환
python convert_hf_to_gguf.py \
/path/to/Llama-3.1-8B-Instruct \
--outtype f16 \
--outfile llama-3.1-8b-f16.gguf
# 3. Q4_K_M으로 양자화
./build/bin/llama-quantize \
llama-3.1-8b-f16.gguf \
llama-3.1-8b-q4_k_m.gguf \
Q4_K_M
# 4. 추론 테스트
./build/bin/llama-cli \
-m llama-3.1-8b-q4_k_m.gguf \
-p "Explain quantum computing in simple terms:" \
-n 256 \
--n-gpu-layers 35 # GPU 오프로딩 레이어 수
llama-cpp-python으로 Python 연동
from llama_cpp import Llama
# GGUF 모델 로드 (GPU 레이어 오프로딩 포함)
llm = Llama(
model_path="./llama-3.1-8b-q4_k_m.gguf",
n_gpu_layers=35, # GPU에 올릴 레이어 수 (-1이면 전체)
n_ctx=4096, # 컨텍스트 길이
n_threads=8, # CPU 스레드 수
verbose=False,
)
# 텍스트 생성
output = llm(
"Explain the difference between TCP and UDP:",
max_tokens=512,
temperature=0.7,
top_p=0.9,
stop=["\\n\\n"],
)
print(output["choices"][0]["text"])
# ChatCompletion API 스타일
response = llm.create_chat_completion(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is quantization in ML?"},
],
max_tokens=512,
temperature=0.7,
)
print(response["choices"][0]["message"]["content"])
BitsAndBytes와 QLoRA 통합
4비트 추론과 파인튜닝
BitsAndBytes는 Hugging Face Transformers와 직접 통합되어 별도 양자화 과정 없이 로딩 시점에 양자화를 적용한다. QLoRA와 결합하면 양자화된 모델 위에 LoRA 어댑터를 학습할 수 있다.
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
)
# 4비트 양자화 설정
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4 (QLoRA 논문)
bnb_4bit_compute_dtype=torch.bfloat16, # 연산은 BF16으로
bnb_4bit_use_double_quant=True, # 이중 양자화 (추가 메모리 절약)
)
# 모델 로드 (로딩 시 자동 양자화)
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 메모리 사용량 확인
print(f"모델 메모리: {model.get_memory_footprint() / 1e9:.2f} GB")
# 추론
inputs = tokenizer("Explain gradient descent:", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
BitsAndBytes의 특징
장점:
- 사전 양자화 과정 불필요 (로딩 시 즉시 적용)
- QLoRA 파인튜닝과 자연스러운 통합
- Hugging Face 에코시스템 완벽 지원
- NF4 양자화로 높은 품질 유지
단점:
- 추론 속도가 GPTQ/AWQ 대비 느림 (커스텀 커널 부재)
- 양자화된 모델을 파일로 저장/공유하기 어려움
- CUDA 전용 (AMD/CPU 미지원, 다만 2025년부터 ROCm 부분 지원)
종합 비교: GPTQ vs AWQ vs GGUF vs BitsAndBytes
핵심 특성 비교표
| 항목 | GPTQ | AWQ | GGUF | BitsAndBytes |
|---|---|---|---|---|
| 양자화 방식 | PTQ (Hessian 기반) | PTQ (Activation-aware) | PTQ (다양한 방식) | 동적 양자화 |
| 기본 비트 | 4bit | 4bit | 2~8bit 선택 | 4bit (NF4) |
| 캘리브레이션 | 필요 (128~256샘플) | 필요 (소량) | 불필요 | 불필요 |
| 양자화 시간 | 15~30분 (8B) | ~10분 (8B) | 수분 | 로딩 시 즉시 |
| GPU 추론 | 매우 빠름 | 가장 빠름 | 빠름 (오프로딩 시) | 보통 |
| CPU 추론 | 미지원 | 미지원 | 지원 | 미지원 |
| vLLM 지원 | 지원 | 지원 | 부분 지원 | 지원 |
| 파인튜닝 | 제한적 | 제한적 | 미지원 | QLoRA 최적 |
| 모델 공유 | HF Hub 업로드 | HF Hub 업로드 | 단일 파일 배포 | 어려움 |
| Perplexity (4bit) | 기준 +0.3~0.5 | 기준 +0.2~0.4 | Q4_K_M: +0.3~0.5 | 기준 +0.2~0.4 |
추론 성능 벤치마크 (Llama-3.1-8B, A100 80GB)
| 방법 | 처리량 (tok/s) | VRAM 사용 | HumanEval Pass@1 | Latency (TTFT) |
|---|---|---|---|---|
| FP16 (기준) | ~350 | 16GB | 53.2% | 45ms |
| GPTQ 4bit | ~520 | 5.5GB | 50.6% | 32ms |
| GPTQ + Marlin | ~712 | 5.5GB | 50.6% | 25ms |
| AWQ 4bit | ~550 | 5.2GB | 51.8% | 30ms |
| AWQ + Marlin | ~741 | 5.2GB | 51.8% | 23ms |
| GGUF Q4_K_M | ~280 | 4.9GB | 51.8% | 55ms |
| BitsAndBytes NF4 | ~300 | 5.8GB | 51.8% | 50ms |
위 벤치마크에서 주목할 점은 커널 구현이 알고리즘보다 중요하다는 것이다. Marlin 커널을 사용하면 GPTQ는 2.6배, AWQ는 10.9배 속도 향상을 얻는다.
vLLM에서 양자화 모델 서빙
from vllm import LLM, SamplingParams
# AWQ 모델 서빙 (Marlin 커널 자동 적용)
llm = LLM(
model="casperhansen/llama-3.1-8b-instruct-awq",
quantization="awq_marlin", # Marlin 커널 명시
max_model_len=8192,
gpu_memory_utilization=0.85,
dtype="half",
)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=1024,
)
prompts = [
"Explain the CAP theorem in distributed systems.",
"Write a Python function to implement binary search.",
"What are the SOLID principles in software engineering?",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated = output.outputs[0].text
print(f"Prompt: {prompt[:50]}...")
print(f"Output: {generated[:200]}...")
print("---")
모델별 양자화 품질 가이드
모델 크기별 권장 양자화
| 모델 크기 | 소비자 GPU (24GB) | 서버 GPU (80GB) | CPU 전용 |
|---|---|---|---|
| 7~8B | GPTQ/AWQ 4bit | FP16 권장 | GGUF Q4_K_M |
| 13B | GPTQ/AWQ 4bit | FP16 또는 AWQ 4bit | GGUF Q4_K_M |
| 34B | GGUF Q4_K_M | AWQ 4bit + Marlin | GGUF Q3_K_M |
| 70B | GGUF Q3_K_M (부분) | AWQ 4bit + Marlin | GGUF Q2_K |
태스크별 권장 양자화 수준
- 코드 생성: AWQ 4bit 권장 (HumanEval 기준 가장 높은 정확도)
- 장문 생성/요약: Q5_K_M 이상 권장 (낮은 양자화에서 반복 증가)
- 분류/NER: INT4도 충분 (정확도 영향 미미)
- 수학적 추론: Q6_K 이상 또는 AWQ 4bit 권장 (수치 민감)
운영 시 주의사항과 트러블슈팅
자주 발생하는 문제
1. GPTQ 양자화 중 OOM 발생
캘리브레이션 시 메모리 부족이 발생하면 desc_act=False로 변경하고, damp_percent를 높여보자.
2. AWQ 모델에서 이상한 출력
양자화 버전(GEMM vs GEMV)과 추론 프레임워크가 맞지 않을 수 있다. vLLM에서는 awq_marlin을 명시하는 것이 안전하다.
3. GGUF 모델이 느릴 때
n_gpu_layers 값을 높여 더 많은 레이어를 GPU로 오프로딩하자. 전체 레이어 수보다 높게 설정하면 전부 GPU로 올라간다.
4. BitsAndBytes에서 torch 버전 충돌
bitsandbytes>=0.43.0과 torch>=2.1.0 조합을 권장한다. CUDA 버전도 12.1 이상이 안정적이다.
양자화 품질 검증 체크리스트
- Perplexity 측정: 원본 대비 perplexity 증가가 0.5 이내인지 확인
- 태스크별 벤치마크: 실제 사용 태스크(코드 생성, 요약 등)에서 품질 확인
- Edge Case 테스트: 긴 입력, 다국어, 수학 문제 등 경계 사례 검증
- 레이턴시 프로파일링: TTFT(Time to First Token)와 TPS(Tokens per Second) 측정
- 메모리 모니터링: 실제 서빙 환경에서의 VRAM 사용량 추적
마치며
LLM 양자화는 단순히 모델 크기를 줄이는 것이 아니라, 배포 가능성을 결정하는 핵심 기술이다. 2026년 현재 권장 사항을 정리하면 다음과 같다.
- 프로덕션 GPU 서빙: AWQ + Marlin 커널 (최고 처리량 + 높은 품질)
- 개발/실험: BitsAndBytes NF4 (양자화 과정 불필요, QLoRA 통합)
- 엣지/CPU 배포: GGUF Q4_K_M (범용성, 단일 파일 배포)
- 레거시 호환: GPTQ (가장 넓은 에코시스템 지원)
양자화 기법은 계속 발전하고 있다. 2025년 이후 등장한 Marlin 커널처럼, 알고리즘 자체보다 커널 구현의 최적화가 실제 성능에 더 큰 영향을 미치는 추세다. 새로운 양자화 방법을 평가할 때는 이론적 장점뿐 아니라 실제 서빙 환경에서의 벤치마크를 반드시 확인하자.
참고자료
- Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (2022)
- Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" (2024)
- Hugging Face Transformers Quantization Guide
- vLLM Quantization Documentation
- llama.cpp GitHub Repository - GGUF Format
- The Complete Guide to LLM Quantization with vLLM: Benchmarks
- AutoGPTQ GitHub Repository
- AutoAWQ - Applying AWQ Quantization
- BitsAndBytes Foundation GitHub
- Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs" (2023)
Complete LLM Quantization Comparison: GPTQ, AWQ, GGUF Practical Application Guide
- Introduction
- Quantization Fundamentals
- GPTQ: Generative Pre-trained Transformer Quantization
- AWQ: Activation-aware Weight Quantization
- GGUF: The Universal Format for GPU-poor
- BitsAndBytes and QLoRA Integration
- Comprehensive Comparison: GPTQ vs AWQ vs GGUF vs BitsAndBytes
- Model-Specific Quantization Quality Guide
- Operational Considerations and Troubleshooting
- Conclusion
- References
- Quiz

Introduction
Loading a 70B parameter LLM in FP16 requires approximately 140GB of VRAM. Even two A100 80GB GPUs are barely sufficient. However, by applying quantization, the same model can be operated on a single GPU. With INT4 quantization, a 70B model shrinks to approximately 35GB, fitting on a single A100.
Between 2024 and 2026, quantization techniques have evolved rapidly. Various methods including GPTQ, AWQ, GGUF, and BitsAndBytes have emerged, each with different accuracy-speed-memory trade-offs. In this article, we analyze the principles of each technique in depth and guide you to the optimal choice through practical code and benchmarks.
Quantization Fundamentals
The Journey from FP16 to INT4
Quantization is the process of converting high-precision floating-point (FP16/FP32) weights to lower-bit integers (INT8/INT4).
| Data Type | Bits | Range | 70B Model Memory |
|---|---|---|---|
| FP32 | 32bit | ~+/-3.4x10^38 | ~280GB |
| FP16 | 16bit | ~+/-6.5x10^4 | ~140GB |
| INT8 | 8bit | -128 ~ 127 | ~70GB |
| INT4 | 4bit | -8 ~ 7 | ~35GB |
Absmax Quantization
The simplest quantization method, scaling based on the absolute maximum value of the tensor.
import torch
def absmax_quantize(tensor: torch.Tensor, bits: int = 8) -> tuple:
"""Absmax quantization: scaling based on absolute maximum value"""
qmax = 2 ** (bits - 1) - 1 # 127 for INT8
scale = tensor.abs().max() / qmax
quantized = torch.round(tensor / scale).clamp(-qmax, qmax).to(torch.int8)
return quantized, scale
def absmax_dequantize(quantized: torch.Tensor, scale: float) -> torch.Tensor:
"""Dequantization: restore to original scale"""
return quantized.float() * scale
# Example: Quantize FP16 weights
weight = torch.randn(4096, 4096, dtype=torch.float16)
q_weight, scale = absmax_quantize(weight.float())
restored = absmax_dequantize(q_weight, scale)
error = (weight.float() - restored).abs().mean()
print(f"Mean quantization error: {error:.6f}")
Zero-Point Quantization
Absmax is inefficient when the distribution is not symmetric. Zero-Point quantization handles asymmetric distributions.
import torch
def zeropoint_quantize(tensor: torch.Tensor, bits: int = 8) -> tuple:
"""Zero-Point quantization: handles asymmetric distributions"""
qmin = -(2 ** (bits - 1))
qmax = 2 ** (bits - 1) - 1
rmin, rmax = tensor.min(), tensor.max()
scale = (rmax - rmin) / (qmax - qmin)
zero_point = torch.round(qmin - rmin / scale).clamp(qmin, qmax)
quantized = torch.round(tensor / scale + zero_point).clamp(qmin, qmax).to(torch.int8)
return quantized, scale, zero_point
def zeropoint_dequantize(quantized: torch.Tensor, scale: float, zero_point: float) -> torch.Tensor:
"""Zero-Point dequantization"""
return (quantized.float() - zero_point) * scale
# Test with asymmetric distribution (positive-biased like ReLU output)
weight = torch.randn(4096, 4096).abs() # Positive only
q_weight, scale, zp = zeropoint_quantize(weight)
restored = zeropoint_dequantize(q_weight, scale, zp)
error = (weight - restored).abs().mean()
print(f"Zero-Point quantization mean error: {error:.6f}")
Group-wise Quantization
Quantizing in small groups (typically 128 elements) rather than the entire tensor significantly improves accuracy. Both GPTQ and AWQ use group_size=128 as the default setting.
GPTQ: Generative Pre-trained Transformer Quantization
Algorithm Details
GPTQ is a Post-Training Quantization (PTQ) method proposed by Frantar et al. in 2022, based on the OBQ (Optimal Brain Quantization) algorithm. The core ideas are:
- Layer-wise quantization: Rather than quantizing the entire model at once, it processes layer by layer sequentially
- Hessian-based correction: Compensates for quantization-induced output error by distributing it to remaining weights using the inverse Hessian matrix
- Column order optimization: Optimizes the quantization order to minimize error propagation
Mathematically, when quantizing each weight w_q, the remaining weights are updated as follows:
delta_w = -(w - quant(w)) / H_inv[q,q] * H_inv[:, q]
Here, H_inv is the inverse Hessian matrix. This allows quantization error to be redistributed across other weights.
Quantization with AutoGPTQ
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
import torch
# 1. Quantization configuration
model_name = "meta-llama/Llama-3.1-8B-Instruct"
quantize_config = BaseQuantizeConfig(
bits=4, # 4-bit quantization
group_size=128, # Groups of 128 elements
desc_act=True, # Use activation order
damp_percent=0.01, # Hessian damping ratio
sym=True, # Symmetric quantization
)
# 2. Load model and tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoGPTQForCausalLM.from_pretrained(
model_name,
quantize_config=quantize_config,
torch_dtype=torch.float16,
)
# 3. Prepare calibration data
calibration_data = [
tokenizer("The meaning of life is", return_tensors="pt"),
tokenizer("Artificial intelligence has", return_tensors="pt"),
tokenizer("In the context of machine learning", return_tensors="pt"),
# In practice, 128-256 diverse samples are recommended
]
# 4. Execute quantization (approximately 15-30 min for Llama-3.1-8B)
model.quantize(calibration_data)
# 5. Save quantized model
output_dir = "./llama-3.1-8b-gptq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"Quantization complete. Saved to: {output_dir}")
GPTQ Pros and Cons
Pros:
- Maintains high accuracy relative to original even at 4-bit quantization
- Significant inference speed improvement with Marlin kernel (2.6x speedup)
- Native Hugging Face Transformers support
Cons:
- Requires calibration data (typically 128-256 samples)
- Relatively long quantization time (15-30 minutes for 8B model)
- GPU-only (no CPU inference support)
AWQ: Activation-aware Weight Quantization
Principles and Differentiators
AWQ is a method proposed by Lin et al. at MIT in 2024, and the core observation is that not all weights are equally important.
- Salient weight identification: Identifies important weight channels (approximately 1%) based on activation magnitude
- Selective protection: Applies scaling factors to important weights to reduce quantization error
- Regular quantization for the rest: Applies standard quantization to the remaining 99% of weights
The key difference from GPTQ is that Hessian inverse matrix computation is unnecessary. AWQ achieves similar or better quality with a simpler statistics-based approach while quantizing faster.
Quantization with AutoAWQ
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
# 1. Load model
model_path = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoAWQForCausalLM.from_pretrained(
model_path,
low_cpu_mem_usage=True,
use_cache=False,
)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 2. Quantization configuration
quant_config = {
"zero_point": True, # Use Zero-Point quantization
"q_group_size": 128, # Group size
"w_bit": 4, # 4-bit quantization
"version": "GEMM", # GEMM kernel (general) vs GEMV (batch=1 optimized)
}
# 3. Execute quantization (faster than GPTQ, approximately 10 minutes)
model.quantize(tokenizer, quant_config=quant_config)
# 4. Save
output_dir = "./llama-3.1-8b-awq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"AWQ quantization complete: {output_dir}")
AWQ Pros and Cons
Pros:
- Faster quantization speed than GPTQ
- High quality retention with activation-aware approach (HumanEval Pass@1: 51.8%)
- High throughput of 741 tok/s with Marlin-AWQ kernel
- Native vLLM support
Cons:
- GPU-only (no CPU inference support)
- Smaller ecosystem compared to GGUF
- Compatibility issues with some model architectures
GGUF: The Universal Format for GPU-poor
GGUF Format and the llama.cpp Ecosystem
GGUF (GPT-Generated Unified Format) is a file format, not a quantization algorithm. Created by the llama.cpp project, this format supports CPU and GPU hybrid inference.
Key features of GGUF:
- Single file: Model weights, tokenizer, and metadata are all contained in one file
- CPU inference support: Inference is possible without a GPU
- Hybrid offloading: Some layers on GPU, the rest on CPU
- Various quantization levels: Fine-grained quality-size control from Q2_K to Q8_0
GGUF Quantization Type Comparison
| Quant Type | Bits | Llama-3.1-8B Size | Perplexity Increase | Recommended Use |
|---|---|---|---|---|
| Q2_K | 2.6bit | ~2.8GB | +2.5~3.0 | Extreme memory limits |
| Q3_K_M | 3.3bit | ~3.5GB | +1.0~1.5 | Memory-constrained env |
| Q4_K_M | 4.5bit | ~4.9GB | +0.3~0.5 | General recommended |
| Q5_K_M | 5.3bit | ~5.7GB | +0.1~0.2 | Quality-focused |
| Q6_K | 6.6bit | ~6.6GB | +0.05 | Near lossless |
| Q8_0 | 8.0bit | ~8.5GB | ~0 | Close to lossless |
GGUF Quantization with llama.cpp
# 1. Build llama.cpp
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp && mkdir build && cd build
cmake .. -DGGML_CUDA=ON # For GPU acceleration
cmake --build . --config Release
# 2. Convert HF model to GGUF FP16
python convert_hf_to_gguf.py \
/path/to/Llama-3.1-8B-Instruct \
--outtype f16 \
--outfile llama-3.1-8b-f16.gguf
# 3. Quantize to Q4_K_M
./build/bin/llama-quantize \
llama-3.1-8b-f16.gguf \
llama-3.1-8b-q4_k_m.gguf \
Q4_K_M
# 4. Inference test
./build/bin/llama-cli \
-m llama-3.1-8b-q4_k_m.gguf \
-p "Explain quantum computing in simple terms:" \
-n 256 \
--n-gpu-layers 35 # Number of layers to offload to GPU
Python Integration with llama-cpp-python
from llama_cpp import Llama
# Load GGUF model (with GPU layer offloading)
llm = Llama(
model_path="./llama-3.1-8b-q4_k_m.gguf",
n_gpu_layers=35, # Number of layers on GPU (-1 for all)
n_ctx=4096, # Context length
n_threads=8, # CPU thread count
verbose=False,
)
# Text generation
output = llm(
"Explain the difference between TCP and UDP:",
max_tokens=512,
temperature=0.7,
top_p=0.9,
stop=["\\n\\n"],
)
print(output["choices"][0]["text"])
# ChatCompletion API style
response = llm.create_chat_completion(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is quantization in ML?"},
],
max_tokens=512,
temperature=0.7,
)
print(response["choices"][0]["message"]["content"])
BitsAndBytes and QLoRA Integration
4-bit Inference and Fine-tuning
BitsAndBytes integrates directly with Hugging Face Transformers, applying quantization at loading time without a separate quantization process. When combined with QLoRA, LoRA adapters can be trained on top of quantized models.
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
)
# 4-bit quantization configuration
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4 (QLoRA paper)
bnb_4bit_compute_dtype=torch.bfloat16, # Compute in BF16
bnb_4bit_use_double_quant=True, # Double quantization (additional memory savings)
)
# Load model (automatic quantization on load)
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Check memory usage
print(f"Model memory: {model.get_memory_footprint() / 1e9:.2f} GB")
# Inference
inputs = tokenizer("Explain gradient descent:", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
BitsAndBytes Characteristics
Pros:
- No pre-quantization process needed (applied instantly on load)
- Natural integration with QLoRA fine-tuning
- Perfect Hugging Face ecosystem support
- High quality retention with NF4 quantization
Cons:
- Inference speed slower than GPTQ/AWQ (no custom kernels)
- Difficult to save/share quantized models as files
- CUDA-only (no AMD/CPU support, though partial ROCm support since 2025)
Comprehensive Comparison: GPTQ vs AWQ vs GGUF vs BitsAndBytes
Key Characteristics Comparison Table
| Item | GPTQ | AWQ | GGUF | BitsAndBytes |
|---|---|---|---|---|
| Quantization method | PTQ (Hessian-based) | PTQ (Activation-aware) | PTQ (various methods) | Dynamic quant |
| Default bits | 4bit | 4bit | 2~8bit selectable | 4bit (NF4) |
| Calibration | Required (128-256) | Required (small amount) | Not required | Not required |
| Quantization time | 15-30 min (8B) | ~10 min (8B) | Minutes | Instant on load |
| GPU inference | Very fast | Fastest | Fast (with offload) | Moderate |
| CPU inference | Not supported | Not supported | Supported | Not supported |
| vLLM support | Supported | Supported | Partial support | Supported |
| Fine-tuning | Limited | Limited | Not supported | QLoRA optimal |
| Model sharing | HF Hub upload | HF Hub upload | Single file deploy | Difficult |
| Perplexity (4bit) | Baseline +0.3~0.5 | Baseline +0.2~0.4 | Q4_K_M: +0.3~0.5 | Baseline +0.2~0.4 |
Inference Performance Benchmark (Llama-3.1-8B, A100 80GB)
| Method | Throughput (tok/s) | VRAM Usage | HumanEval Pass@1 | Latency (TTFT) |
|---|---|---|---|---|
| FP16 (baseline) | ~350 | 16GB | 53.2% | 45ms |
| GPTQ 4bit | ~520 | 5.5GB | 50.6% | 32ms |
| GPTQ + Marlin | ~712 | 5.5GB | 50.6% | 25ms |
| AWQ 4bit | ~550 | 5.2GB | 51.8% | 30ms |
| AWQ + Marlin | ~741 | 5.2GB | 51.8% | 23ms |
| GGUF Q4_K_M | ~280 | 4.9GB | 51.8% | 55ms |
| BitsAndBytes NF4 | ~300 | 5.8GB | 51.8% | 50ms |
The key takeaway from this benchmark is that kernel implementation matters more than the algorithm itself. Using the Marlin kernel, GPTQ achieves a 2.6x speedup and AWQ achieves a 10.9x speedup.
Serving Quantized Models with vLLM
from vllm import LLM, SamplingParams
# Serve AWQ model (Marlin kernel auto-applied)
llm = LLM(
model="casperhansen/llama-3.1-8b-instruct-awq",
quantization="awq_marlin", # Explicitly specify Marlin kernel
max_model_len=8192,
gpu_memory_utilization=0.85,
dtype="half",
)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=1024,
)
prompts = [
"Explain the CAP theorem in distributed systems.",
"Write a Python function to implement binary search.",
"What are the SOLID principles in software engineering?",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated = output.outputs[0].text
print(f"Prompt: {prompt[:50]}...")
print(f"Output: {generated[:200]}...")
print("---")
Model-Specific Quantization Quality Guide
Recommended Quantization by Model Size
| Model Size | Consumer GPU (24GB) | Server GPU (80GB) | CPU Only |
|---|---|---|---|
| 7~8B | GPTQ/AWQ 4bit | FP16 recommended | GGUF Q4_K_M |
| 13B | GPTQ/AWQ 4bit | FP16 or AWQ 4bit | GGUF Q4_K_M |
| 34B | GGUF Q4_K_M | AWQ 4bit + Marlin | GGUF Q3_K_M |
| 70B | GGUF Q3_K_M (partial) | AWQ 4bit + Marlin | GGUF Q2_K |
Recommended Quantization Level by Task
- Code generation: AWQ 4bit recommended (highest accuracy on HumanEval)
- Long-form generation/summarization: Q5_K_M or higher recommended (repetition increases at lower quantization)
- Classification/NER: INT4 is sufficient (minimal accuracy impact)
- Mathematical reasoning: Q6_K or higher, or AWQ 4bit recommended (numerically sensitive)
Operational Considerations and Troubleshooting
Common Issues
1. OOM during GPTQ quantization
If out-of-memory occurs during calibration, try changing desc_act=False and increasing damp_percent.
2. Strange output from AWQ models
The quantization version (GEMM vs GEMV) might not match the inference framework. When using vLLM, it is safest to explicitly specify awq_marlin.
3. Slow GGUF model
Increase the n_gpu_layers value to offload more layers to the GPU. Setting it higher than the total number of layers puts everything on the GPU.
4. torch version conflict with BitsAndBytes
The combination of bitsandbytes>=0.43.0 and torch>=2.1.0 is recommended. CUDA version 12.1 or higher is also more stable.
Quantization Quality Verification Checklist
- Perplexity measurement: Confirm perplexity increase is within 0.5 compared to original
- Task-specific benchmarks: Verify quality on actual use tasks (code generation, summarization, etc.)
- Edge case testing: Validate on boundary cases like long inputs, multilingual, math problems
- Latency profiling: Measure TTFT (Time to First Token) and TPS (Tokens per Second)
- Memory monitoring: Track actual VRAM usage in the serving environment
Conclusion
LLM quantization is not simply about reducing model size -- it is a core technology that determines deployment feasibility. As of 2026, the recommendations can be summarized as follows:
- Production GPU serving: AWQ + Marlin kernel (highest throughput + high quality)
- Development/experimentation: BitsAndBytes NF4 (no quantization process needed, QLoRA integration)
- Edge/CPU deployment: GGUF Q4_K_M (versatility, single file deployment)
- Legacy compatibility: GPTQ (broadest ecosystem support)
Quantization techniques continue to evolve. Like the Marlin kernel that emerged after 2025, the trend is that kernel implementation optimization has a greater impact on actual performance than the algorithm itself. When evaluating new quantization methods, be sure to check not only theoretical advantages but also benchmarks in actual serving environments.
References
- Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (2022)
- Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" (2024)
- Hugging Face Transformers Quantization Guide
- vLLM Quantization Documentation
- llama.cpp GitHub Repository - GGUF Format
- The Complete Guide to LLM Quantization with vLLM: Benchmarks
- AutoGPTQ GitHub Repository
- AutoAWQ - Applying AWQ Quantization
- BitsAndBytes Foundation GitHub
- Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs" (2023)
Quiz
Q1: What is the main topic covered in "Complete LLM Quantization Comparison: GPTQ, AWQ, GGUF
Practical Application Guide"?
From the core principles of LLM quantization to comparative analysis of GPTQ, AWQ, GGUF, and BitsAndBytes techniques, covering practical application in vLLM and llama.cpp environments and quality-performance trade-offs.
Q2: What is Quantization Fundamentals?
The Journey from FP16 to INT4 Quantization is the process of converting high-precision
floating-point (FP16/FP32) weights to lower-bit integers (INT8/INT4). Absmax Quantization The
simplest quantization method, scaling based on the absolute maximum value of the tensor.
Q3: Explain the core concept of GPTQ: Generative Pre-trained Transformer Quantization.
Algorithm Details GPTQ is a Post-Training Quantization (PTQ) method proposed by Frantar et al. in 2022, based on the OBQ (Optimal Brain Quantization) algorithm.
Q4: What are the key aspects of AWQ: Activation-aware Weight Quantization?
Principles and Differentiators AWQ is a method proposed by Lin et al. at MIT in 2024, and the core
observation is that not all weights are equally important.
Q5: How does GGUF: The Universal Format for GPU-poor work?
GGUF Format and the llama.cpp Ecosystem GGUF (GPT-Generated Unified Format) is a file format, not
a quantization algorithm. Created by the llama.cpp project, this format supports CPU and GPU
hybrid inference.