mrkeyoor.com_
Wed 05 Aug 19:58 UTC
PyPIAI / MLupdated 05 Aug 2026

safetensors

safetensors is Hugging Face's file format and library for storing model weights safely. PyTorch's default checkpoint format is pickle, and unpickling a downloaded file can execute arbitrary code; a .safetensors file is just a JSON header plus a raw byte buffer, so loading one can never run code. The format is also fast (memory-mapped, near zero-copy on CPU) and lazy: you can read one tensor, or one slice of one tensor, without touching the rest of the file. The Python package is a thin binding over a small Rust core, with helpers for torch, numpy, JAX, and TensorFlow.

Verdict

The correct default for storing and sharing model weights in the PyTorch ecosystem: safe by construction, faster to load than pickle, and already the Hub standard. Just know it stores tensors, not checkpoints; training state still needs something else.

API stability4/5The on-disk format has been frozen from the start and the save_file/load_file/safe_open surface is small and steady, but the library still versions below 1.0 and sub-byte dtype handling is explicitly evolving.
Docs3/5The format specification in the README is excellent and precise; the Python API docs are thin, and behaviors like shared-tensor rejection are learned from error messages rather than guides.
Maintenance4/5Maintained by Hugging Face with recent pushes and only around 36 open issues (plus PRs), helped by a deliberately tiny scope; the bus factor is low because the core is a few hundred lines of Rust owned by a small team.
Ecosystem5/5The default weight format for transformers and diffusers and the dominant format on the Hugging Face Hub, with bindings for torch, numpy, JAX, TensorFlow, PaddlePaddle, and a Rust crate.

Use it if

  • You load model weights you did not train yourself: safetensors removes the run-arbitrary-code risk that comes with torch.load on a stranger's pickle file
  • You ship or consume Hub models: .safetensors is the default weight format across transformers and diffusers, so you are likely using it already
  • You do sharded or distributed loading: lazy per-tensor access means each rank reads only its slice instead of every rank deserializing the full checkpoint
  • Load time matters: memory-mapped loading is much faster than unpickling, especially for repeated cold starts of large models
Skip it if

Setup reality

pip install safetensors is painless: prebuilt wheels for all mainstream platforms, no dependencies at all in the base install. The gotchas are conceptual rather than build-related. Framework helpers are separate modules (safetensors.torch, safetensors.numpy, safetensors.flax) and the torch extra expects torch>=2.4 already installed. save_file raises on tensors that share memory, which is exactly what tied embeddings in language models do, so real models often need safetensors.torch.save_model instead. Tensors must be contiguous before saving. And safe_open is a context manager whose tensors are only valid inside the with block pattern people expect from files.

Patterns

Save a dict of torch tensorssave-tensors

import torch
from safetensors.torch import save_file

tensors = {
    "weight1": torch.zeros((1024, 1024)),
    "weight2": torch.zeros((1024, 1024)),
}
save_file(tensors, "model.safetensors")

save_file raises if any two tensors share memory (tied weights) or are non-contiguous; call .contiguous() or use save_model for real models.

Load a file back into a dictload-tensors

from safetensors.torch import load_file

tensors = load_file("model.safetensors")
print(tensors["weight1"].shape)

load_file materializes every tensor; for large files where you need a subset, safe_open is the lazy alternative.

Read only specific tensors lazilylazy-load-subset

from safetensors import safe_open

with safe_open("model.safetensors", framework="pt", device="cpu") as f:
    names = f.keys()
    embedding = f.get_tensor("model.embed_tokens.weight")

Only the requested tensor's bytes are read from disk; framework is 'pt', 'np', 'tf', 'flax', or 'paddle'.

Load tensors directly onto a GPUload-to-gpu

from safetensors.torch import load_file

tensors = load_file("model.safetensors", device="cuda:0")

Skips the CPU staging copy that torch.load plus .to('cuda') would do; safe_open accepts the same device argument.

Read a slice of one tensor without loading it allslice-tensor

from safetensors import safe_open

with safe_open("model.safetensors", framework="pt") as f:
    sl = f.get_slice("model.embed_tokens.weight")
    print(sl.get_shape())
    part = sl[0:1000]        # only these rows are read from disk

This is the primitive that makes tensor-parallel loading cheap: each rank slices its own shard straight from the file.

Attach metadata to a checkpointsave-with-metadata

from safetensors.torch import save_file

save_file(tensors, "model.safetensors", metadata={"format": "pt", "step": "12000"})

from safetensors import safe_open
with safe_open("model.safetensors", framework="pt") as f:
    print(f.metadata())     # {'format': 'pt', 'step': '12000'}

Metadata is strictly string-to-string; numbers and nested structures must be serialized to strings yourself.

Save a model with tied weights correctlysave-whole-model

from safetensors.torch import save_model, load_model

save_model(model, "model.safetensors")

model = MyModel()
load_model(model, "model.safetensors")

save_model resolves shared tensors by dropping duplicates that load_model restores; save_file(model.state_dict(), ...) fails on the same model.

Save and load numpy arraysnumpy-roundtrip

import numpy as np
from safetensors.numpy import save_file, load_file

save_file({"embeddings": np.zeros((100, 768), dtype=np.float32)}, "emb.safetensors")
arrays = load_file("emb.safetensors")

Files are framework-neutral: something saved from numpy loads fine through safetensors.torch and vice versa, dtypes permitting.

Convert a torch .bin checkpoint to safetensorsconvert-pickle-checkpoint

import torch
from safetensors.torch import save_file

state = torch.load("pytorch_model.bin", map_location="cpu", weights_only=True)
state = {k: v.contiguous() for k, v in state.items()}
save_file(state, "model.safetensors")

Keep weights_only=True so the conversion itself does not execute pickle code; this is the one time you still touch torch.load.

List tensor names, shapes, and dtypes without loadinginspect-header

from safetensors import safe_open

with safe_open("model.safetensors", framework="pt") as f:
    for name in f.keys():
        sl = f.get_slice(name)
        print(name, sl.get_shape(), sl.get_dtype())

Reads only the JSON header, so it is instant even on multi-gigabyte files; useful for verifying a download before loading.

Serialize to bytes instead of a filein-memory-bytes

from safetensors.torch import save, load

blob = save({"w": torch.ones(4)})     # bytes
tensors = load(blob)

Handy for sending weights over a network or stashing them in object storage without a temp file.

Alternatives

PackageRegistryPick it when
torchPyPIYou need full checkpoints with optimizer state and arbitrary objects, and you control both ends of the file.
ggufPyPIYou target llama.cpp and friends, where quantized single-file GGUF is the native format.
h5pyPyPIYou want hierarchical scientific array storage with compression, not just flat model weights.
onnxPyPIYou need to ship the computation graph together with the weights for portable inference.