pytorch/pytorch
Serialization
Active contributors: mikaylagawarecki
Purpose
Saving and loading models and tensors is a deceptively complicated topic in PyTorch. Three formats coexist:
- Python pickle (
torch.save/torch.load) — the default, most flexible, and (until recently) the most dangerous because pickle can execute arbitrary code on load. - Weights-only loading — a restricted unpickler that only allows
torch.Tensorand other vetted types. This is now the default in PyTorch 2.x fortorch.load. - TorchScript / ZIP archive (
torch.jit.save) — a structured zip with serialized IR + weights. The format used by PyTorch Mobile and most pre-2.0 production deployments.
A fourth, newer path is torch.export + torch._export.save/load, which serializes an ExportedProgram for AOTInductor and ExecuTorch.
Directory layout
| Path | Contents |
|---|---|
torch/serialization.py |
torch.save, torch.load, the user-facing API (~87K lines) |
torch/storage.py |
Storage Python wrapper |
torch/_weights_only_unpickler.py |
Restricted unpickler |
torch/csrc/Storage*.cpp |
C++ Storage Python binding |
torch/csrc/jit/serialization/ |
TorchScript zip format |
caffe2/serialize/ |
Low-level zip / file reader/writer (PyTorchFileReader/Writer) |
torch/_export/serde/ |
torch.export schema (Pydantic) and serialization |
aten/src/ATen/MapAllocator.cpp |
mmap-backed storage for shared/large tensors |
Key abstractions
| Type | File | Purpose |
|---|---|---|
torch.save / torch.load |
torch/serialization.py |
Top-level API |
_open_zipfile_writer |
torch/serialization.py |
The new (PyTorch 1.6+) zip-based format |
_open_legacy_zip |
torch/serialization.py |
The legacy tar-based format |
Unpickler (weights-only) |
torch/_weights_only_unpickler.py |
Restricted Python unpickler |
PyTorchFileReader/Writer |
caffe2/serialize/inline_container.h |
Low-level zip stream reader/writer |
_load_jit_data |
torch/jit/_serialization.py |
TorchScript loader |
How it works
torch.save / torch.load (modern)
The default save format is a zip archive containing:
data.pkl— pickled top-level object (e.g., astate_dictor a model). Pickle uses persistent IDs for tensors, so each tensor in the pickle stream is replaced with a small marker.data/<n>— one file per unique storage, containing the raw bytes.byteorder— little-endian indicator.- (For
torch.jit.saveonly)code/,constants.pkl, etc.
Saving streams the pickled object first, then writes each storage referenced by a persistent ID into a separate zip entry. Loading is the inverse: pickle reads markers, the loader maps each marker to a torch.Storage view of the corresponding zip entry, and tensors are reconstructed with the right shape/strides.
graph LR
User[Python object<br/>state_dict / model] -->|pickle.dump<br/>w/ persistent_id| Pkl[data.pkl]
User -->|raw bytes per storage| Zip[data/0, data/1, ...]
Pkl --> Archive[ZIP archive]
Zip --> ArchiveWeights-only
torch.load(..., weights_only=True) (the default since 2.x for tensors) uses torch/_weights_only_unpickler.py instead of stock pickle.Unpickler. The restricted unpickler:
- Allows only a hard-coded set of types:
torch.Tensor,torch.Storage,torch.dtype,torch.device,collections.OrderedDict, simple types. - Disallows arbitrary
GLOBALopcodes; users can opt-in viatorch.serialization.add_safe_globals. - Refuses
REDUCEcalls except for the vetted constructors.
This closes the long-standing remote-code-execution gotcha where anyone with a .pt file could ship a malicious payload.
torch.jit.save
For ScriptModules the format is the same zip archive but with extra entries:
code/<class>.py— TorchScript source for the model.constants.pkl— pickled constants.attributes.pkl— module attributes.version— serialization format version.
Loaded via torch.jit.load, which goes through torch/csrc/jit/serialization/import.cpp and reconstructs the IR.
torch.export save/load
torch._export.save(ep, path) writes a directory or zip with the FX graph (in textual form), the schema-validated metadata (torch/_export/serde/schema.py), and parameter/buffer tensors. The format is versioned via the schema; backward compatibility is enforced by tests in test/export/.
Maps and mmap
aten/src/ATen/MapAllocator.cpp implements memory-mapped tensors. torch.load(..., mmap=True) mmap-loads each storage instead of reading it into RAM, which is essential for very large LLM checkpoints.
Integration points
- Distributed. Distributed checkpoint (
torch.distributed.checkpoint) bypassestorch.savefor large jobs and uses its own per-shard format. See Distributed. - Mobile. PyTorch Mobile reads
.ptl(lite-interpreter) files, an extension of the JIT zip format with a stripped runtime. - HuggingFace
safetensorsis preferred by some communities for tensor-only data; PyTorch interoperates via thesafetensorspackage, not native code. - Security. Per
SECURITY.md, weights-only load is the supported safe path; the old default (arbitrary pickle) should be considered untrusted.
Entry points for modification
- New tensor type to support in weights-only load → register via
torch.serialization.add_safe_globals(user) or add to the vetted list in_weights_only_unpickler.py(in-tree). - Format changes → bump the
_pyzipfile_data_pkl_format_versionintorch/serialization.pyand update readers. - New mmap behaviour →
aten/src/ATen/MapAllocator.cpp.
Key source files
| File | Purpose |
|---|---|
torch/serialization.py |
User-facing save/load |
torch/_weights_only_unpickler.py |
Restricted unpickler |
caffe2/serialize/inline_container.cc |
Zip reader/writer |
torch/csrc/jit/serialization/pickler.cpp |
TorchScript pickler |
torch/csrc/jit/serialization/import.cpp |
TorchScript loader |
aten/src/ATen/MapAllocator.cpp |
mmap-backed storage |
torch/_export/serde/schema.py |
torch.export serialization schema |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.