Download code/graph/export.py from frontal-labs/graph-reasoner-typed-compositional: direct link, hf CLI and curl.
- Browser
- Download file 5.13 kB
-
https://huggingface.co/frontal-labs/graph-reasoner-typed-compositional/resolve/main/code/graph/export.py
- Command line
-
hf download hf://frontal-labs/graph-reasoner-typed-compositional/code/graph/export.py
-
curl -L -o export.py https://huggingface.co/frontal-labs/graph-reasoner-typed-compositional/resolve/main/code/graph/export.py
5.13 kB
| """Reloadable inference artifact export.""" | |
| from __future__ import annotations | |
| import json | |
| from collections.abc import Mapping | |
| from pathlib import Path | |
| from typing import Any | |
| import torch | |
| from graph.config import GraphConfig, PATConfig | |
| from graph.model import GraphModel | |
| def load_checkpoint_file( | |
| path: str | Path, *, map_location: str | torch.device = "cpu" | |
| ) -> dict[str, Any]: | |
| """Load a GRM checkpoint with ``weights_only=True``. | |
| Checkpoints written by :class:`~graph.training.trainer.GraphTrainer` contain | |
| only tensors and plain containers, so unpickling arbitrary Python objects is | |
| never required. Loading model artifacts with ``weights_only=False`` would | |
| permit code execution from a checkpoint file, which matters because the | |
| registry promotion flow (PLAN section 40) moves artifacts between machines. | |
| """ | |
| # A checkpoint is a heterogeneous payload from outside the process; this is | |
| # the external boundary where dict[str, Any] is the honest type. | |
| payload = torch.load(path, map_location=map_location, weights_only=True) | |
| if not isinstance(payload, dict): | |
| raise TypeError(f"{path}: checkpoint is not a dict payload") | |
| return payload | |
| def save_inference_artifact( | |
| model: GraphModel, directory: str | Path, *, use_safetensors: bool = False | |
| ) -> Path: | |
| root = Path(directory) | |
| root.mkdir(parents=True, exist_ok=True) | |
| if use_safetensors: | |
| try: | |
| from safetensors.torch import save_file | |
| except ( | |
| ImportError | |
| ) as exc: # pragma: no cover - depends on optional export dependency | |
| raise RuntimeError( | |
| "safetensors export requested but safetensors is not installed" | |
| ) from exc | |
| weights = root / "model.safetensors" | |
| save_file( | |
| {key: value.detach().cpu() for key, value in model.state_dict().items()}, | |
| str(weights), | |
| ) | |
| weight_format = "safetensors" | |
| else: | |
| weights = root / "model.pt" | |
| torch.save(model.state_dict(), weights) | |
| weight_format = "torch" | |
| config = model.config.to_dict() | |
| config["weight_format"] = weight_format | |
| (root / "config.json").write_text( | |
| json.dumps(config, indent=2, sort_keys=True), encoding="utf-8" | |
| ) | |
| return weights | |
| def load_inference_artifact( | |
| directory: str | Path, *, map_location: str | torch.device = "cpu" | |
| ) -> GraphModel: | |
| root = Path(directory) | |
| raw = json.loads((root / "config.json").read_text(encoding="utf-8")) | |
| weight_format = raw.pop("weight_format", "torch") | |
| pat = PATConfig(**raw.pop("pat")) | |
| raw.pop("training", None) | |
| raw.pop("loss", None) | |
| config = GraphConfig(pat=pat, **raw) | |
| model = GraphModel(config) | |
| if weight_format == "safetensors": | |
| try: | |
| from safetensors.torch import load_file | |
| except ( | |
| ImportError | |
| ) as exc: # pragma: no cover - depends on optional export dependency | |
| raise RuntimeError( | |
| "safetensors artifact requires the safetensors package" | |
| ) from exc | |
| state = load_file(str(root / "model.safetensors"), device=str(map_location)) | |
| else: | |
| state = torch.load( | |
| root / "model.pt", map_location=map_location, weights_only=True | |
| ) | |
| load_state_dict_compat(model, state) | |
| model.eval() | |
| return model | |
| def load_state_dict_compat( | |
| model: GraphModel, state: Mapping[str, torch.Tensor] | |
| ) -> None: | |
| """Load checkpoints across explicitly supported PAT shape migrations. | |
| The structured PAT token expansion added type/attribute tables and a | |
| larger position table. Older registered smoke artifacts remain | |
| loadable by zero-initializing only those new inputs and padding the old | |
| position rows; every other missing or incompatible tensor fails loudly. | |
| """ | |
| expected = model.state_dict() | |
| migrated: dict[str, torch.Tensor] = {} | |
| missing_allowed = { | |
| "pat.type_embedding.weight", | |
| "pat.attribute_embedding.weight", | |
| } | |
| for key, value in state.items(): | |
| if key not in expected: | |
| raise RuntimeError(f"checkpoint contains unexpected tensor: {key}") | |
| if value.shape == expected[key].shape: | |
| migrated[key] = value | |
| continue | |
| if ( | |
| key == "pat.path_position_embedding.weight" | |
| and value.ndim == 2 | |
| and value.shape[1] == expected[key].shape[1] | |
| and value.shape[0] < expected[key].shape[0] | |
| ): | |
| padded = torch.zeros_like(expected[key]) | |
| padded[: value.shape[0]] = value | |
| migrated[key] = padded | |
| continue | |
| raise RuntimeError( | |
| f"checkpoint tensor shape mismatch for {key}: {tuple(value.shape)} != {tuple(expected[key].shape)}" | |
| ) | |
| for key, value in expected.items(): | |
| if key not in migrated: | |
| if key in missing_allowed: | |
| migrated[key] = torch.zeros_like(value) | |
| else: | |
| raise RuntimeError(f"checkpoint is missing tensor: {key}") | |
| model.load_state_dict(migrated, strict=True) | |