Spaces:
Running on Zero
Running on Zero
Download continuousintelligence/_loader.py from hugging-apps/ci-proto: direct link, hf CLI and curl.
- Browser
- Download file 5.67 kB
-
https://huggingface.co/spaces/hugging-apps/ci-proto/resolve/main/continuousintelligence/_loader.py
- Command line
-
hf download hf://spaces/hugging-apps/ci-proto/continuousintelligence/_loader.py
-
curl -L -o _loader.py https://huggingface.co/spaces/hugging-apps/ci-proto/resolve/main/continuousintelligence/_loader.py
5.67 kB
| """Local and Hugging Face model loading for Continuous Intelligence.""" | |
| import json | |
| import math | |
| import re | |
| from contextlib import nullcontext | |
| from functools import cache | |
| from pathlib import Path, PureWindowsPath | |
| import torch | |
| from .ci_proto import Forecaster | |
| _COMMIT = re.compile(r"[0-9a-f]{40}") | |
| def _device(name: str) -> torch.device: | |
| if name == "auto": | |
| name = "cuda" if torch.cuda.is_available() else "cpu" | |
| if name not in ("cpu", "cuda"): | |
| raise ValueError("device must be 'cpu', 'cuda', or 'auto'") | |
| if name == "cuda" and not torch.cuda.is_available(): | |
| raise RuntimeError("CUDA was requested but is not available") | |
| return torch.device(name) | |
| def _files( | |
| source: str | Path, tta_config: str | Path | None, revision: str | None | |
| ) -> tuple[Path, Path, Path | None]: | |
| path = Path(source).expanduser() | |
| if path.exists(): | |
| if revision is not None: | |
| raise ValueError("revision applies only to Hugging Face models") | |
| else: | |
| if ( | |
| isinstance(source, Path) | |
| or str(source).startswith(("/", "./", "../", "~", ".\\", "..\\")) | |
| or "\\" in str(source) | |
| or PureWindowsPath(source).drive | |
| or str(source).count("/") != 1 | |
| or path.parent.is_dir() | |
| or path.suffix in (".ckpt", ".pt", ".pth") | |
| or tta_config is not None | |
| ): | |
| raise FileNotFoundError(path) | |
| from huggingface_hub import hf_hub_download, snapshot_download | |
| info = Path(hf_hub_download(repo_id=str(source), filename="config.json", revision=revision)) | |
| commit = info.parent.name if _COMMIT.fullmatch(info.parent.name) else revision | |
| path = Path( | |
| snapshot_download( | |
| repo_id=str(source), | |
| revision=commit, | |
| allow_patterns=["model.ckpt", "model-long.ckpt", "tta.json"], | |
| ) | |
| ) | |
| info = None | |
| if path.is_dir(): | |
| if tta_config is not None: | |
| raise ValueError("a model bundle uses its own tta.json") | |
| info = path / "config.json" if (path / "config.json").is_file() else None | |
| path, tta_config = path / "model.ckpt", path / "tta.json" | |
| elif tta_config is None: | |
| raise ValueError("a standalone checkpoint requires tta_config") | |
| tta = Path(tta_config).expanduser() | |
| for file in (path, tta): | |
| if not file.is_file(): | |
| raise FileNotFoundError(file) | |
| return path, tta, info | |
| def _model_info(path: Path, forecaster: Forecaster) -> dict: | |
| """Read a bundle's ``config.json`` and check it describes the loaded weights.""" | |
| info = json.loads(path.read_text()) | |
| if not isinstance(info, dict): | |
| raise ValueError("config.json must contain a JSON object") | |
| missing = {"name", "parameters", "quantiles"} - set(info) | |
| if missing: | |
| raise ValueError(f"config.json is missing {sorted(missing)}") | |
| model = forecaster._model | |
| quantiles = forecaster.quantiles.tolist() | |
| if ( | |
| not isinstance(info["quantiles"], list) | |
| or len(info["quantiles"]) != len(quantiles) | |
| or not all( | |
| isinstance(given, (int, float)) and math.isclose(given, level, abs_tol=1e-6) | |
| for given, level in zip(info["quantiles"], quantiles, strict=True) | |
| ) | |
| ): | |
| raise ValueError("config.json quantiles do not match the checkpoint") | |
| if info["parameters"] != sum(parameter.numel() for parameter in model.parameters()): | |
| raise ValueError("config.json parameters do not match the checkpoint") | |
| return info | |
| def load_model( | |
| checkpoint: str | Path, | |
| tta_config: str | Path | None = None, | |
| device: str = "auto", | |
| *, | |
| revision: str | None = None, | |
| ) -> Forecaster: | |
| """Load a local model bundle, Hugging Face model ID, or checkpoint plus TTA file. | |
| Bundles contain ``model.ckpt`` (weights and embedded architecture) and | |
| ``tta.json``. Hub bundles also contain ``config.json``, which describes the | |
| model and is returned by ``Forecaster.info``; it is checked against the | |
| weights. Hub files are fetched together from the same revision; | |
| pass a commit SHA to pin it. Hub IDs use ``owner/model`` strings. Use a | |
| ``Path`` or ``./`` prefix to mark an ambiguous relative path as local. | |
| An optional ``model-long.ckpt`` shares the bundle's TTA and is selected | |
| when history exceeds the primary checkpoint's context length. | |
| """ | |
| placement = _device(device) | |
| path, tta_path, info_path = _files(checkpoint, tta_config, revision) | |
| payload = torch.load(path, map_location="cpu", weights_only=True, mmap=True) | |
| from .ci_proto._checkpoint import from_checkpoint | |
| forecaster = from_checkpoint(payload, tta_path, placement) | |
| placement = forecaster.device | |
| if info_path is not None: | |
| forecaster._info = _model_info(info_path, forecaster) | |
| long_path = path.with_name("model-long.ckpt") | |
| if tta_config is None and long_path.is_file() and forecaster._model.config.context_len: | |
| quantiles = forecaster.quantiles | |
| future_len = forecaster._model.config.future_len | |
| def load_long() -> Forecaster: | |
| context = torch.cuda.device(placement) if placement.type == "cuda" else nullcontext() | |
| with context: | |
| long = load_model(long_path, tta_path, device=placement.type) | |
| if not torch.equal(long.quantiles, quantiles) or ( | |
| long._model.config.future_len != future_len | |
| ): | |
| raise ValueError("long checkpoint must have the same quantiles and forecast window") | |
| return long | |
| forecaster._long_model = load_long | |
| return forecaster | |