Download code/decision_engine_vr.py from fdemelo/jegue: direct link, hf CLI and curl.
- Browser
- Download file 16.4 kB
-
https://huggingface.co/fdemelo/jegue/resolve/main/code/decision_engine_vr.py
- Command line
-
hf download hf://fdemelo/jegue/code/decision_engine_vr.py
-
curl -L -o decision_engine_vr.py https://huggingface.co/fdemelo/jegue/resolve/main/code/decision_engine_vr.py
16.4 kB
| """Decision Index engine, vLLM path, one instance: direct slot pass for every question, learned router, stock-model rationale only for the escalated questions, adapted slot pass. | |
| Same decisions as decision_engine_v6r:RoutedSlotEngine (Hugging Face) but served by vLLM (CUDA graphs on, prefix cache on). vLLM does not return hidden states, so the model runner's | |
| `sample(hidden_states, input_batch, ...)` is wrapped (single-process engine core, VLLM_ENABLE_V1_MULTIPROCESSING=0): for every request the final-norm hidden states at the wanted slot | |
| positions are kept, and the answer-slot head (frozen LM-head rows of the answer tokens + trained residual, head.pt) and the router head (CKPT/router-direct-brief.npz) run on them in torch. | |
| The rationale is generated by the stock model (adapter off, greedy, at most `brief_cap` tokens) and cleaned like the training rationales (dense/contexts.py). | |
| VLLM_ENABLE_V1_MULTIPROCESSING=0 VLLM_ALLOW_INSECURE_SERIALIZATION=1 /mnt/datasets/venv-vllm/bin/python -m decision_index run --engine decision_engine_vr:VllmRoutedEngine \ | |
| --option model=Qwen/Qwen3.5-9B --option run=RUN --option ckpt=final [--option mode=routed|direct|brief] [--option tau=0.05] --suite-dir ... --rows ... --out ... | |
| Options: model, run, ckpt, mode (routed | direct | brief), tau (override; inf = never escalate), brief_cap (64), max_tokens (prompt limit, 32768), gpu_util (0.88), max_loras lora (converted adapter dir; made next to the checkpoint when absent). | |
| Per-option (parallel slot) questions are always answered by the direct pass (v6b recipe). Temperature: none. | |
| """ | |
| import json | |
| import os | |
| import shutil | |
| import sys | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, HERE) | |
| os.environ.setdefault("VLLM_ENABLE_V1_MULTIPROCESSING", "0") | |
| os.environ.setdefault("VLLM_ALLOW_INSECURE_SERIALIZATION", "1") | |
| import numpy as np # noqa: E402 | |
| import torch # noqa: E402 | |
| import torch.nn.functional as F # noqa: E402 | |
| import answer_slot as AS # noqa: E402 | |
| from common import QTYPES # noqa: E402 | |
| try: | |
| from decision_index.engines.base import Engine, Unsupported # noqa: E402 | |
| except ImportError: # Decision Index kit not installed | |
| from engine_base import Engine, Unsupported # noqa: E402 | |
| from dense.contexts import Contexts # noqa: E402 | |
| def _lg(p): | |
| p = np.clip(p, 1e-4, 1 - 1e-4) | |
| return np.log(p / (1 - p)) | |
| class VllmRoutedEngine(Engine): | |
| name = "dense-slot-routed-vllm" | |
| latency = ("In-process request wall time (vLLM, one instance, CUDA graphs): the direct slot pass of all questions of the request in one batch, the router head, and for the escalated " | |
| "questions a batched stock-model rationale (adapter off, at most 64 tokens) followed by the adapted slot pass. Excludes model loading.") | |
| def __init__(self, model="Qwen/Qwen3.5-9B", run=None, ckpt="final", mode="routed", tau=None, brief_cap=64, max_tokens=32768, gpu_util=0.88, lora=None, **options): | |
| super().__init__(**options) | |
| if not run: | |
| raise ValueError("VllmRoutedEngine needs a trained run (option run=...)") | |
| from transformers import AutoTokenizer | |
| from vllm import LLM, SamplingParams | |
| from vllm.lora.request import LoRARequest | |
| self.mode = str(mode) | |
| if self.mode not in ("routed", "direct", "brief"): | |
| raise ValueError("mode must be routed, direct or brief") | |
| ck = ckpt if os.path.isdir(ckpt) else os.path.join(run, ckpt) | |
| self.tok = AutoTokenizer.from_pretrained(model) | |
| if self.tok.pad_token_id is None: | |
| self.tok.pad_token_id = self.tok.eos_token_id | |
| self.codec = AS.SlotCodec(self.tok, state_max=None, option_max=None, template_kwargs={"enable_thinking": False}, mask_id=self.tok.pad_token_id or 0, strip_label_keys=True, autoregressive=True) | |
| self.brief_cap = int(brief_cap) | |
| self.ctx = Contexts(self.tok, self.codec, brief_cap=self.brief_cap) | |
| self.eos = self.tok.convert_tokens_to_ids("<|im_end|>") | |
| self.model_id = model | |
| lora_dir = lora or os.path.join(ck, "lora-vllm") | |
| if not os.path.isfile(os.path.join(lora_dir, "adapter_model.safetensors")): | |
| self._convert_lora(os.path.join(ck, "lora"), lora_dir) | |
| self.limit = int(max_tokens) | |
| self.llm = LLM(model, dtype="bfloat16", max_model_len=self.limit + 256, max_num_batched_tokens=self.limit + 256, gpu_memory_utilization=float(gpu_util), enable_prefix_caching=True, | |
| enable_lora=True, max_lora_rank=32, max_loras=1) | |
| self.eng = self.llm.llm_engine | |
| core = self.eng.engine_core | |
| core = core.engine_core if hasattr(core, "engine_core") else core | |
| self.runner = core.model_executor.driver_worker.worker.model_runner | |
| self.lora = LoRARequest("slot", 1, lora_dir) | |
| self.sp_slot = SamplingParams(temperature=0, max_tokens=1) | |
| self.sp_slot_nc = SamplingParams(temperature=0, max_tokens=1, skip_reading_prefix_cache=True) | |
| self.sp_gen = SamplingParams(temperature=0, max_tokens=self.brief_cap, stop_token_ids=[self.eos]) | |
| self.cap, self.n_req = {}, 0 | |
| orig = self.runner.sample | |
| cap = self.cap | |
| def wrapped(hidden_states, input_batch, *a, **k): | |
| for b, rid in enumerate(input_batch.req_ids): | |
| w = cap.get(rid.rsplit("-", 1)[0]) | |
| if w is None: | |
| continue | |
| start, comp, n = int(input_batch.query_start_loc_np[b]), int(input_batch.num_computed_tokens_np[b]), int(input_batch.num_scheduled_tokens[b]) | |
| for p in w["pos"]: | |
| if comp <= p < comp + n and p not in w["got"]: | |
| w["got"][p] = hidden_states[start + p - comp].detach().clone() | |
| return orig(hidden_states, input_batch, *a, **k) | |
| self.runner.sample = wrapped | |
| # answer-slot head: frozen LM-head rows of the answer tokens (+ softcap) + trained residual (head.pt) | |
| m = self.runner.get_model() if hasattr(self.runner, "get_model") else self.runner.model | |
| w = next(p for n, p in m.named_parameters() if n.endswith("lm_head.weight")) if any(n.endswith("lm_head.weight") for n, _ in m.named_parameters()) else m.get_input_embeddings().weight | |
| self.cand_w = w[torch.tensor(self.codec.cand, device=w.device)].detach().clone() | |
| self.softcap = getattr(m.config.get_text_config(), "final_logit_softcapping", None) if hasattr(m, "config") else None | |
| self.head = AS.SlotResidual(self.cand_w.size(1), len(self.codec.cand)).to("cuda").eval() | |
| self.head.load_state_dict(torch.load(os.path.join(ck, "head.pt"), map_location="cuda")) | |
| # router head | |
| blob = np.load(os.path.join(ck, "router-direct-brief.npz")) | |
| self.knots = blob["knots"] | |
| self.tau = float(blob["tau"]) if tau in (None, "") else float(tau) | |
| self.W = {p: {k: blob[f"{p}_{k}"] for k in ("mh", "sh", "mc", "sc", "w")} for p in ("w", "f")} | |
| self.provenance = {"kind": "dense-answer-slot", "repo": model, "run": run, "checkpoint": ck, "mode": self.mode, "tau": self.tau, "layout": AS.LAYOUT, "engine": "vLLM, hidden states from a wrapped model-runner sample()", | |
| "context_limit_tokens": self.limit, "temperature": "none", | |
| "policy": "Direct answer-slot pass for every question; a learned head escalates a question to a stock-model rationale of at most %d tokens before the slot is read. Prompts over the limit are refused as unsupported, never truncated." % self.brief_cap} | |
| def _convert_lora(src, dst): | |
| from safetensors.torch import load_file, save_file | |
| os.makedirs(dst, exist_ok=True) | |
| sd = load_file(os.path.join(src, "adapter_model.safetensors")) | |
| new = {k.replace("base_model.model.model.layers.", "base_model.model.model.language_model.layers.", 1): v for k, v in sd.items()} | |
| assert len(new) == len(sd) and any("language_model" in k for k in new) | |
| save_file(new, os.path.join(dst, "adapter_model.safetensors"), metadata={"format": "pt"}) | |
| shutil.copy(os.path.join(src, "adapter_config.json"), os.path.join(dst, "adapter_config.json")) | |
| # ------------------------------------------------------------------ rendering | |
| def _item(self, state, q): | |
| if q["type"] not in ("choice", "noul"): | |
| raise Unsupported("Unsupported question type " + str(q["type"])) | |
| keys = list(q["criteria"]) if q["type"] == "choice" else ["false", "true"] | |
| if not keys: | |
| raise Unsupported("question without options") | |
| crit = q["criteria"] if q["type"] == "choice" else (q.get("criteria") or {}) | |
| k = len(keys) | |
| text = "" if state in (None, "", {}, []) else (state if isinstance(state, str) else json.dumps(state, ensure_ascii=False, separators=(",", ":"))) | |
| return keys, {"qtype": QTYPES[q["type"]], "target": [1.0 / k] * k, "label": 0, "state": text, "source": "index", "qdef": {"type": q["type"], "instructions": q.get("instructions", ""), "criteria": crit}} | |
| # ------------------------------------------------------------------ vLLM plumbing | |
| def _add(self, ids, sp, lora=None): | |
| self.n_req += 1 | |
| rid = f"r{self.n_req}" | |
| self.eng.add_request(rid, {"prompt_token_ids": ids}, sp, lora_request=lora) | |
| return rid | |
| def _drain(self, rids): | |
| wait, done = set(rids), {} | |
| while len(done) < len(wait): | |
| for o in self.eng.step(): | |
| if o.request_id in wait and o.finished: | |
| done[o.request_id] = o | |
| return done | |
| def _states(self, jobs): | |
| """jobs: list of (ids, slot positions) -> list of (S, H) bf16 hidden states at the slot positions (adapter on). A slot position that sits in the prefix cache is | |
| not recomputed, so such a job is run again with the prefix-cache read skipped.""" | |
| res, todo = [None] * len(jobs), list(range(len(jobs))) | |
| for attempt in (0, 1): | |
| rids = {} | |
| for i in todo: | |
| ids, pos = jobs[i] | |
| self.n_req += 1 | |
| rid = f"s{self.n_req}" | |
| self.cap[rid] = {"pos": set(pos), "got": {}} | |
| self.eng.add_request(rid, {"prompt_token_ids": ids}, self.sp_slot_nc if attempt else self.sp_slot, lora_request=self.lora) | |
| rids[rid] = i | |
| self._drain(rids) | |
| todo = [] | |
| for rid, i in rids.items(): | |
| got, pos = self.cap.pop(rid)["got"], jobs[i][1] | |
| if all(p in got for p in pos): | |
| res[i] = torch.stack([got[p] for p in pos]) | |
| else: | |
| todo.append(i) | |
| if not todo: | |
| return res | |
| raise RuntimeError("slot hidden state not captured") | |
| def _readout(self, hs, r): | |
| """Option logits (K,) and pooled slot state (H,) from slot hidden states hs (S, H), as DensePipeline.readout.""" | |
| dev = "cuda" | |
| t = lambda x: torch.tensor([x], device=dev) | |
| k = len(r["target"]) | |
| with torch.amp.autocast("cuda", dtype=torch.bfloat16): | |
| h = hs[None] | |
| lin = F.linear(h, self.cand_w).float() | |
| if self.softcap: | |
| lin = self.softcap * torch.tanh(lin / self.softcap) | |
| z = lin + self.head(h).float() | |
| zs = z.gather(1, t(r["slot_of_opt"]).unsqueeze(-1).expand(-1, -1, z.size(-1))) | |
| pos = zs.gather(2, t(r["pos_idx"]).unsqueeze(-1)).squeeze(-1) | |
| neg = zs.gather(2, t(r["neg_idx"]).clamp(min=0).unsqueeze(-1)).squeeze(-1) | |
| logits = (pos - neg * (t(r["neg_idx"]) >= 0)).masked_fill(~torch.ones(1, k, dtype=torch.bool, device=dev), -1e4)[0] | |
| return logits.float(), hs.float().mean(0) | |
| def _score(self, pooled, conf, k): | |
| x = _lg(np.array([conf], dtype=np.float64)) | |
| C = np.concatenate([x[:, None], np.maximum(0, x[:, None] - self.knots[None]), np.log([[float(k)]]), np.log([[float(k)]]) * x[:, None]], 1) | |
| h = pooled.double().cpu().numpy()[None] | |
| f = lambda M: float(np.clip((np.concatenate([(h - M["mh"]) / M["sh"], (C - M["mc"]) / M["sc"], np.ones((1, 1))], 1) @ M["w"])[0], 0, 1)) | |
| return f(self.W["w"]) * f(self.W["f"]) | |
| def _slot_ids(r): | |
| """Prompt ids and slot positions; a single slot is the last position, so everything after it is dropped (causal).""" | |
| sp = list(r["slot_pos"]) | |
| return (r["ids"][:sp[0] + 1] if len(sp) == 1 else r["ids"]), sp | |
| # ------------------------------------------------------------------ request | |
| def __call__(self, state, questions): | |
| qids = list(questions) | |
| items = {qid: self._item(state, questions[qid]) for qid in qids} | |
| direct = {} | |
| for qid in qids: | |
| keys, item = items[qid] | |
| r = self.codec.render_ar(item) | |
| if len(r["ids"]) + self.brief_cap > self.limit: | |
| raise Unsupported(f"prompt of {len(r['ids'])} tokens exceeds the {self.limit}-token limit") | |
| direct[qid] = r | |
| jobs = [self._slot_ids(direct[qid]) for qid in qids] | |
| hs = self._states(jobs) | |
| answers, raw, esc, probs_of = {}, {}, [], {} | |
| for qid, h in zip(qids, hs): | |
| keys, item = items[qid] | |
| r = direct[qid] | |
| logits, pooled = self._readout(h, r) | |
| p = torch.softmax(logits, -1).cpu().numpy().astype(np.float64) | |
| p = p / p.sum() | |
| q = questions[qid] | |
| score = None | |
| routable = q["type"] == "choice" and AS.slot_mode(r["qtype"], len(keys)) != "parallel" | |
| go = False | |
| if routable and self.mode == "brief": | |
| go = True | |
| elif routable and self.mode == "routed": | |
| score = self._score(pooled, float(p.max()), len(keys)) | |
| go = score >= self.tau | |
| if go: | |
| esc.append(qid) | |
| raw[qid] = {"routed": "brief", "router_score": None if score is None else round(score, 5), "direct_conf": round(float(p.max()), 4)} | |
| continue | |
| probs_of[qid] = dict(zip(keys, (float(x) for x in p))) | |
| raw[qid] = {"routed": "direct", "router_score": None if score is None else round(score, 5), "prompt_tokens": len(r["ids"]), "direct_conf": round(float(p.max()), 4)} | |
| if esc: | |
| gen_prompts = [self.ctx.gen_prompt(items[qid][1]) for qid in esc] | |
| for qid, gp in zip(esc, gen_prompts): | |
| if len(gp) + self.brief_cap > self.limit: | |
| raise Unsupported(f"prompt of {len(gp)} tokens exceeds the {self.limit}-token limit") | |
| rids = {self._add(gp, self.sp_gen): qid for gp, qid in zip(gen_prompts, esc)} | |
| outs = self._drain(rids) | |
| rendered = {} | |
| for rid, qid in rids.items(): | |
| g = list(outs[rid].outputs[0].token_ids) | |
| if self.eos in g: | |
| g = g[:g.index(self.eos)] | |
| rat = self.ctx.clean_brief(g) | |
| keys, item = items[qid] | |
| r = self.ctx.render({**item, "rationale": {"brief": {"tokens": rat, "n": len(rat)}}}, "brief") | |
| if len(r["ids"]) > self.limit: | |
| raise Unsupported(f"prompt of {len(r['ids'])} tokens exceeds the {self.limit}-token limit") | |
| rendered[qid] = (r, rat) | |
| order = list(rendered) | |
| hs2 = self._states([self._slot_ids(rendered[qid][0]) for qid in order]) | |
| for qid, h in zip(order, hs2): | |
| r, rat = rendered[qid] | |
| keys = items[qid][0] | |
| logits, _ = self._readout(h, r) | |
| p = torch.softmax(logits, -1).cpu().numpy().astype(np.float64) | |
| p = p / p.sum() | |
| probs_of[qid] = dict(zip(keys, (float(x) for x in p))) | |
| raw[qid].update({"prompt_tokens": len(r["ids"]), "rationale_tokens": len(rat), "rationale": self.tok.decode(rat)}) | |
| for qid in qids: | |
| probs = probs_of[qid] | |
| answers[qid] = {"type": "choice", "choice": max(probs, key=probs.get), "probabilities": probs} if questions[qid]["type"] == "choice" else {"type": "noul", "noul": probs["true"]} | |
| return {"model": self.model_id, "answers": answers, "usage": {"input_tokens": sum(v["prompt_tokens"] for v in raw.values())}}, raw | |