ANTM

Pickle, Safetensors and GGUF: What Happens When You Load a Downloaded Model

Downloading an open-weight model is not only a licence question. The file format decides whether loading it can run someone else's code, and the format is only part of the answer.

Editorial desk

Published 7 min read

A tangled cyan wireframe on the left passes through a glowing rectangular gate with small red marks, into an orderly grid of cyan dots on the right, on a black background
Illustration generated with AI (FLUX.1 [schnell] (Black Forest Labs) via Cloudflare Workers AI, Apache 2.0). Prompt and direction by ANTM.

Most guides to open-weight models start with the licence and the benchmark table, and skip the step in between: you download a file and a program on your machine reads it. Whether that read can run someone else's code depends on the file format. This guide covers what pickle, safetensors and GGUF each do when loaded, what PyTorch changed about its default loader, and which risks remain even with a safe format. It complements Open-Weight Model Licences Compared and Open Weights vs Open Source AI, which cover what you may do with the weights; this one covers what loading them may do to you.

Why pickle is the problem

PyTorch model weights have traditionally been stored with Python's pickle. Hugging Face's Hub documentation puts it directly: "Pickle is a widely used serialization format in ML. Most notably, it is the default format for PyTorch model weights."[4] The issue is in what pickle is. Python's documentation opens its warning with "The pickle module is not secure. Only unpickle data you trust," and continues that it is "possible to construct malicious pickle data which will execute arbitrary code during unpickling."[5]

The mechanism is documented in both places. Hugging Face describes a pickle as "a stack of instructions or opcodes" read sequentially, where the GLOBAL, STACK_GLOBAL and REDUCE instructions import a function and call it.[4] Python's own example is a hand-crafted stream that imports os.system and runs echo hello world; the documentation notes that "by default, unpickling will import any class or function that it finds in the pickle data."[5] Nothing about this requires a bug. It is how the format works.

What PyTorch changed

PyTorch's torch.load uses "an unpickler under the hood" and still carries the warning "Never load data from an untrusted source."[7] What changed is the default. The serialization notes say: "Starting in version 2.6, torch.load will use weights_only=True if the pickle_module argument is not passed."[6]

With that setting the unpickler is restricted to "only executing functions/building classes" from a limited set and, unlike the standard unpickler, "is not allowed to dynamically import anything during unpickling."[6] PyTorch is explicit that this is a reduction, not a guarantee. It says the mode "narrows the surface of remote code execution attacks" and lists limits, including:[6]

  • "weights_only=True does not guard against denial of service attacks."
  • "We try to prevent memory corruptions during torch.load(weights_only=True) but they might still be possible."
  • Loading "CAN create unexpected objects for the downstream code that can also lead to memory corruption."

Two practical consequences follow. Old code that passed weights_only=False to load a full nn.Module is opting back into the dangerous path; PyTorch's own error text says that route "can result in arbitrary code execution" and should be used only for files from a trusted source.[6] And a restricted unpickler still has to parse untrusted bytes, which is why the denial-of-service caveat remains.

What safetensors is, from its own spec

Safetensors was built to avoid the problem. Its documentation calls it "a new simple format for storing tensors safely (as opposed to pickle) and that is still fast (zero-copy)."[1] The layout, as written in the repository, has three parts:[2]

  1. 8 bytes: an unsigned little-endian 64-bit integer N, the size of the header.
  2. N bytes: a JSON UTF-8 header. It "MUST begin with a { character", duplicate keys are disallowed, and the optional __metadata__ key may hold only a string-to-string map.
  3. The rest of the file: a byte buffer of tensor data.

There is no instruction stream to interpret. The header lists each tensor's name, dtype, shape and byte offsets, and the loader reads the bytes at those offsets. A side effect is that you can inspect a model's tensors without downloading it: Hugging Face's metadata-parsing guide shows reading the first 8 bytes, then the header, using HTTP range requests.[12]

Here is a local version of the same idea, written from the layout above. It reads only the header and never touches tensor data. The size cap is our own precaution, not part of the specification:

read_header.py
import json
import struct

MAX_HEADER = 100 * 1024 * 1024  # our own sanity limit, not from the spec


def read_safetensors_header(path):
    with open(path, "rb") as f:
        (n,) = struct.unpack("<Q", f.read(8))  # little-endian u64
        if n > MAX_HEADER:
            raise ValueError(f"header size {n} exceeds sanity limit")
        header = json.loads(f.read(n))
    return header


if __name__ == "__main__":
    import sys

    header = read_safetensors_header(sys.argv[1])
    meta = header.pop("__metadata__", {})
    print("metadata:", meta)
    for name, info in list(header.items())[:5]:
        print(name, info["dtype"], info["shape"])

Was it checked?

In 2023 Hugging Face, EleutherAI and Stability AI "ordered an external security audit" of the library from Trail of Bits. The result, as Hugging Face reported on 23 May 2023: "No critical security flaw leading to arbitrary code execution was found." The audit did find imprecisions in the specification, which were fixed, and missing validation that allowed polyglot files, also fixed. Hugging Face adds the caveat itself: "While it is impossible to prove the absence of flaws."[8] The audit covered the library, not the contents of any particular model.

The safetensors README also claims the format makes it "almost impossible to use malicious files" for denial-of-service attacks. Compare that with PyTorch's explicit statement that its restricted pickle mode does not guard against them.[2][6] Our reading: the claims are about different formats and do not contradict each other, but "almost impossible" is a design goal, and the independent audit is stronger evidence than the README sentence.

Speed is a secondary benefit

Hugging Face's speed page loads GPT-2 weights both ways. It reports 76.6 times faster on CPU (0.004 s against 0.307 s) and 2.1 times faster on GPU, measured on Ubuntu 18.04 with a Xeon CPU and a Tesla T4.[3] The page attributes the gains to memory-mapping and skipping copies. This is a vendor benchmark of a small model on old hardware; treat it as direction, not as a number to expect for a large checkpoint today.

GGUF: the format of llama.cpp-style runtimes

GGUF is the single-file format used by GGML-based executors. Its specification describes a header (magic number, version, tensor count, metadata count), typed key-value metadata (architecture, tokenizer, licence), tensor information with name, dimensions, type and offset, and the tensor data itself, which "may be quantized."[11] It is designed for "mmap compatibility," with tensor offsets aligned to a default of 32 bytes.[11]

The specification does not mention pickle or serialized code, and it has no security section. That supports a limited claim: GGUF is a plain data layout, not an instruction stream. It does not support a stronger one, such as "GGUF is audited" or "all GGUF readers handle malformed files safely"; we found no source for those and do not make them.

FormatCan loading execute code by design?What the documentation says
Pickle (.bin, .pt checkpoints)Yes, unless restricted"Not secure"; can "execute arbitrary code during unpickling"[5]
torch.load, weights_only=True (default from 2.6)Restricted unpickler, no dynamic importsNarrows attack surface; no protection against denial of service[6]
SafetensorsNo instruction stream; JSON header plus bytesAudited in 2023; no critical code-execution flaw found[2][8]
GGUFSpec describes typed binary fields onlyNo security section in the specification[11]

What a safe format does not cover

Custom model code. Many repositories ship Python for a new architecture. Hugging Face's Transformers guide says that for custom models "the modeling code is not from Transformers," that you load one by setting trust_remote_code=True, and that despite Hub malware scanning "you should still be careful to avoid inadvertently executing malicious code." Its extra precaution is to load "from a specific revision" using the commit hash, to avoid code that "may have changed."[10] Safetensors weights in a repository with trust_remote_code=True still run that repository's Python.

Scanners are advisory. Hugging Face says of its pickle import scan: "this is not 100% foolproof. It is your responsibility as a user to check if something is safe or not," and that its safe/unsafe import lists are maintained "in a best-effort manner."[4] In February 2024 JFrog reported "around 100 instances" of malicious models, a count that "excludes false positives," with one payload injected through pickle's __reduce__ method. JFrog also noted that Hugging Face marks pickle models as unsafe but "doesn't outright block or restrict them from being downloaded."[9] We have not independently verified those counts and the platform's policies may have changed since.

Provenance. Hugging Face notes that signed commits do not guarantee a file is safe, only its origin.[4] A trusted format from an unknown uploader is still an unknown upload.

Model behaviour. None of the sources here address what a model does once it is loaded. A file format that cannot run code on load says nothing about the model's outputs, which is a separate evaluation question (see How to Read an AI System Card).

A practical checklist

  1. Prefer repositories that publish .safetensors (or .gguf for GGML runtimes). Treat a repository that offers only pickle checkpoints as a reason to ask why.
  2. If you must load a pickle checkpoint, use a PyTorch version of 2.6 or later and keep weights_only=True. Do not switch it off to silence an error without reading the file's source.
  3. Do not set trust_remote_code=True casually. If you do, read the Python files and pin the commit hash.[10]
  4. Load unfamiliar files in an environment without credentials or sensitive data, which is our recommendation rather than something the cited documents prescribe.
  5. Inspect the safetensors header before loading when the model's size or dtypes look wrong, using the approach above.[12]
  6. Record the repository, revision and file hashes of what you deploy. Our interpretation: this makes later incident response possible; none of the sources require it.

What remains uncertain

  • Hugging Face's pickle documentation page carries an older framing ("we are experimenting with a new, simple serialization format"), so it reads as dated next to the audit and the safetensors default in Transformers. Treat the scanner description as current only to the extent the page says so.
  • We did not test any of this by loading hostile files, so every claim above is documented, not observed.
  • Other loaders (ONNX, TensorFlow SavedModel, Keras formats) were outside this article and were not researched.

Frequently asked questions

Is safetensors safer than pickle?
For code execution on load, yes by design: the format is a JSON header plus raw tensor bytes, while pickle can import and call arbitrary functions.[1][2][5] The Trail of Bits audit commissioned by Hugging Face, EleutherAI and Stability AI found no critical flaw leading to arbitrary code execution, and noted that it is impossible to prove the absence of flaws.[8]
Does torch.load(weights_only=True) make loading a pickle checkpoint safe?
It narrows the risk. PyTorch says it restricts the unpickler to tensors and primitive types and forbids dynamic imports, but also says it does not guard against denial of service, memory corruption may still be possible, and loading can create unexpected objects for downstream code.[6]
Can GGUF files run code?
The GGUF specification describes typed binary fields (header, key-value metadata, tensor information and tensor data) and does not mention pickle or serialized code.[11] It also has no dedicated security section, so this article does not claim more than that.
Does Hugging Face's pickle scan make pickle models safe to download?
No. Hugging Face says its scan is "not 100% foolproof" and that the user is responsible for deciding whether something is safe.[4] JFrog reported about 100 malicious models in 2024 and noted that the platform marks pickle models as unsafe rather than blocking them.[9]

The ANTM newsletter

The signal, not the noise.

Sourced AI coverage in your inbox. Double opt-in, unsubscribe in one click.

Referenced sources

  1. 1.
    Safetensors documentation (overview)(opens in a new tab)

    Hugging FaceCompanyPublished Nov 8, 2026Accessed Oct 3, 2026

  2. 2.
    huggingface/safetensors README (format layout and comparison table)(opens in a new tab)

    Hugging FaceCompanyPublished Nov 8, 2026Accessed Oct 3, 2026

  3. 3.
    Safetensors documentation: speed comparison(opens in a new tab)

    Hugging FaceCompanyPublished Nov 8, 2026Accessed Oct 3, 2026

  4. 4.
    Hugging Face Hub documentation: Pickle Scanning(opens in a new tab)

    Hugging FaceCompanyPublished Nov 8, 2026Accessed Oct 3, 2026

  5. 5.
    pickle: Python object serialization (security warning, restricting globals)(opens in a new tab)

    Python Software FoundationPrimary sourcePublished Nov 8, 2026Accessed Oct 3, 2026

  6. 6.
    PyTorch serialization semantics (weights_only)(opens in a new tab)

    PyTorchPrimary sourcePublished Nov 8, 2026Accessed Oct 3, 2026

  7. 7.
    torch.load (PyTorch 2.15 documentation)(opens in a new tab)

    PyTorchPrimary sourcePublished Nov 8, 2026Accessed Oct 3, 2026

  8. 8.
    Audit shows that safetensors is safe and ready to become the default(opens in a new tab)

    Hugging FaceCompanyPublished May 23, 2023Accessed Oct 3, 2026

  9. 9.
  10. 10.
  11. 11.
    GGUF specification (ggml docs/gguf.md)(opens in a new tab)

    ggml-orgReferencePublished Nov 8, 2026Accessed Oct 3, 2026

  12. 12.
    Safetensors documentation: Metadata Parsing(opens in a new tab)

    Hugging FaceCompanyPublished Nov 8, 2026Accessed Oct 3, 2026

ANTM Editorial

Editorial desk

The editorial desk at AI's Next Top Model. Every article is sourced to primary documents and approved by an editor before publication. See the editorial policy for how we work.