Save and Load Local Models with Python Transformers
python transformers local models save_pretrained and from_pretrained: Save fine-tuned Hugging Face models locally with save_pretrained and reload them with from_pretra...
python transformers local models save_pretrained and from_pretrained requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you fine-tune a model with the Hugging Face transformers library, the trained weights live in memory until you persist them. The two methods that handle local persistence are save_pretrained and from_pretrained: the first writes a model and its configuration to a directory on disk, and the second reconstructs the model from that directory. Knowing exactly what each method writes, reads, and skips is what separates a reproducible local model workflow from one that breaks when you move directories or change machines.
What save_pretrained Actually Writes to Disk
Calling model.save_pretrained("./my-model") writes at least two files into the target directory:
config.json— the model configuration, including the architecture type, hidden dimensions, vocabulary size, and any custom attributes you set when constructing the model.model.safetensors— the serialized weight tensors. In current transformers versions this is the default format. Ifsafe_serialization=Falseis passed, the weights are written aspytorch_model.binin the older pickle-based format instead.
For large models, the weights are split into shards. A multi-billion-parameter model produces files named model-00001-of-00003.safetensors, model-00002-of-00003.safetensors, and so on, controlled by the max_shard_size argument. The config.json file records the shard layout so from_pretrained can reassemble the weights correctly.
Generation-capable models may also produce a generation_config.json in the same directory.
Crucially, save_pretrained on the model does not save the tokenizer. The directory becomes a complete, loadable checkpoint only when the tokenizer files are written alongside the weights.
Saving a Model and Tokenizer to a Local Directory
The standard pattern after fine-tuning is to save both the model and its tokenizer into the same directory:
from transformers import AutoModelForSequenceClassification, AutoTokenizer model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2) tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") # ... fine-tuning happens here ... model.save_pretrained("./my-finetuned-model") tokenizer.save_pretrained("./my-finetuned-model")
The tokenizer writes its own set of files — vocab.txt, tokenizer.json, special_tokens_map.json, and similar — depending on the tokenizer type. Keeping them in the same directory as the weights makes the directory self-contained: you can copy it to another machine or upload it to the Hub and load everything from one path.
If you save the model without the tokenizer, loading the directory later still works for the model itself, but you will need to reconstruct the tokenizer from its original source or from a separate saved copy.
Loading the Model Back with from_pretrained
Loading from a local directory uses the same API as loading from the Hub:
from transformers import AutoModelForSequenceClassification, AutoTokenizer model = AutoModelForSequenceClassification.from_pretrained("./my-finetuned-model") tokenizer = AutoTokenizer.from_pretrained("./my-finetuned-model")
The loading process reads config.json first, determines the architecture class, instantiates it, and then loads the weight tensors into the model. This is why the class you call from_pretrained on must match the architecture recorded in the config. Loading a checkpoint saved from BertForSequenceClassification with BertModel produces warnings about unused weights or fails with a shape mismatch, and the classification head ends up randomly initialized.
When the weights are sharded, from_pretrained reads the shard list from config.json and loads each shard in sequence. When both model.safetensors and pytorch_model.bin are present, the safetensors file takes precedence.
What Is Not Saved by save_pretrained
save_pretrained persists the model weights and configuration only. Several pieces of state that matter in a training workflow are deliberately left out:
- Optimizer state — Adam momentum and variance buffers are not written. If you need to resume training exactly where you left off, use the
Trainer's checkpoint mechanism (output_dir/checkpoint-*), which saves the optimizer, scheduler, and training arguments alongside the model. - Tokenizer — as noted above, this requires a separate
tokenizer.save_pretrainedcall. - Training arguments and hyperparameters — these live in the
Trainer'strainer_state.jsonandtraining_args.bin, not in the model directory. - Custom model code — if you defined a custom architecture in a Python script, the weights and config are saved, but the class definition is not. Loading such a checkpoint requires the class to be importable at load time, or
trust_remote_code=Trueif the code is stored in the Hub repository.
This distinction matters when you plan to share a checkpoint. A directory containing only config.json and model.safetensors is a weights checkpoint, not a complete reproducible artifact.
How from_pretrained Resolves Its Argument
The first positional argument to from_pretrained is either a local directory path or a Hub repository identifier. The method checks whether the argument points to an existing local directory first; if it does, it loads from disk without contacting the Hub. If not, it treats the argument as a Hub repo ID and downloads the files into the Hugging Face cache.
For offline environments, local_files_only=True forces the method to fail instead of attempting a network download:
model = AutoModel.from_pretrained("./my-finetuned-model", local_files_only=True)
This is useful in CI pipelines or air-gapped deployments where an accidental network call would hang or raise a connection error. The same flag applies when loading from the Hub cache: if the model is already cached, local_files_only=True loads it from cache without checking for updates.
Common Failure Modes and How to Diagnose Them
The most frequent errors with local model loading fall into a few categories.
Architecture mismatch. Loading a checkpoint with the wrong model class produces warnings like Some weights of the model checkpoint were not used or outright shape mismatches. The fix is to load with the same class that saved the checkpoint, or use the appropriate auto class that matches the task.
Missing tokenizer files. If the directory contains weights but no vocab.txt or tokenizer.json, AutoTokenizer.from_pretrained raises an error. The directory is incomplete; re-save the tokenizer into it.
Relative path issues. save_pretrained("./model") writes relative to the current working directory. If a script later runs from a different directory, from_pretrained("./model") fails because the path no longer resolves. Using an absolute path or resolving the path at runtime avoids this.
Custom code without trust_remote_code. A checkpoint whose config.json contains auto_map entries (custom model classes) requires trust_remote_code=True when loading. Without it, transformers raises an error rather than executing arbitrary code from the repository.
Neither safetensors nor bin present. If the directory has a config.json but no weight files, from_pretrained fails with a clear error about missing weights. This usually happens when only the config was saved, or when the weights were written to a different directory.
Operational Considerations: Format, Disk, and Memory
The choice between safetensors and pickle is not cosmetic. pytorch_model.bin uses Python's pickle serialization, which can execute arbitrary code during deserialization. model.safetensors uses a flat binary format that only stores tensors, making it safe to load from untrusted sources. For any checkpoint that will be shared or downloaded, safetensors is the safer default.
Disk usage is straightforward: a checkpoint occupies roughly the size of the model weights in float32, or half that for fp16/bf16. A 7B-parameter model in fp16 is about 14 GB on disk when unsharded; with sharding, the same data is split across multiple files but the total is unchanged.
Memory during loading deserves attention for large models. from_pretrained materializes the full weight tensor in RAM before moving it to the device. For a model that exceeds available RAM, loading fails with an out-of-memory error. Two options exist: low_cpu_mem_usage=True streams the weights and reduces peak CPU memory, and device_map="auto" (with the accelerate library) distributes layers across available devices. Both are relevant when loading a local checkpoint that is large relative to the host machine.