| import os |
| import argparse |
| from pathlib import Path |
| import json |
| from typing import Optional |
|
|
| import torch |
| from PIL import Image |
| from transformers import AutoTokenizer |
|
|
| import gradio as gr |
| from model import MultiModalDenseTransformer |
| from continual_learning import UnifiedMultiModalPreprocessor |
| os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" |
|
|
| from torchvision import transforms |
| image_transform = transforms.Compose([ |
| transforms.Resize((224, 224)), |
| transforms.ToTensor(), |
| transforms.Normalize(mean=[0.485, 0.456, 0.406], |
| std=[0.229, 0.224, 0.225]), |
| ]) |
| class ModelInference: |
| def __init__( |
| self, |
| checkpoint_path: str, |
| tokenizer_name: str, |
| config_path: Optional[str] = None, |
| device: str = 'cuda' if torch.cuda.is_available() else 'cpu' |
| ): |
| self.device = torch.device(device) |
| self.tokenizer = AutoTokenizer.from_pretrained( |
| tokenizer_name, |
| use_fast=True, |
| trust_remote_code=True |
| ) |
| if self.tokenizer.pad_token is None: |
| self.tokenizer.pad_token = self.tokenizer.eos_token |
| self.tokenizer.pad_token_id = self.tokenizer.eos_token_id |
| if config_path and Path(config_path).exists(): |
| with open(config_path, 'r') as f: |
| self.config = json.load(f) |
| else: |
| self.config = { |
| 'model_dim': 1536, |
| 'vocab_size': len(self.tokenizer), |
| 'n_layers': 12, |
| 'n_heads': 12, |
| 'n_kv_heads': 4, |
| 'head_dim': None, |
| 'max_seq_len': 512, |
| 'dropout': 0.0, |
| 'use_moe': False, |
| 'use_adapter': False, |
| 'use_lora': False, |
| 'rope_scaling_type': "yarn", |
| 'use_multimodal_fusion': False, |
| 'use_contrastive': False |
| } |
|
|
| self.model = MultiModalDenseTransformer(**self.config) |
| self.preprocessor = UnifiedMultiModalPreprocessor(model_dim=self.config['model_dim']) |
|
|
| print(f"Loading checkpoint from {checkpoint_path}...") |
| checkpoint = torch.load(checkpoint_path, map_location=self.device) |
| |
| state_dict = None |
| |
| if 'actor_state_dict' in checkpoint: |
| print("Detected GRPO checkpoint format (actor_state_dict)") |
| state_dict = checkpoint['actor_state_dict'] |
| elif 'model_state_dict' in checkpoint: |
| print("Detected Standard/SFT checkpoint format (model_state_dict)") |
| state_dict = checkpoint['model_state_dict'] |
| else: |
| print("Detected raw state dict format") |
| state_dict = checkpoint |
|
|
| new_state_dict = {} |
| for k, v in state_dict.items(): |
| if k.startswith('module.'): |
| new_state_dict[k[7:]] = v |
| else: |
| new_state_dict[k] = v |
|
|
| missing, unexpected = self.model.load_state_dict(new_state_dict, strict=False) |
| if missing: |
| print(f"Warning: Missing keys: {len(missing)}") |
| if len(missing) <= 10: |
| print(f"Missing keys: {missing}") |
| if unexpected: |
| print(f"Warning: Unexpected keys: {len(unexpected)}") |
| if len(unexpected) <= 10: |
| print(f"Unexpected keys: {unexpected}") |
|
|
| self.model.to(self.device) |
| self.preprocessor.to(self.device) |
| self.model.eval() |
|
|
|
|
| def _build_position_ids(self, attention_mask: torch.Tensor) -> torch.Tensor: |
|
|
| batch_size, seq_len = attention_mask.shape |
| position_ids = torch.zeros((batch_size, seq_len), dtype=torch.long, device=self.device) |
| |
| for i in range(batch_size): |
| non_pad_positions = (attention_mask[i] == 1).nonzero(as_tuple=True)[0] |
| if len(non_pad_positions) > 0: |
| start_pos = non_pad_positions[0].item() |
| valid_len = len(non_pad_positions) |
| |
| position_ids[i, start_pos:start_pos + valid_len] = torch.arange( |
| valid_len, |
| device=self.device |
| ) |
| |
| return position_ids |
|
|
| @torch.no_grad() |
| def generate_text( |
| self, |
| prompt: str, |
| max_new_tokens: int = 128, |
| temperature: float = 0.7, |
| top_k: int = 40, |
| top_p: float = 0.9, |
| repetition_penalty: float = 1.1, |
| image: Optional[Image.Image] = None |
| ) -> str: |
| formatted_prompt = f"user: {prompt}\nassistant:\n<think>\n" |
| |
| inputs = self.tokenizer( |
| formatted_prompt, |
| return_tensors="pt", |
| padding=False |
| ) |
| input_ids = inputs['input_ids'].to(self.device) |
| attention_mask = inputs['attention_mask'].to(self.device) |
|
|
| segments = [] |
|
|
| segments.append({ |
| 'type': 'text', |
| 'data': input_ids, |
| 'modality_id': 0 |
| }) |
|
|
| has_image = False |
| if image is not None: |
| try: |
| if image.mode != 'RGB': |
| image = image.convert('RGB') |
| image_tensor = image_transform(image).unsqueeze(0).to(self.device) |
| |
| segments.append({ |
| 'type': 'image', |
| 'data': image_tensor, |
| 'modality_id': 1 |
| }) |
| has_image = True |
| print("Image added to input") |
| |
| except Exception as e: |
| print(f"Warning: Image processing error: {e}") |
|
|
| position_ids = self._build_position_ids(attention_mask) |
| input_data = { |
| 'segments': segments, |
| } |
| input_data['attention_mask'] = attention_mask |
| if not has_image: |
| input_data['position_ids'] = position_ids |
|
|
| try: |
| generated_ids = self.model.generate( |
| input_data, |
| max_new_tokens=max_new_tokens, |
| temperature=temperature, |
| top_k=top_k, |
| top_p=top_p, |
| repetition_penalty=repetition_penalty, |
| do_sample=True, |
| eos_token_id=self.tokenizer.eos_token_id, |
| pad_token_id=self.tokenizer.pad_token_id |
| ) |
|
|
| output_text = self.tokenizer.decode(generated_ids[0], skip_special_tokens=True) |
| return output_text.strip() |
| |
| except Exception as e: |
| import traceback |
| traceback.print_exc() |
| return f"Error during generation: {str(e)}" |
|
|
| def build_ui(model_instance): |
| with gr.Blocks(title="MultiModal Dense Transformer - Gradio", css=""" |
| .gradio-container { max-width: 900px; margin: auto; } |
| """) as demo: |
| gr.Markdown("## 在线推理(文本)") |
| |
| with gr.Row(): |
| with gr.Column(scale=3): |
| txt = gr.Textbox( |
| label="Prompt (Instruction)", |
| placeholder="请输入指令或问题...", |
| lines=5 |
| ) |
| img = gr.Image(type="pil", label="(可选) 上传图片(支持多模态)") |
| btn = gr.Button("生成 (Generate)", variant="primary") |
| |
| with gr.Column(scale=2): |
| max_tokens = gr.Slider( |
| label="Max New Tokens", |
| minimum=16, |
| maximum=1024, |
| step=1, |
| value=128 |
| ) |
| temperature = gr.Slider( |
| label="Temperature", |
| minimum=0.1, |
| maximum=1.5, |
| step=0.01, |
| value=0.7 |
| ) |
| top_k = gr.Slider( |
| label="Top-k", |
| minimum=0, |
| maximum=200, |
| step=1, |
| value=40 |
| ) |
| top_p = gr.Slider( |
| label="Top-p", |
| minimum=0.0, |
| maximum=1.0, |
| step=0.01, |
| value=0.9 |
| ) |
| rep_pen = gr.Slider( |
| label="Repetition Penalty", |
| minimum=0.5, |
| maximum=2.0, |
| step=0.01, |
| value=1.1 |
| ) |
| status = gr.Textbox( |
| label="Status", |
| value="Ready", |
| interactive=False |
| ) |
| |
| output = gr.Textbox(label="Output", lines=12, interactive=False) |
| gr.Examples( |
| examples=[ |
| ["请解释什么是深度学习", None], |
| ["计算 123 + 456 等于多少?", None], |
| ["写一首关于春天的诗", None], |
| ], |
| inputs=[txt, img], |
| ) |
|
|
| def gr_generate(prompt, image, max_tokens_v, temp_v, topk_v, topp_v, rep_v): |
| if not prompt or str(prompt).strip() == "": |
| return "", " 请输入 Prompt" |
| |
| try: |
| status_msg = " Generating..." |
| |
| out = model_instance.generate_text( |
| prompt=prompt, |
| max_new_tokens=int(max_tokens_v), |
| temperature=float(temp_v), |
| top_k=int(topk_v), |
| top_p=float(topp_v), |
| repetition_penalty=float(rep_v), |
| image=image |
| ) |
| return out, " Done" |
| except Exception as e: |
| return f"Error: {str(e)}", " Error" |
|
|
| btn.click( |
| fn=gr_generate, |
| inputs=[txt, img, max_tokens, temperature, top_k, top_p, rep_pen], |
| outputs=[output, status] |
| ) |
|
|
| return demo |
|
|
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Gradio inference interface for MultiModal Dense Transformer" |
| ) |
| parser.add_argument( |
| "--checkpoint", |
| type=str, |
| default="/root/checkpoints/dcpo_posttrain_round3/step_15600.pt", |
| help="Path to model checkpoint" |
| ) |
| parser.add_argument( |
| "--tokenizer", |
| type=str, |
| default="Qwen/Qwen2.5-7B-Instruct", |
| help="Tokenizer name or path" |
| ) |
| parser.add_argument( |
| "--config", |
| type=str, |
| default=None, |
| help="Path to model config JSON (optional)" |
| ) |
| parser.add_argument( |
| "--port", |
| type=int, |
| default=5001, |
| help="Port to run Gradio server" |
| ) |
| parser.add_argument( |
| "--share", |
| type=lambda x: x.lower() in ("true","1","yes"), |
| default=True, |
| help="Create public link (True/False)" |
| ) |
| args = parser.parse_args() |
|
|
| if not Path(args.checkpoint).exists(): |
| print(f" Checkpoint not found: {args.checkpoint}") |
| |
| possible_dirs = [ |
| Path("/root/checkpoints/posttrain/grpo"), |
| Path("/root/checkpoints/dcpo_training"), |
| Path("/root/checkpoints/r1_zero_reproduction"), |
| ] |
| |
| for checkpoint_dir in possible_dirs: |
| if checkpoint_dir.exists(): |
| grpo_files = sorted( |
| [p for p in checkpoint_dir.glob("grpo_iter_*.pt")], |
| key=lambda p: int(p.stem.split('_')[-1]) if p.stem.split('_')[-1].isdigit() else 0 |
| ) |
|
|
| step_files = sorted( |
| [p for p in checkpoint_dir.glob("step_*.pt")], |
| key=lambda p: int(p.stem.split('_')[-1]) if p.stem.split('_')[-1].isdigit() else 0 |
| ) |
| |
| candidates = grpo_files + step_files |
| if candidates: |
| args.checkpoint = str(candidates[-1]) |
| print(f" Using latest checkpoint: {args.checkpoint}") |
| break |
| |
| if not Path(args.checkpoint).exists(): |
| raise FileNotFoundError(f"找不到可用的检查点文件") |
|
|
| |
| global model_instance |
| model_instance = ModelInference( |
| args.checkpoint, |
| args.tokenizer, |
| args.config |
| ) |
|
|
| |
| demo = build_ui(model_instance) |
| demo.launch( |
| server_port=args.port, |
| share=args.share, |
| server_name="0.0.0.0" |
| ) |
|
|
| if __name__ == "__main__": |
| main() |