로컬 LLM 파일 형식과 정밀도 입문

목표: Hugging Face 저장소에서 model-00001-of-00004.safetensors, Qwen3-8B-Q4_K_M.gguf, -AWQ, -FP8, pytorch_model.bin 같은 이름을 봤을 때 무슨 포장에 어떤 정밀도로 담긴 파일인지 읽고, 내 컴퓨터에서 돌릴 파일을 고를 수 있기.

정밀도·메모리 계산의 GPU 쪽 배경은 GPU와 CUDA 입문 §5–6, 원격 서버에서 돌리는 법은 원격 GPU 서버 사용 입문, 터미널·가상환경은 컴퓨터 기본 도구와 개발 환경 입문에서 다룬다.


한 줄로 먼저

LLM 파일은 “포장(파일 형식)“과 “숫자 하나의 크기(정밀도·양자화)” 두 축으로 읽는다. GPU 서버용 원본은 safetensors + BF16/FP8, 개인 PC·Mac용은 GGUF + 4~8비트 양자화가 지금의 표준이다. pickle(.bin, .pt)은 열기만 해도 코드가 실행될 수 있어 밀려났다.

Qwen3-8B-Instruct-Q4_K_M.gguf
└─┬─┘ └┬┘ └──┬───┘ └──┬──┘ └┬─┘
 모델  크기  용도      정밀도  포장
 이름 (8B) (대화용)  (4비트,  (GGUF)
                    k-quant M)

0. 무엇이 들어 있나: 가중치 + 구조 + 토크나이저

모델을 돌리려면 세 가지가 필요하다.

구성 요소내용원본 저장소의 파일
가중치(weights)학습으로 정해진 수십억 개의 숫자*.safetensors (또는 옛 *.bin)
구조(config)층 수, hidden 크기, 헤드 수, 어떤 아키텍처인지config.json, generation_config.json
토크나이저글자를 토큰 ID로 바꾸는 규칙, chat templatetokenizer.json, tokenizer_config.json, chat_template.jinja

chat template은 사용자 메시지를 모델이 학습 때 본 형식(<|im_start|>user ... 같은 특수 토큰 틀)으로 감싸는 규칙이다. 이게 틀리면 모델이 이상하게 대답한다.

포장 형식마다 이 셋을 따로 두느냐, 한 파일에 합치느냐가 다르다. safetensors는 가중치만 담고, GGUF는 셋을 한 파일에 담는다.


1. 파일 형식: 가중치를 담는 포장

pickle 계열: .bin, .pt, .pth, .ckpt

PyTorch가 기본으로 쓰던 저장 방식이다. torch.save는 Python의 pickle(Python 객체를 통째로 바이트로 바꾸는 표준 직렬화)을 쓴다.

  • 문제: pickle은 “이 함수를 이 인자로 불러서 객체를 복원하라”는 명령을 담을 수 있다. 그래서 악의적인 파일은 여는 순간 임의 코드를 실행한다. Python 공식 문서도 “신뢰할 수 없는 데이터를 unpickle하지 말라”고 경고한다.
  • 대응: PyTorch 2.6부터 torch.load의 기본값이 weights_only=True가 됐다. 텐서와 기본 자료형만 복원하고 임의 객체는 거부한다. Hugging Face Hub도 업로드된 pickle 파일을 검사해 위험한 import를 표시한다.
  • 현재: 옛 저장소(pytorch_model.bin)와 연구 코드 체크포인트에 남아 있다. 새 모델 배포에는 거의 쓰지 않는다.

safetensors: .safetensors

Hugging Face가 pickle을 대신하려고 만든 형식이다. 지금 새 모델 원본은 거의 다 이 형식이다.

[8바이트: 헤더 길이 N (little-endian u64)]
[N바이트: JSON 헤더]
   {"model.layers.0.self_attn.q_proj.weight":
       {"dtype": "BF16", "shape": [4096, 4096], "data_offsets": [0, 33554432]},
    ...,
    "__metadata__": {...}}
[나머지: 텐서 바이트를 이어 붙인 덩어리]
  • 안전: 헤더는 JSON, 나머지는 숫자뿐이다. 코드가 들어갈 자리가 없어서 불러와도 아무것도 실행되지 않는다.
  • 빠름: 헤더만 읽으면 각 텐서가 파일 어디에 있는지 안다. 그래서 mmap(파일을 메모리에 복사하지 않고 필요한 부분을 바로 읽는 OS 기능)으로 필요한 텐서만 읽을 수 있다. 여러 GPU에 나눠 올릴 때 각자 자기 몫만 읽는다.
  • 샤딩(sharding): 큰 모델은 수 GB 단위 조각으로 나뉜다. model.safetensors.index.json이 “이 텐서는 몇 번 조각에 있다”는 지도를 준다.
config.json
generation_config.json
model-00001-of-00004.safetensors
model-00002-of-00004.safetensors
model-00003-of-00004.safetensors
model-00004-of-00004.safetensors
model.safetensors.index.json
tokenizer.json
tokenizer_config.json
  • 가중치만 담는다. 구조와 토크나이저는 옆 파일에 있어야 한다. 그래서 .safetensors 하나만 받아서는 못 돌린다. 폴더째 받는다(huggingface-cli download <repo> 또는 hf download <repo>).
  • LoRA 어댑터도 같은 형식(adapter_model.safetensors + adapter_config.json)으로 배포된다. 기반 모델 위에 얹어 쓰는 작은 파일이다(LoRA).

GGUF: .gguf

llama.cpp 프로젝트가 2023년 8월에 만든 형식이다. 그 전의 GGML·GGJT 형식을 대체했다. 이름은 흔히 GPT-Generated Unified Format의 약자로 소개된다.

[magic "GGUF"][버전][텐서 개수][메타데이터 개수]
[메타데이터 key-value]
   general.architecture = "qwen3"
   qwen3.block_count = 36
   qwen3.context_length = 40960
   tokenizer.ggml.model = "gpt2"
   tokenizer.ggml.tokens = [...]
   tokenizer.chat_template = "{% for message in messages %}..."
   general.file_type = ...  (양자화 종류)
[텐서 정보: 이름, shape, 양자화 타입, 위치]
[정렬용 패딩]
[텐서 데이터]
  • 한 파일에 다 들어 있다. 구조, 토크나이저, chat template, 양자화된 가중치가 모두 이 파일 안에 있다. .gguf 하나만 받으면 돌아간다.
  • CPU·Apple Silicon에 강하다. llama.cpp가 CPU SIMD(한 명령으로 여러 숫자를 동시에 계산), Metal(Mac GPU), CUDA, Vulkan을 모두 지원한다. 일부 층만 GPU에 올리고 나머지는 CPU에 두는 것도 된다(-ngl 옵션, offload).
  • 쓰는 프로그램: llama.cpp, Ollama, LM Studio, koboldcpp, Jan 등.
  • 만드는 법: 보통 원본 safetensors를 받아 llama.cpp의 변환 스크립트(convert_hf_to_gguf.py)로 GGUF로 바꾸고, llama-quantize로 비트를 줄인다. 저장소 이름에 -GGUF가 붙어 있으면 누군가 이 작업을 해서 올린 것이다.
  • 큰 모델은 GGUF도 나뉜다: -00001-of-00003.gguf. 첫 조각만 지정하면 나머지를 같이 읽는다.
  • 비전 모델: 이미지 인코더를 mmproj-*.gguf로 따로 두는 경우가 많다. 본체 GGUF와 함께 받아야 이미지를 읽는다.

그 밖의 형식

형식실체쓰는 곳
MLXsafetensors + MLX용 config. 양자화된 버전은 MLX 고유 방식Apple MLX, mlx-lm, LM Studio(Mac). mlx-community 저장소에 4bit/8bit 변환본이 많다
ONNX (.onnx)계산 그래프 + 가중치ONNX Runtime, 브라우저(WebGPU)·모바일·Windows 추론
TensorRT-LLM engine특정 GPU·설정에 맞춰 미리 컴파일한 결과물NVIDIA 서버 추론. 다른 GPU로 옮기면 다시 빌드
EXL2 / EXL3safetensors 안에 ExLlama 고유 양자화ExLlamaV2/V3 (NVIDIA GPU 전용)

2. 정밀도: 숫자 하나에 몇 비트를 쓰나

가중치는 결국 실수 수십억 개다. 실수 하나를 몇 비트로 저장하느냐가 정밀도(precision) 다. 부동소수점은 비트를 지수(얼마나 큰 수까지 담나, 범위)와 가수(얼마나 세밀하게 담나, 정밀도)로 나눈다.

형식바이트지수 / 가수최댓값 감쓰임
FP3248 / 23약 3.4 × 10³⁸학습의 기준. 배포에는 거의 안 씀
FP1625 / 1065504옛 모델 배포, 추론. 값이 크면 inf로 넘침
BF1628 / 7FP32와 같음요즘 원본 배포의 기본
FP8 E4M314 / 3448최신 GPU 추론·학습. 가중치·활성값용
FP8 E5M215 / 257344범위가 더 필요할 때(주로 gradient)
FP4 (E2M1)0.52 / 16MXFP4·NVFP4처럼 블록 scale과 함께만 씀

FP16 vs BF16

같은 2바이트를 다르게 나눴다. FP16은 가수에 비트를 더 줬고, BF16은 지수에 비트를 더 줬다. 비유하면 FP16은 눈금이 촘촘하지만 짧은 자이고, BF16은 눈금이 성기지만 FP32만큼 긴 자다.

LLM은 활성값이 가끔 크게 튀기 때문에 범위가 넓은 BF16이 이겼다. BF16 원본을 FP16으로 바꿔 돌리면 대개 괜찮지만, 큰 값이 나오는 모델에서는 inf나 NaN이 생길 수 있다. 원본이 BF16이면 BF16 그대로 쓰는 것이 안전하다. 오래된 GPU(V100, T4 등)는 BF16 가속이 없어서 FP16으로 돌린다(GPU와 CUDA 입문 §4).

FP8과 FP4: 하드웨어가 따라와야 의미가 있다

  • FP8은 NVIDIA Ada(RTX 40, L40S)·Hopper(H100) 이후 GPU가 하드웨어로 계산한다. DeepSeek-V3는 처음부터 FP8 혼합 정밀도로 학습해 FP8 가중치로 공개했다. 저장소 이름 끝의 -FP8이 이런 버전이다.
  • FP4는 숫자 하나로는 거의 아무것도 못 담는다(비트 패턴이 16개뿐이고 최댓값이 6). 그래서 블록 scale을 붙인다. 숫자 여러 개를 한 블록으로 묶고, 블록마다 공유 배율을 하나 둔다.
    • MXFP4: OCP(Open Compute Project) Microscaling 표준. 32개 묶음마다 8비트 지수 scale(E8M0). OpenAI의 gpt-oss가 MoE 가중치를 MXFP4로 공개했다.
    • NVFP4: NVIDIA Blackwell 세대 형식. 16개 묶음마다 FP8(E4M3) scale.
  • FP8·FP4를 지원하지 않는 GPU나 CPU에서도 돌릴 수는 있다. 하지만 계산할 때 더 높은 정밀도로 풀어서 쓰므로 속도 이득이 없다. 메모리 절약만 남는다.

3. 양자화: 로컬에서 실제로 받는 파일

양자화(quantization) 는 학습이 끝난 가중치를 4~8비트 정수 등으로 줄여 저장하는 일이다. 기본 아이디어는 같다.

원래 값 (BF16):   [0.12, -0.03, 0.40, -0.25, ...]  (블록 하나, 예: 32개)
scale = 블록 최댓값 / 정수 최댓값
저장 값 (INT4):   [2, -1, 7, -4, ...]  + scale 하나
복원:             정수 × scale ≈ 원래 값

블록을 작게 잡을수록 오차가 줄지만 scale 저장 비용이 는다. 그래서 “4비트 양자화”라도 실제 평균 비트는 4보다 조금 크다. 이것을 bpw(bits per weight, 가중치 하나당 평균 비트)로 표시한다.

GGUF 양자화 이름 읽는 법

Q4_K_M
│ │ │
│ │ └─ S / M / L : 중요한 층(attention 출력, FFN 일부 등)을 얼마나 높은 비트로 남겼나
│ └─── K         : k-quant. 큰 블록(super-block) 안에 작은 블록을 두고 scale도 양자화
└───── Q4        : 기본 비트 수 4
이름평균 bpw (약)성격
F16 / BF1616양자화 안 함. 변환 중간 산출물
Q8_08.5원본과 거의 구분 안 됨
Q6_K6.6거의 손실 없음
Q5_K_M5.7좋은 절충
Q4_K_M4.8–4.9가장 많이 받는 기본값
Q4_04.5옛 방식. 일부 하드웨어 가속 경로에서 쓰임
IQ4_XS4.3i-quant. 같은 크기에서 품질이 조금 더 좋음
Q3_K_M / IQ3_M3.7–3.9메모리가 정말 모자랄 때
Q2_K / IQ2_XXS2–3품질이 눈에 띄게 떨어짐. 아주 큰 모델을 억지로 올릴 때

bpw는 모델 구조와 llama.cpp 버전에 따라 조금씩 다르다. 정확한 값은 저장소의 파일 크기를 보는 것이 가장 확실하다.

  • i-quant (IQ*): 가중치 묶음을 미리 정한 격자(코드북)의 점으로 근사하는 방식이다. 3비트 이하에서 k-quant보다 품질이 낫다. CPU에서는 k-quant보다 조금 느릴 수 있다.
  • imatrix(importance matrix): 예시 텍스트를 모델에 통과시켜 “어떤 가중치가 출력에 크게 기여하는지”를 잰 통계다. 양자화할 때 중요한 가중치의 오차를 줄이는 데 쓴다. 저장소 설명에 “imatrix”가 있으면 이 과정을 거쳤다는 뜻이다. 낮은 비트일수록 효과가 크다.
  • 같은 이름이어도 만든 사람마다 다를 수 있다. imatrix에 쓴 데이터, llama.cpp 버전이 다르기 때문이다. bartowski, unsloth, 모델 제작사 공식 GGUF처럼 많이 쓰이는 저장소가 무난하다.

GPU용 양자화: 대부분 safetensors 안에 들어 있다

이름방식언제 양자화하나쓰는 곳
GPTQ층마다 2차 정보(Hessian 근사)로 오차를 보정하며 순서대로 양자화미리 (보정 데이터 필요)vLLM, transformers, ExLlama
AWQ활성값이 큰 채널의 가중치를 보호하도록 scale을 조정미리vLLM, transformers
bitsandbytes (INT8, NF4)LLM.int8()은 이상값 채널만 FP16으로 분리. NF4는 정규분포에 맞춘 4비트불러오는 순간 (load_in_4bit=True)transformers, QLoRA 미세조정
FP8 / MXFP4 / NVFP4부동소수점 저비트 + scale미리, 또는 공식 배포vLLM, SGLang, TensorRT-LLM

저장소 이름 끝의 -AWQ, -GPTQ-Int4, -FP8, -bnb-4bit가 이 표시다. config.json 안의 quantization_config 항목을 보면 정확한 방식이 적혀 있다.


4. 용량 어림 계산

파일 크기 ≈ 파라미터 수 × bpw ÷ 8 바이트. 8B(80억) 모델이면 대략 이렇다.

형식계산크기 (약)
BF168 × 10⁹ × 16 ÷ 816 GB
Q8_08 × 10⁹ × 8.5 ÷ 88.5 GB
Q6_K8 × 10⁹ × 6.6 ÷ 86.6 GB
Q4_K_M8 × 10⁹ × 4.85 ÷ 84.9 GB
IQ3_M8 × 10⁹ × 3.7 ÷ 83.7 GB

실제로 돌릴 때는 파일 크기보다 메모리가 더 든다.

  • KV cache: 앞 토큰의 key·value를 저장하는 공간. 문맥 길이에 비례해 커진다. 계산 예는 GPU와 CUDA 입문 §6에 있다. llama.cpp·Ollama는 기본 문맥 길이를 모델 최대치보다 작게 잡는 경우가 많다. 문맥을 늘리면 메모리가 크게 는다. KV cache도 8비트·4비트로 줄이는 옵션이 있다.
  • 계산용 버퍼와 런타임: 수백 MB~수 GB.
  • MoE 모델: 전체 파라미터와 한 토큰에 쓰는 파라미터(active)가 다르다. 예를 들어 “30B-A3B”는 전체 30B, 토큰당 3B를 쓴다는 뜻이다. 메모리는 전체 30B 기준으로 필요하고, 속도는 3B 모델에 가깝다.

어림 기준: 파일 크기 + 1~3 GB(짧은 문맥 기준) 가 비어 있어야 한다.


5. 내 환경에서 무엇을 고르나

환경추천프로그램
Mac (Apple Silicon)GGUF Q4_K_M~Q8_0, 또는 MLX 4bit/8bitOllama, LM Studio, llama.cpp, mlx-lm
NVIDIA GPU, VRAM 넉넉원본 safetensors BF16, 최신 GPU면 FP8vLLM, SGLang, transformers
NVIDIA GPU, VRAM 부족AWQ/GPTQ 4bit, 또는 GGUF(일부 CPU offload)vLLM, llama.cpp, Ollama
CPU만GGUF Q4_K_M 이하, 작은 모델(1~8B)llama.cpp, Ollama
미세조정할 때원본 safetensors (QLoRA면 불러올 때 4bit)transformers + PEFT, Unsloth
  • Mac 통합 메모리: CPU와 GPU가 같은 메모리를 쓴다. 기본 설정에서 GPU가 쓸 수 있는 양은 전체의 약 2/33/4로 제한된다. 16 GB Mac이면 8B Q4Q6, 32 GB면 14~32B Q4, 64 GB 이상이면 70B Q4 정도가 감이다.
  • “같은 메모리라면 큰 모델의 낮은 비트 > 작은 모델의 높은 비트” 라는 경험칙이 있다. 다만 3비트 아래로 내려가면 이 관계가 깨지기 쉽다.
  • 서버 엔진(vLLM 등)은 원래 safetensors 계열 모델을 기본으로 쓰고, GGUF 지원은 실험적이거나 제한적이다. 반대로 Ollama·LM Studio는 GGUF가 기본이다. 엔진을 먼저 정하고, 그 엔진이 읽는 형식을 받는다.

6. 보안: 어떤 파일이 코드를 실행하나

대상코드 실행 위험
.bin, .pt, .pth, .ckpt (pickle)있음. 출처를 모르면 열지 않는다. torch.load(..., weights_only=True)로 연다
.safetensors, .gguf형식 자체에는 없음. 다만 파서 버그는 가끔 보고되므로 프로그램을 최신으로 유지한다
trust_remote_code=True있음. 저장소에 있는 .py 파일(모델 코드)을 그대로 실행한다. 파일 형식과 무관하다
GGUF 안의 chat templateJinja 템플릿이다. 실행 환경이 샌드박스로 처리하지만 템플릿 처리 취약점이 보고된 적이 있다

trust_remote_code는 새 아키텍처라 transformers에 아직 코드가 없을 때 필요하다. 공식 제작사 저장소인지 확인하고 켠다. 이름이 비슷한 가짜 저장소를 조심한다.


7. 아주 짧은 암기

  • 두 축으로 읽는다: 포장(파일 형식) × 숫자 크기(정밀도·양자화).
  • pickle(.bin, .pt)은 열면 코드가 돌 수 있다. safetensors·GGUF는 데이터뿐이다.
  • safetensors = 가중치만. 폴더째 받는다. GGUF = 한 파일에 전부.
  • 원본 배포는 BF16. BF16은 FP32의 범위, FP16은 더 촘촘하지만 65504에서 넘친다.
  • FP8·FP4는 지원 GPU에서만 빨라진다. 아니면 메모리만 준다.
  • Q4_K_M = 4비트 k-quant 중간 크기, 약 4.8 bpw. 로컬의 기본값.
  • IQ*는 저비트에서 더 좋은 i-quant. imatrix는 중요 가중치 통계.
  • -AWQ, -GPTQ, -FP8은 GPU 서버용. -GGUF는 llama.cpp 계열용.
  • 파일 크기 ≈ 파라미터 수 × bpw ÷ 8. 실행에는 KV cache가 더 든다.
  • MoE는 메모리는 전체, 속도는 active 파라미터 기준.
  • trust_remote_code=True는 남의 Python 코드를 실행한다.

입문자가 조심할 점

  • .safetensors 하나만 받아서 돌리려 하지 않는다. config.json과 토크나이저 파일이 같이 있어야 한다.
  • GGUF 변환본의 chat template이 틀린 경우가 있다. 대답이 이상하거나 특수 토큰이 그대로 출력되면 공식 저장소의 template과 비교한다. 모델 출시 직후의 변환본은 llama.cpp가 아직 그 아키텍처를 완전히 지원하지 않아서 다시 올라오는 경우가 많다.
  • 같은 Q4_K_M이라도 저장소마다 품질이 다를 수 있다. 만든 사람, 날짜, imatrix 여부를 본다.
  • 벤치마크 점수는 대개 원본 BF16 기준이다. 양자화 버전은 특히 수학·코드·긴 문맥에서 점수가 더 떨어질 수 있다. 실험 결과를 남길 때는 정확한 파일 이름(양자화 종류 포함)과 엔진 버전을 함께 적는다.
  • “FP16”과 “BF16”을 같은 것으로 취급하지 않는다. 둘 다 2바이트지만 넘침 특성이 다르다.
  • 라이선스를 확인한다. 가중치가 공개돼 있어도 상업 이용·재배포 조건이 모델마다 다르다. 변환본도 원본 라이선스를 따른다.

출처