minimind-v/scripts/web_demo_vlm.py

174 lines
8.3 KiB
Python

import os
import sys
__package__ = "scripts"
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import argparse
import torch
import warnings
import gradio as gr
from queue import Queue
from threading import Thread, Lock
from PIL import Image
from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer
from model.model_vlm import MiniMindVLM, VLMConfig
from transformers import logging as hf_logging
hf_logging.set_verbosity_error()
warnings.filterwarnings('ignore')
model_lock = Lock()
def scan_vlm_models(base_dir):
models = {}
base_dir = os.path.abspath(base_dir)
for d in sorted(os.listdir(base_dir), reverse=True):
full_path = os.path.join(base_dir, d)
if not os.path.isdir(full_path) or d.startswith('.') or d.startswith('_'):
continue
files = os.listdir(full_path)
has_model = any(f.endswith(('.bin', '.safetensors')) for f in files) or 'model.safetensors.index.json' in files
if has_model:
models[d] = full_path
return models
def load_vlm_model(model_path):
global model, tokenizer, preprocess, lm_config, current_model_name
with model_lock:
[sys.modules.pop(k) for k in list(sys.modules) if 'transformers_modules' in k]
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True)
model.vision_encoder, model.processor = MiniMindVLM.get_vision_model(vision_model_path)
if model.vision_encoder is None: raise FileNotFoundError(f"视觉编码器未找到: {vision_model_path}")
preprocess = model.processor
lm_config = model.config
model = model.half().eval().to(device)
model.vision_encoder = model.vision_encoder.to(device)
current_model_name = os.path.basename(model_path)
param_str = f'{sum(p.numel() for p in model.parameters() if p.requires_grad) / 1e6:.2f}M'
print(f'已加载 {current_model_name},参数量:{param_str}')
return f"已加载: {current_model_name} ({param_str})"
class CustomStreamer(TextStreamer):
def __init__(self, tokenizer, queue):
super().__init__(tokenizer, skip_prompt=True, skip_special_tokens=True)
self.queue = queue
self.tokenizer = tokenizer
def on_finalized_text(self, text: str, stream_end: bool = False):
self.queue.put(text)
if stream_end:
self.queue.put(None)
def chat(prompt, current_image_path=None):
global temperature, top_p
pixel_values = None
if current_image_path:
image = Image.open(current_image_path).convert('RGB')
pixel_values = {k: v.to(model.device) for k, v in MiniMindVLM.image2tensor(image, preprocess).items()}
prompt = f'{lm_config.image_special_token * lm_config.image_token_len}\n{prompt}'
messages = [{"role": "user", "content": prompt}]
new_prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)[-max_seq_len + 1:]
with torch.no_grad():
inputs = tokenizer(
new_prompt,
return_tensors="pt",
truncation=True
).to(device)
queue = Queue()
streamer = CustomStreamer(tokenizer, queue)
def _generate():
with model_lock:
model.generate(
inputs.input_ids,
max_new_tokens=max_seq_len,
do_sample=True,
temperature=temperature,
top_p=top_p,
attention_mask=inputs.attention_mask,
pad_token_id=tokenizer.pad_token_id,
eos_token_id=tokenizer.eos_token_id,
streamer=streamer,
pixel_values=pixel_values
)
Thread(target=_generate).start()
while True:
text = queue.get()
if text is None:
break
yield text
def launch_gradio_server(server_name="0.0.0.0", server_port=7788):
global temperature, top_p
temperature = args.temperature
top_p = args.top_p
def respond(message, history):
if not message or not message.get("text"):
yield history + [{"role": "assistant", "content": "请输入问题"}]
return
files = message.get("files", [])
img_path = files[0] if files else None
text = message["text"]
if img_path:
history = history + [{"role": "user", "content": {"path": img_path}}, {"role": "user", "content": text}]
else:
history = history + [{"role": "user", "content": text}]
response = ''
for chunk in chat(text, img_path):
response += chunk
yield history + [{"role": "assistant", "content": response}]
with gr.Blocks(title="minimind-3v", css="#chatbox img{max-width:120px!important;max-height:120px!important;border-radius:8px} div.full-container{display:flex!important;flex-direction:row!important;flex-wrap:nowrap!important;align-items:center!important} div.full-container>.thumbnails{flex-shrink:0!important;max-width:50px!important} div.full-container>.input-container{flex:1!important} .input-wrapper{display:flex!important;flex-direction:row!important;flex-wrap:nowrap!important;align-items:center!important} .input-wrapper>.thumbnails{flex-shrink:0!important;max-width:50px!important} .input-wrapper>.input-row{flex:1!important} .thumbnail-image{max-width:40px!important;max-height:40px!important} textarea{overflow-y:hidden!important} #chatbox,#chatbox *{scrollbar-width:none!important;-ms-overflow-style:none!important} #chatbox::-webkit-scrollbar,#chatbox *::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}") as demo:
gr.HTML('<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;margin:-8px 0 -6px 0"><div style="display:flex;align-items:center;justify-content:center;gap:8px"><img src="https://www.modelscope.cn/api/v1/studio/gongjy/MiniMind/repo?Revision=master&FilePath=images%2Flogo2.png&View=true" style="height:30px"><span style="font-size:1.5rem;font-weight:bold;font-style:italic">minimind-3v</span></div><div style="color:#bbb;font-style:italic;margin:0">AI-generated content may be inaccurate, please verify</div></div>')
try:
chatbot = gr.Chatbot(label="", height=560, elem_id="chatbox", type="messages")
except TypeError:
chatbot = gr.Chatbot(label="", height=560, elem_id="chatbox")
msg = gr.MultimodalTextbox(placeholder="输入问题,点击📎上传图片", show_label=False, submit_btn="发送")
with gr.Row():
model_dropdown = gr.Dropdown(choices=list(model_dict.keys()), value=current_model_name, show_label=False, scale=1)
status_text = gr.Textbox(value=f"已加载: {current_model_name}", show_label=False, interactive=False, scale=2)
def on_model_change(name):
return load_vlm_model(model_dict[name])
model_dropdown.change(on_model_change, [model_dropdown], [status_text])
msg.submit(respond, [msg, chatbot], chatbot)
demo.launch(server_name=server_name, server_port=server_port)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Chat with MiniMind-V")
parser.add_argument('--load_from', default='./', type=str, help="transformers模型扫描目录")
parser.add_argument('--vision_model', default='./model/siglip2-base-p32-256-ve', type=str, help="视觉编码器路径")
parser.add_argument('--temperature', default=0.7, type=float, help="生成温度")
parser.add_argument('--top_p', default=0.95, type=float, help="nucleus采样阈值")
parser.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu', type=str, help="运行设备")
parser.add_argument('--max_seq_len', default=8192, type=int, help="最大序列长度")
args = parser.parse_args()
device = args.device
max_seq_len = args.max_seq_len
vision_model_path = args.vision_model
model_dict = scan_vlm_models(args.load_from)
if not model_dict:
print(f"未在 {os.path.abspath(args.load_from)} 找到transformers模型")
exit(1)
current_model_name = list(model_dict.keys())[0]
load_vlm_model(model_dict[current_model_name])
launch_gradio_server(server_name="0.0.0.0", server_port=8888)