Load a model from the Hub and check its card

Download a model published with fastermodels and recompute the numbers on its card

FasterAI-Labs/pilot-resnet18-imagenette is a ResNet-18 pruned with fasterai, quantized to INT8 and fine-tuned on Imagenette. Its card reports a top-1 for each of the three files the repo publishes, plus the size and the cost of one forward. Every number on this page is one the card reports, recomputed here from the downloaded files — the repo is private today, so read the page as what a card promises and how a reader checks it.

pip install fastermodels fastai

fastermodels itself depends only on torch, huggingface_hub, safetensors and numpy; fastai is here for the Imagenette loader, and onnxruntime for the last cell.

Everything this page needs comes from one import.

import torch

from fastermodels import load, correct_vector, predictions, wilson, agreement, params, macs, peak_activation_bytes

load downloads the repo and gives back the form it publishes first — here the weights, as a FasterModel in eval mode.

repo = 'FasterAI-Labs/pilot-resnet18-imagenette'
model = load(repo)

print(type(model).__name__, '— training:', model.training)
print(model.provenance)
FasterModel — training: False
{'fasterai': '0.4.0', 'fastermodels': '0.1.0', 'measured_on': '2026-09-11', 'source_state_hash': 'b81b9caef3c9', 'torch': '2.9.1+cu128'}

A pruned ResNet no longer has the shapes torchvision.models.resnet18 builds, so the weights alone would not load. The repo’s config.json carries the factory to call and the constructor arguments of every layer whose shape changed; load rebuilds that architecture first, then reads the weights into it strictly. The first convolution is the visible case: the factory builds conv1 with 64 output channels, the artifact has 40 — both printed below.

import json
from huggingface_hub import hf_hub_download

cfg = json.load(open(hf_hub_download(repo, 'config.json')))
print(cfg['source'], cfg['source_kwargs'])
print(cfg['modules']['conv1'])
print(model.net.conv1)
from torchvision.models import resnet18
print('factory:', resnet18(num_classes=10).conv1)
torchvision.models.resnet18 {'num_classes': 10, 'weights': None}
{'bias': False, 'dilation': [1, 1], 'groups': 1, 'in_channels': 3, 'kernel_size': [7, 7], 'out_channels': 40, 'padding': [3, 3], 'padding_mode': 'zeros', 'stride': [2, 2], 'type': 'Conv2d'}
Conv2d(3, 40, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
factory: Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)

The card names its evaluation set and its preprocessing: Imagenette validation at 160 px, normalized with the ImageNet statistics.

from fastai.vision.all import ImageDataLoaders, Normalize, Resize, URLs, imagenet_stats, untar_data

path = untar_data(URLs.IMAGENETTE_160)
dls = ImageDataLoaders.from_folder(path, valid='val', item_tfms=Resize(160),
                                   batch_tfms=Normalize.from_stats(*imagenet_stats), bs=64)
print(len(dls.valid_ds), 'validation images')
3925 validation images

correct_vector scores one image at a time, so the count is exact and wilson can put an interval around it.

correct = correct_vector(model, dls.valid)
k, n = int(correct.sum()), len(correct)
lo, hi = wilson(k, n)
print(f"{k}/{n} = {100*k/n:.1f} %  [{100*lo:.1f}, {100*hi:.1f}]")
3589/3925 = 91.4 %  [90.5, 92.3]

What one forward costs, at batch 1, on the same image size.

x = torch.zeros(1, 3, 160, 160)

print(f"{params(model)/1e6:.1f} M params")
print(f"{macs(model, x)/1e6:.1f} M MACs")
print(f"{peak_activation_bytes(model, x)/1e6:.1f} MB peak activations")
5.3 M params
425.8 M MACs
2.0 MB peak activations

The same repo publishes the INT8 model as TorchScript: load(repo, form='torchscript') returns a frozen module that runs like any other callable. agreement says how often the two files predict the same class, which is a stricter reading than two top-1 numbers that happen to be close.

int8 = load(repo, form='torchscript')

c8 = correct_vector(int8, dls.valid)
print(f"{int(c8.sum())}/{len(c8)} = {100*c8.mean():.1f} %")
print(f"agreement with the FP32 file: {100*agreement(predictions(model, dls.valid), predictions(int8, dls.valid)):.1f} %")
3584/3925 = 91.3 %
agreement with the FP32 file: 99.4 %

The third file is INT8 ONNX. load returns its path, and the runtime is the reader’s choice — anything callable on a batch is scored by the same harness. It is a different runtime from the one above, so a small disagreement with the TorchScript file is expected.

import onnxruntime as ort

onnx_path = load(repo, form='onnx')
sess = ort.InferenceSession(str(onnx_path), providers=['CPUExecutionProvider'])
f = lambda x: torch.from_numpy(sess.run(None, {'input': x.numpy()})[0])

c_onnx = correct_vector(f, dls.valid)
print(f"{int(c_onnx.sum())}/{len(c_onnx)} = {100*c_onnx.mean():.1f} %")
3580/3925 = 91.2 %

Summary

Function What it gave here
load the three published forms: FasterModel, TorchScript module, ONNX path
correct_vector + wilson top-1 as a count out of 3925, with a 95 % interval
params, macs, peak_activation_bytes 5.3 M params, 425.8 M MACs, 2.0 MB of activations at batch 1
agreement 99.4 % of images given the same class by the FP32 and INT8 files
Artifact Card Recomputed here
pruned FP32 (model.safetensors) 91.4 % 91.4 % (3589/3925)
INT8 TorchScript (model.torchscript.pt) 91.3 % 91.3 % (3584/3925)
INT8 ONNX (model.onnx) 91.2 % 91.2 % (3580/3925)

See Also

  • Model - FasterModel and load
  • Eval - the harness behind the numbers on this page
  • Card - what a card contains
  • Gate - the checks an artifact passes before publication