Skip to content

Checking a GGUF File's Metadata Before You Trust It

9 min read · updated August 11, 2026

A GGUF file is self-describing. Everything you need to decide whether it will load, how much memory it wants, what chat template it expects and what it was quantized from is in a key-value store at the front of the file — readable in milliseconds, before the tensors are touched.

Why the filename is not the answer

Filenames on model repositories are conventions, not guarantees. Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf is a string somebody typed. It can be wrong about the quantization mixture, it says nothing about the context length the file declares, it does not tell you which architecture identifier the runtime will look up, and it cannot tell you whether the file carries a chat template or expects you to supply one. A re-quantized file, a file produced by a fork, or a file renamed during a re-upload will all sit there looking correct.

The header answers all of it. GGUF stores a magic number, a version, a count of tensors, a count of metadata key-value pairs, then the key-value pairs themselves, then a tensor-info table, then the tensor data. The ggml project’s GGUF specification documents the layout and the standardised key names; the magic is the four bytes GGUF and the current format version is 3.

Dumping the header

The reference reader is the gguf Python package that ships from the llama.cpp repository as gguf-py. It installs from PyPI and does not require llama.cpp to be built.

  1. Install the reader into a throwaway environment: python3 -m pip install gguf. The package is the same code llama.cpp’s own conversion scripts use.
  2. Dump the metadata and skip the tensor listing:
    gguf-dump --no-tensors ./Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf
    The --no-tensors flag is the point of the exercise: it reads the header and the key-value store and stops. Nothing is memory-mapped, nothing is dequantized, and a 40 GB file answers as fast as a 4 GB one.
  3. Dump with tensors when you want to know what the quant preset actually did. Run gguf-dump without the flag and read the type column: a file labelled Q4_K_M will show a mixture ofQ4_K and Q6_K tensors, and often an F32 row for each normalisation weight.
  4. Read specific keys programmatically when you are gating a deployment on them:
    from gguf import GGUFReader
    
    r = GGUFReader("model.gguf")
    kv = {f.name: f for f in r.fields.values()}
    
    arch = str(bytes(kv["general.architecture"].parts[-1]), "utf-8")
    print("architecture:", arch)
    print("file_type:", kv["general.file_type"].parts[-1][0])
    print("context:", kv[f"{arch}.context_length"].parts[-1][0])
    print("layers:", kv[f"{arch}.block_count"].parts[-1][0])
    print("tensors:", len(r.tensors))
    Note the pattern in the last three keys: architecture-specific keys are namespaced by the value of general.architecture, so you have to read that field before you can name the others.

The fields that matter

  • general.architecture — the identifier the runtime dispatches on (llama, qwen3, gemma3 and so on). If your llama.cpp build does not know this string, the file will not load no matter how new the format version is. This is the single field to check first when a new release will not open.
  • general.file_type — an enum naming the quantization mixture the file was produced with. The spec lists the values; MOSTLY_Q4_K_M is 15, MOSTLY_Q6_K is 18, MOSTLY_Q2_K is 10, ALL_F32 is 0 and MOSTLY_F16 is 1. The word mostly is doing real work and is the subject of the next section.
  • general.quantization_version — required whenever tensors are quantized. A file from an old quantization epoch can be structurally valid and still not load in a current build.
  • <arch>.context_length, <arch>.block_count, <arch>.embedding_length, <arch>.attention.head_count_kv — the shape of the model. The last two are what you need to work out how much memory the KV cache will want at a given context, which is usually the number that decides whether the model fits.
  • tokenizer.ggml.model, tokenizer.ggml.eos_token_id, tokenizer.chat_template — how text becomes tokens and how a conversation is framed. A missing or wrong eos_token_id is the usual cause of a model that generates correct text and then refuses to stop.
  • general.license, general.license.link, general.size_label, general.quantized_by — provenance. These are optional in the spec, so their absence proves nothing, but where present they are the fastest way to find out whose terms you are operating under before you put the file into a product.

Three fields people misread

Context length is the training length, not your limit

<arch>.context_length records the context the model was trained or extended to. It is not a promise about what you will get, in either direction. Runtimes let you request less — llama-server -c 8192 against a model that declares 131072 — because the KV cache for the declared length may not fit in your memory. Runtimes also let you request more, using RoPE scaling parameters that are themselves stored in the metadata under <arch>.rope.*, with quality consequences the file cannot tell you about. If you are hitting a context-length error locally, this field tells you the ceiling you are negotiating against, not the one you have.

File type is a label on a mixture

general.file_type says which preset was requested. It does not say that every tensor is that type, and for k-quants it never is: llama-quantize deliberately holds some tensors at a higher precision than the preset name, and keeps one-dimensional tensors such as normalisation weights at F32. The tensor listing is the ground truth. This matters when you are checking whether an adapter or a runtime supports the types actually present, rather than the type named on the tin.

Parameter count is not in the header

There is no standard key holding a parameter count. Tools that print one compute it by summing tensor element counts from the tensor-info table. That is why the figure a dump reports can differ slightly from the number on a model card, and why the file size never divides cleanly by the marketing parameter count — the subject of the arithmetic behind model file sizes.

Pinning the file’s identity

Once the header says what you expected, record what the file is so that a later copy can be checked against it. A plain sha256sum model.gguf is the right answer for “is this the same bytes I approved”, and it is what belongs in a transfer manifest.

It is the wrong answer for “are these the same weights”, because editing one metadata key — a name, a chat template, an override applied with --override-kv — changes the file hash while leaving every tensor identical. llama.cpp ships llama-gguf-hash for exactly that distinction: it hashes the tensor payload per tensor and as a whole, with --sha256, --sha1, --xxh64 and --uuid modes and a --check option that verifies against a manifest.

# whole-file identity: has anything at all changed?
sha256sum model.gguf

# tensor identity: are the weights the same despite a metadata edit?
llama-gguf-hash --sha256 --no-layer model.gguf
Flag names in this area move. gguf-dump, llama-gguf-hash and the gguf package are all part of the llama.cpp tree and have been renamed before — check --help against your build rather than trusting a copied command line. The GGUF key names themselves are far more stable, because changing one breaks every published file.