Source code for corerec.serving.model_loader

"""
Model Loader for Production

Handles model loading with caching and versioning.

Author: Vishesh Yadav (mail: [email protected])
"""

from typing import Optional, Dict, Any
from pathlib import Path
import logging


[docs] class ModelLoader: """ Production model loader with caching. Loads models efficiently with caching to avoid repeated loading. Example: loader = ModelLoader() model = loader.load('models/ncf_v1.pkl') # Subsequent loads use cache model2 = loader.load('models/ncf_v1.pkl') # From cache! Author: Vishesh Yadav (mail: [email protected]) """
[docs] def __init__(self, cache_dir: Optional[Path] = None): """ Initialize model loader. Args: cache_dir: Directory for model cache Author: Vishesh Yadav (mail: [email protected]) """ self.cache: Dict[str, Any] = {} self.cache_dir = cache_dir self.logger = logging.getLogger("ModelLoader")
[docs] def load(self, model_path: str, use_cache: bool = True, model_class: Any = None) -> Any: """ Load a model saved with ``model.save(model_path)``. Models don't all write one file at ``model_path``: torch models write ``<stem>.meta.json`` (+ weights/arrays) next to it, classic CF models pickle a state dict. Each format carries the class name, so this reads it and hands off to that class's own ``load()``. Args: model_path: the same path that was passed to ``save()`` use_cache: Whether to use cached model model_class: skip detection and call ``model_class.load(model_path)`` Returns: The reconstructed model """ key = str(model_path) if use_cache and key in self.cache: self.logger.info(f"Loading model from cache: {key}") return self.cache[key] self.logger.info(f"Loading model from disk: {key}") cls = model_class or _detect_class(Path(model_path)) model = cls.load(key) if use_cache: self.cache[key] = model return model
[docs] def clear_cache(self): """Clear the model cache.""" self.cache.clear() self.logger.info("Model cache cleared")
def _engine_class(name: str): from corerec import engines cls = getattr(engines, name, None) if cls is None: raise ValueError(f"Saved model names class {name!r}, which corerec.engines doesn't export") return cls def _detect_class(path: Path): """Find the model class recorded in whatever save() wrote for ``path``.""" import importlib import json import pickle meta = path if path.name.endswith(".meta.json") else path.with_name(path.stem + ".meta.json") if meta.exists(): dotted = json.loads(meta.read_text())["model_class"] module, _, name = dotted.rpartition(".") return getattr(importlib.import_module(module), name) if not path.exists(): raise FileNotFoundError(f"No model at {path} (and no {meta.name} next to it)") try: with open(path, "rb") as f: state = pickle.load(f) except Exception: # MultVAE/MultiDAE use torch.save import torch state = torch.load(path, map_location="cpu", weights_only=False) if isinstance(state, dict): if state.get("corerec_class"): # corerec.nn.Recommender and friends module, _, name = state["corerec_class"].rpartition(".") return getattr(importlib.import_module(module), name) name = state.get("cls") or state.get("params", {}).get("name") or state.get("cfg", {}).get("name") if name: return _engine_class(name) elif hasattr(state, "recommend"): # pickled model object (old saves): wrap so .load() just returns it class _Loaded: @staticmethod def load(_): return state return _Loaded raise ValueError(f"Can't tell which model class wrote {path}; pass model_class=")