decider-75M / typed_decisions.py
muh1's picture
decider-75M: weights, tokenizer, typed_decisions loader and card (CC BY 4.0)
d114dcc verified
Raw History Blame Contribute Delete
2.44 kB
"""Load and run the model in this folder: per question, a probability distribution over its options.
from typed_decisions import TypedDecisions
m = TypedDecisions.load(".") # this folder, or a downloaded snapshot of it
m.predict("invoice_processing", state_dict_or_json)
# {"discrepancy_severity": {"0": 0.71, "1": 0.22, ...}, "disposition": {...}, ...}
"""
from __future__ import annotations
import json
from pathlib import Path
import torch
from safetensors.torch import load_file
from torch import nn
from transformers import AutoConfig, AutoModel, AutoTokenizer
class TypedDecisions(nn.Module):
def __init__(self, folder: Path, cfg: dict):
super().__init__()
enc_cfg = AutoConfig.from_pretrained(folder)
self.enc = AutoModel.from_config(enc_cfg)
h = enc_cfg.hidden_size
self.schema: dict[str, dict[str, list[str]]] = cfg["schema"]
self.heads = nn.ModuleDict({f"{wf}/{q}": nn.Linear(h, len(o)) for wf, qs in self.schema.items() for q, o in qs.items()})
self.max_len = cfg["max_len"]
self.tok = AutoTokenizer.from_pretrained(folder)
@classmethod
def load(cls, folder: str | Path, device: str = "cuda" if torch.cuda.is_available() else "cpu") -> "TypedDecisions":
folder = Path(folder)
m = cls(folder, json.loads((folder / "typed_decisions.json").read_text()))
m.load_state_dict(load_file(str(folder / "model.safetensors")))
return m.to(device).eval()
@torch.no_grad()
def predict(self, workflow: str, state: dict | str) -> dict[str, dict[str, float]]:
"""The input is the workflow name, a newline, then the state as compact JSON (as in training)."""
s = json.dumps(json.loads(state) if isinstance(state, str) else state, ensure_ascii=False)
dev = next(self.parameters()).device
enc = self.tok([f"{workflow}\n{s}"], truncation=True, max_length=self.max_len, return_tensors="pt").to(dev)
with torch.autocast(dev.type, dtype=torch.bfloat16, enabled=dev.type == "cuda"):
hs = self.enc(**enc).last_hidden_state
m = enc.attention_mask.unsqueeze(-1).to(hs.dtype)
z = ((hs * m).sum(1) / m.sum(1)).float()
return {q: dict(zip(opts, torch.softmax(self.heads[f"{workflow}/{q}"](z)[0], -1).tolist()))
for q, opts in self.schema[workflow].items()}