Mage-VL / inference.py
Xinjie-Q's picture
Upload Mage-VL: unified codec-native streaming VLM (image+video understanding + proactive gate)
12acbba verified
Raw
History Blame Contribute Delete
5.99 kB
#!/usr/bin/env python
"""Run Mage-VL-Base image or video inference offline or through SGLang."""
import argparse
import base64
import mimetypes
from pathlib import Path
def image_url(image: str) -> str:
if image.startswith(("http://", "https://", "data:")):
return image
path = Path(image)
mime = mimetypes.guess_type(path.name)[0] or "image/jpeg"
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
return f"data:{mime};base64,{encoded}"
def sample_video(video: str, num_frames: int):
import cv2
import numpy as np
from PIL import Image
capture = cv2.VideoCapture(video)
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
if frame_count <= 0:
capture.release()
raise ValueError(f"Could not read video: {video}")
indices = np.linspace(0, frame_count - 1, min(num_frames, frame_count), dtype=int)
frames = []
for index in indices:
capture.set(cv2.CAP_PROP_POS_FRAMES, int(index))
ok, frame = capture.read()
if not ok:
capture.release()
raise ValueError(f"Could not decode frame {index} from: {video}")
frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
capture.release()
return frames
def run_offline(args):
import os
import torch
from PIL import Image
from transformers import AutoModelForCausalLM, AutoProcessor
model_path = args.model
if args.video and args.video_backend == "codec" and args.codec_engine == "neural":
if not os.path.isdir(model_path):
from huggingface_hub import snapshot_download
model_path = snapshot_download(args.model)
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_path, trust_remote_code=True, torch_dtype="auto", device_map="auto"
).eval()
media_type = "image" if args.image else "video"
messages = [{"role": "user", "content": [
{"type": media_type}, {"type": "text", "text": args.question},
]}]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
if args.image:
inputs = processor(
text=[text], images=[Image.open(args.image).convert("RGB")], return_tensors="pt"
)
elif args.video_backend == "codec":
codec_config = {
"engine": "hevc" if args.codec_engine == "traditional" else "dcvc-rt",
"target_canvas": args.num_frames,
"patch": 16,
}
if args.codec_engine == "neural":
codec_config["dcvc"] = {
"pkg_dir": os.path.join(model_path, "neural_codec"),
"device": str(model.device),
}
inputs = processor(
text=[text],
videos=[args.video],
video_backend="codec",
max_pixels=args.max_pixels,
codec_config=codec_config,
return_tensors="pt",
padding=True,
)
else:
inputs = processor(
text=[text],
videos=[sample_video(args.video, args.num_frames)],
return_tensors="pt",
padding=True,
)
inputs = {k: (v.to(model.device) if hasattr(v, "to") else v) for k, v in inputs.items()}
if "pixel_values" in inputs:
inputs["pixel_values"] = inputs["pixel_values"].to(model.dtype)
with torch.inference_mode():
output = model.generate(**inputs, max_new_tokens=args.max_new_tokens, do_sample=False)
answer = processor.tokenizer.decode(
output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True
)
print(answer.strip())
def run_online(args):
from openai import OpenAI
if args.image:
content = [{"type": "image_url", "image_url": {"url": image_url(args.image)}}]
else:
content = [
{"type": "image_url", "image_url": {"url": frame_url(frame)}}
for frame in sample_video(args.video, args.num_frames)
]
content.append({"type": "text", "text": args.question})
client = OpenAI(base_url=args.base_url, api_key=args.api_key)
response = client.chat.completions.create(
model=args.model,
messages=[{"role": "user", "content": content}],
max_tokens=args.max_new_tokens,
)
print(response.choices[0].message.content)
def frame_url(frame) -> str:
import io
buffer = io.BytesIO()
frame.save(buffer, format="JPEG")
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
return f"data:image/jpeg;base64,{encoded}"
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--mode", choices=("offline", "online"), required=True)
media = parser.add_mutually_exclusive_group(required=True)
media.add_argument("--image", help="Local image path")
media.add_argument("--video", help="Local video path")
parser.add_argument("--video-backend", choices=("frames", "codec"), default="frames")
parser.add_argument(
"--codec-engine", choices=("traditional", "neural"), default="traditional"
)
parser.add_argument("--num-frames", type=int, default=32)
parser.add_argument("--max-pixels", type=int, default=150000)
parser.add_argument("--question", default="Describe this media.")
parser.add_argument("--model", default="microsoft/Mage-VL")
parser.add_argument("--max-new-tokens", type=int, default=256)
parser.add_argument("--base-url", default="http://localhost:30000/v1")
parser.add_argument("--api-key", default="EMPTY")
args = parser.parse_args()
if args.mode == "online" and args.video_backend == "codec":
parser.error("online video inference supports only --video-backend frames")
if args.num_frames <= 0:
parser.error("--num-frames must be positive")
(run_offline if args.mode == "offline" else run_online)(args)
if __name__ == "__main__":
main()