Download decider/vision.py from Mapika/decider-2b-vision: direct link, hf CLI and curl.
- Browser
- Download file 4.53 kB
-
https://huggingface.co/Mapika/decider-2b-vision/resolve/main/decider/vision.py
- Command line
-
hf download hf://Mapika/decider-2b-vision/decider/vision.py
-
curl -L -o vision.py https://huggingface.co/Mapika/decider-2b-vision/resolve/main/decider/vision.py
4.53 kB
| """Vision variant: image(s) + the same lettered prompt, letter logits at the answer slots, one forward pass.""" | |
| import io, torch, torch.nn as nn, torch.nn.functional as F | |
| from PIL import Image | |
| from transformers import AutoProcessor, AutoModelForImageTextToText | |
| from .prompt import build, letter_ids, MAX_OPTIONS | |
| IMG = "<|vision_start|><|image_pad|><|vision_end|>" | |
| def to_pil(x): | |
| if isinstance(x, Image.Image): return x.convert("RGB") | |
| if isinstance(x, (bytes, bytearray)): return Image.open(io.BytesIO(x)).convert("RGB") | |
| return Image.fromarray(x).convert("RGB") | |
| class VisionDecisionModel(nn.Module): | |
| def __init__(self, name, dtype=torch.bfloat16, grad_ckpt=True): | |
| super().__init__() | |
| self.proc = AutoProcessor.from_pretrained(name); self.tok = self.proc.tokenizer | |
| self.lm = AutoModelForImageTextToText.from_pretrained(name, dtype=dtype) | |
| if grad_ckpt: self.lm.gradient_checkpointing_enable() | |
| self.register_buffer("letters", torch.tensor(letter_ids(self.tok)), persistent=False) | |
| self.slot_tok = self.tok.encode(" (", add_special_tokens=False)[-1]; self.colon = self.tok.encode(":", add_special_tokens=False)[-1] | |
| self.answer_tok = self.tok.encode("Answer", add_special_tokens=False)[0] | |
| def prepare(self, examples, max_ctx_tokens=1536): | |
| """examples: list of (image or None, Example). Returns processor inputs + slot bookkeeping.""" | |
| texts, images, golds, nopts, nq = [], [], [], [], [] | |
| for img, ex in examples: | |
| b = build(ex, self.tok, _NoShuffle(), max_ctx_tokens=max_ctx_tokens) | |
| txt = self.tok.decode(b["ids"]) | |
| texts.append((IMG if img is not None else "") + txt); golds.extend(b["golds"]); nopts.extend(b["nopts"]); nq.append(len(b["slots"])) | |
| if img is not None: images.append(to_pil(img)) | |
| self.tok.padding_side = "right" | |
| inp = self.proc(images=images or None, text=texts, return_tensors="pt", padding=True) | |
| ids = inp["input_ids"] | |
| slot_idx, slot_batch = [], [] | |
| for bi in range(ids.shape[0]): | |
| row = ids[bi].tolist(); found = [] | |
| for i in range(2, len(row)): | |
| if row[i] == self.slot_tok and row[i - 1] == self.colon and self.answer_tok in row[max(0, i - 5):i]: | |
| found.append(i) | |
| assert len(found) == nq[bi], (len(found), nq[bi], self.tok.decode(row[-40:])) | |
| slot_idx.extend(found); slot_batch.extend([bi] * len(found)) | |
| inp["slot_idx"] = torch.tensor(slot_idx); inp["slot_batch"] = torch.tensor(slot_batch) | |
| inp["golds"] = torch.tensor(golds); inp["nopts"] = torch.tensor(nopts) | |
| return inp | |
| def slot_logits(self, inp): | |
| dev = self.letters.device | |
| kw = {k: v.to(dev) for k, v in inp.items() if k in ("input_ids", "attention_mask", "pixel_values", "image_grid_thw", "mm_token_type_ids")} | |
| h = self.lm.model(**kw, use_cache=False).last_hidden_state # [B, T, H] | |
| hs = h[inp["slot_batch"].to(dev), inp["slot_idx"].to(dev)] # [N, H] | |
| lg = F.linear(hs, self.lm.lm_head.weight[self.letters]).float() # [N, K] letter logits only | |
| ar = torch.arange(MAX_OPTIONS, device=dev)[None, :] | |
| return lg.masked_fill(ar >= inp["nopts"].to(dev)[:, None], float("-inf")) | |
| class _NoShuffle: | |
| def shuffle(self, x): pass | |
| def sample(self, xs, k): return xs[:k] | |
| if __name__ == "__main__": | |
| import time, numpy as np | |
| from .infer import Example, Q | |
| import gym_super_mario_bros | |
| from nes_py.wrappers import JoypadSpace | |
| from gym_super_mario_bros.actions import SIMPLE_MOVEMENT | |
| m = VisionDecisionModel("Qwen/Qwen3.5-2B-Base", grad_ckpt=False).cuda().eval() | |
| env = JoypadSpace(gym_super_mario_bros.make("SuperMarioBros-1-1-v0"), SIMPLE_MOVEMENT); obs = env.reset() | |
| for _ in range(100): obs, _, _, _ = env.step(3) | |
| ex = Example("The image shows the current Super Mario Bros screen. Mario runs right; jump over enemies and gaps.", | |
| [Q("What should Mario do right now?", ["run right", "jump right", "step left", "wait"], 0), Q("Is an enemy visible?", ["no", "yes"], 1)]) | |
| inp = m.prepare([(obs, ex), (None, ex)]) | |
| with torch.no_grad(): lg = m.slot_logits(inp) | |
| print("slots found:", inp["slot_idx"].tolist(), "batch", inp["slot_batch"].tolist(), "tokens", inp["input_ids"].shape) | |
| print("probs (image / no image):", [[round(x, 3) for x in torch.softmax(r, -1)[:4].tolist()] for r in lg]) | |