Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

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 양자화

가장 단순한 양자화 방법으로, 텐서의 절대 최대값을 기준으로 스케일링한다.

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 양자화는 비대칭 분포를 처리한다.

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

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_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 어댑터를 학습할 수 있다.

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 이상이 안정적이다.

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

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)](https://arxiv.org/abs/2210.17323)

2. [Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" (2024)](https://arxiv.org/abs/2306.00978)

3. [Hugging Face Transformers Quantization Guide](https://huggingface.co/docs/transformers/main/quantization)

4. [vLLM Quantization Documentation](https://docs.vllm.ai/en/latest/features/quantization/)

5. [llama.cpp GitHub Repository - GGUF Format](https://github.com/ggml-org/llama.cpp)

6. [The Complete Guide to LLM Quantization with vLLM: Benchmarks](https://docs.jarvislabs.ai/blog/vllm-quantization-complete-guide-benchmarks)

7. [AutoGPTQ GitHub Repository](https://github.com/AutoGPTQ/AutoGPTQ)

8. [AutoAWQ - Applying AWQ Quantization](https://github.com/casper-hansen/AutoAWQ)

9. [BitsAndBytes Foundation GitHub](https://github.com/bitsandbytes-foundation/bitsandbytes)

10. [Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs" (2023)](https://arxiv.org/abs/2305.14314)

현재 단락 (1/293)

70B 파라미터 LLM을 FP16으로 로딩하면 약 140GB의 VRAM이 필요하다. A100 80GB 2장으로도 빠듯한 수준이다. 하지만 **양자화(Quantization)**를 ...

작성 글자: 0원문 글자: 11,838작성 단락: 0/293