Gallery

Contacts

405 W. Greenlawn Ave Lansing, Michigan 48910

contact@techjacksolutions.com

+1-616-320-4064

PyTorch

PyTorch Model Formats: .pt, .pth & safetensors

The PyTorch model formats you can save to look more confusing than they are: .pt, .pth, .ckpt, .safetensors, .pt2, .gguf. Some of those are the same thing wearing a different name. One of them can run malicious code on your machine the moment you load it. This guide explains what each format actually stores, why the ecosystem moved away from raw pickle files, and which format to reach for depending on whether you are training, sharing, or deploying. For broader context, our PyTorch hub covers the fundamentals, from what PyTorch is to how it compares in PyTorch vs TensorFlow.

.pt = .pth
Same Format, Different Letter
Convention only · pytorch.org
76.6×
safetensors CPU Load vs Pickle
gpt2 benchmark, illustrative · Hugging Face
2.6
weights_only=True Default Since
PyTorch version · pytorch.org
ZIP64
torch.save Archive Since 1.6
Default container · pytorch.org
.pt2
torch.export File Extension
ExportedProgram · pytorch.org

What "Saving a Model" Actually Means

A trained neural network is, underneath, a large collection of numbers: the learned weights and biases of every layer. Saving a model means writing those numbers to disk in a way you can read back later. The formats differ in what else they store alongside the numbers and how they store them.

PyTorch's native mechanism is torch.save(), which uses Python's pickle utility to serialize objects (to serialize is to convert an object to bytes for storage). The documentation is explicit that "models, tensors, and dictionaries of all kinds of objects can be saved using this function." That flexibility is the source of both its convenience and its biggest risk. Since PyTorch 1.6, the file torch.save() produces is an uncompressed ZIP64 archive containing the pickled data, a byte-order marker, the raw storage tensors (tensors are the multi-dimensional arrays of numbers that make up a model), and a version stamp.

Three broad categories cover almost everything you will encounter:

  • Native PyTorch pickle files (.pt, .pth, .ckpt) hold a state_dict or a full checkpoint. Maximum flexibility, but they run pickle on load.
  • safetensors (.safetensors) holds only tensors plus a metadata header. No executable code, fast to load.
  • Deployment graphs (TorchScript .pt, torch.export .pt2, .gguf) capture the model so it can run outside your Python training script.

Mental model: a state_dict is the numbers. A checkpoint is the numbers plus training bookkeeping. A deployment graph is the numbers plus the computation, frozen for inference (inference is actually running the trained model to get outputs, as opposed to training it).


.pt vs .pth: The Same File, Different Letter

This is the question that sends newcomers in circles, so here is the short answer: there is no technical difference. The PyTorch documentation states that "a common PyTorch convention is to save models using either a .pt or .pth file extension." Both are produced by the same torch.save() call, both contain the same ZIP64 pickle archive, and torch.load() reads them identically. You could name the file model.banana and it would still load.

The only practical wrinkle is unrelated to PyTorch. Python's own packaging tooling historically treats .pth files in site-packages as path-configuration files. That collision is why some teams prefer .pt for model weights, purely to avoid confusion. It has no effect on how the model loads.

You will also see .ckpt in the wild, especially from PyTorch Lightning and diffusion-model communities. It is still a pickle archive under the hood, just a naming convention signalling "this is a checkpoint with training state," not only weights.

Practical rule: pick one extension and be consistent. .pt for inference weights, .ckpt for resumable training checkpoints. The bytes inside are the same kind of file.


state_dict vs Saving the Whole Model

Inside a pickle file you can store one of two things, and the choice matters more than the extension ever will. A state_dict is a plain Python dictionary mapping each layer to its tensor of learned parameters. Saving the whole model pickles the entire Python object, including the class definition's reference.

PyTorch's guidance is unambiguous: "saving the model's state_dict with the torch.save() function will give you the most flexibility for restoring the model later, which is why it is the recommended method for saving models." Saving the entire model has a real drawback. The documentation warns that "the serialized data is bound to the specific classes and the exact directory structure used when the model is saved," so moving or refactoring your code can break the load.

The standard pattern saves only the parameters:

Save the state_dict
torch.save(model.state_dict(), "model.pth")

To load it, you re-create the model object from its class, then pour the saved numbers back in. Note that load_state_dict() takes a dictionary, not a path, so you deserialize with torch.load() first. Always call model.eval() before inference to put dropout and batch-norm layers into evaluation mode, or your results will be inconsistent.

Re-create the model, then load the weights
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load("model.pth", weights_only=True))
model.eval()

Why this wins: a state_dict is just data. It survives code refactors, transfers cleanly between projects, and is exactly what safetensors stores too. The whole-model shortcut couples your saved file to one snapshot of your source tree.


Checkpoints: Saving More Than Weights

If a multi-day training run crashes, weights alone are not enough to resume cleanly. You also need the optimizer's state, the epoch counter, and the current loss. A checkpoint is simply a dictionary that bundles all of that and hands it to torch.save(). PyTorch recommends including optimizer state because it "contains buffers and parameters that are updated as the model trains."

Save a training checkpoint
torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss,
}, "checkpoint.pth")

Resuming reverses the process. You load the dictionary, then restore each component into the object it belongs to before continuing training.

Resume from a checkpoint
# weights_only=True with full checkpoints requires PyTorch 2.6+
# For PyTorch < 2.6, use weights_only=False for checkpoints you own and trust
checkpoint = torch.load("checkpoint.pth", weights_only=True)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])

This is the one place where safetensors is not a drop-in replacement. safetensors stores tensors only, so optimizer state and Python scalars like the epoch number do not fit its model. For resumable training, a pickle checkpoint is still the practical choice. For sharing finished weights, safetensors is the better target, which is the next section.


The Pickle Security Problem

Here is the part that turns a file-format question into a security question. Because torch.save() uses pickle, the load step does not just read numbers. The full pickle path can execute arbitrary Python code while it reconstructs objects. A model file downloaded from a stranger can, in principle, run commands on your machine the instant you call torch.load() on it. This is not a hypothetical with pickle in general; it is the documented behavior of the format.

PyTorch's answer is the weights_only argument. With weights_only=True, unpickling is restricted to "only executing functions/building classes required for state_dicts of plain torch.Tensors as well as some other primitive types," and the unpickler "is not allowed to dynamically import anything during unpickling." Starting in PyTorch 2.6, this is the default when you do not pass a custom pickle module.

A .pt file from an untrusted source can run code
If you load a pickle-based model with the legacy weights_only=False path, a malicious file can execute arbitrary code. Treat downloaded .pt/.pth/.ckpt files like you would treat any executable: only run them from sources you trust.
weights_only=True narrows the risk, it does not erase it
PyTorch is clear that weights_only=True "narrows the surface of remote code execution attacks" but does not guard against denial-of-service attacks, and memory corruption may still be possible. It is a strong default, not a guarantee of safety on hostile input.
Older PyTorch defaults to the unsafe path
Before version 2.6, torch.load() defaulted to weights_only=False. If you run an older install, pass weights_only=True explicitly when the file contains only a state_dict. Verify your version before assuming the safe default applies.

Bottom line: the cleanest way to sidestep the pickle problem entirely is to share weights in a format that cannot execute code in the first place. That format is safetensors.


safetensors: The Safer, Faster Default

safetensors is a format from Hugging Face designed, in its own words, "for storing tensors safely (as opposed to pickle) and that is still fast (zero-copy)." It has become the default weight format across the Hugging Face ecosystem, including Transformers, Diffusers, and tools like ComfyUI and text-generation-webui.

The design is deliberately boring, and that is the point. A safetensors file stores a JSON header describing each tensor's name, data type, shape, and byte offsets, followed by the raw tensor bytes. There is no executable code anywhere in the format, so loading one cannot run arbitrary commands. It stores tensors only: no Python objects, no class definitions, no optimizer state.

Safety is the headline, but speed is the reason it stuck. Because the loader memory-maps the file and skips unnecessary copies, it is far faster on a cold load.

76.6×
Faster CPU load than pickle when loading gpt2 weights in Hugging Face's own benchmark (2.1× faster on GPU). This is a single benchmark on a specific machine, not a universal guarantee, but the direction is consistent: memory-mapping beats deserializing. (Source: Hugging Face)

Saving and loading from PyTorch takes two functions. You hand save_file a state_dict and get back a .safetensors file; load_file returns the state_dict to feed into load_state_dict().

Save and load with safetensors
from safetensors.torch import save_file, load_file
save_file(model.state_dict(), "model.safetensors")
state_dict = load_file("model.safetensors")

Already have pickle weights you want to convert? Hugging Face recommends its Convert Space, which downloads the pickled weights, converts them, and opens a pull request with the new .safetensors file. The same conversion script can be run locally for larger models.


Deployment Formats: TorchScript, torch.export, GGUF

A state_dict needs your Python class to mean anything. That is fine for training, but a production server, a mobile app, or a C++ runtime may not have your code or even a Python interpreter. Deployment formats solve this by capturing the computation graph alongside the weights, producing a self-contained artifact.

TorchScript (.pt)

TorchScript is, per the docs, a way to create serializable models from PyTorch code that can run in a high-performance environment such as C++ with no Python dependency. You convert a model with torch.jit.script() (or torch.jit.trace()) and persist it with torch.jit.save(). The result is self-contained, usually with a .pt extension.

TorchScript export
scripted = torch.jit.script(model)
scripted.save("model.pt")
loaded = torch.jit.load("model.pt")

torch.export (.pt2)

torch.export is the newer path. It captures the model as an ExportedProgram, a full graph representation, saved and loaded with torch.export.save() and torch.export.load() using the .pt2 extension. It is the direction PyTorch is steering deployment toward, ahead of legacy TorchScript.

torch.export
ep = torch.export.export(model, (example_input,))
torch.export.save(ep, "model.pt2")
loaded = torch.export.load("model.pt2")

GGUF (.gguf)

GGUF sits slightly outside the PyTorch family but you will meet it constantly when running local LLMs. It is a binary format built for quick loading and saving of models, developed by the author of llama.cpp. Unlike tensor-only safetensors, GGUF encodes both the tensors and a standardized set of metadata in one file, and it supports a wide range of quantization types (quantization means reducing the numeric precision of the weights to shrink the model) for running large models on modest hardware. Models built in PyTorch are commonly converted to GGUF for use with engines like llama.cpp, LM Studio, GPT4All, and Ollama. It is an inference format, not a way to resume PyTorch training.


Which Format Should You Use?

The right format depends on the job. Saving for yourself mid-training, sharing finished weights publicly, and shipping to production are three different problems with three different answers.

FormatStoresRuns code on load?Load speedBest for
.pt / .pth (state_dict)Layer weights onlyYes (pickle; use weights_only=True)Slower (full deserialization)Your own inference weights
.pt / .ckpt (checkpoint)Weights + optimizer + epoch/lossYes (pickle; use weights_only=True)Slower (full deserialization)Resuming your own training
.safetensorsTensors + JSON metadata headerNoFastest (zero-copy / mmap)Sharing and distributing weights
TorchScript .ptWeights + graph (legacy)Loads a graph, no arbitrary codeModerateC++ / no-Python serving
torch.export .pt2Weights + ExportedProgram graphLoads a graph, no arbitrary codeModerateModern PyTorch deployment
.ggufTensors + metadata (quantized)NoFast (mmap)Local LLM inference (llama.cpp)

Note: a .pt file may be either a pickled state_dict or a TorchScript graph. They share the extension but load differently (torch.load vs torch.jit.load).

Researchers & Students
state_dict + checkpoints. Save model.state_dict() for finished weights and full checkpoints during long runs. Load with weights_only=True by habit.
Teams Sharing Models
safetensors. Distribute weights in a format that cannot execute code on download. It is the Hugging Face Hub default and loads faster for everyone who pulls it.
MLOps & Production
torch.export (.pt2) or TorchScript. Ship a self-contained graph that runs without your training code. Prefer torch.export for new work; TorchScript remains for existing C++ pipelines.
Security-Conscious Loaders
Prefer safetensors; never load untrusted pickle. If you must open a downloaded .pt, use weights_only=True and confirm the source. Treat strangers' pickle files as untrusted executables.

Frequently Asked Questions

There is no technical difference. A common PyTorch convention is to save models using either a .pt or .pth file extension, and PyTorch reads them identically. The extension is convention only. Both are produced by torch.save() and both default to an uncompressed ZIP64 archive since PyTorch 1.6.
Saving the state_dict is the recommended method. It gives the most flexibility for restoring the model later. Saving the entire model binds the serialized data to the specific classes and directory structure used at save time, so it can break when you refactor your code.
torch.save() uses Python's pickle, and the full pickle path can execute arbitrary code when a file is loaded. A malicious .pt file can run code on your machine. Mitigations are loading with weights_only=True (the default since PyTorch 2.6) and using the safetensors format, which stores only tensors and cannot execute code. Only load model files from sources you trust.
safetensors is a format from Hugging Face for storing tensors safely as opposed to pickle, while staying fast through zero-copy loading. It stores only raw tensors plus a JSON metadata header, with no executable code, so loading it cannot run arbitrary code. On a gpt2 benchmark it loaded 76.6x faster than pickle on CPU.
Yes. GGUF is a binary format developed by the llama.cpp author for fast, inference-efficient loading, and it encodes both tensors and metadata in one file. Models built in PyTorch can be converted to GGUF for use with engines like llama.cpp, LM Studio, GPT4All, and Ollama, often using the gguf-my-repo tool. GGUF is for inference, not for resuming PyTorch training.
Fact-checked against PyTorch official documentation and Hugging Face safetensors and GGUF docs, June 2026
PyTorch is a trademark of The Linux Foundation. safetensors and the Hugging Face name are trademarks of Hugging Face, Inc. llama.cpp and GGUF are projects of their respective authors and maintainers. All trademarks are the property of their respective owners. Tech Jacks Solutions is editorially independent and is not affiliated with the PyTorch Foundation, Meta Platforms Inc., or Hugging Face, Inc.
Before You Use AI
Your Privacy & Security

Model files are not always safe to open. A PyTorch pickle file (.pt, .pth, .ckpt) can execute code when loaded, so only load model files from sources you trust, prefer safetensors for downloads, and use weights_only=True. PyTorch runs locally on your own hardware; cloud and hosted inference operate under each provider's data processing agreement.

Mental Health & AI Use

PyTorch is a technical framework for machine learning. If technical content or productivity pressure is contributing to distress, please reach out for support. If you are experiencing a mental health crisis:

  • 988 Suicide & Crisis Lifeline: Call or text 988 (US)
  • SAMHSA Helpline: 1-800-662-4357 (free, confidential)
  • Crisis Text Line: Text HOME to 741741

AI systems can produce plausible-sounding but incorrect guidance. For medical, legal, financial, or safety-critical decisions, always consult a qualified professional. Do not rely solely on AI-generated guidance for consequential choices.

Your Rights & Our Transparency

This article was researched and written by Tech Jacks Solutions editorial staff. Factual claims are grounded in PyTorch's official documentation and Hugging Face's safetensors and GGUF documentation. We have no sponsored relationship with the PyTorch Foundation, Meta Platforms, or Hugging Face. We may earn commissions from some links at no cost to you.

Under GDPR and CCPA, you have the right to access, correct, or delete personal data we hold. This content is covered by the EU AI Act's transparency obligations where applicable.