timm: A Warehouse of 1000+ Ready-to-Use Computer Vision Models in PyTorch
Hey everyone, in this tutorial I want to introduce a library that in my opinion belongs in the toolbox of anyone doing computer vision with PyTorch. It is called timm, short for PyTorch Image Models. If you have ever been stuck choosing a backbone for an image classification project, or copy-pasted a ResNet implementation from someone's blog and wondered why your accuracy was mediocre, timm is the answer.
In short, timm is a collection of more than a thousand pretrained vision architectures, complete with the correct preprocessing, modern augmentation, optimizers, schedulers, and training utilities. All of it behind a consistent API. Switching from ResNet to ConvNeXt to EfficientNet to a Vision Transformer is a single string change. Seriously, one string.
In this tutorial we cover installation, how to search for and choose models, simple inference, feature extraction for transfer learning, fine-tuning on your own dataset, and finally practical guidance on picking a model that balances accuracy and speed. Let us get started.
Introduction
Before the code, let me explain why timm is more than just torchvision.models.
What Is timm
timm was created by Ross Wightman and is now part of the HuggingFace ecosystem. It contains the most complete set of vision architecture implementations ever assembled in one library: ResNet and all its variants, EfficientNet, RegNet, ConvNeXt, Vision Transformer (ViT), Swin Transformer, DeiT, BEiT, MaxViT, EVA, MobileNet, and many more.
What makes it special is not just the count, but three things.
First, the pretrained weights are high quality and frequently better than the original paper's official weights, because Ross retrained many models with modern training recipes.
Second, every model carries its own preprocessing configuration. This matters enormously and is often underestimated. ViT was trained with different normalization than ResNet. If you use the wrong mean and standard deviation, accuracy can drop by double digits without you noticing. timm handles this for you.
Third, the API is consistent. Every model is built through timm.createmodel(), every model exposes forwardfeatures(), and every model changes its output class count the same way.
When to Use timm
Use timm when you need a strong backbone for image classification, when you need a feature extractor for another task (detection, segmentation, retrieval, similarity search), or when you are benchmarking several architectures on your dataset and want to swap models without rewriting code.
If you need a ready-made object detection model, that is Ultralytics or Detectron2 territory. timm focuses on backbones and classification, though its backbones are often used inside those libraries too.
Installation
You need Python 3.8 or newer plus PyTorch. Create a virtual environment first.
python -m venv venv
source venv/bin/activate # Linux / Mac
venv\Scripts\activate # Windows
pip install torch torchvision
pip install timm pillow matplotlib
Check the versions:
import timm
import torch
print("timm:", timm.version)
print("torch:", torch.version)
print("cuda available:", torch.cuda.isavailable())
Finding Available Models
With thousands of models, the first skill you need is searching.
import timm
How many models ship with pretrained weights?
allmodels = timm.listmodels(pretrained=True)
print("total pretrained models:", len(allmodels))
Find ConvNeXt models
print(timm.listmodels("convnext", pretrained=True)[:10])
Find small EfficientNet models
print(timm.listmodels("efficientnetb0", pretrained=True))
Find ViT models
print(timm.listmodels("vitbase", pretrained=True)[:10])
The timm naming pattern is usually architecturesize.recipedataset. For example convnexttiny.fbin22kftin1k means ConvNeXt tiny, weights from Facebook, trained on ImageNet-22k then fine-tuned on ImageNet-1k. The more model names you read, the easier it becomes to guess their quality.
First Inference
Let us classify a single image.
import timm
import torch
from PIL import Image
import urllib.request
1. Create the model with pretrained weights
model = timm.createmodel("convnexttiny.fbin1k", pretrained=True)
model.eval()
2. Get the CORRECT transform for this model
config = timm.data.resolvemodeldataconfig(model)
transform = timm.data.createtransform(config, istraining=False)
print(config)
3. Prepare an image
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/pytorch/hub/master/images/dog.jpg", "dog.jpg"
)
img = Image.open("dog.jpg").convert("RGB")
4. Predict
x = transform(img).unsqueeze(0) # add the batch dimension
with torch.nograd():
logits = model(x)
probs = logits.softmax(dim=-1)[0]
top5 = probs.topk(5)
5. Load ImageNet class names
url = "https://raw.githubusercontent.com/pytorch/hub/master/imagenetclasses.txt"
urllib.request.urlretrieve(url, "imagenetclasses.txt")
labels = open("imagenetclasses.txt").read().splitlines()
for score, idx in zip(top5.values, top5.indices):
print(f"{labels[idx]:30s} {score.item():.4f}")
Pay attention to step two. resolvemodeldataconfig is the key that people skip. It tells you the input size, mean, standard deviation, and interpolation method used when the model was trained. Never blindly apply standard ImageNet normalization to every model.
Changing the Number of Classes
For your own dataset you need a different output layer. In timm that is one argument.
# My dataset has 7 classes
model = timm.createmodel("resnet50.a1in1k", pretrained=True, numclasses=7)
x = torch.randn(2, 3, 224, 224)
print(model(x).shape) # torch.Size([2, 7])
If you set numclasses=0, timm removes the classifier head and returns features directly. This is very handy for feature extraction.
backbone = timm.createmodel("resnet50.a1in1k", pretrained=True, numclasses=0)
features = backbone(torch.randn(2, 3, 224, 224))
print(features.shape) # torch.Size([2, 2048])
That 2048 dimensional vector is what you feed into image similarity search, clustering, or a classic classifier such as an SVM.
Extracting Multi-Scale Feature Maps
For tasks like segmentation or detection you need feature maps from several depths, not just the final vector.
import timm
import torch
model = timm.createmodel(
"resnet50.a1in1k",
pretrained=True,
featuresonly=True,
outindices=(1, 2, 3, 4),
)
out = model(torch.randn(1, 3, 224, 224))
for i, f in enumerate(out):
print(i, f.shape)
print(model.featureinfo.channels()) # channels per level
print(model.featureinfo.reduction()) # downsampling factor per level
This is exactly why timm is used as the encoder inside segmentation libraries such as segmentationmodelspytorch. Swapping the backbone is as easy as changing a string.
Fine-Tuning on Your Own Dataset
Now the main event. Here is a simple but complete training loop using the standard ImageFolder layout.
The dataset structure looks like this:
data/
train/
cat/ image1.jpg ...
dog/ image1.jpg ...
val/
cat/ ...
dog/ ...
And the code:
import timm
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder
DEVICE = "cuda" if torch.cuda.isavailable() else "cpu"
MODELNAME = "convnexttiny.fbin1k"
1. Model
model = timm.createmodel(MODELNAME, pretrained=True, numclasses=2)
model.to(DEVICE)
2. Model-specific transforms, train and eval variants
cfg = timm.data.resolvemodeldataconfig(model)
tftrain = timm.data.createtransform(cfg, istraining=True, autoaugment="rand-m9-mstd0.5")
tfval = timm.data.createtransform(*cfg, istraining=False)
3. Datasets and loaders
dstrain = ImageFolder("data/train", transform=tftrain)
dsval = ImageFolder("data/val", transform=tfval)
dltrain = DataLoader(dstrain, batchsize=32, shuffle=True, numworkers=4, pinmemory=True)
dlval = DataLoader(dsval, batchsize=64, shuffle=False, numworkers=4)
print("classes:", dstrain.classes)
4. Optimizer, scheduler, loss
optimizer = timm.optim.createoptimizerv2(model, opt="adamw", lr=3e-4, weightdecay=0.05)
criterion = nn.CrossEntropyLoss(labelsmoothing=0.1)
EPOCHS = 10
scheduler = torch.optim.lrscheduler.CosineAnnealingLR(optimizer, Tmax=EPOCHS)
5. Training loop
for epoch in range(EPOCHS):
model.train()
totalloss = 0.0
for x, y in dltrain:
x, y = x.to(DEVICE), y.to(DEVICE)
optimizer.zerograd()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
totalloss += loss.item() x.size(0)
scheduler.step()
model.eval()
correct = 0
total = 0
with torch.nograd():
for x, y in dlval:
x, y = x.to(DEVICE), y.to(DEVICE)
pred = model(x).argmax(dim=1)
correct += (pred == y).sum().item()
total += y.size(0)
print(f"epoch {epoch+1}/{EPOCHS} "
f"loss {totalloss/len(dstrain):.4f} "
f"valacc {correct/total:.4f}")
torch.save(model.statedict(), "modelfinetuned.pth")
A few things worth noting in that code.
autoaugment="rand-m9-mstd0.5" turns on RandAugment, a modern augmentation policy that usually gives free accuracy on small to medium datasets.
labelsmoothing=0.1 keeps the model from becoming overconfident and typically improves generalization a little.
AdamW with weight decay 0.05 is the standard recipe for modern models like ConvNeXt and ViT. For classic ResNets, SGD with momentum is sometimes still better.
Freezing the Backbone
If your dataset is genuinely small (under a few hundred images per class), it is often safer to train only the head first.
model = timm.createmodel("convnexttiny.fbin1k", pretrained=True, numclasses=2)
for p in model.parameters():
p.requires
grad = False
Unfreeze only the classifier head
for p in model.getclassifier().parameters():
p.requiresgrad = True
optimizer = torch.optim.AdamW(
[p for p in model.parameters() if p.requiresgrad], lr=1e-3
)
After a few epochs and once the loss stabilizes, unfreeze everything and continue with a small learning rate, say 1e-5 to 5e-5. This technique is called gradual unfreezing and it regularly rescues small datasets from overfitting.
Loading the Model Back
import timm
import torch
model = timm.createmodel("convnexttiny.fbin1k", pretrained=False, numclasses=2)
model.loadstatedict(torch.load("modelfinetuned.pth", maplocation="cpu"))
model.eval()
Note pretrained=False here, because the weights come from our own file rather than the internet.
Choosing the Right Model
This is the question I get most often, so here is practical guidance.
If you need something fast and light for CPU or mobile deployment, try mobilenetv3large100, efficientnetb0, or efficientvitb0. Small footprint, fast inference, respectable accuracy.
If you want a good balance of accuracy and cost on a GPU, convnexttiny, resnet50, and efficientnetb3 are sweet spots that rarely disappoint.
If you are chasing maximum accuracy with a big GPU and plenty of data, look at convnextbase, swinbasepatch4window7224, or the eva02 family that frequently tops benchmarks.
If your data is very limited, prefer models trained on larger datasets such as ImageNet-22k (look for in22k in the name), because their features are more general and transfer better.
One last tip: always benchmark two or three candidates on your own data. ImageNet rankings do not always hold on your specific dataset, especially in distant domains like medical or satellite imagery.
Tips and Best Practices
Always use resolvemodeldataconfig and createtransform instead of hardcoding normalization. This is the number one mistake I see in other people's code.
Watch the input resolution. Some models were trained at 224x224, others at 288, 384, or even 512. Forcing the wrong size into a ViT can either error out or quietly hurt accuracy because the position embeddings do not match.
Use mixed precision to save memory and speed up training on modern GPUs, simply by wrapping the forward pass in torch.autocast.
Record the model name and the timm version in your experiment config. Weight names in timm occasionally change between releases, which can silently break reproducibility.
Consider timm.utils.ModelEmaV2 for long training runs. An exponential moving average of the weights often yields more stable and slightly higher validation accuracy for free.
Conclusion
timm is one of the biggest productivity multipliers available if you work in computer vision. The key takeaways:
timm.createmodel() is a single door into more than a thousand architectures with high quality pretrained weights.
Always obtain transforms through resolvemodeldataconfig so preprocessing matches exactly how the model was trained.
numclasses swaps the head for your dataset, numclasses=0 turns the model into a feature extractor, and features_only=True gives multi-scale feature maps for detection and segmentation.
Fine-tuning is a plain PyTorch loop plus modern augmentation and the AdamW optimizer helper from timm.
Model selection should weigh deployment target, dataset size, and domain, not just ImageNet leaderboard position.
Take one of your own small datasets, maybe product classification or manufacturing quality inspection, and compare three architectures by changing a single string. You will feel immediately why this library became the de facto standard in PyTorch computer vision. Happy building.