91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
116 lines
4.0 KiB
Python
116 lines
4.0 KiB
Python
import base64
|
|
import re
|
|
from io import BytesIO
|
|
from typing import Any, Dict, List
|
|
|
|
try:
|
|
import blobfile as _ # assert blobfile is installed
|
|
import torch # type: ignore
|
|
from PIL import Image # type: ignore
|
|
from transformers import ( # type: ignore
|
|
AutoImageProcessor,
|
|
AutoModel,
|
|
AutoTokenizer,
|
|
)
|
|
|
|
OPENCUA_AVAILABLE = True
|
|
except Exception:
|
|
OPENCUA_AVAILABLE = False
|
|
|
|
|
|
class OpenCUAModel:
|
|
"""OpenCUA model handler using AutoTokenizer, AutoModel and AutoImageProcessor."""
|
|
|
|
def __init__(
|
|
self, model_name: str, device: str = "auto", trust_remote_code: bool = False
|
|
) -> None:
|
|
if not OPENCUA_AVAILABLE:
|
|
raise ImportError(
|
|
'OpenCUA requirements not found. Install with: pip install "cua-agent[opencua-hf]"'
|
|
)
|
|
self.model_name = model_name
|
|
self.device = device
|
|
self.model = None
|
|
self.tokenizer = None
|
|
self.image_processor = None
|
|
self.trust_remote_code = trust_remote_code
|
|
self._load()
|
|
|
|
def _load(self) -> None:
|
|
self.tokenizer = AutoTokenizer.from_pretrained(
|
|
self.model_name, trust_remote_code=self.trust_remote_code
|
|
)
|
|
self.model = AutoModel.from_pretrained(
|
|
self.model_name,
|
|
torch_dtype="auto",
|
|
device_map=self.device,
|
|
trust_remote_code=self.trust_remote_code,
|
|
attn_implementation="sdpa",
|
|
)
|
|
self.image_processor = AutoImageProcessor.from_pretrained(
|
|
self.model_name, trust_remote_code=self.trust_remote_code
|
|
)
|
|
|
|
@staticmethod
|
|
def _extract_last_image_b64(messages: List[Dict[str, Any]]) -> str:
|
|
# Expect HF-format messages with content items type: "image" with data URL
|
|
for msg in reversed(messages):
|
|
for item in reversed(msg.get("content", [])):
|
|
if isinstance(item, dict) and item.get("type") == "image":
|
|
url = item.get("image", "")
|
|
if isinstance(url, str) and url.startswith("data:image/"):
|
|
return url.split(",", 1)[1]
|
|
return ""
|
|
|
|
def generate(self, messages: List[Dict[str, Any]], max_new_tokens: int = 512) -> str:
|
|
assert (
|
|
self.model is not None
|
|
and self.tokenizer is not None
|
|
and self.image_processor is not None
|
|
)
|
|
|
|
# Tokenize text side using chat template
|
|
input_ids = self.tokenizer.apply_chat_template(
|
|
messages, tokenize=True, add_generation_prompt=True
|
|
)
|
|
input_ids = torch.tensor([input_ids]).to(self.model.device)
|
|
|
|
# Prepare image inputs from last data URL image
|
|
image_b64 = self._extract_last_image_b64(messages)
|
|
pixel_values = None
|
|
grid_thws = None
|
|
if image_b64:
|
|
image = Image.open(BytesIO(base64.b64decode(image_b64))).convert("RGB")
|
|
image_info = self.image_processor.preprocess(images=[image])
|
|
pixel_values = torch.tensor(image_info["pixel_values"]).to(
|
|
dtype=torch.bfloat16, device=self.model.device
|
|
)
|
|
grid_thws = (
|
|
torch.tensor(image_info["image_grid_thw"])
|
|
if "image_grid_thw" in image_info
|
|
else None
|
|
)
|
|
|
|
gen_kwargs: Dict[str, Any] = {
|
|
"max_new_tokens": max_new_tokens,
|
|
"temperature": 0,
|
|
}
|
|
if pixel_values is not None:
|
|
gen_kwargs["pixel_values"] = pixel_values
|
|
if grid_thws is not None:
|
|
gen_kwargs["grid_thws"] = grid_thws
|
|
|
|
with torch.no_grad():
|
|
generated_ids = self.model.generate(
|
|
input_ids,
|
|
**gen_kwargs,
|
|
)
|
|
|
|
# Remove prompt tokens
|
|
prompt_len = input_ids.shape[1]
|
|
generated_ids = generated_ids[:, prompt_len:]
|
|
output_text = self.tokenizer.batch_decode(
|
|
generated_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False
|
|
)[0]
|
|
return output_text
|