Download jev_core.py from StrangerInAStrangeLand/jev-decision: direct link, hf CLI and curl.
- Browser
- Download file 8.54 kB
-
https://huggingface.co/spaces/StrangerInAStrangeLand/jev-decision/resolve/main/jev_core.py
- Command line
-
hf download hf://spaces/StrangerInAStrangeLand/jev-decision/jev_core.py
-
curl -L -o jev_core.py https://huggingface.co/spaces/StrangerInAStrangeLand/jev-decision/resolve/main/jev_core.py
8.54 kB
| """JEV-9B inference core, ported 1:1 from the model authors' documented `transformers` + | |
| `peft` path (https://huggingface.co/autotrust/JEV-9B -> "Other ways to run it"). | |
| The Blocks-of-Experts architecture: | |
| * backbone = frozen Qwen3.5-9B text tower (bit-identical to the release) | |
| * System 1 = LoRA r=16 (40.1M, shipped unmerged in `adapter/`) + a 24-slot fp32 | |
| decision head (`head.safetensors`) initialised from `lm_head` verbalizer rows | |
| * System 2 = the original `lm_head`; used with the adapter off (pristine Qwen3.5-9B) | |
| System 1 answers a typed question (`noul` yes/no, `choice` over 2-16 options, `score` on a | |
| 0-5 scale) in ONE prefill pass and returns a calibrated probability distribution. The | |
| decision prompt follows the authors' "bare-v1" template; the head is applied to the last | |
| token's post-norm hidden state, divided by the per-kind calibration temperature. | |
| """ | |
| import json | |
| import os | |
| import torch | |
| from huggingface_hub import hf_hub_download | |
| from peft import PeftModel | |
| from safetensors.torch import load_file | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| REPO = "autotrust/JEV-9B" | |
| LETTERS = "ABCDEFGHIJKLMNOP" | |
| KINDS = ("noul", "choice", "score") | |
| MAX_STATE_TOKENS = 1024 # the authors truncate long inputs to 1,024 tokens (head 60% / tail 40%) | |
| DEVICE = os.environ.get("JEV_DEVICE", "cuda") | |
| # Optional local-dir override (used only for offline verification of the wiring). | |
| _LOCAL = os.environ.get("JEV_MODEL_PATH") | |
| def _file(name: str) -> str: | |
| """Resolve a model-repo file to a local path, falling back to the Hub.""" | |
| if _LOCAL: | |
| p = os.path.join(_LOCAL, name) | |
| if os.path.exists(p): | |
| return p | |
| return hf_hub_download(REPO, name) | |
| _SRC = _LOCAL or REPO | |
| # --------------------------------------------------------------------------- | |
| # Model loading. On ZeroGPU the final `_peft.to(DEVICE)` is intercepted at | |
| # startup: tensors are packed to disk and streamed into VRAM on the first | |
| # @spaces.GPU entry, so the load cost is never paid inside a request. | |
| # | |
| # Everything is built on CPU first, then moved once: | |
| # * peft's default would be `torch_device=infer_device()` -> "cuda", making | |
| # safetensors read the adapter straight onto the GPU. That bypasses the | |
| # packer and crashes the GPU-less main process ("No CUDA GPUs are | |
| # available"). `torch_device="cpu"` keeps the read on the CPU. | |
| # * one `.to(DEVICE)` at the end moves every tensor (backbone, LoRA, head) | |
| # through the standard machinery. | |
| # --------------------------------------------------------------------------- | |
| _tok = AutoTokenizer.from_pretrained(_SRC) | |
| _base = AutoModelForCausalLM.from_pretrained(_SRC, dtype=torch.bfloat16) | |
| # Keep the adapter UNMERGED so System 2 (generation) runs with the adapter | |
| # disabled -- `with _peft.disable_adapter():` -- i.e. pristine Qwen3.5-9B. | |
| _peft = PeftModel.from_pretrained(_base, _SRC, subfolder="adapter", torch_device="cpu") | |
| # System 1 head: fp32 linear, 4096 -> 24 slots. | |
| _HEAD = torch.nn.Linear(4096, 24, dtype=torch.float32) | |
| _head_sd = load_file(_file("head.safetensors")) | |
| with torch.no_grad(): | |
| _HEAD.weight.copy_(_head_sd["proj.weight"]) | |
| _HEAD.bias.copy_(_head_sd["proj.bias"]) | |
| _HEAD.eval() | |
| _JUDGE_CFG = json.load(open(_file("judge_config.json"))) | |
| _CALIB = json.load(open(_file("calibration.json")))["per_kind"] | |
| SLOT_RANGES = {k: tuple(v) for k, v in _JUDGE_CFG["slots"]["ranges"].items()} | |
| TEMPERATURES = {k: float(_CALIB[k]) for k in KINDS} | |
| # Module handles: PeftModel -> LoraModel -> Qwen3_5ForCausalLM -> Qwen3_5TextModel. | |
| _CAUSAL = _peft.base_model.model # the Qwen3_5ForCausalLM with LoRA modules inside | |
| _BACKBONE = _CAUSAL.model # the text tower (LoRA active by default) | |
| # Single move of the whole tree (backbone + LoRA + head) to the compute device. | |
| _peft.to(DEVICE) | |
| _HEAD.to(DEVICE) | |
| # --------------------------------------------------------------------------- | |
| # Prompt template -- exactly the authors' `bare-v1` decision format. | |
| # --------------------------------------------------------------------------- | |
| def build_decision_prompt(kind: str, state: str, question: str, options) -> tuple: | |
| """Return (prompt_text, option_labels) in the authors' `bare-v1` format.""" | |
| if kind == "noul": | |
| options = ["false", "true"] | |
| elif kind == "score": | |
| options = [str(i) for i in range(6)] | |
| options = list(options) | |
| if kind == "choice": | |
| lines = [f"{LETTERS[i]}) {o}" for i, o in enumerate(options)] | |
| else: | |
| lines = options | |
| prompt = ( | |
| f"[kind] {kind}\n[state] {state}\n[question] {question}\n[options]\n" | |
| + "\n".join(lines) | |
| + "\n[decision]:" | |
| ) | |
| return prompt, options | |
| def truncate_state(state: str) -> tuple: | |
| """Head-60% / tail-40% truncation of the state to `MAX_STATE_TOKENS` tokens. | |
| Returns (state_used, was_truncated, n_tokens). | |
| """ | |
| ids = _tok(state, add_special_tokens=False)["input_ids"] | |
| if len(ids) <= MAX_STATE_TOKENS: | |
| return state, False, len(ids) | |
| keep_head = int(MAX_STATE_TOKENS * 0.6) | |
| keep_tail = MAX_STATE_TOKENS - keep_head | |
| return _tok.decode(ids[:keep_head] + ids[-keep_tail:], skip_special_tokens=True), True, len(ids) | |
| def decide(kind: str, state: str, question: str, options=None) -> dict: | |
| """Run ONE System 1 typed decision; return the calibrated probability distribution. | |
| This mirrors the model card's reference `decide()` byte for byte: one prefill pass | |
| through the backbone with the LoRA active, the 24-slot head applied to the last | |
| token's hidden state, per-kind temperature, softmax over the active slots. | |
| """ | |
| if kind not in KINDS: | |
| raise ValueError(f"unknown kind {kind!r}") | |
| if kind == "choice": | |
| if not isinstance(options, (list, tuple)) or not 2 <= len(options) <= 16: | |
| raise ValueError("`choice` needs between 2 and 16 options") | |
| if len(set(options)) != len(options): | |
| raise ValueError("`choice` options must be unique") | |
| prompt, labels = build_decision_prompt(kind, state, question, options) | |
| device = _BACKBONE.embed_tokens.weight.device # resolve inside the GPU call | |
| ids = _tok(prompt, return_tensors="pt", add_special_tokens=False).to(device) | |
| with torch.no_grad(): | |
| h = _BACKBONE(**ids).last_hidden_state[0, -1].float() | |
| z = _HEAD(h.to(_HEAD.weight.device)) # [24] fp32 logits | |
| lo = SLOT_RANGES[kind][0] | |
| z = z[lo : lo + len(labels)] / TEMPERATURES[kind] | |
| p = torch.softmax(z, dim=0) | |
| return {label: float(v) for label, v in zip(labels, p.tolist())} | |
| def n_state_tokens(state: str) -> int: | |
| return len(_tok(state, add_special_tokens=False)["input_ids"]) | |
| # --------------------------------------------------------------------------- | |
| # System 2 -- ordinary generation from the pristine base model (adapter off). | |
| # --------------------------------------------------------------------------- | |
| def build_escalation_prompt(state: str, question: str, options) -> str: | |
| """The model card's confidence-gated escalation prompt (System 1 -> System 2).""" | |
| opts = [str(o) for o in options] | |
| if opts == ["false", "true"]: | |
| tail = "Answer `true` or `false` on the last line." | |
| else: | |
| tail = "Options: " + "; ".join(opts) + "\nThink it through, then give exactly one option on the last line." | |
| return f"{state}\n\nQuestion: {question}\n{tail}" | |
| def generate(state: str, question: str, options, max_new_tokens: int = 512, | |
| enable_thinking: bool = True) -> tuple: | |
| """System 2: generate a reasoned answer with the adapter OFF -- pristine Qwen3.5-9B. | |
| Returns (text, completed): `completed` is False when generation hit the token | |
| budget mid-sentence (the model is verbose in thinking mode), which the UI needs | |
| to avoid presenting a cut-off trace as a finished answer. | |
| """ | |
| prompt = build_escalation_prompt(state, question, options) | |
| msgs = [{"role": "user", "content": prompt}] | |
| enc = _tok.apply_chat_template( | |
| msgs, add_generation_prompt=True, enable_thinking=enable_thinking, | |
| return_tensors="pt", return_dict=True, | |
| ).to(_BACKBONE.embed_tokens.weight.device) | |
| n_prompt = enc["input_ids"].shape[1] | |
| with torch.no_grad(), _peft.disable_adapter(): | |
| out = _CAUSAL.generate(**enc, max_new_tokens=int(max_new_tokens), do_sample=False) | |
| text = _tok.decode(out[0, n_prompt:], skip_special_tokens=True) | |
| completed = out.shape[1] - n_prompt < int(max_new_tokens) # stopped before the cap | |
| return text.strip(), completed |