| """Load TinyModel v11 from an artefact directory. |
| |
| An artefact dir (e.g. `tiny-model/model/v11/`) contains: |
| config.json # arch + metadata |
| artifacts/model_compiled.pt # frozen-FFN attention-retrained weights |
| artifacts/model_full.pt # end of Phase 2 training |
| artifacts/train_mask.pt # optional — training-token mask |
| vindex/ # optional — extracted vindex |
| |
| Most callers want `load_from_artifacts(path)`, which constructs the |
| model from `config.json` and loads the checkpoint of their choice. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| from dataclasses import dataclass, field |
| from pathlib import Path |
| from typing import Optional |
|
|
| import torch |
|
|
| from .model import TinyModel |
|
|
|
|
| @dataclass |
| class ModelConfig: |
| vocab_size: int |
| dim: int |
| n_layers: int |
| ffn_dim: int |
| n_heads: int |
| n_kv_heads: int |
| max_seq: int |
| version: str = "v11" |
| notes: dict = field(default_factory=dict) |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "ModelConfig": |
| known = {f for f in cls.__dataclass_fields__} |
| kwargs = {k: v for k, v in d.items() if k in known} |
| kwargs["notes"] = {k: v for k, v in d.items() if k not in known} |
| return cls(**kwargs) |
|
|
| def to_dict(self) -> dict: |
| out = { |
| "version": self.version, |
| "vocab_size": self.vocab_size, |
| "dim": self.dim, |
| "n_layers": self.n_layers, |
| "ffn_dim": self.ffn_dim, |
| "n_heads": self.n_heads, |
| "n_kv_heads": self.n_kv_heads, |
| "max_seq": self.max_seq, |
| } |
| out.update(self.notes) |
| return out |
|
|
|
|
| def load_config(artefact_dir: str | Path) -> ModelConfig: |
| path = Path(artefact_dir) / "config.json" |
| with open(path) as f: |
| return ModelConfig.from_dict(json.load(f)) |
|
|
|
|
| def load_from_artifacts( |
| artefact_dir: str | Path, |
| checkpoint: str = "model_compiled.pt", |
| device: Optional[str | torch.device] = None, |
| strict: bool = True, |
| ) -> tuple[TinyModel, ModelConfig]: |
| """Instantiate TinyModel from a v11-style artefact dir. |
| |
| Returns (model, config). The model is set to eval() and moved to |
| `device` (auto-selects MPS → CUDA → CPU if None). |
| """ |
| root = Path(artefact_dir) |
| config = load_config(root) |
|
|
| if device is None: |
| if torch.backends.mps.is_available(): |
| device = torch.device("mps") |
| elif torch.cuda.is_available(): |
| device = torch.device("cuda") |
| else: |
| device = torch.device("cpu") |
| elif isinstance(device, str): |
| device = torch.device(device) |
|
|
| model = TinyModel( |
| vocab_size=config.vocab_size, |
| dim=config.dim, |
| n_layers=config.n_layers, |
| ffn_dim=config.ffn_dim, |
| n_heads=config.n_heads, |
| n_kv_heads=config.n_kv_heads, |
| max_seq=config.max_seq, |
| ).to(device) |
|
|
| ckpt_path = root / "artifacts" / checkpoint |
| state = torch.load(str(ckpt_path), map_location="cpu", weights_only=True) |
| model.load_state_dict(state, strict=strict) |
| model.eval() |
| return model, config |
|
|