모델 운영

MLOPS / 14번째 글

GGUF 완전 정복: llama.cpp의 양자화 포맷과 실전 사용법

GGUF 파일 구조, Q4_K_M·Q5_K_S 등 양자화 레벨의 차이, Hugging Face에서 모델 다운로드·변환·추론까지 실전 가이드.

PALDYN Team8 MIN READ

지난 글에서 INT8·INT4 양자화의 수학적 원리와 PTQ·QAT 전략을 다뤘다. 이론을 익혔다면 이제 실제 파일을 손에 쥐어야 한다. Hugging Face에서 "GGUF" 확장자 파일을 본 적 있을 것이다. 오늘은 그 GGUF가 정확히 무엇인지, 어떤 양자화 레벨을 골라야 하는지, 그리고 llama.cpp와 Python에서 어떻게 추론하는지를 처음부터 끝까지 다룬다.

GGUF가 등장한 이유

2023년 8월 이전까지 llama.cpp는 GGML 포맷을 사용했다. GGML은 양자화 정보를 담을 수 있었지만 메타데이터(토크나이저, 아키텍처 정보, 컨텍스트 길이)를 저장하는 표준 방법이 없었고, 모델 구조가 바뀔 때마다 파일 레이아웃도 달라져 역호환성이 엉망이었다. GGUF(GPT-Generated Unified Format)는 이 문제를 해결하기 위해 Georgi Gerganov가 설계한 포맷이다. 핵심 설계 원칙 두 가지는 다음과 같다.

단일 파일에 모든 것을 담는다: 토크나이저 어휘, BPE 병합 규칙, 모델 하이퍼파라미터, 양자화된 가중치까지 하나의 파일에 들어간다. 예전에는 tokenizer_config.json, config.json, 여러 개의 .bin 파일을 함께 배포해야 했다.

mmap으로 제로카피 접근: 파일 내 텐서 데이터 영역이 페이지 경계에 정렬되어 있어, 운영체제의 메모리 맵(mmap)을 통해 디스크에서 GPU 메모리로 직접 로드할 수 있다. 대형 모델을 RAM에 전부 올리지 않고도 추론 가능하다.

GGUF 파일 구조

GGUF 파일 구조 분해

바이너리 레이아웃을 이해하면 디버깅이 쉬워진다.

헤더(24바이트 고정):

  • magic: GGUF ASCII 문자열 (0x47475546)
  • version: 현재 3 (하위 호환 유지)
  • tensor_count: 텐서 개수 (7B 모델은 약 291개)
  • metadata_kv_count: KV 쌍 개수

메타데이터 KV 쌍: 키(문자열)와 값(다양한 타입)으로 구성된 딕셔너리다. 주요 키들은 다음과 같다.

# 실제 GGUF 메타데이터 키 예시
"general.architecture"    # "llama"
"general.name"            # "Meta-Llama-3-8B"
"llama.context_length"    # 8192
"llama.embedding_length"  # 4096
"llama.block_count"       # 32
"tokenizer.ggml.model"    # "llama"
"tokenizer.ggml.tokens"   # [...어휘 목록...]

텐서 정보 배열: 각 텐서마다 이름, 차원 수, 형태, 양자화 타입, 파일 내 오프셋을 저장한다.

텐서 데이터: 실제 양자화된 가중치 블록. ALIGNMENT(기본 32바이트) 단위로 정렬된 패딩 이후 시작한다.

양자화 레벨 해석법

GGUF 파일명에서 양자화 레벨을 읽는 패턴을 익혀야 한다.

  • Q숫자: 비트 수. Q4는 4비트, Q8은 8비트.
  • _K: K-Quant 방식. 레이어 중요도에 따라 혼합 비트를 적용한다.
  • _M / _S / _L: K-Quant의 크기 변형. M(Medium), S(Small), L(Large). 같은 비트 수에서 중요 레이어에 할당하는 고비트 비율이 다르다.
  • 숫자만: 단순 균일 양자화. Q4_0은 모든 가중치에 균일하게 4비트 적용.

GGUF 양자화 레벨 비교

7B 모델 기준 실전 선택 가이드:

  • 8~16GB VRAM GPU → Q5_K_M 또는 Q6_K (최고 품질)
  • 6~8GB VRAM GPU → Q4_K_M (품질과 크기의 황금 균형)
  • 4~6GB VRAM 또는 RAM 추론 → Q4_K_S 또는 Q4_0
  • 4GB 이하 초저사양 → Q3_K_M (품질 손실 감수)

llama.cpp로 추론하기

먼저 llama.cpp를 빌드한다.

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# CPU 전용
cmake -B build && cmake --build build -j8

# CUDA (NVIDIA GPU)
cmake -B build -DGGML_CUDA=ON && cmake --build build -j8

Hugging Face에서 GGUF 파일을 다운로드한다.

pip install huggingface-hub
huggingface-cli download \
  bartowski/Meta-Llama-3.1-8B-Instruct-GGUF \
  Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
  --local-dir ./models

명령줄에서 바로 추론한다.

./build/bin/llama-cli \
  -m models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
  -p "한국의 수도는?" \
  -n 128 \
  --temp 0.7 \
  -ngl 33   # GPU 레이어 수 (전체 오프로드)

Python에서 llama-cpp-python 활용

Python 바인딩으로 애플리케이션에 직접 통합할 수 있다.

from llama_cpp import Llama

llm = Llama(
    model_path="models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
    n_gpu_layers=33,    # GPU에 올릴 레이어 수 (-1=전체)
    n_ctx=4096,         # 컨텍스트 길이
    n_batch=512,        # 배치 크기
    verbose=False,
)

response = llm.create_chat_completion(
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Python의 GIL이란 무엇인가요?"},
    ],
    max_tokens=512,
    temperature=0.7,
    stream=False,
)
print(response["choices"][0]["message"]["content"])

스트리밍 응답도 간단하다.

for chunk in llm.create_chat_completion(
    messages=[{"role": "user", "content": "피타고라스 정리를 설명해줘"}],
    stream=True,
):
    delta = chunk["choices"][0]["delta"]
    if "content" in delta:
        print(delta["content"], end="", flush=True)

직접 GGUF로 변환하기

Hugging Face 형식 모델을 GGUF로 변환할 수 있다.

# 의존성 설치
pip install -r requirements.txt

# 변환 (F16 먼저)
python convert_hf_to_gguf.py \
  ./my-finetuned-model \
  --outtype f16 \
  --outfile ./my-model-f16.gguf

# 양자화 적용
./build/bin/llama-quantize \
  ./my-model-f16.gguf \
  ./my-model-Q4_K_M.gguf \
  Q4_K_M

GGUF 메타데이터 직접 읽기

gguf Python 패키지로 파일을 열지 않고도 메타데이터를 확인할 수 있다.

pip install gguf
from gguf import GGUFReader

reader = GGUFReader("Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf")

# 주요 메타데이터 출력
for key in ["general.name", "llama.context_length",
            "llama.block_count", "llama.embedding_length"]:
    if key in reader.fields:
        print(f"{key}: {reader.fields[key].parts[-1]}")

# 텐서 통계
total_params = 0
for t in reader.tensors:
    params = 1
    for d in t.shape:
        params *= d
    total_params += params
print(f"총 파라미터: {total_params/1e9:.2f}B")

정리

GGUF는 로컬 LLM 추론의 사실상 표준 포맷이다. 핵심을 요약하면:

  • 단일 파일: 토크나이저부터 가중치까지 전부 포함
  • K-Quant: 중요 레이어에 상위 비트, 나머지에 하위 비트를 혼합해 같은 크기에서 더 높은 품질
  • Q4_K_M: 7B 모델에서 4GB대 크기에 94% 품질을 달성하는 범용 최선택
  • mmap: 대용량 모델도 디스크에서 직접 읽어 RAM 사용량 최적화

다음 글에서는 GGUF와 함께 INT4 양자화의 양대 산맥인 AWQ와 GPTQ의 알고리즘 원리와 실전 비교를 다룬다.


읽어주셔서 감사합니다. 😊

LATEST

모델 운영의 최신 글

모델 운영2026.09.04

KV 캐시 양자화 — 가중치보다 이쪽이 먼저 넘친다

긴 문맥에서 GPU 메모리를 실제로 잡아먹는 것은 가중치가 아니라 KV 캐시입니다. 캐시 크기를 계산하는 법, K와 V를 다르게 다뤄야 하는 이유, 어디까지 줄여도 되는지를 정리합니다.

16 MIN
모델 운영2026.09.04

양자화 보정 — 데이터 128개가 모델 품질을 정한다

양자화에서 스케일을 정하는 절차가 보정입니다. 무엇을 재는지, 데이터를 어디서 몇 개 뽑아야 하는지, 자르는 지점을 어떻게 고르는지, 그리고 잘못된 보정이 어떤 모양으로 드러나는지를 정리합니다.

21 MIN
모델 운영2026.09.03

과제 특화 증류 — 큰 모델의 답을 작은 모델에 옮긴다

범용 성능이 아니라 우리 과제 하나만 잘하는 작은 모델을 만드는 방법입니다. 교사에게 무엇을 받아야 하는지, 데이터를 어떻게 모으고 거르는지, 손익 분기가 어디인지를 정리합니다.

15 MIN