Spaces:
Sleeping
Sleeping
Download src/model_loader.py from build-small-hackathon/ryugaku-app: direct link, hf CLI and curl.
- Browser
- Download file 3.04 kB
-
https://huggingface.co/spaces/build-small-hackathon/ryugaku-app/resolve/main/src/model_loader.py
- Command line
-
hf download hf://spaces/build-small-hackathon/ryugaku-app/src/model_loader.py
-
curl -L -o model_loader.py https://huggingface.co/spaces/build-small-hackathon/ryugaku-app/resolve/main/src/model_loader.py
3.04 kB
| """Lazy model / tokenizer loader with HF Hub + local fallback.""" | |
| from __future__ import annotations | |
| import os | |
| import logging | |
| from typing import Tuple | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| from src.config import HF_MODEL_ID, LEGACY_MODEL_PATH, FORCE_CPU, DTYPE | |
| logger = logging.getLogger(__name__) | |
| def _resolve_model_path() -> str: | |
| """Return the local path to the model, falling back from HF Hub.""" | |
| # 1. explicit env override | |
| env_path = os.environ.get("RYUGAKU_LOCAL_MODEL") | |
| if env_path and os.path.isdir(env_path): | |
| logger.info("Using local model from RYUGAKU_LOCAL_MODEL: %s", env_path) | |
| return env_path | |
| # 2. legacy local path (dev only) | |
| if os.path.isdir(LEGACY_MODEL_PATH): | |
| logger.info("Using legacy local model: %s", LEGACY_MODEL_PATH) | |
| return LEGACY_MODEL_PATH | |
| # 3. HF Hub repo id | |
| logger.info("Using HF Hub model id: %s", HF_MODEL_ID) | |
| return HF_MODEL_ID | |
| class ModelCache: | |
| """Simple singleton cache for the model and tokenizer.""" | |
| _instance: ModelCache | None = None | |
| model: AutoModelForCausalLM | None = None | |
| tokenizer: AutoTokenizer | None = None | |
| model_path: str | None = None | |
| loading: bool = False | |
| def __new__(cls) -> ModelCache: | |
| if cls._instance is None: | |
| cls._instance = super().__new__(cls) | |
| return cls._instance | |
| def load(self) -> Tuple[AutoModelForCausalLM, AutoTokenizer]: | |
| if self.model is not None and self.tokenizer is not None: | |
| return self.model, self.tokenizer | |
| if self.loading: | |
| raise RuntimeError("Model is already loading") | |
| self.loading = True | |
| try: | |
| self.model_path = _resolve_model_path() | |
| logger.info("Loading tokenizer from %s", self.model_path) | |
| self.tokenizer = AutoTokenizer.from_pretrained( | |
| self.model_path, | |
| trust_remote_code=True, | |
| ) | |
| # Qwen3.5 uses a chat template; ensure pad token exists | |
| if self.tokenizer.pad_token is None: | |
| self.tokenizer.pad_token = self.tokenizer.eos_token | |
| logger.info("Loading model from %s", self.model_path) | |
| kwargs = { | |
| "trust_remote_code": True, | |
| } | |
| if FORCE_CPU: | |
| kwargs["dtype"] = torch.float32 | |
| kwargs["device_map"] = "cpu" | |
| else: | |
| kwargs["dtype"] = DTYPE | |
| kwargs["device_map"] = "auto" | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| self.model_path, | |
| **kwargs, | |
| ) | |
| logger.info("Model loaded successfully") | |
| finally: | |
| self.loading = False | |
| return self.model, self.tokenizer | |
| def get_model_and_tokenizer() -> Tuple[AutoModelForCausalLM, AutoTokenizer]: | |
| return ModelCache().load() | |
| def is_model_ready() -> bool: | |
| cache = ModelCache() | |
| return cache.model is not None and cache.tokenizer is not None | |