Download inference/typical/core.py from OzLabs/typical-medium: direct link, hf CLI and curl.
- Browser
- Download file 8.83 kB
-
https://huggingface.co/OzLabs/typical-medium/resolve/main/inference/typical/core.py
- Command line
-
hf download hf://OzLabs/typical-medium/inference/typical/core.py
-
curl -L -o core.py https://huggingface.co/OzLabs/typical-medium/resolve/main/inference/typical/core.py
8.83 kB
| """Typical: minimal from_pretrained + decide API around a native-readout PCDM checkpoint. | |
| Mirrors pcdm_jev.decider.PCDMDecider(mode="native") exactly (same query_text/to_labels, | |
| same native_kv_decide call) -- see that module's docstring for the mapping this | |
| reproduces. Everything the checkpoint needs to rebuild backbone+head lives in | |
| ckpt["args"]: backbone id, tap_layer, lora_r/lora_layers, nc_head, nc_render, null, | |
| noul_head, score_head. z-score calibration (--zscore) needs no special handling here -- | |
| its buffers (mu_h/sd_h/mu_c/sd_c) are just part of ckpt["tower"]'s state_dict. | |
| """ | |
| import json | |
| import time | |
| from collections import OrderedDict | |
| import torch | |
| from huggingface_hub import hf_hub_download | |
| from .backbone import Backbone, pick_device | |
| from .native import DEFAULT_MAX_OPTION_TOKENS, NativeHead, encode_state, native_kv_decide | |
| MAX_QUERY = 64 # trained query length (see pcdm_jev.decider) -- informational only below | |
| MAX_STATE = 256 # trained state length -- native_kv_decide's own max_state default is separate | |
| class _Head: | |
| """Duck-types the training repo's mcq.MCQHead just enough for native_kv_decide: a | |
| .backbone (with .tokenizer/.model) and .device. The real MCQHead also builds a | |
| restricted LM head over single-token letters for the mcq_zero_shot/energy readout | |
| paths -- native_kv_decide never touches it, so it's not ported here.""" | |
| def __init__(self, backbone: Backbone): | |
| self.backbone = backbone | |
| self.device = backbone.device | |
| def query_text(question: dict) -> str: | |
| """instructions + criteria: `yes: .. no: ..` (noul), `0: .. 1: ..` (score), `label: desc` | |
| (choice). Verbatim port of pcdm_jev.decider.query_text.""" | |
| q, crit = question["instructions"], question.get("criteria") | |
| if not crit: | |
| return q | |
| if question["type"] == "noul": | |
| rubric = f"yes: {crit.get('true', 'Yes')} no: {crit.get('false', 'No')}" | |
| elif isinstance(crit, list): | |
| rubric = " ".join(f"{i}: {c}" for i, c in enumerate(crit)) | |
| else: | |
| rubric = " ".join(f"{k}: {v or k}" for k, v in crit.items()) | |
| return f"{q}\n{rubric}" | |
| def to_labels(p: torch.Tensor, labels: list[str]) -> tuple[dict, float]: | |
| """[K+1] (null last) -> (probs renormalised over labels, p_null). Verbatim port of | |
| pcdm_jev.decider.to_labels.""" | |
| p = p.detach().float().cpu() | |
| if not torch.isfinite(p).all(): | |
| raise RuntimeError("non-finite model output") | |
| s = p[:-1].sum() | |
| lab = p[:-1] / s if s > 0 else torch.zeros_like(p[:-1]) | |
| return dict(zip(labels, lab.tolist())), float(p[-1]) | |
| class Typical: | |
| """from_pretrained("OzLabs/typical-small") -> a ready-to-decide native-readout model.""" | |
| def __init__(self, head: _Head, model: NativeHead, device: str, max_state: int = 4096, | |
| max_states: int = 16, max_option_tokens: int | None = DEFAULT_MAX_OPTION_TOKENS): | |
| self.head, self.model, self.device, self.max_state = head, model, device, max_state | |
| self.max_option_tokens = max_option_tokens | |
| self.tok = head.backbone.tokenizer | |
| # Persistent state -> prefix-KV cache (LRU, keyed on the exact state text): choice/ | |
| # score/noul/decide all route through _raw -> _state_kv_for, so a warm call (same | |
| # state, new question) never re-runs the state_prefix_forward -- see native.py's | |
| # encode_state/native_kv_decide docstrings. max_states caps how many distinct states | |
| # are held at once (each entry is one KV cache, ~O(state_tokens) memory). | |
| self.max_states = max_states | |
| self._state_kv: OrderedDict[str, tuple] = OrderedDict() | |
| def from_pretrained(cls, repo_id: str, device: str = "auto", filename: str = "best.pt", | |
| revision: str | None = None, cache_dir: str | None = None, | |
| max_state: int = 4096, max_states: int = 16, | |
| max_option_tokens: int | None = DEFAULT_MAX_OPTION_TOKENS) -> "Typical": | |
| path = hf_hub_download(repo_id, filename, revision=revision, cache_dir=cache_dir) | |
| ckpt = torch.load(path, map_location="cpu", weights_only=True) | |
| args = ckpt.get("args", {}) | |
| if args.get("readout") != "native": | |
| raise ValueError(f"{repo_id}/{filename} is not a native-readout checkpoint (readout={args.get('readout')!r})") | |
| dev = pick_device(device) | |
| backbone = Backbone(args["backbone"], lora_layers=args.get("lora_layers", 8), | |
| lora_r=args.get("lora_r", 16), device=dev, tap_layer=args.get("tap_layer", 0)) | |
| backbone.load_lora_state_dict(ckpt["lora"]) | |
| model = NativeHead(backbone.d, nc_head=args.get("nc_head", "n2n3"), null=args.get("null", "factored"), | |
| render=args.get("nc_render", "letters"), score_head=args.get("score_head", "choice"), | |
| noul_head=args.get("noul_head", "choice")).to(dev) | |
| model.load_state_dict(ckpt["tower"]) | |
| model.eval() | |
| return cls(_Head(backbone), model, dev, max_state=max_state, max_states=max_states, | |
| max_option_tokens=max_option_tokens) | |
| def _state_kv_for(self, state: str): | |
| """LRU lookup/insert of (past_key_values, Ls) for this exact state text. A hit skips | |
| encode_state entirely -- bit-identical result to a fresh encode (deterministic | |
| eval-mode forward), just without paying for it again.""" | |
| entry = self._state_kv.get(state) | |
| if entry is not None: | |
| self._state_kv.move_to_end(state) | |
| return entry | |
| entry = encode_state(self.head, self.model, state, self.max_state) | |
| self._state_kv[state] = entry | |
| if len(self._state_kv) > self.max_states: | |
| self._state_kv.popitem(last=False) | |
| return entry | |
| def _raw(self, state, query: str, labels: list[str]) -> torch.Tensor: | |
| if not isinstance(state, str): | |
| state = json.dumps(state, ensure_ascii=False) | |
| with torch.inference_mode(): | |
| cache = self._state_kv_for(state) | |
| return native_kv_decide(self.head, self.model, state, [(query, labels)], | |
| max_state=self.max_state, state_cache=cache, | |
| max_option_tokens=self.max_option_tokens)[0] | |
| def choice(self, state, question: str, labels: list[str]) -> dict: | |
| """-> {label: p, ...} + "p_null".""" | |
| probs, p_null = to_labels(self._raw(state, question, labels), labels) | |
| return {**probs, "p_null": p_null} | |
| def noul(self, state, question: str) -> float: | |
| """P(yes), via the Bernoulli path if the checkpoint has one (noul_head="bern"), else | |
| the plain K-way choice over ["no", "yes"] -- both paths are label-order invariant for | |
| a genuine yes/no row (see native._is_bern_row / _yes_idx).""" | |
| probs, _ = to_labels(self._raw(state, question, ["no", "yes"]), ["no", "yes"]) | |
| return probs["yes"] | |
| def score(self, state, question: str, levels: list[str]) -> dict: | |
| """-> {level: p, ...} + "p_null" + "expected" (E[index] under the candidate- | |
| conditional distribution, matching the training repo's metrics.ordinal_metrics -- | |
| candidate index IS the ordinal level, by construction of how levels are rendered).""" | |
| probs, p_null = to_labels(self._raw(state, question, levels), levels) | |
| expected = sum(i * probs[lv] for i, lv in enumerate(levels)) | |
| return {**probs, "p_null": p_null, "expected": expected} | |
| def decide(self, state, question: dict, labels: list[str]) -> tuple[dict, dict]: | |
| """-> (probs over exactly `labels`, runtime block). Mirrors | |
| pcdm_jev.decider.PCDMDecider.decide for a JevBench-style question dict | |
| ({"type", "instructions", "criteria"}).""" | |
| if not isinstance(state, str): | |
| state = json.dumps(state, ensure_ascii=False) | |
| query = query_text(question) | |
| n_state = len(self.tok(state, add_special_tokens=False)["input_ids"]) | |
| n_query = len(self.tok(query, add_special_tokens=False)["input_ids"]) | |
| t0 = time.perf_counter() | |
| p = self._raw(state, query, labels) | |
| probs, p_null = to_labels(p, labels) | |
| latency = time.perf_counter() - t0 | |
| return probs, {"mode": "native", "device": self.device, "latency_s": latency, "p_null": p_null, | |
| "state_tokens": n_state, "max_state_tokens": self.max_state, | |
| "state_truncated": n_state > self.max_state, | |
| "trained_max_state": MAX_STATE, "state_beyond_train_len": n_state > MAX_STATE, | |
| "query_tokens": n_query, "query_truncated": False, | |
| "trained_max_query": MAX_QUERY, "query_beyond_train_len": False, | |
| "probability_origin": "native-softmax"} | |