GGUF: The File Format That Made Local LLMs Practical

Format deep dive

GGUF is not just a container for weights. It is a single-file deployment unit that carries the model architecture, tokenizer, quantization metadata, and tensor data — everything needed to go from file open to first token with zero external dependencies.

If you have ever downloaded a model from Hugging Face and run it locally with llama.cpp, Ollama, LM Studio, or GPT4All, you used a GGUF file. It is the format that turned local LLM inference from a research curiosity into something anyone with a laptop can do.

But most people treat GGUF as a black box: download the Q4_K_M variant, load it, get tokens. This post opens the box and walks through the actual binary layout — header, metadata key-value pairs, tensor descriptors, alignment padding, and the weight data itself. The interactive diagram below lets you step through each section.

GGUF was created by Georgi Gerganov, the developer of llama.cpp and GGML. It replaced the earlier GGML/GGMF/GGJT formats, which had ambiguous layouts and required external files for tokenizer and chat template metadata. GGUF fixed all of that with one design rule: the file must contain everything needed to load the model. No sidecar files. No guessing.

Static GGUF file layout diagram Interactive explainer

Inside a GGUF file: from magic bytes to tensor data

Step through the binary layout of a real GGUF file. See how the header, metadata, tensor info, and weight data are organized for mmap-compatible zero-copy loading.

GGUF binary file layout model.gguf HEADER GGUF magic version: 3 n_tensors: 291 n_kv: 26 offset 0x00 METADATA KEY-VALUE PAIRS general.architecture = "llama" general.name = "Qwen3-0.6B" general.file_type = Q4_K_M llama.embedding_length = 896 llama.block_count = 28 llama.attention.head_count = 14 tokenizer.ggml.model = "gpt2" (BPE) tokenizer.ggml.tokens = [151936 entries] + 18 more KVs... TENSOR INFO DESCRIPTORS blk.0.attn_q.weight | Q4_K | [896, 896] | off: 0x000 blk.0.attn_k.weight | Q4_K | [128, 896] | off: 0x... blk.0.attn_v.weight | Q4_K | [128, 896] | off: 0x... ... 288 more tensor descriptors ALIGNMENT PADDING 0x00 0x00 0x00 ... pad to 32-byte boundary TENSOR DATA (WEIGHTS) blk.0.attn_q Q4_K blocks blk.0.attn_k Q4_K blocks blk.0.attn_v Q4_K blocks blk.0.ffn_up Q6_K blocks ··· output.weight Q6_K blocks ~420 MB (Q4_K_M)

Section 1 of 5

Header: magic bytes, version, and counts.

Every GGUF file starts with the magic bytes 0x47 0x47 0x55 0x46 ("GGUF" in ASCII), followed by the format version (currently 3), the number of tensors, and the number of metadata key-value pairs. This is enough for any loader to know what it is dealing with before reading further.

420 MBfile size (Q4_K_M) 291tensors 26metadata keys 4.5bits per weight
// GGUF header (24 bytes)
uint32_t magic;      // 0x46554747 ("GGUF")
uint32_t version;    // 3
uint64_t n_tensors;  // 291
uint64_t n_kv;       // 26

Why GGUF Won

Before GGUF, the situation was messy. The original GGML format had no metadata — the loader had to guess the architecture from the file size. GGMF added a version field but still required external tokenizer files. GGJT improved alignment but the metadata was still a fixed-order list of untyped values. Adding a new field meant breaking every loader.

GGUF fixed this with one decision: use string-keyed, typed key-value pairs for all metadata. Adding a new field (say, llama.rope_freq_base) does not break old loaders — they simply skip keys they do not recognize. This is why GGUF has survived through Llama 1, Llama 2, Llama 3, Mistral, Mixtral, Qwen, Gemma, Phi, Command-R, and dozens of other architectures without format changes.

FormatMetadataTokenizerExtensiblemmapStatus
GGMLNoneExternal fileNoNoDeprecated
GGMFVersion onlyExternal fileNoNoDeprecated
GGJTFixed-order listExternal fileNoYesDeprecated
GGUFTyped KV pairsEmbeddedYesYesCurrent
SafeTensorsHeader JSONExternal filesLimitedYesActive (different use case)

GGUF vs SafeTensors

SafeTensors is not a competitor to GGUF — they solve different problems. SafeTensors is a safe, fast tensor serialization format designed for PyTorch/JAX/TensorFlow interop. It stores tensors and a small JSON header. It does not store tokenizer vocabularies, chat templates, model architecture metadata, or quantization format details.

GGUF is a deployment format. It is designed for the last step: you have a quantized model and you want to run it on a CPU or GPU with llama.cpp, Ollama, or CKE. The file must be self-contained because the user should not need to download three more files to make it work.

The Quantization Zoo

GGUF supports over 20 quantization types, from FP32 down to 1-bit ternary. The most commonly used formats for local inference:

TypeBits/WeightBlock SizeQualitySpeedWhen to Use
Q2_K2.6256PoorFastestExtreme compression, large models on small RAM
Q3_K_M3.4256FairFastBudget inference, 70B on 32GB
Q4_K_M4.5256GoodGoodBest balance — the default recommendation
Q5_K_M5.5256Very goodModerateWhen you can afford the extra memory
Q6_K6.5256ExcellentModerateNear-FP16 quality, FFN layers in mixed quant
Q8_08.532Near-perfectSlowActivations, reference testing
IQ4_XS4.25256Good+GoodImportance-weighted, better than Q4_K at same size

The _M suffix in Q4_K_M means "mixed quantization": attention layers use Q4_K (4.5 bpw) but FFN layers use Q6_K (6.5 bpw) because FFN weights are more sensitive to quantization error. This per-layer format mixing is declared in the tensor info section — each tensor descriptor carries its own quantization type.

Static Q4_K quantization block anatomy diagram Interactive explainer

Inside a Q4_K block: 256 weights in 144 bytes

Step through the byte layout of a single Q4_K super-block. See the two-level scale hierarchy, nibble packing, and how dequantization reconstructs FP32 values.

Q4_K quantization block byte layout block_q4_K (144 bytes, 256 weights) SUPER-BLOCK SCALES d (fp16) = 0.0312 dmin (fp16) = 0.0156 bytes 0–3 SUB-BLOCK SCALES & MINS 8 sub-blocks × 32 weights each. Each sub-block has a 6-bit scale + 6-bit min. sb0: sc=5 m=3 sb1: sc=7 m=2 sb2: sc=4 m=4 sb3: sc=6 m=1 sb4: sc=8 m=2 sb5: sc=3 m=5 sb6: sc=5 m=3 sb7: sc=6 m=2 bytes 4–15 NIBBLE-PACKED WEIGHTS 256 4-bit values packed into 128 bytes. Each byte = 2 weights (lo nibble + hi nibble). byte 0 0x5A byte 1 0x3C byte 2 0x7E byte 3 0x19 ··· byte 127 0xB4 0x5A → lo=0xA hi=0x5 w₀ = 10, w₁ = 5 256 weights ÷ 2 = 128 bytes + 16 bytes scales = 144 total bytes 16–143 DEQUANTIZATION → FP32 Two-level scale hierarchy reconstructs float values from 4-bit integers. w_float = d × sc × nibble − dmin × m d=super-scale sc=sub-scale nibble=4-bit int dmin=super-min m=sub-min

Step 1 of 4

Super-block scales: d and dmin.

Each Q4_K super-block starts with two FP16 values: the scale factor (d) and the minimum factor (dmin). These are the top level of a two-level scaling hierarchy. Every weight in the block is ultimately multiplied by d and offset by dmin during dequantization.

144bytes per block 256weights per block 4.5bits per weight 5.7×compression vs FP32
// Q4_K super-block header
typedef struct {
  ggml_half d;     // super-block scale
  ggml_half dmin;  // super-block minimum
  // ... sub-block scales, mins, nibbles
} block_q4_K;

How CKE Reads GGUF

C-Kernel-Engine uses GGUF as its primary weight format, but CKE does not treat GGUF as "the model" by itself. GGUF is the deployment artifact that carries the components: metadata, tokenizer rules, tensor descriptors, quantization types, and raw weight blocks. CKE still needs a template, a tensor binding map, a dimension map, and a kernel map before those components become an executable runtime.

That distinction matters. A GGUF file can say general.architecture = "qwen2", list a tensor named blk.0.attn_q.weight, and store it as Q4_K. But CKE still has to answer: which template slot does that tensor bind to, what dimensions should it have, which operation consumes it, which quantized kernel can execute it, and which generated C pointer should point at it?

The loading path:

  1. Read the 24-byte header. Validate magic bytes and version.
  2. Parse all metadata KV pairs. Extract architecture, tokenizer, hyperparameters.
  3. Read all tensor info descriptors. Build a tensor name → offset map.
  4. mmap the data section. Set up tensor pointers using offsets from step 3.
  5. Match general.architecture and family-specific metadata to a CKE template.
  6. Bind GGUF tensor names to template slots such as q_proj, k_proj, v_proj, gate_proj, up_proj, and down_proj.
  7. Validate dimensions against the architecture contract: d_model, head_dim, n_heads, n_kv_heads, n_layers, vocab_size, and sequence/runtime limits.
  8. Match each tensor's quantization type to legal CKE kernel-map candidates. A Q4_K tensor cannot accidentally run through a Q8-only path.
  9. Generate IR, lower it into concrete buffer/kernel calls, and emit C that points directly into the mmap'd GGUF tensor data.

The important detail: CKE does not convert GGUF weights into a different private format before doing useful work. The goal is to operate on the quantized blocks directly. The mmap'd data region becomes the source of truth for weight pointers, and CKE's job is to bind those pointers into a template-defined circuit cleanly enough that generated C can execute it and parity/profiling can verify it.

GGUF Is The Component Box; CKE Builds The Circuit

The best mental model is electronics. GGUF is the box of labeled components. The CKE template is the circuit diagram. The dimension map is the pinout. The graph slots are the wires. The kernel map is the part catalog that says which implementation is legal for each operation. LoweredIR is the physical wiring plan. Generated C is the board that gets powered on.

GGUF artifactWhat it tells CKEWhat CKE still has to decide
general.architectureThe broad model family, such as LLaMA/Qwen/Gemma-like.Which exact CKE template and family overrides apply.
Metadata KV pairsLayer count, embedding length, head count, RoPE settings, tokenizer details.Whether the dimensions match the template contract and runtime mode.
Tensor descriptorsTensor name, shape, quant type, and byte offset.Which semantic slot each tensor binds to.
Quantization typePhysical weight encoding: Q4_K, Q5_K, Q6_K, Q8_0, F16, F32, and so on.Which kernel-map candidates are legal for this tensor and CPU target.
Data offsetWhere the tensor lives inside the mmap'd file.Which generated C pointer should reference it.
Tokenizer/chat templateHow text becomes tokens and conversation turns are formatted.Whether the runtime prompt path matches the model's expected input contract.

This is why GGUF support is not just a file parser. A parser can read bytes. A runtime has to preserve meaning. If blk.0.attn_q.weight binds to the wrong projection slot, the file still loads. If rope_freq_base is ignored, the model may still generate tokens. If a Q4_K tensor is routed through a mismatched dot-product path, the program may still compile. The output can be wrong long before the loader crashes.

Where GGUF Enters The CKE Compiler Path

In CKE, GGUF enters before template lowering, not after. The file provides the physical weights and metadata. The template provides the architecture circuit. The binding layer joins them. From that point forward, the compiler path can become explicit:

GGUF header + metadata
  -> architecture family + tokenizer contract + hyperparameters

GGUF tensor descriptors
  -> tensor name map + quant type map + mmap offsets

CKE template
  -> header/body/footer + operation circuit + graph slots

Binding layer
  -> template slot -> GGUF tensor pointer -> checked dimensions

GraphIR / LoweredIR
  -> symbolic dataflow -> concrete buffers, dtype views, kernel ids

Generated C
  -> pointer expressions + kernel calls + runtime execution

This is also where the CKE IR visualizer becomes useful. A GGUF file can tell CKE what tensors exist. The IR visualizer can show whether those tensors were wired into the expected graph. That is the difference between "the file loaded" and "the model circuit is actually inspectable."

For the full CKE quantization details, see the CKE Quantization Deep Dive and the CKE Quantization Format Infographic. For the runtime/compiler side, the useful next links are the v7 runbook, the v8 runbook, and my note on CKE's v8 IR pipeline and codegen.

mmap Weights And The CKE Bump Arena

There are two different memory stories here, and they should not be mixed up. GGUF weight data lives in the file. When CKE opens the GGUF with mmap, tensor pointers can point directly into the mapped file region. The runtime does not need to deserialize every weight into a new heap allocation before it can reason about the model.

The bump arena is the other side of the runtime: activations, scratch buffers, temporary Q/K/V views, attention workspaces, recurrent state views, and generated-kernel outputs. Those buffers are not the GGUF file. They are runtime memory that CKE can size, reuse, and inspect while executing the template-defined circuit.

Static GGUF mmap loading flow diagram Interactive explainer

GGUF mmap weights vs CKE bump arena

Step through the data flow: open the file, mmap it, build pointer tables, then watch kernels read weights while the bump arena handles activations.

GGUF mmap weights vs CKE bump arena data flow GGUF on disk header + metadata (KV pairs) tensor info (291 descriptors) quantized weight data Q4_K attn blocks Q6_K FFN blocks F32 norms / output contiguous, aligned to 32 bytes ~420 MB on disk (Q4_K_M) mmap region virtual memory → file pages wq = base + 0x1A00 wk = base + 0x3E800 gate = base + 0x7D000 down = base + 0xBB800 no malloc, no memcpy page faults load on demand CKE bump arena runtime activations + scratch token embeddings / residual Q / K / V temporary views attention scratch / softmax logits / output projection KV cache workspace bump cursor → mmap() kernel reads arena reused per layer — no malloc per op

Step 1 of 4

GGUF file on disk: self-contained deployment unit.

The GGUF file sits on disk as a single contiguous binary. Header, metadata, tensor descriptors, alignment padding, and quantized weight data — all in one file. No sidecar tokenizer, no external config. For a Qwen3-0.6B Q4_K_M, this is roughly 420 MB.

// Step 1: open the GGUF file
int fd = open("model-Q4_K_M.gguf",
              O_RDONLY);
// Read 24-byte header
// Parse metadata KV pairs
// Read tensor info descriptors

Weights are mmap-backed file pointers. Activations and scratch buffers live in a separate CKE arena. The two memory worlds never mix.

The CKE Sidecar: What Travels Alongside the Weights

But the bump arena with weight pointers is only half the CKE runtime story. A GGUF file gives CKE the raw components — tensor data, metadata, tokenizer. CKE still needs to answer: which template defines the layer circuit? Which kernel map is legal for this quantization type on this CPU? Where are the precomputed RoPE frequency tables? What is the chat template for conversation formatting? Where is the compiled shared library?

These answers come from the sidecar — the set of runtime artifacts that CKE generates alongside the BUMP weight blob during the conversion step (convert_gguf_to_bump). The sidecar is not a single file. It is a directory of artifacts that together form the complete runtime contract: template, dimension map, kernel map, tokenizer, RoPE tables, generated C, compiled libmodel.so, and canary metadata. Without the sidecar, the weights are just bytes. With it, they become an executable circuit.

Static C-Kernel-Engine GGUF sidecar diagram Interactive explainer

CKE sidecar: from GGUF to executable runtime

Step through the conversion pipeline. See how GGUF becomes a BUMP weight blob plus the sidecar artifacts that make it an executable circuit.

CKE sidecar: GGUF to BUMP conversion and runtime artifact tree GGUF file header + metadata tensor descriptors tokenizer vocab Q4_K weight data convert_gguf_to_bump parse → validate → repack align → write BUMP blob + emit sidecar artifacts BUMP weight blob contiguous, 64-byte aligned Q4_K attn weights Q6_K FFN + F32 norms weights Sidecar artifacts everything the runtime needs beyond raw weights 🔧 Template qwen3 / gemma3 / nanbeige circuit 📐 Dimension map d_model, n_heads, n_kv, n_layers ⚙️ Kernel map Q4_K→gemm_nt_q4_k, Q6_K→q6_k 🔤 Tokenizer BPE vocab + merges + special tokens 🌀 RoPE tables precomputed cos/sin freq tables 💬 Chat template conversation turn formatting 📝 Generated C ck-kernel-inference.c + codegen 📦 libmodel.so compiled shared library 🛡️ Canary metadata corruption detection words 📊 IR report ir_report.html visualizer sidecar CKE Runtime template binds BUMP pointers → IR → generated C → libmodel.so → inference bump arena owns activations • mmap'd weights stay in file • canaries guard boundaries

Step 1 of 5

GGUF input: the raw material.

The GGUF file carries metadata, tensor descriptors, tokenizer vocabulary, and quantized weight data. CKE does not run GGUF directly — it converts it into its own internal format (BUMP) plus a set of sidecar artifacts that together form the executable runtime.

# CKE conversion command
python convert_gguf_to_bump_v8.py \
  --gguf model-Q4_K_M.gguf \
  --run $RUN \
  --template qwen3 \
  --force-convert

This is also the natural handoff point for the next post. The sidecar's template — the architecture circuit definition that tells CKE which ops to wire, in what order, with which attention variant — deserves its own deep dive. That is where CKE's design diverges most from other inference engines: the template is not a Python class, not a config file, not an ONNX graph. It is a declarative circuit contract that the compiler can lower, validate, and generate C from. More on that next time.

Making Your Own GGUF

The easiest path from a Hugging Face model to GGUF:

# Using llama.cpp's convert script
python convert_hf_to_gguf.py \
  --outfile model-f16.gguf \
  --outtype f16 \
  ./path-to-hf-model/

# Quantize to Q4_K_M
./llama-quantize model-f16.gguf model-Q4_K_M.gguf Q4_K_M

# Or use Hugging Face's web tool:
# https://huggingface.co/spaces/ggml-org/gguf-my-repo

The conversion script reads the PyTorch/SafeTensors weights, maps tensor names to GGUF conventions (e.g. model.layers.0.self_attn.q_proj.weightblk.0.attn_q.weight), embeds the tokenizer, and writes the GGUF file. The quantization step then reads the F16 GGUF and produces a quantized variant.

What Makes a Good GGUF File

Not all GGUF files are created equal. A well-made GGUF file:

  • Includes the full tokenizer — vocabulary, merge rules, special tokens, and added tokens. Without these, the model generates garbage.
  • Includes the chat template — the Jinja2 template that formats user/assistant turns. Without it, the model may work but conversations will be formatted wrong.
  • Uses appropriate quantization per layer — Q4_K_M mixes Q4_K for attention and Q6_K for FFN. A naive all-Q4_0 file will be smaller but noticeably dumber.
  • Follows the naming conventionBaseName-SizeLabel-Version-Encoding.gguf (e.g. Qwen3-0.6B-v1.0-Q4_K_M.gguf).
  • Includes RoPE parametersrope_freq_base, rope_scaling_type. Missing these causes silent degradation on long contexts.
Related reading