safetensors
A safe, zero-copy binary format for storing and loading ML tensors without pickle
Repository Health
Technical Analysis
safetensors is a simple, safe binary format for storing and distributing tensors, built by Hugging Face as a drop-in safer replacement for Python’s pickle-based PyTorch checkpoints. The Rust crate (safetensors on crates.io) implements the core reader/writer used by the Python bindings and by every major ML framework’s safetensors integration.
Instead of executing arbitrary code during deserialization the way pickle does, safetensors stores a small JSON header describing each tensor’s name, dtype, shape, and byte offsets, followed by a flat data buffer. Reading a file means parsing that header and then memory-mapping or slicing the buffer directly — no arbitrary code ever runs, and individual tensors can be loaded lazily without touching the rest of the file. This design is what made it possible to cut multi-GPU model-loading times for large models like BLOOM from minutes down to under a minute.
What You Get
- A
no_std-compatible Rust core crate (safetensors) with an optionalstdfeature that adds file-based convenience functions - Zero-copy deserialization via
SafeTensors::deserialize, ideal for memory-mapping large checkpoint files - A
Viewtrait plusserialize/serialize_to_filefunctions for writing arbitrary in-memory tensor collections to disk - Lazy, per-tensor slicing support (
slice.rs) so distributed workers can pull only the shards they need - Hard limits on header size and strict buffer-bounds validation that make crafted files safe to parse even from untrusted sources
Common Use Cases
- Serializing trained model weights for distribution on the Hugging Face Hub instead of pickle-based
.bin/.ptcheckpoints - Loading large language model checkpoints across multiple GPUs or nodes with lazy, per-tensor reads instead of loading the whole file into RAM
- Building a new ML framework or tool that needs a dependency-light, memory-safe tensor file format without pulling in PyTorch or TensorFlow
- Auditing or converting third-party model weights safely, without risking arbitrary code execution from a malicious pickle file
Under The Hood
Architecture: The crate is a single-purpose serialization library split across three files: lib.rs (feature-gated std/no_std facade and public re-exports), tensor.rs (~1600 lines — the core SafeTensors/TensorView/Metadata/Dtype types, the View trait, and the serialize/serialize_to_file/deserialize functions), and slice.rs (~760 lines — slice iteration and range-based indexing for lazy, sub-tensor loads). Serialization computes a JSON header of tensor names/dtypes/shapes/byte-offsets, prefixes it with an 8-byte little-endian length, then writes tensor bytes contiguously; serialize_to_file writes through a sibling tempfile::NamedTempFile and atomically renames it into place, so a reader that already has the destination path memory-mapped is never left looking at a truncated file, and on macOS it opts into F_NOCACHE via an unsafe libc::fcntl call for a reported ~30% direct-I/O speedup. Deserialization parses the length-prefixed JSON header, validates offsets against a header-size cap and the buffer length, and returns a SafeTensors<'data> that borrows the input slice, so tensor accessors hand back views pointing directly into the original buffer with no copy.
Tech Stack: The crate targets Rust edition 2021 with a stated MSRV of 1.80, and is deliberately dependency-light: serde + serde_json (both default-features = false, using only alloc/derive) for the JSON header, hashbrown for a no_std-compatible HashMap, and an optional tempfile gated behind the default std feature for the atomic-write path; a macOS-only libc dependency backs the F_NOCACHE call. Dev-dependencies (criterion, memmap2, proptest) support the benchmark suite and property tests only and aren’t shipped in the published crate. CI builds and tests on Ubuntu/Windows/macOS across a pinned older toolchain (1.74) and stable, runs cargo test both with and without --no-default-features to exercise the no_std path, and runs cargo audit for supply-chain vulnerability scanning on every PR.
Code Quality: Tests are colocated in tensor.rs and slice.rs behind #[cfg(test)] modules (38 #[test]-annotated functions across the crate) plus proptest-based property tests and a fuzz/ directory with fuzz targets for structured fuzzing of the header parser — appropriate given the crate’s job is parsing untrusted input. CI enforces cargo clippy --all-targets -- -D warnings as a hard failure rather than an advisory, and error handling is centralized in a single SafeTensorError enum with From impls for std::io::Error and serde_json::Error instead of .unwrap()-heavy internals, though a few narrowly-scoped unsafe blocks exist for the macOS F_NOCACHE call and for memmap2 usage in tests/benches. The crate root is annotated #![deny(missing_docs)], so every public item is required to carry a doc comment, enforced at compile time.
API Design: The public surface is intentionally small: implement the four-method View trait on your tensor wrapper, call serialize/serialize_to_file to write, and call SafeTensors::deserialize plus .tensor(name)/.iter() to read — there is no builder ceremony or configuration object. The crate’s root doc comment is generated directly from README.md via #![doc = include_str!("../README.md")], so docs.rs stays in sync with the repository README by construction. The one real piece of friction is that View must be implemented per tensor-holding type, which is unavoidable for a true zero-copy design but means each framework integration (PyTorch, NumPy, TensorFlow, Paddle, Flax) carries its own adapter code, documented separately per framework under docs/source/api/.