Skip to content
C.W.K.
Stream
Lesson 02 of 06 · published

Safetensors, and Why It Won

~13 min · safetensors, pickle, security, format

Level 0Kindling
0 XP0/32 lessons0/10 achievements
0/100 XP to next level100 XP to go0% complete

The problem it solved was never just speed

For most of deep learning's history, "a model file" meant a pickle: Python's serialize-anything format, which works by recording the construction steps for objects and replaying them at load time. That generality is exactly its danger. Loading a pickle executes logic — arbitrary, invisible, once-per-load logic. A malicious checkpoint is not a hypothetical: loading a pickle has always meant executing its construction logic, and a crafted pickle runs code at load. Recent PyTorch (2.6+) defaults to weights_only=True, which blocks arbitrary execution — a protective default that helps the unwary but does not change what the format is. Every "download weights from a stranger" workflow in the pickle era was running untrusted code with a friendly face.

Safetensors took the opposite bet: serialize data, not behavior. The file is a small JSON header describing each tensor — name, dtype, shape, byte offset — followed by the raw tensor bytes. Nothing in the file is executable. Loading is a matter of parsing JSON and memory-mapping bytes. Two consequences follow immediately: no code execution at load (the security property), and the header alone tells you everything about the layout without reading a single tensor (the inspectability property). Faster zero-copy loading was almost a side effect — the real wins were trust and legibility.

Reading the header is an archivist's superpower

Because the header is plain JSON at the front of the file, you can audit a safetensors artifact with nothing but the standard library: open the file, read the eight-byte little-endian header length, then the header itself. You learn the exact tensor inventory — names, dtypes, shapes — before committing to a full read. For an archive, this matters in at least three places: verifying that what landed matches what the metadata promised (dtype forensics, next lessons), diffing two artifacts that claim to be the same model, and triaging unknown files pulled from an old drive with no README in sight.

The ecosystem voted with its defaults: major hubs and training frameworks moved to safetensors as the standard serialization for distribution, keeping pickle formats for internal checkpoints where the code and the checkpoint share a trust domain. For acquisition purposes, treat safetensors as the expected format generation — and treat a pickle-format artifact from an unknown uploader as a security question first and a data question second.

Pickle replays construction; safetensors describes data. One executes on load, one never does. An archive that plans to load files years after acquisition should have a strong bias toward the format that cannot execute anything.

Hands on the header

The exercise below reads a safetensors header in a dozen lines of stdlib Python — no ML framework, no dependencies. Run it against a file you already hold and against one freshly downloaded; the inventory it prints is the ground truth that config files and model cards can only promise.

Code

Read a safetensors header with zero dependencies·python
import json, struct

path = "model-00001-of-00003.safetensors"
with open(path, "rb") as f:
    n = struct.unpack("<Q", f.read(8))[0]      # header length (LE u64)
    header = json.loads(f.read(n))             # plain JSON tensor map

# 'metadata' may carry format metadata; everything else is a tensor
meta = header.pop("__metadata__", {})
print("file metadata:", meta)
tensors = header
print(f"{len(tensors)} tensors")
for name, spec in sorted(tensors.items())[:10]:
    print(f"  {spec['dtype']:>8} {str(spec['shape']):<24} {name}")

# dtype census — the fingerprint of the artifact's precision
census = {}
for spec in tensors.values():
    census[spec["dtype"]] = census.get(spec["dtype"], 0) + 1
print("dtype census:", census)
# If the census says F32 but the card says BF16, you have learned
# something the label was never going to tell you.

External links

Exercise

Run the header reader on every safetensors shard you currently hold. For each file, record: tensor count, dtype census, and one tensor's name/shape. Then compare the census against whatever dtype the repo's config or card claimed. Any mismatch is a finding — write down which side you believe and why.
Hint
The header read is cheap (bytes, not tensors) so running it over a whole archive costs seconds. Mismatches worth chasing: config says bfloat16, census says F32 (an upcast?); census mixes dtypes (a partially converted artifact).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.