About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.
Loading a model is a parse of a file you did not write. If that file is a Python pickle, the parser is allowed to import classes and call functions while it rebuilds the object. If the file is safetensors, the parser is specified as a length, a JSON header, and a byte buffer of tensor data. The second file can still be the wrong model, a poisoned model, or a huge model. It is not specified as a program.
An AI writing system produced this article for NetLeverage's editorial studio. No checkpoint was loaded. The layout and the warnings are from the documents opened on 10 November 2026.
Why it matters
Inference servers pull weights from hubs, from a colleague's bucket, and from a training job's output directory. The person who runs the server often treats "it loads" as a test of integrity. Python's pickle documentation says the opposite. It is possible to construct pickle data that executes arbitrary code during unpickling, and the instruction is to never unpickle data that could have come from an untrusted source or that could have been tampered with.
PyTorch's torch.save and torch.load use pickle by default. Modern torch.save normally creates an uncompressed ZIP64 archive containing a data.pkl pickle member and separate tensor storage. Legacy formats also exist. A .pt or .pth suffix does not establish which container or trust boundary applies; constraining the loader does not change the saved container format. A public weight file you did not build is untrusted in the sense of that warning, even when the repository looks popular. Tampering after a trusted author published it is the same class of problem: the bytes changed, and pickle will do what the new bytes say.
How it works
What pickle is allowed to do
The pickle module serialises an object graph. By default, unpickling imports any class or function named in the stream and can invoke it. The documentation's restricting-globals section says this is unacceptable for many applications precisely because the unpickler can import and invoke arbitrary code. The same page shows a short hand-built stream that runs a command when loaded. This article does not repeat that stream. The existence of the example in the official docs is the point: the format is a program, and pickle.loads is the interpreter.
Unpickler.find_class is the hook that runs whenever a global is requested. A subclass can refuse globals or allow a list. The docs say you have to be careful with what you allow, and that if security matters you should consider a different format. They also name a safer way to look at a file you do not trust: python -m pickletools displays the pickle bytecode and does not execute it. python -m pickle is the module's own displayer and is not that safer path.
JSON is the comparison they offer. Deserialising untrusted JSON does not by itself create an arbitrary-code bug. JSON also cannot represent a tensor's storage, strides, or dtype. That gap is why a tensor format exists at all.
What PyTorch changed, and what it did not
PyTorch's serialization notes say torch.save and torch.load use Python's pickle, so tuples, lists, dicts, and other pickleable structures can wrap tensors. Files are conventionally .pt or .pth. Saving a tensor view can write the underlying storage, which is larger than the view. That is a size surprise, not a code-execution bug, and it is a reason a "small" checkpoint can be a large read.
From PyTorch 2.6, torch.load uses weights_only=True when you do not pass pickle_module. The weights_only unpickler may build only what is required for plain tensor state dictionaries and some primitive types, plus types you have allowlisted. Unlike pickle's default unpickler, it is not allowed to import arbitrary modules during the load.
Published advisories show that this restriction has itself been bypassed. CVE-2025-32434 affects PyTorch 2.5.1 and earlier and was fixed in 2.6.0. CVE-2026-24747 affects 2.9.1 and earlier and was fixed in 2.10.0; its malicious checkpoint can corrupt memory and potentially execute code even with weights_only=True. The second advisory means a 2.6 minimum is insufficient. Use a supported patched release, check subsequent advisories, and treat this option as a restriction rather than a security boundary. The fixes for these two flaws do not certify arbitrary checkpoints safe.
The serialization note also lists two limits:
weights_only=Truedoes not stop denial-of-service attacks.- They try to prevent memory corruption during that load, and it might still be possible. Separately, a tensor that loads cleanly can still be shaped so that later user code, such as a sparse index, reads or writes out of bounds.
If the file names a class that is not allowlisted, the load fails with an error that offers two ways forward, and tells you to use either only if you trust the source. One is weights_only=False, which the message says can result in arbitrary code execution. The other is torch.serialization.add_safe_globals or the safe_globals context manager for that class. Allowlisting a class from an untrusted file is a decision that the class's constructor and state methods are safe to run. The notes say an old checkpoint that contains an nn.Module, rather than a state dict, is a case where they recommend weights_only=False. That recommendation is for a file you trust. It is not a way to open a stranger's full module.
get_unsafe_globals_in_checkpoint lists globals the static scan can see. The notes warn that some types are built during unpickling and will not appear in that list. A clean scan is not a proof the load will stay inside the allowlist. TORCH_FORCE_WEIGHTS_ONLY_LOAD=1 forces weights_only=True at every torch.load call. TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 forces the opposite only when the caller did not pass weights_only. Setting the second variable to keep an old training script working re-opens the pickle interpreter for every call that forgot the argument.
For compatibility, the notes prefer saving a state dictionary of tensors rather than a live module. That compatibility recommendation is not a security guarantee. A normal PyTorch state-dictionary checkpoint still has pickle metadata in its container. The flag narrows the accepted operations. It does not convert the archive into a data-only format.
The safetensors layout
The safetensors project describes itself as a format for storing tensors safely, as opposed to pickle, while staying fast enough to load with little copying. The format section of the README on the main branch, retrieved 10 November 2026, is short:
| Region | What it is |
|---|---|
| First 8 bytes | N, an unsigned little-endian 64-bit count, the header size |
Next N bytes |
A UTF-8 JSON object. It must start with {. It may be padded with trailing spaces |
| The rest | A byte buffer of tensor data. Little-endian, row-major, packed. No strides |
Each tensor entry in the header has a dtype, a shape, and data_offsets of [BEGIN, END). Those offsets are relative to the byte buffer, not to the start of the file. The number of bytes is END - BEGIN. A key __metadata__ may hold a map of strings to strings. Arbitrary JSON values are not allowed there. Duplicate keys are disallowed, with the README's caveat that not every parser may enforce that. The byte buffer must be fully indexed and must not contain holes, which the README says prevents polyglot files that are also some other format. Empty tensors and rank-0 scalars are allowed. NaN and infinities are not rejected.
The documented library implementation, not the eight-byte header, adds a 100 MB cap on the header so a huge JSON cannot be used as a denial-of-service against the parser. The README also says offsets must not overlap, so loading should not allocate more than the file's size for the tensor bytes. Those are implementation guarantees of this library's reader. A different reader that ignores the cap or the overlap check does not inherit them. The header itself is parsed as JSON and is not zero-copy; the project says that cost is small next to the tensor bytes.
The README's comparison table is labelled by its author as a personal and probably biased view. The row that matters for this article is the definition of "safe" in the caption: can you take a file downloaded at random and expect it not to run arbitrary code? Pickle is marked no. Safetensors is marked yes. Pin the safetensors release actually installed and consult its release notes; a development-branch document is not evidence of the current stable package version. The format description above is from the main README, not from a release diff.
Two things the format does not do:
- It does not check that the numbers are a model you wanted. A file of zeros is a valid safetensors file. A file whose weights were altered during training to misbehave on a trigger is also valid. Code execution at load time and training-time poisoning are different problems.
- It does not store custom classes. The README lists flexibility, meaning "can I save custom code and load it with no extra code," as something safetensors does not offer. If a project needs a custom object in the checkpoint, that object has to live somewhere else. Putting it back into a pickle beside the safetensors file recreates the original problem for that side file.
The project's own speed claim for BLOOM, loading on 8 GPUs falling from 10 minutes with ordinary PyTorch weights to 45 seconds, is their report of lazy loading, not a measurement made for this article. Lazy loading is real in the format: safe_open can fetch one key, or a slice, without reading every tensor. That is also the property a multi-GPU loader wants, and it is independent of the security claim.
A supply check that matches the format
Work in this order. Each step is a read or a policy. None of it was run here.
- Decide the file type before you open it. A
.safetensorsfile is a candidate for the layout above. A.pt,.pth,.bin, or.pklsuffix does not identify a safe format. Modern PyTorch ZIP checkpoints commonly begin withPK; that signature alone proves neither validity nor trust. Safetensors has an 8-byte length, followed by a JSON header whose first byte, the ninth file byte, must be{. Parsing untrusted containers still needs resource limits. - Prefer a data-only tensor format from an authenticated source. A patched, isolated safetensors reader reduces the executable deserialisation surface; verify provenance, resource limits and expected tensor shapes before admitting it to inference. Do not "also try pickle if that fails."
- Do not load an untrusted PyTorch checkpoint on an inference host. Refuse it, or send it through a separately reviewed isolated conversion process with no credentials, network access or production mounts. For authenticated checkpoints from a trusted pipeline, use
weights_only=Trueon a supported patched release incorporating at least the 2.10.0 fixes, after checking current advisories. Do not setweights_only=Falseto clear an error. Do not allowlist a global from a file you do not trust. Do not setTORCH_FORCE_NO_WEIGHTS_ONLY_LOADon a shared inference host. - Inspection is separate from loading. Python documents
pickletoolsas a non-executing pickle-bytecode inspector. In a ZIP checkpoint the pickle member isdata.pkl, not the entire archive. Any extraction or inspection belongs in the isolated process, with archive size and path checks; this article provides no extraction or conversion procedure. Do not substitutepickle.loadsorpython -m pickle. - Pin the bytes. Record the hash of the file you reviewed and refuse to load a different hash. A later upload with the same filename is a new file.
- Keep code and weights apart. Tokenizer code, custom modelling code, and remote
trust_remote_codepaths are programs. A safe tensor file does not make a downloaded Python file safe. Review that code as code.
The hash pin is an editorial control on top of the format. Neither pickle nor safetensors authenticates the publisher by itself. A signature scheme is a separate system. This article does not describe one, because the documents opened here do not specify a signature.
What to choose and why
| File you have | Load it as | Do not |
|---|---|---|
| Weights from a hub or another team, in safetensors | The safetensors reader, then check the names and shapes you expect | Fall back to pickle on a parse error |
| Your own PyTorch state dict, from a job you trust | torch.load(..., weights_only=True) on a supported patched release incorporating at least the PyTorch 2.10.0 fixes, or safetensors if you exported it |
Unpickle a full nn.Module you did not save |
| An old checkpoint containing a live module | Separate security review and isolated conversion decision; never a production-load workaround | Weaken the inference loader or its process-wide restrictions to clear an error |
| You need to inspect checkpoint metadata | Isolated, resource-limited archive and pickle inspection; container and member are different objects | Treat a clean static scan as approval to load |
Poisoned weights, a swapped tensor name, and a model that was fine-tuned to misbehave are still your problem after a clean safetensors load. The format restriction reduces executable loading mechanisms; a successful parse is not proof that no parser vulnerability was triggered. The evaluation set from a separate process answers "does this model do what we think?"
Risks and limits
weights_only=Trueis a narrower pickle, not a different format. The two cited advisories demonstrate code-execution bypasses; PyTorch also says it does not stop denial of service and memory corruption might still be possible.- Allowlisting a class runs that class's logic. Trust the class, not the error message that names it.
- Safetensors' 100 MB header cap and the no-overlap rule are properties of the library's reader as described in the README. Another parser can ignore them.
- The comparison table in the README is the author's view, including the marks for HDF5, NumPy, and ONNX. This article relies on the pickle and safetensors rows and on the format specification, not on every cell of that table.
- An apparently successful load does not certify the training data, the licence, or the publisher.
- Documents were read on 10 November 2026. PyTorch's default changed in 2.6; releases before 2.6 default to
weights_only=False. Check the version you run. - No file was loaded for this article, and no NetLeverage product behaviour is claimed.
Sources
Sources checked on 10 November 2026; access limitations are identified below.
- Python Software Foundation, pickle — Python object serialization — https://docs.python.org/3/library/pickle.html. Arbitrary code during unpickle, restricting globals,
pickletoolsas the non-executing inspector. - PyTorch, Serialization semantics — https://docs.pytorch.org/docs/2.15/notes/serialization.html. Pickle-backed
torch.save/torch.load,weights_onlyfrom 2.6, denial-of-service and corruption limits, allowlists, environment overrides. - Hugging Face, safetensors README — https://github.com/huggingface/safetensors/blob/main/README.md (raw file retrieved 10 November 2026). Header layout, offset rules, 100 MB header cap, definition of safe.
-
Hugging Face, safetensors documentation index — https://huggingface.co/docs/safetensors/index. Installation guidance; no stable release-candidate claim is made.
-
PyTorch security advisory CVE-2025-32434 — https://github.com/pytorch/pytorch/security/advisories/GHSA-53q9-r3pm-6pq6. Affected through 2.5.1; fixed in 2.6.0.
- PyTorch security advisory CVE-2026-24747 — https://github.com/pytorch/pytorch/security/advisories/GHSA-63cw-57p8-fm3p. Affected through 2.9.1; fixed in 2.10.0.
This article was originally published on .