kiyo07's picture
Store inference weights as safetensors with a strict direct loader
d469c19 verified
Raw History Blame Contribute Delete
3.65 kB
# SPDX-License-Identifier: AGPL-3.0-only
# Safetensors loading added 2026-10-28; inference preprocessing is unchanged.
"""Strict, direct safetensors loader for this pinned RF-DETR Small release."""
from importlib.metadata import version
import json
from pathlib import Path
import warnings
import torch
from safetensors.torch import load_file
HERE = Path(__file__).resolve().parent
def load_model(weights_path=None, config_path=None, device='cpu'):
"""Rebuild Small, load every tensor strictly, and return its RF-DETR facade.
No checkpoint deserialization, temporary pickle, pretrained download, dtype
conversion, or inference optimization is used. The CLI supplies the existing
RGB preparation, tiling, boundary rechecks and NMS via inference_engine.py.
"""
weights_path = Path(weights_path) if weights_path is not None else HERE / 'model.safetensors'
config_path = Path(config_path) if config_path is not None else weights_path.parent / 'model_config.json'
if weights_path.suffix != '.safetensors':
raise ValueError('Use a .safetensors weights file.')
config = json.loads(config_path.read_text())
expected_config = {
'variant': 'RFDETRSmall', 'rfdetr_version': '1.11.2',
'resolution': 512, 'num_classes': 1, 'id2label': {'0': 'door'},
}
if config != expected_config:
raise ValueError('model_config.json does not describe this RF-DETR Small release.')
if version('rfdetr') != config['rfdetr_version']:
raise RuntimeError('Install the pinned rfdetr==1.11.2 inference dependencies.')
if device not in ('cpu', 'mps', 'cuda'):
raise ValueError('Choose cpu, mps or cuda.')
if device == 'mps' and not torch.backends.mps.is_available():
raise RuntimeError('MPS unavailable; use --device cpu.')
if device == 'cuda' and not torch.cuda.is_available():
raise RuntimeError('CUDA unavailable; use --device cpu.')
from rfdetr import RFDETRSmall
from rfdetr.config import PretrainWeightsCompatibilityWarning
# The pinned Small constructor uses bundled backbone configuration. Patch 16
# and positional grid 32 create the backbone locally, without DINOv2 weights.
# Its expected "initialised from scratch" warning is superseded by the strict
# complete-state load below; no randomly initialized weights remain in use.
with warnings.catch_warnings():
warnings.simplefilter('ignore', PretrainWeightsCompatibilityWarning)
model = RFDETRSmall(pretrain_weights=None, num_classes=config['num_classes'],
resolution=config['resolution'], device=device)
module = model.model.model
state = load_file(str(weights_path), device='cpu')
expected = module.state_dict()
missing = sorted(expected.keys() - state.keys())
unexpected = sorted(state.keys() - expected.keys())
if missing or unexpected:
raise RuntimeError(f'Weight key mismatch: missing={missing}, unexpected={unexpected}')
for name, tensor in state.items():
if tensor.shape != expected[name].shape or tensor.dtype != expected[name].dtype:
raise RuntimeError(f'Weight shape/dtype mismatch: {name}')
module.load_state_dict(state, strict=True)
loaded_state = module.state_dict()
if not all(torch.equal(tensor, loaded_state[name]) for name, tensor in state.items()):
raise RuntimeError('Loaded model tensors do not exactly match the safetensors file.')
module.eval()
model.model.class_names = [config['id2label'][str(i)] for i in range(config['num_classes'])]
model.model.args.class_names = model.model.class_names
return model