Safetensors is one of the least interesting file formats in machine learning, and that is the entire achievement. It does one thing — store named arrays of numbers — with no extensibility, no cleverness and no way to express anything that is not a tensor. Every property people value in it follows directly from that refusal.
The whole format
That is the complete specification. The JSON names each tensor and gives its dtype, shape and byte range within the buffer.
A real file, byte by byte
A tiny model with two tensors. Reading from offset zero:
Check the arithmetic, because it is the whole design: 1000 × 64 elements at 2 bytes each (F16) is 128,000 bytes, which is exactly the range given. 1000 elements at 4 bytes each (F32) is 4,000, which is exactly the second range. Nothing is compressed, nothing is encoded, nothing is inferred.
💡 Offsets are relative to the buffer, not the file
A tensor at data_offsets: [0, 128000] does not start at file byte 0 — it starts at byte 0 of the buffer, which begins after the 8-byte prefix and the header.
The absolute position is 8 + header_length + offset. Getting this wrong is the classic error when writing a reader by hand, and it produces tensors that are shifted rather than obviously broken — which is much harder to diagnose.
The dtype table
| Code | Type | Bytes | Common use |
|---|---|---|---|
F64 | double | 8 | Rare |
F32 | float | 4 | Older weights, biases |
F16 | half | 2 | Inference weights |
BF16 | bfloat16 | 2 | Modern default |
F8_E4M3 | fp8 | 1 | Aggressive quantisation |
I64 … I8 | signed int | 8 … 1 | Indices, tokens |
U8 | unsigned byte | 1 | Packed quantised data |
BOOL | boolean | 1 | Masks |
Note what is missing: there is no complex type, no string type, no nested or ragged type. A tensor is a rectangular block of one numeric type, which is why the offset arithmetic always works out exactly.
Why loading is nearly instant
This is the property that makes the format worth adopting even setting security aside.
Because the bytes are already in the layout a tensor expects, there is nothing to convert. The operating system maps the file into the address space and pages it in on demand. A 30GB checkpoint "loads" in milliseconds, with actual disk reads happening lazily as tensors are touched.
Two consequences follow that matter in practice:
- You can load one tensor from a huge file without reading the rest — useful for inspection, surgery on specific layers, and merging.
- Several processes can share one mapping. Two inference workers on the same machine map the same physical pages rather than each holding a private copy.
⚠️ Alignment matters for mmap performance
For memory-mapped access to be efficient, tensor data should begin at an aligned offset. Writers typically pad the header so the buffer starts on an 8-byte boundary, and many pad further.
If you write safetensors by hand and skip this, files remain valid and readable — they just lose the zero-copy speed advantage that was the point. Pad the JSON header with spaces to reach your alignment.
The metadata slot, and its limits
One reserved key, deliberately constrained:
Strings to strings, flat, no nesting. Numbers must be stringified. This looks like an oversight and is a decision: a metadata section that could hold arbitrary structures would need a general parser, and general parsers are where formats acquire vulnerabilities. Keeping it to a flat string map means the header parser is a JSON parser and nothing more.
The practical cost is that anything structured — training config, tokeniser settings, quantisation parameters — lives in sidecar JSON files rather than in the checkpoint. Which is why a model repository is always a directory rather than a single file.
Sharded models
Large models are split across files, with an index describing the mapping:
Each shard is a complete, independently valid safetensors file. The index is a separate concern — which means a missing shard produces a clear "tensor not found" rather than a corrupt-file error, and shards can be downloaded in parallel or resumed individually.
Why it cannot execute anything
The security argument is often stated as "safetensors is safer than pickle", which undersells it. The formats are not on the same axis at all.
| Pickle | Safetensors | |
|---|---|---|
| What it is | A stack-based VM | A table of byte ranges |
| Can name a class or function | Yes | No |
| Loading runs opcodes | Yes | No |
| Worst case on load | Arbitrary code | Invalid tensor shapes |
Pickle can execute code because executing code is what pickle does — reconstructing an object graph requires calling constructors. Safetensors cannot, because there is no field in which to put an instruction. The security property is structural rather than a matter of careful implementation, which is the strongest kind. The full comparison is in safetensors vs pickle.
🚨 Not executable does not mean not hostile
A malformed safetensors file can still cause problems: offsets that overlap, ranges extending past the end of the buffer, shapes whose element count does not match the byte range, or a header claiming a length that exceeds the file.
Good readers validate all of this. If you write your own, check that every range is within bounds, that shape × dtype size equals the range length, and that ranges do not overlap. The failure mode is a crash or nonsense output rather than compromise — but "safe format" is a claim about the worst case, not a promise that any file will work.
Inspecting a file yourself
The header-first layout means you can enumerate a remote 100GB model's full structure over HTTP with two range requests:
This is genuinely useful — verifying a model's architecture, checking quantisation, or confirming a download matches expectations, all without transferring the weights. Once you have the header, our JSON formatter makes it readable in the browser.
Reading a model header?
Format and explore JSON entirely in your browser — nothing is uploaded to a server.
Open JSON Formatter →Summary
- 8-byte length, JSON header, raw tensor buffer. That is the whole format.
- Offsets are relative to the buffer — absolute position is
8 + header_len + offset. - Tensors are rectangular blocks of one numeric type. No strings, no nesting, no ragged shapes.
- Loading is memory-mapping, not parsing — near-instant regardless of size.
- Individual tensors can be read without touching the rest of the file.
__metadata__is flat strings only, by design. Structure lives in sidecar files.- Shards are independently valid files plus a separate index.
- It cannot execute code because there is no field for one — but still validate bounds.
Frequently Asked Questions
What is the structure of a safetensors file?
Three parts in order: an 8-byte little-endian unsigned integer giving the header length, then that many bytes of UTF-8 JSON describing every tensor, then the raw tensor data as one contiguous byte buffer. The JSON gives each tensor a dtype, a shape and a start and end offset into that buffer.
Why is safetensors faster to load than pickle?
Because the data can be memory-mapped rather than deserialised. Tensor bytes sit in the file in their final layout, so the operating system maps them into memory directly with no parsing, no object construction and no copying. Loading becomes close to instant regardless of file size.
Can I read a safetensors header without downloading the whole file?
Yes, and this is one of its most useful properties. Read the first 8 bytes to get the header length, then read that many more bytes to get the complete JSON description of every tensor. A few kilobytes tells you the full structure of a file that might be hundreds of gigabytes.
Can safetensors store arbitrary metadata?
Only limited metadata, deliberately. A reserved __metadata__ key in the header holds a flat map of string keys to string values — no nested structures, no other types. This is a design constraint that keeps the header simple and prevents metadata from becoming a vector for complex parsing.
Why can't safetensors execute code like pickle can?
Because the format has no mechanism for it. A safetensors file describes tensors with a dtype, a shape and byte offsets — there is no way to express an instruction, a class reference or a function call. Pickle is a stack-based virtual machine that constructs objects; safetensors is a table of numbers.