GGUF(GPT-Generated Unified Format)는 llama.cpp에서 사용하는 LLM 모델 파일 포맷이다. llama.cpp 개발자 Georgi Gerganov가 설계했으며(이름의 GG가 이니셜), 이전 포맷인 GGML의 한계를 극복하기 위해 등장했다.
기존에 LLM 모델을 배포하려면 여러 파일이 필요했다. PyTorch의 경우 가중치 파일(.bin 또는 .safetensors), 토크나이저 설정(tokenizer.json, tokenizer_config.json), 모델 설정(config.json), 생성 설정(generation_config.json) 등을 모두 갖춰야 추론이 가능하다.
GGUF 이전에 사용되던 GGML 포맷은 모델 가중치를 하나의 파일로 담을 수 있었지만, 다음과 같은 문제가 있었다:
- 토크나이저 정보를 파일 내에 포함할 수 없어 별도 파일이 필요했다
- 메타데이터 확장이 어려워, 새로운 모델 아키텍처를 지원하려면 포맷 자체를 수정해야 했다
- 하위 호환성이 보장되지 않아, llama.cpp 버전이 올라가면 이전 모델 파일이 깨지는 경우가 있었다
GGUF는 이 문제를 해결하기 위해 유연한 키-값 메타데이터 구조를 도입하여, 모델 가중치와 모든 부가 정보를 단일 파일에 담을 수 있게 했다.
파일 구조
GGUF 파일은 크게 헤더, 메타데이터, 텐서 정보, 텐서 데이터의 네 영역으로 구성된다.
- Header: 매직 넘버(“GGUF”), 포맷 버전, 텐서 수, 메타데이터 KV 수
- Metadata (KV pairs): 모델 아키텍처, 토크나이저, 하이퍼파라미터 등
- Tensor Info: 각 텐서의 이름, 형상, 양자화 타입, 데이터 오프셋
- Tensor Data (aligned): 실제 가중치 데이터 (기본 64바이트 정렬)
Header
- 파일 시작 4바이트는 매직 넘버
0x46475547(ASCII로 “GGUF”)이다 - 포맷 버전(현재 v3), 텐서 개수, 메타데이터 KV 쌍 수가 이어진다
- 이 정보만으로 파일이 유효한 GGUF인지 빠르게 판별할 수 있다
Metadata (KV pairs)
- 키는 문자열, 값은 타입이 지정된 데이터(uint32, float32, string, array 등)다
- 표준화된 키 규칙이 있다. 예를 들어:
general.architecture: 모델 아키텍처 (llama, mistral, phi 등)general.name: 모델 이름llama.context_length: 컨텍스트 길이llama.embedding_length: 임베딩 차원tokenizer.ggml.model: 토크나이저 종류 (BPE, SPM 등)tokenizer.ggml.tokens: 전체 토큰 목록
- 새로운 모델 아키텍처가 나와도 KV 쌍을 추가하면 되기 때문에, 포맷 자체를 변경할 필요가 없다
Tensor Info
- 각 텐서의 이름(예:
blk.0.attn_q.weight), 차원 수, 각 차원의 크기, 양자화 타입, 데이터 영역 내 오프셋을 기록한다 - 이 정보는 메타데이터 바로 뒤에 연속으로 저장되어, 텐서 데이터를 로드하기 전에 전체 모델 구조를 파악할 수 있다
Tensor Data
- 실제 가중치가 저장되는 영역으로, 기본적으로 64바이트 경계에 정렬(alignment)된다
- 정렬 덕분에 mmap(메모리 맵)으로 매핑하여 필요한 텐서만 메모리에 올릴 수 있다
- 각 텐서는 Tensor Info에 명시된 양자화 타입에 따라 인코딩되어 있다
양자화 타입
GGUF는 다양한 양자화 타입을 지원한다. 타입 이름의 규칙은 Q{비트수}_{방식}이다.
- Q4_0: 32개 가중치를 하나의 블록으로 묶고, 블록당 하나의 FP16 scale을 저장한다. 각 가중치는 4비트로 표현된다. 가장 단순한 양자화 방식이다.
- Q4_1: Q4_0에 블록당 FP16 min 값을 추가한다. 비대칭 양자화로 Q4_0보다 정확도가 높지만 파일이 약간 커진다.
- Q8_0: 8비트 양자화. 정확도가 높지만 압축률은 낮다.
K-quant 계열은 더 정교한 양자화를 제공한다:
- Q4_K_S / Q4_K_M: K-quant 4비트의 Small/Medium 변형. 블록 내에서 super-block 구조를 사용하여, 중요한 가중치에 더 많은 비트를 할당한다. M이 S보다 약간 크지만 정확도가 더 높다.
- Q5_K_S / Q5_K_M: K-quant 5비트. 4비트 대비 정확도가 높고 파일 크기는 약 25% 증가한다.
- Q6_K: K-quant 6비트. FP16 대비 거의 손실이 없는 수준의 정확도를 제공한다.
- Q2_K: K-quant 2비트. 매우 높은 압축률이지만 정확도 손실이 크다. 극단적인 VRAM 제약 환경에서만 사용한다.
예를 들어 Llama 2 7B 모델의 파일 크기를 비교하면:
FP16: 약 13.5GBQ8_0: 약 7.2GBQ5_K_M: 약 4.8GBQ4_K_M: 약 4.1GBQ2_K: 약 2.8GB일반적으로 Q4_K_M이 파일 크기와 정확도의 균형이 가장 좋아 널리 사용된다.
mmap을 활용한 로딩
GGUF의 중요한 설계 특징 중 하나는 mmap(memory-mapped file) 로딩을 전제로 한다는 것이다.
일반적인 모델 로딩은 파일 전체를 메모리에 복사(read)하는 방식이다. 7B 모델이 4GB라면, 디스크에서 4GB를 읽어 RAM에 복사하는 시간이 필요하다.
mmap은 파일의 내용을 가상 메모리 주소 공간에 직접 매핑한다. 실제로 접근하는 페이지만 물리 메모리에 올라오기 때문에:
- 로딩 시간이 거의 없다. mmap 시스템 콜 자체는 즉시 반환된다. 실제 I/O는 해당 메모리 영역에 접근할 때 page fault로 발생한다.
- 메모리 효율적이다. 자주 사용되지 않는 레이어는 OS가 자동으로 페이지 아웃할 수 있다.
- 여러 프로세스가 공유할 수 있다. 같은 모델 파일을 여러 프로세스가 mmap하면, 물리 메모리의 같은 페이지를 공유한다.
GGUF의 텐서 데이터가 64바이트 정렬되는 이유도 mmap과 관련이 있다. 정렬된 데이터는 페이지 경계와 잘 맞아 불필요한 페이지 로드를 줄인다.
참고