Skip to content

Split View: LLM 양자화 기법 완벽 비교: GPTQ, AWQ, GGUF 실전 적용 가이드

|

LLM 양자화 기법 완벽 비교: GPTQ, AWQ, GGUF 실전 적용 가이드

LLM 양자화 기법 비교

들어가며

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 모델 메모리
FP3232bit약 ±3.4×10^38~280GB
FP1616bit약 ±6.5×10^4~140GB
INT88bit-128 ~ 127~70GB
INT44bit-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) 알고리즘을 기반으로 한다. 핵심 아이디어는 다음과 같다.

  1. Layer-wise 양자화: 모델 전체를 한꺼번에 양자화하지 않고, 레이어 단위로 순차 처리한다
  2. Hessian 기반 보정: 양자화로 인한 출력 오차를 Hessian 역행렬을 활용해 나머지 가중치로 보상한다
  3. 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 등이 제안한 방법으로, 핵심 관찰은 모든 가중치가 동일하게 중요하지 않다는 점이다.

  1. Salient Weight 식별: 활성화(Activation) 크기를 기준으로 중요한 가중치 채널(약 1%)을 식별한다
  2. 선택적 보호: 중요 가중치는 스케일링 팩터를 적용하여 양자화 오차를 줄인다
  3. 나머지 양자화: 나머지 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_K2.6bit~2.8GB+2.5~3.0극한 메모리 제한
Q3_K_M3.3bit~3.5GB+1.0~1.5메모리 제한 환경
Q4_K_M4.5bit~4.9GB+0.3~0.5범용 권장
Q5_K_M5.3bit~5.7GB+0.1~0.2품질 중시
Q6_K6.6bit~6.6GB+0.05거의 무손실
Q8_08.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

핵심 특성 비교표

항목GPTQAWQGGUFBitsAndBytes
양자화 방식PTQ (Hessian 기반)PTQ (Activation-aware)PTQ (다양한 방식)동적 양자화
기본 비트4bit4bit2~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.4Q4_K_M: +0.3~0.5기준 +0.2~0.4

추론 성능 벤치마크 (Llama-3.1-8B, A100 80GB)

방법처리량 (tok/s)VRAM 사용HumanEval Pass@1Latency (TTFT)
FP16 (기준)~35016GB53.2%45ms
GPTQ 4bit~5205.5GB50.6%32ms
GPTQ + Marlin~7125.5GB50.6%25ms
AWQ 4bit~5505.2GB51.8%30ms
AWQ + Marlin~7415.2GB51.8%23ms
GGUF Q4_K_M~2804.9GB51.8%55ms
BitsAndBytes NF4~3005.8GB51.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~8BGPTQ/AWQ 4bitFP16 권장GGUF Q4_K_M
13BGPTQ/AWQ 4bitFP16 또는 AWQ 4bitGGUF Q4_K_M
34BGGUF Q4_K_MAWQ 4bit + MarlinGGUF Q3_K_M
70BGGUF Q3_K_M (부분)AWQ 4bit + MarlinGGUF 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.0torch>=2.1.0 조합을 권장한다. CUDA 버전도 12.1 이상이 안정적이다.

양자화 품질 검증 체크리스트

  1. Perplexity 측정: 원본 대비 perplexity 증가가 0.5 이내인지 확인
  2. 태스크별 벤치마크: 실제 사용 태스크(코드 생성, 요약 등)에서 품질 확인
  3. Edge Case 테스트: 긴 입력, 다국어, 수학 문제 등 경계 사례 검증
  4. 레이턴시 프로파일링: TTFT(Time to First Token)와 TPS(Tokens per Second) 측정
  5. 메모리 모니터링: 실제 서빙 환경에서의 VRAM 사용량 추적

마치며

LLM 양자화는 단순히 모델 크기를 줄이는 것이 아니라, 배포 가능성을 결정하는 핵심 기술이다. 2026년 현재 권장 사항을 정리하면 다음과 같다.

  • 프로덕션 GPU 서빙: AWQ + Marlin 커널 (최고 처리량 + 높은 품질)
  • 개발/실험: BitsAndBytes NF4 (양자화 과정 불필요, QLoRA 통합)
  • 엣지/CPU 배포: GGUF Q4_K_M (범용성, 단일 파일 배포)
  • 레거시 호환: GPTQ (가장 넓은 에코시스템 지원)

양자화 기법은 계속 발전하고 있다. 2025년 이후 등장한 Marlin 커널처럼, 알고리즘 자체보다 커널 구현의 최적화가 실제 성능에 더 큰 영향을 미치는 추세다. 새로운 양자화 방법을 평가할 때는 이론적 장점뿐 아니라 실제 서빙 환경에서의 벤치마크를 반드시 확인하자.

참고자료

  1. Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (2022)
  2. Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" (2024)
  3. Hugging Face Transformers Quantization Guide
  4. vLLM Quantization Documentation
  5. llama.cpp GitHub Repository - GGUF Format
  6. The Complete Guide to LLM Quantization with vLLM: Benchmarks
  7. AutoGPTQ GitHub Repository
  8. AutoAWQ - Applying AWQ Quantization
  9. BitsAndBytes Foundation GitHub
  10. Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs" (2023)

Complete LLM Quantization Comparison: GPTQ, AWQ, GGUF Practical Application Guide

LLM Quantization Comparison

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 TypeBitsRange70B Model Memory
FP3232bit~+/-3.4x10^38~280GB
FP1616bit~+/-6.5x10^4~140GB
INT88bit-128 ~ 127~70GB
INT44bit-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:

  1. Layer-wise quantization: Rather than quantizing the entire model at once, it processes layer by layer sequentially
  2. Hessian-based correction: Compensates for quantization-induced output error by distributing it to remaining weights using the inverse Hessian matrix
  3. 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.

  1. Salient weight identification: Identifies important weight channels (approximately 1%) based on activation magnitude
  2. Selective protection: Applies scaling factors to important weights to reduce quantization error
  3. 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 TypeBitsLlama-3.1-8B SizePerplexity IncreaseRecommended Use
Q2_K2.6bit~2.8GB+2.5~3.0Extreme memory limits
Q3_K_M3.3bit~3.5GB+1.0~1.5Memory-constrained env
Q4_K_M4.5bit~4.9GB+0.3~0.5General recommended
Q5_K_M5.3bit~5.7GB+0.1~0.2Quality-focused
Q6_K6.6bit~6.6GB+0.05Near lossless
Q8_08.0bit~8.5GB~0Close 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

ItemGPTQAWQGGUFBitsAndBytes
Quantization methodPTQ (Hessian-based)PTQ (Activation-aware)PTQ (various methods)Dynamic quant
Default bits4bit4bit2~8bit selectable4bit (NF4)
CalibrationRequired (128-256)Required (small amount)Not requiredNot required
Quantization time15-30 min (8B)~10 min (8B)MinutesInstant on load
GPU inferenceVery fastFastestFast (with offload)Moderate
CPU inferenceNot supportedNot supportedSupportedNot supported
vLLM supportSupportedSupportedPartial supportSupported
Fine-tuningLimitedLimitedNot supportedQLoRA optimal
Model sharingHF Hub uploadHF Hub uploadSingle file deployDifficult
Perplexity (4bit)Baseline +0.3~0.5Baseline +0.2~0.4Q4_K_M: +0.3~0.5Baseline +0.2~0.4

Inference Performance Benchmark (Llama-3.1-8B, A100 80GB)

MethodThroughput (tok/s)VRAM UsageHumanEval Pass@1Latency (TTFT)
FP16 (baseline)~35016GB53.2%45ms
GPTQ 4bit~5205.5GB50.6%32ms
GPTQ + Marlin~7125.5GB50.6%25ms
AWQ 4bit~5505.2GB51.8%30ms
AWQ + Marlin~7415.2GB51.8%23ms
GGUF Q4_K_M~2804.9GB51.8%55ms
BitsAndBytes NF4~3005.8GB51.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

Model SizeConsumer GPU (24GB)Server GPU (80GB)CPU Only
7~8BGPTQ/AWQ 4bitFP16 recommendedGGUF Q4_K_M
13BGPTQ/AWQ 4bitFP16 or AWQ 4bitGGUF Q4_K_M
34BGGUF Q4_K_MAWQ 4bit + MarlinGGUF Q3_K_M
70BGGUF Q3_K_M (partial)AWQ 4bit + MarlinGGUF Q2_K
  • 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

  1. Perplexity measurement: Confirm perplexity increase is within 0.5 compared to original
  2. Task-specific benchmarks: Verify quality on actual use tasks (code generation, summarization, etc.)
  3. Edge case testing: Validate on boundary cases like long inputs, multilingual, math problems
  4. Latency profiling: Measure TTFT (Time to First Token) and TPS (Tokens per Second)
  5. 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

  1. Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (2022)
  2. Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" (2024)
  3. Hugging Face Transformers Quantization Guide
  4. vLLM Quantization Documentation
  5. llama.cpp GitHub Repository - GGUF Format
  6. The Complete Guide to LLM Quantization with vLLM: Benchmarks
  7. AutoGPTQ GitHub Repository
  8. AutoAWQ - Applying AWQ Quantization
  9. BitsAndBytes Foundation GitHub
  10. 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.