Mapika's picture
Upload folder using huggingface_hub
6243459 verified
Raw History Blame Contribute Delete
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])