What's inside a .safetensors file

3 min read · Updated July 29, 2026

safetensors exists because the format it replaced could execute code. A PyTorch .bin checkpoint is a pickle, and unpickling runs arbitrary Python by design. safetensors is the minimum format that stores the same tensors without that property: three parts, no executable content, and no parser complex enough to hide a vulnerability.

The layout

Nu64 LEJSON headerN bytestensor data bufferconcatenated, row-majorbyte 0end of file
Everything before the buffer is metadata. The buffer itself is raw tensor bytes with no framing.
  1. 8 bytes: N, an unsigned little-endian 64-bit integer, the size of the header.
  2. N bytes: a JSON object mapping tensor name to {"dtype", "shape", "data_offsets"}.
  3. The rest: tensor data, concatenated.
JSON
{
  "model.embed.weight": {
    "dtype": "F16",
    "shape": [32000, 4096],
    "data_offsets": [0, 262144000]
  },
  "model.layers.0.mlp.gate.weight": {
    "dtype": "BF16",
    "shape": [11008, 4096],
    "data_offsets": [262144000, 352321536]
  },
  "__metadata__": { "format": "pt" }
}
The optional __metadata__ key holds free-form string pairs. Every other key is a tensor.

A complete reader

The format is small enough to parse without a library, which is a useful property when debugging a checkpoint that will not load.

Python
import json, struct, numpy as np

DTYPES = {"F64": "f8", "F32": "f4", "F16": "f2", "I64": "i8",
          "I32": "i4", "I16": "i2", "I8": "i1", "U8": "u1", "BOOL": "?"}

with open("model.safetensors", "rb") as f:
    n = struct.unpack("<Q", f.read(8))[0]
    header = json.loads(f.read(n))
    buf = f.read()

for name, meta in header.items():
    if name == "__metadata__":
        continue
    start, end = meta["data_offsets"]
    print(name, meta["dtype"], meta["shape"], end - start)
    if meta["dtype"] in DTYPES:                    # BF16 has no NumPy dtype
        a = np.frombuffer(buf[start:end], dtype=DTYPES[meta["dtype"]])
        arr = a.reshape(meta["shape"])
Reading only the first 8 + N bytes gives the full tensor inventory without touching the weights.

Why pickle was unsafe

Pickle is a stack language for reconstructing Python objects, and two of its opcodes make it a code execution primitive: GLOBAL resolves and imports an arbitrary module attribute, and REDUCE calls a callable with arguments from the stack. Together they let a payload import and invoke anything available in the loading environment.

The Python documentation states plainly: "The pickle module is not secure. Only unpickle data you trust." A checkpoint downloaded from a model hub is not data you trust.

PyTorch's weights_only=True restricts unpickling to a permitted set of types and is the default in recent versions. It substantially reduces the attack surface but works by constraining a general-purpose deserializer, rather than by using a format that cannot express code in the first place.

pickle (.bin, .pt)safetensors
Can express codeYes, by designNo: JSON plus a byte buffer
Read the tensor list cheaplyNo: requires deserializingYes: header only
Lazy / partial loadWhole-file mmap only (torch.load(mmap=True))Per-tensor: offsets are known upfront
Zero-copy on CPUNoYes: buffer can be mapped
Arbitrary Python objectsYesNo: tensors only

What it deliberately does not do

  • No compression. Tensor bytes are stored raw, so the file is the size of its tensors and the buffer can be mapped directly; compression would force a decode pass.
  • No nesting. The header is a flat name-to-tensor map. Module structure lives in the dotted names, by convention only.
  • No arbitrary metadata types. __metadata__ is string-to-string. Anything richer has to be encoded by the caller.
  • No optimizer state or Python objects. Checkpoints that need those still use a pickle-based format alongside.

Because the inventory is just the header, listing every tensor in a 30 GB checkpoint costs one small read. The safetensors viewer uses exactly that: it reads the header locally and lists names, dtypes and shapes without loading any weights.

Sources

  1. huggingface/safetensors (the format specification)
  2. Python: pickle module documentation (the security warning and opcode semantics)
  3. EleutherAI: safetensors security audit (third-party audit of the reference implementation)
  4. PyTorch: torch.load and weights_only

Related articles

Inspect these files

ViewKit opens these formats in the browser. The file is parsed locally and never uploaded.

← All articles