cs553-test / local_model.py
twindinx
fix minors
4221b2e
Raw History Blame Contribute Delete
4.16 kB
import logging
import os
import spaces
import torch
import torchvision.transforms as T
from PIL import Image
from torchvision.transforms.functional import InterpolationMode
from transformers import AutoTokenizer
from transformers.dynamic_module_utils import get_class_from_dynamic_module
from config import ARTIMUSE_SCORE_QUESTION,LOCAL_MODEL, advice_question, LOCAL_MAX_TOKENS
from critique import Critique
logger = logging.getLogger(__name__)
IMAGENET_MEAN = (0.485, 0.456, 0.406)
IMAGENET_STD = (0.229, 0.224, 0.225)
INPUT_SIZE = 448
# First letter selects the block of 25, second letter is the offset within it.
_TOKEN_BASE = {"a": 0, "c": 26, "d": 51, "e": 76}
_model = None
_tokenizer = None
def aestoken2score(token: str) -> int | None:
# decode ArtiMuse's two-letter score code, e.g. 'dc' -> 53
# return None if receiving unexpected output
token = token.strip().lower()
if len(token) < 2:
return None
first, second = token[0], token[1]
if first not in _TOKEN_BASE or not ("a" <= second <= "z"):
return None
score = _TOKEN_BASE[first] + (ord(second) - ord("a"))
return score if 0 <= score <= 100 else None
def load_image(image_path: str) -> torch.Tensor:
# ArtiMuse was trained on 448px tiles, so we resize to that size
transform = T.Compose(
[
T.Lambda(lambda img: img.convert("RGB") if img.mode != "RGB" else img),
T.Resize((INPUT_SIZE, INPUT_SIZE), interpolation=InterpolationMode.BICUBIC),
T.ToTensor(),
T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
]
)
with Image.open(image_path) as image:
return transform(image).unsqueeze(0)
def _device_and_dtype():
if torch.cuda.is_available():
return "cuda", torch.bfloat16
if torch.backends.mps.is_available():
return "mps", torch.bfloat16
return "cpu", torch.float32
def _artimuse_class():
# Fetch ArtiMuse's bundled model class, patched for transformers 5.x
cls = get_class_from_dynamic_module(
"modeling_internvl_chat.InternVLChatModel", LOCAL_MODEL
)
if not hasattr(cls, "all_tied_weights_keys"):
cls.all_tied_weights_keys = {}
return cls
def _ensure_loaded():
# Load the 15.9GB checkpoint once at import
global _model, _tokenizer
if _model is not None:
return _model, _tokenizer
device, dtype = _device_and_dtype()
logger.info("Loading %s on %s (%s)", LOCAL_MODEL, device, dtype)
_model = (
_artimuse_class()
.from_pretrained(
LOCAL_MODEL,
dtype=dtype,
low_cpu_mem_usage=True,
trust_remote_code=True,
use_flash_attn=False,
)
.eval()
.to(device)
)
_tokenizer = AutoTokenizer.from_pretrained(
LOCAL_MODEL, trust_remote_code=True, use_fast=False
)
return _model, _tokenizer
@spaces.GPU(duration=120)
def local_critique(image_path, aspect, temperature, top_p) -> Critique:
model, tokenizer = _ensure_loaded()
device, dtype = _device_and_dtype()
pixel_values = load_image(image_path).to(dtype).to(device)
# Scoring is a single forward pass: we only need the first generated token.
score_config = dict(max_new_tokens=4, do_sample=False, pad_token_id=tokenizer.eos_token_id)
raw_score = model.chat(tokenizer, pixel_values, ARTIMUSE_SCORE_QUESTION, score_config)
score = aestoken2score(str(raw_score))
if score is None:
raise RuntimeError(f"ArtiMuse returned an unparseable score token: {raw_score!r}")
text_config = dict(
max_new_tokens=LOCAL_MAX_TOKENS,
do_sample=temperature > 0,
temperature=max(float(temperature), 0.01),
top_p=float(top_p),
pad_token_id=tokenizer.eos_token_id,
)
evaluation = model.chat(tokenizer, pixel_values, advice_question(aspect), text_config)
return Critique(
score=score,
evaluation=str(evaluation),
model_name=LOCAL_MODEL,
route="Local",
)
# Ensure the model is loaded at boot time of Space, not on first request
if os.environ.get("SPACE_ID"):
_ensure_loaded()