DEV Community

Cover image for GGUF vs safetensors — the runtime decides, not the format
Mr Say Nothing
Mr Say Nothing

Posted on Originally published at mrsaynothing.dev

GGUF vs safetensors — the runtime decides, not the format

Originally published on mrsaynothing.dev · 14 October 2026. The record read, not run — every number below is checkable with one curl.

DuckDuckGo autocompletes gguf vs safetensors as if it were a cage fight between two file extensions. It is a gate check. Pick GGUF and one shelf of runtimes opens — llama.cpp, Ollama, LM Studio, Jan. Pick safetensors and a different shelf opens — transformers, vLLM, ComfyUI. The format is the list of doors that will open; everything else follows. The lie this piece refuses: that the fight is decided by speed numbers nobody has matched on the same box. What the record gives you for free — and what most arguments skip — is what each container weighs, and where each one can even be opened.

What are you actually choosing between?

GGUF is a single-file container built by the llama.cpp authors, and quantization is baked into it per tensor — the quantization ladder everyone argues about only exists inside this format. One file carries weights, tokenizer and chat template: the file is the distribution. Safetensors is a serialization container — a JSON header plus raw tensor bytes — and it carries whatever precision the training repo shipped, usually BF16 or FP16. It does not quantize; it stores.

Safetensors is named after its single job: making sure nobody gets pickled at model-load time. Python's pickle executes code on unpack; safetensors executes nothing — that is the whole security pitch, and it is why the Hugging Face ecosystem standardised on it.

Weigh the same model in each container and the philosophies show in the bytes. On 14 October 2026, the same Qwen3-0.6B shipped as: Q4_K_M GGUF, 396,705,472 bytes (397 MB) · BF16 GGUF, 1,198,182,848 bytes (1.20 GB) · BF16 safetensors, 1,503,300,328 bytes (1.50 GB). The quantized container holds a quarter of the full-precision one because it holds a quarter of the precision — and every one of those numbers is one curl -I away from your own terminal, no benchmark rig required. Whether the missing precision costs you quality is a fidelity question the bytes cannot answer.

The format is not a preference. It is the list of runtimes that will open your file.

What do the bytes say?

The honest rule for a record-read piece: only numbers the record states, attributed, no bench cosplay. The table is one model, three containers, exact bytes as shipped — check them yourself before trusting any of it.

Container Precision Bytes as shipped Share of full-precision
GGUF Q4_K_M 4-bit quantized 396,705,472 (397 MB) 26%
GGUF BF16 full, llama.cpp packing 1,198,182,848 (1.20 GB) 80%
safetensors BF16 full, raw tensors 1,503,300,328 (1.50 GB) 100%

Check it yourself, no benchmark rig required:

curl -sIL https://huggingface.co/unsloth/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q4_K_M.gguf | grep -i content-length
Enter fullscreen mode Exit fullscreen mode

Same one-liner against the BF16 and safetensors files — the byte count, same day or any day.

Read the table the honest way. The 26% is the quantization ladder's whole promise: four bits where sixteen were, on disk and in RAM. The gap between the two full-precision rows — 1.20 against 1.50 GB — is packing overhead, not physics: llama.cpp's BF16 packing stores the same tensors leaner than the raw dump. What the bytes cannot say is speed or quality; a tokens-per-second column with no matched box behind it is forum noise, and this piece refuses to print one.

Three walls, stated plainly. One: quality — Q4 against BF16 is a fidelity trade, and no byte count measures it. Two: speed — load times and tokens per second belong to a matched bench on a named box, which this piece does not have, so it prints none. Three: scale — byte ratios from a 0.6B model hold shape at 70B, but the absolute waits you will feel at 70B are a different article on different hardware.

Where does each format actually run?

This is the part that actually decides. GGUF opens in llama.cpp, Ollama, LM Studio, Jan — the single-file local ecosystem. vLLM reads GGUF too, through its plugin, on GPU, with caveats. Safetensors opens in transformers, vLLM natively, ComfyUI, and Apple's MLX tooling. The overlap at the edges is instructive: Ollama — the appliance built on a GGUF engine — still ships an MLX runner so Apple Silicon users can load safetensors-derived weights. Even the appliance hedges.

You already run Your format picks itself Why
Ollama, LM Studio or llama-server GGUF Their engines eat GGUF natively; quantized files fit small disks
A Python/transformers pipeline safetensors from_pretrained is the native path; no conversion step to maintain
vLLM on a GPU server safetensors Native, no plugin, no GGUF caveats
ComfyUI or an image pipeline safetensors The diffusion ecosystem trades in safetensors checkpoints
A small disk and a small box GGUF Q4_K_M 397 MB against 1.50 GB for the same weights

GGUF makes quantization the default; safetensors makes full precision the default. That default is most of the decision.

Which one should you serve?

Check what is already installed before checking any table. If the machine answers to ollama run, you live in GGUF country — take the Q4_K_M file and stop reading forums. If your day is Python, or your serving layer is vLLM, safetensors was never really a choice; it is the ecosystem's native tongue. Converting between the two is possible and almost never worth it: every conversion is a new place for the weights to silently differ.

The honesty line, house style: I've read the record, not run it. Every number above comes from the files as shipped, pulled the morning this post went up — no bench rig, no tokens-per-second cosplay. What the record does establish is the shape of the trade — a quarter of the bytes for the quantized container, two runtimes that barely overlap, and a default precision on each side that quietly decides for most people.

Nobody ever switched format for speed. They switched runtime for a feature, and the format came along as the ticket.

Over to you

This one is a discuss-first ride, so the argument moves to the comments. My side is on the table: pick the runtime first, let the format tag along. Where I expect pushback — the claim that a fair comparison needs a named box before it needs an opinion, and that most format debates are runtime debates wearing a costume.

So, two things worth arguing: which shelf does your daily driver actually open from — and has anyone here felt the Q4 quality drop on a real task, or is that forum folklore? Name the runtime and the model; the best-receipted answer sets the terms of the next piece.

Top comments (1)

Collapse
 
mrsaynothing profile image
Mr Say Nothing •

The whole argument fits in one line: the same Qwen3-0.6B weighs 397 MB as Q4_K_M GGUF and 1.50 GB as BF16 safetensors — a quarter of the bytes, and both numbers are one curl -I away if you want to check. What the bytes can't settle is whether the missing precision costs anything you'd actually notice. So, honest receipts only: has a low quant ever caught YOU out — a hallucination, a dropped code fence, a mangled table — where the full-precision file behaved? Name the quant and the task; that's the evidence this debate never has enough of.